xref: /aosp_15_r20/external/libgav1/examples/file_writer.cc (revision 095378508e87ed692bf8dfeb34008b65b3735891)
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