| /* |
| * Copyright (c) 2026 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #ifndef API_VIDEO_CODECS_TEST_VIDEO_CODEC_TEST_UTILS_H_ |
| #define API_VIDEO_CODECS_TEST_VIDEO_CODEC_TEST_UTILS_H_ |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| #include <span> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/strings/match.h" |
| #include "api/environment/environment.h" |
| #include "api/scoped_refptr.h" |
| #include "api/units/data_rate.h" |
| #include "api/units/time_delta.h" |
| #include "api/video/encoded_image.h" |
| #include "api/video/video_frame.h" |
| #include "api/video/video_frame_buffer.h" |
| #include "api/video_codecs/video_decoder.h" |
| #include "api/video_codecs/video_decoder_factory.h" |
| #include "api/video_codecs/video_decoder_factory_template.h" |
| #include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" |
| #include "api/video_codecs/video_encoder_builders.h" |
| #include "api/video_codecs/video_encoder_builders_for_test.h" |
| #include "api/video_codecs/video_encoder_factory_interface.h" |
| #include "api/video_codecs/video_encoder_interface.h" |
| #include "api/video_codecs/video_encoding_general.h" |
| #include "common_video/libyuv/include/webrtc_libyuv.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/event.h" |
| #include "test/testsupport/file_utils.h" |
| #include "test/testsupport/frame_reader.h" |
| |
| namespace webrtc { |
| |
| using Cbr = VideoEncoderInterface::FrameEncodeSettings::Cbr; |
| using Cqp = VideoEncoderInterface::FrameEncodeSettings::Cqp; |
| using EncodedData = VideoEncoderInterface::EncodedData; |
| using FrameType = VideoEncoderInterface::FrameType; |
| using TemporalUnitSettings = VideoEncoderInterface::TemporalUnitSettings; |
| using EncOut = TestEncodedOutput; |
| using Fb = FrameEncodeSettingsBuilderForTest; |
| using FactoryCreator = std::unique_ptr<VideoEncoderFactoryInterface> (*)(); |
| |
| template <int N> |
| std::vector<VideoEncoderInterface::FrameEncodeSettings> ToVec( |
| VideoEncoderInterface::FrameEncodeSettings (&&settings)[N]) { |
| return std::vector<VideoEncoderInterface::FrameEncodeSettings>( |
| std::make_move_iterator(std::begin(settings)), |
| std::make_move_iterator(std::end(settings))); |
| } |
| |
| inline double Psnr(const scoped_refptr<I420BufferInterface>& ref_buffer, |
| const VideoFrame& decoded_frame) { |
| return I420PSNR(*ref_buffer, *decoded_frame.video_frame_buffer()->ToI420()); |
| } |
| |
| inline std::unique_ptr<test::FrameReader> CreateFrameReader() { |
| return CreateY4mFrameReader( |
| test::ResourcePath("reference_video_640x360_30fps", "y4m"), |
| test::YuvFrameReaderImpl::RepeatMode::kPingPong); |
| } |
| |
| inline std::unique_ptr<VideoDecoderFactory> CreateTestDecoderFactory() { |
| return std::make_unique<VideoDecoderFactoryTemplate< |
| LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter, |
| OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(); |
| } |
| |
| class TestDecoder : public DecodedImageCallback { |
| public: |
| TestDecoder(const Environment& env, |
| VideoDecoderFactory* factory, |
| const std::string& codec_name) { |
| for (const auto& format : factory->GetSupportedFormats()) { |
| if (absl::EqualsIgnoreCase(format.name, codec_name)) { |
| decoder_ = factory->Create(env, format); |
| break; |
| } |
| } |
| if (decoder_) { |
| decoder_->Configure({}); |
| decoder_->RegisterDecodeCompleteCallback(this); |
| } |
| } |
| |
| bool IsSupported() const { return decoder_ != nullptr; } |
| |
| // DecodedImageCallback |
| int32_t Decoded(VideoFrame& frame) override { |
| decode_result_ = std::make_unique<VideoFrame>(std::move(frame)); |
| decoded_event_.Set(); |
| return 0; |
| } |
| |
| VideoFrame Decode(std::span<const uint8_t> bitstream_data) { |
| RTC_CHECK(decoder_); |
| EncodedImage img; |
| img.SetEncodedData(EncodedImageBuffer::Create(bitstream_data.data(), |
| bitstream_data.size())); |
| decoded_event_.Reset(); |
| decoder_->Decode(img, /*dont_care=*/0); |
| RTC_CHECK(decoded_event_.Wait(TimeDelta::Seconds(5))); |
| RTC_CHECK(decode_result_); |
| VideoFrame res(std::move(*decode_result_)); |
| decode_result_.reset(); |
| return res; |
| } |
| |
| private: |
| std::unique_ptr<VideoDecoder> decoder_; |
| std::unique_ptr<VideoFrame> decode_result_; |
| Event decoded_event_; |
| }; |
| |
| struct TestConfig { |
| VideoEncoderFactoryInterface::StaticEncoderSettings static_settings; |
| std::variant<VideoEncoderInterface::FrameEncodeSettings::Cbr, |
| VideoEncoderInterface::FrameEncodeSettings::Cqp> |
| rate_options; |
| }; |
| |
| inline void ConfigureRate(FrameEncodeSettingsBuilderForTest& fb, |
| const std::variant<Cbr, Cqp>& rate_options) { |
| std::visit( |
| [&fb](const auto& rate) -> void { |
| using T = std::decay_t<decltype(rate)>; |
| if constexpr (std::is_same_v< |
| T, VideoEncoderInterface::FrameEncodeSettings::Cbr>) { |
| fb.Cbr(rate); |
| } else if constexpr (std::is_same_v<T, VideoEncoderInterface:: |
| FrameEncodeSettings::Cqp>) { |
| fb.Cqp(rate.target_qp); |
| } |
| }, |
| rate_options); |
| } |
| |
| inline VideoEncoderInterface::FrameEncodeSettings BuildSettings( |
| FrameEncodeSettingsBuilderForTest&& fb, |
| const std::variant<Cbr, Cqp>& rate_options) { |
| ConfigureRate(fb, rate_options); |
| return fb.Build(); |
| } |
| |
| inline TestConfig CreateTestConfig( |
| const VideoEncoderFactoryInterface::Capabilities& capabilities) { |
| const auto& rc_modes = capabilities.bitrate_control().rc_modes(); |
| RTC_DCHECK(!rc_modes.empty()) << "Encoder must support at least one RC mode."; |
| |
| TestConfig config; |
| if (std::find(rc_modes.begin(), rc_modes.end(), |
| VideoEncoderFactoryInterface::RateControlMode::kCqp) != |
| rc_modes.end()) { |
| config.static_settings = |
| StaticEncoderSettingsBuilder() |
| .MaxEncodeDimensions({.width = 640, .height = 360}) |
| .EncodingFormat({.sub_sampling = EncodingFormat::SubSampling::k420, |
| .bit_depth = 8}) |
| .CqpRcMode() |
| .MaxNumberOfThreads(1) |
| .Build(); |
| config.rate_options = VideoEncoderInterface::FrameEncodeSettings::Cqp{ |
| .target_qp = std::clamp(20, capabilities.bitrate_control().min_qp(), |
| capabilities.bitrate_control().max_qp())}; |
| } else { |
| config.static_settings = |
| StaticEncoderSettingsBuilder() |
| .MaxEncodeDimensions({.width = 640, .height = 360}) |
| .EncodingFormat({.sub_sampling = EncodingFormat::SubSampling::k420, |
| .bit_depth = 8}) |
| .CbrRcMode(TimeDelta::Millis(1000), TimeDelta::Millis(600)) |
| .MaxNumberOfThreads(1) |
| .Build(); |
| config.rate_options = VideoEncoderInterface::FrameEncodeSettings::Cbr{ |
| .duration = TimeDelta::Millis(100), |
| .target_bitrate = DataRate::KilobitsPerSec(1000)}; |
| } |
| return config; |
| } |
| |
| } // namespace webrtc |
| |
| #endif // API_VIDEO_CODECS_TEST_VIDEO_CODEC_TEST_UTILS_H_ |