From b337c40e583b0c24d9b3cc525cec7eb7604d810b Mon Sep 17 00:00:00 2001 From: Mirko Bonadei Date: Mon, 20 Feb 2023 10:02:59 +0000 Subject: [PATCH] Introduce Y4mFrameGenerator. Bug: b/269577953 Change-Id: Id28a395235cc88cb5422dd9754483fbac3e50807 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/294100 Commit-Queue: Mirko Bonadei Reviewed-by: Artem Titov Cr-Commit-Position: refs/heads/main@{#39344} --- test/BUILD.gn | 19 ++++ test/testsupport/y4m_frame_generator.cc | 78 +++++++++++++++ test/testsupport/y4m_frame_generator.h | 66 ++++++++++++ test/testsupport/y4m_frame_generator_test.cc | 100 +++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 test/testsupport/y4m_frame_generator.cc create mode 100644 test/testsupport/y4m_frame_generator.h create mode 100644 test/testsupport/y4m_frame_generator_test.cc diff --git a/test/BUILD.gn b/test/BUILD.gn index 0c71115f0d..57e602f31e 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -78,6 +78,23 @@ rtc_library("frame_generator_impl") { absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] } +rtc_library("y4m_frame_generator") { + visibility = [ "*" ] + testonly = true + sources = [ + "testsupport/y4m_frame_generator.cc", + "testsupport/y4m_frame_generator.h", + ] + deps = [ + ":video_test_support", + "../api:frame_generator_api", + "../api:scoped_refptr", + "../api/video:video_frame", + "../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] +} + rtc_library("frame_utils") { visibility = [ "*" ] testonly = true @@ -632,6 +649,7 @@ if (rtc_include_tests && !build_with_chromium) { ":test_support_test_artifacts", ":video_test_common", ":video_test_support", + ":y4m_frame_generator", "../api:array_view", "../api:create_frame_generator", "../api:create_simulcast_test_fixture_api", @@ -687,6 +705,7 @@ if (rtc_include_tests && !build_with_chromium) { "testsupport/perf_test_unittest.cc", "testsupport/test_artifacts_unittest.cc", "testsupport/video_frame_writer_unittest.cc", + "testsupport/y4m_frame_generator_test.cc", "testsupport/y4m_frame_reader_unittest.cc", "testsupport/y4m_frame_writer_unittest.cc", "testsupport/yuv_frame_reader_unittest.cc", diff --git a/test/testsupport/y4m_frame_generator.cc b/test/testsupport/y4m_frame_generator.cc new file mode 100644 index 0000000000..7614016f0a --- /dev/null +++ b/test/testsupport/y4m_frame_generator.cc @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 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/y4m_frame_generator.h" + +#include +#include + +#include + +#include "absl/strings/string_view.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "rtc_base/checks.h" +#include "test/testsupport/frame_reader.h" + +namespace webrtc { +namespace test { + +namespace { +// Reading 30 bytes from the Y4M header should be enough to get width +// and heigth. +// The header starts with: `YUV4MPEG2 W H`. +constexpr int kHeaderBytesToRead = 30; +} // namespace + +Y4mFrameGenerator::Y4mFrameGenerator(absl::string_view filename, + RepeatMode repeat_mode) + : filename_(filename), repeat_mode_(repeat_mode) { + // Read resolution from the Y4M header. + FILE* file = fopen(filename_.c_str(), "r"); + RTC_CHECK(file != NULL) << "Cannot open " << filename_; + char header[kHeaderBytesToRead]; + RTC_CHECK(fgets(header, sizeof(header), file) != nullptr) + << "File " << filename_ << " is too small"; + fclose(file); + RTC_CHECK_EQ(sscanf(header, "YUV4MPEG2 W%zu H%zu", &width_, &height_), 2); + RTC_CHECK_GT(width_, 0); + RTC_CHECK_GT(height_, 0); + + // Delegate the actual reads (from NextFrame) to a Y4mReader. + frame_reader_ = webrtc::test::CreateY4mFrameReader( + filename_, ToYuvFrameReaderRepeatMode(repeat_mode_)); +} + +Y4mFrameGenerator::VideoFrameData Y4mFrameGenerator::NextFrame() { + webrtc::VideoFrame::UpdateRect update_rect{0, 0, static_cast(width_), + static_cast(height_)}; + rtc::scoped_refptr next_frame_buffer = + frame_reader_->PullFrame(); + return VideoFrameData(next_frame_buffer, update_rect); +} + +FrameGeneratorInterface::Resolution Y4mFrameGenerator::GetResolution() const { + return {.width = width_, .height = height_}; +} + +YuvFrameReaderImpl::RepeatMode Y4mFrameGenerator::ToYuvFrameReaderRepeatMode( + RepeatMode repeat_mode) const { + switch (repeat_mode) { + case RepeatMode::kSingle: + return YuvFrameReaderImpl::RepeatMode::kSingle; + case RepeatMode::kLoop: + return YuvFrameReaderImpl::RepeatMode::kRepeat; + case RepeatMode::kPingPong: + return YuvFrameReaderImpl::RepeatMode::kPingPong; + } +} + +} // namespace test +} // namespace webrtc diff --git a/test/testsupport/y4m_frame_generator.h b/test/testsupport/y4m_frame_generator.h new file mode 100644 index 0000000000..b90b083b65 --- /dev/null +++ b/test/testsupport/y4m_frame_generator.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 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 TEST_TESTSUPPORT_Y4M_FRAME_GENERATOR_H_ +#define TEST_TESTSUPPORT_Y4M_FRAME_GENERATOR_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "api/test/frame_generator_interface.h" +#include "rtc_base/checks.h" +#include "test/testsupport/frame_reader.h" + +namespace webrtc { +namespace test { + +// Generates frames from a Y4M file. The behaviour when reaching EOF is +// configurable via RepeatMode. +class Y4mFrameGenerator : public FrameGeneratorInterface { + public: + enum class RepeatMode { + // Generate frames from the input file, but it stops generating new frames + // once EOF is reached. + kSingle, + // Generate frames from the input file, when EOF is reached it starts from + // the beginning. + kLoop, + // Generate frames from the input file, when EOF is reached it plays frames + // backwards from the end to the beginning of the file (and vice versa, + // literally doing Ping/Pong between the beginning and the end of the file). + kPingPong, + }; + Y4mFrameGenerator(absl::string_view filename, RepeatMode repeat_mode); + ~Y4mFrameGenerator() override = default; + + VideoFrameData NextFrame() override; + + void ChangeResolution(size_t width, size_t height) override { + RTC_CHECK_NOTREACHED(); + } + + Resolution GetResolution() const override; + + private: + YuvFrameReaderImpl::RepeatMode ToYuvFrameReaderRepeatMode( + RepeatMode repeat_mode) const; + std::unique_ptr frame_reader_ = nullptr; + std::string filename_; + size_t width_; + size_t height_; + const RepeatMode repeat_mode_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_TESTSUPPORT_Y4M_FRAME_GENERATOR_H_ diff --git a/test/testsupport/y4m_frame_generator_test.cc b/test/testsupport/y4m_frame_generator_test.cc new file mode 100644 index 0000000000..2af05dfec3 --- /dev/null +++ b/test/testsupport/y4m_frame_generator_test.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023 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/y4m_frame_generator.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace test { + +class Y4mFrameGeneratorTest : public testing::Test { + protected: + Y4mFrameGeneratorTest() = default; + ~Y4mFrameGeneratorTest() = default; + + void SetUp() { + input_filepath_ = TempFilename(OutputPath(), "2x2.y4m"); + FILE* y4m_file = fopen(input_filepath_.c_str(), "wb"); + + // Input Y4M file: 3 YUV frames of 2x2 resolution. + std::string y4m_content = + "YUV4MPEG2 W2 H2 F2:1 C420\n" + "FRAME\n" + "123456FRAME\n" + "abcdefFRAME\n" + "987654"; + std::fprintf(y4m_file, "%s", y4m_content.c_str()); + fclose(y4m_file); + } + + void TearDown() { remove(input_filepath_.c_str()); } + + std::string input_filepath_; +}; + +TEST_F(Y4mFrameGeneratorTest, CanReadResolutionFromFile) { + Y4mFrameGenerator generator(input_filepath_, + Y4mFrameGenerator::RepeatMode::kSingle); + FrameGeneratorInterface::Resolution res = generator.GetResolution(); + EXPECT_EQ(res.width, 2u); + EXPECT_EQ(res.height, 2u); +} + +TEST_F(Y4mFrameGeneratorTest, SingleRepeatMode) { + Y4mFrameGenerator generator(input_filepath_, + Y4mFrameGenerator::RepeatMode::kSingle); + + std::vector expected_frame_ys = {"123456", "abcdef", "987654"}; + for (absl::string_view frame_y : expected_frame_ys) { + EXPECT_EQ(frame_y.size(), 6u); + FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame(); + EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0); + } + FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame(); + EXPECT_EQ(frame.buffer, nullptr); +} + +TEST_F(Y4mFrameGeneratorTest, LoopRepeatMode) { + Y4mFrameGenerator generator(input_filepath_, + Y4mFrameGenerator::RepeatMode::kLoop); + + std::vector expected_frame_ys = {"123456", "abcdef", "987654", + "123456", "abcdef", "987654"}; + for (absl::string_view frame_y : expected_frame_ys) { + EXPECT_EQ(frame_y.size(), 6u); + FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame(); + EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0); + } +} + +TEST_F(Y4mFrameGeneratorTest, PingPongRepeatMode) { + Y4mFrameGenerator generator(input_filepath_, + Y4mFrameGenerator::RepeatMode::kPingPong); + + std::vector expected_frame_ys = { + "123456", "abcdef", "987654", "abcdef", "123456", "abcdef", "987654"}; + for (absl::string_view frame_y : expected_frame_ys) { + EXPECT_EQ(frame_y.size(), 6u); + FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame(); + EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0); + } +} + +} // namespace test +} // namespace webrtc