• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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