ZeroHertzAdapterMode: handle pending key frame requests.

The frame cadence adapter ignores key frame processing that happens
before the point where zero-hertz mode is activated, which leads to
no refresh frame requests if the key frame request comes too early.

Fix this to register a pending refresh frame request that gets
serviced when zero-hertz mode is activated. The CL changes the
FrameCadenceAdapterInterface::ProcessKeyFrameRequest from returning
whether to request a refresh frame into the frame cadence adapter
actively doing so itself via a new Callback::RequestRefreshFrame
API.

go/rtc-0hz-present

Bug: chromium:1255737
Change-Id: I53c2dbf6468e883eb2a2e81498e7134b1b35c336
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/242963
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35598}
diff --git a/video/frame_cadence_adapter.cc b/video/frame_cadence_adapter.cc
index b8af3f0..254ccd6 100644
--- a/video/frame_cadence_adapter.cc
+++ b/video/frame_cadence_adapter.cc
@@ -119,8 +119,9 @@
   absl::optional<uint32_t> GetInputFrameRateFps() override;
   void UpdateFrameRate() override {}
 
-  // Returns true if a refresh frame should be requested from the video source.
-  ABSL_MUST_USE_RESULT bool ProcessKeyFrameRequest();
+  // Conditionally requests a refresh frame via
+  // Callback::RequestRefreshFrame.
+  void ProcessKeyFrameRequest();
 
  private:
   // The tracking state of each spatial layer. Used for determining when to
@@ -220,7 +221,7 @@
   void UpdateLayerQualityConvergence(int spatial_index,
                                      bool quality_converged) override;
   void UpdateLayerStatus(int spatial_index, bool enabled) override;
-  ABSL_MUST_USE_RESULT bool ProcessKeyFrameRequest() override;
+  void ProcessKeyFrameRequest() override;
 
   // VideoFrameSink overrides.
   void OnFrame(const VideoFrame& frame) override;
@@ -278,6 +279,9 @@
   // `queue_`.
   std::atomic<int> frames_scheduled_for_processing_{0};
 
+  // Whether to ask for a refresh frame on activation of zero-hertz mode.
+  bool should_request_refresh_frame_ RTC_GUARDED_BY(queue_) = false;
+
   ScopedTaskSafetyDetached safety_;
 };
 
@@ -368,7 +372,7 @@
   return max_fps_;
 }
 
-bool ZeroHertzAdapterMode::ProcessKeyFrameRequest() {
+void ZeroHertzAdapterMode::ProcessKeyFrameRequest() {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
 
   // If no frame was ever passed to us, request a refresh frame from the source.
@@ -376,7 +380,8 @@
     RTC_LOG(LS_INFO)
         << __func__ << " this " << this
         << " requesting refresh frame due to no frames received yet.";
-    return true;
+    callback_->RequestRefreshFrame();
+    return;
   }
 
   // The next frame encoded will be a key frame. Reset quality convergence so we
@@ -390,7 +395,7 @@
     RTC_LOG(LS_INFO) << __func__ << " this " << this
                      << " not requesting refresh frame because of recently "
                         "incoming frame or short repeating.";
-    return false;
+    return;
   }
 
   // If the repeat is scheduled within a short (i.e. frame_delay_) interval, we
@@ -402,7 +407,7 @@
     RTC_LOG(LS_INFO) << __func__ << " this " << this
                      << " not requesting refresh frame because of soon "
                         "happening idle repeat";
-    return false;
+    return;
   }
 
   // Cancel the current repeat and reschedule a short repeat now. No need for a
@@ -411,7 +416,7 @@
                    << " not requesting refresh frame and scheduling a short "
                       "repeat due to key frame request";
   ScheduleRepeat(++current_frame_id_, /*idle_repeat=*/false);
-  return false;
+  return;
 }
 
 // RTC_RUN_ON(&sequence_checker_)
@@ -590,12 +595,12 @@
     zero_hertz_adapter_->UpdateLayerStatus(spatial_index, enabled);
 }
 
-bool FrameCadenceAdapterImpl::ProcessKeyFrameRequest() {
+void FrameCadenceAdapterImpl::ProcessKeyFrameRequest() {
   RTC_DCHECK_RUN_ON(queue_);
   if (zero_hertz_adapter_)
-    return zero_hertz_adapter_->ProcessKeyFrameRequest();
-  // We depend on min_fps not being zero for passthrough.
-  return false;
+    zero_hertz_adapter_->ProcessKeyFrameRequest();
+  else
+    should_request_refresh_frame_ = true;
 }
 
 void FrameCadenceAdapterImpl::OnFrame(const VideoFrame& frame) {
@@ -658,6 +663,12 @@
       zero_hertz_adapter_.emplace(queue_, clock_, callback_,
                                   source_constraints_->max_fps.value());
       RTC_LOG(LS_INFO) << "Zero hertz mode activated.";
+
+      if (should_request_refresh_frame_) {
+        // Ensure we get a first frame to work with.
+        should_request_refresh_frame_ = false;
+        callback_->RequestRefreshFrame();
+      }
     }
     zero_hertz_adapter_->ReconfigureParameters(zero_hertz_params_.value());
     current_adapter_mode_ = &zero_hertz_adapter_.value();
diff --git a/video/frame_cadence_adapter.h b/video/frame_cadence_adapter.h
index c805400..a881c61 100644
--- a/video/frame_cadence_adapter.h
+++ b/video/frame_cadence_adapter.h
@@ -68,6 +68,9 @@
 
     // Called when the source has discarded a frame.
     virtual void OnDiscardedFrame() = 0;
+
+    // Called when the adapter needs the source to send a refresh frame.
+    virtual void RequestRefreshFrame() = 0;
   };
 
   // Factory function creating a production instance. Deletion of the returned
@@ -104,9 +107,9 @@
   // Updates spatial layer enabled status.
   virtual void UpdateLayerStatus(int spatial_index, bool enabled) = 0;
 
-  // Returns true if a key frame request should cause generation of a new frame
-  // from the source.
-  virtual ABSL_MUST_USE_RESULT bool ProcessKeyFrameRequest() = 0;
+  // Conditionally requests a refresh frame via
+  // Callback::RequestRefreshFrame.
+  virtual void ProcessKeyFrameRequest() = 0;
 };
 
 }  // namespace webrtc
diff --git a/video/frame_cadence_adapter_unittest.cc b/video/frame_cadence_adapter_unittest.cc
index 320019c..b2ddfd8 100644
--- a/video/frame_cadence_adapter_unittest.cc
+++ b/video/frame_cadence_adapter_unittest.cc
@@ -67,6 +67,7 @@
  public:
   MOCK_METHOD(void, OnFrame, (Timestamp, int, const VideoFrame&), (override));
   MOCK_METHOD(void, OnDiscardedFrame, (), (override));
+  MOCK_METHOD(void, RequestRefreshFrame, (), (override));
 };
 
 class ZeroHertzFieldTrialDisabler : public test::ScopedFieldTrials {
@@ -366,7 +367,8 @@
       FrameCadenceAdapterInterface::ZeroHertzModeParams{});
   adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 10});
   time_controller.AdvanceTime(TimeDelta::Zero());
-  EXPECT_TRUE(adapter->ProcessKeyFrameRequest());
+  EXPECT_CALL(callback, RequestRefreshFrame);
+  adapter->ProcessKeyFrameRequest();
 }
 
 TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestShortlyAfterFrame) {
@@ -380,7 +382,8 @@
   adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 10});
   adapter->OnFrame(CreateFrame());
   time_controller.AdvanceTime(TimeDelta::Zero());
-  EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
+  EXPECT_CALL(callback, RequestRefreshFrame).Times(0);
+  adapter->ProcessKeyFrameRequest();
 }
 
 class FrameCadenceAdapterSimulcastLayersParamTest
@@ -431,7 +434,8 @@
   // Since we're unconverged we assume the process continues.
   adapter_->OnFrame(CreateFrame());
   time_controller_.AdvanceTime(2 * kMinFrameDelay);
-  EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
+  EXPECT_CALL(callback_, RequestRefreshFrame).Times(0);
+  adapter_->ProcessKeyFrameRequest();
 
   // Expect short repeating as ususal.
   EXPECT_CALL(callback_, OnFrame).Times(8);
@@ -461,7 +465,8 @@
   // We process the key frame request kMinFrameDelay before the first idle
   // repeat should happen. The resulting repeats should happen spaced by
   // kMinFrameDelay before we get new convergence info.
-  EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
+  EXPECT_CALL(callback_, RequestRefreshFrame).Times(0);
+  adapter_->ProcessKeyFrameRequest();
   EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz);
   time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay);
 }
@@ -488,7 +493,8 @@
   // We process the key frame request (kMaxFpsHz - 1) * kMinFrameDelay before
   // the first idle repeat should happen. The resulting repeats should happen
   // spaced kMinFrameDelay before we get new convergence info.
-  EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
+  EXPECT_CALL(callback_, RequestRefreshFrame).Times(0);
+  adapter_->ProcessKeyFrameRequest();
   EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz);
   time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay);
 }
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 3baf10a..40c46ae 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -1817,6 +1817,13 @@
   }
 }
 
+void VideoStreamEncoder::RequestRefreshFrame() {
+  worker_queue_->PostTask(ToQueuedTask(task_safety_, [this] {
+    RTC_DCHECK_RUN_ON(worker_queue_);
+    video_source_sink_controller_.RequestRefreshFrame();
+  }));
+}
+
 void VideoStreamEncoder::SendKeyFrame() {
   if (!encoder_queue_.IsCurrent()) {
     encoder_queue_.PostTask([this] { SendKeyFrame(); });
@@ -1826,17 +1833,9 @@
   TRACE_EVENT0("webrtc", "OnKeyFrameRequest");
   RTC_DCHECK(!next_frame_types_.empty());
 
-  if (frame_cadence_adapter_) {
-    if (frame_cadence_adapter_->ProcessKeyFrameRequest()) {
-      RTC_DLOG(LS_INFO) << __func__ << " RequestRefreshFrame().";
-      worker_queue_->PostTask(ToQueuedTask(task_safety_, [this] {
-        RTC_DCHECK_RUN_ON(worker_queue_);
-        video_source_sink_controller_.RequestRefreshFrame();
-      }));
-    } else {
-      RTC_DLOG(LS_INFO) << __func__ << " No RequestRefreshFrame().";
-    }
-  }
+  if (frame_cadence_adapter_)
+    frame_cadence_adapter_->ProcessKeyFrameRequest();
+
   if (!encoder_) {
     RTC_DLOG(LS_INFO) << __func__ << " no encoder.";
     return;  // Shutting down, or not configured yet.
diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h
index cd181fc..da4747f 100644
--- a/video/video_stream_encoder.h
+++ b/video/video_stream_encoder.h
@@ -155,6 +155,9 @@
     void OnDiscardedFrame() override {
       video_stream_encoder_.OnDiscardedFrame();
     }
+    void RequestRefreshFrame() override {
+      video_stream_encoder_.RequestRefreshFrame();
+    }
 
    private:
     VideoStreamEncoder& video_stream_encoder_;
@@ -199,6 +202,7 @@
                int frames_scheduled_for_processing,
                const VideoFrame& video_frame);
   void OnDiscardedFrame();
+  void RequestRefreshFrame();
 
   void MaybeEncodeVideoFrame(const VideoFrame& frame,
                              int64_t time_when_posted_in_ms);
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index c1d89d7..d2b2f3b 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -19,6 +19,7 @@
 #include "absl/memory/memory.h"
 #include "api/rtp_parameters.h"
 #include "api/task_queue/default_task_queue_factory.h"
+#include "api/task_queue/task_queue_base.h"
 #include "api/task_queue/task_queue_factory.h"
 #include "api/test/mock_fec_controller_override.h"
 #include "api/test/mock_video_encoder.h"
@@ -118,18 +119,21 @@
     0x02, 0x47, 0x08, 0x85, 0x85, 0x88, 0x85, 0x84, 0x88, 0x0c,
     0x82, 0x00, 0x0c, 0x0d, 0x60, 0x00, 0xfe, 0xfc, 0x5c, 0xd0};
 
+VideoFrame CreateSimpleNV12Frame() {
+  return VideoFrame::Builder()
+      .set_video_frame_buffer(rtc::make_ref_counted<NV12Buffer>(
+          /*width=*/16, /*height=*/16))
+      .build();
+}
+
 void PassAFrame(
     TaskQueueBase* encoder_queue,
     FrameCadenceAdapterInterface::Callback* video_stream_encoder_callback,
     int64_t ntp_time_ms) {
   encoder_queue->PostTask(
       ToQueuedTask([video_stream_encoder_callback, ntp_time_ms] {
-        video_stream_encoder_callback->OnFrame(
-            Timestamp::Millis(ntp_time_ms), 1,
-            VideoFrame::Builder()
-                .set_video_frame_buffer(rtc::make_ref_counted<NV12Buffer>(
-                    /*width=*/16, /*height=*/16))
-                .build());
+        video_stream_encoder_callback->OnFrame(Timestamp::Millis(ntp_time_ms),
+                                               1, CreateSimpleNV12Frame());
       }));
 }
 
@@ -672,14 +676,9 @@
         bitrate_allocator_factory_.get();
   }
 
-  std::unique_ptr<AdaptedVideoStreamEncoder> Create(
+  std::unique_ptr<AdaptedVideoStreamEncoder> CreateWithEncoderQueue(
       std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter,
-      TaskQueueBase** encoder_queue_ptr = nullptr) {
-    auto encoder_queue =
-        time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
-            "EncoderQueue", TaskQueueFactory::Priority::NORMAL);
-    if (encoder_queue_ptr)
-      *encoder_queue_ptr = encoder_queue.get();
+      std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue) {
     auto result = std::make_unique<AdaptedVideoStreamEncoder>(
         time_controller_.GetClock(),
         /*number_of_cores=*/1,
@@ -692,9 +691,25 @@
     return result;
   }
 
+  std::unique_ptr<AdaptedVideoStreamEncoder> Create(
+      std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter,
+      TaskQueueBase** encoder_queue_ptr = nullptr) {
+    auto encoder_queue =
+        time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
+            "EncoderQueue", TaskQueueFactory::Priority::NORMAL);
+    if (encoder_queue_ptr)
+      *encoder_queue_ptr = encoder_queue.get();
+    return CreateWithEncoderQueue(std::move(zero_hertz_adapter),
+                                  std::move(encoder_queue));
+  }
+
   void DepleteTaskQueues() { time_controller_.AdvanceTime(TimeDelta::Zero()); }
   MockFakeEncoder& GetMockFakeEncoder() { return mock_fake_encoder_; }
 
+  GlobalSimulatedTimeController* GetTimeController() {
+    return &time_controller_;
+  }
+
  private:
   class NullEncoderSink : public VideoStreamEncoderInterface::EncoderSink {
    public:
@@ -750,7 +765,7 @@
               UpdateLayerStatus,
               (int spatial_index, bool enabled),
               (override));
-  MOCK_METHOD(bool, ProcessKeyFrameRequest, (), (override));
+  MOCK_METHOD(void, ProcessKeyFrameRequest, (), (override));
 };
 
 class MockEncoderSelector
@@ -9002,17 +9017,54 @@
   // Ensure the encoder is set up.
   factory.DepleteTaskQueues();
 
-  EXPECT_CALL(*adapter_ptr, ProcessKeyFrameRequest).WillOnce(Return(true));
+  EXPECT_CALL(*adapter_ptr, ProcessKeyFrameRequest)
+      .WillOnce(Invoke([video_stream_encoder_callback] {
+        video_stream_encoder_callback->RequestRefreshFrame();
+      }));
   EXPECT_CALL(mock_source, RequestRefreshFrame);
   video_stream_encoder->SendKeyFrame();
   factory.DepleteTaskQueues();
   Mock::VerifyAndClearExpectations(adapter_ptr);
   Mock::VerifyAndClearExpectations(&mock_source);
 
-  EXPECT_CALL(*adapter_ptr, ProcessKeyFrameRequest).WillOnce(Return(false));
+  EXPECT_CALL(*adapter_ptr, ProcessKeyFrameRequest);
   EXPECT_CALL(mock_source, RequestRefreshFrame).Times(0);
   video_stream_encoder->SendKeyFrame();
   factory.DepleteTaskQueues();
 }
 
+TEST(VideoStreamEncoderFrameCadenceTest,
+     RequestsRefreshFrameForEarlyZeroHertzKeyFrameRequest) {
+  SimpleVideoStreamEncoderFactory factory;
+  auto encoder_queue =
+      factory.GetTimeController()->GetTaskQueueFactory()->CreateTaskQueue(
+          "EncoderQueue", TaskQueueFactory::Priority::NORMAL);
+
+  // Enables zero-hertz mode.
+  test::ScopedFieldTrials field_trials("WebRTC-ZeroHertzScreenshare/Enabled/");
+  auto adapter = FrameCadenceAdapterInterface::Create(
+      factory.GetTimeController()->GetClock(), encoder_queue.get());
+  FrameCadenceAdapterInterface* adapter_ptr = adapter.get();
+
+  MockVideoSourceInterface mock_source;
+  auto video_stream_encoder = factory.CreateWithEncoderQueue(
+      std::move(adapter), std::move(encoder_queue));
+
+  video_stream_encoder->SetSource(
+      &mock_source, webrtc::DegradationPreference::MAINTAIN_FRAMERATE);
+  VideoEncoderConfig config;
+  config.content_type = VideoEncoderConfig::ContentType::kScreen;
+  test::FillEncoderConfiguration(kVideoCodecVP8, 1, &config);
+  video_stream_encoder->ConfigureEncoder(std::move(config), 0);
+
+  // Eventually expect a refresh frame request when requesting a key frame
+  // before initializing zero-hertz mode. This can happen in reality because the
+  // threads invoking key frame requests and constraints setup aren't
+  // synchronized.
+  EXPECT_CALL(mock_source, RequestRefreshFrame);
+  video_stream_encoder->SendKeyFrame();
+  adapter_ptr->OnConstraintsChanged(VideoTrackSourceConstraints{0, 30});
+  factory.DepleteTaskQueues();
+}
+
 }  // namespace webrtc