summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc')
-rw-r--r--third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc305
1 files changed, 305 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc b/third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc
new file mode 100644
index 0000000000..8e0c01587f
--- /dev/null
+++ b/third_party/libwebrtc/modules/video_coding/timing/jitter_estimator_unittest.cc
@@ -0,0 +1,305 @@
+/* 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 <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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<FieldTrials> 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<std::pair<TimeDelta, rtc::HistogramPercentileCounter>>
+ 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