Field trial to control SVC frame dropping mode in libvpx VP9 encoder

Example: "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/Enabled,layer_drop_mode:1,max_consec_drop:7/"

It is only possible to enable LAYER_DROP (layer_drop_mode=1) for now. All other modes are ignored. Max consecutive frame drops (max_consec_drop) value from the field is always applied if the field trial is enabled.

LAYER_DROP requires flexible mode (is_flexible_mode_=true) which can be enabled by means of WebRTC-Vp9InterLayerPred: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/media/engine/webrtc_video_engine.cc;l=976

Bug: webrtc:15827, b/320629637
Change-Id: I9c4d4838b11547e608d863198b109cb1485902d6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/335041
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41755}
diff --git a/experiments/field_trials.py b/experiments/field_trials.py
index 86066ec..0273f89 100755
--- a/experiments/field_trials.py
+++ b/experiments/field_trials.py
@@ -95,6 +95,9 @@
     FieldTrial('WebRTC-LibaomAv1Encoder-MaxConsecFrameDrop',
                'webrtc:15821',
                date(2024, 4, 1)),
+    FieldTrial('WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig',
+               'webrtc:15827',
+               date(2024, 9, 1)),
     FieldTrial('WebRTC-Pacer-FastRetransmissions',
                'chromium:1354491',
                date(2024, 4, 1)),
diff --git a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
index b73fd10..20c29ab 100644
--- a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
+++ b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc
@@ -266,7 +266,8 @@
                             "Disabled")),
       performance_flags_(ParsePerformanceFlagsFromTrials(trials)),
       num_steady_state_frames_(0),
-      config_changed_(true) {
+      config_changed_(true),
+      svc_frame_drop_config_(ParseSvcFrameDropConfig(trials)) {
   codec_ = {};
   memset(&svc_params_, 0, sizeof(vpx_svc_extra_cfg_t));
 }
@@ -924,11 +925,24 @@
         svc_drop_frame_.framedrop_thresh[i] = config_->rc_dropframe_thresh;
       }
     } else {
-      // Configure encoder to drop entire superframe whenever it needs to drop
-      // a layer. This mode is preferred over per-layer dropping which causes
-      // quality flickering and is not compatible with RTP non-flexible mode.
-      svc_drop_frame_.framedrop_mode = FULL_SUPERFRAME_DROP;
-      svc_drop_frame_.max_consec_drop = std::numeric_limits<int>::max();
+      if (svc_frame_drop_config_.enabled &&
+          svc_frame_drop_config_.layer_drop_mode == LAYER_DROP &&
+          is_flexible_mode_ && svc_controller_ &&
+          (inter_layer_pred_ == InterLayerPredMode::kOff ||
+           inter_layer_pred_ == InterLayerPredMode::kOnKeyPic)) {
+        // SVC controller is required since it properly accounts for dropped
+        // refs (unlike SetReferences(), which assumes full superframe drop).
+        svc_drop_frame_.framedrop_mode = LAYER_DROP;
+      } else {
+        // Configure encoder to drop entire superframe whenever it needs to drop
+        // a layer. This mode is preferred over per-layer dropping which causes
+        // quality flickering and is not compatible with RTP non-flexible mode.
+        svc_drop_frame_.framedrop_mode = FULL_SUPERFRAME_DROP;
+      }
+      svc_drop_frame_.max_consec_drop =
+          svc_frame_drop_config_.enabled
+              ? svc_frame_drop_config_.max_consec_drop
+              : std::numeric_limits<int>::max();
       for (size_t i = 0; i < num_spatial_layers_; ++i) {
         svc_drop_frame_.framedrop_thresh[i] = config_->rc_dropframe_thresh;
       }
@@ -1910,6 +1924,26 @@
   return config;
 }
 
+LibvpxVp9Encoder::SvcFrameDropConfig LibvpxVp9Encoder::ParseSvcFrameDropConfig(
+    const FieldTrialsView& trials) {
+  FieldTrialFlag enabled = FieldTrialFlag("Enabled");
+  FieldTrialParameter<int> layer_drop_mode("layer_drop_mode",
+                                           FULL_SUPERFRAME_DROP);
+  FieldTrialParameter<int> max_consec_drop("max_consec_drop",
+                                           std::numeric_limits<int>::max());
+  ParseFieldTrial({&enabled, &layer_drop_mode, &max_consec_drop},
+                  trials.Lookup("WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig"));
+  SvcFrameDropConfig config;
+  config.enabled = enabled.Get();
+  config.layer_drop_mode = layer_drop_mode.Get();
+  config.max_consec_drop = max_consec_drop.Get();
+  RTC_LOG(LS_INFO) << "Libvpx VP9 encoder SVC frame drop config: "
+                   << (config.enabled ? "enabled" : "disabled")
+                   << " layer_drop_mode " << config.layer_drop_mode
+                   << " max_consec_drop " << config.max_consec_drop;
+  return config;
+}
+
 void LibvpxVp9Encoder::UpdatePerformanceFlags() {
   flat_map<int, PerformanceFlags::ParameterSet> params_by_resolution;
   if (codec_.GetVideoEncoderComplexity() ==
diff --git a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h
index 0474e7b..3ccaa5f 100644
--- a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h
+++ b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h
@@ -236,6 +236,14 @@
   bool config_changed_;
 
   const LibvpxVp9EncoderInfoSettings encoder_info_override_;
+
+  const struct SvcFrameDropConfig {
+    bool enabled;
+    int layer_drop_mode;  // SVC_LAYER_DROP_MODE
+    int max_consec_drop;
+  } svc_frame_drop_config_;
+  static SvcFrameDropConfig ParseSvcFrameDropConfig(
+      const FieldTrialsView& trials);
 };
 
 }  // namespace webrtc
diff --git a/modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc b/modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc
index 993fd24..50e9cf2 100644
--- a/modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc
+++ b/modules/video_coding/codecs/vp9/test/vp9_impl_unittest.cc
@@ -2459,4 +2459,113 @@
   }
 }
 
+struct SvcFrameDropConfigTestParameters {
+  bool flexible_mode;
+  absl::optional<ScalabilityMode> scalability_mode;
+  std::string field_trial;
+  int expected_framedrop_mode;
+  int expected_max_consec_drop;
+};
+
+class TestVp9ImplSvcFrameDropConfig
+    : public ::testing::TestWithParam<SvcFrameDropConfigTestParameters> {};
+
+TEST_P(TestVp9ImplSvcFrameDropConfig, SvcFrameDropConfig) {
+  SvcFrameDropConfigTestParameters test_params = GetParam();
+  auto* const vpx = new NiceMock<MockLibvpxInterface>();
+  LibvpxVp9Encoder encoder(
+      cricket::CreateVideoCodec(cricket::kVp9CodecName),
+      absl::WrapUnique<LibvpxInterface>(vpx),
+      test::ExplicitKeyValueConfig(test_params.field_trial));
+
+  vpx_image_t img;
+  ON_CALL(*vpx, img_wrap).WillByDefault(GetWrapImageFunction(&img));
+
+  EXPECT_CALL(*vpx,
+              codec_control(_, VP9E_SET_SVC_FRAME_DROP_LAYER,
+                            SafeMatcherCast<vpx_svc_frame_drop_t*>(AllOf(
+                                Field(&vpx_svc_frame_drop_t::framedrop_mode,
+                                      test_params.expected_framedrop_mode),
+                                Field(&vpx_svc_frame_drop_t::max_consec_drop,
+                                      test_params.expected_max_consec_drop)))));
+
+  VideoCodec settings = DefaultCodecSettings();
+  settings.VP9()->flexibleMode = test_params.flexible_mode;
+  if (test_params.scalability_mode.has_value()) {
+    settings.SetScalabilityMode(*test_params.scalability_mode);
+  }
+  settings.VP9()->numberOfSpatialLayers =
+      3;  // to execute SVC code paths even when scalability_mode is not set.
+
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder.InitEncode(&settings, kSettings));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    TestVp9ImplSvcFrameDropConfig,
+    ::testing::Values(
+        // Flexible mode is disabled. Layer drop is not allowed. Ignore
+        // layer_drop_mode from field trial.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = false,
+            .scalability_mode = ScalabilityMode::kL3T3_KEY,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Enabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = FULL_SUPERFRAME_DROP,
+            .expected_max_consec_drop = 7},
+        // Flexible mode is enabled but the field trial is not set. Use default
+        // settings.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = true,
+            .scalability_mode = ScalabilityMode::kL3T3_KEY,
+            .field_trial = "",
+            .expected_framedrop_mode = FULL_SUPERFRAME_DROP,
+            .expected_max_consec_drop = std::numeric_limits<int>::max()},
+        // Flexible mode is enabled but the field trial is disabled. Use default
+        // settings.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = true,
+            .scalability_mode = ScalabilityMode::kL3T3_KEY,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Disabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = FULL_SUPERFRAME_DROP,
+            .expected_max_consec_drop = std::numeric_limits<int>::max()},
+        // Flexible mode is enabled, layer drop is enabled, KSVC. Apply config
+        // from field trial.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = true,
+            .scalability_mode = ScalabilityMode::kL3T3_KEY,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Enabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = LAYER_DROP,
+            .expected_max_consec_drop = 7},
+        // Flexible mode is enabled, layer drop is enabled, simulcast. Apply
+        // config from field trial.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = true,
+            .scalability_mode = ScalabilityMode::kS3T3,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Enabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = LAYER_DROP,
+            .expected_max_consec_drop = 7},
+        // Flexible mode is enabled, layer drop is enabled, full SVC. Apply
+        // config from field trial.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = false,
+            .scalability_mode = ScalabilityMode::kL3T3,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Enabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = FULL_SUPERFRAME_DROP,
+            .expected_max_consec_drop = 7},
+        // Flexible mode is enabled, layer-drop is enabled, scalability mode is
+        // not set (i.e., SVC controller is not enabled). Ignore layer_drop_mode
+        // from field trial.
+        SvcFrameDropConfigTestParameters{
+            .flexible_mode = true,
+            .scalability_mode = absl::nullopt,
+            .field_trial = "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig/"
+                           "Enabled,layer_drop_mode:1,max_consec_drop:7/",
+            .expected_framedrop_mode = FULL_SUPERFRAME_DROP,
+            .expected_max_consec_drop = 7}));
+
 }  // namespace webrtc