[feat] implementation for send side congestion controller

This commit is contained in:
dijunkun
2025-01-14 17:31:18 +08:00
parent ba268016e4
commit a8e9609736
16 changed files with 1747 additions and 82 deletions

36
src/common/limits_base.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LIMITS_BASE_H_
#define _LIMITS_BASE_H_
#include <limits>
template <typename T>
bool IsInfinite(const T& value) {
return value == std::numeric_limits<T>::min() ||
value == std::numeric_limits<T>::max();
}
template <typename T>
bool IsFinite(const T& value) {
return !IsInfinite(value);
}
template <typename T>
bool IsPlusFinite(const T& value) {
return value == std::numeric_limits<T>::max();
}
template <typename T>
bool IsMinusFinite(const T& value) {
return value == std::numeric_limits<T>::min();
}
#define INT64_T_MAX std::numeric_limits<int64_t>::max()
#define INT64_T_MIN std::numeric_limits<int64_t>::min()
#endif

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2017 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 "acknowledged_bitrate_estimator.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "bitrate_estimator.h"
#include "log.h"
#include "network_types.h"
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator()
: AcknowledgedBitrateEstimator(std::make_unique<BitrateEstimator>()) {}
AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {}
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(
std::unique_ptr<BitrateEstimator> bitrate_estimator)
: in_alr_(false), bitrate_estimator_(std::move(bitrate_estimator)) {}
void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector(
const std::vector<PacketResult>& packet_feedback_vector) {
if (!std::is_sorted(packet_feedback_vector.begin(),
packet_feedback_vector.end(),
PacketResult::ReceiveTimeOrder())) {
LOG_FATAL("packet_feedback_vector is not sorted");
}
for (const auto& packet : packet_feedback_vector) {
if (alr_ended_time_ && packet.sent_packet.send_time > *alr_ended_time_) {
bitrate_estimator_->ExpectFastRateChange();
alr_ended_time_.reset();
}
int64_t acknowledged_estimate = packet.sent_packet.size;
acknowledged_estimate += packet.sent_packet.prior_unacked_data;
bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate,
in_alr_);
}
}
std::optional<int64_t> AcknowledgedBitrateEstimator::bitrate() const {
return bitrate_estimator_->bitrate();
}
std::optional<int64_t> AcknowledgedBitrateEstimator::PeekRate() const {
return bitrate_estimator_->PeekRate();
}
void AcknowledgedBitrateEstimator::SetAlrEndedTime(int64_t alr_ended_time) {
alr_ended_time_.emplace(alr_ended_time);
}
void AcknowledgedBitrateEstimator::SetAlr(bool in_alr) { in_alr_ = in_alr; }

View File

@@ -0,0 +1,38 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
#define _ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
#include <memory>
#include <optional>
#include <vector>
#include "bitrate_estimator.h"
#include "network_types.h"
class AcknowledgedBitrateEstimator {
public:
AcknowledgedBitrateEstimator(
std::unique_ptr<BitrateEstimator> bitrate_estimator);
explicit AcknowledgedBitrateEstimator();
~AcknowledgedBitrateEstimator();
void IncomingPacketFeedbackVector(
const std::vector<PacketResult>& packet_feedback_vector);
std::optional<int64_t> bitrate() const;
std::optional<int64_t> PeekRate() const;
void SetAlr(bool in_alr);
void SetAlrEndedTime(int64_t alr_ended_time);
private:
std::optional<int64_t> alr_ended_time_;
bool in_alr_;
std::unique_ptr<BitrateEstimator> bitrate_estimator_;
};
#endif

62
src/qos/alr_detector.cpp Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016 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 "alr_detector.h"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <optional>
AlrDetector::AlrDetector(AlrDetectorConfig config)
: conf_(config), alr_budget_(0, true) {}
AlrDetector::AlrDetector() : alr_budget_(0, true) {}
AlrDetector::~AlrDetector() {}
void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {
if (!last_send_time_ms_.has_value()) {
last_send_time_ms_ = send_time_ms;
// Since the duration for sending the bytes is unknwon, return without
// updating alr state.
return;
}
int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;
last_send_time_ms_ = send_time_ms;
alr_budget_.UseBudget(bytes_sent);
alr_budget_.IncreaseBudget(delta_time_ms);
bool state_changed = false;
if (alr_budget_.budget_ratio() > conf_.start_budget_level_ratio &&
!alr_started_time_ms_) {
alr_started_time_ms_.emplace(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
state_changed = true;
} else if (alr_budget_.budget_ratio() < conf_.stop_budget_level_ratio &&
alr_started_time_ms_) {
state_changed = true;
alr_started_time_ms_.reset();
}
}
void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
int target_rate_kbps =
static_cast<double>(bitrate_bps) * conf_.bandwidth_usage_ratio / 1000;
alr_budget_.set_target_rate_kbps(target_rate_kbps);
}
std::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
const {
return alr_started_time_ms_;
}

60
src/qos/alr_detector.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _ALR_DETECTOR_H_
#define _ALR_DETECTOR_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <optional>
#include "interval_budget.h"
struct AlrDetectorConfig {
// Sent traffic ratio as a function of network capacity used to determine
// application-limited region. ALR region start when bandwidth usage drops
// below kAlrStartUsageRatio and ends when it raises above
// kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment
// until BW adjustments of application limited region is fine tuned.
double bandwidth_usage_ratio = 0.65;
double start_budget_level_ratio = 0.80;
double stop_budget_level_ratio = 0.50;
};
// Application limited region detector is a class that utilizes signals of
// elapsed time and bytes sent to estimate whether network traffic is
// currently limited by the application's ability to generate traffic.
//
// AlrDetector provides a signal that can be utilized to adjust
// estimate bandwidth.
// Note: This class is not thread-safe.
class AlrDetector {
public:
AlrDetector(AlrDetectorConfig config);
AlrDetector();
~AlrDetector();
void OnBytesSent(size_t bytes_sent, int64_t send_time_ms);
// Set current estimated bandwidth.
void SetEstimatedBitrate(int bitrate_bps);
// Returns time in milliseconds when the current application-limited region
// started or empty result if the sender is currently not application-limited.
std::optional<int64_t> GetApplicationLimitedRegionStartTime() const;
private:
friend class GoogCcStatePrinter;
const AlrDetectorConfig conf_;
std::optional<int64_t> last_send_time_ms_;
IntervalBudget alr_budget_;
std::optional<int64_t> alr_started_time_ms_;
};
#endif

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2017 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 "bitrate_estimator.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <optional>
namespace {
constexpr int kInitialRateWindowMs = 500;
constexpr int kRateWindowMs = 150;
constexpr int kMinRateWindowMs = 150;
constexpr int kMaxRateWindowMs = 1000;
const char kBweThroughputWindowConfig[] = "WebRTC-BweThroughputWindowConfig";
} // namespace
BitrateEstimator::BitrateEstimator()
: sum_(0),
initial_window_ms_(kInitialRateWindowMs, kMinRateWindowMs,
kMaxRateWindowMs),
noninitial_window_ms_(kRateWindowMs, kMinRateWindowMs, kMaxRateWindowMs),
uncertainty_scale_(10.0),
uncertainty_scale_in_alr_(uncertainty_scale_),
small_sample_uncertainty_scale_(uncertainty_scale_),
small_sample_threshold_(0),
uncertainty_symmetry_cap_(0),
estimate_floor_(0),
current_window_ms_(0),
prev_time_ms_(-1),
bitrate_estimate_kbps_(-1.0f),
bitrate_estimate_var_(50.0f) {}
BitrateEstimator::~BitrateEstimator() = default;
void BitrateEstimator::Update(int64_t at_time, int64_t amount, bool in_alr) {
int rate_window_ms = noninitial_window_ms_.Get();
// We use a larger window at the beginning to get a more stable sample that
// we can use to initialize the estimate.
if (bitrate_estimate_kbps_ < 0.f) rate_window_ms = initial_window_ms_.Get();
bool is_small_sample = false;
float bitrate_sample_kbps =
UpdateWindow(at_time, amount, rate_window_ms, &is_small_sample);
if (bitrate_sample_kbps < 0.0f) return;
if (bitrate_estimate_kbps_ < 0.0f) {
// This is the very first sample we get. Use it to initialize the estimate.
bitrate_estimate_kbps_ = bitrate_sample_kbps;
return;
}
// Optionally use higher uncertainty for very small samples to avoid dropping
// estimate and for samples obtained in ALR.
float scale = uncertainty_scale_;
if (is_small_sample && bitrate_sample_kbps < bitrate_estimate_kbps_) {
scale = small_sample_uncertainty_scale_;
} else if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) {
// Optionally use higher uncertainty for samples obtained during ALR.
scale = uncertainty_scale_in_alr_;
}
// Define the sample uncertainty as a function of how far away it is from the
// current estimate. With low values of uncertainty_symmetry_cap_ we add more
// uncertainty to increases than to decreases. For higher values we approach
// symmetry.
float sample_uncertainty =
scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) /
(bitrate_estimate_kbps_ +
std::min(bitrate_sample_kbps,
static_cast<float>(uncertainty_symmetry_cap_)));
float sample_var = sample_uncertainty * sample_uncertainty;
// Update a bayesian estimate of the rate, weighting it lower if the sample
// uncertainty is large.
// The bitrate estimate uncertainty is increased with each update to model
// that the bitrate changes over time.
float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;
bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ +
pred_bitrate_estimate_var * bitrate_sample_kbps) /
(sample_var + pred_bitrate_estimate_var);
bitrate_estimate_kbps_ =
std::max(bitrate_estimate_kbps_, static_cast<float>(estimate_floor_));
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
(sample_var + pred_bitrate_estimate_var);
}
float BitrateEstimator::UpdateWindow(int64_t now_ms, int bytes,
int rate_window_ms,
bool* is_small_sample) {
// Reset if time moves backwards.
if (now_ms < prev_time_ms_) {
prev_time_ms_ = -1;
sum_ = 0;
current_window_ms_ = 0;
}
if (prev_time_ms_ >= 0) {
current_window_ms_ += now_ms - prev_time_ms_;
// Reset if nothing has been received for more than a full window.
if (now_ms - prev_time_ms_ > rate_window_ms) {
sum_ = 0;
current_window_ms_ %= rate_window_ms;
}
}
prev_time_ms_ = now_ms;
float bitrate_sample = -1.0f;
if (current_window_ms_ >= rate_window_ms) {
*is_small_sample = sum_ < small_sample_threshold_;
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
current_window_ms_ -= rate_window_ms;
sum_ = 0;
}
sum_ += bytes;
return bitrate_sample;
}
std::optional<int64_t> BitrateEstimator::bitrate() const {
if (bitrate_estimate_kbps_ < 0.f) return std::nullopt;
return static_cast<int64_t>(bitrate_estimate_kbps_);
}
std::optional<int64_t> BitrateEstimator::PeekRate() const {
if (current_window_ms_ > 0) return sum_ / current_window_ms_;
return std::nullopt;
}
void BitrateEstimator::ExpectFastRateChange() {
// By setting the bitrate-estimate variance to a higher value we allow the
// bitrate to change fast for the next few samples.
bitrate_estimate_var_ += 200;
}

View File

@@ -0,0 +1,50 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _BITRATE_ESTIMATOR_H_
#define _BITRATE_ESTIMATOR_H_
#include <stdint.h>
#include <optional>
#include "constrained.h"
// Computes a bayesian estimate of the throughput given acks containing
// the arrival time and payload size. Samples which are far from the current
// estimate or are based on few packets are given a smaller weight, as they
// are considered to be more likely to have been caused by, e.g., delay spikes
// unrelated to congestion.
class BitrateEstimator {
public:
explicit BitrateEstimator();
virtual ~BitrateEstimator();
virtual void Update(int64_t at_time, int64_t amount, bool in_alr);
virtual std::optional<int64_t> bitrate() const;
std::optional<int64_t> PeekRate() const;
virtual void ExpectFastRateChange();
private:
float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms,
bool* is_small_sample);
int sum_;
Constrained<int> initial_window_ms_;
Constrained<int> noninitial_window_ms_;
double uncertainty_scale_;
double uncertainty_scale_in_alr_;
double small_sample_uncertainty_scale_;
int64_t small_sample_threshold_;
int64_t uncertainty_symmetry_cap_;
int64_t estimate_floor_;
int64_t current_window_ms_;
int64_t prev_time_ms_;
float bitrate_estimate_kbps_;
float bitrate_estimate_var_;
};
#endif

View File

@@ -61,16 +61,16 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow) if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)
feedback_max_rtts_.pop_front(); feedback_max_rtts_.pop_front();
// TODO(srte): Use time since last unacknowledged packet. // TODO(srte): Use time since last unacknowledged packet.
// bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time, bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,
// min_propagation_rtt); min_propagation_rtt);
} }
if (packet_feedback_only_) { if (packet_feedback_only_) {
if (!feedback_max_rtts_.empty()) { if (!feedback_max_rtts_.empty()) {
int64_t sum_rtt_ms = int64_t sum_rtt_ms =
std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(), std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(),
static_cast<int64_t>(0)); static_cast<int64_t>(0));
// int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size(); int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();
// if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms); if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms);
} }
int64_t feedback_min_rtt = std::numeric_limits<int64_t>::max(); int64_t feedback_min_rtt = std::numeric_limits<int64_t>::max();
@@ -83,8 +83,7 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
} }
if (feedback_min_rtt != std::numeric_limits<int64_t>::max() && if (feedback_min_rtt != std::numeric_limits<int64_t>::max() &&
feedback_min_rtt != std::numeric_limits<int64_t>::min()) { feedback_min_rtt != std::numeric_limits<int64_t>::min()) {
// bandwidth_estimation_->UpdateRtt(feedback_min_rtt, bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time);
// report.feedback_time);
} }
expected_packets_since_last_loss_update_ += expected_packets_since_last_loss_update_ +=
@@ -95,101 +94,101 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback(
} }
if (report.feedback_time > next_loss_update_) { if (report.feedback_time > next_loss_update_) {
next_loss_update_ = report.feedback_time + kLossUpdateInterval; next_loss_update_ = report.feedback_time + kLossUpdateInterval;
// bandwidth_estimation_->UpdatePacketsLost( bandwidth_estimation_->UpdatePacketsLost(
// lost_packets_since_last_loss_update_, lost_packets_since_last_loss_update_,
// expected_packets_since_last_loss_update_, report.feedback_time); expected_packets_since_last_loss_update_, report.feedback_time);
expected_packets_since_last_loss_update_ = 0; expected_packets_since_last_loss_update_ = 0;
lost_packets_since_last_loss_update_ = 0; lost_packets_since_last_loss_update_ = 0;
} }
} }
// std::optional<int64_t> alr_start_time = std::optional<int64_t> alr_start_time =
// alr_detector_->GetApplicationLimitedRegionStartTime(); alr_detector_->GetApplicationLimitedRegionStartTime();
// if (previously_in_alr_ && !alr_start_time.has_value()) { if (previously_in_alr_ && !alr_start_time.has_value()) {
// int64_t now_ms = report.feedback_time; int64_t now_ms = report.feedback_time;
// acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time); acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);
// probe_controller_->SetAlrEndedTimeMs(now_ms); probe_controller_->SetAlrEndedTimeMs(now_ms);
// } }
// previously_in_alr_ = alr_start_time.has_value(); previously_in_alr_ = alr_start_time.has_value();
// acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
// report.SortedByReceiveTime()); report.SortedByReceiveTime());
// auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate(); auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();
// bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate, bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,
// report.feedback_time); report.feedback_time);
for (const auto& feedback : report.SortedByReceiveTime()) { for (const auto& feedback : report.SortedByReceiveTime()) {
if (feedback.sent_packet.pacing_info.probe_cluster_id != if (feedback.sent_packet.pacing_info.probe_cluster_id !=
PacedPacketInfo::kNotAProbe) { PacedPacketInfo::kNotAProbe) {
// probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback); probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);
} }
} }
// if (network_estimator_) { if (network_estimator_) {
// network_estimator_->OnTransportPacketsFeedback(report); network_estimator_->OnTransportPacketsFeedback(report);
// // SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate()); SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate());
// } }
// std::optional<int64_t> probe_bitrate = std::optional<int64_t> probe_bitrate =
// probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(); probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();
// if (ignore_probes_lower_than_network_estimate_ && probe_bitrate && if (ignore_probes_lower_than_network_estimate_ && probe_bitrate &&
// estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() && estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() &&
// *probe_bitrate < estimate_->link_capacity_lower) { *probe_bitrate < estimate_->link_capacity_lower) {
// probe_bitrate.reset(); probe_bitrate.reset();
// } }
// if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate && if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate &&
// acknowledged_bitrate) { acknowledged_bitrate) {
// Limit the backoff to something slightly below the acknowledged // Limit the backoff to something slightly below the acknowledged
// bitrate. ("Slightly below" because we want to drain the queues // bitrate. ("Slightly below" because we want to drain the queues
// if we are actually overusing.) // if we are actually overusing.)
// The acknowledged bitrate shouldn't normally be higher than the delay // The acknowledged bitrate shouldn't normally be higher than the delay
// based estimate, but it could happen e.g. due to packet bursts or // based estimate, but it could happen e.g. due to packet bursts or
// encoder overshoot. We use std::min to ensure that a probe result // encoder overshoot. We use std::min to ensure that a probe result
// below the current BWE never causes an increase. // below the current BWE never causes an increase.
// int64_t limit = int64_t limit =
// std::min(delay_based_bwe_->last_estimate(), std::min(delay_based_bwe_->last_estimate(),
// *acknowledged_bitrate * kProbeDropThroughputFraction); *acknowledged_bitrate * kProbeDropThroughputFraction);
// probe_bitrate = std::max(*probe_bitrate, limit); probe_bitrate = std::max(*probe_bitrate, limit);
// } }
NetworkControlUpdate update; NetworkControlUpdate update;
bool recovered_from_overuse = false; bool recovered_from_overuse = false;
// DelayBasedBwe::Result result; DelayBasedBwe::Result result;
// result = delay_based_bwe_->IncomingPacketFeedbackVector( result = delay_based_bwe_->IncomingPacketFeedbackVector(
// report, acknowledged_bitrate, probe_bitrate, estimate_, report, acknowledged_bitrate, probe_bitrate, estimate_,
// alr_start_time.has_value()); alr_start_time.has_value());
// if (result.updated) { if (result.updated) {
// if (result.probe) { if (result.probe) {
// bandwidth_estimation_->SetSendBitrate(result.target_bitrate, bandwidth_estimation_->SetSendBitrate(result.target_bitrate,
// report.feedback_time); report.feedback_time);
// } }
// Since SetSendBitrate now resets the delay-based estimate, we have to // Since SetSendBitrate now resets the delay-based estimate, we have to
// call UpdateDelayBasedEstimate after SetSendBitrate. // call UpdateDelayBasedEstimate after SetSendBitrate.
// bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time, bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,
// result.target_bitrate); result.target_bitrate);
// } }
// bandwidth_estimation_->UpdateLossBasedEstimator( bandwidth_estimation_->UpdateLossBasedEstimator(
// report, result.delay_detector_state, probe_bitrate, report, result.delay_detector_state, probe_bitrate,
// alr_start_time.has_value()); alr_start_time.has_value());
// if (result.updated) { if (result.updated) {
// // Update the estimate in the ProbeController, in case we want to probe. // Update the estimate in the ProbeController, in case we want to probe.
// MaybeTriggerOnNetworkChanged(&update, report.feedback_time); MaybeTriggerOnNetworkChanged(&update, report.feedback_time);
// } }
// recovered_from_overuse = result.recovered_from_overuse; recovered_from_overuse = result.recovered_from_overuse;
// if (recovered_from_overuse) { if (recovered_from_overuse) {
// probe_controller_->SetAlrStartTimeMs(alr_start_time); probe_controller_->SetAlrStartTimeMs(alr_start_time);
// auto probes = probe_controller_->RequestProbe(report.feedback_time); auto probes = probe_controller_->RequestProbe(report.feedback_time);
// update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
// probes.begin(), probes.end()); probes.begin(), probes.end());
// } }
// No valid RTT could be because send-side BWE isn't used, in which case // No valid RTT could be because send-side BWE isn't used, in which case
// we don't try to limit the outstanding packets. // we don't try to limit the outstanding packets.
// if (rate_control_settings_.UseCongestionWindow() && if (rate_control_settings_.UseCongestionWindow() &&
// max_feedback_rtt.IsFinite()) { max_feedback_rtt.IsFinite()) {
// UpdateCongestionWindowSize(); UpdateCongestionWindowSize();
// } }
if (congestion_window_pushback_controller_ && current_data_window_) { if (congestion_window_pushback_controller_ && current_data_window_) {
congestion_window_pushback_controller_->SetDataWindow( congestion_window_pushback_controller_->SetDataWindow(
*current_data_window_); *current_data_window_);

View File

@@ -4,8 +4,11 @@
#include <deque> #include <deque>
#include <memory> #include <memory>
#include "acknowledged_bitrate_estimator.h"
#include "alr_detector.h"
#include "congestion_window_pushback_controller.h" #include "congestion_window_pushback_controller.h"
#include "network_types.h" #include "network_types.h"
#include "send_side_bandwidth_estimation.h"
class CongestionControl { class CongestionControl {
public: public:
@@ -22,14 +25,19 @@ class CongestionControl {
private: private:
std::deque<int64_t> feedback_max_rtts_; std::deque<int64_t> feedback_max_rtts_;
// std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
int expected_packets_since_last_loss_update_ = 0; int expected_packets_since_last_loss_update_ = 0;
int lost_packets_since_last_loss_update_ = 0; int lost_packets_since_last_loss_update_ = 0;
int64_t next_loss_update_ = std::numeric_limits<int64_t>::min(); int64_t next_loss_update_ = std::numeric_limits<int64_t>::min();
const bool packet_feedback_only_ = false; const bool packet_feedback_only_ = false;
bool previously_in_alr_ = false; bool previously_in_alr_ = false;
// const bool limit_probes_lower_than_throughput_estimate_; const bool limit_probes_lower_than_throughput_estimate_ = false;
std::optional<int64_t> current_data_window_; std::optional<int64_t> current_data_window_;
private:
std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
std::unique_ptr<AlrDetector> alr_detector_;
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;
std::unique_ptr<DelayBasedBwe> delay_based_bwe_;
}; };
#endif #endif

31
src/qos/constrained.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _CONSTRAIN_H_
#define _CONSTRAIN_H_
#include <optional>
#include <string>
template <typename T>
class Constrained {
public:
Constrained(T default_value, std::optional<T> lower_limit,
std::optional<T> upper_limit)
: value_(default_value),
lower_limit_(lower_limit),
upper_limit_(upper_limit) {}
T Get() const { return value_; }
operator T() const { return Get(); }
const T* operator->() const { return &value_; }
private:
T value_;
std::optional<T> lower_limit_;
std::optional<T> upper_limit_;
};
#endif

285
src/qos/delay_based_bwe.cpp Normal file
View File

@@ -0,0 +1,285 @@
/*
* Copyright (c) 2016 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 "delay_based_bwe.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "api/transport/network_types.h"
namespace {
constexpr int64_t kStreamTimeOut = int64_t::Seconds(2);
constexpr int64_t kSendTimeGroupLength = int64_t::Millis(5);
// This ssrc is used to fulfill the current API but will be removed
// after the API has been changed.
constexpr uint32_t kFixedSsrc = 0;
} // namespace
constexpr char BweSeparateAudioPacketsSettings::kKey[];
BweSeparateAudioPacketsSettings::BweSeparateAudioPacketsSettings(
const FieldTrialsView* key_value_config) {
Parser()->Parse(
key_value_config->Lookup(BweSeparateAudioPacketsSettings::kKey));
}
std::unique_ptr<StructParametersParser>
BweSeparateAudioPacketsSettings::Parser() {
return StructParametersParser::Create( //
"enabled", &enabled, //
"packet_threshold", &packet_threshold, //
"time_threshold", &time_threshold);
}
DelayBasedBwe::Result::Result()
: updated(false),
probe(false),
target_bitrate(int64_t::Zero()),
recovered_from_overuse(false),
delay_detector_state(BandwidthUsage::kBwNormal) {}
DelayBasedBwe::DelayBasedBwe(const FieldTrialsView* key_value_config,
RtcEventLog* event_log,
NetworkStatePredictor* network_state_predictor)
: event_log_(event_log),
key_value_config_(key_value_config),
separate_audio_(key_value_config),
audio_packets_since_last_video_(0),
last_video_packet_recv_time_(int64_t::MinusInfinity()),
network_state_predictor_(network_state_predictor),
video_delay_detector_(
new TrendlineEstimator(key_value_config_, network_state_predictor_)),
audio_delay_detector_(
new TrendlineEstimator(key_value_config_, network_state_predictor_)),
active_delay_detector_(video_delay_detector_.get()),
last_seen_packet_(int64_t::MinusInfinity()),
uma_recorded_(false),
rate_control_(*key_value_config, /*send_side=*/true),
prev_bitrate_(int64_t::Zero()),
prev_state_(BandwidthUsage::kBwNormal) {
RTC_LOG(LS_INFO)
<< "Initialized DelayBasedBwe with separate audio overuse detection"
<< separate_audio_.Parser()->Encode();
}
DelayBasedBwe::~DelayBasedBwe() {}
DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector(
const TransportPacketsFeedback& msg, std::optional<int64_t> acked_bitrate,
std::optional<int64_t> probe_bitrate,
std::optional<NetworkStateEstimate> network_estimate, bool in_alr) {
RTC_DCHECK_RUNS_SERIALIZED(&network_race_);
auto packet_feedback_vector = msg.SortedByReceiveTime();
// TODO(holmer): An empty feedback vector here likely means that
// all acks were too late and that the send time history had
// timed out. We should reduce the rate when this occurs.
if (packet_feedback_vector.empty()) {
RTC_LOG(LS_WARNING) << "Very late feedback received.";
return DelayBasedBwe::Result();
}
if (!uma_recorded_) {
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram,
BweNames::kSendSideTransportSeqNum,
BweNames::kBweNamesMax);
uma_recorded_ = true;
}
bool delayed_feedback = true;
bool recovered_from_overuse = false;
BandwidthUsage prev_detector_state = active_delay_detector_->State();
for (const auto& packet_feedback : packet_feedback_vector) {
delayed_feedback = false;
IncomingPacketFeedback(packet_feedback, msg.feedback_time);
if (prev_detector_state == BandwidthUsage::kBwUnderusing &&
active_delay_detector_->State() == BandwidthUsage::kBwNormal) {
recovered_from_overuse = true;
}
prev_detector_state = active_delay_detector_->State();
}
if (delayed_feedback) {
// TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard
// against building very large network queues.
return Result();
}
rate_control_.SetInApplicationLimitedRegion(in_alr);
rate_control_.SetNetworkStateEstimate(network_estimate);
return MaybeUpdateEstimate(acked_bitrate, probe_bitrate,
std::move(network_estimate),
recovered_from_overuse, in_alr, msg.feedback_time);
}
void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback,
int64_t at_time) {
// Reset if the stream has timed out.
if (last_seen_packet_.IsInfinite() ||
at_time - last_seen_packet_ > kStreamTimeOut) {
video_inter_arrival_delta_ =
std::make_unique<InterArrivalDelta>(kSendTimeGroupLength);
audio_inter_arrival_delta_ =
std::make_unique<InterArrivalDelta>(kSendTimeGroupLength);
video_delay_detector_.reset(
new TrendlineEstimator(key_value_config_, network_state_predictor_));
audio_delay_detector_.reset(
new TrendlineEstimator(key_value_config_, network_state_predictor_));
active_delay_detector_ = video_delay_detector_.get();
}
last_seen_packet_ = at_time;
// As an alternative to ignoring small packets, we can separate audio and
// video packets for overuse detection.
DelayIncreaseDetectorInterface* delay_detector_for_packet =
video_delay_detector_.get();
if (separate_audio_.enabled) {
if (packet_feedback.sent_packet.audio) {
delay_detector_for_packet = audio_delay_detector_.get();
audio_packets_since_last_video_++;
if (audio_packets_since_last_video_ > separate_audio_.packet_threshold &&
packet_feedback.receive_time - last_video_packet_recv_time_ >
separate_audio_.time_threshold) {
active_delay_detector_ = audio_delay_detector_.get();
}
} else {
audio_packets_since_last_video_ = 0;
last_video_packet_recv_time_ =
std::max(last_video_packet_recv_time_, packet_feedback.receive_time);
active_delay_detector_ = video_delay_detector_.get();
}
}
DataSize packet_size = packet_feedback.sent_packet.size;
int64_t send_delta = int64_t::Zero();
int64_t recv_delta = int64_t::Zero();
int size_delta = 0;
InterArrivalDelta* inter_arrival_for_packet =
(separate_audio_.enabled && packet_feedback.sent_packet.audio)
? audio_inter_arrival_delta_.get()
: video_inter_arrival_delta_.get();
bool calculated_deltas = inter_arrival_for_packet->ComputeDeltas(
packet_feedback.sent_packet.send_time, packet_feedback.receive_time,
at_time, packet_size.bytes(), &send_delta, &recv_delta, &size_delta);
delay_detector_for_packet->Update(recv_delta.ms<double>(),
send_delta.ms<double>(),
packet_feedback.sent_packet.send_time.ms(),
packet_feedback.receive_time.ms(),
packet_size.bytes(), calculated_deltas);
}
int64_t DelayBasedBwe::TriggerOveruse(int64_t at_time,
std::optional<int64_t> link_capacity) {
RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity);
return rate_control_.Update(input, at_time);
}
DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(
std::optional<int64_t> acked_bitrate, std::optional<int64_t> probe_bitrate,
std::optional<NetworkStateEstimate> /* state_estimate */,
bool recovered_from_overuse, bool /* in_alr */, int64_t at_time) {
Result result;
// Currently overusing the bandwidth.
if (active_delay_detector_->State() == BandwidthUsage::kBwOverusing) {
if (acked_bitrate &&
rate_control_.TimeToReduceFurther(at_time, *acked_bitrate)) {
result.updated =
UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
} else if (!acked_bitrate && rate_control_.ValidEstimate() &&
rate_control_.InitialTimeToReduceFurther(at_time)) {
// Overusing before we have a measured acknowledged bitrate. Reduce send
// rate by 50% every 200 ms.
// TODO(tschumim): Improve this and/or the acknowledged bitrate estimator
// so that we (almost) always have a bitrate estimate.
rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, at_time);
result.updated = true;
result.probe = false;
result.target_bitrate = rate_control_.LatestEstimate();
}
} else {
if (probe_bitrate) {
result.probe = true;
result.updated = true;
rate_control_.SetEstimate(*probe_bitrate, at_time);
result.target_bitrate = rate_control_.LatestEstimate();
} else {
result.updated =
UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
result.recovered_from_overuse = recovered_from_overuse;
}
}
BandwidthUsage detector_state = active_delay_detector_->State();
if ((result.updated && prev_bitrate_ != result.target_bitrate) ||
detector_state != prev_state_) {
int64_t bitrate = result.updated ? result.target_bitrate : prev_bitrate_;
if (event_log_) {
event_log_->Log(std::make_unique<RtcEventBweUpdateDelayBased>(
bitrate.bps(), detector_state));
}
prev_bitrate_ = bitrate;
prev_state_ = detector_state;
}
result.delay_detector_state = detector_state;
return result;
}
bool DelayBasedBwe::UpdateEstimate(int64_t at_time,
std::optional<int64_t> acked_bitrate,
int64_t* target_rate) {
const RateControlInput input(active_delay_detector_->State(), acked_bitrate);
*target_rate = rate_control_.Update(input, at_time);
return rate_control_.ValidEstimate();
}
void DelayBasedBwe::OnRttUpdate(int64_t avg_rtt) {
rate_control_.SetRtt(avg_rtt);
}
bool DelayBasedBwe::LatestEstimate(std::vector<uint32_t>* ssrcs,
int64_t* bitrate) const {
// Currently accessed from both the process thread (see
// ModuleRtpRtcpImpl::Process()) and the configuration thread (see
// Call::GetStats()). Should in the future only be accessed from a single
// thread.
RTC_DCHECK(ssrcs);
RTC_DCHECK(bitrate);
if (!rate_control_.ValidEstimate()) return false;
*ssrcs = {kFixedSsrc};
*bitrate = rate_control_.LatestEstimate();
return true;
}
void DelayBasedBwe::SetStartBitrate(int64_t start_bitrate) {
RTC_LOG(LS_INFO) << "BWE Setting start bitrate to: "
<< ToString(start_bitrate);
rate_control_.SetStartBitrate(start_bitrate);
}
void DelayBasedBwe::SetMinBitrate(int64_t min_bitrate) {
// Called from both the configuration thread and the network thread. Shouldn't
// be called from the network thread in the future.
rate_control_.SetMinBitrate(min_bitrate);
}
int64_t DelayBasedBwe::GetExpectedBwePeriod() const {
return rate_control_.GetExpectedBandwidthPeriod();
}

115
src/qos/delay_based_bwe.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DELAY_BASED_BWE_H_
#define _DELAY_BASED_BWE_H_
#include <stdint.h>
#include <memory>
#include <optional>
#include <vector>
#include "network_types.h"
enum class BandwidthUsage {
kBwNormal = 0,
kBwUnderusing = 1,
kBwOverusing = 2,
kLast
};
struct BweSeparateAudioPacketsSettings {
static constexpr char kKey[] = "WebRTC-Bwe-SeparateAudioPackets";
BweSeparateAudioPacketsSettings() = default;
explicit BweSeparateAudioPacketsSettings(
const FieldTrialsView* key_value_config);
bool enabled = false;
int packet_threshold = 10;
int64_t time_threshold = int64_t::Seconds(1);
std::unique_ptr<StructParametersParser> Parser();
};
class DelayBasedBwe {
public:
struct Result {
Result();
~Result() = default;
bool updated;
bool probe;
int64_t target_bitrate = int64_t::Zero();
bool recovered_from_overuse;
BandwidthUsage delay_detector_state;
};
explicit DelayBasedBwe(const FieldTrialsView* key_value_config,
RtcEventLog* event_log,
NetworkStatePredictor* network_state_predictor);
DelayBasedBwe() = delete;
DelayBasedBwe(const DelayBasedBwe&) = delete;
DelayBasedBwe& operator=(const DelayBasedBwe&) = delete;
virtual ~DelayBasedBwe();
Result IncomingPacketFeedbackVector(
const TransportPacketsFeedback& msg, std::optional<int64_t> acked_bitrate,
std::optional<int64_t> probe_bitrate,
std::optional<NetworkStateEstimate> network_estimate, bool in_alr);
void OnRttUpdate(int64_t avg_rtt);
bool LatestEstimate(std::vector<uint32_t>* ssrcs, int64_t* bitrate) const;
void SetStartBitrate(int64_t start_bitrate);
void SetMinBitrate(int64_t min_bitrate);
int64_t GetExpectedBwePeriod() const;
int64_t TriggerOveruse(int64_t at_time, std::optional<int64_t> link_capacity);
int64_t last_estimate() const { return prev_bitrate_; }
BandwidthUsage last_state() const { return prev_state_; }
private:
friend class GoogCcStatePrinter;
void IncomingPacketFeedback(const PacketResult& packet_feedback,
int64_t at_time);
Result MaybeUpdateEstimate(std::optional<int64_t> acked_bitrate,
std::optional<int64_t> probe_bitrate,
std::optional<NetworkStateEstimate> state_estimate,
bool recovered_from_overuse, bool in_alr,
int64_t at_time);
// Updates the current remote rate estimate and returns true if a valid
// estimate exists.
bool UpdateEstimate(int64_t at_time, std::optional<int64_t> acked_bitrate,
int64_t* target_rate);
rtc::RaceChecker network_race_;
RtcEventLog* const event_log_;
const FieldTrialsView* const key_value_config_;
// Alternatively, run two separate overuse detectors for audio and video,
// and fall back to the audio one if we haven't seen a video packet in a
// while.
BweSeparateAudioPacketsSettings separate_audio_;
int64_t audio_packets_since_last_video_;
int64_t last_video_packet_recv_time_;
NetworkStatePredictor* network_state_predictor_;
std::unique_ptr<InterArrival> video_inter_arrival_;
std::unique_ptr<InterArrivalDelta> video_inter_arrival_delta_;
std::unique_ptr<DelayIncreaseDetectorInterface> video_delay_detector_;
std::unique_ptr<InterArrival> audio_inter_arrival_;
std::unique_ptr<InterArrivalDelta> audio_inter_arrival_delta_;
std::unique_ptr<DelayIncreaseDetectorInterface> audio_delay_detector_;
DelayIncreaseDetectorInterface* active_delay_detector_;
int64_t last_seen_packet_;
bool uma_recorded_;
AimdRateControl rate_control_;
int64_t prev_bitrate_;
BandwidthUsage prev_state_;
};
#endif

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016 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 "interval_budget.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
namespace {
constexpr int64_t kWindowMs = 500;
}
IntervalBudget::IntervalBudget(int initial_target_rate_kbps)
: IntervalBudget(initial_target_rate_kbps, false) {}
IntervalBudget::IntervalBudget(int initial_target_rate_kbps,
bool can_build_up_underuse)
: bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) {
set_target_rate_kbps(initial_target_rate_kbps);
}
void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {
target_rate_kbps_ = target_rate_kbps;
max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;
bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),
max_bytes_in_budget_);
}
void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {
int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
if (bytes_remaining_ < 0 || can_build_up_underuse_) {
// We overused last interval, compensate this interval.
bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);
} else {
// If we underused last interval we can't use it this interval.
bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);
}
}
void IntervalBudget::UseBudget(size_t bytes) {
bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),
-max_bytes_in_budget_);
}
size_t IntervalBudget::bytes_remaining() const {
return static_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));
}
double IntervalBudget::budget_ratio() const {
if (max_bytes_in_budget_ == 0) return 0.0;
return static_cast<double>(bytes_remaining_) / max_bytes_in_budget_;
}
int IntervalBudget::target_rate_kbps() const { return target_rate_kbps_; }

36
src/qos/interval_budget.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _INTERVAL_BUDGET_H_
#define _INTERVAL_BUDGET_H_
#include <stddef.h>
#include <stdint.h>
// TODO(tschumim): Reflector IntervalBudget so that we can set a under- and
// over-use budget in ms.
class IntervalBudget {
public:
explicit IntervalBudget(int initial_target_rate_kbps);
IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse);
void set_target_rate_kbps(int target_rate_kbps);
// TODO(tschumim): Unify IncreaseBudget and UseBudget to one function.
void IncreaseBudget(int64_t delta_time_ms);
void UseBudget(size_t bytes);
size_t bytes_remaining() const;
double budget_ratio() const;
int target_rate_kbps() const;
private:
int target_rate_kbps_;
int64_t max_bytes_in_budget_;
int64_t bytes_remaining_;
bool can_build_up_underuse_;
};
#endif

View File

@@ -0,0 +1,505 @@
/*
* Copyright (c) 2012 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 "send_side_bandwidth_estimation.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "log.h"
namespace {
constexpr int64_t kBweIncreaseInterval = 1000;
constexpr int64_t kBweDecreaseInterval = 300;
constexpr int64_t kStartPhase = 2000;
constexpr int64_t kBweConverganceTime = 20000;
constexpr int kLimitNumPackets = 20;
constexpr int64_t kDefaultMaxBitrate = 1000000000;
constexpr int64_t kLowBitrateLogPeriod = 10000;
constexpr int64_t kRtcEventLogPeriod = 5000;
// Expecting that RTCP feedback is sent uniformly within [0.5, 1.5]s intervals.
constexpr int64_t kMaxRtcpFeedbackInterval = 5000;
constexpr float kDefaultLowLossThreshold = 0.02f;
constexpr float kDefaultHighLossThreshold = 0.1f;
constexpr int64_t kDefaultBitrateThreshold = 0;
constexpr int64_t kCongestionControllerMinBitrate = 5000;
struct UmaRampUpMetric {
const char* metric_name;
int bitrate_kbps;
};
const UmaRampUpMetric kUmaRampupMetrics[] = {
{"WebRTC.BWE.RampUpTimeTo500kbpsInMs", 500},
{"WebRTC.BWE.RampUpTimeTo1000kbpsInMs", 1000},
{"WebRTC.BWE.RampUpTimeTo2000kbpsInMs", 2000}};
const size_t kNumUmaRampupMetrics =
sizeof(kUmaRampupMetrics) / sizeof(kUmaRampupMetrics[0]);
} // namespace
void LinkCapacityTracker::UpdateDelayBasedEstimate(
int64_t at_time, int64_t delay_based_bitrate) {
if (delay_based_bitrate < last_delay_based_estimate_) {
capacity_estimate_bps_ = std::min(capacity_estimate_bps_,
static_cast<double>(delay_based_bitrate));
last_link_capacity_update_ = at_time;
}
last_delay_based_estimate_ = delay_based_bitrate;
}
void LinkCapacityTracker::OnStartingRate(int64_t start_rate) {
if (IsInfinite(last_link_capacity_update_)) {
capacity_estimate_bps_ = start_rate;
}
}
void LinkCapacityTracker::OnRateUpdate(std::optional<int64_t> acknowledged,
int64_t target, int64_t at_time) {
if (!acknowledged) return;
int64_t acknowledged_target = std::min(*acknowledged, target);
if (acknowledged_target > capacity_estimate_bps_) {
int64_t delta = at_time - last_link_capacity_update_;
double alpha = IsFinite(delta) ? exp(-(delta / 10)) : 0;
capacity_estimate_bps_ =
alpha * capacity_estimate_bps_ + (1 - alpha) * acknowledged_target;
}
last_link_capacity_update_ = at_time;
}
void LinkCapacityTracker::OnRttBackoff(int64_t backoff_rate, int64_t at_time) {
capacity_estimate_bps_ =
std::min(capacity_estimate_bps_, static_cast<double>(backoff_rate));
last_link_capacity_update_ = at_time;
}
int64_t LinkCapacityTracker::estimate() const { return capacity_estimate_bps_; }
RttBasedBackoff::RttBasedBackoff()
: disabled_(true),
configured_limit_(3),
drop_fraction_(0.8),
drop_interval_(1),
bandwidth_floor_(5),
rtt_limit_(INT64_T_MAX),
// By initializing this to plus infinity, we make sure that we never
// trigger rtt backoff unless packet feedback is enabled.
last_propagation_rtt_update_(INT64_T_MAX),
last_propagation_rtt_(0),
last_packet_sent_(INT64_T_MIN) {
if (!disabled_) {
rtt_limit_ = configured_limit_;
}
}
void RttBasedBackoff::UpdatePropagationRtt(int64_t at_time,
int64_t propagation_rtt) {
last_propagation_rtt_update_ = at_time;
last_propagation_rtt_ = propagation_rtt;
}
bool RttBasedBackoff::IsRttAboveLimit() const {
return CorrectedRtt() > rtt_limit_;
}
int64_t RttBasedBackoff::CorrectedRtt() const {
// Avoid timeout when no packets are being sent.
int64_t timeout_correction =
std::max(last_packet_sent_ - last_propagation_rtt_update_,
static_cast<int64_t>(0));
return timeout_correction + last_propagation_rtt_;
}
RttBasedBackoff::~RttBasedBackoff() = default;
SendSideBandwidthEstimation::SendSideBandwidthEstimation()
: lost_packets_since_last_loss_update_(0),
expected_packets_since_last_loss_update_(0),
current_target_(0),
last_logged_target_(0),
min_bitrate_configured_(kCongestionControllerMinBitrate),
max_bitrate_configured_(kDefaultMaxBitrate),
last_low_bitrate_log_(INT64_T_MIN),
has_decreased_since_last_fraction_loss_(false),
last_loss_feedback_(INT64_T_MIN),
last_loss_packet_report_(INT64_T_MIN),
last_fraction_loss_(0),
last_logged_fraction_loss_(0),
last_round_trip_time_(0),
receiver_limit_(INT64_T_MAX),
delay_based_limit_(INT64_T_MAX),
time_last_decrease_(INT64_T_MIN),
first_report_time_(INT64_T_MIN),
initially_lost_packets_(0),
bitrate_at_2_seconds_(0),
uma_update_state_(kNoUpdate),
uma_rtt_state_(kNoUpdate),
rampup_uma_stats_updated_(kNumUmaRampupMetrics, false),
last_rtc_event_log_(INT64_T_MIN),
low_loss_threshold_(kDefaultLowLossThreshold),
high_loss_threshold_(kDefaultHighLossThreshold),
bitrate_threshold_(kDefaultBitrateThreshold),
disable_receiver_limit_caps_only_(true) {}
SendSideBandwidthEstimation::~SendSideBandwidthEstimation() {}
void SendSideBandwidthEstimation::OnRouteChange() {
lost_packets_since_last_loss_update_ = 0;
expected_packets_since_last_loss_update_ = 0;
current_target_ = 0;
min_bitrate_configured_ = kCongestionControllerMinBitrate;
max_bitrate_configured_ = kDefaultMaxBitrate;
last_low_bitrate_log_ = INT64_T_MIN;
has_decreased_since_last_fraction_loss_ = false;
last_loss_feedback_ = INT64_T_MIN;
last_loss_packet_report_ = INT64_T_MIN;
last_fraction_loss_ = 0;
last_logged_fraction_loss_ = 0;
last_round_trip_time_ = 0;
receiver_limit_ = INT64_T_MAX;
delay_based_limit_ = INT64_T_MAX;
time_last_decrease_ = INT64_T_MIN;
first_report_time_ = INT64_T_MIN;
initially_lost_packets_ = 0;
bitrate_at_2_seconds_ = 0;
uma_update_state_ = kNoUpdate;
uma_rtt_state_ = kNoUpdate;
last_rtc_event_log_ = INT64_T_MIN;
}
void SendSideBandwidthEstimation::SetBitrates(
std::optional<int64_t> send_bitrate, int64_t min_bitrate,
int64_t max_bitrate, int64_t at_time) {
SetMinMaxBitrate(min_bitrate, max_bitrate);
if (send_bitrate) {
link_capacity_.OnStartingRate(*send_bitrate);
SetSendBitrate(*send_bitrate, at_time);
}
}
void SendSideBandwidthEstimation::SetSendBitrate(int64_t bitrate,
int64_t at_time) {
// Reset to avoid being capped by the estimate.
delay_based_limit_ = INT64_T_MAX;
UpdateTargetBitrate(bitrate, at_time);
// Clear last sent bitrate history so the new value can be used directly
// and not capped.
min_bitrate_history_.clear();
}
void SendSideBandwidthEstimation::SetMinMaxBitrate(int64_t min_bitrate,
int64_t max_bitrate) {
min_bitrate_configured_ =
std::max(min_bitrate, kCongestionControllerMinBitrate);
if (max_bitrate > 0 && IsFinite(max_bitrate)) {
max_bitrate_configured_ = std::max(min_bitrate_configured_, max_bitrate);
} else {
max_bitrate_configured_ = kDefaultMaxBitrate;
}
}
int SendSideBandwidthEstimation::GetMinBitrate() const {
return min_bitrate_configured_;
}
int64_t SendSideBandwidthEstimation::target_rate() const {
int64_t target = current_target_;
if (!disable_receiver_limit_caps_only_)
target = std::min(target, receiver_limit_);
return std::max(min_bitrate_configured_, target);
}
bool SendSideBandwidthEstimation::IsRttAboveLimit() const {
return rtt_backoff_.IsRttAboveLimit();
}
int64_t SendSideBandwidthEstimation::GetEstimatedLinkCapacity() const {
return link_capacity_.estimate();
}
void SendSideBandwidthEstimation::UpdateReceiverEstimate(int64_t at_time,
int64_t bandwidth) {
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
// limitation.
receiver_limit_ = !bandwidth ? INT64_T_MAX : bandwidth;
ApplyTargetLimits(at_time);
}
void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(int64_t at_time,
int64_t bitrate) {
link_capacity_.UpdateDelayBasedEstimate(at_time, bitrate);
// TODO(srte): Ensure caller passes PlusInfinity, not zero, to represent no
// limitation.
delay_based_limit_ = !bitrate ? INT64_T_MAX : bitrate;
ApplyTargetLimits(at_time);
}
void SendSideBandwidthEstimation::SetAcknowledgedRate(
std::optional<int64_t> acknowledged_rate, int64_t at_time) {
acknowledged_rate_ = acknowledged_rate;
if (!acknowledged_rate.has_value()) {
return;
}
}
void SendSideBandwidthEstimation::UpdatePacketsLost(int64_t packets_lost,
int64_t number_of_packets,
int64_t at_time) {
last_loss_feedback_ = at_time;
if (IsInfinite(first_report_time_)) {
first_report_time_ = at_time;
}
// Check sequence number diff and weight loss report
if (number_of_packets > 0) {
int64_t expected =
expected_packets_since_last_loss_update_ + number_of_packets;
// Don't generate a loss rate until it can be based on enough packets.
if (expected < kLimitNumPackets) {
// Accumulate reports.
expected_packets_since_last_loss_update_ = expected;
lost_packets_since_last_loss_update_ += packets_lost;
return;
}
has_decreased_since_last_fraction_loss_ = false;
int64_t lost_q8 =
std::max<int64_t>(lost_packets_since_last_loss_update_ + packets_lost,
static_cast<int64_t>(0))
<< 8;
last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);
// Reset accumulators.
lost_packets_since_last_loss_update_ = 0;
expected_packets_since_last_loss_update_ = 0;
last_loss_packet_report_ = at_time;
UpdateEstimate(at_time);
}
UpdateUmaStatsPacketsLost(at_time, packets_lost);
}
void SendSideBandwidthEstimation::UpdateUmaStatsPacketsLost(int64_t at_time,
int packets_lost) {
int64_t bitrate_kbps = (current_target_ + 500) / 1000;
for (size_t i = 0; i < kNumUmaRampupMetrics; ++i) {
if (!rampup_uma_stats_updated_[i] &&
bitrate_kbps >= kUmaRampupMetrics[i].bitrate_kbps) {
rampup_uma_stats_updated_[i] = true;
}
}
if (IsInStartPhase(at_time)) {
initially_lost_packets_ += packets_lost;
} else if (uma_update_state_ == kNoUpdate) {
uma_update_state_ = kFirstDone;
bitrate_at_2_seconds_ = bitrate_kbps;
} else if (uma_update_state_ == kFirstDone &&
at_time - first_report_time_ >= kBweConverganceTime) {
uma_update_state_ = kDone;
int bitrate_diff_kbps =
std::max(bitrate_at_2_seconds_ - bitrate_kbps, static_cast<int64_t>(0));
}
}
void SendSideBandwidthEstimation::UpdateRtt(int64_t rtt, int64_t at_time) {
// Update RTT if we were able to compute an RTT based on this RTCP.
// FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.
if (rtt > 0) last_round_trip_time_ = rtt;
if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {
uma_rtt_state_ = kDone;
}
}
void SendSideBandwidthEstimation::UpdateEstimate(int64_t at_time) {
if (rtt_backoff_.IsRttAboveLimit()) {
if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ &&
current_target_ > rtt_backoff_.bandwidth_floor_) {
time_last_decrease_ = at_time;
int64_t new_bitrate =
std::max(current_target_ * rtt_backoff_.drop_fraction_,
static_cast<double>(rtt_backoff_.bandwidth_floor_));
link_capacity_.OnRttBackoff(new_bitrate, at_time);
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
// TODO(srte): This is likely redundant in most cases.
ApplyTargetLimits(at_time);
return;
}
// We trust the REMB and/or delay-based estimate during the first 2 seconds if
// we haven't had any packet loss reported, to allow startup bitrate probing.
if (last_fraction_loss_ == 0 && IsInStartPhase(at_time)) {
int64_t new_bitrate = current_target_;
// TODO(srte): We should not allow the new_bitrate to be larger than the
// receiver limit here.
if (IsFinite(receiver_limit_)) {
new_bitrate = std::max(receiver_limit_, new_bitrate);
}
if (IsFinite(delay_based_limit_)) {
new_bitrate = std::max(delay_based_limit_, new_bitrate);
}
if (new_bitrate != current_target_) {
min_bitrate_history_.clear();
min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
}
UpdateMinHistory(at_time);
if (IsInfinite(last_loss_packet_report_)) {
// No feedback received.
// TODO(srte): This is likely redundant in most cases.
ApplyTargetLimits(at_time);
return;
}
int64_t time_since_loss_packet_report = at_time - last_loss_packet_report_;
if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {
// We only care about loss above a given bitrate threshold.
float loss = last_fraction_loss_ / 256.0f;
// We only make decisions based on loss when the bitrate is above a
// threshold. This is a crude way of handling loss which is uncorrelated
// to congestion.
if (current_target_ < bitrate_threshold_ || loss <= low_loss_threshold_) {
// Loss < 2%: Increase rate by 8% of the min bitrate in the last
// kBweIncreaseInterval.
// Note that by remembering the bitrate over the last second one can
// rampup up one second faster than if only allowed to start ramping
// at 8% per second rate now. E.g.:
// If sending a constant 100kbps it can rampup immediately to 108kbps
// whenever a receiver report is received with lower packet loss.
// If instead one would do: current_bitrate_ *= 1.08^(delta time),
// it would take over one second since the lower packet loss to achieve
// 108kbps.
int64_t new_bitrate = min_bitrate_history_.front().second * 1.08 + 0.5;
// Add 1 kbps extra, just to make sure that we do not get stuck
// (gives a little extra increase at low rates, negligible at higher
// rates).
new_bitrate += 1000;
UpdateTargetBitrate(new_bitrate, at_time);
return;
} else if (current_target_ > bitrate_threshold_) {
if (loss <= high_loss_threshold_) {
// Loss between 2% - 10%: Do nothing.
} else {
// Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval
// + rtt.
if (!has_decreased_since_last_fraction_loss_ &&
(at_time - time_last_decrease_) >=
(kBweDecreaseInterval + last_round_trip_time_)) {
time_last_decrease_ = at_time;
// Reduce rate:
// newRate = rate * (1 - 0.5*lossRate);
// where packetLoss = 256*lossRate;
int64_t new_bitrate =
(current_target_ *
static_cast<double>(512 - last_fraction_loss_)) /
512.0;
has_decreased_since_last_fraction_loss_ = true;
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
}
}
}
// TODO(srte): This is likely redundant in most cases.
ApplyTargetLimits(at_time);
}
void SendSideBandwidthEstimation::UpdatePropagationRtt(
int64_t at_time, int64_t propagation_rtt) {
rtt_backoff_.UpdatePropagationRtt(at_time, propagation_rtt);
}
void SendSideBandwidthEstimation::OnSentPacket(const SentPacket& sent_packet) {
// Only feedback-triggering packets will be reported here.
rtt_backoff_.last_packet_sent_ = sent_packet.send_time;
}
bool SendSideBandwidthEstimation::IsInStartPhase(int64_t at_time) const {
return (IsInfinite(first_report_time_)) ||
at_time - first_report_time_ < kStartPhase;
}
void SendSideBandwidthEstimation::UpdateMinHistory(int64_t at_time) {
// Remove old data points from history.
// Since history precision is in ms, add one so it is able to increase
// bitrate if it is off by as little as 0.5ms.
while (!min_bitrate_history_.empty() &&
at_time - min_bitrate_history_.front().first + 1 >
kBweIncreaseInterval) {
min_bitrate_history_.pop_front();
}
// Typical minimum sliding-window algorithm: Pop values higher than current
// bitrate before pushing it.
while (!min_bitrate_history_.empty() &&
current_target_ <= min_bitrate_history_.back().second) {
min_bitrate_history_.pop_back();
}
min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
}
int64_t SendSideBandwidthEstimation::GetUpperLimit() const {
int64_t upper_limit = delay_based_limit_;
if (disable_receiver_limit_caps_only_)
upper_limit = std::min(upper_limit, receiver_limit_);
return std::min(upper_limit, max_bitrate_configured_);
}
void SendSideBandwidthEstimation::MaybeLogLowBitrateWarning(int64_t bitrate,
int64_t at_time) {
if (at_time - last_low_bitrate_log_ > kLowBitrateLogPeriod) {
LOG_WARN(
"Estimated available bandwidth {} is below configured min bitrate {}",
bitrate, min_bitrate_configured_);
last_low_bitrate_log_ = at_time;
}
}
void SendSideBandwidthEstimation::MaybeLogLossBasedEvent(int64_t at_time) {
if (current_target_ != last_logged_target_ ||
last_fraction_loss_ != last_logged_fraction_loss_ ||
at_time - last_rtc_event_log_ > kRtcEventLogPeriod) {
last_logged_fraction_loss_ = last_fraction_loss_;
last_logged_target_ = current_target_;
last_rtc_event_log_ = at_time;
}
}
void SendSideBandwidthEstimation::UpdateTargetBitrate(int64_t new_bitrate,
int64_t at_time) {
new_bitrate = std::min(new_bitrate, GetUpperLimit());
if (new_bitrate < min_bitrate_configured_) {
MaybeLogLowBitrateWarning(new_bitrate, at_time);
new_bitrate = min_bitrate_configured_;
}
current_target_ = new_bitrate;
MaybeLogLossBasedEvent(at_time);
link_capacity_.OnRateUpdate(acknowledged_rate_, current_target_, at_time);
}
void SendSideBandwidthEstimation::ApplyTargetLimits(int64_t at_time) {
UpdateTargetBitrate(current_target_, at_time);
}

View File

@@ -0,0 +1,178 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-13
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SEND_SIDE_BANDWIDTH_ESTIMATION_H_
#define _SEND_SIDE_BANDWIDTH_ESTIMATION_H_
#include <cstddef>
#include <cstdint>
#include <deque>
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "limits_base.h"
#include "network_types.h"
class LinkCapacityTracker {
public:
LinkCapacityTracker() = default;
~LinkCapacityTracker() = default;
// Call when a new delay-based estimate is available.
void UpdateDelayBasedEstimate(int64_t at_time, int64_t delay_based_bitrate);
void OnStartingRate(int64_t start_rate);
void OnRateUpdate(std::optional<int64_t> acknowledged, int64_t target,
int64_t at_time);
void OnRttBackoff(int64_t backoff_rate, int64_t at_time);
int64_t estimate() const;
private:
double capacity_estimate_bps_ = 0;
int64_t last_link_capacity_update_ = INT64_T_MIN;
int64_t last_delay_based_estimate_ = INT64_T_MAX;
};
class RttBasedBackoff {
public:
explicit RttBasedBackoff();
~RttBasedBackoff();
void UpdatePropagationRtt(int64_t at_time, int64_t propagation_rtt);
bool IsRttAboveLimit() const;
bool disabled_;
int64_t configured_limit_;
double drop_fraction_;
int64_t drop_interval_;
int64_t bandwidth_floor_;
public:
int64_t rtt_limit_;
int64_t last_propagation_rtt_update_;
int64_t last_propagation_rtt_;
int64_t last_packet_sent_;
private:
int64_t CorrectedRtt() const;
};
class SendSideBandwidthEstimation {
public:
SendSideBandwidthEstimation();
~SendSideBandwidthEstimation();
void OnRouteChange();
int64_t target_rate() const;
// Return whether the current rtt is higher than the rtt limited configured in
// RttBasedBackoff.
bool IsRttAboveLimit() const;
uint8_t fraction_loss() const { return last_fraction_loss_; }
int64_t round_trip_time() const { return last_round_trip_time_; }
int64_t GetEstimatedLinkCapacity() const;
// Call periodically to update estimate.
void UpdateEstimate(int64_t at_time);
void OnSentPacket(const SentPacket& sent_packet);
void UpdatePropagationRtt(int64_t at_time, int64_t propagation_rtt);
// Call when we receive a RTCP message with TMMBR or REMB.
void UpdateReceiverEstimate(int64_t at_time, int64_t bandwidth);
// Call when a new delay-based estimate is available.
void UpdateDelayBasedEstimate(int64_t at_time, int64_t bitrate);
// Call when we receive a RTCP message with a ReceiveBlock.
void UpdatePacketsLost(int64_t packets_lost, int64_t number_of_packets,
int64_t at_time);
// Call when we receive a RTCP message with a ReceiveBlock.
void UpdateRtt(int64_t rtt, int64_t at_time);
void SetBitrates(std::optional<int64_t> send_bitrate, int64_t min_bitrate,
int64_t max_bitrate, int64_t at_time);
void SetSendBitrate(int64_t bitrate, int64_t at_time);
void SetMinMaxBitrate(int64_t min_bitrate, int64_t max_bitrate);
int GetMinBitrate() const;
void SetAcknowledgedRate(std::optional<int64_t> acknowledged_rate,
int64_t at_time);
private:
friend class GoogCcStatePrinter;
enum UmaState { kNoUpdate, kFirstDone, kDone };
bool IsInStartPhase(int64_t at_time) const;
void UpdateUmaStatsPacketsLost(int64_t at_time, int packets_lost);
// Updates history of min bitrates.
// After this method returns min_bitrate_history_.front().second contains the
// min bitrate used during last kBweIncreaseIntervalMs.
void UpdateMinHistory(int64_t at_time);
// Gets the upper limit for the target bitrate. This is the minimum of the
// delay based limit, the receiver limit and the loss based controller limit.
int64_t GetUpperLimit() const;
// Prints a warning if `bitrate` if sufficiently long time has past since last
// warning.
void MaybeLogLowBitrateWarning(int64_t bitrate, int64_t at_time);
// Stores an update to the event log if the loss rate has changed, the target
// has changed, or sufficient time has passed since last stored event.
void MaybeLogLossBasedEvent(int64_t at_time);
// Cap `bitrate` to [min_bitrate_configured_, max_bitrate_configured_] and
// set `current_bitrate_` to the capped value and updates the event log.
void UpdateTargetBitrate(int64_t bitrate, int64_t at_time);
// Applies lower and upper bounds to the current target rate.
// TODO(srte): This seems to be called even when limits haven't changed, that
// should be cleaned up.
void ApplyTargetLimits(int64_t at_time);
RttBasedBackoff rtt_backoff_;
LinkCapacityTracker link_capacity_;
std::deque<std::pair<int64_t, int64_t> > min_bitrate_history_;
// incoming filters
int lost_packets_since_last_loss_update_;
int expected_packets_since_last_loss_update_;
std::optional<int64_t> acknowledged_rate_;
int64_t current_target_;
int64_t last_logged_target_;
int64_t min_bitrate_configured_;
int64_t max_bitrate_configured_;
int64_t last_low_bitrate_log_;
bool has_decreased_since_last_fraction_loss_;
int64_t last_loss_feedback_;
int64_t last_loss_packet_report_;
uint8_t last_fraction_loss_;
uint8_t last_logged_fraction_loss_;
int64_t last_round_trip_time_;
// The max bitrate as set by the receiver in the call. This is typically
// signalled using the REMB RTCP message and is used when we don't have any
// send side delay based estimate.
int64_t receiver_limit_;
int64_t delay_based_limit_;
int64_t time_last_decrease_;
int64_t first_report_time_;
int initially_lost_packets_;
int64_t bitrate_at_2_seconds_;
UmaState uma_update_state_;
UmaState uma_rtt_state_;
std::vector<bool> rampup_uma_stats_updated_;
int64_t last_rtc_event_log_;
float low_loss_threshold_;
float high_loss_threshold_;
int64_t bitrate_threshold_;
bool disable_receiver_limit_caps_only_;
};
#endif