Bug 1637235 - Implement the audio drift correction. r=padenot
☠☠ backed out by f5bc2e1b05d9 ☠ ☠
authorAlex Chronopoulos <achronop@gmail.com>
Mon, 01 Jun 2020 12:27:04 +0000
changeset 597297 705f2d35af32cb12bdc2e2ea1a32751d460c7887
parent 597296 d32ea183ab01eafe02e95286f3ccf8400bc6c617
child 597298 3de491b80e70d7f880caed68853d74d4d9d144d3
push id13310
push userffxbld-merge
push dateMon, 29 Jun 2020 14:50:06 +0000
treeherdermozilla-beta@15a59a0afa5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1637235
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1637235 - Implement the audio drift correction. r=padenot Implement the drift correction logic that counts the frames between a source and a target (the master clock) and adjust the source buffer in order to compensate for the drift between source and target. Differential Revision: https://phabricator.services.mozilla.com/D74884
dom/media/AudioDriftCorrection.h
dom/media/gtest/TestAudioDriftCorrection.cpp
dom/media/gtest/moz.build
dom/media/moz.build
modules/libpref/init/StaticPrefList.yaml
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioDriftCorrection.h
@@ -0,0 +1,193 @@
+/* -*- 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 MOZILLA_AUDIO_DRIFT_CORRECTION_H_
+#define MOZILLA_AUDIO_DRIFT_CORRECTION_H_
+
+#include "DynamicResampler.h"
+
+namespace mozilla {
+
+/**
+ * ClockDrift calculates the diverge of the source clock from the nominal
+ * (provided) rate compared to the target clock, which is considered the master
+ * clock. In the case of different sampling rates, it is assumed that resampling
+ * will take place so the returned correction is estimated after the resampling.
+ * That means that resampling is taken into account in the calculations but it
+ * does appear in the correction. The correction must be applied to the top of
+ * the resampling.
+ *
+ * It works by measuring the incoming, the outgoing frames, and the amount of
+ * buffered data and estimates the correction needed. The correction logic has
+ * been created with two things in mind. First, not to run out of frames because
+ * that means the audio will glitch. Second, not to change the correction very
+ * often because this will result in a change in the resampling ratio. The
+ * resampler recreates its internal memory when the ratio changes which has a
+ * performance impact.
+ *
+ * The pref `media.clock drift.buffering` can be used to configure the desired
+ * internal buffering. Right now it is at 5ms. But it can be increased if there
+ * are audio quality problems.
+ */
+class ClockDrift final {
+ public:
+  /**
+   * Provide the nominal source and the target sample rate.
+   */
+  ClockDrift(int32_t aSourceRate, int32_t aTargetRate)
+      : mSourceRate(aSourceRate),
+        mTargetRate(aTargetRate),
+        mDesiredBuffering(5 * mSourceRate / 100) {
+    if (Preferences::HasUserValue("media.clockdrift.buffering")) {
+      int msecs = Preferences::GetInt("media.clockdrift.buffering");
+      mDesiredBuffering = msecs * mSourceRate / 100;
+    }
+  }
+
+  /**
+   * The correction in the form of a ratio. A correction of 0.98 means that the
+   * target is 2% slower compared to the source or 1.03 which means that the
+   * target is 3% faster than the source.
+   */
+  float GetCorrection() { return mCorrection; }
+
+  /**
+   * Update the available source frames, target frames, and the current
+   * buffering, in every iteration. If the condition are met a new correction is
+   * calculated. A new correction is calculated in the following cases:
+   *   1. Every 100 iterations which mean every 100 calls of this method.
+   *   2. Every time we run out of buffered frames (less than 2ms).
+   * In addition to that, the correction is clamped to 10% to avoid sound
+   * distortion so the result will be in [0.9, 1.1].
+   */
+  void UpdateClock(int aSourceClock, int aTargetClock, int aBufferedFrames) {
+    if (mIterations == mAdjustementWindow) {
+      CalculateCorrection(aBufferedFrames);
+    } else if (aBufferedFrames < 2 * mSourceRate / 100 /*20ms*/) {
+      BufferedFramesCorrection(aBufferedFrames);
+    }
+    mTargetClock += aTargetClock;
+    mSourceClock += aSourceClock;
+    ++mIterations;
+  }
+
+ private:
+  void CalculateCorrection(int aBufferedFrames) {
+    // We want to maintain 4 ms buffered
+    int32_t bufferedFramesDiff = aBufferedFrames - mDesiredBuffering;
+    int32_t resampledSourceClock = mSourceClock + bufferedFramesDiff;
+    if (mTargetRate != mSourceRate) {
+      resampledSourceClock =
+          resampledSourceClock *
+          (static_cast<float>(mTargetRate) / static_cast<float>(mSourceRate));
+    }
+    mCorrection = (float)mTargetClock / resampledSourceClock;
+
+    // Clamp to ragnge [0.9, 1.1] to avoid distortion
+    mCorrection = std::min(std::max(mCorrection, 0.9f), 1.1f);
+
+    // If previous correction slightly smaller  (-1%) ignore it to avoid
+    // recalculations. Don't do it when is greater (+1%) to avoid risking
+    // running out of frames.
+    if (mPreviousCorrection - mCorrection <= 0.01 &&
+        mPreviousCorrection - mCorrection > 0) {
+      mCorrection = mPreviousCorrection;
+    }
+    mPreviousCorrection = mCorrection;
+
+    // Reset the counters to preper for the new period.
+    mIterations = 0;
+    mTargetClock = 0;
+    mSourceClock = 0;
+  }
+
+  void BufferedFramesCorrection(int aBufferedFrames) {
+    int32_t bufferedFramesDiff = aBufferedFrames - mDesiredBuffering;
+    int32_t resampledSourceClock = mSourceRate + bufferedFramesDiff;
+    if (mTargetRate != mSourceRate) {
+      resampledSourceClock = resampledSourceClock *
+                             (static_cast<float>(mTargetRate) / mSourceRate);
+    }
+    MOZ_ASSERT(mTargetRate > resampledSourceClock);
+    mPreviousCorrection = mCorrection;
+    mCorrection +=
+        static_cast<float>(mTargetRate) / resampledSourceClock - 1.0f;
+    // Clamp to range [0.9, 1.1] to avoid distortion
+    mCorrection = std::min(std::max(mCorrection, 0.9f), 1.1f);
+  }
+
+ private:
+  const int32_t mSourceRate;
+  const int32_t mTargetRate;
+
+  float mCorrection = 1.0;
+  float mPreviousCorrection = 1.0;
+  const int32_t mAdjustementWindow = 100;
+  int32_t mDesiredBuffering;  // defult: 5ms
+
+  int32_t mSourceClock = 0;
+  int32_t mTargetClock = 0;
+  int32_t mIterations = 0;
+};
+
+/**
+ * Correct the drift between two independent clocks, the source, and the target
+ * clock. The target clock is the master clock so the correction syncs the drift
+ * of the source clock to the target. The nominal sampling rates of source and
+ * target must be provided. If the source and the target operate in different
+ * sample rate the drift correction will be performed on the top of resampling
+ * from the source rate to the target rate.
+ *
+ * It works with AudioSegment in order to be able to be used from the
+ * MediaTrackGraph/MediaTrack. The audio buffers are pre-allocated so there is
+ * no new allocation takes place during operation. The preallocation capacity is
+ * 100ms for input and 100ms for output. The class consists of ClockDrift and
+ * AudioResampler check there for more details.
+ *
+ * The class is not thread-safe. The construction can happen in any thread but
+ * the member method must be used in a single thread that can be different than
+ * the construction thread. Appropriate for being used in the high priority
+ * audio thread.
+ */
+class AudioDriftCorrection final {
+ public:
+  AudioDriftCorrection(int32_t aSourceRate, int32_t aTargetRate)
+      : mClockDrift(aSourceRate, aTargetRate),
+        mResampler(aSourceRate, aTargetRate, aTargetRate / 20 /*50ms*/),
+        mTargetRate(aTargetRate) {}
+
+  /**
+   * The source audio frames and request the number of target audio frames must
+   * be provided. The duration of the source and the output is considered as the
+   * source clock and the target clock. The input is buffered internally so some
+   * latency exists. The returned AudioSegment must be cleaned up because the
+   * internal buffer will be reused after 100ms. If the drift correction (and
+   * possible resampling) is not possible due to lack of input data an empty
+   * AudioSegment will be returned. Not thread-safe.
+   */
+  AudioSegment RequestFrames(const AudioSegment& aInput, int aOutputFrames) {
+    // Very important to go first since the Dynamic will get the sample format
+    // from the chunk.
+    if (aInput.GetDuration()) {
+      // Always go through the resampler because the clock might shift later.
+      mResampler.AppendInput(aInput);
+    }
+    mClockDrift.UpdateClock(aInput.GetDuration(), aOutputFrames,
+                            mResampler.InputDuration());
+    TrackRate receivingRate = mTargetRate * mClockDrift.GetCorrection();
+    // Update resampler's rate if there is a new correction.
+    mResampler.UpdateOutRate(receivingRate);
+    // If it does not have enough frames the result will be an empty segment.
+    return mResampler.Resample(aOutputFrames);
+  }
+
+ private:
+  ClockDrift mClockDrift;
+  AudioResampler mResampler;
+  const int32_t mTargetRate;
+};
+
+};     // namespace mozilla
+#endif /* MOZILLA_AUDIO_DRIFT_CORRECTION_H_ */
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestAudioDriftCorrection.cpp
@@ -0,0 +1,332 @@
+/* -*- 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/. */
+
+#include "AudioDriftCorrection.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest-printers.h"
+#include "gtest/gtest.h"
+
+TEST(TestClockDrift, Basic)
+{
+  ClockDrift c(48000, 48000);
+  EXPECT_EQ(c.GetCorrection(), 1.0);
+
+  // Keep buffered frames to the wanted level in order to not affect that test.
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480 + 48, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480 + 48, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+
+  c.UpdateClock(0, 0, 5 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
+}
+
+TEST(TestClockDrift, BasicResampler)
+{
+  ClockDrift c(24000, 48000);
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240, 480, 5 * 240);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+
+  // Keep buffered frames to the wanted level in order to not affect that test.
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240, 480 + 48, 5 * 240);  // +10%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240 + 24, 480, 5 * 240);  // +10%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240, 480 - 48, 5 * 240);  // -10%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240 + 12, 480 - 24, 5 * 240);  //-5%, -5%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
+  }
+
+  c.UpdateClock(0, 0, 5 * 240);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
+}
+
+TEST(TestClockDrift, BufferedInput)
+{
+  ClockDrift c(48000, 48000);
+  EXPECT_EQ(c.GetCorrection(), 1.0);
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  c.UpdateClock(480, 480, 0);  // 0 buffered on 100th iteration
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(480, 480, 2 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+  }
+  c.UpdateClock(480, 480, 2 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
+  }
+  c.UpdateClock(480, 480, 5 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(480, 480, 7 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  c.UpdateClock(480, 480, 7 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 0.980392);
+}
+
+TEST(TestClockDrift, BufferedInputWithResampling)
+{
+  ClockDrift c(24000, 48000);
+  EXPECT_EQ(c.GetCorrection(), 1.0);
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(240, 480, 5 * 240);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  c.UpdateClock(240, 480, 0);  // 0 buffered on 100th iteration
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(240, 480, 2 * 240);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+  }
+  c.UpdateClock(240, 480, 2 * 240);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(240, 480, 5 * 240);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
+  }
+  c.UpdateClock(240, 480, 5 * 240);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+
+  for (int i = 0; i < 99; ++i) {
+    c.UpdateClock(240, 480, 7 * 240);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  c.UpdateClock(240, 480, 7 * 240);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 0.980392);
+}
+
+TEST(TestClockDrift, Clamp)
+{
+  ClockDrift c(48000, 48000);
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480 + 2 * 48, 5 * 480);  // +20%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480 - 2 * 48, 5 * 480);  // -20%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
+  }
+  c.UpdateClock(0, 0, 5 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 0.9);
+}
+
+TEST(TestClockDrift, SmallDiff)
+{
+  ClockDrift c(48000, 48000);
+
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480 + 4, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480 + 5, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 0.98969072);
+  }
+  // Reset to 1.0 again
+  for (int i = 0; i < 100; ++i) {
+    c.UpdateClock(480, 480 + 4, 5 * 480);  // +0.83%
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  }
+  c.UpdateClock(0, 0, 5 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0083333);
+}
+
+TEST(TestClockDrift, SmallBufferedFrames)
+{
+  ClockDrift c(48000, 48000);
+
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  c.UpdateClock(480, 480, 5 * 480);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
+  c.UpdateClock(480, 480, 0);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+
+  for (int i = 0; i < 50; ++i) {
+    c.UpdateClock(480, 480, 5 * 480);
+    EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
+  }
+  c.UpdateClock(480, 480, 0);
+  EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
+}
+
+void printAudioSegment(const AudioSegment& segment) {
+  for (AudioSegment::ConstChunkIterator iter(segment); !iter.IsEnded();
+       iter.Next()) {
+    const AudioChunk& c = *iter;
+    const float* buffer = c.ChannelData<float>()[0];
+    for (int i = 0; i < c.GetDuration(); ++i) {
+      printf("%f\n", buffer[i]);
+    }
+  }
+}
+
+template <class T>
+AudioChunk CreateAudioChunk(uint32_t aFrames, int aChannels,
+                            AudioSampleFormat aSampleFormat);
+
+void testAudioCorrection(int32_t aSourceRate, int32_t aTargetRate) {
+  const int32_t sampleRateTransmitter = aSourceRate;
+  const int32_t sampleRateReceiver = aTargetRate;
+  AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver);
+
+  const float amplitude = 0.5;
+  const float frequency = 10;
+  const float phase = 0.0;
+  float time = 0.0;
+  const float deltaTime = 1.0f / sampleRateTransmitter;
+
+  int32_t sourceFrames;
+  const int32_t targetFrames = sampleRateReceiver / 100;
+
+  // Run for some time: 6 * 250 = 1500 iterations
+  for (int j = 0; j < 6; ++j) {
+    // apply some drift
+    if (j % 2 == 0) {
+      sourceFrames = sampleRateTransmitter / 100 + 10;
+    } else {
+      sourceFrames = sampleRateTransmitter / 100 - 10;
+    }
+
+    for (int n = 0; n < 250; ++n) {
+      // Create the input (sine tone)
+      AudioChunk chunk =
+          CreateAudioChunk<float>(sourceFrames, 1, AUDIO_FORMAT_FLOAT32);
+      float* monoBuffer = chunk.ChannelDataForWrite<float>(0);
+      for (int i = 0; i < sourceFrames; ++i) {
+        double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+        monoBuffer[i] = static_cast<float>(value);
+        time += deltaTime;
+      }
+      AudioSegment inSegment;
+      inSegment.AppendAndConsumeChunk(&chunk);
+      // Print the input for debugging
+      // printAudioSegment(inSegment);
+
+      // Get the output of the correction
+      AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
+      EXPECT_EQ(outSegment.GetDuration(), targetFrames);
+      // Print the output for debugging
+      // printAudioSegment(outSegment);
+    }
+  }
+}
+
+TEST(TestAudioDriftCorrection, Basic)
+{
+  testAudioCorrection(48000, 48000);
+  testAudioCorrection(48000, 44100);
+  testAudioCorrection(44100, 48000);
+}
+
+void testMonoToStereoInput(int aSourceRate, int aTargetRate) {
+  const int32_t sampleRateTransmitter = aSourceRate;
+  const int32_t sampleRateReceiver = aTargetRate;
+  AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver);
+
+  const float amplitude = 0.5;
+  const float frequency = 10;
+  const float phase = 0.0;
+  float time = 0.0;
+  const float deltaTime = 1.0f / sampleRateTransmitter;
+
+  int32_t sourceFrames;
+  const int32_t targetFrames = sampleRateReceiver / 100;
+
+  // Run for some time: 6 * 250 = 1500 iterations
+  for (int j = 0; j < 6; ++j) {
+    // apply some drift
+    if (j % 2 == 0) {
+      sourceFrames = sampleRateTransmitter / 100 + 10;
+    } else {
+      sourceFrames = sampleRateTransmitter / 100 - 10;
+    }
+
+    for (int n = 0; n < 250; ++n) {
+      // Create the input (sine tone)
+      AudioChunk chunk =
+          CreateAudioChunk<float>(sourceFrames / 2, 1, AUDIO_FORMAT_FLOAT32);
+      float* monoBuffer = chunk.ChannelDataForWrite<float>(0);
+      for (int i = 0; i < chunk.GetDuration(); ++i) {
+        double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+        monoBuffer[i] = static_cast<float>(value);
+        time += deltaTime;
+      }
+      AudioChunk chunk2 =
+          CreateAudioChunk<float>(sourceFrames / 2, 2, AUDIO_FORMAT_FLOAT32);
+      for (int i = 0; i < chunk2.GetDuration(); ++i) {
+        double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+        chunk2.ChannelDataForWrite<float>(0)[i] =
+            chunk2.ChannelDataForWrite<float>(1)[i] = static_cast<float>(value);
+        time += deltaTime;
+      }
+      AudioSegment inSegment;
+      inSegment.AppendAndConsumeChunk(&chunk);
+      inSegment.AppendAndConsumeChunk(&chunk2);
+      // Print the input for debugging
+      // printAudioSegment(inSegment);
+
+      // Get the output of the correction
+      AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
+      EXPECT_EQ(outSegment.GetDuration(), targetFrames);
+      // Print the output for debugging
+      // printAudioSegment(outSegment);
+    }
+  }
+}
+
+TEST(TestAudioDriftCorrection, MonoToStereoInput)
+{
+  testMonoToStereoInput(48000, 48000);
+  testMonoToStereoInput(48000, 44100);
+  testMonoToStereoInput(44100, 48000);
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -15,16 +15,17 @@ LOCAL_INCLUDES += [
 ]
 
 UNIFIED_SOURCES += [
     'AudioGenerator.cpp',
     'MockMediaResource.cpp',
     'TestAudioBuffers.cpp',
     'TestAudioCallbackDriver.cpp',
     'TestAudioCompactor.cpp',
+    'TestAudioDriftCorrection.cpp',
     'TestAudioMixer.cpp',
     'TestAudioPacketizer.cpp',
     'TestAudioRingBuffer.cpp',
     'TestAudioSegment.cpp',
     'TestAudioTrackEncoder.cpp',
     'TestAudioTrackGraph.cpp',
     'TestBenchmarkStorage.cpp',
     'TestBitWriter.cpp',
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -105,16 +105,17 @@ EXPORTS += [
     'ADTSDemuxer.h',
     'AsyncLogger.h',
     'AudioBufferUtils.h',
     'AudioChannelFormat.h',
     'AudioCompactor.h',
     'AudioConfig.h',
     'AudioConverter.h',
     'AudioDeviceInfo.h',
+    'AudioDriftCorrection.h',
     'AudioMixer.h',
     'AudioPacketizer.h',
     'AudioRingBuffer.h',
     'AudioSampleFormat.h',
     'AudioSegment.h',
     'AudioStream.h',
     'AutoplayPolicy.h',
     'BackgroundVideoDecodingPermissionObserver.h',
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -6392,16 +6392,22 @@
 #elif defined(XP_WIN) && defined(_X86_)
   value: @IS_NIGHTLY_BUILD@
 #elif defined(XP_WIN) && !defined(_ARM64_)
   value: true
 #else
   value: false
 #endif
 
+# ClockDrift desired buffering
+- name: media.clockdrift.buffering
+  type: int32_t
+  mirror: always
+  value: 5
+
 # If a resource is known to be smaller than this size (in kilobytes), a
 # memory-backed MediaCache may be used; otherwise the (single shared global)
 # file-backed MediaCache is used.
 - name: media.memory_cache_max_size
   type: uint32_t
   value: 8192        # Measured in KiB
   mirror: always