Add field trial for allowing cropped resolution when limiting max layers.

E.g. 480x270: max_layers:2
     480x268: max_layers:1 -> 2.

Bug: none
Change-Id: Ieb86bc7b04e639d81e73d80aa0940b4c320e4de4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/201730
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33030}
diff --git a/media/engine/simulcast.cc b/media/engine/simulcast.cc
index f74d4ad..375572f 100644
--- a/media/engine/simulcast.cc
+++ b/media/engine/simulcast.cc
@@ -23,6 +23,7 @@
 #include "modules/video_coding/utility/simulcast_rate_allocator.h"
 #include "rtc_base/arraysize.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/experiments/normalize_simulcast_size_experiment.h"
 #include "rtc_base/experiments/rate_control_settings.h"
@@ -61,7 +62,7 @@
   int width;
   int height;
   // The maximum number of simulcast layers can be used for
-  // resolutions at |widthxheigh| for legacy applications.
+  // resolutions at |widthxheight| for legacy applications.
   size_t max_layers;
   // The maximum bitrate for encoding stream at |widthxheight|, when we are
   // not sending the next higher spatial stream.
@@ -162,7 +163,10 @@
   return ((size >> base2_exponent) << base2_exponent);
 }
 
-SimulcastFormat InterpolateSimulcastFormat(int width, int height) {
+SimulcastFormat InterpolateSimulcastFormat(
+    int width,
+    int height,
+    absl::optional<double> max_roundup_rate) {
   const int index = FindSimulcastFormatIndex(width, height);
   if (index == 0)
     return kSimulcastFormats[index];
@@ -174,7 +178,10 @@
   const float rate = (total_pixels_up - total_pixels) /
                      static_cast<float>(total_pixels_up - total_pixels_down);
 
-  size_t max_layers = kSimulcastFormats[index].max_layers;
+  // Use upper resolution if |rate| is below the configured threshold.
+  size_t max_layers = (max_roundup_rate && rate < max_roundup_rate.value())
+                          ? kSimulcastFormats[index - 1].max_layers
+                          : kSimulcastFormats[index].max_layers;
   webrtc::DataRate max_bitrate =
       Interpolate(kSimulcastFormats[index - 1].max_bitrate,
                   kSimulcastFormats[index].max_bitrate, rate);
@@ -188,6 +195,10 @@
   return {width, height, max_layers, max_bitrate, target_bitrate, min_bitrate};
 }
 
+SimulcastFormat InterpolateSimulcastFormat(int width, int height) {
+  return InterpolateSimulcastFormat(width, height, absl::nullopt);
+}
+
 webrtc::DataRate FindSimulcastMaxBitrate(int width, int height) {
   return InterpolateSimulcastFormat(width, height).max_bitrate;
 }
@@ -235,9 +246,18 @@
                                 const webrtc::WebRtcKeyValueConfig& trials) {
   if (!absl::StartsWith(trials.Lookup(kUseLegacySimulcastLayerLimitFieldTrial),
                         "Disabled")) {
+    // Max layers from one higher resolution in kSimulcastFormats will be used
+    // if the ratio (pixels_up - pixels) / (pixels_up - pixels_down) is less
+    // than configured |max_ratio|. pixels_down is the selected index in
+    // kSimulcastFormats based on pixels.
+    webrtc::FieldTrialOptional<double> max_ratio("max_ratio");
+    webrtc::ParseFieldTrial({&max_ratio},
+                            trials.Lookup("WebRTC-SimulcastLayerLimitRoundUp"));
+
     size_t adaptive_layer_count = std::max(
         need_layers,
-        kSimulcastFormats[FindSimulcastFormatIndex(width, height)].max_layers);
+        InterpolateSimulcastFormat(width, height, max_ratio.GetOptional())
+            .max_layers);
     if (layer_count > adaptive_layer_count) {
       RTC_LOG(LS_WARNING) << "Reducing simulcast layer count from "
                           << layer_count << " to " << adaptive_layer_count;
diff --git a/media/engine/simulcast_unittest.cc b/media/engine/simulcast_unittest.cc
index 27b1574..193f8c0 100644
--- a/media/engine/simulcast_unittest.cc
+++ b/media/engine/simulcast_unittest.cc
@@ -378,4 +378,66 @@
   }
 }
 
+TEST(SimulcastTest, MaxLayers) {
+  FieldTrialBasedConfig trials;
+  const size_t kMinLayers = 1;
+  const int kMaxLayers = 3;
+
+  std::vector<VideoStream> streams;
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(3u, streams.size());
+  // <960x540: 2 layers
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 539,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(2u, streams.size());
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(2u, streams.size());
+  // <480x270: 1 layer
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 269,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(1u, streams.size());
+}
+
+TEST(SimulcastTest, MaxLayersWithFieldTrial) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-SimulcastLayerLimitRoundUp/max_ratio:0.1/");
+  FieldTrialBasedConfig trials;
+  const size_t kMinLayers = 1;
+  const int kMaxLayers = 3;
+
+  std::vector<VideoStream> streams;
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 540,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(3u, streams.size());
+  // Lowest cropped height where max layers from higher resolution is used.
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 512,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(3u, streams.size());
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 960, 510,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(2u, streams.size());
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 270,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(2u, streams.size());
+  // Lowest cropped height where max layers from higher resolution is used.
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 256,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(2u, streams.size());
+  streams = cricket::GetSimulcastConfig(kMinLayers, kMaxLayers, 480, 254,
+                                        kBitratePriority, kQpMax, !kScreenshare,
+                                        true, trials);
+  EXPECT_EQ(1u, streams.size());
+}
+
 }  // namespace webrtc