VP9 ResolutionBitrateLimits: If bitrates are configured, use intersection.

Bug: none
Change-Id: I58ada41d7a196837b35df4cf61f7e7561998cf13
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/209704
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33390}
diff --git a/video/BUILD.gn b/video/BUILD.gn
index f296f44..e0399a1 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -601,6 +601,7 @@
       "end_to_end_tests/multi_stream_tester.h",
       "end_to_end_tests/multi_stream_tests.cc",
       "end_to_end_tests/network_state_tests.cc",
+      "end_to_end_tests/resolution_bitrate_limits_tests.cc",
       "end_to_end_tests/retransmission_tests.cc",
       "end_to_end_tests/rtp_rtcp_tests.cc",
       "end_to_end_tests/ssrc_tests.cc",
diff --git a/video/end_to_end_tests/resolution_bitrate_limits_tests.cc b/video/end_to_end_tests/resolution_bitrate_limits_tests.cc
new file mode 100644
index 0000000..16eee8c
--- /dev/null
+++ b/video/end_to_end_tests/resolution_bitrate_limits_tests.cc
@@ -0,0 +1,374 @@
+/*
+ *  Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <algorithm>
+
+#include "media/engine/webrtc_video_engine.h"
+#include "rtc_base/experiments/encoder_info_settings.h"
+#include "test/call_test.h"
+#include "test/fake_encoder.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+#include "test/video_encoder_proxy_factory.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+void SetEncoderSpecific(VideoEncoderConfig* encoder_config,
+                        VideoCodecType type,
+                        size_t num_spatial_layers) {
+  if (type == kVideoCodecVP9) {
+    VideoCodecVP9 vp9 = VideoEncoder::GetDefaultVp9Settings();
+    vp9.numberOfSpatialLayers = num_spatial_layers;
+    encoder_config->encoder_specific_settings = new rtc::RefCountedObject<
+        VideoEncoderConfig::Vp9EncoderSpecificSettings>(vp9);
+  }
+}
+
+SpatialLayer GetLayer(int pixels, const VideoCodec& codec) {
+  if (codec.codecType == VideoCodecType::kVideoCodecVP9) {
+    for (size_t i = 0; i < codec.VP9().numberOfSpatialLayers; ++i) {
+      if (codec.spatialLayers[i].width * codec.spatialLayers[i].height ==
+          pixels) {
+        return codec.spatialLayers[i];
+      }
+    }
+  } else {
+    for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) {
+      if (codec.simulcastStream[i].width * codec.simulcastStream[i].height ==
+          pixels) {
+        return codec.simulcastStream[i];
+      }
+    }
+  }
+  ADD_FAILURE();
+  return SpatialLayer();
+}
+
+}  // namespace
+
+class ResolutionBitrateLimitsTest
+    : public test::CallTest,
+      public ::testing::WithParamInterface<std::string> {
+ public:
+  ResolutionBitrateLimitsTest() : payload_name_(GetParam()) {}
+
+  const std::string payload_name_;
+};
+
+INSTANTIATE_TEST_SUITE_P(PayloadName,
+                         ResolutionBitrateLimitsTest,
+                         ::testing::Values("VP8", "VP9"));
+
+class InitEncodeTest : public test::EndToEndTest,
+                       public test::FrameGeneratorCapturer::SinkWantsObserver,
+                       public test::FakeEncoder {
+ public:
+  struct Bitrate {
+    const absl::optional<uint32_t> min;
+    const absl::optional<uint32_t> max;
+  };
+  struct TestConfig {
+    const bool active;
+    const Bitrate bitrate_bps;
+  };
+  struct Expectation {
+    const uint32_t pixels = 0;
+    const Bitrate eq_bitrate_bps;
+    const Bitrate ne_bitrate_bps;
+  };
+
+  InitEncodeTest(const std::string& payload_name,
+                 const std::vector<TestConfig>& configs,
+                 const std::vector<Expectation>& expectations)
+      : EndToEndTest(test::CallTest::kDefaultTimeoutMs),
+        FakeEncoder(Clock::GetRealTimeClock()),
+        encoder_factory_(this),
+        payload_name_(payload_name),
+        configs_(configs),
+        expectations_(expectations) {}
+
+  void OnFrameGeneratorCapturerCreated(
+      test::FrameGeneratorCapturer* frame_generator_capturer) override {
+    frame_generator_capturer->SetSinkWantsObserver(this);
+    // Set initial resolution.
+    frame_generator_capturer->ChangeResolution(1280, 720);
+  }
+
+  void OnSinkWantsChanged(rtc::VideoSinkInterface<VideoFrame>* sink,
+                          const rtc::VideoSinkWants& wants) override {}
+
+  size_t GetNumVideoStreams() const override {
+    return (payload_name_ == "VP9") ? 1 : configs_.size();
+  }
+
+  void ModifyVideoConfigs(
+      VideoSendStream::Config* send_config,
+      std::vector<VideoReceiveStream::Config>* receive_configs,
+      VideoEncoderConfig* encoder_config) override {
+    send_config->encoder_settings.encoder_factory = &encoder_factory_;
+    send_config->rtp.payload_name = payload_name_;
+    send_config->rtp.payload_type = test::CallTest::kVideoSendPayloadType;
+    const VideoCodecType codec_type = PayloadStringToCodecType(payload_name_);
+    encoder_config->codec_type = codec_type;
+    encoder_config->video_stream_factory =
+        new rtc::RefCountedObject<cricket::EncoderStreamFactory>(
+            payload_name_, /*max qp*/ 0, /*screencast*/ false,
+            /*screenshare enabled*/ false);
+    encoder_config->max_bitrate_bps = -1;
+    if (configs_.size() == 1 && configs_[0].bitrate_bps.max)
+      encoder_config->max_bitrate_bps = *configs_[0].bitrate_bps.max;
+    if (payload_name_ == "VP9") {
+      // Simulcast layers indicates which spatial layers are active.
+      encoder_config->simulcast_layers.resize(configs_.size());
+    }
+    double scale_factor = 1.0;
+    for (int i = configs_.size() - 1; i >= 0; --i) {
+      VideoStream& stream = encoder_config->simulcast_layers[i];
+      stream.active = configs_[i].active;
+      if (configs_[i].bitrate_bps.min)
+        stream.min_bitrate_bps = *configs_[i].bitrate_bps.min;
+      if (configs_[i].bitrate_bps.max)
+        stream.max_bitrate_bps = *configs_[i].bitrate_bps.max;
+      stream.scale_resolution_down_by = scale_factor;
+      scale_factor *= (payload_name_ == "VP9") ? 1.0 : 2.0;
+    }
+    SetEncoderSpecific(encoder_config, codec_type, configs_.size());
+  }
+
+  int32_t InitEncode(const VideoCodec* codec,
+                     const Settings& settings) override {
+    for (const auto& expected : expectations_) {
+      SpatialLayer layer = GetLayer(expected.pixels, *codec);
+      if (expected.eq_bitrate_bps.min)
+        EXPECT_EQ(*expected.eq_bitrate_bps.min, layer.minBitrate * 1000);
+      if (expected.eq_bitrate_bps.max)
+        EXPECT_EQ(*expected.eq_bitrate_bps.max, layer.maxBitrate * 1000);
+      EXPECT_NE(expected.ne_bitrate_bps.min, layer.minBitrate * 1000);
+      EXPECT_NE(expected.ne_bitrate_bps.max, layer.maxBitrate * 1000);
+    }
+    observation_complete_.Set();
+    return 0;
+  }
+
+  VideoEncoder::EncoderInfo GetEncoderInfo() const override {
+    EncoderInfo info = FakeEncoder::GetEncoderInfo();
+    if (!encoder_info_override_.resolution_bitrate_limits().empty()) {
+      info.resolution_bitrate_limits =
+          encoder_info_override_.resolution_bitrate_limits();
+    }
+    return info;
+  }
+
+  void PerformTest() override {
+    ASSERT_TRUE(Wait()) << "Timed out while waiting for InitEncode() call.";
+  }
+
+ private:
+  test::VideoEncoderProxyFactory encoder_factory_;
+  const std::string payload_name_;
+  const std::vector<TestConfig> configs_;
+  const std::vector<Expectation> expectations_;
+  const LibvpxVp8EncoderInfoSettings encoder_info_override_;
+};
+
+TEST_P(ResolutionBitrateLimitsTest, LimitsApplied) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:921600,"
+      "min_start_bitrate_bps:0,"
+      "min_bitrate_bps:32000,"
+      "max_bitrate_bps:3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{1280 * 720,
+        /*eq_bitrate_bps=*/{32000, 3333000},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, EncodingsApplied) {
+  InitEncodeTest test(payload_name_,
+                      {{/*active=*/true, /*bitrate_bps=*/{22000, 3555000}}},
+                      // Expectations:
+                      {{1280 * 720,
+                        /*eq_bitrate_bps=*/{22000, 3555000},
+                        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, IntersectionApplied) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:921600,"
+      "min_start_bitrate_bps:0,"
+      "min_bitrate_bps:32000,"
+      "max_bitrate_bps:3333000/");
+
+  InitEncodeTest test(payload_name_,
+                      {{/*active=*/true, /*bitrate_bps=*/{22000, 1555000}}},
+                      // Expectations:
+                      {{1280 * 720,
+                        /*eq_bitrate_bps=*/{32000, 1555000},
+                        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, LimitsAppliedMiddleActive) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:21000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{640 * 360,
+        /*eq_bitrate_bps=*/{21000, 2222000},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, IntersectionAppliedMiddleActive) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:31000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{30000, 1555000}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{640 * 360,
+        /*eq_bitrate_bps=*/{31000, 1555000},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, DefaultLimitsAppliedMiddleActive) {
+  const absl::optional<VideoEncoder::ResolutionBitrateLimits>
+      kDefaultSinglecastLimits360p =
+          EncoderInfoSettings::GetDefaultSinglecastBitrateLimitsForResolution(
+              PayloadStringToCodecType(payload_name_), 640 * 360);
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{640 * 360,
+        /*eq_bitrate_bps=*/
+        {kDefaultSinglecastLimits360p->min_bitrate_bps,
+         kDefaultSinglecastLimits360p->max_bitrate_bps},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, LimitsAppliedHighestActive) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:31000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{1280 * 720,
+        /*eq_bitrate_bps=*/{32000, 3333000},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, IntersectionAppliedHighestActive) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:31000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{30000, 1555000}}},
+      // Expectations:
+      {{1280 * 720,
+        /*eq_bitrate_bps=*/{32000, 1555000},
+        /*ne_bitrate_bps=*/{absl::nullopt, absl::nullopt}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, LimitsNotAppliedLowestActive) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:31000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/false, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{640 * 360,
+        /*eq_bitrate_bps=*/{absl::nullopt, absl::nullopt},
+        /*ne_bitrate_bps=*/{31000, 2222000}},
+       {1280 * 720,
+        /*eq_bitrate_bps=*/{absl::nullopt, absl::nullopt},
+        /*ne_bitrate_bps=*/{32000, 3333000}}});
+  RunBaseTest(&test);
+}
+
+TEST_P(ResolutionBitrateLimitsTest, LimitsNotAppliedSimulcast) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-GetEncoderInfoOverride/"
+      "frame_size_pixels:230400|921600,"
+      "min_start_bitrate_bps:0|0,"
+      "min_bitrate_bps:31000|32000,"
+      "max_bitrate_bps:2222000|3333000/");
+
+  InitEncodeTest test(
+      payload_name_,
+      {{/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}},
+       {/*active=*/true, /*bitrate_bps=*/{absl::nullopt, absl::nullopt}}},
+      // Expectations:
+      {{640 * 360,
+        /*eq_bitrate_bps=*/{absl::nullopt, absl::nullopt},
+        /*ne_bitrate_bps=*/{31000, 2222000}},
+       {1280 * 720,
+        /*eq_bitrate_bps=*/{absl::nullopt, absl::nullopt},
+        /*ne_bitrate_bps=*/{32000, 3333000}}});
+  RunBaseTest(&test);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index b894bd5..cfef876 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -370,6 +370,7 @@
                            const VideoEncoderConfig& encoder_config,
                            VideoCodec* codec) {
   if (codec->codecType != VideoCodecType::kVideoCodecVP9 ||
+      encoder_config.simulcast_layers.size() <= 1 ||
       VideoStreamEncoderResourceManager::IsSimulcast(encoder_config)) {
     // Resolution bitrate limits usage is restricted to singlecast.
     return;
@@ -387,12 +388,43 @@
     return;
   }
 
+  // Index for the active stream.
+  absl::optional<size_t> index;
+  for (size_t i = 0; i < encoder_config.simulcast_layers.size(); ++i) {
+    if (encoder_config.simulcast_layers[i].active)
+      index = i;
+  }
+  if (!index.has_value()) {
+    return;
+  }
+
+  int min_bitrate_bps;
+  if (encoder_config.simulcast_layers[*index].min_bitrate_bps <= 0) {
+    min_bitrate_bps = bitrate_limits->min_bitrate_bps;
+  } else {
+    min_bitrate_bps =
+        std::max(bitrate_limits->min_bitrate_bps,
+                 encoder_config.simulcast_layers[*index].min_bitrate_bps);
+  }
+  int max_bitrate_bps;
+  if (encoder_config.simulcast_layers[*index].max_bitrate_bps <= 0) {
+    max_bitrate_bps = bitrate_limits->max_bitrate_bps;
+  } else {
+    max_bitrate_bps =
+        std::min(bitrate_limits->max_bitrate_bps,
+                 encoder_config.simulcast_layers[*index].max_bitrate_bps);
+  }
+  if (min_bitrate_bps >= max_bitrate_bps) {
+    RTC_LOG(LS_WARNING) << "Bitrate limits not used, min_bitrate_bps "
+                        << min_bitrate_bps << " >= max_bitrate_bps "
+                        << max_bitrate_bps;
+    return;
+  }
+
   for (int i = 0; i < codec->VP9()->numberOfSpatialLayers; ++i) {
     if (codec->spatialLayers[i].active) {
-      codec->spatialLayers[i].minBitrate =
-          bitrate_limits->min_bitrate_bps / 1000;
-      codec->spatialLayers[i].maxBitrate =
-          bitrate_limits->max_bitrate_bps / 1000;
+      codec->spatialLayers[i].minBitrate = min_bitrate_bps / 1000;
+      codec->spatialLayers[i].maxBitrate = max_bitrate_bps / 1000;
       codec->spatialLayers[i].targetBitrate =
           std::min(codec->spatialLayers[i].targetBitrate,
                    codec->spatialLayers[i].maxBitrate);