• 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 <cerrno>
16 #include <cstddef>
17 #include <cstdint>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <deque>
22 #include <memory>
23 #include <new>
24 #include <vector>
25 
26 #include "absl/strings/numbers.h"
27 #include "absl/time/clock.h"
28 #include "absl/time/time.h"
29 #include "examples/file_reader_factory.h"
30 #include "examples/file_reader_interface.h"
31 #include "examples/file_writer.h"
32 #include "gav1/decoder.h"
33 
34 #ifdef GAV1_DECODE_USE_CV_PIXEL_BUFFER_POOL
35 #include "examples/gav1_decode_cv_pixel_buffer_pool.h"
36 #endif
37 
38 namespace {
39 
40 struct Options {
41   const char* input_file_name = nullptr;
42   const char* output_file_name = nullptr;
43   const char* frame_timing_file_name = nullptr;
44   libgav1::FileWriter::FileType output_file_type =
45       libgav1::FileWriter::kFileTypeRaw;
46   uint8_t post_filter_mask = 0x1f;
47   int threads = 1;
48   bool frame_parallel = false;
49   bool output_all_layers = false;
50   int operating_point = 0;
51   int limit = 0;
52   int skip = 0;
53   int verbose = 0;
54 };
55 
56 struct Timing {
57   absl::Duration input;
58   absl::Duration dequeue;
59 };
60 
61 struct FrameTiming {
62   absl::Time enqueue;
63   absl::Time dequeue;
64 };
65 
PrintHelp(FILE * const fout)66 void PrintHelp(FILE* const fout) {
67   fprintf(fout,
68           "Usage: gav1_decode [options] <input file>"
69           " [-o <output file>]\n");
70   fprintf(fout, "\n");
71   fprintf(fout, "Options:\n");
72   fprintf(fout, "  -h, --help This help message.\n");
73   fprintf(fout, "  --threads <positive integer> (Default 1).\n");
74   fprintf(fout, "  --frame_parallel.\n");
75   fprintf(fout,
76           "  --limit <integer> Stop decoding after N frames (0 = all).\n");
77   fprintf(fout, "  --skip <integer> Skip initial N frames (Default 0).\n");
78   fprintf(fout, "  --version.\n");
79   fprintf(fout, "  --y4m (Default false).\n");
80   fprintf(fout, "  --raw (Default true).\n");
81   fprintf(fout, "  -v logging verbosity, can be used multiple times.\n");
82   fprintf(fout, "  --all_layers.\n");
83   fprintf(fout,
84           "  --operating_point <integer between 0 and 31> (Default 0).\n");
85   fprintf(fout,
86           "  --frame_timing <file> Output per-frame timing to <file> in tsv"
87           " format.\n   Yields meaningful results only when frame parallel is"
88           " off.\n");
89   fprintf(fout, "\nAdvanced settings:\n");
90   fprintf(fout, "  --post_filter_mask <integer> (Default 0x1f).\n");
91   fprintf(fout,
92           "   Mask indicating which post filters should be applied to the"
93           " reconstructed\n   frame. This may be given as octal, decimal or"
94           " hexadecimal. From LSB:\n");
95   fprintf(fout, "     Bit 0: Loop filter (deblocking filter)\n");
96   fprintf(fout, "     Bit 1: Cdef\n");
97   fprintf(fout, "     Bit 2: SuperRes\n");
98   fprintf(fout, "     Bit 3: Loop Restoration\n");
99   fprintf(fout, "     Bit 4: Film Grain Synthesis\n");
100 }
101 
ParseOptions(int argc,char * argv[],Options * const options)102 void ParseOptions(int argc, char* argv[], Options* const options) {
103   for (int i = 1; i < argc; ++i) {
104     int32_t value;
105     if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
106       PrintHelp(stdout);
107       exit(EXIT_SUCCESS);
108     } else if (strcmp(argv[i], "-o") == 0) {
109       if (++i >= argc) {
110         fprintf(stderr, "Missing argument for '-o'\n");
111         PrintHelp(stderr);
112         exit(EXIT_FAILURE);
113       }
114       options->output_file_name = argv[i];
115     } else if (strcmp(argv[i], "--frame_timing") == 0) {
116       if (++i >= argc) {
117         fprintf(stderr, "Missing argument for '--frame_timing'\n");
118         PrintHelp(stderr);
119         exit(EXIT_FAILURE);
120       }
121       options->frame_timing_file_name = argv[i];
122     } else if (strcmp(argv[i], "--version") == 0) {
123       printf("gav1_decode, a libgav1 based AV1 decoder\n");
124       printf("libgav1 %s\n", libgav1::GetVersionString());
125       printf("max bitdepth: %d\n", libgav1::Decoder::GetMaxBitdepth());
126       printf("build configuration: %s\n", libgav1::GetBuildConfiguration());
127       exit(EXIT_SUCCESS);
128     } else if (strcmp(argv[i], "-v") == 0) {
129       ++options->verbose;
130     } else if (strcmp(argv[i], "--raw") == 0) {
131       options->output_file_type = libgav1::FileWriter::kFileTypeRaw;
132     } else if (strcmp(argv[i], "--y4m") == 0) {
133       options->output_file_type = libgav1::FileWriter::kFileTypeY4m;
134     } else if (strcmp(argv[i], "--threads") == 0) {
135       if (++i >= argc || !absl::SimpleAtoi(argv[i], &value)) {
136         fprintf(stderr, "Missing/Invalid value for --threads.\n");
137         PrintHelp(stderr);
138         exit(EXIT_FAILURE);
139       }
140       options->threads = value;
141     } else if (strcmp(argv[i], "--frame_parallel") == 0) {
142       options->frame_parallel = true;
143     } else if (strcmp(argv[i], "--all_layers") == 0) {
144       options->output_all_layers = true;
145     } else if (strcmp(argv[i], "--operating_point") == 0) {
146       if (++i >= argc || !absl::SimpleAtoi(argv[i], &value) || value < 0 ||
147           value >= 32) {
148         fprintf(stderr, "Missing/Invalid value for --operating_point.\n");
149         PrintHelp(stderr);
150         exit(EXIT_FAILURE);
151       }
152       options->operating_point = value;
153     } else if (strcmp(argv[i], "--limit") == 0) {
154       if (++i >= argc || !absl::SimpleAtoi(argv[i], &value) || value < 0) {
155         fprintf(stderr, "Missing/Invalid value for --limit.\n");
156         PrintHelp(stderr);
157         exit(EXIT_FAILURE);
158       }
159       options->limit = value;
160     } else if (strcmp(argv[i], "--skip") == 0) {
161       if (++i >= argc || !absl::SimpleAtoi(argv[i], &value) || value < 0) {
162         fprintf(stderr, "Missing/Invalid value for --skip.\n");
163         PrintHelp(stderr);
164         exit(EXIT_FAILURE);
165       }
166       options->skip = value;
167     } else if (strcmp(argv[i], "--post_filter_mask") == 0) {
168       errno = 0;
169       char* endptr = nullptr;
170       value = (++i >= argc) ? -1
171                             // NOLINTNEXTLINE(runtime/deprecated_fn)
172                             : static_cast<int32_t>(strtol(argv[i], &endptr, 0));
173       // Only the last 5 bits of the mask can be set.
174       if ((value & ~31) != 0 || errno != 0 || endptr == argv[i]) {
175         fprintf(stderr, "Invalid value for --post_filter_mask.\n");
176         PrintHelp(stderr);
177         exit(EXIT_FAILURE);
178       }
179       options->post_filter_mask = value;
180     } else if (strlen(argv[i]) > 1 && argv[i][0] == '-') {
181       fprintf(stderr, "Unknown option '%s'!\n", argv[i]);
182       exit(EXIT_FAILURE);
183     } else {
184       if (options->input_file_name == nullptr) {
185         options->input_file_name = argv[i];
186       } else {
187         fprintf(stderr, "Found invalid parameter: \"%s\".\n", argv[i]);
188         PrintHelp(stderr);
189         exit(EXIT_FAILURE);
190       }
191     }
192   }
193 
194   if (argc < 2 || options->input_file_name == nullptr) {
195     fprintf(stderr, "Input file is required!\n");
196     PrintHelp(stderr);
197     exit(EXIT_FAILURE);
198   }
199 }
200 
201 using InputBuffer = std::vector<uint8_t>;
202 
203 class InputBuffers {
204  public:
~InputBuffers()205   ~InputBuffers() {
206     for (auto buffer : free_buffers_) {
207       delete buffer;
208     }
209   }
GetFreeBuffer()210   InputBuffer* GetFreeBuffer() {
211     if (free_buffers_.empty()) {
212       auto* const buffer = new (std::nothrow) InputBuffer();
213       if (buffer == nullptr) {
214         fprintf(stderr, "Failed to create input buffer.\n");
215         return nullptr;
216       }
217       free_buffers_.push_back(buffer);
218     }
219     InputBuffer* const buffer = free_buffers_.front();
220     free_buffers_.pop_front();
221     return buffer;
222   }
223 
ReleaseInputBuffer(InputBuffer * buffer)224   void ReleaseInputBuffer(InputBuffer* buffer) {
225     free_buffers_.push_back(buffer);
226   }
227 
228  private:
229   std::deque<InputBuffer*> free_buffers_;
230 };
231 
ReleaseInputBuffer(void * callback_private_data,void * buffer_private_data)232 void ReleaseInputBuffer(void* callback_private_data,
233                         void* buffer_private_data) {
234   auto* const input_buffers = static_cast<InputBuffers*>(callback_private_data);
235   input_buffers->ReleaseInputBuffer(
236       static_cast<InputBuffer*>(buffer_private_data));
237 }
238 
CloseFile(FILE * stream)239 int CloseFile(FILE* stream) { return (stream == nullptr) ? 0 : fclose(stream); }
240 
241 }  // namespace
242 
main(int argc,char * argv[])243 int main(int argc, char* argv[]) {
244   Options options;
245   ParseOptions(argc, argv, &options);
246 
247   auto file_reader =
248       libgav1::FileReaderFactory::OpenReader(options.input_file_name);
249   if (file_reader == nullptr) {
250     fprintf(stderr, "Cannot open input file!\n");
251     return EXIT_FAILURE;
252   }
253 
254   std::unique_ptr<FILE, decltype(&CloseFile)> frame_timing_file(nullptr,
255                                                                 &CloseFile);
256   if (options.frame_timing_file_name != nullptr) {
257     frame_timing_file.reset(fopen(options.frame_timing_file_name, "wb"));
258     if (frame_timing_file == nullptr) {
259       fprintf(stderr, "Cannot open frame timing file '%s'!\n",
260               options.frame_timing_file_name);
261       return EXIT_FAILURE;
262     }
263   }
264 
265 #ifdef GAV1_DECODE_USE_CV_PIXEL_BUFFER_POOL
266   // Reference frames + 1 scratch frame (for either the current frame or the
267   // film grain frame).
268   constexpr int kNumBuffers = 8 + 1;
269   std::unique_ptr<Gav1DecodeCVPixelBufferPool> cv_pixel_buffers =
270       Gav1DecodeCVPixelBufferPool::Create(kNumBuffers);
271   if (cv_pixel_buffers == nullptr) {
272     fprintf(stderr, "Cannot create Gav1DecodeCVPixelBufferPool!\n");
273     return EXIT_FAILURE;
274   }
275 #endif
276 
277   InputBuffers input_buffers;
278   libgav1::Decoder decoder;
279   libgav1::DecoderSettings settings;
280   settings.post_filter_mask = options.post_filter_mask;
281   settings.threads = options.threads;
282   settings.frame_parallel = options.frame_parallel;
283   settings.output_all_layers = options.output_all_layers;
284   settings.operating_point = options.operating_point;
285   settings.blocking_dequeue = true;
286   settings.callback_private_data = &input_buffers;
287   settings.release_input_buffer = ReleaseInputBuffer;
288 #ifdef GAV1_DECODE_USE_CV_PIXEL_BUFFER_POOL
289   settings.on_frame_buffer_size_changed = Gav1DecodeOnCVPixelBufferSizeChanged;
290   settings.get_frame_buffer = Gav1DecodeGetCVPixelBuffer;
291   settings.release_frame_buffer = Gav1DecodeReleaseCVPixelBuffer;
292   settings.callback_private_data = cv_pixel_buffers.get();
293   settings.release_input_buffer = nullptr;
294   // TODO(vigneshv): Support frame parallel mode to be used with
295   // CVPixelBufferPool.
296   settings.frame_parallel = false;
297 #endif
298   libgav1::StatusCode status = decoder.Init(&settings);
299   if (status != libgav1::kStatusOk) {
300     fprintf(stderr, "Error initializing decoder: %s\n",
301             libgav1::GetErrorString(status));
302     return EXIT_FAILURE;
303   }
304 
305   fprintf(stderr, "decoding '%s'\n", options.input_file_name);
306   if (options.verbose > 0 && options.skip > 0) {
307     fprintf(stderr, "skipping %d frame(s).\n", options.skip);
308   }
309 
310   int input_frames = 0;
311   int decoded_frames = 0;
312   Timing timing = {};
313   std::vector<FrameTiming> frame_timing;
314   const bool record_frame_timing = frame_timing_file != nullptr;
315   std::unique_ptr<libgav1::FileWriter> file_writer;
316   InputBuffer* input_buffer = nullptr;
317   bool limit_reached = false;
318   bool dequeue_finished = false;
319   const absl::Time decode_loop_start = absl::Now();
320   do {
321     if (input_buffer == nullptr && !file_reader->IsEndOfFile() &&
322         !limit_reached) {
323       input_buffer = input_buffers.GetFreeBuffer();
324       if (input_buffer == nullptr) return EXIT_FAILURE;
325       const absl::Time read_start = absl::Now();
326       if (!file_reader->ReadTemporalUnit(input_buffer,
327                                          /*timestamp=*/nullptr)) {
328         fprintf(stderr, "Error reading input file.\n");
329         return EXIT_FAILURE;
330       }
331       timing.input += absl::Now() - read_start;
332     }
333 
334     if (++input_frames <= options.skip) {
335       input_buffers.ReleaseInputBuffer(input_buffer);
336       input_buffer = nullptr;
337       continue;
338     }
339 
340     if (input_buffer != nullptr) {
341       if (input_buffer->empty()) {
342         input_buffers.ReleaseInputBuffer(input_buffer);
343         input_buffer = nullptr;
344         continue;
345       }
346 
347       const absl::Time enqueue_start = absl::Now();
348       status = decoder.EnqueueFrame(input_buffer->data(), input_buffer->size(),
349                                     static_cast<int64_t>(frame_timing.size()),
350                                     /*buffer_private_data=*/input_buffer);
351       if (status == libgav1::kStatusOk) {
352         if (options.verbose > 1) {
353           fprintf(stderr, "enqueue frame (length %zu)\n", input_buffer->size());
354         }
355         if (record_frame_timing) {
356           FrameTiming enqueue_time = {enqueue_start, absl::UnixEpoch()};
357           frame_timing.emplace_back(enqueue_time);
358         }
359 
360         input_buffer = nullptr;
361         // Continue to enqueue frames until we get a kStatusTryAgain status.
362         continue;
363       }
364       if (status != libgav1::kStatusTryAgain) {
365         fprintf(stderr, "Unable to enqueue frame: %s\n",
366                 libgav1::GetErrorString(status));
367         return EXIT_FAILURE;
368       }
369     }
370 
371     const libgav1::DecoderBuffer* buffer;
372     status = decoder.DequeueFrame(&buffer);
373     if (status == libgav1::kStatusNothingToDequeue) {
374       dequeue_finished = true;
375       continue;
376     }
377     if (status != libgav1::kStatusOk) {
378       fprintf(stderr, "Unable to dequeue frame: %s\n",
379               libgav1::GetErrorString(status));
380       return EXIT_FAILURE;
381     }
382     dequeue_finished = false;
383     if (buffer == nullptr) continue;
384     ++decoded_frames;
385     if (options.verbose > 1) {
386       fprintf(stderr, "buffer dequeued\n");
387     }
388 
389     if (record_frame_timing) {
390       frame_timing[static_cast<int>(buffer->user_private_data)].dequeue =
391           absl::Now();
392     }
393 
394     if (options.output_file_name != nullptr && file_writer == nullptr) {
395       libgav1::FileWriter::Y4mParameters y4m_parameters;
396       y4m_parameters.width = buffer->displayed_width[0];
397       y4m_parameters.height = buffer->displayed_height[0];
398       y4m_parameters.frame_rate_numerator = file_reader->frame_rate();
399       y4m_parameters.frame_rate_denominator = file_reader->time_scale();
400       y4m_parameters.chroma_sample_position = buffer->chroma_sample_position;
401       y4m_parameters.image_format = buffer->image_format;
402       y4m_parameters.bitdepth = static_cast<size_t>(buffer->bitdepth);
403       file_writer = libgav1::FileWriter::Open(
404           options.output_file_name, options.output_file_type, &y4m_parameters);
405       if (file_writer == nullptr) {
406         fprintf(stderr, "Cannot open output file!\n");
407         return EXIT_FAILURE;
408       }
409     }
410 
411     if (!limit_reached && file_writer != nullptr &&
412         !file_writer->WriteFrame(*buffer)) {
413       fprintf(stderr, "Error writing output file.\n");
414       return EXIT_FAILURE;
415     }
416     if (options.limit > 0 && options.limit == decoded_frames) {
417       limit_reached = true;
418       if (input_buffer != nullptr) {
419         input_buffers.ReleaseInputBuffer(input_buffer);
420       }
421       input_buffer = nullptr;
422       // Clear any in progress frames to ensure the output frame limit is
423       // respected.
424       decoder.SignalEOS();
425     }
426   } while (input_buffer != nullptr ||
427            (!file_reader->IsEndOfFile() && !limit_reached) ||
428            !dequeue_finished);
429   timing.dequeue = absl::Now() - decode_loop_start - timing.input;
430 
431   if (record_frame_timing) {
432     // Note timing for frame parallel will be skewed by the time spent queueing
433     // additional frames and in the output queue waiting for previous frames,
434     // the values reported won't be that meaningful.
435     fprintf(frame_timing_file.get(), "frame number\tdecode time us\n");
436     for (size_t i = 0; i < frame_timing.size(); ++i) {
437       const int decode_time_us = static_cast<int>(absl::ToInt64Microseconds(
438           frame_timing[i].dequeue - frame_timing[i].enqueue));
439       fprintf(frame_timing_file.get(), "%zu\t%d\n", i, decode_time_us);
440     }
441   }
442 
443   if (options.verbose > 0) {
444     fprintf(stderr, "time to read input: %d us\n",
445             static_cast<int>(absl::ToInt64Microseconds(timing.input)));
446     const int decode_time_us =
447         static_cast<int>(absl::ToInt64Microseconds(timing.dequeue));
448     const double decode_fps =
449         (decode_time_us == 0) ? 0.0 : 1.0e6 * decoded_frames / decode_time_us;
450     fprintf(stderr, "time to decode input: %d us (%d frames, %.2f fps)\n",
451             decode_time_us, decoded_frames, decode_fps);
452   }
453 
454   return EXIT_SUCCESS;
455 }
456