/* -*- 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; using TimeUnit = media::TimeUnit; // Advance the output by the specified duration, using a calculated input // packet duration that provides the specified buffering level. void AdvanceByOutputDuration(TimeUnit* aCurrentBuffered, DriftController* aController, TimeUnit aOutputDuration, uint32_t aNextBufferedInputFrames) { uint32_t nominalSourceRate = aController->mSourceRate; uint32_t nominalTargetRate = aController->mTargetRate; uint32_t correctedRate = aController->GetCorrectedSourceRate(); // Use a denominator to exactly track (1/nominalTargetRate)ths of // durations in seconds of input frames buffered in the resampler. *aCurrentBuffered = aCurrentBuffered->ToBase( static_cast(nominalSourceRate) * nominalTargetRate); // Buffered input frames to feed the output are removed first, so that the // number of input frames required can be calculated. aCurrentBuffered may // temporarily become negative. *aCurrentBuffered -= aOutputDuration.ToBase(*aCurrentBuffered) * correctedRate / nominalSourceRate; // Determine the input duration (aligned to input frames) that would provide // the specified buffering level when rounded down to the nearest input // frame. int64_t currentBufferedInputFrames = aCurrentBuffered->ToBase(nominalSourceRate) .ToTicksAtRate(nominalSourceRate); TimeUnit inputDuration( CheckedInt64(aNextBufferedInputFrames) - currentBufferedInputFrames, nominalSourceRate); EXPECT_GE(inputDuration.ToTicksAtRate(nominalSourceRate), 0); *aCurrentBuffered += inputDuration; // The buffer size is not used in the controller logic. uint32_t bufferSize = 0; aController->UpdateClock(inputDuration, aOutputDuration, aNextBufferedInputFrames, bufferSize); } TEST(TestDriftController, Basic) { constexpr uint32_t buffered = 5 * 480; constexpr uint32_t bufferedLow = 3 * 480; constexpr uint32_t bufferedHigh = 7 * 480; TimeUnit currentBuffered(buffered, 48000); DriftController c(48000, 48000, currentBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U); // The adjustment interval is 1s. const auto oneSec = media::TimeUnit(48000, 48000); uint32_t stepsPerSec = 50; media::TimeUnit stepDuration = oneSec / stepsPerSec; for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedLow); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47957u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47957u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48005u); } TEST(TestDriftController, BasicResampler) { // This test is equivalent to Basic, but for the output sample rate, so // input buffer frame counts should be equal to those in Basic. constexpr uint32_t buffered = 5 * 480; constexpr uint32_t bufferedLow = 3 * 480; constexpr uint32_t bufferedHigh = 7 * 480; TimeUnit currentBuffered(buffered, 48000); DriftController c(48000, 24000, currentBuffered); // The adjustment interval is 1s. const auto oneSec = media::TimeUnit(48000, 48000); uint32_t stepsPerSec = 50; media::TimeUnit stepDuration = oneSec / stepsPerSec; for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // low for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedLow); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47957u); // high for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47957u); // high for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48005u); } TEST(TestDriftController, BufferedInput) { constexpr uint32_t buffered = 5 * 480; constexpr uint32_t bufferedLow = 3 * 480; constexpr uint32_t bufferedHigh = 7 * 480; TimeUnit currentBuffered(buffered, 48000); DriftController c(48000, 48000, currentBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // The adjustment interval is 1s. const auto oneSec = media::TimeUnit(48000, 48000); uint32_t stepsPerSec = 20; media::TimeUnit stepDuration = oneSec / stepsPerSec; for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // 0 buffered when updating correction for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, 0); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47990u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedLow); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47971u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47960u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } // Hysteresis keeps the corrected rate the same. EXPECT_EQ(c.GetCorrectedSourceRate(), 47960u); } TEST(TestDriftController, BufferedInputWithResampling) { // This test is equivalent to BufferedInput, but for the output sample rate, // so input buffer frame counts should be equal to those in BufferedInput. constexpr uint32_t buffered = 5 * 480; constexpr uint32_t bufferedLow = 3 * 480; constexpr uint32_t bufferedHigh = 7 * 480; TimeUnit currentBuffered(buffered, 48000); DriftController c(48000, 24000, currentBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // The adjustment interval is 1s. const auto oneSec = media::TimeUnit(24000, 24000); uint32_t stepsPerSec = 20; media::TimeUnit stepDuration = oneSec / stepsPerSec; for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // 0 buffered when updating correction for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, 0); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47990u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedLow); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47971u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 47960u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } // Hysteresis keeps the corrected rate the same. EXPECT_EQ(c.GetCorrectedSourceRate(), 47960u); } TEST(TestDriftController, SmallError) { constexpr uint32_t buffered = 5 * 480; constexpr uint32_t bufferedLow = buffered - 48; constexpr uint32_t bufferedHigh = buffered + 48; TimeUnit currentBuffered(buffered, 48000); DriftController c(48000, 48000, currentBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); // The adjustment interval is 1s. const auto oneSec = media::TimeUnit(48000, 48000); uint32_t stepsPerSec = 25; media::TimeUnit stepDuration = oneSec / stepsPerSec; for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, buffered); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedLow); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); for (uint32_t i = 0; i < stepsPerSec; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, stepDuration, bufferedHigh); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000u); } TEST(TestDriftController, SmallBufferedFrames) { constexpr uint32_t bufferedLow = 3 * 480; DriftController c(48000, 48000, media::TimeUnit::FromSeconds(0.05)); media::TimeUnit oneSec = media::TimeUnit::FromSeconds(1); uint32_t stepsPerSec = 40; media::TimeUnit stepDuration = oneSec / stepsPerSec; EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U); for (uint32_t i = 0; i < stepsPerSec - 1; ++i) { c.UpdateClock(stepDuration, stepDuration, bufferedLow, 0); } EXPECT_EQ(c.GetCorrectedSourceRate(), 48000U); c.UpdateClock(stepDuration, stepDuration, bufferedLow, 0); EXPECT_EQ(c.GetCorrectedSourceRate(), 47996U); } TEST(TestDriftController, VerySmallBufferedFrames) { uint32_t bufferedLow = 1; uint32_t nominalRate = 48000; DriftController c(nominalRate, nominalRate, media::TimeUnit::FromSeconds(1)); EXPECT_EQ(c.GetCorrectedSourceRate(), nominalRate); TimeUnit currentBuffered(bufferedLow, 48000); media::TimeUnit hundredMillis = media::TimeUnit(100, 1000); uint32_t previousCorrected = nominalRate; // Perform enough steps (1500 seconds) that the corrected rate can // get to its lower bound, without underflowing zero. for (uint32_t i = 0; i < 15000; ++i) { // The input packet size is reduced each iteration by as much as possible // without completely draining the buffer. AdvanceByOutputDuration(¤tBuffered, &c, hundredMillis, bufferedLow); uint32_t correctedRate = c.GetCorrectedSourceRate(); EXPECT_LE(correctedRate, previousCorrected) << "for i=" << i; EXPECT_GT(correctedRate, 0u) << "for i=" << i; previousCorrected = correctedRate; } // Check that the corrected rate has reached, does not go beyond, and does // not bounce off its lower bound. EXPECT_EQ(previousCorrected, 1u); for (uint32_t i = 15000; i < 15010; ++i) { AdvanceByOutputDuration(¤tBuffered, &c, hundredMillis, bufferedLow); EXPECT_EQ(c.GetCorrectedSourceRate(), 1u) << "for i=" << i; } } TEST(TestDriftController, SmallStepResponse) { // The DriftController is configured with nominal source rate a little less // than the actual rate. uint32_t nominalTargetRate = 48000; uint32_t nominalSourceRate = 48000; uint32_t actualSourceRate = 48000 * 1001 / 1000; // +0.1% drift TimeUnit desiredBuffered = TimeUnit::FromSeconds(0.05); // 50 ms DriftController c(nominalSourceRate, nominalTargetRate, desiredBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), nominalSourceRate); uint32_t stepsPerSec = 25; // Initial buffer level == desired. Choose a base to exactly track // fractions of frames buffered in the resampler. TimeUnit buffered = desiredBuffered.ToBase(nominalSourceRate * stepsPerSec); media::TimeUnit inputStepDuration(actualSourceRate, stepsPerSec * nominalSourceRate); media::TimeUnit outputStepDuration(nominalTargetRate, stepsPerSec * nominalTargetRate); // Perform enough steps to observe convergence. uint32_t iterationCount = 200 /*seconds*/ * stepsPerSec; for (uint32_t i = 0; i < iterationCount; ++i) { uint32_t correctedRate = c.GetCorrectedSourceRate(); buffered += TimeUnit(CheckedInt64(actualSourceRate) - correctedRate, stepsPerSec * nominalSourceRate); // The buffer size is not used in the controller logic. c.UpdateClock(inputStepDuration, outputStepDuration, buffered.ToTicksAtRate(nominalSourceRate), 0); if (outputStepDuration * i > TimeUnit::FromSeconds(50) && /* Corrections are performed only once per second. */ i % stepsPerSec == 0) { EXPECT_EQ(c.GetCorrectedSourceRate(), actualSourceRate) << "for i=" << i; EXPECT_NEAR(buffered.ToTicksAtRate(nominalSourceRate), desiredBuffered.ToTicksAtRate(nominalSourceRate), 10) << "for i=" << i; } } } TEST(TestDriftController, LargeStepResponse) { // The DriftController is configured with nominal source rate much less than // the actual rate. The large difference between nominal and actual // produces large PID terms and capping of the change in resampler input // rate to nominalRate/1000. This does not correspond exactly to an // expected use case, but tests the stability of the response when changes // are capped. uint32_t nominalTargetRate = 48000; uint32_t nominalSourceRate = 48000 * 7 / 8; uint32_t actualSourceRate = 48000; TimeUnit desiredBuffered(actualSourceRate * 10, nominalSourceRate); DriftController c(nominalSourceRate, nominalTargetRate, desiredBuffered); EXPECT_EQ(c.GetCorrectedSourceRate(), nominalSourceRate); uint32_t stepsPerSec = 20; // Initial buffer level == desired. Choose a base to exactly track // fractions of frames buffered in the resampler. TimeUnit buffered = desiredBuffered.ToBase(nominalSourceRate * stepsPerSec); media::TimeUnit inputStepDuration(actualSourceRate, stepsPerSec * nominalSourceRate); media::TimeUnit outputStepDuration(nominalTargetRate, stepsPerSec * nominalTargetRate); // Changes in the corrected rate are limited to nominalRate/1000 per second. // Perform enough steps to get from nominal to actual source rate and then // observe convergence. uint32_t iterationCount = 8 * stepsPerSec * 1000 * (actualSourceRate - nominalSourceRate) / nominalSourceRate; EXPECT_GT(outputStepDuration * (iterationCount - 1), TimeUnit::FromSeconds(1020)); for (uint32_t i = 0; i < iterationCount; ++i) { uint32_t correctedRate = c.GetCorrectedSourceRate(); buffered += TimeUnit(CheckedInt64(actualSourceRate) - correctedRate, stepsPerSec * nominalSourceRate); // The buffer size is not used in the controller logic. c.UpdateClock(inputStepDuration, outputStepDuration, buffered.ToTicksAtRate(nominalSourceRate), 0); if (outputStepDuration * i > TimeUnit::FromSeconds(1020) && /* Corrections are performed only once per second. */ i % stepsPerSec == 0) { EXPECT_EQ(c.GetCorrectedSourceRate(), actualSourceRate) << "for i=" << i; EXPECT_NEAR(buffered.ToTicksAtRate(nominalSourceRate), desiredBuffered.ToTicksAtRate(nominalSourceRate), 10) << "for i=" << i; } } }