Add field trial to cap trendline slope in delay-based BWE.

Bug: webrtc:10932
Change-Id: I34a36a8cad16d65143eff9c675ee98bdbf176ace
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160014
Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org>
Commit-Queue: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29872}
diff --git a/modules/congestion_controller/goog_cc/trendline_estimator.cc b/modules/congestion_controller/goog_cc/trendline_estimator.cc
index 8f4f133..6675a3b 100644
--- a/modules/congestion_controller/goog_cc/trendline_estimator.cc
+++ b/modules/congestion_controller/goog_cc/trendline_estimator.cc
@@ -28,7 +28,6 @@
 namespace {
 
 // Parameters for linear least squares fit of regression line to noisy data.
-constexpr size_t kDefaultTrendlineWindowSize = 20;
 constexpr double kDefaultTrendlineSmoothingCoeff = 0.9;
 constexpr double kDefaultTrendlineThresholdGain = 4.0;
 const char kBweWindowSizeInPacketsExperiment[] =
@@ -48,7 +47,7 @@
   }
   RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweWindowSizeInPackets"
                          " experiment from field trial string. Using default.";
-  return kDefaultTrendlineWindowSize;
+  return TrendlineEstimatorSettings::kDefaultTrendlineWindowSize;
 }
 
 absl::optional<double> LinearFitSlope(
@@ -77,6 +76,34 @@
   return numerator / denominator;
 }
 
+absl::optional<double> ComputeSlopeCap(
+    const std::deque<TrendlineEstimator::PacketTiming>& packets,
+    const TrendlineEstimatorSettings& settings) {
+  RTC_DCHECK(1 <= settings.beginning_packets &&
+             settings.beginning_packets < packets.size());
+  RTC_DCHECK(1 <= settings.end_packets &&
+             settings.end_packets < packets.size());
+  RTC_DCHECK(settings.beginning_packets + settings.end_packets <=
+             packets.size());
+  TrendlineEstimator::PacketTiming early = packets[0];
+  for (size_t i = 1; i < settings.beginning_packets; ++i) {
+    if (packets[i].raw_delay_ms < early.raw_delay_ms)
+      early = packets[i];
+  }
+  size_t late_start = packets.size() - settings.end_packets;
+  TrendlineEstimator::PacketTiming late = packets[late_start];
+  for (size_t i = late_start + 1; i < packets.size(); ++i) {
+    if (packets[i].raw_delay_ms < late.raw_delay_ms)
+      late = packets[i];
+  }
+  if (late.arrival_time_ms - early.arrival_time_ms < 1) {
+    return absl::nullopt;
+  }
+  return (late.raw_delay_ms - early.raw_delay_ms) /
+             (late.arrival_time_ms - early.arrival_time_ms) +
+         settings.cap_uncertainty;
+}
+
 constexpr double kMaxAdaptOffsetMs = 15.0;
 constexpr double kOverUsingTimeThreshold = 10;
 constexpr int kMinNumDeltas = 60;
@@ -84,13 +111,56 @@
 
 }  // namespace
 
+constexpr char TrendlineEstimatorSettings::kKey[];
+
+TrendlineEstimatorSettings::TrendlineEstimatorSettings(
+    const WebRtcKeyValueConfig* key_value_config) {
+  if (key_value_config->Lookup(kBweWindowSizeInPacketsExperiment)
+          .find("Enabled") == 0) {
+    window_size = ReadTrendlineFilterWindowSize(key_value_config);
+  }
+  Parser()->Parse(key_value_config->Lookup(TrendlineEstimatorSettings::kKey));
+  if (window_size < 10 || 200 < window_size) {
+    RTC_LOG(LS_WARNING) << "Window size must be between 10 and 200 packets";
+    window_size = kDefaultTrendlineWindowSize;
+  }
+  if (enable_cap) {
+    if (beginning_packets < 1 || end_packets < 1 ||
+        beginning_packets > window_size || end_packets > window_size) {
+      RTC_LOG(LS_WARNING) << "Size of beginning and end must be between 1 and "
+                          << window_size;
+      enable_cap = false;
+      beginning_packets = end_packets = 0;
+      cap_uncertainty = 0.0;
+    }
+    if (beginning_packets + end_packets > window_size) {
+      RTC_LOG(LS_WARNING)
+          << "Size of beginning plus end can't exceed the window size";
+      enable_cap = false;
+      beginning_packets = end_packets = 0;
+      cap_uncertainty = 0.0;
+    }
+    if (cap_uncertainty < 0.0 || 0.025 < cap_uncertainty) {
+      RTC_LOG(LS_WARNING) << "Cap uncertainty must be between 0 and 0.025";
+      cap_uncertainty = 0.0;
+    }
+  }
+}
+
+std::unique_ptr<StructParametersParser> TrendlineEstimatorSettings::Parser() {
+  return StructParametersParser::Create("sort", &enable_sort,  //
+                                        "cap", &enable_cap,    //
+                                        "beginning_packets",
+                                        &beginning_packets,                   //
+                                        "end_packets", &end_packets,          //
+                                        "cap_uncertainty", &cap_uncertainty,  //
+                                        "window_size", &window_size);
+}
+
 TrendlineEstimator::TrendlineEstimator(
     const WebRtcKeyValueConfig* key_value_config,
     NetworkStatePredictor* network_state_predictor)
-    : window_size_(key_value_config->Lookup(kBweWindowSizeInPacketsExperiment)
-                               .find("Enabled") == 0
-                       ? ReadTrendlineFilterWindowSize(key_value_config)
-                       : kDefaultTrendlineWindowSize),
+    : settings_(key_value_config),
       smoothing_coef_(kDefaultTrendlineSmoothingCoeff),
       threshold_gain_(kDefaultTrendlineThresholdGain),
       num_of_deltas_(0),
@@ -111,8 +181,8 @@
       hypothesis_predicted_(BandwidthUsage::kBwNormal),
       network_state_predictor_(network_state_predictor) {
   RTC_LOG(LS_INFO)
-      << "Using Trendline filter for delay change estimation with window size "
-      << window_size_ << " and "
+      << "Using Trendline filter for delay change estimation with settings "
+      << settings_.Parser()->Encode() << " and "
       << (network_state_predictor_ ? "injected" : "no")
       << " network state predictor";
 }
@@ -139,20 +209,38 @@
   BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
                         smoothed_delay_);
 
-  // Simple linear regression.
+  // Maintain packet window
   delay_hist_.emplace_back(
       static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
-      smoothed_delay_);
-  if (delay_hist_.size() > window_size_)
+      smoothed_delay_, accumulated_delay_);
+  if (settings_.enable_sort) {
+    for (size_t i = delay_hist_.size() - 1;
+         i > 0 &&
+         delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms;
+         --i) {
+      std::swap(delay_hist_[i], delay_hist_[i - 1]);
+    }
+  }
+  if (delay_hist_.size() > settings_.window_size)
     delay_hist_.pop_front();
+
+  // Simple linear regression.
   double trend = prev_trend_;
-  if (delay_hist_.size() == window_size_) {
+  if (delay_hist_.size() == settings_.window_size) {
     // Update trend_ if it is possible to fit a line to the data. The delay
     // trend can be seen as an estimate of (send_rate - capacity)/capacity.
     // 0 < trend < 1   ->  the delay increases, queues are filling up
     //   trend == 0    ->  the delay does not change
     //   trend < 0     ->  the delay decreases, queues are being emptied
     trend = LinearFitSlope(delay_hist_).value_or(trend);
+    if (settings_.enable_cap) {
+      absl::optional<double> cap = ComputeSlopeCap(delay_hist_, settings_);
+      // We only use the cap to filter out overuse detections, not
+      // to detect additional underuses.
+      if (trend >= 0 && cap.has_value() && trend > cap.value()) {
+        trend = cap.value();
+      }
+    }
   }
   BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend);
 
diff --git a/modules/congestion_controller/goog_cc/trendline_estimator.h b/modules/congestion_controller/goog_cc/trendline_estimator.h
index 5bec23b..2db2903 100644
--- a/modules/congestion_controller/goog_cc/trendline_estimator.h
+++ b/modules/congestion_controller/goog_cc/trendline_estimator.h
@@ -22,9 +22,35 @@
 #include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h"
 #include "modules/remote_bitrate_estimator/include/bwe_defines.h"
 #include "rtc_base/constructor_magic.h"
+#include "rtc_base/experiments/struct_parameters_parser.h"
 
 namespace webrtc {
 
+struct TrendlineEstimatorSettings {
+  static constexpr char kKey[] = "WebRTC-Bwe-TrendlineEstimatorSettings";
+  static constexpr unsigned kDefaultTrendlineWindowSize = 20;
+
+  TrendlineEstimatorSettings() = delete;
+  explicit TrendlineEstimatorSettings(
+      const WebRtcKeyValueConfig* key_value_config);
+
+  // Sort the packets in the window. Should be redundant,
+  // but then almost no cost.
+  bool enable_sort = false;
+
+  // Cap the trendline slope based on the minimum delay seen
+  // in the beginning_packets and end_packets respectively.
+  bool enable_cap = false;
+  unsigned beginning_packets = 7;
+  unsigned end_packets = 7;
+  double cap_uncertainty = 0.0;
+
+  // Size (in packets) of the window.
+  unsigned window_size = kDefaultTrendlineWindowSize;
+
+  std::unique_ptr<StructParametersParser> Parser();
+};
+
 class TrendlineEstimator : public DelayIncreaseDetectorInterface {
  public:
   TrendlineEstimator(const WebRtcKeyValueConfig* key_value_config,
@@ -50,11 +76,15 @@
   BandwidthUsage State() const override;
 
   struct PacketTiming {
-    PacketTiming(double arrival_time_ms, double smoothed_delay_ms)
+    PacketTiming(double arrival_time_ms,
+                 double smoothed_delay_ms,
+                 double raw_delay_ms)
         : arrival_time_ms(arrival_time_ms),
-          smoothed_delay_ms(smoothed_delay_ms) {}
+          smoothed_delay_ms(smoothed_delay_ms),
+          raw_delay_ms(raw_delay_ms) {}
     double arrival_time_ms;
     double smoothed_delay_ms;
+    double raw_delay_ms;
   };
 
  private:
@@ -64,7 +94,7 @@
   void UpdateThreshold(double modified_offset, int64_t now_ms);
 
   // Parameters.
-  const size_t window_size_;
+  TrendlineEstimatorSettings settings_;
   const double smoothing_coef_;
   const double threshold_gain_;
   // Used by the existing threshold.