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