1 // Copyright 2019 The libgav1 Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "examples/file_writer.h"
16
17 #include <cerrno>
18 #include <cstdio>
19 #include <cstring>
20 #include <new>
21 #include <string>
22
23 #if defined(_WIN32)
24 #include <fcntl.h>
25 #include <io.h>
26 #endif
27
28 #include "absl/memory/memory.h"
29 #include "absl/strings/str_format.h"
30 #include "examples/logging.h"
31
32 namespace libgav1 {
33 namespace {
34
SetBinaryMode(FILE * stream)35 FILE* SetBinaryMode(FILE* stream) {
36 #if defined(_WIN32)
37 _setmode(_fileno(stream), _O_BINARY);
38 #endif
39 return stream;
40 }
41
GetY4mColorSpaceString(const FileWriter::Y4mParameters & y4m_parameters)42 std::string GetY4mColorSpaceString(
43 const FileWriter::Y4mParameters& y4m_parameters) {
44 std::string color_space_string;
45 switch (y4m_parameters.image_format) {
46 case kImageFormatMonochrome400:
47 color_space_string = "mono";
48 break;
49 case kImageFormatYuv420:
50 if (y4m_parameters.bitdepth == 8) {
51 if (y4m_parameters.chroma_sample_position ==
52 kChromaSamplePositionVertical) {
53 color_space_string = "420mpeg2";
54 } else if (y4m_parameters.chroma_sample_position ==
55 kChromaSamplePositionColocated) {
56 color_space_string = "420";
57 } else {
58 color_space_string = "420jpeg";
59 }
60 } else {
61 color_space_string = "420";
62 }
63 break;
64 case kImageFormatYuv422:
65 color_space_string = "422";
66 break;
67 case kImageFormatYuv444:
68 color_space_string = "444";
69 break;
70 }
71
72 if (y4m_parameters.bitdepth > 8) {
73 const bool monochrome =
74 y4m_parameters.image_format == kImageFormatMonochrome400;
75 color_space_string =
76 absl::StrFormat("%s%s%d", color_space_string, monochrome ? "" : "p",
77 y4m_parameters.bitdepth);
78 }
79
80 return color_space_string;
81 }
82
83 } // namespace
84
~FileWriter()85 FileWriter::~FileWriter() { fclose(file_); }
86
Open(absl::string_view file_name,FileType file_type,const Y4mParameters * const y4m_parameters)87 std::unique_ptr<FileWriter> FileWriter::Open(
88 absl::string_view file_name, FileType file_type,
89 const Y4mParameters* const y4m_parameters) {
90 if (file_name.empty() ||
91 (file_type == kFileTypeY4m && y4m_parameters == nullptr) ||
92 (file_type != kFileTypeRaw && file_type != kFileTypeY4m)) {
93 LIBGAV1_EXAMPLES_LOG_ERROR("Invalid parameters");
94 return nullptr;
95 }
96
97 const std::string fopen_file_name = std::string(file_name);
98 FILE* raw_file_ptr;
99
100 if (file_name == "-") {
101 raw_file_ptr = SetBinaryMode(stdout);
102 } else {
103 raw_file_ptr = fopen(fopen_file_name.c_str(), "wb");
104 }
105
106 if (raw_file_ptr == nullptr) {
107 LIBGAV1_EXAMPLES_LOG_ERROR("Unable to open output file");
108 return nullptr;
109 }
110
111 auto file = absl::WrapUnique(new (std::nothrow) FileWriter(raw_file_ptr));
112 if (file == nullptr) {
113 LIBGAV1_EXAMPLES_LOG_ERROR("Out of memory");
114 fclose(raw_file_ptr);
115 return nullptr;
116 }
117
118 if (file_type == kFileTypeY4m && !file->WriteY4mFileHeader(*y4m_parameters)) {
119 LIBGAV1_EXAMPLES_LOG_ERROR("Error writing Y4M file header");
120 return nullptr;
121 }
122
123 file->file_type_ = file_type;
124 return file;
125 }
126
WriteFrame(const DecoderBuffer & frame_buffer)127 bool FileWriter::WriteFrame(const DecoderBuffer& frame_buffer) {
128 if (file_type_ == kFileTypeY4m) {
129 const char kY4mFrameHeader[] = "FRAME\n";
130 if (fwrite(kY4mFrameHeader, 1, strlen(kY4mFrameHeader), file_) !=
131 strlen(kY4mFrameHeader)) {
132 LIBGAV1_EXAMPLES_LOG_ERROR("Error writing Y4M frame header");
133 return false;
134 }
135 }
136
137 const size_t pixel_size =
138 (frame_buffer.bitdepth == 8) ? sizeof(uint8_t) : sizeof(uint16_t);
139 for (int plane_index = 0; plane_index < frame_buffer.NumPlanes();
140 ++plane_index) {
141 const int height = frame_buffer.displayed_height[plane_index];
142 const int width = frame_buffer.displayed_width[plane_index];
143 const int stride = frame_buffer.stride[plane_index];
144 const uint8_t* const plane_pointer = frame_buffer.plane[plane_index];
145 for (int row = 0; row < height; ++row) {
146 const uint8_t* const row_pointer = &plane_pointer[row * stride];
147 if (fwrite(row_pointer, pixel_size, width, file_) !=
148 static_cast<size_t>(width)) {
149 char error_string[256];
150 snprintf(error_string, sizeof(error_string),
151 "File write failed: %s (errno=%d)", strerror(errno), errno);
152 LIBGAV1_EXAMPLES_LOG_ERROR(error_string);
153 return false;
154 }
155 }
156 }
157
158 return true;
159 }
160
161 // Writes Y4M file header to |file_| and returns true when successful.
162 //
163 // A Y4M file begins with a plaintext file signature of 'YUV4MPEG2 '.
164 //
165 // Following the signature is any number of optional parameters preceded by a
166 // space. We always write:
167 //
168 // Width: 'W' followed by image width in pixels.
169 // Height: 'H' followed by image height in pixels.
170 // Frame Rate: 'F' followed frames/second in the form numerator:denominator.
171 // Interlacing: 'I' followed by 'p' for progressive.
172 // Color space: 'C' followed by a string representation of the color space.
173 //
174 // More info here: https://wiki.multimedia.cx/index.php/YUV4MPEG2
WriteY4mFileHeader(const Y4mParameters & y4m_parameters)175 bool FileWriter::WriteY4mFileHeader(const Y4mParameters& y4m_parameters) {
176 std::string y4m_header = absl::StrFormat(
177 "YUV4MPEG2 W%zu H%zu F%zu:%zu Ip C%s\n", y4m_parameters.width,
178 y4m_parameters.height, y4m_parameters.frame_rate_numerator,
179 y4m_parameters.frame_rate_denominator,
180 GetY4mColorSpaceString(y4m_parameters));
181 return fwrite(y4m_header.c_str(), 1, y4m_header.length(), file_) ==
182 y4m_header.length();
183 }
184
185 } // namespace libgav1
186