Apply stricter bandwidth cap for high loss.

When loss rate is above a certain threshold, set instant_limit = 500 - 1000 * average_loss_rate, which returns 200kbps at 30% loss rate, or 100kbps at 40% loss rate. When the loss rate is above 50%, use the min_bitrate from send_side_bandwidth_estimation.

The high_loss_rate_threshold is set to 1.0, so the change is not activated by default.

Tested the change with hamrit, when average loss rate is above 50%, bandwidth backed to 10kbps, and it took ~10s to ramp up to 1.5Mbps.
https://screenshot.googleplex.com/7dvPoWa2b5SgMSL

Bug: webrtc:12707
Change-Id: I5eea04ef709a183bdf696246094dbd4a204e48f6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/272061
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Commit-Queue: Diep Bui <diepbp@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38243}
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
index c8c69f3..e671bd2 100644
--- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
@@ -181,6 +181,15 @@
   }
 }
 
+void LossBasedBweV2::SetMinBitrate(DataRate min_bitrate) {
+  if (IsValid(min_bitrate)) {
+    min_bitrate_ = min_bitrate;
+  } else {
+    RTC_LOG(LS_WARNING) << "The min bitrate must be finite: "
+                        << ToString(min_bitrate);
+  }
+}
+
 void LossBasedBweV2::UpdateBandwidthEstimate(
     rtc::ArrayView<const PacketResult> packet_results,
     DataRate delay_based_estimate,
@@ -324,7 +333,12 @@
   FieldTrialParameter<bool>
       not_increase_if_inherent_loss_less_than_average_loss(
           "NotIncreaseIfInherentLossLessThanAverageLoss", false);
-
+  FieldTrialParameter<double> high_loss_rate_threshold("HighLossRateThreshold",
+                                                       1.0);
+  FieldTrialParameter<DataRate> bandwidth_cap_at_high_loss_rate(
+      "BandwidthCapAtHighLossRate", DataRate::KilobitsPerSec(500.0));
+  FieldTrialParameter<double> slope_of_bwe_high_loss_func(
+      "SlopeOfBweHighLossFunc", 1000);
   if (key_value_config) {
     ParseFieldTrial({&enabled,
                      &bandwidth_rampup_upper_bound_factor,
@@ -356,7 +370,10 @@
                      &max_increase_factor,
                      &delayed_increase_window,
                      &use_acked_bitrate_only_when_overusing,
-                     &not_increase_if_inherent_loss_less_than_average_loss},
+                     &not_increase_if_inherent_loss_less_than_average_loss,
+                     &high_loss_rate_threshold,
+                     &bandwidth_cap_at_high_loss_rate,
+                     &slope_of_bwe_high_loss_func},
                     key_value_config->Lookup("WebRTC-Bwe-LossBasedBweV2"));
   }
 
@@ -412,6 +429,10 @@
       use_acked_bitrate_only_when_overusing.Get();
   config->not_increase_if_inherent_loss_less_than_average_loss =
       not_increase_if_inherent_loss_less_than_average_loss.Get();
+  config->high_loss_rate_threshold = high_loss_rate_threshold.Get();
+  config->bandwidth_cap_at_high_loss_rate =
+      bandwidth_cap_at_high_loss_rate.Get();
+  config->slope_of_bwe_high_loss_func = slope_of_bwe_high_loss_func.Get();
   return config;
 }
 
@@ -575,8 +596,8 @@
         << config_->bandwidth_backoff_lower_bound_factor;
     valid = false;
   }
-  if (config_->trendline_observations_window_size < 2) {
-    RTC_LOG(LS_WARNING) << "The trendline window size must be at least 2: "
+  if (config_->trendline_observations_window_size < 1) {
+    RTC_LOG(LS_WARNING) << "The trendline window size must be at least 1: "
                         << config_->trendline_observations_window_size;
     valid = false;
   }
@@ -590,6 +611,12 @@
                         << config_->delayed_increase_window.ms();
     valid = false;
   }
+  if (config_->high_loss_rate_threshold <= 0.0 ||
+      config_->high_loss_rate_threshold > 1.0) {
+    RTC_LOG(LS_WARNING) << "The high loss rate threshold must be in (0, 1]: "
+                        << config_->high_loss_rate_threshold;
+    valid = false;
+  }
   return valid;
 }
 
@@ -598,8 +625,8 @@
     return 0.0;
   }
 
-  int num_packets = 0;
-  int num_lost_packets = 0;
+  double num_packets = 0;
+  double num_lost_packets = 0;
   for (const Observation& observation : observations_) {
     if (!observation.IsInitialized()) {
       continue;
@@ -612,7 +639,7 @@
     num_lost_packets += instant_temporal_weight * observation.num_lost_packets;
   }
 
-  return static_cast<double>(num_lost_packets) / num_packets;
+  return num_lost_packets / num_packets;
 }
 
 DataRate LossBasedBweV2::GetCandidateBandwidthUpperBound(
@@ -843,7 +870,16 @@
     instant_limit = config_->instant_upper_bound_bandwidth_balance /
                     (average_reported_loss_ratio -
                      config_->instant_upper_bound_loss_offset);
+    if (average_reported_loss_ratio > config_->high_loss_rate_threshold) {
+      instant_limit = std::min(
+          instant_limit, DataRate::KilobitsPerSec(std::max(
+                             static_cast<double>(min_bitrate_.kbps()),
+                             config_->bandwidth_cap_at_high_loss_rate.kbps() -
+                                 config_->slope_of_bwe_high_loss_func *
+                                     average_reported_loss_ratio)));
+    }
   }
+
   cached_instant_upper_bound_ = instant_limit;
 }
 
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
index fdfb440..88cae01 100644
--- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h
@@ -48,7 +48,7 @@
 
   void SetAcknowledgedBitrate(DataRate acknowledged_bitrate);
   void SetBandwidthEstimate(DataRate bandwidth_estimate);
-
+  void SetMinBitrate(DataRate min_bitrate);
   void UpdateBandwidthEstimate(
       rtc::ArrayView<const PacketResult> packet_results,
       DataRate delay_based_estimate,
@@ -92,6 +92,9 @@
     TimeDelta delayed_increase_window = TimeDelta::Zero();
     bool use_acked_bitrate_only_when_overusing = false;
     bool not_increase_if_inherent_loss_less_than_average_loss = false;
+    double high_loss_rate_threshold = 1.0;
+    DataRate bandwidth_cap_at_high_loss_rate = DataRate::MinusInfinity();
+    double slope_of_bwe_high_loss_func = 1000.0;
   };
 
   struct Derivatives {
@@ -168,6 +171,7 @@
   Timestamp recovering_after_loss_timestamp_ = Timestamp::MinusInfinity();
   DataRate bandwidth_limit_in_current_window_ = DataRate::PlusInfinity();
   bool limited_due_to_loss_candidate_ = false;
+  DataRate min_bitrate_ = DataRate::KilobitsPerSec(1);
 };
 
 }  // namespace webrtc
diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
index 9dc6144..0e039fd 100644
--- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc
@@ -913,6 +913,171 @@
       DataRate::KilobitsPerSec(600));
 }
 
+TEST_P(LossBasedBweV2Test,
+       StricterBoundUsingHighLossRateThresholdAt10pLossRate) {
+  ExplicitKeyValueConfig key_value_config(
+      "WebRTC-Bwe-LossBasedBweV2/"
+      "Enabled:true,CandidateFactors:1.0,AckedRateCandidate:false,"
+      "ObservationWindowSize:2,"
+      "DelayBasedCandidate:true,InstantUpperBoundBwBalance:100kbps,"
+      "ObservationDurationLowerBound:200ms,HigherBwBiasFactor:1000,"
+      "HigherLogBwBiasFactor:1000,LossThresholdOfHighBandwidthPreference:0."
+      "05,HighLossRateThreshold:0.09/");
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+  loss_based_bandwidth_estimator.SetMinBitrate(DataRate::KilobitsPerSec(10));
+  DataRate delay_based_estimate = DataRate::KilobitsPerSec(5000);
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  std::vector<PacketResult> enough_feedback_10p_loss_1 =
+      CreatePacketResultsWith10pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero());
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_10p_loss_1, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  std::vector<PacketResult> enough_feedback_10p_loss_2 =
+      CreatePacketResultsWith10pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero() +
+          kObservationDurationLowerBound);
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_10p_loss_2, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  // At 10% loss rate and high loss rate threshold to be 10%, cap the estimate
+  // to be 500 * 1000-0.1 = 400kbps.
+  EXPECT_EQ(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate(delay_based_estimate),
+      DataRate::KilobitsPerSec(400));
+}
+
+TEST_P(LossBasedBweV2Test,
+       StricterBoundUsingHighLossRateThresholdAt50pLossRate) {
+  ExplicitKeyValueConfig key_value_config(
+      "WebRTC-Bwe-LossBasedBweV2/"
+      "Enabled:true,CandidateFactors:1.0,AckedRateCandidate:false,"
+      "ObservationWindowSize:2,"
+      "DelayBasedCandidate:true,InstantUpperBoundBwBalance:100kbps,"
+      "ObservationDurationLowerBound:200ms,HigherBwBiasFactor:1000,"
+      "HigherLogBwBiasFactor:1000,LossThresholdOfHighBandwidthPreference:0."
+      "05,HighLossRateThreshold:0.3/");
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+  loss_based_bandwidth_estimator.SetMinBitrate(DataRate::KilobitsPerSec(10));
+  DataRate delay_based_estimate = DataRate::KilobitsPerSec(5000);
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  std::vector<PacketResult> enough_feedback_50p_loss_1 =
+      CreatePacketResultsWith50pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero());
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_50p_loss_1, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  std::vector<PacketResult> enough_feedback_50p_loss_2 =
+      CreatePacketResultsWith50pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero() +
+          kObservationDurationLowerBound);
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_50p_loss_2, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  // At 50% loss rate and high loss rate threshold to be 30%, cap the estimate
+  // to be the min bitrate.
+  EXPECT_EQ(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate(delay_based_estimate),
+      DataRate::KilobitsPerSec(10));
+}
+
+TEST_P(LossBasedBweV2Test,
+       StricterBoundUsingHighLossRateThresholdAt100pLossRate) {
+  ExplicitKeyValueConfig key_value_config(
+      "WebRTC-Bwe-LossBasedBweV2/"
+      "Enabled:true,CandidateFactors:1.0,AckedRateCandidate:false,"
+      "ObservationWindowSize:2,"
+      "DelayBasedCandidate:true,InstantUpperBoundBwBalance:100kbps,"
+      "ObservationDurationLowerBound:200ms,HigherBwBiasFactor:1000,"
+      "HigherLogBwBiasFactor:1000,LossThresholdOfHighBandwidthPreference:0."
+      "05,HighLossRateThreshold:0.3/");
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+  loss_based_bandwidth_estimator.SetMinBitrate(DataRate::KilobitsPerSec(10));
+  DataRate delay_based_estimate = DataRate::KilobitsPerSec(5000);
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  std::vector<PacketResult> enough_feedback_100p_loss_1 =
+      CreatePacketResultsWith100pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero());
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_100p_loss_1, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  std::vector<PacketResult> enough_feedback_100p_loss_2 =
+      CreatePacketResultsWith100pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero() +
+          kObservationDurationLowerBound);
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_100p_loss_2, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  // At 100% loss rate and high loss rate threshold to be 30%, cap the estimate
+  // to be the min bitrate.
+  EXPECT_EQ(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate(delay_based_estimate),
+      DataRate::KilobitsPerSec(10));
+}
+
+TEST_P(LossBasedBweV2Test, EstimateRecoversAfterHighLoss) {
+  ExplicitKeyValueConfig key_value_config(
+      "WebRTC-Bwe-LossBasedBweV2/"
+      "Enabled:true,CandidateFactors:1.1|1.0|0.9,AckedRateCandidate:false,"
+      "ObservationWindowSize:2,"
+      "DelayBasedCandidate:true,InstantUpperBoundBwBalance:100kbps,"
+      "ObservationDurationLowerBound:200ms,HigherBwBiasFactor:1000,"
+      "HigherLogBwBiasFactor:1000,LossThresholdOfHighBandwidthPreference:0."
+      "05,HighLossRateThreshold:0.3/");
+  LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config);
+  loss_based_bandwidth_estimator.SetMinBitrate(DataRate::KilobitsPerSec(10));
+  DataRate delay_based_estimate = DataRate::KilobitsPerSec(5000);
+  loss_based_bandwidth_estimator.SetBandwidthEstimate(
+      DataRate::KilobitsPerSec(600));
+
+  std::vector<PacketResult> enough_feedback_100p_loss_1 =
+      CreatePacketResultsWith100pLossRate(
+          /*first_packet_timestamp=*/Timestamp::Zero());
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_100p_loss_1, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  // Make sure that the estimate is set to min bitrate because of 100% loss
+  // rate.
+  EXPECT_EQ(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate(delay_based_estimate),
+      DataRate::KilobitsPerSec(10));
+
+  // Create some feedbacks with 0 loss rate to simulate network recovering.
+  std::vector<PacketResult> enough_feedback_0p_loss_1 =
+      CreatePacketResultsWithReceivedPackets(
+          /*first_packet_timestamp=*/Timestamp::Zero() +
+          kObservationDurationLowerBound);
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_0p_loss_1, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  std::vector<PacketResult> enough_feedback_0p_loss_2 =
+      CreatePacketResultsWithReceivedPackets(
+          /*first_packet_timestamp=*/Timestamp::Zero() +
+          kObservationDurationLowerBound * 2);
+  loss_based_bandwidth_estimator.UpdateBandwidthEstimate(
+      enough_feedback_0p_loss_2, delay_based_estimate,
+      BandwidthUsage::kBwNormal);
+
+  // The estimate increases as network recovers.
+  EXPECT_GT(
+      loss_based_bandwidth_estimator.GetBandwidthEstimate(delay_based_estimate),
+      DataRate::KilobitsPerSec(10));
+}
+
 INSTANTIATE_TEST_SUITE_P(LossBasedBweV2Tests,
                          LossBasedBweV2Test,
                          ::testing::Bool());
diff --git a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
index acf1862..12a7d41 100644
--- a/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
+++ b/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc
@@ -244,6 +244,9 @@
   }
   ParseFieldTrial({&disable_receiver_limit_caps_only_},
                   key_value_config->Lookup("WebRTC-Bwe-ReceiverLimitCapsOnly"));
+  if (LossBasedBandwidthEstimatorV2Enabled()) {
+    loss_based_bandwidth_estimator_v2_.SetMinBitrate(min_bitrate_configured_);
+  }
 }
 
 SendSideBandwidthEstimation::~SendSideBandwidthEstimation() {}