commit 7ad5273dc2ef0a53975b50331ba3c7bbbcc1cf55
parent 8a2da2dd0f0d651e046d47e7e9d0c889c13e9f96
Author: Dan Baker <dbaker@mozilla.com>
Date: Mon, 1 Dec 2025 18:30:55 -0700
Bug 2000941 - Vendor libwebrtc from 6afeb1ce2c
Upstream commit: https://webrtc.googlesource.com/src/+/6afeb1ce2c87539c82cc7f4900e1ae12934bb6f8
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}
Diffstat:
6 files changed, 108 insertions(+), 38 deletions(-)
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor
@@ -1,4 +1,4 @@
# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
-libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T01:27:35.135605+00:00.
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T01:30:37.866900+00:00.
# base of lastest vendoring
-9d4cf430f2
+6afeb1ce2c
diff --git a/third_party/libwebrtc/api/video/video_frame.cc b/third_party/libwebrtc/api/video/video_frame.cc
@@ -174,7 +174,7 @@ VideoFrame VideoFrame::Builder::build() {
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 @@ VideoFrame::Builder& VideoFrame::Builder::set_packet_infos(
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 @@ VideoFrame::VideoFrame(const scoped_refptr<VideoFrameBuffer>& buffer,
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 @@ VideoFrame::VideoFrame(const scoped_refptr<VideoFrameBuffer>& buffer,
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/third_party/libwebrtc/api/video/video_frame.h b/third_party/libwebrtc/api/video/video_frame.h
@@ -124,6 +124,7 @@ class RTC_EXPORT VideoFrame {
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 @@ class RTC_EXPORT VideoFrame {
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 @@ class RTC_EXPORT VideoFrame {
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 @@ class RTC_EXPORT VideoFrame {
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 @@ class RTC_EXPORT VideoFrame {
// 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/third_party/libwebrtc/common_video/video_frame_unittest.cc b/third_party/libwebrtc/common_video/video_frame_unittest.cc
@@ -643,4 +643,32 @@ TEST(TestUpdateRectScale, CropAndScaleByHalf) {
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/third_party/libwebrtc/video/frame_cadence_adapter.cc b/third_party/libwebrtc/video/frame_cadence_adapter.cc
@@ -691,6 +691,7 @@ void ZeroHertzAdapterMode::ProcessRepeatedFrameOnDelayedCadence(int frame_id) {
// 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/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
@@ -338,6 +338,45 @@ TEST(FrameCadenceAdapterTest, RepeatsFramesDelayed) {
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