Remove simulcast constraints in SimulcastEncoderAdapter

The lowest and highest resolution layers are also identified instead
of assuming they are the first and last ones.

Bug: webrtc:10069
Change-Id: If9c76d647415c5065b79dc71850709db6bf16f61
Reviewed-on: https://webrtc-review.googlesource.com/c/114429
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26343}
diff --git a/media/engine/simulcast_encoder_adapter.cc b/media/engine/simulcast_encoder_adapter.cc
index d73c59a..895489c 100644
--- a/media/engine/simulcast_encoder_adapter.cc
+++ b/media/engine/simulcast_encoder_adapter.cc
@@ -66,21 +66,6 @@
   return streams;
 }
 
-bool ValidSimulcastResolutions(const webrtc::VideoCodec& codec,
-                               int num_streams) {
-  if (codec.width != codec.simulcastStream[num_streams - 1].width ||
-      codec.height != codec.simulcastStream[num_streams - 1].height) {
-    return false;
-  }
-  for (int i = 0; i < num_streams; ++i) {
-    if (codec.width * codec.simulcastStream[i].height !=
-        codec.height * codec.simulcastStream[i].width) {
-      return false;
-    }
-  }
-  return true;
-}
-
 int VerifyCodec(const webrtc::VideoCodec* inst) {
   if (inst == nullptr) {
     return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
@@ -102,6 +87,12 @@
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
+bool StreamResolutionCompare(const webrtc::SimulcastStream& a,
+                             const webrtc::SimulcastStream& b) {
+  return std::tie(a.height, a.width, a.maxBitrate, a.maxFramerate) <
+         std::tie(b.height, b.width, b.maxBitrate, b.maxFramerate);
+}
+
 // An EncodedImageCallback implementation that forwards on calls to a
 // SimulcastEncoderAdapter, but with the stream index it's registered with as
 // the first parameter to Encoded.
@@ -195,10 +186,6 @@
   RTC_DCHECK_LE(number_of_streams, kMaxSimulcastStreams);
   const bool doing_simulcast = (number_of_streams > 1);
 
-  if (doing_simulcast && !ValidSimulcastResolutions(*inst, number_of_streams)) {
-    return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
-  }
-
   codec_ = *inst;
   SimulcastRateAllocator rate_allocator(codec_);
   VideoBitrateAllocation allocation = rate_allocator.GetAllocation(
@@ -212,6 +199,19 @@
   encoder_info_.supports_native_handle = true;
   encoder_info_.scaling_settings.thresholds = absl::nullopt;
   // Create |number_of_streams| of encoder instances and init them.
+
+  const auto minmax = std::minmax_element(
+      std::begin(codec_.simulcastStream),
+      std::begin(codec_.simulcastStream) + number_of_streams,
+      StreamResolutionCompare);
+  const auto lowest_resolution_stream_index =
+      std::distance(std::begin(codec_.simulcastStream), minmax.first);
+  const auto highest_resolution_stream_index =
+      std::distance(std::begin(codec_.simulcastStream), minmax.second);
+
+  RTC_DCHECK_LT(lowest_resolution_stream_index, number_of_streams);
+  RTC_DCHECK_LT(highest_resolution_stream_index, number_of_streams);
+
   for (int i = 0; i < number_of_streams; ++i) {
     VideoCodec stream_codec;
     uint32_t start_bitrate_kbps = start_bitrates[i];
@@ -223,11 +223,16 @@
     } else {
       // Cap start bitrate to the min bitrate in order to avoid strange codec
       // behavior. Since sending will be false, this should not matter.
+      StreamResolution stream_resolution =
+          i == highest_resolution_stream_index
+              ? StreamResolution::HIGHEST
+              : i == lowest_resolution_stream_index ? StreamResolution::LOWEST
+                                                    : StreamResolution::OTHER;
+
       start_bitrate_kbps =
           std::max(codec_.simulcastStream[i].minBitrate, start_bitrate_kbps);
-      bool highest_resolution_stream = (i == (number_of_streams - 1));
-      PopulateStreamCodec(codec_, i, start_bitrate_kbps,
-                          highest_resolution_stream, &stream_codec);
+      PopulateStreamCodec(codec_, i, start_bitrate_kbps, stream_resolution,
+                          &stream_codec);
     }
 
     // TODO(ronghuawu): Remove once this is handled in LibvpxVp8Encoder.
@@ -511,7 +516,7 @@
     const webrtc::VideoCodec& inst,
     int stream_index,
     uint32_t start_bitrate_kbps,
-    bool highest_resolution_stream,
+    StreamResolution stream_resolution,
     webrtc::VideoCodec* stream_codec) {
   *stream_codec = inst;
 
@@ -523,8 +528,7 @@
   stream_codec->minBitrate = inst.simulcastStream[stream_index].minBitrate;
   stream_codec->qpMax = inst.simulcastStream[stream_index].qpMax;
   // Settings that are based on stream/resolution.
-  const bool lowest_resolution_stream = (stream_index == 0);
-  if (lowest_resolution_stream) {
+  if (stream_resolution == StreamResolution::LOWEST) {
     // Settings for lowest spatial resolutions.
     if (inst.mode == VideoCodecMode::kScreensharing) {
       if (experimental_boosted_screenshare_qp_) {
@@ -537,7 +541,7 @@
   if (inst.codecType == webrtc::kVideoCodecVP8) {
     stream_codec->VP8()->numberOfTemporalLayers =
         inst.simulcastStream[stream_index].numberOfTemporalLayers;
-    if (!highest_resolution_stream) {
+    if (stream_resolution != StreamResolution::HIGHEST) {
       // For resolutions below CIF, set the codec |complexity| parameter to
       // kComplexityHigher, which maps to cpu_used = -4.
       int pixels_per_frame = stream_codec->width * stream_codec->height;
diff --git a/media/engine/simulcast_encoder_adapter.h b/media/engine/simulcast_encoder_adapter.h
index 696503d..b7b6c7f 100644
--- a/media/engine/simulcast_encoder_adapter.h
+++ b/media/engine/simulcast_encoder_adapter.h
@@ -83,11 +83,17 @@
     bool send_stream;
   };
 
+  enum class StreamResolution {
+    OTHER,
+    HIGHEST,
+    LOWEST,
+  };
+
   // Populate the codec settings for each simulcast stream.
   void PopulateStreamCodec(const webrtc::VideoCodec& inst,
                            int stream_index,
                            uint32_t start_bitrate_kbps,
-                           bool highest_resolution_stream,
+                           StreamResolution stream_resolution,
                            webrtc::VideoCodec* stream_codec);
 
   bool Initialized() const;
diff --git a/media/engine/simulcast_encoder_adapter_unittest.cc b/media/engine/simulcast_encoder_adapter_unittest.cc
index 4cbaf56..c02a585 100644
--- a/media/engine/simulcast_encoder_adapter_unittest.cc
+++ b/media/engine/simulcast_encoder_adapter_unittest.cc
@@ -428,10 +428,13 @@
     // always be 0.
   }
 
-  void InitRefCodec(int stream_index, VideoCodec* ref_codec) {
+  void InitRefCodec(int stream_index,
+                    VideoCodec* ref_codec,
+                    bool reverse_layer_order = false) {
     *ref_codec = codec_;
     ref_codec->VP8()->numberOfTemporalLayers =
-        kTestTemporalLayerProfile[stream_index];
+        kTestTemporalLayerProfile[reverse_layer_order ? 2 - stream_index
+                                                      : stream_index];
     ref_codec->width = codec_.simulcastStream[stream_index].width;
     ref_codec->height = codec_.simulcastStream[stream_index].height;
     ref_codec->maxBitrate = codec_.simulcastStream[stream_index].maxBitrate;
@@ -963,6 +966,39 @@
   VerifyCodec(ref_codec, 0);
 }
 
+TEST_F(TestSimulcastEncoderAdapterFake,
+       DoesNotAlterMaxQpForScreenshareReversedLayer) {
+  const int kHighMaxQp = 56;
+  const int kLowMaxQp = 46;
+
+  SimulcastTestFixtureImpl::DefaultSettings(
+      &codec_, static_cast<const int*>(kTestTemporalLayerProfile),
+      kVideoCodecVP8, true /* reverse_layer_order */);
+  codec_.numberOfSimulcastStreams = 3;
+  codec_.simulcastStream[2].qpMax = kHighMaxQp;
+  codec_.mode = VideoCodecMode::kScreensharing;
+
+  EXPECT_EQ(0, adapter_->InitEncode(&codec_, 1, 1200));
+  EXPECT_EQ(3u, helper_->factory()->encoders().size());
+
+  // Just check the lowest stream, which is the one that where the adapter
+  // might alter the max qp setting.
+  VideoCodec ref_codec;
+  InitRefCodec(2, &ref_codec, true /* reverse_layer_order */);
+  ref_codec.qpMax = kHighMaxQp;
+  ref_codec.VP8()->complexity = webrtc::VideoCodecComplexity::kComplexityHigher;
+  ref_codec.VP8()->denoisingOn = false;
+  ref_codec.startBitrate = 100;  // Should equal to the target bitrate.
+  VerifyCodec(ref_codec, 2);
+
+  // Change the max qp and try again.
+  codec_.simulcastStream[2].qpMax = kLowMaxQp;
+  EXPECT_EQ(0, adapter_->InitEncode(&codec_, 1, 1200));
+  EXPECT_EQ(3u, helper_->factory()->encoders().size());
+  ref_codec.qpMax = kLowMaxQp;
+  VerifyCodec(ref_codec, 2);
+}
+
 TEST_F(TestSimulcastEncoderAdapterFake, ActivatesCorrectStreamsInInitEncode) {
   // Set up common settings for three streams.
   SimulcastTestFixtureImpl::DefaultSettings(
diff --git a/modules/video_coding/utility/simulcast_rate_allocator.cc b/modules/video_coding/utility/simulcast_rate_allocator.cc
index 4e8b708..873f94f 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator.cc
+++ b/modules/video_coding/utility/simulcast_rate_allocator.cc
@@ -13,7 +13,9 @@
 #include <stdio.h>
 #include <algorithm>
 #include <cstdint>
+#include <numeric>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include "common_types.h"  // NOLINT(build/include)
@@ -110,10 +112,21 @@
     }
     return;
   }
+
+  // Sort the layers by maxFramerate, they might not always be from smallest
+  // to biggest
+  std::vector<size_t> layer_index(codec_.numberOfSimulcastStreams);
+  std::iota(layer_index.begin(), layer_index.end(), 0);
+  std::stable_sort(layer_index.begin(), layer_index.end(),
+                   [this](size_t a, size_t b) {
+                     return std::tie(codec_.simulcastStream[a].maxBitrate) <
+                            std::tie(codec_.simulcastStream[b].maxBitrate);
+                   });
+
   // Find the first active layer. We don't allocate to inactive layers.
   size_t active_layer = 0;
   for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
-    if (codec_.simulcastStream[active_layer].active) {
+    if (codec_.simulcastStream[layer_index[active_layer]].active) {
       // Found the first active layer.
       break;
     }
@@ -127,7 +140,8 @@
   // 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[active_layer].minBitrate * 1000, left_to_allocate);
+      codec_.simulcastStream[layer_index[active_layer]].minBitrate * 1000,
+      left_to_allocate);
 
   // Begin by allocating bitrate to simulcast streams, putting all bitrate in
   // temporal layer 0. We'll then distribute this bitrate, across potential
@@ -144,15 +158,16 @@
   size_t top_active_layer = active_layer;
   // Allocate up to the target bitrate for each active simulcast layer.
   for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
-    const SimulcastStream& stream = codec_.simulcastStream[active_layer];
+    const SimulcastStream& stream =
+        codec_.simulcastStream[layer_index[active_layer]];
     if (!stream.active) {
-      stream_enabled_[active_layer] = false;
+      stream_enabled_[layer_index[active_layer]] = false;
       continue;
     }
     // 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;
-    if (!first_allocation && !stream_enabled_[active_layer]) {
+    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);
@@ -162,18 +177,19 @@
     }
 
     // We are allocating to this layer so it is the current active allocation.
-    top_active_layer = active_layer;
-    stream_enabled_[active_layer] = true;
+    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(active_layer, 0, allocation);
+    allocated_bitrates_bps->SetBitrate(layer_index[active_layer], 0,
+                                       allocation);
     RTC_DCHECK_LE(allocation, left_to_allocate);
     left_to_allocate -= allocation;
   }
 
   // All layers above this one are not active.
   for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
-    stream_enabled_[active_layer] = false;
+    stream_enabled_[layer_index[active_layer]] = false;
   }
 
   // Next, try allocate remaining bitrate, up to max bitrate, in top active
diff --git a/modules/video_coding/utility/simulcast_test_fixture_impl.cc b/modules/video_coding/utility/simulcast_test_fixture_impl.cc
index be50286..781c7d0 100644
--- a/modules/video_coding/utility/simulcast_test_fixture_impl.cc
+++ b/modules/video_coding/utility/simulcast_test_fixture_impl.cc
@@ -212,7 +212,8 @@
 void SimulcastTestFixtureImpl::DefaultSettings(
     VideoCodec* settings,
     const int* temporal_layer_profile,
-    VideoCodecType codec_type) {
+    VideoCodecType codec_type,
+    bool reverse_layer_order) {
   RTC_CHECK(settings);
   memset(settings, 0, sizeof(VideoCodec));
   settings->codecType = codec_type;
@@ -227,26 +228,34 @@
   settings->numberOfSimulcastStreams = kNumberOfSimulcastStreams;
   settings->active = true;
   ASSERT_EQ(3, kNumberOfSimulcastStreams);
+  int layer_order[3] = {0, 1, 2};
+  if (reverse_layer_order) {
+    layer_order[0] = 2;
+    layer_order[2] = 0;
+  }
   settings->timing_frame_thresholds = {kDefaultTimingFramesDelayMs,
                                        kDefaultOutlierFrameSizePercent};
   ConfigureStream(kDefaultWidth / 4, kDefaultHeight / 4, kMaxBitrates[0],
                   kMinBitrates[0], kTargetBitrates[0],
-                  &settings->simulcastStream[0], temporal_layer_profile[0]);
+                  &settings->simulcastStream[layer_order[0]],
+                  temporal_layer_profile[0]);
   ConfigureStream(kDefaultWidth / 2, kDefaultHeight / 2, kMaxBitrates[1],
                   kMinBitrates[1], kTargetBitrates[1],
-                  &settings->simulcastStream[1], temporal_layer_profile[1]);
+                  &settings->simulcastStream[layer_order[1]],
+                  temporal_layer_profile[1]);
   ConfigureStream(kDefaultWidth, kDefaultHeight, kMaxBitrates[2],
                   kMinBitrates[2], kTargetBitrates[2],
-                  &settings->simulcastStream[2], temporal_layer_profile[2]);
-    if (codec_type == kVideoCodecVP8) {
-      settings->VP8()->denoisingOn = true;
-      settings->VP8()->automaticResizeOn = false;
-      settings->VP8()->frameDroppingOn = true;
-      settings->VP8()->keyFrameInterval = 3000;
-    } else {
-      settings->H264()->frameDroppingOn = true;
-      settings->H264()->keyFrameInterval = 3000;
-    }
+                  &settings->simulcastStream[layer_order[2]],
+                  temporal_layer_profile[2]);
+  if (codec_type == kVideoCodecVP8) {
+    settings->VP8()->denoisingOn = true;
+    settings->VP8()->automaticResizeOn = false;
+    settings->VP8()->frameDroppingOn = true;
+    settings->VP8()->keyFrameInterval = 3000;
+  } else {
+    settings->H264()->frameDroppingOn = true;
+    settings->H264()->keyFrameInterval = 3000;
+  }
 }
 
 SimulcastTestFixtureImpl::SimulcastTestFixtureImpl(
diff --git a/modules/video_coding/utility/simulcast_test_fixture_impl.h b/modules/video_coding/utility/simulcast_test_fixture_impl.h
index 2f834bd..8881e06 100644
--- a/modules/video_coding/utility/simulcast_test_fixture_impl.h
+++ b/modules/video_coding/utility/simulcast_test_fixture_impl.h
@@ -55,7 +55,8 @@
 
   static void DefaultSettings(VideoCodec* settings,
                               const int* temporal_layer_profile,
-                              VideoCodecType codec_type);
+                              VideoCodecType codec_type,
+                              bool reverse_layer_order = false);
 
  private:
   class TestEncodedImageCallback;