xref: /aosp_15_r20/external/libdav1d/tools/dav1d_cli_parse.c (revision c09093415860a1c2373dacd84c4fde00c507cdfd)
1 /*
2  * Copyright © 2018, VideoLAN and dav1d authors
3  * Copyright © 2018, Two Orioles, LLC
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice, this
10  *    list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 #include "cli_config.h"
30 
31 #include <getopt.h>
32 #include <limits.h>
33 #include <math.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #if HAVE_UNISTD_H
39 # include <unistd.h>
40 #endif
41 
42 #include "dav1d_cli_parse.h"
43 #include "src/cpu.h"
44 
45 static const char short_opts[] = "i:o:vql:s:";
46 
47 enum {
48     ARG_DEMUXER = 256,
49     ARG_MUXER,
50     ARG_FRAME_TIMES,
51     ARG_REALTIME,
52     ARG_REALTIME_CACHE,
53     ARG_THREADS,
54     ARG_FRAME_DELAY,
55     ARG_VERIFY,
56     ARG_FILM_GRAIN,
57     ARG_OPPOINT,
58     ARG_ALL_LAYERS,
59     ARG_SIZE_LIMIT,
60     ARG_STRICT_STD_COMPLIANCE,
61     ARG_CPU_MASK,
62     ARG_NEG_STRIDE,
63     ARG_OUTPUT_INVISIBLE,
64     ARG_INLOOP_FILTERS,
65     ARG_DECODE_FRAME_TYPE,
66 };
67 
68 static const struct option long_opts[] = {
69     { "input",           1, NULL, 'i' },
70     { "output",          1, NULL, 'o' },
71     { "quiet",           0, NULL, 'q' },
72     { "demuxer",         1, NULL, ARG_DEMUXER },
73     { "muxer",           1, NULL, ARG_MUXER },
74     { "version",         0, NULL, 'v' },
75     { "frametimes",      1, NULL, ARG_FRAME_TIMES },
76     { "limit",           1, NULL, 'l' },
77     { "skip",            1, NULL, 's' },
78     { "realtime",        2, NULL, ARG_REALTIME },
79     { "realtimecache",   1, NULL, ARG_REALTIME_CACHE },
80     { "threads",         1, NULL, ARG_THREADS },
81     { "framedelay",      1, NULL, ARG_FRAME_DELAY },
82     { "verify",          1, NULL, ARG_VERIFY },
83     { "filmgrain",       1, NULL, ARG_FILM_GRAIN },
84     { "oppoint",         1, NULL, ARG_OPPOINT },
85     { "alllayers",       1, NULL, ARG_ALL_LAYERS },
86     { "sizelimit",       1, NULL, ARG_SIZE_LIMIT },
87     { "strict",          1, NULL, ARG_STRICT_STD_COMPLIANCE },
88     { "cpumask",         1, NULL, ARG_CPU_MASK },
89     { "negstride",       0, NULL, ARG_NEG_STRIDE },
90     { "outputinvisible", 1, NULL, ARG_OUTPUT_INVISIBLE },
91     { "inloopfilters",   1, NULL, ARG_INLOOP_FILTERS },
92     { "decodeframetype", 1, NULL, ARG_DECODE_FRAME_TYPE },
93     { NULL,              0, NULL, 0 },
94 };
95 
96 #if HAVE_XXHASH_H
97 #define AVAILABLE_MUXERS "'md5', 'xxh3', 'yuv', 'yuv4mpeg2' or 'null'"
98 #else
99 #define AVAILABLE_MUXERS "'md5', 'yuv', 'yuv4mpeg2' or 'null'"
100 #endif
101 
102 #if ARCH_AARCH64 || ARCH_ARM
103 #define ALLOWED_CPU_MASKS ", 'neon', 'dotprod' or 'i8mm'"
104 #elif ARCH_LOONGARCH
105 #define ALLOWED_CPU_MASKS ", 'lsx' or 'lasx'"
106 #elif ARCH_PPC64LE
107 #define ALLOWED_CPU_MASKS ", 'vsx' or 'pwr9'"
108 #elif ARCH_RISCV
109 #define ALLOWED_CPU_MASKS " or 'rvv'"
110 #elif ARCH_X86
111 #define ALLOWED_CPU_MASKS \
112     ", 'sse2', 'ssse3', 'sse41', 'avx2' or 'avx512icl'"
113 #else
114 #define ALLOWED_CPU_MASKS "not yet implemented for this architecture"
115 #endif
116 
usage(const char * const app,const char * const reason,...)117 static void usage(const char *const app, const char *const reason, ...) {
118     if (reason) {
119         va_list args;
120 
121         va_start(args, reason);
122         vfprintf(stderr, reason, args);
123         va_end(args);
124         fprintf(stderr, "\n\n");
125     }
126     fprintf(stderr, "Usage: %s [options]\n\n", app);
127     fprintf(stderr, "Supported options:\n"
128             " --input/-i $file:     input file\n"
129             " --output/-o $file:    output file (%%n, %%w or %%h will be filled in for per-frame files)\n"
130             " --demuxer $name:      force demuxer type ('ivf', 'section5' or 'annexb'; default: detect from content)\n"
131             " --muxer $name:        force muxer type (" AVAILABLE_MUXERS "; default: detect from extension)\n"
132             "                       use 'frame' as prefix to write per-frame files; if filename contains %%n, will default to writing per-frame files\n"
133             " --quiet/-q:           disable status messages\n"
134             " --frametimes $file:   dump frame times to file\n"
135             " --limit/-l $num:      stop decoding after $num frames\n"
136             " --skip/-s $num:       skip decoding of the first $num frames\n"
137             " --realtime [$fract]:  limit framerate, optional argument to override input framerate\n"
138             " --realtimecache $num: set the size of the cache in realtime mode (default: 0)\n"
139             " --version/-v:         print version and exit\n"
140             " --threads $num:       number of threads (default: 0)\n"
141             " --framedelay $num:    maximum frame delay, capped at $threads (default: 0);\n"
142             "                       set to 1 for low-latency decoding\n"
143             " --filmgrain $num:     enable film grain application (default: 1, except if muxer is md5 or xxh3)\n"
144             " --oppoint $num:       select an operating point of a scalable AV1 bitstream (0 - 31)\n"
145             " --alllayers $num:     output all spatial layers of a scalable AV1 bitstream (default: 1)\n"
146             " --sizelimit $num:     stop decoding if the frame size exceeds the specified limit\n"
147             " --strict $num:        whether to abort decoding on standard compliance violations\n"
148             "                       that don't affect bitstream decoding (default: 1)\n"
149             " --verify $md5:        verify decoded md5. implies --muxer md5, no output\n"
150             " --cpumask $mask:      restrict permitted CPU instruction sets (0" ALLOWED_CPU_MASKS "; default: -1)\n"
151             " --negstride:          use negative picture strides\n"
152             "                       this is mostly meant as a developer option\n"
153             " --outputinvisible $num: whether to output invisible (alt-ref) frames (default: 0)\n"
154             " --inloopfilters $str: which in-loop filters to enable (none, (no)deblock, (no)cdef, (no)restoration or all; default: all)\n"
155             " --decodeframetype $str: which frame types to decode (reference, intra, key or all; default: all)\n"
156             );
157     exit(1);
158 }
159 
error(const char * const app,const char * const optarg,const int option,const char * const shouldbe)160 static void error(const char *const app, const char *const optarg,
161                   const int option, const char *const shouldbe)
162 {
163     char optname[256];
164     int n;
165 
166     for (n = 0; long_opts[n].name; n++)
167         if (long_opts[n].val == option)
168             break;
169     assert(long_opts[n].name);
170     if (long_opts[n].val < 256) {
171         sprintf(optname, "-%c/--%s", long_opts[n].val, long_opts[n].name);
172     } else {
173         sprintf(optname, "--%s", long_opts[n].name);
174     }
175 
176     usage(app, "Invalid argument \"%s\" for option %s; should be %s",
177           optarg, optname, shouldbe);
178 }
179 
parse_unsigned(const char * const optarg,const int option,const char * const app)180 static unsigned parse_unsigned(const char *const optarg, const int option,
181                                const char *const app)
182 {
183     char *end;
184     const unsigned res = (unsigned) strtoul(optarg, &end, 0);
185     if (*end || end == optarg) error(app, optarg, option, "an integer");
186     return res;
187 }
188 
parse_optional_fraction(const char * const optarg,const int option,const char * const app,double * value)189 static int parse_optional_fraction(const char *const optarg, const int option,
190                                    const char *const app, double *value)
191 {
192     if (optarg == NULL) return 0;
193     char *end;
194     *value = strtod(optarg, &end);
195     if (*end == '/' && end != optarg) {
196         const char *optarg2 = end + 1;
197         *value /= strtod(optarg2, &end);
198         if (*end || end == optarg2) error(app, optarg, option, "a fraction");
199     } else if (*end || end == optarg) {
200         error(app, optarg, option, "a fraction");
201     }
202     return 1;
203 }
204 
205 typedef struct EnumParseTable {
206     const char *str;
207     const int val;
208 } EnumParseTable;
209 
210 #if ARCH_X86
211 enum CpuMask {
212     X86_CPU_MASK_SSE2      = DAV1D_X86_CPU_FLAG_SSE2,
213     X86_CPU_MASK_SSSE3     = DAV1D_X86_CPU_FLAG_SSSE3     | X86_CPU_MASK_SSE2,
214     X86_CPU_MASK_SSE41     = DAV1D_X86_CPU_FLAG_SSE41     | X86_CPU_MASK_SSSE3,
215     X86_CPU_MASK_AVX2      = DAV1D_X86_CPU_FLAG_AVX2      | X86_CPU_MASK_SSE41,
216     X86_CPU_MASK_AVX512ICL = DAV1D_X86_CPU_FLAG_AVX512ICL | X86_CPU_MASK_AVX2,
217 };
218 #elif ARCH_AARCH64 || ARCH_ARM
219 enum CpuMask {
220     ARM_CPU_MASK_NEON      = DAV1D_ARM_CPU_FLAG_NEON,
221     ARM_CPU_MASK_DOTPROD   = DAV1D_ARM_CPU_FLAG_DOTPROD | ARM_CPU_MASK_NEON,
222     ARM_CPU_MASK_I8MM      = DAV1D_ARM_CPU_FLAG_I8MM    | ARM_CPU_MASK_DOTPROD,
223 #if ARCH_AARCH64
224     // SVE doesn't imply DOTPROD or I8MM.
225     ARM_CPU_MASK_SVE       = DAV1D_ARM_CPU_FLAG_SVE     | ARM_CPU_MASK_NEON,
226     // SVE2 implies DOTPROD, but not I8MM.
227     ARM_CPU_MASK_SVE2      = DAV1D_ARM_CPU_FLAG_SVE2    | ARM_CPU_MASK_SVE | ARM_CPU_MASK_DOTPROD,
228 #endif
229 };
230 #endif
231 
232 #if ARCH_PPC64LE
233 enum CpuMask {
234     PPC_CPU_MASK_VSX       = DAV1D_PPC_CPU_FLAG_VSX,
235     PPC_CPU_MASK_PWR9      = DAV1D_PPC_CPU_FLAG_VSX | DAV1D_PPC_CPU_FLAG_PWR9,
236 };
237 #endif
238 
239 
240 static const EnumParseTable cpu_mask_tbl[] = {
241 #if ARCH_AARCH64 || ARCH_ARM
242     { "neon",    ARM_CPU_MASK_NEON },
243     { "dotprod", ARM_CPU_MASK_DOTPROD },
244     { "i8mm",    ARM_CPU_MASK_I8MM },
245 #if ARCH_AARCH64
246     { "sve",     ARM_CPU_MASK_SVE },
247     { "sve2",    ARM_CPU_MASK_SVE2 },
248 #endif /* ARCH_AARCH64 */
249 #elif ARCH_LOONGARCH
250     { "lsx", DAV1D_LOONGARCH_CPU_FLAG_LSX },
251     { "lasx", DAV1D_LOONGARCH_CPU_FLAG_LASX },
252 #elif ARCH_PPC64LE
253     { "vsx",  PPC_CPU_MASK_VSX },
254     { "pwr9", PPC_CPU_MASK_PWR9 },
255 #elif ARCH_RISCV
256     { "rvv", DAV1D_RISCV_CPU_FLAG_V },
257 #elif ARCH_X86
258     { "sse2",      X86_CPU_MASK_SSE2 },
259     { "ssse3",     X86_CPU_MASK_SSSE3 },
260     { "sse41",     X86_CPU_MASK_SSE41 },
261     { "avx2",      X86_CPU_MASK_AVX2 },
262     { "avx512icl", X86_CPU_MASK_AVX512ICL },
263 #endif
264     { "none",      0 },
265 };
266 
267 static const EnumParseTable inloop_filters_tbl[] = {
268     { "none",          DAV1D_INLOOPFILTER_NONE },
269     { "deblock",       DAV1D_INLOOPFILTER_DEBLOCK },
270     { "nodeblock",     DAV1D_INLOOPFILTER_ALL - DAV1D_INLOOPFILTER_DEBLOCK },
271     { "cdef",          DAV1D_INLOOPFILTER_CDEF },
272     { "nocdef",        DAV1D_INLOOPFILTER_ALL - DAV1D_INLOOPFILTER_CDEF },
273     { "restoration",   DAV1D_INLOOPFILTER_RESTORATION },
274     { "norestoration", DAV1D_INLOOPFILTER_ALL - DAV1D_INLOOPFILTER_RESTORATION },
275     { "all",           DAV1D_INLOOPFILTER_ALL },
276 };
277 
278 static const EnumParseTable decode_frame_type_tbl[] = {
279     { "all",           DAV1D_DECODEFRAMETYPE_ALL },
280     { "reference",     DAV1D_DECODEFRAMETYPE_REFERENCE },
281     { "intra",         DAV1D_DECODEFRAMETYPE_INTRA },
282     { "key",           DAV1D_DECODEFRAMETYPE_KEY },
283 };
284 
285 #define ARRAY_SIZE(n) (sizeof(n)/sizeof(*(n)))
286 
parse_enum(char * optarg,const EnumParseTable * const tbl,const int tbl_sz,const int option,const char * app)287 static unsigned parse_enum(char *optarg, const EnumParseTable *const tbl,
288                            const int tbl_sz, const int option, const char *app)
289 {
290     char str[1024];
291 
292     strcpy(str, "any of ");
293     for (int n = 0; n < tbl_sz; n++) {
294         if (!strcmp(tbl[n].str, optarg))
295             return tbl[n].val;
296 
297         if (n) {
298             if (n < tbl_sz - 1)
299                 strcat(str, ", ");
300             else
301                 strcat(str, " or ");
302         }
303         strcat(str, tbl[n].str);
304     }
305 
306     char *end;
307     unsigned res;
308     if (!strncmp(optarg, "0x", 2)) {
309         res = (unsigned) strtoul(&optarg[2], &end, 16);
310     } else {
311         res = (unsigned) strtoul(optarg, &end, 0);
312     }
313 
314     if (*end || end == optarg) {
315         strcat(str, ", a hexadecimal (starting with 0x), or an integer");
316         error(app, optarg, option, str);
317     }
318 
319     return res;
320 }
321 
parse(const int argc,char * const * const argv,CLISettings * const cli_settings,Dav1dSettings * const lib_settings)322 void parse(const int argc, char *const *const argv,
323            CLISettings *const cli_settings, Dav1dSettings *const lib_settings)
324 {
325     int o;
326 
327     memset(cli_settings, 0, sizeof(*cli_settings));
328     dav1d_default_settings(lib_settings);
329     lib_settings->strict_std_compliance = 1; // override library default
330     int grain_specified = 0;
331 
332     while ((o = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
333         switch (o) {
334         case 'o':
335             cli_settings->outputfile = optarg;
336             break;
337         case 'i':
338             cli_settings->inputfile = optarg;
339             break;
340         case 'q':
341             cli_settings->quiet = 1;
342             break;
343         case 'l':
344             cli_settings->limit = parse_unsigned(optarg, 'l', argv[0]);
345             break;
346         case 's':
347             cli_settings->skip = parse_unsigned(optarg, 's', argv[0]);
348             break;
349         case ARG_DEMUXER:
350             cli_settings->demuxer = optarg;
351             break;
352         case ARG_MUXER:
353             cli_settings->muxer = optarg;
354             break;
355         case ARG_FRAME_TIMES:
356             cli_settings->frametimes = optarg;
357             break;
358         case ARG_REALTIME:
359             // workaround to parse an optional argument of the form `--a b`
360             // (getopt only allows `--a=b`)
361             if (optarg == NULL && optind < argc && argv[optind] != NULL &&
362                 argv[optind][0] != '-')
363             {
364                 optarg = argv[optind];
365                 optind++;
366             }
367             cli_settings->realtime = 1 + parse_optional_fraction(optarg,
368                 ARG_REALTIME, argv[0], &cli_settings->realtime_fps);
369             break;
370         case ARG_REALTIME_CACHE:
371             cli_settings->realtime_cache =
372                 parse_unsigned(optarg, ARG_REALTIME_CACHE, argv[0]);
373             break;
374         case ARG_FRAME_DELAY:
375             lib_settings->max_frame_delay =
376                 parse_unsigned(optarg, ARG_FRAME_DELAY, argv[0]);
377             break;
378         case ARG_THREADS:
379             lib_settings->n_threads =
380                 parse_unsigned(optarg, ARG_THREADS, argv[0]);
381             break;
382         case ARG_VERIFY:
383             cli_settings->verify = optarg;
384             break;
385         case ARG_FILM_GRAIN:
386             lib_settings->apply_grain =
387                 !!parse_unsigned(optarg, ARG_FILM_GRAIN, argv[0]);
388             grain_specified = 1;
389             break;
390         case ARG_OPPOINT:
391             lib_settings->operating_point =
392                 parse_unsigned(optarg, ARG_OPPOINT, argv[0]);
393             break;
394         case ARG_ALL_LAYERS:
395             lib_settings->all_layers =
396                 !!parse_unsigned(optarg, ARG_ALL_LAYERS, argv[0]);
397             break;
398         case ARG_SIZE_LIMIT: {
399             char *arg = optarg, *end;
400             uint64_t res = strtoul(arg, &end, 0);
401             if (*end == 'x') // NxM
402                 res *= strtoul((arg = end + 1), &end, 0);
403             if (*end || end == arg || res >= UINT_MAX)
404                 error(argv[0], optarg, ARG_SIZE_LIMIT, "an integer or dimension");
405             lib_settings->frame_size_limit = (unsigned) res;
406             break;
407         }
408         case ARG_STRICT_STD_COMPLIANCE:
409             lib_settings->strict_std_compliance =
410                 parse_unsigned(optarg, ARG_STRICT_STD_COMPLIANCE, argv[0]);
411             break;
412         case 'v':
413             fprintf(stderr, "%s\n", dav1d_version());
414             exit(0);
415         case ARG_CPU_MASK:
416             dav1d_set_cpu_flags_mask(parse_enum(optarg, cpu_mask_tbl, ARRAY_SIZE(cpu_mask_tbl),
417                                                 ARG_CPU_MASK, argv[0]));
418             break;
419         case ARG_NEG_STRIDE:
420             cli_settings->neg_stride = 1;
421             break;
422         case ARG_OUTPUT_INVISIBLE:
423             lib_settings->output_invisible_frames =
424                 !!parse_unsigned(optarg, ARG_OUTPUT_INVISIBLE, argv[0]);
425             break;
426         case ARG_INLOOP_FILTERS:
427             lib_settings->inloop_filters =
428                 parse_enum(optarg, inloop_filters_tbl,
429                            ARRAY_SIZE(inloop_filters_tbl),ARG_INLOOP_FILTERS, argv[0]);
430             break;
431         case ARG_DECODE_FRAME_TYPE:
432             lib_settings->decode_frame_type =
433                 parse_enum(optarg, decode_frame_type_tbl,
434                            ARRAY_SIZE(decode_frame_type_tbl), ARG_DECODE_FRAME_TYPE, argv[0]);
435             break;
436         default:
437             usage(argv[0], NULL);
438         }
439     }
440 
441     if (optind < argc)
442         usage(argv[0], "Extra/unused arguments found, e.g. '%s'\n", argv[optind]);
443     if (cli_settings->verify) {
444         if (cli_settings->outputfile)
445             usage(argv[0], "Verification (--verify) requires output file (-o/--output) to not set");
446         if (cli_settings->muxer && strcmp(cli_settings->muxer, "md5") &&
447             strcmp(cli_settings->muxer, "xxh3"))
448         {
449             usage(argv[0], "Verification (--verify) requires a checksum muxer (md5 or xxh3)");
450         }
451 
452         cli_settings->outputfile = "-";
453         if (!cli_settings->muxer)
454             cli_settings->muxer = "md5";
455     }
456 
457     if (!grain_specified && cli_settings->muxer &&
458         (!strcmp(cli_settings->muxer, "md5") ||
459         !strcmp(cli_settings->muxer, "xxh3")))
460     {
461         lib_settings->apply_grain = 0;
462     }
463 
464     if (!cli_settings->inputfile)
465         usage(argv[0], "Input file (-i/--input) is required");
466     if ((!cli_settings->muxer || strcmp(cli_settings->muxer, "null")) &&
467         !cli_settings->outputfile)
468     {
469         usage(argv[0], "Output file (-o/--output) is required");
470     }
471 }
472