Add FullStackTest with simulated encoder overshooting

Bug: webrtc:10302
Change-Id: I1d4b9ef22ba1ca9a221cc01e2c44775014c90d4f
Reviewed-on: https://webrtc-review.googlesource.com/c/122082
Commit-Queue: Erik Språng <sprang@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26673}
diff --git a/api/test/video_quality_test_fixture.h b/api/test/video_quality_test_fixture.h
index 79b6d67..c334a2c 100644
--- a/api/test/video_quality_test_fixture.h
+++ b/api/test/video_quality_test_fixture.h
@@ -60,6 +60,7 @@
       std::string clip_name;  // "Generator" to generate frames instead.
       size_t capture_device_index;
       SdpVideoFormat::Parameters sdp_params;
+      double encoder_overshoot_factor;
     } video[2];
     struct Audio {
       bool enabled;
diff --git a/video/full_stack_tests.cc b/video/full_stack_tests.cc
index 5e08fcf..c1848a7 100644
--- a/video/full_stack_tests.cc
+++ b/video/full_stack_tests.cc
@@ -279,6 +279,23 @@
   fixture->RunWithAnalyzer(foreman_cif);
 }
 
+// Restricted network and encoder overproducing by 30%.
+TEST(FullStackTest, ForemanCifLink150kbpsBadRateController) {
+  auto fixture = CreateVideoQualityTestFixture();
+  ParamsWithLogging foreman_cif;
+  foreman_cif.call.send_side_bwe = true;
+  foreman_cif.video[0] = {true,  352,           288, 30, 30000, 500000, 2000000,
+                          false, "VP8",         1,   0,  0,     false,  false,
+                          true,  "foreman_cif", 0,   {}, 1.30};
+  foreman_cif.analyzer = {
+      "foreman_cif_link_150kbps_delay100ms_30pkts_queue_overshoot30", 0.0, 0.0,
+      kFullStackTestDurationSecs};
+  foreman_cif.config->link_capacity_kbps = 150;
+  foreman_cif.config->queue_length_packets = 30;
+  foreman_cif.config->queue_delay_ms = 100;
+  fixture->RunWithAnalyzer(foreman_cif);
+}
+
 TEST_P(GenericDescriptorTest, ForemanCifPlr5) {
   auto fixture = CreateVideoQualityTestFixture();
   ParamsWithLogging foreman_cif;
diff --git a/video/video_quality_test.cc b/video/video_quality_test.cc
index 1b2f9d6..ad3224d 100644
--- a/video/video_quality_test.cc
+++ b/video/video_quality_test.cc
@@ -56,6 +56,27 @@
 
 constexpr int kDefaultMaxQp = cricket::WebRtcVideoChannel::kDefaultQpMax;
 
+std::pair<uint32_t, uint32_t> GetMinMaxBitratesBps(const VideoCodec& codec,
+                                                   size_t spatial_idx) {
+  uint32_t min_bitrate = codec.minBitrate;
+  uint32_t max_bitrate = codec.maxBitrate;
+  if (spatial_idx < codec.numberOfSimulcastStreams) {
+    min_bitrate =
+        std::max(min_bitrate, codec.simulcastStream[spatial_idx].minBitrate);
+    max_bitrate =
+        std::min(max_bitrate, codec.simulcastStream[spatial_idx].maxBitrate);
+  }
+  if (codec.codecType == VideoCodecType::kVideoCodecVP9 &&
+      spatial_idx < codec.VP9().numberOfSpatialLayers) {
+    min_bitrate =
+        std::max(min_bitrate, codec.spatialLayers[spatial_idx].minBitrate);
+    max_bitrate =
+        std::min(max_bitrate, codec.spatialLayers[spatial_idx].maxBitrate);
+  }
+  max_bitrate = std::max(max_bitrate, min_bitrate);
+  return {min_bitrate * 1000, max_bitrate * 1000};
+}
+
 class VideoStreamFactory
     : public VideoEncoderConfig::VideoStreamFactoryInterface {
  public:
@@ -87,8 +108,11 @@
  public:
   QualityTestVideoEncoder(std::unique_ptr<VideoEncoder> encoder,
                           VideoAnalyzer* analyzer,
-                          std::vector<FileWrapper> files)
-      : encoder_(std::move(encoder)), analyzer_(analyzer) {
+                          std::vector<FileWrapper> files,
+                          double overshoot_factor)
+      : encoder_(std::move(encoder)),
+        overshoot_factor_(overshoot_factor),
+        analyzer_(analyzer) {
     for (FileWrapper& file : files) {
       writers_.push_back(
           IvfFileWriter::Wrap(std::move(file), /* byte_limit= */ 100000000));
@@ -98,6 +122,7 @@
   int32_t InitEncode(const VideoCodec* codec_settings,
                      int32_t number_of_cores,
                      size_t max_payload_size) override {
+    codec_settings_ = *codec_settings;
     return encoder_->InitEncode(codec_settings, number_of_cores,
                                 max_payload_size);
   }
@@ -115,15 +140,57 @@
     }
     return encoder_->Encode(frame, codec_specific_info, frame_types);
   }
-  int32_t SetRates(uint32_t bitrate, uint32_t framerate) override {
-    return encoder_->SetRates(bitrate, framerate);
-  }
   int32_t SetRateAllocation(const VideoBitrateAllocation& allocation,
                             uint32_t framerate) override {
-    return encoder_->SetRateAllocation(allocation, framerate);
+    RTC_DCHECK_GT(overshoot_factor_, 0.0);
+    if (overshoot_factor_ == 1.0) {
+      return encoder_->SetRateAllocation(allocation, framerate);
+    }
+
+    // Simulating encoder overshooting target bitrate, by configuring actual
+    // encoder too high. Take care not to adjust past limits of config,
+    // otherwise encoders may crash on DCHECK.
+    VideoBitrateAllocation overshot_allocation;
+    for (size_t si = 0; si < kMaxSpatialLayers; ++si) {
+      const uint32_t spatial_layer_bitrate_bps =
+          allocation.GetSpatialLayerSum(si);
+      if (spatial_layer_bitrate_bps == 0) {
+        continue;
+      }
+
+      uint32_t min_bitrate_bps;
+      uint32_t max_bitrate_bps;
+      std::tie(min_bitrate_bps, max_bitrate_bps) =
+          GetMinMaxBitratesBps(codec_settings_, si);
+      double overshoot_factor = overshoot_factor_;
+      const uint32_t corrected_bitrate = rtc::checked_cast<uint32_t>(
+          overshoot_factor * spatial_layer_bitrate_bps);
+      if (corrected_bitrate < min_bitrate_bps) {
+        overshoot_factor = min_bitrate_bps / spatial_layer_bitrate_bps;
+      } else if (corrected_bitrate > max_bitrate_bps) {
+        overshoot_factor = max_bitrate_bps / spatial_layer_bitrate_bps;
+      }
+
+      for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) {
+        if (allocation.HasBitrate(si, ti)) {
+          overshot_allocation.SetBitrate(
+              si, ti,
+              rtc::checked_cast<uint32_t>(overshoot_factor *
+                                          allocation.GetBitrate(si, ti)));
+        }
+      }
+    }
+
+    return encoder_->SetRateAllocation(overshot_allocation, framerate);
   }
   EncoderInfo GetEncoderInfo() const override {
-    return encoder_->GetEncoderInfo();
+    EncoderInfo info = encoder_->GetEncoderInfo();
+    if (overshoot_factor_ != 1.0) {
+      // We're simulating bad encoder, don't forward trusted setting
+      // from eg libvpx.
+      info.has_trusted_rate_controller = false;
+    }
+    return info;
   }
 
  private:
@@ -157,10 +224,12 @@
     callback_->OnDroppedFrame(reason);
   }
 
-  std::unique_ptr<VideoEncoder> encoder_;
-  EncodedImageCallback* callback_ = nullptr;
+  const std::unique_ptr<VideoEncoder> encoder_;
+  const double overshoot_factor_;
   VideoAnalyzer* const analyzer_;
   std::vector<std::unique_ptr<IvfFileWriter>> writers_;
+  EncodedImageCallback* callback_ = nullptr;
+  VideoCodec codec_settings_;
 };
 
 }  // namespace
@@ -202,22 +271,42 @@
   } else {
     encoder = internal_encoder_factory_.CreateVideoEncoder(format);
   }
+
+  std::vector<FileWrapper> encoded_frame_dump_files;
   if (!params_.logging.encoded_frame_base_path.empty()) {
     char ss_buf[100];
     rtc::SimpleStringBuilder sb(ss_buf);
     sb << send_logs_++;
     std::string prefix =
         params_.logging.encoded_frame_base_path + "." + sb.str() + ".send.";
-    std::vector<FileWrapper> files;
-    files.push_back(FileWrapper::OpenWriteOnly(prefix + "1.ivf"));
-    files.push_back(FileWrapper::OpenWriteOnly(prefix + "2.ivf"));
-    files.push_back(FileWrapper::OpenWriteOnly(prefix + "3.ivf"));
+    encoded_frame_dump_files.push_back(
+        FileWrapper::OpenWriteOnly(prefix + "1.ivf"));
+    encoded_frame_dump_files.push_back(
+        FileWrapper::OpenWriteOnly(prefix + "2.ivf"));
+    encoded_frame_dump_files.push_back(
+        FileWrapper::OpenWriteOnly(prefix + "3.ivf"));
+  }
 
+  double overshoot_factor = 1.0;
+  // Match format to either of the streams in dual-stream mode in order to get
+  // the overshoot factor. This is not very robust but we can't know for sure
+  // which stream this encoder is meant for, from within the factory.
+  if (format ==
+      SdpVideoFormat(params_.video[0].codec, params_.video[0].sdp_params)) {
+    overshoot_factor = params_.video[0].encoder_overshoot_factor;
+  } else if (format == SdpVideoFormat(params_.video[1].codec,
+                                      params_.video[1].sdp_params)) {
+    overshoot_factor = params_.video[1].encoder_overshoot_factor;
+  }
+  if (overshoot_factor == 0.0) {
+    // If params were zero-initialized, set to 1.0 instead.
+    overshoot_factor = 1.0;
+  }
+
+  if (analyzer || !encoded_frame_dump_files.empty() || overshoot_factor > 1.0) {
     encoder = absl::make_unique<QualityTestVideoEncoder>(
-        std::move(encoder), analyzer, std::move(files));
-  } else if (analyzer) {
-    encoder = absl::make_unique<QualityTestVideoEncoder>(
-        std::move(encoder), analyzer, std::vector<FileWrapper>());
+        std::move(encoder), analyzer, std::move(encoded_frame_dump_files),
+        overshoot_factor);
   }
 
   return encoder;
@@ -263,10 +352,44 @@
 
 VideoQualityTest::Params::Params()
     : call({false, false, BitrateConstraints(), 0}),
-      video{{false, 640, 480, 30, 50, 800, 800, false, "VP8", 1, -1, 0, false,
-             false, false, ""},
-            {false, 640, 480, 30, 50, 800, 800, false, "VP8", 1, -1, 0, false,
-             false, false, ""}},
+      video{{false,
+             640,
+             480,
+             30,
+             50,
+             800,
+             800,
+             false,
+             "VP8",
+             1,
+             -1,
+             0,
+             false,
+             false,
+             false,
+             "",
+             0,
+             {},
+             0.0},
+            {false,
+             640,
+             480,
+             30,
+             50,
+             800,
+             800,
+             false,
+             "VP8",
+             1,
+             -1,
+             0,
+             false,
+             false,
+             false,
+             "",
+             0,
+             {},
+             0.0}},
       audio({false, false, false, false}),
       screenshare{{false, false, 10, 0}, {false, false, 10, 0}},
       analyzer({"", 0.0, 0.0, 0, "", ""}),