summaryrefslogtreecommitdiffstats
path: root/dom/media/driftcontrol/gtest
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/driftcontrol/gtest')
-rw-r--r--dom/media/driftcontrol/gtest/TestAudioChunkList.cpp226
-rw-r--r--dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp529
-rw-r--r--dom/media/driftcontrol/gtest/TestAudioResampler.cpp677
-rw-r--r--dom/media/driftcontrol/gtest/TestDriftController.cpp168
-rw-r--r--dom/media/driftcontrol/gtest/TestDynamicResampler.cpp722
-rw-r--r--dom/media/driftcontrol/gtest/moz.build21
6 files changed, 2343 insertions, 0 deletions
diff --git a/dom/media/driftcontrol/gtest/TestAudioChunkList.cpp b/dom/media/driftcontrol/gtest/TestAudioChunkList.cpp
new file mode 100644
index 0000000000..34848821f5
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/TestAudioChunkList.cpp
@@ -0,0 +1,226 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "AudioChunkList.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+TEST(TestAudioChunkList, Basic1)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioChunkList list(256, 2, testPrincipal);
+ list.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+ EXPECT_EQ(list.ChunkCapacity(), 128u);
+ EXPECT_EQ(list.TotalCapacity(), 256u);
+
+ AudioChunk& c1 = list.GetNext();
+ float* c1_ch1 = c1.ChannelDataForWrite<float>(0);
+ float* c1_ch2 = c1.ChannelDataForWrite<float>(1);
+ EXPECT_EQ(c1.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c1.mBufferFormat, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ c1_ch1[i] = c1_ch2[i] = 0.01f * static_cast<float>(i);
+ }
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_EQ(c2.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c2.mBufferFormat, AUDIO_FORMAT_FLOAT32);
+ EXPECT_NE(c1.mBuffer.get(), c2.mBuffer.get());
+ AudioChunk& c3 = list.GetNext();
+ EXPECT_EQ(c3.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c3.mBufferFormat, AUDIO_FORMAT_FLOAT32);
+ // Cycle
+ EXPECT_EQ(c1.mBuffer.get(), c3.mBuffer.get());
+ float* c3_ch1 = c3.ChannelDataForWrite<float>(0);
+ float* c3_ch2 = c3.ChannelDataForWrite<float>(1);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ EXPECT_FLOAT_EQ(c1_ch1[i], c3_ch1[i]);
+ EXPECT_FLOAT_EQ(c1_ch2[i], c3_ch2[i]);
+ }
+}
+
+TEST(TestAudioChunkList, Basic2)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioChunkList list(256, 2, testPrincipal);
+ list.SetSampleFormat(AUDIO_FORMAT_S16);
+ EXPECT_EQ(list.ChunkCapacity(), 256u);
+ EXPECT_EQ(list.TotalCapacity(), 512u);
+
+ AudioChunk& c1 = list.GetNext();
+ EXPECT_EQ(c1.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c1.mBufferFormat, AUDIO_FORMAT_S16);
+ short* c1_ch1 = c1.ChannelDataForWrite<short>(0);
+ short* c1_ch2 = c1.ChannelDataForWrite<short>(1);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ c1_ch1[i] = c1_ch2[i] = static_cast<short>(i);
+ }
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_EQ(c2.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c2.mBufferFormat, AUDIO_FORMAT_S16);
+ EXPECT_NE(c1.mBuffer.get(), c2.mBuffer.get());
+ AudioChunk& c3 = list.GetNext();
+ EXPECT_EQ(c3.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c3.mBufferFormat, AUDIO_FORMAT_S16);
+ AudioChunk& c4 = list.GetNext();
+ EXPECT_EQ(c4.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c4.mBufferFormat, AUDIO_FORMAT_S16);
+ // Cycle
+ AudioChunk& c5 = list.GetNext();
+ EXPECT_EQ(c5.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c5.mBufferFormat, AUDIO_FORMAT_S16);
+ EXPECT_EQ(c1.mBuffer.get(), c5.mBuffer.get());
+ short* c5_ch1 = c5.ChannelDataForWrite<short>(0);
+ short* c5_ch2 = c5.ChannelDataForWrite<short>(1);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ EXPECT_EQ(c1_ch1[i], c5_ch1[i]);
+ EXPECT_EQ(c1_ch2[i], c5_ch2[i]);
+ }
+}
+
+TEST(TestAudioChunkList, Basic3)
+{
+ AudioChunkList list(260, 2, PRINCIPAL_HANDLE_NONE);
+ list.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+ EXPECT_EQ(list.ChunkCapacity(), 128u);
+ EXPECT_EQ(list.TotalCapacity(), 256u + 128u);
+
+ AudioChunk& c1 = list.GetNext();
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_NE(c1.mBuffer.get(), c2.mBuffer.get());
+ AudioChunk& c3 = list.GetNext();
+ EXPECT_NE(c1.mBuffer.get(), c3.mBuffer.get());
+ AudioChunk& c4 = list.GetNext();
+ EXPECT_EQ(c1.mBuffer.get(), c4.mBuffer.get());
+}
+
+TEST(TestAudioChunkList, Basic4)
+{
+ AudioChunkList list(260, 2, PRINCIPAL_HANDLE_NONE);
+ list.SetSampleFormat(AUDIO_FORMAT_S16);
+ EXPECT_EQ(list.ChunkCapacity(), 256u);
+ EXPECT_EQ(list.TotalCapacity(), 512u + 256u);
+
+ AudioChunk& c1 = list.GetNext();
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_NE(c1.mBuffer.get(), c2.mBuffer.get());
+ AudioChunk& c3 = list.GetNext();
+ EXPECT_NE(c1.mBuffer.get(), c3.mBuffer.get());
+ AudioChunk& c4 = list.GetNext();
+ EXPECT_EQ(c1.mBuffer.get(), c4.mBuffer.get());
+}
+
+TEST(TestAudioChunkList, UpdateChannels)
+{
+ AudioChunkList list(256, 2, PRINCIPAL_HANDLE_NONE);
+ list.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ AudioChunk& c1 = list.GetNext();
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_EQ(c1.ChannelCount(), 2u);
+ EXPECT_EQ(c2.ChannelCount(), 2u);
+
+ // Update to Quad
+ list.Update(4);
+
+ AudioChunk& c3 = list.GetNext();
+ AudioChunk& c4 = list.GetNext();
+ EXPECT_EQ(c3.ChannelCount(), 4u);
+ EXPECT_EQ(c4.ChannelCount(), 4u);
+}
+
+TEST(TestAudioChunkList, UpdateBetweenMonoAndStereo)
+{
+ AudioChunkList list(256, 2, PRINCIPAL_HANDLE_NONE);
+ list.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ AudioChunk& c1 = list.GetNext();
+ float* c1_ch1 = c1.ChannelDataForWrite<float>(0);
+ float* c1_ch2 = c1.ChannelDataForWrite<float>(1);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ c1_ch1[i] = c1_ch2[i] = 0.01f * static_cast<float>(i);
+ }
+
+ AudioChunk& c2 = list.GetNext();
+ EXPECT_EQ(c1.ChannelCount(), 2u);
+ EXPECT_EQ(c2.ChannelCount(), 2u);
+
+ // Downmix to mono
+ list.Update(1);
+
+ AudioChunk& c3 = list.GetNext();
+ float* c3_ch1 = c3.ChannelDataForWrite<float>(0);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ EXPECT_FLOAT_EQ(c3_ch1[i], c1_ch1[i]);
+ }
+
+ AudioChunk& c4 = list.GetNext();
+ EXPECT_EQ(c3.ChannelCount(), 1u);
+ EXPECT_EQ(c4.ChannelCount(), 1u);
+ EXPECT_EQ(static_cast<SharedChannelArrayBuffer<float>*>(c3.mBuffer.get())
+ ->mBuffers[0]
+ .Length(),
+ list.ChunkCapacity());
+
+ // Upmix to stereo
+ list.Update(2);
+
+ AudioChunk& c5 = list.GetNext();
+ AudioChunk& c6 = list.GetNext();
+ EXPECT_EQ(c5.ChannelCount(), 2u);
+ EXPECT_EQ(c6.ChannelCount(), 2u);
+ EXPECT_EQ(static_cast<SharedChannelArrayBuffer<float>*>(c5.mBuffer.get())
+ ->mBuffers[0]
+ .Length(),
+ list.ChunkCapacity());
+ EXPECT_EQ(static_cast<SharedChannelArrayBuffer<float>*>(c5.mBuffer.get())
+ ->mBuffers[1]
+ .Length(),
+ list.ChunkCapacity());
+
+ // Downmix to mono
+ list.Update(1);
+
+ AudioChunk& c7 = list.GetNext();
+ float* c7_ch1 = c7.ChannelDataForWrite<float>(0);
+ for (uint32_t i = 0; i < list.ChunkCapacity(); ++i) {
+ EXPECT_FLOAT_EQ(c7_ch1[i], c1_ch1[i]);
+ }
+
+ AudioChunk& c8 = list.GetNext();
+ EXPECT_EQ(c7.ChannelCount(), 1u);
+ EXPECT_EQ(c8.ChannelCount(), 1u);
+ EXPECT_EQ(static_cast<SharedChannelArrayBuffer<float>*>(c7.mBuffer.get())
+ ->mBuffers[0]
+ .Length(),
+ list.ChunkCapacity());
+}
+
+TEST(TestAudioChunkList, ConsumeAndForget)
+{
+ AudioSegment s;
+ AudioChunkList list(256, 2, PRINCIPAL_HANDLE_NONE);
+ list.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ AudioChunk& c1 = list.GetNext();
+ AudioChunk tmp1 = c1;
+ s.AppendAndConsumeChunk(std::move(tmp1));
+ EXPECT_FALSE(c1.mBuffer.get() == nullptr);
+ EXPECT_EQ(c1.ChannelData<float>().Length(), 2u);
+
+ AudioChunk& c2 = list.GetNext();
+ AudioChunk tmp2 = c2;
+ s.AppendAndConsumeChunk(std::move(tmp2));
+ EXPECT_FALSE(c2.mBuffer.get() == nullptr);
+ EXPECT_EQ(c2.ChannelData<float>().Length(), 2u);
+
+ s.ForgetUpTo(256);
+ list.GetNext();
+ list.GetNext();
+}
diff --git a/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp b/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp
new file mode 100644
index 0000000000..c13f443d37
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp
@@ -0,0 +1,529 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "AudioDriftCorrection.h"
+#include "AudioGenerator.h"
+#include "AudioVerifier.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+template <class T>
+AudioChunk CreateAudioChunk(uint32_t aFrames, uint32_t aChannels,
+ AudioSampleFormat aSampleFormat);
+
+void testAudioCorrection(int32_t aSourceRate, int32_t aTargetRate,
+ bool aTestMonoToStereoInput = false) {
+ const uint32_t frequency = 100;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(aSourceRate, aTargetRate, testPrincipal);
+
+ uint8_t numChannels = 1;
+ AudioGenerator<AudioDataValue> tone(numChannels, aSourceRate, frequency);
+ AudioVerifier<AudioDataValue> inToneVerifier(aSourceRate, frequency);
+ AudioVerifier<AudioDataValue> outToneVerifier(aTargetRate, frequency);
+
+ // Run for some time: 3 * 5000 = 15000 iterations
+ for (uint32_t j = 0; j < 3; ++j) {
+ TrackTime sourceFramesIteration = 0;
+ TrackTime targetFramesIteration = 0;
+
+ // apply some drift (+/- .2%)
+ const int8_t additionalDriftFrames =
+ ((j % 2 == 0) ? aSourceRate : -aSourceRate) * 2 / 1000;
+
+ // If the number of frames before changing channel count (and thereby
+ // resetting the resampler) is very low, the measured buffering level curve
+ // may look odd, as each resampler reset will reset the (possibly
+ // fractional) output frame counter.
+ const uint32_t numFramesBeforeChangingChannelCount = aSourceRate;
+ uint32_t numFramesAtCurrentChannelCount = 0;
+
+ // 50 seconds, allows for at least 50 correction changes, to stabilize
+ // on the current drift.
+ for (uint32_t n = 0; n < 5000; ++n) {
+ const TrackTime sourceFrames =
+ (n + 1) * (aSourceRate + additionalDriftFrames) / 100 -
+ sourceFramesIteration;
+ const TrackTime targetFrames =
+ (n + 1) * aTargetRate / 100 - targetFramesIteration;
+ AudioSegment inSegment;
+ if (aTestMonoToStereoInput) {
+ // Create the input (sine tone) of two chunks.
+ const TrackTime sourceFramesPart1 = std::min<TrackTime>(
+ sourceFrames, numFramesBeforeChangingChannelCount -
+ numFramesAtCurrentChannelCount);
+ tone.Generate(inSegment, sourceFramesPart1);
+ numFramesAtCurrentChannelCount += sourceFramesPart1;
+ if (numFramesBeforeChangingChannelCount ==
+ numFramesAtCurrentChannelCount) {
+ tone.SetChannelsCount(numChannels = (numChannels % 2) + 1);
+ numFramesAtCurrentChannelCount = sourceFrames - sourceFramesPart1;
+ tone.Generate(inSegment, numFramesAtCurrentChannelCount);
+ }
+ } else {
+ // Create the input (sine tone)
+ tone.Generate(inSegment, sourceFrames);
+ }
+ inToneVerifier.AppendData(inSegment);
+
+ // Get the output of the correction
+ AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
+ EXPECT_EQ(outSegment.GetDuration(), targetFrames);
+ for (AudioSegment::ConstChunkIterator ci(outSegment); !ci.IsEnded();
+ ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+ outToneVerifier.AppendData(outSegment);
+ sourceFramesIteration += sourceFrames;
+ targetFramesIteration += targetFrames;
+ }
+ }
+
+ // Initial buffering is 50ms, which is then expected to be reduced as the
+ // drift adaptation stabilizes.
+ EXPECT_LT(ad.CurrentBuffering(), aSourceRate * 50U / 1000);
+ // Desired buffering should not go lower than some 130% of the source buffer
+ // size per-iteration.
+ EXPECT_GT(ad.CurrentBuffering(), aSourceRate * 10U / 1000);
+
+ EXPECT_EQ(ad.NumUnderruns(), 0U);
+
+ EXPECT_FLOAT_EQ(inToneVerifier.EstimatedFreq(), tone.mFrequency);
+ EXPECT_EQ(inToneVerifier.PreSilenceSamples(), 0U);
+ EXPECT_EQ(inToneVerifier.CountDiscontinuities(), 0U);
+
+ EXPECT_NEAR(outToneVerifier.EstimatedFreq(), tone.mFrequency, 1.0f);
+ // The expected pre-silence is equal to the initial desired buffering (50ms)
+ // minus what is left after resampling the first input segment.
+ const auto buffering = media::TimeUnit::FromSeconds(0.05);
+ const auto sourceStep =
+ media::TimeUnit(aSourceRate * 1002 / 1000 / 100, aSourceRate);
+ const auto targetStep = media::TimeUnit(aTargetRate / 100, aTargetRate);
+ EXPECT_NEAR(static_cast<int64_t>(outToneVerifier.PreSilenceSamples()),
+ (targetStep + buffering - sourceStep)
+ .ToBase(aSourceRate)
+ .ToBase<media::TimeUnit::CeilingPolicy>(aTargetRate)
+ .ToTicksAtRate(aTargetRate),
+ 1U);
+ EXPECT_EQ(outToneVerifier.CountDiscontinuities(), 0U);
+}
+
+TEST(TestAudioDriftCorrection, Basic)
+{
+ printf("Testing AudioCorrection 48 -> 48\n");
+ testAudioCorrection(48000, 48000);
+ printf("Testing AudioCorrection 48 -> 44.1\n");
+ testAudioCorrection(48000, 44100);
+ printf("Testing AudioCorrection 44.1 -> 48\n");
+ testAudioCorrection(44100, 48000);
+ printf("Testing AudioCorrection 23458 -> 25113\n");
+ testAudioCorrection(23458, 25113);
+}
+
+TEST(TestAudioDriftCorrection, MonoToStereoInput)
+{
+ constexpr bool testMonoToStereoInput = true;
+ printf("Testing MonoToStereoInput 48 -> 48\n");
+ testAudioCorrection(48000, 48000, testMonoToStereoInput);
+ printf("Testing MonoToStereoInput 48 -> 44.1\n");
+ testAudioCorrection(48000, 44100, testMonoToStereoInput);
+ printf("Testing MonoToStereoInput 44.1 -> 48\n");
+ testAudioCorrection(44100, 48000, testMonoToStereoInput);
+}
+
+TEST(TestAudioDriftCorrection, NotEnoughFrames)
+{
+ const uint32_t frequency = 100;
+ const uint32_t sampleRateTransmitter = 48000;
+ const uint32_t sampleRateReceiver = 48000;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver,
+ testPrincipal);
+ const uint32_t targetFrames = sampleRateReceiver / 100;
+
+ AudioGenerator<AudioDataValue> tone(1, sampleRateTransmitter, frequency);
+ AudioVerifier<AudioDataValue> outToneVerifier(sampleRateReceiver, frequency);
+
+ for (uint32_t i = 0; i < 7; ++i) {
+ // Input is something small, 10 frames here, in order to dry out fast,
+ // after 4 iterations (pre-buffer = 2400)
+ AudioSegment inSegment;
+ tone.Generate(inSegment, 10);
+
+ AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
+ EXPECT_EQ(outSegment.GetDuration(), targetFrames);
+ EXPECT_FALSE(outSegment.IsNull());
+ for (AudioSegment::ConstChunkIterator ci(outSegment); !ci.IsEnded();
+ ci.Next()) {
+ if (i < 5) {
+ if (!ci->IsNull()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+ }
+ }
+
+ outToneVerifier.AppendData(outSegment);
+ }
+ EXPECT_EQ(ad.BufferSize(), 4800U);
+ EXPECT_EQ(ad.NumUnderruns(), 1u);
+ EXPECT_EQ(outToneVerifier.CountDiscontinuities(), 1u);
+}
+
+TEST(TestAudioDriftCorrection, CrashInAudioResampler)
+{
+ const uint32_t sampleRateTransmitter = 48000;
+ const uint32_t sampleRateReceiver = 48000;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver,
+ testPrincipal);
+ const uint32_t targetFrames = sampleRateReceiver / 100;
+
+ for (uint32_t i = 0; i < 100; ++i) {
+ AudioChunk chunk = CreateAudioChunk<float>(sampleRateTransmitter / 1000, 1,
+ AUDIO_FORMAT_FLOAT32);
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(chunk));
+
+ AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
+ EXPECT_EQ(outSegment.GetDuration(), targetFrames);
+ for (AudioSegment::ConstChunkIterator ci(outSegment); !ci.IsEnded();
+ ci.Next()) {
+ if (!ci->IsNull()) { // Don't check the data if ad is dried out.
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+ }
+ }
+}
+
+TEST(TestAudioDriftCorrection, HighLatencyProducerLowLatencyConsumer)
+{
+ constexpr uint32_t transmitterBlockSize = 2048;
+ constexpr uint32_t receiverBlockSize = 128;
+ constexpr uint32_t sampleRate = 48000;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRate, sampleRate, testPrincipal);
+
+ uint32_t numBlocksProduced = 0;
+ for (uint32_t i = 0; i < (sampleRate / 1000) * 500; i += receiverBlockSize) {
+ AudioSegment inSegment;
+ if ((i / transmitterBlockSize) > numBlocksProduced) {
+ AudioChunk chunk = CreateAudioChunk<float>(transmitterBlockSize, 1,
+ AUDIO_FORMAT_FLOAT32);
+ inSegment.AppendAndConsumeChunk(std::move(chunk));
+ ++numBlocksProduced;
+ }
+
+ AudioSegment outSegment = ad.RequestFrames(inSegment, receiverBlockSize);
+ EXPECT_EQ(outSegment.GetDuration(), receiverBlockSize);
+ }
+
+ // Input is stable so no corrections should occur.
+ EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
+}
+
+TEST(TestAudioDriftCorrection, LargerTransmitterBlockSizeThanDesiredBuffering)
+{
+ constexpr uint32_t transmitterBlockSize = 4096;
+ constexpr uint32_t receiverBlockSize = 128;
+ constexpr uint32_t sampleRate = 48000;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRate, sampleRate, testPrincipal);
+
+ uint32_t numBlocksTransmitted = 0;
+ for (uint32_t i = 0; i < (sampleRate / 1000) * 500; i += receiverBlockSize) {
+ AudioSegment inSegment;
+ if (uint32_t currentBlock = i / transmitterBlockSize;
+ currentBlock > numBlocksTransmitted) {
+ AudioChunk chunk = CreateAudioChunk<float>(transmitterBlockSize, 1,
+ AUDIO_FORMAT_FLOAT32);
+ inSegment.AppendAndConsumeChunk(std::move(chunk));
+ numBlocksTransmitted = currentBlock;
+ }
+
+ AudioSegment outSegment = ad.RequestFrames(inSegment, receiverBlockSize);
+ EXPECT_EQ(outSegment.GetDuration(), receiverBlockSize);
+
+ if (numBlocksTransmitted > 0) {
+ EXPECT_GT(ad.CurrentBuffering(), 0U);
+ }
+ }
+
+ // Input is stable so no corrections should occur.
+ EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
+ // The drift correction buffer size had to be larger than the desired (the
+ // buffer size is twice the initial buffering level), to accomodate the large
+ // input block size.
+ EXPECT_EQ(ad.BufferSize(), 9600U);
+}
+
+TEST(TestAudioDriftCorrection, LargerReceiverBlockSizeThanDesiredBuffering)
+{
+ constexpr uint32_t transmitterBlockSize = 128;
+ constexpr uint32_t receiverBlockSize = 4096;
+ constexpr uint32_t sampleRate = 48000;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRate, sampleRate, testPrincipal);
+
+ for (uint32_t i = 0; i < (sampleRate / 1000) * 500;
+ i += transmitterBlockSize) {
+ AudioSegment inSegment;
+ AudioChunk chunk =
+ CreateAudioChunk<float>(transmitterBlockSize, 1, AUDIO_FORMAT_FLOAT32);
+ inSegment.AppendAndConsumeChunk(std::move(chunk));
+
+ if (i % receiverBlockSize == 0) {
+ AudioSegment outSegment = ad.RequestFrames(inSegment, receiverBlockSize);
+ EXPECT_EQ(outSegment.GetDuration(), receiverBlockSize);
+ }
+
+ if (i >= receiverBlockSize) {
+ EXPECT_GT(ad.CurrentBuffering(), 0U);
+ }
+ }
+
+ // Input is stable so no corrections should occur.
+ EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
+ // The drift correction buffer size had to be larger than the desired (the
+ // buffer size is twice the initial buffering level), to accomodate the large
+ // input block size that gets buffered in the resampler only when processing
+ // output.
+ EXPECT_EQ(ad.BufferSize(), 19200U);
+}
+
+TEST(TestAudioDriftCorrection, DynamicInputBufferSizeChanges)
+{
+ constexpr uint32_t transmitterBlockSize1 = 2048;
+ constexpr uint32_t transmitterBlockSize2 = 4096;
+ constexpr uint32_t receiverBlockSize = 128;
+ constexpr uint32_t sampleRate = 48000;
+ constexpr uint32_t frequencyHz = 100;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioDriftCorrection ad(sampleRate, sampleRate, testPrincipal);
+
+ AudioGenerator<AudioDataValue> tone(1, sampleRate, frequencyHz);
+ AudioVerifier<AudioDataValue> inToneVerifier(sampleRate, frequencyHz);
+ AudioVerifier<AudioDataValue> outToneVerifier(sampleRate, frequencyHz);
+
+ TrackTime totalFramesTransmitted = 0;
+ TrackTime totalFramesReceived = 0;
+
+ const auto produceSomeData = [&](uint32_t aTransmitterBlockSize,
+ uint32_t aDuration) {
+ TrackTime transmittedFramesStart = totalFramesTransmitted;
+ TrackTime receivedFramesStart = totalFramesReceived;
+ uint32_t numBlocksTransmitted = 0;
+ for (uint32_t i = 0; i < aDuration; i += receiverBlockSize) {
+ AudioSegment inSegment;
+ if (((receivedFramesStart - transmittedFramesStart + i) /
+ aTransmitterBlockSize) > numBlocksTransmitted) {
+ tone.Generate(inSegment, aTransmitterBlockSize);
+ MOZ_ASSERT(!inSegment.IsNull());
+ inToneVerifier.AppendData(inSegment);
+ MOZ_ASSERT(!inSegment.IsNull());
+ ++numBlocksTransmitted;
+ totalFramesTransmitted += aTransmitterBlockSize;
+ }
+
+ AudioSegment outSegment = ad.RequestFrames(inSegment, receiverBlockSize);
+ EXPECT_EQ(outSegment.GetDuration(), receiverBlockSize);
+ outToneVerifier.AppendData(outSegment);
+ totalFramesReceived += receiverBlockSize;
+ }
+ };
+
+ produceSomeData(transmitterBlockSize1, 5 * sampleRate);
+ EXPECT_EQ(ad.BufferSize(), 4800U);
+ // Input is stable so no corrections should occur.
+ EXPECT_EQ(ad.NumCorrectionChanges(), 0U);
+ EXPECT_EQ(ad.NumUnderruns(), 0U);
+
+ // Increase input latency. We expect this to underrun, but only once as the
+ // drift correction adapts its buffer size and desired buffering level.
+ produceSomeData(transmitterBlockSize2, 10 * sampleRate);
+ auto numCorrectionChanges = ad.NumCorrectionChanges();
+ EXPECT_EQ(ad.NumUnderruns(), 1U);
+
+ // Adapting to the new input block size should have stabilized.
+ EXPECT_GT(ad.BufferSize(), transmitterBlockSize2);
+ produceSomeData(transmitterBlockSize2, 10 * sampleRate);
+ EXPECT_EQ(ad.NumCorrectionChanges(), numCorrectionChanges);
+ EXPECT_EQ(ad.NumUnderruns(), 1U);
+
+ // Decrease input latency. We expect the drift correction to gradually
+ // decrease its desired buffering level.
+ produceSomeData(transmitterBlockSize1, 100 * sampleRate);
+ numCorrectionChanges = ad.NumCorrectionChanges();
+ EXPECT_EQ(ad.NumUnderruns(), 1U);
+
+ // Adapting to the new input block size should have stabilized.
+ EXPECT_EQ(ad.BufferSize(), 9600U);
+ produceSomeData(transmitterBlockSize1, 20 * sampleRate);
+ EXPECT_NEAR(ad.NumCorrectionChanges(), numCorrectionChanges, 1U);
+ EXPECT_EQ(ad.NumUnderruns(), 1U);
+
+ EXPECT_NEAR(inToneVerifier.EstimatedFreq(), tone.mFrequency, 1.0f);
+ EXPECT_EQ(inToneVerifier.PreSilenceSamples(), 0U);
+ EXPECT_EQ(inToneVerifier.CountDiscontinuities(), 0U);
+
+ EXPECT_NEAR(outToneVerifier.EstimatedFreq(), tone.mFrequency, 1.0f);
+ // The expected pre-silence is equal to the desired buffering plus what's
+ // needed to resample the first input segment.
+ EXPECT_EQ(outToneVerifier.PreSilenceSamples(), 2528U);
+ // One mid-stream period of silence from increasing the input buffer size,
+ // causing an underrun. Counts as two discontinuities.
+ EXPECT_EQ(outToneVerifier.CountDiscontinuities(), 2U);
+}
+
+/**
+ * This is helpful to run together with
+ * MOZ_LOG=raw,DriftControllerGraphs:5 MOZ_LOG_FILE=./plot_values.csv
+ * to be able to plot the step response of a change in source clock rate (i.e.
+ * drift). Useful for calculating and verifying PID coefficients.
+ */
+TEST(TestAudioDriftCorrection, DriftStepResponse)
+{
+ constexpr uint32_t nominalRate = 48000;
+ constexpr uint32_t interval = nominalRate;
+ constexpr uint32_t inputRate = nominalRate * 1005 / 1000; // 0.5% drift
+ constexpr uint32_t inputInterval = inputRate;
+ constexpr uint32_t iterations = 200;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ AudioGenerator<AudioDataValue> tone(1, nominalRate, 440);
+ AudioDriftCorrection ad(nominalRate, nominalRate, testPrincipal);
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ tone.Generate(inSegment, inputInterval / 100);
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ EXPECT_EQ(ad.BufferSize(), 4800U);
+ EXPECT_EQ(ad.NumUnderruns(), 0u);
+}
+
+/**
+ * Similar to DriftStepResponse but will underrun to allow testing the underrun
+ * handling. This is helpful to run together with
+ * MOZ_LOG=raw,DriftControllerGraphs:5 MOZ_LOG_FILE=./plot_values.csv
+ */
+TEST(TestAudioDriftCorrection, DriftStepResponseUnderrun)
+{
+ constexpr uint32_t nominalRate = 48000;
+ constexpr uint32_t interval = nominalRate;
+ constexpr uint32_t iterations = 200;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ uint32_t inputRate = nominalRate * 1005 / 1000; // 0.5% drift
+ uint32_t inputInterval = inputRate;
+ AudioGenerator<AudioDataValue> tone(1, nominalRate, 440);
+ AudioDriftCorrection ad(nominalRate, nominalRate, testPrincipal);
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ tone.Generate(inSegment, inputInterval / 100);
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ inputRate = nominalRate * 998 / 1000; // -0.2% drift
+ inputInterval = inputRate;
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ tone.Generate(inSegment, inputInterval / 100);
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ EXPECT_EQ(ad.BufferSize(), 4800U);
+ EXPECT_EQ(ad.NumUnderruns(), 1u);
+}
+
+/**
+ * Similar to DriftStepResponse but with a high-latency input, and will underrun
+ * to allow testing the underrun handling. This is helpful to run together with
+ * MOZ_LOG=raw,DriftControllerGraphs:5 MOZ_LOG_FILE=./plot_values.csv
+ */
+TEST(TestAudioDriftCorrection, DriftStepResponseUnderrunHighLatencyInput)
+{
+ constexpr uint32_t nominalRate = 48000;
+ constexpr uint32_t interval = nominalRate;
+ constexpr uint32_t iterations = 200;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+ uint32_t inputRate = nominalRate * 1005 / 1000; // 0.5% drift
+ uint32_t inputInterval = inputRate;
+ AudioGenerator<AudioDataValue> tone(1, nominalRate, 440);
+ AudioDriftCorrection ad(nominalRate, nominalRate, testPrincipal);
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ if (i > 0 && i % interval == 0) {
+ tone.Generate(inSegment, inputInterval);
+ }
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ inputRate = nominalRate * 995 / 1000; // -0.5% drift
+ inputInterval = inputRate;
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ if (i > 0 && i % interval == 0) {
+ tone.Generate(inSegment, inputInterval);
+ }
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ EXPECT_EQ(ad.BufferSize(), 220800U);
+ EXPECT_EQ(ad.NumUnderruns(), 1u);
+}
+
+/**
+ * Similar to DriftStepResponse but with a high-latency input, and will overrun
+ * (input callback buffer is larger than AudioDriftCorrection's ring buffer for
+ * input data) to allow testing the overrun handling. This is helpful to run
+ * together with
+ * MOZ_LOG=raw,DriftControllerGraphs:5 MOZ_LOG_FILE=./plot_values.csv
+ */
+TEST(TestAudioDriftCorrection, DriftStepResponseOverrun)
+{
+ constexpr uint32_t nominalRate = 48000;
+ constexpr uint32_t interval = nominalRate;
+ constexpr uint32_t inputRate = nominalRate * 1005 / 1000; // 0.5% drift
+ constexpr uint32_t inputInterval = inputRate;
+ constexpr uint32_t iterations = 200;
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ AudioGenerator<AudioDataValue> tone(1, nominalRate, 440);
+ AudioDriftCorrection ad(nominalRate, nominalRate, testPrincipal);
+
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ tone.Generate(inSegment, inputInterval / 100);
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ // Change input callbacks to 2000ms (+0.5% drift) = 48200 frames, which will
+ // overrun the ring buffer.
+ for (uint32_t i = 0; i < interval * iterations; i += interval / 100) {
+ AudioSegment inSegment;
+ if (i > 0 && i % interval == 0) {
+ // This simulates the input stream latency increasing externally. It's
+ // building up a second worth of data before the next callback. This also
+ // causes an underrun.
+ tone.Generate(inSegment, inputInterval);
+ }
+ ad.RequestFrames(inSegment, interval / 100);
+ }
+
+ EXPECT_EQ(ad.BufferSize(), 105600U);
+ EXPECT_EQ(ad.NumUnderruns(), 1u);
+}
diff --git a/dom/media/driftcontrol/gtest/TestAudioResampler.cpp b/dom/media/driftcontrol/gtest/TestAudioResampler.cpp
new file mode 100644
index 0000000000..f04bc87314
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/TestAudioResampler.cpp
@@ -0,0 +1,677 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "AudioResampler.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+template <class T>
+AudioChunk CreateAudioChunk(uint32_t aFrames, uint32_t aChannels,
+ AudioSampleFormat aSampleFormat) {
+ AudioChunk chunk;
+ nsTArray<nsTArray<T>> buffer;
+ buffer.AppendElements(aChannels);
+
+ nsTArray<const T*> bufferPtrs;
+ bufferPtrs.AppendElements(aChannels);
+
+ for (uint32_t i = 0; i < aChannels; ++i) {
+ T* ptr = buffer[i].AppendElements(aFrames);
+ bufferPtrs[i] = ptr;
+ for (uint32_t j = 0; j < aFrames; ++j) {
+ if (aSampleFormat == AUDIO_FORMAT_FLOAT32) {
+ ptr[j] = 0.01 * j;
+ } else {
+ ptr[j] = j;
+ }
+ }
+ }
+
+ chunk.mBuffer = new mozilla::SharedChannelArrayBuffer(std::move(buffer));
+ chunk.mBufferFormat = aSampleFormat;
+ chunk.mChannelData.AppendElements(aChannels);
+ for (uint32_t i = 0; i < aChannels; ++i) {
+ chunk.mChannelData[i] = bufferPtrs[i];
+ }
+ chunk.mDuration = aFrames;
+ return chunk;
+}
+
+template <class T>
+AudioSegment CreateAudioSegment(uint32_t aFrames, uint32_t aChannels,
+ AudioSampleFormat aSampleFormat) {
+ AudioSegment segment;
+ AudioChunk chunk = CreateAudioChunk<T>(aFrames, aChannels, aSampleFormat);
+ segment.AppendAndConsumeChunk(std::move(chunk));
+ return segment;
+}
+
+TEST(TestAudioResampler, OutAudioSegment_Float)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 21;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioSegment inSegment =
+ CreateAudioSegment<float>(in_frames, channels, AUDIO_FORMAT_FLOAT32);
+ dr.AppendInput(inSegment);
+
+ out_frames = 20u;
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 20);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+
+ for (AudioSegment::ChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ AudioChunk& c = *ci;
+ EXPECT_EQ(c.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c.ChannelCount(), 2u);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ // The first input segment is part of the pre buffer, so 21-10=11 of the
+ // input is silence. They make up 22 silent output frames after
+ // resampling.
+ EXPECT_FLOAT_EQ(c.ChannelData<float>()[0][i], 0.0);
+ EXPECT_FLOAT_EQ(c.ChannelData<float>()[1][i], 0.0);
+ }
+ }
+
+ // Update out rate
+ out_rate = 44100;
+ dr.UpdateOutRate(out_rate);
+ out_frames = in_frames * out_rate / in_rate;
+ EXPECT_EQ(out_frames, 18u);
+ // Even if we provide no input if we have enough buffered input, we can create
+ // output
+ hasUnderrun = false;
+ AudioSegment s1 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s1.GetDuration(), out_frames);
+ EXPECT_EQ(s1.GetType(), MediaSegment::AUDIO);
+ for (AudioSegment::ConstChunkIterator ci(s1); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, OutAudioSegment_Short)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 21;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioSegment inSegment =
+ CreateAudioSegment<short>(in_frames, channels, AUDIO_FORMAT_S16);
+ dr.AppendInput(inSegment);
+
+ out_frames = 20u;
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 20);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+
+ for (AudioSegment::ChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ AudioChunk& c = *ci;
+ EXPECT_EQ(c.mPrincipalHandle, testPrincipal);
+ EXPECT_EQ(c.ChannelCount(), 2u);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ // The first input segment is part of the pre buffer, so 21-10=11 of the
+ // input is silence. They make up 22 silent output frames after
+ // resampling.
+ EXPECT_FLOAT_EQ(c.ChannelData<short>()[0][i], 0.0);
+ EXPECT_FLOAT_EQ(c.ChannelData<short>()[1][i], 0.0);
+ }
+ }
+
+ // Update out rate
+ out_rate = 44100;
+ dr.UpdateOutRate(out_rate);
+ out_frames = in_frames * out_rate / in_rate;
+ EXPECT_EQ(out_frames, 18u);
+ // Even if we provide no input if we have enough buffered input, we can create
+ // output
+ hasUnderrun = false;
+ AudioSegment s1 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s1.GetDuration(), out_frames);
+ EXPECT_EQ(s1.GetType(), MediaSegment::AUDIO);
+ for (AudioSegment::ConstChunkIterator ci(s1); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, OutAudioSegmentLargerThanResampledInput_Float)
+{
+ const uint32_t in_frames = 130;
+ const uint32_t out_frames = 300;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 5;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ PRINCIPAL_HANDLE_NONE);
+
+ AudioSegment inSegment =
+ CreateAudioSegment<float>(in_frames, channels, AUDIO_FORMAT_FLOAT32);
+
+ // Set the pre-buffer.
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(300, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 300);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+
+ dr.AppendInput(inSegment);
+
+ AudioSegment s2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_TRUE(hasUnderrun);
+ EXPECT_EQ(s2.GetDuration(), 300);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+}
+
+TEST(TestAudioResampler, InAudioSegment_Float)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 20;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 10;
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioSegment inSegment;
+
+ AudioChunk chunk1;
+ chunk1.SetNull(in_frames / 2);
+ inSegment.AppendAndConsumeChunk(std::move(chunk1));
+
+ AudioChunk chunk2;
+ nsTArray<nsTArray<float>> buffer;
+ buffer.AppendElements(channels);
+
+ nsTArray<const float*> bufferPtrs;
+ bufferPtrs.AppendElements(channels);
+
+ for (uint32_t i = 0; i < channels; ++i) {
+ float* ptr = buffer[i].AppendElements(5);
+ bufferPtrs[i] = ptr;
+ for (uint32_t j = 0; j < 5; ++j) {
+ ptr[j] = 0.01f * j;
+ }
+ }
+
+ chunk2.mBuffer = new mozilla::SharedChannelArrayBuffer(std::move(buffer));
+ chunk2.mBufferFormat = AUDIO_FORMAT_FLOAT32;
+ chunk2.mChannelData.AppendElements(channels);
+ for (uint32_t i = 0; i < channels; ++i) {
+ chunk2.mChannelData[i] = bufferPtrs[i];
+ }
+ chunk2.mDuration = in_frames / 2;
+ inSegment.AppendAndConsumeChunk(std::move(chunk2));
+
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment outSegment = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // inSegment contains 10 frames, 5 null, 5 non-null. They're part of the pre
+ // buffer which is 10, meaning there are no extra pre buffered silence frames.
+ EXPECT_EQ(outSegment.GetDuration(), out_frames);
+ EXPECT_EQ(outSegment.MaxChannelCount(), 2u);
+
+ // Add another 5 null and 5 non-null frames.
+ dr.AppendInput(inSegment);
+ AudioSegment outSegment2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(outSegment2.GetDuration(), out_frames);
+ EXPECT_EQ(outSegment2.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(outSegment2); !ci.IsEnded();
+ ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, InAudioSegment_Short)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 20;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 10;
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioSegment inSegment;
+
+ // The null chunk at the beginning will be ignored.
+ AudioChunk chunk1;
+ chunk1.SetNull(in_frames / 2);
+ inSegment.AppendAndConsumeChunk(std::move(chunk1));
+
+ AudioChunk chunk2;
+ nsTArray<nsTArray<short>> buffer;
+ buffer.AppendElements(channels);
+
+ nsTArray<const short*> bufferPtrs;
+ bufferPtrs.AppendElements(channels);
+
+ for (uint32_t i = 0; i < channels; ++i) {
+ short* ptr = buffer[i].AppendElements(5);
+ bufferPtrs[i] = ptr;
+ for (uint32_t j = 0; j < 5; ++j) {
+ ptr[j] = j;
+ }
+ }
+
+ chunk2.mBuffer = new mozilla::SharedChannelArrayBuffer(std::move(buffer));
+ chunk2.mBufferFormat = AUDIO_FORMAT_S16;
+ chunk2.mChannelData.AppendElements(channels);
+ for (uint32_t i = 0; i < channels; ++i) {
+ chunk2.mChannelData[i] = bufferPtrs[i];
+ }
+ chunk2.mDuration = in_frames / 2;
+ inSegment.AppendAndConsumeChunk(std::move(chunk2));
+
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment outSegment = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // inSegment contains 10 frames, 5 null, 5 non-null. They're part of the pre
+ // buffer which is 10, meaning there are no extra pre buffered silence frames.
+ EXPECT_EQ(outSegment.GetDuration(), out_frames);
+ EXPECT_EQ(outSegment.MaxChannelCount(), 2u);
+
+ // Add another 5 null and 5 non-null frames.
+ dr.AppendInput(inSegment);
+ AudioSegment outSegment2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(outSegment2.GetDuration(), out_frames);
+ EXPECT_EQ(outSegment2.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(outSegment2); !ci.IsEnded();
+ ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_MonoToStereo)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 0;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioChunk monoChunk =
+ CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(monoChunk));
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ dr.AppendInput(inSegment);
+
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 40);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_StereoToMono)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 0;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioChunk monoChunk =
+ CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ inSegment.AppendAndConsumeChunk(std::move(monoChunk));
+ dr.AppendInput(inSegment);
+
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 40);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s.MaxChannelCount(), 1u);
+ for (AudioSegment::ConstChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_StereoToQuad)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 0;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate),
+ testPrincipal);
+
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+ AudioChunk quadChunk =
+ CreateAudioChunk<float>(in_frames, 4, AUDIO_FORMAT_FLOAT32);
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ inSegment.AppendAndConsumeChunk(std::move(quadChunk));
+ dr.AppendInput(inSegment);
+
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 40u);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+
+ AudioSegment s2 = dr.Resample(out_frames / 2, &hasUnderrun);
+ EXPECT_TRUE(hasUnderrun);
+ EXPECT_EQ(s2.GetDuration(), 20u);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+ for (AudioSegment::ConstChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_QuadToStereo)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit::Zero(), testPrincipal);
+
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+ AudioChunk quadChunk =
+ CreateAudioChunk<float>(in_frames, 4, AUDIO_FORMAT_FLOAT32);
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(quadChunk));
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ dr.AppendInput(inSegment);
+
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ EXPECT_EQ(s.GetDuration(), 40u);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+
+ AudioSegment s2 = dr.Resample(out_frames / 2, &hasUnderrun);
+ EXPECT_TRUE(hasUnderrun);
+ EXPECT_EQ(s2.GetDuration(), 20u);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+ for (AudioSegment::ConstChunkIterator ci(s); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+void printAudioSegment(const AudioSegment& segment);
+
+TEST(TestAudioResampler, ChannelChange_Discontinuity)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ const float amplitude = 0.5;
+ const float frequency = 200;
+ const float phase = 0.0;
+ float time = 0.0;
+ const float deltaTime = 1.0f / static_cast<float>(in_rate);
+
+ uint32_t in_frames = in_rate / 100;
+ uint32_t out_frames = out_rate / 100;
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit::Zero(), testPrincipal);
+
+ AudioChunk monoChunk =
+ CreateAudioChunk<float>(in_frames, 1, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < monoChunk.GetDuration(); ++i) {
+ double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+ monoChunk.ChannelDataForWrite<float>(0)[i] = static_cast<float>(value);
+ time += deltaTime;
+ }
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < stereoChunk.GetDuration(); ++i) {
+ double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+ stereoChunk.ChannelDataForWrite<float>(0)[i] = static_cast<float>(value);
+ if (stereoChunk.ChannelCount() == 2) {
+ stereoChunk.ChannelDataForWrite<float>(1)[i] = value;
+ }
+ time += deltaTime;
+ }
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ // printAudioSegment(inSegment);
+
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s);
+
+ AudioSegment inSegment2;
+ inSegment2.AppendAndConsumeChunk(std::move(monoChunk));
+ // The resampler here is updated due to the channel change and that creates
+ // discontinuity.
+ dr.AppendInput(inSegment2);
+ AudioSegment s2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s2);
+
+ EXPECT_EQ(s2.GetDuration(), 480);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s2.MaxChannelCount(), 1u);
+ for (AudioSegment::ConstChunkIterator ci(s2); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_Discontinuity2)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ const float amplitude = 0.5;
+ const float frequency = 200;
+ const float phase = 0.0;
+ float time = 0.0;
+ const float deltaTime = 1.0f / static_cast<float>(in_rate);
+
+ uint32_t in_frames = in_rate / 100;
+ uint32_t out_frames = out_rate / 100;
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(10, in_rate),
+ testPrincipal);
+
+ AudioChunk monoChunk =
+ CreateAudioChunk<float>(in_frames / 2, 1, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < monoChunk.GetDuration(); ++i) {
+ double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+ monoChunk.ChannelDataForWrite<float>(0)[i] = static_cast<float>(value);
+ time += deltaTime;
+ }
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames / 2, 2, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < stereoChunk.GetDuration(); ++i) {
+ double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+ stereoChunk.ChannelDataForWrite<float>(0)[i] = static_cast<float>(value);
+ if (stereoChunk.ChannelCount() == 2) {
+ stereoChunk.ChannelDataForWrite<float>(1)[i] = value;
+ }
+ time += deltaTime;
+ }
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(monoChunk));
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ // printAudioSegment(inSegment);
+
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment s1 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s1);
+
+ EXPECT_EQ(s1.GetDuration(), 480);
+ EXPECT_EQ(s1.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s1.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(s1); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+
+ // The resampler here is updated due to the channel change and that creates
+ // discontinuity.
+ dr.AppendInput(inSegment);
+ AudioSegment s2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s2);
+
+ EXPECT_EQ(s2.GetDuration(), 480);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s2.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(s2); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
+
+TEST(TestAudioResampler, ChannelChange_Discontinuity3)
+{
+ const PrincipalHandle testPrincipal =
+ MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
+
+ uint32_t in_rate = 48000;
+ uint32_t out_rate = 48000;
+
+ const float amplitude = 0.5;
+ const float frequency = 200;
+ const float phase = 0.0;
+ float time = 0.0;
+ const float deltaTime = 1.0f / static_cast<float>(in_rate);
+
+ uint32_t in_frames = in_rate / 100;
+ uint32_t out_frames = out_rate / 100;
+ AudioResampler dr(in_rate, out_rate, media::TimeUnit(10, in_rate),
+ testPrincipal);
+
+ AudioChunk stereoChunk =
+ CreateAudioChunk<float>(in_frames, 2, AUDIO_FORMAT_FLOAT32);
+ for (uint32_t i = 0; i < stereoChunk.GetDuration(); ++i) {
+ double value = amplitude * sin(2 * M_PI * frequency * time + phase);
+ stereoChunk.ChannelDataForWrite<float>(0)[i] = static_cast<float>(value);
+ if (stereoChunk.ChannelCount() == 2) {
+ stereoChunk.ChannelDataForWrite<float>(1)[i] = value;
+ }
+ time += deltaTime;
+ }
+
+ AudioSegment inSegment;
+ inSegment.AppendAndConsumeChunk(std::move(stereoChunk));
+ // printAudioSegment(inSegment);
+
+ dr.AppendInput(inSegment);
+ bool hasUnderrun = false;
+ AudioSegment s = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s);
+
+ EXPECT_EQ(s.GetDuration(), 480);
+ EXPECT_EQ(s.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s.MaxChannelCount(), 2u);
+
+ // The resampler here is updated due to the rate change. This is because the
+ // in and out rate was the same so a pass through logic was used. By updating
+ // the out rate to something different than the in rate, the resampler will
+ // start being used and discontinuity will exist.
+ dr.UpdateOutRate(out_rate + 400);
+ dr.AppendInput(inSegment);
+ AudioSegment s2 = dr.Resample(out_frames, &hasUnderrun);
+ EXPECT_FALSE(hasUnderrun);
+ // printAudioSegment(s2);
+
+ EXPECT_EQ(s2.GetDuration(), 480);
+ EXPECT_EQ(s2.GetType(), MediaSegment::AUDIO);
+ EXPECT_EQ(s2.MaxChannelCount(), 2u);
+ for (AudioSegment::ConstChunkIterator ci(s2); !ci.IsEnded(); ci.Next()) {
+ EXPECT_EQ(ci->mPrincipalHandle, testPrincipal);
+ }
+}
diff --git a/dom/media/driftcontrol/gtest/TestDriftController.cpp b/dom/media/driftcontrol/gtest/TestDriftController.cpp
new file mode 100644
index 0000000000..33486f945f
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/TestDriftController.cpp
@@ -0,0 +1,168 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "DriftController.h"
+#include "mozilla/Maybe.h"
+
+using namespace mozilla;
+
+TEST(TestDriftController, Basic)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t buffered = 5 * 480;
+ constexpr uint32_t bufferedLow = 3 * 480;
+ constexpr uint32_t bufferedHigh = 7 * 480;
+
+ DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+
+ // The adjustment interval is 1s.
+ const auto oneSec = media::TimeUnit(48000, 48000);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+}
+
+TEST(TestDriftController, BasicResampler)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t buffered = 5 * 240;
+ constexpr uint32_t bufferedLow = 3 * 240;
+ constexpr uint32_t bufferedHigh = 7 * 240;
+
+ DriftController c(24000, 48000, media::TimeUnit::FromSeconds(0.05));
+
+ // The adjustment interval is 1s.
+ const auto oneSec = media::TimeUnit(48000, 48000);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // low
+ c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+
+ // high
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // high
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 47964u);
+}
+
+TEST(TestDriftController, BufferedInput)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t buffered = 5 * 480;
+ constexpr uint32_t bufferedLow = 3 * 480;
+ constexpr uint32_t bufferedHigh = 7 * 480;
+
+ DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // The adjustment interval is 1s.
+ const auto oneSec = media::TimeUnit(48000, 48000);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // 0 buffered when updating correction
+ c.UpdateClock(oneSec, oneSec, 0, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+}
+
+TEST(TestDriftController, BufferedInputWithResampling)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t buffered = 5 * 240;
+ constexpr uint32_t bufferedLow = 3 * 240;
+ constexpr uint32_t bufferedHigh = 7 * 240;
+
+ DriftController c(24000, 48000, media::TimeUnit::FromSeconds(0.05));
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // The adjustment interval is 1s.
+ const auto oneSec = media::TimeUnit(24000, 24000);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // 0 buffered when updating correction
+ c.UpdateClock(oneSec, oneSec, 0, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48048u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 47952u);
+}
+
+TEST(TestDriftController, SmallError)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t buffered = 5 * 480;
+ constexpr uint32_t bufferedLow = buffered - 48;
+ constexpr uint32_t bufferedHigh = buffered + 48;
+
+ DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ // The adjustment interval is 1s.
+ const auto oneSec = media::TimeUnit(48000, 48000);
+
+ c.UpdateClock(oneSec, oneSec, buffered, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+ c.UpdateClock(oneSec, oneSec, bufferedHigh, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000u);
+}
+
+TEST(TestDriftController, SmallBufferedFrames)
+{
+ // The buffer level is the only input to the controller logic.
+ constexpr uint32_t bufferedLow = 3 * 480;
+
+ DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05));
+ media::TimeUnit oneSec = media::TimeUnit::FromSeconds(1);
+ media::TimeUnit hundredMillis = oneSec / 10;
+
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+ for (uint32_t i = 0; i < 9; ++i) {
+ c.UpdateClock(hundredMillis, hundredMillis, bufferedLow, 0);
+ }
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48000U);
+ c.UpdateClock(hundredMillis, hundredMillis, bufferedLow, 0);
+ EXPECT_EQ(c.GetCorrectedTargetRate(), 48048U);
+}
diff --git a/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp b/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp
new file mode 100644
index 0000000000..fb8ac52ae4
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/TestDynamicResampler.cpp
@@ -0,0 +1,722 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "DynamicResampler.h"
+
+using namespace mozilla;
+
+TEST(TestDynamicResampler, SameRates_Float1)
+{
+ const uint32_t in_frames = 100;
+ const uint32_t out_frames = 100;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+
+ // float in_ch1[] = {.1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0};
+ // float in_ch2[] = {.1, .2, .3, .4, .5, .6, .7, .8, .9, 1.0};
+ float in_ch1[in_frames] = {};
+ float in_ch2[in_frames] = {};
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[out_frames] = {};
+ float out_ch2[out_frames] = {};
+
+ // Warm up with zeros
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_FLOAT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_FLOAT_EQ(in_ch2[i], out_ch2[i]);
+ }
+
+ // Continue with non zero
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = in_ch2[i] = 0.01f * i;
+ }
+ dr.AppendInput(in_buffer, in_frames);
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_FLOAT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_FLOAT_EQ(in_ch2[i], out_ch2[i]);
+ }
+
+ // No more frames in the input buffer
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, SameRates_Short1)
+{
+ uint32_t in_frames = 2;
+ uint32_t out_frames = 2;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+
+ short in_ch1[] = {1, 2, 3};
+ short in_ch2[] = {4, 5, 6};
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ short out_ch1[3] = {};
+ short out_ch2[3] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_EQ(in_ch2[i], out_ch2[i]);
+ }
+
+ // No more frames in the input buffer
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, SameRates_Float2)
+{
+ uint32_t in_frames = 3;
+ uint32_t out_frames = 2;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ float in_ch1[] = {0.1, 0.2, 0.3};
+ float in_ch2[] = {0.4, 0.5, 0.6};
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[3] = {};
+ float out_ch2[3] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_FLOAT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_FLOAT_EQ(in_ch2[i], out_ch2[i]);
+ }
+
+ out_frames = 1;
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_FLOAT_EQ(in_ch1[i + 2], out_ch1[i]);
+ EXPECT_FLOAT_EQ(in_ch2[i + 2], out_ch2[i]);
+ }
+
+ // No more frames, the input buffer has drained
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, SameRates_Short2)
+{
+ uint32_t in_frames = 3;
+ uint32_t out_frames = 2;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+
+ short in_ch1[] = {1, 2, 3};
+ short in_ch2[] = {4, 5, 6};
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ short out_ch1[3] = {};
+ short out_ch2[3] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_EQ(in_ch2[i], out_ch2[i]);
+ }
+
+ out_frames = 1;
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_EQ(in_ch1[i + 2], out_ch1[i]);
+ EXPECT_EQ(in_ch2[i + 2], out_ch2[i]);
+ }
+
+ // No more frames, the input buffer has drained
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, SameRates_Float3)
+{
+ uint32_t in_frames = 2;
+ uint32_t out_frames = 3;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ float in_ch1[] = {0.1, 0.2, 0.3};
+ float in_ch2[] = {0.4, 0.5, 0.6};
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[3] = {};
+ float out_ch2[3] = {};
+
+ // Not enough frames in the input buffer
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+
+ // Add one frame
+ in_buffer[0] = in_ch1 + 2;
+ in_buffer[1] = in_ch2 + 2;
+ dr.AppendInput(in_buffer, 1);
+ out_frames = 1;
+ hasUnderrun = dr.Resample(out_ch1 + 2, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2 + 2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < 3; ++i) {
+ EXPECT_FLOAT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_FLOAT_EQ(in_ch2[i], out_ch2[i]);
+ }
+}
+
+TEST(TestDynamicResampler, SameRates_Short3)
+{
+ uint32_t in_frames = 2;
+ uint32_t out_frames = 3;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+
+ short in_ch1[] = {1, 2, 3};
+ short in_ch2[] = {4, 5, 6};
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ short out_ch1[3] = {};
+ short out_ch2[3] = {};
+
+ // Not enough frames in the input buffer
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+
+ // Add one frame
+ in_buffer[0] = in_ch1 + 2;
+ in_buffer[1] = in_ch2 + 2;
+ dr.AppendInput(in_buffer, 1);
+ out_frames = 1;
+ hasUnderrun = dr.Resample(out_ch1 + 2, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2 + 2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < 3; ++i) {
+ EXPECT_EQ(in_ch1[i], out_ch1[i]);
+ EXPECT_EQ(in_ch2[i], out_ch2[i]);
+ }
+}
+
+TEST(TestDynamicResampler, UpdateOutRate_Float)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 20;
+
+ DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+
+ float in_ch1[10] = {};
+ float in_ch2[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = in_ch2[i] = 0.01f * i;
+ }
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[40] = {};
+ float out_ch2[40] = {};
+
+ dr.AppendInputSilence(pre_buffer - in_frames);
+ dr.AppendInput(in_buffer, in_frames);
+ out_frames = 20u;
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ // Half the input pre-buffer (10) is silence, and half the output (20).
+ EXPECT_FLOAT_EQ(out_ch1[i], 0.0);
+ EXPECT_FLOAT_EQ(out_ch2[i], 0.0);
+ }
+
+ // Update out rate
+ out_rate = 44100;
+ dr.UpdateResampler(out_rate, channels);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+ out_frames = in_frames * out_rate / in_rate;
+ EXPECT_EQ(out_frames, 18u);
+ // Even if we provide no input if we have enough buffered input, we can create
+ // output
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, UpdateOutRate_Short)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 40;
+ uint32_t channels = 2;
+ uint32_t in_rate = 24000;
+ uint32_t out_rate = 48000;
+
+ uint32_t pre_buffer = 20;
+
+ DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+
+ short in_ch1[10] = {};
+ short in_ch2[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = in_ch2[i] = i;
+ }
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ short out_ch1[40] = {};
+ short out_ch2[40] = {};
+
+ dr.AppendInputSilence(pre_buffer - in_frames);
+ dr.AppendInput(in_buffer, in_frames);
+ out_frames = 20u;
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ // Half the input pre-buffer (10) is silence, and half the output (20).
+ EXPECT_EQ(out_ch1[i], 0.0);
+ EXPECT_EQ(out_ch2[i], 0.0);
+ }
+
+ // Update out rate
+ out_rate = 44100;
+ dr.UpdateResampler(out_rate, channels);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+ out_frames = in_frames * out_rate / in_rate;
+ EXPECT_EQ(out_frames, 18u);
+ // Even if we provide no input if we have enough buffered input, we can create
+ // output
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, BigRangeOutRates_Float)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 10;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+ uint32_t pre_buffer = 20;
+
+ DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ const uint32_t in_capacity = 40;
+ float in_ch1[in_capacity] = {};
+ float in_ch2[in_capacity] = {};
+ for (uint32_t i = 0; i < in_capacity; ++i) {
+ in_ch1[i] = in_ch2[i] = 0.01f * i;
+ }
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ const uint32_t out_capacity = 1000;
+ float out_ch1[out_capacity] = {};
+ float out_ch2[out_capacity] = {};
+
+ for (uint32_t rate = 10000; rate < 90000; ++rate) {
+ out_rate = rate;
+ dr.UpdateResampler(out_rate, channels);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+ in_frames = 20; // more than we need
+ out_frames = in_frames * out_rate / in_rate;
+ for (uint32_t y = 0; y < 2; ++y) {
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ }
+ }
+}
+
+TEST(TestDynamicResampler, BigRangeOutRates_Short)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 10;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 44100;
+ uint32_t pre_buffer = 20;
+
+ DynamicResampler dr(in_rate, out_rate, media::TimeUnit(pre_buffer, in_rate));
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+
+ const uint32_t in_capacity = 40;
+ short in_ch1[in_capacity] = {};
+ short in_ch2[in_capacity] = {};
+ for (uint32_t i = 0; i < in_capacity; ++i) {
+ in_ch1[i] = in_ch2[i] = i;
+ }
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ const uint32_t out_capacity = 1000;
+ short out_ch1[out_capacity] = {};
+ short out_ch2[out_capacity] = {};
+
+ for (uint32_t rate = 10000; rate < 90000; ++rate) {
+ out_rate = rate;
+ dr.UpdateResampler(out_rate, channels);
+ in_frames = 20; // more than we need
+ out_frames = in_frames * out_rate / in_rate;
+ for (uint32_t y = 0; y < 2; ++y) {
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ }
+ }
+}
+
+TEST(TestDynamicResampler, UpdateChannels_Float)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 10;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 48000;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+ float in_ch1[10] = {};
+ float in_ch2[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = in_ch2[i] = 0.01f * i;
+ }
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[10] = {};
+ float out_ch2[10] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+
+ // Add 3rd channel
+ dr.UpdateResampler(out_rate, 3);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), 3u);
+
+ float in_ch3[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch3[i] = 0.01f * i;
+ }
+ in_buffer.AppendElement();
+ in_buffer[2] = in_ch3;
+ float out_ch3[10] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch3, out_frames, 2);
+ EXPECT_FALSE(hasUnderrun);
+
+ float in_ch4[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch3[i] = 0.01f * i;
+ }
+ in_buffer.AppendElement();
+ in_buffer[3] = in_ch4;
+ float out_ch4[10] = {};
+
+ dr.UpdateResampler(out_rate, 4);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), 4u);
+ dr.AppendInput(in_buffer, in_frames);
+
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch3, out_frames, 2);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch4, out_frames, 3);
+ EXPECT_FALSE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, UpdateChannels_Short)
+{
+ uint32_t in_frames = 10;
+ uint32_t out_frames = 10;
+ uint32_t channels = 2;
+ uint32_t in_rate = 44100;
+ uint32_t out_rate = 48000;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_S16);
+
+ short in_ch1[10] = {};
+ short in_ch2[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = in_ch2[i] = i;
+ }
+ AutoTArray<const short*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ short out_ch1[10] = {};
+ short out_ch2[10] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+
+ // Add 3rd channel
+ dr.UpdateResampler(out_rate, 3);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), 3u);
+
+ short in_ch3[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch3[i] = i;
+ }
+ in_buffer.AppendElement();
+ in_buffer[2] = in_ch3;
+ short out_ch3[10] = {};
+
+ dr.AppendInput(in_buffer, in_frames);
+
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch3, out_frames, 2);
+ EXPECT_FALSE(hasUnderrun);
+
+ // Check update with AudioSegment
+ short in_ch4[10] = {};
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch3[i] = i;
+ }
+ in_buffer.AppendElement();
+ in_buffer[3] = in_ch4;
+ short out_ch4[10] = {};
+
+ dr.UpdateResampler(out_rate, 4);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), 4u);
+ dr.AppendInput(in_buffer, in_frames);
+
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch3, out_frames, 2);
+ EXPECT_FALSE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch4, out_frames, 3);
+ EXPECT_FALSE(hasUnderrun);
+}
+
+TEST(TestDynamicResampler, Underrun)
+{
+ const uint32_t in_frames = 100;
+ const uint32_t out_frames = 200;
+ uint32_t channels = 2;
+ uint32_t in_rate = 48000;
+ uint32_t out_rate = 48000;
+
+ DynamicResampler dr(in_rate, out_rate);
+ dr.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+ EXPECT_EQ(dr.GetOutRate(), out_rate);
+ EXPECT_EQ(dr.GetChannels(), channels);
+
+ float in_ch1[in_frames] = {};
+ float in_ch2[in_frames] = {};
+ AutoTArray<const float*, 2> in_buffer;
+ in_buffer.AppendElements(channels);
+ in_buffer[0] = in_ch1;
+ in_buffer[1] = in_ch2;
+
+ float out_ch1[out_frames] = {};
+ float out_ch2[out_frames] = {};
+
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ in_ch1[i] = 0.01f * i;
+ in_ch2[i] = -0.01f * i;
+ }
+ dr.AppendInput(in_buffer, in_frames);
+ bool hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+ for (uint32_t i = 0; i < in_frames; ++i) {
+ EXPECT_EQ(out_ch1[i], in_ch1[i]);
+ EXPECT_EQ(out_ch2[i], in_ch2[i]);
+ }
+ for (uint32_t i = in_frames; i < out_frames; ++i) {
+ EXPECT_EQ(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_EQ(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+
+ // No more frames in the input buffer
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_EQ(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_EQ(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+
+ // Now try with resampling.
+ dr.UpdateResampler(out_rate / 2, channels);
+ dr.AppendInput(in_buffer, in_frames);
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+ // There is some buffering in the resampler, which is why the below is not
+ // exact.
+ for (uint32_t i = 0; i < 50; ++i) {
+ EXPECT_GT(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_LT(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+ for (uint32_t i = 50; i < 54; ++i) {
+ EXPECT_NE(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_NE(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+ for (uint32_t i = 54; i < out_frames; ++i) {
+ EXPECT_EQ(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_EQ(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+
+ // No more frames in the input buffer
+ hasUnderrun = dr.Resample(out_ch1, out_frames, 0);
+ EXPECT_TRUE(hasUnderrun);
+ hasUnderrun = dr.Resample(out_ch2, out_frames, 1);
+ EXPECT_TRUE(hasUnderrun);
+ for (uint32_t i = 0; i < out_frames; ++i) {
+ EXPECT_EQ(out_ch1[i], 0.0f) << "for i=" << i;
+ EXPECT_EQ(out_ch2[i], 0.0f) << "for i=" << i;
+ }
+}
diff --git a/dom/media/driftcontrol/gtest/moz.build b/dom/media/driftcontrol/gtest/moz.build
new file mode 100644
index 0000000000..d645760841
--- /dev/null
+++ b/dom/media/driftcontrol/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestAudioChunkList.cpp",
+ "TestAudioDriftCorrection.cpp",
+ "TestAudioResampler.cpp",
+ "TestDriftController.cpp",
+ "TestDynamicResampler.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/media",
+ "/dom/media/driftcontrol",
+ "/dom/media/gtest",
+]
+
+FINAL_LIBRARY = "xul-gtest"