/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioDriftCorrection.h" #include "AudioGenerator.h" #include "AudioVerifier.h" #include "mozilla/StaticPrefs_media.h" #include "nsContentUtils.h" #include "gmock/gmock.h" #include "gtest/gtest-printers.h" #include "gtest/gtest.h" using namespace mozilla; // Runs UpdateClock() and checks that the reported correction level doesn't // change for enough time to trigger a correction update on the first // following UpdateClock(). Returns the first reported correction level. static float RunUntilCorrectionUpdate(ClockDrift& aC, uint32_t aSource, uint32_t aTarget, uint32_t aBuffering, uint32_t aSaturation, uint32_t aSourceOffset = 0, uint32_t aTargetOffset = 0) { Maybe correction; for (uint32_t s = aSourceOffset, t = aTargetOffset; s < aC.mSourceRate && t < aC.mTargetRate; s += aSource, t += aTarget) { aC.UpdateClock(aSource, aTarget, aBuffering, aSaturation); if (correction) { EXPECT_FLOAT_EQ(aC.GetCorrection(), *correction) << "s=" << s << "; t=" << t; } else { correction = Some(aC.GetCorrection()); } } return *correction; }; TEST(TestClockDrift, Basic) { // Keep buffered frames to the wanted level in order to not affect that test. const uint32_t buffered = 5 * 480; ClockDrift c(48000, 48000, buffered); EXPECT_EQ(c.GetCorrection(), 1.0); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480, 480, buffered, buffered), 1.0); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480 + 48, buffered, buffered), 1.0); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480, 480, buffered, buffered), 1.06); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480 + 48, 480, buffered, buffered), 1.024); c.UpdateClock(0, 0, 5 * 480, 5 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 0.95505452); } TEST(TestClockDrift, BasicResampler) { // Keep buffered frames to the wanted level in order to not affect that test. const uint32_t buffered = 5 * 240; ClockDrift c(24000, 48000, buffered); // Keep buffered frames to the wanted level in order to not affect that test. EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 240, 480, buffered, buffered), 1.0); // +10% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240, 480 + 48, buffered, buffered), 1.0); // +10% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240 + 24, 480, buffered, buffered), 1.06); // -10% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240, 480 - 48, buffered, buffered), 0.96945453); // +5%, -5% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240 + 12, 480 - 24, buffered, buffered), 0.92778182); c.UpdateClock(0, 0, buffered, buffered); EXPECT_FLOAT_EQ(c.GetCorrection(), 0.91396987); } TEST(TestClockDrift, BufferedInput) { ClockDrift c(48000, 48000, 5 * 480); EXPECT_EQ(c.GetCorrection(), 1.0); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480, 480, 5 * 480, 8 * 480), 1.0); c.UpdateClock(480, 480, 0, 10 * 480); // 0 buffered when updating correction EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0473685); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480, 3 * 480, 7 * 480, 480, 480), 1.0473685); c.UpdateClock(480, 480, 3 * 480, 7 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0311923); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480, 5 * 480, 5 * 480, 480, 480), 1.0311923); c.UpdateClock(480, 480, 5 * 480, 5 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0124769); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480, 7 * 480, 3 * 480, 480, 480), 1.0124769); c.UpdateClock(480, 480, 7 * 480, 3 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 0.99322605); } TEST(TestClockDrift, BufferedInputWithResampling) { ClockDrift c(24000, 48000, 5 * 240); EXPECT_EQ(c.GetCorrection(), 1.0); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 240, 480, 5 * 240, 5 * 240), 1.0); c.UpdateClock(240, 480, 0, 10 * 240); // 0 buffered when updating correction EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0473685); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240, 480, 3 * 240, 7 * 240, 240, 480), 1.0473685); c.UpdateClock(240, 480, 3 * 240, 7 * 240); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0311923); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240, 480, 5 * 240, 5 * 240, 240, 480), 1.0311923); c.UpdateClock(240, 480, 5 * 240, 5 * 240); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0124769); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 240, 480, 7 * 240, 3 * 240, 240, 480), 1.0124769); c.UpdateClock(240, 480, 7 * 240, 3 * 240); EXPECT_FLOAT_EQ(c.GetCorrection(), 0.99322605); } TEST(TestClockDrift, Clamp) { // Keep buffered frames to the wanted level in order to not affect that test. const uint32_t buffered = 5 * 480; ClockDrift c(48000, 48000, buffered); // +30% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480 + 3 * 48, buffered, buffered), 1.0); // -30% EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480 - 3 * 48, buffered, buffered), 1.1); c.UpdateClock(0, 0, buffered, buffered); EXPECT_FLOAT_EQ(c.GetCorrection(), 0.9); } TEST(TestClockDrift, SmallDiff) { // Keep buffered frames to the wanted level in order to not affect that test. const uint32_t buffered = 5 * 480; ClockDrift c(48000, 48000, buffered); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480 + 4, 480, buffered, buffered), 1.0); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480 + 5, 480, buffered, buffered), 0.99504131); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480, 480, buffered, buffered), 0.991831); EXPECT_FLOAT_EQ(RunUntilCorrectionUpdate(c, 480, 480 + 4, buffered, buffered), 0.99673241); c.UpdateClock(0, 0, buffered, buffered); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.003693); } TEST(TestClockDrift, SmallBufferedFrames) { ClockDrift c(48000, 48000, 5 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0); for (uint32_t i = 0; i < 10; ++i) { c.UpdateClock(480, 480, 5 * 480, 5 * 480); } EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0); c.UpdateClock(480, 480, 0, 10 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1); EXPECT_FLOAT_EQ( RunUntilCorrectionUpdate(c, 480, 480, 5 * 480, 5 * 480, 24000, 24000), 1.1); c.UpdateClock(480, 480, 0, 10 * 480); EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1); } // Print the mono channel of a segment. void printAudioSegment(const AudioSegment& segment) { for (AudioSegment::ConstChunkIterator iter(segment); !iter.IsEnded(); iter.Next()) { const AudioChunk& c = *iter; for (uint32_t i = 0; i < c.GetDuration(); ++i) { if (c.mBufferFormat == AUDIO_FORMAT_FLOAT32) { printf("%f\n", c.ChannelData()[0][i]); } else { printf("%d\n", c.ChannelData()[0][i]); } } } } template AudioChunk CreateAudioChunk(uint32_t aFrames, uint32_t aChannels, AudioSampleFormat aSampleFormat); void testAudioCorrection(int32_t aSourceRate, int32_t aTargetRate) { const uint32_t sampleRateTransmitter = aSourceRate; const uint32_t sampleRateReceiver = aTargetRate; const uint32_t frequency = 100; const uint32_t buffering = StaticPrefs::media_clockdrift_buffering(); const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver, buffering, testPrincipal); AudioGenerator tone(1, sampleRateTransmitter, frequency); AudioVerifier inToneVerifier(sampleRateTransmitter, frequency); AudioVerifier outToneVerifier(sampleRateReceiver, frequency); uint32_t sourceFrames; const uint32_t targetFrames = sampleRateReceiver / 100; // Run for some time: 3 * 1050 = 3150 iterations for (uint32_t j = 0; j < 3; ++j) { // apply some drift if (j % 2 == 0) { sourceFrames = sampleRateTransmitter * /*1.02*/ 102 / 100 / /*1s->10ms*/ 100; } else { sourceFrames = sampleRateTransmitter * /*0.98*/ 98 / 100 / /*1s->10ms*/ 100; } // 10.5 seconds, allows for at least 10 correction changes, to stabilize // around the desired buffer. for (uint32_t n = 0; n < 1050; ++n) { // Create the input (sine tone) AudioSegment inSegment; tone.Generate(inSegment, sourceFrames); inToneVerifier.AppendData(inSegment); // Print the input for debugging // printAudioSegment(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); } // Print the output for debugging // printAudioSegment(outSegment); outToneVerifier.AppendData(outSegment); } } const int32_t expectedBuffering = ad.mDesiredBuffering - sampleRateTransmitter / 100 /* 10ms */; EXPECT_NEAR(ad.CurrentBuffering(), expectedBuffering, 512); 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 50ms plus the resampling. EXPECT_GE(outToneVerifier.PreSilenceSamples(), aTargetRate * 50 / 1000U); 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); } void testMonoToStereoInput(uint32_t aSourceRate, uint32_t aTargetRate) { const uint32_t frequency = 100; const uint32_t sampleRateTransmitter = aSourceRate; const uint32_t sampleRateReceiver = aTargetRate; const uint32_t buffering = StaticPrefs::media_clockdrift_buffering(); const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver, buffering, testPrincipal); AudioGenerator tone(1, sampleRateTransmitter, frequency); AudioVerifier inToneVerify(sampleRateTransmitter, frequency); AudioVerifier outToneVerify(sampleRateReceiver, frequency); uint32_t sourceFrames; const uint32_t targetFrames = sampleRateReceiver / 100; // Run for some time: 6 * 250 = 1500 iterations for (uint32_t j = 0; j < 6; ++j) { // apply some drift if (j % 2 == 0) { sourceFrames = sampleRateTransmitter / 100 + 10; } else { sourceFrames = sampleRateTransmitter / 100 - 10; } for (uint32_t n = 0; n < 250; ++n) { // Create the input (sine tone) of two chunks. AudioSegment inSegment; tone.Generate(inSegment, sourceFrames / 2); tone.SetChannelsCount(2); tone.Generate(inSegment, sourceFrames / 2); tone.SetChannelsCount(1); inToneVerify.AppendData(inSegment); // Print the input for debugging // printAudioSegment(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); } // Print the output for debugging // printAudioSegment(outSegment); outToneVerify.AppendData(outSegment); } } EXPECT_EQ(inToneVerify.EstimatedFreq(), frequency); EXPECT_EQ(inToneVerify.PreSilenceSamples(), 0U); EXPECT_EQ(inToneVerify.CountDiscontinuities(), 0U); EXPECT_GT(outToneVerify.CountDiscontinuities(), 0U) << "Expect discontinuities"; EXPECT_NE(outToneVerify.EstimatedFreq(), frequency) << "Estimation is not accurate due to discontinuities"; // The expected pre-silence is 50ms plus the resampling. However, due to // discontinuities pre-silence is expected only in the first iteration which // is routhly a little more than 400 frames for the chosen sample rates. EXPECT_GT(outToneVerify.PreSilenceSamples(), 400U); } TEST(TestAudioDriftCorrection, MonoToStereoInput) { testMonoToStereoInput(48000, 48000); testMonoToStereoInput(48000, 44100); testMonoToStereoInput(44100, 48000); } TEST(TestAudioDriftCorrection, NotEnoughFrames) { const uint32_t sampleRateTransmitter = 48000; const uint32_t sampleRateReceiver = 48000; const uint32_t buffering = StaticPrefs::media_clockdrift_buffering(); const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver, buffering, testPrincipal); const uint32_t targetFrames = sampleRateReceiver / 100; for (uint32_t i = 0; i < 7; ++i) { // Input is something small, 10 frames here, in order to dry out fast, // after 4 iterations AudioChunk chunk = CreateAudioChunk(10, 1, AUDIO_FORMAT_FLOAT32); AudioSegment inSegment; inSegment.AppendAndConsumeChunk(std::move(chunk)); AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames); EXPECT_EQ(outSegment.GetDuration(), targetFrames); if (i < 5) { EXPECT_FALSE(outSegment.IsNull()); for (AudioSegment::ConstChunkIterator ci(outSegment); !ci.IsEnded(); ci.Next()) { EXPECT_EQ(ci->mPrincipalHandle, testPrincipal); } } else { // Last 2 iterations, the 5th and 6th, will be null. It has used all // buffered data so the output is silence. EXPECT_TRUE(outSegment.IsNull()); } } } TEST(TestAudioDriftCorrection, CrashInAudioResampler) { const uint32_t sampleRateTransmitter = 48000; const uint32_t sampleRateReceiver = 48000; const uint32_t buffering = StaticPrefs::media_clockdrift_buffering(); const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver, buffering, testPrincipal); const uint32_t targetFrames = sampleRateReceiver / 100; for (uint32_t i = 0; i < 100; ++i) { AudioChunk chunk = CreateAudioChunk(sampleRateTransmitter / 1000, 1, AUDIO_FORMAT_FLOAT32); AudioSegment inSegment; inSegment.AppendAndConsumeChunk(std::move(chunk)); AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames); EXPECT_EQ(outSegment.GetDuration(), targetFrames); if (!outSegment.IsNull()) { // Don't check the data if ad is dried out. for (AudioSegment::ConstChunkIterator ci(outSegment); !ci.IsEnded(); ci.Next()) { EXPECT_EQ(ci->mPrincipalHandle, testPrincipal); } } } }