From 645df9e3b5c858fdc03234dc5e9841a8e6dfb551 Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Mon, 14 Jan 2019 14:21:59 +0100 Subject: [PATCH] Introduce Y4mFrameReader. Bug: webrtc:10138 Change-Id: I213a4309a8a4b1a7afd296bf45566c9b3f9a215c Reviewed-on: https://webrtc-review.googlesource.com/c/117301 Commit-Queue: Artem Titov Reviewed-by: Ilya Nikolaevskiy Cr-Commit-Position: refs/heads/master@{#26243} --- test/BUILD.gn | 3 + test/testsupport/frame_reader.h | 19 +++- test/testsupport/y4m_frame_reader.cc | 85 +++++++++++++++++ test/testsupport/y4m_frame_reader_unittest.cc | 93 +++++++++++++++++++ test/testsupport/yuv_frame_reader.cc | 6 +- 5 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 test/testsupport/y4m_frame_reader.cc create mode 100644 test/testsupport/y4m_frame_reader_unittest.cc diff --git a/test/BUILD.gn b/test/BUILD.gn index bbd0ebb374..3d4b884f21 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -254,6 +254,7 @@ if (rtc_include_tests) { "testsupport/frame_reader.h", "testsupport/frame_writer.h", "testsupport/mock/mock_frame_reader.h", + "testsupport/y4m_frame_reader.cc", "testsupport/y4m_frame_writer.cc", "testsupport/yuv_frame_reader.cc", "testsupport/yuv_frame_writer.cc", @@ -352,6 +353,7 @@ if (rtc_include_tests) { "//testing/gmock", "//testing/gtest", "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", ] sources = [ "direct_transport_unittest.cc", @@ -363,6 +365,7 @@ if (rtc_include_tests) { "testsupport/always_passing_unittest.cc", "testsupport/perf_test_unittest.cc", "testsupport/test_artifacts_unittest.cc", + "testsupport/y4m_frame_reader_unittest.cc", "testsupport/y4m_frame_writer_unittest.cc", "testsupport/yuv_frame_reader_unittest.cc", "testsupport/yuv_frame_writer_unittest.cc", diff --git a/test/testsupport/frame_reader.h b/test/testsupport/frame_reader.h index de8962e126..4c0f206426 100644 --- a/test/testsupport/frame_reader.h +++ b/test/testsupport/frame_reader.h @@ -59,8 +59,9 @@ class YuvFrameReaderImpl : public FrameReader { size_t FrameLength() override; int NumberOfFrames() override; - private: + protected: const std::string input_filename_; + // It is not const, so subclasses will be able to add frame header size. size_t frame_length_in_bytes_; const int width_; const int height_; @@ -68,6 +69,22 @@ class YuvFrameReaderImpl : public FrameReader { FILE* input_file_; }; +class Y4mFrameReaderImpl : public YuvFrameReaderImpl { + public: + // Creates a file handler. The input file is assumed to exist and be readable. + // Parameters: + // input_filename The file to read from. + // width, height Size of each frame to read. + Y4mFrameReaderImpl(std::string input_filename, int width, int height); + ~Y4mFrameReaderImpl() override; + bool Init() override; + rtc::scoped_refptr ReadFrame() override; + + private: + // Buffer that is used to read file and frame headers. + uint8_t* buffer_; +}; + } // namespace test } // namespace webrtc diff --git a/test/testsupport/y4m_frame_reader.cc b/test/testsupport/y4m_frame_reader.cc new file mode 100644 index 0000000000..00ced05cc2 --- /dev/null +++ b/test/testsupport/y4m_frame_reader.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 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 +#include + +#include "api/video/i420_buffer.h" +#include "rtc_base/scoped_ref_ptr.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" + +namespace webrtc { +namespace test { +namespace { + +// Size of header: "YUV4MPEG2 W50 H20 F30:1 C420\n" +const size_t kFileHeaderSize = 29; +// Size of header: "FRAME\n" +const size_t kFrameHeaderSize = 6; + +} // namespace + +Y4mFrameReaderImpl::Y4mFrameReaderImpl(std::string input_filename, + int width, + int height) + : YuvFrameReaderImpl(input_filename, width, height) { + frame_length_in_bytes_ += kFrameHeaderSize; + buffer_ = new uint8_t[kFileHeaderSize]; +} +Y4mFrameReaderImpl::~Y4mFrameReaderImpl() { + delete[] buffer_; +} + +bool Y4mFrameReaderImpl::Init() { + if (width_ <= 0 || height_ <= 0) { + fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", width_, + height_); + return false; + } + input_file_ = fopen(input_filename_.c_str(), "rb"); + if (input_file_ == nullptr) { + fprintf(stderr, "Couldn't open input file for reading: %s\n", + input_filename_.c_str()); + return false; + } + size_t source_file_size = GetFileSize(input_filename_); + if (source_file_size <= 0u) { + fprintf(stderr, "Found empty file: %s\n", input_filename_.c_str()); + return false; + } + if (fread(buffer_, 1, kFileHeaderSize, input_file_) < kFileHeaderSize) { + fprintf(stderr, "Failed to read file header from input file: %s\n", + input_filename_.c_str()); + return false; + } + // Calculate total number of frames. + number_of_frames_ = static_cast((source_file_size - kFileHeaderSize) / + frame_length_in_bytes_); + return true; +} + +rtc::scoped_refptr Y4mFrameReaderImpl::ReadFrame() { + if (input_file_ == nullptr) { + fprintf(stderr, + "Y4mFrameReaderImpl is not initialized (input file is NULL)\n"); + return nullptr; + } + if (fread(buffer_, 1, kFrameHeaderSize, input_file_) < kFrameHeaderSize && + ferror(input_file_)) { + fprintf(stderr, "Failed to read frame header from input file: %s\n", + input_filename_.c_str()); + return nullptr; + } + return YuvFrameReaderImpl::ReadFrame(); +} + +} // namespace test +} // namespace webrtc diff --git a/test/testsupport/y4m_frame_reader_unittest.cc b/test/testsupport/y4m_frame_reader_unittest.cc new file mode 100644 index 0000000000..957b88d0cb --- /dev/null +++ b/test/testsupport/y4m_frame_reader_unittest.cc @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 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 +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame_buffer.h" +#include "rtc_base/scoped_ref_ptr.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" + +namespace webrtc { +namespace test { +namespace { + +const absl::string_view kFileHeader = "YUV4MPEG2 W50 H20 F30:1 C420\n"; +const absl::string_view kFrameHeader = "FRAME\n"; +const absl::string_view kInputVideoContents = "abcdef"; + +const size_t kFrameWidth = 2; +const size_t kFrameHeight = 2; +const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420. + +} // namespace + +class Y4mFrameReaderTest : public testing::Test { + protected: + Y4mFrameReaderTest() = default; + ~Y4mFrameReaderTest() override = default; + + void SetUp() override { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "y4m_frame_reader_unittest"); + FILE* dummy = fopen(temp_filename_.c_str(), "wb"); + fprintf(dummy, "%s", + (std::string(kFileHeader) + std::string(kFrameHeader) + + std::string(kInputVideoContents)) + .c_str()); + fclose(dummy); + + frame_reader_.reset( + new Y4mFrameReaderImpl(temp_filename_, kFrameWidth, kFrameHeight)); + ASSERT_TRUE(frame_reader_->Init()); + } + + void TearDown() override { remove(temp_filename_.c_str()); } + + std::unique_ptr frame_reader_; + std::string temp_filename_; +}; + +TEST_F(Y4mFrameReaderTest, InitSuccess) {} + +TEST_F(Y4mFrameReaderTest, FrameLength) { + EXPECT_EQ(kFrameHeader.size() + kFrameLength, frame_reader_->FrameLength()); +} + +TEST_F(Y4mFrameReaderTest, NumberOfFrames) { + EXPECT_EQ(1, frame_reader_->NumberOfFrames()); +} + +TEST_F(Y4mFrameReaderTest, ReadFrame) { + rtc::scoped_refptr buffer = frame_reader_->ReadFrame(); + ASSERT_TRUE(buffer); + // Expect I420 packed as YUV. + EXPECT_EQ(kInputVideoContents[0], buffer->DataY()[0]); + EXPECT_EQ(kInputVideoContents[1], buffer->DataY()[1]); + EXPECT_EQ(kInputVideoContents[2], buffer->DataY()[2]); + EXPECT_EQ(kInputVideoContents[3], buffer->DataY()[3]); + EXPECT_EQ(kInputVideoContents[4], buffer->DataU()[0]); + EXPECT_EQ(kInputVideoContents[5], buffer->DataV()[0]); + EXPECT_FALSE(frame_reader_->ReadFrame()); // End of file. +} + +TEST_F(Y4mFrameReaderTest, ReadFrameUninitialized) { + Y4mFrameReaderImpl file_reader(temp_filename_, kFrameWidth, kFrameHeight); + EXPECT_FALSE(file_reader.ReadFrame()); +} + +} // namespace test +} // namespace webrtc diff --git a/test/testsupport/yuv_frame_reader.cc b/test/testsupport/yuv_frame_reader.cc index 97b94bb4d1..c502ee7de5 100644 --- a/test/testsupport/yuv_frame_reader.cc +++ b/test/testsupport/yuv_frame_reader.cc @@ -24,7 +24,8 @@ YuvFrameReaderImpl::YuvFrameReaderImpl(std::string input_filename, int width, int height) : input_filename_(input_filename), - frame_length_in_bytes_(0), + frame_length_in_bytes_(width * height + + 2 * ((width + 1) / 2) * ((height + 1) / 2)), width_(width), height_(height), number_of_frames_(-1), @@ -40,9 +41,6 @@ bool YuvFrameReaderImpl::Init() { height_); return false; } - frame_length_in_bytes_ = - width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2); - input_file_ = fopen(input_filename_.c_str(), "rb"); if (input_file_ == nullptr) { fprintf(stderr, "Couldn't open input file for reading: %s\n",