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"
12 #include "mozilla/CheckedInt.h"
13 #include "mozilla/Logging.h"
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() \
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," \
39 id, t, buffering, avgbuffered, desired, buffersize, inlatency, \
40 outlatency, inframesavg, outframesavg, inrate, outrate, \
41 steadystaterate, nearthreshold, corrected, hysteresiscorrected, \
44 static uint8_t GenerateId() {
45 static std::atomic
<uint8_t> id
{0};
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) {
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.
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
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
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
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
);
198 (CheckedInt32(aBufferedFrames
) - desiredBufferedFrames
).value();
199 if (std::abs(error
) > NearThreshold()) {
200 // The error is outside a threshold boundary.
201 mDurationNearDesired
= media::TimeUnit::Zero();
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
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.
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
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
)) {
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
,
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
);
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