// Copyright 2021 The libgav1 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "examples/file_writer.h" #include #include #include #include #include #include #include #include #include "absl/memory/memory.h" #include "gav1/decoder_buffer.h" #include "gtest/gtest.h" #include "tests/utils.h" namespace libgav1 { namespace { const char kExpectedY4mHeader8bit[] = "YUV4MPEG2 W352 H288 F30:1 Ip C420jpeg\n"; const char kExpectedY4mHeader10bit[] = "YUV4MPEG2 W352 H288 F30:1 Ip C420p10\n"; const char kExpectedY4mHeader8bitMonochrome[] = "YUV4MPEG2 W352 H288 F30:1 Ip Cmono\n"; const char kExpectedY4mHeader10bitMonochrome[] = "YUV4MPEG2 W352 H288 F30:1 Ip Cmono10\n"; // Note: These are non-const because DecoderBuffer.plane is non-const. char fake_plane0[] = "PLANE0\n"; char fake_plane1[] = "PLANE1\n"; char fake_plane2[] = "PLANE2\n"; constexpr size_t kExpectedRawDataBufferCount = 3; const char* kExpectedRawData[kExpectedRawDataBufferCount] = { fake_plane0, fake_plane1, fake_plane2}; const char* const kExpectedRawDataMonochrome = fake_plane0; constexpr size_t kExpectedY4mDataBufferCount = 5; const char* const kExpectedY4mFileData8bit[kExpectedY4mDataBufferCount] = { kExpectedY4mHeader8bit, "FRAME\n", fake_plane0, fake_plane1, fake_plane2}; const char* const kExpectedY4mFileData10bit[kExpectedY4mDataBufferCount] = { kExpectedY4mHeader10bit, "FRAME\n", fake_plane0, fake_plane1, fake_plane2}; constexpr size_t kExpectedY4mDataBufferCountMonochrome = 3; const char* const kExpectedY4mFileData8bitMonochrome[kExpectedY4mDataBufferCountMonochrome] = {kExpectedY4mHeader8bitMonochrome, "FRAME\n", fake_plane0}; const char* const kExpectedY4mFileData10bitMonochrome[kExpectedY4mDataBufferCountMonochrome] = {kExpectedY4mHeader10bitMonochrome, "FRAME\n", fake_plane0}; // TODO(tomfinegan): Add a bitdepth arg, and test writing 10 bit frame buffers. std::unique_ptr GetFakeDecoderBuffer(ImageFormat image_format) { auto buffer = absl::WrapUnique(new (std::nothrow) DecoderBuffer); if (buffer == nullptr) return nullptr; buffer->chroma_sample_position = kChromaSamplePositionUnknown; buffer->image_format = image_format; buffer->bitdepth = 8; buffer->displayed_width[0] = static_cast(strlen(fake_plane0)); buffer->displayed_width[1] = static_cast(strlen(fake_plane1)); buffer->displayed_width[2] = static_cast(strlen(fake_plane2)); buffer->displayed_height[0] = 1; buffer->displayed_height[1] = 1; buffer->displayed_height[2] = 1; buffer->stride[0] = static_cast(strlen(fake_plane0)); buffer->stride[1] = static_cast(strlen(fake_plane1)); buffer->stride[2] = static_cast(strlen(fake_plane2)); buffer->plane[0] = reinterpret_cast(fake_plane0); buffer->plane[1] = reinterpret_cast(fake_plane1); buffer->plane[2] = reinterpret_cast(fake_plane2); buffer->user_private_data = 0; buffer->buffer_private_data = nullptr; return buffer; } TEST(FileWriterTest, FailOpen) { EXPECT_EQ(FileWriter::Open(test_utils::GetTestOutputFilePath("fail_open"), static_cast(3), nullptr), nullptr); EXPECT_EQ(FileWriter::Open(test_utils::GetTestOutputFilePath("fail_open"), FileWriter::kFileTypeY4m, nullptr), nullptr); } struct FileWriterY4mHeaderTestParameters { FileWriterY4mHeaderTestParameters() = default; FileWriterY4mHeaderTestParameters(const FileWriterY4mHeaderTestParameters&) = default; FileWriterY4mHeaderTestParameters& operator=( const FileWriterY4mHeaderTestParameters&) = default; FileWriterY4mHeaderTestParameters(FileWriterY4mHeaderTestParameters&&) = default; FileWriterY4mHeaderTestParameters& operator=( FileWriterY4mHeaderTestParameters&&) = default; ~FileWriterY4mHeaderTestParameters() = default; FileWriterY4mHeaderTestParameters(std::string file_name, ChromaSamplePosition chroma_sample_position, ImageFormat image_format, int bitdepth, const char* expected_header_string) : file_name(std::move(file_name)), chroma_sample_position(chroma_sample_position), image_format(image_format), bitdepth(bitdepth), expected_header_string(expected_header_string) {} std::string file_name; ChromaSamplePosition chroma_sample_position = kChromaSamplePositionUnknown; ImageFormat image_format = kImageFormatMonochrome400; int bitdepth = 8; const char* expected_header_string = nullptr; }; std::ostream& operator<<(std::ostream& stream, const FileWriterY4mHeaderTestParameters& parameters) { stream << "file_name=" << parameters.file_name << "\n" << "chroma_sample_position=" << parameters.chroma_sample_position << "\n" << "image_format=" << parameters.image_format << "\n" << "bitdepth=" << parameters.bitdepth << "\n" << "expected_header_string=" << parameters.expected_header_string << "\n"; return stream; } class FileWriterY4mHeaderTest : public testing::TestWithParam { public: FileWriterY4mHeaderTest() { test_parameters_ = GetParam(); y4m_parameters_.width = 352; y4m_parameters_.height = 288; y4m_parameters_.frame_rate_numerator = 30; y4m_parameters_.frame_rate_denominator = 1; y4m_parameters_.chroma_sample_position = test_parameters_.chroma_sample_position; y4m_parameters_.image_format = test_parameters_.image_format; y4m_parameters_.bitdepth = test_parameters_.bitdepth; } FileWriterY4mHeaderTest(const FileWriterY4mHeaderTest&) = delete; FileWriterY4mHeaderTest& operator=(const FileWriterY4mHeaderTest&) = delete; ~FileWriterY4mHeaderTest() override = default; protected: FileWriterY4mHeaderTestParameters test_parameters_; FileWriter::Y4mParameters y4m_parameters_; }; TEST_P(FileWriterY4mHeaderTest, WriteY4mHeader) { const std::string file_name = test_utils::GetTestOutputFilePath(test_parameters_.file_name); EXPECT_NE( FileWriter::Open(file_name, FileWriter::kFileTypeY4m, &y4m_parameters_), nullptr); std::string y4m_header_string; test_utils::GetTestData(test_parameters_.file_name, true, &y4m_header_string); EXPECT_STREQ(y4m_header_string.c_str(), test_parameters_.expected_header_string); } INSTANTIATE_TEST_SUITE_P( WriteY4mHeader, FileWriterY4mHeaderTest, testing::Values( FileWriterY4mHeaderTestParameters( "y4m_header_8bit", kChromaSamplePositionUnknown, kImageFormatYuv420, /*bitdepth=*/8, kExpectedY4mHeader8bit), FileWriterY4mHeaderTestParameters("y4m_header_10bit", kChromaSamplePositionUnknown, kImageFormatYuv420, /*bitdepth=*/10, kExpectedY4mHeader10bit), FileWriterY4mHeaderTestParameters("y4m_header_8bit_monochrome", kChromaSamplePositionUnknown, kImageFormatMonochrome400, /*bitdepth=*/8, kExpectedY4mHeader8bitMonochrome), FileWriterY4mHeaderTestParameters("y4m_header_10bit_monochrome", kChromaSamplePositionUnknown, kImageFormatMonochrome400, /*bitdepth=*/10, kExpectedY4mHeader10bitMonochrome))); struct FileWriterTestParameters { FileWriterTestParameters() = default; FileWriterTestParameters(const FileWriterTestParameters&) = default; FileWriterTestParameters& operator=(const FileWriterTestParameters&) = default; FileWriterTestParameters(FileWriterTestParameters&&) = default; FileWriterTestParameters& operator=(FileWriterTestParameters&&) = default; ~FileWriterTestParameters() = default; FileWriterTestParameters(std::string file_name, FileWriter::FileType file_type, const FileWriter::Y4mParameters* y4m_parameters, size_t num_frames) : file_name(std::move(file_name)), file_type(file_type), y4m_parameters(y4m_parameters), num_frames(num_frames) {} std::string file_name; FileWriter::FileType file_type = FileWriter::kFileTypeRaw; const FileWriter::Y4mParameters* y4m_parameters = nullptr; size_t num_frames = 1; }; std::ostream& operator<<(std::ostream& stream, const ChromaSamplePosition& position) { switch (position) { case kChromaSamplePositionUnknown: stream << "kCromaSamplePositionUnknown"; break; case kChromaSamplePositionVertical: stream << "kChromaSamplePositionVertical"; break; case kChromaSamplePositionColocated: stream << "kChromaSamplePositionColocated"; break; case kChromaSamplePositionReserved: stream << "kChromaSamplePositionReserved"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const ImageFormat& image_format) { switch (image_format) { case kImageFormatMonochrome400: stream << "kImageFormatMonochrome400"; break; case kImageFormatYuv420: stream << "kImageFormatYuv420"; break; case kImageFormatYuv422: stream << "kImageFormatYuv422"; break; case kImageFormatYuv444: stream << "kImageFormatYuv444"; break; } return stream; } std::ostream& operator<<(std::ostream& stream, const FileWriter::Y4mParameters& parameters) { stream << "y4m_parameters:\n" << " width=" << parameters.width << "\n" << " height=" << parameters.height << "\n" << " frame_rate_numerator=" << parameters.frame_rate_numerator << "\n" << " frame_rate_denominator=" << parameters.frame_rate_denominator << "\n" << " chroma_sample_position=" << parameters.chroma_sample_position << "\n" << " image_format=" << parameters.image_format << "\n" << " bitdepth=" << parameters.bitdepth << "\n"; return stream; } std::ostream& operator<<(std::ostream& stream, const FileWriterTestParameters& parameters) { stream << "file_name=" << parameters.file_name << "\n" << "file_type=" << (parameters.file_type == FileWriter::kFileTypeRaw ? "kFileTypeRaw" : "kFileTypeY4m") << "\n"; if (parameters.y4m_parameters != nullptr) { stream << *parameters.y4m_parameters; } else { stream << "y4m_parameters: \n"; } stream << "num_frames=" << parameters.num_frames << "\n"; return stream; } class FileWriterTestBase : public testing::TestWithParam { public: FileWriterTestBase() = default; FileWriterTestBase(const FileWriterTestBase&) = delete; FileWriterTestBase& operator=(const FileWriterTestBase&) = delete; ~FileWriterTestBase() override = default; protected: void SetUp() override { OpenWriter(GetParam()); } void OpenWriter(const FileWriterTestParameters& parameters) { parameters_ = parameters; parameters_.file_name = parameters.file_name; file_writer_ = FileWriter::Open( test_utils::GetTestOutputFilePath(parameters.file_name), parameters_.file_type, parameters_.y4m_parameters); ASSERT_NE(file_writer_, nullptr); } void WriteFramesAndCloseFile() { if (parameters_.y4m_parameters != nullptr) { image_format_ = parameters_.y4m_parameters->image_format; } decoder_buffer_ = GetFakeDecoderBuffer(image_format_); for (size_t frame_num = 0; frame_num < parameters_.num_frames; ++frame_num) { ASSERT_TRUE(file_writer_->WriteFrame(*decoder_buffer_)); } file_writer_ = nullptr; } ImageFormat image_format_ = kImageFormatYuv420; FileWriterTestParameters parameters_; std::unique_ptr file_writer_; std::unique_ptr decoder_buffer_; }; class FileWriterTestRaw : public FileWriterTestBase { public: FileWriterTestRaw() = default; FileWriterTestRaw(const FileWriterTestRaw&) = delete; FileWriterTestRaw& operator=(const FileWriterTestRaw&) = delete; ~FileWriterTestRaw() override = default; protected: void SetUp() override { FileWriterTestBase::SetUp(); } }; class FileWriterTestY4m : public FileWriterTestBase { public: FileWriterTestY4m() = default; FileWriterTestY4m(const FileWriterTestY4m&) = delete; FileWriterTestY4m& operator=(const FileWriterTestY4m&) = delete; ~FileWriterTestY4m() override = default; protected: void SetUp() override { FileWriterTestBase::SetUp(); } }; TEST_P(FileWriterTestRaw, WriteRawFrames) { WriteFramesAndCloseFile(); std::string actual_file_data; test_utils::GetTestData(parameters_.file_name, true, &actual_file_data); std::string expected_file_data; for (size_t frame_num = 0; frame_num < parameters_.num_frames; ++frame_num) { if (image_format_ == kImageFormatMonochrome400) { expected_file_data += kExpectedRawDataMonochrome; } else { for (const auto& buffer : kExpectedRawData) { expected_file_data += buffer; } } } ASSERT_EQ(actual_file_data, expected_file_data); } TEST_P(FileWriterTestY4m, WriteY4mFrames) { WriteFramesAndCloseFile(); std::string actual_file_data; test_utils::GetTestData(parameters_.file_name, true, &actual_file_data); std::string expected_file_data; for (size_t frame_num = 0; frame_num < parameters_.num_frames; ++frame_num) { if (image_format_ == kImageFormatMonochrome400) { const char* const* expected_data_planes = (parameters_.y4m_parameters->bitdepth == 8) ? kExpectedY4mFileData8bitMonochrome : kExpectedY4mFileData10bitMonochrome; // Skip the Y4M file header "plane" after frame 0. for (size_t buffer_num = (frame_num == 0) ? 0 : 1; buffer_num < kExpectedY4mDataBufferCountMonochrome; ++buffer_num) { expected_file_data += expected_data_planes[buffer_num]; } } else { const char* const* expected_data_planes = (parameters_.y4m_parameters->bitdepth == 8) ? kExpectedY4mFileData8bit : kExpectedY4mFileData10bit; // Skip the Y4M file header "plane" after frame 0. for (size_t buffer_num = (frame_num == 0) ? 0 : 1; buffer_num < kExpectedY4mDataBufferCount; ++buffer_num) { expected_file_data += expected_data_planes[buffer_num]; } } } ASSERT_EQ(actual_file_data, expected_file_data); } INSTANTIATE_TEST_SUITE_P( WriteRawFrames, FileWriterTestRaw, testing::Values( FileWriterTestParameters("raw_frames_test_1frame", FileWriter::kFileTypeRaw, /*y4m_parameters=*/nullptr, /*num_frames=*/1), FileWriterTestParameters("raw_frames_test_5frames", FileWriter::kFileTypeRaw, /*y4m_parameters=*/nullptr, /*num_frames=*/5), FileWriterTestParameters("raw_frames_test_1frame_monochrome", FileWriter::kFileTypeRaw, /*y4m_parameters=*/nullptr, /*num_frames=*/1), FileWriterTestParameters("raw_frames_test_5frames_monochrome", FileWriter::kFileTypeRaw, /*y4m_parameters=*/nullptr, /*num_frames=*/5))); const FileWriter::Y4mParameters kY4mParameters8Bit = { 352, // width 288, // height 30, // frame_rate_numerator 1, // frame_rate_denominator kChromaSamplePositionUnknown, kImageFormatYuv420, 8 // bitdepth }; const FileWriter::Y4mParameters kY4mParameters10Bit = { 352, // width 288, // height 30, // frame_rate_numerator 1, // frame_rate_denominator kChromaSamplePositionUnknown, kImageFormatYuv420, 10 // bitdepth }; const FileWriter::Y4mParameters kY4mParameters8BitMonochrome = { 352, // width 288, // height 30, // frame_rate_numerator 1, // frame_rate_denominator kChromaSamplePositionUnknown, kImageFormatMonochrome400, 8 // bitdepth }; const FileWriter::Y4mParameters kY4mParameters10BitMonochrome = { 352, // width 288, // height 30, // frame_rate_numerator 1, // frame_rate_denominator kChromaSamplePositionUnknown, kImageFormatMonochrome400, 10 // bitdepth }; INSTANTIATE_TEST_SUITE_P( WriteY4mFrames, FileWriterTestY4m, testing::Values( FileWriterTestParameters("y4m_frames_test_8bit_1frame", FileWriter::kFileTypeY4m, &kY4mParameters8Bit, /*num_frames=*/1), FileWriterTestParameters("y4m_frames_test_8bit_5frames", FileWriter::kFileTypeY4m, &kY4mParameters8Bit, /*num_frames=*/5), FileWriterTestParameters("y4m_frames_test_10bit_1frame", FileWriter::kFileTypeY4m, &kY4mParameters10Bit, /*num_frames=*/1), FileWriterTestParameters("y4m_frames_test_10bit_5frames", FileWriter::kFileTypeY4m, &kY4mParameters10Bit, /*num_frames=*/5), FileWriterTestParameters("y4m_frames_test_8bit_1frame_monochrome", FileWriter::kFileTypeY4m, &kY4mParameters8BitMonochrome, /*num_frames=*/1), FileWriterTestParameters("y4m_frames_test_8bit_5frames_monochrome", FileWriter::kFileTypeY4m, &kY4mParameters8BitMonochrome, /*num_frames=*/5), FileWriterTestParameters("y4m_frames_test_10bit_1frame_monochrome", FileWriter::kFileTypeY4m, &kY4mParameters10BitMonochrome, /*num_frames=*/1), FileWriterTestParameters("y4m_frames_test_10bit_5frames_monochrome", FileWriter::kFileTypeY4m, &kY4mParameters10BitMonochrome, /*num_frames=*/5))); } // namespace } // namespace libgav1