commit 1a970ccb483397c434bd9f2a3d566d72b2444c82
parent 7ae22cf7c12da613f218c921f8956a22931ec0ca
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:
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 {