mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 13:50:40 +01:00

This change adds a median filter that can replace the IIR filter that is currently used to estimate the avg frame size (in bytes). It is enabled through a boolean, and reuses the window length from the max percentile filter. The median filter is only used by the delay calculation in `CalculateEstimate()`. It does not replaced the use of the IIR estimate in the size outlier rejection heuristic. Bug: webrtc:14151 Change-Id: I519b6b57a8bee3c41a300ed2e92a1981c61cca15 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/275121 Reviewed-by: Philip Eliasson <philipel@webrtc.org> Commit-Queue: Rasmus Brandt <brandtr@webrtc.org> Cr-Commit-Position: refs/heads/main@{#38077}
241 lines
8.2 KiB
C++
241 lines
8.2 KiB
C++
/* 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"
|
|
#include "test/scoped_key_value_config.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());
|
|
}
|
|
|
|
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_outlier.has_value());
|
|
EXPECT_FALSE(config.num_stddev_size_outlier.has_value());
|
|
EXPECT_FALSE(config.congestion_rejection_factor.has_value());
|
|
}
|
|
|
|
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_outlier:2,"
|
|
"num_stddev_size_outlier:3.1,"
|
|
"congestion_rejection_factor:-1.55/") {}
|
|
~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_outlier, 2.0);
|
|
EXPECT_EQ(*config.num_stddev_size_outlier, 3.1);
|
|
EXPECT_EQ(*config.congestion_rejection_factor, -1.55);
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace webrtc
|