tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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