/* * 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. */ #include "test/testsupport/ivf_frame_reader.h" #include #include #include #include #include #include #include "api/environment/environment.h" #include "api/scoped_refptr.h" #include "api/test/create_frame_generator.h" #include "api/test/frame_generator_interface.h" #include "api/units/time_delta.h" #include "api/video/encoded_image.h" #include "api/video/i420_buffer.h" #include "api/video/resolution.h" #include "api/video/video_bitrate_allocation.h" #include "api/video/video_codec_type.h" #include "api/video/video_frame.h" #include "api/video_codecs/video_codec.h" #include "api/video_codecs/video_encoder.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/video_coding/codecs/av1/libaom_av1_encoder.h" #include "modules/video_coding/codecs/h264/include/h264.h" #include "modules/video_coding/codecs/vp8/include/vp8.h" #include "modules/video_coding/codecs/vp9/include/vp9.h" #include "modules/video_coding/include/video_error_codes.h" #include "modules/video_coding/utility/ivf_file_writer.h" #include "rtc_base/checks.h" #include "rtc_base/event.h" #include "rtc_base/synchronization/mutex.h" #include "rtc_base/thread_annotations.h" #include "test/create_test_environment.h" #include "test/gtest.h" #include "test/testsupport/file_utils.h" #include "test/testsupport/frame_reader.h" #include "test/video_codec_settings.h" namespace webrtc { namespace test { namespace { constexpr int kWidth = 320; constexpr int kHeight = 240; constexpr int kVideoFramesCount = 10; constexpr int kMaxFramerate = 30; constexpr TimeDelta kMaxFrameEncodeWaitTimeout = TimeDelta::Seconds(2); const VideoEncoder::Capabilities kCapabilities(false); #if defined(WEBRTC_USE_H264) constexpr bool kUseH264 = true; #else constexpr bool kUseH264 = false; #endif std::vector GetCodecsToTest() { std::vector codecs = {VideoCodecType::kVideoCodecVP8, VideoCodecType::kVideoCodecVP9, VideoCodecType::kVideoCodecAV1}; if (kUseH264) { codecs.push_back(VideoCodecType::kVideoCodecH264); } return codecs; } class IvfFileWriterEncodedCallback : public EncodedImageCallback { public: IvfFileWriterEncodedCallback(const std::string& file_name, VideoCodecType video_codec_type, int expected_frames_count) : file_writer_(IvfFileWriter::Wrap(file_name, 0)), video_codec_type_(video_codec_type), expected_frames_count_(expected_frames_count) { EXPECT_TRUE(file_writer_.get()); } ~IvfFileWriterEncodedCallback() override { EXPECT_TRUE(file_writer_->Close()); } Result OnEncodedImage(const EncodedImage& encoded_image, const CodecSpecificInfo* codec_specific_info) override { EXPECT_TRUE(file_writer_->WriteFrame(encoded_image, video_codec_type_)); MutexLock lock(&lock_); received_frames_count_++; RTC_CHECK_LE(received_frames_count_, expected_frames_count_); if (received_frames_count_ == expected_frames_count_) { expected_frames_count_received_.Set(); } return Result(Result::Error::OK); } void OnFrameDropped(uint32_t /*rtp_timestamp*/, int /*spatial_id*/, bool /*is_end_of_temporal_unit*/) override {} bool WaitForExpectedFramesReceived(TimeDelta timeout) { return expected_frames_count_received_.Wait(timeout); } private: std::unique_ptr file_writer_; const VideoCodecType video_codec_type_; const int expected_frames_count_; Mutex lock_; int received_frames_count_ RTC_GUARDED_BY(lock_) = 0; Event expected_frames_count_received_; }; class IvfFrameReaderTest : public ::testing::TestWithParam { protected: void SetUp() override { file_name_ = test::TempFilename(test::OutputPath(), "test_file.ivf"); } void TearDown() override { test::RemoveFile(file_name_); } std::unique_ptr CreateEncoder(VideoCodecType codec_type) { switch (codec_type) { case VideoCodecType::kVideoCodecVP8: return CreateVp8Encoder(env_); case VideoCodecType::kVideoCodecVP9: return CreateVp9Encoder(env_); case VideoCodecType::kVideoCodecAV1: return CreateLibaomAv1Encoder(env_); #if defined(WEBRTC_USE_H264) case VideoCodecType::kVideoCodecH264: return CreateH264Encoder(env_); #endif default: RTC_CHECK(false) << "Unsupported codec type"; } } void CreateTestVideoFile(VideoCodecType video_codec_type, std::unique_ptr video_encoder) { std::unique_ptr frame_generator = test::CreateSquareFrameGenerator( kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kI420, std::nullopt); VideoCodec codec_settings; test::CodecSettings(video_codec_type, &codec_settings); codec_settings.width = kWidth; codec_settings.height = kHeight; codec_settings.maxFramerate = kMaxFramerate; const uint32_t kBitrateBps = 500000; VideoBitrateAllocation bitrate_allocation; bitrate_allocation.SetBitrate(0, 0, kBitrateBps); IvfFileWriterEncodedCallback ivf_writer_callback( file_name_, video_codec_type, kVideoFramesCount); video_encoder->RegisterEncodeCompleteCallback(&ivf_writer_callback); ASSERT_EQ(WEBRTC_VIDEO_CODEC_OK, video_encoder->InitEncode( &codec_settings, VideoEncoder::Settings(kCapabilities, /*number_of_cores=*/1, /*max_payload_size=*/0))); video_encoder->SetRates(VideoEncoder::RateControlParameters( bitrate_allocation, static_cast(codec_settings.maxFramerate))); uint32_t last_frame_timestamp = 0; for (int i = 0; i < kVideoFramesCount; ++i) { FrameGeneratorInterface::VideoFrameData frame_data = frame_generator->NextFrame(); VideoFrame frame = VideoFrame::Builder() .set_video_frame_buffer(frame_data.buffer) .set_update_rect(frame_data.update_rect) .build(); const uint32_t timestamp = last_frame_timestamp + kVideoPayloadTypeFrequency / codec_settings.maxFramerate; frame.set_rtp_timestamp(timestamp); last_frame_timestamp = timestamp; ASSERT_EQ(WEBRTC_VIDEO_CODEC_OK, video_encoder->Encode(frame, nullptr)); video_frames_.push_back(frame); } ASSERT_TRUE(ivf_writer_callback.WaitForExpectedFramesReceived( kMaxFrameEncodeWaitTimeout)); } Environment env_ = CreateTestEnvironment(); std::string file_name_; std::vector video_frames_; }; } // namespace TEST_P(IvfFrameReaderTest, ReadsAllFrames) { VideoCodecType codec_type = GetParam(); CreateTestVideoFile(codec_type, CreateEncoder(codec_type)); IvfFrameReader reader(env_, file_name_, /*repeat=*/false); EXPECT_EQ(reader.num_frames(), kVideoFramesCount); for (int i = 0; i < kVideoFramesCount; ++i) { int frame_num; scoped_refptr frame = reader.PullFrame(&frame_num); ASSERT_TRUE(frame); EXPECT_EQ(frame_num, i); EXPECT_EQ(frame->width(), kWidth); EXPECT_EQ(frame->height(), kHeight); } // After all frames read, PullFrame should return nullptr. int frame_num; EXPECT_FALSE(reader.PullFrame(&frame_num)); } TEST_P(IvfFrameReaderTest, ScalesOutput) { VideoCodecType codec_type = GetParam(); CreateTestVideoFile(codec_type, CreateEncoder(codec_type)); IvfFrameReader reader(env_, file_name_, /*repeat=*/false); int frame_num; Resolution target_resolution = {.width = kWidth * 2, .height = kHeight / 2}; scoped_refptr frame = reader.PullFrame( &frame_num, target_resolution, FrameReader::Ratio({.num = 1, .den = 1})); ASSERT_TRUE(frame); EXPECT_EQ(frame_num, 0); EXPECT_EQ(frame->width(), kWidth * 2); EXPECT_EQ(frame->height(), kHeight / 2); } TEST_P(IvfFrameReaderTest, RepeatsFrames) { VideoCodecType codec_type = GetParam(); CreateTestVideoFile(codec_type, CreateEncoder(codec_type)); IvfFrameReader reader(env_, file_name_, /*repeat=*/true); EXPECT_EQ(reader.num_frames(), kVideoFramesCount); for (int i = 0; i < kVideoFramesCount; ++i) { int frame_num; scoped_refptr frame = reader.PullFrame(&frame_num); ASSERT_TRUE(frame); EXPECT_EQ(frame_num, i); } int frame_num; scoped_refptr frame = reader.PullFrame(&frame_num); ASSERT_TRUE(frame); EXPECT_EQ(frame_num, kVideoFramesCount); } TEST_P(IvfFrameReaderTest, ScalesFramerateDown) { VideoCodecType codec_type = GetParam(); CreateTestVideoFile(codec_type, CreateEncoder(codec_type)); IvfFrameReader reader(env_, file_name_, /*repeat=*/false); int count = 0; int frame_num; while (reader.PullFrame(&frame_num, {.width = kWidth, .height = kHeight}, FrameReader::Ratio({.num = 1, .den = 2}))) { EXPECT_EQ(frame_num, count); count++; } EXPECT_EQ(count, kVideoFramesCount / 2); } TEST_P(IvfFrameReaderTest, ScalesFramerateUp) { VideoCodecType codec_type = GetParam(); CreateTestVideoFile(codec_type, CreateEncoder(codec_type)); IvfFrameReader reader(env_, file_name_, /*repeat=*/false); int count = 0; int frame_num; while (reader.PullFrame(&frame_num, {.width = kWidth, .height = kHeight}, FrameReader::Ratio({.num = 2, .den = 1}))) { EXPECT_EQ(frame_num, count); count++; } EXPECT_EQ(count, kVideoFramesCount * 2); } INSTANTIATE_TEST_SUITE_P(AllCodecs, IvfFrameReaderTest, ::testing::ValuesIn(GetCodecsToTest())); } // namespace test } // namespace webrtc