From 3a83748422c9bf7a7b28a7c00b5f732762412b18 Mon Sep 17 00:00:00 2001 From: Christoffer Rodbro Date: Mon, 19 Nov 2018 15:30:23 +0100 Subject: [PATCH] New loss-based bandwidth control mechanism. Bug: none Change-Id: Ie60e9225e2a2260624342ffbadb08cb887b2b6f5 Reviewed-on: https://webrtc-review.googlesource.com/c/109923 Commit-Queue: Christoffer Rodbro Reviewed-by: Sebastian Jansson Cr-Commit-Position: refs/heads/master@{#25696} --- modules/bitrate_controller/BUILD.gn | 2 + .../loss_based_bandwidth_estimation.cc | 215 ++++++++++++++++++ .../loss_based_bandwidth_estimation.h | 80 +++++++ .../send_side_bandwidth_estimation.cc | 64 +++++- .../send_side_bandwidth_estimation.h | 7 + .../goog_cc/goog_cc_network_control.cc | 3 + 6 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 modules/bitrate_controller/loss_based_bandwidth_estimation.cc create mode 100644 modules/bitrate_controller/loss_based_bandwidth_estimation.h diff --git a/modules/bitrate_controller/BUILD.gn b/modules/bitrate_controller/BUILD.gn index 460dfc4c22..b501cb4457 100644 --- a/modules/bitrate_controller/BUILD.gn +++ b/modules/bitrate_controller/BUILD.gn @@ -15,6 +15,8 @@ rtc_static_library("bitrate_controller") { "bitrate_controller_impl.cc", "bitrate_controller_impl.h", "include/bitrate_controller.h", + "loss_based_bandwidth_estimation.cc", + "loss_based_bandwidth_estimation.h", "send_side_bandwidth_estimation.cc", "send_side_bandwidth_estimation.h", ] diff --git a/modules/bitrate_controller/loss_based_bandwidth_estimation.cc b/modules/bitrate_controller/loss_based_bandwidth_estimation.cc new file mode 100644 index 0000000000..5d7f8aa2c3 --- /dev/null +++ b/modules/bitrate_controller/loss_based_bandwidth_estimation.cc @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2018 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 "modules/bitrate_controller/loss_based_bandwidth_estimation.h" + +#include +#include +#include + +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { +namespace { +const char kBweLossBasedControl[] = "WebRTC-Bwe-LossBasedControl"; + +// Increase slower when RTT is high. +double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) { + // Clamp the RTT + if (rtt < config.increase_low_rtt) { + rtt = config.increase_low_rtt; + } else if (rtt > config.increase_high_rtt) { + rtt = config.increase_high_rtt; + } + auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt; + if (rtt_range <= TimeDelta::Zero()) { + RTC_DCHECK(false); // Only on misconfiguration. + return config.min_increase_factor; + } + auto rtt_offset = rtt - config.increase_low_rtt; + auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0)); + auto factor_range = config.max_increase_factor - config.min_increase_factor; + return config.min_increase_factor + (1 - relative_offset) * factor_range; +} + +double LossFromBitrate(DataRate bitrate, + DataRate loss_bandwidth_balance, + double exponent) { + if (loss_bandwidth_balance >= bitrate) + return 1.0; + return pow(loss_bandwidth_balance / bitrate, exponent); +} + +DataRate BitrateFromLoss(double loss, + DataRate loss_bandwidth_balance, + double exponent) { + if (exponent <= 0) { + RTC_DCHECK(false); + return DataRate::Infinity(); + } + if (loss < 1e-5) + return DataRate::Infinity(); + return loss_bandwidth_balance * pow(loss, -1.0 / exponent); +} + +double ExponentialUpdate(TimeDelta window, TimeDelta interval) { + // Use the convention that exponential window length (which is really + // infinite) is the time it takes to dampen to 1/e. + if (window <= TimeDelta::Zero()) { + RTC_DCHECK(false); + return 1.0f; + } + return 1.0f - exp(interval / window * -1.0); +} + +} // namespace + +LossBasedControlConfig::LossBasedControlConfig() + : enabled(field_trial::IsEnabled(kBweLossBasedControl)), + min_increase_factor("min_incr", 1.02), + max_increase_factor("max_incr", 1.08), + increase_low_rtt("incr_low_rtt", TimeDelta::ms(200)), + increase_high_rtt("incr_high_rtt", TimeDelta::ms(800)), + decrease_factor("decr", 0.99), + loss_window("loss_win", TimeDelta::ms(800)), + loss_max_window("loss_max_win", TimeDelta::ms(800)), + acknowledged_rate_max_window("ackrate_max_win", TimeDelta::ms(800)), + increase_offset("incr_offset", DataRate::bps(1000)), + loss_bandwidth_balance_increase("balance_incr", DataRate::kbps(0.5)), + loss_bandwidth_balance_decrease("balance_decr", DataRate::kbps(4)), + loss_bandwidth_balance_exponent("exponent", 0.5), + allow_resets("resets", false), + decrease_interval("decr_intvl", TimeDelta::ms(300)), + loss_report_timeout("timeout", TimeDelta::ms(6000)) { + std::string trial_string = field_trial::FindFullName(kBweLossBasedControl); + ParseFieldTrial( + {&min_increase_factor, &max_increase_factor, &increase_low_rtt, + &increase_high_rtt, &decrease_factor, &loss_window, &loss_max_window, + &acknowledged_rate_max_window, &increase_offset, + &loss_bandwidth_balance_increase, &loss_bandwidth_balance_decrease, + &loss_bandwidth_balance_exponent, &allow_resets, &decrease_interval, + &loss_report_timeout}, + trial_string); +} +LossBasedControlConfig::LossBasedControlConfig(const LossBasedControlConfig&) = + default; +LossBasedControlConfig::~LossBasedControlConfig() = default; + +LossBasedBandwidthEstimation::LossBasedBandwidthEstimation() + : config_(LossBasedControlConfig()), + average_loss_(0), + average_loss_max_(0), + loss_based_bitrate_(DataRate::Zero()), + acknowledged_bitrate_max_(DataRate::Zero()), + acknowledged_bitrate_last_update_(Timestamp::MinusInfinity()), + time_last_decrease_(Timestamp::MinusInfinity()), + has_decreased_since_last_loss_report_(false), + last_loss_packet_report_(Timestamp::MinusInfinity()), + last_loss_ratio_(0) {} + +void LossBasedBandwidthEstimation::UpdateLossStatistics( + const std::vector& packet_results, + Timestamp at_time) { + if (packet_results.empty()) { + RTC_DCHECK(false); + return; + } + int loss_count = 0; + for (auto pkt : packet_results) { + loss_count += pkt.receive_time.IsInfinite() ? 1 : 0; + } + last_loss_ratio_ = static_cast(loss_count) / packet_results.size(); + const TimeDelta time_passed = last_loss_packet_report_.IsFinite() + ? at_time - last_loss_packet_report_ + : TimeDelta::seconds(1); + last_loss_packet_report_ = at_time; + has_decreased_since_last_loss_report_ = false; + + average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) * + (last_loss_ratio_ - average_loss_); + if (average_loss_ > average_loss_max_) { + average_loss_max_ = average_loss_; + } else { + average_loss_max_ += + ExponentialUpdate(config_.loss_max_window, time_passed) * + (average_loss_ - average_loss_max_); + } +} + +void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate( + DataRate acknowledged_bitrate, + Timestamp at_time) { + const TimeDelta time_passed = + acknowledged_bitrate_last_update_.IsFinite() + ? at_time - acknowledged_bitrate_last_update_ + : TimeDelta::seconds(1); + acknowledged_bitrate_last_update_ = at_time; + if (acknowledged_bitrate > acknowledged_bitrate_max_) { + acknowledged_bitrate_max_ = acknowledged_bitrate; + } else { + acknowledged_bitrate_max_ += + ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) * + (acknowledged_bitrate - acknowledged_bitrate_max_); + } +} + +void LossBasedBandwidthEstimation::Update(Timestamp at_time, + DataRate min_bitrate, + TimeDelta last_round_trip_time) { + // Only increase if loss has been low for some time. + const double loss_estimate_for_increase = average_loss_max_; + // Avoid multiple decreases from averaging over one loss spike. + const double loss_estimate_for_decrease = + std::min(average_loss_, last_loss_ratio_); + + const double loss_increase_threshold = LossFromBitrate( + loss_based_bitrate_, config_.loss_bandwidth_balance_increase, + config_.loss_bandwidth_balance_exponent); + const double loss_decrease_threshold = LossFromBitrate( + loss_based_bitrate_, config_.loss_bandwidth_balance_decrease, + config_.loss_bandwidth_balance_exponent); + const bool allow_decrease = + !has_decreased_since_last_loss_report_ && + (at_time - time_last_decrease_ >= + last_round_trip_time + config_.decrease_interval); + + if (loss_estimate_for_increase < loss_increase_threshold) { + // Increase bitrate by RTT-adaptive ratio. + DataRate new_increased_bitrate = + min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) + + config_.increase_offset; + // The bitrate that would make the loss "just high enough". + const DataRate new_increased_bitrate_cap = BitrateFromLoss( + loss_estimate_for_increase, config_.loss_bandwidth_balance_increase, + config_.loss_bandwidth_balance_exponent); + new_increased_bitrate = + std::min(new_increased_bitrate, new_increased_bitrate_cap); + loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_); + } else if (loss_estimate_for_decrease > loss_decrease_threshold && + allow_decrease) { + DataRate new_decreased_bitrate = + config_.decrease_factor * acknowledged_bitrate_max_; + // The bitrate that would make the loss "just acceptable". + const DataRate new_decreased_bitrate_floor = BitrateFromLoss( + loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease, + config_.loss_bandwidth_balance_exponent); + new_decreased_bitrate = + std::max(new_decreased_bitrate, new_decreased_bitrate_floor); + if (new_decreased_bitrate < loss_based_bitrate_) { + time_last_decrease_ = at_time; + has_decreased_since_last_loss_report_ = true; + loss_based_bitrate_ = new_decreased_bitrate; + } + } +} + +} // namespace webrtc diff --git a/modules/bitrate_controller/loss_based_bandwidth_estimation.h b/modules/bitrate_controller/loss_based_bandwidth_estimation.h new file mode 100644 index 0000000000..0f560769ba --- /dev/null +++ b/modules/bitrate_controller/loss_based_bandwidth_estimation.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ +#define MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ + +#include + +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/experiments/field_trial_parser.h" + +namespace webrtc { + +struct LossBasedControlConfig { + LossBasedControlConfig(); + LossBasedControlConfig(const LossBasedControlConfig&); + LossBasedControlConfig& operator=(const LossBasedControlConfig&) = default; + ~LossBasedControlConfig(); + bool enabled; + FieldTrialParameter min_increase_factor; + FieldTrialParameter max_increase_factor; + FieldTrialParameter increase_low_rtt; + FieldTrialParameter increase_high_rtt; + FieldTrialParameter decrease_factor; + FieldTrialParameter loss_window; + FieldTrialParameter loss_max_window; + FieldTrialParameter acknowledged_rate_max_window; + FieldTrialParameter increase_offset; + FieldTrialParameter loss_bandwidth_balance_increase; + FieldTrialParameter loss_bandwidth_balance_decrease; + FieldTrialParameter loss_bandwidth_balance_exponent; + FieldTrialParameter allow_resets; + FieldTrialParameter decrease_interval; + FieldTrialParameter loss_report_timeout; +}; + +class LossBasedBandwidthEstimation { + public: + LossBasedBandwidthEstimation(); + void Update(Timestamp at_time, + DataRate min_bitrate, + TimeDelta last_round_trip_time); + void UpdateAcknowledgedBitrate(DataRate acknowledged_bitrate, + Timestamp at_time); + void MaybeReset(DataRate bitrate) { + if (config_.allow_resets) + loss_based_bitrate_ = bitrate; + } + void SetInitialBitrate(DataRate bitrate) { loss_based_bitrate_ = bitrate; } + bool Enabled() const { return config_.enabled; } + void UpdateLossStatistics(const std::vector& packet_results, + Timestamp at_time); + DataRate GetEstimate() const { return loss_based_bitrate_; } + + private: + LossBasedControlConfig config_; + double average_loss_; + double average_loss_max_; + DataRate loss_based_bitrate_; + DataRate acknowledged_bitrate_max_; + Timestamp acknowledged_bitrate_last_update_; + Timestamp time_last_decrease_; + bool has_decreased_since_last_loss_report_; + Timestamp last_loss_packet_report_; + double last_loss_ratio_; +}; + +} // namespace webrtc + +#endif // MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ diff --git a/modules/bitrate_controller/send_side_bandwidth_estimation.cc b/modules/bitrate_controller/send_side_bandwidth_estimation.cc index 7dfee62a31..0833dd2262 100644 --- a/modules/bitrate_controller/send_side_bandwidth_estimation.cc +++ b/modules/bitrate_controller/send_side_bandwidth_estimation.cc @@ -229,6 +229,9 @@ void SendSideBandwidthEstimation::SetSendBitrate(DataRate bitrate, RTC_DCHECK(bitrate > DataRate::Zero()); // Reset to avoid being capped by the estimate. delay_based_bitrate_ = DataRate::Zero(); + if (loss_based_bandwidth_estimation_.Enabled()) { + loss_based_bandwidth_estimation_.MaybeReset(bitrate); + } CapBitrateToThresholds(at_time, bitrate); // Clear last sent bitrate history so the new value can be used directly // and not capped. @@ -270,6 +273,20 @@ void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(Timestamp at_time, CapBitrateToThresholds(at_time, current_bitrate_); } +void SendSideBandwidthEstimation::IncomingPacketFeedbackVector( + const TransportPacketsFeedback& report, + absl::optional acked_bitrate_bps) { + if (!loss_based_bandwidth_estimation_.Enabled()) + return; + if (acked_bitrate_bps) { + DataRate acked_bitrate = DataRate::bps(*acked_bitrate_bps); + loss_based_bandwidth_estimation_.UpdateAcknowledgedBitrate( + acked_bitrate, report.feedback_time); + } + loss_based_bandwidth_estimation_.UpdateLossStatistics(report.packet_feedbacks, + report.feedback_time); +} + void SendSideBandwidthEstimation::UpdateReceiverBlock(uint8_t fraction_loss, TimeDelta rtt, int number_of_packets, @@ -372,6 +389,9 @@ void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) { if (last_fraction_loss_ == 0 && IsInStartPhase(at_time)) { new_bitrate = std::max(bwe_incoming_, new_bitrate); new_bitrate = std::max(delay_based_bitrate_, new_bitrate); + if (loss_based_bandwidth_estimation_.Enabled()) { + loss_based_bandwidth_estimation_.SetInitialBitrate(new_bitrate); + } if (new_bitrate != current_bitrate_) { min_bitrate_history_.clear(); @@ -386,6 +406,15 @@ void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) { CapBitrateToThresholds(at_time, current_bitrate_); return; } + + if (loss_based_bandwidth_estimation_.Enabled()) { + loss_based_bandwidth_estimation_.Update( + at_time, min_bitrate_history_.front().second, last_round_trip_time_); + new_bitrate = MaybeRampupOrBackoff(new_bitrate, at_time); + CapBitrateToThresholds(at_time, new_bitrate); + return; + } + TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_; TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_; if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) { @@ -400,7 +429,7 @@ void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) { // 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 immediatly to 108kbps + // 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 @@ -485,6 +514,35 @@ void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) { min_bitrate_history_.push_back(std::make_pair(at_time, current_bitrate_)); } +DataRate SendSideBandwidthEstimation::MaybeRampupOrBackoff(DataRate new_bitrate, + Timestamp at_time) { + // TODO(crodbro): reuse this code in UpdateEstimate instead of current + // inlining of very similar functionality. + const TimeDelta time_since_loss_packet_report = + at_time - last_loss_packet_report_; + const TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_; + if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) { + new_bitrate = min_bitrate_history_.front().second * 1.08; + new_bitrate += DataRate::bps(1000); + } else if (time_since_loss_feedback > + kFeedbackTimeoutIntervals * kMaxRtcpFeedbackInterval && + (last_timeout_.IsInfinite() || + at_time - last_timeout_ > kTimeoutInterval)) { + if (in_timeout_experiment_) { + RTC_LOG(LS_WARNING) << "Feedback timed out (" + << ToString(time_since_loss_feedback) + << "), reducing bitrate."; + new_bitrate = new_bitrate * 0.8; + // Reset accumulators since we've already acted on missing feedback and + // shouldn't to act again on these old lost packets. + lost_packets_since_last_loss_update_ = 0; + expected_packets_since_last_loss_update_ = 0; + last_timeout_ = at_time; + } + } + return new_bitrate; +} + void SendSideBandwidthEstimation::CapBitrateToThresholds(Timestamp at_time, DataRate bitrate) { if (bwe_incoming_ > DataRate::Zero() && bitrate > bwe_incoming_) { @@ -494,6 +552,10 @@ void SendSideBandwidthEstimation::CapBitrateToThresholds(Timestamp at_time, bitrate > delay_based_bitrate_) { bitrate = delay_based_bitrate_; } + if (loss_based_bandwidth_estimation_.Enabled() && + loss_based_bandwidth_estimation_.GetEstimate() > DataRate::Zero()) { + bitrate = std::min(bitrate, loss_based_bandwidth_estimation_.GetEstimate()); + } if (bitrate > max_bitrate_configured_) { bitrate = max_bitrate_configured_; } diff --git a/modules/bitrate_controller/send_side_bandwidth_estimation.h b/modules/bitrate_controller/send_side_bandwidth_estimation.h index 83efe92378..40105e152b 100644 --- a/modules/bitrate_controller/send_side_bandwidth_estimation.h +++ b/modules/bitrate_controller/send_side_bandwidth_estimation.h @@ -23,6 +23,8 @@ #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" +#include "modules/bitrate_controller/loss_based_bandwidth_estimation.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "rtc_base/experiments/field_trial_parser.h" namespace webrtc { @@ -88,6 +90,8 @@ class SendSideBandwidthEstimation { void SetSendBitrate(DataRate bitrate, Timestamp at_time); void SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate); int GetMinBitrate() const; + void IncomingPacketFeedbackVector(const TransportPacketsFeedback& report, + absl::optional acked_bitrate_bps); private: enum UmaState { kNoUpdate, kFirstDone, kDone }; @@ -101,6 +105,8 @@ class SendSideBandwidthEstimation { // min bitrate used during last kBweIncreaseIntervalMs. void UpdateMinHistory(Timestamp at_time); + DataRate MaybeRampupOrBackoff(DataRate new_bitrate, Timestamp 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 CapBitrateToThresholds(Timestamp at_time, DataRate bitrate); @@ -141,6 +147,7 @@ class SendSideBandwidthEstimation { float low_loss_threshold_; float high_loss_threshold_; DataRate bitrate_threshold_; + LossBasedBandwidthEstimation loss_based_bandwidth_estimation_; }; } // namespace webrtc #endif // MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_ diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc index 3b06ed64d0..8d8b9c180a 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc @@ -482,6 +482,9 @@ NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback( acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( received_feedback_vector); auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate_bps(); + bandwidth_estimation_->IncomingPacketFeedbackVector(report, + acknowledged_bitrate); + DelayBasedBwe::Result result; result = delay_based_bwe_->IncomingPacketFeedbackVector( received_feedback_vector, acknowledged_bitrate,