commit 969a53ef2106b2a263385751000866802bd74e72
parent 43fc2cd9eaaa347e082ea1be4c112a1288800a21
Author: Dan Baker <dbaker@mozilla.com>
Date: Mon, 1 Dec 2025 23:28:40 -0700
Bug 2000941 - Vendor libwebrtc from 6db6f177d3
Upstream commit: https://webrtc.googlesource.com/src/+/6db6f177d3be614eaf0c300ef7b9803483e6542d
[AV1] Drop repeat frames on enhancement layers.
This CL introduces a field trial which allows the libaom AV1 encoder
wrapper to drop frames if the input frame is repat frame and the
scalabilit controller assigns it a non-base layer.
Bug: webrtc:445115234
Change-Id: I56692808c07251c01136b2246b5ee3bf7c7bfeda
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/409564
Commit-Queue: Erik Språng <sprang@webrtc.org>
Reviewed-by: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45712}
Diffstat:
5 files changed, 252 insertions(+), 43 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-02T06:25:17.904692+00:00.
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T06:28:26.322059+00:00.
# base of lastest vendoring
-ea14c99d67
+6db6f177d3
diff --git a/third_party/libwebrtc/experiments/field_trials.py b/third_party/libwebrtc/experiments/field_trials.py
@@ -110,6 +110,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
FieldTrial('WebRTC-JitterEstimatorConfig',
42224404,
date(2024, 4, 1)),
+ FieldTrial('WebRTC-LibaomAv1Encoder-DropRepeatFramesOnEnhancementLayers',
+ 445115234,
+ date(2026, 3, 19)),
FieldTrial('WebRTC-LibaomAv1Encoder-PostEncodeFrameDrop',
351644568,
date(2026, 1, 30)),
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn b/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn
@@ -138,6 +138,7 @@ if (rtc_include_tests) {
"../../../../api/units:time_delta",
"../../../../modules/rtp_rtcp:rtp_rtcp_format",
"../../../../test:fileutils",
+ "../../../../test:test_support",
"../../../../test:video_test_support",
"../../svc:scalability_mode_util",
"../../svc:scalability_structures",
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc
@@ -11,6 +11,7 @@
#include <cstddef>
#include <cstdint>
+#include <map>
#include <memory>
#include <numeric>
#include <optional>
@@ -182,6 +183,8 @@ class LibaomAv1Encoder final : public VideoEncoder {
FrameSampler psnr_frame_sampler_;
// TODO(webrtc:388070060): Remove after rollout.
const bool calculate_psnr_;
+ const bool drop_repeat_frames_on_enhancement_layers_;
+ std::map<int, uint32_t> last_encoded_timestamp_by_sid_;
};
int32_t VerifyCodecSettings(const VideoCodec& codec_settings) {
@@ -225,7 +228,9 @@ LibaomAv1Encoder::LibaomAv1Encoder(const Environment& env,
post_encode_frame_drop_(!env.field_trials().IsDisabled(
"WebRTC-LibaomAv1Encoder-PostEncodeFrameDrop")),
calculate_psnr_(
- env.field_trials().IsEnabled("WebRTC-Video-CalculatePsnr")) {}
+ env.field_trials().IsEnabled("WebRTC-Video-CalculatePsnr")),
+ drop_repeat_frames_on_enhancement_layers_(env.field_trials().IsEnabled(
+ "WebRTC-LibaomAv1Encoder-DropRepeatFramesOnEnhancementLayers")) {}
LibaomAv1Encoder::~LibaomAv1Encoder() {
Release();
@@ -665,6 +670,35 @@ int32_t LibaomAv1Encoder::Encode(
return WEBRTC_VIDEO_CODEC_ERROR;
}
+ if (drop_repeat_frames_on_enhancement_layers_ && frame.is_repeat_frame()) {
+ bool all_layers_droppable = !layer_frames.empty();
+ for (const auto& layer_frame : layer_frames) {
+ if (layer_frame.TemporalId() == 0) {
+ all_layers_droppable = false;
+ break;
+ }
+ if (auto it =
+ last_encoded_timestamp_by_sid_.find(layer_frame.SpatialId());
+ it != last_encoded_timestamp_by_sid_.end()) {
+ // Get the time since the last encoded frame for this spatial layer.
+ // Don't drop enhancement layer repeat frame if last encode was more
+ // than one second ago.
+ if ((frame.rtp_timestamp() - it->second) > kVideoPayloadTypeFrequency) {
+ all_layers_droppable = false;
+ break;
+ }
+ }
+ }
+
+ if (all_layers_droppable) {
+ RTC_LOG(LS_VERBOSE) << "Dropping repeat frame on enhancement layers.";
+ for (const auto& layer_frame : layer_frames) {
+ svc_controller_->OnEncodeDone(layer_frame);
+ }
+ return WEBRTC_VIDEO_CODEC_OK; // Frame dropped
+ }
+ }
+
scoped_refptr<VideoFrameBuffer> buffer = frame.video_frame_buffer();
absl::InlinedVector<VideoFrameBuffer::Type, kMaxPreferredPixelFormats>
supported_formats = {VideoFrameBuffer::Type::kI420,
@@ -820,9 +854,12 @@ int32_t LibaomAv1Encoder::Encode(
if (!encoded_images.empty()) {
encoded_images.back().second.end_of_picture = true;
}
- for (auto& [encoded_image, codec_specific_info] : encoded_images) {
- encoded_image_callback_->OnEncodedImage(encoded_image,
- &codec_specific_info);
+ for (auto& [encoded_image, codec_specifics] : encoded_images) {
+ encoded_image_callback_->OnEncodedImage(encoded_image, &codec_specifics);
+ if (encoded_image.SpatialIndex().has_value()) {
+ last_encoded_timestamp_by_sid_[*encoded_image.SpatialIndex()] =
+ frame.rtp_timestamp();
+ }
}
return WEBRTC_VIDEO_CODEC_OK;
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc
@@ -25,6 +25,7 @@
#include "api/field_trials.h"
#include "api/test/create_frame_generator.h"
#include "api/test/frame_generator_interface.h"
+#include "api/test/mock_video_encoder.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
@@ -51,10 +52,13 @@
namespace webrtc {
namespace {
+using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
+using ::testing::Property;
+using ::testing::Return;
using ::testing::SizeIs;
using ::testing::Values;
@@ -87,15 +91,38 @@ VideoEncoder::Settings DefaultEncoderSettings() {
/*number_of_cores=*/1, /*max_payload_size=*/1200);
}
-TEST(LibaomAv1EncoderTest, CanCreate) {
+class LibaomAv1EncoderTest : public ::testing::TestWithParam<bool> {
+ public:
+ LibaomAv1EncoderTest()
+ : drop_repeat_frames_on_enhancement_layers_(GetParam()) {}
+
+ protected:
+ Environment CreateTestEnvironment() {
+ auto field_trials = std::make_unique<FieldTrials>(CreateTestFieldTrials(
+ drop_repeat_frames_on_enhancement_layers_
+ ? "WebRTC-LibaomAv1Encoder-DropRepeatFramesOnEnhancementLayers/"
+ "Enabled/"
+ : "WebRTC-LibaomAv1Encoder-DropRepeatFramesOnEnhancementLayers/"
+ "Disabled/"));
+ return CreateEnvironment(std::move(field_trials));
+ }
+
+ const bool drop_repeat_frames_on_enhancement_layers_;
+};
+
+INSTANTIATE_TEST_SUITE_P(DropRepeatFrames,
+ LibaomAv1EncoderTest,
+ testing::Bool());
+
+TEST_P(LibaomAv1EncoderTest, CanCreate) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
EXPECT_TRUE(encoder);
}
-TEST(LibaomAv1EncoderTest, InitAndRelease) {
+TEST_P(LibaomAv1EncoderTest, InitAndRelease) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
ASSERT_TRUE(encoder);
VideoCodec codec_settings = DefaultCodecSettings();
EXPECT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
@@ -103,10 +130,11 @@ TEST(LibaomAv1EncoderTest, InitAndRelease) {
EXPECT_EQ(encoder->Release(), WEBRTC_VIDEO_CODEC_OK);
}
-TEST(LibaomAv1EncoderTest, NoBitrateOnTopLayerRefecltedInActiveDecodeTargets) {
+TEST_P(LibaomAv1EncoderTest,
+ NoBitrateOnTopLayerRefecltedInActiveDecodeTargets) {
// Configure encoder with 2 temporal layers.
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL1T2);
ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
@@ -130,10 +158,10 @@ TEST(LibaomAv1EncoderTest, NoBitrateOnTopLayerRefecltedInActiveDecodeTargets) {
0b01);
}
-TEST(LibaomAv1EncoderTest,
- SpatialScalabilityInTemporalUnitReportedAsDeltaFrame) {
+TEST_P(LibaomAv1EncoderTest,
+ SpatialScalabilityInTemporalUnitReportedAsDeltaFrame) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL2T1);
ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
@@ -154,9 +182,9 @@ TEST(LibaomAv1EncoderTest,
Eq(VideoFrameType::kVideoFrameDelta));
}
-TEST(LibaomAv1EncoderTest, NoBitrateOnTopSpatialLayerProduceDeltaFrames) {
+TEST_P(LibaomAv1EncoderTest, NoBitrateOnTopSpatialLayerProduceDeltaFrames) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL2T1);
ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
@@ -177,14 +205,14 @@ TEST(LibaomAv1EncoderTest, NoBitrateOnTopSpatialLayerProduceDeltaFrames) {
Eq(VideoFrameType::kVideoFrameDelta));
}
-TEST(LibaomAv1EncoderTest, SetsEndOfPictureForLastFrameInTemporalUnit) {
+TEST_P(LibaomAv1EncoderTest, SetsEndOfPictureForLastFrameInTemporalUnit) {
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 30000);
allocation.SetBitrate(1, 0, 40000);
allocation.SetBitrate(2, 0, 30000);
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
// Configure encoder with 3 spatial layers.
codec_settings.SetScalabilityMode(ScalabilityMode::kL3T1);
@@ -206,8 +234,8 @@ TEST(LibaomAv1EncoderTest, SetsEndOfPictureForLastFrameInTemporalUnit) {
EXPECT_TRUE(encoded_frames[5].codec_specific_info.end_of_picture);
}
-TEST(LibaomAv1EncoderTest,
- SetsEndOfPictureForLastFrameInTemporalUnitWhenLayerDrop) {
+TEST_P(LibaomAv1EncoderTest,
+ SetsEndOfPictureForLastFrameInTemporalUnitWhenLayerDrop) {
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 30000);
allocation.SetBitrate(1, 0, 40000);
@@ -215,7 +243,7 @@ TEST(LibaomAv1EncoderTest,
allocation.SetBitrate(2, 0, 500);
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
// Configure encoder with 3 spatial layers.
codec_settings.SetScalabilityMode(ScalabilityMode::kL3T1);
@@ -235,13 +263,13 @@ TEST(LibaomAv1EncoderTest,
EXPECT_TRUE(encoded_frames[3].codec_specific_info.end_of_picture);
}
-TEST(LibaomAv1EncoderTest, CheckOddDimensionsWithSpatialLayers) {
+TEST_P(LibaomAv1EncoderTest, CheckOddDimensionsWithSpatialLayers) {
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 30000);
allocation.SetBitrate(1, 0, 40000);
allocation.SetBitrate(2, 0, 30000);
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
// Configure encoder with 3 spatial layers.
codec_settings.SetScalabilityMode(ScalabilityMode::kL3T1);
@@ -308,13 +336,13 @@ INSTANTIATE_TEST_SUITE_P(LibaomAv1EncoderMaxConsecDropTests,
LibaomAv1EncoderMaxConsecDropTest,
Values(1, 2, 5, 15, 30, 60));
-TEST(LibaomAv1EncoderTest, EncoderInfoWithoutResolutionBitrateLimits) {
+TEST_P(LibaomAv1EncoderTest, EncoderInfoWithoutResolutionBitrateLimits) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
EXPECT_TRUE(encoder->GetEncoderInfo().resolution_bitrate_limits.empty());
}
-TEST(LibaomAv1EncoderTest, EncoderInfoWithBitrateLimitsFromFieldTrial) {
+TEST_P(LibaomAv1EncoderTest, EncoderInfoWithBitrateLimitsFromFieldTrial) {
auto field_trials = std::make_unique<FieldTrials>(
CreateTestFieldTrials("WebRTC-Av1-GetEncoderInfoOverride/"
"frame_size_pixels:123|456|789,"
@@ -332,9 +360,9 @@ TEST(LibaomAv1EncoderTest, EncoderInfoWithBitrateLimitsFromFieldTrial) {
VideoEncoder::ResolutionBitrateLimits{789, 33000, 66000, 99000}));
}
-TEST(LibaomAv1EncoderTest, EncoderInfoProvidesFpsAllocation) {
+TEST_P(LibaomAv1EncoderTest, EncoderInfoProvidesFpsAllocation) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL3T3);
codec_settings.maxFramerate = 60;
@@ -351,13 +379,13 @@ TEST(LibaomAv1EncoderTest, EncoderInfoProvidesFpsAllocation) {
EXPECT_THAT(encoder_info.fps_allocation[3], IsEmpty());
}
-TEST(LibaomAv1EncoderTest, PopulatesEncodedFrameSize) {
+TEST_P(LibaomAv1EncoderTest, PopulatesEncodedFrameSize) {
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 30000);
allocation.SetBitrate(1, 0, 40000);
allocation.SetBitrate(2, 0, 30000);
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.startBitrate = allocation.get_sum_kbps();
ASSERT_GT(codec_settings.width, 4);
@@ -389,9 +417,9 @@ TEST(LibaomAv1EncoderTest, PopulatesEncodedFrameSize) {
codec_settings.height)))));
}
-TEST(LibaomAv1EncoderTest, RtpTimestampWrap) {
+TEST_P(LibaomAv1EncoderTest, RtpTimestampWrap) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL1T1);
ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
@@ -414,9 +442,9 @@ TEST(LibaomAv1EncoderTest, RtpTimestampWrap) {
Eq(VideoFrameType::kVideoFrameDelta));
}
-TEST(LibaomAv1EncoderTest, TestPresentationTimestamp) {
+TEST_P(LibaomAv1EncoderTest, TestPresentationTimestamp) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
const Timestamp presentation_timestamp = Timestamp::Micros(2000);
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL2T1);
@@ -447,9 +475,9 @@ TEST(LibaomAv1EncoderTest, TestPresentationTimestamp) {
presentation_timestamp.us());
}
-TEST(LibaomAv1EncoderTest, AdheresToTargetBitrateDespiteUnevenFrameTiming) {
+TEST_P(LibaomAv1EncoderTest, AdheresToTargetBitrateDespiteUnevenFrameTiming) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.SetScalabilityMode(ScalabilityMode::kL1T1);
codec_settings.startBitrate = 300; // kbps
@@ -526,9 +554,9 @@ TEST(LibaomAv1EncoderTest, AdheresToTargetBitrateDespiteUnevenFrameTiming) {
kTargetBitrateBps, kTargetBitrateBps / 10);
}
-TEST(LibaomAv1EncoderTest, DisableAutomaticResize) {
+TEST_P(LibaomAv1EncoderTest, DisableAutomaticResize) {
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
ASSERT_TRUE(encoder);
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.AV1()->automatic_resize_on = false;
@@ -538,7 +566,7 @@ TEST(LibaomAv1EncoderTest, DisableAutomaticResize) {
std::nullopt);
}
-TEST(LibaomAv1EncoderTest, PostEncodeFrameDrop) {
+TEST_P(LibaomAv1EncoderTest, PostEncodeFrameDrop) {
// To trigger post-encode frame drop, encode a frame of a high complexity
// using a medium bitrate, then reduce the bitrate and encode the same frame
// again.
@@ -559,7 +587,7 @@ TEST(LibaomAv1EncoderTest, PostEncodeFrameDrop) {
allocation.SetBitrate(/*spatial_index=*/0, /*temporal_index=*/0,
/*bitrate_bps=*/10000000);
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = DefaultCodecSettings();
codec_settings.width = input_frame.width();
codec_settings.height = input_frame.height();
@@ -605,7 +633,7 @@ TEST(LibaomAv1EncoderTest, PostEncodeFrameDrop) {
RTC_CHECK_EQ(callback.frames_encoded(), 1);
}
-TEST(LibaomAv1EncoderTest, EnableDisableSpatialLayersWithSvcController) {
+TEST_P(LibaomAv1EncoderTest, EnableDisableSpatialLayersWithSvcController) {
constexpr int kNumSpatialLayers = 3;
constexpr int kNumTemporalLayers = 1;
constexpr size_t kWidth = 1280;
@@ -618,7 +646,7 @@ TEST(LibaomAv1EncoderTest, EnableDisableSpatialLayersWithSvcController) {
// control, the encoder should always produce a frame. A dropped
// frame indicates a problem and the test will fail.
std::unique_ptr<VideoEncoder> encoder =
- CreateLibaomAv1Encoder(CreateEnvironment());
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
VideoCodec codec_settings = HDCodecSettings();
SetAv1SvcConfig(codec_settings, kNumTemporalLayers, kNumSpatialLayers);
codec_settings.SetFrameDropEnabled(true);
@@ -687,5 +715,145 @@ TEST(LibaomAv1EncoderTest, EnableDisableSpatialLayersWithSvcController) {
}
}
+TEST_P(LibaomAv1EncoderTest, L1T2RepeatFrame) {
+ std::unique_ptr<VideoEncoder> encoder =
+ CreateLibaomAv1Encoder(CreateTestEnvironment());
+ VideoCodec codec_settings = DefaultCodecSettings();
+ codec_settings.SetScalabilityMode(ScalabilityMode::kL1T2);
+ ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
+ WEBRTC_VIDEO_CODEC_OK);
+
+ VideoEncoder::RateControlParameters rate_parameters;
+ rate_parameters.framerate_fps = 30;
+ rate_parameters.bitrate.SetBitrate(0, 0, 150'000);
+ rate_parameters.bitrate.SetBitrate(0, 1, 150'000);
+ encoder->SetRates(rate_parameters);
+
+ MockEncodedImageCallback callback;
+ encoder->RegisterEncodeCompleteCallback(&callback);
+
+ auto frame_generator = test::CreateSquareFrameGenerator(
+ codec_settings.width, codec_settings.height,
+ test::FrameGeneratorInterface::OutputType::kI420, std::nullopt);
+
+ // Encode a key frame.
+ VideoFrame key_frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_generator->NextFrame().buffer)
+ .set_rtp_timestamp(1000)
+ .build();
+ std::vector<VideoFrameType> key_frame_types = {
+ VideoFrameType::kVideoFrameKey};
+ EXPECT_CALL(callback,
+ OnEncodedImage(Property(&EncodedImage::TemporalIndex, Eq(0)), _))
+ .WillOnce(Return(EncodedImageCallback::Result(
+ EncodedImageCallback::Result::Error::OK)));
+ ASSERT_EQ(encoder->Encode(key_frame, &key_frame_types),
+ WEBRTC_VIDEO_CODEC_OK);
+ testing::Mock::VerifyAndClearExpectations(&callback);
+
+ // Encode 4 delta frames, marked as repeat.
+ const int expected_frames = drop_repeat_frames_on_enhancement_layers_ ? 2 : 4;
+ EXPECT_CALL(callback, OnEncodedImage)
+ .Times(expected_frames)
+ .WillRepeatedly(testing::Invoke(
+ [&](const EncodedImage& image, const CodecSpecificInfo* info) {
+ if (drop_repeat_frames_on_enhancement_layers_) {
+ EXPECT_EQ(image.TemporalIndex(), 0);
+ }
+ return EncodedImageCallback::Result(
+ EncodedImageCallback::Result::Error::OK);
+ }));
+
+ for (int i = 0; i < 4; ++i) {
+ VideoFrame delta_frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_generator->NextFrame().buffer)
+ .set_rtp_timestamp(key_frame.rtp_timestamp() + (i + 1) * 3000)
+ .set_is_repeat_frame(true)
+ .build();
+ std::vector<VideoFrameType> delta_frame_types = {
+ VideoFrameType::kVideoFrameDelta};
+ ASSERT_EQ(encoder->Encode(delta_frame, &delta_frame_types),
+ WEBRTC_VIDEO_CODEC_OK);
+ }
+}
+
+TEST_P(LibaomAv1EncoderTest, L1T2RepeatFrameNotDroppedIfDeltaTooLarge) {
+ if (!drop_repeat_frames_on_enhancement_layers_) {
+ GTEST_SKIP() << "Test only relevant when drop feature is enabled";
+ }
+
+ const Environment env = CreateTestEnvironment();
+ std::unique_ptr<VideoEncoder> encoder = CreateLibaomAv1Encoder(env);
+ VideoCodec codec_settings = DefaultCodecSettings();
+ codec_settings.SetScalabilityMode(ScalabilityMode::kL1T2);
+ ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()),
+ WEBRTC_VIDEO_CODEC_OK);
+
+ VideoEncoder::RateControlParameters rate_parameters;
+ rate_parameters.framerate_fps = 30;
+ rate_parameters.bitrate.SetBitrate(0, 0, 150'000);
+ rate_parameters.bitrate.SetBitrate(0, 1, 150'000);
+ encoder->SetRates(rate_parameters);
+
+ MockEncodedImageCallback callback;
+ encoder->RegisterEncodeCompleteCallback(&callback);
+
+ auto frame_generator = test::CreateSquareFrameGenerator(
+ codec_settings.width, codec_settings.height,
+ test::FrameGeneratorInterface::OutputType::kI420, std::nullopt);
+
+ // Encode a key frame.
+ VideoFrame key_frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_generator->NextFrame().buffer)
+ .set_rtp_timestamp(1000)
+ .build();
+ std::vector<VideoFrameType> key_frame_types = {
+ VideoFrameType::kVideoFrameKey};
+ EXPECT_CALL(callback, OnEncodedImage)
+ .Times(1)
+ .WillOnce(testing::Invoke(
+ [](const EncodedImage& image, const CodecSpecificInfo* info) {
+ return EncodedImageCallback::Result(
+ EncodedImageCallback::Result::Error::OK);
+ }));
+ ASSERT_EQ(encoder->Encode(key_frame, &key_frame_types),
+ WEBRTC_VIDEO_CODEC_OK);
+ testing::Mock::VerifyAndClearExpectations(&callback);
+
+ // Encode a repeat delta frame with a small gap - should be dropped.
+ EXPECT_CALL(callback, OnEncodedImage).Times(0);
+ VideoFrame delta_frame_drop =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_generator->NextFrame().buffer)
+ .set_rtp_timestamp(key_frame.rtp_timestamp() + 3000)
+ .set_is_repeat_frame(true)
+ .build();
+ std::vector<VideoFrameType> delta_frame_types = {
+ VideoFrameType::kVideoFrameDelta};
+ ASSERT_EQ(encoder->Encode(delta_frame_drop, &delta_frame_types),
+ WEBRTC_VIDEO_CODEC_OK);
+ testing::Mock::VerifyAndClearExpectations(&callback);
+
+ // Encode a repeat delta frame with a large gap - should NOT be dropped.
+ EXPECT_CALL(callback, OnEncodedImage)
+ .Times(1)
+ .WillOnce(testing::Invoke(
+ [](const EncodedImage& image, const CodecSpecificInfo* info) {
+ return EncodedImageCallback::Result(
+ EncodedImageCallback::Result::Error::OK);
+ }));
+ VideoFrame delta_frame_keep =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(frame_generator->NextFrame().buffer)
+ .set_rtp_timestamp(key_frame.rtp_timestamp() + 93000)
+ .set_is_repeat_frame(true)
+ .build();
+ ASSERT_EQ(encoder->Encode(delta_frame_keep, &delta_frame_types),
+ WEBRTC_VIDEO_CODEC_OK);
+}
+
} // namespace
} // namespace webrtc