QualityRampupExperiment: SetMaxBitrate may not be set correctly.

Call SetMaxBitrate when encoder is configured instead of in OnMaybeEncodeFrame (which is called after the initial frame dropping ->
max bitrate is not set for dropped frames).

Added support for single active stream configuration.

Bug: none
Change-Id: I33ff96e7feed70b9ea3c9b3da89f117859108347
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231681
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34973}
diff --git a/rtc_base/experiments/quality_rampup_experiment.cc b/rtc_base/experiments/quality_rampup_experiment.cc
index ee6675c..35c83f7 100644
--- a/rtc_base/experiments/quality_rampup_experiment.cc
+++ b/rtc_base/experiments/quality_rampup_experiment.cc
@@ -70,8 +70,13 @@
   return (now_ms - *start_ms_) >= min_duration_ms_.Value();
 }
 
+void QualityRampupExperiment::Reset() {
+  start_ms_.reset();
+  max_bitrate_kbps_.reset();
+}
+
 bool QualityRampupExperiment::Enabled() const {
-  return min_pixels_ || min_duration_ms_ || max_bitrate_kbps_;
+  return min_pixels_ && min_duration_ms_;
 }
 
 }  // namespace webrtc
diff --git a/rtc_base/experiments/quality_rampup_experiment.h b/rtc_base/experiments/quality_rampup_experiment.h
index 78556eb..719b189 100644
--- a/rtc_base/experiments/quality_rampup_experiment.h
+++ b/rtc_base/experiments/quality_rampup_experiment.h
@@ -33,6 +33,7 @@
   // (max_bitrate_factor_) above `max_bitrate_kbps_` for `min_duration_ms_`.
   bool BwHigh(int64_t now_ms, uint32_t available_bw_kbps);
 
+  void Reset();
   bool Enabled() const;
 
  private:
diff --git a/video/adaptation/quality_rampup_experiment_helper.cc b/video/adaptation/quality_rampup_experiment_helper.cc
index 6d82503..adcad40 100644
--- a/video/adaptation/quality_rampup_experiment_helper.cc
+++ b/video/adaptation/quality_rampup_experiment_helper.cc
@@ -43,22 +43,30 @@
   return nullptr;
 }
 
+void QualityRampUpExperimentHelper::ConfigureQualityRampupExperiment(
+    bool reset,
+    absl::optional<uint32_t> pixels,
+    absl::optional<DataRate> max_bitrate) {
+  if (reset)
+    quality_rampup_experiment_.Reset();
+  if (pixels && max_bitrate)
+    quality_rampup_experiment_.SetMaxBitrate(*pixels, max_bitrate->kbps());
+}
+
 void QualityRampUpExperimentHelper::PerformQualityRampupExperiment(
     rtc::scoped_refptr<QualityScalerResource> quality_scaler_resource,
     DataRate bandwidth,
     DataRate encoder_target_bitrate,
-    DataRate max_bitrate,
-    int pixels) {
-  if (!quality_scaler_resource->is_started())
+    absl::optional<DataRate> max_bitrate) {
+  if (!quality_scaler_resource->is_started() || !max_bitrate)
     return;
 
   int64_t now_ms = clock_->TimeInMilliseconds();
-  quality_rampup_experiment_.SetMaxBitrate(pixels, max_bitrate.kbps());
 
   bool try_quality_rampup = false;
   if (quality_rampup_experiment_.BwHigh(now_ms, bandwidth.kbps())) {
     // Verify that encoder is at max bitrate and the QP is low.
-    if (encoder_target_bitrate == max_bitrate &&
+    if (encoder_target_bitrate == *max_bitrate &&
         quality_scaler_resource->QpFastFilterLow()) {
       try_quality_rampup = true;
     }
diff --git a/video/adaptation/quality_rampup_experiment_helper.h b/video/adaptation/quality_rampup_experiment_helper.h
index 81be982..4fe1f24 100644
--- a/video/adaptation/quality_rampup_experiment_helper.h
+++ b/video/adaptation/quality_rampup_experiment_helper.h
@@ -44,12 +44,15 @@
   void cpu_adapted(bool cpu_adapted);
   void qp_resolution_adaptations(int qp_adaptations);
 
+  void ConfigureQualityRampupExperiment(bool reset,
+                                        absl::optional<uint32_t> pixels,
+                                        absl::optional<DataRate> max_bitrate);
+
   void PerformQualityRampupExperiment(
       rtc::scoped_refptr<QualityScalerResource> quality_scaler_resource,
       DataRate bandwidth,
       DataRate encoder_target_bitrate,
-      DataRate max_bitrate,
-      int pixels);
+      absl::optional<DataRate> max_bitrate);
 
  private:
   QualityRampUpExperimentHelper(
diff --git a/video/adaptation/video_stream_encoder_resource_manager.cc b/video/adaptation/video_stream_encoder_resource_manager.cc
index 23a3593..0b2fa89 100644
--- a/video/adaptation/video_stream_encoder_resource_manager.cc
+++ b/video/adaptation/video_stream_encoder_resource_manager.cc
@@ -88,6 +88,30 @@
   return std::equal(a.begin(), a.end(), b.begin());
 }
 
+absl::optional<DataRate> GetSingleActiveLayerMaxBitrate(
+    const VideoCodec& codec) {
+  int num_active = 0;
+  absl::optional<DataRate> max_bitrate;
+  if (codec.codecType == VideoCodecType::kVideoCodecVP9) {
+    for (int i = 0; i < codec.VP9().numberOfSpatialLayers; ++i) {
+      if (codec.spatialLayers[i].active) {
+        ++num_active;
+        max_bitrate =
+            DataRate::KilobitsPerSec(codec.spatialLayers[i].maxBitrate);
+      }
+    }
+  } else {
+    for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) {
+      if (codec.simulcastStream[i].active) {
+        ++num_active;
+        max_bitrate =
+            DataRate::KilobitsPerSec(codec.simulcastStream[i].maxBitrate);
+      }
+    }
+  }
+  return (num_active > 1) ? absl::nullopt : max_bitrate;
+}
+
 }  // namespace
 
 class VideoStreamEncoderResourceManager::InitialFrameDropper {
@@ -103,7 +127,8 @@
         use_bandwidth_allocation_(false),
         bandwidth_allocation_(DataRate::Zero()),
         last_input_width_(0),
-        last_input_height_(0) {
+        last_input_height_(0),
+        last_stream_configuration_changed_(false) {
     RTC_DCHECK(quality_scaler_resource_);
   }
 
@@ -123,6 +148,10 @@
                : absl::nullopt;
   }
 
+  bool last_stream_configuration_changed() const {
+    return last_stream_configuration_changed_;
+  }
+
   // Input signals.
   void SetStartBitrate(DataRate start_bitrate, int64_t now_ms) {
     set_start_bitrate_ = start_bitrate;
@@ -156,6 +185,7 @@
   void OnEncoderSettingsUpdated(
       const VideoCodec& codec,
       const VideoAdaptationCounters& adaptation_counters) {
+    last_stream_configuration_changed_ = false;
     std::vector<bool> active_flags = GetActiveLayersFlags(codec);
     // Check if the source resolution has changed for the external reasons,
     // i.e. without any adaptation from WebRTC.
@@ -167,6 +197,7 @@
     if (!EqualFlags(active_flags, last_active_flags_) ||
         source_resolution_changed) {
       // Streams configuration has changed.
+      last_stream_configuration_changed_ = true;
       // Initial frame drop must be enabled because BWE might be way too low
       // for the selected resolution.
       if (quality_scaler_resource_->is_started()) {
@@ -226,6 +257,7 @@
   VideoAdaptationCounters last_adaptation_counters_;
   int last_input_width_;
   int last_input_height_;
+  bool last_stream_configuration_changed_;
 };
 
 VideoStreamEncoderResourceManager::VideoStreamEncoderResourceManager(
@@ -394,6 +426,12 @@
   initial_frame_dropper_->OnEncoderSettingsUpdated(
       encoder_settings_->video_codec(), current_adaptation_counters_);
   MaybeUpdateTargetFrameRate();
+  if (quality_rampup_experiment_) {
+    quality_rampup_experiment_->ConfigureQualityRampupExperiment(
+        initial_frame_dropper_->last_stream_configuration_changed(),
+        initial_frame_dropper_->single_active_stream_pixels(),
+        GetSingleActiveLayerMaxBitrate(encoder_settings_->video_codec()));
+  }
 }
 
 void VideoStreamEncoderResourceManager::SetStartBitrate(
@@ -497,8 +535,7 @@
     quality_rampup_experiment_->PerformQualityRampupExperiment(
         quality_scaler_resource_, bandwidth,
         DataRate::BitsPerSec(encoder_target_bitrate_bps_.value_or(0)),
-        DataRate::KilobitsPerSec(encoder_settings_->video_codec().maxBitrate),
-        LastFrameSizeOrDefault());
+        GetSingleActiveLayerMaxBitrate(encoder_settings_->video_codec()));
   }
 }
 
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index b9c6f8f..c1dc8a3 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -6140,7 +6140,13 @@
 
 TEST_F(VideoStreamEncoderTest, RampsUpInQualityWhenBwIsHigh) {
   webrtc::test::ScopedFieldTrials field_trials(
-      "WebRTC-Video-QualityRampupSettings/min_pixels:1,min_duration_ms:2000/");
+      "WebRTC-Video-QualityRampupSettings/"
+      "min_pixels:921600,min_duration_ms:2000/");
+
+  const int kWidth = 1280;
+  const int kHeight = 720;
+  const int kFps = 10;
+  max_framerate_ = kFps;
 
   // Reset encoder for field trials to take effect.
   VideoEncoderConfig config = video_encoder_config_.Copy();
@@ -6163,9 +6169,7 @@
       DataRate::BitsPerSec(kLowBitrateBps), 0, 0, 0);
 
   // Expect first frame to be dropped and resolution to be limited.
-  const int kWidth = 1280;
-  const int kHeight = 720;
-  const int64_t kFrameIntervalMs = 100;
+  const int64_t kFrameIntervalMs = 1000 / kFps;
   int64_t timestamp_ms = kFrameIntervalMs;
   source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
   ExpectDroppedFrame();
@@ -6177,17 +6181,23 @@
       max_bitrate, max_bitrate, max_bitrate, 0, 0, 0);
 
   // Insert frames and advance `min_duration_ms`.
+  const int64_t start_bw_high_ms = CurrentTimeMs();
   for (size_t i = 1; i <= 10; i++) {
     timestamp_ms += kFrameIntervalMs;
     source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
     WaitForEncodedFrame(timestamp_ms);
   }
+
+  // Advance to `min_duration_ms` - 1, frame should not trigger high BW.
+  int64_t elapsed_bw_high_ms = CurrentTimeMs() - start_bw_high_ms;
+  AdvanceTime(TimeDelta::Millis(2000 - elapsed_bw_high_ms - 1));
+  timestamp_ms += kFrameIntervalMs;
+  source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+  WaitForEncodedFrame(timestamp_ms);
   EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_LT(source.sink_wants().max_pixel_count, kWidth * kHeight);
 
-  AdvanceTime(TimeDelta::Millis(2000));
-
-  // Insert frame should trigger high BW and release quality limitation.
+  // Frame should trigger high BW and release quality limitation.
   timestamp_ms += kFrameIntervalMs;
   source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
   WaitForEncodedFrame(timestamp_ms);