FrameCadenceAdapter: survive layer updates for unconfigured layers.

The frame cadence adapter would previously crash on encountering
unconfigured layer updates. Fix this to silently ignore such updates.

Fixed: webrtc:14417
Change-Id: I524aa196f6aed10ffb8cd4ddcb4b053c71dfabba
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/273325
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37980}
diff --git a/video/frame_cadence_adapter.cc b/video/frame_cadence_adapter.cc
index c24a82c..efffa96 100644
--- a/video/frame_cadence_adapter.cc
+++ b/video/frame_cadence_adapter.cc
@@ -107,10 +107,11 @@
       const FrameCadenceAdapterInterface::ZeroHertzModeParams& params);
 
   // Updates spatial layer quality convergence status.
-  void UpdateLayerQualityConvergence(int spatial_index, bool quality_converged);
+  void UpdateLayerQualityConvergence(size_t spatial_index,
+                                     bool quality_converged);
 
   // Updates spatial layer enabled status.
-  void UpdateLayerStatus(int spatial_index, bool enabled);
+  void UpdateLayerStatus(size_t spatial_index, bool enabled);
 
   // Adapter overrides.
   void OnFrame(Timestamp post_time,
@@ -232,9 +233,9 @@
       absl::optional<ZeroHertzModeParams> params) override;
   absl::optional<uint32_t> GetInputFrameRateFps() override;
   void UpdateFrameRate() override;
-  void UpdateLayerQualityConvergence(int spatial_index,
+  void UpdateLayerQualityConvergence(size_t spatial_index,
                                      bool quality_converged) override;
-  void UpdateLayerStatus(int spatial_index, bool enabled) override;
+  void UpdateLayerStatus(size_t spatial_index, bool enabled) override;
   void ProcessKeyFrameRequest() override;
 
   // VideoFrameSink overrides.
@@ -323,19 +324,22 @@
 }
 
 void ZeroHertzAdapterMode::UpdateLayerQualityConvergence(
-    int spatial_index,
+    size_t spatial_index,
     bool quality_converged) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
-  RTC_DCHECK_LT(spatial_index, layer_trackers_.size());
   RTC_LOG(LS_INFO) << __func__ << " this " << this << " layer " << spatial_index
                    << " quality has converged: " << quality_converged;
+  if (spatial_index >= layer_trackers_.size())
+    return;
   if (layer_trackers_[spatial_index].quality_converged.has_value())
     layer_trackers_[spatial_index].quality_converged = quality_converged;
 }
 
-void ZeroHertzAdapterMode::UpdateLayerStatus(int spatial_index, bool enabled) {
+void ZeroHertzAdapterMode::UpdateLayerStatus(size_t spatial_index,
+                                             bool enabled) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
-  RTC_DCHECK_LT(spatial_index, layer_trackers_.size());
+  if (spatial_index >= layer_trackers_.size())
+    return;
   if (enabled) {
     if (!layer_trackers_[spatial_index].quality_converged.has_value()) {
       // Assume quality has not converged until hearing otherwise.
@@ -624,14 +628,14 @@
 }
 
 void FrameCadenceAdapterImpl::UpdateLayerQualityConvergence(
-    int spatial_index,
+    size_t spatial_index,
     bool quality_converged) {
   if (zero_hertz_adapter_.has_value())
     zero_hertz_adapter_->UpdateLayerQualityConvergence(spatial_index,
                                                        quality_converged);
 }
 
-void FrameCadenceAdapterImpl::UpdateLayerStatus(int spatial_index,
+void FrameCadenceAdapterImpl::UpdateLayerStatus(size_t spatial_index,
                                                 bool enabled) {
   if (zero_hertz_adapter_.has_value())
     zero_hertz_adapter_->UpdateLayerStatus(spatial_index, enabled);
diff --git a/video/frame_cadence_adapter.h b/video/frame_cadence_adapter.h
index 149a915..d0eab7e 100644
--- a/video/frame_cadence_adapter.h
+++ b/video/frame_cadence_adapter.h
@@ -47,7 +47,7 @@
 
   struct ZeroHertzModeParams {
     // The number of simulcast layers used in this configuration.
-    int num_simulcast_layers = 0;
+    size_t num_simulcast_layers = 0;
   };
 
   // Callback interface used to inform instance owners.
@@ -106,11 +106,11 @@
   // Updates quality convergence status for an enabled spatial layer.
   // Convergence means QP has dropped to a low-enough level to warrant ceasing
   // to send identical frames at high frequency.
-  virtual void UpdateLayerQualityConvergence(int spatial_index,
+  virtual void UpdateLayerQualityConvergence(size_t spatial_index,
                                              bool converged) = 0;
 
   // Updates spatial layer enabled status.
-  virtual void UpdateLayerStatus(int spatial_index, bool enabled) = 0;
+  virtual void UpdateLayerStatus(size_t spatial_index, bool enabled) = 0;
 
   // Conditionally requests a refresh frame via
   // Callback::RequestRefreshFrame.
diff --git a/video/frame_cadence_adapter_unittest.cc b/video/frame_cadence_adapter_unittest.cc
index 0824ac3..afc675f 100644
--- a/video/frame_cadence_adapter_unittest.cc
+++ b/video/frame_cadence_adapter_unittest.cc
@@ -499,6 +499,24 @@
   Mock::VerifyAndClearExpectations(&callback);
 }
 
+TEST(FrameCadenceAdapterTest, AcceptsUnconfiguredLayerFeedback) {
+  // This is a regression test for bugs.webrtc.org/14417.
+  ZeroHertzFieldTrialEnabler enabler;
+  MockCallback callback;
+  GlobalSimulatedTimeController time_controller(Timestamp::Zero());
+  auto adapter = CreateAdapter(enabler, time_controller.GetClock());
+  adapter->Initialize(&callback);
+  adapter->SetZeroHertzModeEnabled(
+      FrameCadenceAdapterInterface::ZeroHertzModeParams{.num_simulcast_layers =
+                                                            1});
+  constexpr int kMaxFps = 10;
+  adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps});
+  time_controller.AdvanceTime(TimeDelta::Zero());
+
+  adapter->UpdateLayerQualityConvergence(2, false);
+  adapter->UpdateLayerStatus(2, false);
+}
+
 class FrameCadenceAdapterSimulcastLayersParamTest
     : public ::testing::TestWithParam<int> {
  public:
@@ -514,7 +532,7 @@
     time_controller_.AdvanceTime(TimeDelta::Zero());
     adapter_->SetZeroHertzModeEnabled(
         FrameCadenceAdapterInterface::ZeroHertzModeParams{});
-    const int num_spatial_layers = GetParam();
+    const size_t num_spatial_layers = GetParam();
     adapter_->SetZeroHertzModeEnabled(
         FrameCadenceAdapterInterface::ZeroHertzModeParams{num_spatial_layers});
   }
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index 4400a27..3a5b03d 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -777,11 +777,11 @@
   MOCK_METHOD(void, UpdateFrameRate, (), (override));
   MOCK_METHOD(void,
               UpdateLayerQualityConvergence,
-              (int spatial_index, bool converged),
+              (size_t spatial_index, bool converged),
               (override));
   MOCK_METHOD(void,
               UpdateLayerStatus,
-              (int spatial_index, bool enabled),
+              (size_t spatial_index, bool enabled),
               (override));
   MOCK_METHOD(void, ProcessKeyFrameRequest, (), (override));
 };
@@ -9174,7 +9174,7 @@
   EXPECT_CALL(*adapter_ptr, SetZeroHertzModeEnabled(Optional(Field(
                                 &FrameCadenceAdapterInterface::
                                     ZeroHertzModeParams::num_simulcast_layers,
-                                Eq(0)))));
+                                Eq(0u)))));
   VideoEncoderConfig config;
   test::FillEncoderConfiguration(kVideoCodecVP8, 1, &config);
   config.content_type = VideoEncoderConfig::ContentType::kScreen;
@@ -9186,7 +9186,7 @@
   EXPECT_CALL(*adapter_ptr, SetZeroHertzModeEnabled(Optional(Field(
                                 &FrameCadenceAdapterInterface::
                                     ZeroHertzModeParams::num_simulcast_layers,
-                                Gt(0)))));
+                                Gt(0u)))));
   PassAFrame(encoder_queue, video_stream_encoder_callback, /*ntp_time_ms=*/1);
   factory.DepleteTaskQueues();
   Mock::VerifyAndClearExpectations(adapter_ptr);