Add is_repeat_frame flag to VideoFrame

This flag indicates if a VideoFrame is a copy of a previous frame.
It is set to true by the FrameCadenceAdapter when generating
repeated frames in zero-hertz mode.

Bug: webrtc:443906251
Change-Id: I9447c08a5b09c8d758462284546f08e54bc1b748
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/408782
Reviewed-by: Markus Handell <handellm@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45604}
diff --git a/api/video/video_frame.cc b/api/video/video_frame.cc
index 8a99177..85816ed 100644
--- a/api/video/video_frame.cc
+++ b/api/video/video_frame.cc
@@ -174,7 +174,7 @@
   return VideoFrame(id_, video_frame_buffer_, timestamp_us_,
                     presentation_timestamp_, reference_time_, timestamp_rtp_,
                     ntp_time_ms_, rotation_, color_space_, render_parameters_,
-                    update_rect_, packet_infos_);
+                    update_rect_, packet_infos_, is_repeat_frame_);
 }
 
 VideoFrame::Builder& VideoFrame::Builder::set_video_frame_buffer(
@@ -264,6 +264,12 @@
   return *this;
 }
 
+VideoFrame::Builder& VideoFrame::Builder::set_is_repeat_frame(
+    bool is_repeat_frame) {
+  is_repeat_frame_ = is_repeat_frame;
+  return *this;
+}
+
 VideoFrame::VideoFrame(const scoped_refptr<VideoFrameBuffer>& buffer,
                        VideoRotation rotation,
                        int64_t timestamp_us)
@@ -271,7 +277,8 @@
       timestamp_rtp_(0),
       ntp_time_ms_(0),
       timestamp_us_(timestamp_us),
-      rotation_(rotation) {}
+      rotation_(rotation),
+      is_repeat_frame_(false) {}
 
 VideoFrame::VideoFrame(const scoped_refptr<VideoFrameBuffer>& buffer,
                        uint32_t timestamp_rtp,
@@ -281,40 +288,9 @@
       timestamp_rtp_(timestamp_rtp),
       ntp_time_ms_(0),
       timestamp_us_(render_time_ms * kNumMicrosecsPerMillisec),
-      rotation_(rotation) {
-  RTC_DCHECK(buffer);
-}
-
-VideoFrame::VideoFrame(uint16_t id,
-                       const scoped_refptr<VideoFrameBuffer>& buffer,
-                       int64_t timestamp_us,
-                       const std::optional<Timestamp>& presentation_timestamp,
-                       const std::optional<Timestamp>& reference_time,
-                       uint32_t timestamp_rtp,
-                       int64_t ntp_time_ms,
-                       VideoRotation rotation,
-                       const std::optional<ColorSpace>& color_space,
-                       const RenderParameters& render_parameters,
-                       const std::optional<UpdateRect>& update_rect,
-                       RtpPacketInfos packet_infos)
-    : id_(id),
-      video_frame_buffer_(buffer),
-      timestamp_rtp_(timestamp_rtp),
-      ntp_time_ms_(ntp_time_ms),
-      timestamp_us_(timestamp_us),
-      presentation_timestamp_(presentation_timestamp),
-      reference_time_(reference_time),
       rotation_(rotation),
-      color_space_(color_space),
-      render_parameters_(render_parameters),
-      update_rect_(update_rect),
-      packet_infos_(std::move(packet_infos)) {
-  if (update_rect_) {
-    RTC_DCHECK_GE(update_rect_->offset_x, 0);
-    RTC_DCHECK_GE(update_rect_->offset_y, 0);
-    RTC_DCHECK_LE(update_rect_->offset_x + update_rect_->width, width());
-    RTC_DCHECK_LE(update_rect_->offset_y + update_rect_->height, height());
-  }
+      is_repeat_frame_(false) {
+  RTC_DCHECK(buffer);
 }
 
 VideoFrame::~VideoFrame() = default;
diff --git a/api/video/video_frame.h b/api/video/video_frame.h
index e39d06f..395a68d 100644
--- a/api/video/video_frame.h
+++ b/api/video/video_frame.h
@@ -124,6 +124,7 @@
     Builder& set_id(uint16_t id);
     Builder& set_update_rect(const std::optional<UpdateRect>& update_rect);
     Builder& set_packet_infos(RtpPacketInfos packet_infos);
+    Builder& set_is_repeat_frame(bool is_repeat_frame);
 
    private:
     uint16_t id_ = kNotSetId;
@@ -138,6 +139,7 @@
     RenderParameters render_parameters_;
     std::optional<UpdateRect> update_rect_;
     RtpPacketInfos packet_infos_;
+    bool is_repeat_frame_ = false;
   };
 
   // To be deprecated. Migrate all use to Builder.
@@ -283,6 +285,11 @@
     processing_time_ = processing_time;
   }
 
+  bool is_repeat_frame() const { return is_repeat_frame_; }
+  void set_is_repeat_frame(bool is_repeat_frame) {
+    is_repeat_frame_ = is_repeat_frame;
+  }
+
  private:
   VideoFrame(uint16_t id,
              const scoped_refptr<VideoFrameBuffer>& buffer,
@@ -295,7 +302,21 @@
              const std::optional<ColorSpace>& color_space,
              const RenderParameters& render_parameters,
              const std::optional<UpdateRect>& update_rect,
-             RtpPacketInfos packet_infos);
+             RtpPacketInfos packet_infos,
+             bool is_repeat_frame)
+      : id_(id),
+        video_frame_buffer_(buffer),
+        timestamp_rtp_(timestamp_rtp),
+        ntp_time_ms_(ntp_time_ms),
+        timestamp_us_(timestamp_us),
+        presentation_timestamp_(presentation_timestamp),
+        reference_time_(reference_time),
+        rotation_(rotation),
+        color_space_(color_space),
+        render_parameters_(render_parameters),
+        update_rect_(update_rect),
+        packet_infos_(std::move(packet_infos)),
+        is_repeat_frame_(is_repeat_frame) {}
 
   uint16_t id_;
   // An opaque reference counted handle that stores the pixel data.
@@ -328,6 +349,11 @@
   // returned from the decoder.
   // Currently, not set for locally captured video frames.
   std::optional<ProcessingTime> processing_time_;
+  // Indicates if this is a "repeat frame" - i.e. a copy a previous frame,
+  // inserted in order to make a video codec converge towards a stable quality
+  // in cases where a capturer is using a variable frame rate and stops
+  // producing frames when nothing has changed.
+  bool is_repeat_frame_;
 };
 
 }  // namespace webrtc
diff --git a/common_video/video_frame_unittest.cc b/common_video/video_frame_unittest.cc
index bc7aa79..de96e6d 100644
--- a/common_video/video_frame_unittest.cc
+++ b/common_video/video_frame_unittest.cc
@@ -643,4 +643,32 @@
   EXPECT_EQ(scaled, VideoFrame::UpdateRect({42, 22, 56, 106}));
 }
 
+TEST(TestVideoFrame, IsRepeatFrame) {
+  // Defaults to false.
+  VideoFrame frame1 = VideoFrame::Builder()
+                          .set_video_frame_buffer(I420Buffer::Create(10, 10))
+                          .set_rotation(kVideoRotation_0)
+                          .set_timestamp_ms(123)
+                          .build();
+  EXPECT_FALSE(frame1.is_repeat_frame());
+
+  // Set to true.
+  VideoFrame frame2 = VideoFrame::Builder()
+                          .set_video_frame_buffer(I420Buffer::Create(10, 10))
+                          .set_rotation(kVideoRotation_0)
+                          .set_timestamp_ms(456)
+                          .set_is_repeat_frame(true)
+                          .build();
+  EXPECT_TRUE(frame2.is_repeat_frame());
+
+  // Set to false.
+  VideoFrame frame3 = VideoFrame::Builder()
+                          .set_video_frame_buffer(I420Buffer::Create(10, 10))
+                          .set_rotation(kVideoRotation_0)
+                          .set_timestamp_ms(789)
+                          .set_is_repeat_frame(false)
+                          .build();
+  EXPECT_FALSE(frame3.is_repeat_frame());
+}
+
 }  // namespace webrtc
diff --git a/video/frame_cadence_adapter.cc b/video/frame_cadence_adapter.cc
index 63feaee..a519c10 100644
--- a/video/frame_cadence_adapter.cc
+++ b/video/frame_cadence_adapter.cc
@@ -691,6 +691,7 @@
 
   // Schedule another repeat before sending the frame off which could take time.
   ScheduleRepeat(frame_id, HasQualityConverged());
+  frame.set_is_repeat_frame(true);
   SendFrameNow(std::nullopt, frame);
 }
 
diff --git a/video/frame_cadence_adapter_unittest.cc b/video/frame_cadence_adapter_unittest.cc
index a0e1ddf..9f36d10 100644
--- a/video/frame_cadence_adapter_unittest.cc
+++ b/video/frame_cadence_adapter_unittest.cc
@@ -338,6 +338,45 @@
   time_controller.AdvanceTime(TimeDelta::Seconds(1));
 }
 
+TEST(FrameCadenceAdapterTest, SetsIsRepeatFrameFlag) {
+  MockCallback callback;
+  GlobalSimulatedTimeController time_controller(Timestamp::Millis(47892223));
+  FieldTrials no_field_trials = CreateTestFieldTrials();
+  auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock());
+  adapter->Initialize(&callback);
+  adapter->SetZeroHertzModeEnabled(
+      FrameCadenceAdapterInterface::ZeroHertzModeParams{});
+  adapter->OnConstraintsChanged(
+      VideoTrackSourceConstraints{.min_fps = 0, .max_fps = 1});
+
+  // Send one frame, expect is_repeat_frame to be false.
+  auto frame = CreateFrameWithTimestamps(&time_controller);
+  adapter->OnFrame(frame);
+  EXPECT_CALL(callback, OnFrame)
+      .WillOnce(Invoke([&](Timestamp, bool, const VideoFrame& frame) {
+        EXPECT_FALSE(frame.is_repeat_frame());
+      }));
+  time_controller.AdvanceTime(TimeDelta::Seconds(1));
+  Mock::VerifyAndClearExpectations(&callback);
+
+  // Expect the repeated frame to have is_repeat_frame set to true.
+  EXPECT_CALL(callback, OnFrame)
+      .WillOnce(Invoke([&](Timestamp, bool, const VideoFrame& frame) {
+        EXPECT_TRUE(frame.is_repeat_frame());
+      }));
+  time_controller.AdvanceTime(TimeDelta::Seconds(1));
+  Mock::VerifyAndClearExpectations(&callback);
+
+  // Send a new frame, expect is_repeat_frame to be false again.
+  auto new_frame = CreateFrameWithTimestamps(&time_controller);
+  adapter->OnFrame(new_frame);
+  EXPECT_CALL(callback, OnFrame)
+      .WillOnce(Invoke([&](Timestamp, bool, const VideoFrame& frame) {
+        EXPECT_FALSE(frame.is_repeat_frame());
+      }));
+  time_controller.AdvanceTime(TimeDelta::Seconds(1));
+}
+
 TEST(FrameCadenceAdapterTest,
      RepeatsFramesWithoutTimestampsWithUnsetTimestamps) {
   // Logic in the frame cadence adapter avoids modifying frame NTP and render