Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  TestDriftController.cpp   Sprache: C

 
/* -*- 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<int64_t>(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<TimeUnit::FloorPolicy>(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;
    }
  }
}

Messung V0.5
C=86 H=-22 G=62

¤ Dauer der Verarbeitung: 0.10 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge