diff --git a/api/test/video_codec_tester.h b/api/test/video_codec_tester.h index 149f0de611..ab5a91574c 100644 --- a/api/test/video_codec_tester.h +++ b/api/test/video_codec_tester.h @@ -12,6 +12,7 @@ #define API_TEST_VIDEO_CODEC_TESTER_H_ #include +#include #include "absl/functional/any_invocable.h" #include "absl/types/optional.h" @@ -46,10 +47,12 @@ class VideoCodecTester { struct DecoderSettings { PacingSettings pacing; + absl::optional decoded_y4m_base_path; }; struct EncoderSettings { PacingSettings pacing; + absl::optional encoded_ivf_base_path; }; virtual ~VideoCodecTester() = default; diff --git a/modules/video_coding/codecs/test/video_codec_test.cc b/modules/video_coding/codecs/test/video_codec_test.cc index 27a8a2b9e0..89665ae814 100644 --- a/modules/video_coding/codecs/test/video_codec_test.cc +++ b/modules/video_coding/codecs/test/video_codec_test.cc @@ -98,7 +98,9 @@ class TestRawVideoSource : public VideoCodecTester::RawVideoSource { frame_settings_(frame_settings), num_frames_(num_frames), frame_num_(0), - timestamp_rtp_(0) { + // Start with non-zero timestamp to force using frame RTP timestamps in + // IvfFrameWriter. + timestamp_rtp_(90000) { // Ensure settings for the first frame are provided. RTC_CHECK_GT(frame_settings_.size(), 0u); RTC_CHECK_EQ(frame_settings_.begin()->first, 0); @@ -439,6 +441,16 @@ void SetTargetRates(const std::map& frame_settings, f.target_framerate = layer_settings.framerate; } } + +std::string TestOutputPath() { + std::string output_path = + OutputPath() + + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::string output_dir = DirName(output_path); + bool result = CreateDir(output_dir); + RTC_CHECK(result) << "Cannot create " << output_dir; + return output_path; +} } // namespace std::unique_ptr RunEncodeDecodeTest( @@ -446,7 +458,8 @@ std::unique_ptr RunEncodeDecodeTest( std::string codec_impl, const VideoInfo& video_info, const std::map& frame_settings, - int num_frames) { + int num_frames, + bool save_codec_output) { std::unique_ptr video_source = CreateVideoSource(video_info, frame_settings, num_frames); @@ -468,6 +481,12 @@ std::unique_ptr RunEncodeDecodeTest( ? PacingMode::kRealTime : PacingMode::kNoPacing; + if (save_codec_output) { + std::string output_path = TestOutputPath(); + encoder_settings.encoded_ivf_base_path = output_path; + decoder_settings.decoded_y4m_base_path = output_path; + } + std::unique_ptr tester = CreateVideoCodecTester(); return tester->RunEncodeDecodeTest(video_source.get(), encoder.get(), decoder.get(), encoder_settings, @@ -479,7 +498,8 @@ std::unique_ptr RunEncodeTest( std::string codec_impl, const VideoInfo& video_info, const std::map& frame_settings, - int num_frames) { + int num_frames, + bool save_codec_output) { std::unique_ptr video_source = CreateVideoSource(video_info, frame_settings, num_frames); @@ -492,6 +512,10 @@ std::unique_ptr RunEncodeTest( ? PacingMode::kRealTime : PacingMode::kNoPacing; + if (save_codec_output) { + encoder_settings.encoded_ivf_base_path = TestOutputPath(); + } + std::unique_ptr tester = CreateVideoCodecTester(); return tester->RunEncodeTest(video_source.get(), encoder.get(), encoder_settings); @@ -535,8 +559,9 @@ TEST_P(SpatialQualityTest, DISABLED_SpatialQuality) { int duration_s = 10; int num_frames = duration_s * framerate_fps; - std::unique_ptr stats = RunEncodeDecodeTest( - codec_type, codec_impl, video_info, frame_settings, num_frames); + std::unique_ptr stats = + RunEncodeDecodeTest(codec_type, codec_impl, video_info, frame_settings, + num_frames, /*save_codec_output=*/false); std::vector frames = stats->Slice(); SetTargetRates(frame_settings, frames); @@ -613,8 +638,9 @@ TEST_P(BitrateAdaptationTest, DISABLED_BitrateAdaptation) { .framerate = video_info.framerate, .bitrate = DataRate::KilobitsPerSec(bitrate_kbps.second)}}}}}}; - std::unique_ptr stats = RunEncodeTest( - codec_type, codec_impl, video_info, frame_settings, num_frames); + std::unique_ptr stats = + RunEncodeTest(codec_type, codec_impl, video_info, frame_settings, + num_frames, /*save_codec_output=*/false); std::vector frames = stats->Slice(VideoCodecStats::Filter{.first_frame = first_frame}); @@ -686,8 +712,9 @@ TEST_P(FramerateAdaptationTest, DISABLED_FramerateAdaptation) { .framerate = Frequency::MilliHertz(1000 * framerate_fps.second), .bitrate = DataRate::KilobitsPerSec(512)}}}}}}; - std::unique_ptr stats = RunEncodeTest( - codec_type, codec_impl, video_info, frame_settings, num_frames); + std::unique_ptr stats = + RunEncodeTest(codec_type, codec_impl, video_info, frame_settings, + num_frames, /*save_codec_output=*/false); std::vector frames = stats->Slice(VideoCodecStats::Filter{.first_frame = first_frame}); diff --git a/modules/video_coding/codecs/test/video_codec_tester_impl.cc b/modules/video_coding/codecs/test/video_codec_tester_impl.cc index 3583b59b4b..1d79933954 100644 --- a/modules/video_coding/codecs/test/video_codec_tester_impl.cc +++ b/modules/video_coding/codecs/test/video_codec_tester_impl.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include "api/task_queue/default_task_queue_factory.h" @@ -20,11 +21,14 @@ #include "api/units/timestamp.h" #include "api/video/encoded_image.h" #include "api/video/i420_buffer.h" +#include "api/video/video_codec_type.h" #include "api/video/video_frame.h" #include "modules/video_coding/codecs/test/video_codec_analyzer.h" +#include "modules/video_coding/utility/ivf_file_writer.h" #include "rtc_base/event.h" #include "rtc_base/time_utils.h" #include "system_wrappers/include/sleep.h" +#include "test/testsupport/video_frame_writer.h" namespace webrtc { namespace test { @@ -150,6 +154,39 @@ class LimitedTaskQueue { rtc::Event task_executed_; }; +class TesterY4mWriter { + public: + explicit TesterY4mWriter(absl::string_view base_path) + : base_path_(base_path) {} + + ~TesterY4mWriter() { + task_queue_.SendTask([] {}); + } + + void Write(const VideoFrame& frame, int spatial_idx) { + task_queue_.PostTask([this, frame, spatial_idx] { + if (y4m_writers_.find(spatial_idx) == y4m_writers_.end()) { + std::string file_path = + base_path_ + "_s" + std::to_string(spatial_idx) + ".y4m"; + + Y4mVideoFrameWriterImpl* y4m_writer = new Y4mVideoFrameWriterImpl( + file_path, frame.width(), frame.height(), /*fps=*/30); + RTC_CHECK(y4m_writer); + + y4m_writers_[spatial_idx] = + std::unique_ptr(y4m_writer); + } + + y4m_writers_.at(spatial_idx)->WriteFrame(frame); + }); + } + + protected: + std::string base_path_; + std::map> y4m_writers_; + TaskQueueForTest task_queue_; +}; + class TesterDecoder { public: TesterDecoder(Decoder* decoder, @@ -160,6 +197,11 @@ class TesterDecoder { settings_(settings), pacer_(settings.pacing) { RTC_CHECK(analyzer_) << "Analyzer must be provided"; + + if (settings.decoded_y4m_base_path) { + y4m_writer_ = + std::make_unique(*settings.decoded_y4m_base_path); + } } void Decode(const EncodedImage& frame) { @@ -168,10 +210,15 @@ class TesterDecoder { task_queue_.PostScheduledTask( [this, frame] { analyzer_->StartDecode(frame); + decoder_->Decode( frame, [this, spatial_idx = frame.SpatialIndex().value_or(0)]( const VideoFrame& decoded_frame) { analyzer_->FinishDecode(decoded_frame, spatial_idx); + + if (y4m_writer_) { + y4m_writer_->Write(decoded_frame, spatial_idx); + } }); }, pacer_.Schedule(timestamp)); @@ -189,6 +236,45 @@ class TesterDecoder { const DecoderSettings& settings_; Pacer pacer_; LimitedTaskQueue task_queue_; + std::unique_ptr y4m_writer_; +}; + +class TesterIvfWriter { + public: + explicit TesterIvfWriter(absl::string_view base_path) + : base_path_(base_path) {} + + ~TesterIvfWriter() { + task_queue_.SendTask([] {}); + } + + void Write(const EncodedImage& encoded_frame) { + task_queue_.PostTask([this, encoded_frame] { + int spatial_idx = encoded_frame.SpatialIndex().value_or(0); + if (ivf_file_writers_.find(spatial_idx) == ivf_file_writers_.end()) { + std::string ivf_path = + base_path_ + "_s" + std::to_string(spatial_idx) + ".ivf"; + + FileWrapper ivf_file = FileWrapper::OpenWriteOnly(ivf_path); + RTC_CHECK(ivf_file.is_open()); + + std::unique_ptr ivf_writer = + IvfFileWriter::Wrap(std::move(ivf_file), /*byte_limit=*/0); + RTC_CHECK(ivf_writer); + + ivf_file_writers_[spatial_idx] = std::move(ivf_writer); + } + + // To play: ffplay -vcodec vp8|vp9|av1|hevc|h264 filename + ivf_file_writers_.at(spatial_idx) + ->WriteFrame(encoded_frame, VideoCodecType::kVideoCodecGeneric); + }); + } + + protected: + std::string base_path_; + std::map> ivf_file_writers_; + TaskQueueForTest task_queue_; }; class TesterEncoder { @@ -203,6 +289,10 @@ class TesterEncoder { settings_(settings), pacer_(settings.pacing) { RTC_CHECK(analyzer_) << "Analyzer must be provided"; + if (settings.encoded_ivf_base_path) { + ivf_writer_ = + std::make_unique(*settings.encoded_ivf_base_path); + } } void Encode(const VideoFrame& frame) { @@ -213,9 +303,14 @@ class TesterEncoder { analyzer_->StartEncode(frame); encoder_->Encode(frame, [this](const EncodedImage& encoded_frame) { analyzer_->FinishEncode(encoded_frame); + if (decoder_ != nullptr) { decoder_->Decode(encoded_frame); } + + if (ivf_writer_ != nullptr) { + ivf_writer_->Write(encoded_frame); + } }); }, pacer_.Schedule(timestamp)); @@ -232,6 +327,7 @@ class TesterEncoder { TesterDecoder* const decoder_; VideoCodecAnalyzer* const analyzer_; const EncoderSettings& settings_; + std::unique_ptr ivf_writer_; Pacer pacer_; LimitedTaskQueue task_queue_; };