commit e8963b5923b52555159bd08c0a24eb82b9cbb9df
parent 8e58b5d090c09347be8dddf06e7ac411998d467d
Author: Karl Tomlinson <karlt+@karlt.net>
Date: Mon, 27 Oct 2025 01:06:22 +0000
Bug 1985195 Advance OfflineClockDriver time only when rendering r=pehrsons
Previously the driver would advance its time, but the graph would not, which
sometimes led to a large rendering batch the first iteration that the graph
rendered.
Differential Revision: https://phabricator.services.mozilla.com/D268334
Diffstat:
7 files changed, 86 insertions(+), 25 deletions(-)
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
@@ -275,8 +275,9 @@ void OfflineClockDriver::RunThread() {
}
MediaTime OfflineClockDriver::GetIntervalForIteration() {
- return MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
- MillisecondsToMediaTime(MEDIA_GRAPH_TARGET_PERIOD_MS));
+ return MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(std::clamp<MediaTime>(
+ mEndTime - mStateComputedTime, 0,
+ MillisecondsToMediaTime(MEDIA_GRAPH_TARGET_PERIOD_MS)));
}
/* Helper to proxy the GraphInterface methods used by a running
diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h
@@ -520,9 +520,19 @@ class OfflineClockDriver final : public ThreadedDriver {
void RunThread() override;
+ void SetTickCountToRender(uint32_t aTicksToProcess) {
+ MOZ_ASSERT(InIteration());
+ MOZ_ASSERT(mEndTime == 0);
+ mEndTime = aTicksToProcess;
+ }
+
protected:
TimeDuration NextIterationWaitDuration() override { return TimeDuration(); }
MediaTime GetIntervalForIteration() override;
+
+ private:
+ // The graph will advance up to this time. Graph thread.
+ GraphTime mEndTime = 0;
};
enum class AudioInputType { Unknown, Voice };
diff --git a/dom/media/MediaTrackGraph.cpp b/dom/media/MediaTrackGraph.cpp
@@ -1678,14 +1678,13 @@ auto MediaTrackGraphImpl::OneIterationImpl(
NS_ProcessPendingEvents(nullptr);
}
- GraphTime stateTime = std::min(aStateTime, GraphTime(mEndTime));
- UpdateGraph(stateTime);
+ UpdateGraph(aStateTime);
- mStateComputedTime = stateTime;
+ mStateComputedTime = aStateTime;
GraphTime oldProcessedTime = mProcessedTime;
Process(aMixerReceiver);
- MOZ_ASSERT(mProcessedTime == stateTime);
+ MOZ_ASSERT(mProcessedTime == aStateTime);
UpdateCurrentTimeForTracks(oldProcessedTime);
@@ -4124,25 +4123,17 @@ void MediaTrackGraph::StartNonRealtimeProcessing(uint32_t aTicksToProcess) {
MediaTrackGraphImpl* graph = static_cast<MediaTrackGraphImpl*>(this);
NS_ASSERTION(!graph->mRealtime, "non-realtime only");
- class Message : public ControlMessage {
- public:
- explicit Message(MediaTrackGraphImpl* aGraph, uint32_t aTicksToProcess)
- : ControlMessage(nullptr),
- mGraph(aGraph),
- mTicksToProcess(aTicksToProcess) {}
- void Run() override {
- TRACE("MTG::StartNonRealtimeProcessing ControlMessage");
- MOZ_ASSERT(mGraph->mEndTime == 0,
- "StartNonRealtimeProcessing should be called only once");
- mGraph->mEndTime = mGraph->RoundUpToEndOfAudioBlock(
- mGraph->mStateComputedTime + mTicksToProcess);
- }
- // The graph owns this message.
- MediaTrackGraphImpl* MOZ_NON_OWNING_REF mGraph;
- uint32_t mTicksToProcess;
- };
-
- graph->AppendMessage(MakeUnique<Message>(graph, aTicksToProcess));
+ graph->QueueControlMessageWithNoShutdown([graph = RefPtr{graph},
+ aTicksToProcess]() {
+ TRACE("MTG::StartNonRealtimeProcessing ControlMessage");
+ MOZ_ASSERT(graph->mStateComputedTime == 0);
+ MOZ_ASSERT(graph->mEndTime == 0,
+ "StartNonRealtimeProcessing should be called only once");
+ graph->mEndTime = aTicksToProcess;
+ OfflineClockDriver* driver = graph->CurrentDriver()->AsOfflineClockDriver();
+ MOZ_ASSERT(driver);
+ driver->SetTickCountToRender(aTicksToProcess);
+ });
}
void MediaTrackGraphImpl::InterruptJS() {
diff --git a/dom/media/gtest/MockGraphInterface.h b/dom/media/gtest/MockGraphInterface.h
@@ -27,8 +27,10 @@ class MockGraphInterface : public GraphInterface {
#endif
/* OneIteration cannot be mocked because IterationResult is non-memmovable and
* cannot be passed as a parameter, which GMock does internally. */
+ MOCK_METHOD(void, MockIteration, (GraphTime aStateComputedTime), ());
IterationResult OneIteration(GraphTime aStateComputedTime,
MixerCallbackReceiver* aMixerReceiver) {
+ MockIteration(aStateComputedTime);
GraphDriver* driver = mCurrentDriver;
if (aMixerReceiver) {
mMixer.StartMixing();
diff --git a/dom/media/gtest/TestMediaDataEncoder.cpp b/dom/media/gtest/TestMediaDataEncoder.cpp
@@ -1160,5 +1160,6 @@ TEST_F(MediaDataEncoderTest, VP9EncodeWithScalabilityModeL1T3) {
# endif
#endif
+#undef BLOCK_SIZE
#undef GET_OR_RETURN_ON_ERROR
#undef RUN_IF_SUPPORTED
diff --git a/dom/media/gtest/TestOfflineClockDriver.cpp b/dom/media/gtest/TestOfflineClockDriver.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#include "MockGraphInterface.h"
+#include "gtest/gtest.h"
+#include "mozilla/gtest/WaitFor.h"
+
+using namespace mozilla;
+using testing::InSequence;
+using testing::Return;
+
+TEST(TestOfflineClockDriver, TimeAdvance)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ TrackRate rate =
+ CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false);
+ RefPtr graph = new MockGraphInterface(rate);
+ RefPtr driver = new OfflineClockDriver(graph, rate);
+ MozPromiseHolder<GenericPromise> doneHolder;
+ RefPtr<GenericPromise> done = doneHolder.Ensure(__func__);
+ {
+ GraphTime length = WEBAUDIO_BLOCK_SIZE / 4; // < WEBAUDIO_BLOCK_SIZE
+ auto EnsureNextIteration = [&](GraphTime aTime) {
+ driver->EnsureNextIteration();
+ };
+ InSequence s;
+ EXPECT_CALL(*graph, MockIteration(0))
+ // Time should not advance on the first iteration to process control
+ // messages
+ .WillOnce(EnsureNextIteration)
+ // ... nor on subsequent.
+ .WillOnce([&driver, length](GraphTime aTime) {
+ driver->SetTickCountToRender(length);
+ driver->EnsureNextIteration();
+ });
+ EXPECT_CALL(*graph, MockIteration(WEBAUDIO_BLOCK_SIZE))
+ // Rendering iteration
+ .WillOnce(EnsureNextIteration)
+ // Time should not advance after rendering.
+ .WillOnce([&](GraphTime aTime) {
+ // Tell the driver to exit its event loop.
+ graph->StopIterating();
+ doneHolder.Resolve(true, __func__);
+ });
+ }
+
+ graph->SetCurrentDriver(driver);
+ driver->EnsureNextIteration();
+ driver->Start();
+ WaitForResolve(done);
+ // Clean up.
+ driver->Shutdown();
+}
diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build
@@ -58,6 +58,7 @@ UNIFIED_SOURCES += [
"TestMP3Demuxer.cpp",
"TestMP4Demuxer.cpp",
"TestMuxer.cpp",
+ "TestOfflineClockDriver.cpp",
"TestOggWriter.cpp",
"TestOpusParser.cpp",
"TestRust.cpp",