tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7ae22cf7c12da613f218c921f8956a22931ec0ca
parent 6bc5f33ade36f71c360721f04c77744d662ace1b
Author: Andreas Pehrson <apehrson@mozilla.com>
Date:   Tue, 11 Nov 2025 08:21:03 +0000

Bug 1931328 - Set up a test fixture and a trivial first gtest for DecodedStream. r=padenot

Differential Revision: https://phabricator.services.mozilla.com/D238751

Diffstat:
Mdom/media/MediaDecoderStateMachine.cpp | 6+++---
Adom/media/gtest/TestDecodedStream.cpp | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/gtest/moz.build | 1+
Mdom/media/mediasink/DecodedStream.cpp | 20+++++++++-----------
Mdom/media/mediasink/DecodedStream.h | 7+++----
5 files changed, 214 insertions(+), 18 deletions(-)

diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp @@ -3493,12 +3493,12 @@ void MediaDecoderStateMachine::AudioAudibleChanged(bool aAudible) { MediaSink* MediaDecoderStateMachine::CreateAudioSink() { if (mOutputCaptureState != MediaDecoder::OutputCaptureState::None) { DecodedStream* stream = new DecodedStream( - this, + OwnerThread(), mOutputCaptureState == MediaDecoder::OutputCaptureState::Capture ? mOutputDummyTrack.Ref() : nullptr, - mOutputTracks, mVolume, mPlaybackRate, mPreservesPitch, mAudioQueue, - mVideoQueue, mSinkDevice.Ref()); + mOutputTracks, CanonicalOutputPrincipal(), mVolume, mPlaybackRate, + mPreservesPitch, mAudioQueue, mVideoQueue); mAudibleListener.DisconnectIfExists(); mAudibleListener = stream->AudibleEvent().Connect( OwnerThread(), this, &MediaDecoderStateMachine::AudioAudibleChanged); diff --git a/dom/media/gtest/TestDecodedStream.cpp b/dom/media/gtest/TestDecodedStream.cpp @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#include "BlankDecoderModule.h" +#include "DecodedStream.h" +#include "MediaData.h" +#include "MediaQueue.h" +#include "MediaTrackGraphImpl.h" +#include "MediaTrackListener.h" +#include "MockCubeb.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/WaitFor.h" +#include "nsJSEnvironment.h" + +using mozilla::media::TimeUnit; +using testing::Test; + +namespace mozilla { +// Short-hand for DispatchToCurrentThread with a function. +#define DispatchFunction(f) \ + NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f)) + +enum MediaType { Audio = 1, Video = 2, AudioVideo = Audio | Video }; + +template <MediaType Type> +CopyableTArray<RefPtr<ProcessedMediaTrack>> CreateOutputTracks( + MediaTrackGraphImpl* aGraph) { + CopyableTArray<RefPtr<ProcessedMediaTrack>> outputTracks; + if constexpr (Type & Audio) { + outputTracks.AppendElement( + aGraph->CreateForwardedInputTrack(MediaSegment::AUDIO)); + } + if constexpr (Type & Video) { + outputTracks.AppendElement( + aGraph->CreateForwardedInputTrack(MediaSegment::VIDEO)); + } + return outputTracks; +} + +template <MediaType Type> +MediaInfo CreateMediaInfo() { + MediaInfo info; + info.mStartTime = TimeUnit::Zero(); + if constexpr (Type & Audio) { + info.EnableAudio(); + } + if constexpr (Type & Video) { + info.EnableVideo(); + } + return info; +} + +class OnFallbackListener : public MediaTrackListener { + const RefPtr<MediaTrack> mTrack; + Atomic<bool> mOnFallback{true}; + + public: + explicit OnFallbackListener(MediaTrack* aTrack) : mTrack(aTrack) {} + + void Reset() { mOnFallback = true; } + bool OnFallback() { return mOnFallback; } + + void NotifyOutput(MediaTrackGraph*, TrackTime) override { + if (auto* ad = + mTrack->GraphImpl()->CurrentDriver()->AsAudioCallbackDriver()) { + mOnFallback = ad->OnFallback(); + } + } +}; + +template <MediaType Type> +class TestDecodedStream : public Test { + public: + static constexpr TrackRate kRate = 48000; + static constexpr uint32_t kChannels = 2; + const RefPtr<MockCubeb> mMockCubeb; + RefPtr<SmartMockCubebStream> mMockCubebStream; + MediaQueue<AudioData> mAudioQueue; + MediaQueue<VideoData> mVideoQueue; + RefPtr<MediaTrackGraphImpl> mGraph; + nsMainThreadPtrHandle<SharedDummyTrack> mDummyTrack; + CopyableTArray<RefPtr<ProcessedMediaTrack>> mOutputTracks; + Canonical<PrincipalHandle> mCanonicalOutputPrincipal; + RefPtr<DecodedStream> mDecodedStream; + + TestDecodedStream() + : mMockCubeb(MakeRefPtr<MockCubeb>(MockCubeb::RunningMode::Manual)), + mGraph(MediaTrackGraphImpl::GetInstance( + MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, kRate, + nullptr, GetMainThreadSerialEventTarget())), + mDummyTrack(new nsMainThreadPtrHolder<SharedDummyTrack>( + __func__, new SharedDummyTrack( + mGraph->CreateSourceTrack(MediaSegment::AUDIO)))), + mOutputTracks(CreateOutputTracks<Type>(mGraph)), + mCanonicalOutputPrincipal( + AbstractThread::GetCurrent(), PRINCIPAL_HANDLE_NONE, + "TestDecodedStream::mCanonicalOutputPrincipal"), + mDecodedStream(MakeRefPtr<DecodedStream>( + AbstractThread::GetCurrent(), mDummyTrack, mOutputTracks, + &mCanonicalOutputPrincipal, /* aVolume = */ 1.0, + /* aPlaybackRate = */ 1.0, + /* aPreservesPitch = */ true, mAudioQueue, mVideoQueue)) { + MOZ_ASSERT(NS_IsMainThread()); + }; + + void SetUp() override { + MOZ_ASSERT(NS_IsMainThread()); + CubebUtils::ForceSetCubebContext(mMockCubeb->AsCubebContext()); + + for (const auto& track : mOutputTracks) { + track->QueueSetAutoend(false); + } + + // Resume the dummy track because a suspended audio track will not use an + // AudioCallbackDriver. + mDummyTrack->mTrack->Resume(); + + RefPtr fallbackListener = new OnFallbackListener(mDummyTrack->mTrack); + mDummyTrack->mTrack->AddListener(fallbackListener); + + mMockCubebStream = WaitFor(mMockCubeb->StreamInitEvent()); + while (mMockCubebStream->State().isNothing()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + ASSERT_EQ(*mMockCubebStream->State(), CUBEB_STATE_STARTED); + // Wait for the AudioCallbackDriver to come into effect. + while (fallbackListener->OnFallback()) { + ASSERT_EQ(mMockCubebStream->ManualDataCallback(1), + MockCubebStream::KeepProcessing::Yes); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + void TearDown() override { + MOZ_ASSERT(NS_IsMainThread()); + // Destroy all tracks so they're removed from the graph. + mDecodedStream->Shutdown(); + for (const auto& t : mOutputTracks) { + t->Destroy(); + } + mDummyTrack = nullptr; + // DecodedStream also has a ref to the dummy track. + mDecodedStream = nullptr; + + // Wait for the graph to shutdown. If all tracks are indeed removed, it will + // not switch to another driver. + MockCubebStream::KeepProcessing keepProcessing{}; + while ((keepProcessing = mMockCubebStream->ManualDataCallback(0)) == + MockCubebStream::KeepProcessing::Yes) { + NS_ProcessPendingEvents(nullptr); + } + ASSERT_EQ(keepProcessing, MockCubebStream::KeepProcessing::No); + + // Process the final track removal and run the stable state runnable. + NS_ProcessPendingEvents(nullptr); + // Process the shutdown runnable. + NS_ProcessPendingEvents(nullptr); + + // Graph should be shut down. + ASSERT_TRUE(mGraph->OnGraphThreadOrNotRunning()) + << "Not on graph thread so graph must still be running!"; + ASSERT_EQ(mGraph->LifecycleStateRef(), + MediaTrackGraphImpl::LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN) + << "The graph should be in its final state. Note it does not advance " + "the state any further on thread shutdown."; + CubebUtils::ForceSetCubebContext(nullptr); + + // mGraph should be the last or second last reference to the graph. The last + // reference may be the JS-based shutdown blocker, which will eventually be + // destroyed by CC and GC. + MediaTrackGraphImpl* graph{}; + mGraph.forget(&graph); + int32_t refcnt = static_cast<int32_t>(graph->Release()); + EXPECT_LE(refcnt, 1); + + // Attempt to release the last reference to the graph, to avoid its lifetime + // reaching into future tests. + nsJSContext::CycleCollectNow(CCReason::API); + nsJSContext::GarbageCollectNow(JS::GCReason::API); + NS_ProcessPendingEvents(nullptr); + } + + MediaInfo CreateMediaInfo() { return mozilla::CreateMediaInfo<Type>(); } +}; + +using TestDecodedStreamA = TestDecodedStream<Audio>; +using TestDecodedStreamV = TestDecodedStream<Video>; +using TestDecodedStreamAV = TestDecodedStream<AudioVideo>; + +TEST_F(TestDecodedStreamAV, StartStop) { + mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); + mDecodedStream->SetPlaying(true); + mDecodedStream->Stop(); +} +} // namespace mozilla diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build @@ -40,6 +40,7 @@ UNIFIED_SOURCES += [ "TestBufferReader.cpp", "TestCubebInputStream.cpp", "TestDataMutex.cpp", + "TestDecodedStream.cpp", "TestDeviceInputTrack.cpp", "TestDriftCompensation.cpp", "TestGMPUtils.cpp", diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp @@ -7,20 +7,17 @@ #include "DecodedStream.h" #include "AudioDecoderInputTrack.h" -#include "AudioSegment.h" #include "MediaData.h" #include "MediaDecoderStateMachine.h" #include "MediaQueue.h" #include "MediaTrackGraph.h" #include "MediaTrackListener.h" -#include "SharedBuffer.h" #include "Tracing.h" #include "VideoSegment.h" #include "VideoUtils.h" #include "mozilla/AbstractThread.h" #include "mozilla/CheckedInt.h" #include "mozilla/ProfilerLabels.h" -#include "mozilla/ProfilerMarkerTypes.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/SyncRunnable.h" #include "mozilla/gfx/Point.h" @@ -470,19 +467,20 @@ void DecodedStreamData::GetDebugInfo(dom::DecodedStreamDataDebugInfo& aInfo) { } DecodedStream::DecodedStream( - MediaDecoderStateMachine* aStateMachine, + AbstractThread* aOwnerThread, nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, - CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, double aVolume, - double aPlaybackRate, bool aPreservesPitch, - MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue, - RefPtr<AudioDeviceInfo> aAudioDevice) - : mOwnerThread(aStateMachine->OwnerThread()), + CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, + AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, + double aVolume, double aPlaybackRate, bool aPreservesPitch, + MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue) + : mOwnerThread(aOwnerThread), mDummyTrack(std::move(aDummyTrack)), + mWatchManager(this, mOwnerThread), mPlaying(false, "DecodedStream::mPlaying"), - mPrincipalHandle(aStateMachine->OwnerThread(), PRINCIPAL_HANDLE_NONE, + mPrincipalHandle(aOwnerThread, PRINCIPAL_HANDLE_NONE, "DecodedStream::mPrincipalHandle (Mirror)"), - mCanonicalOutputPrincipal(aStateMachine->CanonicalOutputPrincipal()), + mCanonicalOutputPrincipal(aCanonicalOutputPrincipal), mOutputTracks(std::move(aOutputTracks)), mVolume(aVolume), mPlaybackRate(aPlaybackRate), diff --git a/dom/media/mediasink/DecodedStream.h b/dom/media/mediasink/DecodedStream.h @@ -10,7 +10,6 @@ #include "AudibilityMonitor.h" #include "MediaEventSource.h" #include "MediaInfo.h" -#include "MediaSegment.h" #include "MediaSink.h" #include "mozilla/AbstractThread.h" #include "mozilla/AwakeTimeStamp.h" @@ -36,13 +35,13 @@ class MediaQueue; class DecodedStream : public MediaSink { public: - DecodedStream(MediaDecoderStateMachine* aStateMachine, + DecodedStream(AbstractThread* aOwnerThread, nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, + AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, double aVolume, double aPlaybackRate, bool aPreservesPitch, MediaQueue<AudioData>& aAudioQueue, - MediaQueue<VideoData>& aVideoQueue, - RefPtr<AudioDeviceInfo> aAudioDevice); + MediaQueue<VideoData>& aVideoQueue); RefPtr<EndedPromise> OnEnded(TrackType aType) override; media::TimeUnit GetEndTime(TrackType aType) const override;