diff options
Diffstat (limited to 'dom/media/driftcontrol/gtest')
-rw-r--r-- | dom/media/driftcontrol/gtest/TestAudioChunkList.cpp | 226 | ||||
-rw-r--r-- | dom/media/driftcontrol/gtest/TestAudioDriftCorrection.cpp | 529 | ||||
-rw-r--r-- | dom/media/driftcontrol/gtest/TestAudioResampler.cpp | 677 | ||||
-rw-r--r-- | dom/media/driftcontrol/gtest/TestDriftController.cpp | 168 | ||||
-rw-r--r-- | dom/media/driftcontrol/gtest/TestDynamicResampler.cpp | 722 | ||||
-rw-r--r-- | dom/media/driftcontrol/gtest/moz.build | 21 |
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" |