DriftController.cpp (14035B)
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/. */ 5 6 #include "DriftController.h" 7 8 #include <atomic> 9 #include <cmath> 10 #include <mutex> 11 12 #include "mozilla/CheckedInt.h" 13 #include "mozilla/Logging.h" 14 15 namespace mozilla { 16 17 LazyLogModule gDriftControllerGraphsLog("DriftControllerGraphs"); 18 extern LazyLogModule gMediaTrackGraphLog; 19 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)) 43 44 static uint8_t GenerateId() { 45 static std::atomic<uint8_t> id{0}; 46 return ++id; 47 } 48 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(); }); 64 } 65 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); 72 } 73 74 void DriftController::ResetAfterUnderrun() { 75 mIsHandlingUnderrun = true; 76 // Trigger a recalculation on the next clock update. 77 mTargetClock = mAdjustmentInterval; 78 } 79 80 uint32_t DriftController::GetCorrectedSourceRate() const { 81 return std::lround(mCorrectedSourceRate); 82 } 83 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% 89 90 // +/- 10ms band maximum half-width. 91 const media::TimeUnit nearCap = media::TimeUnit::FromSeconds(0.01); 92 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); 99 } 100 101 void DriftController::UpdateClock(media::TimeUnit aSourceDuration, 102 media::TimeUnit aTargetDuration, 103 uint32_t aBufferedFrames, 104 uint32_t aBufferSize) { 105 MOZ_ASSERT(!aTargetDuration.IsZero()); 106 107 mTargetClock += aTargetDuration; 108 mTotalTargetClock += aTargetDuration; 109 110 mMeasuredTargetLatency.insert(aTargetDuration); 111 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; 123 } 124 125 media::TimeUnit targetDuration = 126 mTotalTargetClock - mTargetClockAfterLastSourcePacket; 127 mTargetClockAfterLastSourcePacket = mTotalTargetClock; 128 129 mMeasuredSourceLatency.insert(aSourceDuration); 130 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); 142 } 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. 147 // 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. 153 // 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); 160 }; 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); 184 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; 194 } 195 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; 205 }; 206 207 if (mTargetClock >= mAdjustmentInterval) { 208 // The adjustment interval has passed. Recalculate. 209 CalculateCorrection(aBufferedFrames, aBufferSize); 210 } 211 } 212 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; 217 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); 227 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); 254 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); 270 271 ++mNumCorrectionChanges; 272 } 273 274 mCorrectedSourceRate = std::max(1.f, cappedRate); 275 } 276 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)); 285 286 // Reset the counters to prepare for the next period. 287 mTargetClock = media::TimeUnit::Zero(); 288 } 289 } // namespace mozilla 290 291 #undef LOG_PLOT_VALUES 292 #undef LOG_PLOT_NAMES 293 #undef LOG_CONTROLLER