Dont use SimulcastToSvcConverter if the middle stream is inactive

Bug: chromium:375048794
Change-Id: I0acc3b0096c81e00d60c9339b86f30fbe8f92212
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/366523
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43296}
diff --git a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
index 2da0918..65906a3 100644
--- a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
+++ b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
@@ -58,7 +58,6 @@
 #include "modules/video_coding/svc/scalable_video_controller_no_layering.h"
 #include "modules/video_coding/svc/svc_rate_allocator.h"
 #include "modules/video_coding/utility/framerate_controller_deprecated.h"
-#include "modules/video_coding/utility/simulcast_utility.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/containers/flat_map.h"
 #include "rtc_base/experiments/field_trial_list.h"
@@ -553,8 +552,7 @@
   }
 
   if (enable_svc_for_simulcast_ && codec_.numberOfSimulcastStreams > 1) {
-    if (!SimulcastUtility::ValidSimulcastParameters(
-            codec_, codec_.numberOfSimulcastStreams)) {
+    if (!SimulcastToSvcConverter::IsConfigSupported(codec_)) {
       return WEBRTC_VIDEO_CODEC_ERR_SIMULCAST_PARAMETERS_NOT_SUPPORTED;
     }
     RTC_LOG(LS_INFO) << "Rewriting simulcast config to SVC.";
diff --git a/modules/video_coding/svc/BUILD.gn b/modules/video_coding/svc/BUILD.gn
index 603b029..d029d13 100644
--- a/modules/video_coding/svc/BUILD.gn
+++ b/modules/video_coding/svc/BUILD.gn
@@ -98,6 +98,7 @@
     "../../../api/video:encoded_image",
     "../../../api/video_codecs:video_codecs_api",
     "../../../modules/video_coding:video_codec_interface",
+    "../../../modules/video_coding:video_coding_utility",
     "../../../rtc_base:checks",
     "../../../rtc_base/system:rtc_export",
   ]
diff --git a/modules/video_coding/svc/simulcast_to_svc_converter.cc b/modules/video_coding/svc/simulcast_to_svc_converter.cc
index 77d5b29..6863d9b 100644
--- a/modules/video_coding/svc/simulcast_to_svc_converter.cc
+++ b/modules/video_coding/svc/simulcast_to_svc_converter.cc
@@ -12,6 +12,7 @@
 
 #include "modules/video_coding/svc/create_scalability_structure.h"
 #include "modules/video_coding/svc/scalability_mode_util.h"
+#include "modules/video_coding/utility/simulcast_utility.h"
 #include "rtc_base/checks.h"
 
 namespace webrtc {
@@ -138,4 +139,38 @@
   video_controller->OnRatesUpdated(dummy_bitrates);
 }
 
+// static
+bool SimulcastToSvcConverter::IsConfigSupported(const VideoCodec& codec) {
+  if (codec.numberOfSimulcastStreams <= 1 ||
+      !SimulcastUtility::ValidSimulcastParameters(
+          codec, codec.numberOfSimulcastStreams)) {
+    return false;
+  }
+  // Ensure there's 4:2:1 scaling.
+  for (int i = 1; i < codec.numberOfSimulcastStreams; ++i) {
+    if (codec.simulcastStream[i].active &&
+        codec.simulcastStream[i - 1].active &&
+        (codec.simulcastStream[i].width !=
+             codec.simulcastStream[i - 1].width * 2 ||
+         codec.simulcastStream[i].height !=
+             codec.simulcastStream[i - 1].height * 2)) {
+      return false;
+    }
+  }
+  int first_active_layer = -1;
+  int last_active_layer = -1;
+  int num_active_layers = 0;
+  for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) {
+    if (codec.simulcastStream[i].active) {
+      if (first_active_layer < 0)
+        first_active_layer = i;
+      last_active_layer = i;
+      ++num_active_layers;
+    }
+  }
+  // Active layers must form a continuous segment. Can't have holes, because
+  // most SVC encoders can't process that.
+  return num_active_layers == last_active_layer - first_active_layer + 1;
+}
+
 }  // namespace webrtc
diff --git a/modules/video_coding/svc/simulcast_to_svc_converter.h b/modules/video_coding/svc/simulcast_to_svc_converter.h
index 5d03d4a..8472fe7 100644
--- a/modules/video_coding/svc/simulcast_to_svc_converter.h
+++ b/modules/video_coding/svc/simulcast_to_svc_converter.h
@@ -35,6 +35,8 @@
 
   ~SimulcastToSvcConverter() = default;
 
+  static bool IsConfigSupported(const VideoCodec& codec);
+
   VideoCodec GetConfig() const;
 
   void EncodeStarted(bool force_keyframe);
diff --git a/modules/video_coding/svc/simulcast_to_svc_converter_unittest.cc b/modules/video_coding/svc/simulcast_to_svc_converter_unittest.cc
index fcf8c48..6af35b5 100644
--- a/modules/video_coding/svc/simulcast_to_svc_converter_unittest.cc
+++ b/modules/video_coding/svc/simulcast_to_svc_converter_unittest.cc
@@ -99,7 +99,7 @@
                               .minBitrate = 100,
                               .qpMax = 150,
                               .active = true};
-  codec.simulcastStream[2] = {.width = 12800,
+  codec.simulcastStream[2] = {.width = 1280,
                               .height = 720,
                               .maxFramerate = 30,
                               .numberOfTemporalLayers = 3,
@@ -168,7 +168,7 @@
                               .minBitrate = 100,
                               .qpMax = 150,
                               .active = true};
-  codec.simulcastStream[2] = {.width = 12800,
+  codec.simulcastStream[2] = {.width = 1280,
                               .height = 720,
                               .maxFramerate = 30,
                               .numberOfTemporalLayers = 3,
@@ -235,4 +235,150 @@
   }
 }
 
+TEST(SimulcastToSvc, SupportsOnlyContinuousActiveStreams) {
+  VideoCodec codec;
+  codec.codecType = kVideoCodecVP9;
+  codec.SetScalabilityMode(ScalabilityMode::kL1T3);
+  codec.width = 1280;
+  codec.height = 720;
+  codec.minBitrate = 10;
+  codec.maxBitrate = 2500;
+  codec.numberOfSimulcastStreams = 3;
+  codec.VP9()->numberOfSpatialLayers = 1;
+  codec.VP9()->interLayerPred = InterLayerPredMode::kOff;
+
+  codec.simulcastStream[0] = {.width = 320,
+                              .height = 180,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 100,
+                              .targetBitrate = 70,
+                              .minBitrate = 50,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[1] = {.width = 640,
+                              .height = 360,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 250,
+                              .targetBitrate = 150,
+                              .minBitrate = 100,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[2] = {.width = 1280,
+                              .height = 720,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 1500,
+                              .targetBitrate = 1200,
+                              .minBitrate = 800,
+                              .qpMax = 150,
+                              .active = true};
+  EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec));
+
+  codec.simulcastStream[0].active = false;
+  codec.simulcastStream[1].active = true;
+  codec.simulcastStream[2].active = true;
+  EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec));
+
+  codec.simulcastStream[0].active = true;
+  codec.simulcastStream[1].active = true;
+  codec.simulcastStream[2].active = false;
+  EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec));
+
+  codec.simulcastStream[0].active = true;
+  codec.simulcastStream[1].active = false;
+  codec.simulcastStream[2].active = true;
+  EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec));
+}
+
+TEST(SimulcastToSvc, SupportsOnlySameTemporalStructure) {
+  VideoCodec codec;
+  codec.codecType = kVideoCodecVP9;
+  codec.width = 1280;
+  codec.height = 720;
+  codec.minBitrate = 10;
+  codec.maxBitrate = 2500;
+  codec.numberOfSimulcastStreams = 3;
+  codec.VP9()->numberOfSpatialLayers = 1;
+  codec.VP9()->interLayerPred = InterLayerPredMode::kOff;
+
+  codec.simulcastStream[0] = {.width = 320,
+                              .height = 180,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 100,
+                              .targetBitrate = 70,
+                              .minBitrate = 50,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[1] = {.width = 640,
+                              .height = 360,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 250,
+                              .targetBitrate = 150,
+                              .minBitrate = 100,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[2] = {.width = 1280,
+                              .height = 720,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 1500,
+                              .targetBitrate = 1200,
+                              .minBitrate = 800,
+                              .qpMax = 150,
+                              .active = true};
+  EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec));
+
+  codec.simulcastStream[0].numberOfTemporalLayers = 1;
+  EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec));
+}
+
+TEST(SimulcastToSvc, SupportsOnly421Scaling) {
+  VideoCodec codec;
+  codec.codecType = kVideoCodecVP9;
+  codec.width = 1280;
+  codec.height = 720;
+  codec.minBitrate = 10;
+  codec.maxBitrate = 2500;
+  codec.numberOfSimulcastStreams = 3;
+  codec.VP9()->numberOfSpatialLayers = 1;
+  codec.VP9()->interLayerPred = InterLayerPredMode::kOff;
+
+  codec.simulcastStream[0] = {.width = 320,
+                              .height = 180,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 100,
+                              .targetBitrate = 70,
+                              .minBitrate = 50,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[1] = {.width = 640,
+                              .height = 360,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 250,
+                              .targetBitrate = 150,
+                              .minBitrate = 100,
+                              .qpMax = 150,
+                              .active = true};
+  codec.simulcastStream[2] = {.width = 1280,
+                              .height = 720,
+                              .maxFramerate = 30,
+                              .numberOfTemporalLayers = 3,
+                              .maxBitrate = 1500,
+                              .targetBitrate = 1200,
+                              .minBitrate = 800,
+                              .qpMax = 150,
+                              .active = true};
+  EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec));
+
+  codec.simulcastStream[0].width = 160;
+  codec.simulcastStream[0].height = 90;
+  EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec));
+}
+
 }  // namespace webrtc