From a8e9609736f2973876d5da89c37cdb2f73859548 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 14 Jan 2025 17:31:18 +0800 Subject: [PATCH] [feat] implementation for send side congestion controller --- src/common/limits_base.h | 36 ++ src/qos/acknowledged_bitrate_estimator.cpp | 63 +++ src/qos/acknowledged_bitrate_estimator.h | 38 ++ src/qos/alr_detector.cpp | 62 +++ src/qos/alr_detector.h | 60 +++ src/qos/bitrate_estimator.cpp | 137 ++++++ src/qos/bitrate_estimator.h | 50 ++ src/qos/congestion_control.cpp | 159 ++++--- src/qos/congestion_control.h | 12 +- src/qos/constrained.h | 31 ++ src/qos/delay_based_bwe.cpp | 285 ++++++++++++ src/qos/delay_based_bwe.h | 115 +++++ src/qos/interval_budget.cpp | 62 +++ src/qos/interval_budget.h | 36 ++ src/qos/send_side_bandwidth_estimation.cpp | 505 +++++++++++++++++++++ src/qos/send_side_bandwidth_estimation.h | 178 ++++++++ 16 files changed, 1747 insertions(+), 82 deletions(-) create mode 100644 src/common/limits_base.h create mode 100644 src/qos/acknowledged_bitrate_estimator.cpp create mode 100644 src/qos/acknowledged_bitrate_estimator.h create mode 100644 src/qos/alr_detector.cpp create mode 100644 src/qos/alr_detector.h create mode 100644 src/qos/bitrate_estimator.cpp create mode 100644 src/qos/bitrate_estimator.h create mode 100644 src/qos/constrained.h create mode 100644 src/qos/delay_based_bwe.cpp create mode 100644 src/qos/delay_based_bwe.h create mode 100644 src/qos/interval_budget.cpp create mode 100644 src/qos/interval_budget.h create mode 100644 src/qos/send_side_bandwidth_estimation.cpp create mode 100644 src/qos/send_side_bandwidth_estimation.h diff --git a/src/common/limits_base.h b/src/common/limits_base.h new file mode 100644 index 0000000..5cd4022 --- /dev/null +++ b/src/common/limits_base.h @@ -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 + +template +bool IsInfinite(const T& value) { + return value == std::numeric_limits::min() || + value == std::numeric_limits::max(); +} + +template +bool IsFinite(const T& value) { + return !IsInfinite(value); +} + +template +bool IsPlusFinite(const T& value) { + return value == std::numeric_limits::max(); +} + +template +bool IsMinusFinite(const T& value) { + return value == std::numeric_limits::min(); +} + +#define INT64_T_MAX std::numeric_limits::max() +#define INT64_T_MIN std::numeric_limits::min() + +#endif \ No newline at end of file diff --git a/src/qos/acknowledged_bitrate_estimator.cpp b/src/qos/acknowledged_bitrate_estimator.cpp new file mode 100644 index 0000000..b344f3d --- /dev/null +++ b/src/qos/acknowledged_bitrate_estimator.cpp @@ -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 +#include +#include +#include +#include + +#include "bitrate_estimator.h" +#include "log.h" +#include "network_types.h" + +AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator() + : AcknowledgedBitrateEstimator(std::make_unique()) {} + +AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {} + +AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator( + std::unique_ptr bitrate_estimator) + : in_alr_(false), bitrate_estimator_(std::move(bitrate_estimator)) {} + +void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector( + const std::vector& 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 AcknowledgedBitrateEstimator::bitrate() const { + return bitrate_estimator_->bitrate(); +} + +std::optional 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; } diff --git a/src/qos/acknowledged_bitrate_estimator.h b/src/qos/acknowledged_bitrate_estimator.h new file mode 100644 index 0000000..da97106 --- /dev/null +++ b/src/qos/acknowledged_bitrate_estimator.h @@ -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 +#include +#include + +#include "bitrate_estimator.h" +#include "network_types.h" + +class AcknowledgedBitrateEstimator { + public: + AcknowledgedBitrateEstimator( + std::unique_ptr bitrate_estimator); + + explicit AcknowledgedBitrateEstimator(); + ~AcknowledgedBitrateEstimator(); + + void IncomingPacketFeedbackVector( + const std::vector& packet_feedback_vector); + std::optional bitrate() const; + std::optional PeekRate() const; + void SetAlr(bool in_alr); + void SetAlrEndedTime(int64_t alr_ended_time); + + private: + std::optional alr_ended_time_; + bool in_alr_; + std::unique_ptr bitrate_estimator_; +}; + +#endif \ No newline at end of file diff --git a/src/qos/alr_detector.cpp b/src/qos/alr_detector.cpp new file mode 100644 index 0000000..ca264b0 --- /dev/null +++ b/src/qos/alr_detector.cpp @@ -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 +#include +#include +#include +#include + +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::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(bitrate_bps) * conf_.bandwidth_usage_ratio / 1000; + alr_budget_.set_target_rate_kbps(target_rate_kbps); +} + +std::optional AlrDetector::GetApplicationLimitedRegionStartTime() + const { + return alr_started_time_ms_; +} diff --git a/src/qos/alr_detector.h b/src/qos/alr_detector.h new file mode 100644 index 0000000..2d3a092 --- /dev/null +++ b/src/qos/alr_detector.h @@ -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 +#include + +#include +#include + +#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 GetApplicationLimitedRegionStartTime() const; + + private: + friend class GoogCcStatePrinter; + const AlrDetectorConfig conf_; + + std::optional last_send_time_ms_; + + IntervalBudget alr_budget_; + std::optional alr_started_time_ms_; +}; + +#endif \ No newline at end of file diff --git a/src/qos/bitrate_estimator.cpp b/src/qos/bitrate_estimator.cpp new file mode 100644 index 0000000..1d400ae --- /dev/null +++ b/src/qos/bitrate_estimator.cpp @@ -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 +#include +#include +#include + +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(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(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(rate_window_ms); + current_window_ms_ -= rate_window_ms; + sum_ = 0; + } + sum_ += bytes; + return bitrate_sample; +} + +std::optional BitrateEstimator::bitrate() const { + if (bitrate_estimate_kbps_ < 0.f) return std::nullopt; + return static_cast(bitrate_estimate_kbps_); +} + +std::optional 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; +} diff --git a/src/qos/bitrate_estimator.h b/src/qos/bitrate_estimator.h new file mode 100644 index 0000000..74a6c88 --- /dev/null +++ b/src/qos/bitrate_estimator.h @@ -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 + +#include + +#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 bitrate() const; + std::optional 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 initial_window_ms_; + Constrained 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 \ No newline at end of file diff --git a/src/qos/congestion_control.cpp b/src/qos/congestion_control.cpp index 0003266..8db3857 100644 --- a/src/qos/congestion_control.cpp +++ b/src/qos/congestion_control.cpp @@ -61,16 +61,16 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback( if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow) feedback_max_rtts_.pop_front(); // TODO(srte): Use time since last unacknowledged packet. - // bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time, - // min_propagation_rtt); + bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time, + min_propagation_rtt); } if (packet_feedback_only_) { if (!feedback_max_rtts_.empty()) { int64_t sum_rtt_ms = std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(), static_cast(0)); - // int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size(); - // if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms); + int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size(); + if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(mean_rtt_ms); } int64_t feedback_min_rtt = std::numeric_limits::max(); @@ -83,8 +83,7 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback( } if (feedback_min_rtt != std::numeric_limits::max() && feedback_min_rtt != std::numeric_limits::min()) { - // bandwidth_estimation_->UpdateRtt(feedback_min_rtt, - // report.feedback_time); + bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time); } expected_packets_since_last_loss_update_ += @@ -95,101 +94,101 @@ NetworkControlUpdate CongestionControl::OnTransportPacketsFeedback( } if (report.feedback_time > next_loss_update_) { next_loss_update_ = report.feedback_time + kLossUpdateInterval; - // bandwidth_estimation_->UpdatePacketsLost( - // lost_packets_since_last_loss_update_, - // expected_packets_since_last_loss_update_, report.feedback_time); + bandwidth_estimation_->UpdatePacketsLost( + lost_packets_since_last_loss_update_, + expected_packets_since_last_loss_update_, report.feedback_time); expected_packets_since_last_loss_update_ = 0; lost_packets_since_last_loss_update_ = 0; } } - // std::optional alr_start_time = - // alr_detector_->GetApplicationLimitedRegionStartTime(); + std::optional alr_start_time = + alr_detector_->GetApplicationLimitedRegionStartTime(); - // if (previously_in_alr_ && !alr_start_time.has_value()) { - // int64_t now_ms = report.feedback_time; - // acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time); - // probe_controller_->SetAlrEndedTimeMs(now_ms); - // } - // previously_in_alr_ = alr_start_time.has_value(); - // acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( - // report.SortedByReceiveTime()); - // auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate(); - // bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate, - // report.feedback_time); + if (previously_in_alr_ && !alr_start_time.has_value()) { + int64_t now_ms = report.feedback_time; + acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time); + probe_controller_->SetAlrEndedTimeMs(now_ms); + } + previously_in_alr_ = alr_start_time.has_value(); + acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( + report.SortedByReceiveTime()); + auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate(); + bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate, + report.feedback_time); for (const auto& feedback : report.SortedByReceiveTime()) { if (feedback.sent_packet.pacing_info.probe_cluster_id != PacedPacketInfo::kNotAProbe) { - // probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback); + probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback); } } - // if (network_estimator_) { - // network_estimator_->OnTransportPacketsFeedback(report); - // // SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate()); - // } - // std::optional probe_bitrate = - // probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(); - // if (ignore_probes_lower_than_network_estimate_ && probe_bitrate && - // estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() && - // *probe_bitrate < estimate_->link_capacity_lower) { - // probe_bitrate.reset(); - // } - // if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate && - // acknowledged_bitrate) { - // Limit the backoff to something slightly below the acknowledged - // bitrate. ("Slightly below" because we want to drain the queues - // if we are actually overusing.) - // The acknowledged bitrate shouldn't normally be higher than the delay - // 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 - // below the current BWE never causes an increase. - // int64_t limit = - // std::min(delay_based_bwe_->last_estimate(), - // *acknowledged_bitrate * kProbeDropThroughputFraction); - // probe_bitrate = std::max(*probe_bitrate, limit); - // } + if (network_estimator_) { + network_estimator_->OnTransportPacketsFeedback(report); + SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate()); + } + std::optional probe_bitrate = + probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(); + if (ignore_probes_lower_than_network_estimate_ && probe_bitrate && + estimate_ && *probe_bitrate < delay_based_bwe_->last_estimate() && + *probe_bitrate < estimate_->link_capacity_lower) { + probe_bitrate.reset(); + } + if (limit_probes_lower_than_throughput_estimate_ && probe_bitrate && + acknowledged_bitrate) { + // Limit the backoff to something slightly below the acknowledged + // bitrate. ("Slightly below" because we want to drain the queues + // if we are actually overusing.) + // The acknowledged bitrate shouldn't normally be higher than the delay + // 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 + // below the current BWE never causes an increase. + int64_t limit = + std::min(delay_based_bwe_->last_estimate(), + *acknowledged_bitrate * kProbeDropThroughputFraction); + probe_bitrate = std::max(*probe_bitrate, limit); + } NetworkControlUpdate update; bool recovered_from_overuse = false; - // DelayBasedBwe::Result result; - // result = delay_based_bwe_->IncomingPacketFeedbackVector( - // report, acknowledged_bitrate, probe_bitrate, estimate_, - // alr_start_time.has_value()); + DelayBasedBwe::Result result; + result = delay_based_bwe_->IncomingPacketFeedbackVector( + report, acknowledged_bitrate, probe_bitrate, estimate_, + alr_start_time.has_value()); - // if (result.updated) { - // if (result.probe) { - // bandwidth_estimation_->SetSendBitrate(result.target_bitrate, - // report.feedback_time); - // } - // Since SetSendBitrate now resets the delay-based estimate, we have to - // call UpdateDelayBasedEstimate after SetSendBitrate. - // bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time, - // result.target_bitrate); - // } - // bandwidth_estimation_->UpdateLossBasedEstimator( - // report, result.delay_detector_state, probe_bitrate, - // alr_start_time.has_value()); - // if (result.updated) { - // // Update the estimate in the ProbeController, in case we want to probe. - // MaybeTriggerOnNetworkChanged(&update, report.feedback_time); - // } + if (result.updated) { + if (result.probe) { + bandwidth_estimation_->SetSendBitrate(result.target_bitrate, + report.feedback_time); + } + // Since SetSendBitrate now resets the delay-based estimate, we have to + // call UpdateDelayBasedEstimate after SetSendBitrate. + bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time, + result.target_bitrate); + } + bandwidth_estimation_->UpdateLossBasedEstimator( + report, result.delay_detector_state, probe_bitrate, + alr_start_time.has_value()); + if (result.updated) { + // Update the estimate in the ProbeController, in case we want to probe. + MaybeTriggerOnNetworkChanged(&update, report.feedback_time); + } - // recovered_from_overuse = result.recovered_from_overuse; + recovered_from_overuse = result.recovered_from_overuse; - // if (recovered_from_overuse) { - // probe_controller_->SetAlrStartTimeMs(alr_start_time); - // auto probes = probe_controller_->RequestProbe(report.feedback_time); - // update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), - // probes.begin(), probes.end()); - // } + if (recovered_from_overuse) { + probe_controller_->SetAlrStartTimeMs(alr_start_time); + auto probes = probe_controller_->RequestProbe(report.feedback_time); + update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), + probes.begin(), probes.end()); + } // No valid RTT could be because send-side BWE isn't used, in which case // we don't try to limit the outstanding packets. - // if (rate_control_settings_.UseCongestionWindow() && - // max_feedback_rtt.IsFinite()) { - // UpdateCongestionWindowSize(); - // } + if (rate_control_settings_.UseCongestionWindow() && + max_feedback_rtt.IsFinite()) { + UpdateCongestionWindowSize(); + } if (congestion_window_pushback_controller_ && current_data_window_) { congestion_window_pushback_controller_->SetDataWindow( *current_data_window_); diff --git a/src/qos/congestion_control.h b/src/qos/congestion_control.h index ca6e7db..9e77b6a 100644 --- a/src/qos/congestion_control.h +++ b/src/qos/congestion_control.h @@ -4,8 +4,11 @@ #include #include +#include "acknowledged_bitrate_estimator.h" +#include "alr_detector.h" #include "congestion_window_pushback_controller.h" #include "network_types.h" +#include "send_side_bandwidth_estimation.h" class CongestionControl { public: @@ -22,14 +25,19 @@ class CongestionControl { private: std::deque feedback_max_rtts_; - // std::unique_ptr bandwidth_estimation_; int expected_packets_since_last_loss_update_ = 0; int lost_packets_since_last_loss_update_ = 0; int64_t next_loss_update_ = std::numeric_limits::min(); const bool packet_feedback_only_ = 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 current_data_window_; + + private: + std::unique_ptr bandwidth_estimation_; + std::unique_ptr alr_detector_; + std::unique_ptr acknowledged_bitrate_estimator_; + std::unique_ptr delay_based_bwe_; }; #endif \ No newline at end of file diff --git a/src/qos/constrained.h b/src/qos/constrained.h new file mode 100644 index 0000000..8faa076 --- /dev/null +++ b/src/qos/constrained.h @@ -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 +#include + +template +class Constrained { + public: + Constrained(T default_value, std::optional lower_limit, + std::optional 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 lower_limit_; + std::optional upper_limit_; +}; + +#endif \ No newline at end of file diff --git a/src/qos/delay_based_bwe.cpp b/src/qos/delay_based_bwe.cpp new file mode 100644 index 0000000..d32b799 --- /dev/null +++ b/src/qos/delay_based_bwe.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 +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 acked_bitrate, + std::optional probe_bitrate, + std::optional 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(kSendTimeGroupLength); + audio_inter_arrival_delta_ = + std::make_unique(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(), + send_delta.ms(), + 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 link_capacity) { + RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity); + return rate_control_.Update(input, at_time); +} + +DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate( + std::optional acked_bitrate, std::optional probe_bitrate, + std::optional /* 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( + 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 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* 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(); +} diff --git a/src/qos/delay_based_bwe.h b/src/qos/delay_based_bwe.h new file mode 100644 index 0000000..fb714d3 --- /dev/null +++ b/src/qos/delay_based_bwe.h @@ -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 + +#include +#include +#include + +#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 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 acked_bitrate, + std::optional probe_bitrate, + std::optional network_estimate, bool in_alr); + void OnRttUpdate(int64_t avg_rtt); + bool LatestEstimate(std::vector* 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 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 acked_bitrate, + std::optional probe_bitrate, + std::optional 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 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 video_inter_arrival_; + std::unique_ptr video_inter_arrival_delta_; + std::unique_ptr video_delay_detector_; + std::unique_ptr audio_inter_arrival_; + std::unique_ptr audio_inter_arrival_delta_; + std::unique_ptr 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 \ No newline at end of file diff --git a/src/qos/interval_budget.cpp b/src/qos/interval_budget.cpp new file mode 100644 index 0000000..e8b4140 --- /dev/null +++ b/src/qos/interval_budget.cpp @@ -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 +#include +#include + +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(bytes), + -max_bytes_in_budget_); +} + +size_t IntervalBudget::bytes_remaining() const { + return static_cast(std::max(0, bytes_remaining_)); +} + +double IntervalBudget::budget_ratio() const { + if (max_bytes_in_budget_ == 0) return 0.0; + return static_cast(bytes_remaining_) / max_bytes_in_budget_; +} + +int IntervalBudget::target_rate_kbps() const { return target_rate_kbps_; } diff --git a/src/qos/interval_budget.h b/src/qos/interval_budget.h new file mode 100644 index 0000000..0d9894f --- /dev/null +++ b/src/qos/interval_budget.h @@ -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 +#include + +// 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 \ No newline at end of file diff --git a/src/qos/send_side_bandwidth_estimation.cpp b/src/qos/send_side_bandwidth_estimation.cpp new file mode 100644 index 0000000..8974225 --- /dev/null +++ b/src/qos/send_side_bandwidth_estimation.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 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(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(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 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 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(lost_packets_since_last_loss_update_ + packets_lost, + static_cast(0)) + << 8; + last_fraction_loss_ = std::min(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(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(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(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); +} diff --git a/src/qos/send_side_bandwidth_estimation.h b/src/qos/send_side_bandwidth_estimation.h new file mode 100644 index 0000000..51829b4 --- /dev/null +++ b/src/qos/send_side_bandwidth_estimation.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 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 > min_bitrate_history_; + + // incoming filters + int lost_packets_since_last_loss_update_; + int expected_packets_since_last_loss_update_; + + std::optional 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 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 \ No newline at end of file