From 08a9b618a630789759bade26c9308999b5e131d5 Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Thu, 17 Jan 2019 21:37:19 +0100 Subject: [PATCH] Introduce VideoFrameWriter. VideoFrameWriter is designed to accept webrtc::VideoFrame as input and write it with Y4mFrameWriterImpl to the output file, transforming webrtc::VideoFrame to the uint8_t* frame_buffer. VideoFrameWriter will be used to write webrtc::VideoFrames during dumping input and output video in peer connection level test framework and will be injected in webrtc::test::FrameGenerator and rtc::VideoSinkInterface. Bug: webrtc:10138 Change-Id: Iadec7d3ad66f226836acbebe070cf88ceb242f62 Reviewed-on: https://webrtc-review.googlesource.com/c/117200 Commit-Queue: Artem Titov Reviewed-by: Stefan Holmer Cr-Commit-Position: refs/heads/master@{#26305} --- test/BUILD.gn | 6 +- test/testsupport/video_frame_writer.cc | 85 +++++++++++ test/testsupport/video_frame_writer.h | 53 +++++++ .../video_frame_writer_unittest.cc | 134 ++++++++++++++++++ 4 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 test/testsupport/video_frame_writer.cc create mode 100644 test/testsupport/video_frame_writer.h create mode 100644 test/testsupport/video_frame_writer_unittest.cc diff --git a/test/BUILD.gn b/test/BUILD.gn index 8fa8485a9e..70e554f9c6 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -256,6 +256,8 @@ if (rtc_include_tests) { "testsupport/frame_reader.h", "testsupport/frame_writer.h", "testsupport/mock/mock_frame_reader.h", + "testsupport/video_frame_writer.cc", + "testsupport/video_frame_writer.h", "testsupport/y4m_frame_reader.cc", "testsupport/y4m_frame_writer.cc", "testsupport/yuv_frame_reader.cc", @@ -267,13 +269,14 @@ if (rtc_include_tests) { ":test_support", ":video_test_common", "..:webrtc_common", + "../api:scoped_refptr", "../api/video:video_frame", "../api/video:video_frame_i420", "../common_video", "../rtc_base:checks", "../rtc_base:rtc_base_approved", "../system_wrappers", - "//testing/gtest", + "//third_party/abseil-cpp/absl/memory:memory", "//third_party/libyuv", ] @@ -372,6 +375,7 @@ if (rtc_include_tests) { "testsupport/always_passing_unittest.cc", "testsupport/perf_test_unittest.cc", "testsupport/test_artifacts_unittest.cc", + "testsupport/video_frame_writer_unittest.cc", "testsupport/y4m_frame_reader_unittest.cc", "testsupport/y4m_frame_writer_unittest.cc", "testsupport/yuv_frame_reader_unittest.cc", diff --git a/test/testsupport/video_frame_writer.cc b/test/testsupport/video_frame_writer.cc new file mode 100644 index 0000000000..64aec0de9c --- /dev/null +++ b/test/testsupport/video_frame_writer.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 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/video_frame_writer.h" + +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "common_types.h" // NOLINT(build/include) +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace test { + +VideoFrameWriter::VideoFrameWriter(std::string output_file_name, + int width, + int height, + int fps) + // We will move string here to prevent extra copy. We won't use const ref + // to not corrupt caller variable with move and don't assume that caller's + // variable won't be destructed before writer. + : output_file_name_(std::move(output_file_name)), + width_(width), + height_(height), + fps_(fps), + frame_writer_(absl::make_unique(output_file_name_, + width_, + height_, + fps_)) { + // Init underlying frame writer and ensure that it is operational. + RTC_CHECK(frame_writer_->Init()); +} +VideoFrameWriter::~VideoFrameWriter() = default; + +bool VideoFrameWriter::WriteFrame(const webrtc::VideoFrame& frame) { + rtc::Buffer frame_buffer = ExtractI420BufferWithSize(frame, width_, height_); + RTC_CHECK_EQ(frame_buffer.size(), frame_writer_->FrameLength()); + return frame_writer_->WriteFrame(frame_buffer.data()); +} + +void VideoFrameWriter::Close() { + frame_writer_->Close(); +} + +rtc::Buffer VideoFrameWriter::ExtractI420BufferWithSize(const VideoFrame& frame, + int width, + int height) { + if (frame.width() != width || frame.height() != height) { + RTC_CHECK_LE(std::abs(static_cast(width) / height - + static_cast(frame.width()) / frame.height()), + 2 * std::numeric_limits::epsilon()); + // Same aspect ratio, no cropping needed. + rtc::scoped_refptr scaled(I420Buffer::Create(width, height)); + scaled->ScaleFrom(*frame.video_frame_buffer()->ToI420()); + + size_t length = + CalcBufferSize(VideoType::kI420, scaled->width(), scaled->height()); + rtc::Buffer buffer(length); + RTC_CHECK_NE(ExtractBuffer(scaled, length, buffer.data()), -1); + return buffer; + } + + // No resize. + size_t length = + CalcBufferSize(VideoType::kI420, frame.width(), frame.height()); + rtc::Buffer buffer(length); + RTC_CHECK_NE(ExtractBuffer(frame, length, buffer.data()), -1); + return buffer; +} + +} // namespace test +} // namespace webrtc diff --git a/test/testsupport/video_frame_writer.h b/test/testsupport/video_frame_writer.h new file mode 100644 index 0000000000..c96faf6d27 --- /dev/null +++ b/test/testsupport/video_frame_writer.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 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_VIDEO_FRAME_WRITER_H_ +#define TEST_TESTSUPPORT_VIDEO_FRAME_WRITER_H_ + +#include +#include + +#include "api/video/video_frame.h" +#include "rtc_base/buffer.h" +#include "rtc_base/critical_section.h" +#include "test/testsupport/frame_writer.h" + +namespace webrtc { +namespace test { + +// Writes webrtc::VideoFrame to specified file with y4m frame writer +class VideoFrameWriter { + public: + VideoFrameWriter(std::string output_file_name, + int width, + int height, + int fps); + virtual ~VideoFrameWriter(); + + bool WriteFrame(const webrtc::VideoFrame& frame); + void Close(); + + private: + rtc::Buffer ExtractI420BufferWithSize(const VideoFrame& frame, + int width, + int height); + + const std::string output_file_name_; + const int width_; + const int height_; + const int fps_; + + std::unique_ptr frame_writer_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_TESTSUPPORT_VIDEO_FRAME_WRITER_H_ diff --git a/test/testsupport/video_frame_writer_unittest.cc b/test/testsupport/video_frame_writer_unittest.cc new file mode 100644 index 0000000000..8ab931b365 --- /dev/null +++ b/test/testsupport/video_frame_writer_unittest.cc @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 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 + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "api/video/i420_buffer.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" +#include "test/testsupport/video_frame_writer.h" + +namespace webrtc { +namespace test { +namespace { + +const size_t kFrameWidth = 50; +const size_t kFrameHeight = 20; +const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420. +const size_t kFrameRate = 30; + +// 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; + +rtc::scoped_refptr CreateI420Buffer(int width, int height) { + rtc::scoped_refptr buffer(I420Buffer::Create(width, height)); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + buffer->MutableDataY()[x + y * width] = 128; + } + } + int chroma_width = buffer->ChromaWidth(); + int chroma_height = buffer->ChromaHeight(); + for (int x = 0; x < chroma_width; x++) { + for (int y = 0; y < chroma_height; y++) { + buffer->MutableDataU()[x + y * chroma_width] = 1; + buffer->MutableDataV()[x + y * chroma_width] = 255; + } + } + return buffer; +} + +void AssertI420BuffersEq( + rtc::scoped_refptr actual, + rtc::scoped_refptr expected) { + ASSERT_TRUE(actual); + + ASSERT_EQ(actual->width(), expected->width()); + ASSERT_EQ(actual->height(), expected->height()); + const int width = expected->width(); + const int height = expected->height(); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + ASSERT_EQ(actual->DataY()[x + y * width], + expected->DataY()[x + y * width]); + } + } + + ASSERT_EQ(actual->ChromaWidth(), expected->ChromaWidth()); + ASSERT_EQ(actual->ChromaHeight(), expected->ChromaHeight()); + int chroma_width = expected->ChromaWidth(); + int chroma_height = expected->ChromaHeight(); + for (int x = 0; x < chroma_width; x++) { + for (int y = 0; y < chroma_height; y++) { + ASSERT_EQ(actual->DataU()[x + y * chroma_width], + expected->DataU()[x + y * chroma_width]); + ASSERT_EQ(actual->DataV()[x + y * chroma_width], + expected->DataV()[x + y * chroma_width]); + } + } +} + +} // namespace + +class VideoFrameWriterTest : public testing::Test { + protected: + VideoFrameWriterTest() = default; + ~VideoFrameWriterTest() override = default; + + void SetUp() override { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "video_frame_writer_unittest"); + frame_writer_ = absl::make_unique( + temp_filename_, kFrameWidth, kFrameHeight, kFrameRate); + } + + void TearDown() override { remove(temp_filename_.c_str()); } + + std::unique_ptr frame_writer_; + std::string temp_filename_; +}; + +TEST_F(VideoFrameWriterTest, InitSuccess) {} + +TEST_F(VideoFrameWriterTest, WriteFrame) { + rtc::scoped_refptr expected_buffer = + CreateI420Buffer(kFrameWidth, kFrameHeight); + + VideoFrame frame = + VideoFrame::Builder().set_video_frame_buffer(expected_buffer).build(); + + ASSERT_TRUE(frame_writer_->WriteFrame(frame)); + ASSERT_TRUE(frame_writer_->WriteFrame(frame)); + + frame_writer_->Close(); + EXPECT_EQ(kFileHeaderSize + 2 * kFrameHeaderSize + 2 * kFrameLength, + GetFileSize(temp_filename_)); + + std::unique_ptr frame_reader = + absl::make_unique(temp_filename_, kFrameWidth, + kFrameHeight); + ASSERT_TRUE(frame_reader->Init()); + AssertI420BuffersEq(frame_reader->ReadFrame(), expected_buffer); + AssertI420BuffersEq(frame_reader->ReadFrame(), expected_buffer); + EXPECT_FALSE(frame_reader->ReadFrame()); // End of file. + frame_reader->Close(); +} + +} // namespace test +} // namespace webrtc