/* Copyright (c) 2014 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/jitter_estimator.h" #include #include #include #include #include #include "absl/types/optional.h" #include "api/array_view.h" #include "api/field_trials.h" #include "api/units/data_size.h" #include "api/units/frequency.h" #include "api/units/time_delta.h" #include "rtc_base/numerics/histogram_percentile_counter.h" #include "rtc_base/time_utils.h" #include "system_wrappers/include/clock.h" #include "test/gtest.h" namespace webrtc { namespace { // Generates some simple test data in the form of a sawtooth wave. class ValueGenerator { public: explicit ValueGenerator(int32_t amplitude) : amplitude_(amplitude), counter_(0) {} virtual ~ValueGenerator() = default; TimeDelta Delay() const { return TimeDelta::Millis((counter_ % 11) - 5) * amplitude_; } DataSize FrameSize() const { return DataSize::Bytes(1000 + Delay().ms() / 5); } void Advance() { ++counter_; } private: const int32_t amplitude_; int64_t counter_; }; class JitterEstimatorTest : public ::testing::Test { protected: explicit JitterEstimatorTest(const std::string& field_trials) : fake_clock_(0), field_trials_(FieldTrials::CreateNoGlobal(field_trials)), estimator_(&fake_clock_, *field_trials_) {} JitterEstimatorTest() : JitterEstimatorTest("") {} virtual ~JitterEstimatorTest() {} void Run(int duration_s, int framerate_fps, ValueGenerator& gen) { TimeDelta tick = 1 / Frequency::Hertz(framerate_fps); for (int i = 0; i < duration_s * framerate_fps; ++i) { estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); fake_clock_.AdvanceTime(tick); gen.Advance(); } } SimulatedClock fake_clock_; std::unique_ptr field_trials_; JitterEstimator estimator_; }; TEST_F(JitterEstimatorTest, SteadyStateConvergence) { ValueGenerator gen(10); Run(/*duration_s=*/60, /*framerate_fps=*/30, gen); EXPECT_EQ(estimator_.GetJitterEstimate(0, absl::nullopt).ms(), 54); } TEST_F(JitterEstimatorTest, SizeOutlierIsNotRejectedAndIncreasesJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/60, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // A single outlier frame size... estimator_.UpdateEstimate(gen.Delay(), 10 * gen.FrameSize()); TimeDelta outlier_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // ...changes the estimate. EXPECT_GT(outlier_jitter.ms(), 1.25 * steady_state_jitter.ms()); } TEST_F(JitterEstimatorTest, LowFramerateDisablesJitterEstimator) { ValueGenerator gen(10); // At 5 fps, we disable jitter delay altogether. TimeDelta time_delta = 1 / Frequency::Hertz(5); for (int i = 0; i < 60; ++i) { estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); fake_clock_.AdvanceTime(time_delta); if (i > 2) EXPECT_EQ(estimator_.GetJitterEstimate(0, absl::nullopt), TimeDelta::Zero()); gen.Advance(); } } TEST_F(JitterEstimatorTest, RttMultAddCap) { std::vector> jitter_by_rtt_mult_cap; jitter_by_rtt_mult_cap.emplace_back( /*rtt_mult_add_cap=*/TimeDelta::Millis(10), /*long_tail_boundary=*/1000); jitter_by_rtt_mult_cap.emplace_back( /*rtt_mult_add_cap=*/TimeDelta::Millis(200), /*long_tail_boundary=*/1000); for (auto& [rtt_mult_add_cap, jitter] : jitter_by_rtt_mult_cap) { estimator_.Reset(); ValueGenerator gen(50); TimeDelta time_delta = 1 / Frequency::Hertz(30); constexpr TimeDelta kRtt = TimeDelta::Millis(250); for (int i = 0; i < 100; ++i) { estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); fake_clock_.AdvanceTime(time_delta); estimator_.FrameNacked(); estimator_.UpdateRtt(kRtt); jitter.Add( estimator_.GetJitterEstimate(/*rtt_mult=*/1.0, rtt_mult_add_cap) .ms()); gen.Advance(); } } // 200ms cap should result in at least 25% higher max compared to 10ms. EXPECT_GT(*jitter_by_rtt_mult_cap[1].second.GetPercentile(1.0), *jitter_by_rtt_mult_cap[0].second.GetPercentile(1.0) * 1.25); } // By default, the `JitterEstimator` is not robust against single large frames. TEST_F(JitterEstimatorTest, Single2xFrameSizeImpactsJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/60, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // A single outlier frame size... estimator_.UpdateEstimate(gen.Delay(), 2 * gen.FrameSize()); TimeDelta outlier_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // ...impacts the estimate. EXPECT_GT(outlier_jitter.ms(), steady_state_jitter.ms()); } // Under the default config, congested frames are used when calculating the // noise variance, meaning that they will impact the final jitter estimate. TEST_F(JitterEstimatorTest, CongestedFrameImpactsJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/10, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // Congested frame... estimator_.UpdateEstimate(-10 * gen.Delay(), 0.1 * gen.FrameSize()); TimeDelta outlier_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // ...impacts the estimate. EXPECT_GT(outlier_jitter.ms(), steady_state_jitter.ms()); } TEST_F(JitterEstimatorTest, EmptyFieldTrialsParsesToUnsetConfig) { JitterEstimator::Config config = estimator_.GetConfigForTest(); EXPECT_FALSE(config.avg_frame_size_median); EXPECT_FALSE(config.max_frame_size_percentile.has_value()); EXPECT_FALSE(config.frame_size_window.has_value()); EXPECT_FALSE(config.num_stddev_delay_clamp.has_value()); EXPECT_FALSE(config.num_stddev_delay_outlier.has_value()); EXPECT_FALSE(config.num_stddev_size_outlier.has_value()); EXPECT_FALSE(config.congestion_rejection_factor.has_value()); EXPECT_TRUE(config.estimate_noise_when_congested); } class FieldTrialsOverriddenJitterEstimatorTest : public JitterEstimatorTest { protected: FieldTrialsOverriddenJitterEstimatorTest() : JitterEstimatorTest( "WebRTC-JitterEstimatorConfig/" "avg_frame_size_median:true," "max_frame_size_percentile:0.9," "frame_size_window:30," "num_stddev_delay_clamp:1.1," "num_stddev_delay_outlier:2," "num_stddev_size_outlier:3.1," "congestion_rejection_factor:-1.55," "estimate_noise_when_congested:false/") {} ~FieldTrialsOverriddenJitterEstimatorTest() {} }; TEST_F(FieldTrialsOverriddenJitterEstimatorTest, FieldTrialsParsesCorrectly) { JitterEstimator::Config config = estimator_.GetConfigForTest(); EXPECT_TRUE(config.avg_frame_size_median); EXPECT_EQ(*config.max_frame_size_percentile, 0.9); EXPECT_EQ(*config.frame_size_window, 30); EXPECT_EQ(*config.num_stddev_delay_clamp, 1.1); EXPECT_EQ(*config.num_stddev_delay_outlier, 2.0); EXPECT_EQ(*config.num_stddev_size_outlier, 3.1); EXPECT_EQ(*config.congestion_rejection_factor, -1.55); EXPECT_FALSE(config.estimate_noise_when_congested); } TEST_F(FieldTrialsOverriddenJitterEstimatorTest, DelayOutlierIsRejectedAndMaintainsJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/60, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // A single outlier frame size... estimator_.UpdateEstimate(10 * gen.Delay(), gen.FrameSize()); TimeDelta outlier_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // ...does not change the estimate. EXPECT_EQ(outlier_jitter.ms(), steady_state_jitter.ms()); } // The field trial is configured to be robust against the `(1 - 0.9) = 10%` // largest frames over a window of length `30`. TEST_F(FieldTrialsOverriddenJitterEstimatorTest, Four2xFrameSizesImpactJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/60, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // Three outlier frames do not impact the jitter estimate. for (int i = 0; i < 3; ++i) { estimator_.UpdateEstimate(gen.Delay(), 2 * gen.FrameSize()); } TimeDelta outlier_jitter_3x = estimator_.GetJitterEstimate(0, absl::nullopt); EXPECT_EQ(outlier_jitter_3x.ms(), steady_state_jitter.ms()); // Four outlier frames do impact the jitter estimate. estimator_.UpdateEstimate(gen.Delay(), 2 * gen.FrameSize()); TimeDelta outlier_jitter_4x = estimator_.GetJitterEstimate(0, absl::nullopt); EXPECT_GT(outlier_jitter_4x.ms(), outlier_jitter_3x.ms()); } // When so configured, congested frames are NOT used when calculating the // noise variance, meaning that they will NOT impact the final jitter estimate. TEST_F(FieldTrialsOverriddenJitterEstimatorTest, CongestedFrameDoesNotImpactJitterEstimate) { ValueGenerator gen(10); // Steady state. Run(/*duration_s=*/10, /*framerate_fps=*/30, gen); TimeDelta steady_state_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // Congested frame... estimator_.UpdateEstimate(-10 * gen.Delay(), 0.1 * gen.FrameSize()); TimeDelta outlier_jitter = estimator_.GetJitterEstimate(0, absl::nullopt); // ...does not impact the estimate. EXPECT_EQ(outlier_jitter.ms(), steady_state_jitter.ms()); } class MisconfiguredFieldTrialsJitterEstimatorTest : public JitterEstimatorTest { protected: MisconfiguredFieldTrialsJitterEstimatorTest() : JitterEstimatorTest( "WebRTC-JitterEstimatorConfig/" "max_frame_size_percentile:-0.9," "frame_size_window:-1," "num_stddev_delay_clamp:-1.9," "num_stddev_delay_outlier:-2," "num_stddev_size_outlier:-23.1/") {} ~MisconfiguredFieldTrialsJitterEstimatorTest() {} }; TEST_F(MisconfiguredFieldTrialsJitterEstimatorTest, FieldTrialsAreValidated) { JitterEstimator::Config config = estimator_.GetConfigForTest(); EXPECT_EQ(*config.max_frame_size_percentile, 0.0); EXPECT_EQ(*config.frame_size_window, 1); EXPECT_EQ(*config.num_stddev_delay_clamp, 0.0); EXPECT_EQ(*config.num_stddev_delay_outlier, 0.0); EXPECT_EQ(*config.num_stddev_size_outlier, 0.0); } } // namespace } // namespace webrtc