| /* |
| * 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 <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #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<VideoCodecType> GetCodecsToTest() { |
| std::vector<VideoCodecType> 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<IvfFileWriter> 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<VideoCodecType> { |
| protected: |
| void SetUp() override { |
| file_name_ = test::TempFilename(test::OutputPath(), "test_file.ivf"); |
| } |
| void TearDown() override { test::RemoveFile(file_name_); } |
| |
| std::unique_ptr<VideoEncoder> 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<VideoEncoder> video_encoder) { |
| std::unique_ptr<test::FrameGeneratorInterface> 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<double>(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<VideoFrame> 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<I420Buffer> 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<I420Buffer> 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<I420Buffer> frame = reader.PullFrame(&frame_num); |
| ASSERT_TRUE(frame); |
| EXPECT_EQ(frame_num, i); |
| } |
| |
| int frame_num; |
| scoped_refptr<I420Buffer> 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 |