Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / dom / media / driftcontrol / DriftController.cpp
blob2fbecdf41897f79fa91335458b0c32dcbe229604
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DriftController.h"
8 #include <atomic>
9 #include <cmath>
10 #include <mutex>
12 #include "mozilla/CheckedInt.h"
13 #include "mozilla/Logging.h"
15 namespace mozilla {
17 LazyLogModule gDriftControllerGraphsLog("DriftControllerGraphs");
18 extern LazyLogModule gMediaTrackGraphLog;
20 #define LOG_CONTROLLER(level, controller, format, ...) \
21 MOZ_LOG(gMediaTrackGraphLog, level, \
22 ("DriftController %p: (plot-id %u) " format, controller, \
23 (controller)->mPlotId, ##__VA_ARGS__))
24 #define LOG_PLOT_NAMES() \
25 MOZ_LOG( \
26 gDriftControllerGraphsLog, LogLevel::Verbose, \
27 ("id,t,buffering,avgbuffered,desired,buffersize,inlatency,outlatency," \
28 "inframesavg,outframesavg,inrate,outrate,steadystaterate," \
29 "nearthreshold,corrected,hysteresiscorrected,configured"))
30 #define LOG_PLOT_VALUES(id, t, buffering, avgbuffered, desired, buffersize, \
31 inlatency, outlatency, inframesavg, outframesavg, \
32 inrate, outrate, steadystaterate, nearthreshold, \
33 corrected, hysteresiscorrected, configured) \
34 MOZ_LOG(gDriftControllerGraphsLog, LogLevel::Verbose, \
35 ("DriftController %u,%.3f,%u,%.5f,%" PRId64 ",%u,%" PRId64 "," \
36 "%" PRId64 ",%.5f,%.5f,%u,%u," \
37 "%.5f,%" PRId64 ",%.5f,%.5f," \
38 "%ld", \
39 id, t, buffering, avgbuffered, desired, buffersize, inlatency, \
40 outlatency, inframesavg, outframesavg, inrate, outrate, \
41 steadystaterate, nearthreshold, corrected, hysteresiscorrected, \
42 configured))
44 static uint8_t GenerateId() {
45 static std::atomic<uint8_t> id{0};
46 return ++id;
49 DriftController::DriftController(uint32_t aSourceRate, uint32_t aTargetRate,
50 media::TimeUnit aDesiredBuffering)
51 : mPlotId(GenerateId()),
52 mSourceRate(aSourceRate),
53 mTargetRate(aTargetRate),
54 mDesiredBuffering(aDesiredBuffering),
55 mCorrectedSourceRate(static_cast<float>(aSourceRate)),
56 mMeasuredSourceLatency(5),
57 mMeasuredTargetLatency(5) {
58 LOG_CONTROLLER(
59 LogLevel::Info, this,
60 "Created. Resampling %uHz->%uHz. Initial desired buffering: %.2fms.",
61 mSourceRate, mTargetRate, mDesiredBuffering.ToSeconds() * 1000.0);
62 static std::once_flag sOnceFlag;
63 std::call_once(sOnceFlag, [] { LOG_PLOT_NAMES(); });
66 void DriftController::SetDesiredBuffering(media::TimeUnit aDesiredBuffering) {
67 LOG_CONTROLLER(LogLevel::Debug, this, "SetDesiredBuffering %.2fms->%.2fms",
68 mDesiredBuffering.ToSeconds() * 1000.0,
69 aDesiredBuffering.ToSeconds() * 1000.0);
70 mLastDesiredBufferingChangeTime = mTotalTargetClock;
71 mDesiredBuffering = aDesiredBuffering.ToBase(mSourceRate);
74 void DriftController::ResetAfterUnderrun() {
75 mIsHandlingUnderrun = true;
76 // Trigger a recalculation on the next clock update.
77 mTargetClock = mAdjustmentInterval;
80 uint32_t DriftController::GetCorrectedSourceRate() const {
81 return std::lround(mCorrectedSourceRate);
84 int64_t DriftController::NearThreshold() const {
85 // mDesiredBuffering is divided by this to calculate a maximum error that
86 // would be considered "near" desired buffering. A denominator of 5
87 // corresponds to an error of +/- 20% of the desired buffering.
88 static constexpr uint32_t kNearDenominator = 5; // +/- 20%
90 // +/- 10ms band maximum half-width.
91 const media::TimeUnit nearCap = media::TimeUnit::FromSeconds(0.01);
93 // For the minimum desired buffering of 10ms we have a "near" error band
94 // of +/- 2ms (20%). This goes up to +/- 10ms (clamped) at most for when the
95 // desired buffering is 50 ms or higher. AudioDriftCorrection uses this
96 // threshold when deciding whether to reduce buffering.
97 return std::min(nearCap, mDesiredBuffering / kNearDenominator)
98 .ToTicksAtRate(mSourceRate);
101 void DriftController::UpdateClock(media::TimeUnit aSourceDuration,
102 media::TimeUnit aTargetDuration,
103 uint32_t aBufferedFrames,
104 uint32_t aBufferSize) {
105 MOZ_ASSERT(!aTargetDuration.IsZero());
107 mTargetClock += aTargetDuration;
108 mTotalTargetClock += aTargetDuration;
110 mMeasuredTargetLatency.insert(aTargetDuration);
112 if (aSourceDuration.IsZero()) {
113 // Only update after having received input, so that controller input,
114 // packet sizes and buffering measurements, are more stable when the input
115 // stream's callback interval is much larger than that of the output
116 // stream. The buffer level is therefore sampled at high points (rather
117 // than being an average of all points), which is consistent with the
118 // desired level of pre-buffering set on the DynamicResampler only after
119 // an input packet has recently arrived. There is some symmetry with
120 // output durations, which are similarly never zero: the buffer level is
121 // sampled at the lesser of input and output callback rates.
122 return;
125 media::TimeUnit targetDuration =
126 mTotalTargetClock - mTargetClockAfterLastSourcePacket;
127 mTargetClockAfterLastSourcePacket = mTotalTargetClock;
129 mMeasuredSourceLatency.insert(aSourceDuration);
131 double sourceDurationSecs = aSourceDuration.ToSeconds();
132 double targetDurationSecs = targetDuration.ToSeconds();
133 if (mOutputDurationAvg == 0.0) {
134 // Initialize the packet duration moving averages with equal values for an
135 // initial estimate of zero clock drift. When the input packets are much
136 // larger than output packets, targetDurationSecs may initially be much
137 // smaller. Use the maximum for a better estimate of the average output
138 // duration per input packet (or average input duration per output packet
139 // if input packets are smaller than output packets).
140 mInputDurationAvg = mOutputDurationAvg =
141 std::max(sourceDurationSecs, targetDurationSecs);
143 // UpdateAverageWithMeasurement() implements an exponential moving average
144 // with a weight small enough so that the influence of short term variations
145 // is small, but not so small that response time is delayed more than
146 // necessary.
148 // For the packet duration averages, a constant weight means that the moving
149 // averages behave similarly to sums of durations, and so can be used in a
150 // ratio for the drift estimate. Input arriving shortly before or after
151 // an UpdateClock() call, in response to an output request, is weighted
152 // similarly.
154 // For 10 ms packet durations, a weight of 0.01 corresponds to a time
155 // constant of about 1 second (the time over which the effect of old data
156 // points attenuates with a factor of exp(-1)).
157 auto UpdateAverageWithMeasurement = [](double* aAvg, double aData) {
158 constexpr double kMovingAvgWeight = 0.01;
159 *aAvg += kMovingAvgWeight * (aData - *aAvg);
161 UpdateAverageWithMeasurement(&mInputDurationAvg, sourceDurationSecs);
162 UpdateAverageWithMeasurement(&mOutputDurationAvg, targetDurationSecs);
163 double driftEstimate = mInputDurationAvg / mOutputDurationAvg;
164 // The driftEstimate is susceptible to changes in the input packet timing or
165 // duration, so use exponential smoothing to reduce the effect of short term
166 // variations. Apply a cascade of two exponential smoothing filters, which
167 // is a second order low pass filter, which attenuates high frequency
168 // components better than a single first order filter with the same total
169 // time constant. The attenuations of multiple filters are multiplicative
170 // while the time constants are only additive.
171 UpdateAverageWithMeasurement(&mStage1Drift, driftEstimate);
172 UpdateAverageWithMeasurement(&mDriftEstimate, mStage1Drift);
173 // Adjust the average buffer level estimates for drift and for the
174 // correction that was applied with this output packet, so that it still
175 // provides an estimate of the average buffer level.
176 double adjustment = targetDurationSecs *
177 (mSourceRate * mDriftEstimate - GetCorrectedSourceRate());
178 mStage1Buffered += adjustment;
179 mAvgBufferedFramesEst += adjustment;
180 // Include the current buffer level as a data point in the average buffer
181 // level estimate.
182 UpdateAverageWithMeasurement(&mStage1Buffered, aBufferedFrames);
183 UpdateAverageWithMeasurement(&mAvgBufferedFramesEst, mStage1Buffered);
185 if (mIsHandlingUnderrun) {
186 mIsHandlingUnderrun = false;
187 // Underrun handling invalidates the average buffer level estimate
188 // because silent input frames are inserted. Reset the estimate.
189 // This reset also performs the initial estimate when no previous
190 // input packets have been received.
191 mAvgBufferedFramesEst =
192 static_cast<double>(mDesiredBuffering.ToTicksAtRate(mSourceRate));
193 mStage1Buffered = mAvgBufferedFramesEst;
196 uint32_t desiredBufferedFrames = mDesiredBuffering.ToTicksAtRate(mSourceRate);
197 int32_t error =
198 (CheckedInt32(aBufferedFrames) - desiredBufferedFrames).value();
199 if (std::abs(error) > NearThreshold()) {
200 // The error is outside a threshold boundary.
201 mDurationNearDesired = media::TimeUnit::Zero();
202 } else {
203 // The error is within the "near" threshold boundaries.
204 mDurationNearDesired += mTargetClock;
207 if (mTargetClock >= mAdjustmentInterval) {
208 // The adjustment interval has passed. Recalculate.
209 CalculateCorrection(aBufferedFrames, aBufferSize);
213 void DriftController::CalculateCorrection(uint32_t aBufferedFrames,
214 uint32_t aBufferSize) {
215 // Maximum 0.1% change per update.
216 const float cap = static_cast<float>(mSourceRate) / 1000.0f;
218 // Resampler source rate that is expected to maintain a constant average
219 // buffering level.
220 float steadyStateRate =
221 static_cast<float>(mDriftEstimate) * static_cast<float>(mSourceRate);
222 // Use nominal (not corrected) source rate when interpreting desired
223 // buffering so that the set point is independent of the control value.
224 uint32_t desiredBufferedFrames = mDesiredBuffering.ToTicksAtRate(mSourceRate);
225 float avgError = static_cast<float>(mAvgBufferedFramesEst) -
226 static_cast<float>(desiredBufferedFrames);
228 // rateError is positive when pushing the buffering towards the desired level.
229 float rateError =
230 (mCorrectedSourceRate - steadyStateRate) * std::copysign(1.f, avgError);
231 float absAvgError = std::abs(avgError);
232 // Longest period over which convergence to the desired buffering level is
233 // accepted.
234 constexpr float slowConvergenceSecs = 30;
235 // Convergence period to use when resetting the sample rate.
236 constexpr float resetConvergenceSecs = 15;
237 float correctedRate = steadyStateRate + avgError / resetConvergenceSecs;
238 // Allow slower or faster convergence to the desired buffering level, within
239 // acceptable limits, if it means that the same resampling rate can be used,
240 // so that the resampler filters do not need to be recalculated.
241 float hysteresisCorrectedRate = mCorrectedSourceRate;
242 // Allow up to 1 frame/sec resampling rate difference beyond the slowest
243 // convergence boundary, which provides hysteresis to avoid frequent
244 // oscillations in the rate as avgError changes sign when around the
245 // desired buffering level.
246 constexpr float slowHysteresis = 1.f;
247 if (/* current rate is slower than will converge in acceptable time, or */
248 (rateError + slowHysteresis) * slowConvergenceSecs <= absAvgError ||
249 /* current rate is so fast as to overshoot. */
250 rateError * mAdjustmentInterval.ToSeconds() >= absAvgError) {
251 hysteresisCorrectedRate = correctedRate;
252 float cappedRate = std::clamp(correctedRate, mCorrectedSourceRate - cap,
253 mCorrectedSourceRate + cap);
255 if (std::lround(mCorrectedSourceRate) != std::lround(cappedRate)) {
256 LOG_CONTROLLER(
257 LogLevel::Verbose, this,
258 "Updating Correction: Nominal: %uHz->%uHz, Corrected: "
259 "%.2fHz->%uHz (diff %.2fHz), error: %.2fms (nearThreshold: "
260 "%.2fms), buffering: %.2fms, desired buffering: %.2fms",
261 mSourceRate, mTargetRate, cappedRate, mTargetRate,
262 cappedRate - mCorrectedSourceRate,
263 media::TimeUnit(CheckedInt64(aBufferedFrames) - desiredBufferedFrames,
264 mSourceRate)
265 .ToSeconds() *
266 1000.0,
267 media::TimeUnit(NearThreshold(), mSourceRate).ToSeconds() * 1000.0,
268 media::TimeUnit(aBufferedFrames, mSourceRate).ToSeconds() * 1000.0,
269 mDesiredBuffering.ToSeconds() * 1000.0);
271 ++mNumCorrectionChanges;
274 mCorrectedSourceRate = std::max(1.f, cappedRate);
277 LOG_PLOT_VALUES(
278 mPlotId, mTotalTargetClock.ToSeconds(), aBufferedFrames,
279 mAvgBufferedFramesEst, mDesiredBuffering.ToTicksAtRate(mSourceRate),
280 aBufferSize, mMeasuredSourceLatency.mean().ToTicksAtRate(mSourceRate),
281 mMeasuredTargetLatency.mean().ToTicksAtRate(mTargetRate),
282 mInputDurationAvg * mSourceRate, mOutputDurationAvg * mTargetRate,
283 mSourceRate, mTargetRate, steadyStateRate, NearThreshold(), correctedRate,
284 hysteresisCorrectedRate, std::lround(mCorrectedSourceRate));
286 // Reset the counters to prepare for the next period.
287 mTargetClock = media::TimeUnit::Zero();
289 } // namespace mozilla
291 #undef LOG_PLOT_VALUES
292 #undef LOG_PLOT_NAMES
293 #undef LOG_CONTROLLER