New loss-based bandwidth control mechanism.
Bug: none
Change-Id: Ie60e9225e2a2260624342ffbadb08cb887b2b6f5
Reviewed-on: https://webrtc-review.googlesource.com/c/109923
Commit-Queue: Christoffer Rodbro <crodbro@webrtc.org>
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25696}
diff --git a/modules/bitrate_controller/BUILD.gn b/modules/bitrate_controller/BUILD.gn
index 460dfc4..b501cb4 100644
--- a/modules/bitrate_controller/BUILD.gn
+++ b/modules/bitrate_controller/BUILD.gn
@@ -15,6 +15,8 @@
"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 0000000..5d7f8aa
--- /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 <algorithm>
+#include <string>
+#include <vector>
+
+#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<PacketResult>& 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<double>(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 0000000..0f56076
--- /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 <vector>
+
+#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<double> min_increase_factor;
+ FieldTrialParameter<double> max_increase_factor;
+ FieldTrialParameter<TimeDelta> increase_low_rtt;
+ FieldTrialParameter<TimeDelta> increase_high_rtt;
+ FieldTrialParameter<double> decrease_factor;
+ FieldTrialParameter<TimeDelta> loss_window;
+ FieldTrialParameter<TimeDelta> loss_max_window;
+ FieldTrialParameter<TimeDelta> acknowledged_rate_max_window;
+ FieldTrialParameter<DataRate> increase_offset;
+ FieldTrialParameter<DataRate> loss_bandwidth_balance_increase;
+ FieldTrialParameter<DataRate> loss_bandwidth_balance_decrease;
+ FieldTrialParameter<double> loss_bandwidth_balance_exponent;
+ FieldTrialParameter<bool> allow_resets;
+ FieldTrialParameter<TimeDelta> decrease_interval;
+ FieldTrialParameter<TimeDelta> 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<PacketResult>& 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 7dfee62..0833dd2 100644
--- a/modules/bitrate_controller/send_side_bandwidth_estimation.cc
+++ b/modules/bitrate_controller/send_side_bandwidth_estimation.cc
@@ -229,6 +229,9 @@
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 @@
CapBitrateToThresholds(at_time, current_bitrate_);
}
+void SendSideBandwidthEstimation::IncomingPacketFeedbackVector(
+ const TransportPacketsFeedback& report,
+ absl::optional<uint32_t> 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 83efe92..40105e1 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 @@
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<uint32_t> acked_bitrate_bps);
private:
enum UmaState { kNoUpdate, kFirstDone, kDone };
@@ -101,6 +105,8 @@
// 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 @@
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 3b06ed6..8d8b9c1 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 @@
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,