Skip to content

Commit

Permalink
Upgrade trendline estimator to improve low bandwidth conditions (#1055)
Browse files Browse the repository at this point in the history
* Uprade trendline estimator to improve low bandwidth conditions

* Missing constant change
  • Loading branch information
ggarber authored Apr 14, 2023
1 parent 937bb6c commit abfd942
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 134 deletions.
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

0 comments on commit abfd942

Please sign in to comment.