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