| /* | |
| * Copyright © 2018-2026, VideoLAN and dav2d authors | |
| * Copyright © 2018-2026, Two Orioles, LLC | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright notice, this | |
| * list of conditions and the following disclaimer. | |
| * | |
| * 2. Redistributions in binary form must reproduce the above copyright notice, | |
| * this list of conditions and the following disclaimer in the documentation | |
| * and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "config.h" | |
| #include "cli_config.h" | |
| #include <getopt.h> | |
| #include <limits.h> | |
| #include <math.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #if HAVE_UNISTD_H | |
| # include <unistd.h> | |
| #endif | |
| #include "dav2d_cli_parse.h" | |
| #include "common/attributes.h" | |
| #include "src/cpu.h" | |
| static const char short_opts[] = "i:o:vql:s:"; | |
| enum { | |
| ARG_DEMUXER = 256, | |
| ARG_MUXER, | |
| ARG_FRAME_TIMES, | |
| ARG_REALTIME, | |
| ARG_REALTIME_CACHE, | |
| ARG_THREADS, | |
| ARG_FRAME_DELAY, | |
| ARG_VERIFY, | |
| ARG_FILM_GRAIN, | |
| ARG_OPPOINT, | |
| ARG_ALL_LAYERS, | |
| ARG_SIZE_LIMIT, | |
| ARG_STRICT_STD_COMPLIANCE, | |
| ARG_CPU_MASK, | |
| ARG_NEG_STRIDE, | |
| ARG_OUTPUT_INVISIBLE, | |
| ARG_INLOOP_FILTERS, | |
| ARG_DECODE_FRAME_TYPE, | |
| }; | |
| static const struct option long_opts[] = { | |
| { "input", 1, NULL, 'i' }, | |
| { "output", 1, NULL, 'o' }, | |
| { "quiet", 0, NULL, 'q' }, | |
| { "demuxer", 1, NULL, ARG_DEMUXER }, | |
| { "muxer", 1, NULL, ARG_MUXER }, | |
| { "version", 0, NULL, 'v' }, | |
| { "frametimes", 1, NULL, ARG_FRAME_TIMES }, | |
| { "limit", 1, NULL, 'l' }, | |
| { "skip", 1, NULL, 's' }, | |
| { "realtime", 2, NULL, ARG_REALTIME }, | |
| { "realtimecache", 1, NULL, ARG_REALTIME_CACHE }, | |
| { "threads", 1, NULL, ARG_THREADS }, | |
| { "framedelay", 1, NULL, ARG_FRAME_DELAY }, | |
| { "verify", 1, NULL, ARG_VERIFY }, | |
| { "filmgrain", 1, NULL, ARG_FILM_GRAIN }, | |
| { "oppoint", 1, NULL, ARG_OPPOINT }, | |
| { "alllayers", 1, NULL, ARG_ALL_LAYERS }, | |
| { "sizelimit", 1, NULL, ARG_SIZE_LIMIT }, | |
| { "strict", 1, NULL, ARG_STRICT_STD_COMPLIANCE }, | |
| { "cpumask", 1, NULL, ARG_CPU_MASK }, | |
| { "negstride", 0, NULL, ARG_NEG_STRIDE }, | |
| { "outputinvisible", 1, NULL, ARG_OUTPUT_INVISIBLE }, | |
| { "inloopfilters", 1, NULL, ARG_INLOOP_FILTERS }, | |
| { "decodeframetype", 1, NULL, ARG_DECODE_FRAME_TYPE }, | |
| { NULL, 0, NULL, 0 }, | |
| }; | |
| #if HAVE_XXHASH_H | |
| #define AVAILABLE_MUXERS "'md5', 'xxh3', 'yuv', 'yuv4mpeg2' or 'null'" | |
| #else | |
| #define AVAILABLE_MUXERS "'md5', 'yuv', 'yuv4mpeg2' or 'null'" | |
| #endif | |
| #if ARCH_AARCH64 || ARCH_ARM | |
| #define ALLOWED_CPU_MASKS ", 'neon', 'dotprod' or 'i8mm'" | |
| #elif ARCH_LOONGARCH | |
| #define ALLOWED_CPU_MASKS ", 'lsx' or 'lasx'" | |
| #elif ARCH_PPC64LE | |
| #define ALLOWED_CPU_MASKS ", 'vsx' or 'pwr9'" | |
| #elif ARCH_RISCV | |
| #define ALLOWED_CPU_MASKS " or 'rvv'" | |
| #elif ARCH_X86 | |
| #define ALLOWED_CPU_MASKS \ | |
| ", 'sse2', 'ssse3', 'sse41', 'avx2' or 'avx512icl'" | |
| #else | |
| #define ALLOWED_CPU_MASKS "not yet implemented for this architecture" | |
| #endif | |
| static void usage(const char *const app, const char *const reason, ...) { | |
| if (reason) { | |
| va_list args; | |
| va_start(args, reason); | |
| vfprintf(stderr, reason, args); | |
| va_end(args); | |
| fprintf(stderr, "\n\n"); | |
| } | |
| fprintf(stderr, "Usage: %s [options]\n\n", app); | |
| fprintf(stderr, "Supported options:\n" | |
| " --input/-i $file: input file\n" | |
| " --output/-o $file: output file (%%n, %%w or %%h will be filled in for per-frame files)\n" | |
| " --demuxer $name: force demuxer type ('ivf', 'section5' or 'annexb'; default: detect from content)\n" | |
| " --muxer $name: force muxer type (" AVAILABLE_MUXERS "; default: detect from extension)\n" | |
| " use 'frame' as prefix to write per-frame files; if filename contains %%n, will default to writing per-frame files\n" | |
| " --quiet/-q: disable status messages\n" | |
| " --frametimes $file: dump frame times to file\n" | |
| " --limit/-l $num: stop decoding after $num frames\n" | |
| " --skip/-s $num: skip decoding of the first $num frames\n" | |
| " --realtime [$fract]: limit framerate, optional argument to override input framerate\n" | |
| " --realtimecache $num: set the size of the cache in realtime mode (default: 0)\n" | |
| " --version/-v: print version and exit\n" | |
| " --threads $num: number of threads (default: 0)\n" | |
| " --framedelay $num: maximum frame delay, capped at $threads (default: 0);\n" | |
| " set to 1 for low-latency decoding\n" | |
| " --filmgrain $num: enable film grain application (default: 1, except if muxer is md5 or xxh3)\n" | |
| " --oppoint $num: select an operating point of a scalable AV2 bitstream (0 - 31)\n" | |
| " --alllayers $num: output all spatial layers of a scalable AV2 bitstream (default: 1)\n" | |
| " --sizelimit $num: stop decoding if the frame size exceeds the specified limit\n" | |
| " --strict $num: whether to abort decoding on standard compliance violations\n" | |
| " that don't affect bitstream decoding (default: 1)\n" | |
| " --verify $md5: verify decoded md5. implies --muxer md5, no output\n" | |
| " --cpumask $mask: restrict permitted CPU instruction sets (0" ALLOWED_CPU_MASKS "; default: -1)\n" | |
| " --negstride: use negative picture strides\n" | |
| " this is mostly meant as a developer option\n" | |
| " --outputinvisible $num: whether to output invisible (alt-ref) frames (default: 0)\n" | |
| " --inloopfilters $str: which in-loop filters to enable: plus/min-separated list of none,\n" | |
| " deblock, cdef, ccso, wiener, gdf or all; a leading +/- changes\n" | |
| " the default (e.g. -cdef; default: all)\n" | |
| " --decodeframetype $str: which frame types to decode (reference, intra, key or all; default: all)\n" | |
| ); | |
| exit(1); | |
| } | |
| static void error(const char *const app, const char *const optarg, | |
| const int option, const char *const shouldbe) | |
| { | |
| char optname[256]; | |
| int n; | |
| for (n = 0; long_opts[n].name; n++) | |
| if (long_opts[n].val == option) | |
| break; | |
| assert(long_opts[n].name); | |
| if (long_opts[n].val < 256) { | |
| snprintf(optname, sizeof(optname), "-%c/--%s", long_opts[n].val, long_opts[n].name); | |
| } else { | |
| snprintf(optname, sizeof(optname), "--%s", long_opts[n].name); | |
| } | |
| usage(app, "Invalid argument \"%s\" for option %s; should be %s", | |
| optarg, optname, shouldbe); | |
| } | |
| static unsigned parse_unsigned(const char *const optarg, const int option, | |
| const char *const app) | |
| { | |
| char *end; | |
| const unsigned res = (unsigned) strtoul(optarg, &end, 0); | |
| if (*end || end == optarg) error(app, optarg, option, "an integer"); | |
| return res; | |
| } | |
| static int parse_optional_fraction(const char *const optarg, const int option, | |
| const char *const app, double *value) | |
| { | |
| if (optarg == NULL) return 0; | |
| char *end; | |
| *value = strtod(optarg, &end); | |
| if (*end == '/' && end != optarg) { | |
| const char *optarg2 = end + 1; | |
| *value /= strtod(optarg2, &end); | |
| if (*end || end == optarg2) error(app, optarg, option, "a fraction"); | |
| } else if (*end || end == optarg) { | |
| error(app, optarg, option, "a fraction"); | |
| } | |
| return 1; | |
| } | |
| typedef struct EnumParseTable { | |
| const char *str; | |
| const int val; | |
| } EnumParseTable; | |
| #if ARCH_X86 | |
| enum CpuMask { | |
| X86_CPU_MASK_SSE2 = DAV2D_X86_CPU_FLAG_SSE2, | |
| X86_CPU_MASK_SSSE3 = DAV2D_X86_CPU_FLAG_SSSE3 | X86_CPU_MASK_SSE2, | |
| X86_CPU_MASK_SSE41 = DAV2D_X86_CPU_FLAG_SSE41 | X86_CPU_MASK_SSSE3, | |
| X86_CPU_MASK_AVX2 = DAV2D_X86_CPU_FLAG_AVX2 | X86_CPU_MASK_SSE41, | |
| X86_CPU_MASK_AVX512ICL = DAV2D_X86_CPU_FLAG_AVX512ICL | X86_CPU_MASK_AVX2, | |
| }; | |
| #elif ARCH_AARCH64 || ARCH_ARM | |
| enum CpuMask { | |
| ARM_CPU_MASK_NEON = DAV2D_ARM_CPU_FLAG_NEON, | |
| ARM_CPU_MASK_DOTPROD = DAV2D_ARM_CPU_FLAG_DOTPROD | ARM_CPU_MASK_NEON, | |
| ARM_CPU_MASK_I8MM = DAV2D_ARM_CPU_FLAG_I8MM | ARM_CPU_MASK_DOTPROD, | |
| #if ARCH_AARCH64 | |
| // SVE doesn't imply DOTPROD or I8MM. | |
| ARM_CPU_MASK_SVE = DAV2D_ARM_CPU_FLAG_SVE | ARM_CPU_MASK_NEON, | |
| // SVE2 implies DOTPROD, but not I8MM. | |
| ARM_CPU_MASK_SVE2 = DAV2D_ARM_CPU_FLAG_SVE2 | ARM_CPU_MASK_SVE | ARM_CPU_MASK_DOTPROD, | |
| #endif | |
| }; | |
| #endif | |
| #if ARCH_PPC64LE | |
| enum CpuMask { | |
| PPC_CPU_MASK_VSX = DAV2D_PPC_CPU_FLAG_VSX, | |
| PPC_CPU_MASK_PWR9 = DAV2D_PPC_CPU_FLAG_VSX | DAV2D_PPC_CPU_FLAG_PWR9, | |
| }; | |
| #endif | |
| static const EnumParseTable cpu_mask_tbl[] = { | |
| #if ARCH_AARCH64 || ARCH_ARM | |
| { "neon", ARM_CPU_MASK_NEON }, | |
| { "dotprod", ARM_CPU_MASK_DOTPROD }, | |
| { "i8mm", ARM_CPU_MASK_I8MM }, | |
| #if ARCH_AARCH64 | |
| { "sve", ARM_CPU_MASK_SVE }, | |
| { "sve2", ARM_CPU_MASK_SVE2 }, | |
| #endif /* ARCH_AARCH64 */ | |
| #elif ARCH_LOONGARCH | |
| { "lsx", DAV2D_LOONGARCH_CPU_FLAG_LSX }, | |
| { "lasx", DAV2D_LOONGARCH_CPU_FLAG_LASX }, | |
| #elif ARCH_PPC64LE | |
| { "vsx", PPC_CPU_MASK_VSX }, | |
| { "pwr9", PPC_CPU_MASK_PWR9 }, | |
| #elif ARCH_RISCV | |
| { "rvv", DAV2D_RISCV_CPU_FLAG_V }, | |
| #elif ARCH_X86 | |
| { "sse2", X86_CPU_MASK_SSE2 }, | |
| { "ssse3", X86_CPU_MASK_SSSE3 }, | |
| { "sse41", X86_CPU_MASK_SSE41 }, | |
| { "avx2", X86_CPU_MASK_AVX2 }, | |
| { "avx512icl", X86_CPU_MASK_AVX512ICL }, | |
| #endif | |
| { "none", 0 }, | |
| }; | |
| static const EnumParseTable inloop_filters_tbl[] = { | |
| { "none", 0 }, | |
| { "deblock", DAV2D_INLOOPFILTER_DEBLOCK }, | |
| { "cdef", DAV2D_INLOOPFILTER_CDEF }, | |
| { "ccso", DAV2D_INLOOPFILTER_CCSO }, | |
| { "wiener", DAV2D_INLOOPFILTER_WIENER }, | |
| { "gdf", DAV2D_INLOOPFILTER_GDF }, | |
| { "all", DAV2D_INLOOPFILTER_ALL }, | |
| }; | |
| static const EnumParseTable decode_frame_type_tbl[] = { | |
| { "all", DAV2D_DECODEFRAMETYPE_ALL }, | |
| { "reference", DAV2D_DECODEFRAMETYPE_REFERENCE }, | |
| { "intra", DAV2D_DECODEFRAMETYPE_INTRA }, | |
| { "key", DAV2D_DECODEFRAMETYPE_KEY }, | |
| }; | |
| static unsigned parse_enum(char *optarg, const EnumParseTable *const tbl, | |
| const int tbl_sz, const int option, const char *app) | |
| { | |
| char str[1024]; | |
| strcpy(str, "any of "); | |
| for (int n = 0; n < tbl_sz; n++) { | |
| if (!strcmp(tbl[n].str, optarg)) | |
| return tbl[n].val; | |
| if (n) { | |
| if (n < tbl_sz - 1) | |
| strcat(str, ", "); | |
| else | |
| strcat(str, " or "); | |
| } | |
| strcat(str, tbl[n].str); | |
| } | |
| char *end; | |
| unsigned res; | |
| if (!strncmp(optarg, "0x", 2)) { | |
| res = (unsigned) strtoul(&optarg[2], &end, 16); | |
| } else { | |
| res = (unsigned) strtoul(optarg, &end, 0); | |
| } | |
| if (*end || end == optarg) { | |
| strcat(str, ", a hexadecimal (starting with 0x), or an integer"); | |
| error(app, optarg, option, str); | |
| } | |
| return res; | |
| } | |
| static int parse_enum_mask(const char *const optargs, unsigned const start_mask, | |
| const EnumParseTable *const tbl, const int tbl_sz, | |
| const int option, const char *app) | |
| { | |
| const int start_with_sign = optargs[0] == '+' || optargs[0] == '-'; | |
| unsigned res = start_with_sign ? start_mask : 0; | |
| int sub_instead_of_add = optargs[0] == '-'; | |
| const char *end = optargs + start_with_sign; | |
| for (;;) { | |
| const char *start = end; | |
| const char *plus = strchr(start, '+'); | |
| const char *min = strchr(start, '-'); | |
| if (plus && min) { | |
| assert(plus != min); | |
| end = plus < min ? plus : min; | |
| } else { | |
| end = plus ? plus : min; | |
| } | |
| const ptrdiff_t len = end ? end - start : (ptrdiff_t) strlen(start); | |
| int n; | |
| for (n = 0; n < tbl_sz; n++) | |
| if (!strncmp(tbl[n].str, start, len) && !tbl[n].str[len]) | |
| break; | |
| if (n == tbl_sz) break; | |
| if (sub_instead_of_add) { | |
| res &= ~tbl[n].val; | |
| } else { | |
| res |= tbl[n].val; | |
| } | |
| if (end) { | |
| sub_instead_of_add = *end == '-'; | |
| end++; | |
| } else { | |
| return res; | |
| } | |
| } | |
| char err_msg[1024]; | |
| size_t len = snprintf(err_msg, sizeof(err_msg), "plus/min-separated values from "); | |
| for (int n = 0; n < tbl_sz; n++) { | |
| len += snprintf(err_msg + len, sizeof(err_msg) - len, "%s%s", | |
| n ? n + 1 < tbl_sz ? ", " : " or " : "", | |
| tbl[n].str); | |
| } | |
| error(app, optarg, option, err_msg); | |
| return -1; | |
| } | |
| void parse(const int argc, char *const *const argv, | |
| CLISettings *const cli_settings, Dav2dSettings *const lib_settings) | |
| { | |
| int o; | |
| memset(cli_settings, 0, sizeof(*cli_settings)); | |
| dav2d_default_settings(lib_settings); | |
| lib_settings->strict_std_compliance = 1; // override library default | |
| int grain_specified = 0; | |
| while ((o = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) { | |
| switch (o) { | |
| case 'o': | |
| cli_settings->outputfile = optarg; | |
| break; | |
| case 'i': | |
| cli_settings->inputfile = optarg; | |
| break; | |
| case 'q': | |
| cli_settings->quiet = 1; | |
| break; | |
| case 'l': | |
| cli_settings->limit = parse_unsigned(optarg, 'l', argv[0]); | |
| break; | |
| case 's': | |
| cli_settings->skip = parse_unsigned(optarg, 's', argv[0]); | |
| break; | |
| case ARG_DEMUXER: | |
| cli_settings->demuxer = optarg; | |
| break; | |
| case ARG_MUXER: | |
| cli_settings->muxer = optarg; | |
| break; | |
| case ARG_FRAME_TIMES: | |
| cli_settings->frametimes = optarg; | |
| break; | |
| case ARG_REALTIME: | |
| // workaround to parse an optional argument of the form `--a b` | |
| // (getopt only allows `--a=b`) | |
| if (optarg == NULL && optind < argc && argv[optind] != NULL && | |
| argv[optind][0] != '-') | |
| { | |
| optarg = argv[optind]; | |
| optind++; | |
| } | |
| cli_settings->realtime = 1 + parse_optional_fraction(optarg, | |
| ARG_REALTIME, argv[0], &cli_settings->realtime_fps); | |
| break; | |
| case ARG_REALTIME_CACHE: | |
| cli_settings->realtime_cache = | |
| parse_unsigned(optarg, ARG_REALTIME_CACHE, argv[0]); | |
| break; | |
| case ARG_FRAME_DELAY: | |
| lib_settings->max_frame_delay = | |
| parse_unsigned(optarg, ARG_FRAME_DELAY, argv[0]); | |
| break; | |
| case ARG_THREADS: | |
| lib_settings->n_threads = | |
| parse_unsigned(optarg, ARG_THREADS, argv[0]); | |
| break; | |
| case ARG_VERIFY: | |
| cli_settings->verify = optarg; | |
| break; | |
| case ARG_FILM_GRAIN: | |
| lib_settings->apply_grain = | |
| !!parse_unsigned(optarg, ARG_FILM_GRAIN, argv[0]); | |
| grain_specified = 1; | |
| break; | |
| case ARG_OPPOINT: | |
| lib_settings->operating_point = | |
| parse_unsigned(optarg, ARG_OPPOINT, argv[0]); | |
| break; | |
| case ARG_ALL_LAYERS: | |
| lib_settings->all_layers = | |
| !!parse_unsigned(optarg, ARG_ALL_LAYERS, argv[0]); | |
| break; | |
| case ARG_SIZE_LIMIT: { | |
| char *arg = optarg, *end; | |
| uint64_t res = strtoul(arg, &end, 0); | |
| if (*end == 'x') // NxM | |
| res *= strtoul((arg = end + 1), &end, 0); | |
| if (*end || end == arg || res >= UINT_MAX) | |
| error(argv[0], optarg, ARG_SIZE_LIMIT, "an integer or dimension"); | |
| lib_settings->frame_size_limit = (unsigned) res; | |
| break; | |
| } | |
| case ARG_STRICT_STD_COMPLIANCE: | |
| lib_settings->strict_std_compliance = | |
| parse_unsigned(optarg, ARG_STRICT_STD_COMPLIANCE, argv[0]); | |
| break; | |
| case 'v': | |
| fprintf(stderr, "%s\n", dav2d_version()); | |
| exit(0); | |
| case ARG_CPU_MASK: | |
| dav2d_set_cpu_flags_mask(parse_enum(optarg, cpu_mask_tbl, ARRAY_SIZE(cpu_mask_tbl), | |
| ARG_CPU_MASK, argv[0])); | |
| break; | |
| case ARG_NEG_STRIDE: | |
| cli_settings->neg_stride = 1; | |
| break; | |
| case ARG_OUTPUT_INVISIBLE: | |
| lib_settings->output_invisible_frames = | |
| !!parse_unsigned(optarg, ARG_OUTPUT_INVISIBLE, argv[0]); | |
| break; | |
| case ARG_INLOOP_FILTERS: | |
| lib_settings->inloop_filters = | |
| parse_enum_mask(optarg, lib_settings->inloop_filters, | |
| inloop_filters_tbl, ARRAY_SIZE(inloop_filters_tbl), | |
| ARG_INLOOP_FILTERS, argv[0]); | |
| break; | |
| case ARG_DECODE_FRAME_TYPE: | |
| lib_settings->decode_frame_type = | |
| parse_enum(optarg, decode_frame_type_tbl, | |
| ARRAY_SIZE(decode_frame_type_tbl), ARG_DECODE_FRAME_TYPE, argv[0]); | |
| break; | |
| default: | |
| usage(argv[0], NULL); | |
| } | |
| } | |
| if (optind < argc) | |
| usage(argv[0], "Extra/unused arguments found, e.g. '%s'\n", argv[optind]); | |
| if (cli_settings->verify) { | |
| if (cli_settings->outputfile) | |
| usage(argv[0], "Verification (--verify) requires output file (-o/--output) to not set"); | |
| if (cli_settings->muxer && strcmp(cli_settings->muxer, "md5") && | |
| strcmp(cli_settings->muxer, "xxh3")) | |
| { | |
| usage(argv[0], "Verification (--verify) requires a checksum muxer (md5 or xxh3)"); | |
| } | |
| cli_settings->outputfile = "-"; | |
| if (!cli_settings->muxer) | |
| cli_settings->muxer = "md5"; | |
| } | |
| if (!grain_specified && cli_settings->muxer && | |
| (!strcmp(cli_settings->muxer, "md5") || | |
| !strcmp(cli_settings->muxer, "xxh3"))) | |
| { | |
| lib_settings->apply_grain = 0; | |
| } | |
| if (!cli_settings->inputfile) | |
| usage(argv[0], "Input file (-i/--input) is required"); | |
| if ((!cli_settings->muxer || strcmp(cli_settings->muxer, "null")) && | |
| !cli_settings->outputfile) | |
| { | |
| usage(argv[0], "Output file (-o/--output) is required"); | |
| } | |
| } |