JitterEstimator: add field trial overrides for avg frame filter

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}
This commit is contained in:
Rasmus Brandt
2022-09-14 12:09:36 +02:00
committed by WebRTC LUCI CQ
parent 6fa8a759b4
commit 0a617882df
3 changed files with 58 additions and 19 deletions

View File

@ -46,7 +46,7 @@ constexpr double kPhi = 0.97;
constexpr double kPsi = 0.9999;
// Default constants for percentile frame size filter.
constexpr double kDefaultMaxFrameSizePercentile = 0.95;
constexpr int kDefaultMaxFrameSizeWindow = 30 * 10;
constexpr int kDefaultFrameSizeWindow = 30 * 10;
// Outlier rejection constants.
constexpr double kDefaultMaxTimestampDeviationInSigmas = 3.5;
@ -88,6 +88,8 @@ constexpr char JitterEstimator::Config::kFieldTrialsKey[];
JitterEstimator::JitterEstimator(Clock* clock,
const FieldTrialsView& field_trials)
: config_(Config::Parse(field_trials.Lookup(Config::kFieldTrialsKey))),
avg_frame_size_median_bytes_(static_cast<size_t>(
config_.frame_size_window.value_or(kDefaultFrameSizeWindow))),
max_frame_size_bytes_percentile_(
config_.max_frame_size_percentile.value_or(
kDefaultMaxFrameSizePercentile)),
@ -104,6 +106,7 @@ void JitterEstimator::Reset() {
avg_frame_size_bytes_ = kInitialAvgAndMaxFrameSizeBytes;
max_frame_size_bytes_ = kInitialAvgAndMaxFrameSizeBytes;
var_frame_size_bytes2_ = 100;
avg_frame_size_median_bytes_.Reset();
max_frame_size_bytes_percentile_.Reset();
frame_sizes_in_percentile_filter_ = std::queue<int64_t>();
last_update_time_ = absl::nullopt;
@ -160,12 +163,15 @@ void JitterEstimator::UpdateEstimate(TimeDelta frame_delay,
max_frame_size_bytes_ =
std::max<double>(kPsi * max_frame_size_bytes_, frame_size.bytes());
// Maybe update percentile estimate of max frame size.
// Maybe update percentile estimates of frame sizes.
if (config_.avg_frame_size_median) {
avg_frame_size_median_bytes_.Insert(frame_size.bytes());
}
if (config_.MaxFrameSizePercentileEnabled()) {
frame_sizes_in_percentile_filter_.push(frame_size.bytes());
if (frame_sizes_in_percentile_filter_.size() >
static_cast<size_t>(config_.max_frame_size_window.value_or(
kDefaultMaxFrameSizeWindow))) {
static_cast<size_t>(
config_.frame_size_window.value_or(kDefaultFrameSizeWindow))) {
max_frame_size_bytes_percentile_.Erase(
frame_sizes_in_percentile_filter_.front());
frame_sizes_in_percentile_filter_.pop();
@ -188,11 +194,25 @@ void JitterEstimator::UpdateEstimate(TimeDelta frame_delay,
frame_delay.ms() -
kalman_filter_.GetFrameDelayVariationEstimateTotal(delta_frame_bytes);
// Outlier rejection.
// Outlier rejection: these conditions depend on filtered versions of the
// delay and frame size _means_, respectively, together with a configurable
// number of standard deviations. If a sample is large with respect to the
// corresponding mean and dispersion (defined by the number of
// standard deviations and the sample standard deviation), it is deemed an
// outlier. This "empirical rule" is further described in
// https://en.wikipedia.org/wiki/68-95-99.7_rule. Note that neither of the
// estimated means are true sample means, which implies that they are possibly
// not normally distributed. Hence, this rejection method is just a heuristic.
double num_stddev_delay_outlier = GetNumStddevDelayOutlier();
// Delay outlier rejection is two-sided.
bool abs_delay_is_not_outlier =
fabs(delay_deviation_ms) <
num_stddev_delay_outlier * sqrt(var_noise_ms2_);
// The reasoning above means, in particular, that we should use the sample
// mean-style `avg_frame_size_bytes_` estimate, as opposed to the
// median-filtered version, even if configured to use latter for the
// calculation in `CalculateEstimate()`.
// Size outlier rejection is one-sided.
bool size_is_positive_outlier =
frame_size.bytes() >
avg_frame_size_bytes_ +
@ -251,9 +271,8 @@ JitterEstimator::Config JitterEstimator::GetConfigForTest() const {
double JitterEstimator::GetMaxFrameSizeEstimateBytes() const {
if (config_.MaxFrameSizePercentileEnabled()) {
RTC_DCHECK_GT(frame_sizes_in_percentile_filter_.size(), 1u);
RTC_DCHECK_LE(
frame_sizes_in_percentile_filter_.size(),
config_.max_frame_size_window.value_or(kDefaultMaxFrameSizeWindow));
RTC_DCHECK_LE(frame_sizes_in_percentile_filter_.size(),
config_.frame_size_window.value_or(kDefaultFrameSizeWindow));
return max_frame_size_bytes_percentile_.GetPercentileValue();
}
return max_frame_size_bytes_;
@ -332,8 +351,14 @@ double JitterEstimator::NoiseThreshold() const {
// Calculates the current jitter estimate from the filtered estimates.
TimeDelta JitterEstimator::CalculateEstimate() {
// Using median- and percentile-filtered versions of the frame sizes may be
// more robust than using sample mean-style estimates.
double filtered_avg_frame_size_bytes =
config_.avg_frame_size_median
? avg_frame_size_median_bytes_.GetFilteredValue()
: avg_frame_size_bytes_;
double worst_case_frame_size_deviation_bytes =
GetMaxFrameSizeEstimateBytes() - avg_frame_size_bytes_;
GetMaxFrameSizeEstimateBytes() - filtered_avg_frame_size_bytes;
double ret_ms = kalman_filter_.GetFrameDelayVariationEstimateSizeBased(
worst_case_frame_size_deviation_bytes) +
NoiseThreshold();