diff --git a/experiments/field_trials.py b/experiments/field_trials.py index 6d8e83c291..768b42f4d0 100755 --- a/experiments/field_trials.py +++ b/experiments/field_trials.py @@ -104,6 +104,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-LibaomAv1Encoder-AdaptiveMaxConsecDrops', 351644568, date(2025, 7, 1)), + FieldTrial('WebRTC-LibaomAv1Encoder-PostEncodeFrameDrop', + 351644568, + date(2026, 1, 30)), FieldTrial('WebRTC-LibvpxVp8Encoder-AndroidSpecificThreadingSettings', 42226191, date(2024, 9, 1)), diff --git a/modules/BUILD.gn b/modules/BUILD.gn index 76fcf085f2..d2be5c5b27 100644 --- a/modules/BUILD.gn +++ b/modules/BUILD.gn @@ -163,6 +163,7 @@ if (rtc_include_tests && !build_with_chromium) { "../resources/near88_stereo.pcm", "../resources/near8_stereo.pcm", "../resources/near96_stereo.pcm", + "../resources/photo_1850_1110.yuv", "../resources/ref03.aecdump", "../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_AST.bin", "../resources/remote_bitrate_estimator/VideoSendersTest_BweTest_IncreasingChoke1_0_TOF.bin", @@ -199,7 +200,7 @@ if (rtc_include_tests && !build_with_chromium) { bundle_data("modules_unittests_bundle_data") { testonly = true sources = modules_unittests_resources - outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + outputs = [ "{{bundle_resources_dir}}/{{source_root_relative_dir}}/{{source_file_part}}" ] } } diff --git a/modules/video_coding/codecs/av1/BUILD.gn b/modules/video_coding/codecs/av1/BUILD.gn index 8a2d4909d7..2769420fad 100644 --- a/modules/video_coding/codecs/av1/BUILD.gn +++ b/modules/video_coding/codecs/av1/BUILD.gn @@ -105,7 +105,9 @@ if (rtc_include_tests) { "../../../../api/units:time_delta", "../../../../api/video:video_frame", "../../../../modules/rtp_rtcp:rtp_rtcp_format", + "../../../../test:fileutils", "../../../../test:scoped_key_value_config", + "../../../../test:video_test_support", "../../svc:scalability_mode_util", "../../svc:scalability_structures", "../../svc:scalable_video_controller", diff --git a/modules/video_coding/codecs/av1/libaom_av1_encoder.cc b/modules/video_coding/codecs/av1/libaom_av1_encoder.cc index a5a87eeed2..919c79a985 100644 --- a/modules/video_coding/codecs/av1/libaom_av1_encoder.cc +++ b/modules/video_coding/codecs/av1/libaom_av1_encoder.cc @@ -135,7 +135,10 @@ class LibaomAv1Encoder final : public VideoEncoder { const LibaomAv1EncoderInfoSettings encoder_info_override_; // TODO(webrtc:351644568): Remove this kill-switch after the feature is fully // deployed. - bool adaptive_max_consec_drops_; + const bool adaptive_max_consec_drops_; + // TODO(webrtc:351644568): Remove this kill-switch after the feature is fully + // deployed. + const bool post_encode_frame_drop_; }; int32_t VerifyCodecSettings(const VideoCodec& codec_settings) { @@ -177,7 +180,9 @@ LibaomAv1Encoder::LibaomAv1Encoder(const Environment& env, timestamp_(0), encoder_info_override_(env.field_trials()), adaptive_max_consec_drops_(!env.field_trials().IsDisabled( - "WebRTC-LibaomAv1Encoder-AdaptiveMaxConsecDrops")) {} + "WebRTC-LibaomAv1Encoder-AdaptiveMaxConsecDrops")), + post_encode_frame_drop_(!env.field_trials().IsDisabled( + "WebRTC-LibaomAv1Encoder-PostEncodeFrameDrop")) {} LibaomAv1Encoder::~LibaomAv1Encoder() { Release(); @@ -336,6 +341,10 @@ int LibaomAv1Encoder::InitEncode(const VideoCodec* codec_settings, 250); } + if (post_encode_frame_drop_) { + SET_ENCODER_PARAM_OR_RETURN_ERROR(AV1E_SET_POSTENCODE_DROP_RTC, 1); + } + return WEBRTC_VIDEO_CODEC_OK; } diff --git a/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc b/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc index 79275e1210..ba2f1af93a 100644 --- a/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc +++ b/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc @@ -21,6 +21,7 @@ #include "api/environment/environment_factory.h" #include "api/test/create_frame_generator.h" #include "api/test/frame_generator_interface.h" +#include "api/video/i420_buffer.h" #include "api/video_codecs/video_codec.h" #include "api/video_codecs/video_encoder.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" @@ -29,6 +30,8 @@ #include "test/gmock.h" #include "test/gtest.h" #include "test/scoped_key_value_config.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" namespace webrtc { namespace { @@ -237,7 +240,7 @@ class LibaomAv1EncoderMaxConsecDropTest TEST_P(LibaomAv1EncoderMaxConsecDropTest, MaxConsecDrops) { VideoBitrateAllocation allocation; allocation.SetBitrate(0, 0, - 1000); // Very low bitrate to provoke frame drops. + 2000); // A low bitrate to provoke frame drops. std::unique_ptr encoder = CreateLibaomAv1Encoder(CreateEnvironment()); VideoCodec codec_settings = DefaultCodecSettings(); @@ -509,5 +512,72 @@ TEST(LibaomAv1EncoderTest, DisableAutomaticResize) { std::nullopt); } +TEST(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. + // Using a medium bitrate for the first frame prevents quality and QP + // saturation. Encoding the same content twice prevents scene change + // detection. The second frame overshoots RC buffer and provokes post-encode + // drop. + VideoFrame input_frame = + VideoFrame::Builder() + .set_video_frame_buffer( + test::CreateYuvFrameReader( + test::ResourcePath("photo_1850_1110", "yuv"), + {.width = 1850, .height = 1110}) + ->PullFrame()) + .build(); + + VideoBitrateAllocation allocation; + allocation.SetBitrate(/*spatial_index=*/0, /*temporal_index=*/0, + /*bitrate_bps=*/10000000); + std::unique_ptr encoder = + CreateLibaomAv1Encoder(CreateEnvironment()); + VideoCodec codec_settings = DefaultCodecSettings(); + codec_settings.width = input_frame.width(); + codec_settings.height = input_frame.height(); + codec_settings.startBitrate = allocation.get_sum_kbps(); + codec_settings.SetFrameDropEnabled(true); + codec_settings.SetScalabilityMode(ScalabilityMode::kL1T1); + ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()), + WEBRTC_VIDEO_CODEC_OK); + encoder->SetRates(VideoEncoder::RateControlParameters( + allocation, codec_settings.maxFramerate)); + + class EncoderCallback : public EncodedImageCallback { + public: + EncoderCallback() = default; + int frames_encoded() const { return frames_encoded_; } + + private: + Result OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* /* codec_specific_info */) override { + frames_encoded_++; + return Result(Result::Error::OK); + } + + int frames_encoded_ = 0; + } callback; + encoder->RegisterEncodeCompleteCallback(&callback); + + input_frame.set_rtp_timestamp(1 * kVideoPayloadTypeFrequency / + codec_settings.maxFramerate); + RTC_CHECK_EQ(encoder->Encode(input_frame, /*frame_types=*/nullptr), + WEBRTC_VIDEO_CODEC_OK); + + allocation.SetBitrate(/*spatial_index=*/0, /*temporal_index=*/0, + /*bitrate_bps=*/1000); + encoder->SetRates(VideoEncoder::RateControlParameters( + allocation, codec_settings.maxFramerate)); + + input_frame.set_rtp_timestamp(2 * kVideoPayloadTypeFrequency / + codec_settings.maxFramerate); + RTC_CHECK_EQ(encoder->Encode(input_frame, /*frame_types=*/nullptr), + WEBRTC_VIDEO_CODEC_OK); + RTC_CHECK_EQ(callback.frames_encoded(), 1); +} + } // namespace } // namespace webrtc