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 #ifndef MOZILLA_AUDIO_DRIFT_CORRECTION_H_
7 #define MOZILLA_AUDIO_DRIFT_CORRECTION_H_
9 #include "DynamicResampler.h"
10 #include "mozilla/Preferences.h"
14 extern LazyLogModule gMediaTrackGraphLog
;
17 * ClockDrift calculates the diverge of the source clock from the nominal
18 * (provided) rate compared to the target clock, which is considered the master
19 * clock. In the case of different sampling rates, it is assumed that resampling
20 * will take place so the returned correction is estimated after the resampling.
21 * That means that resampling is taken into account in the calculations but it
22 * does appear in the correction. The correction must be applied to the top of
25 * It works by measuring the incoming, the outgoing frames, and the amount of
26 * buffered data and estimates the correction needed. The correction logic has
27 * been created with two things in mind. First, not to run out of frames because
28 * that means the audio will glitch. Second, not to change the correction very
29 * often because this will result in a change in the resampling ratio. The
30 * resampler recreates its internal memory when the ratio changes which has a
33 * The pref `media.clock drift.buffering` can be used to configure the desired
34 * internal buffering. Right now it is at 50ms. But it can be increased if there
35 * are audio quality problems.
37 class ClockDrift final
{
40 * Provide the nominal source and the target sample rate.
42 ClockDrift(uint32_t aSourceRate
, uint32_t aTargetRate
,
43 uint32_t aDesiredBuffering
)
44 : mSourceRate(aSourceRate
),
45 mTargetRate(aTargetRate
),
46 mDesiredBuffering(aDesiredBuffering
) {}
49 * The correction in the form of a ratio. A correction of 0.98 means that the
50 * target is 2% slower compared to the source or 1.03 which means that the
51 * target is 3% faster than the source.
53 float GetCorrection() { return mCorrection
; }
56 * Update the available source frames, target frames, and the current
57 * buffer, in every iteration. If the conditions are met a new correction is
58 * calculated. A new correction is calculated in the following cases:
59 * 1. Every mAdjustmentIntervalMs milliseconds (1000ms).
60 * 2. Every time we run low on buffered frames (less than 20ms).
61 * In addition to that, the correction is clamped to 10% to avoid sound
62 * distortion so the result will be in [0.9, 1.1].
64 void UpdateClock(uint32_t aSourceFrames
, uint32_t aTargetFrames
,
65 uint32_t aBufferedFrames
, uint32_t aRemainingFrames
) {
66 if (mSourceClock
>= mSourceRate
/ 10 || mTargetClock
>= mTargetRate
/ 10) {
67 // Only update the correction if 100ms has passed since last update.
68 if (aBufferedFrames
< mDesiredBuffering
* 4 / 10 /*40%*/ ||
69 aRemainingFrames
< mDesiredBuffering
* 4 / 10 /*40%*/) {
70 // We are getting close to the lower or upper bound of the internal
71 // buffer. Steer clear.
72 CalculateCorrection(0.9, aBufferedFrames
, aRemainingFrames
);
73 } else if ((mTargetClock
* 1000 / mTargetRate
) >= mAdjustmentIntervalMs
||
74 (mSourceClock
* 1000 / mSourceRate
) >= mAdjustmentIntervalMs
) {
75 // The adjustment interval has passed on one side. Recalculate.
76 CalculateCorrection(0.6, aBufferedFrames
, aRemainingFrames
);
79 mTargetClock
+= aTargetFrames
;
80 mSourceClock
+= aSourceFrames
;
85 * aCalculationWeight is a percentage [0, 1] with which the calculated
86 * correction will be weighted. The existing correction will be weighted with
87 * 1 - aCalculationWeight. This gives some inertia to the speed at which the
88 * correction changes, for smoother changes.
90 void CalculateCorrection(float aCalculationWeight
, uint32_t aBufferedFrames
,
91 uint32_t aRemainingFrames
) {
92 // We want to maintain the desired buffer
93 uint32_t bufferedFramesDiff
= aBufferedFrames
- mDesiredBuffering
;
94 uint32_t resampledSourceClock
=
95 std::max(1u, mSourceClock
+ bufferedFramesDiff
);
96 if (mTargetRate
!= mSourceRate
) {
97 resampledSourceClock
*= static_cast<float>(mTargetRate
) / mSourceRate
;
100 MOZ_LOG(gMediaTrackGraphLog
, LogLevel::Verbose
,
101 ("ClockDrift %p Calculated correction %.3f (with weight: %.1f -> "
102 "%.3f) (buffer: %u, desired: %u, remaining: %u)",
103 this, static_cast<float>(mTargetClock
) / resampledSourceClock
,
105 (1 - aCalculationWeight
) * mCorrection
+
106 aCalculationWeight
* mTargetClock
/ resampledSourceClock
,
107 aBufferedFrames
, mDesiredBuffering
, aRemainingFrames
));
109 mCorrection
= (1 - aCalculationWeight
) * mCorrection
+
110 aCalculationWeight
* mTargetClock
/ resampledSourceClock
;
112 // Clamp to range [0.9, 1.1] to avoid distortion
113 mCorrection
= std::min(std::max(mCorrection
, 0.9f
), 1.1f
);
115 // Reset the counters to prepare for the next period.
121 const uint32_t mSourceRate
;
122 const uint32_t mTargetRate
;
123 const uint32_t mAdjustmentIntervalMs
= 1000;
124 const uint32_t mDesiredBuffering
;
127 float mCorrection
= 1.0;
129 uint32_t mSourceClock
= 0;
130 uint32_t mTargetClock
= 0;
134 * Correct the drift between two independent clocks, the source, and the target
135 * clock. The target clock is the master clock so the correction syncs the drift
136 * of the source clock to the target. The nominal sampling rates of source and
137 * target must be provided. If the source and the target operate in different
138 * sample rate the drift correction will be performed on the top of resampling
139 * from the source rate to the target rate.
141 * It works with AudioSegment in order to be able to be used from the
142 * MediaTrackGraph/MediaTrack. The audio buffers are pre-allocated so there is
143 * no new allocation takes place during operation. The preallocation capacity is
144 * 100ms for input and 100ms for output. The class consists of ClockDrift and
145 * AudioResampler check there for more details.
147 * The class is not thread-safe. The construction can happen in any thread but
148 * the member method must be used in a single thread that can be different than
149 * the construction thread. Appropriate for being used in the high priority
152 class AudioDriftCorrection final
{
154 AudioDriftCorrection(uint32_t aSourceRate
, uint32_t aTargetRate
)
156 std::max(5, Preferences::GetInt("media.clockdrift.buffering", 50)) *
158 mTargetRate(aTargetRate
),
159 mClockDrift(aSourceRate
, aTargetRate
, mDesiredBuffering
),
160 mResampler(aSourceRate
, aTargetRate
, mDesiredBuffering
) {}
163 * The source audio frames and request the number of target audio frames must
164 * be provided. The duration of the source and the output is considered as the
165 * source clock and the target clock. The input is buffered internally so some
166 * latency exists. The returned AudioSegment must be cleaned up because the
167 * internal buffer will be reused after 100ms. If the drift correction (and
168 * possible resampling) is not possible due to lack of input data an empty
169 * AudioSegment will be returned. Not thread-safe.
171 AudioSegment
RequestFrames(const AudioSegment
& aInput
,
172 uint32_t aOutputFrames
) {
173 // Very important to go first since the Dynamic will get the sample format
175 if (aInput
.GetDuration()) {
176 // Always go through the resampler because the clock might shift later.
177 mResampler
.AppendInput(aInput
);
179 mClockDrift
.UpdateClock(aInput
.GetDuration(), aOutputFrames
,
180 mResampler
.InputReadableFrames(),
181 mResampler
.InputWritableFrames());
182 TrackRate receivingRate
= mTargetRate
* mClockDrift
.GetCorrection();
183 // Update resampler's rate if there is a new correction.
184 mResampler
.UpdateOutRate(receivingRate
);
185 // If it does not have enough frames the result will be an empty segment.
186 AudioSegment output
= mResampler
.Resample(aOutputFrames
);
187 if (output
.IsEmpty()) {
188 NS_WARNING("Got nothing from the resampler");
189 output
.AppendNullData(aOutputFrames
);
194 // Only accessible from the same thread that is driving RequestFrames().
195 uint32_t CurrentBuffering() const { return mResampler
.InputReadableFrames(); }
197 const uint32_t mDesiredBuffering
;
198 const uint32_t mTargetRate
;
201 ClockDrift mClockDrift
;
202 AudioResampler mResampler
;
205 }; // namespace mozilla
206 #endif /* MOZILLA_AUDIO_DRIFT_CORRECTION_H_ */