commit 37667964d8b1317840994ff139bcee1351a4ea45
parent 2fa05e69bbe2744ad4103d3be3f0717b705df16b
Author: Karl Tomlinson <karlt+@karlt.net>
Date: Wed, 8 Oct 2025 03:36:00 +0000
Bug 1992922 Run the system clock driver with the same rendering interval each iteration r=padenot
Rounding to audio blocks is now performed before determining the iteration
interval.
GetIntervalForIteration() was trying to render more than the target 10ms in
each iteration, up to 30ms, as rendering lagged behind the target
iteration time. It now returns the same 10ms interval for each iteration.
AUDIO_TARGET_MS and mIterationEnd were a remnant from when the graph
calculated state after each rendering interval. Since
https://hg.mozilla.org/mozilla-central/rev/51ab07894b49, each iteration first
calculates state and then immediately renders up to state time.
mIterationEnd is now unused. It will be removed in a subsequent patch.
The interaction of mIterationEnd, mStateComputedTime,
GetIntervalForIteration() behavior, and the reset of mIterationEnd to the new
mStateComputedTime on "Global underrun" roughly meant that rendering could lag
by up to 20ms and still attempt to catch up before bankrupcy was declared
after which attempts to catch up were limited to 20ms. This is replaced with
logic that directly resets mCurrentTimeStamp when lagging by more than 10ms.
mIterationEnd was having some effect on the first rendering duration when
switching from AudioCallbackDriver to ThreadedDriver, but the purpose and
effect of the logic was unclear since
https://hg.mozilla.org/mozilla-central/rev/51ab07894b49.
After such as switch, 10-20ms of additional rendering was performed,
beyond what was expected from time advance.
Differential Revision: https://phabricator.services.mozilla.com/D267736
Diffstat:
2 files changed, 40 insertions(+), 67 deletions(-)
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
@@ -180,41 +180,24 @@ SystemClockDriver::SystemClockDriver(GraphInterface* aGraphInterface,
uint32_t aSampleRate)
: ThreadedDriver(aGraphInterface, aPreviousDriver, aSampleRate),
mInitialTimeStamp(TimeStamp::Now()),
- mCurrentTimeStamp(TimeStamp::Now()) {}
+ mTargetIterationTimeStamp(TimeStamp::Now()) {}
SystemClockDriver::~SystemClockDriver() = default;
void ThreadedDriver::RunThread() {
mThreadRunning = true;
while (true) {
+ MediaTime interval = GetIntervalForIteration();
auto iterationStart = mIterationEnd;
- mIterationEnd += GetIntervalForIteration();
+ mIterationEnd += interval;
+ MOZ_ASSERT(iterationStart <= mIterationEnd);
if (mStateComputedTime < mIterationEnd) {
LOG(LogLevel::Warning, ("%p: Global underrun detected", Graph()));
mIterationEnd = mStateComputedTime;
}
- if (iterationStart >= mIterationEnd) {
- NS_ASSERTION(iterationStart == mIterationEnd, "Time can't go backwards!");
- // This could happen due to low clock resolution, maybe?
- LOG(LogLevel::Debug, ("%p: Time did not advance", Graph()));
- }
-
- GraphTime nextStateComputedTime =
- MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
- mIterationEnd + MillisecondsToMediaTime(AUDIO_TARGET_MS));
- if (nextStateComputedTime < mStateComputedTime) {
- // A previous driver may have been processing further ahead of
- // iterationEnd.
- LOG(LogLevel::Warning,
- ("%p: Prevent state from going backwards. interval[%ld; %ld] "
- "state[%ld; "
- "%ld]",
- Graph(), (long)iterationStart, (long)mIterationEnd,
- (long)mStateComputedTime, (long)nextStateComputedTime));
- nextStateComputedTime = mStateComputedTime;
- }
+ GraphTime nextStateComputedTime = mStateComputedTime + interval;
LOG(LogLevel::Verbose,
("%p: interval[%ld; %ld] state[%ld; %ld]", Graph(),
(long)iterationStart, (long)mIterationEnd, (long)mStateComputedTime,
@@ -243,18 +226,8 @@ void ThreadedDriver::RunThread() {
}
MediaTime SystemClockDriver::GetIntervalForIteration() {
- TimeStamp now = TimeStamp::Now();
- MediaTime interval =
- SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds());
- mCurrentTimeStamp = now;
-
- MOZ_LOG(gMediaTrackGraphLog, LogLevel::Verbose,
- ("%p: Updating current time to %f (real %f, StateComputedTime() %f)",
- Graph(), MediaTimeToSeconds(mIterationEnd + interval),
- (now - mInitialTimeStamp).ToSeconds(),
- MediaTimeToSeconds(mStateComputedTime)));
-
- return interval;
+ return MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
+ MillisecondsToMediaTime(MEDIA_GRAPH_TARGET_PERIOD_MS));
}
void ThreadedDriver::EnsureNextIteration() {
@@ -264,20 +237,30 @@ void ThreadedDriver::EnsureNextIteration() {
void ThreadedDriver::WaitForNextIteration() {
MOZ_ASSERT(mThread);
MOZ_ASSERT(OnThread());
- mWaitHelper.WaitForNextIterationAtLeast(WaitInterval());
+ mWaitHelper.WaitForNextIterationAtLeast(NextIterationWaitDuration());
}
TimeDuration ThreadedDriver::IterationDuration() {
- return TimeDuration::FromMilliseconds(MEDIA_GRAPH_TARGET_PERIOD_MS);
+ return MediaTimeToTimeDuration(GetIntervalForIteration());
}
-TimeDuration SystemClockDriver::WaitInterval() {
+TimeDuration SystemClockDriver::NextIterationWaitDuration() {
MOZ_ASSERT(mThread);
MOZ_ASSERT(OnThread());
TimeStamp now = TimeStamp::Now();
- TimeDuration timeout = IterationDuration() - (now - mCurrentTimeStamp);
+ mTargetIterationTimeStamp += IterationDuration();
+ TimeDuration timeout = mTargetIterationTimeStamp - now;
+ if (timeout < TimeDuration::FromMilliseconds(-MEDIA_GRAPH_TARGET_PERIOD_MS)) {
+ // Rendering has fallen so far behind that the entire next rendering
+ // period has already passed. Don't try to catch up again, but instead
+ // try to render at consistent time intervals from now.
+ LOG(LogLevel::Warning, ("%p: Global underrun detected", Graph()));
+ mTargetIterationTimeStamp = now;
+ }
+
LOG(LogLevel::Verbose,
- ("%p: Waiting for next iteration; at %f, timeout=%f", Graph(),
+ ("%p: Waiting for next iteration; at %f (real %f), timeout=%f", Graph(),
+ MediaTimeToSeconds(mStateComputedTime),
(now - mInitialTimeStamp).ToSeconds(), timeout.ToSeconds()));
return timeout;
}
@@ -297,7 +280,8 @@ void OfflineClockDriver::RunThread() {
}
MediaTime OfflineClockDriver::GetIntervalForIteration() {
- return MillisecondsToMediaTime(mSlice);
+ return MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
+ MillisecondsToMediaTime(mSlice));
}
/* Helper to proxy the GraphInterface methods used by a running
diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h
@@ -38,23 +38,6 @@ namespace mozilla {
static const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
/**
- * Assume that we might miss our scheduled wakeup of the MediaTrackGraph by
- * this much.
- */
-static const int SCHEDULE_SAFETY_MARGIN_MS = 10;
-
-/**
- * Try have this much audio buffered in streams and queued to the hardware.
- * The maximum delay to the end of the next control loop
- * is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
- * There is no point in buffering more audio than this in a stream at any
- * given time (until we add processing).
- * This is not optimal yet.
- */
-static const int AUDIO_TARGET_MS =
- 2 * MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS;
-
-/**
* After starting a fallback driver, wait this long before attempting to re-init
* the audio stream the first time.
*/
@@ -328,12 +311,16 @@ class GraphDriver {
// GraphDriver's thread has started and the thread is running.
virtual bool ThreadRunning() const = 0;
- double MediaTimeToSeconds(GraphTime aTime) const {
+ double MediaTimeToSeconds(MediaTime aTime) const {
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
"Bad time");
return static_cast<double>(aTime) / mSampleRate;
}
+ TimeDuration MediaTimeToTimeDuration(MediaTime aTime) const {
+ return TimeDuration::FromSeconds(MediaTimeToSeconds(aTime));
+ }
+
GraphTime SecondsToMediaTime(double aS) const {
NS_ASSERTION(0 <= aS && aS <= TRACK_TICKS_MAX / TRACK_RATE_MAX,
"Bad seconds");
@@ -449,13 +436,12 @@ class ThreadedDriver : public GraphDriver {
protected:
/* Waits until it's time to process more data. */
void WaitForNextIteration();
- /* Implementation dependent time the ThreadedDriver should wait between
- * iterations. */
- virtual TimeDuration WaitInterval() = 0;
+ /* Return the implementation-dependent time that the ThreadedDriver should
+ * wait for the next iteration. Called only once per iteration;
+ * SystemClockDriver advances it's target iteration time stamp.*/
+ virtual TimeDuration NextIterationWaitDuration() = 0;
/* When the graph wakes up to do an iteration, implementations return the
- * range of time that will be processed. This is called only once per
- * iteration; it may determine the interval from state in a previous
- * call. */
+ * range of time that will be processed. */
virtual MediaTime GetIntervalForIteration() = 0;
virtual ~ThreadedDriver();
@@ -485,14 +471,17 @@ class SystemClockDriver final : public ThreadedDriver {
protected:
/* Return the TimeDuration to wait before the next rendering iteration. */
- TimeDuration WaitInterval() override;
+ TimeDuration NextIterationWaitDuration() override;
MediaTime GetIntervalForIteration() override;
private:
// Those are only modified (after initialization) on the graph thread. The
// graph thread does not run during the initialization.
TimeStamp mInitialTimeStamp;
- TimeStamp mCurrentTimeStamp;
+ // The system clock time when the next or in-progress iteration should start
+ // if the time that rendering happens advances consistently with the frames
+ // rendered. Advanced before waiting to render the next iteration.
+ TimeStamp mTargetIterationTimeStamp;
};
/**
@@ -512,7 +501,7 @@ class OfflineClockDriver final : public ThreadedDriver {
void RunThread() override;
protected:
- TimeDuration WaitInterval() override { return TimeDuration(); }
+ TimeDuration NextIterationWaitDuration() override { return TimeDuration(); }
MediaTime GetIntervalForIteration() override;
private: