tor-browser

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

commit af6f375966b447cbc873ef90cf76f9e0b82657ff
parent a2d9f68284683b441d0d0e21a9ae5d02b0edbd81
Author: Andreas Pehrson <apehrson@mozilla.com>
Date:   Tue, 11 Nov 2025 08:21:04 +0000

Bug 1931328 - Allow for deterministic output time testing of DecodedStream. r=padenot

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

Diffstat:
Mdom/media/gtest/TestDecodedStream.cpp | 46++++++++++++++++++++++++++++++++++++++++++++--
Mdom/media/mediasink/AudioDecoderInputTrack.cpp | 2+-
Mdom/media/mediasink/AudioDecoderInputTrack.h | 8+++++---
Mdom/media/mediasink/DecodedStream.cpp | 59+++++++++++++++++++++++++++++++++++++++--------------------
Mdom/media/mediasink/DecodedStream.h | 8+++++++-
5 files changed, 96 insertions(+), 27 deletions(-)

diff --git a/dom/media/gtest/TestDecodedStream.cpp b/dom/media/gtest/TestDecodedStream.cpp @@ -71,6 +71,24 @@ class OnFallbackListener : public MediaTrackListener { } }; +class TestableDecodedStream : public DecodedStream { + public: + TestableDecodedStream( + AbstractThread* aOwnerThread, + nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, + CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, + AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, + double aVolume, double aPlaybackRate, bool aPreservesPitch, + MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue) + : DecodedStream(aOwnerThread, std::move(aDummyTrack), + std::move(aOutputTracks), aCanonicalOutputPrincipal, + aVolume, aPlaybackRate, aPreservesPitch, aAudioQueue, + aVideoQueue) {} + + using DecodedStream::GetPositionImpl; + using DecodedStream::LastOutputSystemTime; +}; + template <MediaType Type> class TestDecodedStream : public Test { public: @@ -84,7 +102,7 @@ class TestDecodedStream : public Test { nsMainThreadPtrHandle<SharedDummyTrack> mDummyTrack; CopyableTArray<RefPtr<ProcessedMediaTrack>> mOutputTracks; Canonical<PrincipalHandle> mCanonicalOutputPrincipal; - RefPtr<DecodedStream> mDecodedStream; + RefPtr<TestableDecodedStream> mDecodedStream; TestDecodedStream() : mMockCubeb(MakeRefPtr<MockCubeb>(MockCubeb::RunningMode::Manual)), @@ -98,7 +116,7 @@ class TestDecodedStream : public Test { mCanonicalOutputPrincipal( AbstractThread::GetCurrent(), PRINCIPAL_HANDLE_NONE, "TestDecodedStream::mCanonicalOutputPrincipal"), - mDecodedStream(MakeRefPtr<DecodedStream>( + mDecodedStream(MakeRefPtr<TestableDecodedStream>( AbstractThread::GetCurrent(), mDummyTrack, mOutputTracks, &mCanonicalOutputPrincipal, /* aVolume = */ 1.0, /* aPlaybackRate = */ 1.0, @@ -195,4 +213,28 @@ TEST_F(TestDecodedStreamAV, StartStop) { mDecodedStream->SetPlaying(true); mDecodedStream->Stop(); } + +TEST_F(TestDecodedStreamA, LastOutputSystemTime) { + auto start = AwakeTimeStamp::Now(); + BlankAudioDataCreator creator(2, kRate); + auto raw = MakeRefPtr<MediaRawData>(); + raw->mDuration = TimeUnit(kRate, kRate); + mAudioQueue.Push(RefPtr(creator.Create(raw))->As<AudioData>()); + + mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); + mDecodedStream->SetPlaying(true); + NS_ProcessPendingEvents(nullptr); + mMockCubebStream->ManualDataCallback(0); + + auto before = AwakeTimeStamp::Now(); + // This runs the events on the graph thread, sampling the system clock. + mMockCubebStream->ManualDataCallback(512); + auto after = AwakeTimeStamp::Now(); + // This runs the event handlers on the MDSM thread, updating the timestamps. + NS_ProcessPendingEvents(nullptr); + EXPECT_GE(mDecodedStream->LastOutputSystemTime() - start, before - start); + EXPECT_LE(mDecodedStream->LastOutputSystemTime() - start, after - start); + + mDecodedStream->Stop(); +} } // namespace mozilla diff --git a/dom/media/mediasink/AudioDecoderInputTrack.cpp b/dom/media/mediasink/AudioDecoderInputTrack.cpp @@ -597,7 +597,7 @@ void AudioDecoderInputTrack::NotifyInTheEndOfProcessInput( LOG("Notify, fill=%" PRId64 ", total written=%" PRId64 ", ended=%d", aFillDuration, mWrittenFrames, Ended()); if (aFillDuration > 0) { - mOnOutput.Notify(mWrittenFrames, AwakeTimeStamp::Now()); + mOnOutput.Notify(mWrittenFrames, TimeStamp::Now(), AwakeTimeStamp::Now()); } if (Ended()) { mOnEnd.Notify(); diff --git a/dom/media/mediasink/AudioDecoderInputTrack.h b/dom/media/mediasink/AudioDecoderInputTrack.h @@ -100,7 +100,9 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { void Close(); bool HasBatchedData() const; - MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput() { return mOnOutput; } + MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput() { + return mOnOutput; + } MediaEventSource<void>& OnEnd() { return mOnEnd; } // Graph Thread API @@ -176,8 +178,8 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { const RefPtr<nsISerialEventTarget> mDecoderThread; // Notify the amount of audio frames which have been sent to the track, - // sampled by the system time they were sent. - MediaEventProducer<int64_t, AwakeTimeStamp> mOnOutput; + // sampled by the awake system time (and non-awake, for now) they were sent. + MediaEventProducer<int64_t, TimeStamp, AwakeTimeStamp> mOnOutput; // Notify when the track is ended. MediaEventProducer<void> mOnEnd; diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp @@ -102,8 +102,10 @@ class DecodedStreamGraphListener { if (mAudioTrack) { mOnAudioOutput = mAudioTrack->OnOutput().Connect( mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]( - TrackTime aTime, AwakeTimeStamp aSystemTime) { - self->NotifyOutput(MediaSegment::AUDIO, aTime, aSystemTime); + TrackTime aTime, TimeStamp aSystemTime, + AwakeTimeStamp aAwakeSystemTime) { + self->NotifyOutput(MediaSegment::AUDIO, aTime, aSystemTime, + aAwakeSystemTime); }); mOnAudioEnd = mAudioTrack->OnEnd().Connect( mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]() { @@ -145,7 +147,7 @@ class DecodedStreamGraphListener { } void NotifyOutput(MediaSegment::Type aType, TrackTime aCurrentTrackTime, - AwakeTimeStamp aCurrentSystemTime) { + TimeStamp aSystemTime, AwakeTimeStamp aAwakeSystemTime) { AssertOnDecoderThread(); if (aType == MediaSegment::AUDIO) { mAudioOutputFrames = aCurrentTrackTime; @@ -180,7 +182,7 @@ class DecodedStreamGraphListener { ? static_cast<MediaTrack*>(mVideoTrack) : static_cast<MediaTrack*>(mAudioTrack); mOnOutput.Notify(track->TrackTimeToMicroseconds(aCurrentTrackTime), - aCurrentSystemTime); + aSystemTime, aAwakeSystemTime); } void NotifyEnded(MediaSegment::Type aType) { @@ -236,7 +238,9 @@ class DecodedStreamGraphListener { return mAudioOutputFrames; } - MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput() { return mOnOutput; } + MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput() { + return mOnOutput; + } private: ~DecodedStreamGraphListener() { @@ -251,7 +255,7 @@ class DecodedStreamGraphListener { const RefPtr<nsISerialEventTarget> mDecoderThread; // Accessible on any thread, but only notify on the decoder thread. - MediaEventProducer<int64_t, AwakeTimeStamp> mOnOutput; + MediaEventProducer<int64_t, TimeStamp, AwakeTimeStamp> mOnOutput; RefPtr<SourceVideoTrackListener> mVideoTrackListener; @@ -299,9 +303,11 @@ void SourceVideoTrackListener::NotifyOutput(MediaTrackGraph* aGraph, mDecoderThread->Dispatch(NS_NewRunnableFunction( "SourceVideoTrackListener::NotifyOutput", [self = RefPtr<SourceVideoTrackListener>(this), aCurrentTrackTime, - currentSystemTime = AwakeTimeStamp::Now()]() { - self->mGraphListener->NotifyOutput( - MediaSegment::VIDEO, aCurrentTrackTime, currentSystemTime); + systemTime = TimeStamp::Now(), + awakeSystemTime = AwakeTimeStamp::Now()]() { + self->mGraphListener->NotifyOutput(MediaSegment::VIDEO, + aCurrentTrackTime, systemTime, + awakeSystemTime); })); } @@ -333,7 +339,7 @@ class DecodedStreamData final { float aPlaybackRate, float aVolume, bool aPreservesPitch, nsISerialEventTarget* aDecoderThread); ~DecodedStreamData(); - MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput(); + MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput(); // This is used to mark track as closed and should be called before Forget(). // Decoder thread only. void Close(); @@ -443,7 +449,8 @@ DecodedStreamData::~DecodedStreamData() { } } -MediaEventSource<int64_t, AwakeTimeStamp>& DecodedStreamData::OnOutput() { +MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& +DecodedStreamData::OnOutput() { return mListener->OnOutput(); } @@ -1104,18 +1111,23 @@ TimeUnit DecodedStream::GetEndTime(TrackType aType) const { TimeUnit DecodedStream::GetPosition(TimeStamp* aTimeStamp) { AssertOwnerThread(); TRACE("DecodedStream::GetPosition"); + return GetPositionImpl(TimeStamp::Now(), AwakeTimeStamp::Now(), aTimeStamp); +} + +TimeUnit DecodedStream::GetPositionImpl(TimeStamp aNow, + AwakeTimeStamp aAwakeNow, + TimeStamp* aTimeStamp) { + AssertOwnerThread(); // This is only called after MDSM starts playback. So mStartTime is // guaranteed to be something. MOZ_ASSERT(mStartTime.isSome()); - TimeStamp now = TimeStamp::Now(); - AwakeTimeStamp awakeNow = AwakeTimeStamp::Now(); if (aTimeStamp) { - *aTimeStamp = now; + *aTimeStamp = aNow; } AwakeTimeDuration timeSinceLastOutput; if (mLastOutputSystemTime) { - MOZ_ASSERT(awakeNow >= *mLastOutputSystemTime); - timeSinceLastOutput = awakeNow - *mLastOutputSystemTime; + MOZ_ASSERT(aAwakeNow >= *mLastOutputSystemTime); + timeSinceLastOutput = aAwakeNow - *mLastOutputSystemTime; } TimeUnit position = mStartTime.ref() + mLastOutputTime + TimeUnit::FromSeconds(timeSinceLastOutput.ToSeconds()); @@ -1128,7 +1140,13 @@ TimeUnit DecodedStream::GetPosition(TimeStamp* aTimeStamp) { return position; } -void DecodedStream::NotifyOutput(int64_t aTime, AwakeTimeStamp aSystemTime) { +AwakeTimeStamp DecodedStream::LastOutputSystemTime() const { + AssertOwnerThread(); + return *mLastOutputSystemTime; +} + +void DecodedStream::NotifyOutput(int64_t aTime, TimeStamp aSystemTime, + AwakeTimeStamp aAwakeSystemTime) { AssertOwnerThread(); TimeUnit time = TimeUnit::FromMicroseconds(aTime); if (time == mLastOutputTime) { @@ -1136,9 +1154,10 @@ void DecodedStream::NotifyOutput(int64_t aTime, AwakeTimeStamp aSystemTime) { } MOZ_ASSERT(mLastOutputTime < time); mLastOutputTime = time; - MOZ_ASSERT_IF(mLastOutputSystemTime, *mLastOutputSystemTime < aSystemTime); - mLastOutputSystemTime = Some(aSystemTime); - auto currentTime = GetPosition(); + MOZ_ASSERT_IF(mLastOutputSystemTime, + *mLastOutputSystemTime < aAwakeSystemTime); + mLastOutputSystemTime = Some(aAwakeSystemTime); + auto currentTime = GetPositionImpl(aSystemTime, aAwakeSystemTime); if (profiler_thread_is_being_profiled_for_markers()) { nsPrintfCString markerString("OutputTime=%" PRId64, diff --git a/dom/media/mediasink/DecodedStream.h b/dom/media/mediasink/DecodedStream.h @@ -78,6 +78,11 @@ class DecodedStream : public MediaSink { protected: virtual ~DecodedStream(); + // A bit many clocks to sample, but what do you do... + media::TimeUnit GetPositionImpl(TimeStamp aNow, AwakeTimeStamp aAwakeNow, + TimeStamp* aTimeStamp = nullptr); + AwakeTimeStamp LastOutputSystemTime() const; + private: void DestroyData(UniquePtr<DecodedStreamData>&& aData); void SendAudio(const PrincipalHandle& aPrincipalHandle); @@ -85,7 +90,8 @@ class DecodedStream : public MediaSink { void ResetAudio(); void ResetVideo(const PrincipalHandle& aPrincipalHandle); void SendData(); - void NotifyOutput(int64_t aTime, AwakeTimeStamp aSystemTime); + void NotifyOutput(int64_t aTime, TimeStamp aSystemTime, + AwakeTimeStamp aAwakeSystemTime); void CheckIsDataAudible(const AudioData* aData); void AssertOwnerThread() const {