Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade trendline estimator to improve low bandwidth conditions #1055

Merged
merged 2 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ DelayBasedBwe::DelayBasedBwe(const WebRtcKeyValueConfig* key_value_config,
network_state_predictor_(network_state_predictor),
inter_arrival_(),
delay_detector_(
new TrendlineEstimator(key_value_config_, network_state_predictor_)),
new TrendlineEstimator(network_state_predictor_)),
last_seen_packet_(Timestamp::MinusInfinity()),
uma_recorded_(false),
rate_control_(key_value_config, /*send_side=*/true),
Expand Down Expand Up @@ -127,7 +127,7 @@ void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback,
new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,
kTimestampToMs, true));
delay_detector_.reset(
new TrendlineEstimator(key_value_config_, network_state_predictor_));
new TrendlineEstimator(network_state_predictor_));
}
last_seen_packet_ = at_time;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,96 +13,92 @@

#include "modules/congestion_controller/goog_cc/trendline_estimator.h"

#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
#include "rtc_base/numerics/safe_minmax.h"

#include "Logger.hpp"

#include <absl/types/optional.h>
#include <math.h>

#include <algorithm>
#include <string>

#include "absl/strings/match.h"
#include "absl/types/optional.h"
#include "api/network_state_predictor.h"
#include "rtc_base/numerics/safe_minmax.h"
#include "api/transport/webrtc_key_value_config.h"

#include "Logger.hpp"

namespace webrtc {

namespace {

// Parameters for linear least squares fit of regression line to noisy data.
constexpr size_t kDefaultTrendlineWindowSize = 20;
constexpr double kDefaultTrendlineSmoothingCoeff = 0.6;
constexpr double kDefaultTrendlineSmoothingCoeff = 0.9;
constexpr double kDefaultTrendlineThresholdGain = 4.0;
const char kBweWindowSizeInPacketsExperiment[] =
"WebRTC-BweWindowSizeInPackets";

size_t ReadTrendlineFilterWindowSize(
const WebRtcKeyValueConfig* key_value_config) {
std::string experiment_string =
key_value_config->Lookup(kBweWindowSizeInPacketsExperiment);
size_t window_size;
int parsed_values =
sscanf(experiment_string.c_str(), "Enabled-%zu", &window_size);
if (parsed_values == 1) {
if (window_size > 1)
return window_size;
MS_WARN_DEV("window size must be greater than 1");
}
MS_WARN_DEV(
"failed to parse parameters for BweWindowSizeInPackets"
" experiment from field trial string, using default");
return kDefaultTrendlineWindowSize;
}

absl::optional<double> LinearFitSlope(
const std::deque<std::pair<double, double>>& points) {
//RTC_DCHECK(points.size() >= 2);
const std::deque<TrendlineEstimator::PacketTiming>& packets) {
// RTC_DCHECK(packets.size() >= 2);
// Compute the "center of mass".
double sum_x = 0;
double sum_y = 0;
for (const auto& point : points) {
sum_x += point.first;
sum_y += point.second;
for (const auto& packet : packets) {
sum_x += packet.arrival_time_ms;
sum_y += packet.smoothed_delay_ms;
}
double x_avg = sum_x / points.size();
double y_avg = sum_y / points.size();
double x_avg = sum_x / packets.size();
double y_avg = sum_y / packets.size();
// Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2
double numerator = 0;
double denominator = 0;
for (const auto& point : points) {
numerator += (point.first - x_avg) * (point.second - y_avg);
denominator += (point.first - x_avg) * (point.first - x_avg);
for (const auto& packet : packets) {
double x = packet.arrival_time_ms;
double y = packet.smoothed_delay_ms;
numerator += (x - x_avg) * (y - y_avg);
denominator += (x - x_avg) * (x - x_avg);
}
if (denominator == 0)
return absl::nullopt;
return numerator / denominator;
}

absl::optional<double> ComputeSlopeCap(
const std::deque<TrendlineEstimator::PacketTiming>& packets,
const TrendlineEstimatorSettings& settings) {
// RTC_DCHECK(1 <= settings.beginning_packets &&
// settings.beginning_packets < packets.size());
// RTC_DCHECK(1 <= settings.end_packets &&
// settings.end_packets < packets.size());
// RTC_DCHECK(settings.beginning_packets + settings.end_packets <=
// packets.size());
TrendlineEstimator::PacketTiming early = packets[0];
for (size_t i = 1; i < settings.beginning_packets; ++i) {
if (packets[i].raw_delay_ms < early.raw_delay_ms)
early = packets[i];
}
size_t late_start = packets.size() - settings.end_packets;
TrendlineEstimator::PacketTiming late = packets[late_start];
for (size_t i = late_start + 1; i < packets.size(); ++i) {
if (packets[i].raw_delay_ms < late.raw_delay_ms)
late = packets[i];
}
if (late.arrival_time_ms - early.arrival_time_ms < 1) {
return absl::nullopt;
}
return (late.raw_delay_ms - early.raw_delay_ms) /
(late.arrival_time_ms - early.arrival_time_ms) +
settings.cap_uncertainty;
}

constexpr double kMaxAdaptOffsetMs = 15.0;
constexpr double kOverUsingTimeThreshold = 30;
constexpr double kOverUsingTimeThreshold = 10;
constexpr int kMinNumDeltas = 60;
constexpr int kDeltaCounterMax = 1000;

} // namespace

TrendlineEstimator::TrendlineEstimator(
const WebRtcKeyValueConfig* key_value_config,
NetworkStatePredictor* network_state_predictor)
: TrendlineEstimator(
key_value_config->Lookup(kBweWindowSizeInPacketsExperiment)
.find("Enabled") == 0
? ReadTrendlineFilterWindowSize(key_value_config)
: kDefaultTrendlineWindowSize,
kDefaultTrendlineSmoothingCoeff,
kDefaultTrendlineThresholdGain,
network_state_predictor) {}

TrendlineEstimator::TrendlineEstimator(
size_t window_size,
double smoothing_coef,
double threshold_gain,
NetworkStatePredictor* network_state_predictor)
: window_size_(window_size),
smoothing_coef_(smoothing_coef),
threshold_gain_(threshold_gain),
: smoothing_coef_(kDefaultTrendlineSmoothingCoeff),
threshold_gain_(kDefaultTrendlineThresholdGain),
num_of_deltas_(0),
first_arrival_time_ms_(-1),
accumulated_delay_(0),
Expand All @@ -120,63 +116,75 @@ TrendlineEstimator::TrendlineEstimator(
hypothesis_(BandwidthUsage::kBwNormal),
hypothesis_predicted_(BandwidthUsage::kBwNormal),
network_state_predictor_(network_state_predictor) {
MS_DEBUG_DEV(
"using Trendline filter for delay change estimation with window size: %zu",
window_size_);
}

TrendlineEstimator::~TrendlineEstimator() {}

void TrendlineEstimator::UpdateTrendline(double recv_delta_ms,
double send_delta_ms,
int64_t send_time_ms,
int64_t arrival_time_ms) {
const double delta_ms = recv_delta_ms - send_delta_ms;
++num_of_deltas_;
num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax);
if (first_arrival_time_ms_ == -1)
first_arrival_time_ms_ = arrival_time_ms;

// Exponential backoff filter.
accumulated_delay_ += delta_ms;
// BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
// accumulated_delay_);
smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
(1 - smoothing_coef_) * accumulated_delay_;
// BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
// smoothed_delay_);

// Maintain packet window
delay_hist_.emplace_back(
static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
smoothed_delay_, accumulated_delay_);
if (settings_.enable_sort) {
for (size_t i = delay_hist_.size() - 1;
i > 0 &&
delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms;
--i) {
std::swap(delay_hist_[i], delay_hist_[i - 1]);
}
}
if (delay_hist_.size() > settings_.window_size)
delay_hist_.pop_front();

// Simple linear regression.
double trend = prev_trend_;
if (delay_hist_.size() == settings_.window_size) {
// Update trend_ if it is possible to fit a line to the data. The delay
// trend can be seen as an estimate of (send_rate - capacity)/capacity.
// 0 < trend < 1 -> the delay increases, queues are filling up
// trend == 0 -> the delay does not change
// trend < 0 -> the delay decreases, queues are being emptied
trend = LinearFitSlope(delay_hist_).value_or(trend);
if (settings_.enable_cap) {
absl::optional<double> cap = ComputeSlopeCap(delay_hist_, settings_);
// We only use the cap to filter out overuse detections, not
// to detect additional underuses.
if (trend >= 0 && cap.has_value() && trend > cap.value()) {
trend = cap.value();
}
}
}
// BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend);

Detect(trend, send_delta_ms, arrival_time_ms);
}

void TrendlineEstimator::Update(double recv_delta_ms,
double send_delta_ms,
int64_t send_time_ms,
int64_t arrival_time_ms,
bool calculated_deltas) {
if (calculated_deltas) {
const double delta_ms = recv_delta_ms - send_delta_ms;
++num_of_deltas_;
num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax);
if (first_arrival_time_ms_ == -1)
first_arrival_time_ms_ = arrival_time_ms;

// Exponential backoff filter.
accumulated_delay_ += delta_ms;
// BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
// accumulated_delay_);
// smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
// (1 - smoothing_coef_) * accumulated_delay_;
// MS_NOTE: Apply WEMA to the current delta_ms. Don't consider the
// accumulated delay. Tests show it behaves more robustly upon delta peaks.
smoothed_delay_ = smoothing_coef_ * delta_ms +
(1 - smoothing_coef_) * smoothed_delay_;
// BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
// smoothed_delay_);

// Simple linear regression.
delay_hist_.push_back(std::make_pair(
static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
smoothed_delay_));
if (delay_hist_.size() > window_size_)
delay_hist_.pop_front();
double trend = prev_trend_;
if (delay_hist_.size() == window_size_) {
// Update trend_ if it is possible to fit a line to the data. The delay
// trend can be seen as an estimate of (send_rate - capacity)/capacity.
// 0 < trend < 1 -> the delay increases, queues are filling up
// trend == 0 -> the delay does not change
// trend < 0 -> the delay decreases, queues are being emptied
trend = LinearFitSlope(delay_hist_).value_or(trend);
}

// BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend);

MS_DEBUG_DEV("trend:%f, send_delta_ms:%f, recv_delta_ms:%f, delta_ms:%f arrival_time_ms:%" PRIi64 ", accumulated_delay_:%f, smoothed_delay_:%f", trend, send_delta_ms, recv_delta_ms, delta_ms, arrival_time_ms, accumulated_delay_, smoothed_delay_);
Detect(trend, send_delta_ms, arrival_time_ms);
}
else {
MS_DEBUG_DEV("no calculated deltas");
UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms);
}

if (network_state_predictor_) {
hypothesis_predicted_ = network_state_predictor_->Update(
send_time_ms, arrival_time_ms, hypothesis_);
Expand Down Expand Up @@ -212,15 +220,14 @@ void TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) {
if (trend >= prev_trend_) {
time_over_using_ = 0;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwOverusing;
MS_DEBUG_DEV("hypothesis_: BandwidthUsage::kBwOverusing");

#if MS_LOG_DEV_LEVEL == 3
for (auto& kv : delay_hist_) {
MS_DEBUG_DEV("arrival_time_ms - first_arrival_time_ms_:%f, smoothed_delay_:%f", kv.first, kv.second);
}
#endif

hypothesis_ = BandwidthUsage::kBwOverusing;
}
}
} else if (modified_trend < -threshold_) {
Expand All @@ -231,8 +238,8 @@ void TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) {
} else {
time_over_using_ = -1;
overuse_counter_ = 0;
MS_DEBUG_DEV("---- BandwidthUsage::kBwNormal ---");
hypothesis_ = BandwidthUsage::kBwNormal;
MS_DEBUG_DEV("---- BandwidthUsage::kBwNormal ---");
}
prev_trend_ = trend;
UpdateThreshold(modified_trend, now_ms);
Expand Down
Loading