| /* |
| * 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 <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| |
| #include "absl/strings/string_view.h" |
| #include "api/environment/environment.h" |
| #include "api/scoped_refptr.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_codec_type.h" |
| #include "api/video/video_frame.h" |
| #include "api/video/video_frame_buffer.h" |
| #include "api/video_codecs/video_decoder.h" |
| #include "modules/video_coding/codecs/av1/dav1d_decoder.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_reader.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/synchronization/mutex.h" |
| #include "rtc_base/system/file_wrapper.h" |
| |
| namespace webrtc { |
| namespace test { |
| namespace { |
| |
| constexpr TimeDelta kMaxNextFrameWaitTimeout = TimeDelta::Seconds(1); |
| |
| std::unique_ptr<VideoDecoder> CreateDecoder(const Environment& env, |
| VideoCodecType codec_type) { |
| switch (codec_type) { |
| case VideoCodecType::kVideoCodecVP8: |
| return CreateVp8Decoder(env); |
| case VideoCodecType::kVideoCodecVP9: |
| return VP9Decoder::Create(); |
| case VideoCodecType::kVideoCodecH264: |
| return H264Decoder::Create(); |
| case VideoCodecType::kVideoCodecAV1: |
| return CreateDav1dDecoder(env); |
| case VideoCodecType::kVideoCodecH265: |
| return nullptr; |
| case VideoCodecType::kVideoCodecGeneric: |
| return nullptr; |
| } |
| } |
| |
| } // namespace |
| |
| IvfFrameReader::IvfFrameReader(const Environment& env, |
| absl::string_view filepath, |
| bool repeat) |
| : file_reader_(IvfFileReader::Create(FileWrapper::OpenReadOnly(filepath))), |
| callback_(this), |
| frame_num_(0), |
| repeat_(repeat) { |
| RTC_CHECK(file_reader_) << "Failed to open IVF file: " << filepath; |
| resolution_ = {.width = file_reader_->GetFrameWidth(), |
| .height = file_reader_->GetFrameHeight()}; |
| video_decoder_ = CreateDecoder(env, file_reader_->GetVideoCodecType()); |
| RTC_CHECK(video_decoder_) << "No decoder found for file's video codec type"; |
| |
| VideoDecoder::Settings decoder_settings; |
| decoder_settings.set_codec_type(file_reader_->GetVideoCodecType()); |
| decoder_settings.set_max_render_resolution( |
| {file_reader_->GetFrameWidth(), file_reader_->GetFrameHeight()}); |
| decoder_settings.set_buffer_pool_size(std::numeric_limits<int>::max()); |
| |
| RTC_CHECK_EQ(video_decoder_->RegisterDecodeCompleteCallback(&callback_), |
| WEBRTC_VIDEO_CODEC_OK); |
| RTC_CHECK(video_decoder_->Configure(decoder_settings)); |
| } |
| |
| IvfFrameReader::~IvfFrameReader() { |
| MutexLock lock(&lock_); |
| if (!file_reader_) { |
| return; |
| } |
| file_reader_->Close(); |
| file_reader_.reset(); |
| video_decoder_.reset(); |
| { |
| MutexLock frame_lock(&frame_decode_lock_); |
| next_frame_ = std::nullopt; |
| next_frame_decoded_.Set(); |
| } |
| } |
| |
| int IvfFrameReader::RateScaler::Skip(Ratio framerate_scale) { |
| ticks_ = ticks_.value_or(framerate_scale.num); |
| int skip = 0; |
| while (ticks_ <= 0) { |
| *ticks_ += framerate_scale.num; |
| ++skip; |
| } |
| *ticks_ -= framerate_scale.den; |
| return skip; |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::PullFrame() { |
| int frame_num; |
| return PullFrame(&frame_num); |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::PullFrame(int* frame_num) { |
| return PullFrame(frame_num, resolution_, Ratio()); |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::DecodeNextFrameLocked() { |
| next_frame_decoded_.Reset(); |
| RTC_CHECK(file_reader_); |
| |
| if (!file_reader_->HasMoreFrames() && repeat_) { |
| file_reader_->Reset(); |
| } |
| |
| std::optional<EncodedImage> image = file_reader_->NextFrame(); |
| if (!image) { |
| return nullptr; |
| } |
| |
| RTC_CHECK_EQ(WEBRTC_VIDEO_CODEC_OK, |
| video_decoder_->Decode(*image, /*render_time_ms=*/0)); |
| |
| bool decoded = next_frame_decoded_.Wait(kMaxNextFrameWaitTimeout); |
| RTC_CHECK(decoded) << "Failed to decode next frame in " |
| << kMaxNextFrameWaitTimeout; |
| |
| MutexLock frame_lock(&frame_decode_lock_); |
| if (!next_frame_) { |
| RTC_LOG(LS_WARNING) << "next_frame_ is null after decode event!"; |
| return nullptr; |
| } |
| |
| auto i420_buffer = next_frame_->video_frame_buffer()->ToI420(); |
| RTC_CHECK(i420_buffer); |
| |
| // The FrameReader interface requires returning a concrete I420Buffer, |
| // which owns its memory and is mutable. The decoder might return a |
| // read-only buffer or a different implementation of I420BufferInterface, |
| // so we must copy it. |
| scoped_refptr<I420Buffer> output_buffer = I420Buffer::Copy(*i420_buffer); |
| next_frame_ = std::nullopt; |
| |
| return output_buffer; |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::PullFrame(int* frame_num, |
| Resolution resolution, |
| Ratio framerate_scale) { |
| MutexLock lock(&lock_); |
| |
| int skip = framerate_scaler_.Skip(framerate_scale); |
| if (!last_decoded_buffer_) { |
| skip = 1; // Force reading the first frame. |
| } |
| |
| scoped_refptr<I420Buffer> buffer; |
| if (skip == 0) { |
| RTC_CHECK(last_decoded_buffer_); |
| buffer = last_decoded_buffer_; |
| } else { |
| for (int i = 0; i < skip; ++i) { |
| buffer = DecodeNextFrameLocked(); |
| if (!buffer) { |
| return nullptr; |
| } |
| } |
| last_decoded_buffer_ = buffer; |
| } |
| |
| if (frame_num) { |
| *frame_num = frame_num_; |
| } |
| frame_num_++; |
| |
| if (buffer->width() != static_cast<int>(resolution.width) || |
| buffer->height() != static_cast<int>(resolution.height)) { |
| scoped_refptr<I420Buffer> scaled_buffer = |
| I420Buffer::Create(resolution.width, resolution.height); |
| scaled_buffer->ScaleFrom(*buffer); |
| return scaled_buffer; |
| } |
| |
| return buffer; |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::ReadFrame(int frame_num) { |
| RTC_CHECK(false) << "ReadFrame is not supported for IVF files."; |
| return nullptr; |
| } |
| |
| scoped_refptr<I420Buffer> IvfFrameReader::ReadFrame(int frame_num, |
| Resolution resolution) { |
| RTC_CHECK(false) << "ReadFrame is not supported for IVF files."; |
| return nullptr; |
| } |
| |
| int IvfFrameReader::num_frames() const { |
| MutexLock lock(&lock_); |
| return static_cast<int>(file_reader_->GetFramesCount()); |
| } |
| |
| int32_t IvfFrameReader::DecodedCallback::Decoded(VideoFrame& decoded_image) { |
| Decoded(decoded_image, 0, 0); |
| return WEBRTC_VIDEO_CODEC_OK; |
| } |
| |
| int32_t IvfFrameReader::DecodedCallback::Decoded(VideoFrame& decoded_image, |
| int64_t decode_time_ms) { |
| Decoded(decoded_image, decode_time_ms, 0); |
| return WEBRTC_VIDEO_CODEC_OK; |
| } |
| |
| void IvfFrameReader::DecodedCallback::Decoded( |
| VideoFrame& decoded_image, |
| std::optional<int32_t> decode_time_ms, |
| std::optional<uint8_t> qp) { |
| reader_->OnFrameDecoded(decoded_image); |
| } |
| |
| void IvfFrameReader::OnFrameDecoded(const VideoFrame& decoded_frame) { |
| MutexLock lock(&frame_decode_lock_); |
| next_frame_ = decoded_frame; |
| next_frame_decoded_.Set(); |
| } |
| |
| } // namespace test |
| } // namespace webrtc |