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