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,