summaryrefslogtreecommitdiffstats
path: root/dom/media/driftcontrol/DriftController.h
blob: 0bd745c7374491d763842052b74ac2b28ddd24bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_
#define DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_

#include "TimeUnits.h"
#include "mozilla/RollingMean.h"

#include <algorithm>
#include <cstdint>

#include "MediaSegment.h"

namespace mozilla {

/**
 * DriftController calculates the divergence of the source clock from its
 * nominal (provided) rate compared to that of the target clock, which drives
 * the calculations.
 *
 * The DriftController looks at how the current buffering level differs from the
 * desired buffering level and sets a corrected target rate. A resampler should
 * be configured to resample from the nominal source rate to the corrected
 * target rate. It assumes that the resampler is initially configured to
 * resample from the nominal source rate to the nominal target rate.
 *
 * The pref `media.clock drift.buffering` can be used to configure the minimum
 * initial desired internal buffering. Right now it is at 50ms. A larger desired
 * buffering level will be used if deemed necessary based on input device
 * latency, reported or observed. It will also be increased as a response to an
 * underrun, since that indicates the buffer was too small.
 */
class DriftController final {
 public:
  /**
   * Provide the nominal source and the target sample rate.
   */
  DriftController(uint32_t aSourceRate, uint32_t aTargetRate,
                  media::TimeUnit aDesiredBuffering);

  /**
   * Set the buffering level that the controller should target.
   */
  void SetDesiredBuffering(media::TimeUnit aDesiredBuffering);

  /**
   * Reset internal PID-controller state in a way that is suitable for handling
   * an underrun.
   */
  void ResetAfterUnderrun();

  /**
   * Returns the drift-corrected target rate.
   */
  uint32_t GetCorrectedTargetRate() const;

  /**
   * The number of times mCorrectedTargetRate has been changed to adjust to
   * drift.
   */
  uint32_t NumCorrectionChanges() const { return mNumCorrectionChanges; }

  /**
   * The amount of time the buffering level has been within the hysteresis
   * threshold.
   */
  media::TimeUnit DurationWithinHysteresis() const {
    return mDurationWithinHysteresis;
  }

  /**
   * The amount of time that has passed since the last time SetDesiredBuffering
   * was called.
   */
  media::TimeUnit DurationSinceDesiredBufferingChange() const {
    return mTotalTargetClock - mLastDesiredBufferingChangeTime;
  }

  /**
   * A rolling window average measurement of source latency by looking at the
   * duration of the source buffer.
   */
  media::TimeUnit MeasuredSourceLatency() const {
    return mMeasuredSourceLatency.mean();
  }

  /**
   * Update the available source frames, target frames, and the current
   * buffer, in every iteration. If the conditions are met a new correction is
   * calculated. A new correction is calculated every mAdjustmentInterval. In
   * addition to that, the correction is clamped so that the output sample rate
   * changes by at most 0.1% of its nominal rate each correction.
   */
  void UpdateClock(media::TimeUnit aSourceDuration,
                   media::TimeUnit aTargetDuration, uint32_t aBufferedFrames,
                   uint32_t aBufferSize);

 private:
  // This implements a simple PID controller with feedback.
  // Set point:     SP = mDesiredBuffering.
  // Process value: PV(t) = aBufferedFrames. This is the feedback.
  // Error:         e(t) = mDesiredBuffering - aBufferedFrames.
  // Control value: CV(t) = the number to add to the nominal target rate, i.e.
  //                the corrected target rate = CV(t) + nominal target rate.
  //
  // Controller:
  // Proportional part: The error, p(t) = e(t), multiplied by a gain factor, Kp.
  // Integral part:     The historic cumulative value of the error,
  //                    i(t+1) = i(t) + e(t+1), multiplied by a gain factor, Ki.
  // Derivative part:   The error's rate of change, d(t+1) = (e(t+1)-e(t))/1,
  //                    multiplied by a gain factor, Kd.
  // Control signal:    The sum of the parts' output,
  //                    u(t) = Kp*p(t) + Ki*i(t) + Kd*d(t).
  //
  // Control action: Converting the control signal to a target sample rate.
  //                 Simplified, a positive control signal means the buffer is
  //                 lower than desired (because the error is positive), so the
  //                 target sample rate must be increased in order to consume
  //                 input data slower. We calculate the corrected target rate
  //                 by simply adding the control signal, u(t), to the nominal
  //                 target rate.
  //
  // Hysteresis: As long as the error is within a threshold of 20% of the set
  //             point (desired buffering level) (up to 10ms for >50ms desired
  //             buffering), we call this the hysteresis threshold, the control
  //             signal does not influence the corrected target rate at all.
  //             This is to reduce the frequency at which we need to reconfigure
  //             the resampler, as it causes some allocations.
  void CalculateCorrection(uint32_t aBufferedFrames, uint32_t aBufferSize);

 public:
  const uint8_t mPlotId;
  const uint32_t mSourceRate;
  const uint32_t mTargetRate;
  const media::TimeUnit mAdjustmentInterval = media::TimeUnit::FromSeconds(1);
  const media::TimeUnit mIntegralCapTimeLimit =
      media::TimeUnit(10, 1).ToBase(mTargetRate);

 private:
  media::TimeUnit mDesiredBuffering;
  int32_t mPreviousError = 0;
  float mIntegral = 0.0;
  Maybe<float> mIntegralCenterForCap;
  float mCorrectedTargetRate;
  Maybe<int32_t> mLastHysteresisBoundaryCorrection;
  media::TimeUnit mDurationWithinHysteresis;
  uint32_t mNumCorrectionChanges = 0;

  // An estimate of the source's latency, i.e. callback buffer size, in frames.
  RollingMean<media::TimeUnit, media::TimeUnit> mMeasuredSourceLatency;
  // An estimate of the target's latency, i.e. callback buffer size, in frames.
  RollingMean<media::TimeUnit, media::TimeUnit> mMeasuredTargetLatency;

  media::TimeUnit mTargetClock;
  media::TimeUnit mTotalTargetClock;
  media::TimeUnit mLastDesiredBufferingChangeTime;
};

}  // namespace mozilla
#endif  // DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_