Implement stable rate support in SimulcastRateAllocator

Bug: webrtc:10126
Change-Id: I2ea8d27b0bd6f7ffd1ebbba451bd1ce1f2eee3d9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/151121
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29097}
diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn
index eafbd39..dc9f018 100644
--- a/modules/video_coding/BUILD.gn
+++ b/modules/video_coding/BUILD.gn
@@ -264,6 +264,7 @@
     "../../rtc_base/experiments:quality_scaler_settings",
     "../../rtc_base/experiments:quality_scaling_experiment",
     "../../rtc_base/experiments:rate_control_settings",
+    "../../rtc_base/experiments:stable_target_rate_experiment",
     "../../rtc_base/synchronization:sequence_checker",
     "../../rtc_base/system:arch",
     "../../rtc_base/system:file_wrapper",
diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
index 45fc986..8513b43 100644
--- a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
+++ b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
@@ -200,15 +200,11 @@
   // Figure out how many spatial layers should be active.
   if (experiment_settings_.IsEnabled() &&
       parameters.stable_bitrate > DataRate::Zero()) {
-    double hysteresis_factor = 1.0;
+    double hysteresis_factor;
     if (codec_.mode == VideoCodecMode::kScreensharing) {
-      hysteresis_factor =
-          experiment_settings_.GetScreenshareHysteresisFactor().value_or(
-              hysteresis_factor);
+      hysteresis_factor = experiment_settings_.GetScreenshareHysteresisFactor();
     } else {
-      hysteresis_factor =
-          experiment_settings_.GetVideoHysteresisFactor().value_or(
-              hysteresis_factor);
+      hysteresis_factor = experiment_settings_.GetVideoHysteresisFactor();
     }
 
     DataRate stable_rate =
diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
index f721608..f4d0924 100644
--- a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
+++ b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
@@ -283,7 +283,9 @@
 }
 
 TEST_P(SvcRateAllocatorTestParametrizedContentType, StableBitrate) {
-  ScopedFieldTrials field_trial("WebRTC-StableTargetRate/enabled:true/");
+  ScopedFieldTrials field_trial(
+      "WebRTC-StableTargetRate/enabled:true,video_hysteresis_factor:1.0,"
+      "screenshare_hysteresis_factor:1.0/");
 
   const VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_);
   const auto start_rates = SvcRateAllocator::GetLayerStartBitrates(codec);
diff --git a/modules/video_coding/utility/simulcast_rate_allocator.cc b/modules/video_coding/utility/simulcast_rate_allocator.cc
index 5929572..15b8e54 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator.cc
+++ b/modules/video_coding/utility/simulcast_rate_allocator.cc
@@ -59,32 +59,45 @@
 
 SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec)
     : codec_(codec),
-      hysteresis_factor_(RateControlSettings::ParseFromFieldTrials()
-                             .GetSimulcastHysteresisFactor(codec.mode)) {}
+      stable_rate_settings_(
+          StableTargetRateExperiment::ParseFromFieldTrials()) {}
 
 SimulcastRateAllocator::~SimulcastRateAllocator() = default;
 
 VideoBitrateAllocation SimulcastRateAllocator::Allocate(
     VideoBitrateAllocationParameters parameters) {
-  VideoBitrateAllocation allocated_bitrates_bps;
-  DistributeAllocationToSimulcastLayers(parameters.total_bitrate.bps(),
-                                        &allocated_bitrates_bps);
-  DistributeAllocationToTemporalLayers(&allocated_bitrates_bps);
-  return allocated_bitrates_bps;
+  VideoBitrateAllocation allocated_bitrates;
+  DataRate stable_rate = parameters.total_bitrate;
+  if (stable_rate_settings_.IsEnabled() &&
+      parameters.stable_bitrate > DataRate::Zero()) {
+    stable_rate = std::min(parameters.stable_bitrate, parameters.total_bitrate);
+  }
+  DistributeAllocationToSimulcastLayers(parameters.total_bitrate, stable_rate,
+                                        &allocated_bitrates);
+  DistributeAllocationToTemporalLayers(&allocated_bitrates);
+  return allocated_bitrates;
 }
 
 void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers(
-    uint32_t total_bitrate_bps,
-    VideoBitrateAllocation* allocated_bitrates_bps) {
-  uint32_t left_to_allocate = total_bitrate_bps;
-  if (codec_.maxBitrate && codec_.maxBitrate * 1000 < left_to_allocate)
-    left_to_allocate = codec_.maxBitrate * 1000;
+    DataRate total_bitrate,
+    DataRate stable_bitrate,
+    VideoBitrateAllocation* allocated_bitrates) {
+  DataRate left_in_total_allocation = total_bitrate;
+  DataRate left_in_stable_allocation = stable_bitrate;
+
+  if (codec_.maxBitrate) {
+    DataRate max_rate = DataRate::kbps(codec_.maxBitrate);
+    left_in_total_allocation = std::min(left_in_total_allocation, max_rate);
+    left_in_stable_allocation = std::min(left_in_stable_allocation, max_rate);
+  }
 
   if (codec_.numberOfSimulcastStreams == 0) {
     // No simulcast, just set the target as this has been capped already.
     if (codec_.active) {
-      allocated_bitrates_bps->SetBitrate(
-          0, 0, std::max(codec_.minBitrate * 1000, left_to_allocate));
+      allocated_bitrates->SetBitrate(
+          0, 0,
+          std::max(DataRate::kbps(codec_.minBitrate), left_in_total_allocation)
+              .bps());
     }
     return;
   }
@@ -115,9 +128,10 @@
   // Always allocate enough bitrate for the minimum bitrate of the first
   // active layer. Suspending below min bitrate is controlled outside the
   // codec implementation and is not overridden by this.
-  left_to_allocate = std::max(
-      codec_.simulcastStream[layer_index[active_layer]].minBitrate * 1000,
-      left_to_allocate);
+  DataRate min_rate = DataRate::kbps(
+      codec_.simulcastStream[layer_index[active_layer]].minBitrate);
+  left_in_total_allocation = std::max(left_in_total_allocation, min_rate);
+  left_in_stable_allocation = std::max(left_in_stable_allocation, min_rate);
 
   // Begin by allocating bitrate to simulcast streams, putting all bitrate in
   // temporal layer 0. We'll then distribute this bitrate, across potential
@@ -142,25 +156,28 @@
     }
     // If we can't allocate to the current layer we can't allocate to higher
     // layers because they require a higher minimum bitrate.
-    uint32_t min_bitrate = stream.minBitrate * 1000;
+    DataRate min_bitrate = DataRate::kbps(stream.minBitrate);
+    DataRate target_bitrate = DataRate::kbps(stream.targetBitrate);
+    double hysteresis_factor =
+        codec_.mode == VideoCodecMode::kRealtimeVideo
+            ? stable_rate_settings_.GetVideoHysteresisFactor()
+            : stable_rate_settings_.GetScreenshareHysteresisFactor();
     if (!first_allocation && !stream_enabled_[layer_index[active_layer]]) {
-      min_bitrate = std::min(
-          static_cast<uint32_t>(hysteresis_factor_ * min_bitrate + 0.5),
-          stream.targetBitrate * 1000);
+      min_bitrate = std::min(hysteresis_factor * min_bitrate, target_bitrate);
     }
-    if (left_to_allocate < min_bitrate) {
+    if (left_in_stable_allocation < min_bitrate) {
       break;
     }
 
     // We are allocating to this layer so it is the current active allocation.
     top_active_layer = layer_index[active_layer];
     stream_enabled_[layer_index[active_layer]] = true;
-    uint32_t allocation =
-        std::min(left_to_allocate, stream.targetBitrate * 1000);
-    allocated_bitrates_bps->SetBitrate(layer_index[active_layer], 0,
-                                       allocation);
-    RTC_DCHECK_LE(allocation, left_to_allocate);
-    left_to_allocate -= allocation;
+    DataRate layer_rate = std::min(left_in_total_allocation, target_bitrate);
+    allocated_bitrates->SetBitrate(layer_index[active_layer], 0,
+                                   layer_rate.bps());
+    left_in_total_allocation -= layer_rate;
+    left_in_stable_allocation -=
+        std::min(left_in_stable_allocation, target_bitrate);
   }
 
   // All layers above this one are not active.
@@ -172,16 +189,16 @@
   // stream.
   // TODO(sprang): Allocate up to max bitrate for all layers once we have a
   //               better idea of possible performance implications.
-  if (left_to_allocate > 0) {
+  if (left_in_total_allocation > DataRate::Zero()) {
     const SimulcastStream& stream = codec_.simulcastStream[top_active_layer];
-    uint32_t bitrate_bps =
-        allocated_bitrates_bps->GetSpatialLayerSum(top_active_layer);
-    uint32_t allocation =
-        std::min(left_to_allocate, stream.maxBitrate * 1000 - bitrate_bps);
-    bitrate_bps += allocation;
-    RTC_DCHECK_LE(allocation, left_to_allocate);
-    left_to_allocate -= allocation;
-    allocated_bitrates_bps->SetBitrate(top_active_layer, 0, bitrate_bps);
+    DataRate initial_layer_rate =
+        DataRate::bps(allocated_bitrates->GetSpatialLayerSum(top_active_layer));
+    DataRate additional_allocation =
+        std::min(left_in_total_allocation,
+                 DataRate::kbps(stream.maxBitrate) - initial_layer_rate);
+    allocated_bitrates->SetBitrate(
+        top_active_layer, 0,
+        (initial_layer_rate + additional_allocation).bps());
   }
 }
 
diff --git a/modules/video_coding/utility/simulcast_rate_allocator.h b/modules/video_coding/utility/simulcast_rate_allocator.h
index efbe514..97d50df 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator.h
+++ b/modules/video_coding/utility/simulcast_rate_allocator.h
@@ -20,6 +20,7 @@
 #include "api/video/video_bitrate_allocator.h"
 #include "api/video_codecs/video_codec.h"
 #include "rtc_base/constructor_magic.h"
+#include "rtc_base/experiments/stable_target_rate_experiment.h"
 
 namespace webrtc {
 
@@ -36,10 +37,11 @@
 
  private:
   void DistributeAllocationToSimulcastLayers(
-      uint32_t total_bitrate_bps,
-      VideoBitrateAllocation* allocated_bitrates_bps);
+      DataRate total_bitrate,
+      DataRate stable_bitrate,
+      VideoBitrateAllocation* allocated_bitrates);
   void DistributeAllocationToTemporalLayers(
-      VideoBitrateAllocation* allocated_bitrates_bps) const;
+      VideoBitrateAllocation* allocated_bitrates) const;
   std::vector<uint32_t> DefaultTemporalLayerAllocation(int bitrate_kbps,
                                                        int max_bitrate_kbps,
                                                        int simulcast_id) const;
@@ -50,7 +52,7 @@
   int NumTemporalStreams(size_t simulcast_id) const;
 
   const VideoCodec codec_;
-  const double hysteresis_factor_;
+  const StableTargetRateExperiment stable_rate_settings_;
   std::vector<bool> stream_enabled_;
 
   RTC_DISALLOW_COPY_AND_ASSIGN(SimulcastRateAllocator);
diff --git a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
index 2c2b7c7..eb01481 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
+++ b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
@@ -136,6 +136,24 @@
         DataRate::kbps(target_bitrate), kDefaultFrameRate));
   }
 
+  VideoBitrateAllocation GetAllocation(DataRate target_rate,
+                                       DataRate stable_rate) {
+    return allocator_->Allocate(VideoBitrateAllocationParameters(
+        target_rate, stable_rate, kDefaultFrameRate));
+  }
+
+  DataRate MinRate(size_t layer_index) const {
+    return DataRate::kbps(codec_.simulcastStream[layer_index].minBitrate);
+  }
+
+  DataRate TargetRate(size_t layer_index) const {
+    return DataRate::kbps(codec_.simulcastStream[layer_index].targetBitrate);
+  }
+
+  DataRate MaxRate(size_t layer_index) const {
+    return DataRate::kbps(codec_.simulcastStream[layer_index].maxBitrate);
+  }
+
  protected:
   static const int kDefaultFrameRate = 30;
   VideoCodec codec_;
@@ -524,6 +542,71 @@
   EXPECT_EQ(alloc.GetTemporalLayerAllocation(2).size(), 3u);
 }
 
+TEST_F(SimulcastRateAllocatorTest, StableRate) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-StableTargetRate/"
+      "enabled:true,"
+      "video_hysteresis_factor:1.1/");
+
+  SetupCodec3SL3TL({true, true, true});
+  CreateAllocator();
+
+  // Let the volatile rate always be be enough for all streams, in this test we
+  // are only interested in how the stable rate affects enablement.
+  const DataRate volatile_rate =
+      (TargetRate(0) + TargetRate(1) + MinRate(2)) * 1.1;
+
+  {
+    // On the first call to a new SimulcastRateAllocator instance, hysteresis
+    // is disabled, but stable rate still caps layers.
+    uint32_t expected[] = {TargetRate(0).kbps<uint32_t>(),
+                           MaxRate(1).kbps<uint32_t>()};
+    ExpectEqual(expected,
+                GetAllocation(volatile_rate, TargetRate(0) + MinRate(1)));
+  }
+
+  {
+    // Let stable rate go to a bitrate below what is needed for two streams.
+    uint32_t expected[] = {MaxRate(0).kbps<uint32_t>(), 0};
+    ExpectEqual(expected,
+                GetAllocation(volatile_rate,
+                              TargetRate(0) + MinRate(1) - DataRate::bps(1)));
+  }
+
+  {
+    // Don't enable stream as we need to get up above hysteresis threshold.
+    uint32_t expected[] = {MaxRate(0).kbps<uint32_t>(), 0};
+    ExpectEqual(expected,
+                GetAllocation(volatile_rate, TargetRate(0) + MinRate(1)));
+  }
+
+  {
+    // Above threshold with hysteresis, enable second stream.
+    uint32_t expected[] = {TargetRate(0).kbps<uint32_t>(),
+                           MaxRate(1).kbps<uint32_t>()};
+    ExpectEqual(expected, GetAllocation(volatile_rate,
+                                        (TargetRate(0) + MinRate(1)) * 1.1));
+  }
+
+  {
+    // Enough to enable all thee layers.
+    uint32_t expected[] = {
+        TargetRate(0).kbps<uint32_t>(), TargetRate(1).kbps<uint32_t>(),
+        (volatile_rate - TargetRate(0) - TargetRate(1)).kbps<uint32_t>()};
+    ExpectEqual(expected, GetAllocation(volatile_rate, volatile_rate));
+  }
+
+  {
+    // Drop hysteresis, all three still on.
+    uint32_t expected[] = {
+        TargetRate(0).kbps<uint32_t>(), TargetRate(1).kbps<uint32_t>(),
+        (volatile_rate - TargetRate(0) - TargetRate(1)).kbps<uint32_t>()};
+    ExpectEqual(expected,
+                GetAllocation(volatile_rate,
+                              TargetRate(0) + TargetRate(1) + MinRate(2)));
+  }
+}
+
 class ScreenshareRateAllocationTest : public SimulcastRateAllocatorTest {
  public:
   void SetupConferenceScreenshare(bool use_simulcast, bool active = true) {
diff --git a/rtc_base/experiments/stable_target_rate_experiment.cc b/rtc_base/experiments/stable_target_rate_experiment.cc
index 185bd40..fa7a97b 100644
--- a/rtc_base/experiments/stable_target_rate_experiment.cc
+++ b/rtc_base/experiments/stable_target_rate_experiment.cc
@@ -20,8 +20,8 @@
 
 StableTargetRateExperiment::StableTargetRateExperiment(
     const WebRtcKeyValueConfig* const key_value_config,
-    absl::optional<double> default_video_hysteresis,
-    absl::optional<double> default_screenshare_hysteresis)
+    double default_video_hysteresis,
+    double default_screenshare_hysteresis)
     : enabled_("enabled", false),
       video_hysteresis_factor_("video_hysteresis_factor",
                                default_video_hysteresis),
@@ -44,31 +44,25 @@
 
 StableTargetRateExperiment StableTargetRateExperiment::ParseFromKeyValueConfig(
     const WebRtcKeyValueConfig* const key_value_config) {
-  if (key_value_config->Lookup("WebRTC-VideoRateControl") != "") {
-    RateControlSettings rate_control =
-        RateControlSettings::ParseFromKeyValueConfig(key_value_config);
-    return StableTargetRateExperiment(key_value_config,
-                                      rate_control.GetSimulcastHysteresisFactor(
-                                          VideoCodecMode::kRealtimeVideo),
-                                      rate_control.GetSimulcastHysteresisFactor(
-                                          VideoCodecMode::kScreensharing));
-  }
-  return StableTargetRateExperiment(key_value_config, absl::nullopt,
-                                    absl::nullopt);
+  RateControlSettings rate_control =
+      RateControlSettings::ParseFromKeyValueConfig(key_value_config);
+  return StableTargetRateExperiment(
+      key_value_config,
+      rate_control.GetSimulcastHysteresisFactor(VideoCodecMode::kRealtimeVideo),
+      rate_control.GetSimulcastHysteresisFactor(
+          VideoCodecMode::kScreensharing));
 }
 
 bool StableTargetRateExperiment::IsEnabled() const {
   return enabled_.Get();
 }
 
-absl::optional<double> StableTargetRateExperiment::GetVideoHysteresisFactor()
-    const {
-  return video_hysteresis_factor_.GetOptional();
+double StableTargetRateExperiment::GetVideoHysteresisFactor() const {
+  return video_hysteresis_factor_.Get();
 }
 
-absl::optional<double>
-StableTargetRateExperiment::GetScreenshareHysteresisFactor() const {
-  return screenshare_hysteresis_factor_.GetOptional();
+double StableTargetRateExperiment::GetScreenshareHysteresisFactor() const {
+  return screenshare_hysteresis_factor_.Get();
 }
 
 }  // namespace webrtc
diff --git a/rtc_base/experiments/stable_target_rate_experiment.h b/rtc_base/experiments/stable_target_rate_experiment.h
index 7a2c06c..299299c 100644
--- a/rtc_base/experiments/stable_target_rate_experiment.h
+++ b/rtc_base/experiments/stable_target_rate_experiment.h
@@ -25,18 +25,18 @@
       const WebRtcKeyValueConfig* const key_value_config);
 
   bool IsEnabled() const;
-  absl::optional<double> GetVideoHysteresisFactor() const;
-  absl::optional<double> GetScreenshareHysteresisFactor() const;
+  double GetVideoHysteresisFactor() const;
+  double GetScreenshareHysteresisFactor() const;
 
  private:
   explicit StableTargetRateExperiment(
       const WebRtcKeyValueConfig* const key_value_config,
-      absl::optional<double> default_video_hysteresis,
-      absl::optional<double> default_screenshare_hysteresis);
+      double default_video_hysteresis,
+      double default_screenshare_hysteresis);
 
   FieldTrialParameter<bool> enabled_;
-  FieldTrialOptional<double> video_hysteresis_factor_;
-  FieldTrialOptional<double> screenshare_hysteresis_factor_;
+  FieldTrialParameter<double> video_hysteresis_factor_;
+  FieldTrialParameter<double> screenshare_hysteresis_factor_;
 };
 
 }  // namespace webrtc
diff --git a/rtc_base/experiments/stable_target_rate_experiment_unittest.cc b/rtc_base/experiments/stable_target_rate_experiment_unittest.cc
index 86629f4..71e757d 100644
--- a/rtc_base/experiments/stable_target_rate_experiment_unittest.cc
+++ b/rtc_base/experiments/stable_target_rate_experiment_unittest.cc
@@ -19,8 +19,8 @@
   StableTargetRateExperiment config =
       StableTargetRateExperiment::ParseFromFieldTrials();
   EXPECT_FALSE(config.IsEnabled());
-  EXPECT_FALSE(config.GetVideoHysteresisFactor());
-  EXPECT_FALSE(config.GetScreenshareHysteresisFactor());
+  EXPECT_EQ(config.GetVideoHysteresisFactor(), 1.0);
+  EXPECT_EQ(config.GetScreenshareHysteresisFactor(), 1.35);
 }
 
 TEST(StableBweExperimentTest, EnabledNoHysteresis) {
@@ -30,8 +30,8 @@
   StableTargetRateExperiment config =
       StableTargetRateExperiment::ParseFromFieldTrials();
   EXPECT_TRUE(config.IsEnabled());
-  EXPECT_FALSE(config.GetVideoHysteresisFactor());
-  EXPECT_FALSE(config.GetScreenshareHysteresisFactor());
+  EXPECT_EQ(config.GetVideoHysteresisFactor(), 1.0);
+  EXPECT_EQ(config.GetScreenshareHysteresisFactor(), 1.35);
 }
 
 TEST(StableBweExperimentTest, EnabledWithHysteresis) {