diff --git a/dom/media/AudioBufferUtils.h b/dom/media/AudioBufferUtils.h index bb50fe37282b..573c8729a4f5 100644 --- a/dom/media/AudioBufferUtils.h +++ b/dom/media/AudioBufferUtils.h @@ -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; diff --git a/dom/media/CrossGraphPort.cpp b/dom/media/CrossGraphPort.cpp index c4cb4d764563..44a712787a7a 100644 --- a/dom/media/CrossGraphPort.cpp +++ b/dom/media/CrossGraphPort.cpp @@ -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::Connect( const RefPtr& 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; } diff --git a/dom/media/DynamicResampler.cpp b/dom/media/DynamicResampler.cpp index b823597a717c..43405cf1d77f 100644 --- a/dom/media/DynamicResampler.cpp +++ b/dom/media/DynamicResampler.cpp @@ -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 diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index c3c792a9d974..55cafa2aeb42 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -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 || diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h index 1f67d723ae5b..f057b3eca50c 100644 --- a/dom/media/GraphDriver.h +++ b/dom/media/GraphDriver.h @@ -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. diff --git a/dom/media/GraphRunner.cpp b/dom/media/GraphRunner.cpp index be884aa6ab3a..e96a6bcd81bd 100644 --- a/dom/media/GraphRunner.cpp +++ b/dom/media/GraphRunner.cpp @@ -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; } diff --git a/dom/media/GraphRunner.h b/dom/media/GraphRunner.h index ae24d7d282e7..ade8bbd948c6 100644 --- a/dom/media/GraphRunner.h +++ b/dom/media/GraphRunner.h @@ -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. diff --git a/dom/media/MediaTrackGraph.cpp b/dom/media/MediaTrackGraph.cpp index dac482d98343..346c80b66463 100644 --- a/dom/media/MediaTrackGraph.cpp +++ b/dom/media/MediaTrackGraph.cpp @@ -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", diff --git a/dom/media/MediaTrackGraphImpl.h b/dom/media/MediaTrackGraphImpl.h index dc1f4bc3fd5f..1988ffe1145a 100644 --- a/dom/media/MediaTrackGraphImpl.h +++ b/dom/media/MediaTrackGraphImpl.h @@ -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 diff --git a/dom/media/gtest/MockCubeb.cpp b/dom/media/gtest/MockCubeb.cpp index 88a226dcb89f..36f1793cf11c 100644 --- a/dom/media/gtest/MockCubeb.cpp +++ b/dom/media/gtest/MockCubeb.cpp @@ -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(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(context)->StartStream(this); + cubeb_stream* stream = reinterpret_cast(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(context)->StopStream(this); if (rv == CUBEB_OK) { - mStateCallback(AsCubebStream(), mUserPtr, CUBEB_STATE_STOPPED); + cubeb_stream* stream = reinterpret_cast(this); + mStateCallback(stream, mUserPtr, CUBEB_STATE_STOPPED); } return rv; } -cubeb_stream* MockCubebStream::AsCubebStream() { - return reinterpret_cast(this); -} - -MockCubebStream* MockCubebStream::AsMock(cubeb_stream* aStream) { - return reinterpret_cast(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& 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(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(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(this); } -MockCubeb* MockCubeb::AsMock(cubeb* aContext) { - return reinterpret_cast(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 { - RefPtr 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( + MockCubebStream* mockStream = new MockCubebStream( aContext, aInputDevice, aInputStreamParams, aOutputDevice, - aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr, - mStreamStartFreezeEnabled); - *aStream = mockStream->AsCubebStream(); + aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr); + *aStream = reinterpret_cast(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 mockStream = - dont_AddRef(MockCubebStream::AsMock(aStream)->mSelf); + MockCubebStream* mockStream = reinterpret_cast(aStream); + delete mockStream; } void MockCubeb::GoFaster() { mFastMode = true; } void MockCubeb::DontGoFaster() { mFastMode = false; } -MediaEventSource>& MockCubeb::StreamInitEvent() { +MediaEventSource& MockCubeb::StreamInitEvent() { return mStreamInitEvent; } @@ -376,13 +322,8 @@ MediaEventSource& 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 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(this), - __func__); - } while (true) { { auto streams = mLiveStreams.Lock(); for (auto& stream : *streams) { stream->Process10Ms(); } - if (streams->IsEmpty() && !mForcedAudioThread) { + if (streams->IsEmpty()) { break; } } diff --git a/dom/media/gtest/MockCubeb.h b/dom/media/gtest/MockCubeb.h index 7afdf4d4dc7c..c225bb0d926f 100644 --- a/dom/media/gtest/MockCubeb.h +++ b/dom/media/gtest/MockCubeb.h @@ -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 #include #include @@ -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& FramesProcessedEvent(); MediaEventSource& 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 mErrorForcedEvent; }; -class SmartMockCubebStream - : public MockCubebStream, - public SupportsThreadSafeWeakPtr { - 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, nsresult, false>; - RefPtr 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>& StreamInitEvent(); + MediaEventSource& StreamInitEvent(); MediaEventSource& 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 mStreamStartFreezeEnabled{false}; - // Whether the audio thread is forced, i.e., whether it remains active even - // with no live streams. - Atomic mForcedAudioThread{false}; - MozPromiseHolder mForcedAudioThreadPromise; // Our input and output devices. nsTArray mInputDevices; nsTArray mOutputDevices; // The streams that are currently running. - DataMutex>> mLiveStreams{ - "MockCubeb::mLiveStreams"}; + DataMutex> 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 mFastMode{false}; - MediaEventProducer> mStreamInitEvent; + MediaEventProducer mStreamInitEvent; MediaEventProducer mStreamDestroyEvent; }; -void cubeb_mock_destroy(cubeb* context) { delete MockCubeb::AsMock(context); } +void cubeb_mock_destroy(cubeb* context) { + delete reinterpret_cast(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(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(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(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(stream); + return mockStream->Start(); } int cubeb_mock_stream_stop(cubeb_stream* stream) { - return MockCubebStream::AsMock(stream)->Stop(); + MockCubebStream* mockStream = reinterpret_cast(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(stream); + MockCubeb* mock = reinterpret_cast(mockStream->context); return mock->StreamDestroy(stream); } diff --git a/dom/media/gtest/TestAudioCallbackDriver.cpp b/dom/media/gtest/TestAudioCallbackDriver.cpp index 981d3aca5d8e..db6f939ecb2e 100644 --- a/dom/media/gtest/TestAudioCallbackDriver.cpp +++ b/dom/media/gtest/TestAudioCallbackDriver.cpp @@ -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& FramesIteratedEvent() { - return mFramesIteratedEvent; - } protected: - Atomic mIterationCount{0}; - Atomic mStateComputedTime{0}; - Atomic mCurrentDriver{nullptr}; - Atomic mEnsureNextIteration{false}; Atomic mKeepProcessing{true}; Atomic mNextDriver{nullptr}; - MediaEventProducer 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 driver; - auto graph = MakeRefPtr>(rate); + auto graph = MakeRefPtr>(); EXPECT_CALL(*graph, NotifyInputStopped).Times(0); ON_CALL(*graph, NotifyOutputData) .WillByDefault([&](AudioDataValue*, size_t, TrackRate, uint32_t) {}); - driver = MakeRefPtr(graph, nullptr, rate, 2, 0, nullptr, + driver = MakeRefPtr(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 driver; - auto graph = MakeRefPtr>(aRate); - EXPECT_CALL(*graph, NotifyInputStopped).Times(0); - - Maybe audioStart; - Maybe 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(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(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 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(WEBAUDIO_BLOCK_SIZE, tenMillisMinIteration); - EXPECT_GE(aFrames, minIteration) - << "Fallback driver iteration >= 10ms, modulo an audio block"; - EXPECT_LT(aFrames, static_cast(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 -} diff --git a/dom/media/gtest/TestAudioTrackGraph.cpp b/dom/media/gtest/TestAudioTrackGraph.cpp index 874893e18fee..6cfb1b2cde7e 100644 --- a/dom/media/gtest/TestAudioTrackGraph.cpp +++ b/dom/media/gtest/TestAudioTrackGraph.cpp @@ -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 +T WaitFor(MediaEventSource& aEvent) { + Maybe value; + MediaEventListener listener = aEvent.Connect( + AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); }); + SpinEventLoopUntil( + [&] { return value.isSome(); }); + listener.Disconnect(); + return value.value(); +} + +/** + * Specialization of WaitFor for void. + */ +void WaitFor(MediaEventSource& aEvent) { + bool done = false; + MediaEventListener listener = + aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; }); + SpinEventLoopUntil( + [&] { return done; }); + listener.Disconnect(); +} + +/** + * Variant of WaitFor that blocks the caller until a MozPromise has either been + * resolved or rejected. + */ +template +Result WaitFor(const RefPtr>& aPromise) { + Maybe> result; + aPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](R aResult) { result = Some(Result(aResult)); }, + [&](E aError) { result = Some(Result(aError)); }); + SpinEventLoopUntil( + [&] { 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 +void WaitUntil(MediaEventSource& aEvent, const CallbackFunction& aF) { + bool done = false; + MediaEventListener listener = + aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) { + if (!done) { + done = aF(aValue); + } + }); + SpinEventLoopUntil( + [&] { 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 stream = WaitFor(cubeb->StreamInitEvent()); + MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_EQ(stream->GetOutputDeviceID(), reinterpret_cast(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 inputTrack; RefPtr 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(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 stream = WaitFor(cubeb->StreamInitEvent()); + MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent()); Result 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 inputTrack; RefPtr outputTrack; RefPtr port; RefPtr 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(inputTrack, listener)); + return graph->NotifyWhenDeviceStarted(inputTrack); + })); + + DispatchFunction([&] { // Device id does not matter. Ignore. inputTrack->OpenAudioInput((void*)1, listener); - return graph->NotifyWhenDeviceStarted(inputTrack); }); - RefPtr 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 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 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(1)); - RefPtr inputTrack; RefPtr 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(inputTrack, listener)); - inputTrack->OpenAudioInput((void*)1, listener); - return primary->NotifyWhenDeviceStarted(inputTrack); }); + WaitFor(cubeb->StreamInitEvent()); - RefPtr 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(1)); + RefPtr receiver; RefPtr transmitter; RefPtr port; - RefPtr 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 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(cubeb)); @@ -718,9 +775,7 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate, static_cast(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) diff --git a/dom/media/gtest/WaitFor.h b/dom/media/gtest/WaitFor.h deleted file mode 100644 index 60ede9b1cf95..000000000000 --- a/dom/media/gtest/WaitFor.h +++ /dev/null @@ -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 -T WaitFor(MediaEventSource& aEvent) { - Maybe value; - MediaEventListener listener = aEvent.Connect( - AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); }); - SpinEventLoopUntil( - [&] { return value.isSome(); }); - listener.Disconnect(); - return value.value(); -} - -/** - * Specialization of WaitFor for void. - */ -void WaitFor(MediaEventSource& aEvent) { - bool done = false; - MediaEventListener listener = - aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; }); - SpinEventLoopUntil( - [&] { return done; }); - listener.Disconnect(); -} - -/** - * Variant of WaitFor that blocks the caller until a MozPromise has either been - * resolved or rejected. - */ -template -Result WaitFor(const RefPtr>& aPromise) { - Maybe success; - Maybe error; - aPromise->Then( - GetCurrentSerialEventTarget(), __func__, - [&](R aResult) { success = Some(aResult); }, - [&](E aError) { error = Some(aError); }); - SpinEventLoopUntil( - [&] { 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 -void WaitUntil(MediaEventSource& aEvent, const CallbackFunction& aF) { - bool done = false; - MediaEventListener listener = - aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) { - if (!done) { - done = aF(aValue); - } - }); - SpinEventLoopUntil( - [&] { return done; }); - listener.Disconnect(); -} - -} // namespace mozilla - -#endif // WAITFOR_H_