blob: 9678486c3089236664b06cae07c57a6ab42b56a8 [file] [log] [blame] [edit]
/*
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/video_coding/timing/timestamp_extrapolator.h"
#include <stdint.h>
#include <cstdlib>
#include <limits>
#include <optional>
#include <string>
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "system_wrappers/include/clock.h"
#include "system_wrappers/include/metrics.h"
#include "test/create_test_field_trials.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
using ::testing::Eq;
using ::testing::Optional;
namespace {
constexpr Frequency kRtpHz = Frequency::KiloHertz(90);
constexpr Frequency k25Fps = Frequency::Hertz(25);
constexpr TimeDelta k25FpsDelay = 1 / k25Fps;
} // namespace
TEST(TimestampExtrapolatorTest, ExtrapolationOccursAfter2Packets) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
// No packets so no timestamp.
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(90000), Eq(std::nullopt));
uint32_t rtp = 90000;
clock.AdvanceTime(k25FpsDelay);
// First result is a bit confusing since it is based off the "start" time,
// which is arbitrary.
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
}
TEST(TimestampExtrapolatorTest, ResetsAfter10SecondPause) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 90000;
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
rtp += 10 * kRtpHz.hertz();
clock.AdvanceTime(TimeDelta::Seconds(10) + TimeDelta::Micros(1));
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
}
TEST(TimestampExtrapolatorTest, TimestampExtrapolatesMultipleRtpWrapArounds) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = std::numeric_limits<uint32_t>::max();
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
// One overflow. Static cast to avoid undefined behaviour with +=.
rtp += static_cast<uint32_t>(kRtpHz / k25Fps);
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
// Assert that extrapolation works across the boundary as expected.
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
// This is not quite 1s since the math always rounds up.
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp - 90000),
Optional(clock.CurrentTime() - TimeDelta::Millis(999)));
// In order to avoid a wrap arounds reset, add a packet every 10s until we
// overflow twice.
constexpr TimeDelta kRtpOverflowDelay =
std::numeric_limits<uint32_t>::max() / kRtpHz;
const Timestamp overflow_time = clock.CurrentTime() + kRtpOverflowDelay * 2;
while (clock.CurrentTime() < overflow_time) {
clock.AdvanceTime(TimeDelta::Seconds(10));
// Static-cast before += to avoid undefined behaviour of overflow.
rtp += static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
}
}
TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAround) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 0;
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
// Go backwards! Static cast to avoid undefined behaviour with -=.
rtp -= static_cast<uint32_t>(kRtpHz.hertz());
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime() - TimeDelta::Seconds(1)));
}
TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAroundSecondScenario) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 0;
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
// Go backwards! Static cast to avoid undefined behaviour with -=.
rtp -= static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp), std::nullopt);
}
TEST(TimestampExtrapolatorTest, Slow90KHzClock) {
// This simulates a slow camera, which produces frames at 24Hz instead of
// 25Hz. The extrapolator should be able to resolve this with enough data.
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
constexpr TimeDelta k24FpsDelay = 1 / Frequency::Hertz(24);
uint32_t rtp = 90000;
// Slow camera will increment RTP at 25 FPS rate even though its producing at
// 24 FPS. After 25 frames the extrapolator should settle at this rate.
for (int i = 0; i < 25; ++i) {
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k24FpsDelay);
}
// The camera would normally produce 25 frames in 90K ticks, but is slow
// so takes 1s + k24FpsDelay for 90K ticks.
constexpr Frequency kSlowRtpHz = 90000 / (25 * k24FpsDelay);
// The extrapolator will be predicting that time at millisecond precision.
auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
ASSERT_TRUE(ts.has_value());
EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
}
TEST(TimestampExtrapolatorTest, Fast90KHzClock) {
// This simulates a fast camera, which produces frames at 26Hz instead of
// 25Hz. The extrapolator should be able to resolve this with enough data.
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
constexpr TimeDelta k26FpsDelay = 1 / Frequency::Hertz(26);
uint32_t rtp = 90000;
// Fast camera will increment RTP at 25 FPS rate even though its producing at
// 26 FPS. After 25 frames the extrapolator should settle at this rate.
for (int i = 0; i < 25; ++i) {
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k26FpsDelay);
}
// The camera would normally produce 25 frames in 90K ticks, but is slow
// so takes 1s + k24FpsDelay for 90K ticks.
constexpr Frequency kSlowRtpHz = 90000 / (25 * k26FpsDelay);
// The extrapolator will be predicting that time at millisecond precision.
auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
ASSERT_TRUE(ts.has_value());
EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
}
TEST(TimestampExtrapolatorTest, TimestampJump) {
// This simulates a jump in RTP timestamp, which could occur if a camera was
// swapped for example.
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 90000;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
// Jump RTP.
uint32_t new_rtp = 1337 * 90000;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
new_rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(new_rtp),
Optional(clock.CurrentTime()));
}
TEST(TimestampExtrapolatorTest, GapInReceivedFrames) {
SimulatedClock clock(
Timestamp::Seconds(std::numeric_limits<uint32_t>::max() / 90000 - 31));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = std::numeric_limits<uint32_t>::max();
clock.AdvanceTime(k25FpsDelay);
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += 30 * 90000;
clock.AdvanceTime(TimeDelta::Seconds(30));
ts_extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
Optional(clock.CurrentTime()));
}
TEST(TimestampExtrapolatorTest, EstimatedClockDriftHistogram) {
const std::string kHistogramName = "WebRTC.Video.EstimatedClockDrift_ppm";
constexpr int kPpmTolerance = 50;
constexpr int kToPpmFactor = 1e6;
constexpr int kMinimumSamples = 3000;
constexpr Frequency k24Fps = Frequency::Hertz(24);
constexpr TimeDelta k24FpsDelay = 1 / k24Fps;
// This simulates a remote clock without drift with frames produced at 25 fps.
// Local scope to trigger the destructor of TimestampExtrapolator.
{
// Clear all histogram data.
metrics::Reset();
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 90000;
for (int i = 0; i < kMinimumSamples; ++i) {
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
}
}
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
const int kExpectedIdealClockDriftPpm = 0;
EXPECT_NEAR(kExpectedIdealClockDriftPpm, metrics::MinSample(kHistogramName),
kPpmTolerance);
// This simulates a slow remote clock, where the RTP timestamps are
// incremented as if the camera was 25 fps even though frames arrive at 24
// fps. Local scope to trigger the destructor of TimestampExtrapolator.
{
// Clear all histogram data.
metrics::Reset();
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 90000;
for (int i = 0; i < kMinimumSamples; ++i) {
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k24FpsDelay);
}
}
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
const int kExpectedSlowClockDriftPpm =
std::abs(k24Fps / k25Fps - 1.0) * kToPpmFactor;
EXPECT_NEAR(kExpectedSlowClockDriftPpm, metrics::MinSample(kHistogramName),
kPpmTolerance);
// This simulates a fast remote clock, where the RTP timestamps are
// incremented as if the camera was 24 fps even though frames arrive at 25
// fps. Local scope to trigger the destructor of TimestampExtrapolator.
{
// Clear all histogram data.
metrics::Reset();
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator ts_extrapolator(clock.CurrentTime(),
CreateTestFieldTrials());
uint32_t rtp = 90000;
for (int i = 0; i < kMinimumSamples; ++i) {
ts_extrapolator.Update(clock.CurrentTime(), rtp);
rtp += kRtpHz / k24Fps;
clock.AdvanceTime(k25FpsDelay);
}
}
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
const int kExpectedFastClockDriftPpm = (k25Fps / k24Fps - 1.0) * kToPpmFactor;
EXPECT_NEAR(kExpectedFastClockDriftPpm, metrics::MinSample(kHistogramName),
kPpmTolerance);
}
TEST(TimestampExtrapolatorTest, SetsValidConfig) {
SimulatedClock clock(Timestamp::Millis(1337));
// clang-format off
TimestampExtrapolator ts_extrapolator(
clock.CurrentTime(), CreateTestFieldTrials(
"WebRTC-TimestampExtrapolatorConfig/"
"hard_reset_timeout:1s,"
"hard_reset_rtp_timestamp_jump_threshold:45000,"
"outlier_rejection_startup_delay:123,"
"outlier_rejection_max_consecutive:456,"
"outlier_rejection_forgetting_factor:0.987,"
"outlier_rejection_stddev:3.5,"
"alarm_threshold:123,"
"acc_drift:456,"
"acc_max_error:789,"
"reset_full_cov_on_alarm:true/"));
// clang-format on
TimestampExtrapolator::Config config = ts_extrapolator.GetConfigForTest();
EXPECT_TRUE(config.OutlierRejectionEnabled());
EXPECT_EQ(config.hard_reset_timeout, TimeDelta::Seconds(1));
EXPECT_EQ(config.hard_reset_rtp_timestamp_jump_threshold, 45000);
EXPECT_EQ(config.outlier_rejection_startup_delay, 123);
EXPECT_EQ(config.outlier_rejection_max_consecutive, 456);
EXPECT_EQ(config.outlier_rejection_forgetting_factor, 0.987);
EXPECT_EQ(config.outlier_rejection_stddev, 3.5);
EXPECT_EQ(config.alarm_threshold, 123);
EXPECT_EQ(config.acc_drift, 456);
EXPECT_EQ(config.acc_max_error, 789);
EXPECT_TRUE(config.reset_full_cov_on_alarm);
}
TEST(TimestampExtrapolatorTest, DoesNotSetInvalidConfig) {
SimulatedClock clock(Timestamp::Millis(1337));
// clang-format off
TimestampExtrapolator ts_extrapolator(
clock.CurrentTime(), CreateTestFieldTrials(
"WebRTC-TimestampExtrapolatorConfig/"
"hard_reset_timeout:-1s,"
"hard_reset_rtp_timestamp_jump_threshold:-1,"
"outlier_rejection_startup_delay:-1,"
"outlier_rejection_max_consecutive:0,"
"outlier_rejection_forgetting_factor:1.1,"
"outlier_rejection_stddev:-1,"
"alarm_threshold:-123,"
"acc_drift:-456,"
"acc_max_error:-789/"));
// clang-format on
TimestampExtrapolator::Config config = ts_extrapolator.GetConfigForTest();
EXPECT_FALSE(config.OutlierRejectionEnabled());
EXPECT_EQ(config.hard_reset_timeout, TimeDelta::Seconds(10));
EXPECT_EQ(config.hard_reset_rtp_timestamp_jump_threshold, 900000);
EXPECT_EQ(config.outlier_rejection_startup_delay, 300);
EXPECT_EQ(config.outlier_rejection_max_consecutive, 150);
EXPECT_EQ(config.outlier_rejection_forgetting_factor, 0.999);
EXPECT_FALSE(config.outlier_rejection_stddev.has_value());
EXPECT_EQ(config.alarm_threshold, 60000);
EXPECT_EQ(config.acc_drift, 6600);
EXPECT_EQ(config.acc_max_error, 7000);
}
TEST(TimestampExtrapolatorTest, ExtrapolationNotAffectedByRtpTimestampJump) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator extrapolator(
clock.CurrentTime(),
CreateTestFieldTrials("WebRTC-TimestampExtrapolatorConfig/"
"outlier_rejection_stddev:3,hard_reset_rtp_"
"timestamp_jump_threshold:900000/"));
// Stabilize filter.
uint32_t rtp = 0;
for (int i = 0; i < 2000; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
}
// Last frame before jump is expected on time.
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
// Next frame arrives on time, but with a 20 second RTP timestamp jump.
rtp += 2 * 900000; // 20 seconds.
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
// First frame after jump is expected on time.
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
}
TEST(TimestampExtrapolatorTest, ExtrapolationNotAffectedByFrameOutliers) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator extrapolator(
clock.CurrentTime(),
CreateTestFieldTrials(
"WebRTC-TimestampExtrapolatorConfig/outlier_rejection_stddev:3/"));
// Stabilize filter.
uint32_t rtp = 0;
for (int i = 0; i < 2000; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
}
// Last frame before outlier arrives on time.
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
// Outlier frame arrives 1000ms late, but is expected on time.
rtp += kRtpHz / k25Fps;
Timestamp expected = clock.CurrentTime() + k25FpsDelay;
clock.AdvanceTime(TimeDelta::Millis(1000));
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected);
// Congested frames arrive back-to-back, but are expected on time.
for (int i = 0; i < 24; ++i) {
rtp += kRtpHz / k25Fps;
expected += k25FpsDelay;
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected);
}
// Regular frame after outliers arrives on time.
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
}
TEST(TimestampExtrapolatorTest,
ExtrapolationAffectedByFrameOutliersAfterRejectionPeriod) {
SimulatedClock clock(Timestamp::Millis(1337));
TimestampExtrapolator extrapolator(
clock.CurrentTime(),
CreateTestFieldTrials(
"WebRTC-TimestampExtrapolatorConfig/"
"outlier_rejection_stddev:3,outlier_rejection_max_consecutive:20/"));
// Stabilize filter.
uint32_t rtp = 0;
for (int i = 0; i < 2000; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
}
// Last frame before outlier arrives on time.
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
// Outlier frame arrives 1000ms late, but is expected on time.
rtp += kRtpHz / k25Fps;
Timestamp expected = clock.CurrentTime() + k25FpsDelay;
clock.AdvanceTime(TimeDelta::Millis(1000));
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected);
// Congested frames arrive back-to-back. The first 19 are expected on time.
for (int i = 0; i < 19; ++i) {
rtp += kRtpHz / k25Fps;
expected += k25FpsDelay;
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected);
}
// After the 20 consecutive outlier frames, the filter soft resets and starts
// expecting frames on the new baseline, which is partially congested.
rtp += kRtpHz / k25Fps;
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
for (int i = 0; i < 4; ++i) {
rtp += kRtpHz / k25Fps;
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp),
clock.CurrentTime() + (i + 1) * k25FpsDelay);
}
// Now we have caught up with realtime, but since the soft reset happened
// 4 frames too early, the new baseline is 4 * 1000/25 = 160ms off.
for (int i = 0; i < 10; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp),
clock.CurrentTime() + 4 * k25FpsDelay);
}
// Let the filter stabilize at a realtime rate again.
for (int i = 0; i < 2000; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
}
// After the stabilization, the 160ms congestion offset has been canceled.
for (int i = 0; i < 10; ++i) {
rtp += kRtpHz / k25Fps;
clock.AdvanceTime(k25FpsDelay);
extrapolator.Update(clock.CurrentTime(), rtp);
EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime());
}
}
} // namespace webrtc