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