зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 21 changesets (bug 1666116, bug 1605134) for build bustage at checkouts/gecko/dom/media/gtest/WaitFor.h on a CLOSED TREE
Backed out changeset fedcb7e94929 (bug 1666116) Backed out changeset 506522ae896e (bug 1666116) Backed out changeset 351419be5c0d (bug 1666116) Backed out changeset 93aef991a475 (bug 1666116) Backed out changeset 9e2a2c13ef60 (bug 1666116) Backed out changeset 81d82ce7eecd (bug 1605134) Backed out changeset 18ca27b07da3 (bug 1605134) Backed out changeset addf9298c4fe (bug 1605134) Backed out changeset c3cf634bffb3 (bug 1605134) Backed out changeset 2747068f6397 (bug 1605134) Backed out changeset ac23d3b40f2d (bug 1605134) Backed out changeset c010356d4a48 (bug 1605134) Backed out changeset ce6924b1f900 (bug 1605134) Backed out changeset e88bb30c3221 (bug 1605134) Backed out changeset efa10675b86d (bug 1605134) Backed out changeset 3c5d3f8baf28 (bug 1605134) Backed out changeset bffa738ccb6f (bug 1605134) Backed out changeset c8143eab62d2 (bug 1605134) Backed out changeset 7dfb915d51ef (bug 1605134) Backed out changeset 9bce7dcae91c (bug 1605134) Backed out changeset 3714fa7460ce (bug 1605134)
This commit is contained in:
Родитель
c9260a369b
Коммит
011cf4e1e0
|
@ -103,7 +103,22 @@ class AudioCallbackBufferWrapper {
|
|||
* instance can be reused.
|
||||
*/
|
||||
void BufferFilled() {
|
||||
MOZ_ASSERT(Available() == 0, "Frames should have been written");
|
||||
// It's okay to have exactly zero samples here, it can happen we have an
|
||||
// audio callback driver because of a hint on MTG creation, but the
|
||||
// AudioOutputStream has not been created yet, or if all the tracks have
|
||||
// finished but we're still running. Note: it's also ok if we had data in
|
||||
// the scratch buffer - and we usually do - and all the tracks were ended
|
||||
// (no mixer callback occured).
|
||||
// XXX Remove this warning, or find a way to avoid it if the mixer callback
|
||||
// isn't called.
|
||||
NS_WARNING_ASSERTION(
|
||||
Available() == 0 || mSampleWriteOffset == 0,
|
||||
"Audio Buffer is not full by the end of the callback.");
|
||||
// Make sure the data returned is always set and not random!
|
||||
if (Available()) {
|
||||
PodZero(mBuffer + mSampleWriteOffset,
|
||||
FramesToSamples(mChannels, Available()));
|
||||
}
|
||||
MOZ_ASSERT(mSamples, "Buffer not set.");
|
||||
mSamples = 0;
|
||||
mSampleWriteOffset = 0;
|
||||
|
|
|
@ -18,9 +18,9 @@ namespace mozilla {
|
|||
# undef LOG_TEST
|
||||
#endif
|
||||
|
||||
extern LazyLogModule gMediaTrackGraphLog;
|
||||
#define LOG(type, msg) MOZ_LOG(gMediaTrackGraphLog, type, msg)
|
||||
#define LOG_TEST(type) MOZ_LOG_TEST(gMediaTrackGraphLog, type)
|
||||
extern LazyLogModule gForwardedInputTrackLog;
|
||||
#define LOG(type, msg) MOZ_LOG(gForwardedInputTrackLog, type, msg)
|
||||
#define LOG_TEST(type) MOZ_LOG_TEST(gForwardedInputTrackLog, type)
|
||||
|
||||
UniquePtr<CrossGraphPort> CrossGraphPort::Connect(
|
||||
const RefPtr<dom::AudioStreamTrack>& aStreamTrack, AudioDeviceInfo* aSink,
|
||||
|
@ -106,8 +106,9 @@ void CrossGraphTransmitter::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|||
|
||||
MediaTrack* input = mInputs[0]->GetSource();
|
||||
|
||||
if (input->Ended() &&
|
||||
(input->GetEnd() <= input->GraphTimeToTrackTimeWithBlocking(aFrom))) {
|
||||
if (mInputs[0]->GetSource()->Ended() &&
|
||||
(mInputs[0]->GetSource()->GetEnd() <=
|
||||
mInputs[0]->GetSource()->GraphTimeToTrackTimeWithBlocking(aFrom))) {
|
||||
mEnded = true;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -396,6 +396,8 @@ void AudioResampler::AppendInput(const AudioSegment& aInSegment) {
|
|||
}
|
||||
|
||||
AudioSegment AudioResampler::Resample(uint32_t aOutFrames) {
|
||||
MOZ_ASSERT(aOutFrames);
|
||||
|
||||
AudioSegment segment;
|
||||
|
||||
// We don't know what to do yet and we only have received silence if any just
|
||||
|
|
|
@ -52,7 +52,7 @@ void GraphDriver::SetState(GraphTime aIterationStart, GraphTime aIterationEnd,
|
|||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool GraphDriver::InIteration() const {
|
||||
bool GraphDriver::InIteration() {
|
||||
return OnThread() || Graph()->InDriverIteration(this);
|
||||
}
|
||||
#endif
|
||||
|
@ -399,7 +399,7 @@ class AudioCallbackDriver::FallbackWrapper : public GraphInterface {
|
|||
MOZ_CRASH("Unexpected DeviceChanged from fallback SystemClockDriver");
|
||||
}
|
||||
#ifdef DEBUG
|
||||
bool InDriverIteration(const GraphDriver* aDriver) const override {
|
||||
bool InDriverIteration(GraphDriver* aDriver) override {
|
||||
return !mOwner->ThreadRunning() && mOwner->InIteration();
|
||||
}
|
||||
#endif
|
||||
|
@ -867,21 +867,10 @@ long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
|||
if (!mSandboxed && CheckThreadIdChanged()) {
|
||||
CubebUtils::GetAudioThreadRegistry()->Register(mAudioThreadId);
|
||||
}
|
||||
|
||||
if (mAudioStreamState.compareExchange(AudioStreamState::Pending,
|
||||
AudioStreamState::Running)) {
|
||||
LOG(LogLevel::Verbose, ("%p: AudioCallbackDriver %p First audio callback "
|
||||
"close the Fallback driver",
|
||||
Graph(), this));
|
||||
}
|
||||
|
||||
FallbackDriverState fallbackState = mFallbackDriverState;
|
||||
if (MOZ_UNLIKELY(fallbackState == FallbackDriverState::Running)) {
|
||||
// Wait for the fallback driver to stop. Wake it up so it can stop if it's
|
||||
// sleeping.
|
||||
LOG(LogLevel::Verbose,
|
||||
("%p: AudioCallbackDriver %p Waiting for the Fallback driver to stop",
|
||||
Graph(), this));
|
||||
EnsureNextIteration();
|
||||
PodZero(aOutputBuffer, aFrames * mOutputChannelCount);
|
||||
return aFrames;
|
||||
|
@ -977,9 +966,6 @@ long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
|||
result = IterationResult::CreateStillProcessing();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mBuffer.Available() == 0,
|
||||
"The graph should have filled the buffer");
|
||||
|
||||
mBuffer.BufferFilled();
|
||||
|
||||
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
|
||||
|
@ -1073,11 +1059,11 @@ void AudioCallbackDriver::StateCallback(cubeb_state aState) {
|
|||
LOG(LogLevel::Debug,
|
||||
("AudioCallbackDriver(%p) State: %s", this, StateToString(aState)));
|
||||
|
||||
AudioStreamState streamState = mAudioStreamState;
|
||||
if (aState != CUBEB_STATE_STARTED) {
|
||||
// Clear the flag for the not running states: stopped, drained, error.
|
||||
streamState = mAudioStreamState.exchange(AudioStreamState::None);
|
||||
}
|
||||
// Clear the flag for the not running
|
||||
// states: stopped, drained, error.
|
||||
AudioStreamState streamState = mAudioStreamState.exchange(
|
||||
aState == CUBEB_STATE_STARTED ? AudioStreamState::Running
|
||||
: AudioStreamState::None);
|
||||
|
||||
if (aState == CUBEB_STATE_ERROR) {
|
||||
// About to hand over control of the graph. Do not start a new driver if
|
||||
|
@ -1231,11 +1217,6 @@ TimeDuration AudioCallbackDriver::AudioOutputLatency() {
|
|||
mSampleRate);
|
||||
}
|
||||
|
||||
bool AudioCallbackDriver::OnFallback() const {
|
||||
MOZ_ASSERT(InIteration());
|
||||
return mFallbackDriverState == FallbackDriverState::Running;
|
||||
}
|
||||
|
||||
void AudioCallbackDriver::FallbackToSystemClockDriver() {
|
||||
MOZ_ASSERT(!ThreadRunning());
|
||||
MOZ_ASSERT(mAudioStreamState == AudioStreamState::None ||
|
||||
|
|
|
@ -200,7 +200,7 @@ struct GraphInterface : public nsISupports {
|
|||
#ifdef DEBUG
|
||||
/* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
|
||||
* and mGraphRunner is currently run by aDriver. */
|
||||
virtual bool InDriverIteration(const GraphDriver* aDriver) const = 0;
|
||||
virtual bool InDriverIteration(GraphDriver* aDriver) = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -299,19 +299,10 @@ class GraphDriver {
|
|||
void SetPreviousDriver(GraphDriver* aPreviousDriver);
|
||||
|
||||
virtual AudioCallbackDriver* AsAudioCallbackDriver() { return nullptr; }
|
||||
virtual const AudioCallbackDriver* AsAudioCallbackDriver() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual OfflineClockDriver* AsOfflineClockDriver() { return nullptr; }
|
||||
virtual const OfflineClockDriver* AsOfflineClockDriver() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual SystemClockDriver* AsSystemClockDriver() { return nullptr; }
|
||||
virtual const SystemClockDriver* AsSystemClockDriver() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the driver so it can start at the right point in time,
|
||||
|
@ -324,12 +315,12 @@ class GraphDriver {
|
|||
|
||||
#ifdef DEBUG
|
||||
// True if the current thread is currently iterating the MTG.
|
||||
bool InIteration() const;
|
||||
bool InIteration();
|
||||
#endif
|
||||
// True if the current thread is the GraphDriver's thread.
|
||||
virtual bool OnThread() const = 0;
|
||||
virtual bool OnThread() = 0;
|
||||
// GraphDriver's thread has started and the thread is running.
|
||||
virtual bool ThreadRunning() const = 0;
|
||||
virtual bool ThreadRunning() = 0;
|
||||
|
||||
double MediaTimeToSeconds(GraphTime aTime) const {
|
||||
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
|
||||
|
@ -441,13 +432,13 @@ class ThreadedDriver : public GraphDriver {
|
|||
friend class MediaTrackGraphInitThreadRunnable;
|
||||
uint32_t IterationDuration() override { return MEDIA_GRAPH_TARGET_PERIOD_MS; }
|
||||
|
||||
nsIThread* Thread() const { return mThread; }
|
||||
nsIThread* Thread() { return mThread; }
|
||||
|
||||
bool OnThread() const override {
|
||||
bool OnThread() override {
|
||||
return !mThread || mThread->EventTarget()->IsOnCurrentThread();
|
||||
}
|
||||
|
||||
bool ThreadRunning() const override { return mThreadRunning; }
|
||||
bool ThreadRunning() override { return mThreadRunning; }
|
||||
|
||||
protected:
|
||||
/* Waits until it's time to process more data. */
|
||||
|
@ -484,7 +475,6 @@ class SystemClockDriver : public ThreadedDriver {
|
|||
GraphDriver* aPreviousDriver, uint32_t aSampleRate);
|
||||
virtual ~SystemClockDriver();
|
||||
SystemClockDriver* AsSystemClockDriver() override { return this; }
|
||||
const SystemClockDriver* AsSystemClockDriver() const override { return this; }
|
||||
|
||||
protected:
|
||||
/* Return the TimeDuration to wait before the next rendering iteration. */
|
||||
|
@ -509,9 +499,6 @@ class OfflineClockDriver : public ThreadedDriver {
|
|||
GraphTime aSlice);
|
||||
virtual ~OfflineClockDriver();
|
||||
OfflineClockDriver* AsOfflineClockDriver() override { return this; }
|
||||
const OfflineClockDriver* AsOfflineClockDriver() const override {
|
||||
return this;
|
||||
}
|
||||
|
||||
void RunThread() override;
|
||||
|
||||
|
@ -621,9 +608,6 @@ class AudioCallbackDriver : public GraphDriver,
|
|||
uint32_t aSampleRate) override;
|
||||
|
||||
AudioCallbackDriver* AsAudioCallbackDriver() override { return this; }
|
||||
const AudioCallbackDriver* AsAudioCallbackDriver() const override {
|
||||
return this;
|
||||
}
|
||||
|
||||
uint32_t OutputChannelCount() { return mOutputChannelCount; }
|
||||
|
||||
|
@ -636,7 +620,7 @@ class AudioCallbackDriver : public GraphDriver,
|
|||
return AudioInputType::Unknown;
|
||||
}
|
||||
|
||||
std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); }
|
||||
std::thread::id ThreadId() { return mAudioThreadIdInCb.load(); }
|
||||
|
||||
/* Called when the thread servicing the callback has changed. This can be
|
||||
* fairly expensive */
|
||||
|
@ -645,13 +629,13 @@ class AudioCallbackDriver : public GraphDriver,
|
|||
* changed. */
|
||||
bool CheckThreadIdChanged();
|
||||
|
||||
bool OnThread() const override {
|
||||
bool OnThread() override {
|
||||
return mAudioThreadIdInCb.load() == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
/* Returns true if this audio callback driver has successfully started and not
|
||||
* yet stopped. If the fallback driver is active, this returns false. */
|
||||
bool ThreadRunning() const override {
|
||||
bool ThreadRunning() override {
|
||||
return mAudioStreamState == AudioStreamState::Running;
|
||||
}
|
||||
|
||||
|
@ -662,9 +646,6 @@ class AudioCallbackDriver : public GraphDriver,
|
|||
// Returns the output latency for the current audio output stream.
|
||||
TimeDuration AudioOutputLatency();
|
||||
|
||||
/* Returns true if this driver is currently driven by the fallback driver. */
|
||||
bool OnFallback() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* On certain MacBookPro, the microphone is located near the left speaker.
|
||||
|
|
|
@ -56,13 +56,13 @@ void GraphRunner::Shutdown() {
|
|||
mThread->Shutdown();
|
||||
}
|
||||
|
||||
auto GraphRunner::OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
auto GraphRunner::OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) -> IterationResult {
|
||||
TRACE();
|
||||
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
MOZ_ASSERT(mThreadState == ThreadState::Wait);
|
||||
mIterationState = Some(IterationState(aStateTime, aIterationEnd, aMixer));
|
||||
mIterationState = Some(IterationState(aStateEnd, aIterationEnd, aMixer));
|
||||
|
||||
#ifdef DEBUG
|
||||
if (auto audioDriver = mGraph->CurrentDriver()->AsAudioCallbackDriver()) {
|
||||
|
@ -113,7 +113,7 @@ NS_IMETHODIMP GraphRunner::Run() {
|
|||
}
|
||||
MOZ_DIAGNOSTIC_ASSERT(mIterationState.isSome());
|
||||
TRACE();
|
||||
mIterationResult = mGraph->OneIterationImpl(mIterationState->StateTime(),
|
||||
mIterationResult = mGraph->OneIterationImpl(mIterationState->StateEnd(),
|
||||
mIterationState->IterationEnd(),
|
||||
mIterationState->Mixer());
|
||||
// Signal that mIterationResult was updated
|
||||
|
@ -130,12 +130,12 @@ NS_IMETHODIMP GraphRunner::Run() {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool GraphRunner::OnThread() const {
|
||||
bool GraphRunner::OnThread() {
|
||||
return mThread->EventTarget()->IsOnCurrentThread();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool GraphRunner::InDriverIteration(const GraphDriver* aDriver) const {
|
||||
bool GraphRunner::InDriverIteration(GraphDriver* aDriver) {
|
||||
if (!OnThread()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class GraphRunner final : public Runnable {
|
|||
* Signals one iteration of mGraph. Hands state over to mThread and runs
|
||||
* the iteration there.
|
||||
*/
|
||||
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
IterationResult OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer);
|
||||
|
||||
/**
|
||||
|
@ -46,14 +46,14 @@ class GraphRunner final : public Runnable {
|
|||
/**
|
||||
* Returns true if called on mThread.
|
||||
*/
|
||||
bool OnThread() const;
|
||||
bool OnThread();
|
||||
|
||||
#ifdef DEBUG
|
||||
/**
|
||||
* Returns true if called on mThread, and aDriver was the driver that called
|
||||
* OneIteration() last.
|
||||
*/
|
||||
bool InDriverIteration(const GraphDriver* aDriver) const;
|
||||
bool InDriverIteration(GraphDriver* aDriver);
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
@ -62,18 +62,16 @@ class GraphRunner final : public Runnable {
|
|||
~GraphRunner();
|
||||
|
||||
class IterationState {
|
||||
GraphTime mStateTime;
|
||||
GraphTime mStateEnd;
|
||||
GraphTime mIterationEnd;
|
||||
AudioMixer* MOZ_NON_OWNING_REF mMixer;
|
||||
|
||||
public:
|
||||
IterationState(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
IterationState(GraphTime aStateEnd, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer)
|
||||
: mStateTime(aStateTime),
|
||||
mIterationEnd(aIterationEnd),
|
||||
mMixer(aMixer) {}
|
||||
: mStateEnd(aStateEnd), mIterationEnd(aIterationEnd), mMixer(aMixer) {}
|
||||
IterationState& operator=(const IterationState& aOther) = default;
|
||||
GraphTime StateTime() const { return mStateTime; }
|
||||
GraphTime StateEnd() const { return mStateEnd; }
|
||||
GraphTime IterationEnd() const { return mIterationEnd; }
|
||||
AudioMixer* Mixer() const { return mMixer; }
|
||||
};
|
||||
|
@ -92,8 +90,8 @@ class GraphRunner final : public Runnable {
|
|||
|
||||
enum class ThreadState {
|
||||
Wait, // Waiting for a message. This is the initial state.
|
||||
// A transition from Run back to Wait occurs on the runner thread
|
||||
// after it processes as far as mIterationState->mStateTime
|
||||
// A transition from Run back to Wait occurs on the runner
|
||||
// thread after it processes as far as mIterationState->mStateEnd
|
||||
// and sets mIterationResult.
|
||||
Run, // Set on driver thread after each mIterationState update.
|
||||
Shutdown, // Set when Shutdown() is called on main thread.
|
||||
|
|
|
@ -1365,17 +1365,17 @@ bool MediaTrackGraphImpl::UpdateMainThreadState() {
|
|||
return false;
|
||||
}
|
||||
|
||||
auto MediaTrackGraphImpl::OneIteration(GraphTime aStateTime,
|
||||
auto MediaTrackGraphImpl::OneIteration(GraphTime aStateEnd,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) -> IterationResult {
|
||||
if (mGraphRunner) {
|
||||
return mGraphRunner->OneIteration(aStateTime, aIterationEnd, aMixer);
|
||||
return mGraphRunner->OneIteration(aStateEnd, aIterationEnd, aMixer);
|
||||
}
|
||||
|
||||
return OneIterationImpl(aStateTime, aIterationEnd, aMixer);
|
||||
return OneIterationImpl(aStateEnd, aIterationEnd, aMixer);
|
||||
}
|
||||
|
||||
auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
|
||||
auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateEnd,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer)
|
||||
-> IterationResult {
|
||||
|
@ -1406,14 +1406,14 @@ auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
|
|||
NS_ProcessPendingEvents(nullptr);
|
||||
}
|
||||
|
||||
GraphTime stateTime = std::min(aStateTime, GraphTime(mEndTime));
|
||||
UpdateGraph(stateTime);
|
||||
GraphTime stateEnd = std::min(aStateEnd, GraphTime(mEndTime));
|
||||
UpdateGraph(stateEnd);
|
||||
|
||||
mStateComputedTime = stateTime;
|
||||
mStateComputedTime = stateEnd;
|
||||
|
||||
GraphTime oldProcessedTime = mProcessedTime;
|
||||
Process(aMixer);
|
||||
MOZ_ASSERT(mProcessedTime == stateTime);
|
||||
MOZ_ASSERT(mProcessedTime == stateEnd);
|
||||
|
||||
UpdateCurrentTimeForTracks(oldProcessedTime);
|
||||
|
||||
|
@ -3095,7 +3095,7 @@ AbstractThread* MediaTrackGraph::AbstractMainThread() {
|
|||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool MediaTrackGraphImpl::InDriverIteration(const GraphDriver* aDriver) const {
|
||||
bool MediaTrackGraphImpl::InDriverIteration(GraphDriver* aDriver) {
|
||||
return aDriver->OnThread() ||
|
||||
(mGraphRunner && mGraphRunner->InDriverIteration(aDriver));
|
||||
}
|
||||
|
@ -3447,8 +3447,7 @@ void MediaTrackGraphImpl::NotifyWhenGraphStarted(
|
|||
// ControlMessage.
|
||||
MediaTrackGraphImpl* graphImpl = mMediaTrack->GraphImpl();
|
||||
if (graphImpl->CurrentDriver()->AsAudioCallbackDriver() &&
|
||||
graphImpl->CurrentDriver()->ThreadRunning() &&
|
||||
!graphImpl->CurrentDriver()->AsAudioCallbackDriver()->OnFallback()) {
|
||||
graphImpl->CurrentDriver()->ThreadRunning()) {
|
||||
// Avoid Resolve's locking on the graph thread by doing it on main.
|
||||
graphImpl->Dispatch(NS_NewRunnableFunction(
|
||||
"MediaTrackGraphImpl::NotifyWhenGraphStarted::Resolver",
|
||||
|
|
|
@ -131,7 +131,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
|
||||
* and mGraphRunner is currently run by aDriver.
|
||||
*/
|
||||
bool InDriverIteration(const GraphDriver* aDriver) const override;
|
||||
bool InDriverIteration(GraphDriver* aDriver) override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -222,14 +222,14 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
* OneIterationImpl is called directly. Output from the graph gets mixed into
|
||||
* aMixer, if it is non-null.
|
||||
*/
|
||||
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
IterationResult OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) override;
|
||||
|
||||
/**
|
||||
* Returns true if this MediaTrackGraph should keep running
|
||||
*/
|
||||
IterationResult OneIterationImpl(GraphTime aStateTime,
|
||||
GraphTime aIterationEnd, AudioMixer* aMixer);
|
||||
IterationResult OneIterationImpl(GraphTime aStateEnd, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer);
|
||||
|
||||
/**
|
||||
* Called from the driver, when the graph thread is about to stop, to tell
|
||||
|
|
|
@ -15,14 +15,10 @@ MockCubebStream::MockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
|
|||
cubeb_stream_params* aOutputStreamParams,
|
||||
cubeb_data_callback aDataCallback,
|
||||
cubeb_state_callback aStateCallback,
|
||||
void* aUserPtr, SmartMockCubebStream* aSelf,
|
||||
bool aFrozenStart)
|
||||
void* aUserPtr)
|
||||
: context(aContext),
|
||||
mHasInput(aInputStreamParams),
|
||||
mHasOutput(aOutputStreamParams),
|
||||
mSelf(aSelf),
|
||||
mFrozenStartMonitor("MockCubebStream::mFrozenStartMonitor"),
|
||||
mFrozenStart(aFrozenStart),
|
||||
mDataCallback(aDataCallback),
|
||||
mStateCallback(aStateCallback),
|
||||
mUserPtr(aUserPtr),
|
||||
|
@ -46,47 +42,26 @@ MockCubebStream::MockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
|
|||
MockCubebStream::~MockCubebStream() = default;
|
||||
|
||||
int MockCubebStream::Start() {
|
||||
mStateCallback(AsCubebStream(), mUserPtr, CUBEB_STATE_STARTED);
|
||||
mStreamStop = false;
|
||||
MonitorAutoLock lock(mFrozenStartMonitor);
|
||||
if (mFrozenStart) {
|
||||
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
||||
"MockCubebStream::WaitForThawBeforeStart",
|
||||
[this, self = RefPtr<SmartMockCubebStream>(mSelf)] {
|
||||
MonitorAutoLock lock(mFrozenStartMonitor);
|
||||
while (mFrozenStart) {
|
||||
mFrozenStartMonitor.Wait();
|
||||
}
|
||||
if (!mStreamStop) {
|
||||
MockCubeb::AsMock(context)->StartStream(mSelf);
|
||||
}
|
||||
}));
|
||||
return CUBEB_OK;
|
||||
}
|
||||
MockCubeb::AsMock(context)->StartStream(this);
|
||||
reinterpret_cast<MockCubeb*>(context)->StartStream(this);
|
||||
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
|
||||
mStateCallback(stream, mUserPtr, CUBEB_STATE_STARTED);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int MockCubebStream::Stop() {
|
||||
mStreamStop = true;
|
||||
mOutputVerificationEvent.Notify(MakeTuple(
|
||||
mAudioVerifier.PreSilenceSamples(), mAudioVerifier.EstimatedFreq(),
|
||||
mAudioVerifier.CountDiscontinuities()));
|
||||
int rv = MockCubeb::AsMock(context)->StopStream(this);
|
||||
mStreamStop = true;
|
||||
int rv = reinterpret_cast<MockCubeb*>(context)->StopStream(this);
|
||||
if (rv == CUBEB_OK) {
|
||||
mStateCallback(AsCubebStream(), mUserPtr, CUBEB_STATE_STOPPED);
|
||||
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
|
||||
mStateCallback(stream, mUserPtr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
cubeb_stream* MockCubebStream::AsCubebStream() {
|
||||
return reinterpret_cast<cubeb_stream*>(this);
|
||||
}
|
||||
|
||||
MockCubebStream* MockCubebStream::AsMock(cubeb_stream* aStream) {
|
||||
return reinterpret_cast<MockCubebStream*>(aStream);
|
||||
}
|
||||
|
||||
cubeb_devid MockCubebStream::GetInputDeviceID() const { return mInputDeviceID; }
|
||||
|
||||
cubeb_devid MockCubebStream::GetOutputDeviceID() const {
|
||||
|
@ -111,12 +86,6 @@ void MockCubebStream::SetDriftFactor(float aDriftFactor) {
|
|||
|
||||
void MockCubebStream::ForceError() { mForceErrorState = true; }
|
||||
|
||||
void MockCubebStream::Thaw() {
|
||||
MonitorAutoLock l(mFrozenStartMonitor);
|
||||
mFrozenStart = false;
|
||||
mFrozenStartMonitor.Notify();
|
||||
}
|
||||
|
||||
MediaEventSource<uint32_t>& MockCubebStream::FramesProcessedEvent() {
|
||||
return mFramesProcessedEvent;
|
||||
}
|
||||
|
@ -146,7 +115,7 @@ void MockCubebStream::Process10Ms() {
|
|||
if (mInputParams.rate) {
|
||||
mAudioGenerator.GenerateInterleaved(mInputBuffer, nrFrames);
|
||||
}
|
||||
cubeb_stream* stream = AsCubebStream();
|
||||
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
|
||||
const long outframes =
|
||||
mDataCallback(stream, mUserPtr, mHasInput ? mInputBuffer : nullptr,
|
||||
mHasOutput ? mOutputBuffer : nullptr, nrFrames);
|
||||
|
@ -168,9 +137,10 @@ void MockCubebStream::Process10Ms() {
|
|||
mForceErrorState = false;
|
||||
// Let the audio thread (this thread!) run to completion before
|
||||
// being released, by joining and releasing on main.
|
||||
NS_DispatchBackgroundTask(
|
||||
NS_NewRunnableFunction(__func__, [cubeb = MockCubeb::AsMock(context),
|
||||
this] { cubeb->StopStream(this); }));
|
||||
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
||||
__func__, [cubeb = reinterpret_cast<MockCubeb*>(context), this] {
|
||||
cubeb->StopStream(this);
|
||||
}));
|
||||
mStateCallback(stream, mUserPtr, CUBEB_STATE_ERROR);
|
||||
mErrorForcedEvent.Notify();
|
||||
mStreamStop = true;
|
||||
|
@ -184,10 +154,6 @@ MockCubeb::~MockCubeb() { MOZ_ASSERT(!mFakeAudioThread); };
|
|||
|
||||
cubeb* MockCubeb::AsCubebContext() { return reinterpret_cast<cubeb*>(this); }
|
||||
|
||||
MockCubeb* MockCubeb::AsMock(cubeb* aContext) {
|
||||
return reinterpret_cast<MockCubeb*>(aContext);
|
||||
}
|
||||
|
||||
int MockCubeb::EnumerateDevices(cubeb_device_type aType,
|
||||
cubeb_device_collection* collection) {
|
||||
#ifdef ANDROID
|
||||
|
@ -321,23 +287,6 @@ void MockCubeb::SetSupportDeviceChangeCallback(bool aSupports) {
|
|||
mSupportsDeviceCollectionChangedCallback = aSupports;
|
||||
}
|
||||
|
||||
void MockCubeb::SetStreamStartFreezeEnabled(bool aEnabled) {
|
||||
mStreamStartFreezeEnabled = aEnabled;
|
||||
}
|
||||
|
||||
auto MockCubeb::ForceAudioThread() -> RefPtr<ForcedAudioThreadPromise> {
|
||||
RefPtr<ForcedAudioThreadPromise> p =
|
||||
mForcedAudioThreadPromise.Ensure(__func__);
|
||||
mForcedAudioThread = true;
|
||||
StartStream(nullptr);
|
||||
return p;
|
||||
}
|
||||
|
||||
void MockCubeb::UnforceAudioThread() {
|
||||
mForcedAudioThread = false;
|
||||
StopStream(nullptr);
|
||||
}
|
||||
|
||||
int MockCubeb::StreamInit(cubeb* aContext, cubeb_stream** aStream,
|
||||
cubeb_devid aInputDevice,
|
||||
cubeb_stream_params* aInputStreamParams,
|
||||
|
@ -345,28 +294,25 @@ int MockCubeb::StreamInit(cubeb* aContext, cubeb_stream** aStream,
|
|||
cubeb_stream_params* aOutputStreamParams,
|
||||
cubeb_data_callback aDataCallback,
|
||||
cubeb_state_callback aStateCallback, void* aUserPtr) {
|
||||
auto mockStream = MakeRefPtr<SmartMockCubebStream>(
|
||||
MockCubebStream* mockStream = new MockCubebStream(
|
||||
aContext, aInputDevice, aInputStreamParams, aOutputDevice,
|
||||
aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr,
|
||||
mStreamStartFreezeEnabled);
|
||||
*aStream = mockStream->AsCubebStream();
|
||||
aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr);
|
||||
*aStream = reinterpret_cast<cubeb_stream*>(mockStream);
|
||||
mStreamInitEvent.Notify(mockStream);
|
||||
// AddRef the stream to keep it alive. StreamDestroy releases it.
|
||||
Unused << mockStream.forget().take();
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void MockCubeb::StreamDestroy(cubeb_stream* aStream) {
|
||||
mStreamDestroyEvent.Notify();
|
||||
RefPtr<SmartMockCubebStream> mockStream =
|
||||
dont_AddRef(MockCubebStream::AsMock(aStream)->mSelf);
|
||||
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
|
||||
delete mockStream;
|
||||
}
|
||||
|
||||
void MockCubeb::GoFaster() { mFastMode = true; }
|
||||
|
||||
void MockCubeb::DontGoFaster() { mFastMode = false; }
|
||||
|
||||
MediaEventSource<RefPtr<SmartMockCubebStream>>& MockCubeb::StreamInitEvent() {
|
||||
MediaEventSource<MockCubebStream*>& MockCubeb::StreamInitEvent() {
|
||||
return mStreamInitEvent;
|
||||
}
|
||||
|
||||
|
@ -376,13 +322,8 @@ MediaEventSource<void>& MockCubeb::StreamDestroyEvent() {
|
|||
|
||||
void MockCubeb::StartStream(MockCubebStream* aStream) {
|
||||
auto streams = mLiveStreams.Lock();
|
||||
MOZ_ASSERT_IF(!aStream, mForcedAudioThread);
|
||||
// Forcing an audio thread must happen before starting streams
|
||||
MOZ_ASSERT_IF(!aStream, streams->IsEmpty());
|
||||
if (aStream) {
|
||||
MOZ_ASSERT(!streams->Contains(aStream->mSelf));
|
||||
streams->AppendElement(aStream->mSelf);
|
||||
}
|
||||
MOZ_ASSERT(!streams->Contains(aStream));
|
||||
streams->AppendElement(aStream);
|
||||
if (!mFakeAudioThread) {
|
||||
mFakeAudioThread = WrapUnique(new std::thread(ThreadFunction_s, this));
|
||||
}
|
||||
|
@ -392,14 +333,13 @@ int MockCubeb::StopStream(MockCubebStream* aStream) {
|
|||
UniquePtr<std::thread> audioThread;
|
||||
{
|
||||
auto streams = mLiveStreams.Lock();
|
||||
if (aStream) {
|
||||
if (!streams->Contains(aStream->mSelf)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
streams->RemoveElement(aStream->mSelf);
|
||||
if (!streams->Contains(aStream)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
MOZ_ASSERT(streams->Contains(aStream));
|
||||
streams->RemoveElement(aStream);
|
||||
MOZ_ASSERT(mFakeAudioThread);
|
||||
if (streams->IsEmpty() && !mForcedAudioThread) {
|
||||
if (streams->IsEmpty()) {
|
||||
audioThread = std::move(mFakeAudioThread);
|
||||
}
|
||||
}
|
||||
|
@ -410,17 +350,13 @@ int MockCubeb::StopStream(MockCubebStream* aStream) {
|
|||
}
|
||||
|
||||
void MockCubeb::ThreadFunction() {
|
||||
if (mForcedAudioThread) {
|
||||
mForcedAudioThreadPromise.Resolve(MakeRefPtr<AudioThreadAutoUnforcer>(this),
|
||||
__func__);
|
||||
}
|
||||
while (true) {
|
||||
{
|
||||
auto streams = mLiveStreams.Lock();
|
||||
for (auto& stream : *streams) {
|
||||
stream->Process10Ms();
|
||||
}
|
||||
if (streams->IsEmpty() && !mForcedAudioThread) {
|
||||
if (streams->IsEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
#include "AudioVerifier.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "mozilla/DataMutex.h"
|
||||
#include "mozilla/ThreadSafeWeakPtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
#include "mozilla/DataMutex.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
@ -123,8 +124,6 @@ cubeb_ops const mock_ops = {
|
|||
|
||||
cubeb_mock_register_device_collection_changed};
|
||||
|
||||
class SmartMockCubebStream;
|
||||
|
||||
// Represents the fake cubeb_stream. The context instance is needed to
|
||||
// provide access on cubeb_ops struct.
|
||||
class MockCubebStream {
|
||||
|
@ -134,17 +133,13 @@ class MockCubebStream {
|
|||
cubeb_devid aOutputDevice,
|
||||
cubeb_stream_params* aOutputStreamParams,
|
||||
cubeb_data_callback aDataCallback,
|
||||
cubeb_state_callback aStateCallback, void* aUserPtr,
|
||||
SmartMockCubebStream* aSelf, bool aFrozenStart);
|
||||
cubeb_state_callback aStateCallback, void* aUserPtr);
|
||||
|
||||
~MockCubebStream();
|
||||
|
||||
int Start();
|
||||
int Stop();
|
||||
|
||||
cubeb_stream* AsCubebStream();
|
||||
static MockCubebStream* AsMock(cubeb_stream* aStream);
|
||||
|
||||
cubeb_devid GetInputDeviceID() const;
|
||||
cubeb_devid GetOutputDeviceID() const;
|
||||
|
||||
|
@ -154,7 +149,6 @@ class MockCubebStream {
|
|||
|
||||
void SetDriftFactor(float aDriftFactor);
|
||||
void ForceError();
|
||||
void Thaw();
|
||||
|
||||
MediaEventSource<uint32_t>& FramesProcessedEvent();
|
||||
MediaEventSource<uint32_t>& FramesVerifiedEvent();
|
||||
|
@ -168,14 +162,8 @@ class MockCubebStream {
|
|||
|
||||
const bool mHasInput;
|
||||
const bool mHasOutput;
|
||||
SmartMockCubebStream* const mSelf;
|
||||
|
||||
private:
|
||||
// Monitor used to block start until mFrozenStart is false.
|
||||
Monitor mFrozenStartMonitor;
|
||||
// Whether this stream should wait for an explicit start request before
|
||||
// starting. Protected by FrozenStartMonitor.
|
||||
bool mFrozenStart;
|
||||
// Signal to the audio thread that stream is stopped.
|
||||
std::atomic_bool mStreamStop{true};
|
||||
// The audio buffer used on data callback.
|
||||
|
@ -206,24 +194,6 @@ class MockCubebStream {
|
|||
MediaEventProducer<void> mErrorForcedEvent;
|
||||
};
|
||||
|
||||
class SmartMockCubebStream
|
||||
: public MockCubebStream,
|
||||
public SupportsThreadSafeWeakPtr<SmartMockCubebStream> {
|
||||
public:
|
||||
MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(SmartMockCubebStream)
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(SmartMockCubebStream)
|
||||
SmartMockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
|
||||
cubeb_stream_params* aInputStreamParams,
|
||||
cubeb_devid aOutputDevice,
|
||||
cubeb_stream_params* aOutputStreamParams,
|
||||
cubeb_data_callback aDataCallback,
|
||||
cubeb_state_callback aStateCallback, void* aUserPtr,
|
||||
bool aFrozenStart)
|
||||
: MockCubebStream(aContext, aInputDevice, aInputStreamParams,
|
||||
aOutputDevice, aOutputStreamParams, aDataCallback,
|
||||
aStateCallback, aUserPtr, this, aFrozenStart) {}
|
||||
};
|
||||
|
||||
// This class has two facets: it is both a fake cubeb backend that is intended
|
||||
// to be used for testing, and passed to Gecko code that expects a normal
|
||||
// backend, but is also controllable by the test code to decide what the backend
|
||||
|
@ -235,7 +205,6 @@ class MockCubeb {
|
|||
// Cubeb backend implementation
|
||||
// This allows passing this class as a cubeb* instance.
|
||||
cubeb* AsCubebContext();
|
||||
static MockCubeb* AsMock(cubeb* aContext);
|
||||
// Fill in the collection parameter with all devices of aType.
|
||||
int EnumerateDevices(cubeb_device_type aType,
|
||||
cubeb_device_collection* collection);
|
||||
|
@ -264,36 +233,6 @@ class MockCubeb {
|
|||
// collection invalidation callback, to be able to test the fallback path.
|
||||
void SetSupportDeviceChangeCallback(bool aSupports);
|
||||
|
||||
// Makes MockCubebStreams starting after this point wait for AllowStart().
|
||||
// Callers must ensure they get a hold of the stream through StreamInitEvent
|
||||
// to be able to start them.
|
||||
void SetStreamStartFreezeEnabled(bool aEnabled);
|
||||
|
||||
// Helper class that automatically unforces a forced audio thread on release.
|
||||
class AudioThreadAutoUnforcer {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioThreadAutoUnforcer)
|
||||
|
||||
public:
|
||||
explicit AudioThreadAutoUnforcer(MockCubeb* aContext)
|
||||
: mContext(aContext) {}
|
||||
|
||||
protected:
|
||||
virtual ~AudioThreadAutoUnforcer() { mContext->UnforceAudioThread(); }
|
||||
MockCubeb* mContext;
|
||||
};
|
||||
|
||||
// Creates the audio thread if one is not available. The audio thread remains
|
||||
// forced until UnforceAudioThread is called. The returned promise is resolved
|
||||
// when the audio thread is running. With this, a test can ensure starting
|
||||
// audio streams is deterministically fast across platforms for more accurate
|
||||
// results.
|
||||
using ForcedAudioThreadPromise =
|
||||
MozPromise<RefPtr<AudioThreadAutoUnforcer>, nsresult, false>;
|
||||
RefPtr<ForcedAudioThreadPromise> ForceAudioThread();
|
||||
|
||||
// Allows a forced audio thread to stop.
|
||||
void UnforceAudioThread();
|
||||
|
||||
int StreamInit(cubeb* aContext, cubeb_stream** aStream,
|
||||
cubeb_devid aInputDevice,
|
||||
cubeb_stream_params* aInputStreamParams,
|
||||
|
@ -307,7 +246,7 @@ class MockCubeb {
|
|||
void GoFaster();
|
||||
void DontGoFaster();
|
||||
|
||||
MediaEventSource<RefPtr<SmartMockCubebStream>>& StreamInitEvent();
|
||||
MediaEventSource<MockCubebStream*>& StreamInitEvent();
|
||||
MediaEventSource<void>& StreamDestroyEvent();
|
||||
|
||||
// MockCubeb specific API
|
||||
|
@ -341,19 +280,12 @@ class MockCubeb {
|
|||
// notification via a system callback. If not, Gecko is expected to re-query
|
||||
// the list every time.
|
||||
bool mSupportsDeviceCollectionChangedCallback = true;
|
||||
// Whether new MockCubebStreams should be frozen on start.
|
||||
Atomic<bool> mStreamStartFreezeEnabled{false};
|
||||
// Whether the audio thread is forced, i.e., whether it remains active even
|
||||
// with no live streams.
|
||||
Atomic<bool> mForcedAudioThread{false};
|
||||
MozPromiseHolder<ForcedAudioThreadPromise> mForcedAudioThreadPromise;
|
||||
// Our input and output devices.
|
||||
nsTArray<cubeb_device_info> mInputDevices;
|
||||
nsTArray<cubeb_device_info> mOutputDevices;
|
||||
|
||||
// The streams that are currently running.
|
||||
DataMutex<nsTArray<RefPtr<SmartMockCubebStream>>> mLiveStreams{
|
||||
"MockCubeb::mLiveStreams"};
|
||||
DataMutex<nsTArray<MockCubebStream*>> mLiveStreams{"MockCubeb::mLiveStreams"};
|
||||
// Thread that simulates the audio thread, shared across MockCubebStreams to
|
||||
// avoid unintended drift. This is set together with mLiveStreams, under the
|
||||
// mLiveStreams DataMutex.
|
||||
|
@ -363,15 +295,18 @@ class MockCubeb {
|
|||
// true we sleep(0) between iterations instead of 10ms.
|
||||
std::atomic<bool> mFastMode{false};
|
||||
|
||||
MediaEventProducer<RefPtr<SmartMockCubebStream>> mStreamInitEvent;
|
||||
MediaEventProducer<MockCubebStream*> mStreamInitEvent;
|
||||
MediaEventProducer<void> mStreamDestroyEvent;
|
||||
};
|
||||
|
||||
void cubeb_mock_destroy(cubeb* context) { delete MockCubeb::AsMock(context); }
|
||||
void cubeb_mock_destroy(cubeb* context) {
|
||||
delete reinterpret_cast<MockCubeb*>(context);
|
||||
}
|
||||
|
||||
int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
|
||||
cubeb_device_collection* out) {
|
||||
return MockCubeb::AsMock(context)->EnumerateDevices(type, out);
|
||||
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
|
||||
return mock->EnumerateDevices(type, out);
|
||||
}
|
||||
|
||||
int cubeb_mock_device_collection_destroy(cubeb* context,
|
||||
|
@ -383,8 +318,9 @@ int cubeb_mock_device_collection_destroy(cubeb* context,
|
|||
int cubeb_mock_register_device_collection_changed(
|
||||
cubeb* context, cubeb_device_type devtype,
|
||||
cubeb_device_collection_changed_callback callback, void* user_ptr) {
|
||||
return MockCubeb::AsMock(context)->RegisterDeviceCollectionChangeCallback(
|
||||
devtype, callback, user_ptr);
|
||||
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
|
||||
return mock->RegisterDeviceCollectionChangeCallback(devtype, callback,
|
||||
user_ptr);
|
||||
}
|
||||
|
||||
int cubeb_mock_stream_init(
|
||||
|
@ -393,22 +329,25 @@ int cubeb_mock_stream_init(
|
|||
cubeb_devid output_device, cubeb_stream_params* output_stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void* user_ptr) {
|
||||
return MockCubeb::AsMock(context)->StreamInit(
|
||||
context, stream, input_device, input_stream_params, output_device,
|
||||
output_stream_params, data_callback, state_callback, user_ptr);
|
||||
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
|
||||
return mock->StreamInit(context, stream, input_device, input_stream_params,
|
||||
output_device, output_stream_params, data_callback,
|
||||
state_callback, user_ptr);
|
||||
}
|
||||
|
||||
int cubeb_mock_stream_start(cubeb_stream* stream) {
|
||||
return MockCubebStream::AsMock(stream)->Start();
|
||||
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
|
||||
return mockStream->Start();
|
||||
}
|
||||
|
||||
int cubeb_mock_stream_stop(cubeb_stream* stream) {
|
||||
return MockCubebStream::AsMock(stream)->Stop();
|
||||
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
|
||||
return mockStream->Stop();
|
||||
}
|
||||
|
||||
void cubeb_mock_stream_destroy(cubeb_stream* stream) {
|
||||
MockCubebStream* mockStream = MockCubebStream::AsMock(stream);
|
||||
MockCubeb* mock = MockCubeb::AsMock(mockStream->context);
|
||||
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
|
||||
MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
|
||||
return mock->StreamDestroy(stream);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,22 +10,19 @@
|
|||
#include "gtest/gtest-printers.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "MediaTrackGraphImpl.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
#include "MockCubeb.h"
|
||||
#include "WaitFor.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using IterationResult = GraphInterface::IterationResult;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
class MockGraphInterface : public GraphInterface {
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
explicit MockGraphInterface(TrackRate aSampleRate)
|
||||
: mSampleRate(aSampleRate) {}
|
||||
MOCK_METHOD4(NotifyOutputData,
|
||||
void(AudioDataValue*, size_t, TrackRate, uint32_t));
|
||||
MOCK_METHOD0(NotifyInputStopped, void());
|
||||
|
@ -34,21 +31,7 @@ class MockGraphInterface : public GraphInterface {
|
|||
MOCK_METHOD0(DeviceChanged, void());
|
||||
/* OneIteration cannot be mocked because IterationResult is non-memmovable and
|
||||
* cannot be passed as a parameter, which GMock does internally. */
|
||||
IterationResult OneIteration(GraphTime aStateComputedTime, GraphTime,
|
||||
AudioMixer* aMixer) {
|
||||
GraphDriver* driver = mCurrentDriver;
|
||||
if (aMixer) {
|
||||
aMixer->StartMixing();
|
||||
aMixer->Mix(nullptr,
|
||||
driver->AsAudioCallbackDriver()->OutputChannelCount(),
|
||||
aStateComputedTime - mStateComputedTime, mSampleRate);
|
||||
aMixer->FinishMixing();
|
||||
}
|
||||
if (aStateComputedTime != mStateComputedTime) {
|
||||
mFramesIteratedEvent.Notify(aStateComputedTime - mStateComputedTime);
|
||||
++mIterationCount;
|
||||
}
|
||||
mStateComputedTime = aStateComputedTime;
|
||||
IterationResult OneIteration(GraphTime, GraphTime, AudioMixer*) {
|
||||
if (!mKeepProcessing) {
|
||||
return IterationResult::CreateStop(
|
||||
NS_NewRunnableFunction(__func__, [] {}));
|
||||
|
@ -58,41 +41,21 @@ class MockGraphInterface : public GraphInterface {
|
|||
return IterationResult::CreateSwitchDriver(
|
||||
next, NS_NewRunnableFunction(__func__, [] {}));
|
||||
}
|
||||
if (mEnsureNextIteration) {
|
||||
driver->EnsureNextIteration();
|
||||
}
|
||||
return IterationResult::CreateStillProcessing();
|
||||
}
|
||||
void SetEnsureNextIteration(bool aEnsure) { mEnsureNextIteration = aEnsure; }
|
||||
|
||||
#ifdef DEBUG
|
||||
bool InDriverIteration(const GraphDriver* aDriver) const override {
|
||||
bool InDriverIteration(GraphDriver* aDriver) override {
|
||||
return aDriver->OnThread();
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t IterationCount() const { return mIterationCount; }
|
||||
|
||||
GraphTime StateComputedTime() const { return mStateComputedTime; }
|
||||
void SetCurrentDriver(GraphDriver* aDriver) { mCurrentDriver = aDriver; }
|
||||
|
||||
void StopIterating() { mKeepProcessing = false; }
|
||||
|
||||
void SwitchTo(GraphDriver* aDriver) { mNextDriver = aDriver; }
|
||||
const TrackRate mSampleRate;
|
||||
|
||||
MediaEventSource<uint32_t>& FramesIteratedEvent() {
|
||||
return mFramesIteratedEvent;
|
||||
}
|
||||
|
||||
protected:
|
||||
Atomic<size_t> mIterationCount{0};
|
||||
Atomic<GraphTime> mStateComputedTime{0};
|
||||
Atomic<GraphDriver*> mCurrentDriver{nullptr};
|
||||
Atomic<bool> mEnsureNextIteration{false};
|
||||
Atomic<bool> mKeepProcessing{true};
|
||||
Atomic<GraphDriver*> mNextDriver{nullptr};
|
||||
MediaEventProducer<uint32_t> mFramesIteratedEvent;
|
||||
virtual ~MockGraphInterface() = default;
|
||||
};
|
||||
|
||||
|
@ -100,22 +63,20 @@ NS_IMPL_ISUPPORTS0(MockGraphInterface)
|
|||
|
||||
TEST(TestAudioCallbackDriver, StartStop)
|
||||
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
|
||||
const TrackRate rate = 44100;
|
||||
MockCubeb* cubeb = new MockCubeb();
|
||||
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
||||
|
||||
RefPtr<AudioCallbackDriver> driver;
|
||||
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
|
||||
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>();
|
||||
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
|
||||
ON_CALL(*graph, NotifyOutputData)
|
||||
.WillByDefault([&](AudioDataValue*, size_t, TrackRate, uint32_t) {});
|
||||
|
||||
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, rate, 2, 0, nullptr,
|
||||
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, 44100, 2, 0, nullptr,
|
||||
nullptr, AudioInputType::Unknown);
|
||||
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
|
||||
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
|
||||
|
||||
graph->SetCurrentDriver(driver);
|
||||
driver->Start();
|
||||
// Allow some time to "play" audio.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
@ -123,102 +84,8 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
|
|||
EXPECT_TRUE(driver->IsStarted()) << "Verify thread is started";
|
||||
|
||||
// This will block untill all events have been executed.
|
||||
graph->StopIterating();
|
||||
MOZ_KnownLive(driver)->Shutdown();
|
||||
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
|
||||
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
|
||||
}
|
||||
|
||||
void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
|
||||
std::cerr << "TestSlowStart with rate " << aRate << std::endl;
|
||||
|
||||
MockCubeb* cubeb = new MockCubeb();
|
||||
cubeb->SetStreamStartFreezeEnabled(true);
|
||||
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
|
||||
Unused << unforcer;
|
||||
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
||||
|
||||
RefPtr<AudioCallbackDriver> driver;
|
||||
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(aRate);
|
||||
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
|
||||
|
||||
Maybe<int64_t> audioStart;
|
||||
Maybe<uint32_t> alreadyBuffered;
|
||||
int64_t inputFrameCount = 0;
|
||||
int64_t outputFrameCount = 0;
|
||||
int64_t processedFrameCount = 0;
|
||||
ON_CALL(*graph, NotifyInputData)
|
||||
.WillByDefault([&](const AudioDataValue*, size_t aFrames, TrackRate,
|
||||
uint32_t, uint32_t aAlreadyBuffered) {
|
||||
if (!audioStart) {
|
||||
audioStart = Some(graph->StateComputedTime());
|
||||
alreadyBuffered = Some(aAlreadyBuffered);
|
||||
}
|
||||
EXPECT_NEAR(inputFrameCount,
|
||||
static_cast<int64_t>(graph->StateComputedTime() -
|
||||
*audioStart + *alreadyBuffered),
|
||||
WEBAUDIO_BLOCK_SIZE)
|
||||
<< "Input should be behind state time, due to the delayed start. "
|
||||
"stateComputedTime="
|
||||
<< graph->StateComputedTime() << ", audioStartTime=" << *audioStart
|
||||
<< ", alreadyBuffered=" << *alreadyBuffered;
|
||||
inputFrameCount += aFrames;
|
||||
});
|
||||
ON_CALL(*graph, NotifyOutputData)
|
||||
.WillByDefault([&](AudioDataValue*, size_t aFrames, TrackRate aRate,
|
||||
uint32_t) { outputFrameCount += aFrames; });
|
||||
|
||||
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, aRate, 2, 2, nullptr,
|
||||
(void*)1, AudioInputType::Voice);
|
||||
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
|
||||
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
|
||||
|
||||
graph->SetCurrentDriver(driver);
|
||||
graph->SetEnsureNextIteration(true);
|
||||
|
||||
driver->Start();
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
cubeb->SetStreamStartFreezeEnabled(false);
|
||||
|
||||
const int fallbackIterations = 3;
|
||||
WaitUntil(graph->FramesIteratedEvent(), [&](uint32_t aFrames) {
|
||||
const GraphTime tenMillis = aRate / 100;
|
||||
// An iteration is always rounded upwards to the next full block.
|
||||
const GraphTime tenMillisIteration =
|
||||
MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(tenMillis);
|
||||
// The iteration may be smaller because up to an extra block may have been
|
||||
// processed and buffered.
|
||||
const GraphTime tenMillisMinIteration =
|
||||
tenMillisIteration - WEBAUDIO_BLOCK_SIZE;
|
||||
// An iteration must be at least one audio block.
|
||||
const GraphTime minIteration =
|
||||
std::max<GraphTime>(WEBAUDIO_BLOCK_SIZE, tenMillisMinIteration);
|
||||
EXPECT_GE(aFrames, minIteration)
|
||||
<< "Fallback driver iteration >= 10ms, modulo an audio block";
|
||||
EXPECT_LT(aFrames, static_cast<size_t>(aRate))
|
||||
<< "Fallback driver iteration <1s (sanity)";
|
||||
return graph->IterationCount() >= fallbackIterations;
|
||||
});
|
||||
stream->Thaw();
|
||||
|
||||
// Wait for at least 100ms of audio data.
|
||||
WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
|
||||
processedFrameCount += aFrames;
|
||||
return processedFrameCount >= aRate / 10;
|
||||
});
|
||||
|
||||
// This will block untill all events have been executed.
|
||||
MOZ_KnownLive(driver)->Shutdown();
|
||||
|
||||
EXPECT_EQ(inputFrameCount, outputFrameCount);
|
||||
EXPECT_NEAR(graph->StateComputedTime() - *audioStart,
|
||||
inputFrameCount + *alreadyBuffered, WEBAUDIO_BLOCK_SIZE)
|
||||
<< "Graph progresses while audio driver runs. stateComputedTime="
|
||||
<< graph->StateComputedTime() << ", inputFrameCount=" << inputFrameCount;
|
||||
}
|
||||
|
||||
TEST(TestAudioCallbackDriver, SlowStart)
|
||||
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
|
||||
TestSlowStart(1000); // 10ms = 10 <<< 128 samples
|
||||
TestSlowStart(8000); // 10ms = 80 < 128 samples
|
||||
TestSlowStart(44100); // 10ms = 441 > 128 samples
|
||||
}
|
||||
|
|
|
@ -16,13 +16,80 @@
|
|||
#include "MockCubeb.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/SpinEventLoopUntil.h"
|
||||
#include "WaitFor.h"
|
||||
|
||||
#define DRIFT_BUFFERING_PREF "media.clockdrift.buffering"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Waits for an occurrence of aEvent on the current thread (by blocking it,
|
||||
* except tasks added to the event loop may run) and returns the event's
|
||||
* templated value, if it's non-void.
|
||||
*
|
||||
* The caller must be wary of eventloop issues, in
|
||||
* particular cases where we rely on a stable state runnable, but there is never
|
||||
* a task to trigger stable state. In such cases it is the responsibility of the
|
||||
* caller to create the needed tasks, as JS would. A noteworthy API that relies
|
||||
* on stable state is MediaTrackGraph::GetInstance.
|
||||
*/
|
||||
template <typename T>
|
||||
T WaitFor(MediaEventSource<T>& aEvent) {
|
||||
Maybe<T> value;
|
||||
MediaEventListener listener = aEvent.Connect(
|
||||
AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return value.isSome(); });
|
||||
listener.Disconnect();
|
||||
return value.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of WaitFor<T> for void.
|
||||
*/
|
||||
void WaitFor(MediaEventSource<void>& aEvent) {
|
||||
bool done = false;
|
||||
MediaEventListener listener =
|
||||
aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return done; });
|
||||
listener.Disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of WaitFor that blocks the caller until a MozPromise has either been
|
||||
* resolved or rejected.
|
||||
*/
|
||||
template <typename R, typename E, bool Exc>
|
||||
Result<R, E> WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise) {
|
||||
Maybe<Result<R, E>> result;
|
||||
aPromise->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[&](R aResult) { result = Some(Result<R, E>(aResult)); },
|
||||
[&](E aError) { result = Some(Result<R, E>(aError)); });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return result.isSome(); });
|
||||
return result.extract();
|
||||
}
|
||||
|
||||
/**
|
||||
* A variation of WaitFor that takes a callback to be called each time aEvent is
|
||||
* raised. Blocks the caller until the callback function returns true.
|
||||
*/
|
||||
template <typename T, typename CallbackFunction>
|
||||
void WaitUntil(MediaEventSource<T>& aEvent, const CallbackFunction& aF) {
|
||||
bool done = false;
|
||||
MediaEventListener listener =
|
||||
aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) {
|
||||
if (!done) {
|
||||
done = aF(aValue);
|
||||
}
|
||||
});
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return done; });
|
||||
listener.Disconnect();
|
||||
}
|
||||
|
||||
// Short-hand for InvokeAsync on the current thread.
|
||||
#define Invoke(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)
|
||||
|
||||
|
@ -167,7 +234,7 @@ TEST(TestAudioTrackGraph, SetOutputDeviceID)
|
|||
DispatchFunction(
|
||||
[&] { dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO); });
|
||||
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
|
||||
|
||||
EXPECT_EQ(stream->GetOutputDeviceID(), reinterpret_cast<cubeb_devid>(2))
|
||||
<< "After init confirm the expected output device id";
|
||||
|
@ -214,18 +281,14 @@ TEST(TestAudioTrackGraph, ErrorCallback)
|
|||
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
||||
|
||||
MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
|
||||
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
|
||||
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
|
||||
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
|
||||
|
||||
// Dummy track to make graph rolling. Add it and remove it to remove the
|
||||
// graph from the global hash table and let it shutdown.
|
||||
//
|
||||
// We open an input through this track so that there's something triggering
|
||||
// EnsureNextIteration on the fallback driver after the callback driver has
|
||||
// gotten the error.
|
||||
RefPtr<AudioInputTrack> inputTrack;
|
||||
RefPtr<AudioInputProcessing> listener;
|
||||
auto started = Invoke([&] {
|
||||
Unused << WaitFor(Invoke([&] {
|
||||
inputTrack = AudioInputTrack::Create(graph);
|
||||
listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
|
||||
inputTrack->GraphImpl()->AppendMessage(
|
||||
|
@ -233,11 +296,18 @@ TEST(TestAudioTrackGraph, ErrorCallback)
|
|||
inputTrack->SetInputProcessing(listener);
|
||||
inputTrack->GraphImpl()->AppendMessage(
|
||||
MakeUnique<StartInputProcessing>(inputTrack, listener));
|
||||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
}));
|
||||
|
||||
// We open an input through this track so that there's something triggering
|
||||
// EnsureNextIteration on the fallback driver after the callback driver has
|
||||
// gotten the error.
|
||||
auto started = Invoke([&] {
|
||||
inputTrack->OpenAudioInput((void*)1, listener);
|
||||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
});
|
||||
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
|
||||
Result<bool, nsresult> rv = WaitFor(started);
|
||||
EXPECT_TRUE(rv.unwrapOr(false));
|
||||
|
||||
|
@ -271,21 +341,16 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
|
|||
{
|
||||
MockCubeb* cubeb = new MockCubeb();
|
||||
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
||||
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
|
||||
Unused << unforcer;
|
||||
|
||||
// Start on a system clock driver, then switch to full-duplex in one go. If we
|
||||
// did output-then-full-duplex we'd risk a second NotifyWhenDeviceStarted
|
||||
// resolving early after checking the first audio driver only.
|
||||
MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
|
||||
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
|
||||
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
|
||||
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
|
||||
|
||||
RefPtr<AudioInputTrack> inputTrack;
|
||||
RefPtr<ProcessedMediaTrack> outputTrack;
|
||||
RefPtr<MediaInputPort> port;
|
||||
RefPtr<AudioInputProcessing> listener;
|
||||
auto p = Invoke([&] {
|
||||
Unused << WaitFor(Invoke([&] {
|
||||
inputTrack = AudioInputTrack::Create(graph);
|
||||
outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
|
||||
outputTrack->QueueSetAutoend(false);
|
||||
|
@ -298,12 +363,16 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
|
|||
inputTrack->SetInputProcessing(listener);
|
||||
inputTrack->GraphImpl()->AppendMessage(
|
||||
MakeUnique<StartInputProcessing>(inputTrack, listener));
|
||||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
}));
|
||||
|
||||
DispatchFunction([&] {
|
||||
// Device id does not matter. Ignore.
|
||||
inputTrack->OpenAudioInput((void*)1, listener);
|
||||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
});
|
||||
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
auto p = Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); });
|
||||
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
|
||||
EXPECT_TRUE(stream->mHasInput);
|
||||
Unused << WaitFor(p);
|
||||
|
||||
|
@ -352,9 +421,10 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
|
|||
// driver has started to complete the switch, *usually* resulting two
|
||||
// 10ms-iterations of silence; sometimes only one.
|
||||
EXPECT_LE(preSilenceSamples, 128U + 2 * inputRate / 100 /* 2*10ms */);
|
||||
// The waveform from AudioGenerator starts at 0, but we don't control its
|
||||
// ending, so we expect a discontinuity there.
|
||||
EXPECT_LE(nrDiscontinuities, 1U);
|
||||
// Waveform may start after the beginning. In this case, there is a gap
|
||||
// at the beginning and the end which is counted as discontinuity.
|
||||
EXPECT_GE(nrDiscontinuities, 0U);
|
||||
EXPECT_LE(nrDiscontinuities, 2U);
|
||||
}
|
||||
|
||||
TEST(TestAudioTrackGraph, ReOpenAudioInput)
|
||||
|
@ -388,7 +458,7 @@ TEST(TestAudioTrackGraph, ReOpenAudioInput)
|
|||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
});
|
||||
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
|
||||
EXPECT_TRUE(stream->mHasInput);
|
||||
Unused << WaitFor(p);
|
||||
|
||||
|
@ -530,7 +600,7 @@ TEST(TestAudioTrackGraph, AudioInputTrackDisabling)
|
|||
return graph->NotifyWhenDeviceStarted(inputTrack);
|
||||
});
|
||||
|
||||
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
||||
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
|
||||
EXPECT_TRUE(stream->mHasInput);
|
||||
Unused << WaitFor(p);
|
||||
|
||||
|
@ -605,25 +675,15 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
|
|||
|
||||
MockCubeb* cubeb = new MockCubeb();
|
||||
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
||||
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
|
||||
Unused << unforcer;
|
||||
|
||||
cubeb->SetStreamStartFreezeEnabled(true);
|
||||
|
||||
/* Primary graph: Create the graph. */
|
||||
/* Primary graph: Create the graph and a SourceMediaTrack. */
|
||||
MediaTrackGraph* primary =
|
||||
MediaTrackGraph::GetInstance(MediaTrackGraph::SYSTEM_THREAD_DRIVER,
|
||||
MediaTrackGraph::GetInstance(MediaTrackGraph::AUDIO_THREAD_DRIVER,
|
||||
/*window*/ nullptr, aInputRate, nullptr);
|
||||
|
||||
/* Partner graph: Create the graph. */
|
||||
MediaTrackGraph* partner = MediaTrackGraph::GetInstance(
|
||||
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr, aOutputRate,
|
||||
/*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
|
||||
|
||||
RefPtr<AudioInputTrack> inputTrack;
|
||||
RefPtr<AudioInputProcessing> listener;
|
||||
auto primaryStarted = Invoke([&] {
|
||||
/* Primary graph: Create input track and open it */
|
||||
DispatchFunction([&] {
|
||||
inputTrack = AudioInputTrack::Create(primary);
|
||||
listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
|
||||
inputTrack->GraphImpl()->AppendMessage(
|
||||
|
@ -631,17 +691,18 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
|
|||
inputTrack->SetInputProcessing(listener);
|
||||
inputTrack->GraphImpl()->AppendMessage(
|
||||
MakeUnique<StartInputProcessing>(inputTrack, listener));
|
||||
inputTrack->OpenAudioInput((void*)1, listener);
|
||||
return primary->NotifyWhenDeviceStarted(inputTrack);
|
||||
});
|
||||
WaitFor(cubeb->StreamInitEvent());
|
||||
|
||||
RefPtr<SmartMockCubebStream> inputStream = WaitFor(cubeb->StreamInitEvent());
|
||||
/* Partner graph: Create graph and the CrossGraphReceiver. */
|
||||
MediaTrackGraph* partner = MediaTrackGraph::GetInstance(
|
||||
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr, aOutputRate,
|
||||
/*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
|
||||
|
||||
RefPtr<CrossGraphReceiver> receiver;
|
||||
RefPtr<CrossGraphTransmitter> transmitter;
|
||||
RefPtr<MediaInputPort> port;
|
||||
RefPtr<CrossGraphReceiver> receiver;
|
||||
auto partnerStarted = Invoke([&] {
|
||||
/* Partner graph: Create CrossGraphReceiver */
|
||||
DispatchFunction([&] {
|
||||
receiver = partner->CreateCrossGraphReceiver(primary->GraphRate());
|
||||
|
||||
/* Primary graph: Create CrossGraphTransmitter */
|
||||
|
@ -651,34 +712,30 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
|
|||
* Check in MediaManager how it is connected to AudioStreamTrack. */
|
||||
port = transmitter->AllocateInputPort(inputTrack);
|
||||
receiver->AddAudioOutput((void*)1);
|
||||
return partner->NotifyWhenDeviceStarted(receiver);
|
||||
|
||||
/* Primary graph: Open Audio Input through SourceMediaTrack */
|
||||
// Device id does not matter. Ignore.
|
||||
inputTrack->OpenAudioInput((void*)1, listener);
|
||||
});
|
||||
|
||||
MockCubebStream* inputStream = nullptr;
|
||||
MockCubebStream* partnerStream = nullptr;
|
||||
// Wait for the streams to be created.
|
||||
WaitUntil(cubeb->StreamInitEvent(), [&](MockCubebStream* aStream) {
|
||||
if (aStream->mHasInput) {
|
||||
inputStream = aStream;
|
||||
} else {
|
||||
partnerStream = aStream;
|
||||
}
|
||||
return inputStream && partnerStream;
|
||||
});
|
||||
|
||||
RefPtr<SmartMockCubebStream> partnerStream =
|
||||
WaitFor(cubeb->StreamInitEvent());
|
||||
partnerStream->SetDriftFactor(aDriftFactor);
|
||||
|
||||
cubeb->SetStreamStartFreezeEnabled(false);
|
||||
|
||||
// One source of non-determinism in this type of test is that inputStream
|
||||
// and partnerStream are started in sequence by the CubebOperation thread pool
|
||||
// (of size 1). To minimize the chance that the stream that starts first sees
|
||||
// an iteration before the other has started - this is a source of pre-silence
|
||||
// - we freeze both on start and thaw them together here.
|
||||
// Note that another source of non-determinism is the fallback driver. Handing
|
||||
// over from the fallback to the audio driver requires first an audio callback
|
||||
// (deterministic with the fake audio thread), then a fallback driver
|
||||
// iteration (non-deterministic, since each graph has its own fallback driver,
|
||||
// each with its own dedicated thread, which we have no control over). This
|
||||
// non-determinism is worrisome, but both fallback drivers are likely to
|
||||
// exhibit similar characteristics, hopefully keeping the level of
|
||||
// non-determinism down sufficiently for this test to pass.
|
||||
inputStream->Thaw();
|
||||
partnerStream->Thaw();
|
||||
|
||||
Unused << WaitFor(primaryStarted);
|
||||
Unused << WaitFor(partnerStarted);
|
||||
|
||||
// Wait for a second worth of audio data. GoFaster is dispatched through a
|
||||
// ControlMessage so that it is called in the first audio driver iteration.
|
||||
// Otherwise the audio driver might be going very fast while the fallback
|
||||
// system clock driver is still in an iteration.
|
||||
// Wait for 3s worth of audio data on the receiver stream.
|
||||
DispatchFunction([&] {
|
||||
inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
|
||||
|
@ -718,9 +775,7 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
|
|||
static_cast<uint32_t>(partnerRate * aDriftFactor / 1000 * aBufferMs);
|
||||
uint32_t margin = partnerRate / 20 /* +/- 50ms */;
|
||||
EXPECT_NEAR(preSilenceSamples, expectedPreSilence, margin);
|
||||
// The waveform from AudioGenerator starts at 0, but we don't control its
|
||||
// ending, so we expect a discontinuity there.
|
||||
EXPECT_LE(nrDiscontinuities, 1U);
|
||||
EXPECT_LE(nrDiscontinuities, 2U);
|
||||
}
|
||||
|
||||
TEST(TestAudioTrackGraph, CrossGraphPort)
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef WAITFOR_H_
|
||||
#define WAITFOR_H_
|
||||
|
||||
#include "MediaEventSource.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Result.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Waits for an occurrence of aEvent on the current thread (by blocking it,
|
||||
* except tasks added to the event loop may run) and returns the event's
|
||||
* templated value, if it's non-void.
|
||||
*
|
||||
* The caller must be wary of eventloop issues, in
|
||||
* particular cases where we rely on a stable state runnable, but there is never
|
||||
* a task to trigger stable state. In such cases it is the responsibility of the
|
||||
* caller to create the needed tasks, as JS would. A noteworthy API that relies
|
||||
* on stable state is MediaTrackGraph::GetInstance.
|
||||
*/
|
||||
template <typename T>
|
||||
T WaitFor(MediaEventSource<T>& aEvent) {
|
||||
Maybe<T> value;
|
||||
MediaEventListener listener = aEvent.Connect(
|
||||
AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return value.isSome(); });
|
||||
listener.Disconnect();
|
||||
return value.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of WaitFor<T> for void.
|
||||
*/
|
||||
void WaitFor(MediaEventSource<void>& aEvent) {
|
||||
bool done = false;
|
||||
MediaEventListener listener =
|
||||
aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return done; });
|
||||
listener.Disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of WaitFor that blocks the caller until a MozPromise has either been
|
||||
* resolved or rejected.
|
||||
*/
|
||||
template <typename R, typename E, bool Exc>
|
||||
Result<R, E> WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise) {
|
||||
Maybe<R> success;
|
||||
Maybe<E> error;
|
||||
aPromise->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[&](R aResult) { success = Some(aResult); },
|
||||
[&](E aError) { error = Some(aError); });
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return success.isSome() || error.isSome(); });
|
||||
if (success.isSome()) {
|
||||
return success.extract();
|
||||
}
|
||||
return Err(error.extract());
|
||||
}
|
||||
|
||||
/**
|
||||
* A variation of WaitFor that takes a callback to be called each time aEvent is
|
||||
* raised. Blocks the caller until the callback function returns true.
|
||||
*/
|
||||
template <typename T, typename CallbackFunction>
|
||||
void WaitUntil(MediaEventSource<T>& aEvent, const CallbackFunction& aF) {
|
||||
bool done = false;
|
||||
MediaEventListener listener =
|
||||
aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) {
|
||||
if (!done) {
|
||||
done = aF(aValue);
|
||||
}
|
||||
});
|
||||
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
||||
[&] { return done; });
|
||||
listener.Disconnect();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // WAITFOR_H_
|
Загрузка…
Ссылка в новой задаче