diff options
author | James Almer <jamrial@gmail.com> | 2017-10-01 18:21:02 -0300 |
---|---|---|
committer | James Almer <jamrial@gmail.com> | 2017-10-01 18:26:36 -0300 |
commit | fd5f4ac0813c27c34c387f00044905a859e29e37 (patch) | |
tree | 7074b367c4ce2a3f34ac9a2fd9e870e0c4e086ad /fftools | |
parent | 2f7ca0b94e49c2bfce8bda3f883766101ebd7a9b (diff) | |
parent | c95169f0ec68bdeeabc5fde8aa4076f406242524 (diff) | |
download | ffmpeg-fd5f4ac0813c27c34c387f00044905a859e29e37.tar.gz |
Merge commit 'c95169f0ec68bdeeabc5fde8aa4076f406242524'
* commit 'c95169f0ec68bdeeabc5fde8aa4076f406242524':
build: Move cli tool sources to a separate subdirectory
Merged-by: James Almer <jamrial@gmail.com>
Diffstat (limited to 'fftools')
-rw-r--r-- | fftools/Makefile | 57 | ||||
-rw-r--r-- | fftools/cmdutils.c | 2274 | ||||
-rw-r--r-- | fftools/cmdutils.h | 662 | ||||
-rw-r--r-- | fftools/cmdutils_opencl.c | 283 | ||||
-rw-r--r-- | fftools/ffmpeg.c | 4826 | ||||
-rw-r--r-- | fftools/ffmpeg.h | 679 | ||||
-rw-r--r-- | fftools/ffmpeg_cuvid.c | 73 | ||||
-rw-r--r-- | fftools/ffmpeg_filter.c | 1210 | ||||
-rw-r--r-- | fftools/ffmpeg_hw.c | 385 | ||||
-rw-r--r-- | fftools/ffmpeg_opt.c | 3756 | ||||
-rw-r--r-- | fftools/ffmpeg_qsv.c | 109 | ||||
-rw-r--r-- | fftools/ffmpeg_videotoolbox.c | 201 | ||||
-rw-r--r-- | fftools/ffplay.c | 3765 | ||||
-rw-r--r-- | fftools/ffprobe.c | 3692 | ||||
-rw-r--r-- | fftools/ffserver.c | 4026 | ||||
-rw-r--r-- | fftools/ffserver_config.c | 1325 | ||||
-rw-r--r-- | fftools/ffserver_config.h | 155 |
17 files changed, 27478 insertions, 0 deletions
diff --git a/fftools/Makefile b/fftools/Makefile new file mode 100644 index 0000000000..094f6d6265 --- /dev/null +++ b/fftools/Makefile @@ -0,0 +1,57 @@ +AVPROGS-$(CONFIG_FFMPEG) += ffmpeg +AVPROGS-$(CONFIG_FFPLAY) += ffplay +AVPROGS-$(CONFIG_FFPROBE) += ffprobe +AVPROGS-$(CONFIG_FFSERVER) += ffserver + +AVPROGS := $(AVPROGS-yes:%=%$(PROGSSUF)$(EXESUF)) +PROGS += $(AVPROGS) + +AVBASENAMES = ffmpeg ffplay ffprobe ffserver +ALLAVPROGS = $(AVBASENAMES:%=%$(PROGSSUF)$(EXESUF)) +ALLAVPROGS_G = $(AVBASENAMES:%=%$(PROGSSUF)_g$(EXESUF)) + +OBJS-ffmpeg += fftools/ffmpeg_opt.o fftools/ffmpeg_filter.o fftools/ffmpeg_hw.o +OBJS-ffmpeg-$(CONFIG_CUVID) += fftools/ffmpeg_cuvid.o +OBJS-ffmpeg-$(CONFIG_LIBMFX) += fftools/ffmpeg_qsv.o +ifndef CONFIG_VIDEOTOOLBOX +OBJS-ffmpeg-$(CONFIG_VDA) += fftools/ffmpeg_videotoolbox.o +endif +OBJS-ffmpeg-$(CONFIG_VIDEOTOOLBOX) += fftools/ffmpeg_videotoolbox.o +OBJS-ffserver += fftools/ffserver_config.o + +define DOFFTOOL +OBJS-$(1)-$(CONFIG_OPENCL) += fftools/cmdutils_opencl.o +OBJS-$(1) += fftools/cmdutils.o fftools/$(1).o $(OBJS-$(1)-yes) +$(1)$(PROGSSUF)_g$(EXESUF): $$(OBJS-$(1)) +$$(OBJS-$(1)): | fftools +$$(OBJS-$(1)): CFLAGS += $(CFLAGS-$(1)) +$(1)$(PROGSSUF)_g$(EXESUF): LDFLAGS += $(LDFLAGS-$(1)) +$(1)$(PROGSSUF)_g$(EXESUF): FF_EXTRALIBS += $(EXTRALIBS-$(1)) +-include $$(OBJS-$(1):.o=.d) +endef + +$(foreach P,$(AVPROGS-yes),$(eval $(call DOFFTOOL,$(P)))) + +all: $(AVPROGS) + +fftools/ffprobe.o fftools/cmdutils.o: libavutil/ffversion.h | fftools +OBJDIRS += fftools + +ifdef AVPROGS +install: install-progs install-data +endif + +install-progs-yes: +install-progs-$(CONFIG_SHARED): install-libs + +install-progs: install-progs-yes $(AVPROGS) + $(Q)mkdir -p "$(BINDIR)" + $(INSTALL) -c -m 755 $(AVPROGS) "$(BINDIR)" + +uninstall: uninstall-progs + +uninstall-progs: + $(RM) $(addprefix "$(BINDIR)/", $(ALLAVPROGS)) + +clean:: + $(RM) $(ALLAVPROGS) $(ALLAVPROGS_G) $(CLEANSUFFIXES:%=fftools/%) diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c new file mode 100644 index 0000000000..3d428f3eea --- /dev/null +++ b/fftools/cmdutils.c @@ -0,0 +1,2274 @@ +/* + * Various utilities for command line tools + * Copyright (c) 2000-2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <math.h> + +/* Include only the enabled headers since some compilers (namely, Sun + Studio) will not omit unused inline functions and create undefined + references to libraries that are not being built. */ + +#include "config.h" +#include "compat/va_copy.h" +#include "libavformat/avformat.h" +#include "libavfilter/avfilter.h" +#include "libavdevice/avdevice.h" +#include "libavresample/avresample.h" +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +#include "libpostproc/postprocess.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/display.h" +#include "libavutil/mathematics.h" +#include "libavutil/imgutils.h" +#include "libavutil/libm.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/eval.h" +#include "libavutil/dict.h" +#include "libavutil/opt.h" +#include "libavutil/cpu.h" +#include "libavutil/ffversion.h" +#include "libavutil/version.h" +#include "cmdutils.h" +#if CONFIG_NETWORK +#include "libavformat/network.h" +#endif +#if HAVE_SYS_RESOURCE_H +#include <sys/time.h> +#include <sys/resource.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#endif + +static int init_report(const char *env); + +AVDictionary *sws_dict; +AVDictionary *swr_opts; +AVDictionary *format_opts, *codec_opts, *resample_opts; + +static FILE *report_file; +static int report_file_level = AV_LOG_DEBUG; +int hide_banner = 0; + +enum show_muxdemuxers { + SHOW_DEFAULT, + SHOW_DEMUXERS, + SHOW_MUXERS, +}; + +void init_opts(void) +{ + av_dict_set(&sws_dict, "flags", "bicubic", 0); +} + +void uninit_opts(void) +{ + av_dict_free(&swr_opts); + av_dict_free(&sws_dict); + av_dict_free(&format_opts); + av_dict_free(&codec_opts); + av_dict_free(&resample_opts); +} + +void log_callback_help(void *ptr, int level, const char *fmt, va_list vl) +{ + vfprintf(stdout, fmt, vl); +} + +static void log_callback_report(void *ptr, int level, const char *fmt, va_list vl) +{ + va_list vl2; + char line[1024]; + static int print_prefix = 1; + + va_copy(vl2, vl); + av_log_default_callback(ptr, level, fmt, vl); + av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix); + va_end(vl2); + if (report_file_level >= level) { + fputs(line, report_file); + fflush(report_file); + } +} + +void init_dynload(void) +{ +#ifdef _WIN32 + /* Calling SetDllDirectory with the empty string (but not NULL) removes the + * current working directory from the DLL search path as a security pre-caution. */ + SetDllDirectory(""); +#endif +} + +static void (*program_exit)(int ret); + +void register_exit(void (*cb)(int ret)) +{ + program_exit = cb; +} + +void exit_program(int ret) +{ + if (program_exit) + program_exit(ret); + + exit(ret); +} + +double parse_number_or_die(const char *context, const char *numstr, int type, + double min, double max) +{ + char *tail; + const char *error; + double d = av_strtod(numstr, &tail); + if (*tail) + error = "Expected number for %s but found: %s\n"; + else if (d < min || d > max) + error = "The value for %s was %s which is not within %f - %f\n"; + else if (type == OPT_INT64 && (int64_t)d != d) + error = "Expected int64 for %s but found %s\n"; + else if (type == OPT_INT && (int)d != d) + error = "Expected int for %s but found %s\n"; + else + return d; + av_log(NULL, AV_LOG_FATAL, error, context, numstr, min, max); + exit_program(1); + return 0; +} + +int64_t parse_time_or_die(const char *context, const char *timestr, + int is_duration) +{ + int64_t us; + if (av_parse_time(&us, timestr, is_duration) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid %s specification for %s: %s\n", + is_duration ? "duration" : "date", context, timestr); + exit_program(1); + } + return us; +} + +void show_help_options(const OptionDef *options, const char *msg, int req_flags, + int rej_flags, int alt_flags) +{ + const OptionDef *po; + int first; + + first = 1; + for (po = options; po->name; po++) { + char buf[64]; + + if (((po->flags & req_flags) != req_flags) || + (alt_flags && !(po->flags & alt_flags)) || + (po->flags & rej_flags)) + continue; + + if (first) { + printf("%s\n", msg); + first = 0; + } + av_strlcpy(buf, po->name, sizeof(buf)); + if (po->argname) { + av_strlcat(buf, " ", sizeof(buf)); + av_strlcat(buf, po->argname, sizeof(buf)); + } + printf("-%-17s %s\n", buf, po->help); + } + printf("\n"); +} + +void show_help_children(const AVClass *class, int flags) +{ + const AVClass *child = NULL; + if (class->option) { + av_opt_show2(&class, NULL, flags, 0); + printf("\n"); + } + + while (child = av_opt_child_class_next(class, child)) + show_help_children(child, flags); +} + +static const OptionDef *find_option(const OptionDef *po, const char *name) +{ + const char *p = strchr(name, ':'); + int len = p ? p - name : strlen(name); + + while (po->name) { + if (!strncmp(name, po->name, len) && strlen(po->name) == len) + break; + po++; + } + return po; +} + +/* _WIN32 means using the windows libc - cygwin doesn't define that + * by default. HAVE_COMMANDLINETOARGVW is true on cygwin, while + * it doesn't provide the actual command line via GetCommandLineW(). */ +#if HAVE_COMMANDLINETOARGVW && defined(_WIN32) +#include <shellapi.h> +/* Will be leaked on exit */ +static char** win32_argv_utf8 = NULL; +static int win32_argc = 0; + +/** + * Prepare command line arguments for executable. + * For Windows - perform wide-char to UTF-8 conversion. + * Input arguments should be main() function arguments. + * @param argc_ptr Arguments number (including executable) + * @param argv_ptr Arguments list. + */ +static void prepare_app_arguments(int *argc_ptr, char ***argv_ptr) +{ + char *argstr_flat; + wchar_t **argv_w; + int i, buffsize = 0, offset = 0; + + if (win32_argv_utf8) { + *argc_ptr = win32_argc; + *argv_ptr = win32_argv_utf8; + return; + } + + win32_argc = 0; + argv_w = CommandLineToArgvW(GetCommandLineW(), &win32_argc); + if (win32_argc <= 0 || !argv_w) + return; + + /* determine the UTF-8 buffer size (including NULL-termination symbols) */ + for (i = 0; i < win32_argc; i++) + buffsize += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, + NULL, 0, NULL, NULL); + + win32_argv_utf8 = av_mallocz(sizeof(char *) * (win32_argc + 1) + buffsize); + argstr_flat = (char *)win32_argv_utf8 + sizeof(char *) * (win32_argc + 1); + if (!win32_argv_utf8) { + LocalFree(argv_w); + return; + } + + for (i = 0; i < win32_argc; i++) { + win32_argv_utf8[i] = &argstr_flat[offset]; + offset += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, + &argstr_flat[offset], + buffsize - offset, NULL, NULL); + } + win32_argv_utf8[i] = NULL; + LocalFree(argv_w); + + *argc_ptr = win32_argc; + *argv_ptr = win32_argv_utf8; +} +#else +static inline void prepare_app_arguments(int *argc_ptr, char ***argv_ptr) +{ + /* nothing to do */ +} +#endif /* HAVE_COMMANDLINETOARGVW */ + +static int write_option(void *optctx, const OptionDef *po, const char *opt, + const char *arg) +{ + /* new-style options contain an offset into optctx, old-style address of + * a global var*/ + void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? + (uint8_t *)optctx + po->u.off : po->u.dst_ptr; + int *dstcount; + + if (po->flags & OPT_SPEC) { + SpecifierOpt **so = dst; + char *p = strchr(opt, ':'); + char *str; + + dstcount = (int *)(so + 1); + *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1); + str = av_strdup(p ? p + 1 : ""); + if (!str) + return AVERROR(ENOMEM); + (*so)[*dstcount - 1].specifier = str; + dst = &(*so)[*dstcount - 1].u; + } + + if (po->flags & OPT_STRING) { + char *str; + str = av_strdup(arg); + av_freep(dst); + if (!str) + return AVERROR(ENOMEM); + *(char **)dst = str; + } else if (po->flags & OPT_BOOL || po->flags & OPT_INT) { + *(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX); + } else if (po->flags & OPT_INT64) { + *(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX); + } else if (po->flags & OPT_TIME) { + *(int64_t *)dst = parse_time_or_die(opt, arg, 1); + } else if (po->flags & OPT_FLOAT) { + *(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY); + } else if (po->flags & OPT_DOUBLE) { + *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY); + } else if (po->u.func_arg) { + int ret = po->u.func_arg(optctx, opt, arg); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Failed to set value '%s' for option '%s': %s\n", + arg, opt, av_err2str(ret)); + return ret; + } + } + if (po->flags & OPT_EXIT) + exit_program(0); + + return 0; +} + +int parse_option(void *optctx, const char *opt, const char *arg, + const OptionDef *options) +{ + const OptionDef *po; + int ret; + + po = find_option(options, opt); + if (!po->name && opt[0] == 'n' && opt[1] == 'o') { + /* handle 'no' bool option */ + po = find_option(options, opt + 2); + if ((po->name && (po->flags & OPT_BOOL))) + arg = "0"; + } else if (po->flags & OPT_BOOL) + arg = "1"; + + if (!po->name) + po = find_option(options, "default"); + if (!po->name) { + av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt); + return AVERROR(EINVAL); + } + if (po->flags & HAS_ARG && !arg) { + av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'\n", opt); + return AVERROR(EINVAL); + } + + ret = write_option(optctx, po, opt, arg); + if (ret < 0) + return ret; + + return !!(po->flags & HAS_ARG); +} + +void parse_options(void *optctx, int argc, char **argv, const OptionDef *options, + void (*parse_arg_function)(void *, const char*)) +{ + const char *opt; + int optindex, handleoptions = 1, ret; + + /* perform system-dependent conversions for arguments list */ + prepare_app_arguments(&argc, &argv); + + /* parse options */ + optindex = 1; + while (optindex < argc) { + opt = argv[optindex++]; + + if (handleoptions && opt[0] == '-' && opt[1] != '\0') { + if (opt[1] == '-' && opt[2] == '\0') { + handleoptions = 0; + continue; + } + opt++; + + if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0) + exit_program(1); + optindex += ret; + } else { + if (parse_arg_function) + parse_arg_function(optctx, opt); + } + } +} + +int parse_optgroup(void *optctx, OptionGroup *g) +{ + int i, ret; + + av_log(NULL, AV_LOG_DEBUG, "Parsing a group of options: %s %s.\n", + g->group_def->name, g->arg); + + for (i = 0; i < g->nb_opts; i++) { + Option *o = &g->opts[i]; + + if (g->group_def->flags && + !(g->group_def->flags & o->opt->flags)) { + av_log(NULL, AV_LOG_ERROR, "Option %s (%s) cannot be applied to " + "%s %s -- you are trying to apply an input option to an " + "output file or vice versa. Move this option before the " + "file it belongs to.\n", o->key, o->opt->help, + g->group_def->name, g->arg); + return AVERROR(EINVAL); + } + + av_log(NULL, AV_LOG_DEBUG, "Applying option %s (%s) with argument %s.\n", + o->key, o->opt->help, o->val); + + ret = write_option(optctx, o->opt, o->key, o->val); + if (ret < 0) + return ret; + } + + av_log(NULL, AV_LOG_DEBUG, "Successfully parsed a group of options.\n"); + + return 0; +} + +int locate_option(int argc, char **argv, const OptionDef *options, + const char *optname) +{ + const OptionDef *po; + int i; + + for (i = 1; i < argc; i++) { + const char *cur_opt = argv[i]; + + if (*cur_opt++ != '-') + continue; + + po = find_option(options, cur_opt); + if (!po->name && cur_opt[0] == 'n' && cur_opt[1] == 'o') + po = find_option(options, cur_opt + 2); + + if ((!po->name && !strcmp(cur_opt, optname)) || + (po->name && !strcmp(optname, po->name))) + return i; + + if (!po->name || po->flags & HAS_ARG) + i++; + } + return 0; +} + +static void dump_argument(const char *a) +{ + const unsigned char *p; + + for (p = a; *p; p++) + if (!((*p >= '+' && *p <= ':') || (*p >= '@' && *p <= 'Z') || + *p == '_' || (*p >= 'a' && *p <= 'z'))) + break; + if (!*p) { + fputs(a, report_file); + return; + } + fputc('"', report_file); + for (p = a; *p; p++) { + if (*p == '\\' || *p == '"' || *p == '$' || *p == '`') + fprintf(report_file, "\\%c", *p); + else if (*p < ' ' || *p > '~') + fprintf(report_file, "\\x%02x", *p); + else + fputc(*p, report_file); + } + fputc('"', report_file); +} + +static void check_options(const OptionDef *po) +{ + while (po->name) { + if (po->flags & OPT_PERFILE) + av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT)); + po++; + } +} + +void parse_loglevel(int argc, char **argv, const OptionDef *options) +{ + int idx = locate_option(argc, argv, options, "loglevel"); + const char *env; + + check_options(options); + + if (!idx) + idx = locate_option(argc, argv, options, "v"); + if (idx && argv[idx + 1]) + opt_loglevel(NULL, "loglevel", argv[idx + 1]); + idx = locate_option(argc, argv, options, "report"); + if ((env = getenv("FFREPORT")) || idx) { + init_report(env); + if (report_file) { + int i; + fprintf(report_file, "Command line:\n"); + for (i = 0; i < argc; i++) { + dump_argument(argv[i]); + fputc(i < argc - 1 ? ' ' : '\n', report_file); + } + fflush(report_file); + } + } + idx = locate_option(argc, argv, options, "hide_banner"); + if (idx) + hide_banner = 1; +} + +static const AVOption *opt_find(void *obj, const char *name, const char *unit, + int opt_flags, int search_flags) +{ + const AVOption *o = av_opt_find(obj, name, unit, opt_flags, search_flags); + if(o && !o->flags) + return NULL; + return o; +} + +#define FLAGS (o->type == AV_OPT_TYPE_FLAGS && (arg[0]=='-' || arg[0]=='+')) ? AV_DICT_APPEND : 0 +int opt_default(void *optctx, const char *opt, const char *arg) +{ + const AVOption *o; + int consumed = 0; + char opt_stripped[128]; + const char *p; + const AVClass *cc = avcodec_get_class(), *fc = avformat_get_class(); +#if CONFIG_AVRESAMPLE + const AVClass *rc = avresample_get_class(); +#endif +#if CONFIG_SWSCALE + const AVClass *sc = sws_get_class(); +#endif +#if CONFIG_SWRESAMPLE + const AVClass *swr_class = swr_get_class(); +#endif + + if (!strcmp(opt, "debug") || !strcmp(opt, "fdebug")) + av_log_set_level(AV_LOG_DEBUG); + + if (!(p = strchr(opt, ':'))) + p = opt + strlen(opt); + av_strlcpy(opt_stripped, opt, FFMIN(sizeof(opt_stripped), p - opt + 1)); + + if ((o = opt_find(&cc, opt_stripped, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) || + ((opt[0] == 'v' || opt[0] == 'a' || opt[0] == 's') && + (o = opt_find(&cc, opt + 1, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)))) { + av_dict_set(&codec_opts, opt, arg, FLAGS); + consumed = 1; + } + if ((o = opt_find(&fc, opt, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) { + av_dict_set(&format_opts, opt, arg, FLAGS); + if (consumed) + av_log(NULL, AV_LOG_VERBOSE, "Routing option %s to both codec and muxer layer\n", opt); + consumed = 1; + } +#if CONFIG_SWSCALE + if (!consumed && (o = opt_find(&sc, opt, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) { + struct SwsContext *sws = sws_alloc_context(); + int ret = av_opt_set(sws, opt, arg, 0); + sws_freeContext(sws); + if (!strcmp(opt, "srcw") || !strcmp(opt, "srch") || + !strcmp(opt, "dstw") || !strcmp(opt, "dsth") || + !strcmp(opt, "src_format") || !strcmp(opt, "dst_format")) { + av_log(NULL, AV_LOG_ERROR, "Directly using swscale dimensions/format options is not supported, please use the -s or -pix_fmt options\n"); + return AVERROR(EINVAL); + } + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error setting option %s.\n", opt); + return ret; + } + + av_dict_set(&sws_dict, opt, arg, FLAGS); + + consumed = 1; + } +#else + if (!consumed && !strcmp(opt, "sws_flags")) { + av_log(NULL, AV_LOG_WARNING, "Ignoring %s %s, due to disabled swscale\n", opt, arg); + consumed = 1; + } +#endif +#if CONFIG_SWRESAMPLE + if (!consumed && (o=opt_find(&swr_class, opt, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) { + struct SwrContext *swr = swr_alloc(); + int ret = av_opt_set(swr, opt, arg, 0); + swr_free(&swr); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error setting option %s.\n", opt); + return ret; + } + av_dict_set(&swr_opts, opt, arg, FLAGS); + consumed = 1; + } +#endif +#if CONFIG_AVRESAMPLE + if ((o=opt_find(&rc, opt, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) { + av_dict_set(&resample_opts, opt, arg, FLAGS); + consumed = 1; + } +#endif + + if (consumed) + return 0; + return AVERROR_OPTION_NOT_FOUND; +} + +/* + * Check whether given option is a group separator. + * + * @return index of the group definition that matched or -1 if none + */ +static int match_group_separator(const OptionGroupDef *groups, int nb_groups, + const char *opt) +{ + int i; + + for (i = 0; i < nb_groups; i++) { + const OptionGroupDef *p = &groups[i]; + if (p->sep && !strcmp(p->sep, opt)) + return i; + } + + return -1; +} + +/* + * Finish parsing an option group. + * + * @param group_idx which group definition should this group belong to + * @param arg argument of the group delimiting option + */ +static void finish_group(OptionParseContext *octx, int group_idx, + const char *arg) +{ + OptionGroupList *l = &octx->groups[group_idx]; + OptionGroup *g; + + GROW_ARRAY(l->groups, l->nb_groups); + g = &l->groups[l->nb_groups - 1]; + + *g = octx->cur_group; + g->arg = arg; + g->group_def = l->group_def; + g->sws_dict = sws_dict; + g->swr_opts = swr_opts; + g->codec_opts = codec_opts; + g->format_opts = format_opts; + g->resample_opts = resample_opts; + + codec_opts = NULL; + format_opts = NULL; + resample_opts = NULL; + sws_dict = NULL; + swr_opts = NULL; + init_opts(); + + memset(&octx->cur_group, 0, sizeof(octx->cur_group)); +} + +/* + * Add an option instance to currently parsed group. + */ +static void add_opt(OptionParseContext *octx, const OptionDef *opt, + const char *key, const char *val) +{ + int global = !(opt->flags & (OPT_PERFILE | OPT_SPEC | OPT_OFFSET)); + OptionGroup *g = global ? &octx->global_opts : &octx->cur_group; + + GROW_ARRAY(g->opts, g->nb_opts); + g->opts[g->nb_opts - 1].opt = opt; + g->opts[g->nb_opts - 1].key = key; + g->opts[g->nb_opts - 1].val = val; +} + +static void init_parse_context(OptionParseContext *octx, + const OptionGroupDef *groups, int nb_groups) +{ + static const OptionGroupDef global_group = { "global" }; + int i; + + memset(octx, 0, sizeof(*octx)); + + octx->nb_groups = nb_groups; + octx->groups = av_mallocz_array(octx->nb_groups, sizeof(*octx->groups)); + if (!octx->groups) + exit_program(1); + + for (i = 0; i < octx->nb_groups; i++) + octx->groups[i].group_def = &groups[i]; + + octx->global_opts.group_def = &global_group; + octx->global_opts.arg = ""; + + init_opts(); +} + +void uninit_parse_context(OptionParseContext *octx) +{ + int i, j; + + for (i = 0; i < octx->nb_groups; i++) { + OptionGroupList *l = &octx->groups[i]; + + for (j = 0; j < l->nb_groups; j++) { + av_freep(&l->groups[j].opts); + av_dict_free(&l->groups[j].codec_opts); + av_dict_free(&l->groups[j].format_opts); + av_dict_free(&l->groups[j].resample_opts); + + av_dict_free(&l->groups[j].sws_dict); + av_dict_free(&l->groups[j].swr_opts); + } + av_freep(&l->groups); + } + av_freep(&octx->groups); + + av_freep(&octx->cur_group.opts); + av_freep(&octx->global_opts.opts); + + uninit_opts(); +} + +int split_commandline(OptionParseContext *octx, int argc, char *argv[], + const OptionDef *options, + const OptionGroupDef *groups, int nb_groups) +{ + int optindex = 1; + int dashdash = -2; + + /* perform system-dependent conversions for arguments list */ + prepare_app_arguments(&argc, &argv); + + init_parse_context(octx, groups, nb_groups); + av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n"); + + while (optindex < argc) { + const char *opt = argv[optindex++], *arg; + const OptionDef *po; + int ret; + + av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt); + + if (opt[0] == '-' && opt[1] == '-' && !opt[2]) { + dashdash = optindex; + continue; + } + /* unnamed group separators, e.g. output filename */ + if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) { + finish_group(octx, 0, opt); + av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name); + continue; + } + opt++; + +#define GET_ARG(arg) \ +do { \ + arg = argv[optindex++]; \ + if (!arg) { \ + av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);\ + return AVERROR(EINVAL); \ + } \ +} while (0) + + /* named group separators, e.g. -i */ + if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) { + GET_ARG(arg); + finish_group(octx, ret, arg); + av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n", + groups[ret].name, arg); + continue; + } + + /* normal options */ + po = find_option(options, opt); + if (po->name) { + if (po->flags & OPT_EXIT) { + /* optional argument, e.g. -h */ + arg = argv[optindex++]; + } else if (po->flags & HAS_ARG) { + GET_ARG(arg); + } else { + arg = "1"; + } + + add_opt(octx, po, opt, arg); + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " + "argument '%s'.\n", po->name, po->help, arg); + continue; + } + + /* AVOptions */ + if (argv[optindex]) { + ret = opt_default(NULL, opt, argv[optindex]); + if (ret >= 0) { + av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with " + "argument '%s'.\n", opt, argv[optindex]); + optindex++; + continue; + } else if (ret != AVERROR_OPTION_NOT_FOUND) { + av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' " + "with argument '%s'.\n", opt, argv[optindex]); + return ret; + } + } + + /* boolean -nofoo options */ + if (opt[0] == 'n' && opt[1] == 'o' && + (po = find_option(options, opt + 2)) && + po->name && po->flags & OPT_BOOL) { + add_opt(octx, po, opt, "0"); + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " + "argument 0.\n", po->name, po->help); + continue; + } + + av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'.\n", opt); + return AVERROR_OPTION_NOT_FOUND; + } + + if (octx->cur_group.nb_opts || codec_opts || format_opts || resample_opts) + av_log(NULL, AV_LOG_WARNING, "Trailing options were found on the " + "commandline.\n"); + + av_log(NULL, AV_LOG_DEBUG, "Finished splitting the commandline.\n"); + + return 0; +} + +int opt_cpuflags(void *optctx, const char *opt, const char *arg) +{ + int ret; + unsigned flags = av_get_cpu_flags(); + + if ((ret = av_parse_cpu_caps(&flags, arg)) < 0) + return ret; + + av_force_cpu_flags(flags); + return 0; +} + +int opt_loglevel(void *optctx, const char *opt, const char *arg) +{ + const struct { const char *name; int level; } log_levels[] = { + { "quiet" , AV_LOG_QUIET }, + { "panic" , AV_LOG_PANIC }, + { "fatal" , AV_LOG_FATAL }, + { "error" , AV_LOG_ERROR }, + { "warning", AV_LOG_WARNING }, + { "info" , AV_LOG_INFO }, + { "verbose", AV_LOG_VERBOSE }, + { "debug" , AV_LOG_DEBUG }, + { "trace" , AV_LOG_TRACE }, + }; + char *tail; + int level; + int flags; + int i; + + flags = av_log_get_flags(); + tail = strstr(arg, "repeat"); + if (tail) + flags &= ~AV_LOG_SKIP_REPEATED; + else + flags |= AV_LOG_SKIP_REPEATED; + + av_log_set_flags(flags); + if (tail == arg) + arg += 6 + (arg[6]=='+'); + if(tail && !*arg) + return 0; + + for (i = 0; i < FF_ARRAY_ELEMS(log_levels); i++) { + if (!strcmp(log_levels[i].name, arg)) { + av_log_set_level(log_levels[i].level); + return 0; + } + } + + level = strtol(arg, &tail, 10); + if (*tail) { + av_log(NULL, AV_LOG_FATAL, "Invalid loglevel \"%s\". " + "Possible levels are numbers or:\n", arg); + for (i = 0; i < FF_ARRAY_ELEMS(log_levels); i++) + av_log(NULL, AV_LOG_FATAL, "\"%s\"\n", log_levels[i].name); + exit_program(1); + } + av_log_set_level(level); + return 0; +} + +static void expand_filename_template(AVBPrint *bp, const char *template, + struct tm *tm) +{ + int c; + + while ((c = *(template++))) { + if (c == '%') { + if (!(c = *(template++))) + break; + switch (c) { + case 'p': + av_bprintf(bp, "%s", program_name); + break; + case 't': + av_bprintf(bp, "%04d%02d%02d-%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + break; + case '%': + av_bprint_chars(bp, c, 1); + break; + } + } else { + av_bprint_chars(bp, c, 1); + } + } +} + +static int init_report(const char *env) +{ + char *filename_template = NULL; + char *key, *val; + int ret, count = 0; + time_t now; + struct tm *tm; + AVBPrint filename; + + if (report_file) /* already opened */ + return 0; + time(&now); + tm = localtime(&now); + + while (env && *env) { + if ((ret = av_opt_get_key_value(&env, "=", ":", 0, &key, &val)) < 0) { + if (count) + av_log(NULL, AV_LOG_ERROR, + "Failed to parse FFREPORT environment variable: %s\n", + av_err2str(ret)); + break; + } + if (*env) + env++; + count++; + if (!strcmp(key, "file")) { + av_free(filename_template); + filename_template = val; + val = NULL; + } else if (!strcmp(key, "level")) { + char *tail; + report_file_level = strtol(val, &tail, 10); + if (*tail) { + av_log(NULL, AV_LOG_FATAL, "Invalid report file level\n"); + exit_program(1); + } + } else { + av_log(NULL, AV_LOG_ERROR, "Unknown key '%s' in FFREPORT\n", key); + } + av_free(val); + av_free(key); + } + + av_bprint_init(&filename, 0, 1); + expand_filename_template(&filename, + av_x_if_null(filename_template, "%p-%t.log"), tm); + av_free(filename_template); + if (!av_bprint_is_complete(&filename)) { + av_log(NULL, AV_LOG_ERROR, "Out of memory building report file name\n"); + return AVERROR(ENOMEM); + } + + report_file = fopen(filename.str, "w"); + if (!report_file) { + int ret = AVERROR(errno); + av_log(NULL, AV_LOG_ERROR, "Failed to open report \"%s\": %s\n", + filename.str, strerror(errno)); + return ret; + } + av_log_set_callback(log_callback_report); + av_log(NULL, AV_LOG_INFO, + "%s started on %04d-%02d-%02d at %02d:%02d:%02d\n" + "Report written to \"%s\"\n", + program_name, + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + filename.str); + av_bprint_finalize(&filename, NULL); + return 0; +} + +int opt_report(const char *opt) +{ + return init_report(NULL); +} + +int opt_max_alloc(void *optctx, const char *opt, const char *arg) +{ + char *tail; + size_t max; + + max = strtol(arg, &tail, 10); + if (*tail) { + av_log(NULL, AV_LOG_FATAL, "Invalid max_alloc \"%s\".\n", arg); + exit_program(1); + } + av_max_alloc(max); + return 0; +} + +int opt_timelimit(void *optctx, const char *opt, const char *arg) +{ +#if HAVE_SETRLIMIT + int lim = parse_number_or_die(opt, arg, OPT_INT64, 0, INT_MAX); + struct rlimit rl = { lim, lim + 1 }; + if (setrlimit(RLIMIT_CPU, &rl)) + perror("setrlimit"); +#else + av_log(NULL, AV_LOG_WARNING, "-%s not implemented on this OS\n", opt); +#endif + return 0; +} + +void print_error(const char *filename, int err) +{ + char errbuf[128]; + const char *errbuf_ptr = errbuf; + + if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) + errbuf_ptr = strerror(AVUNERROR(err)); + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr); +} + +static int warned_cfg = 0; + +#define INDENT 1 +#define SHOW_VERSION 2 +#define SHOW_CONFIG 4 +#define SHOW_COPYRIGHT 8 + +#define PRINT_LIB_INFO(libname, LIBNAME, flags, level) \ + if (CONFIG_##LIBNAME) { \ + const char *indent = flags & INDENT? " " : ""; \ + if (flags & SHOW_VERSION) { \ + unsigned int version = libname##_version(); \ + av_log(NULL, level, \ + "%slib%-11s %2d.%3d.%3d / %2d.%3d.%3d\n", \ + indent, #libname, \ + LIB##LIBNAME##_VERSION_MAJOR, \ + LIB##LIBNAME##_VERSION_MINOR, \ + LIB##LIBNAME##_VERSION_MICRO, \ + AV_VERSION_MAJOR(version), AV_VERSION_MINOR(version),\ + AV_VERSION_MICRO(version)); \ + } \ + if (flags & SHOW_CONFIG) { \ + const char *cfg = libname##_configuration(); \ + if (strcmp(FFMPEG_CONFIGURATION, cfg)) { \ + if (!warned_cfg) { \ + av_log(NULL, level, \ + "%sWARNING: library configuration mismatch\n", \ + indent); \ + warned_cfg = 1; \ + } \ + av_log(NULL, level, "%s%-11s configuration: %s\n", \ + indent, #libname, cfg); \ + } \ + } \ + } \ + +static void print_all_libs_info(int flags, int level) +{ + PRINT_LIB_INFO(avutil, AVUTIL, flags, level); + PRINT_LIB_INFO(avcodec, AVCODEC, flags, level); + PRINT_LIB_INFO(avformat, AVFORMAT, flags, level); + PRINT_LIB_INFO(avdevice, AVDEVICE, flags, level); + PRINT_LIB_INFO(avfilter, AVFILTER, flags, level); + PRINT_LIB_INFO(avresample, AVRESAMPLE, flags, level); + PRINT_LIB_INFO(swscale, SWSCALE, flags, level); + PRINT_LIB_INFO(swresample, SWRESAMPLE, flags, level); + PRINT_LIB_INFO(postproc, POSTPROC, flags, level); +} + +static void print_program_info(int flags, int level) +{ + const char *indent = flags & INDENT? " " : ""; + + av_log(NULL, level, "%s version " FFMPEG_VERSION, program_name); + if (flags & SHOW_COPYRIGHT) + av_log(NULL, level, " Copyright (c) %d-%d the FFmpeg developers", + program_birth_year, CONFIG_THIS_YEAR); + av_log(NULL, level, "\n"); + av_log(NULL, level, "%sbuilt with %s\n", indent, CC_IDENT); + + av_log(NULL, level, "%sconfiguration: " FFMPEG_CONFIGURATION "\n", indent); +} + +static void print_buildconf(int flags, int level) +{ + const char *indent = flags & INDENT ? " " : ""; + char str[] = { FFMPEG_CONFIGURATION }; + char *conflist, *remove_tilde, *splitconf; + + // Change all the ' --' strings to '~--' so that + // they can be identified as tokens. + while ((conflist = strstr(str, " --")) != NULL) { + strncpy(conflist, "~--", 3); + } + + // Compensate for the weirdness this would cause + // when passing 'pkg-config --static'. + while ((remove_tilde = strstr(str, "pkg-config~")) != NULL) { + strncpy(remove_tilde, "pkg-config ", 11); + } + + splitconf = strtok(str, "~"); + av_log(NULL, level, "\n%sconfiguration:\n", indent); + while (splitconf != NULL) { + av_log(NULL, level, "%s%s%s\n", indent, indent, splitconf); + splitconf = strtok(NULL, "~"); + } +} + +void show_banner(int argc, char **argv, const OptionDef *options) +{ + int idx = locate_option(argc, argv, options, "version"); + if (hide_banner || idx) + return; + + print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO); + print_all_libs_info(INDENT|SHOW_CONFIG, AV_LOG_INFO); + print_all_libs_info(INDENT|SHOW_VERSION, AV_LOG_INFO); +} + +int show_version(void *optctx, const char *opt, const char *arg) +{ + av_log_set_callback(log_callback_help); + print_program_info (SHOW_COPYRIGHT, AV_LOG_INFO); + print_all_libs_info(SHOW_VERSION, AV_LOG_INFO); + + return 0; +} + +int show_buildconf(void *optctx, const char *opt, const char *arg) +{ + av_log_set_callback(log_callback_help); + print_buildconf (INDENT|0, AV_LOG_INFO); + + return 0; +} + +int show_license(void *optctx, const char *opt, const char *arg) +{ +#if CONFIG_NONFREE + printf( + "This version of %s has nonfree parts compiled in.\n" + "Therefore it is not legally redistributable.\n", + program_name ); +#elif CONFIG_GPLV3 + printf( + "%s is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "%s is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with %s. If not, see <http://www.gnu.org/licenses/>.\n", + program_name, program_name, program_name ); +#elif CONFIG_GPL + printf( + "%s is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "%s is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with %s; if not, write to the Free Software\n" + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n", + program_name, program_name, program_name ); +#elif CONFIG_LGPLV3 + printf( + "%s is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU Lesser General Public License as published by\n" + "the Free Software Foundation; either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "%s is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU Lesser General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU Lesser General Public License\n" + "along with %s. If not, see <http://www.gnu.org/licenses/>.\n", + program_name, program_name, program_name ); +#else + printf( + "%s is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU Lesser General Public\n" + "License as published by the Free Software Foundation; either\n" + "version 2.1 of the License, or (at your option) any later version.\n" + "\n" + "%s is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + "Lesser General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU Lesser General Public\n" + "License along with %s; if not, write to the Free Software\n" + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n", + program_name, program_name, program_name ); +#endif + + return 0; +} + +static int is_device(const AVClass *avclass) +{ + if (!avclass) + return 0; + return AV_IS_INPUT_DEVICE(avclass->category) || AV_IS_OUTPUT_DEVICE(avclass->category); +} + +static int show_formats_devices(void *optctx, const char *opt, const char *arg, int device_only, int muxdemuxers) +{ + AVInputFormat *ifmt = NULL; + AVOutputFormat *ofmt = NULL; + const char *last_name; + int is_dev; + + printf("%s\n" + " D. = Demuxing supported\n" + " .E = Muxing supported\n" + " --\n", device_only ? "Devices:" : "File formats:"); + last_name = "000"; + for (;;) { + int decode = 0; + int encode = 0; + const char *name = NULL; + const char *long_name = NULL; + + if (muxdemuxers !=SHOW_DEMUXERS) { + while ((ofmt = av_oformat_next(ofmt))) { + is_dev = is_device(ofmt->priv_class); + if (!is_dev && device_only) + continue; + if ((!name || strcmp(ofmt->name, name) < 0) && + strcmp(ofmt->name, last_name) > 0) { + name = ofmt->name; + long_name = ofmt->long_name; + encode = 1; + } + } + } + if (muxdemuxers != SHOW_MUXERS) { + while ((ifmt = av_iformat_next(ifmt))) { + is_dev = is_device(ifmt->priv_class); + if (!is_dev && device_only) + continue; + if ((!name || strcmp(ifmt->name, name) < 0) && + strcmp(ifmt->name, last_name) > 0) { + name = ifmt->name; + long_name = ifmt->long_name; + encode = 0; + } + if (name && strcmp(ifmt->name, name) == 0) + decode = 1; + } + } + if (!name) + break; + last_name = name; + + printf(" %s%s %-15s %s\n", + decode ? "D" : " ", + encode ? "E" : " ", + name, + long_name ? long_name:" "); + } + return 0; +} + +int show_formats(void *optctx, const char *opt, const char *arg) +{ + return show_formats_devices(optctx, opt, arg, 0, SHOW_DEFAULT); +} + +int show_muxers(void *optctx, const char *opt, const char *arg) +{ + return show_formats_devices(optctx, opt, arg, 0, SHOW_MUXERS); +} + +int show_demuxers(void *optctx, const char *opt, const char *arg) +{ + return show_formats_devices(optctx, opt, arg, 0, SHOW_DEMUXERS); +} + +int show_devices(void *optctx, const char *opt, const char *arg) +{ + return show_formats_devices(optctx, opt, arg, 1, SHOW_DEFAULT); +} + +#define PRINT_CODEC_SUPPORTED(codec, field, type, list_name, term, get_name) \ + if (codec->field) { \ + const type *p = codec->field; \ + \ + printf(" Supported " list_name ":"); \ + while (*p != term) { \ + get_name(*p); \ + printf(" %s", name); \ + p++; \ + } \ + printf("\n"); \ + } \ + +static void print_codec(const AVCodec *c) +{ + int encoder = av_codec_is_encoder(c); + + printf("%s %s [%s]:\n", encoder ? "Encoder" : "Decoder", c->name, + c->long_name ? c->long_name : ""); + + printf(" General capabilities: "); + if (c->capabilities & AV_CODEC_CAP_DRAW_HORIZ_BAND) + printf("horizband "); + if (c->capabilities & AV_CODEC_CAP_DR1) + printf("dr1 "); + if (c->capabilities & AV_CODEC_CAP_TRUNCATED) + printf("trunc "); + if (c->capabilities & AV_CODEC_CAP_DELAY) + printf("delay "); + if (c->capabilities & AV_CODEC_CAP_SMALL_LAST_FRAME) + printf("small "); + if (c->capabilities & AV_CODEC_CAP_SUBFRAMES) + printf("subframes "); + if (c->capabilities & AV_CODEC_CAP_EXPERIMENTAL) + printf("exp "); + if (c->capabilities & AV_CODEC_CAP_CHANNEL_CONF) + printf("chconf "); + if (c->capabilities & AV_CODEC_CAP_PARAM_CHANGE) + printf("paramchange "); + if (c->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) + printf("variable "); + if (c->capabilities & (AV_CODEC_CAP_FRAME_THREADS | + AV_CODEC_CAP_SLICE_THREADS | + AV_CODEC_CAP_AUTO_THREADS)) + printf("threads "); + if (!c->capabilities) + printf("none"); + printf("\n"); + + if (c->type == AVMEDIA_TYPE_VIDEO || + c->type == AVMEDIA_TYPE_AUDIO) { + printf(" Threading capabilities: "); + switch (c->capabilities & (AV_CODEC_CAP_FRAME_THREADS | + AV_CODEC_CAP_SLICE_THREADS | + AV_CODEC_CAP_AUTO_THREADS)) { + case AV_CODEC_CAP_FRAME_THREADS | + AV_CODEC_CAP_SLICE_THREADS: printf("frame and slice"); break; + case AV_CODEC_CAP_FRAME_THREADS: printf("frame"); break; + case AV_CODEC_CAP_SLICE_THREADS: printf("slice"); break; + case AV_CODEC_CAP_AUTO_THREADS : printf("auto"); break; + default: printf("none"); break; + } + printf("\n"); + } + + if (c->supported_framerates) { + const AVRational *fps = c->supported_framerates; + + printf(" Supported framerates:"); + while (fps->num) { + printf(" %d/%d", fps->num, fps->den); + fps++; + } + printf("\n"); + } + PRINT_CODEC_SUPPORTED(c, pix_fmts, enum AVPixelFormat, "pixel formats", + AV_PIX_FMT_NONE, GET_PIX_FMT_NAME); + PRINT_CODEC_SUPPORTED(c, supported_samplerates, int, "sample rates", 0, + GET_SAMPLE_RATE_NAME); + PRINT_CODEC_SUPPORTED(c, sample_fmts, enum AVSampleFormat, "sample formats", + AV_SAMPLE_FMT_NONE, GET_SAMPLE_FMT_NAME); + PRINT_CODEC_SUPPORTED(c, channel_layouts, uint64_t, "channel layouts", + 0, GET_CH_LAYOUT_DESC); + + if (c->priv_class) { + show_help_children(c->priv_class, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_DECODING_PARAM); + } +} + +static char get_media_type_char(enum AVMediaType type) +{ + switch (type) { + case AVMEDIA_TYPE_VIDEO: return 'V'; + case AVMEDIA_TYPE_AUDIO: return 'A'; + case AVMEDIA_TYPE_DATA: return 'D'; + case AVMEDIA_TYPE_SUBTITLE: return 'S'; + case AVMEDIA_TYPE_ATTACHMENT:return 'T'; + default: return '?'; + } +} + +static const AVCodec *next_codec_for_id(enum AVCodecID id, const AVCodec *prev, + int encoder) +{ + while ((prev = av_codec_next(prev))) { + if (prev->id == id && + (encoder ? av_codec_is_encoder(prev) : av_codec_is_decoder(prev))) + return prev; + } + return NULL; +} + +static int compare_codec_desc(const void *a, const void *b) +{ + const AVCodecDescriptor * const *da = a; + const AVCodecDescriptor * const *db = b; + + return (*da)->type != (*db)->type ? FFDIFFSIGN((*da)->type, (*db)->type) : + strcmp((*da)->name, (*db)->name); +} + +static unsigned get_codecs_sorted(const AVCodecDescriptor ***rcodecs) +{ + const AVCodecDescriptor *desc = NULL; + const AVCodecDescriptor **codecs; + unsigned nb_codecs = 0, i = 0; + + while ((desc = avcodec_descriptor_next(desc))) + nb_codecs++; + if (!(codecs = av_calloc(nb_codecs, sizeof(*codecs)))) { + av_log(NULL, AV_LOG_ERROR, "Out of memory\n"); + exit_program(1); + } + desc = NULL; + while ((desc = avcodec_descriptor_next(desc))) + codecs[i++] = desc; + av_assert0(i == nb_codecs); + qsort(codecs, nb_codecs, sizeof(*codecs), compare_codec_desc); + *rcodecs = codecs; + return nb_codecs; +} + +static void print_codecs_for_id(enum AVCodecID id, int encoder) +{ + const AVCodec *codec = NULL; + + printf(" (%s: ", encoder ? "encoders" : "decoders"); + + while ((codec = next_codec_for_id(id, codec, encoder))) + printf("%s ", codec->name); + + printf(")"); +} + +int show_codecs(void *optctx, const char *opt, const char *arg) +{ + const AVCodecDescriptor **codecs; + unsigned i, nb_codecs = get_codecs_sorted(&codecs); + + printf("Codecs:\n" + " D..... = Decoding supported\n" + " .E.... = Encoding supported\n" + " ..V... = Video codec\n" + " ..A... = Audio codec\n" + " ..S... = Subtitle codec\n" + " ...I.. = Intra frame-only codec\n" + " ....L. = Lossy compression\n" + " .....S = Lossless compression\n" + " -------\n"); + for (i = 0; i < nb_codecs; i++) { + const AVCodecDescriptor *desc = codecs[i]; + const AVCodec *codec = NULL; + + if (strstr(desc->name, "_deprecated")) + continue; + + printf(" "); + printf(avcodec_find_decoder(desc->id) ? "D" : "."); + printf(avcodec_find_encoder(desc->id) ? "E" : "."); + + printf("%c", get_media_type_char(desc->type)); + printf((desc->props & AV_CODEC_PROP_INTRA_ONLY) ? "I" : "."); + printf((desc->props & AV_CODEC_PROP_LOSSY) ? "L" : "."); + printf((desc->props & AV_CODEC_PROP_LOSSLESS) ? "S" : "."); + + printf(" %-20s %s", desc->name, desc->long_name ? desc->long_name : ""); + + /* print decoders/encoders when there's more than one or their + * names are different from codec name */ + while ((codec = next_codec_for_id(desc->id, codec, 0))) { + if (strcmp(codec->name, desc->name)) { + print_codecs_for_id(desc->id, 0); + break; + } + } + codec = NULL; + while ((codec = next_codec_for_id(desc->id, codec, 1))) { + if (strcmp(codec->name, desc->name)) { + print_codecs_for_id(desc->id, 1); + break; + } + } + + printf("\n"); + } + av_free(codecs); + return 0; +} + +static void print_codecs(int encoder) +{ + const AVCodecDescriptor **codecs; + unsigned i, nb_codecs = get_codecs_sorted(&codecs); + + printf("%s:\n" + " V..... = Video\n" + " A..... = Audio\n" + " S..... = Subtitle\n" + " .F.... = Frame-level multithreading\n" + " ..S... = Slice-level multithreading\n" + " ...X.. = Codec is experimental\n" + " ....B. = Supports draw_horiz_band\n" + " .....D = Supports direct rendering method 1\n" + " ------\n", + encoder ? "Encoders" : "Decoders"); + for (i = 0; i < nb_codecs; i++) { + const AVCodecDescriptor *desc = codecs[i]; + const AVCodec *codec = NULL; + + while ((codec = next_codec_for_id(desc->id, codec, encoder))) { + printf(" %c", get_media_type_char(desc->type)); + printf((codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) ? "F" : "."); + printf((codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) ? "S" : "."); + printf((codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) ? "X" : "."); + printf((codec->capabilities & AV_CODEC_CAP_DRAW_HORIZ_BAND)?"B" : "."); + printf((codec->capabilities & AV_CODEC_CAP_DR1) ? "D" : "."); + + printf(" %-20s %s", codec->name, codec->long_name ? codec->long_name : ""); + if (strcmp(codec->name, desc->name)) + printf(" (codec %s)", desc->name); + + printf("\n"); + } + } + av_free(codecs); +} + +int show_decoders(void *optctx, const char *opt, const char *arg) +{ + print_codecs(0); + return 0; +} + +int show_encoders(void *optctx, const char *opt, const char *arg) +{ + print_codecs(1); + return 0; +} + +int show_bsfs(void *optctx, const char *opt, const char *arg) +{ + const AVBitStreamFilter *bsf = NULL; + void *opaque = NULL; + + printf("Bitstream filters:\n"); + while ((bsf = av_bsf_next(&opaque))) + printf("%s\n", bsf->name); + printf("\n"); + return 0; +} + +int show_protocols(void *optctx, const char *opt, const char *arg) +{ + void *opaque = NULL; + const char *name; + + printf("Supported file protocols:\n" + "Input:\n"); + while ((name = avio_enum_protocols(&opaque, 0))) + printf(" %s\n", name); + printf("Output:\n"); + while ((name = avio_enum_protocols(&opaque, 1))) + printf(" %s\n", name); + return 0; +} + +int show_filters(void *optctx, const char *opt, const char *arg) +{ +#if CONFIG_AVFILTER + const AVFilter *filter = NULL; + char descr[64], *descr_cur; + int i, j; + const AVFilterPad *pad; + + printf("Filters:\n" + " T.. = Timeline support\n" + " .S. = Slice threading\n" + " ..C = Command support\n" + " A = Audio input/output\n" + " V = Video input/output\n" + " N = Dynamic number and/or type of input/output\n" + " | = Source or sink filter\n"); + while ((filter = avfilter_next(filter))) { + descr_cur = descr; + for (i = 0; i < 2; i++) { + if (i) { + *(descr_cur++) = '-'; + *(descr_cur++) = '>'; + } + pad = i ? filter->outputs : filter->inputs; + for (j = 0; pad && avfilter_pad_get_name(pad, j); j++) { + if (descr_cur >= descr + sizeof(descr) - 4) + break; + *(descr_cur++) = get_media_type_char(avfilter_pad_get_type(pad, j)); + } + if (!j) + *(descr_cur++) = ((!i && (filter->flags & AVFILTER_FLAG_DYNAMIC_INPUTS)) || + ( i && (filter->flags & AVFILTER_FLAG_DYNAMIC_OUTPUTS))) ? 'N' : '|'; + } + *descr_cur = 0; + printf(" %c%c%c %-17s %-10s %s\n", + filter->flags & AVFILTER_FLAG_SUPPORT_TIMELINE ? 'T' : '.', + filter->flags & AVFILTER_FLAG_SLICE_THREADS ? 'S' : '.', + filter->process_command ? 'C' : '.', + filter->name, descr, filter->description); + } +#else + printf("No filters available: libavfilter disabled\n"); +#endif + return 0; +} + +int show_colors(void *optctx, const char *opt, const char *arg) +{ + const char *name; + const uint8_t *rgb; + int i; + + printf("%-32s #RRGGBB\n", "name"); + + for (i = 0; name = av_get_known_color_name(i, &rgb); i++) + printf("%-32s #%02x%02x%02x\n", name, rgb[0], rgb[1], rgb[2]); + + return 0; +} + +int show_pix_fmts(void *optctx, const char *opt, const char *arg) +{ + const AVPixFmtDescriptor *pix_desc = NULL; + + printf("Pixel formats:\n" + "I.... = Supported Input format for conversion\n" + ".O... = Supported Output format for conversion\n" + "..H.. = Hardware accelerated format\n" + "...P. = Paletted format\n" + "....B = Bitstream format\n" + "FLAGS NAME NB_COMPONENTS BITS_PER_PIXEL\n" + "-----\n"); + +#if !CONFIG_SWSCALE +# define sws_isSupportedInput(x) 0 +# define sws_isSupportedOutput(x) 0 +#endif + + while ((pix_desc = av_pix_fmt_desc_next(pix_desc))) { + enum AVPixelFormat pix_fmt = av_pix_fmt_desc_get_id(pix_desc); + printf("%c%c%c%c%c %-16s %d %2d\n", + sws_isSupportedInput (pix_fmt) ? 'I' : '.', + sws_isSupportedOutput(pix_fmt) ? 'O' : '.', + pix_desc->flags & AV_PIX_FMT_FLAG_HWACCEL ? 'H' : '.', + pix_desc->flags & AV_PIX_FMT_FLAG_PAL ? 'P' : '.', + pix_desc->flags & AV_PIX_FMT_FLAG_BITSTREAM ? 'B' : '.', + pix_desc->name, + pix_desc->nb_components, + av_get_bits_per_pixel(pix_desc)); + } + return 0; +} + +int show_layouts(void *optctx, const char *opt, const char *arg) +{ + int i = 0; + uint64_t layout, j; + const char *name, *descr; + + printf("Individual channels:\n" + "NAME DESCRIPTION\n"); + for (i = 0; i < 63; i++) { + name = av_get_channel_name((uint64_t)1 << i); + if (!name) + continue; + descr = av_get_channel_description((uint64_t)1 << i); + printf("%-14s %s\n", name, descr); + } + printf("\nStandard channel layouts:\n" + "NAME DECOMPOSITION\n"); + for (i = 0; !av_get_standard_channel_layout(i, &layout, &name); i++) { + if (name) { + printf("%-14s ", name); + for (j = 1; j; j <<= 1) + if ((layout & j)) + printf("%s%s", (layout & (j - 1)) ? "+" : "", av_get_channel_name(j)); + printf("\n"); + } + } + return 0; +} + +int show_sample_fmts(void *optctx, const char *opt, const char *arg) +{ + int i; + char fmt_str[128]; + for (i = -1; i < AV_SAMPLE_FMT_NB; i++) + printf("%s\n", av_get_sample_fmt_string(fmt_str, sizeof(fmt_str), i)); + return 0; +} + +static void show_help_codec(const char *name, int encoder) +{ + const AVCodecDescriptor *desc; + const AVCodec *codec; + + if (!name) { + av_log(NULL, AV_LOG_ERROR, "No codec name specified.\n"); + return; + } + + codec = encoder ? avcodec_find_encoder_by_name(name) : + avcodec_find_decoder_by_name(name); + + if (codec) + print_codec(codec); + else if ((desc = avcodec_descriptor_get_by_name(name))) { + int printed = 0; + + while ((codec = next_codec_for_id(desc->id, codec, encoder))) { + printed = 1; + print_codec(codec); + } + + if (!printed) { + av_log(NULL, AV_LOG_ERROR, "Codec '%s' is known to FFmpeg, " + "but no %s for it are available. FFmpeg might need to be " + "recompiled with additional external libraries.\n", + name, encoder ? "encoders" : "decoders"); + } + } else { + av_log(NULL, AV_LOG_ERROR, "Codec '%s' is not recognized by FFmpeg.\n", + name); + } +} + +static void show_help_demuxer(const char *name) +{ + const AVInputFormat *fmt = av_find_input_format(name); + + if (!fmt) { + av_log(NULL, AV_LOG_ERROR, "Unknown format '%s'.\n", name); + return; + } + + printf("Demuxer %s [%s]:\n", fmt->name, fmt->long_name); + + if (fmt->extensions) + printf(" Common extensions: %s.\n", fmt->extensions); + + if (fmt->priv_class) + show_help_children(fmt->priv_class, AV_OPT_FLAG_DECODING_PARAM); +} + +static void show_help_muxer(const char *name) +{ + const AVCodecDescriptor *desc; + const AVOutputFormat *fmt = av_guess_format(name, NULL, NULL); + + if (!fmt) { + av_log(NULL, AV_LOG_ERROR, "Unknown format '%s'.\n", name); + return; + } + + printf("Muxer %s [%s]:\n", fmt->name, fmt->long_name); + + if (fmt->extensions) + printf(" Common extensions: %s.\n", fmt->extensions); + if (fmt->mime_type) + printf(" Mime type: %s.\n", fmt->mime_type); + if (fmt->video_codec != AV_CODEC_ID_NONE && + (desc = avcodec_descriptor_get(fmt->video_codec))) { + printf(" Default video codec: %s.\n", desc->name); + } + if (fmt->audio_codec != AV_CODEC_ID_NONE && + (desc = avcodec_descriptor_get(fmt->audio_codec))) { + printf(" Default audio codec: %s.\n", desc->name); + } + if (fmt->subtitle_codec != AV_CODEC_ID_NONE && + (desc = avcodec_descriptor_get(fmt->subtitle_codec))) { + printf(" Default subtitle codec: %s.\n", desc->name); + } + + if (fmt->priv_class) + show_help_children(fmt->priv_class, AV_OPT_FLAG_ENCODING_PARAM); +} + +#if CONFIG_AVFILTER +static void show_help_filter(const char *name) +{ +#if CONFIG_AVFILTER + const AVFilter *f = avfilter_get_by_name(name); + int i, count; + + if (!name) { + av_log(NULL, AV_LOG_ERROR, "No filter name specified.\n"); + return; + } else if (!f) { + av_log(NULL, AV_LOG_ERROR, "Unknown filter '%s'.\n", name); + return; + } + + printf("Filter %s\n", f->name); + if (f->description) + printf(" %s\n", f->description); + + if (f->flags & AVFILTER_FLAG_SLICE_THREADS) + printf(" slice threading supported\n"); + + printf(" Inputs:\n"); + count = avfilter_pad_count(f->inputs); + for (i = 0; i < count; i++) { + printf(" #%d: %s (%s)\n", i, avfilter_pad_get_name(f->inputs, i), + media_type_string(avfilter_pad_get_type(f->inputs, i))); + } + if (f->flags & AVFILTER_FLAG_DYNAMIC_INPUTS) + printf(" dynamic (depending on the options)\n"); + else if (!count) + printf(" none (source filter)\n"); + + printf(" Outputs:\n"); + count = avfilter_pad_count(f->outputs); + for (i = 0; i < count; i++) { + printf(" #%d: %s (%s)\n", i, avfilter_pad_get_name(f->outputs, i), + media_type_string(avfilter_pad_get_type(f->outputs, i))); + } + if (f->flags & AVFILTER_FLAG_DYNAMIC_OUTPUTS) + printf(" dynamic (depending on the options)\n"); + else if (!count) + printf(" none (sink filter)\n"); + + if (f->priv_class) + show_help_children(f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | + AV_OPT_FLAG_AUDIO_PARAM); + if (f->flags & AVFILTER_FLAG_SUPPORT_TIMELINE) + printf("This filter has support for timeline through the 'enable' option.\n"); +#else + av_log(NULL, AV_LOG_ERROR, "Build without libavfilter; " + "can not to satisfy request\n"); +#endif +} +#endif + +int show_help(void *optctx, const char *opt, const char *arg) +{ + char *topic, *par; + av_log_set_callback(log_callback_help); + + topic = av_strdup(arg ? arg : ""); + if (!topic) + return AVERROR(ENOMEM); + par = strchr(topic, '='); + if (par) + *par++ = 0; + + if (!*topic) { + show_help_default(topic, par); + } else if (!strcmp(topic, "decoder")) { + show_help_codec(par, 0); + } else if (!strcmp(topic, "encoder")) { + show_help_codec(par, 1); + } else if (!strcmp(topic, "demuxer")) { + show_help_demuxer(par); + } else if (!strcmp(topic, "muxer")) { + show_help_muxer(par); +#if CONFIG_AVFILTER + } else if (!strcmp(topic, "filter")) { + show_help_filter(par); +#endif + } else { + show_help_default(topic, par); + } + + av_freep(&topic); + return 0; +} + +int read_yesno(void) +{ + int c = getchar(); + int yesno = (av_toupper(c) == 'Y'); + + while (c != '\n' && c != EOF) + c = getchar(); + + return yesno; +} + +FILE *get_preset_file(char *filename, size_t filename_size, + const char *preset_name, int is_path, + const char *codec_name) +{ + FILE *f = NULL; + int i; + const char *base[3] = { getenv("FFMPEG_DATADIR"), + getenv("HOME"), + FFMPEG_DATADIR, }; + + if (is_path) { + av_strlcpy(filename, preset_name, filename_size); + f = fopen(filename, "r"); + } else { +#ifdef _WIN32 + char datadir[MAX_PATH], *ls; + base[2] = NULL; + + if (GetModuleFileNameA(GetModuleHandleA(NULL), datadir, sizeof(datadir) - 1)) + { + for (ls = datadir; ls < datadir + strlen(datadir); ls++) + if (*ls == '\\') *ls = '/'; + + if (ls = strrchr(datadir, '/')) + { + *ls = 0; + strncat(datadir, "/ffpresets", sizeof(datadir) - 1 - strlen(datadir)); + base[2] = datadir; + } + } +#endif + for (i = 0; i < 3 && !f; i++) { + if (!base[i]) + continue; + snprintf(filename, filename_size, "%s%s/%s.ffpreset", base[i], + i != 1 ? "" : "/.ffmpeg", preset_name); + f = fopen(filename, "r"); + if (!f && codec_name) { + snprintf(filename, filename_size, + "%s%s/%s-%s.ffpreset", + base[i], i != 1 ? "" : "/.ffmpeg", codec_name, + preset_name); + f = fopen(filename, "r"); + } + } + } + + return f; +} + +int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec) +{ + int ret = avformat_match_stream_specifier(s, st, spec); + if (ret < 0) + av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec); + return ret; +} + +AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, + AVFormatContext *s, AVStream *st, AVCodec *codec) +{ + AVDictionary *ret = NULL; + AVDictionaryEntry *t = NULL; + int flags = s->oformat ? AV_OPT_FLAG_ENCODING_PARAM + : AV_OPT_FLAG_DECODING_PARAM; + char prefix = 0; + const AVClass *cc = avcodec_get_class(); + + if (!codec) + codec = s->oformat ? avcodec_find_encoder(codec_id) + : avcodec_find_decoder(codec_id); + + switch (st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: + prefix = 'v'; + flags |= AV_OPT_FLAG_VIDEO_PARAM; + break; + case AVMEDIA_TYPE_AUDIO: + prefix = 'a'; + flags |= AV_OPT_FLAG_AUDIO_PARAM; + break; + case AVMEDIA_TYPE_SUBTITLE: + prefix = 's'; + flags |= AV_OPT_FLAG_SUBTITLE_PARAM; + break; + } + + while (t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX)) { + char *p = strchr(t->key, ':'); + + /* check stream specification in opt name */ + if (p) + switch (check_stream_specifier(s, st, p + 1)) { + case 1: *p = 0; break; + case 0: continue; + default: exit_program(1); + } + + if (av_opt_find(&cc, t->key, NULL, flags, AV_OPT_SEARCH_FAKE_OBJ) || + !codec || + (codec->priv_class && + av_opt_find(&codec->priv_class, t->key, NULL, flags, + AV_OPT_SEARCH_FAKE_OBJ))) + av_dict_set(&ret, t->key, t->value, 0); + else if (t->key[0] == prefix && + av_opt_find(&cc, t->key + 1, NULL, flags, + AV_OPT_SEARCH_FAKE_OBJ)) + av_dict_set(&ret, t->key + 1, t->value, 0); + + if (p) + *p = ':'; + } + return ret; +} + +AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, + AVDictionary *codec_opts) +{ + int i; + AVDictionary **opts; + + if (!s->nb_streams) + return NULL; + opts = av_mallocz_array(s->nb_streams, sizeof(*opts)); + if (!opts) { + av_log(NULL, AV_LOG_ERROR, + "Could not alloc memory for stream options.\n"); + return NULL; + } + for (i = 0; i < s->nb_streams; i++) + opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, + s, s->streams[i], NULL); + return opts; +} + +void *grow_array(void *array, int elem_size, int *size, int new_size) +{ + if (new_size >= INT_MAX / elem_size) { + av_log(NULL, AV_LOG_ERROR, "Array too big.\n"); + exit_program(1); + } + if (*size < new_size) { + uint8_t *tmp = av_realloc_array(array, new_size, elem_size); + if (!tmp) { + av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); + exit_program(1); + } + memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size); + *size = new_size; + return tmp; + } + return array; +} + +double get_rotation(AVStream *st) +{ + uint8_t* displaymatrix = av_stream_get_side_data(st, + AV_PKT_DATA_DISPLAYMATRIX, NULL); + double theta = 0; + if (displaymatrix) + theta = -av_display_rotation_get((int32_t*) displaymatrix); + + theta -= 360*floor(theta/360 + 0.9/360); + + if (fabs(theta - 90*round(theta/90)) > 2) + av_log(NULL, AV_LOG_WARNING, "Odd rotation angle.\n" + "If you want to help, upload a sample " + "of this file to ftp://upload.ffmpeg.org/incoming/ " + "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)"); + + return theta; +} + +#if CONFIG_AVDEVICE +static int print_device_sources(AVInputFormat *fmt, AVDictionary *opts) +{ + int ret, i; + AVDeviceInfoList *device_list = NULL; + + if (!fmt || !fmt->priv_class || !AV_IS_INPUT_DEVICE(fmt->priv_class->category)) + return AVERROR(EINVAL); + + printf("Auto-detected sources for %s:\n", fmt->name); + if (!fmt->get_device_list) { + ret = AVERROR(ENOSYS); + printf("Cannot list sources. Not implemented.\n"); + goto fail; + } + + if ((ret = avdevice_list_input_sources(fmt, NULL, opts, &device_list)) < 0) { + printf("Cannot list sources.\n"); + goto fail; + } + + for (i = 0; i < device_list->nb_devices; i++) { + printf("%s %s [%s]\n", device_list->default_device == i ? "*" : " ", + device_list->devices[i]->device_name, device_list->devices[i]->device_description); + } + + fail: + avdevice_free_list_devices(&device_list); + return ret; +} + +static int print_device_sinks(AVOutputFormat *fmt, AVDictionary *opts) +{ + int ret, i; + AVDeviceInfoList *device_list = NULL; + + if (!fmt || !fmt->priv_class || !AV_IS_OUTPUT_DEVICE(fmt->priv_class->category)) + return AVERROR(EINVAL); + + printf("Auto-detected sinks for %s:\n", fmt->name); + if (!fmt->get_device_list) { + ret = AVERROR(ENOSYS); + printf("Cannot list sinks. Not implemented.\n"); + goto fail; + } + + if ((ret = avdevice_list_output_sinks(fmt, NULL, opts, &device_list)) < 0) { + printf("Cannot list sinks.\n"); + goto fail; + } + + for (i = 0; i < device_list->nb_devices; i++) { + printf("%s %s [%s]\n", device_list->default_device == i ? "*" : " ", + device_list->devices[i]->device_name, device_list->devices[i]->device_description); + } + + fail: + avdevice_free_list_devices(&device_list); + return ret; +} + +static int show_sinks_sources_parse_arg(const char *arg, char **dev, AVDictionary **opts) +{ + int ret; + if (arg) { + char *opts_str = NULL; + av_assert0(dev && opts); + *dev = av_strdup(arg); + if (!*dev) + return AVERROR(ENOMEM); + if ((opts_str = strchr(*dev, ','))) { + *(opts_str++) = '\0'; + if (opts_str[0] && ((ret = av_dict_parse_string(opts, opts_str, "=", ":", 0)) < 0)) { + av_freep(dev); + return ret; + } + } + } else + printf("\nDevice name is not provided.\n" + "You can pass devicename[,opt1=val1[,opt2=val2...]] as an argument.\n\n"); + return 0; +} + +int show_sources(void *optctx, const char *opt, const char *arg) +{ + AVInputFormat *fmt = NULL; + char *dev = NULL; + AVDictionary *opts = NULL; + int ret = 0; + int error_level = av_log_get_level(); + + av_log_set_level(AV_LOG_ERROR); + + if ((ret = show_sinks_sources_parse_arg(arg, &dev, &opts)) < 0) + goto fail; + + do { + fmt = av_input_audio_device_next(fmt); + if (fmt) { + if (!strcmp(fmt->name, "lavfi")) + continue; //it's pointless to probe lavfi + if (dev && !av_match_name(dev, fmt->name)) + continue; + print_device_sources(fmt, opts); + } + } while (fmt); + do { + fmt = av_input_video_device_next(fmt); + if (fmt) { + if (dev && !av_match_name(dev, fmt->name)) + continue; + print_device_sources(fmt, opts); + } + } while (fmt); + fail: + av_dict_free(&opts); + av_free(dev); + av_log_set_level(error_level); + return ret; +} + +int show_sinks(void *optctx, const char *opt, const char *arg) +{ + AVOutputFormat *fmt = NULL; + char *dev = NULL; + AVDictionary *opts = NULL; + int ret = 0; + int error_level = av_log_get_level(); + + av_log_set_level(AV_LOG_ERROR); + + if ((ret = show_sinks_sources_parse_arg(arg, &dev, &opts)) < 0) + goto fail; + + do { + fmt = av_output_audio_device_next(fmt); + if (fmt) { + if (dev && !av_match_name(dev, fmt->name)) + continue; + print_device_sinks(fmt, opts); + } + } while (fmt); + do { + fmt = av_output_video_device_next(fmt); + if (fmt) { + if (dev && !av_match_name(dev, fmt->name)) + continue; + print_device_sinks(fmt, opts); + } + } while (fmt); + fail: + av_dict_free(&opts); + av_free(dev); + av_log_set_level(error_level); + return ret; +} + +#endif diff --git a/fftools/cmdutils.h b/fftools/cmdutils.h new file mode 100644 index 0000000000..2997ee37b5 --- /dev/null +++ b/fftools/cmdutils.h @@ -0,0 +1,662 @@ +/* + * Various utilities for command line tools + * copyright (c) 2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FFTOOLS_CMDUTILS_H +#define FFTOOLS_CMDUTILS_H + +#include <stdint.h> + +#include "config.h" +#include "libavcodec/avcodec.h" +#include "libavfilter/avfilter.h" +#include "libavformat/avformat.h" +#include "libswscale/swscale.h" + +#ifdef _WIN32 +#undef main /* We don't want SDL to override our main() */ +#endif + +/** + * program name, defined by the program for show_version(). + */ +extern const char program_name[]; + +/** + * program birth year, defined by the program for show_banner() + */ +extern const int program_birth_year; + +extern AVCodecContext *avcodec_opts[AVMEDIA_TYPE_NB]; +extern AVFormatContext *avformat_opts; +extern AVDictionary *sws_dict; +extern AVDictionary *swr_opts; +extern AVDictionary *format_opts, *codec_opts, *resample_opts; +extern int hide_banner; + +/** + * Register a program-specific cleanup routine. + */ +void register_exit(void (*cb)(int ret)); + +/** + * Wraps exit with a program-specific cleanup routine. + */ +void exit_program(int ret) av_noreturn; + +/** + * Initialize dynamic library loading + */ +void init_dynload(void); + +/** + * Initialize the cmdutils option system, in particular + * allocate the *_opts contexts. + */ +void init_opts(void); +/** + * Uninitialize the cmdutils option system, in particular + * free the *_opts contexts and their contents. + */ +void uninit_opts(void); + +/** + * Trivial log callback. + * Only suitable for opt_help and similar since it lacks prefix handling. + */ +void log_callback_help(void* ptr, int level, const char* fmt, va_list vl); + +/** + * Override the cpuflags. + */ +int opt_cpuflags(void *optctx, const char *opt, const char *arg); + +/** + * Fallback for options that are not explicitly handled, these will be + * parsed through AVOptions. + */ +int opt_default(void *optctx, const char *opt, const char *arg); + +/** + * Set the libav* libraries log level. + */ +int opt_loglevel(void *optctx, const char *opt, const char *arg); + +int opt_report(const char *opt); + +int opt_max_alloc(void *optctx, const char *opt, const char *arg); + +int opt_codec_debug(void *optctx, const char *opt, const char *arg); + +#if CONFIG_OPENCL +int opt_opencl(void *optctx, const char *opt, const char *arg); + +int opt_opencl_bench(void *optctx, const char *opt, const char *arg); +#endif + +/** + * Limit the execution time. + */ +int opt_timelimit(void *optctx, const char *opt, const char *arg); + +/** + * Parse a string and return its corresponding value as a double. + * Exit from the application if the string cannot be correctly + * parsed or the corresponding value is invalid. + * + * @param context the context of the value to be set (e.g. the + * corresponding command line option name) + * @param numstr the string to be parsed + * @param type the type (OPT_INT64 or OPT_FLOAT) as which the + * string should be parsed + * @param min the minimum valid accepted value + * @param max the maximum valid accepted value + */ +double parse_number_or_die(const char *context, const char *numstr, int type, + double min, double max); + +/** + * Parse a string specifying a time and return its corresponding + * value as a number of microseconds. Exit from the application if + * the string cannot be correctly parsed. + * + * @param context the context of the value to be set (e.g. the + * corresponding command line option name) + * @param timestr the string to be parsed + * @param is_duration a flag which tells how to interpret timestr, if + * not zero timestr is interpreted as a duration, otherwise as a + * date + * + * @see av_parse_time() + */ +int64_t parse_time_or_die(const char *context, const char *timestr, + int is_duration); + +typedef struct SpecifierOpt { + char *specifier; /**< stream/chapter/program/... specifier */ + union { + uint8_t *str; + int i; + int64_t i64; + float f; + double dbl; + } u; +} SpecifierOpt; + +typedef struct OptionDef { + const char *name; + int flags; +#define HAS_ARG 0x0001 +#define OPT_BOOL 0x0002 +#define OPT_EXPERT 0x0004 +#define OPT_STRING 0x0008 +#define OPT_VIDEO 0x0010 +#define OPT_AUDIO 0x0020 +#define OPT_INT 0x0080 +#define OPT_FLOAT 0x0100 +#define OPT_SUBTITLE 0x0200 +#define OPT_INT64 0x0400 +#define OPT_EXIT 0x0800 +#define OPT_DATA 0x1000 +#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only). + implied by OPT_OFFSET or OPT_SPEC */ +#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */ +#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt. + Implies OPT_OFFSET. Next element after the offset is + an int containing element count in the array. */ +#define OPT_TIME 0x10000 +#define OPT_DOUBLE 0x20000 +#define OPT_INPUT 0x40000 +#define OPT_OUTPUT 0x80000 + union { + void *dst_ptr; + int (*func_arg)(void *, const char *, const char *); + size_t off; + } u; + const char *help; + const char *argname; +} OptionDef; + +/** + * Print help for all options matching specified flags. + * + * @param options a list of options + * @param msg title of this group. Only printed if at least one option matches. + * @param req_flags print only options which have all those flags set. + * @param rej_flags don't print options which have any of those flags set. + * @param alt_flags print only options that have at least one of those flags set + */ +void show_help_options(const OptionDef *options, const char *msg, int req_flags, + int rej_flags, int alt_flags); + +#if CONFIG_OPENCL +#define CMDUTILS_COMMON_OPTIONS_OPENCL \ + { "opencl_bench", OPT_EXIT, {.func_arg = opt_opencl_bench}, \ + "run benchmark on all OpenCL devices and show results" }, \ + { "opencl_options", HAS_ARG, {.func_arg = opt_opencl}, \ + "set OpenCL environment options" }, \ + +#else +#define CMDUTILS_COMMON_OPTIONS_OPENCL +#endif + +#if CONFIG_AVDEVICE +#define CMDUTILS_COMMON_OPTIONS_AVDEVICE \ + { "sources" , OPT_EXIT | HAS_ARG, { .func_arg = show_sources }, \ + "list sources of the input device", "device" }, \ + { "sinks" , OPT_EXIT | HAS_ARG, { .func_arg = show_sinks }, \ + "list sinks of the output device", "device" }, \ + +#else +#define CMDUTILS_COMMON_OPTIONS_AVDEVICE +#endif + +#define CMDUTILS_COMMON_OPTIONS \ + { "L", OPT_EXIT, { .func_arg = show_license }, "show license" }, \ + { "h", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \ + { "?", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \ + { "help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \ + { "-help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \ + { "version", OPT_EXIT, { .func_arg = show_version }, "show version" }, \ + { "buildconf", OPT_EXIT, { .func_arg = show_buildconf }, "show build configuration" }, \ + { "formats", OPT_EXIT, { .func_arg = show_formats }, "show available formats" }, \ + { "muxers", OPT_EXIT, { .func_arg = show_muxers }, "show available muxers" }, \ + { "demuxers", OPT_EXIT, { .func_arg = show_demuxers }, "show available demuxers" }, \ + { "devices", OPT_EXIT, { .func_arg = show_devices }, "show available devices" }, \ + { "codecs", OPT_EXIT, { .func_arg = show_codecs }, "show available codecs" }, \ + { "decoders", OPT_EXIT, { .func_arg = show_decoders }, "show available decoders" }, \ + { "encoders", OPT_EXIT, { .func_arg = show_encoders }, "show available encoders" }, \ + { "bsfs", OPT_EXIT, { .func_arg = show_bsfs }, "show available bit stream filters" }, \ + { "protocols", OPT_EXIT, { .func_arg = show_protocols }, "show available protocols" }, \ + { "filters", OPT_EXIT, { .func_arg = show_filters }, "show available filters" }, \ + { "pix_fmts", OPT_EXIT, { .func_arg = show_pix_fmts }, "show available pixel formats" }, \ + { "layouts", OPT_EXIT, { .func_arg = show_layouts }, "show standard channel layouts" }, \ + { "sample_fmts", OPT_EXIT, { .func_arg = show_sample_fmts }, "show available audio sample formats" }, \ + { "colors", OPT_EXIT, { .func_arg = show_colors }, "show available color names" }, \ + { "loglevel", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \ + { "v", HAS_ARG, { .func_arg = opt_loglevel }, "set logging level", "loglevel" }, \ + { "report", 0, { (void*)opt_report }, "generate a report" }, \ + { "max_alloc", HAS_ARG, { .func_arg = opt_max_alloc }, "set maximum size of a single allocated block", "bytes" }, \ + { "cpuflags", HAS_ARG | OPT_EXPERT, { .func_arg = opt_cpuflags }, "force specific cpu flags", "flags" }, \ + { "hide_banner", OPT_BOOL | OPT_EXPERT, {&hide_banner}, "do not show program banner", "hide_banner" }, \ + CMDUTILS_COMMON_OPTIONS_OPENCL \ + CMDUTILS_COMMON_OPTIONS_AVDEVICE \ + +/** + * Show help for all options with given flags in class and all its + * children. + */ +void show_help_children(const AVClass *class, int flags); + +/** + * Per-fftool specific help handler. Implemented in each + * fftool, called by show_help(). + */ +void show_help_default(const char *opt, const char *arg); + +/** + * Generic -h handler common to all fftools. + */ +int show_help(void *optctx, const char *opt, const char *arg); + +/** + * Parse the command line arguments. + * + * @param optctx an opaque options context + * @param argc number of command line arguments + * @param argv values of command line arguments + * @param options Array with the definitions required to interpret every + * option of the form: -option_name [argument] + * @param parse_arg_function Name of the function called to process every + * argument without a leading option name flag. NULL if such arguments do + * not have to be processed. + */ +void parse_options(void *optctx, int argc, char **argv, const OptionDef *options, + void (* parse_arg_function)(void *optctx, const char*)); + +/** + * Parse one given option. + * + * @return on success 1 if arg was consumed, 0 otherwise; negative number on error + */ +int parse_option(void *optctx, const char *opt, const char *arg, + const OptionDef *options); + +/** + * An option extracted from the commandline. + * Cannot use AVDictionary because of options like -map which can be + * used multiple times. + */ +typedef struct Option { + const OptionDef *opt; + const char *key; + const char *val; +} Option; + +typedef struct OptionGroupDef { + /**< group name */ + const char *name; + /** + * Option to be used as group separator. Can be NULL for groups which + * are terminated by a non-option argument (e.g. ffmpeg output files) + */ + const char *sep; + /** + * Option flags that must be set on each option that is + * applied to this group + */ + int flags; +} OptionGroupDef; + +typedef struct OptionGroup { + const OptionGroupDef *group_def; + const char *arg; + + Option *opts; + int nb_opts; + + AVDictionary *codec_opts; + AVDictionary *format_opts; + AVDictionary *resample_opts; + AVDictionary *sws_dict; + AVDictionary *swr_opts; +} OptionGroup; + +/** + * A list of option groups that all have the same group type + * (e.g. input files or output files) + */ +typedef struct OptionGroupList { + const OptionGroupDef *group_def; + + OptionGroup *groups; + int nb_groups; +} OptionGroupList; + +typedef struct OptionParseContext { + OptionGroup global_opts; + + OptionGroupList *groups; + int nb_groups; + + /* parsing state */ + OptionGroup cur_group; +} OptionParseContext; + +/** + * Parse an options group and write results into optctx. + * + * @param optctx an app-specific options context. NULL for global options group + */ +int parse_optgroup(void *optctx, OptionGroup *g); + +/** + * Split the commandline into an intermediate form convenient for further + * processing. + * + * The commandline is assumed to be composed of options which either belong to a + * group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global + * (everything else). + * + * A group (defined by an OptionGroupDef struct) is a sequence of options + * terminated by either a group separator option (e.g. -i) or a parameter that + * is not an option (doesn't start with -). A group without a separator option + * must always be first in the supplied groups list. + * + * All options within the same group are stored in one OptionGroup struct in an + * OptionGroupList, all groups with the same group definition are stored in one + * OptionGroupList in OptionParseContext.groups. The order of group lists is the + * same as the order of group definitions. + */ +int split_commandline(OptionParseContext *octx, int argc, char *argv[], + const OptionDef *options, + const OptionGroupDef *groups, int nb_groups); + +/** + * Free all allocated memory in an OptionParseContext. + */ +void uninit_parse_context(OptionParseContext *octx); + +/** + * Find the '-loglevel' option in the command line args and apply it. + */ +void parse_loglevel(int argc, char **argv, const OptionDef *options); + +/** + * Return index of option opt in argv or 0 if not found. + */ +int locate_option(int argc, char **argv, const OptionDef *options, + const char *optname); + +/** + * Check if the given stream matches a stream specifier. + * + * @param s Corresponding format context. + * @param st Stream from s to be checked. + * @param spec A stream specifier of the [v|a|s|d]:[\<stream index\>] form. + * + * @return 1 if the stream matches, 0 if it doesn't, <0 on error + */ +int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec); + +/** + * Filter out options for given codec. + * + * Create a new options dictionary containing only the options from + * opts which apply to the codec with ID codec_id. + * + * @param opts dictionary to place options in + * @param codec_id ID of the codec that should be filtered for + * @param s Corresponding format context. + * @param st A stream from s for which the options should be filtered. + * @param codec The particular codec for which the options should be filtered. + * If null, the default one is looked up according to the codec id. + * @return a pointer to the created dictionary + */ +AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, + AVFormatContext *s, AVStream *st, AVCodec *codec); + +/** + * Setup AVCodecContext options for avformat_find_stream_info(). + * + * Create an array of dictionaries, one dictionary for each stream + * contained in s. + * Each dictionary will contain the options from codec_opts which can + * be applied to the corresponding stream codec context. + * + * @return pointer to the created array of dictionaries, NULL if it + * cannot be created + */ +AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, + AVDictionary *codec_opts); + +/** + * Print an error message to stderr, indicating filename and a human + * readable description of the error code err. + * + * If strerror_r() is not available the use of this function in a + * multithreaded application may be unsafe. + * + * @see av_strerror() + */ +void print_error(const char *filename, int err); + +/** + * Print the program banner to stderr. The banner contents depend on the + * current version of the repository and of the libav* libraries used by + * the program. + */ +void show_banner(int argc, char **argv, const OptionDef *options); + +/** + * Print the version of the program to stdout. The version message + * depends on the current versions of the repository and of the libav* + * libraries. + * This option processing function does not utilize the arguments. + */ +int show_version(void *optctx, const char *opt, const char *arg); + +/** + * Print the build configuration of the program to stdout. The contents + * depend on the definition of FFMPEG_CONFIGURATION. + * This option processing function does not utilize the arguments. + */ +int show_buildconf(void *optctx, const char *opt, const char *arg); + +/** + * Print the license of the program to stdout. The license depends on + * the license of the libraries compiled into the program. + * This option processing function does not utilize the arguments. + */ +int show_license(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the formats supported by the + * program (including devices). + * This option processing function does not utilize the arguments. + */ +int show_formats(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the muxers supported by the + * program (including devices). + * This option processing function does not utilize the arguments. + */ +int show_muxers(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the demuxer supported by the + * program (including devices). + * This option processing function does not utilize the arguments. + */ +int show_demuxers(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the devices supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_devices(void *optctx, const char *opt, const char *arg); + +#if CONFIG_AVDEVICE +/** + * Print a listing containing autodetected sinks of the output device. + * Device name with options may be passed as an argument to limit results. + */ +int show_sinks(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing autodetected sources of the input device. + * Device name with options may be passed as an argument to limit results. + */ +int show_sources(void *optctx, const char *opt, const char *arg); +#endif + +/** + * Print a listing containing all the codecs supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_codecs(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the decoders supported by the + * program. + */ +int show_decoders(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the encoders supported by the + * program. + */ +int show_encoders(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the filters supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_filters(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the bit stream filters supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_bsfs(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the protocols supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_protocols(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the pixel formats supported by the + * program. + * This option processing function does not utilize the arguments. + */ +int show_pix_fmts(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the standard channel layouts supported by + * the program. + * This option processing function does not utilize the arguments. + */ +int show_layouts(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the sample formats supported by the + * program. + */ +int show_sample_fmts(void *optctx, const char *opt, const char *arg); + +/** + * Print a listing containing all the color names and values recognized + * by the program. + */ +int show_colors(void *optctx, const char *opt, const char *arg); + +/** + * Return a positive value if a line read from standard input + * starts with [yY], otherwise return 0. + */ +int read_yesno(void); + +/** + * Get a file corresponding to a preset file. + * + * If is_path is non-zero, look for the file in the path preset_name. + * Otherwise search for a file named arg.ffpreset in the directories + * $FFMPEG_DATADIR (if set), $HOME/.ffmpeg, and in the datadir defined + * at configuration time or in a "ffpresets" folder along the executable + * on win32, in that order. If no such file is found and + * codec_name is defined, then search for a file named + * codec_name-preset_name.avpreset in the above-mentioned directories. + * + * @param filename buffer where the name of the found filename is written + * @param filename_size size in bytes of the filename buffer + * @param preset_name name of the preset to search + * @param is_path tell if preset_name is a filename path + * @param codec_name name of the codec for which to look for the + * preset, may be NULL + */ +FILE *get_preset_file(char *filename, size_t filename_size, + const char *preset_name, int is_path, const char *codec_name); + +/** + * Realloc array to hold new_size elements of elem_size. + * Calls exit() on failure. + * + * @param array array to reallocate + * @param elem_size size in bytes of each element + * @param size new element count will be written here + * @param new_size number of elements to place in reallocated array + * @return reallocated array + */ +void *grow_array(void *array, int elem_size, int *size, int new_size); + +#define media_type_string av_get_media_type_string + +#define GROW_ARRAY(array, nb_elems)\ + array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1) + +#define GET_PIX_FMT_NAME(pix_fmt)\ + const char *name = av_get_pix_fmt_name(pix_fmt); + +#define GET_SAMPLE_FMT_NAME(sample_fmt)\ + const char *name = av_get_sample_fmt_name(sample_fmt) + +#define GET_SAMPLE_RATE_NAME(rate)\ + char name[16];\ + snprintf(name, sizeof(name), "%d", rate); + +#define GET_CH_LAYOUT_NAME(ch_layout)\ + char name[16];\ + snprintf(name, sizeof(name), "0x%"PRIx64, ch_layout); + +#define GET_CH_LAYOUT_DESC(ch_layout)\ + char name[128];\ + av_get_channel_layout_string(name, sizeof(name), 0, ch_layout); + +double get_rotation(AVStream *st); + +#endif /* FFTOOLS_CMDUTILS_H */ diff --git a/fftools/cmdutils_opencl.c b/fftools/cmdutils_opencl.c new file mode 100644 index 0000000000..906aef4836 --- /dev/null +++ b/fftools/cmdutils_opencl.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013 Lenny Wang + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/opt.h" +#include "libavutil/time.h" +#include "libavutil/log.h" +#include "libavutil/opencl.h" +#include "libavutil/avstring.h" +#include "cmdutils.h" + +typedef struct { + int platform_idx; + int device_idx; + char device_name[64]; + int64_t runtime; +} OpenCLDeviceBenchmark; + +const char *ocl_bench_source = AV_OPENCL_KERNEL( +inline unsigned char clip_uint8(int a) +{ + if (a & (~0xFF)) + return (-a)>>31; + else + return a; +} + +kernel void unsharp_bench( + global unsigned char *src, + global unsigned char *dst, + global int *mask, + int width, + int height) +{ + int i, j, local_idx, lc_idx, sum = 0; + int2 thread_idx, block_idx, global_idx, lm_idx; + thread_idx.x = get_local_id(0); + thread_idx.y = get_local_id(1); + block_idx.x = get_group_id(0); + block_idx.y = get_group_id(1); + global_idx.x = get_global_id(0); + global_idx.y = get_global_id(1); + local uchar data[32][32]; + local int lc[128]; + + for (i = 0; i <= 1; i++) { + lm_idx.y = -8 + (block_idx.y + i) * 16 + thread_idx.y; + lm_idx.y = lm_idx.y < 0 ? 0 : lm_idx.y; + lm_idx.y = lm_idx.y >= height ? height - 1: lm_idx.y; + for (j = 0; j <= 1; j++) { + lm_idx.x = -8 + (block_idx.x + j) * 16 + thread_idx.x; + lm_idx.x = lm_idx.x < 0 ? 0 : lm_idx.x; + lm_idx.x = lm_idx.x >= width ? width - 1: lm_idx.x; + data[i*16 + thread_idx.y][j*16 + thread_idx.x] = src[lm_idx.y*width + lm_idx.x]; + } + } + local_idx = thread_idx.y*16 + thread_idx.x; + if (local_idx < 128) + lc[local_idx] = mask[local_idx]; + barrier(CLK_LOCAL_MEM_FENCE); + + \n#pragma unroll\n + for (i = -4; i <= 4; i++) { + lm_idx.y = 8 + i + thread_idx.y; + \n#pragma unroll\n + for (j = -4; j <= 4; j++) { + lm_idx.x = 8 + j + thread_idx.x; + lc_idx = (i + 4)*8 + j + 4; + sum += (int)data[lm_idx.y][lm_idx.x] * lc[lc_idx]; + } + } + int temp = (int)data[thread_idx.y + 8][thread_idx.x + 8]; + int res = temp + (((temp - (int)((sum + 1<<15) >> 16))) >> 16); + if (global_idx.x < width && global_idx.y < height) + dst[global_idx.x + global_idx.y*width] = clip_uint8(res); +} +); + +#define OCLCHECK(method, ... ) \ +do { \ + status = method(__VA_ARGS__); \ + if (status != CL_SUCCESS) { \ + av_log(NULL, AV_LOG_ERROR, # method " error '%s'\n", \ + av_opencl_errstr(status)); \ + ret = AVERROR_EXTERNAL; \ + goto end; \ + } \ +} while (0) + +#define CREATEBUF(out, flags, size) \ +do { \ + out = clCreateBuffer(ext_opencl_env->context, flags, size, NULL, &status); \ + if (status != CL_SUCCESS) { \ + av_log(NULL, AV_LOG_ERROR, "Could not create OpenCL buffer\n"); \ + ret = AVERROR_EXTERNAL; \ + goto end; \ + } \ +} while (0) + +static void fill_rand_int(int *data, int n) +{ + int i; + srand(av_gettime()); + for (i = 0; i < n; i++) + data[i] = rand(); +} + +#define OPENCL_NB_ITER 5 +static int64_t run_opencl_bench(AVOpenCLExternalEnv *ext_opencl_env) +{ + int i, arg = 0, width = 1920, height = 1088; + int64_t start, ret = 0; + cl_int status; + size_t kernel_len; + char *inbuf; + int *mask = NULL; + int buf_size = width * height * sizeof(char); + int mask_size = sizeof(uint32_t) * 128; + + cl_mem cl_mask = NULL, cl_inbuf = NULL, cl_outbuf = NULL; + cl_kernel kernel = NULL; + cl_program program = NULL; + size_t local_work_size_2d[2] = {16, 16}; + size_t global_work_size_2d[2] = {(size_t)width, (size_t)height}; + + if (!(inbuf = av_malloc(buf_size)) || !(mask = av_malloc(mask_size))) { + av_log(NULL, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + goto end; + } + fill_rand_int((int*)inbuf, buf_size/4); + fill_rand_int(mask, mask_size/4); + + CREATEBUF(cl_mask, CL_MEM_READ_ONLY, mask_size); + CREATEBUF(cl_inbuf, CL_MEM_READ_ONLY, buf_size); + CREATEBUF(cl_outbuf, CL_MEM_READ_WRITE, buf_size); + + kernel_len = strlen(ocl_bench_source); + program = clCreateProgramWithSource(ext_opencl_env->context, 1, &ocl_bench_source, + &kernel_len, &status); + if (status != CL_SUCCESS || !program) { + av_log(NULL, AV_LOG_ERROR, "OpenCL unable to create benchmark program\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + status = clBuildProgram(program, 1, &(ext_opencl_env->device_id), NULL, NULL, NULL); + if (status != CL_SUCCESS) { + av_log(NULL, AV_LOG_ERROR, "OpenCL unable to build benchmark program\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + kernel = clCreateKernel(program, "unsharp_bench", &status); + if (status != CL_SUCCESS) { + av_log(NULL, AV_LOG_ERROR, "OpenCL unable to create benchmark kernel\n"); + ret = AVERROR_EXTERNAL; + goto end; + } + + OCLCHECK(clEnqueueWriteBuffer, ext_opencl_env->command_queue, cl_inbuf, CL_TRUE, 0, + buf_size, inbuf, 0, NULL, NULL); + OCLCHECK(clEnqueueWriteBuffer, ext_opencl_env->command_queue, cl_mask, CL_TRUE, 0, + mask_size, mask, 0, NULL, NULL); + OCLCHECK(clSetKernelArg, kernel, arg++, sizeof(cl_mem), &cl_inbuf); + OCLCHECK(clSetKernelArg, kernel, arg++, sizeof(cl_mem), &cl_outbuf); + OCLCHECK(clSetKernelArg, kernel, arg++, sizeof(cl_mem), &cl_mask); + OCLCHECK(clSetKernelArg, kernel, arg++, sizeof(cl_int), &width); + OCLCHECK(clSetKernelArg, kernel, arg++, sizeof(cl_int), &height); + + start = av_gettime_relative(); + for (i = 0; i < OPENCL_NB_ITER; i++) + OCLCHECK(clEnqueueNDRangeKernel, ext_opencl_env->command_queue, kernel, 2, NULL, + global_work_size_2d, local_work_size_2d, 0, NULL, NULL); + clFinish(ext_opencl_env->command_queue); + ret = (av_gettime_relative() - start)/OPENCL_NB_ITER; +end: + if (kernel) + clReleaseKernel(kernel); + if (program) + clReleaseProgram(program); + if (cl_inbuf) + clReleaseMemObject(cl_inbuf); + if (cl_outbuf) + clReleaseMemObject(cl_outbuf); + if (cl_mask) + clReleaseMemObject(cl_mask); + av_free(inbuf); + av_free(mask); + return ret; +} + +static int compare_ocl_device_desc(const void *a, const void *b) +{ + const OpenCLDeviceBenchmark* va = (const OpenCLDeviceBenchmark*)a; + const OpenCLDeviceBenchmark* vb = (const OpenCLDeviceBenchmark*)b; + return FFDIFFSIGN(va->runtime , vb->runtime); +} + +int opt_opencl_bench(void *optctx, const char *opt, const char *arg) +{ + int i, j, nb_devices = 0, count = 0, ret = 0; + int64_t score = 0; + AVOpenCLDeviceList *device_list; + AVOpenCLDeviceNode *device_node = NULL; + OpenCLDeviceBenchmark *devices = NULL; + cl_platform_id platform; + + ret = av_opencl_get_device_list(&device_list); + if (ret < 0) { + return ret; + } + for (i = 0; i < device_list->platform_num; i++) + nb_devices += device_list->platform_node[i]->device_num; + if (!nb_devices) { + av_log(NULL, AV_LOG_ERROR, "No OpenCL device detected!\n"); + av_opencl_free_device_list(&device_list); + return AVERROR(EINVAL); + } + if (!(devices = av_malloc_array(nb_devices, sizeof(OpenCLDeviceBenchmark)))) { + av_log(NULL, AV_LOG_ERROR, "Could not allocate buffer\n"); + av_opencl_free_device_list(&device_list); + return AVERROR(ENOMEM); + } + + for (i = 0; i < device_list->platform_num; i++) { + for (j = 0; j < device_list->platform_node[i]->device_num; j++) { + device_node = device_list->platform_node[i]->device_node[j]; + platform = device_list->platform_node[i]->platform_id; + score = av_opencl_benchmark(device_node, platform, run_opencl_bench); + if (score > 0) { + devices[count].platform_idx = i; + devices[count].device_idx = j; + devices[count].runtime = score; + av_strlcpy(devices[count].device_name, device_node->device_name, + sizeof(devices[count].device_name)); + count++; + } + } + } + qsort(devices, count, sizeof(OpenCLDeviceBenchmark), compare_ocl_device_desc); + fprintf(stderr, "platform_idx\tdevice_idx\tdevice_name\truntime\n"); + for (i = 0; i < count; i++) + fprintf(stdout, "%d\t%d\t%s\t%"PRId64"\n", + devices[i].platform_idx, devices[i].device_idx, + devices[i].device_name, devices[i].runtime); + + av_opencl_free_device_list(&device_list); + av_free(devices); + return 0; +} + +int opt_opencl(void *optctx, const char *opt, const char *arg) +{ + char *key, *value; + const char *opts = arg; + int ret = 0; + while (*opts) { + ret = av_opt_get_key_value(&opts, "=", ":", 0, &key, &value); + if (ret < 0) + return ret; + ret = av_opencl_set_option(key, value); + if (ret < 0) + return ret; + if (*opts) + opts++; + } + return ret; +} diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c new file mode 100644 index 0000000000..1d248bc269 --- /dev/null +++ b/fftools/ffmpeg.c @@ -0,0 +1,4826 @@ +/* + * Copyright (c) 2000-2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * multimedia converter based on the FFmpeg libraries + */ + +#include "config.h" +#include <ctype.h> +#include <string.h> +#include <math.h> +#include <stdlib.h> +#include <errno.h> +#include <limits.h> +#include <stdatomic.h> +#include <stdint.h> + +#if HAVE_IO_H +#include <io.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "libavformat/avformat.h" +#include "libavdevice/avdevice.h" +#include "libswresample/swresample.h" +#include "libavutil/opt.h" +#include "libavutil/channel_layout.h" +#include "libavutil/parseutils.h" +#include "libavutil/samplefmt.h" +#include "libavutil/fifo.h" +#include "libavutil/hwcontext.h" +#include "libavutil/internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/dict.h" +#include "libavutil/display.h" +#include "libavutil/mathematics.h" +#include "libavutil/pixdesc.h" +#include "libavutil/avstring.h" +#include "libavutil/libm.h" +#include "libavutil/imgutils.h" +#include "libavutil/timestamp.h" +#include "libavutil/bprint.h" +#include "libavutil/time.h" +#include "libavutil/threadmessage.h" +#include "libavcodec/mathops.h" +#include "libavformat/os_support.h" + +# include "libavfilter/avfilter.h" +# include "libavfilter/buffersrc.h" +# include "libavfilter/buffersink.h" + +#if HAVE_SYS_RESOURCE_H +#include <sys/time.h> +#include <sys/types.h> +#include <sys/resource.h> +#elif HAVE_GETPROCESSTIMES +#include <windows.h> +#endif +#if HAVE_GETPROCESSMEMORYINFO +#include <windows.h> +#include <psapi.h> +#endif +#if HAVE_SETCONSOLECTRLHANDLER +#include <windows.h> +#endif + + +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#if HAVE_TERMIOS_H +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <termios.h> +#elif HAVE_KBHIT +#include <conio.h> +#endif + +#if HAVE_PTHREADS +#include <pthread.h> +#endif + +#include <time.h> + +#include "ffmpeg.h" +#include "cmdutils.h" + +#include "libavutil/avassert.h" + +const char program_name[] = "ffmpeg"; +const int program_birth_year = 2000; + +static FILE *vstats_file; + +const char *const forced_keyframes_const_names[] = { + "n", + "n_forced", + "prev_forced_n", + "prev_forced_t", + "t", + NULL +}; + +static void do_video_stats(OutputStream *ost, int frame_size); +static int64_t getutime(void); +static int64_t getmaxrss(void); +static int ifilter_has_all_input_formats(FilterGraph *fg); + +static int run_as_daemon = 0; +static int nb_frames_dup = 0; +static unsigned dup_warning = 1000; +static int nb_frames_drop = 0; +static int64_t decode_error_stat[2]; + +static int want_sdp = 1; + +static int current_time; +AVIOContext *progress_avio = NULL; + +static uint8_t *subtitle_out; + +InputStream **input_streams = NULL; +int nb_input_streams = 0; +InputFile **input_files = NULL; +int nb_input_files = 0; + +OutputStream **output_streams = NULL; +int nb_output_streams = 0; +OutputFile **output_files = NULL; +int nb_output_files = 0; + +FilterGraph **filtergraphs; +int nb_filtergraphs; + +#if HAVE_TERMIOS_H + +/* init terminal so that we can grab keys */ +static struct termios oldtty; +static int restore_tty; +#endif + +#if HAVE_PTHREADS +static void free_input_threads(void); +#endif + +/* sub2video hack: + Convert subtitles to video with alpha to insert them in filter graphs. + This is a temporary solution until libavfilter gets real subtitles support. + */ + +static int sub2video_get_blank_frame(InputStream *ist) +{ + int ret; + AVFrame *frame = ist->sub2video.frame; + + av_frame_unref(frame); + ist->sub2video.frame->width = ist->dec_ctx->width ? ist->dec_ctx->width : ist->sub2video.w; + ist->sub2video.frame->height = ist->dec_ctx->height ? ist->dec_ctx->height : ist->sub2video.h; + ist->sub2video.frame->format = AV_PIX_FMT_RGB32; + if ((ret = av_frame_get_buffer(frame, 32)) < 0) + return ret; + memset(frame->data[0], 0, frame->height * frame->linesize[0]); + return 0; +} + +static void sub2video_copy_rect(uint8_t *dst, int dst_linesize, int w, int h, + AVSubtitleRect *r) +{ + uint32_t *pal, *dst2; + uint8_t *src, *src2; + int x, y; + + if (r->type != SUBTITLE_BITMAP) { + av_log(NULL, AV_LOG_WARNING, "sub2video: non-bitmap subtitle\n"); + return; + } + if (r->x < 0 || r->x + r->w > w || r->y < 0 || r->y + r->h > h) { + av_log(NULL, AV_LOG_WARNING, "sub2video: rectangle (%d %d %d %d) overflowing %d %d\n", + r->x, r->y, r->w, r->h, w, h + ); + return; + } + + dst += r->y * dst_linesize + r->x * 4; + src = r->data[0]; + pal = (uint32_t *)r->data[1]; + for (y = 0; y < r->h; y++) { + dst2 = (uint32_t *)dst; + src2 = src; + for (x = 0; x < r->w; x++) + *(dst2++) = pal[*(src2++)]; + dst += dst_linesize; + src += r->linesize[0]; + } +} + +static void sub2video_push_ref(InputStream *ist, int64_t pts) +{ + AVFrame *frame = ist->sub2video.frame; + int i; + + av_assert1(frame->data[0]); + ist->sub2video.last_pts = frame->pts = pts; + for (i = 0; i < ist->nb_filters; i++) + av_buffersrc_add_frame_flags(ist->filters[i]->filter, frame, + AV_BUFFERSRC_FLAG_KEEP_REF | + AV_BUFFERSRC_FLAG_PUSH); +} + +void sub2video_update(InputStream *ist, AVSubtitle *sub) +{ + AVFrame *frame = ist->sub2video.frame; + int8_t *dst; + int dst_linesize; + int num_rects, i; + int64_t pts, end_pts; + + if (!frame) + return; + if (sub) { + pts = av_rescale_q(sub->pts + sub->start_display_time * 1000LL, + AV_TIME_BASE_Q, ist->st->time_base); + end_pts = av_rescale_q(sub->pts + sub->end_display_time * 1000LL, + AV_TIME_BASE_Q, ist->st->time_base); + num_rects = sub->num_rects; + } else { + pts = ist->sub2video.end_pts; + end_pts = INT64_MAX; + num_rects = 0; + } + if (sub2video_get_blank_frame(ist) < 0) { + av_log(ist->dec_ctx, AV_LOG_ERROR, + "Impossible to get a blank canvas.\n"); + return; + } + dst = frame->data [0]; + dst_linesize = frame->linesize[0]; + for (i = 0; i < num_rects; i++) + sub2video_copy_rect(dst, dst_linesize, frame->width, frame->height, sub->rects[i]); + sub2video_push_ref(ist, pts); + ist->sub2video.end_pts = end_pts; +} + +static void sub2video_heartbeat(InputStream *ist, int64_t pts) +{ + InputFile *infile = input_files[ist->file_index]; + int i, j, nb_reqs; + int64_t pts2; + + /* When a frame is read from a file, examine all sub2video streams in + the same file and send the sub2video frame again. Otherwise, decoded + video frames could be accumulating in the filter graph while a filter + (possibly overlay) is desperately waiting for a subtitle frame. */ + for (i = 0; i < infile->nb_streams; i++) { + InputStream *ist2 = input_streams[infile->ist_index + i]; + if (!ist2->sub2video.frame) + continue; + /* subtitles seem to be usually muxed ahead of other streams; + if not, subtracting a larger time here is necessary */ + pts2 = av_rescale_q(pts, ist->st->time_base, ist2->st->time_base) - 1; + /* do not send the heartbeat frame if the subtitle is already ahead */ + if (pts2 <= ist2->sub2video.last_pts) + continue; + if (pts2 >= ist2->sub2video.end_pts || !ist2->sub2video.frame->data[0]) + sub2video_update(ist2, NULL); + for (j = 0, nb_reqs = 0; j < ist2->nb_filters; j++) + nb_reqs += av_buffersrc_get_nb_failed_requests(ist2->filters[j]->filter); + if (nb_reqs) + sub2video_push_ref(ist2, pts2); + } +} + +static void sub2video_flush(InputStream *ist) +{ + int i; + + if (ist->sub2video.end_pts < INT64_MAX) + sub2video_update(ist, NULL); + for (i = 0; i < ist->nb_filters; i++) + av_buffersrc_add_frame(ist->filters[i]->filter, NULL); +} + +/* end of sub2video hack */ + +static void term_exit_sigsafe(void) +{ +#if HAVE_TERMIOS_H + if(restore_tty) + tcsetattr (0, TCSANOW, &oldtty); +#endif +} + +void term_exit(void) +{ + av_log(NULL, AV_LOG_QUIET, "%s", ""); + term_exit_sigsafe(); +} + +static volatile int received_sigterm = 0; +static volatile int received_nb_signals = 0; +static atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); +static volatile int ffmpeg_exited = 0; +static int main_return_code = 0; + +static void +sigterm_handler(int sig) +{ + received_sigterm = sig; + received_nb_signals++; + term_exit_sigsafe(); + if(received_nb_signals > 3) { + write(2/*STDERR_FILENO*/, "Received > 3 system signals, hard exiting\n", + strlen("Received > 3 system signals, hard exiting\n")); + + exit(123); + } +} + +#if HAVE_SETCONSOLECTRLHANDLER +static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) +{ + av_log(NULL, AV_LOG_DEBUG, "\nReceived windows signal %ld\n", fdwCtrlType); + + switch (fdwCtrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + sigterm_handler(SIGINT); + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + sigterm_handler(SIGTERM); + /* Basically, with these 3 events, when we return from this method the + process is hard terminated, so stall as long as we need to + to try and let the main thread(s) clean up and gracefully terminate + (we have at most 5 seconds, but should be done far before that). */ + while (!ffmpeg_exited) { + Sleep(0); + } + return TRUE; + + default: + av_log(NULL, AV_LOG_ERROR, "Received unknown windows signal %ld\n", fdwCtrlType); + return FALSE; + } +} +#endif + +void term_init(void) +{ +#if HAVE_TERMIOS_H + if (!run_as_daemon && stdin_interaction) { + struct termios tty; + if (tcgetattr (0, &tty) == 0) { + oldtty = tty; + restore_tty = 1; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr (0, TCSANOW, &tty); + } + signal(SIGQUIT, sigterm_handler); /* Quit (POSIX). */ + } +#endif + + signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */ + signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */ +#ifdef SIGXCPU + signal(SIGXCPU, sigterm_handler); +#endif +#if HAVE_SETCONSOLECTRLHANDLER + SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE); +#endif +} + +/* read a key without blocking */ +static int read_key(void) +{ + unsigned char ch; +#if HAVE_TERMIOS_H + int n = 1; + struct timeval tv; + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + n = select(1, &rfds, NULL, NULL, &tv); + if (n > 0) { + n = read(0, &ch, 1); + if (n == 1) + return ch; + + return n; + } +#elif HAVE_KBHIT +# if HAVE_PEEKNAMEDPIPE + static int is_pipe; + static HANDLE input_handle; + DWORD dw, nchars; + if(!input_handle){ + input_handle = GetStdHandle(STD_INPUT_HANDLE); + is_pipe = !GetConsoleMode(input_handle, &dw); + } + + if (is_pipe) { + /* When running under a GUI, you will end here. */ + if (!PeekNamedPipe(input_handle, NULL, 0, NULL, &nchars, NULL)) { + // input pipe may have been closed by the program that ran ffmpeg + return -1; + } + //Read it + if(nchars != 0) { + read(0, &ch, 1); + return ch; + }else{ + return -1; + } + } +# endif + if(kbhit()) + return(getch()); +#endif + return -1; +} + +static int decode_interrupt_cb(void *ctx) +{ + return received_nb_signals > atomic_load(&transcode_init_done); +} + +const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL }; + +static void ffmpeg_cleanup(int ret) +{ + int i, j; + + if (do_benchmark) { + int maxrss = getmaxrss() / 1024; + av_log(NULL, AV_LOG_INFO, "bench: maxrss=%ikB\n", maxrss); + } + + for (i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + avfilter_graph_free(&fg->graph); + for (j = 0; j < fg->nb_inputs; j++) { + while (av_fifo_size(fg->inputs[j]->frame_queue)) { + AVFrame *frame; + av_fifo_generic_read(fg->inputs[j]->frame_queue, &frame, + sizeof(frame), NULL); + av_frame_free(&frame); + } + av_fifo_freep(&fg->inputs[j]->frame_queue); + if (fg->inputs[j]->ist->sub2video.sub_queue) { + while (av_fifo_size(fg->inputs[j]->ist->sub2video.sub_queue)) { + AVSubtitle sub; + av_fifo_generic_read(fg->inputs[j]->ist->sub2video.sub_queue, + &sub, sizeof(sub), NULL); + avsubtitle_free(&sub); + } + av_fifo_freep(&fg->inputs[j]->ist->sub2video.sub_queue); + } + av_buffer_unref(&fg->inputs[j]->hw_frames_ctx); + av_freep(&fg->inputs[j]->name); + av_freep(&fg->inputs[j]); + } + av_freep(&fg->inputs); + for (j = 0; j < fg->nb_outputs; j++) { + av_freep(&fg->outputs[j]->name); + av_freep(&fg->outputs[j]->formats); + av_freep(&fg->outputs[j]->channel_layouts); + av_freep(&fg->outputs[j]->sample_rates); + av_freep(&fg->outputs[j]); + } + av_freep(&fg->outputs); + av_freep(&fg->graph_desc); + + av_freep(&filtergraphs[i]); + } + av_freep(&filtergraphs); + + av_freep(&subtitle_out); + + /* close files */ + for (i = 0; i < nb_output_files; i++) { + OutputFile *of = output_files[i]; + AVFormatContext *s; + if (!of) + continue; + s = of->ctx; + if (s && s->oformat && !(s->oformat->flags & AVFMT_NOFILE)) + avio_closep(&s->pb); + avformat_free_context(s); + av_dict_free(&of->opts); + + av_freep(&output_files[i]); + } + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + + if (!ost) + continue; + + for (j = 0; j < ost->nb_bitstream_filters; j++) + av_bsf_free(&ost->bsf_ctx[j]); + av_freep(&ost->bsf_ctx); + + av_frame_free(&ost->filtered_frame); + av_frame_free(&ost->last_frame); + av_dict_free(&ost->encoder_opts); + + av_parser_close(ost->parser); + avcodec_free_context(&ost->parser_avctx); + + av_freep(&ost->forced_keyframes); + av_expr_free(ost->forced_keyframes_pexpr); + av_freep(&ost->avfilter); + av_freep(&ost->logfile_prefix); + + av_freep(&ost->audio_channels_map); + ost->audio_channels_mapped = 0; + + av_dict_free(&ost->sws_dict); + + avcodec_free_context(&ost->enc_ctx); + avcodec_parameters_free(&ost->ref_par); + + if (ost->muxing_queue) { + while (av_fifo_size(ost->muxing_queue)) { + AVPacket pkt; + av_fifo_generic_read(ost->muxing_queue, &pkt, sizeof(pkt), NULL); + av_packet_unref(&pkt); + } + av_fifo_freep(&ost->muxing_queue); + } + + av_freep(&output_streams[i]); + } +#if HAVE_PTHREADS + free_input_threads(); +#endif + for (i = 0; i < nb_input_files; i++) { + avformat_close_input(&input_files[i]->ctx); + av_freep(&input_files[i]); + } + for (i = 0; i < nb_input_streams; i++) { + InputStream *ist = input_streams[i]; + + av_frame_free(&ist->decoded_frame); + av_frame_free(&ist->filter_frame); + av_dict_free(&ist->decoder_opts); + avsubtitle_free(&ist->prev_sub.subtitle); + av_frame_free(&ist->sub2video.frame); + av_freep(&ist->filters); + av_freep(&ist->hwaccel_device); + av_freep(&ist->dts_buffer); + + avcodec_free_context(&ist->dec_ctx); + + av_freep(&input_streams[i]); + } + + if (vstats_file) { + if (fclose(vstats_file)) + av_log(NULL, AV_LOG_ERROR, + "Error closing vstats file, loss of information possible: %s\n", + av_err2str(AVERROR(errno))); + } + av_freep(&vstats_filename); + + av_freep(&input_streams); + av_freep(&input_files); + av_freep(&output_streams); + av_freep(&output_files); + + uninit_opts(); + + avformat_network_deinit(); + + if (received_sigterm) { + av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n", + (int) received_sigterm); + } else if (ret && atomic_load(&transcode_init_done)) { + av_log(NULL, AV_LOG_INFO, "Conversion failed!\n"); + } + term_exit(); + ffmpeg_exited = 1; +} + +void remove_avoptions(AVDictionary **a, AVDictionary *b) +{ + AVDictionaryEntry *t = NULL; + + while ((t = av_dict_get(b, "", t, AV_DICT_IGNORE_SUFFIX))) { + av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); + } +} + +void assert_avoptions(AVDictionary *m) +{ + AVDictionaryEntry *t; + if ((t = av_dict_get(m, "", NULL, AV_DICT_IGNORE_SUFFIX))) { + av_log(NULL, AV_LOG_FATAL, "Option %s not found.\n", t->key); + exit_program(1); + } +} + +static void abort_codec_experimental(AVCodec *c, int encoder) +{ + exit_program(1); +} + +static void update_benchmark(const char *fmt, ...) +{ + if (do_benchmark_all) { + int64_t t = getutime(); + va_list va; + char buf[1024]; + + if (fmt) { + va_start(va, fmt); + vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + av_log(NULL, AV_LOG_INFO, "bench: %8"PRIu64" %s \n", t - current_time, buf); + } + current_time = t; + } +} + +static void close_all_output_streams(OutputStream *ost, OSTFinished this_stream, OSTFinished others) +{ + int i; + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost2 = output_streams[i]; + ost2->finished |= ost == ost2 ? this_stream : others; + } +} + +static void write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int unqueue) +{ + AVFormatContext *s = of->ctx; + AVStream *st = ost->st; + int ret; + + /* + * Audio encoders may split the packets -- #frames in != #packets out. + * But there is no reordering, so we can limit the number of output packets + * by simply dropping them here. + * Counting encoded video frames needs to be done separately because of + * reordering, see do_video_out(). + * Do not count the packet when unqueued because it has been counted when queued. + */ + if (!(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->encoding_needed) && !unqueue) { + if (ost->frame_number >= ost->max_frames) { + av_packet_unref(pkt); + return; + } + ost->frame_number++; + } + + if (!of->header_written) { + AVPacket tmp_pkt = {0}; + /* the muxer is not initialized yet, buffer the packet */ + if (!av_fifo_space(ost->muxing_queue)) { + int new_size = FFMIN(2 * av_fifo_size(ost->muxing_queue), + ost->max_muxing_queue_size); + if (new_size <= av_fifo_size(ost->muxing_queue)) { + av_log(NULL, AV_LOG_ERROR, + "Too many packets buffered for output stream %d:%d.\n", + ost->file_index, ost->st->index); + exit_program(1); + } + ret = av_fifo_realloc2(ost->muxing_queue, new_size); + if (ret < 0) + exit_program(1); + } + ret = av_packet_ref(&tmp_pkt, pkt); + if (ret < 0) + exit_program(1); + av_fifo_generic_write(ost->muxing_queue, &tmp_pkt, sizeof(tmp_pkt), NULL); + av_packet_unref(pkt); + return; + } + + if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_sync_method == VSYNC_DROP) || + (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_sync_method < 0)) + pkt->pts = pkt->dts = AV_NOPTS_VALUE; + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + int i; + uint8_t *sd = av_packet_get_side_data(pkt, AV_PKT_DATA_QUALITY_STATS, + NULL); + ost->quality = sd ? AV_RL32(sd) : -1; + ost->pict_type = sd ? sd[4] : AV_PICTURE_TYPE_NONE; + + for (i = 0; i<FF_ARRAY_ELEMS(ost->error); i++) { + if (sd && i < sd[5]) + ost->error[i] = AV_RL64(sd + 8 + 8*i); + else + ost->error[i] = -1; + } + + if (ost->frame_rate.num && ost->is_cfr) { + if (pkt->duration > 0) + av_log(NULL, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); + pkt->duration = av_rescale_q(1, av_inv_q(ost->frame_rate), + ost->mux_timebase); + } + } + + av_packet_rescale_ts(pkt, ost->mux_timebase, ost->st->time_base); + + if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) { + if (pkt->dts != AV_NOPTS_VALUE && + pkt->pts != AV_NOPTS_VALUE && + pkt->dts > pkt->pts) { + av_log(s, AV_LOG_WARNING, "Invalid DTS: %"PRId64" PTS: %"PRId64" in output stream %d:%d, replacing by guess\n", + pkt->dts, pkt->pts, + ost->file_index, ost->st->index); + pkt->pts = + pkt->dts = pkt->pts + pkt->dts + ost->last_mux_dts + 1 + - FFMIN3(pkt->pts, pkt->dts, ost->last_mux_dts + 1) + - FFMAX3(pkt->pts, pkt->dts, ost->last_mux_dts + 1); + } + if ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && + pkt->dts != AV_NOPTS_VALUE && + !(st->codecpar->codec_id == AV_CODEC_ID_VP9 && ost->stream_copy) && + ost->last_mux_dts != AV_NOPTS_VALUE) { + int64_t max = ost->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); + if (pkt->dts < max) { + int loglevel = max - pkt->dts > 2 || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? AV_LOG_WARNING : AV_LOG_DEBUG; + av_log(s, loglevel, "Non-monotonous DTS in output stream " + "%d:%d; previous: %"PRId64", current: %"PRId64"; ", + ost->file_index, ost->st->index, ost->last_mux_dts, pkt->dts); + if (exit_on_error) { + av_log(NULL, AV_LOG_FATAL, "aborting.\n"); + exit_program(1); + } + av_log(s, loglevel, "changing to %"PRId64". This may result " + "in incorrect timestamps in the output file.\n", + max); + if (pkt->pts >= pkt->dts) + pkt->pts = FFMAX(pkt->pts, max); + pkt->dts = max; + } + } + } + ost->last_mux_dts = pkt->dts; + + ost->data_size += pkt->size; + ost->packets_written++; + + pkt->stream_index = ost->index; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "muxer <- type:%s " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s size:%d\n", + av_get_media_type_string(ost->enc_ctx->codec_type), + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ost->st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ost->st->time_base), + pkt->size + ); + } + + ret = av_interleaved_write_frame(s, pkt); + if (ret < 0) { + print_error("av_interleaved_write_frame()", ret); + main_return_code = 1; + close_all_output_streams(ost, MUXER_FINISHED | ENCODER_FINISHED, ENCODER_FINISHED); + } + av_packet_unref(pkt); +} + +static void close_output_stream(OutputStream *ost) +{ + OutputFile *of = output_files[ost->file_index]; + + ost->finished |= ENCODER_FINISHED; + if (of->shortest) { + int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, ost->enc_ctx->time_base, AV_TIME_BASE_Q); + of->recording_time = FFMIN(of->recording_time, end); + } +} + +/* + * Send a single packet to the output, applying any bitstream filters + * associated with the output stream. This may result in any number + * of packets actually being written, depending on what bitstream + * filters are applied. The supplied packet is consumed and will be + * blank (as if newly-allocated) when this function returns. + * + * If eof is set, instead indicate EOF to all bitstream filters and + * therefore flush any delayed packets to the output. A blank packet + * must be supplied in this case. + */ +static void output_packet(OutputFile *of, AVPacket *pkt, + OutputStream *ost, int eof) +{ + int ret = 0; + + /* apply the output bitstream filters, if any */ + if (ost->nb_bitstream_filters) { + int idx; + + ret = av_bsf_send_packet(ost->bsf_ctx[0], eof ? NULL : pkt); + if (ret < 0) + goto finish; + + eof = 0; + idx = 1; + while (idx) { + /* get a packet from the previous filter up the chain */ + ret = av_bsf_receive_packet(ost->bsf_ctx[idx - 1], pkt); + if (ret == AVERROR(EAGAIN)) { + ret = 0; + idx--; + continue; + } else if (ret == AVERROR_EOF) { + eof = 1; + } else if (ret < 0) + goto finish; + + /* send it to the next filter down the chain or to the muxer */ + if (idx < ost->nb_bitstream_filters) { + ret = av_bsf_send_packet(ost->bsf_ctx[idx], eof ? NULL : pkt); + if (ret < 0) + goto finish; + idx++; + eof = 0; + } else if (eof) + goto finish; + else + write_packet(of, pkt, ost, 0); + } + } else if (!eof) + write_packet(of, pkt, ost, 0); + +finish: + if (ret < 0 && ret != AVERROR_EOF) { + av_log(NULL, AV_LOG_ERROR, "Error applying bitstream filters to an output " + "packet for stream #%d:%d.\n", ost->file_index, ost->index); + if(exit_on_error) + exit_program(1); + } +} + +static int check_recording_time(OutputStream *ost) +{ + OutputFile *of = output_files[ost->file_index]; + + if (of->recording_time != INT64_MAX && + av_compare_ts(ost->sync_opts - ost->first_pts, ost->enc_ctx->time_base, of->recording_time, + AV_TIME_BASE_Q) >= 0) { + close_output_stream(ost); + return 0; + } + return 1; +} + +static void do_audio_out(OutputFile *of, OutputStream *ost, + AVFrame *frame) +{ + AVCodecContext *enc = ost->enc_ctx; + AVPacket pkt; + int ret; + + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + if (!check_recording_time(ost)) + return; + + if (frame->pts == AV_NOPTS_VALUE || audio_sync_method < 0) + frame->pts = ost->sync_opts; + ost->sync_opts = frame->pts + frame->nb_samples; + ost->samples_encoded += frame->nb_samples; + ost->frames_encoded++; + + av_assert0(pkt.size || !pkt.data); + update_benchmark(NULL); + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "encoder <- type:audio " + "frame_pts:%s frame_pts_time:%s time_base:%d/%d\n", + av_ts2str(frame->pts), av_ts2timestr(frame->pts, &enc->time_base), + enc->time_base.num, enc->time_base.den); + } + + ret = avcodec_send_frame(enc, frame); + if (ret < 0) + goto error; + + while (1) { + ret = avcodec_receive_packet(enc, &pkt); + if (ret == AVERROR(EAGAIN)) + break; + if (ret < 0) + goto error; + + update_benchmark("encode_audio %d.%d", ost->file_index, ost->index); + + av_packet_rescale_ts(&pkt, enc->time_base, ost->mux_timebase); + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "encoder -> type:audio " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n", + av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &enc->time_base), + av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &enc->time_base)); + } + + output_packet(of, &pkt, ost, 0); + } + + return; +error: + av_log(NULL, AV_LOG_FATAL, "Audio encoding failed\n"); + exit_program(1); +} + +static void do_subtitle_out(OutputFile *of, + OutputStream *ost, + AVSubtitle *sub) +{ + int subtitle_out_max_size = 1024 * 1024; + int subtitle_out_size, nb, i; + AVCodecContext *enc; + AVPacket pkt; + int64_t pts; + + if (sub->pts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); + if (exit_on_error) + exit_program(1); + return; + } + + enc = ost->enc_ctx; + + if (!subtitle_out) { + subtitle_out = av_malloc(subtitle_out_max_size); + if (!subtitle_out) { + av_log(NULL, AV_LOG_FATAL, "Failed to allocate subtitle_out\n"); + exit_program(1); + } + } + + /* Note: DVB subtitle need one packet to draw them and one other + packet to clear them */ + /* XXX: signal it in the codec context ? */ + if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) + nb = 2; + else + nb = 1; + + /* shift timestamp to honor -ss and make check_recording_time() work with -t */ + pts = sub->pts; + if (output_files[ost->file_index]->start_time != AV_NOPTS_VALUE) + pts -= output_files[ost->file_index]->start_time; + for (i = 0; i < nb; i++) { + unsigned save_num_rects = sub->num_rects; + + ost->sync_opts = av_rescale_q(pts, AV_TIME_BASE_Q, enc->time_base); + if (!check_recording_time(ost)) + return; + + sub->pts = pts; + // start_display_time is required to be 0 + sub->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, AV_TIME_BASE_Q); + sub->end_display_time -= sub->start_display_time; + sub->start_display_time = 0; + if (i == 1) + sub->num_rects = 0; + + ost->frames_encoded++; + + subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out, + subtitle_out_max_size, sub); + if (i == 1) + sub->num_rects = save_num_rects; + if (subtitle_out_size < 0) { + av_log(NULL, AV_LOG_FATAL, "Subtitle encoding failed\n"); + exit_program(1); + } + + av_init_packet(&pkt); + pkt.data = subtitle_out; + pkt.size = subtitle_out_size; + pkt.pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, ost->mux_timebase); + pkt.duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { + /* XXX: the pts correction is handled here. Maybe handling + it in the codec would be better */ + if (i == 0) + pkt.pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + else + pkt.pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + } + pkt.dts = pkt.pts; + output_packet(of, &pkt, ost, 0); + } +} + +static void do_video_out(OutputFile *of, + OutputStream *ost, + AVFrame *next_picture, + double sync_ipts) +{ + int ret, format_video_sync; + AVPacket pkt; + AVCodecContext *enc = ost->enc_ctx; + AVCodecParameters *mux_par = ost->st->codecpar; + AVRational frame_rate; + int nb_frames, nb0_frames, i; + double delta, delta0; + double duration = 0; + int frame_size = 0; + InputStream *ist = NULL; + AVFilterContext *filter = ost->filter->filter; + + if (ost->source_index >= 0) + ist = input_streams[ost->source_index]; + + frame_rate = av_buffersink_get_frame_rate(filter); + if (frame_rate.num > 0 && frame_rate.den > 0) + duration = 1/(av_q2d(frame_rate) * av_q2d(enc->time_base)); + + if(ist && ist->st->start_time != AV_NOPTS_VALUE && ist->st->first_dts != AV_NOPTS_VALUE && ost->frame_rate.num) + duration = FFMIN(duration, 1/(av_q2d(ost->frame_rate) * av_q2d(enc->time_base))); + + if (!ost->filters_script && + !ost->filters && + next_picture && + ist && + lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { + duration = lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); + } + + if (!next_picture) { + //end, flushing + nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0], + ost->last_nb0_frames[1], + ost->last_nb0_frames[2]); + } else { + delta0 = sync_ipts - ost->sync_opts; // delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output. + delta = delta0 + duration; + + /* by default, we output a single frame */ + nb0_frames = 0; // tracks the number of times the PREVIOUS frame should be duplicated, mostly for variable framerate (VFR) + nb_frames = 1; + + format_video_sync = video_sync_method; + if (format_video_sync == VSYNC_AUTO) { + if(!strcmp(of->ctx->oformat->name, "avi")) { + format_video_sync = VSYNC_VFR; + } else + format_video_sync = (of->ctx->oformat->flags & AVFMT_VARIABLE_FPS) ? ((of->ctx->oformat->flags & AVFMT_NOTIMESTAMPS) ? VSYNC_PASSTHROUGH : VSYNC_VFR) : VSYNC_CFR; + if ( ist + && format_video_sync == VSYNC_CFR + && input_files[ist->file_index]->ctx->nb_streams == 1 + && input_files[ist->file_index]->input_ts_offset == 0) { + format_video_sync = VSYNC_VSCFR; + } + if (format_video_sync == VSYNC_CFR && copy_ts) { + format_video_sync = VSYNC_VSCFR; + } + } + ost->is_cfr = (format_video_sync == VSYNC_CFR || format_video_sync == VSYNC_VSCFR); + + if (delta0 < 0 && + delta > 0 && + format_video_sync != VSYNC_PASSTHROUGH && + format_video_sync != VSYNC_DROP) { + if (delta0 < -0.6) { + av_log(NULL, AV_LOG_WARNING, "Past duration %f too large\n", -delta0); + } else + av_log(NULL, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); + sync_ipts = ost->sync_opts; + duration += delta0; + delta0 = 0; + } + + switch (format_video_sync) { + case VSYNC_VSCFR: + if (ost->frame_number == 0 && delta0 >= 0.5) { + av_log(NULL, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); + delta = duration; + delta0 = 0; + ost->sync_opts = lrint(sync_ipts); + } + case VSYNC_CFR: + // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c + if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) { + nb_frames = 0; + } else if (delta < -1.1) + nb_frames = 0; + else if (delta > 1.1) { + nb_frames = lrintf(delta); + if (delta0 > 1.1) + nb0_frames = lrintf(delta0 - 0.6); + } + break; + case VSYNC_VFR: + if (delta <= -0.6) + nb_frames = 0; + else if (delta > 0.6) + ost->sync_opts = lrint(sync_ipts); + break; + case VSYNC_DROP: + case VSYNC_PASSTHROUGH: + ost->sync_opts = lrint(sync_ipts); + break; + default: + av_assert0(0); + } + } + + nb_frames = FFMIN(nb_frames, ost->max_frames - ost->frame_number); + nb0_frames = FFMIN(nb0_frames, nb_frames); + + memmove(ost->last_nb0_frames + 1, + ost->last_nb0_frames, + sizeof(ost->last_nb0_frames[0]) * (FF_ARRAY_ELEMS(ost->last_nb0_frames) - 1)); + ost->last_nb0_frames[0] = nb0_frames; + + if (nb0_frames == 0 && ost->last_dropped) { + nb_frames_drop++; + av_log(NULL, AV_LOG_VERBOSE, + "*** dropping frame %d from stream %d at ts %"PRId64"\n", + ost->frame_number, ost->st->index, ost->last_frame->pts); + } + if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) { + if (nb_frames > dts_error_threshold * 30) { + av_log(NULL, AV_LOG_ERROR, "%d frame duplication too large, skipping\n", nb_frames - 1); + nb_frames_drop++; + return; + } + nb_frames_dup += nb_frames - (nb0_frames && ost->last_dropped) - (nb_frames > nb0_frames); + av_log(NULL, AV_LOG_VERBOSE, "*** %d dup!\n", nb_frames - 1); + if (nb_frames_dup > dup_warning) { + av_log(NULL, AV_LOG_WARNING, "More than %d frames duplicated\n", dup_warning); + dup_warning *= 10; + } + } + ost->last_dropped = nb_frames == nb0_frames && next_picture; + + /* duplicates frame if needed */ + for (i = 0; i < nb_frames; i++) { + AVFrame *in_picture; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + if (i < nb0_frames && ost->last_frame) { + in_picture = ost->last_frame; + } else + in_picture = next_picture; + + if (!in_picture) + return; + + in_picture->pts = ost->sync_opts; + +#if 1 + if (!check_recording_time(ost)) +#else + if (ost->frame_number >= ost->max_frames) +#endif + return; + +#if FF_API_LAVF_FMT_RAWPICTURE + if (of->ctx->oformat->flags & AVFMT_RAWPICTURE && + enc->codec->id == AV_CODEC_ID_RAWVIDEO) { + /* raw pictures are written as AVPicture structure to + avoid any copies. We support temporarily the older + method. */ + if (in_picture->interlaced_frame) + mux_par->field_order = in_picture->top_field_first ? AV_FIELD_TB:AV_FIELD_BT; + else + mux_par->field_order = AV_FIELD_PROGRESSIVE; + pkt.data = (uint8_t *)in_picture; + pkt.size = sizeof(AVPicture); + pkt.pts = av_rescale_q(in_picture->pts, enc->time_base, ost->mux_timebase); + pkt.flags |= AV_PKT_FLAG_KEY; + + output_packet(of, &pkt, ost, 0); + } else +#endif + { + int forced_keyframe = 0; + double pts_time; + + if (enc->flags & (AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME) && + ost->top_field_first >= 0) + in_picture->top_field_first = !!ost->top_field_first; + + if (in_picture->interlaced_frame) { + if (enc->codec->id == AV_CODEC_ID_MJPEG) + mux_par->field_order = in_picture->top_field_first ? AV_FIELD_TT:AV_FIELD_BB; + else + mux_par->field_order = in_picture->top_field_first ? AV_FIELD_TB:AV_FIELD_BT; + } else + mux_par->field_order = AV_FIELD_PROGRESSIVE; + + in_picture->quality = enc->global_quality; + in_picture->pict_type = 0; + + pts_time = in_picture->pts != AV_NOPTS_VALUE ? + in_picture->pts * av_q2d(enc->time_base) : NAN; + if (ost->forced_kf_index < ost->forced_kf_count && + in_picture->pts >= ost->forced_kf_pts[ost->forced_kf_index]) { + ost->forced_kf_index++; + forced_keyframe = 1; + } else if (ost->forced_keyframes_pexpr) { + double res; + ost->forced_keyframes_expr_const_values[FKF_T] = pts_time; + res = av_expr_eval(ost->forced_keyframes_pexpr, + ost->forced_keyframes_expr_const_values, NULL); + ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", + ost->forced_keyframes_expr_const_values[FKF_N], + ost->forced_keyframes_expr_const_values[FKF_N_FORCED], + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N], + ost->forced_keyframes_expr_const_values[FKF_T], + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T], + res); + if (res) { + forced_keyframe = 1; + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = + ost->forced_keyframes_expr_const_values[FKF_N]; + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = + ost->forced_keyframes_expr_const_values[FKF_T]; + ost->forced_keyframes_expr_const_values[FKF_N_FORCED] += 1; + } + + ost->forced_keyframes_expr_const_values[FKF_N] += 1; + } else if ( ost->forced_keyframes + && !strncmp(ost->forced_keyframes, "source", 6) + && in_picture->key_frame==1) { + forced_keyframe = 1; + } + + if (forced_keyframe) { + in_picture->pict_type = AV_PICTURE_TYPE_I; + av_log(NULL, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); + } + + update_benchmark(NULL); + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "encoder <- type:video " + "frame_pts:%s frame_pts_time:%s time_base:%d/%d\n", + av_ts2str(in_picture->pts), av_ts2timestr(in_picture->pts, &enc->time_base), + enc->time_base.num, enc->time_base.den); + } + + ost->frames_encoded++; + + ret = avcodec_send_frame(enc, in_picture); + if (ret < 0) + goto error; + + while (1) { + ret = avcodec_receive_packet(enc, &pkt); + update_benchmark("encode_video %d.%d", ost->file_index, ost->index); + if (ret == AVERROR(EAGAIN)) + break; + if (ret < 0) + goto error; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "encoder -> type:video " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n", + av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &enc->time_base), + av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &enc->time_base)); + } + + if (pkt.pts == AV_NOPTS_VALUE && !(enc->codec->capabilities & AV_CODEC_CAP_DELAY)) + pkt.pts = ost->sync_opts; + + av_packet_rescale_ts(&pkt, enc->time_base, ost->mux_timebase); + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "encoder -> type:video " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n", + av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ost->mux_timebase), + av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ost->mux_timebase)); + } + + frame_size = pkt.size; + output_packet(of, &pkt, ost, 0); + + /* if two pass, output log */ + if (ost->logfile && enc->stats_out) { + fprintf(ost->logfile, "%s", enc->stats_out); + } + } + } + ost->sync_opts++; + /* + * For video, number of frames in == number of packets out. + * But there may be reordering, so we can't throw away frames on encoder + * flush, we need to limit them here, before they go into encoder. + */ + ost->frame_number++; + + if (vstats_filename && frame_size) + do_video_stats(ost, frame_size); + } + + if (!ost->last_frame) + ost->last_frame = av_frame_alloc(); + av_frame_unref(ost->last_frame); + if (next_picture && ost->last_frame) + av_frame_ref(ost->last_frame, next_picture); + else + av_frame_free(&ost->last_frame); + + return; +error: + av_log(NULL, AV_LOG_FATAL, "Video encoding failed\n"); + exit_program(1); +} + +static double psnr(double d) +{ + return -10.0 * log10(d); +} + +static void do_video_stats(OutputStream *ost, int frame_size) +{ + AVCodecContext *enc; + int frame_number; + double ti1, bitrate, avg_bitrate; + + /* this is executed just the first time do_video_stats is called */ + if (!vstats_file) { + vstats_file = fopen(vstats_filename, "w"); + if (!vstats_file) { + perror("fopen"); + exit_program(1); + } + } + + enc = ost->enc_ctx; + if (enc->codec_type == AVMEDIA_TYPE_VIDEO) { + frame_number = ost->st->nb_frames; + if (vstats_version <= 1) { + fprintf(vstats_file, "frame= %5d q= %2.1f ", frame_number, + ost->quality / (float)FF_QP2LAMBDA); + } else { + fprintf(vstats_file, "out= %2d st= %2d frame= %5d q= %2.1f ", ost->file_index, ost->index, frame_number, + ost->quality / (float)FF_QP2LAMBDA); + } + + if (ost->error[0]>=0 && (enc->flags & AV_CODEC_FLAG_PSNR)) + fprintf(vstats_file, "PSNR= %6.2f ", psnr(ost->error[0] / (enc->width * enc->height * 255.0 * 255.0))); + + fprintf(vstats_file,"f_size= %6d ", frame_size); + /* compute pts value */ + ti1 = av_stream_get_end_pts(ost->st) * av_q2d(ost->st->time_base); + if (ti1 < 0.01) + ti1 = 0.01; + + bitrate = (frame_size * 8) / av_q2d(enc->time_base) / 1000.0; + avg_bitrate = (double)(ost->data_size * 8) / ti1 / 1000.0; + fprintf(vstats_file, "s_size= %8.0fkB time= %0.3f br= %7.1fkbits/s avg_br= %7.1fkbits/s ", + (double)ost->data_size / 1024, ti1, bitrate, avg_bitrate); + fprintf(vstats_file, "type= %c\n", av_get_picture_type_char(ost->pict_type)); + } +} + +static int init_output_stream(OutputStream *ost, char *error, int error_len); + +static void finish_output_stream(OutputStream *ost) +{ + OutputFile *of = output_files[ost->file_index]; + int i; + + ost->finished = ENCODER_FINISHED | MUXER_FINISHED; + + if (of->shortest) { + for (i = 0; i < of->ctx->nb_streams; i++) + output_streams[of->ost_index + i]->finished = ENCODER_FINISHED | MUXER_FINISHED; + } +} + +/** + * Get and encode new output from any of the filtergraphs, without causing + * activity. + * + * @return 0 for success, <0 for severe errors + */ +static int reap_filters(int flush) +{ + AVFrame *filtered_frame = NULL; + int i; + + /* Reap all buffers present in the buffer sinks */ + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + OutputFile *of = output_files[ost->file_index]; + AVFilterContext *filter; + AVCodecContext *enc = ost->enc_ctx; + int ret = 0; + + if (!ost->filter || !ost->filter->graph->graph) + continue; + filter = ost->filter->filter; + + if (!ost->initialized) { + char error[1024] = ""; + ret = init_output_stream(ost, error, sizeof(error)); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", + ost->file_index, ost->index, error); + exit_program(1); + } + } + + if (!ost->filtered_frame && !(ost->filtered_frame = av_frame_alloc())) { + return AVERROR(ENOMEM); + } + filtered_frame = ost->filtered_frame; + + while (1) { + double float_pts = AV_NOPTS_VALUE; // this is identical to filtered_frame.pts but with higher precision + ret = av_buffersink_get_frame_flags(filter, filtered_frame, + AV_BUFFERSINK_FLAG_NO_REQUEST); + if (ret < 0) { + if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { + av_log(NULL, AV_LOG_WARNING, + "Error in av_buffersink_get_frame_flags(): %s\n", av_err2str(ret)); + } else if (flush && ret == AVERROR_EOF) { + if (av_buffersink_get_type(filter) == AVMEDIA_TYPE_VIDEO) + do_video_out(of, ost, NULL, AV_NOPTS_VALUE); + } + break; + } + if (ost->finished) { + av_frame_unref(filtered_frame); + continue; + } + if (filtered_frame->pts != AV_NOPTS_VALUE) { + int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; + AVRational filter_tb = av_buffersink_get_time_base(filter); + AVRational tb = enc->time_base; + int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); + + tb.den <<= extra_bits; + float_pts = + av_rescale_q(filtered_frame->pts, filter_tb, tb) - + av_rescale_q(start_time, AV_TIME_BASE_Q, tb); + float_pts /= 1 << extra_bits; + // avoid exact midoints to reduce the chance of rounding differences, this can be removed in case the fps code is changed to work with integers + float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + + filtered_frame->pts = + av_rescale_q(filtered_frame->pts, filter_tb, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + } + //if (ost->source_index >= 0) + // *filtered_frame= *input_streams[ost->source_index]->decoded_frame; //for me_threshold + + switch (av_buffersink_get_type(filter)) { + case AVMEDIA_TYPE_VIDEO: + if (!ost->frame_aspect_ratio.num) + enc->sample_aspect_ratio = filtered_frame->sample_aspect_ratio; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "filter -> pts:%s pts_time:%s exact:%f time_base:%d/%d\n", + av_ts2str(filtered_frame->pts), av_ts2timestr(filtered_frame->pts, &enc->time_base), + float_pts, + enc->time_base.num, enc->time_base.den); + } + + do_video_out(of, ost, filtered_frame, float_pts); + break; + case AVMEDIA_TYPE_AUDIO: + if (!(enc->codec->capabilities & AV_CODEC_CAP_PARAM_CHANGE) && + enc->channels != filtered_frame->channels) { + av_log(NULL, AV_LOG_ERROR, + "Audio filter graph output is not normalized and encoder does not support parameter changes\n"); + break; + } + do_audio_out(of, ost, filtered_frame); + break; + default: + // TODO support subtitle filters + av_assert0(0); + } + + av_frame_unref(filtered_frame); + } + } + + return 0; +} + +static void print_final_stats(int64_t total_size) +{ + uint64_t video_size = 0, audio_size = 0, extra_size = 0, other_size = 0; + uint64_t subtitle_size = 0; + uint64_t data_size = 0; + float percent = -1.0; + int i, j; + int pass1_used = 1; + + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + switch (ost->enc_ctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: video_size += ost->data_size; break; + case AVMEDIA_TYPE_AUDIO: audio_size += ost->data_size; break; + case AVMEDIA_TYPE_SUBTITLE: subtitle_size += ost->data_size; break; + default: other_size += ost->data_size; break; + } + extra_size += ost->enc_ctx->extradata_size; + data_size += ost->data_size; + if ( (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) + != AV_CODEC_FLAG_PASS1) + pass1_used = 0; + } + + if (data_size && total_size>0 && total_size >= data_size) + percent = 100.0 * (total_size - data_size) / data_size; + + av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: ", + video_size / 1024.0, + audio_size / 1024.0, + subtitle_size / 1024.0, + other_size / 1024.0, + extra_size / 1024.0); + if (percent >= 0.0) + av_log(NULL, AV_LOG_INFO, "%f%%", percent); + else + av_log(NULL, AV_LOG_INFO, "unknown"); + av_log(NULL, AV_LOG_INFO, "\n"); + + /* print verbose per-stream stats */ + for (i = 0; i < nb_input_files; i++) { + InputFile *f = input_files[i]; + uint64_t total_packets = 0, total_size = 0; + + av_log(NULL, AV_LOG_VERBOSE, "Input file #%d (%s):\n", + i, f->ctx->filename); + + for (j = 0; j < f->nb_streams; j++) { + InputStream *ist = input_streams[f->ist_index + j]; + enum AVMediaType type = ist->dec_ctx->codec_type; + + total_size += ist->data_size; + total_packets += ist->nb_packets; + + av_log(NULL, AV_LOG_VERBOSE, " Input stream #%d:%d (%s): ", + i, j, media_type_string(type)); + av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets read (%"PRIu64" bytes); ", + ist->nb_packets, ist->data_size); + + if (ist->decoding_needed) { + av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames decoded", + ist->frames_decoded); + if (type == AVMEDIA_TYPE_AUDIO) + av_log(NULL, AV_LOG_VERBOSE, " (%"PRIu64" samples)", ist->samples_decoded); + av_log(NULL, AV_LOG_VERBOSE, "; "); + } + + av_log(NULL, AV_LOG_VERBOSE, "\n"); + } + + av_log(NULL, AV_LOG_VERBOSE, " Total: %"PRIu64" packets (%"PRIu64" bytes) demuxed\n", + total_packets, total_size); + } + + for (i = 0; i < nb_output_files; i++) { + OutputFile *of = output_files[i]; + uint64_t total_packets = 0, total_size = 0; + + av_log(NULL, AV_LOG_VERBOSE, "Output file #%d (%s):\n", + i, of->ctx->filename); + + for (j = 0; j < of->ctx->nb_streams; j++) { + OutputStream *ost = output_streams[of->ost_index + j]; + enum AVMediaType type = ost->enc_ctx->codec_type; + + total_size += ost->data_size; + total_packets += ost->packets_written; + + av_log(NULL, AV_LOG_VERBOSE, " Output stream #%d:%d (%s): ", + i, j, media_type_string(type)); + if (ost->encoding_needed) { + av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames encoded", + ost->frames_encoded); + if (type == AVMEDIA_TYPE_AUDIO) + av_log(NULL, AV_LOG_VERBOSE, " (%"PRIu64" samples)", ost->samples_encoded); + av_log(NULL, AV_LOG_VERBOSE, "; "); + } + + av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets muxed (%"PRIu64" bytes); ", + ost->packets_written, ost->data_size); + + av_log(NULL, AV_LOG_VERBOSE, "\n"); + } + + av_log(NULL, AV_LOG_VERBOSE, " Total: %"PRIu64" packets (%"PRIu64" bytes) muxed\n", + total_packets, total_size); + } + if(video_size + data_size + audio_size + subtitle_size + extra_size == 0){ + av_log(NULL, AV_LOG_WARNING, "Output file is empty, nothing was encoded "); + if (pass1_used) { + av_log(NULL, AV_LOG_WARNING, "\n"); + } else { + av_log(NULL, AV_LOG_WARNING, "(check -ss / -t / -frames parameters if used)\n"); + } + } +} + +static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time) +{ + char buf[1024]; + AVBPrint buf_script; + OutputStream *ost; + AVFormatContext *oc; + int64_t total_size; + AVCodecContext *enc; + int frame_number, vid, i; + double bitrate; + double speed; + int64_t pts = INT64_MIN + 1; + static int64_t last_time = -1; + static int qp_histogram[52]; + int hours, mins, secs, us; + int ret; + float t; + + if (!print_stats && !is_last_report && !progress_avio) + return; + + if (!is_last_report) { + if (last_time == -1) { + last_time = cur_time; + return; + } + if ((cur_time - last_time) < 500000) + return; + last_time = cur_time; + } + + t = (cur_time-timer_start) / 1000000.0; + + + oc = output_files[0]->ctx; + + total_size = avio_size(oc->pb); + if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too + total_size = avio_tell(oc->pb); + + buf[0] = '\0'; + vid = 0; + av_bprint_init(&buf_script, 0, 1); + for (i = 0; i < nb_output_streams; i++) { + float q = -1; + ost = output_streams[i]; + enc = ost->enc_ctx; + if (!ost->stream_copy) + q = ost->quality / (float) FF_QP2LAMBDA; + + if (vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "q=%2.1f ", q); + av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", + ost->file_index, ost->index, q); + } + if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { + float fps; + + frame_number = ost->frame_number; + fps = t > 1 ? frame_number / t : 0; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "frame=%5d fps=%3.*f q=%3.1f ", + frame_number, fps < 9.95, fps, q); + av_bprintf(&buf_script, "frame=%d\n", frame_number); + av_bprintf(&buf_script, "fps=%.1f\n", fps); + av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", + ost->file_index, ost->index, q); + if (is_last_report) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "L"); + if (qp_hist) { + int j; + int qp = lrintf(q); + if (qp >= 0 && qp < FF_ARRAY_ELEMS(qp_histogram)) + qp_histogram[qp]++; + for (j = 0; j < 32; j++) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%X", av_log2(qp_histogram[j] + 1)); + } + + if ((enc->flags & AV_CODEC_FLAG_PSNR) && (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { + int j; + double error, error_sum = 0; + double scale, scale_sum = 0; + double p; + char type[3] = { 'Y','U','V' }; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "PSNR="); + for (j = 0; j < 3; j++) { + if (is_last_report) { + error = enc->error[j]; + scale = enc->width * enc->height * 255.0 * 255.0 * frame_number; + } else { + error = ost->error[j]; + scale = enc->width * enc->height * 255.0 * 255.0; + } + if (j) + scale /= 4; + error_sum += error; + scale_sum += scale; + p = psnr(error / scale); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%c:%2.2f ", type[j], p); + av_bprintf(&buf_script, "stream_%d_%d_psnr_%c=%2.2f\n", + ost->file_index, ost->index, type[j] | 32, p); + } + p = psnr(error_sum / scale_sum); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "*:%2.2f ", psnr(error_sum / scale_sum)); + av_bprintf(&buf_script, "stream_%d_%d_psnr_all=%2.2f\n", + ost->file_index, ost->index, p); + } + vid = 1; + } + /* compute min output value */ + if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) + pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), + ost->st->time_base, AV_TIME_BASE_Q)); + if (is_last_report) + nb_frames_drop += ost->last_dropped; + } + + secs = FFABS(pts) / AV_TIME_BASE; + us = FFABS(pts) % AV_TIME_BASE; + mins = secs / 60; + secs %= 60; + hours = mins / 60; + mins %= 60; + + bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; + speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; + + if (total_size < 0) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "size=N/A time="); + else snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "size=%8.0fkB time=", total_size / 1024.0); + if (pts < 0) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "-"); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d:%02d.%02d ", hours, mins, secs, + (100 * us) / AV_TIME_BASE); + + if (bitrate < 0) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),"bitrate=N/A"); + av_bprintf(&buf_script, "bitrate=N/A\n"); + }else{ + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),"bitrate=%6.1fkbits/s", bitrate); + av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); + } + + if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); + else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); + av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time=%02d:%02d:%02d.%06d\n", + hours, mins, secs, us); + + if (nb_frames_dup || nb_frames_drop) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " dup=%d drop=%d", + nb_frames_dup, nb_frames_drop); + av_bprintf(&buf_script, "dup_frames=%d\n", nb_frames_dup); + av_bprintf(&buf_script, "drop_frames=%d\n", nb_frames_drop); + + if (speed < 0) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf)," speed=N/A"); + av_bprintf(&buf_script, "speed=N/A\n"); + } else { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf)," speed=%4.3gx", speed); + av_bprintf(&buf_script, "speed=%4.3gx\n", speed); + } + + if (print_stats || is_last_report) { + const char end = is_last_report ? '\n' : '\r'; + if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { + fprintf(stderr, "%s %c", buf, end); + } else + av_log(NULL, AV_LOG_INFO, "%s %c", buf, end); + + fflush(stderr); + } + + if (progress_avio) { + av_bprintf(&buf_script, "progress=%s\n", + is_last_report ? "end" : "continue"); + avio_write(progress_avio, buf_script.str, + FFMIN(buf_script.len, buf_script.size - 1)); + avio_flush(progress_avio); + av_bprint_finalize(&buf_script, NULL); + if (is_last_report) { + if ((ret = avio_closep(&progress_avio)) < 0) + av_log(NULL, AV_LOG_ERROR, + "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + } + } + + if (is_last_report) + print_final_stats(total_size); +} + +static void flush_encoders(void) +{ + int i, ret; + + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + AVCodecContext *enc = ost->enc_ctx; + OutputFile *of = output_files[ost->file_index]; + + if (!ost->encoding_needed) + continue; + + // Try to enable encoding with no input frames. + // Maybe we should just let encoding fail instead. + if (!ost->initialized) { + FilterGraph *fg = ost->filter->graph; + char error[1024] = ""; + + av_log(NULL, AV_LOG_WARNING, + "Finishing stream %d:%d without any data written to it.\n", + ost->file_index, ost->st->index); + + if (ost->filter && !fg->graph) { + int x; + for (x = 0; x < fg->nb_inputs; x++) { + InputFilter *ifilter = fg->inputs[x]; + if (ifilter->format < 0) { + AVCodecParameters *par = ifilter->ist->st->codecpar; + // We never got any input. Set a fake format, which will + // come from libavformat. + ifilter->format = par->format; + ifilter->sample_rate = par->sample_rate; + ifilter->channels = par->channels; + ifilter->channel_layout = par->channel_layout; + ifilter->width = par->width; + ifilter->height = par->height; + ifilter->sample_aspect_ratio = par->sample_aspect_ratio; + } + } + + if (!ifilter_has_all_input_formats(fg)) + continue; + + ret = configure_filtergraph(fg); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); + exit_program(1); + } + + finish_output_stream(ost); + } + + ret = init_output_stream(ost, error, sizeof(error)); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", + ost->file_index, ost->index, error); + exit_program(1); + } + } + + if (enc->codec_type == AVMEDIA_TYPE_AUDIO && enc->frame_size <= 1) + continue; +#if FF_API_LAVF_FMT_RAWPICTURE + if (enc->codec_type == AVMEDIA_TYPE_VIDEO && (of->ctx->oformat->flags & AVFMT_RAWPICTURE) && enc->codec->id == AV_CODEC_ID_RAWVIDEO) + continue; +#endif + + if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + + for (;;) { + const char *desc = NULL; + AVPacket pkt; + int pkt_size; + + switch (enc->codec_type) { + case AVMEDIA_TYPE_AUDIO: + desc = "audio"; + break; + case AVMEDIA_TYPE_VIDEO: + desc = "video"; + break; + default: + av_assert0(0); + } + + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + update_benchmark(NULL); + + while ((ret = avcodec_receive_packet(enc, &pkt)) == AVERROR(EAGAIN)) { + ret = avcodec_send_frame(enc, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "%s encoding failed: %s\n", + desc, + av_err2str(ret)); + exit_program(1); + } + } + + update_benchmark("flush_%s %d.%d", desc, ost->file_index, ost->index); + if (ret < 0 && ret != AVERROR_EOF) { + av_log(NULL, AV_LOG_FATAL, "%s encoding failed: %s\n", + desc, + av_err2str(ret)); + exit_program(1); + } + if (ost->logfile && enc->stats_out) { + fprintf(ost->logfile, "%s", enc->stats_out); + } + if (ret == AVERROR_EOF) { + output_packet(of, &pkt, ost, 1); + break; + } + if (ost->finished & MUXER_FINISHED) { + av_packet_unref(&pkt); + continue; + } + av_packet_rescale_ts(&pkt, enc->time_base, ost->mux_timebase); + pkt_size = pkt.size; + output_packet(of, &pkt, ost, 0); + if (ost->enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO && vstats_filename) { + do_video_stats(ost, pkt_size); + } + } + } +} + +/* + * Check whether a packet from ist should be written into ost at this time + */ +static int check_output_constraints(InputStream *ist, OutputStream *ost) +{ + OutputFile *of = output_files[ost->file_index]; + int ist_index = input_files[ist->file_index]->ist_index + ist->st->index; + + if (ost->source_index != ist_index) + return 0; + + if (ost->finished) + return 0; + + if (of->start_time != AV_NOPTS_VALUE && ist->pts < of->start_time) + return 0; + + return 1; +} + +static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *pkt) +{ + OutputFile *of = output_files[ost->file_index]; + InputFile *f = input_files [ist->file_index]; + int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; + int64_t ost_tb_start_time = av_rescale_q(start_time, AV_TIME_BASE_Q, ost->mux_timebase); + AVPicture pict; + AVPacket opkt; + + av_init_packet(&opkt); + + if ((!ost->frame_number && !(pkt->flags & AV_PKT_FLAG_KEY)) && + !ost->copy_initial_nonkeyframes) + return; + + if (!ost->frame_number && !ost->copy_prior_start) { + int64_t comp_start = start_time; + if (copy_ts && f->start_time != AV_NOPTS_VALUE) + comp_start = FFMAX(start_time, f->start_time + f->ts_offset); + if (pkt->pts == AV_NOPTS_VALUE ? + ist->pts < comp_start : + pkt->pts < av_rescale_q(comp_start, AV_TIME_BASE_Q, ist->st->time_base)) + return; + } + + if (of->recording_time != INT64_MAX && + ist->pts >= of->recording_time + start_time) { + close_output_stream(ost); + return; + } + + if (f->recording_time != INT64_MAX) { + start_time = f->ctx->start_time; + if (f->start_time != AV_NOPTS_VALUE && copy_ts) + start_time += f->start_time; + if (ist->pts >= f->recording_time + start_time) { + close_output_stream(ost); + return; + } + } + + /* force the input stream PTS */ + if (ost->enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) + ost->sync_opts++; + + if (pkt->pts != AV_NOPTS_VALUE) + opkt.pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->mux_timebase) - ost_tb_start_time; + else + opkt.pts = AV_NOPTS_VALUE; + + if (pkt->dts == AV_NOPTS_VALUE) + opkt.dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ost->mux_timebase); + else + opkt.dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase); + opkt.dts -= ost_tb_start_time; + + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && pkt->dts != AV_NOPTS_VALUE) { + int duration = av_get_audio_frame_duration(ist->dec_ctx, pkt->size); + if(!duration) + duration = ist->dec_ctx->frame_size; + opkt.dts = opkt.pts = av_rescale_delta(ist->st->time_base, pkt->dts, + (AVRational){1, ist->dec_ctx->sample_rate}, duration, &ist->filter_in_rescale_delta_last, + ost->mux_timebase) - ost_tb_start_time; + } + + opkt.duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); + + opkt.flags = pkt->flags; + // FIXME remove the following 2 lines they shall be replaced by the bitstream filters + if ( ost->st->codecpar->codec_id != AV_CODEC_ID_H264 + && ost->st->codecpar->codec_id != AV_CODEC_ID_MPEG1VIDEO + && ost->st->codecpar->codec_id != AV_CODEC_ID_MPEG2VIDEO + && ost->st->codecpar->codec_id != AV_CODEC_ID_VC1 + ) { + int ret = av_parser_change(ost->parser, ost->parser_avctx, + &opkt.data, &opkt.size, + pkt->data, pkt->size, + pkt->flags & AV_PKT_FLAG_KEY); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "av_parser_change failed: %s\n", + av_err2str(ret)); + exit_program(1); + } + if (ret) { + opkt.buf = av_buffer_create(opkt.data, opkt.size, av_buffer_default_free, NULL, 0); + if (!opkt.buf) + exit_program(1); + } + } else { + opkt.data = pkt->data; + opkt.size = pkt->size; + } + av_copy_packet_side_data(&opkt, pkt); + +#if FF_API_LAVF_FMT_RAWPICTURE + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + ost->st->codecpar->codec_id == AV_CODEC_ID_RAWVIDEO && + (of->ctx->oformat->flags & AVFMT_RAWPICTURE)) { + /* store AVPicture in AVPacket, as expected by the output format */ + int ret = avpicture_fill(&pict, opkt.data, ost->st->codecpar->format, ost->st->codecpar->width, ost->st->codecpar->height); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "avpicture_fill failed: %s\n", + av_err2str(ret)); + exit_program(1); + } + opkt.data = (uint8_t *)&pict; + opkt.size = sizeof(AVPicture); + opkt.flags |= AV_PKT_FLAG_KEY; + } +#endif + + output_packet(of, &opkt, ost, 0); +} + +int guess_input_channel_layout(InputStream *ist) +{ + AVCodecContext *dec = ist->dec_ctx; + + if (!dec->channel_layout) { + char layout_name[256]; + + if (dec->channels > ist->guess_layout_max) + return 0; + dec->channel_layout = av_get_default_channel_layout(dec->channels); + if (!dec->channel_layout) + return 0; + av_get_channel_layout_string(layout_name, sizeof(layout_name), + dec->channels, dec->channel_layout); + av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " + "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); + } + return 1; +} + +static void check_decode_result(InputStream *ist, int *got_output, int ret) +{ + if (*got_output || ret<0) + decode_error_stat[ret<0] ++; + + if (ret < 0 && exit_on_error) + exit_program(1); + + if (exit_on_error && *got_output && ist) { + if (ist->decoded_frame->decode_error_flags || (ist->decoded_frame->flags & AV_FRAME_FLAG_CORRUPT)) { + av_log(NULL, AV_LOG_FATAL, "%s: corrupt decoded frame in stream %d\n", input_files[ist->file_index]->ctx->filename, ist->st->index); + exit_program(1); + } + } +} + +// Filters can be configured only if the formats of all inputs are known. +static int ifilter_has_all_input_formats(FilterGraph *fg) +{ + int i; + for (i = 0; i < fg->nb_inputs; i++) { + if (fg->inputs[i]->format < 0 && (fg->inputs[i]->type == AVMEDIA_TYPE_AUDIO || + fg->inputs[i]->type == AVMEDIA_TYPE_VIDEO)) + return 0; + } + return 1; +} + +static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame) +{ + FilterGraph *fg = ifilter->graph; + int need_reinit, ret, i; + + /* determine if the parameters for this input changed */ + need_reinit = ifilter->format != frame->format; + if (!!ifilter->hw_frames_ctx != !!frame->hw_frames_ctx || + (ifilter->hw_frames_ctx && ifilter->hw_frames_ctx->data != frame->hw_frames_ctx->data)) + need_reinit = 1; + + switch (ifilter->ist->st->codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + need_reinit |= ifilter->sample_rate != frame->sample_rate || + ifilter->channels != frame->channels || + ifilter->channel_layout != frame->channel_layout; + break; + case AVMEDIA_TYPE_VIDEO: + need_reinit |= ifilter->width != frame->width || + ifilter->height != frame->height; + break; + } + + if (need_reinit) { + ret = ifilter_parameters_from_frame(ifilter, frame); + if (ret < 0) + return ret; + } + + /* (re)init the graph if possible, otherwise buffer the frame and return */ + if (need_reinit || !fg->graph) { + for (i = 0; i < fg->nb_inputs; i++) { + if (!ifilter_has_all_input_formats(fg)) { + AVFrame *tmp = av_frame_clone(frame); + if (!tmp) + return AVERROR(ENOMEM); + av_frame_unref(frame); + + if (!av_fifo_space(ifilter->frame_queue)) { + ret = av_fifo_realloc2(ifilter->frame_queue, 2 * av_fifo_size(ifilter->frame_queue)); + if (ret < 0) { + av_frame_free(&tmp); + return ret; + } + } + av_fifo_generic_write(ifilter->frame_queue, &tmp, sizeof(tmp), NULL); + return 0; + } + } + + ret = reap_filters(1); + if (ret < 0 && ret != AVERROR_EOF) { + char errbuf[128]; + av_strerror(ret, errbuf, sizeof(errbuf)); + + av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", errbuf); + return ret; + } + + ret = configure_filtergraph(fg); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n"); + return ret; + } + } + + ret = av_buffersrc_add_frame_flags(ifilter->filter, frame, AV_BUFFERSRC_FLAG_PUSH); + if (ret < 0) { + if (ret != AVERROR_EOF) + av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret)); + return ret; + } + + return 0; +} + +static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) +{ + int i, j, ret; + + ifilter->eof = 1; + + if (ifilter->filter) { + ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH); + if (ret < 0) + return ret; + } else { + // the filtergraph was never configured + FilterGraph *fg = ifilter->graph; + for (i = 0; i < fg->nb_inputs; i++) + if (!fg->inputs[i]->eof) + break; + if (i == fg->nb_inputs) { + // All the input streams have finished without the filtergraph + // ever being configured. + // Mark the output streams as finished. + for (j = 0; j < fg->nb_outputs; j++) + finish_output_stream(fg->outputs[j]->ost); + } + } + + return 0; +} + +// This does not quite work like avcodec_decode_audio4/avcodec_decode_video2. +// There is the following difference: if you got a frame, you must call +// it again with pkt=NULL. pkt==NULL is treated differently from pkt->size==0 +// (pkt==NULL means get more output, pkt->size==0 is a flush/drain packet) +static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +{ + int ret; + + *got_frame = 0; + + if (pkt) { + ret = avcodec_send_packet(avctx, pkt); + // In particular, we don't expect AVERROR(EAGAIN), because we read all + // decoded frames with avcodec_receive_frame() until done. + if (ret < 0 && ret != AVERROR_EOF) + return ret; + } + + ret = avcodec_receive_frame(avctx, frame); + if (ret < 0 && ret != AVERROR(EAGAIN)) + return ret; + if (ret >= 0) + *got_frame = 1; + + return 0; +} + +static int send_frame_to_filters(InputStream *ist, AVFrame *decoded_frame) +{ + int i, ret; + AVFrame *f; + + av_assert1(ist->nb_filters > 0); /* ensure ret is initialized */ + for (i = 0; i < ist->nb_filters; i++) { + if (i < ist->nb_filters - 1) { + f = ist->filter_frame; + ret = av_frame_ref(f, decoded_frame); + if (ret < 0) + break; + } else + f = decoded_frame; + ret = ifilter_send_frame(ist->filters[i], f); + if (ret == AVERROR_EOF) + ret = 0; /* ignore */ + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Failed to inject frame into filter network: %s\n", av_err2str(ret)); + break; + } + } + return ret; +} + +static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, + int *decode_failed) +{ + AVFrame *decoded_frame; + AVCodecContext *avctx = ist->dec_ctx; + int ret, err = 0; + AVRational decoded_frame_tb; + + if (!ist->decoded_frame && !(ist->decoded_frame = av_frame_alloc())) + return AVERROR(ENOMEM); + if (!ist->filter_frame && !(ist->filter_frame = av_frame_alloc())) + return AVERROR(ENOMEM); + decoded_frame = ist->decoded_frame; + + update_benchmark(NULL); + ret = decode(avctx, decoded_frame, got_output, pkt); + update_benchmark("decode_audio %d.%d", ist->file_index, ist->st->index); + if (ret < 0) + *decode_failed = 1; + + if (ret >= 0 && avctx->sample_rate <= 0) { + av_log(avctx, AV_LOG_ERROR, "Sample rate %d invalid\n", avctx->sample_rate); + ret = AVERROR_INVALIDDATA; + } + + if (ret != AVERROR_EOF) + check_decode_result(ist, got_output, ret); + + if (!*got_output || ret < 0) + return ret; + + ist->samples_decoded += decoded_frame->nb_samples; + ist->frames_decoded++; + +#if 1 + /* increment next_dts to use for the case where the input stream does not + have timestamps or there are multiple frames in the packet */ + ist->next_pts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / + avctx->sample_rate; + ist->next_dts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / + avctx->sample_rate; +#endif + + if (decoded_frame->pts != AV_NOPTS_VALUE) { + decoded_frame_tb = ist->st->time_base; + } else if (pkt && pkt->pts != AV_NOPTS_VALUE) { + decoded_frame->pts = pkt->pts; + decoded_frame_tb = ist->st->time_base; + }else { + decoded_frame->pts = ist->dts; + decoded_frame_tb = AV_TIME_BASE_Q; + } + if (decoded_frame->pts != AV_NOPTS_VALUE) + decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts, + (AVRational){1, avctx->sample_rate}, decoded_frame->nb_samples, &ist->filter_in_rescale_delta_last, + (AVRational){1, avctx->sample_rate}); + ist->nb_samples = decoded_frame->nb_samples; + err = send_frame_to_filters(ist, decoded_frame); + + av_frame_unref(ist->filter_frame); + av_frame_unref(decoded_frame); + return err < 0 ? err : ret; +} + +static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_t *duration_pts, int eof, + int *decode_failed) +{ + AVFrame *decoded_frame; + int i, ret = 0, err = 0; + int64_t best_effort_timestamp; + int64_t dts = AV_NOPTS_VALUE; + AVPacket avpkt; + + // With fate-indeo3-2, we're getting 0-sized packets before EOF for some + // reason. This seems like a semi-critical bug. Don't trigger EOF, and + // skip the packet. + if (!eof && pkt && pkt->size == 0) + return 0; + + if (!ist->decoded_frame && !(ist->decoded_frame = av_frame_alloc())) + return AVERROR(ENOMEM); + if (!ist->filter_frame && !(ist->filter_frame = av_frame_alloc())) + return AVERROR(ENOMEM); + decoded_frame = ist->decoded_frame; + if (ist->dts != AV_NOPTS_VALUE) + dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt) { + avpkt = *pkt; + avpkt.dts = dts; // ffmpeg.c probably shouldn't do this + } + + // The old code used to set dts on the drain packet, which does not work + // with the new API anymore. + if (eof) { + void *new = av_realloc_array(ist->dts_buffer, ist->nb_dts_buffer + 1, sizeof(ist->dts_buffer[0])); + if (!new) + return AVERROR(ENOMEM); + ist->dts_buffer = new; + ist->dts_buffer[ist->nb_dts_buffer++] = dts; + } + + update_benchmark(NULL); + ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt ? &avpkt : NULL); + update_benchmark("decode_video %d.%d", ist->file_index, ist->st->index); + if (ret < 0) + *decode_failed = 1; + + // The following line may be required in some cases where there is no parser + // or the parser does not has_b_frames correctly + if (ist->st->codecpar->video_delay < ist->dec_ctx->has_b_frames) { + if (ist->dec_ctx->codec_id == AV_CODEC_ID_H264) { + ist->st->codecpar->video_delay = ist->dec_ctx->has_b_frames; + } else + av_log(ist->dec_ctx, AV_LOG_WARNING, + "video_delay is larger in decoder than demuxer %d > %d.\n" + "If you want to help, upload a sample " + "of this file to ftp://upload.ffmpeg.org/incoming/ " + "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)\n", + ist->dec_ctx->has_b_frames, + ist->st->codecpar->video_delay); + } + + if (ret != AVERROR_EOF) + check_decode_result(ist, got_output, ret); + + if (*got_output && ret >= 0) { + if (ist->dec_ctx->width != decoded_frame->width || + ist->dec_ctx->height != decoded_frame->height || + ist->dec_ctx->pix_fmt != decoded_frame->format) { + av_log(NULL, AV_LOG_DEBUG, "Frame parameters mismatch context %d,%d,%d != %d,%d,%d\n", + decoded_frame->width, + decoded_frame->height, + decoded_frame->format, + ist->dec_ctx->width, + ist->dec_ctx->height, + ist->dec_ctx->pix_fmt); + } + } + + if (!*got_output || ret < 0) + return ret; + + if(ist->top_field_first>=0) + decoded_frame->top_field_first = ist->top_field_first; + + ist->frames_decoded++; + + if (ist->hwaccel_retrieve_data && decoded_frame->format == ist->hwaccel_pix_fmt) { + err = ist->hwaccel_retrieve_data(ist->dec_ctx, decoded_frame); + if (err < 0) + goto fail; + } + ist->hwaccel_retrieved_pix_fmt = decoded_frame->format; + + best_effort_timestamp= decoded_frame->best_effort_timestamp; + *duration_pts = decoded_frame->pkt_duration; + + if (ist->framerate.num) + best_effort_timestamp = ist->cfr_next_pts++; + + if (eof && best_effort_timestamp == AV_NOPTS_VALUE && ist->nb_dts_buffer > 0) { + best_effort_timestamp = ist->dts_buffer[0]; + + for (i = 0; i < ist->nb_dts_buffer - 1; i++) + ist->dts_buffer[i] = ist->dts_buffer[i + 1]; + ist->nb_dts_buffer--; + } + + if(best_effort_timestamp != AV_NOPTS_VALUE) { + int64_t ts = av_rescale_q(decoded_frame->pts = best_effort_timestamp, ist->st->time_base, AV_TIME_BASE_Q); + + if (ts != AV_NOPTS_VALUE) + ist->next_pts = ist->pts = ts; + } + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "decoder -> ist_index:%d type:video " + "frame_pts:%s frame_pts_time:%s best_effort_ts:%"PRId64" best_effort_ts_time:%s keyframe:%d frame_type:%d time_base:%d/%d\n", + ist->st->index, av_ts2str(decoded_frame->pts), + av_ts2timestr(decoded_frame->pts, &ist->st->time_base), + best_effort_timestamp, + av_ts2timestr(best_effort_timestamp, &ist->st->time_base), + decoded_frame->key_frame, decoded_frame->pict_type, + ist->st->time_base.num, ist->st->time_base.den); + } + + if (ist->st->sample_aspect_ratio.num) + decoded_frame->sample_aspect_ratio = ist->st->sample_aspect_ratio; + + err = send_frame_to_filters(ist, decoded_frame); + +fail: + av_frame_unref(ist->filter_frame); + av_frame_unref(decoded_frame); + return err < 0 ? err : ret; +} + +static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, + int *decode_failed) +{ + AVSubtitle subtitle; + int free_sub = 1; + int i, ret = avcodec_decode_subtitle2(ist->dec_ctx, + &subtitle, got_output, pkt); + + check_decode_result(NULL, got_output, ret); + + if (ret < 0 || !*got_output) { + *decode_failed = 1; + if (!pkt->size) + sub2video_flush(ist); + return ret; + } + + if (ist->fix_sub_duration) { + int end = 1; + if (ist->prev_sub.got_output) { + end = av_rescale(subtitle.pts - ist->prev_sub.subtitle.pts, + 1000, AV_TIME_BASE); + if (end < ist->prev_sub.subtitle.end_display_time) { + av_log(ist->dec_ctx, AV_LOG_DEBUG, + "Subtitle duration reduced from %"PRId32" to %d%s\n", + ist->prev_sub.subtitle.end_display_time, end, + end <= 0 ? ", dropping it" : ""); + ist->prev_sub.subtitle.end_display_time = end; + } + } + FFSWAP(int, *got_output, ist->prev_sub.got_output); + FFSWAP(int, ret, ist->prev_sub.ret); + FFSWAP(AVSubtitle, subtitle, ist->prev_sub.subtitle); + if (end <= 0) + goto out; + } + + if (!*got_output) + return ret; + + if (ist->sub2video.frame) { + sub2video_update(ist, &subtitle); + } else if (ist->nb_filters) { + if (!ist->sub2video.sub_queue) + ist->sub2video.sub_queue = av_fifo_alloc(8 * sizeof(AVSubtitle)); + if (!ist->sub2video.sub_queue) + exit_program(1); + if (!av_fifo_space(ist->sub2video.sub_queue)) { + ret = av_fifo_realloc2(ist->sub2video.sub_queue, 2 * av_fifo_size(ist->sub2video.sub_queue)); + if (ret < 0) + exit_program(1); + } + av_fifo_generic_write(ist->sub2video.sub_queue, &subtitle, sizeof(subtitle), NULL); + free_sub = 0; + } + + if (!subtitle.num_rects) + goto out; + + ist->frames_decoded++; + + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + + if (!check_output_constraints(ist, ost) || !ost->encoding_needed + || ost->enc->type != AVMEDIA_TYPE_SUBTITLE) + continue; + + do_subtitle_out(output_files[ost->file_index], ost, &subtitle); + } + +out: + if (free_sub) + avsubtitle_free(&subtitle); + return ret; +} + +static int send_filter_eof(InputStream *ist) +{ + int i, ret; + /* TODO keep pts also in stream time base to avoid converting back */ + int64_t pts = av_rescale_q_rnd(ist->pts, AV_TIME_BASE_Q, ist->st->time_base, + AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); + + for (i = 0; i < ist->nb_filters; i++) { + ret = ifilter_send_eof(ist->filters[i], pts); + if (ret < 0) + return ret; + } + return 0; +} + +/* pkt = NULL means EOF (needed to flush decoder buffers) */ +static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof) +{ + int ret = 0, i; + int repeating = 0; + int eof_reached = 0; + + AVPacket avpkt; + if (!ist->saw_first_ts) { + ist->dts = ist->st->avg_frame_rate.num ? - ist->dec_ctx->has_b_frames * AV_TIME_BASE / av_q2d(ist->st->avg_frame_rate) : 0; + ist->pts = 0; + if (pkt && pkt->pts != AV_NOPTS_VALUE && !ist->decoding_needed) { + ist->dts += av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); + ist->pts = ist->dts; //unused but better to set it to a value thats not totally wrong + } + ist->saw_first_ts = 1; + } + + if (ist->next_dts == AV_NOPTS_VALUE) + ist->next_dts = ist->dts; + if (ist->next_pts == AV_NOPTS_VALUE) + ist->next_pts = ist->pts; + + if (!pkt) { + /* EOF handling */ + av_init_packet(&avpkt); + avpkt.data = NULL; + avpkt.size = 0; + } else { + avpkt = *pkt; + } + + if (pkt && pkt->dts != AV_NOPTS_VALUE) { + ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); + if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) + ist->next_pts = ist->pts = ist->dts; + } + + // while we have more to decode or while the decoder did output something on EOF + while (ist->decoding_needed) { + int64_t duration_dts = 0; + int64_t duration_pts = 0; + int got_output = 0; + int decode_failed = 0; + + ist->pts = ist->next_pts; + ist->dts = ist->next_dts; + + switch (ist->dec_ctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: + ret = decode_audio (ist, repeating ? NULL : &avpkt, &got_output, + &decode_failed); + break; + case AVMEDIA_TYPE_VIDEO: + ret = decode_video (ist, repeating ? NULL : &avpkt, &got_output, &duration_pts, !pkt, + &decode_failed); + if (!repeating || !pkt || got_output) { + if (pkt && pkt->duration) { + duration_dts = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); + } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) { + int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame; + duration_dts = ((int64_t)AV_TIME_BASE * + ist->dec_ctx->framerate.den * ticks) / + ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; + } + + if(ist->dts != AV_NOPTS_VALUE && duration_dts) { + ist->next_dts += duration_dts; + }else + ist->next_dts = AV_NOPTS_VALUE; + } + + if (got_output) + ist->next_pts += av_rescale_q(duration_pts, ist->st->time_base, AV_TIME_BASE_Q); + break; + case AVMEDIA_TYPE_SUBTITLE: + if (repeating) + break; + ret = transcode_subtitles(ist, &avpkt, &got_output, &decode_failed); + if (!pkt && ret >= 0) + ret = AVERROR_EOF; + break; + default: + return -1; + } + + if (ret == AVERROR_EOF) { + eof_reached = 1; + break; + } + + if (ret < 0) { + if (decode_failed) { + av_log(NULL, AV_LOG_ERROR, "Error while decoding stream #%d:%d: %s\n", + ist->file_index, ist->st->index, av_err2str(ret)); + } else { + av_log(NULL, AV_LOG_FATAL, "Error while processing the decoded " + "data for stream #%d:%d\n", ist->file_index, ist->st->index); + } + if (!decode_failed || exit_on_error) + exit_program(1); + break; + } + + if (got_output) + ist->got_output = 1; + + if (!got_output) + break; + + // During draining, we might get multiple output frames in this loop. + // ffmpeg.c does not drain the filter chain on configuration changes, + // which means if we send multiple frames at once to the filters, and + // one of those frames changes configuration, the buffered frames will + // be lost. This can upset certain FATE tests. + // Decode only 1 frame per call on EOF to appease these FATE tests. + // The ideal solution would be to rewrite decoding to use the new + // decoding API in a better way. + if (!pkt) + break; + + repeating = 1; + } + + /* after flushing, send an EOF on all the filter inputs attached to the stream */ + /* except when looping we need to flush but not to send an EOF */ + if (!pkt && ist->decoding_needed && eof_reached && !no_eof) { + int ret = send_filter_eof(ist); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error marking filters as finished\n"); + exit_program(1); + } + } + + /* handle stream copy */ + if (!ist->decoding_needed) { + ist->dts = ist->next_dts; + switch (ist->dec_ctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: + ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->frame_size) / + ist->dec_ctx->sample_rate; + break; + case AVMEDIA_TYPE_VIDEO: + if (ist->framerate.num) { + // TODO: Remove work-around for c99-to-c89 issue 7 + AVRational time_base_q = AV_TIME_BASE_Q; + int64_t next_dts = av_rescale_q(ist->next_dts, time_base_q, av_inv_q(ist->framerate)); + ist->next_dts = av_rescale_q(next_dts + 1, av_inv_q(ist->framerate), time_base_q); + } else if (pkt->duration) { + ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); + } else if(ist->dec_ctx->framerate.num != 0) { + int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict + 1 : ist->dec_ctx->ticks_per_frame; + ist->next_dts += ((int64_t)AV_TIME_BASE * + ist->dec_ctx->framerate.den * ticks) / + ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; + } + break; + } + ist->pts = ist->dts; + ist->next_pts = ist->next_dts; + } + for (i = 0; pkt && i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + + if (!check_output_constraints(ist, ost) || ost->encoding_needed) + continue; + + do_streamcopy(ist, ost, pkt); + } + + return !eof_reached; +} + +static void print_sdp(void) +{ + char sdp[16384]; + int i; + int j; + AVIOContext *sdp_pb; + AVFormatContext **avc; + + for (i = 0; i < nb_output_files; i++) { + if (!output_files[i]->header_written) + return; + } + + avc = av_malloc_array(nb_output_files, sizeof(*avc)); + if (!avc) + exit_program(1); + for (i = 0, j = 0; i < nb_output_files; i++) { + if (!strcmp(output_files[i]->ctx->oformat->name, "rtp")) { + avc[j] = output_files[i]->ctx; + j++; + } + } + + if (!j) + goto fail; + + av_sdp_create(avc, j, sdp, sizeof(sdp)); + + if (!sdp_filename) { + printf("SDP:\n%s\n", sdp); + fflush(stdout); + } else { + if (avio_open2(&sdp_pb, sdp_filename, AVIO_FLAG_WRITE, &int_cb, NULL) < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to open sdp file '%s'\n", sdp_filename); + } else { + avio_printf(sdp_pb, "SDP:\n%s", sdp); + avio_closep(&sdp_pb); + av_freep(&sdp_filename); + } + } + +fail: + av_freep(&avc); +} + +static const HWAccel *get_hwaccel(enum AVPixelFormat pix_fmt) +{ + int i; + for (i = 0; hwaccels[i].name; i++) + if (hwaccels[i].pix_fmt == pix_fmt) + return &hwaccels[i]; + return NULL; +} + +static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat *pix_fmts) +{ + InputStream *ist = s->opaque; + const enum AVPixelFormat *p; + int ret; + + for (p = pix_fmts; *p != -1; p++) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(*p); + const HWAccel *hwaccel; + + if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) + break; + + hwaccel = get_hwaccel(*p); + if (!hwaccel || + (ist->active_hwaccel_id && ist->active_hwaccel_id != hwaccel->id) || + (ist->hwaccel_id != HWACCEL_AUTO && ist->hwaccel_id != hwaccel->id)) + continue; + + ret = hwaccel->init(s); + if (ret < 0) { + if (ist->hwaccel_id == hwaccel->id) { + av_log(NULL, AV_LOG_FATAL, + "%s hwaccel requested for input stream #%d:%d, " + "but cannot be initialized.\n", hwaccel->name, + ist->file_index, ist->st->index); + return AV_PIX_FMT_NONE; + } + continue; + } + + if (ist->hw_frames_ctx) { + s->hw_frames_ctx = av_buffer_ref(ist->hw_frames_ctx); + if (!s->hw_frames_ctx) + return AV_PIX_FMT_NONE; + } + + ist->active_hwaccel_id = hwaccel->id; + ist->hwaccel_pix_fmt = *p; + break; + } + + return *p; +} + +static int get_buffer(AVCodecContext *s, AVFrame *frame, int flags) +{ + InputStream *ist = s->opaque; + + if (ist->hwaccel_get_buffer && frame->format == ist->hwaccel_pix_fmt) + return ist->hwaccel_get_buffer(s, frame, flags); + + return avcodec_default_get_buffer2(s, frame, flags); +} + +static int init_input_stream(int ist_index, char *error, int error_len) +{ + int ret; + InputStream *ist = input_streams[ist_index]; + + if (ist->decoding_needed) { + AVCodec *codec = ist->dec; + if (!codec) { + snprintf(error, error_len, "Decoder (codec %s) not found for input stream #%d:%d", + avcodec_get_name(ist->dec_ctx->codec_id), ist->file_index, ist->st->index); + return AVERROR(EINVAL); + } + + ist->dec_ctx->opaque = ist; + ist->dec_ctx->get_format = get_format; + ist->dec_ctx->get_buffer2 = get_buffer; + ist->dec_ctx->thread_safe_callbacks = 1; + + av_opt_set_int(ist->dec_ctx, "refcounted_frames", 1, 0); + if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE && + (ist->decoding_needed & DECODING_FOR_OST)) { + av_dict_set(&ist->decoder_opts, "compute_edt", "1", AV_DICT_DONT_OVERWRITE); + if (ist->decoding_needed & DECODING_FOR_FILTER) + av_log(NULL, AV_LOG_WARNING, "Warning using DVB subtitles for filtering and output at the same time is not fully supported, also see -compute_edt [0|1]\n"); + } + + av_dict_set(&ist->decoder_opts, "sub_text_format", "ass", AV_DICT_DONT_OVERWRITE); + + /* Useful for subtitles retiming by lavf (FIXME), skipping samples in + * audio, and video decoders such as cuvid or mediacodec */ + av_codec_set_pkt_timebase(ist->dec_ctx, ist->st->time_base); + + if (!av_dict_get(ist->decoder_opts, "threads", NULL, 0)) + av_dict_set(&ist->decoder_opts, "threads", "auto", 0); + + ret = hw_device_setup_for_decode(ist); + if (ret < 0) { + snprintf(error, error_len, "Device setup failed for " + "decoder on input stream #%d:%d : %s", + ist->file_index, ist->st->index, av_err2str(ret)); + return ret; + } + + if ((ret = avcodec_open2(ist->dec_ctx, codec, &ist->decoder_opts)) < 0) { + if (ret == AVERROR_EXPERIMENTAL) + abort_codec_experimental(codec, 0); + + snprintf(error, error_len, + "Error while opening decoder for input stream " + "#%d:%d : %s", + ist->file_index, ist->st->index, av_err2str(ret)); + return ret; + } + assert_avoptions(ist->decoder_opts); + } + + ist->next_pts = AV_NOPTS_VALUE; + ist->next_dts = AV_NOPTS_VALUE; + + return 0; +} + +static InputStream *get_input_stream(OutputStream *ost) +{ + if (ost->source_index >= 0) + return input_streams[ost->source_index]; + return NULL; +} + +static int compare_int64(const void *a, const void *b) +{ + return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); +} + +/* open the muxer when all the streams are initialized */ +static int check_init_output_file(OutputFile *of, int file_index) +{ + int ret, i; + + for (i = 0; i < of->ctx->nb_streams; i++) { + OutputStream *ost = output_streams[of->ost_index + i]; + if (!ost->initialized) + return 0; + } + + of->ctx->interrupt_callback = int_cb; + + ret = avformat_write_header(of->ctx, &of->opts); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Could not write header for output file #%d " + "(incorrect codec parameters ?): %s\n", + file_index, av_err2str(ret)); + return ret; + } + //assert_avoptions(of->opts); + of->header_written = 1; + + av_dump_format(of->ctx, file_index, of->ctx->filename, 1); + + if (sdp_filename || want_sdp) + print_sdp(); + + /* flush the muxing queues */ + for (i = 0; i < of->ctx->nb_streams; i++) { + OutputStream *ost = output_streams[of->ost_index + i]; + + /* try to improve muxing time_base (only possible if nothing has been written yet) */ + if (!av_fifo_size(ost->muxing_queue)) + ost->mux_timebase = ost->st->time_base; + + while (av_fifo_size(ost->muxing_queue)) { + AVPacket pkt; + av_fifo_generic_read(ost->muxing_queue, &pkt, sizeof(pkt), NULL); + write_packet(of, &pkt, ost, 1); + } + } + + return 0; +} + +static int init_output_bsfs(OutputStream *ost) +{ + AVBSFContext *ctx; + int i, ret; + + if (!ost->nb_bitstream_filters) + return 0; + + for (i = 0; i < ost->nb_bitstream_filters; i++) { + ctx = ost->bsf_ctx[i]; + + ret = avcodec_parameters_copy(ctx->par_in, + i ? ost->bsf_ctx[i - 1]->par_out : ost->st->codecpar); + if (ret < 0) + return ret; + + ctx->time_base_in = i ? ost->bsf_ctx[i - 1]->time_base_out : ost->st->time_base; + + ret = av_bsf_init(ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", + ost->bsf_ctx[i]->filter->name); + return ret; + } + } + + ctx = ost->bsf_ctx[ost->nb_bitstream_filters - 1]; + ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); + if (ret < 0) + return ret; + + ost->st->time_base = ctx->time_base_out; + + return 0; +} + +static int init_output_stream_streamcopy(OutputStream *ost) +{ + OutputFile *of = output_files[ost->file_index]; + InputStream *ist = get_input_stream(ost); + AVCodecParameters *par_dst = ost->st->codecpar; + AVCodecParameters *par_src = ost->ref_par; + AVRational sar; + int i, ret; + uint32_t codec_tag = par_dst->codec_tag; + + av_assert0(ist && !ost->filter); + + ret = avcodec_parameters_to_context(ost->enc_ctx, ist->st->codecpar); + if (ret >= 0) + ret = av_opt_set_dict(ost->enc_ctx, &ost->encoder_opts); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, + "Error setting up codec context options.\n"); + return ret; + } + avcodec_parameters_from_context(par_src, ost->enc_ctx); + + if (!codec_tag) { + unsigned int codec_tag_tmp; + if (!of->ctx->oformat->codec_tag || + av_codec_get_id (of->ctx->oformat->codec_tag, par_src->codec_tag) == par_src->codec_id || + !av_codec_get_tag2(of->ctx->oformat->codec_tag, par_src->codec_id, &codec_tag_tmp)) + codec_tag = par_src->codec_tag; + } + + ret = avcodec_parameters_copy(par_dst, par_src); + if (ret < 0) + return ret; + + par_dst->codec_tag = codec_tag; + + if (!ost->frame_rate.num) + ost->frame_rate = ist->framerate; + ost->st->avg_frame_rate = ost->frame_rate; + + ret = avformat_transfer_internal_stream_timing_info(of->ctx->oformat, ost->st, ist->st, copy_tb); + if (ret < 0) + return ret; + + // copy timebase while removing common factors + if (ost->st->time_base.num <= 0 || ost->st->time_base.den <= 0) + ost->st->time_base = av_add_q(av_stream_get_codec_timebase(ost->st), (AVRational){0, 1}); + + // copy estimated duration as a hint to the muxer + if (ost->st->duration <= 0 && ist->st->duration > 0) + ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); + + // copy disposition + ost->st->disposition = ist->st->disposition; + + if (ist->st->nb_side_data) { + for (i = 0; i < ist->st->nb_side_data; i++) { + const AVPacketSideData *sd_src = &ist->st->side_data[i]; + uint8_t *dst_data; + + dst_data = av_stream_new_side_data(ost->st, sd_src->type, sd_src->size); + if (!dst_data) + return AVERROR(ENOMEM); + memcpy(dst_data, sd_src->data, sd_src->size); + } + } + + if (ost->rotate_overridden) { + uint8_t *sd = av_stream_new_side_data(ost->st, AV_PKT_DATA_DISPLAYMATRIX, + sizeof(int32_t) * 9); + if (sd) + av_display_rotation_set((int32_t *)sd, -ost->rotate_override_value); + } + + ost->parser = av_parser_init(par_dst->codec_id); + ost->parser_avctx = avcodec_alloc_context3(NULL); + if (!ost->parser_avctx) + return AVERROR(ENOMEM); + + switch (par_dst->codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (audio_volume != 256) { + av_log(NULL, AV_LOG_FATAL, "-acodec copy and -vol are incompatible (frames are not decoded)\n"); + exit_program(1); + } + if((par_dst->block_align == 1 || par_dst->block_align == 1152 || par_dst->block_align == 576) && par_dst->codec_id == AV_CODEC_ID_MP3) + par_dst->block_align= 0; + if(par_dst->codec_id == AV_CODEC_ID_AC3) + par_dst->block_align= 0; + break; + case AVMEDIA_TYPE_VIDEO: + if (ost->frame_aspect_ratio.num) { // overridden by the -aspect cli option + sar = + av_mul_q(ost->frame_aspect_ratio, + (AVRational){ par_dst->height, par_dst->width }); + av_log(NULL, AV_LOG_WARNING, "Overriding aspect ratio " + "with stream copy may produce invalid files\n"); + } + else if (ist->st->sample_aspect_ratio.num) + sar = ist->st->sample_aspect_ratio; + else + sar = par_src->sample_aspect_ratio; + ost->st->sample_aspect_ratio = par_dst->sample_aspect_ratio = sar; + ost->st->avg_frame_rate = ist->st->avg_frame_rate; + ost->st->r_frame_rate = ist->st->r_frame_rate; + break; + } + + ost->mux_timebase = ist->st->time_base; + + return 0; +} + +static void set_encoder_id(OutputFile *of, OutputStream *ost) +{ + AVDictionaryEntry *e; + + uint8_t *encoder_string; + int encoder_string_len; + int format_flags = 0; + int codec_flags = 0; + + if (av_dict_get(ost->st->metadata, "encoder", NULL, 0)) + return; + + e = av_dict_get(of->opts, "fflags", NULL, 0); + if (e) { + const AVOption *o = av_opt_find(of->ctx, "fflags", NULL, 0, 0); + if (!o) + return; + av_opt_eval_flags(of->ctx, o, e->value, &format_flags); + } + e = av_dict_get(ost->encoder_opts, "flags", NULL, 0); + if (e) { + const AVOption *o = av_opt_find(ost->enc_ctx, "flags", NULL, 0, 0); + if (!o) + return; + av_opt_eval_flags(ost->enc_ctx, o, e->value, &codec_flags); + } + + encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(ost->enc->name) + 2; + encoder_string = av_mallocz(encoder_string_len); + if (!encoder_string) + exit_program(1); + + if (!(format_flags & AVFMT_FLAG_BITEXACT) && !(codec_flags & AV_CODEC_FLAG_BITEXACT)) + av_strlcpy(encoder_string, LIBAVCODEC_IDENT " ", encoder_string_len); + else + av_strlcpy(encoder_string, "Lavc ", encoder_string_len); + av_strlcat(encoder_string, ost->enc->name, encoder_string_len); + av_dict_set(&ost->st->metadata, "encoder", encoder_string, + AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE); +} + +static void parse_forced_key_frames(char *kf, OutputStream *ost, + AVCodecContext *avctx) +{ + char *p; + int n = 1, i, size, index = 0; + int64_t t, *pts; + + for (p = kf; *p; p++) + if (*p == ',') + n++; + size = n; + pts = av_malloc_array(size, sizeof(*pts)); + if (!pts) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate forced key frames array.\n"); + exit_program(1); + } + + p = kf; + for (i = 0; i < n; i++) { + char *next = strchr(p, ','); + + if (next) + *next++ = 0; + + if (!memcmp(p, "chapters", 8)) { + + AVFormatContext *avf = output_files[ost->file_index]->ctx; + int j; + + if (avf->nb_chapters > INT_MAX - size || + !(pts = av_realloc_f(pts, size += avf->nb_chapters - 1, + sizeof(*pts)))) { + av_log(NULL, AV_LOG_FATAL, + "Could not allocate forced key frames array.\n"); + exit_program(1); + } + t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; + t = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); + + for (j = 0; j < avf->nb_chapters; j++) { + AVChapter *c = avf->chapters[j]; + av_assert1(index < size); + pts[index++] = av_rescale_q(c->start, c->time_base, + avctx->time_base) + t; + } + + } else { + + t = parse_time_or_die("force_key_frames", p, 1); + av_assert1(index < size); + pts[index++] = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); + + } + + p = next; + } + + av_assert0(index == size); + qsort(pts, size, sizeof(*pts), compare_int64); + ost->forced_kf_count = size; + ost->forced_kf_pts = pts; +} + +static void init_encoder_time_base(OutputStream *ost, AVRational default_time_base) +{ + InputStream *ist = get_input_stream(ost); + AVCodecContext *enc_ctx = ost->enc_ctx; + AVFormatContext *oc; + + if (ost->enc_timebase.num > 0) { + enc_ctx->time_base = ost->enc_timebase; + return; + } + + if (ost->enc_timebase.num < 0) { + if (ist) { + enc_ctx->time_base = ist->st->time_base; + return; + } + + oc = output_files[ost->file_index]->ctx; + av_log(oc, AV_LOG_WARNING, "Input stream data not available, using default time base\n"); + } + + enc_ctx->time_base = default_time_base; +} + +static int init_output_stream_encode(OutputStream *ost) +{ + InputStream *ist = get_input_stream(ost); + AVCodecContext *enc_ctx = ost->enc_ctx; + AVCodecContext *dec_ctx = NULL; + AVFormatContext *oc = output_files[ost->file_index]->ctx; + int j, ret; + + set_encoder_id(output_files[ost->file_index], ost); + + // Muxers use AV_PKT_DATA_DISPLAYMATRIX to signal rotation. On the other + // hand, the legacy API makes demuxers set "rotate" metadata entries, + // which have to be filtered out to prevent leaking them to output files. + av_dict_set(&ost->st->metadata, "rotate", NULL, 0); + + if (ist) { + ost->st->disposition = ist->st->disposition; + + dec_ctx = ist->dec_ctx; + + enc_ctx->chroma_sample_location = dec_ctx->chroma_sample_location; + } else { + for (j = 0; j < oc->nb_streams; j++) { + AVStream *st = oc->streams[j]; + if (st != ost->st && st->codecpar->codec_type == ost->st->codecpar->codec_type) + break; + } + if (j == oc->nb_streams) + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + ost->st->disposition = AV_DISPOSITION_DEFAULT; + } + + if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { + if (!ost->frame_rate.num) + ost->frame_rate = av_buffersink_get_frame_rate(ost->filter->filter); + if (ist && !ost->frame_rate.num) + ost->frame_rate = ist->framerate; + if (ist && !ost->frame_rate.num) + ost->frame_rate = ist->st->r_frame_rate; + if (ist && !ost->frame_rate.num) { + ost->frame_rate = (AVRational){25, 1}; + av_log(NULL, AV_LOG_WARNING, + "No information " + "about the input framerate is available. Falling " + "back to a default value of 25fps for output stream #%d:%d. Use the -r option " + "if you want a different framerate.\n", + ost->file_index, ost->index); + } +// ost->frame_rate = ist->st->avg_frame_rate.num ? ist->st->avg_frame_rate : (AVRational){25, 1}; + if (ost->enc->supported_framerates && !ost->force_fps) { + int idx = av_find_nearest_q_idx(ost->frame_rate, ost->enc->supported_framerates); + ost->frame_rate = ost->enc->supported_framerates[idx]; + } + // reduce frame rate for mpeg4 to be within the spec limits + if (enc_ctx->codec_id == AV_CODEC_ID_MPEG4) { + av_reduce(&ost->frame_rate.num, &ost->frame_rate.den, + ost->frame_rate.num, ost->frame_rate.den, 65535); + } + } + + switch (enc_ctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: + enc_ctx->sample_fmt = av_buffersink_get_format(ost->filter->filter); + if (dec_ctx) + enc_ctx->bits_per_raw_sample = FFMIN(dec_ctx->bits_per_raw_sample, + av_get_bytes_per_sample(enc_ctx->sample_fmt) << 3); + enc_ctx->sample_rate = av_buffersink_get_sample_rate(ost->filter->filter); + enc_ctx->channel_layout = av_buffersink_get_channel_layout(ost->filter->filter); + enc_ctx->channels = av_buffersink_get_channels(ost->filter->filter); + + init_encoder_time_base(ost, av_make_q(1, enc_ctx->sample_rate)); + break; + + case AVMEDIA_TYPE_VIDEO: + init_encoder_time_base(ost, av_inv_q(ost->frame_rate)); + + if (!(enc_ctx->time_base.num && enc_ctx->time_base.den)) + enc_ctx->time_base = av_buffersink_get_time_base(ost->filter->filter); + if ( av_q2d(enc_ctx->time_base) < 0.001 && video_sync_method != VSYNC_PASSTHROUGH + && (video_sync_method == VSYNC_CFR || video_sync_method == VSYNC_VSCFR || (video_sync_method == VSYNC_AUTO && !(oc->oformat->flags & AVFMT_VARIABLE_FPS)))){ + av_log(oc, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" + "Please consider specifying a lower framerate, a different muxer or -vsync 2\n"); + } + for (j = 0; j < ost->forced_kf_count; j++) + ost->forced_kf_pts[j] = av_rescale_q(ost->forced_kf_pts[j], + AV_TIME_BASE_Q, + enc_ctx->time_base); + + enc_ctx->width = av_buffersink_get_w(ost->filter->filter); + enc_ctx->height = av_buffersink_get_h(ost->filter->filter); + enc_ctx->sample_aspect_ratio = ost->st->sample_aspect_ratio = + ost->frame_aspect_ratio.num ? // overridden by the -aspect cli option + av_mul_q(ost->frame_aspect_ratio, (AVRational){ enc_ctx->height, enc_ctx->width }) : + av_buffersink_get_sample_aspect_ratio(ost->filter->filter); + + enc_ctx->pix_fmt = av_buffersink_get_format(ost->filter->filter); + if (dec_ctx) + enc_ctx->bits_per_raw_sample = FFMIN(dec_ctx->bits_per_raw_sample, + av_pix_fmt_desc_get(enc_ctx->pix_fmt)->comp[0].depth); + + enc_ctx->framerate = ost->frame_rate; + + ost->st->avg_frame_rate = ost->frame_rate; + + if (!dec_ctx || + enc_ctx->width != dec_ctx->width || + enc_ctx->height != dec_ctx->height || + enc_ctx->pix_fmt != dec_ctx->pix_fmt) { + enc_ctx->bits_per_raw_sample = frame_bits_per_raw_sample; + } + + if (ost->forced_keyframes) { + if (!strncmp(ost->forced_keyframes, "expr:", 5)) { + ret = av_expr_parse(&ost->forced_keyframes_pexpr, ost->forced_keyframes+5, + forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Invalid force_key_frames expression '%s'\n", ost->forced_keyframes+5); + return ret; + } + ost->forced_keyframes_expr_const_values[FKF_N] = 0; + ost->forced_keyframes_expr_const_values[FKF_N_FORCED] = 0; + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = NAN; + ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = NAN; + + // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', + // parse it only for static kf timings + } else if(strncmp(ost->forced_keyframes, "source", 6)) { + parse_forced_key_frames(ost->forced_keyframes, ost, ost->enc_ctx); + } + } + break; + case AVMEDIA_TYPE_SUBTITLE: + enc_ctx->time_base = AV_TIME_BASE_Q; + if (!enc_ctx->width) { + enc_ctx->width = input_streams[ost->source_index]->st->codecpar->width; + enc_ctx->height = input_streams[ost->source_index]->st->codecpar->height; + } + break; + case AVMEDIA_TYPE_DATA: + break; + default: + abort(); + break; + } + + ost->mux_timebase = enc_ctx->time_base; + + return 0; +} + +static int init_output_stream(OutputStream *ost, char *error, int error_len) +{ + int ret = 0; + + if (ost->encoding_needed) { + AVCodec *codec = ost->enc; + AVCodecContext *dec = NULL; + InputStream *ist; + + ret = init_output_stream_encode(ost); + if (ret < 0) + return ret; + + if ((ist = get_input_stream(ost))) + dec = ist->dec_ctx; + if (dec && dec->subtitle_header) { + /* ASS code assumes this buffer is null terminated so add extra byte. */ + ost->enc_ctx->subtitle_header = av_mallocz(dec->subtitle_header_size + 1); + if (!ost->enc_ctx->subtitle_header) + return AVERROR(ENOMEM); + memcpy(ost->enc_ctx->subtitle_header, dec->subtitle_header, dec->subtitle_header_size); + ost->enc_ctx->subtitle_header_size = dec->subtitle_header_size; + } + if (!av_dict_get(ost->encoder_opts, "threads", NULL, 0)) + av_dict_set(&ost->encoder_opts, "threads", "auto", 0); + if (ost->enc->type == AVMEDIA_TYPE_AUDIO && + !codec->defaults && + !av_dict_get(ost->encoder_opts, "b", NULL, 0) && + !av_dict_get(ost->encoder_opts, "ab", NULL, 0)) + av_dict_set(&ost->encoder_opts, "b", "128000", 0); + + if (ost->filter && av_buffersink_get_hw_frames_ctx(ost->filter->filter) && + ((AVHWFramesContext*)av_buffersink_get_hw_frames_ctx(ost->filter->filter)->data)->format == + av_buffersink_get_format(ost->filter->filter)) { + ost->enc_ctx->hw_frames_ctx = av_buffer_ref(av_buffersink_get_hw_frames_ctx(ost->filter->filter)); + if (!ost->enc_ctx->hw_frames_ctx) + return AVERROR(ENOMEM); + } else { + ret = hw_device_setup_for_encode(ost); + if (ret < 0) { + snprintf(error, error_len, "Device setup failed for " + "encoder on output stream #%d:%d : %s", + ost->file_index, ost->index, av_err2str(ret)); + return ret; + } + } + + if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0) { + if (ret == AVERROR_EXPERIMENTAL) + abort_codec_experimental(codec, 1); + snprintf(error, error_len, + "Error while opening encoder for output stream #%d:%d - " + "maybe incorrect parameters such as bit_rate, rate, width or height", + ost->file_index, ost->index); + return ret; + } + if (ost->enc->type == AVMEDIA_TYPE_AUDIO && + !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + av_buffersink_set_frame_size(ost->filter->filter, + ost->enc_ctx->frame_size); + assert_avoptions(ost->encoder_opts); + if (ost->enc_ctx->bit_rate && ost->enc_ctx->bit_rate < 1000) + av_log(NULL, AV_LOG_WARNING, "The bitrate parameter is set too low." + " It takes bits/s as argument, not kbits/s\n"); + + ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, + "Error initializing the output stream codec context.\n"); + exit_program(1); + } + /* + * FIXME: ost->st->codec should't be needed here anymore. + */ + ret = avcodec_copy_context(ost->st->codec, ost->enc_ctx); + if (ret < 0) + return ret; + + if (ost->enc_ctx->nb_coded_side_data) { + int i; + + for (i = 0; i < ost->enc_ctx->nb_coded_side_data; i++) { + const AVPacketSideData *sd_src = &ost->enc_ctx->coded_side_data[i]; + uint8_t *dst_data; + + dst_data = av_stream_new_side_data(ost->st, sd_src->type, sd_src->size); + if (!dst_data) + return AVERROR(ENOMEM); + memcpy(dst_data, sd_src->data, sd_src->size); + } + } + + /* + * Add global input side data. For now this is naive, and copies it + * from the input stream's global side data. All side data should + * really be funneled over AVFrame and libavfilter, then added back to + * packet side data, and then potentially using the first packet for + * global side data. + */ + if (ist) { + int i; + for (i = 0; i < ist->st->nb_side_data; i++) { + AVPacketSideData *sd = &ist->st->side_data[i]; + uint8_t *dst = av_stream_new_side_data(ost->st, sd->type, sd->size); + if (!dst) + return AVERROR(ENOMEM); + memcpy(dst, sd->data, sd->size); + if (ist->autorotate && sd->type == AV_PKT_DATA_DISPLAYMATRIX) + av_display_rotation_set((uint32_t *)dst, 0); + } + } + + // copy timebase while removing common factors + if (ost->st->time_base.num <= 0 || ost->st->time_base.den <= 0) + ost->st->time_base = av_add_q(ost->enc_ctx->time_base, (AVRational){0, 1}); + + // copy estimated duration as a hint to the muxer + if (ost->st->duration <= 0 && ist && ist->st->duration > 0) + ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); + + ost->st->codec->codec= ost->enc_ctx->codec; + } else if (ost->stream_copy) { + ret = init_output_stream_streamcopy(ost); + if (ret < 0) + return ret; + + /* + * FIXME: will the codec context used by the parser during streamcopy + * This should go away with the new parser API. + */ + ret = avcodec_parameters_to_context(ost->parser_avctx, ost->st->codecpar); + if (ret < 0) + return ret; + } + + // parse user provided disposition, and update stream values + if (ost->disposition) { + static const AVOption opts[] = { + { "disposition" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, INT64_MAX, .unit = "flags" }, + { "default" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DEFAULT }, .unit = "flags" }, + { "dub" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DUB }, .unit = "flags" }, + { "original" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_ORIGINAL }, .unit = "flags" }, + { "comment" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_COMMENT }, .unit = "flags" }, + { "lyrics" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_LYRICS }, .unit = "flags" }, + { "karaoke" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_KARAOKE }, .unit = "flags" }, + { "forced" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_FORCED }, .unit = "flags" }, + { "hearing_impaired" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_HEARING_IMPAIRED }, .unit = "flags" }, + { "visual_impaired" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_VISUAL_IMPAIRED }, .unit = "flags" }, + { "clean_effects" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_CLEAN_EFFECTS }, .unit = "flags" }, + { "captions" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_CAPTIONS }, .unit = "flags" }, + { "descriptions" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_DESCRIPTIONS }, .unit = "flags" }, + { "metadata" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_DISPOSITION_METADATA }, .unit = "flags" }, + { NULL }, + }; + static const AVClass class = { + .class_name = "", + .item_name = av_default_item_name, + .option = opts, + .version = LIBAVUTIL_VERSION_INT, + }; + const AVClass *pclass = &class; + + ret = av_opt_eval_flags(&pclass, &opts[0], ost->disposition, &ost->st->disposition); + if (ret < 0) + return ret; + } + + /* initialize bitstream filters for the output stream + * needs to be done here, because the codec id for streamcopy is not + * known until now */ + ret = init_output_bsfs(ost); + if (ret < 0) + return ret; + + ost->initialized = 1; + + ret = check_init_output_file(output_files[ost->file_index], ost->file_index); + if (ret < 0) + return ret; + + return ret; +} + +static void report_new_stream(int input_index, AVPacket *pkt) +{ + InputFile *file = input_files[input_index]; + AVStream *st = file->ctx->streams[pkt->stream_index]; + + if (pkt->stream_index < file->nb_streams_warn) + return; + av_log(file->ctx, AV_LOG_WARNING, + "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", + av_get_media_type_string(st->codecpar->codec_type), + input_index, pkt->stream_index, + pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); + file->nb_streams_warn = pkt->stream_index + 1; +} + +static int transcode_init(void) +{ + int ret = 0, i, j, k; + AVFormatContext *oc; + OutputStream *ost; + InputStream *ist; + char error[1024] = {0}; + + for (i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + for (j = 0; j < fg->nb_outputs; j++) { + OutputFilter *ofilter = fg->outputs[j]; + if (!ofilter->ost || ofilter->ost->source_index >= 0) + continue; + if (fg->nb_inputs != 1) + continue; + for (k = nb_input_streams-1; k >= 0 ; k--) + if (fg->inputs[0]->ist == input_streams[k]) + break; + ofilter->ost->source_index = k; + } + } + + /* init framerate emulation */ + for (i = 0; i < nb_input_files; i++) { + InputFile *ifile = input_files[i]; + if (ifile->rate_emu) + for (j = 0; j < ifile->nb_streams; j++) + input_streams[j + ifile->ist_index]->start = av_gettime_relative(); + } + + /* init input streams */ + for (i = 0; i < nb_input_streams; i++) + if ((ret = init_input_stream(i, error, sizeof(error))) < 0) { + for (i = 0; i < nb_output_streams; i++) { + ost = output_streams[i]; + avcodec_close(ost->enc_ctx); + } + goto dump_format; + } + + /* open each encoder */ + for (i = 0; i < nb_output_streams; i++) { + // skip streams fed from filtergraphs until we have a frame for them + if (output_streams[i]->filter) + continue; + + ret = init_output_stream(output_streams[i], error, sizeof(error)); + if (ret < 0) + goto dump_format; + } + + /* discard unused programs */ + for (i = 0; i < nb_input_files; i++) { + InputFile *ifile = input_files[i]; + for (j = 0; j < ifile->ctx->nb_programs; j++) { + AVProgram *p = ifile->ctx->programs[j]; + int discard = AVDISCARD_ALL; + + for (k = 0; k < p->nb_stream_indexes; k++) + if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) { + discard = AVDISCARD_DEFAULT; + break; + } + p->discard = discard; + } + } + + /* write headers for files with no streams */ + for (i = 0; i < nb_output_files; i++) { + oc = output_files[i]->ctx; + if (oc->oformat->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { + ret = check_init_output_file(output_files[i], i); + if (ret < 0) + goto dump_format; + } + } + + dump_format: + /* dump the stream mapping */ + av_log(NULL, AV_LOG_INFO, "Stream mapping:\n"); + for (i = 0; i < nb_input_streams; i++) { + ist = input_streams[i]; + + for (j = 0; j < ist->nb_filters; j++) { + if (!filtergraph_is_simple(ist->filters[j]->graph)) { + av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s", + ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?", + ist->filters[j]->name); + if (nb_filtergraphs > 1) + av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index); + av_log(NULL, AV_LOG_INFO, "\n"); + } + } + } + + for (i = 0; i < nb_output_streams; i++) { + ost = output_streams[i]; + + if (ost->attachment_filename) { + /* an attached file */ + av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n", + ost->attachment_filename, ost->file_index, ost->index); + continue; + } + + if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) { + /* output from a complex graph */ + av_log(NULL, AV_LOG_INFO, " %s", ost->filter->name); + if (nb_filtergraphs > 1) + av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index); + + av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index, + ost->index, ost->enc ? ost->enc->name : "?"); + continue; + } + + av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d", + input_streams[ost->source_index]->file_index, + input_streams[ost->source_index]->st->index, + ost->file_index, + ost->index); + if (ost->sync_ist != input_streams[ost->source_index]) + av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]", + ost->sync_ist->file_index, + ost->sync_ist->st->index); + if (ost->stream_copy) + av_log(NULL, AV_LOG_INFO, " (copy)"); + else { + const AVCodec *in_codec = input_streams[ost->source_index]->dec; + const AVCodec *out_codec = ost->enc; + const char *decoder_name = "?"; + const char *in_codec_name = "?"; + const char *encoder_name = "?"; + const char *out_codec_name = "?"; + const AVCodecDescriptor *desc; + + if (in_codec) { + decoder_name = in_codec->name; + desc = avcodec_descriptor_get(in_codec->id); + if (desc) + in_codec_name = desc->name; + if (!strcmp(decoder_name, in_codec_name)) + decoder_name = "native"; + } + + if (out_codec) { + encoder_name = out_codec->name; + desc = avcodec_descriptor_get(out_codec->id); + if (desc) + out_codec_name = desc->name; + if (!strcmp(encoder_name, out_codec_name)) + encoder_name = "native"; + } + + av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))", + in_codec_name, decoder_name, + out_codec_name, encoder_name); + } + av_log(NULL, AV_LOG_INFO, "\n"); + } + + if (ret) { + av_log(NULL, AV_LOG_ERROR, "%s\n", error); + return ret; + } + + atomic_store(&transcode_init_done, 1); + + return 0; +} + +/* Return 1 if there remain streams where more output is wanted, 0 otherwise. */ +static int need_output(void) +{ + int i; + + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + OutputFile *of = output_files[ost->file_index]; + AVFormatContext *os = output_files[ost->file_index]->ctx; + + if (ost->finished || + (os->pb && avio_tell(os->pb) >= of->limit_filesize)) + continue; + if (ost->frame_number >= ost->max_frames) { + int j; + for (j = 0; j < of->ctx->nb_streams; j++) + close_output_stream(output_streams[of->ost_index + j]); + continue; + } + + return 1; + } + + return 0; +} + +/** + * Select the output stream to process. + * + * @return selected output stream, or NULL if none available + */ +static OutputStream *choose_output(void) +{ + int i; + int64_t opts_min = INT64_MAX; + OutputStream *ost_min = NULL; + + for (i = 0; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + int64_t opts = ost->st->cur_dts == AV_NOPTS_VALUE ? INT64_MIN : + av_rescale_q(ost->st->cur_dts, ost->st->time_base, + AV_TIME_BASE_Q); + if (ost->st->cur_dts == AV_NOPTS_VALUE) + av_log(NULL, AV_LOG_DEBUG, "cur_dts is invalid (this is harmless if it occurs once at the start per stream)\n"); + + if (!ost->initialized && !ost->inputs_done) + return ost; + + if (!ost->finished && opts < opts_min) { + opts_min = opts; + ost_min = ost->unavailable ? NULL : ost; + } + } + return ost_min; +} + +static void set_tty_echo(int on) +{ +#if HAVE_TERMIOS_H + struct termios tty; + if (tcgetattr(0, &tty) == 0) { + if (on) tty.c_lflag |= ECHO; + else tty.c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, &tty); + } +#endif +} + +static int check_keyboard_interaction(int64_t cur_time) +{ + int i, ret, key; + static int64_t last_time; + if (received_nb_signals) + return AVERROR_EXIT; + /* read_key() returns 0 on EOF */ + if(cur_time - last_time >= 100000 && !run_as_daemon){ + key = read_key(); + last_time = cur_time; + }else + key = -1; + if (key == 'q') + return AVERROR_EXIT; + if (key == '+') av_log_set_level(av_log_get_level()+10); + if (key == '-') av_log_set_level(av_log_get_level()-10); + if (key == 's') qp_hist ^= 1; + if (key == 'h'){ + if (do_hex_dump){ + do_hex_dump = do_pkt_dump = 0; + } else if(do_pkt_dump){ + do_hex_dump = 1; + } else + do_pkt_dump = 1; + av_log_set_level(AV_LOG_DEBUG); + } + if (key == 'c' || key == 'C'){ + char buf[4096], target[64], command[256], arg[256] = {0}; + double time; + int k, n = 0; + fprintf(stderr, "\nEnter command: <target>|all <time>|-1 <command>[ <argument>]\n"); + i = 0; + set_tty_echo(1); + while ((k = read_key()) != '\n' && k != '\r' && i < sizeof(buf)-1) + if (k > 0) + buf[i++] = k; + buf[i] = 0; + set_tty_echo(0); + fprintf(stderr, "\n"); + if (k > 0 && + (n = sscanf(buf, "%63[^ ] %lf %255[^ ] %255[^\n]", target, &time, command, arg)) >= 3) { + av_log(NULL, AV_LOG_DEBUG, "Processing command target:%s time:%f command:%s arg:%s", + target, time, command, arg); + for (i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + if (fg->graph) { + if (time < 0) { + ret = avfilter_graph_send_command(fg->graph, target, command, arg, buf, sizeof(buf), + key == 'c' ? AVFILTER_CMD_FLAG_ONE : 0); + fprintf(stderr, "Command reply for stream %d: ret:%d res:\n%s", i, ret, buf); + } else if (key == 'c') { + fprintf(stderr, "Queuing commands only on filters supporting the specific command is unsupported\n"); + ret = AVERROR_PATCHWELCOME; + } else { + ret = avfilter_graph_queue_command(fg->graph, target, command, arg, 0, time); + if (ret < 0) + fprintf(stderr, "Queuing command failed with error %s\n", av_err2str(ret)); + } + } + } + } else { + av_log(NULL, AV_LOG_ERROR, + "Parse error, at least 3 arguments were expected, " + "only %d given in string '%s'\n", n, buf); + } + } + if (key == 'd' || key == 'D'){ + int debug=0; + if(key == 'D') { + debug = input_streams[0]->st->codec->debug<<1; + if(!debug) debug = 1; + while(debug & (FF_DEBUG_DCT_COEFF +#if FF_API_DEBUG_MV + |FF_DEBUG_VIS_QP|FF_DEBUG_VIS_MB_TYPE +#endif + )) //unsupported, would just crash + debug += debug; + }else{ + char buf[32]; + int k = 0; + i = 0; + set_tty_echo(1); + while ((k = read_key()) != '\n' && k != '\r' && i < sizeof(buf)-1) + if (k > 0) + buf[i++] = k; + buf[i] = 0; + set_tty_echo(0); + fprintf(stderr, "\n"); + if (k <= 0 || sscanf(buf, "%d", &debug)!=1) + fprintf(stderr,"error parsing debug value\n"); + } + for(i=0;i<nb_input_streams;i++) { + input_streams[i]->st->codec->debug = debug; + } + for(i=0;i<nb_output_streams;i++) { + OutputStream *ost = output_streams[i]; + ost->enc_ctx->debug = debug; + } + if(debug) av_log_set_level(AV_LOG_DEBUG); + fprintf(stderr,"debug=%d\n", debug); + } + if (key == '?'){ + fprintf(stderr, "key function\n" + "? show this help\n" + "+ increase verbosity\n" + "- decrease verbosity\n" + "c Send command to first matching filter supporting it\n" + "C Send/Queue command to all matching filters\n" + "D cycle through available debug modes\n" + "h dump packets/hex press to cycle through the 3 states\n" + "q quit\n" + "s Show QP histogram\n" + ); + } + return 0; +} + +#if HAVE_PTHREADS +static void *input_thread(void *arg) +{ + InputFile *f = arg; + unsigned flags = f->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; + int ret = 0; + + while (1) { + AVPacket pkt; + ret = av_read_frame(f->ctx, &pkt); + + if (ret == AVERROR(EAGAIN)) { + av_usleep(10000); + continue; + } + if (ret < 0) { + av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); + break; + } + ret = av_thread_message_queue_send(f->in_thread_queue, &pkt, flags); + if (flags && ret == AVERROR(EAGAIN)) { + flags = 0; + ret = av_thread_message_queue_send(f->in_thread_queue, &pkt, flags); + av_log(f->ctx, AV_LOG_WARNING, + "Thread message queue blocking; consider raising the " + "thread_queue_size option (current value: %d)\n", + f->thread_queue_size); + } + if (ret < 0) { + if (ret != AVERROR_EOF) + av_log(f->ctx, AV_LOG_ERROR, + "Unable to send packet to main thread: %s\n", + av_err2str(ret)); + av_packet_unref(&pkt); + av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); + break; + } + } + + return NULL; +} + +static void free_input_threads(void) +{ + int i; + + for (i = 0; i < nb_input_files; i++) { + InputFile *f = input_files[i]; + AVPacket pkt; + + if (!f || !f->in_thread_queue) + continue; + av_thread_message_queue_set_err_send(f->in_thread_queue, AVERROR_EOF); + while (av_thread_message_queue_recv(f->in_thread_queue, &pkt, 0) >= 0) + av_packet_unref(&pkt); + + pthread_join(f->thread, NULL); + f->joined = 1; + av_thread_message_queue_free(&f->in_thread_queue); + } +} + +static int init_input_threads(void) +{ + int i, ret; + + if (nb_input_files == 1) + return 0; + + for (i = 0; i < nb_input_files; i++) { + InputFile *f = input_files[i]; + + if (f->ctx->pb ? !f->ctx->pb->seekable : + strcmp(f->ctx->iformat->name, "lavfi")) + f->non_blocking = 1; + ret = av_thread_message_queue_alloc(&f->in_thread_queue, + f->thread_queue_size, sizeof(AVPacket)); + if (ret < 0) + return ret; + + if ((ret = pthread_create(&f->thread, NULL, input_thread, f))) { + av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); + av_thread_message_queue_free(&f->in_thread_queue); + return AVERROR(ret); + } + } + return 0; +} + +static int get_input_packet_mt(InputFile *f, AVPacket *pkt) +{ + return av_thread_message_queue_recv(f->in_thread_queue, pkt, + f->non_blocking ? + AV_THREAD_MESSAGE_NONBLOCK : 0); +} +#endif + +static int get_input_packet(InputFile *f, AVPacket *pkt) +{ + if (f->rate_emu) { + int i; + for (i = 0; i < f->nb_streams; i++) { + InputStream *ist = input_streams[f->ist_index + i]; + int64_t pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); + int64_t now = av_gettime_relative() - ist->start; + if (pts > now) + return AVERROR(EAGAIN); + } + } + +#if HAVE_PTHREADS + if (nb_input_files > 1) + return get_input_packet_mt(f, pkt); +#endif + return av_read_frame(f->ctx, pkt); +} + +static int got_eagain(void) +{ + int i; + for (i = 0; i < nb_output_streams; i++) + if (output_streams[i]->unavailable) + return 1; + return 0; +} + +static void reset_eagain(void) +{ + int i; + for (i = 0; i < nb_input_files; i++) + input_files[i]->eagain = 0; + for (i = 0; i < nb_output_streams; i++) + output_streams[i]->unavailable = 0; +} + +// set duration to max(tmp, duration) in a proper time base and return duration's time_base +static AVRational duration_max(int64_t tmp, int64_t *duration, AVRational tmp_time_base, + AVRational time_base) +{ + int ret; + + if (!*duration) { + *duration = tmp; + return tmp_time_base; + } + + ret = av_compare_ts(*duration, time_base, tmp, tmp_time_base); + if (ret < 0) { + *duration = tmp; + return tmp_time_base; + } + + return time_base; +} + +static int seek_to_start(InputFile *ifile, AVFormatContext *is) +{ + InputStream *ist; + AVCodecContext *avctx; + int i, ret, has_audio = 0; + int64_t duration = 0; + + ret = av_seek_frame(is, -1, is->start_time, 0); + if (ret < 0) + return ret; + + for (i = 0; i < ifile->nb_streams; i++) { + ist = input_streams[ifile->ist_index + i]; + avctx = ist->dec_ctx; + + // flush decoders + if (ist->decoding_needed) { + process_input_packet(ist, NULL, 1); + avcodec_flush_buffers(avctx); + } + + /* duration is the length of the last frame in a stream + * when audio stream is present we don't care about + * last video frame length because it's not defined exactly */ + if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) + has_audio = 1; + } + + for (i = 0; i < ifile->nb_streams; i++) { + ist = input_streams[ifile->ist_index + i]; + avctx = ist->dec_ctx; + + if (has_audio) { + if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) { + AVRational sample_rate = {1, avctx->sample_rate}; + + duration = av_rescale_q(ist->nb_samples, sample_rate, ist->st->time_base); + } else + continue; + } else { + if (ist->framerate.num) { + duration = av_rescale_q(1, ist->framerate, ist->st->time_base); + } else if (ist->st->avg_frame_rate.num) { + duration = av_rescale_q(1, ist->st->avg_frame_rate, ist->st->time_base); + } else duration = 1; + } + if (!ifile->duration) + ifile->time_base = ist->st->time_base; + /* the total duration of the stream, max_pts - min_pts is + * the duration of the stream without the last frame */ + duration += ist->max_pts - ist->min_pts; + ifile->time_base = duration_max(duration, &ifile->duration, ist->st->time_base, + ifile->time_base); + } + + if (ifile->loop > 0) + ifile->loop--; + + return ret; +} + +/* + * Return + * - 0 -- one packet was read and processed + * - AVERROR(EAGAIN) -- no packets were available for selected file, + * this function should be called again + * - AVERROR_EOF -- this function should not be called again + */ +static int process_input(int file_index) +{ + InputFile *ifile = input_files[file_index]; + AVFormatContext *is; + InputStream *ist; + AVPacket pkt; + int ret, i, j; + int64_t duration; + int64_t pkt_dts; + + is = ifile->ctx; + ret = get_input_packet(ifile, &pkt); + + if (ret == AVERROR(EAGAIN)) { + ifile->eagain = 1; + return ret; + } + if (ret < 0 && ifile->loop) { + if ((ret = seek_to_start(ifile, is)) < 0) + return ret; + ret = get_input_packet(ifile, &pkt); + if (ret == AVERROR(EAGAIN)) { + ifile->eagain = 1; + return ret; + } + } + if (ret < 0) { + if (ret != AVERROR_EOF) { + print_error(is->filename, ret); + if (exit_on_error) + exit_program(1); + } + + for (i = 0; i < ifile->nb_streams; i++) { + ist = input_streams[ifile->ist_index + i]; + if (ist->decoding_needed) { + ret = process_input_packet(ist, NULL, 0); + if (ret>0) + return 0; + } + + /* mark all outputs that don't go through lavfi as finished */ + for (j = 0; j < nb_output_streams; j++) { + OutputStream *ost = output_streams[j]; + + if (ost->source_index == ifile->ist_index + i && + (ost->stream_copy || ost->enc->type == AVMEDIA_TYPE_SUBTITLE)) + finish_output_stream(ost); + } + } + + ifile->eof_reached = 1; + return AVERROR(EAGAIN); + } + + reset_eagain(); + + if (do_pkt_dump) { + av_pkt_dump_log2(NULL, AV_LOG_INFO, &pkt, do_hex_dump, + is->streams[pkt.stream_index]); + } + /* the following test is needed in case new streams appear + dynamically in stream : we ignore them */ + if (pkt.stream_index >= ifile->nb_streams) { + report_new_stream(file_index, &pkt); + goto discard_packet; + } + + ist = input_streams[ifile->ist_index + pkt.stream_index]; + + ist->data_size += pkt.size; + ist->nb_packets++; + + if (ist->discard) + goto discard_packet; + + if (exit_on_error && (pkt.flags & AV_PKT_FLAG_CORRUPT)) { + av_log(NULL, AV_LOG_FATAL, "%s: corrupt input packet in stream %d\n", is->filename, pkt.stream_index); + exit_program(1); + } + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d type:%s " + "next_dts:%s next_dts_time:%s next_pts:%s next_pts_time:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s off:%s off_time:%s\n", + ifile->ist_index + pkt.stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), + av_ts2str(ist->next_dts), av_ts2timestr(ist->next_dts, &AV_TIME_BASE_Q), + av_ts2str(ist->next_pts), av_ts2timestr(ist->next_pts, &AV_TIME_BASE_Q), + av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ist->st->time_base), + av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ist->st->time_base), + av_ts2str(input_files[ist->file_index]->ts_offset), + av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q)); + } + + if(!ist->wrap_correction_done && is->start_time != AV_NOPTS_VALUE && ist->st->pts_wrap_bits < 64){ + int64_t stime, stime2; + // Correcting starttime based on the enabled streams + // FIXME this ideally should be done before the first use of starttime but we do not know which are the enabled streams at that point. + // so we instead do it here as part of discontinuity handling + if ( ist->next_dts == AV_NOPTS_VALUE + && ifile->ts_offset == -is->start_time + && (is->iformat->flags & AVFMT_TS_DISCONT)) { + int64_t new_start_time = INT64_MAX; + for (i=0; i<is->nb_streams; i++) { + AVStream *st = is->streams[i]; + if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) + continue; + new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); + } + if (new_start_time > is->start_time) { + av_log(is, AV_LOG_VERBOSE, "Correcting start time by %"PRId64"\n", new_start_time - is->start_time); + ifile->ts_offset = -new_start_time; + } + } + + stime = av_rescale_q(is->start_time, AV_TIME_BASE_Q, ist->st->time_base); + stime2= stime + (1ULL<<ist->st->pts_wrap_bits); + ist->wrap_correction_done = 1; + + if(stime2 > stime && pkt.dts != AV_NOPTS_VALUE && pkt.dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt.dts -= 1ULL<<ist->st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + if(stime2 > stime && pkt.pts != AV_NOPTS_VALUE && pkt.pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt.pts -= 1ULL<<ist->st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + } + + /* add the stream-global side data to the first packet */ + if (ist->nb_packets == 1) { + for (i = 0; i < ist->st->nb_side_data; i++) { + AVPacketSideData *src_sd = &ist->st->side_data[i]; + uint8_t *dst_data; + + if (src_sd->type == AV_PKT_DATA_DISPLAYMATRIX) + continue; + + if (av_packet_get_side_data(&pkt, src_sd->type, NULL)) + continue; + + dst_data = av_packet_new_side_data(&pkt, src_sd->type, src_sd->size); + if (!dst_data) + exit_program(1); + + memcpy(dst_data, src_sd->data, src_sd->size); + } + } + + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts *= ist->ts_scale; + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts *= ist->ts_scale; + + pkt_dts = av_rescale_q_rnd(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || + ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt_dts != AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts + && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE) { + int64_t delta = pkt_dts - ifile->last_ts; + if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || + delta > 1LL*dts_delta_threshold*AV_TIME_BASE){ + ifile->ts_offset -= delta; + av_log(NULL, AV_LOG_DEBUG, + "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", + delta, ifile->ts_offset); + pkt.dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + } + } + + duration = av_rescale_q(ifile->duration, ifile->time_base, ist->st->time_base); + if (pkt.pts != AV_NOPTS_VALUE) { + pkt.pts += duration; + ist->max_pts = FFMAX(pkt.pts, ist->max_pts); + ist->min_pts = FFMIN(pkt.pts, ist->min_pts); + } + + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts += duration; + + pkt_dts = av_rescale_q_rnd(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || + ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && + !copy_ts) { + int64_t delta = pkt_dts - ist->next_dts; + if (is->iformat->flags & AVFMT_TS_DISCONT) { + if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || + delta > 1LL*dts_delta_threshold*AV_TIME_BASE || + pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { + ifile->ts_offset -= delta; + av_log(NULL, AV_LOG_DEBUG, + "timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", + delta, ifile->ts_offset); + pkt.dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + } + } else { + if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || + delta > 1LL*dts_error_threshold*AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt.dts, ist->next_dts, pkt.stream_index); + pkt.dts = AV_NOPTS_VALUE; + } + if (pkt.pts != AV_NOPTS_VALUE){ + int64_t pkt_pts = av_rescale_q(pkt.pts, ist->st->time_base, AV_TIME_BASE_Q); + delta = pkt_pts - ist->next_dts; + if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || + delta > 1LL*dts_error_threshold*AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt.pts, ist->next_dts, pkt.stream_index); + pkt.pts = AV_NOPTS_VALUE; + } + } + } + } + + if (pkt.dts != AV_NOPTS_VALUE) + ifile->last_ts = av_rescale_q(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q); + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s off:%s off_time:%s\n", + ifile->ist_index + pkt.stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), + av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ist->st->time_base), + av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ist->st->time_base), + av_ts2str(input_files[ist->file_index]->ts_offset), + av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q)); + } + + sub2video_heartbeat(ist, pkt.pts); + + process_input_packet(ist, &pkt, 0); + +discard_packet: + av_packet_unref(&pkt); + + return 0; +} + +/** + * Perform a step of transcoding for the specified filter graph. + * + * @param[in] graph filter graph to consider + * @param[out] best_ist input stream where a frame would allow to continue + * @return 0 for success, <0 for error + */ +static int transcode_from_filter(FilterGraph *graph, InputStream **best_ist) +{ + int i, ret; + int nb_requests, nb_requests_max = 0; + InputFilter *ifilter; + InputStream *ist; + + *best_ist = NULL; + ret = avfilter_graph_request_oldest(graph->graph); + if (ret >= 0) + return reap_filters(0); + + if (ret == AVERROR_EOF) { + ret = reap_filters(1); + for (i = 0; i < graph->nb_outputs; i++) + close_output_stream(graph->outputs[i]->ost); + return ret; + } + if (ret != AVERROR(EAGAIN)) + return ret; + + for (i = 0; i < graph->nb_inputs; i++) { + ifilter = graph->inputs[i]; + ist = ifilter->ist; + if (input_files[ist->file_index]->eagain || + input_files[ist->file_index]->eof_reached) + continue; + nb_requests = av_buffersrc_get_nb_failed_requests(ifilter->filter); + if (nb_requests > nb_requests_max) { + nb_requests_max = nb_requests; + *best_ist = ist; + } + } + + if (!*best_ist) + for (i = 0; i < graph->nb_outputs; i++) + graph->outputs[i]->ost->unavailable = 1; + + return 0; +} + +/** + * Run a single step of transcoding. + * + * @return 0 for success, <0 for error + */ +static int transcode_step(void) +{ + OutputStream *ost; + InputStream *ist = NULL; + int ret; + + ost = choose_output(); + if (!ost) { + if (got_eagain()) { + reset_eagain(); + av_usleep(10000); + return 0; + } + av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n"); + return AVERROR_EOF; + } + + if (ost->filter && !ost->filter->graph->graph) { + if (ifilter_has_all_input_formats(ost->filter->graph)) { + ret = configure_filtergraph(ost->filter->graph); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n"); + return ret; + } + } + } + + if (ost->filter && ost->filter->graph->graph) { + if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 0) + return ret; + if (!ist) + return 0; + } else if (ost->filter) { + int i; + for (i = 0; i < ost->filter->graph->nb_inputs; i++) { + InputFilter *ifilter = ost->filter->graph->inputs[i]; + if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) { + ist = ifilter->ist; + break; + } + } + if (!ist) { + ost->inputs_done = 1; + return 0; + } + } else { + av_assert0(ost->source_index >= 0); + ist = input_streams[ost->source_index]; + } + + ret = process_input(ist->file_index); + if (ret == AVERROR(EAGAIN)) { + if (input_files[ist->file_index]->eagain) + ost->unavailable = 1; + return 0; + } + + if (ret < 0) + return ret == AVERROR_EOF ? 0 : ret; + + return reap_filters(0); +} + +/* + * The following code is the main loop of the file converter + */ +static int transcode(void) +{ + int ret, i; + AVFormatContext *os; + OutputStream *ost; + InputStream *ist; + int64_t timer_start; + int64_t total_packets_written = 0; + + ret = transcode_init(); + if (ret < 0) + goto fail; + + if (stdin_interaction) { + av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n"); + } + + timer_start = av_gettime_relative(); + +#if HAVE_PTHREADS + if ((ret = init_input_threads()) < 0) + goto fail; +#endif + + while (!received_sigterm) { + int64_t cur_time= av_gettime_relative(); + + /* if 'q' pressed, exits */ + if (stdin_interaction) + if (check_keyboard_interaction(cur_time) < 0) + break; + + /* check if there's any stream where output is still needed */ + if (!need_output()) { + av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n"); + break; + } + + ret = transcode_step(); + if (ret < 0 && ret != AVERROR_EOF) { + char errbuf[128]; + av_strerror(ret, errbuf, sizeof(errbuf)); + + av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", errbuf); + break; + } + + /* dump report by using the output first video and audio streams */ + print_report(0, timer_start, cur_time); + } +#if HAVE_PTHREADS + free_input_threads(); +#endif + + /* at the end of stream, we must flush the decoder buffers */ + for (i = 0; i < nb_input_streams; i++) { + ist = input_streams[i]; + if (!input_files[ist->file_index]->eof_reached && ist->decoding_needed) { + process_input_packet(ist, NULL, 0); + } + } + flush_encoders(); + + term_exit(); + + /* write the trailer if needed and close file */ + for (i = 0; i < nb_output_files; i++) { + os = output_files[i]->ctx; + if (!output_files[i]->header_written) { + av_log(NULL, AV_LOG_ERROR, + "Nothing was written into output file %d (%s), because " + "at least one of its streams received no packets.\n", + i, os->filename); + continue; + } + if ((ret = av_write_trailer(os)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s\n", os->filename, av_err2str(ret)); + if (exit_on_error) + exit_program(1); + } + } + + /* dump report by using the first video and audio streams */ + print_report(1, timer_start, av_gettime_relative()); + + /* close each encoder */ + for (i = 0; i < nb_output_streams; i++) { + ost = output_streams[i]; + if (ost->encoding_needed) { + av_freep(&ost->enc_ctx->stats_in); + } + total_packets_written += ost->packets_written; + } + + if (!total_packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT)) { + av_log(NULL, AV_LOG_FATAL, "Empty output\n"); + exit_program(1); + } + + /* close each decoder */ + for (i = 0; i < nb_input_streams; i++) { + ist = input_streams[i]; + if (ist->decoding_needed) { + avcodec_close(ist->dec_ctx); + if (ist->hwaccel_uninit) + ist->hwaccel_uninit(ist->dec_ctx); + } + } + + av_buffer_unref(&hw_device_ctx); + hw_device_free_all(); + + /* finished ! */ + ret = 0; + + fail: +#if HAVE_PTHREADS + free_input_threads(); +#endif + + if (output_streams) { + for (i = 0; i < nb_output_streams; i++) { + ost = output_streams[i]; + if (ost) { + if (ost->logfile) { + if (fclose(ost->logfile)) + av_log(NULL, AV_LOG_ERROR, + "Error closing logfile, loss of information possible: %s\n", + av_err2str(AVERROR(errno))); + ost->logfile = NULL; + } + av_freep(&ost->forced_kf_pts); + av_freep(&ost->apad); + av_freep(&ost->disposition); + av_dict_free(&ost->encoder_opts); + av_dict_free(&ost->sws_dict); + av_dict_free(&ost->swr_opts); + av_dict_free(&ost->resample_opts); + } + } + } + return ret; +} + + +static int64_t getutime(void) +{ +#if HAVE_GETRUSAGE + struct rusage rusage; + + getrusage(RUSAGE_SELF, &rusage); + return (rusage.ru_utime.tv_sec * 1000000LL) + rusage.ru_utime.tv_usec; +#elif HAVE_GETPROCESSTIMES + HANDLE proc; + FILETIME c, e, k, u; + proc = GetCurrentProcess(); + GetProcessTimes(proc, &c, &e, &k, &u); + return ((int64_t) u.dwHighDateTime << 32 | u.dwLowDateTime) / 10; +#else + return av_gettime_relative(); +#endif +} + +static int64_t getmaxrss(void) +{ +#if HAVE_GETRUSAGE && HAVE_STRUCT_RUSAGE_RU_MAXRSS + struct rusage rusage; + getrusage(RUSAGE_SELF, &rusage); + return (int64_t)rusage.ru_maxrss * 1024; +#elif HAVE_GETPROCESSMEMORYINFO + HANDLE proc; + PROCESS_MEMORY_COUNTERS memcounters; + proc = GetCurrentProcess(); + memcounters.cb = sizeof(memcounters); + GetProcessMemoryInfo(proc, &memcounters, sizeof(memcounters)); + return memcounters.PeakPagefileUsage; +#else + return 0; +#endif +} + +static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl) +{ +} + +int main(int argc, char **argv) +{ + int i, ret; + int64_t ti; + + init_dynload(); + + register_exit(ffmpeg_cleanup); + + setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */ + + av_log_set_flags(AV_LOG_SKIP_REPEATED); + parse_loglevel(argc, argv, options); + + if(argc>1 && !strcmp(argv[1], "-d")){ + run_as_daemon=1; + av_log_set_callback(log_callback_null); + argc--; + argv++; + } + + avcodec_register_all(); +#if CONFIG_AVDEVICE + avdevice_register_all(); +#endif + avfilter_register_all(); + av_register_all(); + avformat_network_init(); + + show_banner(argc, argv, options); + + /* parse options and open all input/output files */ + ret = ffmpeg_parse_options(argc, argv); + if (ret < 0) + exit_program(1); + + if (nb_output_files <= 0 && nb_input_files == 0) { + show_usage(); + av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name); + exit_program(1); + } + + /* file converter / grab */ + if (nb_output_files <= 0) { + av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n"); + exit_program(1); + } + +// if (nb_input_files == 0) { +// av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n"); +// exit_program(1); +// } + + for (i = 0; i < nb_output_files; i++) { + if (strcmp(output_files[i]->ctx->oformat->name, "rtp")) + want_sdp = 0; + } + + current_time = ti = getutime(); + if (transcode() < 0) + exit_program(1); + ti = getutime() - ti; + if (do_benchmark) { + av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs\n", ti / 1000000.0); + } + av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n", + decode_error_stat[0], decode_error_stat[1]); + if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]) + exit_program(69); + + exit_program(received_nb_signals ? 255 : main_return_code); + return main_return_code; +} diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h new file mode 100644 index 0000000000..f6c76bcc55 --- /dev/null +++ b/fftools/ffmpeg.h @@ -0,0 +1,679 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FFTOOLS_FFMPEG_H +#define FFTOOLS_FFMPEG_H + +#include "config.h" + +#include <stdint.h> +#include <stdio.h> +#include <signal.h> + +#if HAVE_PTHREADS +#include <pthread.h> +#endif + +#include "cmdutils.h" + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" + +#include "libavcodec/avcodec.h" + +#include "libavfilter/avfilter.h" + +#include "libavutil/avutil.h" +#include "libavutil/dict.h" +#include "libavutil/eval.h" +#include "libavutil/fifo.h" +#include "libavutil/hwcontext.h" +#include "libavutil/pixfmt.h" +#include "libavutil/rational.h" +#include "libavutil/threadmessage.h" + +#include "libswresample/swresample.h" + +#define VSYNC_AUTO -1 +#define VSYNC_PASSTHROUGH 0 +#define VSYNC_CFR 1 +#define VSYNC_VFR 2 +#define VSYNC_VSCFR 0xfe +#define VSYNC_DROP 0xff + +#define MAX_STREAMS 1024 /* arbitrary sanity check value */ + +enum HWAccelID { + HWACCEL_NONE = 0, + HWACCEL_AUTO, + HWACCEL_VDPAU, + HWACCEL_DXVA2, + HWACCEL_VDA, + HWACCEL_VIDEOTOOLBOX, + HWACCEL_QSV, + HWACCEL_VAAPI, + HWACCEL_CUVID, + HWACCEL_D3D11VA, +}; + +typedef struct HWAccel { + const char *name; + int (*init)(AVCodecContext *s); + enum HWAccelID id; + enum AVPixelFormat pix_fmt; + enum AVHWDeviceType device_type; +} HWAccel; + +typedef struct HWDevice { + char *name; + enum AVHWDeviceType type; + AVBufferRef *device_ref; +} HWDevice; + +/* select an input stream for an output stream */ +typedef struct StreamMap { + int disabled; /* 1 is this mapping is disabled by a negative map */ + int file_index; + int stream_index; + int sync_file_index; + int sync_stream_index; + char *linklabel; /* name of an output link, for mapping lavfi outputs */ +} StreamMap; + +typedef struct { + int file_idx, stream_idx, channel_idx; // input + int ofile_idx, ostream_idx; // output +} AudioChannelMap; + +typedef struct OptionsContext { + OptionGroup *g; + + /* input/output options */ + int64_t start_time; + int64_t start_time_eof; + int seek_timestamp; + const char *format; + + SpecifierOpt *codec_names; + int nb_codec_names; + SpecifierOpt *audio_channels; + int nb_audio_channels; + SpecifierOpt *audio_sample_rate; + int nb_audio_sample_rate; + SpecifierOpt *frame_rates; + int nb_frame_rates; + SpecifierOpt *frame_sizes; + int nb_frame_sizes; + SpecifierOpt *frame_pix_fmts; + int nb_frame_pix_fmts; + + /* input options */ + int64_t input_ts_offset; + int loop; + int rate_emu; + int accurate_seek; + int thread_queue_size; + + SpecifierOpt *ts_scale; + int nb_ts_scale; + SpecifierOpt *dump_attachment; + int nb_dump_attachment; + SpecifierOpt *hwaccels; + int nb_hwaccels; + SpecifierOpt *hwaccel_devices; + int nb_hwaccel_devices; + SpecifierOpt *hwaccel_output_formats; + int nb_hwaccel_output_formats; + SpecifierOpt *autorotate; + int nb_autorotate; + + /* output options */ + StreamMap *stream_maps; + int nb_stream_maps; + AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */ + int nb_audio_channel_maps; /* number of (valid) -map_channel settings */ + int metadata_global_manual; + int metadata_streams_manual; + int metadata_chapters_manual; + const char **attachments; + int nb_attachments; + + int chapters_input_file; + + int64_t recording_time; + int64_t stop_time; + uint64_t limit_filesize; + float mux_preload; + float mux_max_delay; + int shortest; + + int video_disable; + int audio_disable; + int subtitle_disable; + int data_disable; + + /* indexed by output file stream index */ + int *streamid_map; + int nb_streamid_map; + + SpecifierOpt *metadata; + int nb_metadata; + SpecifierOpt *max_frames; + int nb_max_frames; + SpecifierOpt *bitstream_filters; + int nb_bitstream_filters; + SpecifierOpt *codec_tags; + int nb_codec_tags; + SpecifierOpt *sample_fmts; + int nb_sample_fmts; + SpecifierOpt *qscale; + int nb_qscale; + SpecifierOpt *forced_key_frames; + int nb_forced_key_frames; + SpecifierOpt *force_fps; + int nb_force_fps; + SpecifierOpt *frame_aspect_ratios; + int nb_frame_aspect_ratios; + SpecifierOpt *rc_overrides; + int nb_rc_overrides; + SpecifierOpt *intra_matrices; + int nb_intra_matrices; + SpecifierOpt *inter_matrices; + int nb_inter_matrices; + SpecifierOpt *chroma_intra_matrices; + int nb_chroma_intra_matrices; + SpecifierOpt *top_field_first; + int nb_top_field_first; + SpecifierOpt *metadata_map; + int nb_metadata_map; + SpecifierOpt *presets; + int nb_presets; + SpecifierOpt *copy_initial_nonkeyframes; + int nb_copy_initial_nonkeyframes; + SpecifierOpt *copy_prior_start; + int nb_copy_prior_start; + SpecifierOpt *filters; + int nb_filters; + SpecifierOpt *filter_scripts; + int nb_filter_scripts; + SpecifierOpt *reinit_filters; + int nb_reinit_filters; + SpecifierOpt *fix_sub_duration; + int nb_fix_sub_duration; + SpecifierOpt *canvas_sizes; + int nb_canvas_sizes; + SpecifierOpt *pass; + int nb_pass; + SpecifierOpt *passlogfiles; + int nb_passlogfiles; + SpecifierOpt *max_muxing_queue_size; + int nb_max_muxing_queue_size; + SpecifierOpt *guess_layout_max; + int nb_guess_layout_max; + SpecifierOpt *apad; + int nb_apad; + SpecifierOpt *discard; + int nb_discard; + SpecifierOpt *disposition; + int nb_disposition; + SpecifierOpt *program; + int nb_program; + SpecifierOpt *time_bases; + int nb_time_bases; + SpecifierOpt *enc_time_bases; + int nb_enc_time_bases; +} OptionsContext; + +typedef struct InputFilter { + AVFilterContext *filter; + struct InputStream *ist; + struct FilterGraph *graph; + uint8_t *name; + enum AVMediaType type; // AVMEDIA_TYPE_SUBTITLE for sub2video + + AVFifoBuffer *frame_queue; + + // parameters configured for this input + int format; + + int width, height; + AVRational sample_aspect_ratio; + + int sample_rate; + int channels; + uint64_t channel_layout; + + AVBufferRef *hw_frames_ctx; + + int eof; +} InputFilter; + +typedef struct OutputFilter { + AVFilterContext *filter; + struct OutputStream *ost; + struct FilterGraph *graph; + uint8_t *name; + + /* temporary storage until stream maps are processed */ + AVFilterInOut *out_tmp; + enum AVMediaType type; + + /* desired output stream properties */ + int width, height; + AVRational frame_rate; + int format; + int sample_rate; + uint64_t channel_layout; + + // those are only set if no format is specified and the encoder gives us multiple options + int *formats; + uint64_t *channel_layouts; + int *sample_rates; +} OutputFilter; + +typedef struct FilterGraph { + int index; + const char *graph_desc; + + AVFilterGraph *graph; + int reconfiguration; + + InputFilter **inputs; + int nb_inputs; + OutputFilter **outputs; + int nb_outputs; +} FilterGraph; + +typedef struct InputStream { + int file_index; + AVStream *st; + int discard; /* true if stream data should be discarded */ + int user_set_discard; + int decoding_needed; /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */ +#define DECODING_FOR_OST 1 +#define DECODING_FOR_FILTER 2 + + AVCodecContext *dec_ctx; + AVCodec *dec; + AVFrame *decoded_frame; + AVFrame *filter_frame; /* a ref of decoded_frame, to be sent to filters */ + + int64_t start; /* time when read started */ + /* predicted dts of the next packet read for this stream or (when there are + * several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */ + int64_t next_dts; + int64_t dts; ///< dts of the last packet read for this stream (in AV_TIME_BASE units) + + int64_t next_pts; ///< synthetic pts for the next decode frame (in AV_TIME_BASE units) + int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units) + int wrap_correction_done; + + int64_t filter_in_rescale_delta_last; + + int64_t min_pts; /* pts with the smallest value in a current stream */ + int64_t max_pts; /* pts with the higher value in a current stream */ + + // when forcing constant input framerate through -r, + // this contains the pts that will be given to the next decoded frame + int64_t cfr_next_pts; + + int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */ + + double ts_scale; + int saw_first_ts; + AVDictionary *decoder_opts; + AVRational framerate; /* framerate forced with -r */ + int top_field_first; + int guess_layout_max; + + int autorotate; + + int fix_sub_duration; + struct { /* previous decoded subtitle and related variables */ + int got_output; + int ret; + AVSubtitle subtitle; + } prev_sub; + + struct sub2video { + int64_t last_pts; + int64_t end_pts; + AVFifoBuffer *sub_queue; ///< queue of AVSubtitle* before filter init + AVFrame *frame; + int w, h; + } sub2video; + + int dr1; + + /* decoded data from this stream goes into all those filters + * currently video and audio only */ + InputFilter **filters; + int nb_filters; + + int reinit_filters; + + /* hwaccel options */ + enum HWAccelID hwaccel_id; + char *hwaccel_device; + enum AVPixelFormat hwaccel_output_format; + + /* hwaccel context */ + enum HWAccelID active_hwaccel_id; + void *hwaccel_ctx; + void (*hwaccel_uninit)(AVCodecContext *s); + int (*hwaccel_get_buffer)(AVCodecContext *s, AVFrame *frame, int flags); + int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame); + enum AVPixelFormat hwaccel_pix_fmt; + enum AVPixelFormat hwaccel_retrieved_pix_fmt; + AVBufferRef *hw_frames_ctx; + + /* stats */ + // combined size of all the packets read + uint64_t data_size; + /* number of packets successfully read for this stream */ + uint64_t nb_packets; + // number of frames/samples retrieved from the decoder + uint64_t frames_decoded; + uint64_t samples_decoded; + + int64_t *dts_buffer; + int nb_dts_buffer; + + int got_output; +} InputStream; + +typedef struct InputFile { + AVFormatContext *ctx; + int eof_reached; /* true if eof reached */ + int eagain; /* true if last read attempt returned EAGAIN */ + int ist_index; /* index of first stream in input_streams */ + int loop; /* set number of times input stream should be looped */ + int64_t duration; /* actual duration of the longest stream in a file + at the moment when looping happens */ + AVRational time_base; /* time base of the duration */ + int64_t input_ts_offset; + + int64_t ts_offset; + int64_t last_ts; + int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */ + int seek_timestamp; + int64_t recording_time; + int nb_streams; /* number of stream that ffmpeg is aware of; may be different + from ctx.nb_streams if new streams appear during av_read_frame() */ + int nb_streams_warn; /* number of streams that the user was warned of */ + int rate_emu; + int accurate_seek; + +#if HAVE_PTHREADS + AVThreadMessageQueue *in_thread_queue; + pthread_t thread; /* thread reading from this file */ + int non_blocking; /* reading packets from the thread should not block */ + int joined; /* the thread has been joined */ + int thread_queue_size; /* maximum number of queued packets */ +#endif +} InputFile; + +enum forced_keyframes_const { + FKF_N, + FKF_N_FORCED, + FKF_PREV_FORCED_N, + FKF_PREV_FORCED_T, + FKF_T, + FKF_NB +}; + +#define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0) + +extern const char *const forced_keyframes_const_names[]; + +typedef enum { + ENCODER_FINISHED = 1, + MUXER_FINISHED = 2, +} OSTFinished ; + +typedef struct OutputStream { + int file_index; /* file index */ + int index; /* stream index in the output file */ + int source_index; /* InputStream index */ + AVStream *st; /* stream in the output file */ + int encoding_needed; /* true if encoding needed for this stream */ + int frame_number; + /* input pts and corresponding output pts + for A/V sync */ + struct InputStream *sync_ist; /* input stream to sync against */ + int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number + /* pts of the first frame encoded for this stream, used for limiting + * recording time */ + int64_t first_pts; + /* dts of the last packet sent to the muxer */ + int64_t last_mux_dts; + // the timebase of the packets sent to the muxer + AVRational mux_timebase; + AVRational enc_timebase; + + int nb_bitstream_filters; + AVBSFContext **bsf_ctx; + + AVCodecContext *enc_ctx; + AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */ + AVCodec *enc; + int64_t max_frames; + AVFrame *filtered_frame; + AVFrame *last_frame; + int last_dropped; + int last_nb0_frames[3]; + + void *hwaccel_ctx; + + /* video only */ + AVRational frame_rate; + int is_cfr; + int force_fps; + int top_field_first; + int rotate_overridden; + double rotate_override_value; + + AVRational frame_aspect_ratio; + + /* forced key frames */ + int64_t *forced_kf_pts; + int forced_kf_count; + int forced_kf_index; + char *forced_keyframes; + AVExpr *forced_keyframes_pexpr; + double forced_keyframes_expr_const_values[FKF_NB]; + + /* audio only */ + int *audio_channels_map; /* list of the channels id to pick from the source stream */ + int audio_channels_mapped; /* number of channels in audio_channels_map */ + + char *logfile_prefix; + FILE *logfile; + + OutputFilter *filter; + char *avfilter; + char *filters; ///< filtergraph associated to the -filter option + char *filters_script; ///< filtergraph script associated to the -filter_script option + + AVDictionary *encoder_opts; + AVDictionary *sws_dict; + AVDictionary *swr_opts; + AVDictionary *resample_opts; + char *apad; + OSTFinished finished; /* no more packets should be written for this stream */ + int unavailable; /* true if the steram is unavailable (possibly temporarily) */ + int stream_copy; + + // init_output_stream() has been called for this stream + // The encoder and the bitstream filters have been initialized and the stream + // parameters are set in the AVStream. + int initialized; + + int inputs_done; + + const char *attachment_filename; + int copy_initial_nonkeyframes; + int copy_prior_start; + char *disposition; + + int keep_pix_fmt; + + AVCodecParserContext *parser; + AVCodecContext *parser_avctx; + + /* stats */ + // combined size of all the packets written + uint64_t data_size; + // number of packets send to the muxer + uint64_t packets_written; + // number of frames/samples sent to the encoder + uint64_t frames_encoded; + uint64_t samples_encoded; + + /* packet quality factor */ + int quality; + + int max_muxing_queue_size; + + /* the packets are buffered here until the muxer is ready to be initialized */ + AVFifoBuffer *muxing_queue; + + /* packet picture type */ + int pict_type; + + /* frame encode sum of squared error values */ + int64_t error[4]; +} OutputStream; + +typedef struct OutputFile { + AVFormatContext *ctx; + AVDictionary *opts; + int ost_index; /* index of the first stream in output_streams */ + int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units + int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units + uint64_t limit_filesize; /* filesize limit expressed in bytes */ + + int shortest; + + int header_written; +} OutputFile; + +extern InputStream **input_streams; +extern int nb_input_streams; +extern InputFile **input_files; +extern int nb_input_files; + +extern OutputStream **output_streams; +extern int nb_output_streams; +extern OutputFile **output_files; +extern int nb_output_files; + +extern FilterGraph **filtergraphs; +extern int nb_filtergraphs; + +extern char *vstats_filename; +extern char *sdp_filename; + +extern float audio_drift_threshold; +extern float dts_delta_threshold; +extern float dts_error_threshold; + +extern int audio_volume; +extern int audio_sync_method; +extern int video_sync_method; +extern float frame_drop_threshold; +extern int do_benchmark; +extern int do_benchmark_all; +extern int do_deinterlace; +extern int do_hex_dump; +extern int do_pkt_dump; +extern int copy_ts; +extern int start_at_zero; +extern int copy_tb; +extern int debug_ts; +extern int exit_on_error; +extern int abort_on_flags; +extern int print_stats; +extern int qp_hist; +extern int stdin_interaction; +extern int frame_bits_per_raw_sample; +extern AVIOContext *progress_avio; +extern float max_error_rate; +extern char *videotoolbox_pixfmt; + +extern int filter_nbthreads; +extern int filter_complex_nbthreads; +extern int vstats_version; + +extern const AVIOInterruptCB int_cb; + +extern const OptionDef options[]; +extern const HWAccel hwaccels[]; +extern int hwaccel_lax_profile_check; +extern AVBufferRef *hw_device_ctx; +#if CONFIG_QSV +extern char *qsv_device; +#endif +extern HWDevice *filter_hw_device; + + +void term_init(void); +void term_exit(void); + +void reset_options(OptionsContext *o, int is_input); +void show_usage(void); + +void opt_output_file(void *optctx, const char *filename); + +void remove_avoptions(AVDictionary **a, AVDictionary *b); +void assert_avoptions(AVDictionary *m); + +int guess_input_channel_layout(InputStream *ist); + +enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *avctx, AVCodec *codec, enum AVPixelFormat target); +void choose_sample_fmt(AVStream *st, AVCodec *codec); + +int configure_filtergraph(FilterGraph *fg); +int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out); +void check_filter_outputs(void); +int ist_in_filtergraph(FilterGraph *fg, InputStream *ist); +int filtergraph_is_simple(FilterGraph *fg); +int init_simple_filtergraph(InputStream *ist, OutputStream *ost); +int init_complex_filtergraph(FilterGraph *fg); + +void sub2video_update(InputStream *ist, AVSubtitle *sub); + +int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame); + +int ffmpeg_parse_options(int argc, char **argv); + +int vda_init(AVCodecContext *s); +int videotoolbox_init(AVCodecContext *s); +int qsv_init(AVCodecContext *s); +int cuvid_init(AVCodecContext *s); + +HWDevice *hw_device_get_by_name(const char *name); +int hw_device_init_from_string(const char *arg, HWDevice **dev); +void hw_device_free_all(void); + +int hw_device_setup_for_decode(InputStream *ist); +int hw_device_setup_for_encode(OutputStream *ost); + +int hwaccel_decode_init(AVCodecContext *avctx); + +#endif /* FFTOOLS_FFMPEG_H */ diff --git a/fftools/ffmpeg_cuvid.c b/fftools/ffmpeg_cuvid.c new file mode 100644 index 0000000000..3ff3b40f17 --- /dev/null +++ b/fftools/ffmpeg_cuvid.c @@ -0,0 +1,73 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/hwcontext.h" +#include "libavutil/pixdesc.h" + +#include "ffmpeg.h" + +static void cuvid_uninit(AVCodecContext *avctx) +{ + InputStream *ist = avctx->opaque; + av_buffer_unref(&ist->hw_frames_ctx); +} + +int cuvid_init(AVCodecContext *avctx) +{ + InputStream *ist = avctx->opaque; + AVHWFramesContext *frames_ctx; + int ret; + + av_log(avctx, AV_LOG_VERBOSE, "Initializing cuvid hwaccel\n"); + + if (!hw_device_ctx) { + ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, + ist->hwaccel_device, NULL, 0); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Error creating a CUDA device\n"); + return ret; + } + } + + av_buffer_unref(&ist->hw_frames_ctx); + ist->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx); + if (!ist->hw_frames_ctx) { + av_log(avctx, AV_LOG_ERROR, "Error creating a CUDA frames context\n"); + return AVERROR(ENOMEM); + } + + frames_ctx = (AVHWFramesContext*)ist->hw_frames_ctx->data; + + frames_ctx->format = AV_PIX_FMT_CUDA; + frames_ctx->sw_format = avctx->sw_pix_fmt; + frames_ctx->width = avctx->width; + frames_ctx->height = avctx->height; + + av_log(avctx, AV_LOG_DEBUG, "Initializing CUDA frames context: sw_format = %s, width = %d, height = %d\n", + av_get_pix_fmt_name(frames_ctx->sw_format), frames_ctx->width, frames_ctx->height); + + ret = av_hwframe_ctx_init(ist->hw_frames_ctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Error initializing a CUDA frame pool\n"); + return ret; + } + + ist->hwaccel_uninit = cuvid_uninit; + + return 0; +} diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c new file mode 100644 index 0000000000..aacc185059 --- /dev/null +++ b/fftools/ffmpeg_filter.c @@ -0,0 +1,1210 @@ +/* + * ffmpeg filter configuration + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> + +#include "ffmpeg.h" + +#include "libavfilter/avfilter.h" +#include "libavfilter/buffersink.h" +#include "libavfilter/buffersrc.h" + +#include "libavresample/avresample.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/channel_layout.h" +#include "libavutil/display.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" +#include "libavutil/imgutils.h" +#include "libavutil/samplefmt.h" + +static const enum AVPixelFormat *get_compliance_unofficial_pix_fmts(enum AVCodecID codec_id, const enum AVPixelFormat default_formats[]) +{ + static const enum AVPixelFormat mjpeg_formats[] = + { AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_NONE }; + static const enum AVPixelFormat ljpeg_formats[] = + { AV_PIX_FMT_BGR24 , AV_PIX_FMT_BGRA , AV_PIX_FMT_BGR0, + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUV420P , AV_PIX_FMT_YUV444P , AV_PIX_FMT_YUV422P, + AV_PIX_FMT_NONE}; + + if (codec_id == AV_CODEC_ID_MJPEG) { + return mjpeg_formats; + } else if (codec_id == AV_CODEC_ID_LJPEG) { + return ljpeg_formats; + } else { + return default_formats; + } +} + +enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, AVCodec *codec, enum AVPixelFormat target) +{ + if (codec && codec->pix_fmts) { + const enum AVPixelFormat *p = codec->pix_fmts; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(target); + int has_alpha = desc ? desc->nb_components % 2 == 0 : 0; + enum AVPixelFormat best= AV_PIX_FMT_NONE; + + if (enc_ctx->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL) { + p = get_compliance_unofficial_pix_fmts(enc_ctx->codec_id, p); + } + for (; *p != AV_PIX_FMT_NONE; p++) { + best= avcodec_find_best_pix_fmt_of_2(best, *p, target, has_alpha, NULL); + if (*p == target) + break; + } + if (*p == AV_PIX_FMT_NONE) { + if (target != AV_PIX_FMT_NONE) + av_log(NULL, AV_LOG_WARNING, + "Incompatible pixel format '%s' for codec '%s', auto-selecting format '%s'\n", + av_get_pix_fmt_name(target), + codec->name, + av_get_pix_fmt_name(best)); + return best; + } + } + return target; +} + +void choose_sample_fmt(AVStream *st, AVCodec *codec) +{ + if (codec && codec->sample_fmts) { + const enum AVSampleFormat *p = codec->sample_fmts; + for (; *p != -1; p++) { + if (*p == st->codecpar->format) + break; + } + if (*p == -1) { + if((codec->capabilities & AV_CODEC_CAP_LOSSLESS) && av_get_sample_fmt_name(st->codecpar->format) > av_get_sample_fmt_name(codec->sample_fmts[0])) + av_log(NULL, AV_LOG_ERROR, "Conversion will not be lossless.\n"); + if(av_get_sample_fmt_name(st->codecpar->format)) + av_log(NULL, AV_LOG_WARNING, + "Incompatible sample format '%s' for codec '%s', auto-selecting format '%s'\n", + av_get_sample_fmt_name(st->codecpar->format), + codec->name, + av_get_sample_fmt_name(codec->sample_fmts[0])); + st->codecpar->format = codec->sample_fmts[0]; + } + } +} + +static char *choose_pix_fmts(OutputFilter *ofilter) +{ + OutputStream *ost = ofilter->ost; + AVDictionaryEntry *strict_dict = av_dict_get(ost->encoder_opts, "strict", NULL, 0); + if (strict_dict) + // used by choose_pixel_fmt() and below + av_opt_set(ost->enc_ctx, "strict", strict_dict->value, 0); + + if (ost->keep_pix_fmt) { + avfilter_graph_set_auto_convert(ofilter->graph->graph, + AVFILTER_AUTO_CONVERT_NONE); + if (ost->enc_ctx->pix_fmt == AV_PIX_FMT_NONE) + return NULL; + return av_strdup(av_get_pix_fmt_name(ost->enc_ctx->pix_fmt)); + } + if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { + return av_strdup(av_get_pix_fmt_name(choose_pixel_fmt(ost->st, ost->enc_ctx, ost->enc, ost->enc_ctx->pix_fmt))); + } else if (ost->enc && ost->enc->pix_fmts) { + const enum AVPixelFormat *p; + AVIOContext *s = NULL; + uint8_t *ret; + int len; + + if (avio_open_dyn_buf(&s) < 0) + exit_program(1); + + p = ost->enc->pix_fmts; + if (ost->enc_ctx->strict_std_compliance <= FF_COMPLIANCE_UNOFFICIAL) { + p = get_compliance_unofficial_pix_fmts(ost->enc_ctx->codec_id, p); + } + + for (; *p != AV_PIX_FMT_NONE; p++) { + const char *name = av_get_pix_fmt_name(*p); + avio_printf(s, "%s|", name); + } + len = avio_close_dyn_buf(s, &ret); + ret[len - 1] = 0; + return ret; + } else + return NULL; +} + +/* Define a function for building a string containing a list of + * allowed formats. */ +#define DEF_CHOOSE_FORMAT(suffix, type, var, supported_list, none, get_name) \ +static char *choose_ ## suffix (OutputFilter *ofilter) \ +{ \ + if (ofilter->var != none) { \ + get_name(ofilter->var); \ + return av_strdup(name); \ + } else if (ofilter->supported_list) { \ + const type *p; \ + AVIOContext *s = NULL; \ + uint8_t *ret; \ + int len; \ + \ + if (avio_open_dyn_buf(&s) < 0) \ + exit_program(1); \ + \ + for (p = ofilter->supported_list; *p != none; p++) { \ + get_name(*p); \ + avio_printf(s, "%s|", name); \ + } \ + len = avio_close_dyn_buf(s, &ret); \ + ret[len - 1] = 0; \ + return ret; \ + } else \ + return NULL; \ +} + +//DEF_CHOOSE_FORMAT(pix_fmts, enum AVPixelFormat, format, formats, AV_PIX_FMT_NONE, +// GET_PIX_FMT_NAME) + +DEF_CHOOSE_FORMAT(sample_fmts, enum AVSampleFormat, format, formats, + AV_SAMPLE_FMT_NONE, GET_SAMPLE_FMT_NAME) + +DEF_CHOOSE_FORMAT(sample_rates, int, sample_rate, sample_rates, 0, + GET_SAMPLE_RATE_NAME) + +DEF_CHOOSE_FORMAT(channel_layouts, uint64_t, channel_layout, channel_layouts, 0, + GET_CH_LAYOUT_NAME) + +int init_simple_filtergraph(InputStream *ist, OutputStream *ost) +{ + FilterGraph *fg = av_mallocz(sizeof(*fg)); + + if (!fg) + exit_program(1); + fg->index = nb_filtergraphs; + + GROW_ARRAY(fg->outputs, fg->nb_outputs); + if (!(fg->outputs[0] = av_mallocz(sizeof(*fg->outputs[0])))) + exit_program(1); + fg->outputs[0]->ost = ost; + fg->outputs[0]->graph = fg; + fg->outputs[0]->format = -1; + + ost->filter = fg->outputs[0]; + + GROW_ARRAY(fg->inputs, fg->nb_inputs); + if (!(fg->inputs[0] = av_mallocz(sizeof(*fg->inputs[0])))) + exit_program(1); + fg->inputs[0]->ist = ist; + fg->inputs[0]->graph = fg; + fg->inputs[0]->format = -1; + + fg->inputs[0]->frame_queue = av_fifo_alloc(8 * sizeof(AVFrame*)); + if (!fg->inputs[0]->frame_queue) + exit_program(1); + + GROW_ARRAY(ist->filters, ist->nb_filters); + ist->filters[ist->nb_filters - 1] = fg->inputs[0]; + + GROW_ARRAY(filtergraphs, nb_filtergraphs); + filtergraphs[nb_filtergraphs - 1] = fg; + + return 0; +} + +static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in) +{ + AVFilterContext *ctx = inout->filter_ctx; + AVFilterPad *pads = in ? ctx->input_pads : ctx->output_pads; + int nb_pads = in ? ctx->nb_inputs : ctx->nb_outputs; + AVIOContext *pb; + uint8_t *res = NULL; + + if (avio_open_dyn_buf(&pb) < 0) + exit_program(1); + + avio_printf(pb, "%s", ctx->filter->name); + if (nb_pads > 1) + avio_printf(pb, ":%s", avfilter_pad_get_name(pads, inout->pad_idx)); + avio_w8(pb, 0); + avio_close_dyn_buf(pb, &res); + return res; +} + +static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) +{ + InputStream *ist = NULL; + enum AVMediaType type = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx); + int i; + + // TODO: support other filter types + if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) { + av_log(NULL, AV_LOG_FATAL, "Only video and audio filters supported " + "currently.\n"); + exit_program(1); + } + + if (in->name) { + AVFormatContext *s; + AVStream *st = NULL; + char *p; + int file_idx = strtol(in->name, &p, 0); + + if (file_idx < 0 || file_idx >= nb_input_files) { + av_log(NULL, AV_LOG_FATAL, "Invalid file index %d in filtergraph description %s.\n", + file_idx, fg->graph_desc); + exit_program(1); + } + s = input_files[file_idx]->ctx; + + for (i = 0; i < s->nb_streams; i++) { + enum AVMediaType stream_type = s->streams[i]->codecpar->codec_type; + if (stream_type != type && + !(stream_type == AVMEDIA_TYPE_SUBTITLE && + type == AVMEDIA_TYPE_VIDEO /* sub2video hack */)) + continue; + if (check_stream_specifier(s, s->streams[i], *p == ':' ? p + 1 : p) == 1) { + st = s->streams[i]; + break; + } + } + if (!st) { + av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s " + "matches no streams.\n", p, fg->graph_desc); + exit_program(1); + } + ist = input_streams[input_files[file_idx]->ist_index + st->index]; + } else { + /* find the first unused stream of corresponding type */ + for (i = 0; i < nb_input_streams; i++) { + ist = input_streams[i]; + if (ist->dec_ctx->codec_type == type && ist->discard) + break; + } + if (i == nb_input_streams) { + av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for " + "unlabeled input pad %d on filter %s\n", in->pad_idx, + in->filter_ctx->name); + exit_program(1); + } + } + av_assert0(ist); + + ist->discard = 0; + ist->decoding_needed |= DECODING_FOR_FILTER; + ist->st->discard = AVDISCARD_NONE; + + GROW_ARRAY(fg->inputs, fg->nb_inputs); + if (!(fg->inputs[fg->nb_inputs - 1] = av_mallocz(sizeof(*fg->inputs[0])))) + exit_program(1); + fg->inputs[fg->nb_inputs - 1]->ist = ist; + fg->inputs[fg->nb_inputs - 1]->graph = fg; + fg->inputs[fg->nb_inputs - 1]->format = -1; + fg->inputs[fg->nb_inputs - 1]->type = ist->st->codecpar->codec_type; + fg->inputs[fg->nb_inputs - 1]->name = describe_filter_link(fg, in, 1); + + fg->inputs[fg->nb_inputs - 1]->frame_queue = av_fifo_alloc(8 * sizeof(AVFrame*)); + if (!fg->inputs[fg->nb_inputs - 1]->frame_queue) + exit_program(1); + + GROW_ARRAY(ist->filters, ist->nb_filters); + ist->filters[ist->nb_filters - 1] = fg->inputs[fg->nb_inputs - 1]; +} + +int init_complex_filtergraph(FilterGraph *fg) +{ + AVFilterInOut *inputs, *outputs, *cur; + AVFilterGraph *graph; + int ret = 0; + + /* this graph is only used for determining the kinds of inputs + * and outputs we have, and is discarded on exit from this function */ + graph = avfilter_graph_alloc(); + if (!graph) + return AVERROR(ENOMEM); + + ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs); + if (ret < 0) + goto fail; + + for (cur = inputs; cur; cur = cur->next) + init_input_filter(fg, cur); + + for (cur = outputs; cur;) { + GROW_ARRAY(fg->outputs, fg->nb_outputs); + fg->outputs[fg->nb_outputs - 1] = av_mallocz(sizeof(*fg->outputs[0])); + if (!fg->outputs[fg->nb_outputs - 1]) + exit_program(1); + + fg->outputs[fg->nb_outputs - 1]->graph = fg; + fg->outputs[fg->nb_outputs - 1]->out_tmp = cur; + fg->outputs[fg->nb_outputs - 1]->type = avfilter_pad_get_type(cur->filter_ctx->output_pads, + cur->pad_idx); + fg->outputs[fg->nb_outputs - 1]->name = describe_filter_link(fg, cur, 0); + cur = cur->next; + fg->outputs[fg->nb_outputs - 1]->out_tmp->next = NULL; + } + +fail: + avfilter_inout_free(&inputs); + avfilter_graph_free(&graph); + return ret; +} + +static int insert_trim(int64_t start_time, int64_t duration, + AVFilterContext **last_filter, int *pad_idx, + const char *filter_name) +{ + AVFilterGraph *graph = (*last_filter)->graph; + AVFilterContext *ctx; + const AVFilter *trim; + enum AVMediaType type = avfilter_pad_get_type((*last_filter)->output_pads, *pad_idx); + const char *name = (type == AVMEDIA_TYPE_VIDEO) ? "trim" : "atrim"; + int ret = 0; + + if (duration == INT64_MAX && start_time == AV_NOPTS_VALUE) + return 0; + + trim = avfilter_get_by_name(name); + if (!trim) { + av_log(NULL, AV_LOG_ERROR, "%s filter not present, cannot limit " + "recording time.\n", name); + return AVERROR_FILTER_NOT_FOUND; + } + + ctx = avfilter_graph_alloc_filter(graph, trim, filter_name); + if (!ctx) + return AVERROR(ENOMEM); + + if (duration != INT64_MAX) { + ret = av_opt_set_int(ctx, "durationi", duration, + AV_OPT_SEARCH_CHILDREN); + } + if (ret >= 0 && start_time != AV_NOPTS_VALUE) { + ret = av_opt_set_int(ctx, "starti", start_time, + AV_OPT_SEARCH_CHILDREN); + } + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Error configuring the %s filter", name); + return ret; + } + + ret = avfilter_init_str(ctx, NULL); + if (ret < 0) + return ret; + + ret = avfilter_link(*last_filter, *pad_idx, ctx, 0); + if (ret < 0) + return ret; + + *last_filter = ctx; + *pad_idx = 0; + return 0; +} + +static int insert_filter(AVFilterContext **last_filter, int *pad_idx, + const char *filter_name, const char *args) +{ + AVFilterGraph *graph = (*last_filter)->graph; + AVFilterContext *ctx; + int ret; + + ret = avfilter_graph_create_filter(&ctx, + avfilter_get_by_name(filter_name), + filter_name, args, NULL, graph); + if (ret < 0) + return ret; + + ret = avfilter_link(*last_filter, *pad_idx, ctx, 0); + if (ret < 0) + return ret; + + *last_filter = ctx; + *pad_idx = 0; + return 0; +} + +static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out) +{ + char *pix_fmts; + OutputStream *ost = ofilter->ost; + OutputFile *of = output_files[ost->file_index]; + AVFilterContext *last_filter = out->filter_ctx; + int pad_idx = out->pad_idx; + int ret; + char name[255]; + + snprintf(name, sizeof(name), "out_%d_%d", ost->file_index, ost->index); + ret = avfilter_graph_create_filter(&ofilter->filter, + avfilter_get_by_name("buffersink"), + name, NULL, NULL, fg->graph); + + if (ret < 0) + return ret; + + if (ofilter->width || ofilter->height) { + char args[255]; + AVFilterContext *filter; + AVDictionaryEntry *e = NULL; + + snprintf(args, sizeof(args), "%d:%d", + ofilter->width, ofilter->height); + + while ((e = av_dict_get(ost->sws_dict, "", e, + AV_DICT_IGNORE_SUFFIX))) { + av_strlcatf(args, sizeof(args), ":%s=%s", e->key, e->value); + } + + snprintf(name, sizeof(name), "scaler_out_%d_%d", + ost->file_index, ost->index); + if ((ret = avfilter_graph_create_filter(&filter, avfilter_get_by_name("scale"), + name, args, NULL, fg->graph)) < 0) + return ret; + if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0) + return ret; + + last_filter = filter; + pad_idx = 0; + } + + if ((pix_fmts = choose_pix_fmts(ofilter))) { + AVFilterContext *filter; + snprintf(name, sizeof(name), "format_out_%d_%d", + ost->file_index, ost->index); + ret = avfilter_graph_create_filter(&filter, + avfilter_get_by_name("format"), + "format", pix_fmts, NULL, fg->graph); + av_freep(&pix_fmts); + if (ret < 0) + return ret; + if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0) + return ret; + + last_filter = filter; + pad_idx = 0; + } + + if (ost->frame_rate.num && 0) { + AVFilterContext *fps; + char args[255]; + + snprintf(args, sizeof(args), "fps=%d/%d", ost->frame_rate.num, + ost->frame_rate.den); + snprintf(name, sizeof(name), "fps_out_%d_%d", + ost->file_index, ost->index); + ret = avfilter_graph_create_filter(&fps, avfilter_get_by_name("fps"), + name, args, NULL, fg->graph); + if (ret < 0) + return ret; + + ret = avfilter_link(last_filter, pad_idx, fps, 0); + if (ret < 0) + return ret; + last_filter = fps; + pad_idx = 0; + } + + snprintf(name, sizeof(name), "trim_out_%d_%d", + ost->file_index, ost->index); + ret = insert_trim(of->start_time, of->recording_time, + &last_filter, &pad_idx, name); + if (ret < 0) + return ret; + + + if ((ret = avfilter_link(last_filter, pad_idx, ofilter->filter, 0)) < 0) + return ret; + + return 0; +} + +static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out) +{ + OutputStream *ost = ofilter->ost; + OutputFile *of = output_files[ost->file_index]; + AVCodecContext *codec = ost->enc_ctx; + AVFilterContext *last_filter = out->filter_ctx; + int pad_idx = out->pad_idx; + char *sample_fmts, *sample_rates, *channel_layouts; + char name[255]; + int ret; + + snprintf(name, sizeof(name), "out_%d_%d", ost->file_index, ost->index); + ret = avfilter_graph_create_filter(&ofilter->filter, + avfilter_get_by_name("abuffersink"), + name, NULL, NULL, fg->graph); + if (ret < 0) + return ret; + if ((ret = av_opt_set_int(ofilter->filter, "all_channel_counts", 1, AV_OPT_SEARCH_CHILDREN)) < 0) + return ret; + +#define AUTO_INSERT_FILTER(opt_name, filter_name, arg) do { \ + AVFilterContext *filt_ctx; \ + \ + av_log(NULL, AV_LOG_INFO, opt_name " is forwarded to lavfi " \ + "similarly to -af " filter_name "=%s.\n", arg); \ + \ + ret = avfilter_graph_create_filter(&filt_ctx, \ + avfilter_get_by_name(filter_name), \ + filter_name, arg, NULL, fg->graph); \ + if (ret < 0) \ + return ret; \ + \ + ret = avfilter_link(last_filter, pad_idx, filt_ctx, 0); \ + if (ret < 0) \ + return ret; \ + \ + last_filter = filt_ctx; \ + pad_idx = 0; \ +} while (0) + if (ost->audio_channels_mapped) { + int i; + AVBPrint pan_buf; + av_bprint_init(&pan_buf, 256, 8192); + av_bprintf(&pan_buf, "0x%"PRIx64, + av_get_default_channel_layout(ost->audio_channels_mapped)); + for (i = 0; i < ost->audio_channels_mapped; i++) + if (ost->audio_channels_map[i] != -1) + av_bprintf(&pan_buf, "|c%d=c%d", i, ost->audio_channels_map[i]); + + AUTO_INSERT_FILTER("-map_channel", "pan", pan_buf.str); + av_bprint_finalize(&pan_buf, NULL); + } + + if (codec->channels && !codec->channel_layout) + codec->channel_layout = av_get_default_channel_layout(codec->channels); + + sample_fmts = choose_sample_fmts(ofilter); + sample_rates = choose_sample_rates(ofilter); + channel_layouts = choose_channel_layouts(ofilter); + if (sample_fmts || sample_rates || channel_layouts) { + AVFilterContext *format; + char args[256]; + args[0] = 0; + + if (sample_fmts) + av_strlcatf(args, sizeof(args), "sample_fmts=%s:", + sample_fmts); + if (sample_rates) + av_strlcatf(args, sizeof(args), "sample_rates=%s:", + sample_rates); + if (channel_layouts) + av_strlcatf(args, sizeof(args), "channel_layouts=%s:", + channel_layouts); + + av_freep(&sample_fmts); + av_freep(&sample_rates); + av_freep(&channel_layouts); + + snprintf(name, sizeof(name), "format_out_%d_%d", + ost->file_index, ost->index); + ret = avfilter_graph_create_filter(&format, + avfilter_get_by_name("aformat"), + name, args, NULL, fg->graph); + if (ret < 0) + return ret; + + ret = avfilter_link(last_filter, pad_idx, format, 0); + if (ret < 0) + return ret; + + last_filter = format; + pad_idx = 0; + } + + if (audio_volume != 256 && 0) { + char args[256]; + + snprintf(args, sizeof(args), "%f", audio_volume / 256.); + AUTO_INSERT_FILTER("-vol", "volume", args); + } + + if (ost->apad && of->shortest) { + char args[256]; + int i; + + for (i=0; i<of->ctx->nb_streams; i++) + if (of->ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + break; + + if (i<of->ctx->nb_streams) { + snprintf(args, sizeof(args), "%s", ost->apad); + AUTO_INSERT_FILTER("-apad", "apad", args); + } + } + + snprintf(name, sizeof(name), "trim for output stream %d:%d", + ost->file_index, ost->index); + ret = insert_trim(of->start_time, of->recording_time, + &last_filter, &pad_idx, name); + if (ret < 0) + return ret; + + if ((ret = avfilter_link(last_filter, pad_idx, ofilter->filter, 0)) < 0) + return ret; + + return 0; +} + +int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out) +{ + if (!ofilter->ost) { + av_log(NULL, AV_LOG_FATAL, "Filter %s has an unconnected output\n", ofilter->name); + exit_program(1); + } + + switch (avfilter_pad_get_type(out->filter_ctx->output_pads, out->pad_idx)) { + case AVMEDIA_TYPE_VIDEO: return configure_output_video_filter(fg, ofilter, out); + case AVMEDIA_TYPE_AUDIO: return configure_output_audio_filter(fg, ofilter, out); + default: av_assert0(0); + } +} + +void check_filter_outputs(void) +{ + int i; + for (i = 0; i < nb_filtergraphs; i++) { + int n; + for (n = 0; n < filtergraphs[i]->nb_outputs; n++) { + OutputFilter *output = filtergraphs[i]->outputs[n]; + if (!output->ost) { + av_log(NULL, AV_LOG_FATAL, "Filter %s has an unconnected output\n", output->name); + exit_program(1); + } + } + } +} + +static int sub2video_prepare(InputStream *ist, InputFilter *ifilter) +{ + AVFormatContext *avf = input_files[ist->file_index]->ctx; + int i, w, h; + + /* Compute the size of the canvas for the subtitles stream. + If the subtitles codecpar has set a size, use it. Otherwise use the + maximum dimensions of the video streams in the same file. */ + w = ifilter->width; + h = ifilter->height; + if (!(w && h)) { + for (i = 0; i < avf->nb_streams; i++) { + if (avf->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + w = FFMAX(w, avf->streams[i]->codecpar->width); + h = FFMAX(h, avf->streams[i]->codecpar->height); + } + } + if (!(w && h)) { + w = FFMAX(w, 720); + h = FFMAX(h, 576); + } + av_log(avf, AV_LOG_INFO, "sub2video: using %dx%d canvas\n", w, h); + } + ist->sub2video.w = ifilter->width = w; + ist->sub2video.h = ifilter->height = h; + + ifilter->width = ist->dec_ctx->width ? ist->dec_ctx->width : ist->sub2video.w; + ifilter->height = ist->dec_ctx->height ? ist->dec_ctx->height : ist->sub2video.h; + + /* rectangles are AV_PIX_FMT_PAL8, but we have no guarantee that the + palettes for all rectangles are identical or compatible */ + ifilter->format = AV_PIX_FMT_RGB32; + + ist->sub2video.frame = av_frame_alloc(); + if (!ist->sub2video.frame) + return AVERROR(ENOMEM); + ist->sub2video.last_pts = INT64_MIN; + return 0; +} + +static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, + AVFilterInOut *in) +{ + AVFilterContext *last_filter; + const AVFilter *buffer_filt = avfilter_get_by_name("buffer"); + InputStream *ist = ifilter->ist; + InputFile *f = input_files[ist->file_index]; + AVRational tb = ist->framerate.num ? av_inv_q(ist->framerate) : + ist->st->time_base; + AVRational fr = ist->framerate; + AVRational sar; + AVBPrint args; + char name[255]; + int ret, pad_idx = 0; + int64_t tsoffset = 0; + AVBufferSrcParameters *par = av_buffersrc_parameters_alloc(); + + if (!par) + return AVERROR(ENOMEM); + memset(par, 0, sizeof(*par)); + par->format = AV_PIX_FMT_NONE; + + if (ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { + av_log(NULL, AV_LOG_ERROR, "Cannot connect video filter to audio input\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + if (!fr.num) + fr = av_guess_frame_rate(input_files[ist->file_index]->ctx, ist->st, NULL); + + if (ist->dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + ret = sub2video_prepare(ist, ifilter); + if (ret < 0) + goto fail; + } + + sar = ifilter->sample_aspect_ratio; + if(!sar.den) + sar = (AVRational){0,1}; + av_bprint_init(&args, 0, 1); + av_bprintf(&args, + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:" + "pixel_aspect=%d/%d:sws_param=flags=%d", + ifilter->width, ifilter->height, ifilter->format, + tb.num, tb.den, sar.num, sar.den, + SWS_BILINEAR + ((ist->dec_ctx->flags&AV_CODEC_FLAG_BITEXACT) ? SWS_BITEXACT:0)); + if (fr.num && fr.den) + av_bprintf(&args, ":frame_rate=%d/%d", fr.num, fr.den); + snprintf(name, sizeof(name), "graph %d input from stream %d:%d", fg->index, + ist->file_index, ist->st->index); + + + if ((ret = avfilter_graph_create_filter(&ifilter->filter, buffer_filt, name, + args.str, NULL, fg->graph)) < 0) + goto fail; + par->hw_frames_ctx = ifilter->hw_frames_ctx; + ret = av_buffersrc_parameters_set(ifilter->filter, par); + if (ret < 0) + goto fail; + av_freep(&par); + last_filter = ifilter->filter; + + if (ist->autorotate) { + double theta = get_rotation(ist->st); + + if (fabs(theta - 90) < 1.0) { + ret = insert_filter(&last_filter, &pad_idx, "transpose", "clock"); + } else if (fabs(theta - 180) < 1.0) { + ret = insert_filter(&last_filter, &pad_idx, "hflip", NULL); + if (ret < 0) + return ret; + ret = insert_filter(&last_filter, &pad_idx, "vflip", NULL); + } else if (fabs(theta - 270) < 1.0) { + ret = insert_filter(&last_filter, &pad_idx, "transpose", "cclock"); + } else if (fabs(theta) > 1.0) { + char rotate_buf[64]; + snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta); + ret = insert_filter(&last_filter, &pad_idx, "rotate", rotate_buf); + } + if (ret < 0) + return ret; + } + + if (do_deinterlace) { + AVFilterContext *yadif; + + snprintf(name, sizeof(name), "deinterlace_in_%d_%d", + ist->file_index, ist->st->index); + if ((ret = avfilter_graph_create_filter(&yadif, + avfilter_get_by_name("yadif"), + name, "", NULL, + fg->graph)) < 0) + return ret; + + if ((ret = avfilter_link(last_filter, 0, yadif, 0)) < 0) + return ret; + + last_filter = yadif; + } + + snprintf(name, sizeof(name), "trim_in_%d_%d", + ist->file_index, ist->st->index); + if (copy_ts) { + tsoffset = f->start_time == AV_NOPTS_VALUE ? 0 : f->start_time; + if (!start_at_zero && f->ctx->start_time != AV_NOPTS_VALUE) + tsoffset += f->ctx->start_time; + } + ret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ? + AV_NOPTS_VALUE : tsoffset, f->recording_time, + &last_filter, &pad_idx, name); + if (ret < 0) + return ret; + + if ((ret = avfilter_link(last_filter, 0, in->filter_ctx, in->pad_idx)) < 0) + return ret; + return 0; +fail: + av_freep(&par); + + return ret; +} + +static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter, + AVFilterInOut *in) +{ + AVFilterContext *last_filter; + const AVFilter *abuffer_filt = avfilter_get_by_name("abuffer"); + InputStream *ist = ifilter->ist; + InputFile *f = input_files[ist->file_index]; + AVBPrint args; + char name[255]; + int ret, pad_idx = 0; + int64_t tsoffset = 0; + + if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO) { + av_log(NULL, AV_LOG_ERROR, "Cannot connect audio filter to non audio input\n"); + return AVERROR(EINVAL); + } + + av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprintf(&args, "time_base=%d/%d:sample_rate=%d:sample_fmt=%s", + 1, ifilter->sample_rate, + ifilter->sample_rate, + av_get_sample_fmt_name(ifilter->format)); + if (ifilter->channel_layout) + av_bprintf(&args, ":channel_layout=0x%"PRIx64, + ifilter->channel_layout); + else + av_bprintf(&args, ":channels=%d", ifilter->channels); + snprintf(name, sizeof(name), "graph_%d_in_%d_%d", fg->index, + ist->file_index, ist->st->index); + + if ((ret = avfilter_graph_create_filter(&ifilter->filter, abuffer_filt, + name, args.str, NULL, + fg->graph)) < 0) + return ret; + last_filter = ifilter->filter; + +#define AUTO_INSERT_FILTER_INPUT(opt_name, filter_name, arg) do { \ + AVFilterContext *filt_ctx; \ + \ + av_log(NULL, AV_LOG_INFO, opt_name " is forwarded to lavfi " \ + "similarly to -af " filter_name "=%s.\n", arg); \ + \ + snprintf(name, sizeof(name), "graph_%d_%s_in_%d_%d", \ + fg->index, filter_name, ist->file_index, ist->st->index); \ + ret = avfilter_graph_create_filter(&filt_ctx, \ + avfilter_get_by_name(filter_name), \ + name, arg, NULL, fg->graph); \ + if (ret < 0) \ + return ret; \ + \ + ret = avfilter_link(last_filter, 0, filt_ctx, 0); \ + if (ret < 0) \ + return ret; \ + \ + last_filter = filt_ctx; \ +} while (0) + + if (audio_sync_method > 0) { + char args[256] = {0}; + + av_strlcatf(args, sizeof(args), "async=%d", audio_sync_method); + if (audio_drift_threshold != 0.1) + av_strlcatf(args, sizeof(args), ":min_hard_comp=%f", audio_drift_threshold); + if (!fg->reconfiguration) + av_strlcatf(args, sizeof(args), ":first_pts=0"); + AUTO_INSERT_FILTER_INPUT("-async", "aresample", args); + } + +// if (ost->audio_channels_mapped) { +// int i; +// AVBPrint pan_buf; +// av_bprint_init(&pan_buf, 256, 8192); +// av_bprintf(&pan_buf, "0x%"PRIx64, +// av_get_default_channel_layout(ost->audio_channels_mapped)); +// for (i = 0; i < ost->audio_channels_mapped; i++) +// if (ost->audio_channels_map[i] != -1) +// av_bprintf(&pan_buf, ":c%d=c%d", i, ost->audio_channels_map[i]); +// AUTO_INSERT_FILTER_INPUT("-map_channel", "pan", pan_buf.str); +// av_bprint_finalize(&pan_buf, NULL); +// } + + if (audio_volume != 256) { + char args[256]; + + av_log(NULL, AV_LOG_WARNING, "-vol has been deprecated. Use the volume " + "audio filter instead.\n"); + + snprintf(args, sizeof(args), "%f", audio_volume / 256.); + AUTO_INSERT_FILTER_INPUT("-vol", "volume", args); + } + + snprintf(name, sizeof(name), "trim for input stream %d:%d", + ist->file_index, ist->st->index); + if (copy_ts) { + tsoffset = f->start_time == AV_NOPTS_VALUE ? 0 : f->start_time; + if (!start_at_zero && f->ctx->start_time != AV_NOPTS_VALUE) + tsoffset += f->ctx->start_time; + } + ret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ? + AV_NOPTS_VALUE : tsoffset, f->recording_time, + &last_filter, &pad_idx, name); + if (ret < 0) + return ret; + + if ((ret = avfilter_link(last_filter, 0, in->filter_ctx, in->pad_idx)) < 0) + return ret; + + return 0; +} + +static int configure_input_filter(FilterGraph *fg, InputFilter *ifilter, + AVFilterInOut *in) +{ + if (!ifilter->ist->dec) { + av_log(NULL, AV_LOG_ERROR, + "No decoder for stream #%d:%d, filtering impossible\n", + ifilter->ist->file_index, ifilter->ist->st->index); + return AVERROR_DECODER_NOT_FOUND; + } + switch (avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx)) { + case AVMEDIA_TYPE_VIDEO: return configure_input_video_filter(fg, ifilter, in); + case AVMEDIA_TYPE_AUDIO: return configure_input_audio_filter(fg, ifilter, in); + default: av_assert0(0); + } +} + +static void cleanup_filtergraph(FilterGraph *fg) +{ + int i; + for (i = 0; i < fg->nb_outputs; i++) + fg->outputs[i]->filter = (AVFilterContext *)NULL; + for (i = 0; i < fg->nb_inputs; i++) + fg->inputs[i]->filter = (AVFilterContext *)NULL; + avfilter_graph_free(&fg->graph); +} + +int configure_filtergraph(FilterGraph *fg) +{ + AVFilterInOut *inputs, *outputs, *cur; + int ret, i, simple = filtergraph_is_simple(fg); + const char *graph_desc = simple ? fg->outputs[0]->ost->avfilter : + fg->graph_desc; + + cleanup_filtergraph(fg); + if (!(fg->graph = avfilter_graph_alloc())) + return AVERROR(ENOMEM); + + if (simple) { + OutputStream *ost = fg->outputs[0]->ost; + char args[512]; + AVDictionaryEntry *e = NULL; + + fg->graph->nb_threads = filter_nbthreads; + + args[0] = 0; + while ((e = av_dict_get(ost->sws_dict, "", e, + AV_DICT_IGNORE_SUFFIX))) { + av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + } + if (strlen(args)) + args[strlen(args)-1] = 0; + fg->graph->scale_sws_opts = av_strdup(args); + + args[0] = 0; + while ((e = av_dict_get(ost->swr_opts, "", e, + AV_DICT_IGNORE_SUFFIX))) { + av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + } + if (strlen(args)) + args[strlen(args)-1] = 0; + av_opt_set(fg->graph, "aresample_swr_opts", args, 0); + + args[0] = '\0'; + while ((e = av_dict_get(fg->outputs[0]->ost->resample_opts, "", e, + AV_DICT_IGNORE_SUFFIX))) { + av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + } + if (strlen(args)) + args[strlen(args) - 1] = '\0'; + + e = av_dict_get(ost->encoder_opts, "threads", NULL, 0); + if (e) + av_opt_set(fg->graph, "threads", e->value, 0); + } else { + fg->graph->nb_threads = filter_complex_nbthreads; + } + + if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0) + goto fail; + + if (filter_hw_device || hw_device_ctx) { + AVBufferRef *device = filter_hw_device ? filter_hw_device->device_ref + : hw_device_ctx; + for (i = 0; i < fg->graph->nb_filters; i++) { + fg->graph->filters[i]->hw_device_ctx = av_buffer_ref(device); + if (!fg->graph->filters[i]->hw_device_ctx) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + } + + if (simple && (!inputs || inputs->next || !outputs || outputs->next)) { + const char *num_inputs; + const char *num_outputs; + if (!outputs) { + num_outputs = "0"; + } else if (outputs->next) { + num_outputs = ">1"; + } else { + num_outputs = "1"; + } + if (!inputs) { + num_inputs = "0"; + } else if (inputs->next) { + num_inputs = ">1"; + } else { + num_inputs = "1"; + } + av_log(NULL, AV_LOG_ERROR, "Simple filtergraph '%s' was expected " + "to have exactly 1 input and 1 output." + " However, it had %s input(s) and %s output(s)." + " Please adjust, or use a complex filtergraph (-filter_complex) instead.\n", + graph_desc, num_inputs, num_outputs); + ret = AVERROR(EINVAL); + goto fail; + } + + for (cur = inputs, i = 0; cur; cur = cur->next, i++) + if ((ret = configure_input_filter(fg, fg->inputs[i], cur)) < 0) { + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + goto fail; + } + avfilter_inout_free(&inputs); + + for (cur = outputs, i = 0; cur; cur = cur->next, i++) + configure_output_filter(fg, fg->outputs[i], cur); + avfilter_inout_free(&outputs); + + if ((ret = avfilter_graph_config(fg->graph, NULL)) < 0) + goto fail; + + /* limit the lists of allowed formats to the ones selected, to + * make sure they stay the same if the filtergraph is reconfigured later */ + for (i = 0; i < fg->nb_outputs; i++) { + OutputFilter *ofilter = fg->outputs[i]; + AVFilterContext *sink = ofilter->filter; + + ofilter->format = av_buffersink_get_format(sink); + + ofilter->width = av_buffersink_get_w(sink); + ofilter->height = av_buffersink_get_h(sink); + + ofilter->sample_rate = av_buffersink_get_sample_rate(sink); + ofilter->channel_layout = av_buffersink_get_channel_layout(sink); + } + + fg->reconfiguration = 1; + + for (i = 0; i < fg->nb_outputs; i++) { + OutputStream *ost = fg->outputs[i]->ost; + if (!ost->enc) { + /* identical to the same check in ffmpeg.c, needed because + complex filter graphs are initialized earlier */ + av_log(NULL, AV_LOG_ERROR, "Encoder (codec %s) not found for output stream #%d:%d\n", + avcodec_get_name(ost->st->codecpar->codec_id), ost->file_index, ost->index); + ret = AVERROR(EINVAL); + goto fail; + } + if (ost->enc->type == AVMEDIA_TYPE_AUDIO && + !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + av_buffersink_set_frame_size(ost->filter->filter, + ost->enc_ctx->frame_size); + } + + for (i = 0; i < fg->nb_inputs; i++) { + while (av_fifo_size(fg->inputs[i]->frame_queue)) { + AVFrame *tmp; + av_fifo_generic_read(fg->inputs[i]->frame_queue, &tmp, sizeof(tmp), NULL); + ret = av_buffersrc_add_frame(fg->inputs[i]->filter, tmp); + av_frame_free(&tmp); + if (ret < 0) + goto fail; + } + } + + /* send the EOFs for the finished inputs */ + for (i = 0; i < fg->nb_inputs; i++) { + if (fg->inputs[i]->eof) { + ret = av_buffersrc_add_frame(fg->inputs[i]->filter, NULL); + if (ret < 0) + goto fail; + } + } + + /* process queued up subtitle packets */ + for (i = 0; i < fg->nb_inputs; i++) { + InputStream *ist = fg->inputs[i]->ist; + if (ist->sub2video.sub_queue && ist->sub2video.frame) { + while (av_fifo_size(ist->sub2video.sub_queue)) { + AVSubtitle tmp; + av_fifo_generic_read(ist->sub2video.sub_queue, &tmp, sizeof(tmp), NULL); + sub2video_update(ist, &tmp); + avsubtitle_free(&tmp); + } + } + } + + return 0; + +fail: + cleanup_filtergraph(fg); + return ret; +} + +int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame) +{ + av_buffer_unref(&ifilter->hw_frames_ctx); + + ifilter->format = frame->format; + + ifilter->width = frame->width; + ifilter->height = frame->height; + ifilter->sample_aspect_ratio = frame->sample_aspect_ratio; + + ifilter->sample_rate = frame->sample_rate; + ifilter->channels = frame->channels; + ifilter->channel_layout = frame->channel_layout; + + if (frame->hw_frames_ctx) { + ifilter->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx); + if (!ifilter->hw_frames_ctx) + return AVERROR(ENOMEM); + } + + return 0; +} + +int ist_in_filtergraph(FilterGraph *fg, InputStream *ist) +{ + int i; + for (i = 0; i < fg->nb_inputs; i++) + if (fg->inputs[i]->ist == ist) + return 1; + return 0; +} + +int filtergraph_is_simple(FilterGraph *fg) +{ + return !fg->graph_desc; +} diff --git a/fftools/ffmpeg_hw.c b/fftools/ffmpeg_hw.c new file mode 100644 index 0000000000..a4d1cada59 --- /dev/null +++ b/fftools/ffmpeg_hw.c @@ -0,0 +1,385 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> + +#include "libavutil/avstring.h" + +#include "ffmpeg.h" + +static int nb_hw_devices; +static HWDevice **hw_devices; + +static HWDevice *hw_device_get_by_type(enum AVHWDeviceType type) +{ + HWDevice *found = NULL; + int i; + for (i = 0; i < nb_hw_devices; i++) { + if (hw_devices[i]->type == type) { + if (found) + return NULL; + found = hw_devices[i]; + } + } + return found; +} + +HWDevice *hw_device_get_by_name(const char *name) +{ + int i; + for (i = 0; i < nb_hw_devices; i++) { + if (!strcmp(hw_devices[i]->name, name)) + return hw_devices[i]; + } + return NULL; +} + +static HWDevice *hw_device_add(void) +{ + int err; + err = av_reallocp_array(&hw_devices, nb_hw_devices + 1, + sizeof(*hw_devices)); + if (err) { + nb_hw_devices = 0; + return NULL; + } + hw_devices[nb_hw_devices] = av_mallocz(sizeof(HWDevice)); + if (!hw_devices[nb_hw_devices]) + return NULL; + return hw_devices[nb_hw_devices++]; +} + +int hw_device_init_from_string(const char *arg, HWDevice **dev_out) +{ + // "type=name:device,key=value,key2=value2" + // "type:device,key=value,key2=value2" + // -> av_hwdevice_ctx_create() + // "type=name@name" + // "type@name" + // -> av_hwdevice_ctx_create_derived() + + AVDictionary *options = NULL; + char *type_name = NULL, *name = NULL, *device = NULL; + enum AVHWDeviceType type; + HWDevice *dev, *src; + AVBufferRef *device_ref = NULL; + int err; + const char *errmsg, *p, *q; + size_t k; + + k = strcspn(arg, ":=@"); + p = arg + k; + + type_name = av_strndup(arg, k); + if (!type_name) { + err = AVERROR(ENOMEM); + goto fail; + } + type = av_hwdevice_find_type_by_name(type_name); + if (type == AV_HWDEVICE_TYPE_NONE) { + errmsg = "unknown device type"; + goto invalid; + } + + if (*p == '=') { + k = strcspn(p + 1, ":@"); + + name = av_strndup(p + 1, k); + if (!name) { + err = AVERROR(ENOMEM); + goto fail; + } + if (hw_device_get_by_name(name)) { + errmsg = "named device already exists"; + goto invalid; + } + + p += 1 + k; + } else { + // Give the device an automatic name of the form "type%d". + // We arbitrarily limit at 1000 anonymous devices of the same + // type - there is probably something else very wrong if you + // get to this limit. + size_t index_pos; + int index, index_limit = 1000; + index_pos = strlen(type_name); + name = av_malloc(index_pos + 4); + if (!name) { + err = AVERROR(ENOMEM); + goto fail; + } + for (index = 0; index < index_limit; index++) { + snprintf(name, index_pos + 4, "%s%d", type_name, index); + if (!hw_device_get_by_name(name)) + break; + } + if (index >= index_limit) { + errmsg = "too many devices"; + goto invalid; + } + } + + if (!*p) { + // New device with no parameters. + err = av_hwdevice_ctx_create(&device_ref, type, + NULL, NULL, 0); + if (err < 0) + goto fail; + + } else if (*p == ':') { + // New device with some parameters. + ++p; + q = strchr(p, ','); + if (q) { + device = av_strndup(p, q - p); + if (!device) { + err = AVERROR(ENOMEM); + goto fail; + } + err = av_dict_parse_string(&options, q + 1, "=", ",", 0); + if (err < 0) { + errmsg = "failed to parse options"; + goto invalid; + } + } + + err = av_hwdevice_ctx_create(&device_ref, type, + device ? device : p, options, 0); + if (err < 0) + goto fail; + + } else if (*p == '@') { + // Derive from existing device. + + src = hw_device_get_by_name(p + 1); + if (!src) { + errmsg = "invalid source device name"; + goto invalid; + } + + err = av_hwdevice_ctx_create_derived(&device_ref, type, + src->device_ref, 0); + if (err < 0) + goto fail; + } else { + errmsg = "parse error"; + goto invalid; + } + + dev = hw_device_add(); + if (!dev) { + err = AVERROR(ENOMEM); + goto fail; + } + + dev->name = name; + dev->type = type; + dev->device_ref = device_ref; + + if (dev_out) + *dev_out = dev; + + name = NULL; + err = 0; +done: + av_freep(&type_name); + av_freep(&name); + av_freep(&device); + av_dict_free(&options); + return err; +invalid: + av_log(NULL, AV_LOG_ERROR, + "Invalid device specification \"%s\": %s\n", arg, errmsg); + err = AVERROR(EINVAL); + goto done; +fail: + av_log(NULL, AV_LOG_ERROR, + "Device creation failed: %d.\n", err); + av_buffer_unref(&device_ref); + goto done; +} + +void hw_device_free_all(void) +{ + int i; + for (i = 0; i < nb_hw_devices; i++) { + av_freep(&hw_devices[i]->name); + av_buffer_unref(&hw_devices[i]->device_ref); + av_freep(&hw_devices[i]); + } + av_freep(&hw_devices); + nb_hw_devices = 0; +} + +static enum AVHWDeviceType hw_device_match_type_by_hwaccel(enum HWAccelID hwaccel_id) +{ + int i; + if (hwaccel_id == HWACCEL_NONE) + return AV_HWDEVICE_TYPE_NONE; + for (i = 0; hwaccels[i].name; i++) { + if (hwaccels[i].id == hwaccel_id) + return hwaccels[i].device_type; + } + return AV_HWDEVICE_TYPE_NONE; +} + +static enum AVHWDeviceType hw_device_match_type_in_name(const char *codec_name) +{ + const char *type_name; + enum AVHWDeviceType type; + for (type = av_hwdevice_iterate_types(AV_HWDEVICE_TYPE_NONE); + type != AV_HWDEVICE_TYPE_NONE; + type = av_hwdevice_iterate_types(type)) { + type_name = av_hwdevice_get_type_name(type); + if (strstr(codec_name, type_name)) + return type; + } + return AV_HWDEVICE_TYPE_NONE; +} + +int hw_device_setup_for_decode(InputStream *ist) +{ + enum AVHWDeviceType type; + HWDevice *dev; + int err; + + if (ist->hwaccel_device) { + dev = hw_device_get_by_name(ist->hwaccel_device); + if (!dev) { + char *tmp; + type = hw_device_match_type_by_hwaccel(ist->hwaccel_id); + if (type == AV_HWDEVICE_TYPE_NONE) { + // No match - this isn't necessarily invalid, though, + // because an explicit device might not be needed or + // the hwaccel setup could be handled elsewhere. + return 0; + } + tmp = av_asprintf("%s:%s", av_hwdevice_get_type_name(type), + ist->hwaccel_device); + if (!tmp) + return AVERROR(ENOMEM); + err = hw_device_init_from_string(tmp, &dev); + av_free(tmp); + if (err < 0) + return err; + } + } else { + if (ist->hwaccel_id != HWACCEL_NONE) + type = hw_device_match_type_by_hwaccel(ist->hwaccel_id); + else + type = hw_device_match_type_in_name(ist->dec->name); + if (type != AV_HWDEVICE_TYPE_NONE) { + dev = hw_device_get_by_type(type); + if (!dev) { + hw_device_init_from_string(av_hwdevice_get_type_name(type), + &dev); + } + } else { + // No device required. + return 0; + } + } + + if (!dev) { + av_log(ist->dec_ctx, AV_LOG_WARNING, "No device available " + "for decoder (device type %s for codec %s).\n", + av_hwdevice_get_type_name(type), ist->dec->name); + return 0; + } + + ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref); + if (!ist->dec_ctx->hw_device_ctx) + return AVERROR(ENOMEM); + + return 0; +} + +int hw_device_setup_for_encode(OutputStream *ost) +{ + enum AVHWDeviceType type; + HWDevice *dev; + + type = hw_device_match_type_in_name(ost->enc->name); + if (type != AV_HWDEVICE_TYPE_NONE) { + dev = hw_device_get_by_type(type); + if (!dev) { + av_log(ost->enc_ctx, AV_LOG_WARNING, "No device available " + "for encoder (device type %s for codec %s).\n", + av_hwdevice_get_type_name(type), ost->enc->name); + return 0; + } + ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref); + if (!ost->enc_ctx->hw_device_ctx) + return AVERROR(ENOMEM); + return 0; + } else { + // No device required. + return 0; + } +} + +static int hwaccel_retrieve_data(AVCodecContext *avctx, AVFrame *input) +{ + InputStream *ist = avctx->opaque; + AVFrame *output = NULL; + enum AVPixelFormat output_format = ist->hwaccel_output_format; + int err; + + if (input->format == output_format) { + // Nothing to do. + return 0; + } + + output = av_frame_alloc(); + if (!output) + return AVERROR(ENOMEM); + + output->format = output_format; + + err = av_hwframe_transfer_data(output, input, 0); + if (err < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to transfer data to " + "output frame: %d.\n", err); + goto fail; + } + + err = av_frame_copy_props(output, input); + if (err < 0) { + av_frame_unref(output); + goto fail; + } + + av_frame_unref(input); + av_frame_move_ref(input, output); + av_frame_free(&output); + + return 0; + +fail: + av_frame_free(&output); + return err; +} + +int hwaccel_decode_init(AVCodecContext *avctx) +{ + InputStream *ist = avctx->opaque; + + ist->hwaccel_retrieve_data = &hwaccel_retrieve_data; + + return 0; +} diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c new file mode 100644 index 0000000000..100fa76e46 --- /dev/null +++ b/fftools/ffmpeg_opt.c @@ -0,0 +1,3756 @@ +/* + * ffmpeg option parsing + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> + +#include "ffmpeg.h" +#include "cmdutils.h" + +#include "libavformat/avformat.h" + +#include "libavcodec/avcodec.h" + +#include "libavfilter/avfilter.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/avutil.h" +#include "libavutil/channel_layout.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/fifo.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" + +#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" + +#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ +{\ + int i, ret;\ + for (i = 0; i < o->nb_ ## name; i++) {\ + char *spec = o->name[i].specifier;\ + if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0)\ + outvar = o->name[i].u.type;\ + else if (ret < 0)\ + exit_program(1);\ + }\ +} + +#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ +{\ + int i;\ + for (i = 0; i < o->nb_ ## name; i++) {\ + char *spec = o->name[i].specifier;\ + if (!strcmp(spec, mediatype))\ + outvar = o->name[i].u.type;\ + }\ +} + +const HWAccel hwaccels[] = { +#if HAVE_VDPAU_X11 + { "vdpau", hwaccel_decode_init, HWACCEL_VDPAU, AV_PIX_FMT_VDPAU, + AV_HWDEVICE_TYPE_VDPAU }, +#endif +#if CONFIG_D3D11VA + { "d3d11va", hwaccel_decode_init, HWACCEL_D3D11VA, AV_PIX_FMT_D3D11, + AV_HWDEVICE_TYPE_D3D11VA }, +#endif +#if CONFIG_DXVA2 + { "dxva2", hwaccel_decode_init, HWACCEL_DXVA2, AV_PIX_FMT_DXVA2_VLD, + AV_HWDEVICE_TYPE_DXVA2 }, +#endif +#if CONFIG_VDA + { "vda", videotoolbox_init, HWACCEL_VDA, AV_PIX_FMT_VDA, + AV_HWDEVICE_TYPE_NONE }, +#endif +#if CONFIG_VIDEOTOOLBOX + { "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX, + AV_HWDEVICE_TYPE_NONE }, +#endif +#if CONFIG_LIBMFX + { "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV, + AV_HWDEVICE_TYPE_NONE }, +#endif +#if CONFIG_VAAPI + { "vaapi", hwaccel_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI, + AV_HWDEVICE_TYPE_VAAPI }, +#endif +#if CONFIG_CUVID + { "cuvid", cuvid_init, HWACCEL_CUVID, AV_PIX_FMT_CUDA, + AV_HWDEVICE_TYPE_NONE }, +#endif + { 0 }, +}; +int hwaccel_lax_profile_check = 0; +AVBufferRef *hw_device_ctx; +HWDevice *filter_hw_device; + +char *vstats_filename; +char *sdp_filename; + +float audio_drift_threshold = 0.1; +float dts_delta_threshold = 10; +float dts_error_threshold = 3600*30; + +int audio_volume = 256; +int audio_sync_method = 0; +int video_sync_method = VSYNC_AUTO; +float frame_drop_threshold = 0; +int do_deinterlace = 0; +int do_benchmark = 0; +int do_benchmark_all = 0; +int do_hex_dump = 0; +int do_pkt_dump = 0; +int copy_ts = 0; +int start_at_zero = 0; +int copy_tb = -1; +int debug_ts = 0; +int exit_on_error = 0; +int abort_on_flags = 0; +int print_stats = -1; +int qp_hist = 0; +int stdin_interaction = 1; +int frame_bits_per_raw_sample = 0; +float max_error_rate = 2.0/3; +int filter_nbthreads = 0; +int filter_complex_nbthreads = 0; +int vstats_version = 2; + + +static int intra_only = 0; +static int file_overwrite = 0; +static int no_file_overwrite = 0; +static int do_psnr = 0; +static int input_sync; +static int override_ffserver = 0; +static int input_stream_potentially_available = 0; +static int ignore_unknown_streams = 0; +static int copy_unknown_streams = 0; +static int find_stream_info = 1; + +static void uninit_options(OptionsContext *o) +{ + const OptionDef *po = options; + int i; + + /* all OPT_SPEC and OPT_STRING can be freed in generic way */ + while (po->name) { + void *dst = (uint8_t*)o + po->u.off; + + if (po->flags & OPT_SPEC) { + SpecifierOpt **so = dst; + int i, *count = (int*)(so + 1); + for (i = 0; i < *count; i++) { + av_freep(&(*so)[i].specifier); + if (po->flags & OPT_STRING) + av_freep(&(*so)[i].u.str); + } + av_freep(so); + *count = 0; + } else if (po->flags & OPT_OFFSET && po->flags & OPT_STRING) + av_freep(dst); + po++; + } + + for (i = 0; i < o->nb_stream_maps; i++) + av_freep(&o->stream_maps[i].linklabel); + av_freep(&o->stream_maps); + av_freep(&o->audio_channel_maps); + av_freep(&o->streamid_map); + av_freep(&o->attachments); +} + +static void init_options(OptionsContext *o) +{ + memset(o, 0, sizeof(*o)); + + o->stop_time = INT64_MAX; + o->mux_max_delay = 0.7; + o->start_time = AV_NOPTS_VALUE; + o->start_time_eof = AV_NOPTS_VALUE; + o->recording_time = INT64_MAX; + o->limit_filesize = UINT64_MAX; + o->chapters_input_file = INT_MAX; + o->accurate_seek = 1; +} + +static int show_hwaccels(void *optctx, const char *opt, const char *arg) +{ + int i; + + printf("Hardware acceleration methods:\n"); + for (i = 0; hwaccels[i].name; i++) { + printf("%s\n", hwaccels[i].name); + } + printf("\n"); + return 0; +} + +/* return a copy of the input with the stream specifiers removed from the keys */ +static AVDictionary *strip_specifiers(AVDictionary *dict) +{ + AVDictionaryEntry *e = NULL; + AVDictionary *ret = NULL; + + while ((e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX))) { + char *p = strchr(e->key, ':'); + + if (p) + *p = 0; + av_dict_set(&ret, e->key, e->value, 0); + if (p) + *p = ':'; + } + return ret; +} + +static int opt_abort_on(void *optctx, const char *opt, const char *arg) +{ + static const AVOption opts[] = { + { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, INT64_MAX, .unit = "flags" }, + { "empty_output" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT }, .unit = "flags" }, + { NULL }, + }; + static const AVClass class = { + .class_name = "", + .item_name = av_default_item_name, + .option = opts, + .version = LIBAVUTIL_VERSION_INT, + }; + const AVClass *pclass = &class; + + return av_opt_eval_flags(&pclass, &opts[0], arg, &abort_on_flags); +} + +static int opt_sameq(void *optctx, const char *opt, const char *arg) +{ + av_log(NULL, AV_LOG_ERROR, "Option '%s' was removed. " + "If you are looking for an option to preserve the quality (which is not " + "what -%s was for), use -qscale 0 or an equivalent quality factor option.\n", + opt, opt); + return AVERROR(EINVAL); +} + +static int opt_video_channel(void *optctx, const char *opt, const char *arg) +{ + av_log(NULL, AV_LOG_WARNING, "This option is deprecated, use -channel.\n"); + return opt_default(optctx, "channel", arg); +} + +static int opt_video_standard(void *optctx, const char *opt, const char *arg) +{ + av_log(NULL, AV_LOG_WARNING, "This option is deprecated, use -standard.\n"); + return opt_default(optctx, "standard", arg); +} + +static int opt_audio_codec(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "codec:a", arg, options); +} + +static int opt_video_codec(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "codec:v", arg, options); +} + +static int opt_subtitle_codec(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "codec:s", arg, options); +} + +static int opt_data_codec(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "codec:d", arg, options); +} + +static int opt_map(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + StreamMap *m = NULL; + int i, negative = 0, file_idx; + int sync_file_idx = -1, sync_stream_idx = 0; + char *p, *sync; + char *map; + char *allow_unused; + + if (*arg == '-') { + negative = 1; + arg++; + } + map = av_strdup(arg); + if (!map) + return AVERROR(ENOMEM); + + /* parse sync stream first, just pick first matching stream */ + if (sync = strchr(map, ',')) { + *sync = 0; + sync_file_idx = strtol(sync + 1, &sync, 0); + if (sync_file_idx >= nb_input_files || sync_file_idx < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid sync file index: %d.\n", sync_file_idx); + exit_program(1); + } + if (*sync) + sync++; + for (i = 0; i < input_files[sync_file_idx]->nb_streams; i++) + if (check_stream_specifier(input_files[sync_file_idx]->ctx, + input_files[sync_file_idx]->ctx->streams[i], sync) == 1) { + sync_stream_idx = i; + break; + } + if (i == input_files[sync_file_idx]->nb_streams) { + av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s does not " + "match any streams.\n", arg); + exit_program(1); + } + } + + + if (map[0] == '[') { + /* this mapping refers to lavfi output */ + const char *c = map + 1; + GROW_ARRAY(o->stream_maps, o->nb_stream_maps); + m = &o->stream_maps[o->nb_stream_maps - 1]; + m->linklabel = av_get_token(&c, "]"); + if (!m->linklabel) { + av_log(NULL, AV_LOG_ERROR, "Invalid output link label: %s.\n", map); + exit_program(1); + } + } else { + if (allow_unused = strchr(map, '?')) + *allow_unused = 0; + file_idx = strtol(map, &p, 0); + if (file_idx >= nb_input_files || file_idx < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid input file index: %d.\n", file_idx); + exit_program(1); + } + if (negative) + /* disable some already defined maps */ + for (i = 0; i < o->nb_stream_maps; i++) { + m = &o->stream_maps[i]; + if (file_idx == m->file_index && + check_stream_specifier(input_files[m->file_index]->ctx, + input_files[m->file_index]->ctx->streams[m->stream_index], + *p == ':' ? p + 1 : p) > 0) + m->disabled = 1; + } + else + for (i = 0; i < input_files[file_idx]->nb_streams; i++) { + if (check_stream_specifier(input_files[file_idx]->ctx, input_files[file_idx]->ctx->streams[i], + *p == ':' ? p + 1 : p) <= 0) + continue; + GROW_ARRAY(o->stream_maps, o->nb_stream_maps); + m = &o->stream_maps[o->nb_stream_maps - 1]; + + m->file_index = file_idx; + m->stream_index = i; + + if (sync_file_idx >= 0) { + m->sync_file_index = sync_file_idx; + m->sync_stream_index = sync_stream_idx; + } else { + m->sync_file_index = file_idx; + m->sync_stream_index = i; + } + } + } + + if (!m) { + if (allow_unused) { + av_log(NULL, AV_LOG_VERBOSE, "Stream map '%s' matches no streams; ignoring.\n", arg); + } else { + av_log(NULL, AV_LOG_FATAL, "Stream map '%s' matches no streams.\n" + "To ignore this, add a trailing '?' to the map.\n", arg); + exit_program(1); + } + } + + av_freep(&map); + return 0; +} + +static int opt_attach(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + GROW_ARRAY(o->attachments, o->nb_attachments); + o->attachments[o->nb_attachments - 1] = arg; + return 0; +} + +static int opt_map_channel(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + int n; + AVStream *st; + AudioChannelMap *m; + char *allow_unused; + char *mapchan; + mapchan = av_strdup(arg); + if (!mapchan) + return AVERROR(ENOMEM); + + GROW_ARRAY(o->audio_channel_maps, o->nb_audio_channel_maps); + m = &o->audio_channel_maps[o->nb_audio_channel_maps - 1]; + + /* muted channel syntax */ + n = sscanf(arg, "%d:%d.%d", &m->channel_idx, &m->ofile_idx, &m->ostream_idx); + if ((n == 1 || n == 3) && m->channel_idx == -1) { + m->file_idx = m->stream_idx = -1; + if (n == 1) + m->ofile_idx = m->ostream_idx = -1; + av_free(mapchan); + return 0; + } + + /* normal syntax */ + n = sscanf(arg, "%d.%d.%d:%d.%d", + &m->file_idx, &m->stream_idx, &m->channel_idx, + &m->ofile_idx, &m->ostream_idx); + + if (n != 3 && n != 5) { + av_log(NULL, AV_LOG_FATAL, "Syntax error, mapchan usage: " + "[file.stream.channel|-1][:syncfile:syncstream]\n"); + exit_program(1); + } + + if (n != 5) // only file.stream.channel specified + m->ofile_idx = m->ostream_idx = -1; + + /* check input */ + if (m->file_idx < 0 || m->file_idx >= nb_input_files) { + av_log(NULL, AV_LOG_FATAL, "mapchan: invalid input file index: %d\n", + m->file_idx); + exit_program(1); + } + if (m->stream_idx < 0 || + m->stream_idx >= input_files[m->file_idx]->nb_streams) { + av_log(NULL, AV_LOG_FATAL, "mapchan: invalid input file stream index #%d.%d\n", + m->file_idx, m->stream_idx); + exit_program(1); + } + st = input_files[m->file_idx]->ctx->streams[m->stream_idx]; + if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { + av_log(NULL, AV_LOG_FATAL, "mapchan: stream #%d.%d is not an audio stream.\n", + m->file_idx, m->stream_idx); + exit_program(1); + } + /* allow trailing ? to map_channel */ + if (allow_unused = strchr(mapchan, '?')) + *allow_unused = 0; + if (m->channel_idx < 0 || m->channel_idx >= st->codecpar->channels) { + if (allow_unused) { + av_log(NULL, AV_LOG_VERBOSE, "mapchan: invalid audio channel #%d.%d.%d\n", + m->file_idx, m->stream_idx, m->channel_idx); + } else { + av_log(NULL, AV_LOG_FATAL, "mapchan: invalid audio channel #%d.%d.%d\n" + "To ignore this, add a trailing '?' to the map_channel.\n", + m->file_idx, m->stream_idx, m->channel_idx); + exit_program(1); + } + + } + av_free(mapchan); + return 0; +} + +static int opt_sdp_file(void *optctx, const char *opt, const char *arg) +{ + av_free(sdp_filename); + sdp_filename = av_strdup(arg); + return 0; +} + +#if CONFIG_VAAPI +static int opt_vaapi_device(void *optctx, const char *opt, const char *arg) +{ + HWDevice *dev; + const char *prefix = "vaapi:"; + char *tmp; + int err; + tmp = av_asprintf("%s%s", prefix, arg); + if (!tmp) + return AVERROR(ENOMEM); + err = hw_device_init_from_string(tmp, &dev); + av_free(tmp); + if (err < 0) + return err; + hw_device_ctx = av_buffer_ref(dev->device_ref); + if (!hw_device_ctx) + return AVERROR(ENOMEM); + return 0; +} +#endif + +static int opt_init_hw_device(void *optctx, const char *opt, const char *arg) +{ + if (!strcmp(arg, "list")) { + enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; + printf("Supported hardware device types:\n"); + while ((type = av_hwdevice_iterate_types(type)) != + AV_HWDEVICE_TYPE_NONE) + printf("%s\n", av_hwdevice_get_type_name(type)); + printf("\n"); + exit_program(0); + } else { + return hw_device_init_from_string(arg, NULL); + } +} + +static int opt_filter_hw_device(void *optctx, const char *opt, const char *arg) +{ + if (filter_hw_device) { + av_log(NULL, AV_LOG_ERROR, "Only one filter device can be used.\n"); + return AVERROR(EINVAL); + } + filter_hw_device = hw_device_get_by_name(arg); + if (!filter_hw_device) { + av_log(NULL, AV_LOG_ERROR, "Invalid filter device %s.\n", arg); + return AVERROR(EINVAL); + } + return 0; +} + +/** + * Parse a metadata specifier passed as 'arg' parameter. + * @param arg metadata string to parse + * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) + * @param index for type c/p, chapter/program index is written here + * @param stream_spec for type s, the stream specifier is written here + */ +static void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec) +{ + if (*arg) { + *type = *arg; + switch (*arg) { + case 'g': + break; + case 's': + if (*(++arg) && *arg != ':') { + av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); + exit_program(1); + } + *stream_spec = *arg == ':' ? arg + 1 : ""; + break; + case 'c': + case 'p': + if (*(++arg) == ':') + *index = strtol(++arg, NULL, 0); + break; + default: + av_log(NULL, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); + exit_program(1); + } + } else + *type = 'g'; +} + +static int copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o) +{ + AVDictionary **meta_in = NULL; + AVDictionary **meta_out = NULL; + int i, ret = 0; + char type_in, type_out; + const char *istream_spec = NULL, *ostream_spec = NULL; + int idx_in = 0, idx_out = 0; + + parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); + parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); + + if (!ic) { + if (type_out == 'g' || !*outspec) + o->metadata_global_manual = 1; + if (type_out == 's' || !*outspec) + o->metadata_streams_manual = 1; + if (type_out == 'c' || !*outspec) + o->metadata_chapters_manual = 1; + return 0; + } + + if (type_in == 'g' || type_out == 'g') + o->metadata_global_manual = 1; + if (type_in == 's' || type_out == 's') + o->metadata_streams_manual = 1; + if (type_in == 'c' || type_out == 'c') + o->metadata_chapters_manual = 1; + + /* ic is NULL when just disabling automatic mappings */ + if (!ic) + return 0; + +#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ + if ((index) < 0 || (index) >= (nb_elems)) {\ + av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ + (desc), (index));\ + exit_program(1);\ + } + +#define SET_DICT(type, meta, context, index)\ + switch (type) {\ + case 'g':\ + meta = &context->metadata;\ + break;\ + case 'c':\ + METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ + meta = &context->chapters[index]->metadata;\ + break;\ + case 'p':\ + METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ + meta = &context->programs[index]->metadata;\ + break;\ + case 's':\ + break; /* handled separately below */ \ + default: av_assert0(0);\ + }\ + + SET_DICT(type_in, meta_in, ic, idx_in); + SET_DICT(type_out, meta_out, oc, idx_out); + + /* for input streams choose first matching stream */ + if (type_in == 's') { + for (i = 0; i < ic->nb_streams; i++) { + if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { + meta_in = &ic->streams[i]->metadata; + break; + } else if (ret < 0) + exit_program(1); + } + if (!meta_in) { + av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); + exit_program(1); + } + } + + if (type_out == 's') { + for (i = 0; i < oc->nb_streams; i++) { + if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { + meta_out = &oc->streams[i]->metadata; + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + } else if (ret < 0) + exit_program(1); + } + } else + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + + return 0; +} + +static int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + char buf[128]; + int64_t recording_timestamp = parse_time_or_die(opt, arg, 0) / 1E6; + struct tm time = *gmtime((time_t*)&recording_timestamp); + if (!strftime(buf, sizeof(buf), "creation_time=%Y-%m-%dT%H:%M:%S%z", &time)) + return -1; + parse_option(o, "metadata", buf, options); + + av_log(NULL, AV_LOG_WARNING, "%s is deprecated, set the 'creation_time' metadata " + "tag instead.\n", opt); + return 0; +} + +static AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int encoder) +{ + const AVCodecDescriptor *desc; + const char *codec_string = encoder ? "encoder" : "decoder"; + AVCodec *codec; + + codec = encoder ? + avcodec_find_encoder_by_name(name) : + avcodec_find_decoder_by_name(name); + + if (!codec && (desc = avcodec_descriptor_get_by_name(name))) { + codec = encoder ? avcodec_find_encoder(desc->id) : + avcodec_find_decoder(desc->id); + if (codec) + av_log(NULL, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", + codec_string, codec->name, desc->name); + } + + if (!codec) { + av_log(NULL, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); + exit_program(1); + } + if (codec->type != type) { + av_log(NULL, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); + exit_program(1); + } + return codec; +} + +static AVCodec *choose_decoder(OptionsContext *o, AVFormatContext *s, AVStream *st) +{ + char *codec_name = NULL; + + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); + if (codec_name) { + AVCodec *codec = find_codec_or_die(codec_name, st->codecpar->codec_type, 0); + st->codecpar->codec_id = codec->id; + return codec; + } else + return avcodec_find_decoder(st->codecpar->codec_id); +} + +/* Add all the streams from the given input file to the global + * list of input streams. */ +static void add_input_streams(OptionsContext *o, AVFormatContext *ic) +{ + int i, ret; + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + AVCodecParameters *par = st->codecpar; + InputStream *ist = av_mallocz(sizeof(*ist)); + char *framerate = NULL, *hwaccel = NULL, *hwaccel_device = NULL; + char *hwaccel_output_format = NULL; + char *codec_tag = NULL; + char *next; + char *discard_str = NULL; + const AVClass *cc = avcodec_get_class(); + const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, 0, 0); + + if (!ist) + exit_program(1); + + GROW_ARRAY(input_streams, nb_input_streams); + input_streams[nb_input_streams - 1] = ist; + + ist->st = st; + ist->file_index = nb_input_files; + ist->discard = 1; + st->discard = AVDISCARD_ALL; + ist->nb_samples = 0; + ist->min_pts = INT64_MAX; + ist->max_pts = INT64_MIN; + + ist->ts_scale = 1.0; + MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); + + ist->autorotate = 1; + MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + st->codecpar->codec_tag = tag; + } + + ist->dec = choose_decoder(o, ic, st); + ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); + + ist->reinit_filters = -1; + MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); + + MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); + ist->user_set_discard = AVDISCARD_NONE; + if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", + discard_str); + exit_program(1); + } + + ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; + + ist->dec_ctx = avcodec_alloc_context3(ist->dec); + if (!ist->dec_ctx) { + av_log(NULL, AV_LOG_ERROR, "Error allocating the decoder context.\n"); + exit_program(1); + } + + ret = avcodec_parameters_to_context(ist->dec_ctx, par); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if(!ist->dec) + ist->dec = avcodec_find_decoder(par->codec_id); +#if FF_API_LOWRES + if (av_codec_get_lowres(st->codec)) { + av_codec_set_lowres(ist->dec_ctx, av_codec_get_lowres(st->codec)); + ist->dec_ctx->width = st->codec->width; + ist->dec_ctx->height = st->codec->height; + ist->dec_ctx->coded_width = st->codec->coded_width; + ist->dec_ctx->coded_height = st->codec->coded_height; +#if FF_API_EMU_EDGE + ist->dec_ctx->flags |= CODEC_FLAG_EMU_EDGE; +#endif + } +#endif + + // avformat_find_stream_info() doesn't set this for us anymore. + ist->dec_ctx->framerate = st->avg_frame_rate; + + MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); + if (framerate && av_parse_video_rate(&ist->framerate, + framerate) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", + framerate); + exit_program(1); + } + + ist->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); + + MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); + if (hwaccel) { + if (!strcmp(hwaccel, "none")) + ist->hwaccel_id = HWACCEL_NONE; + else if (!strcmp(hwaccel, "auto")) + ist->hwaccel_id = HWACCEL_AUTO; + else { + int i; + for (i = 0; hwaccels[i].name; i++) { + if (!strcmp(hwaccels[i].name, hwaccel)) { + ist->hwaccel_id = hwaccels[i].id; + break; + } + } + + if (!ist->hwaccel_id) { + av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", + hwaccel); + av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); + for (i = 0; hwaccels[i].name; i++) + av_log(NULL, AV_LOG_FATAL, "%s ", hwaccels[i].name); + av_log(NULL, AV_LOG_FATAL, "\n"); + exit_program(1); + } + } + } + + MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); + if (hwaccel_device) { + ist->hwaccel_device = av_strdup(hwaccel_device); + if (!ist->hwaccel_device) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, + hwaccel_output_format, ic, st); + if (hwaccel_output_format) { + ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); + if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " + "format: %s", hwaccel_output_format); + } + } else { + ist->hwaccel_output_format = AV_PIX_FMT_NONE; + } + + ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; + + break; + case AVMEDIA_TYPE_AUDIO: + ist->guess_layout_max = INT_MAX; + MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); + guess_input_channel_layout(ist); + break; + case AVMEDIA_TYPE_DATA: + case AVMEDIA_TYPE_SUBTITLE: { + char *canvas_size = NULL; + if(!ist->dec) + ist->dec = avcodec_find_decoder(par->codec_id); + MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); + MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); + if (canvas_size && + av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); + exit_program(1); + } + break; + } + case AVMEDIA_TYPE_ATTACHMENT: + case AVMEDIA_TYPE_UNKNOWN: + break; + default: + abort(); + } + + ret = avcodec_parameters_from_context(par, ist->dec_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + } +} + +static void assert_file_overwrite(const char *filename) +{ + if (file_overwrite && no_file_overwrite) { + fprintf(stderr, "Error, both -y and -n supplied. Exiting.\n"); + exit_program(1); + } + + if (!file_overwrite) { + const char *proto_name = avio_find_protocol_name(filename); + if (proto_name && !strcmp(proto_name, "file") && avio_check(filename, 0) == 0) { + if (stdin_interaction && !no_file_overwrite) { + fprintf(stderr,"File '%s' already exists. Overwrite ? [y/N] ", filename); + fflush(stderr); + term_exit(); + signal(SIGINT, SIG_DFL); + if (!read_yesno()) { + av_log(NULL, AV_LOG_FATAL, "Not overwriting - exiting\n"); + exit_program(1); + } + term_init(); + } + else { + av_log(NULL, AV_LOG_FATAL, "File '%s' already exists. Exiting.\n", filename); + exit_program(1); + } + } + } +} + +static void dump_attachment(AVStream *st, const char *filename) +{ + int ret; + AVIOContext *out = NULL; + AVDictionaryEntry *e; + + if (!st->codecpar->extradata_size) { + av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", + nb_input_files - 1, st->index); + return; + } + if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) + filename = e->value; + if (!*filename) { + av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" + "in stream #%d:%d.\n", nb_input_files - 1, st->index); + exit_program(1); + } + + assert_file_overwrite(filename); + + if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", + filename); + exit_program(1); + } + + avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); + avio_flush(out); + avio_close(out); +} + +static int open_input_file(OptionsContext *o, const char *filename) +{ + InputFile *f; + AVFormatContext *ic; + AVInputFormat *file_iformat = NULL; + int err, i, ret; + int64_t timestamp; + AVDictionary *unused_opts = NULL; + AVDictionaryEntry *e = NULL; + char * video_codec_name = NULL; + char * audio_codec_name = NULL; + char *subtitle_codec_name = NULL; + char * data_codec_name = NULL; + int scan_all_pmts_set = 0; + + if (o->format) { + if (!(file_iformat = av_find_input_format(o->format))) { + av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); + exit_program(1); + } + } + + if (!strcmp(filename, "-")) + filename = "pipe:"; + + stdin_interaction &= strncmp(filename, "pipe:", 5) && + strcmp(filename, "/dev/stdin"); + + /* get default parameters from command line */ + ic = avformat_alloc_context(); + if (!ic) { + print_error(filename, AVERROR(ENOMEM)); + exit_program(1); + } + ic->flags |= AVFMT_FLAG_KEEP_SIDE_DATA; + if (o->nb_audio_sample_rate) { + av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); + } + if (o->nb_audio_channels) { + /* because we set audio_channels based on both the "ac" and + * "channel_layout" options, we need to check that the specified + * demuxer actually has the "channels" option before setting it */ + if (file_iformat && file_iformat->priv_class && + av_opt_find(&file_iformat->priv_class, "channels", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set_int(&o->g->format_opts, "channels", o->audio_channels[o->nb_audio_channels - 1].u.i, 0); + } + } + if (o->nb_frame_rates) { + /* set the format-level framerate option; + * this is important for video grabbers, e.g. x11 */ + if (file_iformat && file_iformat->priv_class && + av_opt_find(&file_iformat->priv_class, "framerate", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "framerate", + o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + } + } + if (o->nb_frame_sizes) { + av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); + } + if (o->nb_frame_pix_fmts) + av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); + + MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); + MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); + MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); + + ic->video_codec_id = video_codec_name ? + find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0)->id : AV_CODEC_ID_NONE; + ic->audio_codec_id = audio_codec_name ? + find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0)->id : AV_CODEC_ID_NONE; + ic->subtitle_codec_id= subtitle_codec_name ? + find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0)->id : AV_CODEC_ID_NONE; + ic->data_codec_id = data_codec_name ? + find_codec_or_die(data_codec_name, AVMEDIA_TYPE_DATA, 0)->id : AV_CODEC_ID_NONE; + + if (video_codec_name) + av_format_set_video_codec (ic, find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0)); + if (audio_codec_name) + av_format_set_audio_codec (ic, find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0)); + if (subtitle_codec_name) + av_format_set_subtitle_codec(ic, find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0)); + if (data_codec_name) + av_format_set_data_codec(ic, find_codec_or_die(data_codec_name, AVMEDIA_TYPE_DATA, 0)); + + ic->flags |= AVFMT_FLAG_NONBLOCK; + ic->interrupt_callback = int_cb; + + if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + /* open the input file with generic avformat function */ + err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); + if (err < 0) { + print_error(filename, err); + if (err == AVERROR_PROTOCOL_NOT_FOUND) + av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + exit_program(1); + } + if (scan_all_pmts_set) + av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + remove_avoptions(&o->g->format_opts, o->g->codec_opts); + assert_avoptions(o->g->format_opts); + + /* apply forced codec ids */ + for (i = 0; i < ic->nb_streams; i++) + choose_decoder(o, ic, ic->streams[i]); + + if (find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); + int orig_nb_streams = ic->nb_streams; + + /* If not enough info to get the stream parameters, we decode the + first frames to get it. (used in mpeg case for example) */ + ret = avformat_find_stream_info(ic, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); + if (ic->nb_streams == 0) { + avformat_close_input(&ic); + exit_program(1); + } + } + } + + if (o->start_time_eof != AV_NOPTS_VALUE) { + if (ic->duration>0) { + o->start_time = o->start_time_eof + ic->duration; + } else + av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); + } + timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time; + /* add the stream start time */ + if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) + timestamp += ic->start_time; + + /* if seeking requested, we execute it */ + if (o->start_time != AV_NOPTS_VALUE) { + int64_t seek_timestamp = timestamp; + + if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { + int dts_heuristic = 0; + for (i=0; i<ic->nb_streams; i++) { + const AVCodecParameters *par = ic->streams[i]->codecpar; + if (par->video_delay) + dts_heuristic = 1; + } + if (dts_heuristic) { + seek_timestamp -= 3*AV_TIME_BASE / 23; + } + } + ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", + filename, (double)timestamp / AV_TIME_BASE); + } + } + + /* update the current parameters so that they match the one of the input stream */ + add_input_streams(o, ic); + + /* dump the file content */ + av_dump_format(ic, nb_input_files, filename, 0); + + GROW_ARRAY(input_files, nb_input_files); + f = av_mallocz(sizeof(*f)); + if (!f) + exit_program(1); + input_files[nb_input_files - 1] = f; + + f->ctx = ic; + f->ist_index = nb_input_streams - ic->nb_streams; + f->start_time = o->start_time; + f->recording_time = o->recording_time; + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); + f->nb_streams = ic->nb_streams; + f->rate_emu = o->rate_emu; + f->accurate_seek = o->accurate_seek; + f->loop = o->loop; + f->duration = 0; + f->time_base = (AVRational){ 1, 1 }; +#if HAVE_PTHREADS + f->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8; +#endif + + /* check if all codec options have been used */ + unused_opts = strip_specifiers(o->g->codec_opts); + for (i = f->ist_index; i < nb_input_streams; i++) { + e = NULL; + while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e, + AV_DICT_IGNORE_SUFFIX))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { + const AVClass *class = avcodec_get_class(); + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVClass *fclass = avformat_get_class(); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + + if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { + av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) is not a decoding option.\n", e->key, + option->help ? option->help : "", nb_input_files - 1, + filename); + exit_program(1); + } + + av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) has not been used for any stream. The most " + "likely reason is either wrong type (e.g. a video option with " + "no video streams) or that it is a private option of some decoder " + "which was not actually used for any stream.\n", e->key, + option->help ? option->help : "", nb_input_files - 1, filename); + } + av_dict_free(&unused_opts); + + for (i = 0; i < o->nb_dump_attachment; i++) { + int j; + + for (j = 0; j < ic->nb_streams; j++) { + AVStream *st = ic->streams[j]; + + if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) + dump_attachment(st, o->dump_attachment[i].u.str); + } + } + + input_stream_potentially_available = 1; + + return 0; +} + +static uint8_t *get_line(AVIOContext *s) +{ + AVIOContext *line; + uint8_t *buf; + char c; + + if (avio_open_dyn_buf(&line) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not alloc buffer for reading preset.\n"); + exit_program(1); + } + + while ((c = avio_r8(s)) && c != '\n') + avio_w8(line, c); + avio_w8(line, 0); + avio_close_dyn_buf(line, &buf); + + return buf; +} + +static int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) +{ + int i, ret = -1; + char filename[1000]; + const char *base[3] = { getenv("AVCONV_DATADIR"), + getenv("HOME"), + AVCONV_DATADIR, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { + if (!base[i]) + continue; + if (codec_name) { + snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", codec_name, preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + if (ret < 0) { + snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + } + return ret; +} + +static int choose_encoder(OptionsContext *o, AVFormatContext *s, OutputStream *ost) +{ + enum AVMediaType type = ost->st->codecpar->codec_type; + char *codec_name = NULL; + + if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); + if (!codec_name) { + ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->filename, + NULL, ost->st->codecpar->codec_type); + ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id); + if (!ost->enc) { + av_log(NULL, AV_LOG_FATAL, "Automatic encoder selection failed for " + "output stream #%d:%d. Default encoder for format %s (codec %s) is " + "probably disabled. Please choose an encoder manually.\n", + ost->file_index, ost->index, s->oformat->name, + avcodec_get_name(ost->st->codecpar->codec_id)); + return AVERROR_ENCODER_NOT_FOUND; + } + } else if (!strcmp(codec_name, "copy")) + ost->stream_copy = 1; + else { + ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1); + ost->st->codecpar->codec_id = ost->enc->id; + } + ost->encoding_needed = !ost->stream_copy; + } else { + /* no encoding supported for other media types */ + ost->stream_copy = 1; + ost->encoding_needed = 0; + } + + return 0; +} + +static OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, enum AVMediaType type, int source_index) +{ + OutputStream *ost; + AVStream *st = avformat_new_stream(oc, NULL); + int idx = oc->nb_streams - 1, ret = 0; + const char *bsfs = NULL, *time_base = NULL; + char *next, *codec_tag = NULL; + double qscale = -1; + int i; + + if (!st) { + av_log(NULL, AV_LOG_FATAL, "Could not alloc stream.\n"); + exit_program(1); + } + + if (oc->nb_streams - 1 < o->nb_streamid_map) + st->id = o->streamid_map[oc->nb_streams - 1]; + + GROW_ARRAY(output_streams, nb_output_streams); + if (!(ost = av_mallocz(sizeof(*ost)))) + exit_program(1); + output_streams[nb_output_streams - 1] = ost; + + ost->file_index = nb_output_files - 1; + ost->index = idx; + ost->st = st; + st->codecpar->codec_type = type; + + ret = choose_encoder(o, oc, ost); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error selecting an encoder for stream " + "%d:%d\n", ost->file_index, ost->index); + exit_program(1); + } + + ost->enc_ctx = avcodec_alloc_context3(ost->enc); + if (!ost->enc_ctx) { + av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding context.\n"); + exit_program(1); + } + ost->enc_ctx->codec_type = type; + + ost->ref_par = avcodec_parameters_alloc(); + if (!ost->ref_par) { + av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding parameters.\n"); + exit_program(1); + } + + if (ost->enc) { + AVIOContext *s = NULL; + char *buf = NULL, *arg = NULL, *preset = NULL; + + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc); + + MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); + if (preset && (!(ret = get_preset_file_2(preset, ost->enc->name, &s)))) { + do { + buf = get_line(s); + if (!buf[0] || buf[0] == '#') { + av_free(buf); + continue; + } + if (!(arg = strchr(buf, '='))) { + av_log(NULL, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); + exit_program(1); + } + *arg++ = 0; + av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); + av_free(buf); + } while (!s->eof_reached); + avio_closep(&s); + } + if (ret) { + av_log(NULL, AV_LOG_FATAL, + "Preset %s specified for stream %d:%d, but could not be opened.\n", + preset, ost->file_index, ost->index); + exit_program(1); + } + } else { + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); + } + + MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + st->time_base = q; + } + + MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.den <= 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + ost->enc_timebase = q; + } + + ost->max_frames = INT64_MAX; + MATCH_PER_STREAM_OPT(max_frames, i64, ost->max_frames, oc, st); + for (i = 0; i<o->nb_max_frames; i++) { + char *p = o->max_frames[i].specifier; + if (!*p && type != AVMEDIA_TYPE_VIDEO) { + av_log(NULL, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); + break; + } + } + + ost->copy_prior_start = -1; + MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); + + MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); + while (bsfs && *bsfs) { + const AVBitStreamFilter *filter; + char *bsf, *bsf_options_str, *bsf_name; + + bsf = av_get_token(&bsfs, ","); + if (!bsf) + exit_program(1); + bsf_name = av_strtok(bsf, "=", &bsf_options_str); + if (!bsf_name) + exit_program(1); + + filter = av_bsf_get_by_name(bsf_name); + if (!filter) { + av_log(NULL, AV_LOG_FATAL, "Unknown bitstream filter %s\n", bsf_name); + exit_program(1); + } + + ost->bsf_ctx = av_realloc_array(ost->bsf_ctx, + ost->nb_bitstream_filters + 1, + sizeof(*ost->bsf_ctx)); + if (!ost->bsf_ctx) + exit_program(1); + + ret = av_bsf_alloc(filter, &ost->bsf_ctx[ost->nb_bitstream_filters]); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error allocating a bitstream filter context\n"); + exit_program(1); + } + + ost->nb_bitstream_filters++; + + if (bsf_options_str && filter->priv_class) { + const AVOption *opt = av_opt_next(ost->bsf_ctx[ost->nb_bitstream_filters-1]->priv_data, NULL); + const char * shorthand[2] = {NULL}; + + if (opt) + shorthand[0] = opt->name; + + ret = av_opt_set_from_string(ost->bsf_ctx[ost->nb_bitstream_filters-1]->priv_data, bsf_options_str, shorthand, "=", ":"); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing options for bitstream filter %s\n", bsf_name); + exit_program(1); + } + } + av_freep(&bsf); + + if (*bsfs) + bsfs++; + } + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + ost->st->codecpar->codec_tag = + ost->enc_ctx->codec_tag = tag; + } + + MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); + if (qscale >= 0) { + ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; + ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; + } + + MATCH_PER_STREAM_OPT(disposition, str, ost->disposition, oc, st); + ost->disposition = av_strdup(ost->disposition); + + ost->max_muxing_queue_size = 128; + MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ost->max_muxing_queue_size, oc, st); + ost->max_muxing_queue_size *= sizeof(AVPacket); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); + + av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); + if (ost->enc && av_get_exact_bits_per_sample(ost->enc->id) == 24) + av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); + + av_dict_copy(&ost->resample_opts, o->g->resample_opts, 0); + + ost->source_index = source_index; + if (source_index >= 0) { + ost->sync_ist = input_streams[source_index]; + input_streams[source_index]->discard = 0; + input_streams[source_index]->st->discard = input_streams[source_index]->user_set_discard; + } + ost->last_mux_dts = AV_NOPTS_VALUE; + + ost->muxing_queue = av_fifo_alloc(8 * sizeof(AVPacket)); + if (!ost->muxing_queue) + exit_program(1); + + return ost; +} + +static void parse_matrix_coeffs(uint16_t *dest, const char *str) +{ + int i; + const char *p = str; + for (i = 0;; i++) { + dest[i] = atoi(p); + if (i == 63) + break; + p = strchr(p, ','); + if (!p) { + av_log(NULL, AV_LOG_FATAL, "Syntax error in matrix \"%s\" at coeff %d\n", str, i); + exit_program(1); + } + p++; + } +} + +/* read file contents into a string */ +static uint8_t *read_file(const char *filename) +{ + AVIOContext *pb = NULL; + AVIOContext *dyn_buf = NULL; + int ret = avio_open(&pb, filename, AVIO_FLAG_READ); + uint8_t buf[1024], *str; + + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); + return NULL; + } + + ret = avio_open_dyn_buf(&dyn_buf); + if (ret < 0) { + avio_closep(&pb); + return NULL; + } + while ((ret = avio_read(pb, buf, sizeof(buf))) > 0) + avio_write(dyn_buf, buf, ret); + avio_w8(dyn_buf, 0); + avio_closep(&pb); + + ret = avio_close_dyn_buf(dyn_buf, &str); + if (ret < 0) + return NULL; + return str; +} + +static char *get_ost_filters(OptionsContext *o, AVFormatContext *oc, + OutputStream *ost) +{ + AVStream *st = ost->st; + + if (ost->filters_script && ost->filters) { + av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for " + "output stream #%d:%d.\n", nb_output_files, st->index); + exit_program(1); + } + + if (ost->filters_script) + return read_file(ost->filters_script); + else if (ost->filters) + return av_strdup(ost->filters); + + return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? + "null" : "anull"); +} + +static void check_streamcopy_filters(OptionsContext *o, AVFormatContext *oc, + const OutputStream *ost, enum AVMediaType type) +{ + if (ost->filters_script || ost->filters) { + av_log(NULL, AV_LOG_ERROR, + "%s '%s' was defined for %s output stream %d:%d but codec copy was selected.\n" + "Filtering and streamcopy cannot be used together.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script, + av_get_media_type_string(type), ost->file_index, ost->index); + exit_program(1); + } +} + +static OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + AVStream *st; + OutputStream *ost; + AVCodecContext *video_enc; + char *frame_rate = NULL, *frame_aspect_ratio = NULL; + + ost = new_output_stream(o, oc, AVMEDIA_TYPE_VIDEO, source_index); + st = ost->st; + video_enc = ost->enc_ctx; + + MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); + if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); + exit_program(1); + } + if (frame_rate && video_sync_method == VSYNC_PASSTHROUGH) + av_log(NULL, AV_LOG_ERROR, "Using -vsync 0 and -r can produce invalid output files\n"); + + MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); + if (frame_aspect_ratio) { + AVRational q; + if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); + exit_program(1); + } + ost->frame_aspect_ratio = q; + } + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (!ost->stream_copy) { + const char *p = NULL; + char *frame_size = NULL; + char *frame_pix_fmt = NULL; + char *intra_matrix = NULL, *inter_matrix = NULL; + char *chroma_intra_matrix = NULL; + int do_pass = 0; + int i; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); + if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + + video_enc->bits_per_raw_sample = frame_bits_per_raw_sample; + MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); + if (frame_pix_fmt && *frame_pix_fmt == '+') { + ost->keep_pix_fmt = 1; + if (!*++frame_pix_fmt) + frame_pix_fmt = NULL; + } + if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); + exit_program(1); + } + st->sample_aspect_ratio = video_enc->sample_aspect_ratio; + + if (intra_only) + video_enc->gop_size = 0; + MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); + if (intra_matrix) { + if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); + exit_program(1); + } + parse_matrix_coeffs(video_enc->intra_matrix, intra_matrix); + } + MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); + if (chroma_intra_matrix) { + uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); + if (!p) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); + exit_program(1); + } + av_codec_set_chroma_intra_matrix(video_enc, p); + parse_matrix_coeffs(p, chroma_intra_matrix); + } + MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); + if (inter_matrix) { + if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for inter matrix.\n"); + exit_program(1); + } + parse_matrix_coeffs(video_enc->inter_matrix, inter_matrix); + } + + MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); + for (i = 0; p; i++) { + int start, end, q; + int e = sscanf(p, "%d,%d,%d", &start, &end, &q); + if (e != 3) { + av_log(NULL, AV_LOG_FATAL, "error parsing rc_override\n"); + exit_program(1); + } + video_enc->rc_override = + av_realloc_array(video_enc->rc_override, + i + 1, sizeof(RcOverride)); + if (!video_enc->rc_override) { + av_log(NULL, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); + exit_program(1); + } + video_enc->rc_override[i].start_frame = start; + video_enc->rc_override[i].end_frame = end; + if (q > 0) { + video_enc->rc_override[i].qscale = q; + video_enc->rc_override[i].quality_factor = 1.0; + } + else { + video_enc->rc_override[i].qscale = 0; + video_enc->rc_override[i].quality_factor = -q/100.0; + } + p = strchr(p, '/'); + if (p) p++; + } + video_enc->rc_override_count = i; + + if (do_psnr) + video_enc->flags|= AV_CODEC_FLAG_PSNR; + + /* two pass mode */ + MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); + if (do_pass) { + if (do_pass & 1) { + video_enc->flags |= AV_CODEC_FLAG_PASS1; + av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); + } + if (do_pass & 2) { + video_enc->flags |= AV_CODEC_FLAG_PASS2; + av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); + } + } + + MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); + if (ost->logfile_prefix && + !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) + exit_program(1); + + if (do_pass) { + char logfilename[1024]; + FILE *f; + + snprintf(logfilename, sizeof(logfilename), "%s-%d.log", + ost->logfile_prefix ? ost->logfile_prefix : + DEFAULT_PASS_LOGFILENAME_PREFIX, + i); + if (!strcmp(ost->enc->name, "libx264")) { + av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); + } else { + if (video_enc->flags & AV_CODEC_FLAG_PASS2) { + char *logbuffer = read_file(logfilename); + + if (!logbuffer) { + av_log(NULL, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", + logfilename); + exit_program(1); + } + video_enc->stats_in = logbuffer; + } + if (video_enc->flags & AV_CODEC_FLAG_PASS1) { + f = av_fopen_utf8(logfilename, "wb"); + if (!f) { + av_log(NULL, AV_LOG_FATAL, + "Cannot write log file '%s' for pass-1 encoding: %s\n", + logfilename, strerror(errno)); + exit_program(1); + } + ost->logfile = f; + } + } + } + + MATCH_PER_STREAM_OPT(forced_key_frames, str, ost->forced_keyframes, oc, st); + if (ost->forced_keyframes) + ost->forced_keyframes = av_strdup(ost->forced_keyframes); + + MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); + + ost->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); + + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + } else { + MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, ost->copy_initial_nonkeyframes, oc ,st); + } + + if (ost->stream_copy) + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); + + return ost; +} + +static OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + int n; + AVStream *st; + OutputStream *ost; + AVCodecContext *audio_enc; + + ost = new_output_stream(o, oc, AVMEDIA_TYPE_AUDIO, source_index); + st = ost->st; + + audio_enc = ost->enc_ctx; + audio_enc->codec_type = AVMEDIA_TYPE_AUDIO; + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (!ost->stream_copy) { + char *sample_fmt = NULL; + + MATCH_PER_STREAM_OPT(audio_channels, i, audio_enc->channels, oc, st); + + MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); + if (sample_fmt && + (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); + + MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); + ost->apad = av_strdup(ost->apad); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + + /* check for channel mapping for this audio stream */ + for (n = 0; n < o->nb_audio_channel_maps; n++) { + AudioChannelMap *map = &o->audio_channel_maps[n]; + if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && + (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { + InputStream *ist; + + if (map->channel_idx == -1) { + ist = NULL; + } else if (ost->source_index < 0) { + av_log(NULL, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", + ost->file_index, ost->st->index); + continue; + } else { + ist = input_streams[ost->source_index]; + } + + if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { + if (av_reallocp_array(&ost->audio_channels_map, + ost->audio_channels_mapped + 1, + sizeof(*ost->audio_channels_map) + ) < 0 ) + exit_program(1); + + ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; + } + } + } + } + + if (ost->stream_copy) + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); + + return ost; +} + +static OutputStream *new_data_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + OutputStream *ost; + + ost = new_output_stream(o, oc, AVMEDIA_TYPE_DATA, source_index); + if (!ost->stream_copy) { + av_log(NULL, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_unknown_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + OutputStream *ost; + + ost = new_output_stream(o, oc, AVMEDIA_TYPE_UNKNOWN, source_index); + if (!ost->stream_copy) { + av_log(NULL, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_attachment_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + OutputStream *ost = new_output_stream(o, oc, AVMEDIA_TYPE_ATTACHMENT, source_index); + ost->stream_copy = 1; + ost->finished = 1; + return ost; +} + +static OutputStream *new_subtitle_stream(OptionsContext *o, AVFormatContext *oc, int source_index) +{ + AVStream *st; + OutputStream *ost; + AVCodecContext *subtitle_enc; + + ost = new_output_stream(o, oc, AVMEDIA_TYPE_SUBTITLE, source_index); + st = ost->st; + subtitle_enc = ost->enc_ctx; + + subtitle_enc->codec_type = AVMEDIA_TYPE_SUBTITLE; + + MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, ost->copy_initial_nonkeyframes, oc, st); + + if (!ost->stream_copy) { + char *frame_size = NULL; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); + if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + } + + return ost; +} + +/* arg format is "output-stream-index:streamid-value". */ +static int opt_streamid(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + int idx; + char *p; + char idx_str[16]; + + av_strlcpy(idx_str, arg, sizeof(idx_str)); + p = strchr(idx_str, ':'); + if (!p) { + av_log(NULL, AV_LOG_FATAL, + "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", + arg, opt); + exit_program(1); + } + *p++ = '\0'; + idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); + o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); + o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); + return 0; +} + +static int copy_chapters(InputFile *ifile, OutputFile *ofile, int copy_metadata) +{ + AVFormatContext *is = ifile->ctx; + AVFormatContext *os = ofile->ctx; + AVChapter **tmp; + int i; + + tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); + if (!tmp) + return AVERROR(ENOMEM); + os->chapters = tmp; + + for (i = 0; i < is->nb_chapters; i++) { + AVChapter *in_ch = is->chapters[i], *out_ch; + int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; + int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, + AV_TIME_BASE_Q, in_ch->time_base); + int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : + av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); + + + if (in_ch->end < ts_off) + continue; + if (rt != INT64_MAX && in_ch->start > rt + ts_off) + break; + + out_ch = av_mallocz(sizeof(AVChapter)); + if (!out_ch) + return AVERROR(ENOMEM); + + out_ch->id = in_ch->id; + out_ch->time_base = in_ch->time_base; + out_ch->start = FFMAX(0, in_ch->start - ts_off); + out_ch->end = FFMIN(rt, in_ch->end - ts_off); + + if (copy_metadata) + av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); + + os->chapters[os->nb_chapters++] = out_ch; + } + return 0; +} + +static int read_ffserver_streams(OptionsContext *o, AVFormatContext *s, const char *filename) +{ + int i, err; + AVFormatContext *ic = avformat_alloc_context(); + + ic->flags |= AVFMT_FLAG_KEEP_SIDE_DATA; + ic->interrupt_callback = int_cb; + err = avformat_open_input(&ic, filename, NULL, NULL); + if (err < 0) + return err; + /* copy stream format */ + for(i=0;i<ic->nb_streams;i++) { + AVStream *st; + OutputStream *ost; + AVCodec *codec; + const char *enc_config; + + codec = avcodec_find_encoder(ic->streams[i]->codecpar->codec_id); + if (!codec) { + av_log(s, AV_LOG_ERROR, "no encoder found for codec id %i\n", ic->streams[i]->codecpar->codec_id); + return AVERROR(EINVAL); + } + if (codec->type == AVMEDIA_TYPE_AUDIO) + opt_audio_codec(o, "c:a", codec->name); + else if (codec->type == AVMEDIA_TYPE_VIDEO) + opt_video_codec(o, "c:v", codec->name); + ost = new_output_stream(o, s, codec->type, -1); + st = ost->st; + + avcodec_get_context_defaults3(st->codec, codec); + enc_config = av_stream_get_recommended_encoder_configuration(ic->streams[i]); + if (enc_config) { + AVDictionary *opts = NULL; + av_dict_parse_string(&opts, enc_config, "=", ",", 0); + av_opt_set_dict2(st->codec, &opts, AV_OPT_SEARCH_CHILDREN); + av_dict_free(&opts); + } + + if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && !ost->stream_copy) + choose_sample_fmt(st, codec); + else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !ost->stream_copy) + choose_pixel_fmt(st, st->codec, codec, st->codecpar->format); + avcodec_copy_context(ost->enc_ctx, st->codec); + if (enc_config) + av_dict_parse_string(&ost->encoder_opts, enc_config, "=", ",", 0); + } + + avformat_close_input(&ic); + return err; +} + +static void init_output_filter(OutputFilter *ofilter, OptionsContext *o, + AVFormatContext *oc) +{ + OutputStream *ost; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(o, oc, -1); break; + case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(o, oc, -1); break; + default: + av_log(NULL, AV_LOG_FATAL, "Only video and audio filters are supported " + "currently.\n"); + exit_program(1); + } + + ost->source_index = -1; + ost->filter = ofilter; + + ofilter->ost = ost; + ofilter->format = -1; + + if (ost->stream_copy) { + av_log(NULL, AV_LOG_ERROR, "Streamcopy requested for output stream %d:%d, " + "which is fed from a complex filtergraph. Filtering and streamcopy " + "cannot be used together.\n", ost->file_index, ost->index); + exit_program(1); + } + + if (ost->avfilter && (ost->filters || ost->filters_script)) { + const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; + av_log(NULL, AV_LOG_ERROR, + "%s '%s' was specified through the %s option " + "for output stream %d:%d, which is fed from a complex filtergraph.\n" + "%s and -filter_complex cannot be used together for the same stream.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script, + opt, ost->file_index, ost->index, opt); + exit_program(1); + } + + avfilter_inout_free(&ofilter->out_tmp); +} + +static int init_complex_filters(void) +{ + int i, ret = 0; + + for (i = 0; i < nb_filtergraphs; i++) { + ret = init_complex_filtergraph(filtergraphs[i]); + if (ret < 0) + return ret; + } + return 0; +} + +static int open_output_file(OptionsContext *o, const char *filename) +{ + AVFormatContext *oc; + int i, j, err; + AVOutputFormat *file_oformat; + OutputFile *of; + OutputStream *ost; + InputStream *ist; + AVDictionary *unused_opts = NULL; + AVDictionaryEntry *e = NULL; + int format_flags = 0; + + if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { + o->stop_time = INT64_MAX; + av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { + int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; + if (o->stop_time <= start_time) { + av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + o->recording_time = o->stop_time - start_time; + } + } + + GROW_ARRAY(output_files, nb_output_files); + of = av_mallocz(sizeof(*of)); + if (!of) + exit_program(1); + output_files[nb_output_files - 1] = of; + + of->ost_index = nb_output_streams; + of->recording_time = o->recording_time; + of->start_time = o->start_time; + of->limit_filesize = o->limit_filesize; + of->shortest = o->shortest; + av_dict_copy(&of->opts, o->g->format_opts, 0); + + if (!strcmp(filename, "-")) + filename = "pipe:"; + + err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); + if (!oc) { + print_error(filename, err); + exit_program(1); + } + + of->ctx = oc; + if (o->recording_time != INT64_MAX) + oc->duration = o->recording_time; + + file_oformat= oc->oformat; + oc->interrupt_callback = int_cb; + + e = av_dict_get(o->g->format_opts, "fflags", NULL, 0); + if (e) { + const AVOption *o = av_opt_find(oc, "fflags", NULL, 0, 0); + av_opt_eval_flags(oc, o, e->value, &format_flags); + } + + /* create streams for all unlabeled output pads */ + for (i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + for (j = 0; j < fg->nb_outputs; j++) { + OutputFilter *ofilter = fg->outputs[j]; + + if (!ofilter->out_tmp || ofilter->out_tmp->name) + continue; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: o->video_disable = 1; break; + case AVMEDIA_TYPE_AUDIO: o->audio_disable = 1; break; + case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break; + } + init_output_filter(ofilter, o, oc); + } + } + + /* ffserver seeking with date=... needs a date reference */ + if (!strcmp(file_oformat->name, "ffm") && + !(format_flags & AVFMT_FLAG_BITEXACT) && + av_strstart(filename, "http:", NULL)) { + int err = parse_option(o, "metadata", "creation_time=now", options); + if (err < 0) { + print_error(filename, err); + exit_program(1); + } + } + + if (!strcmp(file_oformat->name, "ffm") && !override_ffserver && + av_strstart(filename, "http:", NULL)) { + int j; + /* special case for files sent to ffserver: we get the stream + parameters from ffserver */ + int err = read_ffserver_streams(o, oc, filename); + if (err < 0) { + print_error(filename, err); + exit_program(1); + } + for(j = nb_output_streams - oc->nb_streams; j < nb_output_streams; j++) { + ost = output_streams[j]; + for (i = 0; i < nb_input_streams; i++) { + ist = input_streams[i]; + if(ist->st->codecpar->codec_type == ost->st->codecpar->codec_type){ + ost->sync_ist= ist; + ost->source_index= i; + if(ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ost->avfilter = av_strdup("anull"); + if(ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) ost->avfilter = av_strdup("null"); + ist->discard = 0; + ist->st->discard = ist->user_set_discard; + break; + } + } + if(!ost->sync_ist){ + av_log(NULL, AV_LOG_FATAL, "Missing %s stream which is required by this ffm\n", av_get_media_type_string(ost->st->codecpar->codec_type)); + exit_program(1); + } + } + } else if (!o->nb_stream_maps) { + char *subtitle_codec_name = NULL; + /* pick the "best" stream of each type */ + + /* video: highest resolution */ + if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) { + int area = 0, idx = -1; + int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); + for (i = 0; i < nb_input_streams; i++) { + int new_area; + ist = input_streams[i]; + new_area = ist->st->codecpar->width * ist->st->codecpar->height + 100000000*!!ist->st->codec_info_nb_frames; + if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + new_area = 1; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + new_area > area) { + if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + continue; + area = new_area; + idx = i; + } + } + if (idx >= 0) + new_video_stream(o, oc, idx); + } + + /* audio: most channels */ + if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) { + int best_score = 0, idx = -1; + for (i = 0; i < nb_input_streams; i++) { + int score; + ist = input_streams[i]; + score = ist->st->codecpar->channels + 100000000*!!ist->st->codec_info_nb_frames; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && + score > best_score) { + best_score = score; + idx = i; + } + } + if (idx >= 0) + new_audio_stream(o, oc, idx); + } + + /* subtitles: pick first */ + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); + if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) { + for (i = 0; i < nb_input_streams; i++) + if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id); + AVCodecDescriptor const *output_descriptor = NULL; + AVCodec const *output_codec = + avcodec_find_encoder(oc->oformat->subtitle_codec); + int input_props = 0, output_props = 0; + if (output_codec) + output_descriptor = avcodec_descriptor_get(output_codec->id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (subtitle_codec_name || + input_props & output_props || + // Map dvb teletext which has neither property to any output subtitle encoder + input_descriptor && output_descriptor && + (!input_descriptor->props || + !output_descriptor->props)) { + new_subtitle_stream(o, oc, i); + break; + } + } + } + /* Data only if codec id match */ + if (!o->data_disable ) { + enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA); + for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) { + if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA + && input_streams[i]->st->codecpar->codec_id == codec_id ) + new_data_stream(o, oc, i); + } + } + } else { + for (i = 0; i < o->nb_stream_maps; i++) { + StreamMap *map = &o->stream_maps[i]; + + if (map->disabled) + continue; + + if (map->linklabel) { + FilterGraph *fg; + OutputFilter *ofilter = NULL; + int j, k; + + for (j = 0; j < nb_filtergraphs; j++) { + fg = filtergraphs[j]; + for (k = 0; k < fg->nb_outputs; k++) { + AVFilterInOut *out = fg->outputs[k]->out_tmp; + if (out && !strcmp(out->name, map->linklabel)) { + ofilter = fg->outputs[k]; + goto loop_end; + } + } + } +loop_end: + if (!ofilter) { + av_log(NULL, AV_LOG_FATAL, "Output with label '%s' does not exist " + "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); + exit_program(1); + } + init_output_filter(ofilter, o, oc); + } else { + int src_idx = input_files[map->file_index]->ist_index + map->stream_index; + + ist = input_streams[input_files[map->file_index]->ist_index + map->stream_index]; + if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + continue; + if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + continue; + if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + continue; + if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + continue; + + ost = NULL; + switch (ist->st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: ost = new_video_stream (o, oc, src_idx); break; + case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream (o, oc, src_idx); break; + case AVMEDIA_TYPE_SUBTITLE: ost = new_subtitle_stream (o, oc, src_idx); break; + case AVMEDIA_TYPE_DATA: ost = new_data_stream (o, oc, src_idx); break; + case AVMEDIA_TYPE_ATTACHMENT: ost = new_attachment_stream(o, oc, src_idx); break; + case AVMEDIA_TYPE_UNKNOWN: + if (copy_unknown_streams) { + ost = new_unknown_stream (o, oc, src_idx); + break; + } + default: + av_log(NULL, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, + "Cannot map stream #%d:%d - unsupported type.\n", + map->file_index, map->stream_index); + if (!ignore_unknown_streams) { + av_log(NULL, AV_LOG_FATAL, + "If you want unsupported types ignored instead " + "of failing, please use the -ignore_unknown option\n" + "If you want them copied, please use -copy_unknown\n"); + exit_program(1); + } + } + if (ost) + ost->sync_ist = input_streams[ input_files[map->sync_file_index]->ist_index + + map->sync_stream_index]; + } + } + } + + /* handle attached files */ + for (i = 0; i < o->nb_attachments; i++) { + AVIOContext *pb; + uint8_t *attachment; + const char *p; + int64_t len; + + if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not open attachment file %s.\n", + o->attachments[i]); + exit_program(1); + } + if ((len = avio_size(pb)) <= 0) { + av_log(NULL, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", + o->attachments[i]); + exit_program(1); + } + if (!(attachment = av_malloc(len))) { + av_log(NULL, AV_LOG_FATAL, "Attachment %s too large to fit into memory.\n", + o->attachments[i]); + exit_program(1); + } + avio_read(pb, attachment, len); + + ost = new_attachment_stream(o, oc, -1); + ost->stream_copy = 0; + ost->attachment_filename = o->attachments[i]; + ost->st->codecpar->extradata = attachment; + ost->st->codecpar->extradata_size = len; + + p = strrchr(o->attachments[i], '/'); + av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); + avio_closep(&pb); + } + +#if FF_API_LAVF_AVCTX + for (i = nb_output_streams - oc->nb_streams; i < nb_output_streams; i++) { //for all streams of this output file + AVDictionaryEntry *e; + ost = output_streams[i]; + + if ((ost->stream_copy || ost->attachment_filename) + && (e = av_dict_get(o->g->codec_opts, "flags", NULL, AV_DICT_IGNORE_SUFFIX)) + && (!e->key[5] || check_stream_specifier(oc, ost->st, e->key+6))) + if (av_opt_set(ost->st->codec, "flags", e->value, 0) < 0) + exit_program(1); + } +#endif + + if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { + av_dump_format(oc, nb_output_files - 1, oc->filename, 1); + av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1); + exit_program(1); + } + + /* check if all codec options have been used */ + unused_opts = strip_specifiers(o->g->codec_opts); + for (i = of->ost_index; i < nb_output_streams; i++) { + e = NULL; + while ((e = av_dict_get(output_streams[i]->encoder_opts, "", e, + AV_DICT_IGNORE_SUFFIX))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { + const AVClass *class = avcodec_get_class(); + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVClass *fclass = avformat_get_class(); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + + if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { + av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " + "output file #%d (%s) is not an encoding option.\n", e->key, + option->help ? option->help : "", nb_output_files - 1, + filename); + exit_program(1); + } + + // gop_timecode is injected by generic code but not always used + if (!strcmp(e->key, "gop_timecode")) + continue; + + av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " + "output file #%d (%s) has not been used for any stream. The most " + "likely reason is either wrong type (e.g. a video option with " + "no video streams) or that it is a private option of some encoder " + "which was not actually used for any stream.\n", e->key, + option->help ? option->help : "", nb_output_files - 1, filename); + } + av_dict_free(&unused_opts); + + /* set the decoding_needed flags and create simple filtergraphs */ + for (i = of->ost_index; i < nb_output_streams; i++) { + OutputStream *ost = output_streams[i]; + + if (ost->encoding_needed && ost->source_index >= 0) { + InputStream *ist = input_streams[ost->source_index]; + ist->decoding_needed |= DECODING_FOR_OST; + + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + err = init_simple_filtergraph(ist, ost); + if (err < 0) { + av_log(NULL, AV_LOG_ERROR, + "Error initializing a simple filtergraph between streams " + "%d:%d->%d:%d\n", ist->file_index, ost->source_index, + nb_output_files - 1, ost->st->index); + exit_program(1); + } + } + } + + /* set the filter output constraints */ + if (ost->filter) { + OutputFilter *f = ost->filter; + int count; + switch (ost->enc_ctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + f->frame_rate = ost->frame_rate; + f->width = ost->enc_ctx->width; + f->height = ost->enc_ctx->height; + if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { + f->format = ost->enc_ctx->pix_fmt; + } else if (ost->enc->pix_fmts) { + count = 0; + while (ost->enc->pix_fmts[count] != AV_PIX_FMT_NONE) + count++; + f->formats = av_mallocz_array(count + 1, sizeof(*f->formats)); + if (!f->formats) + exit_program(1); + memcpy(f->formats, ost->enc->pix_fmts, (count + 1) * sizeof(*f->formats)); + } + break; + case AVMEDIA_TYPE_AUDIO: + if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { + f->format = ost->enc_ctx->sample_fmt; + } else if (ost->enc->sample_fmts) { + count = 0; + while (ost->enc->sample_fmts[count] != AV_SAMPLE_FMT_NONE) + count++; + f->formats = av_mallocz_array(count + 1, sizeof(*f->formats)); + if (!f->formats) + exit_program(1); + memcpy(f->formats, ost->enc->sample_fmts, (count + 1) * sizeof(*f->formats)); + } + if (ost->enc_ctx->sample_rate) { + f->sample_rate = ost->enc_ctx->sample_rate; + } else if (ost->enc->supported_samplerates) { + count = 0; + while (ost->enc->supported_samplerates[count]) + count++; + f->sample_rates = av_mallocz_array(count + 1, sizeof(*f->sample_rates)); + if (!f->sample_rates) + exit_program(1); + memcpy(f->sample_rates, ost->enc->supported_samplerates, + (count + 1) * sizeof(*f->sample_rates)); + } + if (ost->enc_ctx->channels) { + f->channel_layout = av_get_default_channel_layout(ost->enc_ctx->channels); + } else if (ost->enc->channel_layouts) { + count = 0; + while (ost->enc->channel_layouts[count]) + count++; + f->channel_layouts = av_mallocz_array(count + 1, sizeof(*f->channel_layouts)); + if (!f->channel_layouts) + exit_program(1); + memcpy(f->channel_layouts, ost->enc->channel_layouts, + (count + 1) * sizeof(*f->channel_layouts)); + } + break; + } + } + } + + /* check filename in case of an image number is expected */ + if (oc->oformat->flags & AVFMT_NEEDNUMBER) { + if (!av_filename_number_test(oc->filename)) { + print_error(oc->filename, AVERROR(EINVAL)); + exit_program(1); + } + } + + if (!(oc->oformat->flags & AVFMT_NOSTREAMS) && !input_stream_potentially_available) { + av_log(NULL, AV_LOG_ERROR, + "No input streams but output needs an input stream\n"); + exit_program(1); + } + + if (!(oc->oformat->flags & AVFMT_NOFILE)) { + /* test if it already exists to avoid losing precious files */ + assert_file_overwrite(filename); + + /* open the file */ + if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, + &oc->interrupt_callback, + &of->opts)) < 0) { + print_error(filename, err); + exit_program(1); + } + } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) + assert_file_overwrite(filename); + + if (o->mux_preload) { + av_dict_set_int(&of->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); + } + oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); + + /* copy metadata */ + for (i = 0; i < o->nb_metadata_map; i++) { + char *p; + int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); + + if (in_file_index >= nb_input_files) { + av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index); + exit_program(1); + } + copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, + in_file_index >= 0 ? + input_files[in_file_index]->ctx : NULL, o); + } + + /* copy chapters */ + if (o->chapters_input_file >= nb_input_files) { + if (o->chapters_input_file == INT_MAX) { + /* copy chapters from the first input file that has them*/ + o->chapters_input_file = -1; + for (i = 0; i < nb_input_files; i++) + if (input_files[i]->ctx->nb_chapters) { + o->chapters_input_file = i; + break; + } + } else { + av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", + o->chapters_input_file); + exit_program(1); + } + } + if (o->chapters_input_file >= 0) + copy_chapters(input_files[o->chapters_input_file], of, + !o->metadata_chapters_manual); + + /* copy global metadata by default */ + if (!o->metadata_global_manual && nb_input_files){ + av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, + AV_DICT_DONT_OVERWRITE); + if(o->recording_time != INT64_MAX) + av_dict_set(&oc->metadata, "duration", NULL, 0); + av_dict_set(&oc->metadata, "creation_time", NULL, 0); + } + if (!o->metadata_streams_manual) + for (i = of->ost_index; i < nb_output_streams; i++) { + InputStream *ist; + if (output_streams[i]->source_index < 0) /* this is true e.g. for attached files */ + continue; + ist = input_streams[output_streams[i]->source_index]; + av_dict_copy(&output_streams[i]->st->metadata, ist->st->metadata, AV_DICT_DONT_OVERWRITE); + if (!output_streams[i]->stream_copy) { + av_dict_set(&output_streams[i]->st->metadata, "encoder", NULL, 0); + } + } + + /* process manually set programs */ + for (i = 0; i < o->nb_program; i++) { + const char *p = o->program[i].u.str; + int progid = i+1; + AVProgram *program; + + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key || !*p2) { + av_freep(&to_dealloc); + av_freep(&key); + break; + } + p2++; + + if (!strcmp(key, "program_num")) + progid = strtol(p2, NULL, 0); + av_freep(&to_dealloc); + av_freep(&key); + } + + program = av_new_program(oc, progid); + + p = o->program[i].u.str; + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key) { + av_log(NULL, AV_LOG_FATAL, + "No '=' character in program string %s.\n", + p2); + exit_program(1); + } + if (!*p2) + exit_program(1); + p2++; + + if (!strcmp(key, "title")) { + av_dict_set(&program->metadata, "title", p2, 0); + } else if (!strcmp(key, "program_num")) { + } else if (!strcmp(key, "st")) { + int st_num = strtol(p2, NULL, 0); + av_program_add_stream_index(oc, progid, st_num); + } else { + av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key); + exit_program(1); + } + av_freep(&to_dealloc); + av_freep(&key); + } + } + + /* process manually set metadata */ + for (i = 0; i < o->nb_metadata; i++) { + AVDictionary **m; + char type, *val; + const char *stream_spec; + int index = 0, j, ret = 0; + + val = strchr(o->metadata[i].u.str, '='); + if (!val) { + av_log(NULL, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", + o->metadata[i].u.str); + exit_program(1); + } + *val++ = 0; + + parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); + if (type == 's') { + for (j = 0; j < oc->nb_streams; j++) { + ost = output_streams[nb_output_streams - oc->nb_streams + j]; + if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { + if (!strcmp(o->metadata[i].u.str, "rotate")) { + char *tail; + double theta = av_strtod(val, &tail); + if (!*tail) { + ost->rotate_overridden = 1; + ost->rotate_override_value = theta; + } + } else { + av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); + } + } else if (ret < 0) + exit_program(1); + } + } + else { + switch (type) { + case 'g': + m = &oc->metadata; + break; + case 'c': + if (index < 0 || index >= oc->nb_chapters) { + av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->chapters[index]->metadata; + break; + case 'p': + if (index < 0 || index >= oc->nb_programs) { + av_log(NULL, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->programs[index]->metadata; + break; + default: + av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); + exit_program(1); + } + av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); + } + } + + return 0; +} + +static int opt_target(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; + static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; + + if (!strncmp(arg, "pal-", 4)) { + norm = PAL; + arg += 4; + } else if (!strncmp(arg, "ntsc-", 5)) { + norm = NTSC; + arg += 5; + } else if (!strncmp(arg, "film-", 5)) { + norm = FILM; + arg += 5; + } else { + /* Try to determine PAL/NTSC by peeking in the input files */ + if (nb_input_files) { + int i, j, fr; + for (j = 0; j < nb_input_files; j++) { + for (i = 0; i < input_files[j]->nb_streams; i++) { + AVStream *st = input_files[j]->ctx->streams[i]; + if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + fr = st->time_base.den * 1000 / st->time_base.num; + if (fr == 25000) { + norm = PAL; + break; + } else if ((fr == 29970) || (fr == 23976)) { + norm = NTSC; + break; + } + } + if (norm != UNKNOWN) + break; + } + } + if (norm != UNKNOWN) + av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); + } + + if (norm == UNKNOWN) { + av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); + av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); + av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); + exit_program(1); + } + + if (!strcmp(arg, "vcd")) { + opt_video_codec(o, "c:v", "mpeg1video"); + opt_audio_codec(o, "c:a", "mp2"); + parse_option(o, "f", "vcd", options); + + parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); + parse_option(o, "r", frame_rates[norm], options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); + + opt_default(NULL, "b:v", "1150000"); + opt_default(NULL, "maxrate:v", "1150000"); + opt_default(NULL, "minrate:v", "1150000"); + opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; + + opt_default(NULL, "b:a", "224000"); + parse_option(o, "ar", "44100", options); + parse_option(o, "ac", "2", options); + + opt_default(NULL, "packetsize", "2324"); + opt_default(NULL, "muxrate", "1411200"); // 2352 * 75 * 8; + + /* We have to offset the PTS, so that it is consistent with the SCR. + SCR starts at 36000, but the first two packs contain only padding + and the first pack from the other stream, respectively, may also have + been written before. + So the real data starts at SCR 36000+3*1200. */ + o->mux_preload = (36000 + 3 * 1200) / 90000.0; // 0.44 + } else if (!strcmp(arg, "svcd")) { + + opt_video_codec(o, "c:v", "mpeg2video"); + opt_audio_codec(o, "c:a", "mp2"); + parse_option(o, "f", "svcd", options); + + parse_option(o, "s", norm == PAL ? "480x576" : "480x480", options); + parse_option(o, "r", frame_rates[norm], options); + parse_option(o, "pix_fmt", "yuv420p", options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); + + opt_default(NULL, "b:v", "2040000"); + opt_default(NULL, "maxrate:v", "2516000"); + opt_default(NULL, "minrate:v", "0"); // 1145000; + opt_default(NULL, "bufsize:v", "1835008"); // 224*1024*8; + opt_default(NULL, "scan_offset", "1"); + + opt_default(NULL, "b:a", "224000"); + parse_option(o, "ar", "44100", options); + + opt_default(NULL, "packetsize", "2324"); + + } else if (!strcmp(arg, "dvd")) { + + opt_video_codec(o, "c:v", "mpeg2video"); + opt_audio_codec(o, "c:a", "ac3"); + parse_option(o, "f", "dvd", options); + + parse_option(o, "s", norm == PAL ? "720x576" : "720x480", options); + parse_option(o, "r", frame_rates[norm], options); + parse_option(o, "pix_fmt", "yuv420p", options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); + + opt_default(NULL, "b:v", "6000000"); + opt_default(NULL, "maxrate:v", "9000000"); + opt_default(NULL, "minrate:v", "0"); // 1500000; + opt_default(NULL, "bufsize:v", "1835008"); // 224*1024*8; + + opt_default(NULL, "packetsize", "2048"); // from www.mpucoder.com: DVD sectors contain 2048 bytes of data, this is also the size of one pack. + opt_default(NULL, "muxrate", "10080000"); // from mplex project: data_rate = 1260000. mux_rate = data_rate * 8 + + opt_default(NULL, "b:a", "448000"); + parse_option(o, "ar", "48000", options); + + } else if (!strncmp(arg, "dv", 2)) { + + parse_option(o, "f", "dv", options); + + parse_option(o, "s", norm == PAL ? "720x576" : "720x480", options); + parse_option(o, "pix_fmt", !strncmp(arg, "dv50", 4) ? "yuv422p" : + norm == PAL ? "yuv420p" : "yuv411p", options); + parse_option(o, "r", frame_rates[norm], options); + + parse_option(o, "ar", "48000", options); + parse_option(o, "ac", "2", options); + + } else { + av_log(NULL, AV_LOG_ERROR, "Unknown target: %s\n", arg); + return AVERROR(EINVAL); + } + + av_dict_copy(&o->g->codec_opts, codec_opts, AV_DICT_DONT_OVERWRITE); + av_dict_copy(&o->g->format_opts, format_opts, AV_DICT_DONT_OVERWRITE); + + return 0; +} + +static int opt_vstats_file(void *optctx, const char *opt, const char *arg) +{ + av_free (vstats_filename); + vstats_filename = av_strdup (arg); + return 0; +} + +static int opt_vstats(void *optctx, const char *opt, const char *arg) +{ + char filename[40]; + time_t today2 = time(NULL); + struct tm *today = localtime(&today2); + + if (!today) { // maybe tomorrow + av_log(NULL, AV_LOG_FATAL, "Unable to get current time: %s\n", strerror(errno)); + exit_program(1); + } + + snprintf(filename, sizeof(filename), "vstats_%02d%02d%02d.log", today->tm_hour, today->tm_min, + today->tm_sec); + return opt_vstats_file(NULL, opt, filename); +} + +static int opt_video_frames(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "frames:v", arg, options); +} + +static int opt_audio_frames(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "frames:a", arg, options); +} + +static int opt_data_frames(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "frames:d", arg, options); +} + +static int opt_default_new(OptionsContext *o, const char *opt, const char *arg) +{ + int ret; + AVDictionary *cbak = codec_opts; + AVDictionary *fbak = format_opts; + codec_opts = NULL; + format_opts = NULL; + + ret = opt_default(NULL, opt, arg); + + av_dict_copy(&o->g->codec_opts , codec_opts, 0); + av_dict_copy(&o->g->format_opts, format_opts, 0); + av_dict_free(&codec_opts); + av_dict_free(&format_opts); + codec_opts = cbak; + format_opts = fbak; + + return ret; +} + +static int opt_preset(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + FILE *f=NULL; + char filename[1000], line[1000], tmp_line[1000]; + const char *codec_name = NULL; + + tmp_line[0] = *opt; + tmp_line[1] = 0; + MATCH_PER_TYPE_OPT(codec_names, str, codec_name, NULL, tmp_line); + + if (!(f = get_preset_file(filename, sizeof(filename), arg, *opt == 'f', codec_name))) { + if(!strncmp(arg, "libx264-lossless", strlen("libx264-lossless"))){ + av_log(NULL, AV_LOG_FATAL, "Please use -preset <speed> -qp 0\n"); + }else + av_log(NULL, AV_LOG_FATAL, "File for preset '%s' not found\n", arg); + exit_program(1); + } + + while (fgets(line, sizeof(line), f)) { + char *key = tmp_line, *value, *endptr; + + if (strcspn(line, "#\n\r") == 0) + continue; + av_strlcpy(tmp_line, line, sizeof(tmp_line)); + if (!av_strtok(key, "=", &value) || + !av_strtok(value, "\r\n", &endptr)) { + av_log(NULL, AV_LOG_FATAL, "%s: Invalid syntax: '%s'\n", filename, line); + exit_program(1); + } + av_log(NULL, AV_LOG_DEBUG, "ffpreset[%s]: set '%s' = '%s'\n", filename, key, value); + + if (!strcmp(key, "acodec")) opt_audio_codec (o, key, value); + else if (!strcmp(key, "vcodec")) opt_video_codec (o, key, value); + else if (!strcmp(key, "scodec")) opt_subtitle_codec(o, key, value); + else if (!strcmp(key, "dcodec")) opt_data_codec (o, key, value); + else if (opt_default_new(o, key, value) < 0) { + av_log(NULL, AV_LOG_FATAL, "%s: Invalid option or argument: '%s', parsed as '%s' = '%s'\n", + filename, line, key, value); + exit_program(1); + } + } + + fclose(f); + + return 0; +} + +static int opt_old2new(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + char *s = av_asprintf("%s:%c", opt + 1, *opt); + int ret = parse_option(o, s, arg, options); + av_free(s); + return ret; +} + +static int opt_bitrate(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + + if(!strcmp(opt, "ab")){ + av_dict_set(&o->g->codec_opts, "b:a", arg, 0); + return 0; + } else if(!strcmp(opt, "b")){ + av_log(NULL, AV_LOG_WARNING, "Please use -b:a or -b:v, -b is ambiguous\n"); + av_dict_set(&o->g->codec_opts, "b:v", arg, 0); + return 0; + } + av_dict_set(&o->g->codec_opts, opt, arg, 0); + return 0; +} + +static int opt_qscale(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + char *s; + int ret; + if(!strcmp(opt, "qscale")){ + av_log(NULL, AV_LOG_WARNING, "Please use -q:a or -q:v, -qscale is ambiguous\n"); + return parse_option(o, "q:v", arg, options); + } + s = av_asprintf("q%s", opt + 6); + ret = parse_option(o, s, arg, options); + av_free(s); + return ret; +} + +static int opt_profile(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + if(!strcmp(opt, "profile")){ + av_log(NULL, AV_LOG_WARNING, "Please use -profile:a or -profile:v, -profile is ambiguous\n"); + av_dict_set(&o->g->codec_opts, "profile:v", arg, 0); + return 0; + } + av_dict_set(&o->g->codec_opts, opt, arg, 0); + return 0; +} + +static int opt_video_filters(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "filter:v", arg, options); +} + +static int opt_audio_filters(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "filter:a", arg, options); +} + +static int opt_vsync(void *optctx, const char *opt, const char *arg) +{ + if (!av_strcasecmp(arg, "cfr")) video_sync_method = VSYNC_CFR; + else if (!av_strcasecmp(arg, "vfr")) video_sync_method = VSYNC_VFR; + else if (!av_strcasecmp(arg, "passthrough")) video_sync_method = VSYNC_PASSTHROUGH; + else if (!av_strcasecmp(arg, "drop")) video_sync_method = VSYNC_DROP; + + if (video_sync_method == VSYNC_AUTO) + video_sync_method = parse_number_or_die("vsync", arg, OPT_INT, VSYNC_AUTO, VSYNC_VFR); + return 0; +} + +static int opt_timecode(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + char *tcr = av_asprintf("timecode=%s", arg); + int ret = parse_option(o, "metadata:g", tcr, options); + if (ret >= 0) + ret = av_dict_set(&o->g->codec_opts, "gop_timecode", arg, 0); + av_free(tcr); + return ret; +} + +static int opt_channel_layout(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + char layout_str[32]; + char *stream_str; + char *ac_str; + int ret, channels, ac_str_size; + uint64_t layout; + + layout = av_get_channel_layout(arg); + if (!layout) { + av_log(NULL, AV_LOG_ERROR, "Unknown channel layout: %s\n", arg); + return AVERROR(EINVAL); + } + snprintf(layout_str, sizeof(layout_str), "%"PRIu64, layout); + ret = opt_default_new(o, opt, layout_str); + if (ret < 0) + return ret; + + /* set 'ac' option based on channel layout */ + channels = av_get_channel_layout_nb_channels(layout); + snprintf(layout_str, sizeof(layout_str), "%d", channels); + stream_str = strchr(opt, ':'); + ac_str_size = 3 + (stream_str ? strlen(stream_str) : 0); + ac_str = av_mallocz(ac_str_size); + if (!ac_str) + return AVERROR(ENOMEM); + av_strlcpy(ac_str, "ac", 3); + if (stream_str) + av_strlcat(ac_str, stream_str, ac_str_size); + ret = parse_option(o, ac_str, layout_str, options); + av_free(ac_str); + + return ret; +} + +static int opt_audio_qscale(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + return parse_option(o, "q:a", arg, options); +} + +static int opt_filter_complex(void *optctx, const char *opt, const char *arg) +{ + GROW_ARRAY(filtergraphs, nb_filtergraphs); + if (!(filtergraphs[nb_filtergraphs - 1] = av_mallocz(sizeof(*filtergraphs[0])))) + return AVERROR(ENOMEM); + filtergraphs[nb_filtergraphs - 1]->index = nb_filtergraphs - 1; + filtergraphs[nb_filtergraphs - 1]->graph_desc = av_strdup(arg); + if (!filtergraphs[nb_filtergraphs - 1]->graph_desc) + return AVERROR(ENOMEM); + + input_stream_potentially_available = 1; + + return 0; +} + +static int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) +{ + uint8_t *graph_desc = read_file(arg); + if (!graph_desc) + return AVERROR(EINVAL); + + GROW_ARRAY(filtergraphs, nb_filtergraphs); + if (!(filtergraphs[nb_filtergraphs - 1] = av_mallocz(sizeof(*filtergraphs[0])))) + return AVERROR(ENOMEM); + filtergraphs[nb_filtergraphs - 1]->index = nb_filtergraphs - 1; + filtergraphs[nb_filtergraphs - 1]->graph_desc = graph_desc; + + input_stream_potentially_available = 1; + + return 0; +} + +void show_help_default(const char *opt, const char *arg) +{ + /* per-file options have at least one of those set */ + const int per_file = OPT_SPEC | OPT_OFFSET | OPT_PERFILE; + int show_advanced = 0, show_avoptions = 0; + + if (opt && *opt) { + if (!strcmp(opt, "long")) + show_advanced = 1; + else if (!strcmp(opt, "full")) + show_advanced = show_avoptions = 1; + else + av_log(NULL, AV_LOG_ERROR, "Unknown help option '%s'.\n", opt); + } + + show_usage(); + + printf("Getting help:\n" + " -h -- print basic options\n" + " -h long -- print more options\n" + " -h full -- print all options (including all format and codec specific options, very long)\n" + " -h type=name -- print all options for the named decoder/encoder/demuxer/muxer/filter\n" + " See man %s for detailed description of the options.\n" + "\n", program_name); + + show_help_options(options, "Print help / information / capabilities:", + OPT_EXIT, 0, 0); + + show_help_options(options, "Global options (affect whole program " + "instead of just one file:", + 0, per_file | OPT_EXIT | OPT_EXPERT, 0); + if (show_advanced) + show_help_options(options, "Advanced global options:", OPT_EXPERT, + per_file | OPT_EXIT, 0); + + show_help_options(options, "Per-file main options:", 0, + OPT_EXPERT | OPT_AUDIO | OPT_VIDEO | OPT_SUBTITLE | + OPT_EXIT, per_file); + if (show_advanced) + show_help_options(options, "Advanced per-file options:", + OPT_EXPERT, OPT_AUDIO | OPT_VIDEO | OPT_SUBTITLE, per_file); + + show_help_options(options, "Video options:", + OPT_VIDEO, OPT_EXPERT | OPT_AUDIO, 0); + if (show_advanced) + show_help_options(options, "Advanced Video options:", + OPT_EXPERT | OPT_VIDEO, OPT_AUDIO, 0); + + show_help_options(options, "Audio options:", + OPT_AUDIO, OPT_EXPERT | OPT_VIDEO, 0); + if (show_advanced) + show_help_options(options, "Advanced Audio options:", + OPT_EXPERT | OPT_AUDIO, OPT_VIDEO, 0); + show_help_options(options, "Subtitle options:", + OPT_SUBTITLE, 0, 0); + printf("\n"); + + if (show_avoptions) { + int flags = AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM; + show_help_children(avcodec_get_class(), flags); + show_help_children(avformat_get_class(), flags); +#if CONFIG_SWSCALE + show_help_children(sws_get_class(), flags); +#endif + show_help_children(swr_get_class(), AV_OPT_FLAG_AUDIO_PARAM); + show_help_children(avfilter_get_class(), AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM); + } +} + +void show_usage(void) +{ + av_log(NULL, AV_LOG_INFO, "Hyper fast Audio and Video encoder\n"); + av_log(NULL, AV_LOG_INFO, "usage: %s [options] [[infile options] -i infile]... {[outfile options] outfile}...\n", program_name); + av_log(NULL, AV_LOG_INFO, "\n"); +} + +enum OptGroup { + GROUP_OUTFILE, + GROUP_INFILE, +}; + +static const OptionGroupDef groups[] = { + [GROUP_OUTFILE] = { "output url", NULL, OPT_OUTPUT }, + [GROUP_INFILE] = { "input url", "i", OPT_INPUT }, +}; + +static int open_files(OptionGroupList *l, const char *inout, + int (*open_file)(OptionsContext*, const char*)) +{ + int i, ret; + + for (i = 0; i < l->nb_groups; i++) { + OptionGroup *g = &l->groups[i]; + OptionsContext o; + + init_options(&o); + o.g = g; + + ret = parse_optgroup(&o, g); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing options for %s file " + "%s.\n", inout, g->arg); + return ret; + } + + av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\n", inout, g->arg); + ret = open_file(&o, g->arg); + uninit_options(&o); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening %s file %s.\n", + inout, g->arg); + return ret; + } + av_log(NULL, AV_LOG_DEBUG, "Successfully opened the file.\n"); + } + + return 0; +} + +int ffmpeg_parse_options(int argc, char **argv) +{ + OptionParseContext octx; + uint8_t error[128]; + int ret; + + memset(&octx, 0, sizeof(octx)); + + /* split the commandline into an internal representation */ + ret = split_commandline(&octx, argc, argv, options, groups, + FF_ARRAY_ELEMS(groups)); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error splitting the argument list: "); + goto fail; + } + + /* apply global options */ + ret = parse_optgroup(NULL, &octx.global_opts); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error parsing global options: "); + goto fail; + } + + /* configure terminal and setup signal handlers */ + term_init(); + + /* open input files */ + ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); + goto fail; + } + + /* create the complex filtergraphs */ + ret = init_complex_filters(); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n"); + goto fail; + } + + /* open output files */ + ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); + goto fail; + } + + check_filter_outputs(); + +fail: + uninit_parse_context(&octx); + if (ret < 0) { + av_strerror(ret, error, sizeof(error)); + av_log(NULL, AV_LOG_FATAL, "%s\n", error); + } + return ret; +} + +static int opt_progress(void *optctx, const char *opt, const char *arg) +{ + AVIOContext *avio = NULL; + int ret; + + if (!strcmp(arg, "-")) + arg = "pipe:"; + ret = avio_open2(&avio, arg, AVIO_FLAG_WRITE, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to open progress URL \"%s\": %s\n", + arg, av_err2str(ret)); + return ret; + } + progress_avio = avio; + return 0; +} + +#define OFFSET(x) offsetof(OptionsContext, x) +const OptionDef options[] = { + /* main options */ + CMDUTILS_COMMON_OPTIONS + { "f", HAS_ARG | OPT_STRING | OPT_OFFSET | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(format) }, + "force format", "fmt" }, + { "y", OPT_BOOL, { &file_overwrite }, + "overwrite output files" }, + { "n", OPT_BOOL, { &no_file_overwrite }, + "never overwrite output files" }, + { "ignore_unknown", OPT_BOOL, { &ignore_unknown_streams }, + "Ignore unknown stream types" }, + { "copy_unknown", OPT_BOOL | OPT_EXPERT, { ©_unknown_streams }, + "Copy unknown stream types" }, + { "c", HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(codec_names) }, + "codec name", "codec" }, + { "codec", HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(codec_names) }, + "codec name", "codec" }, + { "pre", HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(presets) }, + "preset name", "preset" }, + { "map", HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_OUTPUT, { .func_arg = opt_map }, + "set input stream mapping", + "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, + { "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_map_channel }, + "map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" }, + { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(metadata_map) }, + "set metadata information of outfile from infile", + "outfile[,metadata]:infile[,metadata]" }, + { "map_chapters", HAS_ARG | OPT_INT | OPT_EXPERT | OPT_OFFSET | + OPT_OUTPUT, { .off = OFFSET(chapters_input_file) }, + "set chapters mapping", "input_file_index" }, + { "t", HAS_ARG | OPT_TIME | OPT_OFFSET | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(recording_time) }, + "record or transcode \"duration\" seconds of audio/video", + "duration" }, + { "to", HAS_ARG | OPT_TIME | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(stop_time) }, + "record or transcode stop time", "time_stop" }, + { "fs", HAS_ARG | OPT_INT64 | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(limit_filesize) }, + "set the limit file size in bytes", "limit_size" }, + { "ss", HAS_ARG | OPT_TIME | OPT_OFFSET | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(start_time) }, + "set the start time offset", "time_off" }, + { "sseof", HAS_ARG | OPT_TIME | OPT_OFFSET | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(start_time_eof) }, + "set the start time offset relative to EOF", "time_off" }, + { "seek_timestamp", HAS_ARG | OPT_INT | OPT_OFFSET | + OPT_INPUT, { .off = OFFSET(seek_timestamp) }, + "enable/disable seeking by timestamp with -ss" }, + { "accurate_seek", OPT_BOOL | OPT_OFFSET | OPT_EXPERT | + OPT_INPUT, { .off = OFFSET(accurate_seek) }, + "enable/disable accurate seeking with -ss" }, + { "itsoffset", HAS_ARG | OPT_TIME | OPT_OFFSET | + OPT_EXPERT | OPT_INPUT, { .off = OFFSET(input_ts_offset) }, + "set the input ts offset", "time_off" }, + { "itsscale", HAS_ARG | OPT_DOUBLE | OPT_SPEC | + OPT_EXPERT | OPT_INPUT, { .off = OFFSET(ts_scale) }, + "set the input ts scale", "scale" }, + { "timestamp", HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_recording_timestamp }, + "set the recording timestamp ('now' to set the current time)", "time" }, + { "metadata", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(metadata) }, + "add metadata", "string=string" }, + { "program", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(program) }, + "add program with specified streams", "title=string:st=number..." }, + { "dframes", HAS_ARG | OPT_PERFILE | OPT_EXPERT | + OPT_OUTPUT, { .func_arg = opt_data_frames }, + "set the number of data frames to output", "number" }, + { "benchmark", OPT_BOOL | OPT_EXPERT, { &do_benchmark }, + "add timings for benchmarking" }, + { "benchmark_all", OPT_BOOL | OPT_EXPERT, { &do_benchmark_all }, + "add timings for each task" }, + { "progress", HAS_ARG | OPT_EXPERT, { .func_arg = opt_progress }, + "write program-readable progress information", "url" }, + { "stdin", OPT_BOOL | OPT_EXPERT, { &stdin_interaction }, + "enable or disable interaction on standard input" }, + { "timelimit", HAS_ARG | OPT_EXPERT, { .func_arg = opt_timelimit }, + "set max runtime in seconds", "limit" }, + { "dump", OPT_BOOL | OPT_EXPERT, { &do_pkt_dump }, + "dump each input packet" }, + { "hex", OPT_BOOL | OPT_EXPERT, { &do_hex_dump }, + "when dumping packets, also dump the payload" }, + { "re", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | + OPT_INPUT, { .off = OFFSET(rate_emu) }, + "read input at native frame rate", "" }, + { "target", HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_target }, + "specify target file type (\"vcd\", \"svcd\", \"dvd\", \"dv\" or \"dv50\" " + "with optional prefixes \"pal-\", \"ntsc-\" or \"film-\")", "type" }, + { "vsync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_vsync }, + "video sync method", "" }, + { "frame_drop_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &frame_drop_threshold }, + "frame drop threshold", "" }, + { "async", HAS_ARG | OPT_INT | OPT_EXPERT, { &audio_sync_method }, + "audio sync method", "" }, + { "adrift_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &audio_drift_threshold }, + "audio drift threshold", "threshold" }, + { "copyts", OPT_BOOL | OPT_EXPERT, { ©_ts }, + "copy timestamps" }, + { "start_at_zero", OPT_BOOL | OPT_EXPERT, { &start_at_zero }, + "shift input timestamps to start at 0 when using copyts" }, + { "copytb", HAS_ARG | OPT_INT | OPT_EXPERT, { ©_tb }, + "copy input stream time base when stream copying", "mode" }, + { "shortest", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | + OPT_OUTPUT, { .off = OFFSET(shortest) }, + "finish encoding within shortest input" }, + { "apad", OPT_STRING | HAS_ARG | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(apad) }, + "audio pad", "" }, + { "dts_delta_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &dts_delta_threshold }, + "timestamp discontinuity delta threshold", "threshold" }, + { "dts_error_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &dts_error_threshold }, + "timestamp error delta threshold", "threshold" }, + { "xerror", OPT_BOOL | OPT_EXPERT, { &exit_on_error }, + "exit on error", "error" }, + { "abort_on", HAS_ARG | OPT_EXPERT, { .func_arg = opt_abort_on }, + "abort on the specified condition flags", "flags" }, + { "copyinkf", OPT_BOOL | OPT_EXPERT | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(copy_initial_nonkeyframes) }, + "copy initial non-keyframes" }, + { "copypriorss", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(copy_prior_start) }, + "copy or discard frames before start time" }, + { "frames", OPT_INT64 | HAS_ARG | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(max_frames) }, + "set the number of frames to output", "number" }, + { "tag", OPT_STRING | HAS_ARG | OPT_SPEC | + OPT_EXPERT | OPT_OUTPUT | OPT_INPUT, { .off = OFFSET(codec_tags) }, + "force codec tag/fourcc", "fourcc/tag" }, + { "q", HAS_ARG | OPT_EXPERT | OPT_DOUBLE | + OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(qscale) }, + "use fixed quality scale (VBR)", "q" }, + { "qscale", HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_OUTPUT, { .func_arg = opt_qscale }, + "use fixed quality scale (VBR)", "q" }, + { "profile", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_profile }, + "set profile", "profile" }, + { "filter", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(filters) }, + "set stream filtergraph", "filter_graph" }, + { "filter_threads", HAS_ARG | OPT_INT, { &filter_nbthreads }, + "number of non-complex filter threads" }, + { "filter_script", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(filter_scripts) }, + "read stream filtergraph description from a file", "filename" }, + { "reinit_filter", HAS_ARG | OPT_INT | OPT_SPEC | OPT_INPUT, { .off = OFFSET(reinit_filters) }, + "reinit filtergraph on input parameter changes", "" }, + { "filter_complex", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex }, + "create a complex filtergraph", "graph_description" }, + { "filter_complex_threads", HAS_ARG | OPT_INT, { &filter_complex_nbthreads }, + "number of threads for -filter_complex" }, + { "lavfi", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex }, + "create a complex filtergraph", "graph_description" }, + { "filter_complex_script", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex_script }, + "read complex filtergraph description from a file", "filename" }, + { "stats", OPT_BOOL, { &print_stats }, + "print progress report during encoding", }, + { "attach", HAS_ARG | OPT_PERFILE | OPT_EXPERT | + OPT_OUTPUT, { .func_arg = opt_attach }, + "add an attachment to the output file", "filename" }, + { "dump_attachment", HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_EXPERT | OPT_INPUT, { .off = OFFSET(dump_attachment) }, + "extract an attachment into a file", "filename" }, + { "stream_loop", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_INPUT | + OPT_OFFSET, { .off = OFFSET(loop) }, "set number of times input stream shall be looped", "loop count" }, + { "debug_ts", OPT_BOOL | OPT_EXPERT, { &debug_ts }, + "print timestamp debugging info" }, + { "max_error_rate", HAS_ARG | OPT_FLOAT, { &max_error_rate }, + "maximum error rate", "ratio of errors (0.0: no errors, 1.0: 100% errors) above which ffmpeg returns an error instead of success." }, + { "discard", OPT_STRING | HAS_ARG | OPT_SPEC | + OPT_INPUT, { .off = OFFSET(discard) }, + "discard", "" }, + { "disposition", OPT_STRING | HAS_ARG | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(disposition) }, + "disposition", "" }, + { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT, + { .off = OFFSET(thread_queue_size) }, + "set the maximum number of queued packets from the demuxer" }, + { "find_stream_info", OPT_BOOL | OPT_PERFILE | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + "read and decode the streams to fill missing information with heuristics" }, + + /* video options */ + { "vframes", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_video_frames }, + "set the number of video frames to output", "number" }, + { "r", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_rates) }, + "set frame rate (Hz value, fraction or abbreviation)", "rate" }, + { "s", OPT_VIDEO | HAS_ARG | OPT_SUBTITLE | OPT_STRING | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_sizes) }, + "set frame size (WxH or abbreviation)", "size" }, + { "aspect", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(frame_aspect_ratios) }, + "set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)", "aspect" }, + { "pix_fmt", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_pix_fmts) }, + "set pixel format", "format" }, + { "bits_per_raw_sample", OPT_VIDEO | OPT_INT | HAS_ARG, { &frame_bits_per_raw_sample }, + "set the number of bits per raw sample", "number" }, + { "intra", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &intra_only }, + "deprecated use -g 1" }, + { "vn", OPT_VIDEO | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(video_disable) }, + "disable video" }, + { "rc_override", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(rc_overrides) }, + "rate control override for specific intervals", "override" }, + { "vcodec", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_INPUT | + OPT_OUTPUT, { .func_arg = opt_video_codec }, + "force video codec ('copy' to copy stream)", "codec" }, + { "sameq", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_sameq }, + "Removed" }, + { "same_quant", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_sameq }, + "Removed" }, + { "timecode", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_timecode }, + "set initial TimeCode value.", "hh:mm:ss[:;.]ff" }, + { "pass", OPT_VIDEO | HAS_ARG | OPT_SPEC | OPT_INT | OPT_OUTPUT, { .off = OFFSET(pass) }, + "select the pass number (1 to 3)", "n" }, + { "passlogfile", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(passlogfiles) }, + "select two pass log file name prefix", "prefix" }, + { "deinterlace", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &do_deinterlace }, + "this option is deprecated, use the yadif filter instead" }, + { "psnr", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &do_psnr }, + "calculate PSNR of compressed frames" }, + { "vstats", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_vstats }, + "dump video coding statistics to file" }, + { "vstats_file", OPT_VIDEO | HAS_ARG | OPT_EXPERT , { .func_arg = opt_vstats_file }, + "dump video coding statistics to file", "file" }, + { "vstats_version", OPT_VIDEO | OPT_INT | HAS_ARG | OPT_EXPERT , { &vstats_version }, + "Version of the vstats format to use."}, + { "vf", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_video_filters }, + "set video filters", "filter_graph" }, + { "intra_matrix", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(intra_matrices) }, + "specify intra matrix coeffs", "matrix" }, + { "inter_matrix", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(inter_matrices) }, + "specify inter matrix coeffs", "matrix" }, + { "chroma_intra_matrix", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(chroma_intra_matrices) }, + "specify intra matrix coeffs", "matrix" }, + { "top", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_INT| OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(top_field_first) }, + "top=1/bottom=0/auto=-1 field first", "" }, + { "vtag", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_old2new }, + "force video tag/fourcc", "fourcc/tag" }, + { "qphist", OPT_VIDEO | OPT_BOOL | OPT_EXPERT , { &qp_hist }, + "show QP histogram" }, + { "force_fps", OPT_VIDEO | OPT_BOOL | OPT_EXPERT | OPT_SPEC | + OPT_OUTPUT, { .off = OFFSET(force_fps) }, + "force the selected framerate, disable the best supported framerate selection" }, + { "streamid", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_OUTPUT, { .func_arg = opt_streamid }, + "set the value of an outfile streamid", "streamIndex:value" }, + { "force_key_frames", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | + OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(forced_key_frames) }, + "force key frames at specified timestamps", "timestamps" }, + { "ab", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, + "audio bitrate (please use -b:a)", "bitrate" }, + { "b", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, + "video bitrate (please use -b:v)", "bitrate" }, + { "hwaccel", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | + OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccels) }, + "use HW accelerated decoding", "hwaccel name" }, + { "hwaccel_device", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | + OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccel_devices) }, + "select a device for HW acceleration", "devicename" }, + { "hwaccel_output_format", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | + OPT_SPEC | OPT_INPUT, { .off = OFFSET(hwaccel_output_formats) }, + "select output format used with HW accelerated decoding", "format" }, +#if CONFIG_VDA || CONFIG_VIDEOTOOLBOX + { "videotoolbox_pixfmt", HAS_ARG | OPT_STRING | OPT_EXPERT, { &videotoolbox_pixfmt}, "" }, +#endif + { "hwaccels", OPT_EXIT, { .func_arg = show_hwaccels }, + "show available HW acceleration methods" }, + { "autorotate", HAS_ARG | OPT_BOOL | OPT_SPEC | + OPT_EXPERT | OPT_INPUT, { .off = OFFSET(autorotate) }, + "automatically insert correct rotate filters" }, + { "hwaccel_lax_profile_check", OPT_BOOL | OPT_EXPERT, { &hwaccel_lax_profile_check}, + "attempt to decode anyway if HW accelerated decoder's supported profiles do not exactly match the stream" }, + + /* audio options */ + { "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames }, + "set the number of audio frames to output", "number" }, + { "aq", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_qscale }, + "set audio quality (codec-specific)", "quality", }, + { "ar", OPT_AUDIO | HAS_ARG | OPT_INT | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(audio_sample_rate) }, + "set audio sampling rate (in Hz)", "rate" }, + { "ac", OPT_AUDIO | HAS_ARG | OPT_INT | OPT_SPEC | + OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(audio_channels) }, + "set number of audio channels", "channels" }, + { "an", OPT_AUDIO | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(audio_disable) }, + "disable audio" }, + { "acodec", OPT_AUDIO | HAS_ARG | OPT_PERFILE | + OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_audio_codec }, + "force audio codec ('copy' to copy stream)", "codec" }, + { "atag", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_OUTPUT, { .func_arg = opt_old2new }, + "force audio tag/fourcc", "fourcc/tag" }, + { "vol", OPT_AUDIO | HAS_ARG | OPT_INT, { &audio_volume }, + "change audio volume (256=normal)" , "volume" }, + { "sample_fmt", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_SPEC | + OPT_STRING | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(sample_fmts) }, + "set sample format", "format" }, + { "channel_layout", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | + OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_channel_layout }, + "set channel layout", "layout" }, + { "af", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_filters }, + "set audio filters", "filter_graph" }, + { "guess_layout_max", OPT_AUDIO | HAS_ARG | OPT_INT | OPT_SPEC | OPT_EXPERT | OPT_INPUT, { .off = OFFSET(guess_layout_max) }, + "set the maximum number of channels to try to guess the channel layout" }, + + /* subtitle options */ + { "sn", OPT_SUBTITLE | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(subtitle_disable) }, + "disable subtitle" }, + { "scodec", OPT_SUBTITLE | HAS_ARG | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_subtitle_codec }, + "force subtitle codec ('copy' to copy stream)", "codec" }, + { "stag", OPT_SUBTITLE | HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new } + , "force subtitle tag/fourcc", "fourcc/tag" }, + { "fix_sub_duration", OPT_BOOL | OPT_EXPERT | OPT_SUBTITLE | OPT_SPEC | OPT_INPUT, { .off = OFFSET(fix_sub_duration) }, + "fix subtitles duration" }, + { "canvas_size", OPT_SUBTITLE | HAS_ARG | OPT_STRING | OPT_SPEC | OPT_INPUT, { .off = OFFSET(canvas_sizes) }, + "set canvas size (WxH or abbreviation)", "size" }, + + /* grab options */ + { "vc", HAS_ARG | OPT_EXPERT | OPT_VIDEO, { .func_arg = opt_video_channel }, + "deprecated, use -channel", "channel" }, + { "tvstd", HAS_ARG | OPT_EXPERT | OPT_VIDEO, { .func_arg = opt_video_standard }, + "deprecated, use -standard", "standard" }, + { "isync", OPT_BOOL | OPT_EXPERT, { &input_sync }, "this option is deprecated and does nothing", "" }, + + /* muxer options */ + { "muxdelay", OPT_FLOAT | HAS_ARG | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(mux_max_delay) }, + "set the maximum demux-decode delay", "seconds" }, + { "muxpreload", OPT_FLOAT | HAS_ARG | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(mux_preload) }, + "set the initial demux-decode delay", "seconds" }, + { "override_ffserver", OPT_BOOL | OPT_EXPERT | OPT_OUTPUT, { &override_ffserver }, + "override the options from ffserver", "" }, + { "sdp_file", HAS_ARG | OPT_EXPERT | OPT_OUTPUT, { .func_arg = opt_sdp_file }, + "specify a file in which to print sdp information", "file" }, + + { "time_base", HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(time_bases) }, + "set the desired time base hint for output stream (1:24, 1:48000 or 0.04166, 2.0833e-5)", "ratio" }, + { "enc_time_base", HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(enc_time_bases) }, + "set the desired time base for the encoder (1:24, 1:48000 or 0.04166, 2.0833e-5). " + "two special values are defined - " + "0 = use frame rate (video) or sample rate (audio)," + "-1 = match source time base", "ratio" }, + + { "bsf", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(bitstream_filters) }, + "A comma-separated list of bitstream filters", "bitstream_filters" }, + { "absf", HAS_ARG | OPT_AUDIO | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new }, + "deprecated", "audio bitstream_filters" }, + { "vbsf", OPT_VIDEO | HAS_ARG | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new }, + "deprecated", "video bitstream_filters" }, + + { "apre", HAS_ARG | OPT_AUDIO | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_preset }, + "set the audio options to the indicated preset", "preset" }, + { "vpre", OPT_VIDEO | HAS_ARG | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_preset }, + "set the video options to the indicated preset", "preset" }, + { "spre", HAS_ARG | OPT_SUBTITLE | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_preset }, + "set the subtitle options to the indicated preset", "preset" }, + { "fpre", HAS_ARG | OPT_EXPERT| OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_preset }, + "set options from indicated preset file", "filename" }, + + { "max_muxing_queue_size", HAS_ARG | OPT_INT | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(max_muxing_queue_size) }, + "maximum number of packets that can be buffered while waiting for all streams to initialize", "packets" }, + + /* data codec support */ + { "dcodec", HAS_ARG | OPT_DATA | OPT_PERFILE | OPT_EXPERT | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_data_codec }, + "force data codec ('copy' to copy stream)", "codec" }, + { "dn", OPT_BOOL | OPT_VIDEO | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(data_disable) }, + "disable data" }, + +#if CONFIG_VAAPI + { "vaapi_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_vaapi_device }, + "set VAAPI hardware device (DRM path or X11 display name)", "device" }, +#endif + +#if CONFIG_QSV + { "qsv_device", HAS_ARG | OPT_STRING | OPT_EXPERT, { &qsv_device }, + "set QSV hardware device (DirectX adapter index, DRM path or X11 display name)", "device"}, +#endif + + { "init_hw_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_init_hw_device }, + "initialise hardware device", "args" }, + { "filter_hw_device", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_hw_device }, + "set hardware device used when filtering", "device" }, + + { NULL, }, +}; diff --git a/fftools/ffmpeg_qsv.c b/fftools/ffmpeg_qsv.c new file mode 100644 index 0000000000..7442750029 --- /dev/null +++ b/fftools/ffmpeg_qsv.c @@ -0,0 +1,109 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <mfx/mfxvideo.h> +#include <stdlib.h> + +#include "libavutil/dict.h" +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_qsv.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavcodec/qsv.h" + +#include "ffmpeg.h" + +char *qsv_device = NULL; + +static int qsv_get_buffer(AVCodecContext *s, AVFrame *frame, int flags) +{ + InputStream *ist = s->opaque; + + return av_hwframe_get_buffer(ist->hw_frames_ctx, frame, 0); +} + +static void qsv_uninit(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + av_buffer_unref(&ist->hw_frames_ctx); +} + +static int qsv_device_init(InputStream *ist) +{ + int err; + AVDictionary *dict = NULL; + + if (qsv_device) { + err = av_dict_set(&dict, "child_device", qsv_device, 0); + if (err < 0) + return err; + } + + err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, + ist->hwaccel_device, dict, 0); + if (err < 0) { + av_log(NULL, AV_LOG_ERROR, "Error creating a QSV device\n"); + goto err_out; + } + +err_out: + if (dict) + av_dict_free(&dict); + + return err; +} + +int qsv_init(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + AVHWFramesContext *frames_ctx; + AVQSVFramesContext *frames_hwctx; + int ret; + + if (!hw_device_ctx) { + ret = qsv_device_init(ist); + if (ret < 0) + return ret; + } + + av_buffer_unref(&ist->hw_frames_ctx); + ist->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx); + if (!ist->hw_frames_ctx) + return AVERROR(ENOMEM); + + frames_ctx = (AVHWFramesContext*)ist->hw_frames_ctx->data; + frames_hwctx = frames_ctx->hwctx; + + frames_ctx->width = FFALIGN(s->coded_width, 32); + frames_ctx->height = FFALIGN(s->coded_height, 32); + frames_ctx->format = AV_PIX_FMT_QSV; + frames_ctx->sw_format = s->sw_pix_fmt; + frames_ctx->initial_pool_size = 64; + frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; + + ret = av_hwframe_ctx_init(ist->hw_frames_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing a QSV frame pool\n"); + return ret; + } + + ist->hwaccel_get_buffer = qsv_get_buffer; + ist->hwaccel_uninit = qsv_uninit; + + return 0; +} diff --git a/fftools/ffmpeg_videotoolbox.c b/fftools/ffmpeg_videotoolbox.c new file mode 100644 index 0000000000..e9039654b9 --- /dev/null +++ b/fftools/ffmpeg_videotoolbox.c @@ -0,0 +1,201 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#if HAVE_UTGETOSTYPEFROMSTRING +#include <CoreServices/CoreServices.h> +#endif + +#include "libavcodec/avcodec.h" +#if CONFIG_VDA +# include "libavcodec/vda.h" +#endif +#if CONFIG_VIDEOTOOLBOX +# include "libavcodec/videotoolbox.h" +#endif +#include "libavutil/imgutils.h" +#include "ffmpeg.h" + +typedef struct VTContext { + AVFrame *tmp_frame; +} VTContext; + +char *videotoolbox_pixfmt; + +static int videotoolbox_retrieve_data(AVCodecContext *s, AVFrame *frame) +{ + InputStream *ist = s->opaque; + VTContext *vt = ist->hwaccel_ctx; + CVPixelBufferRef pixbuf = (CVPixelBufferRef)frame->data[3]; + OSType pixel_format = CVPixelBufferGetPixelFormatType(pixbuf); + CVReturn err; + uint8_t *data[4] = { 0 }; + int linesize[4] = { 0 }; + int planes, ret, i; + + av_frame_unref(vt->tmp_frame); + + switch (pixel_format) { + case kCVPixelFormatType_420YpCbCr8Planar: vt->tmp_frame->format = AV_PIX_FMT_YUV420P; break; + case kCVPixelFormatType_422YpCbCr8: vt->tmp_frame->format = AV_PIX_FMT_UYVY422; break; + case kCVPixelFormatType_32BGRA: vt->tmp_frame->format = AV_PIX_FMT_BGRA; break; +#ifdef kCFCoreFoundationVersionNumber10_7 + case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: vt->tmp_frame->format = AV_PIX_FMT_NV12; break; +#endif + default: + av_log(NULL, AV_LOG_ERROR, + "%s: Unsupported pixel format: %s\n", + av_fourcc2str(s->codec_tag), videotoolbox_pixfmt); + return AVERROR(ENOSYS); + } + + vt->tmp_frame->width = frame->width; + vt->tmp_frame->height = frame->height; + ret = av_frame_get_buffer(vt->tmp_frame, 32); + if (ret < 0) + return ret; + + err = CVPixelBufferLockBaseAddress(pixbuf, kCVPixelBufferLock_ReadOnly); + if (err != kCVReturnSuccess) { + av_log(NULL, AV_LOG_ERROR, "Error locking the pixel buffer.\n"); + return AVERROR_UNKNOWN; + } + + if (CVPixelBufferIsPlanar(pixbuf)) { + + planes = CVPixelBufferGetPlaneCount(pixbuf); + for (i = 0; i < planes; i++) { + data[i] = CVPixelBufferGetBaseAddressOfPlane(pixbuf, i); + linesize[i] = CVPixelBufferGetBytesPerRowOfPlane(pixbuf, i); + } + } else { + data[0] = CVPixelBufferGetBaseAddress(pixbuf); + linesize[0] = CVPixelBufferGetBytesPerRow(pixbuf); + } + + av_image_copy(vt->tmp_frame->data, vt->tmp_frame->linesize, + (const uint8_t **)data, linesize, vt->tmp_frame->format, + frame->width, frame->height); + + ret = av_frame_copy_props(vt->tmp_frame, frame); + CVPixelBufferUnlockBaseAddress(pixbuf, kCVPixelBufferLock_ReadOnly); + if (ret < 0) + return ret; + + av_frame_unref(frame); + av_frame_move_ref(frame, vt->tmp_frame); + + return 0; +} + +static void videotoolbox_uninit(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + VTContext *vt = ist->hwaccel_ctx; + + ist->hwaccel_uninit = NULL; + ist->hwaccel_retrieve_data = NULL; + + av_frame_free(&vt->tmp_frame); + + if (ist->hwaccel_id == HWACCEL_VIDEOTOOLBOX) { +#if CONFIG_VIDEOTOOLBOX + av_videotoolbox_default_free(s); +#endif + } else { +#if CONFIG_VDA + av_vda_default_free(s); +#endif + } + av_freep(&ist->hwaccel_ctx); +} + +int videotoolbox_init(AVCodecContext *s) +{ + InputStream *ist = s->opaque; + int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR; + int ret = 0; + VTContext *vt; + + vt = av_mallocz(sizeof(*vt)); + if (!vt) + return AVERROR(ENOMEM); + + ist->hwaccel_ctx = vt; + ist->hwaccel_uninit = videotoolbox_uninit; + ist->hwaccel_retrieve_data = videotoolbox_retrieve_data; + + vt->tmp_frame = av_frame_alloc(); + if (!vt->tmp_frame) { + ret = AVERROR(ENOMEM); + goto fail; + } + + if (ist->hwaccel_id == HWACCEL_VIDEOTOOLBOX) { +#if CONFIG_VIDEOTOOLBOX + if (!videotoolbox_pixfmt) { + ret = av_videotoolbox_default_init(s); + } else { + AVVideotoolboxContext *vtctx = av_videotoolbox_alloc_context(); + CFStringRef pixfmt_str = CFStringCreateWithCString(kCFAllocatorDefault, + videotoolbox_pixfmt, + kCFStringEncodingUTF8); +#if HAVE_UTGETOSTYPEFROMSTRING + vtctx->cv_pix_fmt_type = UTGetOSTypeFromString(pixfmt_str); +#else + av_log(s, loglevel, "UTGetOSTypeFromString() is not available " + "on this platform, %s pixel format can not be honored from " + "the command line\n", videotoolbox_pixfmt); +#endif + ret = av_videotoolbox_default_init2(s, vtctx); + CFRelease(pixfmt_str); + } +#endif + } else { +#if CONFIG_VDA + if (!videotoolbox_pixfmt) { + ret = av_vda_default_init(s); + } else { + AVVDAContext *vdactx = av_vda_alloc_context(); + CFStringRef pixfmt_str = CFStringCreateWithCString(kCFAllocatorDefault, + videotoolbox_pixfmt, + kCFStringEncodingUTF8); +#if HAVE_UTGETOSTYPEFROMSTRING + vdactx->cv_pix_fmt_type = UTGetOSTypeFromString(pixfmt_str); +#else + av_log(s, loglevel, "UTGetOSTypeFromString() is not available " + "on this platform, %s pixel format can not be honored from " + "the command line\n", videotoolbox_pixfmt); +#endif + ret = av_vda_default_init2(s, vdactx); + CFRelease(pixfmt_str); + } +#endif + } + if (ret < 0) { + av_log(NULL, loglevel, + "Error creating %s decoder.\n", ist->hwaccel_id == HWACCEL_VIDEOTOOLBOX ? "Videotoolbox" : "VDA"); + goto fail; + } + + return 0; +fail: + videotoolbox_uninit(s); + return ret; +} diff --git a/fftools/ffplay.c b/fftools/ffplay.c new file mode 100644 index 0000000000..9f7774613c --- /dev/null +++ b/fftools/ffplay.c @@ -0,0 +1,3765 @@ +/* + * Copyright (c) 2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * simple media player based on the FFmpeg libraries + */ + +#include "config.h" +#include <inttypes.h> +#include <math.h> +#include <limits.h> +#include <signal.h> +#include <stdint.h> + +#include "libavutil/avstring.h" +#include "libavutil/eval.h" +#include "libavutil/mathematics.h" +#include "libavutil/pixdesc.h" +#include "libavutil/imgutils.h" +#include "libavutil/dict.h" +#include "libavutil/parseutils.h" +#include "libavutil/samplefmt.h" +#include "libavutil/avassert.h" +#include "libavutil/time.h" +#include "libavformat/avformat.h" +#include "libavdevice/avdevice.h" +#include "libswscale/swscale.h" +#include "libavutil/opt.h" +#include "libavcodec/avfft.h" +#include "libswresample/swresample.h" + +#if CONFIG_AVFILTER +# include "libavfilter/avfilter.h" +# include "libavfilter/buffersink.h" +# include "libavfilter/buffersrc.h" +#endif + +#include <SDL.h> +#include <SDL_thread.h> + +#include "cmdutils.h" + +#include <assert.h> + +const char program_name[] = "ffplay"; +const int program_birth_year = 2003; + +#define MAX_QUEUE_SIZE (15 * 1024 * 1024) +#define MIN_FRAMES 25 +#define EXTERNAL_CLOCK_MIN_FRAMES 2 +#define EXTERNAL_CLOCK_MAX_FRAMES 10 + +/* Minimum SDL audio buffer size, in samples. */ +#define SDL_AUDIO_MIN_BUFFER_SIZE 512 +/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */ +#define SDL_AUDIO_MAX_CALLBACKS_PER_SEC 30 + +/* Step size for volume control in dB */ +#define SDL_VOLUME_STEP (0.75) + +/* no AV sync correction is done if below the minimum AV sync threshold */ +#define AV_SYNC_THRESHOLD_MIN 0.04 +/* AV sync correction is done if above the maximum AV sync threshold */ +#define AV_SYNC_THRESHOLD_MAX 0.1 +/* If a frame duration is longer than this, it will not be duplicated to compensate AV sync */ +#define AV_SYNC_FRAMEDUP_THRESHOLD 0.1 +/* no AV correction is done if too big error */ +#define AV_NOSYNC_THRESHOLD 10.0 + +/* maximum audio speed change to get correct sync */ +#define SAMPLE_CORRECTION_PERCENT_MAX 10 + +/* external clock speed adjustment constants for realtime sources based on buffer fullness */ +#define EXTERNAL_CLOCK_SPEED_MIN 0.900 +#define EXTERNAL_CLOCK_SPEED_MAX 1.010 +#define EXTERNAL_CLOCK_SPEED_STEP 0.001 + +/* we use about AUDIO_DIFF_AVG_NB A-V differences to make the average */ +#define AUDIO_DIFF_AVG_NB 20 + +/* polls for possible required screen refresh at least this often, should be less than 1/fps */ +#define REFRESH_RATE 0.01 + +/* NOTE: the size must be big enough to compensate the hardware audio buffersize size */ +/* TODO: We assume that a decoded and resampled frame fits into this buffer */ +#define SAMPLE_ARRAY_SIZE (8 * 65536) + +#define CURSOR_HIDE_DELAY 1000000 + +#define USE_ONEPASS_SUBTITLE_RENDER 1 + +static unsigned sws_flags = SWS_BICUBIC; + +typedef struct MyAVPacketList { + AVPacket pkt; + struct MyAVPacketList *next; + int serial; +} MyAVPacketList; + +typedef struct PacketQueue { + MyAVPacketList *first_pkt, *last_pkt; + int nb_packets; + int size; + int64_t duration; + int abort_request; + int serial; + SDL_mutex *mutex; + SDL_cond *cond; +} PacketQueue; + +#define VIDEO_PICTURE_QUEUE_SIZE 3 +#define SUBPICTURE_QUEUE_SIZE 16 +#define SAMPLE_QUEUE_SIZE 9 +#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE)) + +typedef struct AudioParams { + int freq; + int channels; + int64_t channel_layout; + enum AVSampleFormat fmt; + int frame_size; + int bytes_per_sec; +} AudioParams; + +typedef struct Clock { + double pts; /* clock base */ + double pts_drift; /* clock base minus time at which we updated the clock */ + double last_updated; + double speed; + int serial; /* clock is based on a packet with this serial */ + int paused; + int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */ +} Clock; + +/* Common struct for handling all types of decoded data and allocated render buffers. */ +typedef struct Frame { + AVFrame *frame; + AVSubtitle sub; + int serial; + double pts; /* presentation timestamp for the frame */ + double duration; /* estimated duration of the frame */ + int64_t pos; /* byte position of the frame in the input file */ + int width; + int height; + int format; + AVRational sar; + int uploaded; + int flip_v; +} Frame; + +typedef struct FrameQueue { + Frame queue[FRAME_QUEUE_SIZE]; + int rindex; + int windex; + int size; + int max_size; + int keep_last; + int rindex_shown; + SDL_mutex *mutex; + SDL_cond *cond; + PacketQueue *pktq; +} FrameQueue; + +enum { + AV_SYNC_AUDIO_MASTER, /* default choice */ + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_CLOCK, /* synchronize to an external clock */ +}; + +typedef struct Decoder { + AVPacket pkt; + PacketQueue *queue; + AVCodecContext *avctx; + int pkt_serial; + int finished; + int packet_pending; + SDL_cond *empty_queue_cond; + int64_t start_pts; + AVRational start_pts_tb; + int64_t next_pts; + AVRational next_pts_tb; + SDL_Thread *decoder_tid; +} Decoder; + +typedef struct VideoState { + SDL_Thread *read_tid; + AVInputFormat *iformat; + int abort_request; + int force_refresh; + int paused; + int last_paused; + int queue_attachments_req; + int seek_req; + int seek_flags; + int64_t seek_pos; + int64_t seek_rel; + int read_pause_return; + AVFormatContext *ic; + int realtime; + + Clock audclk; + Clock vidclk; + Clock extclk; + + FrameQueue pictq; + FrameQueue subpq; + FrameQueue sampq; + + Decoder auddec; + Decoder viddec; + Decoder subdec; + + int audio_stream; + + int av_sync_type; + + double audio_clock; + int audio_clock_serial; + double audio_diff_cum; /* used for AV difference average computation */ + double audio_diff_avg_coef; + double audio_diff_threshold; + int audio_diff_avg_count; + AVStream *audio_st; + PacketQueue audioq; + int audio_hw_buf_size; + uint8_t *audio_buf; + uint8_t *audio_buf1; + unsigned int audio_buf_size; /* in bytes */ + unsigned int audio_buf1_size; + int audio_buf_index; /* in bytes */ + int audio_write_buf_size; + int audio_volume; + int muted; + struct AudioParams audio_src; +#if CONFIG_AVFILTER + struct AudioParams audio_filter_src; +#endif + struct AudioParams audio_tgt; + struct SwrContext *swr_ctx; + int frame_drops_early; + int frame_drops_late; + + enum ShowMode { + SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB + } show_mode; + int16_t sample_array[SAMPLE_ARRAY_SIZE]; + int sample_array_index; + int last_i_start; + RDFTContext *rdft; + int rdft_bits; + FFTSample *rdft_data; + int xpos; + double last_vis_time; + SDL_Texture *vis_texture; + SDL_Texture *sub_texture; + SDL_Texture *vid_texture; + + int subtitle_stream; + AVStream *subtitle_st; + PacketQueue subtitleq; + + double frame_timer; + double frame_last_returned_time; + double frame_last_filter_delay; + int video_stream; + AVStream *video_st; + PacketQueue videoq; + double max_frame_duration; // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity + struct SwsContext *img_convert_ctx; + struct SwsContext *sub_convert_ctx; + int eof; + + char *filename; + int width, height, xleft, ytop; + int step; + +#if CONFIG_AVFILTER + int vfilter_idx; + AVFilterContext *in_video_filter; // the first filter in the video chain + AVFilterContext *out_video_filter; // the last filter in the video chain + AVFilterContext *in_audio_filter; // the first filter in the audio chain + AVFilterContext *out_audio_filter; // the last filter in the audio chain + AVFilterGraph *agraph; // audio filter graph +#endif + + int last_video_stream, last_audio_stream, last_subtitle_stream; + + SDL_cond *continue_read_thread; +} VideoState; + +/* options specified by the user */ +static AVInputFormat *file_iformat; +static const char *input_filename; +static const char *window_title; +static int default_width = 640; +static int default_height = 480; +static int screen_width = 0; +static int screen_height = 0; +static int audio_disable; +static int video_disable; +static int subtitle_disable; +static const char* wanted_stream_spec[AVMEDIA_TYPE_NB] = {0}; +static int seek_by_bytes = -1; +static int display_disable; +static int borderless; +static int startup_volume = 100; +static int show_status = 1; +static int av_sync_type = AV_SYNC_AUDIO_MASTER; +static int64_t start_time = AV_NOPTS_VALUE; +static int64_t duration = AV_NOPTS_VALUE; +static int fast = 0; +static int genpts = 0; +static int lowres = 0; +static int decoder_reorder_pts = -1; +static int autoexit; +static int exit_on_keydown; +static int exit_on_mousedown; +static int loop = 1; +static int framedrop = -1; +static int infinite_buffer = -1; +static enum ShowMode show_mode = SHOW_MODE_NONE; +static const char *audio_codec_name; +static const char *subtitle_codec_name; +static const char *video_codec_name; +double rdftspeed = 0.02; +static int64_t cursor_last_shown; +static int cursor_hidden = 0; +#if CONFIG_AVFILTER +static const char **vfilters_list = NULL; +static int nb_vfilters = 0; +static char *afilters = NULL; +#endif +static int autorotate = 1; +static int find_stream_info = 1; + +/* current context */ +static int is_full_screen; +static int64_t audio_callback_time; + +static AVPacket flush_pkt; + +#define FF_QUIT_EVENT (SDL_USEREVENT + 2) + +static SDL_Window *window; +static SDL_Renderer *renderer; + +static const struct TextureFormatEntry { + enum AVPixelFormat format; + int texture_fmt; +} sdl_texture_format_map[] = { + { AV_PIX_FMT_RGB8, SDL_PIXELFORMAT_RGB332 }, + { AV_PIX_FMT_RGB444, SDL_PIXELFORMAT_RGB444 }, + { AV_PIX_FMT_RGB555, SDL_PIXELFORMAT_RGB555 }, + { AV_PIX_FMT_BGR555, SDL_PIXELFORMAT_BGR555 }, + { AV_PIX_FMT_RGB565, SDL_PIXELFORMAT_RGB565 }, + { AV_PIX_FMT_BGR565, SDL_PIXELFORMAT_BGR565 }, + { AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24 }, + { AV_PIX_FMT_BGR24, SDL_PIXELFORMAT_BGR24 }, + { AV_PIX_FMT_0RGB32, SDL_PIXELFORMAT_RGB888 }, + { AV_PIX_FMT_0BGR32, SDL_PIXELFORMAT_BGR888 }, + { AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888 }, + { AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888 }, + { AV_PIX_FMT_RGB32, SDL_PIXELFORMAT_ARGB8888 }, + { AV_PIX_FMT_RGB32_1, SDL_PIXELFORMAT_RGBA8888 }, + { AV_PIX_FMT_BGR32, SDL_PIXELFORMAT_ABGR8888 }, + { AV_PIX_FMT_BGR32_1, SDL_PIXELFORMAT_BGRA8888 }, + { AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV }, + { AV_PIX_FMT_YUYV422, SDL_PIXELFORMAT_YUY2 }, + { AV_PIX_FMT_UYVY422, SDL_PIXELFORMAT_UYVY }, + { AV_PIX_FMT_NONE, SDL_PIXELFORMAT_UNKNOWN }, +}; + +#if CONFIG_AVFILTER +static int opt_add_vfilter(void *optctx, const char *opt, const char *arg) +{ + GROW_ARRAY(vfilters_list, nb_vfilters); + vfilters_list[nb_vfilters - 1] = arg; + return 0; +} +#endif + +static inline +int cmp_audio_fmts(enum AVSampleFormat fmt1, int64_t channel_count1, + enum AVSampleFormat fmt2, int64_t channel_count2) +{ + /* If channel count == 1, planar and non-planar formats are the same */ + if (channel_count1 == 1 && channel_count2 == 1) + return av_get_packed_sample_fmt(fmt1) != av_get_packed_sample_fmt(fmt2); + else + return channel_count1 != channel_count2 || fmt1 != fmt2; +} + +static inline +int64_t get_valid_channel_layout(int64_t channel_layout, int channels) +{ + if (channel_layout && av_get_channel_layout_nb_channels(channel_layout) == channels) + return channel_layout; + else + return 0; +} + +static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) +{ + MyAVPacketList *pkt1; + + if (q->abort_request) + return -1; + + pkt1 = av_malloc(sizeof(MyAVPacketList)); + if (!pkt1) + return -1; + pkt1->pkt = *pkt; + pkt1->next = NULL; + if (pkt == &flush_pkt) + q->serial++; + pkt1->serial = q->serial; + + if (!q->last_pkt) + q->first_pkt = pkt1; + else + q->last_pkt->next = pkt1; + q->last_pkt = pkt1; + q->nb_packets++; + q->size += pkt1->pkt.size + sizeof(*pkt1); + q->duration += pkt1->pkt.duration; + /* XXX: should duplicate packet data in DV case */ + SDL_CondSignal(q->cond); + return 0; +} + +static int packet_queue_put(PacketQueue *q, AVPacket *pkt) +{ + int ret; + + SDL_LockMutex(q->mutex); + ret = packet_queue_put_private(q, pkt); + SDL_UnlockMutex(q->mutex); + + if (pkt != &flush_pkt && ret < 0) + av_packet_unref(pkt); + + return ret; +} + +static int packet_queue_put_nullpacket(PacketQueue *q, int stream_index) +{ + AVPacket pkt1, *pkt = &pkt1; + av_init_packet(pkt); + pkt->data = NULL; + pkt->size = 0; + pkt->stream_index = stream_index; + return packet_queue_put(q, pkt); +} + +/* packet queue handling */ +static int packet_queue_init(PacketQueue *q) +{ + memset(q, 0, sizeof(PacketQueue)); + q->mutex = SDL_CreateMutex(); + if (!q->mutex) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); + return AVERROR(ENOMEM); + } + q->cond = SDL_CreateCond(); + if (!q->cond) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError()); + return AVERROR(ENOMEM); + } + q->abort_request = 1; + return 0; +} + +static void packet_queue_flush(PacketQueue *q) +{ + MyAVPacketList *pkt, *pkt1; + + SDL_LockMutex(q->mutex); + for (pkt = q->first_pkt; pkt; pkt = pkt1) { + pkt1 = pkt->next; + av_packet_unref(&pkt->pkt); + av_freep(&pkt); + } + q->last_pkt = NULL; + q->first_pkt = NULL; + q->nb_packets = 0; + q->size = 0; + q->duration = 0; + SDL_UnlockMutex(q->mutex); +} + +static void packet_queue_destroy(PacketQueue *q) +{ + packet_queue_flush(q); + SDL_DestroyMutex(q->mutex); + SDL_DestroyCond(q->cond); +} + +static void packet_queue_abort(PacketQueue *q) +{ + SDL_LockMutex(q->mutex); + + q->abort_request = 1; + + SDL_CondSignal(q->cond); + + SDL_UnlockMutex(q->mutex); +} + +static void packet_queue_start(PacketQueue *q) +{ + SDL_LockMutex(q->mutex); + q->abort_request = 0; + packet_queue_put_private(q, &flush_pkt); + SDL_UnlockMutex(q->mutex); +} + +/* return < 0 if aborted, 0 if no packet and > 0 if packet. */ +static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial) +{ + MyAVPacketList *pkt1; + int ret; + + SDL_LockMutex(q->mutex); + + for (;;) { + if (q->abort_request) { + ret = -1; + break; + } + + pkt1 = q->first_pkt; + if (pkt1) { + q->first_pkt = pkt1->next; + if (!q->first_pkt) + q->last_pkt = NULL; + q->nb_packets--; + q->size -= pkt1->pkt.size + sizeof(*pkt1); + q->duration -= pkt1->pkt.duration; + *pkt = pkt1->pkt; + if (serial) + *serial = pkt1->serial; + av_free(pkt1); + ret = 1; + break; + } else if (!block) { + ret = 0; + break; + } else { + SDL_CondWait(q->cond, q->mutex); + } + } + SDL_UnlockMutex(q->mutex); + return ret; +} + +static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) { + memset(d, 0, sizeof(Decoder)); + d->avctx = avctx; + d->queue = queue; + d->empty_queue_cond = empty_queue_cond; + d->start_pts = AV_NOPTS_VALUE; + d->pkt_serial = -1; +} + +static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) { + int ret = AVERROR(EAGAIN); + + for (;;) { + AVPacket pkt; + + if (d->queue->serial == d->pkt_serial) { + do { + if (d->queue->abort_request) + return -1; + + switch (d->avctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + ret = avcodec_receive_frame(d->avctx, frame); + if (ret >= 0) { + if (decoder_reorder_pts == -1) { + frame->pts = frame->best_effort_timestamp; + } else if (!decoder_reorder_pts) { + frame->pts = frame->pkt_dts; + } + } + break; + case AVMEDIA_TYPE_AUDIO: + ret = avcodec_receive_frame(d->avctx, frame); + if (ret >= 0) { + AVRational tb = (AVRational){1, frame->sample_rate}; + if (frame->pts != AV_NOPTS_VALUE) + frame->pts = av_rescale_q(frame->pts, av_codec_get_pkt_timebase(d->avctx), tb); + else if (d->next_pts != AV_NOPTS_VALUE) + frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb); + if (frame->pts != AV_NOPTS_VALUE) { + d->next_pts = frame->pts + frame->nb_samples; + d->next_pts_tb = tb; + } + } + break; + } + if (ret == AVERROR_EOF) { + d->finished = d->pkt_serial; + avcodec_flush_buffers(d->avctx); + return 0; + } + if (ret >= 0) + return 1; + } while (ret != AVERROR(EAGAIN)); + } + + do { + if (d->queue->nb_packets == 0) + SDL_CondSignal(d->empty_queue_cond); + if (d->packet_pending) { + av_packet_move_ref(&pkt, &d->pkt); + d->packet_pending = 0; + } else { + if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0) + return -1; + } + } while (d->queue->serial != d->pkt_serial); + + if (pkt.data == flush_pkt.data) { + avcodec_flush_buffers(d->avctx); + d->finished = 0; + d->next_pts = d->start_pts; + d->next_pts_tb = d->start_pts_tb; + } else { + if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + int got_frame = 0; + ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &pkt); + if (ret < 0) { + ret = AVERROR(EAGAIN); + } else { + if (got_frame && !pkt.data) { + d->packet_pending = 1; + av_packet_move_ref(&d->pkt, &pkt); + } + ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF); + } + } else { + if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) { + av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); + d->packet_pending = 1; + av_packet_move_ref(&d->pkt, &pkt); + } + } + av_packet_unref(&pkt); + } + } +} + +static void decoder_destroy(Decoder *d) { + av_packet_unref(&d->pkt); + avcodec_free_context(&d->avctx); +} + +static void frame_queue_unref_item(Frame *vp) +{ + av_frame_unref(vp->frame); + avsubtitle_free(&vp->sub); +} + +static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) +{ + int i; + memset(f, 0, sizeof(FrameQueue)); + if (!(f->mutex = SDL_CreateMutex())) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); + return AVERROR(ENOMEM); + } + if (!(f->cond = SDL_CreateCond())) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError()); + return AVERROR(ENOMEM); + } + f->pktq = pktq; + f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE); + f->keep_last = !!keep_last; + for (i = 0; i < f->max_size; i++) + if (!(f->queue[i].frame = av_frame_alloc())) + return AVERROR(ENOMEM); + return 0; +} + +static void frame_queue_destory(FrameQueue *f) +{ + int i; + for (i = 0; i < f->max_size; i++) { + Frame *vp = &f->queue[i]; + frame_queue_unref_item(vp); + av_frame_free(&vp->frame); + } + SDL_DestroyMutex(f->mutex); + SDL_DestroyCond(f->cond); +} + +static void frame_queue_signal(FrameQueue *f) +{ + SDL_LockMutex(f->mutex); + SDL_CondSignal(f->cond); + SDL_UnlockMutex(f->mutex); +} + +static Frame *frame_queue_peek(FrameQueue *f) +{ + return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; +} + +static Frame *frame_queue_peek_next(FrameQueue *f) +{ + return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size]; +} + +static Frame *frame_queue_peek_last(FrameQueue *f) +{ + return &f->queue[f->rindex]; +} + +static Frame *frame_queue_peek_writable(FrameQueue *f) +{ + /* wait until we have space to put a new frame */ + SDL_LockMutex(f->mutex); + while (f->size >= f->max_size && + !f->pktq->abort_request) { + SDL_CondWait(f->cond, f->mutex); + } + SDL_UnlockMutex(f->mutex); + + if (f->pktq->abort_request) + return NULL; + + return &f->queue[f->windex]; +} + +static Frame *frame_queue_peek_readable(FrameQueue *f) +{ + /* wait until we have a readable a new frame */ + SDL_LockMutex(f->mutex); + while (f->size - f->rindex_shown <= 0 && + !f->pktq->abort_request) { + SDL_CondWait(f->cond, f->mutex); + } + SDL_UnlockMutex(f->mutex); + + if (f->pktq->abort_request) + return NULL; + + return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; +} + +static void frame_queue_push(FrameQueue *f) +{ + if (++f->windex == f->max_size) + f->windex = 0; + SDL_LockMutex(f->mutex); + f->size++; + SDL_CondSignal(f->cond); + SDL_UnlockMutex(f->mutex); +} + +static void frame_queue_next(FrameQueue *f) +{ + if (f->keep_last && !f->rindex_shown) { + f->rindex_shown = 1; + return; + } + frame_queue_unref_item(&f->queue[f->rindex]); + if (++f->rindex == f->max_size) + f->rindex = 0; + SDL_LockMutex(f->mutex); + f->size--; + SDL_CondSignal(f->cond); + SDL_UnlockMutex(f->mutex); +} + +/* return the number of undisplayed frames in the queue */ +static int frame_queue_nb_remaining(FrameQueue *f) +{ + return f->size - f->rindex_shown; +} + +/* return last shown position */ +static int64_t frame_queue_last_pos(FrameQueue *f) +{ + Frame *fp = &f->queue[f->rindex]; + if (f->rindex_shown && fp->serial == f->pktq->serial) + return fp->pos; + else + return -1; +} + +static void decoder_abort(Decoder *d, FrameQueue *fq) +{ + packet_queue_abort(d->queue); + frame_queue_signal(fq); + SDL_WaitThread(d->decoder_tid, NULL); + d->decoder_tid = NULL; + packet_queue_flush(d->queue); +} + +static inline void fill_rectangle(int x, int y, int w, int h) +{ + SDL_Rect rect; + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + if (w && h) + SDL_RenderFillRect(renderer, &rect); +} + +static int realloc_texture(SDL_Texture **texture, Uint32 new_format, int new_width, int new_height, SDL_BlendMode blendmode, int init_texture) +{ + Uint32 format; + int access, w, h; + if (SDL_QueryTexture(*texture, &format, &access, &w, &h) < 0 || new_width != w || new_height != h || new_format != format) { + void *pixels; + int pitch; + SDL_DestroyTexture(*texture); + if (!(*texture = SDL_CreateTexture(renderer, new_format, SDL_TEXTUREACCESS_STREAMING, new_width, new_height))) + return -1; + if (SDL_SetTextureBlendMode(*texture, blendmode) < 0) + return -1; + if (init_texture) { + if (SDL_LockTexture(*texture, NULL, &pixels, &pitch) < 0) + return -1; + memset(pixels, 0, pitch * new_height); + SDL_UnlockTexture(*texture); + } + av_log(NULL, AV_LOG_VERBOSE, "Created %dx%d texture with %s.\n", new_width, new_height, SDL_GetPixelFormatName(new_format)); + } + return 0; +} + +static void calculate_display_rect(SDL_Rect *rect, + int scr_xleft, int scr_ytop, int scr_width, int scr_height, + int pic_width, int pic_height, AVRational pic_sar) +{ + float aspect_ratio; + int width, height, x, y; + + if (pic_sar.num == 0) + aspect_ratio = 0; + else + aspect_ratio = av_q2d(pic_sar); + + if (aspect_ratio <= 0.0) + aspect_ratio = 1.0; + aspect_ratio *= (float)pic_width / (float)pic_height; + + /* XXX: we suppose the screen has a 1.0 pixel ratio */ + height = scr_height; + width = lrint(height * aspect_ratio) & ~1; + if (width > scr_width) { + width = scr_width; + height = lrint(width / aspect_ratio) & ~1; + } + x = (scr_width - width) / 2; + y = (scr_height - height) / 2; + rect->x = scr_xleft + x; + rect->y = scr_ytop + y; + rect->w = FFMAX(width, 1); + rect->h = FFMAX(height, 1); +} + +static void get_sdl_pix_fmt_and_blendmode(int format, Uint32 *sdl_pix_fmt, SDL_BlendMode *sdl_blendmode) +{ + int i; + *sdl_blendmode = SDL_BLENDMODE_NONE; + *sdl_pix_fmt = SDL_PIXELFORMAT_UNKNOWN; + if (format == AV_PIX_FMT_RGB32 || + format == AV_PIX_FMT_RGB32_1 || + format == AV_PIX_FMT_BGR32 || + format == AV_PIX_FMT_BGR32_1) + *sdl_blendmode = SDL_BLENDMODE_BLEND; + for (i = 0; i < FF_ARRAY_ELEMS(sdl_texture_format_map) - 1; i++) { + if (format == sdl_texture_format_map[i].format) { + *sdl_pix_fmt = sdl_texture_format_map[i].texture_fmt; + return; + } + } +} + +static int upload_texture(SDL_Texture **tex, AVFrame *frame, struct SwsContext **img_convert_ctx) { + int ret = 0; + Uint32 sdl_pix_fmt; + SDL_BlendMode sdl_blendmode; + get_sdl_pix_fmt_and_blendmode(frame->format, &sdl_pix_fmt, &sdl_blendmode); + if (realloc_texture(tex, sdl_pix_fmt == SDL_PIXELFORMAT_UNKNOWN ? SDL_PIXELFORMAT_ARGB8888 : sdl_pix_fmt, frame->width, frame->height, sdl_blendmode, 0) < 0) + return -1; + switch (sdl_pix_fmt) { + case SDL_PIXELFORMAT_UNKNOWN: + /* This should only happen if we are not using avfilter... */ + *img_convert_ctx = sws_getCachedContext(*img_convert_ctx, + frame->width, frame->height, frame->format, frame->width, frame->height, + AV_PIX_FMT_BGRA, sws_flags, NULL, NULL, NULL); + if (*img_convert_ctx != NULL) { + uint8_t *pixels[4]; + int pitch[4]; + if (!SDL_LockTexture(*tex, NULL, (void **)pixels, pitch)) { + sws_scale(*img_convert_ctx, (const uint8_t * const *)frame->data, frame->linesize, + 0, frame->height, pixels, pitch); + SDL_UnlockTexture(*tex); + } + } else { + av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n"); + ret = -1; + } + break; + case SDL_PIXELFORMAT_IYUV: + if (frame->linesize[0] > 0 && frame->linesize[1] > 0 && frame->linesize[2] > 0) { + ret = SDL_UpdateYUVTexture(*tex, NULL, frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); + } else if (frame->linesize[0] < 0 && frame->linesize[1] < 0 && frame->linesize[2] < 0) { + ret = SDL_UpdateYUVTexture(*tex, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0], + frame->data[1] + frame->linesize[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[1], + frame->data[2] + frame->linesize[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[2]); + } else { + av_log(NULL, AV_LOG_ERROR, "Mixed negative and positive linesizes are not supported.\n"); + return -1; + } + break; + default: + if (frame->linesize[0] < 0) { + ret = SDL_UpdateTexture(*tex, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0]); + } else { + ret = SDL_UpdateTexture(*tex, NULL, frame->data[0], frame->linesize[0]); + } + break; + } + return ret; +} + +static void video_image_display(VideoState *is) +{ + Frame *vp; + Frame *sp = NULL; + SDL_Rect rect; + + vp = frame_queue_peek_last(&is->pictq); + if (is->subtitle_st) { + if (frame_queue_nb_remaining(&is->subpq) > 0) { + sp = frame_queue_peek(&is->subpq); + + if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) { + if (!sp->uploaded) { + uint8_t* pixels[4]; + int pitch[4]; + int i; + if (!sp->width || !sp->height) { + sp->width = vp->width; + sp->height = vp->height; + } + if (realloc_texture(&is->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0) + return; + + for (i = 0; i < sp->sub.num_rects; i++) { + AVSubtitleRect *sub_rect = sp->sub.rects[i]; + + sub_rect->x = av_clip(sub_rect->x, 0, sp->width ); + sub_rect->y = av_clip(sub_rect->y, 0, sp->height); + sub_rect->w = av_clip(sub_rect->w, 0, sp->width - sub_rect->x); + sub_rect->h = av_clip(sub_rect->h, 0, sp->height - sub_rect->y); + + is->sub_convert_ctx = sws_getCachedContext(is->sub_convert_ctx, + sub_rect->w, sub_rect->h, AV_PIX_FMT_PAL8, + sub_rect->w, sub_rect->h, AV_PIX_FMT_BGRA, + 0, NULL, NULL, NULL); + if (!is->sub_convert_ctx) { + av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n"); + return; + } + if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch)) { + sws_scale(is->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize, + 0, sub_rect->h, pixels, pitch); + SDL_UnlockTexture(is->sub_texture); + } + } + sp->uploaded = 1; + } + } else + sp = NULL; + } + } + + calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp->width, vp->height, vp->sar); + + if (!vp->uploaded) { + if (upload_texture(&is->vid_texture, vp->frame, &is->img_convert_ctx) < 0) + return; + vp->uploaded = 1; + vp->flip_v = vp->frame->linesize[0] < 0; + } + + SDL_RenderCopyEx(renderer, is->vid_texture, NULL, &rect, 0, NULL, vp->flip_v ? SDL_FLIP_VERTICAL : 0); + if (sp) { +#if USE_ONEPASS_SUBTITLE_RENDER + SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect); +#else + int i; + double xratio = (double)rect.w / (double)sp->width; + double yratio = (double)rect.h / (double)sp->height; + for (i = 0; i < sp->sub.num_rects; i++) { + SDL_Rect *sub_rect = (SDL_Rect*)sp->sub.rects[i]; + SDL_Rect target = {.x = rect.x + sub_rect->x * xratio, + .y = rect.y + sub_rect->y * yratio, + .w = sub_rect->w * xratio, + .h = sub_rect->h * yratio}; + SDL_RenderCopy(renderer, is->sub_texture, sub_rect, &target); + } +#endif + } +} + +static inline int compute_mod(int a, int b) +{ + return a < 0 ? a%b + b : a%b; +} + +static void video_audio_display(VideoState *s) +{ + int i, i_start, x, y1, y, ys, delay, n, nb_display_channels; + int ch, channels, h, h2; + int64_t time_diff; + int rdft_bits, nb_freq; + + for (rdft_bits = 1; (1 << rdft_bits) < 2 * s->height; rdft_bits++) + ; + nb_freq = 1 << (rdft_bits - 1); + + /* compute display index : center on currently output samples */ + channels = s->audio_tgt.channels; + nb_display_channels = channels; + if (!s->paused) { + int data_used= s->show_mode == SHOW_MODE_WAVES ? s->width : (2*nb_freq); + n = 2 * channels; + delay = s->audio_write_buf_size; + delay /= n; + + /* to be more precise, we take into account the time spent since + the last buffer computation */ + if (audio_callback_time) { + time_diff = av_gettime_relative() - audio_callback_time; + delay -= (time_diff * s->audio_tgt.freq) / 1000000; + } + + delay += 2 * data_used; + if (delay < data_used) + delay = data_used; + + i_start= x = compute_mod(s->sample_array_index - delay * channels, SAMPLE_ARRAY_SIZE); + if (s->show_mode == SHOW_MODE_WAVES) { + h = INT_MIN; + for (i = 0; i < 1000; i += channels) { + int idx = (SAMPLE_ARRAY_SIZE + x - i) % SAMPLE_ARRAY_SIZE; + int a = s->sample_array[idx]; + int b = s->sample_array[(idx + 4 * channels) % SAMPLE_ARRAY_SIZE]; + int c = s->sample_array[(idx + 5 * channels) % SAMPLE_ARRAY_SIZE]; + int d = s->sample_array[(idx + 9 * channels) % SAMPLE_ARRAY_SIZE]; + int score = a - d; + if (h < score && (b ^ c) < 0) { + h = score; + i_start = idx; + } + } + } + + s->last_i_start = i_start; + } else { + i_start = s->last_i_start; + } + + if (s->show_mode == SHOW_MODE_WAVES) { + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + /* total height for one channel */ + h = s->height / nb_display_channels; + /* graph height / 2 */ + h2 = (h * 9) / 20; + for (ch = 0; ch < nb_display_channels; ch++) { + i = i_start + ch; + y1 = s->ytop + ch * h + (h / 2); /* position of center line */ + for (x = 0; x < s->width; x++) { + y = (s->sample_array[i] * h2) >> 15; + if (y < 0) { + y = -y; + ys = y1 - y; + } else { + ys = y1; + } + fill_rectangle(s->xleft + x, ys, 1, y); + i += channels; + if (i >= SAMPLE_ARRAY_SIZE) + i -= SAMPLE_ARRAY_SIZE; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + + for (ch = 1; ch < nb_display_channels; ch++) { + y = s->ytop + ch * h; + fill_rectangle(s->xleft, y, s->width, 1); + } + } else { + if (realloc_texture(&s->vis_texture, SDL_PIXELFORMAT_ARGB8888, s->width, s->height, SDL_BLENDMODE_NONE, 1) < 0) + return; + + nb_display_channels= FFMIN(nb_display_channels, 2); + if (rdft_bits != s->rdft_bits) { + av_rdft_end(s->rdft); + av_free(s->rdft_data); + s->rdft = av_rdft_init(rdft_bits, DFT_R2C); + s->rdft_bits = rdft_bits; + s->rdft_data = av_malloc_array(nb_freq, 4 *sizeof(*s->rdft_data)); + } + if (!s->rdft || !s->rdft_data){ + av_log(NULL, AV_LOG_ERROR, "Failed to allocate buffers for RDFT, switching to waves display\n"); + s->show_mode = SHOW_MODE_WAVES; + } else { + FFTSample *data[2]; + SDL_Rect rect = {.x = s->xpos, .y = 0, .w = 1, .h = s->height}; + uint32_t *pixels; + int pitch; + for (ch = 0; ch < nb_display_channels; ch++) { + data[ch] = s->rdft_data + 2 * nb_freq * ch; + i = i_start + ch; + for (x = 0; x < 2 * nb_freq; x++) { + double w = (x-nb_freq) * (1.0 / nb_freq); + data[ch][x] = s->sample_array[i] * (1.0 - w * w); + i += channels; + if (i >= SAMPLE_ARRAY_SIZE) + i -= SAMPLE_ARRAY_SIZE; + } + av_rdft_calc(s->rdft, data[ch]); + } + /* Least efficient way to do this, we should of course + * directly access it but it is more than fast enough. */ + if (!SDL_LockTexture(s->vis_texture, &rect, (void **)&pixels, &pitch)) { + pitch >>= 2; + pixels += pitch * s->height; + for (y = 0; y < s->height; y++) { + double w = 1 / sqrt(nb_freq); + int a = sqrt(w * sqrt(data[0][2 * y + 0] * data[0][2 * y + 0] + data[0][2 * y + 1] * data[0][2 * y + 1])); + int b = (nb_display_channels == 2 ) ? sqrt(w * hypot(data[1][2 * y + 0], data[1][2 * y + 1])) + : a; + a = FFMIN(a, 255); + b = FFMIN(b, 255); + pixels -= pitch; + *pixels = (a << 16) + (b << 8) + ((a+b) >> 1); + } + SDL_UnlockTexture(s->vis_texture); + } + SDL_RenderCopy(renderer, s->vis_texture, NULL, NULL); + } + if (!s->paused) + s->xpos++; + if (s->xpos >= s->width) + s->xpos= s->xleft; + } +} + +static void stream_component_close(VideoState *is, int stream_index) +{ + AVFormatContext *ic = is->ic; + AVCodecParameters *codecpar; + + if (stream_index < 0 || stream_index >= ic->nb_streams) + return; + codecpar = ic->streams[stream_index]->codecpar; + + switch (codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + decoder_abort(&is->auddec, &is->sampq); + SDL_CloseAudio(); + decoder_destroy(&is->auddec); + swr_free(&is->swr_ctx); + av_freep(&is->audio_buf1); + is->audio_buf1_size = 0; + is->audio_buf = NULL; + + if (is->rdft) { + av_rdft_end(is->rdft); + av_freep(&is->rdft_data); + is->rdft = NULL; + is->rdft_bits = 0; + } + break; + case AVMEDIA_TYPE_VIDEO: + decoder_abort(&is->viddec, &is->pictq); + decoder_destroy(&is->viddec); + break; + case AVMEDIA_TYPE_SUBTITLE: + decoder_abort(&is->subdec, &is->subpq); + decoder_destroy(&is->subdec); + break; + default: + break; + } + + ic->streams[stream_index]->discard = AVDISCARD_ALL; + switch (codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + is->audio_st = NULL; + is->audio_stream = -1; + break; + case AVMEDIA_TYPE_VIDEO: + is->video_st = NULL; + is->video_stream = -1; + break; + case AVMEDIA_TYPE_SUBTITLE: + is->subtitle_st = NULL; + is->subtitle_stream = -1; + break; + default: + break; + } +} + +static void stream_close(VideoState *is) +{ + /* XXX: use a special url_shutdown call to abort parse cleanly */ + is->abort_request = 1; + SDL_WaitThread(is->read_tid, NULL); + + /* close each stream */ + if (is->audio_stream >= 0) + stream_component_close(is, is->audio_stream); + if (is->video_stream >= 0) + stream_component_close(is, is->video_stream); + if (is->subtitle_stream >= 0) + stream_component_close(is, is->subtitle_stream); + + avformat_close_input(&is->ic); + + packet_queue_destroy(&is->videoq); + packet_queue_destroy(&is->audioq); + packet_queue_destroy(&is->subtitleq); + + /* free all pictures */ + frame_queue_destory(&is->pictq); + frame_queue_destory(&is->sampq); + frame_queue_destory(&is->subpq); + SDL_DestroyCond(is->continue_read_thread); + sws_freeContext(is->img_convert_ctx); + sws_freeContext(is->sub_convert_ctx); + av_free(is->filename); + if (is->vis_texture) + SDL_DestroyTexture(is->vis_texture); + if (is->vid_texture) + SDL_DestroyTexture(is->vid_texture); + if (is->sub_texture) + SDL_DestroyTexture(is->sub_texture); + av_free(is); +} + +static void do_exit(VideoState *is) +{ + if (is) { + stream_close(is); + } + if (renderer) + SDL_DestroyRenderer(renderer); + if (window) + SDL_DestroyWindow(window); + av_lockmgr_register(NULL); + uninit_opts(); +#if CONFIG_AVFILTER + av_freep(&vfilters_list); +#endif + avformat_network_deinit(); + if (show_status) + printf("\n"); + SDL_Quit(); + av_log(NULL, AV_LOG_QUIET, "%s", ""); + exit(0); +} + +static void sigterm_handler(int sig) +{ + exit(123); +} + +static void set_default_window_size(int width, int height, AVRational sar) +{ + SDL_Rect rect; + calculate_display_rect(&rect, 0, 0, INT_MAX, height, width, height, sar); + default_width = rect.w; + default_height = rect.h; +} + +static int video_open(VideoState *is) +{ + int w,h; + + if (screen_width) { + w = screen_width; + h = screen_height; + } else { + w = default_width; + h = default_height; + } + + if (!window) { + int flags = SDL_WINDOW_SHOWN; + if (!window_title) + window_title = input_filename; + if (is_full_screen) + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + if (borderless) + flags |= SDL_WINDOW_BORDERLESS; + else + flags |= SDL_WINDOW_RESIZABLE; + window = SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, flags); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + if (window) { + SDL_RendererInfo info; + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError()); + renderer = SDL_CreateRenderer(window, -1, 0); + } + if (renderer) { + if (!SDL_GetRendererInfo(renderer, &info)) + av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", info.name); + } + } + } else { + SDL_SetWindowSize(window, w, h); + } + + if (!window || !renderer) { + av_log(NULL, AV_LOG_FATAL, "SDL: could not set video mode - exiting\n"); + do_exit(is); + } + + is->width = w; + is->height = h; + + return 0; +} + +/* display the current picture, if any */ +static void video_display(VideoState *is) +{ + if (!window) + video_open(is); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO) + video_audio_display(is); + else if (is->video_st) + video_image_display(is); + SDL_RenderPresent(renderer); +} + +static double get_clock(Clock *c) +{ + if (*c->queue_serial != c->serial) + return NAN; + if (c->paused) { + return c->pts; + } else { + double time = av_gettime_relative() / 1000000.0; + return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed); + } +} + +static void set_clock_at(Clock *c, double pts, int serial, double time) +{ + c->pts = pts; + c->last_updated = time; + c->pts_drift = c->pts - time; + c->serial = serial; +} + +static void set_clock(Clock *c, double pts, int serial) +{ + double time = av_gettime_relative() / 1000000.0; + set_clock_at(c, pts, serial, time); +} + +static void set_clock_speed(Clock *c, double speed) +{ + set_clock(c, get_clock(c), c->serial); + c->speed = speed; +} + +static void init_clock(Clock *c, int *queue_serial) +{ + c->speed = 1.0; + c->paused = 0; + c->queue_serial = queue_serial; + set_clock(c, NAN, -1); +} + +static void sync_clock_to_slave(Clock *c, Clock *slave) +{ + double clock = get_clock(c); + double slave_clock = get_clock(slave); + if (!isnan(slave_clock) && (isnan(clock) || fabs(clock - slave_clock) > AV_NOSYNC_THRESHOLD)) + set_clock(c, slave_clock, slave->serial); +} + +static int get_master_sync_type(VideoState *is) { + if (is->av_sync_type == AV_SYNC_VIDEO_MASTER) { + if (is->video_st) + return AV_SYNC_VIDEO_MASTER; + else + return AV_SYNC_AUDIO_MASTER; + } else if (is->av_sync_type == AV_SYNC_AUDIO_MASTER) { + if (is->audio_st) + return AV_SYNC_AUDIO_MASTER; + else + return AV_SYNC_EXTERNAL_CLOCK; + } else { + return AV_SYNC_EXTERNAL_CLOCK; + } +} + +/* get the current master clock value */ +static double get_master_clock(VideoState *is) +{ + double val; + + switch (get_master_sync_type(is)) { + case AV_SYNC_VIDEO_MASTER: + val = get_clock(&is->vidclk); + break; + case AV_SYNC_AUDIO_MASTER: + val = get_clock(&is->audclk); + break; + default: + val = get_clock(&is->extclk); + break; + } + return val; +} + +static void check_external_clock_speed(VideoState *is) { + if (is->video_stream >= 0 && is->videoq.nb_packets <= EXTERNAL_CLOCK_MIN_FRAMES || + is->audio_stream >= 0 && is->audioq.nb_packets <= EXTERNAL_CLOCK_MIN_FRAMES) { + set_clock_speed(&is->extclk, FFMAX(EXTERNAL_CLOCK_SPEED_MIN, is->extclk.speed - EXTERNAL_CLOCK_SPEED_STEP)); + } else if ((is->video_stream < 0 || is->videoq.nb_packets > EXTERNAL_CLOCK_MAX_FRAMES) && + (is->audio_stream < 0 || is->audioq.nb_packets > EXTERNAL_CLOCK_MAX_FRAMES)) { + set_clock_speed(&is->extclk, FFMIN(EXTERNAL_CLOCK_SPEED_MAX, is->extclk.speed + EXTERNAL_CLOCK_SPEED_STEP)); + } else { + double speed = is->extclk.speed; + if (speed != 1.0) + set_clock_speed(&is->extclk, speed + EXTERNAL_CLOCK_SPEED_STEP * (1.0 - speed) / fabs(1.0 - speed)); + } +} + +/* seek in the stream */ +static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes) +{ + if (!is->seek_req) { + is->seek_pos = pos; + is->seek_rel = rel; + is->seek_flags &= ~AVSEEK_FLAG_BYTE; + if (seek_by_bytes) + is->seek_flags |= AVSEEK_FLAG_BYTE; + is->seek_req = 1; + SDL_CondSignal(is->continue_read_thread); + } +} + +/* pause or resume the video */ +static void stream_toggle_pause(VideoState *is) +{ + if (is->paused) { + is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated; + if (is->read_pause_return != AVERROR(ENOSYS)) { + is->vidclk.paused = 0; + } + set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial); + } + set_clock(&is->extclk, get_clock(&is->extclk), is->extclk.serial); + is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused; +} + +static void toggle_pause(VideoState *is) +{ + stream_toggle_pause(is); + is->step = 0; +} + +static void toggle_mute(VideoState *is) +{ + is->muted = !is->muted; +} + +static void update_volume(VideoState *is, int sign, double step) +{ + double volume_level = is->audio_volume ? (20 * log(is->audio_volume / (double)SDL_MIX_MAXVOLUME) / log(10)) : -1000.0; + int new_volume = lrint(SDL_MIX_MAXVOLUME * pow(10.0, (volume_level + sign * step) / 20.0)); + is->audio_volume = av_clip(is->audio_volume == new_volume ? (is->audio_volume + sign) : new_volume, 0, SDL_MIX_MAXVOLUME); +} + +static void step_to_next_frame(VideoState *is) +{ + /* if the stream is paused unpause it, then step */ + if (is->paused) + stream_toggle_pause(is); + is->step = 1; +} + +static double compute_target_delay(double delay, VideoState *is) +{ + double sync_threshold, diff = 0; + + /* update delay to follow master synchronisation source */ + if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) { + /* if video is slave, we try to correct big delays by + duplicating or deleting a frame */ + diff = get_clock(&is->vidclk) - get_master_clock(is); + + /* skip or repeat frame. We take into account the + delay to compute the threshold. I still don't know + if it is the best guess */ + sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay)); + if (!isnan(diff) && fabs(diff) < is->max_frame_duration) { + if (diff <= -sync_threshold) + delay = FFMAX(0, delay + diff); + else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) + delay = delay + diff; + else if (diff >= sync_threshold) + delay = 2 * delay; + } + } + + av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n", + delay, -diff); + + return delay; +} + +static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) { + if (vp->serial == nextvp->serial) { + double duration = nextvp->pts - vp->pts; + if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration) + return vp->duration; + else + return duration; + } else { + return 0.0; + } +} + +static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) { + /* update current video pts */ + set_clock(&is->vidclk, pts, serial); + sync_clock_to_slave(&is->extclk, &is->vidclk); +} + +/* called to display each frame */ +static void video_refresh(void *opaque, double *remaining_time) +{ + VideoState *is = opaque; + double time; + + Frame *sp, *sp2; + + if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime) + check_external_clock_speed(is); + + if (!display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) { + time = av_gettime_relative() / 1000000.0; + if (is->force_refresh || is->last_vis_time + rdftspeed < time) { + video_display(is); + is->last_vis_time = time; + } + *remaining_time = FFMIN(*remaining_time, is->last_vis_time + rdftspeed - time); + } + + if (is->video_st) { +retry: + if (frame_queue_nb_remaining(&is->pictq) == 0) { + // nothing to do, no picture to display in the queue + } else { + double last_duration, duration, delay; + Frame *vp, *lastvp; + + /* dequeue the picture */ + lastvp = frame_queue_peek_last(&is->pictq); + vp = frame_queue_peek(&is->pictq); + + if (vp->serial != is->videoq.serial) { + frame_queue_next(&is->pictq); + goto retry; + } + + if (lastvp->serial != vp->serial) + is->frame_timer = av_gettime_relative() / 1000000.0; + + if (is->paused) + goto display; + + /* compute nominal last_duration */ + last_duration = vp_duration(is, lastvp, vp); + delay = compute_target_delay(last_duration, is); + + time= av_gettime_relative()/1000000.0; + if (time < is->frame_timer + delay) { + *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); + goto display; + } + + is->frame_timer += delay; + if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) + is->frame_timer = time; + + SDL_LockMutex(is->pictq.mutex); + if (!isnan(vp->pts)) + update_video_pts(is, vp->pts, vp->pos, vp->serial); + SDL_UnlockMutex(is->pictq.mutex); + + if (frame_queue_nb_remaining(&is->pictq) > 1) { + Frame *nextvp = frame_queue_peek_next(&is->pictq); + duration = vp_duration(is, vp, nextvp); + if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){ + is->frame_drops_late++; + frame_queue_next(&is->pictq); + goto retry; + } + } + + if (is->subtitle_st) { + while (frame_queue_nb_remaining(&is->subpq) > 0) { + sp = frame_queue_peek(&is->subpq); + + if (frame_queue_nb_remaining(&is->subpq) > 1) + sp2 = frame_queue_peek_next(&is->subpq); + else + sp2 = NULL; + + if (sp->serial != is->subtitleq.serial + || (is->vidclk.pts > (sp->pts + ((float) sp->sub.end_display_time / 1000))) + || (sp2 && is->vidclk.pts > (sp2->pts + ((float) sp2->sub.start_display_time / 1000)))) + { + if (sp->uploaded) { + int i; + for (i = 0; i < sp->sub.num_rects; i++) { + AVSubtitleRect *sub_rect = sp->sub.rects[i]; + uint8_t *pixels; + int pitch, j; + + if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)&pixels, &pitch)) { + for (j = 0; j < sub_rect->h; j++, pixels += pitch) + memset(pixels, 0, sub_rect->w << 2); + SDL_UnlockTexture(is->sub_texture); + } + } + } + frame_queue_next(&is->subpq); + } else { + break; + } + } + } + + frame_queue_next(&is->pictq); + is->force_refresh = 1; + + if (is->step && !is->paused) + stream_toggle_pause(is); + } +display: + /* display picture */ + if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown) + video_display(is); + } + is->force_refresh = 0; + if (show_status) { + static int64_t last_time; + int64_t cur_time; + int aqsize, vqsize, sqsize; + double av_diff; + + cur_time = av_gettime_relative(); + if (!last_time || (cur_time - last_time) >= 30000) { + aqsize = 0; + vqsize = 0; + sqsize = 0; + if (is->audio_st) + aqsize = is->audioq.size; + if (is->video_st) + vqsize = is->videoq.size; + if (is->subtitle_st) + sqsize = is->subtitleq.size; + av_diff = 0; + if (is->audio_st && is->video_st) + av_diff = get_clock(&is->audclk) - get_clock(&is->vidclk); + else if (is->video_st) + av_diff = get_master_clock(is) - get_clock(&is->vidclk); + else if (is->audio_st) + av_diff = get_master_clock(is) - get_clock(&is->audclk); + av_log(NULL, AV_LOG_INFO, + "%7.2f %s:%7.3f fd=%4d aq=%5dKB vq=%5dKB sq=%5dB f=%"PRId64"/%"PRId64" \r", + get_master_clock(is), + (is->audio_st && is->video_st) ? "A-V" : (is->video_st ? "M-V" : (is->audio_st ? "M-A" : " ")), + av_diff, + is->frame_drops_early + is->frame_drops_late, + aqsize / 1024, + vqsize / 1024, + sqsize, + is->video_st ? is->viddec.avctx->pts_correction_num_faulty_dts : 0, + is->video_st ? is->viddec.avctx->pts_correction_num_faulty_pts : 0); + fflush(stdout); + last_time = cur_time; + } + } +} + +static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial) +{ + Frame *vp; + +#if defined(DEBUG_SYNC) + printf("frame_type=%c pts=%0.3f\n", + av_get_picture_type_char(src_frame->pict_type), pts); +#endif + + if (!(vp = frame_queue_peek_writable(&is->pictq))) + return -1; + + vp->sar = src_frame->sample_aspect_ratio; + vp->uploaded = 0; + + vp->width = src_frame->width; + vp->height = src_frame->height; + vp->format = src_frame->format; + + vp->pts = pts; + vp->duration = duration; + vp->pos = pos; + vp->serial = serial; + + set_default_window_size(vp->width, vp->height, vp->sar); + + av_frame_move_ref(vp->frame, src_frame); + frame_queue_push(&is->pictq); + return 0; +} + +static int get_video_frame(VideoState *is, AVFrame *frame) +{ + int got_picture; + + if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0) + return -1; + + if (got_picture) { + double dpts = NAN; + + if (frame->pts != AV_NOPTS_VALUE) + dpts = av_q2d(is->video_st->time_base) * frame->pts; + + frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame); + + if (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) { + if (frame->pts != AV_NOPTS_VALUE) { + double diff = dpts - get_master_clock(is); + if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD && + diff - is->frame_last_filter_delay < 0 && + is->viddec.pkt_serial == is->vidclk.serial && + is->videoq.nb_packets) { + is->frame_drops_early++; + av_frame_unref(frame); + got_picture = 0; + } + } + } + } + + return got_picture; +} + +#if CONFIG_AVFILTER +static int configure_filtergraph(AVFilterGraph *graph, const char *filtergraph, + AVFilterContext *source_ctx, AVFilterContext *sink_ctx) +{ + int ret, i; + int nb_filters = graph->nb_filters; + AVFilterInOut *outputs = NULL, *inputs = NULL; + + if (filtergraph) { + outputs = avfilter_inout_alloc(); + inputs = avfilter_inout_alloc(); + if (!outputs || !inputs) { + ret = AVERROR(ENOMEM); + goto fail; + } + + outputs->name = av_strdup("in"); + outputs->filter_ctx = source_ctx; + outputs->pad_idx = 0; + outputs->next = NULL; + + inputs->name = av_strdup("out"); + inputs->filter_ctx = sink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + + if ((ret = avfilter_graph_parse_ptr(graph, filtergraph, &inputs, &outputs, NULL)) < 0) + goto fail; + } else { + if ((ret = avfilter_link(source_ctx, 0, sink_ctx, 0)) < 0) + goto fail; + } + + /* Reorder the filters to ensure that inputs of the custom filters are merged first */ + for (i = 0; i < graph->nb_filters - nb_filters; i++) + FFSWAP(AVFilterContext*, graph->filters[i], graph->filters[i + nb_filters]); + + ret = avfilter_graph_config(graph, NULL); +fail: + avfilter_inout_free(&outputs); + avfilter_inout_free(&inputs); + return ret; +} + +static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const char *vfilters, AVFrame *frame) +{ + enum AVPixelFormat pix_fmts[FF_ARRAY_ELEMS(sdl_texture_format_map)]; + char sws_flags_str[512] = ""; + char buffersrc_args[256]; + int ret; + AVFilterContext *filt_src = NULL, *filt_out = NULL, *last_filter = NULL; + AVCodecParameters *codecpar = is->video_st->codecpar; + AVRational fr = av_guess_frame_rate(is->ic, is->video_st, NULL); + AVDictionaryEntry *e = NULL; + int i; + + for (i = 0; i < FF_ARRAY_ELEMS(pix_fmts); i++) + pix_fmts[i] = sdl_texture_format_map[i].format; + + while ((e = av_dict_get(sws_dict, "", e, AV_DICT_IGNORE_SUFFIX))) { + if (!strcmp(e->key, "sws_flags")) { + av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", "flags", e->value); + } else + av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", e->key, e->value); + } + if (strlen(sws_flags_str)) + sws_flags_str[strlen(sws_flags_str)-1] = '\0'; + + graph->scale_sws_opts = av_strdup(sws_flags_str); + + snprintf(buffersrc_args, sizeof(buffersrc_args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + frame->width, frame->height, frame->format, + is->video_st->time_base.num, is->video_st->time_base.den, + codecpar->sample_aspect_ratio.num, FFMAX(codecpar->sample_aspect_ratio.den, 1)); + if (fr.num && fr.den) + av_strlcatf(buffersrc_args, sizeof(buffersrc_args), ":frame_rate=%d/%d", fr.num, fr.den); + + if ((ret = avfilter_graph_create_filter(&filt_src, + avfilter_get_by_name("buffer"), + "ffplay_buffer", buffersrc_args, NULL, + graph)) < 0) + goto fail; + + ret = avfilter_graph_create_filter(&filt_out, + avfilter_get_by_name("buffersink"), + "ffplay_buffersink", NULL, NULL, graph); + if (ret < 0) + goto fail; + + if ((ret = av_opt_set_int_list(filt_out, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0) + goto fail; + + last_filter = filt_out; + +/* Note: this macro adds a filter before the lastly added filter, so the + * processing order of the filters is in reverse */ +#define INSERT_FILT(name, arg) do { \ + AVFilterContext *filt_ctx; \ + \ + ret = avfilter_graph_create_filter(&filt_ctx, \ + avfilter_get_by_name(name), \ + "ffplay_" name, arg, NULL, graph); \ + if (ret < 0) \ + goto fail; \ + \ + ret = avfilter_link(filt_ctx, 0, last_filter, 0); \ + if (ret < 0) \ + goto fail; \ + \ + last_filter = filt_ctx; \ +} while (0) + + if (autorotate) { + double theta = get_rotation(is->video_st); + + if (fabs(theta - 90) < 1.0) { + INSERT_FILT("transpose", "clock"); + } else if (fabs(theta - 180) < 1.0) { + INSERT_FILT("hflip", NULL); + INSERT_FILT("vflip", NULL); + } else if (fabs(theta - 270) < 1.0) { + INSERT_FILT("transpose", "cclock"); + } else if (fabs(theta) > 1.0) { + char rotate_buf[64]; + snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta); + INSERT_FILT("rotate", rotate_buf); + } + } + + if ((ret = configure_filtergraph(graph, vfilters, filt_src, last_filter)) < 0) + goto fail; + + is->in_video_filter = filt_src; + is->out_video_filter = filt_out; + +fail: + return ret; +} + +static int configure_audio_filters(VideoState *is, const char *afilters, int force_output_format) +{ + static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE }; + int sample_rates[2] = { 0, -1 }; + int64_t channel_layouts[2] = { 0, -1 }; + int channels[2] = { 0, -1 }; + AVFilterContext *filt_asrc = NULL, *filt_asink = NULL; + char aresample_swr_opts[512] = ""; + AVDictionaryEntry *e = NULL; + char asrc_args[256]; + int ret; + + avfilter_graph_free(&is->agraph); + if (!(is->agraph = avfilter_graph_alloc())) + return AVERROR(ENOMEM); + + while ((e = av_dict_get(swr_opts, "", e, AV_DICT_IGNORE_SUFFIX))) + av_strlcatf(aresample_swr_opts, sizeof(aresample_swr_opts), "%s=%s:", e->key, e->value); + if (strlen(aresample_swr_opts)) + aresample_swr_opts[strlen(aresample_swr_opts)-1] = '\0'; + av_opt_set(is->agraph, "aresample_swr_opts", aresample_swr_opts, 0); + + ret = snprintf(asrc_args, sizeof(asrc_args), + "sample_rate=%d:sample_fmt=%s:channels=%d:time_base=%d/%d", + is->audio_filter_src.freq, av_get_sample_fmt_name(is->audio_filter_src.fmt), + is->audio_filter_src.channels, + 1, is->audio_filter_src.freq); + if (is->audio_filter_src.channel_layout) + snprintf(asrc_args + ret, sizeof(asrc_args) - ret, + ":channel_layout=0x%"PRIx64, is->audio_filter_src.channel_layout); + + ret = avfilter_graph_create_filter(&filt_asrc, + avfilter_get_by_name("abuffer"), "ffplay_abuffer", + asrc_args, NULL, is->agraph); + if (ret < 0) + goto end; + + + ret = avfilter_graph_create_filter(&filt_asink, + avfilter_get_by_name("abuffersink"), "ffplay_abuffersink", + NULL, NULL, is->agraph); + if (ret < 0) + goto end; + + if ((ret = av_opt_set_int_list(filt_asink, "sample_fmts", sample_fmts, AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 1, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + + if (force_output_format) { + channel_layouts[0] = is->audio_tgt.channel_layout; + channels [0] = is->audio_tgt.channels; + sample_rates [0] = is->audio_tgt.freq; + if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 0, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if ((ret = av_opt_set_int_list(filt_asink, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if ((ret = av_opt_set_int_list(filt_asink, "channel_counts" , channels , -1, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + if ((ret = av_opt_set_int_list(filt_asink, "sample_rates" , sample_rates , -1, AV_OPT_SEARCH_CHILDREN)) < 0) + goto end; + } + + + if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0) + goto end; + + is->in_audio_filter = filt_asrc; + is->out_audio_filter = filt_asink; + +end: + if (ret < 0) + avfilter_graph_free(&is->agraph); + return ret; +} +#endif /* CONFIG_AVFILTER */ + +static int audio_thread(void *arg) +{ + VideoState *is = arg; + AVFrame *frame = av_frame_alloc(); + Frame *af; +#if CONFIG_AVFILTER + int last_serial = -1; + int64_t dec_channel_layout; + int reconfigure; +#endif + int got_frame = 0; + AVRational tb; + int ret = 0; + + if (!frame) + return AVERROR(ENOMEM); + + do { + if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0) + goto the_end; + + if (got_frame) { + tb = (AVRational){1, frame->sample_rate}; + +#if CONFIG_AVFILTER + dec_channel_layout = get_valid_channel_layout(frame->channel_layout, frame->channels); + + reconfigure = + cmp_audio_fmts(is->audio_filter_src.fmt, is->audio_filter_src.channels, + frame->format, frame->channels) || + is->audio_filter_src.channel_layout != dec_channel_layout || + is->audio_filter_src.freq != frame->sample_rate || + is->auddec.pkt_serial != last_serial; + + if (reconfigure) { + char buf1[1024], buf2[1024]; + av_get_channel_layout_string(buf1, sizeof(buf1), -1, is->audio_filter_src.channel_layout); + av_get_channel_layout_string(buf2, sizeof(buf2), -1, dec_channel_layout); + av_log(NULL, AV_LOG_DEBUG, + "Audio frame changed from rate:%d ch:%d fmt:%s layout:%s serial:%d to rate:%d ch:%d fmt:%s layout:%s serial:%d\n", + is->audio_filter_src.freq, is->audio_filter_src.channels, av_get_sample_fmt_name(is->audio_filter_src.fmt), buf1, last_serial, + frame->sample_rate, frame->channels, av_get_sample_fmt_name(frame->format), buf2, is->auddec.pkt_serial); + + is->audio_filter_src.fmt = frame->format; + is->audio_filter_src.channels = frame->channels; + is->audio_filter_src.channel_layout = dec_channel_layout; + is->audio_filter_src.freq = frame->sample_rate; + last_serial = is->auddec.pkt_serial; + + if ((ret = configure_audio_filters(is, afilters, 1)) < 0) + goto the_end; + } + + if ((ret = av_buffersrc_add_frame(is->in_audio_filter, frame)) < 0) + goto the_end; + + while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) { + tb = av_buffersink_get_time_base(is->out_audio_filter); +#endif + if (!(af = frame_queue_peek_writable(&is->sampq))) + goto the_end; + + af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); + af->pos = frame->pkt_pos; + af->serial = is->auddec.pkt_serial; + af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate}); + + av_frame_move_ref(af->frame, frame); + frame_queue_push(&is->sampq); + +#if CONFIG_AVFILTER + if (is->audioq.serial != is->auddec.pkt_serial) + break; + } + if (ret == AVERROR_EOF) + is->auddec.finished = is->auddec.pkt_serial; +#endif + } + } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF); + the_end: +#if CONFIG_AVFILTER + avfilter_graph_free(&is->agraph); +#endif + av_frame_free(&frame); + return ret; +} + +static int decoder_start(Decoder *d, int (*fn)(void *), void *arg) +{ + packet_queue_start(d->queue); + d->decoder_tid = SDL_CreateThread(fn, "decoder", arg); + if (!d->decoder_tid) { + av_log(NULL, AV_LOG_ERROR, "SDL_CreateThread(): %s\n", SDL_GetError()); + return AVERROR(ENOMEM); + } + return 0; +} + +static int video_thread(void *arg) +{ + VideoState *is = arg; + AVFrame *frame = av_frame_alloc(); + double pts; + double duration; + int ret; + AVRational tb = is->video_st->time_base; + AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL); + +#if CONFIG_AVFILTER + AVFilterGraph *graph = avfilter_graph_alloc(); + AVFilterContext *filt_out = NULL, *filt_in = NULL; + int last_w = 0; + int last_h = 0; + enum AVPixelFormat last_format = -2; + int last_serial = -1; + int last_vfilter_idx = 0; + if (!graph) { + av_frame_free(&frame); + return AVERROR(ENOMEM); + } + +#endif + + if (!frame) { +#if CONFIG_AVFILTER + avfilter_graph_free(&graph); +#endif + return AVERROR(ENOMEM); + } + + for (;;) { + ret = get_video_frame(is, frame); + if (ret < 0) + goto the_end; + if (!ret) + continue; + +#if CONFIG_AVFILTER + if ( last_w != frame->width + || last_h != frame->height + || last_format != frame->format + || last_serial != is->viddec.pkt_serial + || last_vfilter_idx != is->vfilter_idx) { + av_log(NULL, AV_LOG_DEBUG, + "Video frame changed from size:%dx%d format:%s serial:%d to size:%dx%d format:%s serial:%d\n", + last_w, last_h, + (const char *)av_x_if_null(av_get_pix_fmt_name(last_format), "none"), last_serial, + frame->width, frame->height, + (const char *)av_x_if_null(av_get_pix_fmt_name(frame->format), "none"), is->viddec.pkt_serial); + avfilter_graph_free(&graph); + graph = avfilter_graph_alloc(); + if ((ret = configure_video_filters(graph, is, vfilters_list ? vfilters_list[is->vfilter_idx] : NULL, frame)) < 0) { + SDL_Event event; + event.type = FF_QUIT_EVENT; + event.user.data1 = is; + SDL_PushEvent(&event); + goto the_end; + } + filt_in = is->in_video_filter; + filt_out = is->out_video_filter; + last_w = frame->width; + last_h = frame->height; + last_format = frame->format; + last_serial = is->viddec.pkt_serial; + last_vfilter_idx = is->vfilter_idx; + frame_rate = av_buffersink_get_frame_rate(filt_out); + } + + ret = av_buffersrc_add_frame(filt_in, frame); + if (ret < 0) + goto the_end; + + while (ret >= 0) { + is->frame_last_returned_time = av_gettime_relative() / 1000000.0; + + ret = av_buffersink_get_frame_flags(filt_out, frame, 0); + if (ret < 0) { + if (ret == AVERROR_EOF) + is->viddec.finished = is->viddec.pkt_serial; + ret = 0; + break; + } + + is->frame_last_filter_delay = av_gettime_relative() / 1000000.0 - is->frame_last_returned_time; + if (fabs(is->frame_last_filter_delay) > AV_NOSYNC_THRESHOLD / 10.0) + is->frame_last_filter_delay = 0; + tb = av_buffersink_get_time_base(filt_out); +#endif + duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0); + pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); + ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial); + av_frame_unref(frame); +#if CONFIG_AVFILTER + } +#endif + + if (ret < 0) + goto the_end; + } + the_end: +#if CONFIG_AVFILTER + avfilter_graph_free(&graph); +#endif + av_frame_free(&frame); + return 0; +} + +static int subtitle_thread(void *arg) +{ + VideoState *is = arg; + Frame *sp; + int got_subtitle; + double pts; + + for (;;) { + if (!(sp = frame_queue_peek_writable(&is->subpq))) + return 0; + + if ((got_subtitle = decoder_decode_frame(&is->subdec, NULL, &sp->sub)) < 0) + break; + + pts = 0; + + if (got_subtitle && sp->sub.format == 0) { + if (sp->sub.pts != AV_NOPTS_VALUE) + pts = sp->sub.pts / (double)AV_TIME_BASE; + sp->pts = pts; + sp->serial = is->subdec.pkt_serial; + sp->width = is->subdec.avctx->width; + sp->height = is->subdec.avctx->height; + sp->uploaded = 0; + + /* now we can update the picture count */ + frame_queue_push(&is->subpq); + } else if (got_subtitle) { + avsubtitle_free(&sp->sub); + } + } + return 0; +} + +/* copy samples for viewing in editor window */ +static void update_sample_display(VideoState *is, short *samples, int samples_size) +{ + int size, len; + + size = samples_size / sizeof(short); + while (size > 0) { + len = SAMPLE_ARRAY_SIZE - is->sample_array_index; + if (len > size) + len = size; + memcpy(is->sample_array + is->sample_array_index, samples, len * sizeof(short)); + samples += len; + is->sample_array_index += len; + if (is->sample_array_index >= SAMPLE_ARRAY_SIZE) + is->sample_array_index = 0; + size -= len; + } +} + +/* return the wanted number of samples to get better sync if sync_type is video + * or external master clock */ +static int synchronize_audio(VideoState *is, int nb_samples) +{ + int wanted_nb_samples = nb_samples; + + /* if not master, then we try to remove or add samples to correct the clock */ + if (get_master_sync_type(is) != AV_SYNC_AUDIO_MASTER) { + double diff, avg_diff; + int min_nb_samples, max_nb_samples; + + diff = get_clock(&is->audclk) - get_master_clock(is); + + if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) { + is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum; + if (is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) { + /* not enough measures to have a correct estimate */ + is->audio_diff_avg_count++; + } else { + /* estimate the A-V difference */ + avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef); + + if (fabs(avg_diff) >= is->audio_diff_threshold) { + wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq); + min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100)); + max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100)); + wanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples); + } + av_log(NULL, AV_LOG_TRACE, "diff=%f adiff=%f sample_diff=%d apts=%0.3f %f\n", + diff, avg_diff, wanted_nb_samples - nb_samples, + is->audio_clock, is->audio_diff_threshold); + } + } else { + /* too big difference : may be initial PTS errors, so + reset A-V filter */ + is->audio_diff_avg_count = 0; + is->audio_diff_cum = 0; + } + } + + return wanted_nb_samples; +} + +/** + * Decode one audio frame and return its uncompressed size. + * + * The processed audio frame is decoded, converted if required, and + * stored in is->audio_buf, with size in bytes given by the return + * value. + */ +static int audio_decode_frame(VideoState *is) +{ + int data_size, resampled_data_size; + int64_t dec_channel_layout; + av_unused double audio_clock0; + int wanted_nb_samples; + Frame *af; + + if (is->paused) + return -1; + + do { +#if defined(_WIN32) + while (frame_queue_nb_remaining(&is->sampq) == 0) { + if ((av_gettime_relative() - audio_callback_time) > 1000000LL * is->audio_hw_buf_size / is->audio_tgt.bytes_per_sec / 2) + return -1; + av_usleep (1000); + } +#endif + if (!(af = frame_queue_peek_readable(&is->sampq))) + return -1; + frame_queue_next(&is->sampq); + } while (af->serial != is->audioq.serial); + + data_size = av_samples_get_buffer_size(NULL, af->frame->channels, + af->frame->nb_samples, + af->frame->format, 1); + + dec_channel_layout = + (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ? + af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels); + wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples); + + if (af->frame->format != is->audio_src.fmt || + dec_channel_layout != is->audio_src.channel_layout || + af->frame->sample_rate != is->audio_src.freq || + (wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) { + swr_free(&is->swr_ctx); + is->swr_ctx = swr_alloc_set_opts(NULL, + is->audio_tgt.channel_layout, is->audio_tgt.fmt, is->audio_tgt.freq, + dec_channel_layout, af->frame->format, af->frame->sample_rate, + 0, NULL); + if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) { + av_log(NULL, AV_LOG_ERROR, + "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n", + af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), af->frame->channels, + is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels); + swr_free(&is->swr_ctx); + return -1; + } + is->audio_src.channel_layout = dec_channel_layout; + is->audio_src.channels = af->frame->channels; + is->audio_src.freq = af->frame->sample_rate; + is->audio_src.fmt = af->frame->format; + } + + if (is->swr_ctx) { + const uint8_t **in = (const uint8_t **)af->frame->extended_data; + uint8_t **out = &is->audio_buf1; + int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + 256; + int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels, out_count, is->audio_tgt.fmt, 0); + int len2; + if (out_size < 0) { + av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n"); + return -1; + } + if (wanted_nb_samples != af->frame->nb_samples) { + if (swr_set_compensation(is->swr_ctx, (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate, + wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate) < 0) { + av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failed\n"); + return -1; + } + } + av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size); + if (!is->audio_buf1) + return AVERROR(ENOMEM); + len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples); + if (len2 < 0) { + av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n"); + return -1; + } + if (len2 == out_count) { + av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n"); + if (swr_init(is->swr_ctx) < 0) + swr_free(&is->swr_ctx); + } + is->audio_buf = is->audio_buf1; + resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt); + } else { + is->audio_buf = af->frame->data[0]; + resampled_data_size = data_size; + } + + audio_clock0 = is->audio_clock; + /* update the audio clock with the pts */ + if (!isnan(af->pts)) + is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate; + else + is->audio_clock = NAN; + is->audio_clock_serial = af->serial; +#ifdef DEBUG + { + static double last_clock; + printf("audio: delay=%0.3f clock=%0.3f clock0=%0.3f\n", + is->audio_clock - last_clock, + is->audio_clock, audio_clock0); + last_clock = is->audio_clock; + } +#endif + return resampled_data_size; +} + +/* prepare a new audio buffer */ +static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) +{ + VideoState *is = opaque; + int audio_size, len1; + + audio_callback_time = av_gettime_relative(); + + while (len > 0) { + if (is->audio_buf_index >= is->audio_buf_size) { + audio_size = audio_decode_frame(is); + if (audio_size < 0) { + /* if error, just output silence */ + is->audio_buf = NULL; + is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size; + } else { + if (is->show_mode != SHOW_MODE_VIDEO) + update_sample_display(is, (int16_t *)is->audio_buf, audio_size); + is->audio_buf_size = audio_size; + } + is->audio_buf_index = 0; + } + len1 = is->audio_buf_size - is->audio_buf_index; + if (len1 > len) + len1 = len; + if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME) + memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1); + else { + memset(stream, 0, len1); + if (!is->muted && is->audio_buf) + SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume); + } + len -= len1; + stream += len1; + is->audio_buf_index += len1; + } + is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index; + /* Let's assume the audio driver that is used by SDL has two periods. */ + if (!isnan(is->audio_clock)) { + set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0); + sync_clock_to_slave(&is->extclk, &is->audclk); + } +} + +static int audio_open(void *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params) +{ + SDL_AudioSpec wanted_spec, spec; + const char *env; + static const int next_nb_channels[] = {0, 0, 1, 6, 2, 6, 4, 6}; + static const int next_sample_rates[] = {0, 44100, 48000, 96000, 192000}; + int next_sample_rate_idx = FF_ARRAY_ELEMS(next_sample_rates) - 1; + + env = SDL_getenv("SDL_AUDIO_CHANNELS"); + if (env) { + wanted_nb_channels = atoi(env); + wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels); + } + if (!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) { + wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels); + wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX; + } + wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout); + wanted_spec.channels = wanted_nb_channels; + wanted_spec.freq = wanted_sample_rate; + if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid sample rate or channel count!\n"); + return -1; + } + while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq) + next_sample_rate_idx--; + wanted_spec.format = AUDIO_S16SYS; + wanted_spec.silence = 0; + wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC)); + wanted_spec.callback = sdl_audio_callback; + wanted_spec.userdata = opaque; + while (SDL_OpenAudio(&wanted_spec, &spec) < 0) { + av_log(NULL, AV_LOG_WARNING, "SDL_OpenAudio (%d channels, %d Hz): %s\n", + wanted_spec.channels, wanted_spec.freq, SDL_GetError()); + wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)]; + if (!wanted_spec.channels) { + wanted_spec.freq = next_sample_rates[next_sample_rate_idx--]; + wanted_spec.channels = wanted_nb_channels; + if (!wanted_spec.freq) { + av_log(NULL, AV_LOG_ERROR, + "No more combinations to try, audio open failed\n"); + return -1; + } + } + wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels); + } + if (spec.format != AUDIO_S16SYS) { + av_log(NULL, AV_LOG_ERROR, + "SDL advised audio format %d is not supported!\n", spec.format); + return -1; + } + if (spec.channels != wanted_spec.channels) { + wanted_channel_layout = av_get_default_channel_layout(spec.channels); + if (!wanted_channel_layout) { + av_log(NULL, AV_LOG_ERROR, + "SDL advised channel count %d is not supported!\n", spec.channels); + return -1; + } + } + + audio_hw_params->fmt = AV_SAMPLE_FMT_S16; + audio_hw_params->freq = spec.freq; + audio_hw_params->channel_layout = wanted_channel_layout; + audio_hw_params->channels = spec.channels; + audio_hw_params->frame_size = av_samples_get_buffer_size(NULL, audio_hw_params->channels, 1, audio_hw_params->fmt, 1); + audio_hw_params->bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params->channels, audio_hw_params->freq, audio_hw_params->fmt, 1); + if (audio_hw_params->bytes_per_sec <= 0 || audio_hw_params->frame_size <= 0) { + av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size failed\n"); + return -1; + } + return spec.size; +} + +/* open a given stream. Return 0 if OK */ +static int stream_component_open(VideoState *is, int stream_index) +{ + AVFormatContext *ic = is->ic; + AVCodecContext *avctx; + AVCodec *codec; + const char *forced_codec_name = NULL; + AVDictionary *opts = NULL; + AVDictionaryEntry *t = NULL; + int sample_rate, nb_channels; + int64_t channel_layout; + int ret = 0; + int stream_lowres = lowres; + + if (stream_index < 0 || stream_index >= ic->nb_streams) + return -1; + + avctx = avcodec_alloc_context3(NULL); + if (!avctx) + return AVERROR(ENOMEM); + + ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar); + if (ret < 0) + goto fail; + av_codec_set_pkt_timebase(avctx, ic->streams[stream_index]->time_base); + + codec = avcodec_find_decoder(avctx->codec_id); + + switch(avctx->codec_type){ + case AVMEDIA_TYPE_AUDIO : is->last_audio_stream = stream_index; forced_codec_name = audio_codec_name; break; + case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = subtitle_codec_name; break; + case AVMEDIA_TYPE_VIDEO : is->last_video_stream = stream_index; forced_codec_name = video_codec_name; break; + } + if (forced_codec_name) + codec = avcodec_find_decoder_by_name(forced_codec_name); + if (!codec) { + if (forced_codec_name) av_log(NULL, AV_LOG_WARNING, + "No codec could be found with name '%s'\n", forced_codec_name); + else av_log(NULL, AV_LOG_WARNING, + "No codec could be found with id %d\n", avctx->codec_id); + ret = AVERROR(EINVAL); + goto fail; + } + + avctx->codec_id = codec->id; + if(stream_lowres > av_codec_get_max_lowres(codec)){ + av_log(avctx, AV_LOG_WARNING, "The maximum value for lowres supported by the decoder is %d\n", + av_codec_get_max_lowres(codec)); + stream_lowres = av_codec_get_max_lowres(codec); + } + av_codec_set_lowres(avctx, stream_lowres); + +#if FF_API_EMU_EDGE + if(stream_lowres) avctx->flags |= CODEC_FLAG_EMU_EDGE; +#endif + if (fast) + avctx->flags2 |= AV_CODEC_FLAG2_FAST; +#if FF_API_EMU_EDGE + if(codec->capabilities & AV_CODEC_CAP_DR1) + avctx->flags |= CODEC_FLAG_EMU_EDGE; +#endif + + opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec); + if (!av_dict_get(opts, "threads", NULL, 0)) + av_dict_set(&opts, "threads", "auto", 0); + if (stream_lowres) + av_dict_set_int(&opts, "lowres", stream_lowres, 0); + if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO) + av_dict_set(&opts, "refcounted_frames", "1", 0); + if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) { + goto fail; + } + if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { + av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); + ret = AVERROR_OPTION_NOT_FOUND; + goto fail; + } + + is->eof = 0; + ic->streams[stream_index]->discard = AVDISCARD_DEFAULT; + switch (avctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: +#if CONFIG_AVFILTER + { + AVFilterContext *sink; + + is->audio_filter_src.freq = avctx->sample_rate; + is->audio_filter_src.channels = avctx->channels; + is->audio_filter_src.channel_layout = get_valid_channel_layout(avctx->channel_layout, avctx->channels); + is->audio_filter_src.fmt = avctx->sample_fmt; + if ((ret = configure_audio_filters(is, afilters, 0)) < 0) + goto fail; + sink = is->out_audio_filter; + sample_rate = av_buffersink_get_sample_rate(sink); + nb_channels = av_buffersink_get_channels(sink); + channel_layout = av_buffersink_get_channel_layout(sink); + } +#else + sample_rate = avctx->sample_rate; + nb_channels = avctx->channels; + channel_layout = avctx->channel_layout; +#endif + + /* prepare audio output */ + if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0) + goto fail; + is->audio_hw_buf_size = ret; + is->audio_src = is->audio_tgt; + is->audio_buf_size = 0; + is->audio_buf_index = 0; + + /* init averaging filter */ + is->audio_diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB); + is->audio_diff_avg_count = 0; + /* since we do not have a precise anough audio FIFO fullness, + we correct audio sync only if larger than this threshold */ + is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec; + + is->audio_stream = stream_index; + is->audio_st = ic->streams[stream_index]; + + decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread); + if ((is->ic->iformat->flags & (AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK)) && !is->ic->iformat->read_seek) { + is->auddec.start_pts = is->audio_st->start_time; + is->auddec.start_pts_tb = is->audio_st->time_base; + } + if ((ret = decoder_start(&is->auddec, audio_thread, is)) < 0) + goto out; + SDL_PauseAudio(0); + break; + case AVMEDIA_TYPE_VIDEO: + is->video_stream = stream_index; + is->video_st = ic->streams[stream_index]; + + decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread); + if ((ret = decoder_start(&is->viddec, video_thread, is)) < 0) + goto out; + is->queue_attachments_req = 1; + break; + case AVMEDIA_TYPE_SUBTITLE: + is->subtitle_stream = stream_index; + is->subtitle_st = ic->streams[stream_index]; + + decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread); + if ((ret = decoder_start(&is->subdec, subtitle_thread, is)) < 0) + goto out; + break; + default: + break; + } + goto out; + +fail: + avcodec_free_context(&avctx); +out: + av_dict_free(&opts); + + return ret; +} + +static int decode_interrupt_cb(void *ctx) +{ + VideoState *is = ctx; + return is->abort_request; +} + +static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue) { + return stream_id < 0 || + queue->abort_request || + (st->disposition & AV_DISPOSITION_ATTACHED_PIC) || + queue->nb_packets > MIN_FRAMES && (!queue->duration || av_q2d(st->time_base) * queue->duration > 1.0); +} + +static int is_realtime(AVFormatContext *s) +{ + if( !strcmp(s->iformat->name, "rtp") + || !strcmp(s->iformat->name, "rtsp") + || !strcmp(s->iformat->name, "sdp") + ) + return 1; + + if(s->pb && ( !strncmp(s->filename, "rtp:", 4) + || !strncmp(s->filename, "udp:", 4) + ) + ) + return 1; + return 0; +} + +/* this thread gets the stream from the disk or the network */ +static int read_thread(void *arg) +{ + VideoState *is = arg; + AVFormatContext *ic = NULL; + int err, i, ret; + int st_index[AVMEDIA_TYPE_NB]; + AVPacket pkt1, *pkt = &pkt1; + int64_t stream_start_time; + int pkt_in_play_range = 0; + AVDictionaryEntry *t; + SDL_mutex *wait_mutex = SDL_CreateMutex(); + int scan_all_pmts_set = 0; + int64_t pkt_ts; + + if (!wait_mutex) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); + ret = AVERROR(ENOMEM); + goto fail; + } + + memset(st_index, -1, sizeof(st_index)); + is->last_video_stream = is->video_stream = -1; + is->last_audio_stream = is->audio_stream = -1; + is->last_subtitle_stream = is->subtitle_stream = -1; + is->eof = 0; + + ic = avformat_alloc_context(); + if (!ic) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + ic->interrupt_callback.callback = decode_interrupt_cb; + ic->interrupt_callback.opaque = is; + if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts); + if (err < 0) { + print_error(is->filename, err); + ret = -1; + goto fail; + } + if (scan_all_pmts_set) + av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + + if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { + av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); + ret = AVERROR_OPTION_NOT_FOUND; + goto fail; + } + is->ic = ic; + + if (genpts) + ic->flags |= AVFMT_FLAG_GENPTS; + + av_format_inject_global_side_data(ic); + + if (find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts); + int orig_nb_streams = ic->nb_streams; + + err = avformat_find_stream_info(ic, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (err < 0) { + av_log(NULL, AV_LOG_WARNING, + "%s: could not find codec parameters\n", is->filename); + ret = -1; + goto fail; + } + } + + if (ic->pb) + ic->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use avio_feof() to test for the end + + if (seek_by_bytes < 0) + seek_by_bytes = !!(ic->iformat->flags & AVFMT_TS_DISCONT) && strcmp("ogg", ic->iformat->name); + + is->max_frame_duration = (ic->iformat->flags & AVFMT_TS_DISCONT) ? 10.0 : 3600.0; + + if (!window_title && (t = av_dict_get(ic->metadata, "title", NULL, 0))) + window_title = av_asprintf("%s - %s", t->value, input_filename); + + /* if seeking requested, we execute it */ + if (start_time != AV_NOPTS_VALUE) { + int64_t timestamp; + + timestamp = start_time; + /* add the stream start time */ + if (ic->start_time != AV_NOPTS_VALUE) + timestamp += ic->start_time; + ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", + is->filename, (double)timestamp / AV_TIME_BASE); + } + } + + is->realtime = is_realtime(ic); + + if (show_status) + av_dump_format(ic, 0, is->filename, 0); + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + enum AVMediaType type = st->codecpar->codec_type; + st->discard = AVDISCARD_ALL; + if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1) + if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0) + st_index[type] = i; + } + for (i = 0; i < AVMEDIA_TYPE_NB; i++) { + if (wanted_stream_spec[i] && st_index[i] == -1) { + av_log(NULL, AV_LOG_ERROR, "Stream specifier %s does not match any %s stream\n", wanted_stream_spec[i], av_get_media_type_string(i)); + st_index[i] = INT_MAX; + } + } + + if (!video_disable) + st_index[AVMEDIA_TYPE_VIDEO] = + av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, + st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0); + if (!audio_disable) + st_index[AVMEDIA_TYPE_AUDIO] = + av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, + st_index[AVMEDIA_TYPE_AUDIO], + st_index[AVMEDIA_TYPE_VIDEO], + NULL, 0); + if (!video_disable && !subtitle_disable) + st_index[AVMEDIA_TYPE_SUBTITLE] = + av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE, + st_index[AVMEDIA_TYPE_SUBTITLE], + (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ? + st_index[AVMEDIA_TYPE_AUDIO] : + st_index[AVMEDIA_TYPE_VIDEO]), + NULL, 0); + + is->show_mode = show_mode; + if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { + AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]]; + AVCodecParameters *codecpar = st->codecpar; + AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL); + if (codecpar->width) + set_default_window_size(codecpar->width, codecpar->height, sar); + } + + /* open the streams */ + if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) { + stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]); + } + + ret = -1; + if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { + ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]); + } + if (is->show_mode == SHOW_MODE_NONE) + is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT; + + if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) { + stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]); + } + + if (is->video_stream < 0 && is->audio_stream < 0) { + av_log(NULL, AV_LOG_FATAL, "Failed to open file '%s' or configure filtergraph\n", + is->filename); + ret = -1; + goto fail; + } + + if (infinite_buffer < 0 && is->realtime) + infinite_buffer = 1; + + for (;;) { + if (is->abort_request) + break; + if (is->paused != is->last_paused) { + is->last_paused = is->paused; + if (is->paused) + is->read_pause_return = av_read_pause(ic); + else + av_read_play(ic); + } +#if CONFIG_RTSP_DEMUXER || CONFIG_MMSH_PROTOCOL + if (is->paused && + (!strcmp(ic->iformat->name, "rtsp") || + (ic->pb && !strncmp(input_filename, "mmsh:", 5)))) { + /* wait 10 ms to avoid trying to get another packet */ + /* XXX: horrible */ + SDL_Delay(10); + continue; + } +#endif + if (is->seek_req) { + int64_t seek_target = is->seek_pos; + int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN; + int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX; +// FIXME the +-2 is due to rounding being not done in the correct direction in generation +// of the seek_pos/seek_rel variables + + ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "%s: error while seeking\n", is->ic->filename); + } else { + if (is->audio_stream >= 0) { + packet_queue_flush(&is->audioq); + packet_queue_put(&is->audioq, &flush_pkt); + } + if (is->subtitle_stream >= 0) { + packet_queue_flush(&is->subtitleq); + packet_queue_put(&is->subtitleq, &flush_pkt); + } + if (is->video_stream >= 0) { + packet_queue_flush(&is->videoq); + packet_queue_put(&is->videoq, &flush_pkt); + } + if (is->seek_flags & AVSEEK_FLAG_BYTE) { + set_clock(&is->extclk, NAN, 0); + } else { + set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0); + } + } + is->seek_req = 0; + is->queue_attachments_req = 1; + is->eof = 0; + if (is->paused) + step_to_next_frame(is); + } + if (is->queue_attachments_req) { + if (is->video_st && is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC) { + AVPacket copy = { 0 }; + if ((ret = av_packet_ref(©, &is->video_st->attached_pic)) < 0) + goto fail; + packet_queue_put(&is->videoq, ©); + packet_queue_put_nullpacket(&is->videoq, is->video_stream); + } + is->queue_attachments_req = 0; + } + + /* if the queue are full, no need to read more */ + if (infinite_buffer<1 && + (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE + || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) && + stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) && + stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) { + /* wait 10 ms */ + SDL_LockMutex(wait_mutex); + SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10); + SDL_UnlockMutex(wait_mutex); + continue; + } + if (!is->paused && + (!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining(&is->sampq) == 0)) && + (!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0))) { + if (loop != 1 && (!loop || --loop)) { + stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0); + } else if (autoexit) { + ret = AVERROR_EOF; + goto fail; + } + } + ret = av_read_frame(ic, pkt); + if (ret < 0) { + if ((ret == AVERROR_EOF || avio_feof(ic->pb)) && !is->eof) { + if (is->video_stream >= 0) + packet_queue_put_nullpacket(&is->videoq, is->video_stream); + if (is->audio_stream >= 0) + packet_queue_put_nullpacket(&is->audioq, is->audio_stream); + if (is->subtitle_stream >= 0) + packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream); + is->eof = 1; + } + if (ic->pb && ic->pb->error) + break; + SDL_LockMutex(wait_mutex); + SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10); + SDL_UnlockMutex(wait_mutex); + continue; + } else { + is->eof = 0; + } + /* check if packet is in play range specified by user, then queue, otherwise discard */ + stream_start_time = ic->streams[pkt->stream_index]->start_time; + pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; + pkt_in_play_range = duration == AV_NOPTS_VALUE || + (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) * + av_q2d(ic->streams[pkt->stream_index]->time_base) - + (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000 + <= ((double)duration / 1000000); + if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { + packet_queue_put(&is->audioq, pkt); + } else if (pkt->stream_index == is->video_stream && pkt_in_play_range + && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) { + packet_queue_put(&is->videoq, pkt); + } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) { + packet_queue_put(&is->subtitleq, pkt); + } else { + av_packet_unref(pkt); + } + } + + ret = 0; + fail: + if (ic && !is->ic) + avformat_close_input(&ic); + + if (ret != 0) { + SDL_Event event; + + event.type = FF_QUIT_EVENT; + event.user.data1 = is; + SDL_PushEvent(&event); + } + SDL_DestroyMutex(wait_mutex); + return 0; +} + +static VideoState *stream_open(const char *filename, AVInputFormat *iformat) +{ + VideoState *is; + + is = av_mallocz(sizeof(VideoState)); + if (!is) + return NULL; + is->filename = av_strdup(filename); + if (!is->filename) + goto fail; + is->iformat = iformat; + is->ytop = 0; + is->xleft = 0; + + /* start video display */ + if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0) + goto fail; + if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0) + goto fail; + if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0) + goto fail; + + if (packet_queue_init(&is->videoq) < 0 || + packet_queue_init(&is->audioq) < 0 || + packet_queue_init(&is->subtitleq) < 0) + goto fail; + + if (!(is->continue_read_thread = SDL_CreateCond())) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError()); + goto fail; + } + + init_clock(&is->vidclk, &is->videoq.serial); + init_clock(&is->audclk, &is->audioq.serial); + init_clock(&is->extclk, &is->extclk.serial); + is->audio_clock_serial = -1; + if (startup_volume < 0) + av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0\n", startup_volume); + if (startup_volume > 100) + av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n", startup_volume); + startup_volume = av_clip(startup_volume, 0, 100); + startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME); + is->audio_volume = startup_volume; + is->muted = 0; + is->av_sync_type = av_sync_type; + is->read_tid = SDL_CreateThread(read_thread, "read_thread", is); + if (!is->read_tid) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError()); +fail: + stream_close(is); + return NULL; + } + return is; +} + +static void stream_cycle_channel(VideoState *is, int codec_type) +{ + AVFormatContext *ic = is->ic; + int start_index, stream_index; + int old_index; + AVStream *st; + AVProgram *p = NULL; + int nb_streams = is->ic->nb_streams; + + if (codec_type == AVMEDIA_TYPE_VIDEO) { + start_index = is->last_video_stream; + old_index = is->video_stream; + } else if (codec_type == AVMEDIA_TYPE_AUDIO) { + start_index = is->last_audio_stream; + old_index = is->audio_stream; + } else { + start_index = is->last_subtitle_stream; + old_index = is->subtitle_stream; + } + stream_index = start_index; + + if (codec_type != AVMEDIA_TYPE_VIDEO && is->video_stream != -1) { + p = av_find_program_from_stream(ic, NULL, is->video_stream); + if (p) { + nb_streams = p->nb_stream_indexes; + for (start_index = 0; start_index < nb_streams; start_index++) + if (p->stream_index[start_index] == stream_index) + break; + if (start_index == nb_streams) + start_index = -1; + stream_index = start_index; + } + } + + for (;;) { + if (++stream_index >= nb_streams) + { + if (codec_type == AVMEDIA_TYPE_SUBTITLE) + { + stream_index = -1; + is->last_subtitle_stream = -1; + goto the_end; + } + if (start_index == -1) + return; + stream_index = 0; + } + if (stream_index == start_index) + return; + st = is->ic->streams[p ? p->stream_index[stream_index] : stream_index]; + if (st->codecpar->codec_type == codec_type) { + /* check that parameters are OK */ + switch (codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (st->codecpar->sample_rate != 0 && + st->codecpar->channels != 0) + goto the_end; + break; + case AVMEDIA_TYPE_VIDEO: + case AVMEDIA_TYPE_SUBTITLE: + goto the_end; + default: + break; + } + } + } + the_end: + if (p && stream_index != -1) + stream_index = p->stream_index[stream_index]; + av_log(NULL, AV_LOG_INFO, "Switch %s stream from #%d to #%d\n", + av_get_media_type_string(codec_type), + old_index, + stream_index); + + stream_component_close(is, old_index); + stream_component_open(is, stream_index); +} + + +static void toggle_full_screen(VideoState *is) +{ + is_full_screen = !is_full_screen; + SDL_SetWindowFullscreen(window, is_full_screen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); +} + +static void toggle_audio_display(VideoState *is) +{ + int next = is->show_mode; + do { + next = (next + 1) % SHOW_MODE_NB; + } while (next != is->show_mode && (next == SHOW_MODE_VIDEO && !is->video_st || next != SHOW_MODE_VIDEO && !is->audio_st)); + if (is->show_mode != next) { + is->force_refresh = 1; + is->show_mode = next; + } +} + +static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) { + double remaining_time = 0.0; + SDL_PumpEvents(); + while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) { + if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) { + SDL_ShowCursor(0); + cursor_hidden = 1; + } + if (remaining_time > 0.0) + av_usleep((int64_t)(remaining_time * 1000000.0)); + remaining_time = REFRESH_RATE; + if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh)) + video_refresh(is, &remaining_time); + SDL_PumpEvents(); + } +} + +static void seek_chapter(VideoState *is, int incr) +{ + int64_t pos = get_master_clock(is) * AV_TIME_BASE; + int i; + + if (!is->ic->nb_chapters) + return; + + /* find the current chapter */ + for (i = 0; i < is->ic->nb_chapters; i++) { + AVChapter *ch = is->ic->chapters[i]; + if (av_compare_ts(pos, AV_TIME_BASE_Q, ch->start, ch->time_base) < 0) { + i--; + break; + } + } + + i += incr; + i = FFMAX(i, 0); + if (i >= is->ic->nb_chapters) + return; + + av_log(NULL, AV_LOG_VERBOSE, "Seeking to chapter %d.\n", i); + stream_seek(is, av_rescale_q(is->ic->chapters[i]->start, is->ic->chapters[i]->time_base, + AV_TIME_BASE_Q), 0, 0); +} + +/* handle an event sent by the GUI */ +static void event_loop(VideoState *cur_stream) +{ + SDL_Event event; + double incr, pos, frac; + + for (;;) { + double x; + refresh_loop_wait_event(cur_stream, &event); + switch (event.type) { + case SDL_KEYDOWN: + if (exit_on_keydown) { + do_exit(cur_stream); + break; + } + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + case SDLK_q: + do_exit(cur_stream); + break; + case SDLK_f: + toggle_full_screen(cur_stream); + cur_stream->force_refresh = 1; + break; + case SDLK_p: + case SDLK_SPACE: + toggle_pause(cur_stream); + break; + case SDLK_m: + toggle_mute(cur_stream); + break; + case SDLK_KP_MULTIPLY: + case SDLK_0: + update_volume(cur_stream, 1, SDL_VOLUME_STEP); + break; + case SDLK_KP_DIVIDE: + case SDLK_9: + update_volume(cur_stream, -1, SDL_VOLUME_STEP); + break; + case SDLK_s: // S: Step to next frame + step_to_next_frame(cur_stream); + break; + case SDLK_a: + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO); + break; + case SDLK_v: + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO); + break; + case SDLK_c: + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO); + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO); + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE); + break; + case SDLK_t: + stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE); + break; + case SDLK_w: +#if CONFIG_AVFILTER + if (cur_stream->show_mode == SHOW_MODE_VIDEO && cur_stream->vfilter_idx < nb_vfilters - 1) { + if (++cur_stream->vfilter_idx >= nb_vfilters) + cur_stream->vfilter_idx = 0; + } else { + cur_stream->vfilter_idx = 0; + toggle_audio_display(cur_stream); + } +#else + toggle_audio_display(cur_stream); +#endif + break; + case SDLK_PAGEUP: + if (cur_stream->ic->nb_chapters <= 1) { + incr = 600.0; + goto do_seek; + } + seek_chapter(cur_stream, 1); + break; + case SDLK_PAGEDOWN: + if (cur_stream->ic->nb_chapters <= 1) { + incr = -600.0; + goto do_seek; + } + seek_chapter(cur_stream, -1); + break; + case SDLK_LEFT: + incr = -10.0; + goto do_seek; + case SDLK_RIGHT: + incr = 10.0; + goto do_seek; + case SDLK_UP: + incr = 60.0; + goto do_seek; + case SDLK_DOWN: + incr = -60.0; + do_seek: + if (seek_by_bytes) { + pos = -1; + if (pos < 0 && cur_stream->video_stream >= 0) + pos = frame_queue_last_pos(&cur_stream->pictq); + if (pos < 0 && cur_stream->audio_stream >= 0) + pos = frame_queue_last_pos(&cur_stream->sampq); + if (pos < 0) + pos = avio_tell(cur_stream->ic->pb); + if (cur_stream->ic->bit_rate) + incr *= cur_stream->ic->bit_rate / 8.0; + else + incr *= 180000.0; + pos += incr; + stream_seek(cur_stream, pos, incr, 1); + } else { + pos = get_master_clock(cur_stream); + if (isnan(pos)) + pos = (double)cur_stream->seek_pos / AV_TIME_BASE; + pos += incr; + if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE) + pos = cur_stream->ic->start_time / (double)AV_TIME_BASE; + stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0); + } + break; + default: + break; + } + break; + case SDL_MOUSEBUTTONDOWN: + if (exit_on_mousedown) { + do_exit(cur_stream); + break; + } + if (event.button.button == SDL_BUTTON_LEFT) { + static int64_t last_mouse_left_click = 0; + if (av_gettime_relative() - last_mouse_left_click <= 500000) { + toggle_full_screen(cur_stream); + cur_stream->force_refresh = 1; + last_mouse_left_click = 0; + } else { + last_mouse_left_click = av_gettime_relative(); + } + } + case SDL_MOUSEMOTION: + if (cursor_hidden) { + SDL_ShowCursor(1); + cursor_hidden = 0; + } + cursor_last_shown = av_gettime_relative(); + if (event.type == SDL_MOUSEBUTTONDOWN) { + if (event.button.button != SDL_BUTTON_RIGHT) + break; + x = event.button.x; + } else { + if (!(event.motion.state & SDL_BUTTON_RMASK)) + break; + x = event.motion.x; + } + if (seek_by_bytes || cur_stream->ic->duration <= 0) { + uint64_t size = avio_size(cur_stream->ic->pb); + stream_seek(cur_stream, size*x/cur_stream->width, 0, 1); + } else { + int64_t ts; + int ns, hh, mm, ss; + int tns, thh, tmm, tss; + tns = cur_stream->ic->duration / 1000000LL; + thh = tns / 3600; + tmm = (tns % 3600) / 60; + tss = (tns % 60); + frac = x / cur_stream->width; + ns = frac * tns; + hh = ns / 3600; + mm = (ns % 3600) / 60; + ss = (ns % 60); + av_log(NULL, AV_LOG_INFO, + "Seek to %2.0f%% (%2d:%02d:%02d) of total duration (%2d:%02d:%02d) \n", frac*100, + hh, mm, ss, thh, tmm, tss); + ts = frac * cur_stream->ic->duration; + if (cur_stream->ic->start_time != AV_NOPTS_VALUE) + ts += cur_stream->ic->start_time; + stream_seek(cur_stream, ts, 0, 0); + } + break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_RESIZED: + screen_width = cur_stream->width = event.window.data1; + screen_height = cur_stream->height = event.window.data2; + if (cur_stream->vis_texture) { + SDL_DestroyTexture(cur_stream->vis_texture); + cur_stream->vis_texture = NULL; + } + case SDL_WINDOWEVENT_EXPOSED: + cur_stream->force_refresh = 1; + } + break; + case SDL_QUIT: + case FF_QUIT_EVENT: + do_exit(cur_stream); + break; + default: + break; + } + } +} + +static int opt_frame_size(void *optctx, const char *opt, const char *arg) +{ + av_log(NULL, AV_LOG_WARNING, "Option -s is deprecated, use -video_size.\n"); + return opt_default(NULL, "video_size", arg); +} + +static int opt_width(void *optctx, const char *opt, const char *arg) +{ + screen_width = parse_number_or_die(opt, arg, OPT_INT64, 1, INT_MAX); + return 0; +} + +static int opt_height(void *optctx, const char *opt, const char *arg) +{ + screen_height = parse_number_or_die(opt, arg, OPT_INT64, 1, INT_MAX); + return 0; +} + +static int opt_format(void *optctx, const char *opt, const char *arg) +{ + file_iformat = av_find_input_format(arg); + if (!file_iformat) { + av_log(NULL, AV_LOG_FATAL, "Unknown input format: %s\n", arg); + return AVERROR(EINVAL); + } + return 0; +} + +static int opt_frame_pix_fmt(void *optctx, const char *opt, const char *arg) +{ + av_log(NULL, AV_LOG_WARNING, "Option -pix_fmt is deprecated, use -pixel_format.\n"); + return opt_default(NULL, "pixel_format", arg); +} + +static int opt_sync(void *optctx, const char *opt, const char *arg) +{ + if (!strcmp(arg, "audio")) + av_sync_type = AV_SYNC_AUDIO_MASTER; + else if (!strcmp(arg, "video")) + av_sync_type = AV_SYNC_VIDEO_MASTER; + else if (!strcmp(arg, "ext")) + av_sync_type = AV_SYNC_EXTERNAL_CLOCK; + else { + av_log(NULL, AV_LOG_ERROR, "Unknown value for %s: %s\n", opt, arg); + exit(1); + } + return 0; +} + +static int opt_seek(void *optctx, const char *opt, const char *arg) +{ + start_time = parse_time_or_die(opt, arg, 1); + return 0; +} + +static int opt_duration(void *optctx, const char *opt, const char *arg) +{ + duration = parse_time_or_die(opt, arg, 1); + return 0; +} + +static int opt_show_mode(void *optctx, const char *opt, const char *arg) +{ + show_mode = !strcmp(arg, "video") ? SHOW_MODE_VIDEO : + !strcmp(arg, "waves") ? SHOW_MODE_WAVES : + !strcmp(arg, "rdft" ) ? SHOW_MODE_RDFT : + parse_number_or_die(opt, arg, OPT_INT, 0, SHOW_MODE_NB-1); + return 0; +} + +static void opt_input_file(void *optctx, const char *filename) +{ + if (input_filename) { + av_log(NULL, AV_LOG_FATAL, + "Argument '%s' provided as input filename, but '%s' was already specified.\n", + filename, input_filename); + exit(1); + } + if (!strcmp(filename, "-")) + filename = "pipe:"; + input_filename = filename; +} + +static int opt_codec(void *optctx, const char *opt, const char *arg) +{ + const char *spec = strchr(opt, ':'); + if (!spec) { + av_log(NULL, AV_LOG_ERROR, + "No media specifier was specified in '%s' in option '%s'\n", + arg, opt); + return AVERROR(EINVAL); + } + spec++; + switch (spec[0]) { + case 'a' : audio_codec_name = arg; break; + case 's' : subtitle_codec_name = arg; break; + case 'v' : video_codec_name = arg; break; + default: + av_log(NULL, AV_LOG_ERROR, + "Invalid media specifier '%s' in option '%s'\n", spec, opt); + return AVERROR(EINVAL); + } + return 0; +} + +static int dummy; + +static const OptionDef options[] = { + CMDUTILS_COMMON_OPTIONS + { "x", HAS_ARG, { .func_arg = opt_width }, "force displayed width", "width" }, + { "y", HAS_ARG, { .func_arg = opt_height }, "force displayed height", "height" }, + { "s", HAS_ARG | OPT_VIDEO, { .func_arg = opt_frame_size }, "set frame size (WxH or abbreviation)", "size" }, + { "fs", OPT_BOOL, { &is_full_screen }, "force full screen" }, + { "an", OPT_BOOL, { &audio_disable }, "disable audio" }, + { "vn", OPT_BOOL, { &video_disable }, "disable video" }, + { "sn", OPT_BOOL, { &subtitle_disable }, "disable subtitling" }, + { "ast", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream", "stream_specifier" }, + { "vst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_VIDEO] }, "select desired video stream", "stream_specifier" }, + { "sst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream", "stream_specifier" }, + { "ss", HAS_ARG, { .func_arg = opt_seek }, "seek to a given position in seconds", "pos" }, + { "t", HAS_ARG, { .func_arg = opt_duration }, "play \"duration\" seconds of audio/video", "duration" }, + { "bytes", OPT_INT | HAS_ARG, { &seek_by_bytes }, "seek by bytes 0=off 1=on -1=auto", "val" }, + { "nodisp", OPT_BOOL, { &display_disable }, "disable graphical display" }, + { "noborder", OPT_BOOL, { &borderless }, "borderless window" }, + { "volume", OPT_INT | HAS_ARG, { &startup_volume}, "set startup volume 0=min 100=max", "volume" }, + { "f", HAS_ARG, { .func_arg = opt_format }, "force format", "fmt" }, + { "pix_fmt", HAS_ARG | OPT_EXPERT | OPT_VIDEO, { .func_arg = opt_frame_pix_fmt }, "set pixel format", "format" }, + { "stats", OPT_BOOL | OPT_EXPERT, { &show_status }, "show status", "" }, + { "fast", OPT_BOOL | OPT_EXPERT, { &fast }, "non spec compliant optimizations", "" }, + { "genpts", OPT_BOOL | OPT_EXPERT, { &genpts }, "generate pts", "" }, + { "drp", OPT_INT | HAS_ARG | OPT_EXPERT, { &decoder_reorder_pts }, "let decoder reorder pts 0=off 1=on -1=auto", ""}, + { "lowres", OPT_INT | HAS_ARG | OPT_EXPERT, { &lowres }, "", "" }, + { "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audio-video sync. type (type=audio/video/ext)", "type" }, + { "autoexit", OPT_BOOL | OPT_EXPERT, { &autoexit }, "exit at the end", "" }, + { "exitonkeydown", OPT_BOOL | OPT_EXPERT, { &exit_on_keydown }, "exit on key down", "" }, + { "exitonmousedown", OPT_BOOL | OPT_EXPERT, { &exit_on_mousedown }, "exit on mouse down", "" }, + { "loop", OPT_INT | HAS_ARG | OPT_EXPERT, { &loop }, "set number of times the playback shall be looped", "loop count" }, + { "framedrop", OPT_BOOL | OPT_EXPERT, { &framedrop }, "drop frames when cpu is too slow", "" }, + { "infbuf", OPT_BOOL | OPT_EXPERT, { &infinite_buffer }, "don't limit the input buffer size (useful with realtime streams)", "" }, + { "window_title", OPT_STRING | HAS_ARG, { &window_title }, "set window title", "window title" }, +#if CONFIG_AVFILTER + { "vf", OPT_EXPERT | HAS_ARG, { .func_arg = opt_add_vfilter }, "set video filters", "filter_graph" }, + { "af", OPT_STRING | HAS_ARG, { &afilters }, "set audio filters", "filter_graph" }, +#endif + { "rdftspeed", OPT_INT | HAS_ARG| OPT_AUDIO | OPT_EXPERT, { &rdftspeed }, "rdft speed", "msecs" }, + { "showmode", HAS_ARG, { .func_arg = opt_show_mode}, "select show mode (0 = video, 1 = waves, 2 = RDFT)", "mode" }, + { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, { .func_arg = opt_default }, "generic catch all option", "" }, + { "i", OPT_BOOL, { &dummy}, "read specified file", "input_file"}, + { "codec", HAS_ARG, { .func_arg = opt_codec}, "force decoder", "decoder_name" }, + { "acodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &audio_codec_name }, "force audio decoder", "decoder_name" }, + { "scodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &subtitle_codec_name }, "force subtitle decoder", "decoder_name" }, + { "vcodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &video_codec_name }, "force video decoder", "decoder_name" }, + { "autorotate", OPT_BOOL, { &autorotate }, "automatically rotate video", "" }, + { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + "read and decode the streams to fill missing information with heuristics" }, + { NULL, }, +}; + +static void show_usage(void) +{ + av_log(NULL, AV_LOG_INFO, "Simple media player\n"); + av_log(NULL, AV_LOG_INFO, "usage: %s [options] input_file\n", program_name); + av_log(NULL, AV_LOG_INFO, "\n"); +} + +void show_help_default(const char *opt, const char *arg) +{ + av_log_set_callback(log_callback_help); + show_usage(); + show_help_options(options, "Main options:", 0, OPT_EXPERT, 0); + show_help_options(options, "Advanced options:", OPT_EXPERT, 0, 0); + printf("\n"); + show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM); + show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); +#if !CONFIG_AVFILTER + show_help_children(sws_get_class(), AV_OPT_FLAG_ENCODING_PARAM); +#else + show_help_children(avfilter_get_class(), AV_OPT_FLAG_FILTERING_PARAM); +#endif + printf("\nWhile playing:\n" + "q, ESC quit\n" + "f toggle full screen\n" + "p, SPC pause\n" + "m toggle mute\n" + "9, 0 decrease and increase volume respectively\n" + "/, * decrease and increase volume respectively\n" + "a cycle audio channel in the current program\n" + "v cycle video channel\n" + "t cycle subtitle channel in the current program\n" + "c cycle program\n" + "w cycle video filters or show modes\n" + "s activate frame-step mode\n" + "left/right seek backward/forward 10 seconds\n" + "down/up seek backward/forward 1 minute\n" + "page down/page up seek backward/forward 10 minutes\n" + "right mouse click seek to percentage in file corresponding to fraction of width\n" + "left double-click toggle full screen\n" + ); +} + +static int lockmgr(void **mtx, enum AVLockOp op) +{ + switch(op) { + case AV_LOCK_CREATE: + *mtx = SDL_CreateMutex(); + if(!*mtx) { + av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); + return 1; + } + return 0; + case AV_LOCK_OBTAIN: + return !!SDL_LockMutex(*mtx); + case AV_LOCK_RELEASE: + return !!SDL_UnlockMutex(*mtx); + case AV_LOCK_DESTROY: + SDL_DestroyMutex(*mtx); + return 0; + } + return 1; +} + +/* Called from the main */ +int main(int argc, char **argv) +{ + int flags; + VideoState *is; + + init_dynload(); + + av_log_set_flags(AV_LOG_SKIP_REPEATED); + parse_loglevel(argc, argv, options); + + /* register all codecs, demux and protocols */ +#if CONFIG_AVDEVICE + avdevice_register_all(); +#endif +#if CONFIG_AVFILTER + avfilter_register_all(); +#endif + av_register_all(); + avformat_network_init(); + + init_opts(); + + signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */ + signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */ + + show_banner(argc, argv, options); + + parse_options(NULL, argc, argv, options, opt_input_file); + + if (!input_filename) { + show_usage(); + av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n"); + av_log(NULL, AV_LOG_FATAL, + "Use -h to get full help or, even better, run 'man %s'\n", program_name); + exit(1); + } + + if (display_disable) { + video_disable = 1; + } + flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER; + if (audio_disable) + flags &= ~SDL_INIT_AUDIO; + else { + /* Try to work around an occasional ALSA buffer underflow issue when the + * period size is NPOT due to ALSA resampling by forcing the buffer size. */ + if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE")) + SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1); + } + if (display_disable) + flags &= ~SDL_INIT_VIDEO; + if (SDL_Init (flags)) { + av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError()); + av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n"); + exit(1); + } + + SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE); + SDL_EventState(SDL_USEREVENT, SDL_IGNORE); + + if (av_lockmgr_register(lockmgr)) { + av_log(NULL, AV_LOG_FATAL, "Could not initialize lock manager!\n"); + do_exit(NULL); + } + + av_init_packet(&flush_pkt); + flush_pkt.data = (uint8_t *)&flush_pkt; + + is = stream_open(input_filename, file_iformat); + if (!is) { + av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n"); + do_exit(NULL); + } + + event_loop(is); + + /* never returns */ + + return 0; +} diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c new file mode 100644 index 0000000000..b2e8949d9f --- /dev/null +++ b/fftools/ffprobe.c @@ -0,0 +1,3692 @@ +/* + * Copyright (c) 2007-2010 Stefano Sabatini + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * simple media prober based on the FFmpeg libraries + */ + +#include "config.h" +#include "libavutil/ffversion.h" + +#include <string.h> + +#include "libavformat/avformat.h" +#include "libavcodec/avcodec.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/bprint.h" +#include "libavutil/display.h" +#include "libavutil/hash.h" +#include "libavutil/mastering_display_metadata.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/spherical.h" +#include "libavutil/stereo3d.h" +#include "libavutil/dict.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/libm.h" +#include "libavutil/parseutils.h" +#include "libavutil/timecode.h" +#include "libavutil/timestamp.h" +#include "libavdevice/avdevice.h" +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +#include "libpostproc/postprocess.h" +#include "cmdutils.h" + +#include "libavutil/thread.h" + +#if !HAVE_THREADS +# ifdef pthread_mutex_lock +# undef pthread_mutex_lock +# endif +# define pthread_mutex_lock(a) do{}while(0) +# ifdef pthread_mutex_unlock +# undef pthread_mutex_unlock +# endif +# define pthread_mutex_unlock(a) do{}while(0) +#endif + +typedef struct InputStream { + AVStream *st; + + AVCodecContext *dec_ctx; +} InputStream; + +typedef struct InputFile { + AVFormatContext *fmt_ctx; + + InputStream *streams; + int nb_streams; +} InputFile; + +const char program_name[] = "ffprobe"; +const int program_birth_year = 2007; + +static int do_bitexact = 0; +static int do_count_frames = 0; +static int do_count_packets = 0; +static int do_read_frames = 0; +static int do_read_packets = 0; +static int do_show_chapters = 0; +static int do_show_error = 0; +static int do_show_format = 0; +static int do_show_frames = 0; +static int do_show_packets = 0; +static int do_show_programs = 0; +static int do_show_streams = 0; +static int do_show_stream_disposition = 0; +static int do_show_data = 0; +static int do_show_program_version = 0; +static int do_show_library_versions = 0; +static int do_show_pixel_formats = 0; +static int do_show_pixel_format_flags = 0; +static int do_show_pixel_format_components = 0; +static int do_show_log = 0; + +static int do_show_chapter_tags = 0; +static int do_show_format_tags = 0; +static int do_show_frame_tags = 0; +static int do_show_program_tags = 0; +static int do_show_stream_tags = 0; +static int do_show_packet_tags = 0; + +static int show_value_unit = 0; +static int use_value_prefix = 0; +static int use_byte_value_binary_prefix = 0; +static int use_value_sexagesimal_format = 0; +static int show_private_data = 1; + +static char *print_format; +static char *stream_specifier; +static char *show_data_hash; + +typedef struct ReadInterval { + int id; ///< identifier + int64_t start, end; ///< start, end in second/AV_TIME_BASE units + int has_start, has_end; + int start_is_offset, end_is_offset; + int duration_frames; +} ReadInterval; + +static ReadInterval *read_intervals; +static int read_intervals_nb = 0; + +static int find_stream_info = 1; + +/* section structure definition */ + +#define SECTION_MAX_NB_CHILDREN 10 + +struct section { + int id; ///< unique id identifying a section + const char *name; + +#define SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level +#define SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type +#define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. + /// For these sections the element_name field is mandatory. + int flags; + int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 + const char *element_name; ///< name of the contained element, if provided + const char *unique_name; ///< unique section name, in case the name is ambiguous + AVDictionary *entries_to_show; + int show_all_entries; +}; + +typedef enum { + SECTION_ID_NONE = -1, + SECTION_ID_CHAPTER, + SECTION_ID_CHAPTER_TAGS, + SECTION_ID_CHAPTERS, + SECTION_ID_ERROR, + SECTION_ID_FORMAT, + SECTION_ID_FORMAT_TAGS, + SECTION_ID_FRAME, + SECTION_ID_FRAMES, + SECTION_ID_FRAME_TAGS, + SECTION_ID_FRAME_SIDE_DATA_LIST, + SECTION_ID_FRAME_SIDE_DATA, + SECTION_ID_FRAME_LOG, + SECTION_ID_FRAME_LOGS, + SECTION_ID_LIBRARY_VERSION, + SECTION_ID_LIBRARY_VERSIONS, + SECTION_ID_PACKET, + SECTION_ID_PACKET_TAGS, + SECTION_ID_PACKETS, + SECTION_ID_PACKETS_AND_FRAMES, + SECTION_ID_PACKET_SIDE_DATA_LIST, + SECTION_ID_PACKET_SIDE_DATA, + SECTION_ID_PIXEL_FORMAT, + SECTION_ID_PIXEL_FORMAT_FLAGS, + SECTION_ID_PIXEL_FORMAT_COMPONENT, + SECTION_ID_PIXEL_FORMAT_COMPONENTS, + SECTION_ID_PIXEL_FORMATS, + SECTION_ID_PROGRAM_STREAM_DISPOSITION, + SECTION_ID_PROGRAM_STREAM_TAGS, + SECTION_ID_PROGRAM, + SECTION_ID_PROGRAM_STREAMS, + SECTION_ID_PROGRAM_STREAM, + SECTION_ID_PROGRAM_TAGS, + SECTION_ID_PROGRAM_VERSION, + SECTION_ID_PROGRAMS, + SECTION_ID_ROOT, + SECTION_ID_STREAM, + SECTION_ID_STREAM_DISPOSITION, + SECTION_ID_STREAMS, + SECTION_ID_STREAM_TAGS, + SECTION_ID_STREAM_SIDE_DATA_LIST, + SECTION_ID_STREAM_SIDE_DATA, + SECTION_ID_SUBTITLE, +} SectionID; + +static struct section sections[] = { + [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } }, + [SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } }, + [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" }, + [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } }, + [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } }, + [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" }, + [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } }, + [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, SECTION_ID_FRAME_SIDE_DATA_LIST, SECTION_ID_FRAME_LOGS, -1 } }, + [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" }, + [SECTION_ID_FRAME_SIDE_DATA_LIST] ={ SECTION_ID_FRAME_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "frame_side_data_list" }, + [SECTION_ID_FRAME_SIDE_DATA] = { SECTION_ID_FRAME_SIDE_DATA, "side_data", 0, { -1 } }, + [SECTION_ID_FRAME_LOGS] = { SECTION_ID_FRAME_LOGS, "logs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME_LOG, -1 } }, + [SECTION_ID_FRAME_LOG] = { SECTION_ID_FRAME_LOG, "log", 0, { -1 }, }, + [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } }, + [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } }, + [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, + [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, + [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { SECTION_ID_PACKET_TAGS, SECTION_ID_PACKET_SIDE_DATA_LIST, -1 } }, + [SECTION_ID_PACKET_TAGS] = { SECTION_ID_PACKET_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "packet_tags" }, + [SECTION_ID_PACKET_SIDE_DATA_LIST] ={ SECTION_ID_PACKET_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "packet_side_data_list" }, + [SECTION_ID_PACKET_SIDE_DATA] = { SECTION_ID_PACKET_SIDE_DATA, "side_data", 0, { -1 } }, + [SECTION_ID_PIXEL_FORMATS] = { SECTION_ID_PIXEL_FORMATS, "pixel_formats", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PIXEL_FORMAT, -1 } }, + [SECTION_ID_PIXEL_FORMAT] = { SECTION_ID_PIXEL_FORMAT, "pixel_format", 0, { SECTION_ID_PIXEL_FORMAT_FLAGS, SECTION_ID_PIXEL_FORMAT_COMPONENTS, -1 } }, + [SECTION_ID_PIXEL_FORMAT_FLAGS] = { SECTION_ID_PIXEL_FORMAT_FLAGS, "flags", 0, { -1 }, .unique_name = "pixel_format_flags" }, + [SECTION_ID_PIXEL_FORMAT_COMPONENTS] = { SECTION_ID_PIXEL_FORMAT_COMPONENTS, "components", SECTION_FLAG_IS_ARRAY, {SECTION_ID_PIXEL_FORMAT_COMPONENT, -1 }, .unique_name = "pixel_format_components" }, + [SECTION_ID_PIXEL_FORMAT_COMPONENT] = { SECTION_ID_PIXEL_FORMAT_COMPONENT, "component", 0, { -1 } }, + [SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" }, + [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" }, + [SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } }, + [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" }, + [SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" }, + [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" }, + [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, + [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } }, + [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER, + { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS, + SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, + SECTION_ID_PIXEL_FORMATS, -1} }, + [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } }, + [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, SECTION_ID_STREAM_SIDE_DATA_LIST, -1 } }, + [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" }, + [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" }, + [SECTION_ID_STREAM_SIDE_DATA_LIST] ={ SECTION_ID_STREAM_SIDE_DATA_LIST, "side_data_list", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_SIDE_DATA, -1 }, .element_name = "side_data", .unique_name = "stream_side_data_list" }, + [SECTION_ID_STREAM_SIDE_DATA] = { SECTION_ID_STREAM_SIDE_DATA, "side_data", 0, { -1 } }, + [SECTION_ID_SUBTITLE] = { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } }, +}; + +static const OptionDef *options; + +/* FFprobe context */ +static const char *input_filename; +static AVInputFormat *iformat = NULL; + +static struct AVHashContext *hash; + +static const struct { + double bin_val; + double dec_val; + const char *bin_str; + const char *dec_str; +} si_prefixes[] = { + { 1.0, 1.0, "", "" }, + { 1.024e3, 1e3, "Ki", "K" }, + { 1.048576e6, 1e6, "Mi", "M" }, + { 1.073741824e9, 1e9, "Gi", "G" }, + { 1.099511627776e12, 1e12, "Ti", "T" }, + { 1.125899906842624e15, 1e15, "Pi", "P" }, +}; + +static const char unit_second_str[] = "s" ; +static const char unit_hertz_str[] = "Hz" ; +static const char unit_byte_str[] = "byte" ; +static const char unit_bit_per_second_str[] = "bit/s"; + +static int nb_streams; +static uint64_t *nb_streams_packets; +static uint64_t *nb_streams_frames; +static int *selected_streams; + +#if HAVE_THREADS +pthread_mutex_t log_mutex; +#endif +typedef struct LogBuffer { + char *context_name; + int log_level; + char *log_message; + AVClassCategory category; + char *parent_name; + AVClassCategory parent_category; +}LogBuffer; + +static LogBuffer *log_buffer; +static int log_buffer_size; + +static void log_callback(void *ptr, int level, const char *fmt, va_list vl) +{ + AVClass* avc = ptr ? *(AVClass **) ptr : NULL; + va_list vl2; + char line[1024]; + static int print_prefix = 1; + void *new_log_buffer; + + va_copy(vl2, vl); + av_log_default_callback(ptr, level, fmt, vl); + av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix); + va_end(vl2); + +#if HAVE_THREADS + pthread_mutex_lock(&log_mutex); + + new_log_buffer = av_realloc_array(log_buffer, log_buffer_size + 1, sizeof(*log_buffer)); + if (new_log_buffer) { + char *msg; + int i; + + log_buffer = new_log_buffer; + memset(&log_buffer[log_buffer_size], 0, sizeof(log_buffer[log_buffer_size])); + log_buffer[log_buffer_size].context_name= avc ? av_strdup(avc->item_name(ptr)) : NULL; + if (avc) { + if (avc->get_category) log_buffer[log_buffer_size].category = avc->get_category(ptr); + else log_buffer[log_buffer_size].category = avc->category; + } + log_buffer[log_buffer_size].log_level = level; + msg = log_buffer[log_buffer_size].log_message = av_strdup(line); + for (i=strlen(msg) - 1; i>=0 && msg[i] == '\n'; i--) { + msg[i] = 0; + } + if (avc && avc->parent_log_context_offset) { + AVClass** parent = *(AVClass ***) (((uint8_t *) ptr) + + avc->parent_log_context_offset); + if (parent && *parent) { + log_buffer[log_buffer_size].parent_name = av_strdup((*parent)->item_name(parent)); + log_buffer[log_buffer_size].parent_category = + (*parent)->get_category ? (*parent)->get_category(parent) :(*parent)->category; + } + } + log_buffer_size ++; + } + + pthread_mutex_unlock(&log_mutex); +#endif +} + +static void ffprobe_cleanup(int ret) +{ + int i; + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) + av_dict_free(&(sections[i].entries_to_show)); + +#if HAVE_THREADS + pthread_mutex_destroy(&log_mutex); +#endif +} + +struct unit_value { + union { double d; long long int i; } val; + const char *unit; +}; + +static char *value_string(char *buf, int buf_size, struct unit_value uv) +{ + double vald; + long long int vali; + int show_float = 0; + + if (uv.unit == unit_second_str) { + vald = uv.val.d; + show_float = 1; + } else { + vald = vali = uv.val.i; + } + + if (uv.unit == unit_second_str && use_value_sexagesimal_format) { + double secs; + int hours, mins; + secs = vald; + mins = (int)secs / 60; + secs = secs - mins * 60; + hours = mins / 60; + mins %= 60; + snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); + } else { + const char *prefix_string = ""; + + if (use_value_prefix && vald > 1) { + long long int index; + + if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) { + index = (long long int) (log2(vald)) / 10; + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); + vald /= si_prefixes[index].bin_val; + prefix_string = si_prefixes[index].bin_str; + } else { + index = (long long int) (log10(vald)) / 3; + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); + vald /= si_prefixes[index].dec_val; + prefix_string = si_prefixes[index].dec_str; + } + vali = vald; + } + + if (show_float || (use_value_prefix && vald != (long long int)vald)) + snprintf(buf, buf_size, "%f", vald); + else + snprintf(buf, buf_size, "%lld", vali); + av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "", + prefix_string, show_value_unit ? uv.unit : ""); + } + + return buf; +} + +/* WRITERS API */ + +typedef struct WriterContext WriterContext; + +#define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1 +#define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 + +typedef enum { + WRITER_STRING_VALIDATION_FAIL, + WRITER_STRING_VALIDATION_REPLACE, + WRITER_STRING_VALIDATION_IGNORE, + WRITER_STRING_VALIDATION_NB +} StringValidation; + +typedef struct Writer { + const AVClass *priv_class; ///< private class of the writer, if any + int priv_size; ///< private size for the writer context + const char *name; + + int (*init) (WriterContext *wctx); + void (*uninit)(WriterContext *wctx); + + void (*print_section_header)(WriterContext *wctx); + void (*print_section_footer)(WriterContext *wctx); + void (*print_integer) (WriterContext *wctx, const char *, long long int); + void (*print_rational) (WriterContext *wctx, AVRational *q, char *sep); + void (*print_string) (WriterContext *wctx, const char *, const char *); + int flags; ///< a combination or WRITER_FLAG_* +} Writer; + +#define SECTION_MAX_NB_LEVELS 10 + +struct WriterContext { + const AVClass *class; ///< class of the writer + const Writer *writer; ///< the Writer of which this is an instance + char *name; ///< name of this writer instance + void *priv; ///< private data for use by the filter + + const struct section *sections; ///< array containing all sections + int nb_sections; ///< number of sections + + int level; ///< current level, starting from 0 + + /** number of the item printed in the given section, starting from 0 */ + unsigned int nb_item[SECTION_MAX_NB_LEVELS]; + + /** section per each level */ + const struct section *section[SECTION_MAX_NB_LEVELS]; + AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, + /// used by various writers + + unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section + unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section + unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames + + int string_validation; + char *string_validation_replacement; + unsigned int string_validation_utf8_flags; +}; + +static const char *writer_get_name(void *p) +{ + WriterContext *wctx = p; + return wctx->writer->name; +} + +#define OFFSET(x) offsetof(WriterContext, x) + +static const AVOption writer_options[] = { + { "string_validation", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "sv", "set string validation mode", + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" }, + { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE}, .unit = "sv" }, + { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" }, + { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL}, .unit = "sv" }, + { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, + { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}}, + { NULL } +}; + +static void *writer_child_next(void *obj, void *prev) +{ + WriterContext *ctx = obj; + if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv) + return ctx->priv; + return NULL; +} + +static const AVClass writer_class = { + .class_name = "Writer", + .item_name = writer_get_name, + .option = writer_options, + .version = LIBAVUTIL_VERSION_INT, + .child_next = writer_child_next, +}; + +static void writer_close(WriterContext **wctx) +{ + int i; + + if (!*wctx) + return; + + if ((*wctx)->writer->uninit) + (*wctx)->writer->uninit(*wctx); + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) + av_bprint_finalize(&(*wctx)->section_pbuf[i], NULL); + if ((*wctx)->writer->priv_class) + av_opt_free((*wctx)->priv); + av_freep(&((*wctx)->priv)); + av_opt_free(*wctx); + av_freep(wctx); +} + +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) +{ + int i; + av_bprintf(bp, "0X"); + for (i = 0; i < ubuf_size; i++) + av_bprintf(bp, "%02X", ubuf[i]); +} + + +static int writer_open(WriterContext **wctx, const Writer *writer, const char *args, + const struct section *sections, int nb_sections) +{ + int i, ret = 0; + + if (!(*wctx = av_mallocz(sizeof(WriterContext)))) { + ret = AVERROR(ENOMEM); + goto fail; + } + + if (!((*wctx)->priv = av_mallocz(writer->priv_size))) { + ret = AVERROR(ENOMEM); + goto fail; + } + + (*wctx)->class = &writer_class; + (*wctx)->writer = writer; + (*wctx)->level = -1; + (*wctx)->sections = sections; + (*wctx)->nb_sections = nb_sections; + + av_opt_set_defaults(*wctx); + + if (writer->priv_class) { + void *priv_ctx = (*wctx)->priv; + *((const AVClass **)priv_ctx) = writer->priv_class; + av_opt_set_defaults(priv_ctx); + } + + /* convert options to dictionary */ + if (args) { + AVDictionary *opts = NULL; + AVDictionaryEntry *opt = NULL; + + if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args); + av_dict_free(&opts); + goto fail; + } + + while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { + av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", + opt->key, opt->value); + av_dict_free(&opts); + goto fail; + } + } + + av_dict_free(&opts); + } + + /* validate replace string */ + { + const uint8_t *p = (*wctx)->string_validation_replacement; + const uint8_t *endp = p + strlen(p); + while (*p) { + const uint8_t *p0 = p; + int32_t code; + ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags); + if (ret < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0), + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF8 sequence %s found in string validation replace '%s'\n", + bp.str, (*wctx)->string_validation_replacement); + return ret; + } + } + } + + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) + av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED); + + if ((*wctx)->writer->init) + ret = (*wctx)->writer->init(*wctx); + if (ret < 0) + goto fail; + + return 0; + +fail: + writer_close(wctx); + return ret; +} + +static inline void writer_print_section_header(WriterContext *wctx, + int section_id) +{ + int parent_section_id; + wctx->level++; + av_assert0(wctx->level < SECTION_MAX_NB_LEVELS); + parent_section_id = wctx->level ? + (wctx->section[wctx->level-1])->id : SECTION_ID_NONE; + + wctx->nb_item[wctx->level] = 0; + wctx->section[wctx->level] = &wctx->sections[section_id]; + + if (section_id == SECTION_ID_PACKETS_AND_FRAMES) { + wctx->nb_section_packet = wctx->nb_section_frame = + wctx->nb_section_packet_frame = 0; + } else if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) { + wctx->nb_section_packet_frame = section_id == SECTION_ID_PACKET ? + wctx->nb_section_packet : wctx->nb_section_frame; + } + + if (wctx->writer->print_section_header) + wctx->writer->print_section_header(wctx); +} + +static inline void writer_print_section_footer(WriterContext *wctx) +{ + int section_id = wctx->section[wctx->level]->id; + int parent_section_id = wctx->level ? + wctx->section[wctx->level-1]->id : SECTION_ID_NONE; + + if (parent_section_id != SECTION_ID_NONE) + wctx->nb_item[wctx->level-1]++; + if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) { + if (section_id == SECTION_ID_PACKET) wctx->nb_section_packet++; + else wctx->nb_section_frame++; + } + if (wctx->writer->print_section_footer) + wctx->writer->print_section_footer(wctx); + wctx->level--; +} + +static inline void writer_print_integer(WriterContext *wctx, + const char *key, long long int val) +{ + const struct section *section = wctx->section[wctx->level]; + + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { + wctx->writer->print_integer(wctx, key, val); + wctx->nb_item[wctx->level]++; + } +} + +static inline int validate_string(WriterContext *wctx, char **dstp, const char *src) +{ + const uint8_t *p, *endp; + AVBPrint dstbuf; + int invalid_chars_nb = 0, ret = 0; + + av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED); + + endp = src + strlen(src); + for (p = (uint8_t *)src; *p;) { + uint32_t code; + int invalid = 0; + const uint8_t *p0 = p; + + if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) { + AVBPrint bp; + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + bprint_bytes(&bp, p0, p-p0); + av_log(wctx, AV_LOG_DEBUG, + "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src); + invalid = 1; + } + + if (invalid) { + invalid_chars_nb++; + + switch (wctx->string_validation) { + case WRITER_STRING_VALIDATION_FAIL: + av_log(wctx, AV_LOG_ERROR, + "Invalid UTF-8 sequence found in string '%s'\n", src); + ret = AVERROR_INVALIDDATA; + goto end; + break; + + case WRITER_STRING_VALIDATION_REPLACE: + av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement); + break; + } + } + + if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE) + av_bprint_append_data(&dstbuf, p0, p-p0); + } + + if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) { + av_log(wctx, AV_LOG_WARNING, + "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n", + invalid_chars_nb, src, wctx->string_validation_replacement); + } + +end: + av_bprint_finalize(&dstbuf, dstp); + return ret; +} + +#define PRINT_STRING_OPT 1 +#define PRINT_STRING_VALIDATE 2 + +static inline int writer_print_string(WriterContext *wctx, + const char *key, const char *val, int flags) +{ + const struct section *section = wctx->section[wctx->level]; + int ret = 0; + + if ((flags & PRINT_STRING_OPT) + && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) + return 0; + + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { + if (flags & PRINT_STRING_VALIDATE) { + char *key1 = NULL, *val1 = NULL; + ret = validate_string(wctx, &key1, key); + if (ret < 0) goto end; + ret = validate_string(wctx, &val1, val); + if (ret < 0) goto end; + wctx->writer->print_string(wctx, key1, val1); + end: + if (ret < 0) { + av_log(wctx, AV_LOG_ERROR, + "Invalid key=value string combination %s=%s in section %s\n", + key, val, section->unique_name); + } + av_free(key1); + av_free(val1); + } else { + wctx->writer->print_string(wctx, key, val); + } + + wctx->nb_item[wctx->level]++; + } + + return ret; +} + +static inline void writer_print_rational(WriterContext *wctx, + const char *key, AVRational q, char sep) +{ + AVBPrint buf; + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprintf(&buf, "%d%c%d", q.num, sep, q.den); + writer_print_string(wctx, key, buf.str, 0); +} + +static void writer_print_time(WriterContext *wctx, const char *key, + int64_t ts, const AVRational *time_base, int is_duration) +{ + char buf[128]; + + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); + } else { + double d = ts * av_q2d(*time_base); + struct unit_value uv; + uv.val.d = d; + uv.unit = unit_second_str; + value_string(buf, sizeof(buf), uv); + writer_print_string(wctx, key, buf, 0); + } +} + +static void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration) +{ + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { + writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT); + } else { + writer_print_integer(wctx, key, ts); + } +} + +static void writer_print_data(WriterContext *wctx, const char *name, + uint8_t *data, int size) +{ + AVBPrint bp; + int offset = 0, l, i; + + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprintf(&bp, "\n"); + while (size) { + av_bprintf(&bp, "%08x: ", offset); + l = FFMIN(size, 16); + for (i = 0; i < l; i++) { + av_bprintf(&bp, "%02x", data[i]); + if (i & 1) + av_bprintf(&bp, " "); + } + av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2); + for (i = 0; i < l; i++) + av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1); + av_bprintf(&bp, "\n"); + offset += l; + data += l; + size -= l; + } + writer_print_string(wctx, name, bp.str, 0); + av_bprint_finalize(&bp, NULL); +} + +static void writer_print_data_hash(WriterContext *wctx, const char *name, + uint8_t *data, int size) +{ + char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 }; + + if (!hash) + return; + av_hash_init(hash); + av_hash_update(hash, data, size); + snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(hash)); + p = buf + strlen(buf); + av_hash_final_hex(hash, p, buf + sizeof(buf) - p); + writer_print_string(wctx, name, buf, 0); +} + +static void writer_print_integers(WriterContext *wctx, const char *name, + uint8_t *data, int size, const char *format, + int columns, int bytes, int offset_add) +{ + AVBPrint bp; + int offset = 0, l, i; + + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprintf(&bp, "\n"); + while (size) { + av_bprintf(&bp, "%08x: ", offset); + l = FFMIN(size, columns); + for (i = 0; i < l; i++) { + if (bytes == 1) av_bprintf(&bp, format, *data); + else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data)); + else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data)); + data += bytes; + size --; + } + av_bprintf(&bp, "\n"); + offset += offset_add; + } + writer_print_string(wctx, name, bp.str, 0); + av_bprint_finalize(&bp, NULL); +} + +#define MAX_REGISTERED_WRITERS_NB 64 + +static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1]; + +static int writer_register(const Writer *writer) +{ + static int next_registered_writer_idx = 0; + + if (next_registered_writer_idx == MAX_REGISTERED_WRITERS_NB) + return AVERROR(ENOMEM); + + registered_writers[next_registered_writer_idx++] = writer; + return 0; +} + +static const Writer *writer_get_by_name(const char *name) +{ + int i; + + for (i = 0; registered_writers[i]; i++) + if (!strcmp(registered_writers[i]->name, name)) + return registered_writers[i]; + + return NULL; +} + + +/* WRITERS */ + +#define DEFINE_WRITER_CLASS(name) \ +static const char *name##_get_name(void *ctx) \ +{ \ + return #name ; \ +} \ +static const AVClass name##_class = { \ + .class_name = #name, \ + .item_name = name##_get_name, \ + .option = name##_options \ +} + +/* Default output */ + +typedef struct DefaultContext { + const AVClass *class; + int nokey; + int noprint_wrappers; + int nested_section[SECTION_MAX_NB_LEVELS]; +} DefaultContext; + +#undef OFFSET +#define OFFSET(x) offsetof(DefaultContext, x) + +static const AVOption default_options[] = { + { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + { "nw", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(default); + +/* lame uppercasing routine, assumes the string is lower case ASCII */ +static inline char *upcase_string(char *dst, size_t dst_size, const char *src) +{ + int i; + for (i = 0; src[i] && i < dst_size-1; i++) + dst[i] = av_toupper(src[i]); + dst[i] = 0; + return dst; +} + +static void default_print_section_header(WriterContext *wctx) +{ + DefaultContext *def = wctx->priv; + char buf[32]; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + + av_bprint_clear(&wctx->section_pbuf[wctx->level]); + if (parent_section && + !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) { + def->nested_section[wctx->level] = 1; + av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", + wctx->section_pbuf[wctx->level-1].str, + upcase_string(buf, sizeof(buf), + av_x_if_null(section->element_name, section->name))); + } + + if (def->noprint_wrappers || def->nested_section[wctx->level]) + return; + + if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) + printf("[%s]\n", upcase_string(buf, sizeof(buf), section->name)); +} + +static void default_print_section_footer(WriterContext *wctx) +{ + DefaultContext *def = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + char buf[32]; + + if (def->noprint_wrappers || def->nested_section[wctx->level]) + return; + + if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) + printf("[/%s]\n", upcase_string(buf, sizeof(buf), section->name)); +} + +static void default_print_str(WriterContext *wctx, const char *key, const char *value) +{ + DefaultContext *def = wctx->priv; + + if (!def->nokey) + printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); + printf("%s\n", value); +} + +static void default_print_int(WriterContext *wctx, const char *key, long long int value) +{ + DefaultContext *def = wctx->priv; + + if (!def->nokey) + printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); + printf("%lld\n", value); +} + +static const Writer default_writer = { + .name = "default", + .priv_size = sizeof(DefaultContext), + .print_section_header = default_print_section_header, + .print_section_footer = default_print_section_footer, + .print_integer = default_print_int, + .print_string = default_print_str, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, + .priv_class = &default_class, +}; + +/* Compact output */ + +/** + * Apply C-language-like string escaping. + */ +static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + const char *p; + + for (p = src; *p; p++) { + switch (*p) { + case '\b': av_bprintf(dst, "%s", "\\b"); break; + case '\f': av_bprintf(dst, "%s", "\\f"); break; + case '\n': av_bprintf(dst, "%s", "\\n"); break; + case '\r': av_bprintf(dst, "%s", "\\r"); break; + case '\\': av_bprintf(dst, "%s", "\\\\"); break; + default: + if (*p == sep) + av_bprint_chars(dst, '\\', 1); + av_bprint_chars(dst, *p, 1); + } + } + return dst->str; +} + +/** + * Quote fields containing special characters, check RFC4180. + */ +static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; + int needs_quoting = !!src[strcspn(src, meta_chars)]; + + if (needs_quoting) + av_bprint_chars(dst, '"', 1); + + for (; *src; src++) { + if (*src == '"') + av_bprint_chars(dst, '"', 1); + av_bprint_chars(dst, *src, 1); + } + if (needs_quoting) + av_bprint_chars(dst, '"', 1); + return dst->str; +} + +static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + return src; +} + +typedef struct CompactContext { + const AVClass *class; + char *item_sep_str; + char item_sep; + int nokey; + int print_section; + char *escape_mode_str; + const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); + int nested_section[SECTION_MAX_NB_LEVELS]; + int has_nested_elems[SECTION_MAX_NB_LEVELS]; + int terminate_line[SECTION_MAX_NB_LEVELS]; +} CompactContext; + +#undef OFFSET +#define OFFSET(x) offsetof(CompactContext, x) + +static const AVOption compact_options[]= { + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(compact); + +static av_cold int compact_init(WriterContext *wctx) +{ + CompactContext *compact = wctx->priv; + + if (strlen(compact->item_sep_str) != 1) { + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", + compact->item_sep_str); + return AVERROR(EINVAL); + } + compact->item_sep = compact->item_sep_str[0]; + + if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; + else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; + else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; + else { + av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); + return AVERROR(EINVAL); + } + + return 0; +} + +static void compact_print_section_header(WriterContext *wctx) +{ + CompactContext *compact = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + compact->terminate_line[wctx->level] = 1; + compact->has_nested_elems[wctx->level] = 0; + + av_bprint_clear(&wctx->section_pbuf[wctx->level]); + if (!(section->flags & SECTION_FLAG_IS_ARRAY) && parent_section && + !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) { + compact->nested_section[wctx->level] = 1; + compact->has_nested_elems[wctx->level-1] = 1; + av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", + wctx->section_pbuf[wctx->level-1].str, + (char *)av_x_if_null(section->element_name, section->name)); + wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; + } else { + if (parent_section && compact->has_nested_elems[wctx->level-1] && + (section->flags & SECTION_FLAG_IS_ARRAY)) { + compact->terminate_line[wctx->level-1] = 0; + printf("\n"); + } + if (compact->print_section && + !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) + printf("%s%c", section->name, compact->item_sep); + } +} + +static void compact_print_section_footer(WriterContext *wctx) +{ + CompactContext *compact = wctx->priv; + + if (!compact->nested_section[wctx->level] && + compact->terminate_line[wctx->level] && + !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) + printf("\n"); +} + +static void compact_print_str(WriterContext *wctx, const char *key, const char *value) +{ + CompactContext *compact = wctx->priv; + AVBPrint buf; + + if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); + if (!compact->nokey) + printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + printf("%s", compact->escape_str(&buf, value, compact->item_sep, wctx)); + av_bprint_finalize(&buf, NULL); +} + +static void compact_print_int(WriterContext *wctx, const char *key, long long int value) +{ + CompactContext *compact = wctx->priv; + + if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); + if (!compact->nokey) + printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); + printf("%lld", value); +} + +static const Writer compact_writer = { + .name = "compact", + .priv_size = sizeof(CompactContext), + .init = compact_init, + .print_section_header = compact_print_section_header, + .print_section_footer = compact_print_section_footer, + .print_integer = compact_print_int, + .print_string = compact_print_str, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, + .priv_class = &compact_class, +}; + +/* CSV output */ + +#undef OFFSET +#define OFFSET(x) offsetof(CompactContext, x) + +static const AVOption csv_options[] = { + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, CHAR_MIN, CHAR_MAX }, + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, CHAR_MIN, CHAR_MAX }, + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX }, + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX }, + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(csv); + +static const Writer csv_writer = { + .name = "csv", + .priv_size = sizeof(CompactContext), + .init = compact_init, + .print_section_header = compact_print_section_header, + .print_section_footer = compact_print_section_footer, + .print_integer = compact_print_int, + .print_string = compact_print_str, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, + .priv_class = &csv_class, +}; + +/* Flat output */ + +typedef struct FlatContext { + const AVClass *class; + const char *sep_str; + char sep; + int hierarchical; +} FlatContext; + +#undef OFFSET +#define OFFSET(x) offsetof(FlatContext, x) + +static const AVOption flat_options[]= { + {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, CHAR_MIN, CHAR_MAX }, + {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, CHAR_MIN, CHAR_MAX }, + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(flat); + +static av_cold int flat_init(WriterContext *wctx) +{ + FlatContext *flat = wctx->priv; + + if (strlen(flat->sep_str) != 1) { + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", + flat->sep_str); + return AVERROR(EINVAL); + } + flat->sep = flat->sep_str[0]; + + return 0; +} + +static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep) +{ + const char *p; + + for (p = src; *p; p++) { + if (!((*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z'))) + av_bprint_chars(dst, '_', 1); + else + av_bprint_chars(dst, *p, 1); + } + return dst->str; +} + +static const char *flat_escape_value_str(AVBPrint *dst, const char *src) +{ + const char *p; + + for (p = src; *p; p++) { + switch (*p) { + case '\n': av_bprintf(dst, "%s", "\\n"); break; + case '\r': av_bprintf(dst, "%s", "\\r"); break; + case '\\': av_bprintf(dst, "%s", "\\\\"); break; + case '"': av_bprintf(dst, "%s", "\\\""); break; + case '`': av_bprintf(dst, "%s", "\\`"); break; + case '$': av_bprintf(dst, "%s", "\\$"); break; + default: av_bprint_chars(dst, *p, 1); break; + } + } + return dst->str; +} + +static void flat_print_section_header(WriterContext *wctx) +{ + FlatContext *flat = wctx->priv; + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + + /* build section header */ + av_bprint_clear(buf); + if (!parent_section) + return; + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); + + if (flat->hierarchical || + !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) { + av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str); + + if (parent_section->flags & SECTION_FLAG_IS_ARRAY) { + int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ? + wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1]; + av_bprintf(buf, "%d%s", n, flat->sep_str); + } + } +} + +static void flat_print_int(WriterContext *wctx, const char *key, long long int value) +{ + printf("%s%s=%lld\n", wctx->section_pbuf[wctx->level].str, key, value); +} + +static void flat_print_str(WriterContext *wctx, const char *key, const char *value) +{ + FlatContext *flat = wctx->priv; + AVBPrint buf; + + printf("%s", wctx->section_pbuf[wctx->level].str); + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + printf("%s=", flat_escape_key_str(&buf, key, flat->sep)); + av_bprint_clear(&buf); + printf("\"%s\"\n", flat_escape_value_str(&buf, value)); + av_bprint_finalize(&buf, NULL); +} + +static const Writer flat_writer = { + .name = "flat", + .priv_size = sizeof(FlatContext), + .init = flat_init, + .print_section_header = flat_print_section_header, + .print_integer = flat_print_int, + .print_string = flat_print_str, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, + .priv_class = &flat_class, +}; + +/* INI format output */ + +typedef struct INIContext { + const AVClass *class; + int hierarchical; +} INIContext; + +#undef OFFSET +#define OFFSET(x) offsetof(INIContext, x) + +static const AVOption ini_options[] = { + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(ini); + +static char *ini_escape_str(AVBPrint *dst, const char *src) +{ + int i = 0; + char c = 0; + + while (c = src[i++]) { + switch (c) { + case '\b': av_bprintf(dst, "%s", "\\b"); break; + case '\f': av_bprintf(dst, "%s", "\\f"); break; + case '\n': av_bprintf(dst, "%s", "\\n"); break; + case '\r': av_bprintf(dst, "%s", "\\r"); break; + case '\t': av_bprintf(dst, "%s", "\\t"); break; + case '\\': + case '#' : + case '=' : + case ':' : av_bprint_chars(dst, '\\', 1); + default: + if ((unsigned char)c < 32) + av_bprintf(dst, "\\x00%02x", c & 0xff); + else + av_bprint_chars(dst, c, 1); + break; + } + } + return dst->str; +} + +static void ini_print_section_header(WriterContext *wctx) +{ + INIContext *ini = wctx->priv; + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + + av_bprint_clear(buf); + if (!parent_section) { + printf("# ffprobe output\n\n"); + return; + } + + if (wctx->nb_item[wctx->level-1]) + printf("\n"); + + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); + if (ini->hierarchical || + !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) { + av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name); + + if (parent_section->flags & SECTION_FLAG_IS_ARRAY) { + int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ? + wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1]; + av_bprintf(buf, ".%d", n); + } + } + + if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) + printf("[%s]\n", buf->str); +} + +static void ini_print_str(WriterContext *wctx, const char *key, const char *value) +{ + AVBPrint buf; + + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + printf("%s=", ini_escape_str(&buf, key)); + av_bprint_clear(&buf); + printf("%s\n", ini_escape_str(&buf, value)); + av_bprint_finalize(&buf, NULL); +} + +static void ini_print_int(WriterContext *wctx, const char *key, long long int value) +{ + printf("%s=%lld\n", key, value); +} + +static const Writer ini_writer = { + .name = "ini", + .priv_size = sizeof(INIContext), + .print_section_header = ini_print_section_header, + .print_integer = ini_print_int, + .print_string = ini_print_str, + .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, + .priv_class = &ini_class, +}; + +/* JSON output */ + +typedef struct JSONContext { + const AVClass *class; + int indent_level; + int compact; + const char *item_sep, *item_start_end; +} JSONContext; + +#undef OFFSET +#define OFFSET(x) offsetof(JSONContext, x) + +static const AVOption json_options[]= { + { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + { "c", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + { NULL } +}; + +DEFINE_WRITER_CLASS(json); + +static av_cold int json_init(WriterContext *wctx) +{ + JSONContext *json = wctx->priv; + + json->item_sep = json->compact ? ", " : ",\n"; + json->item_start_end = json->compact ? " " : "\n"; + + return 0; +} + +static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx) +{ + static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0}; + static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0}; + const char *p; + + for (p = src; *p; p++) { + char *s = strchr(json_escape, *p); + if (s) { + av_bprint_chars(dst, '\\', 1); + av_bprint_chars(dst, json_subst[s - json_escape], 1); + } else if ((unsigned char)*p < 32) { + av_bprintf(dst, "\\u00%02x", *p & 0xff); + } else { + av_bprint_chars(dst, *p, 1); + } + } + return dst->str; +} + +#define JSON_INDENT() printf("%*c", json->indent_level * 4, ' ') + +static void json_print_section_header(WriterContext *wctx) +{ + JSONContext *json = wctx->priv; + AVBPrint buf; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + + if (wctx->level && wctx->nb_item[wctx->level-1]) + printf(",\n"); + + if (section->flags & SECTION_FLAG_IS_WRAPPER) { + printf("{\n"); + json->indent_level++; + } else { + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + json_escape_str(&buf, section->name, wctx); + JSON_INDENT(); + + json->indent_level++; + if (section->flags & SECTION_FLAG_IS_ARRAY) { + printf("\"%s\": [\n", buf.str); + } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) { + printf("\"%s\": {%s", buf.str, json->item_start_end); + } else { + printf("{%s", json->item_start_end); + + /* this is required so the parser can distinguish between packets and frames */ + if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) { + if (!json->compact) + JSON_INDENT(); + printf("\"type\": \"%s\"%s", section->name, json->item_sep); + } + } + av_bprint_finalize(&buf, NULL); + } +} + +static void json_print_section_footer(WriterContext *wctx) +{ + JSONContext *json = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + + if (wctx->level == 0) { + json->indent_level--; + printf("\n}\n"); + } else if (section->flags & SECTION_FLAG_IS_ARRAY) { + printf("\n"); + json->indent_level--; + JSON_INDENT(); + printf("]"); + } else { + printf("%s", json->item_start_end); + json->indent_level--; + if (!json->compact) + JSON_INDENT(); + printf("}"); + } +} + +static inline void json_print_item_str(WriterContext *wctx, + const char *key, const char *value) +{ + AVBPrint buf; + + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + printf("\"%s\":", json_escape_str(&buf, key, wctx)); + av_bprint_clear(&buf); + printf(" \"%s\"", json_escape_str(&buf, value, wctx)); + av_bprint_finalize(&buf, NULL); +} + +static void json_print_str(WriterContext *wctx, const char *key, const char *value) +{ + JSONContext *json = wctx->priv; + + if (wctx->nb_item[wctx->level]) + printf("%s", json->item_sep); + if (!json->compact) + JSON_INDENT(); + json_print_item_str(wctx, key, value); +} + +static void json_print_int(WriterContext *wctx, const char *key, long long int value) +{ + JSONContext *json = wctx->priv; + AVBPrint buf; + + if (wctx->nb_item[wctx->level]) + printf("%s", json->item_sep); + if (!json->compact) + JSON_INDENT(); + + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + printf("\"%s\": %lld", json_escape_str(&buf, key, wctx), value); + av_bprint_finalize(&buf, NULL); +} + +static const Writer json_writer = { + .name = "json", + .priv_size = sizeof(JSONContext), + .init = json_init, + .print_section_header = json_print_section_header, + .print_section_footer = json_print_section_footer, + .print_integer = json_print_int, + .print_string = json_print_str, + .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, + .priv_class = &json_class, +}; + +/* XML output */ + +typedef struct XMLContext { + const AVClass *class; + int within_tag; + int indent_level; + int fully_qualified; + int xsd_strict; +} XMLContext; + +#undef OFFSET +#define OFFSET(x) offsetof(XMLContext, x) + +static const AVOption xml_options[] = { + {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {NULL}, +}; + +DEFINE_WRITER_CLASS(xml); + +static av_cold int xml_init(WriterContext *wctx) +{ + XMLContext *xml = wctx->priv; + + if (xml->xsd_strict) { + xml->fully_qualified = 1; +#define CHECK_COMPLIANCE(opt, opt_name) \ + if (opt) { \ + av_log(wctx, AV_LOG_ERROR, \ + "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \ + "You need to disable such option with '-no%s'\n", opt_name, opt_name); \ + return AVERROR(EINVAL); \ + } + CHECK_COMPLIANCE(show_private_data, "private"); + CHECK_COMPLIANCE(show_value_unit, "unit"); + CHECK_COMPLIANCE(use_value_prefix, "prefix"); + + if (do_show_frames && do_show_packets) { + av_log(wctx, AV_LOG_ERROR, + "Interleaved frames and packets are not allowed in XSD. " + "Select only one between the -show_frames and the -show_packets options.\n"); + return AVERROR(EINVAL); + } + } + + return 0; +} + +static const char *xml_escape_str(AVBPrint *dst, const char *src, void *log_ctx) +{ + const char *p; + + for (p = src; *p; p++) { + switch (*p) { + case '&' : av_bprintf(dst, "%s", "&"); break; + case '<' : av_bprintf(dst, "%s", "<"); break; + case '>' : av_bprintf(dst, "%s", ">"); break; + case '"' : av_bprintf(dst, "%s", """); break; + case '\'': av_bprintf(dst, "%s", "'"); break; + default: av_bprint_chars(dst, *p, 1); + } + } + + return dst->str; +} + +#define XML_INDENT() printf("%*c", xml->indent_level * 4, ' ') + +static void xml_print_section_header(WriterContext *wctx) +{ + XMLContext *xml = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + const struct section *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + + if (wctx->level == 0) { + const char *qual = " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + "xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' " + "xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'"; + + printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + printf("<%sffprobe%s>\n", + xml->fully_qualified ? "ffprobe:" : "", + xml->fully_qualified ? qual : ""); + return; + } + + if (xml->within_tag) { + xml->within_tag = 0; + printf(">\n"); + } + if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { + xml->indent_level++; + } else { + if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) && + wctx->level && wctx->nb_item[wctx->level-1]) + printf("\n"); + xml->indent_level++; + + if (section->flags & SECTION_FLAG_IS_ARRAY) { + XML_INDENT(); printf("<%s>\n", section->name); + } else { + XML_INDENT(); printf("<%s ", section->name); + xml->within_tag = 1; + } + } +} + +static void xml_print_section_footer(WriterContext *wctx) +{ + XMLContext *xml = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + + if (wctx->level == 0) { + printf("</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : ""); + } else if (xml->within_tag) { + xml->within_tag = 0; + printf("/>\n"); + xml->indent_level--; + } else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { + xml->indent_level--; + } else { + XML_INDENT(); printf("</%s>\n", section->name); + xml->indent_level--; + } +} + +static void xml_print_str(WriterContext *wctx, const char *key, const char *value) +{ + AVBPrint buf; + XMLContext *xml = wctx->priv; + const struct section *section = wctx->section[wctx->level]; + + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + + if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { + XML_INDENT(); + printf("<%s key=\"%s\"", + section->element_name, xml_escape_str(&buf, key, wctx)); + av_bprint_clear(&buf); + printf(" value=\"%s\"/>\n", xml_escape_str(&buf, value, wctx)); + } else { + if (wctx->nb_item[wctx->level]) + printf(" "); + printf("%s=\"%s\"", key, xml_escape_str(&buf, value, wctx)); + } + + av_bprint_finalize(&buf, NULL); +} + +static void xml_print_int(WriterContext *wctx, const char *key, long long int value) +{ + if (wctx->nb_item[wctx->level]) + printf(" "); + printf("%s=\"%lld\"", key, value); +} + +static Writer xml_writer = { + .name = "xml", + .priv_size = sizeof(XMLContext), + .init = xml_init, + .print_section_header = xml_print_section_header, + .print_section_footer = xml_print_section_footer, + .print_integer = xml_print_int, + .print_string = xml_print_str, + .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, + .priv_class = &xml_class, +}; + +static void writer_register_all(void) +{ + static int initialized; + + if (initialized) + return; + initialized = 1; + + writer_register(&default_writer); + writer_register(&compact_writer); + writer_register(&csv_writer); + writer_register(&flat_writer); + writer_register(&ini_writer); + writer_register(&json_writer); + writer_register(&xml_writer); +} + +#define print_fmt(k, f, ...) do { \ + av_bprint_clear(&pbuf); \ + av_bprintf(&pbuf, f, __VA_ARGS__); \ + writer_print_string(w, k, pbuf.str, 0); \ +} while (0) + +#define print_int(k, v) writer_print_integer(w, k, v) +#define print_q(k, v, s) writer_print_rational(w, k, v, s) +#define print_str(k, v) writer_print_string(w, k, v, 0) +#define print_str_opt(k, v) writer_print_string(w, k, v, PRINT_STRING_OPT) +#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE) +#define print_time(k, v, tb) writer_print_time(w, k, v, tb, 0) +#define print_ts(k, v) writer_print_ts(w, k, v, 0) +#define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1) +#define print_duration_ts(k, v) writer_print_ts(w, k, v, 1) +#define print_val(k, v, u) do { \ + struct unit_value uv; \ + uv.val.i = v; \ + uv.unit = u; \ + writer_print_string(w, k, value_string(val_str, sizeof(val_str), uv), 0); \ +} while (0) + +#define print_section_header(s) writer_print_section_header(w, s) +#define print_section_footer(s) writer_print_section_footer(w, s) + +#define REALLOCZ_ARRAY_STREAM(ptr, cur_n, new_n) \ +{ \ + ret = av_reallocp_array(&(ptr), (new_n), sizeof(*(ptr))); \ + if (ret < 0) \ + goto end; \ + memset( (ptr) + (cur_n), 0, ((new_n) - (cur_n)) * sizeof(*(ptr)) ); \ +} + +static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id) +{ + AVDictionaryEntry *tag = NULL; + int ret = 0; + + if (!tags) + return 0; + writer_print_section_header(w, section_id); + + while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = print_str_validate(tag->key, tag->value)) < 0) + break; + } + writer_print_section_footer(w); + + return ret; +} + +static void print_pkt_side_data(WriterContext *w, + AVCodecParameters *par, + const AVPacketSideData *side_data, + int nb_side_data, + SectionID id_data_list, + SectionID id_data) +{ + int i; + + writer_print_section_header(w, id_data_list); + for (i = 0; i < nb_side_data; i++) { + const AVPacketSideData *sd = &side_data[i]; + const char *name = av_packet_side_data_name(sd->type); + + writer_print_section_header(w, id_data); + print_str("side_data_type", name ? name : "unknown"); + if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + } else if (sd->type == AV_PKT_DATA_STEREO3D) { + const AVStereo3D *stereo = (AVStereo3D *)sd->data; + print_str("type", av_stereo3d_type_name(stereo->type)); + print_int("inverted", !!(stereo->flags & AV_STEREO3D_FLAG_INVERT)); + } else if (sd->type == AV_PKT_DATA_SPHERICAL) { + const AVSphericalMapping *spherical = (AVSphericalMapping *)sd->data; + print_str("projection", av_spherical_projection_name(spherical->projection)); + if (spherical->projection == AV_SPHERICAL_CUBEMAP) { + print_int("padding", spherical->padding); + } else if (spherical->projection == AV_SPHERICAL_EQUIRECTANGULAR_TILE) { + size_t l, t, r, b; + av_spherical_tile_bounds(spherical, par->width, par->height, + &l, &t, &r, &b); + print_int("bound_left", l); + print_int("bound_top", t); + print_int("bound_right", r); + print_int("bound_bottom", b); + } + + print_int("yaw", (double) spherical->yaw / (1 << 16)); + print_int("pitch", (double) spherical->pitch / (1 << 16)); + print_int("roll", (double) spherical->roll / (1 << 16)); + } else if (sd->type == AV_PKT_DATA_SKIP_SAMPLES && sd->size == 10) { + print_int("skip_samples", AV_RL32(sd->data)); + print_int("discard_padding", AV_RL32(sd->data + 4)); + print_int("skip_reason", AV_RL8(sd->data + 8)); + print_int("discard_reason", AV_RL8(sd->data + 9)); + } else if (sd->type == AV_PKT_DATA_MASTERING_DISPLAY_METADATA) { + AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data; + + if (metadata->has_primaries) { + print_q("red_x", metadata->display_primaries[0][0], '/'); + print_q("red_y", metadata->display_primaries[0][1], '/'); + print_q("green_x", metadata->display_primaries[1][0], '/'); + print_q("green_y", metadata->display_primaries[1][1], '/'); + print_q("blue_x", metadata->display_primaries[2][0], '/'); + print_q("blue_y", metadata->display_primaries[2][1], '/'); + + print_q("white_point_x", metadata->white_point[0], '/'); + print_q("white_point_y", metadata->white_point[1], '/'); + } + + if (metadata->has_luminance) { + print_q("min_luminance", metadata->min_luminance, '/'); + print_q("max_luminance", metadata->max_luminance, '/'); + } + } else if (sd->type == AV_PKT_DATA_CONTENT_LIGHT_LEVEL) { + AVContentLightMetadata *metadata = (AVContentLightMetadata *)sd->data; + print_int("max_content", metadata->MaxCLL); + print_int("max_average", metadata->MaxFALL); + } + writer_print_section_footer(w); + } + writer_print_section_footer(w); +} + +static void print_color_range(WriterContext *w, enum AVColorRange color_range) +{ + const char *val = av_color_range_name(color_range); + if (!val || color_range == AVCOL_RANGE_UNSPECIFIED) { + print_str_opt("color_range", "unknown"); + } else { + print_str("color_range", val); + } +} + +static void print_color_space(WriterContext *w, enum AVColorSpace color_space) +{ + const char *val = av_color_space_name(color_space); + if (!val || color_space == AVCOL_SPC_UNSPECIFIED) { + print_str_opt("color_space", "unknown"); + } else { + print_str("color_space", val); + } +} + +static void print_primaries(WriterContext *w, enum AVColorPrimaries color_primaries) +{ + const char *val = av_color_primaries_name(color_primaries); + if (!val || color_primaries == AVCOL_PRI_UNSPECIFIED) { + print_str_opt("color_primaries", "unknown"); + } else { + print_str("color_primaries", val); + } +} + +static void print_color_trc(WriterContext *w, enum AVColorTransferCharacteristic color_trc) +{ + const char *val = av_color_transfer_name(color_trc); + if (!val || color_trc == AVCOL_TRC_UNSPECIFIED) { + print_str_opt("color_transfer", "unknown"); + } else { + print_str("color_transfer", val); + } +} + +static void print_chroma_location(WriterContext *w, enum AVChromaLocation chroma_location) +{ + const char *val = av_chroma_location_name(chroma_location); + if (!val || chroma_location == AVCHROMA_LOC_UNSPECIFIED) { + print_str_opt("chroma_location", "unspecified"); + } else { + print_str("chroma_location", val); + } +} + + +static void clear_log(int need_lock) +{ + int i; + + if (need_lock) + pthread_mutex_lock(&log_mutex); + for (i=0; i<log_buffer_size; i++) { + av_freep(&log_buffer[i].context_name); + av_freep(&log_buffer[i].parent_name); + av_freep(&log_buffer[i].log_message); + } + log_buffer_size = 0; + if(need_lock) + pthread_mutex_unlock(&log_mutex); +} + +static int show_log(WriterContext *w, int section_ids, int section_id, int log_level) +{ + int i; + pthread_mutex_lock(&log_mutex); + if (!log_buffer_size) { + pthread_mutex_unlock(&log_mutex); + return 0; + } + writer_print_section_header(w, section_ids); + + for (i=0; i<log_buffer_size; i++) { + if (log_buffer[i].log_level <= log_level) { + writer_print_section_header(w, section_id); + print_str("context", log_buffer[i].context_name); + print_int("level", log_buffer[i].log_level); + print_int("category", log_buffer[i].category); + if (log_buffer[i].parent_name) { + print_str("parent_context", log_buffer[i].parent_name); + print_int("parent_category", log_buffer[i].parent_category); + } else { + print_str_opt("parent_context", "N/A"); + print_str_opt("parent_category", "N/A"); + } + print_str("message", log_buffer[i].log_message); + writer_print_section_footer(w); + } + } + clear_log(0); + pthread_mutex_unlock(&log_mutex); + + writer_print_section_footer(w); + + return 0; +} + +static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx) +{ + char val_str[128]; + AVStream *st = ifile->streams[pkt->stream_index].st; + AVBPrint pbuf; + const char *s; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, SECTION_ID_PACKET); + + s = av_get_media_type_string(st->codecpar->codec_type); + if (s) print_str ("codec_type", s); + else print_str_opt("codec_type", "unknown"); + print_int("stream_index", pkt->stream_index); + print_ts ("pts", pkt->pts); + print_time("pts_time", pkt->pts, &st->time_base); + print_ts ("dts", pkt->dts); + print_time("dts_time", pkt->dts, &st->time_base); + print_duration_ts("duration", pkt->duration); + print_duration_time("duration_time", pkt->duration, &st->time_base); + print_duration_ts("convergence_duration", pkt->convergence_duration); + print_duration_time("convergence_duration_time", pkt->convergence_duration, &st->time_base); + print_val("size", pkt->size, unit_byte_str); + if (pkt->pos != -1) print_fmt ("pos", "%"PRId64, pkt->pos); + else print_str_opt("pos", "N/A"); + print_fmt("flags", "%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', + pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_'); + + if (pkt->side_data_elems) { + int size; + const uint8_t *side_metadata; + + side_metadata = av_packet_get_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA, &size); + if (side_metadata && size && do_show_packet_tags) { + AVDictionary *dict = NULL; + if (av_packet_unpack_dictionary(side_metadata, size, &dict) >= 0) + show_tags(w, dict, SECTION_ID_PACKET_TAGS); + av_dict_free(&dict); + } + + print_pkt_side_data(w, st->codecpar, pkt->side_data, pkt->side_data_elems, + SECTION_ID_PACKET_SIDE_DATA_LIST, + SECTION_ID_PACKET_SIDE_DATA); + } + + if (do_show_data) + writer_print_data(w, "data", pkt->data, pkt->size); + writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); + writer_print_section_footer(w); + + av_bprint_finalize(&pbuf, NULL); + fflush(stdout); +} + +static void show_subtitle(WriterContext *w, AVSubtitle *sub, AVStream *stream, + AVFormatContext *fmt_ctx) +{ + AVBPrint pbuf; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, SECTION_ID_SUBTITLE); + + print_str ("media_type", "subtitle"); + print_ts ("pts", sub->pts); + print_time("pts_time", sub->pts, &AV_TIME_BASE_Q); + print_int ("format", sub->format); + print_int ("start_display_time", sub->start_display_time); + print_int ("end_display_time", sub->end_display_time); + print_int ("num_rects", sub->num_rects); + + writer_print_section_footer(w); + + av_bprint_finalize(&pbuf, NULL); + fflush(stdout); +} + +static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, + AVFormatContext *fmt_ctx) +{ + AVBPrint pbuf; + char val_str[128]; + const char *s; + int i; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, SECTION_ID_FRAME); + + s = av_get_media_type_string(stream->codecpar->codec_type); + if (s) print_str ("media_type", s); + else print_str_opt("media_type", "unknown"); + print_int("stream_index", stream->index); + print_int("key_frame", frame->key_frame); + print_ts ("pkt_pts", frame->pts); + print_time("pkt_pts_time", frame->pts, &stream->time_base); + print_ts ("pkt_dts", frame->pkt_dts); + print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); + print_ts ("best_effort_timestamp", frame->best_effort_timestamp); + print_time("best_effort_timestamp_time", frame->best_effort_timestamp, &stream->time_base); + print_duration_ts ("pkt_duration", frame->pkt_duration); + print_duration_time("pkt_duration_time", frame->pkt_duration, &stream->time_base); + if (frame->pkt_pos != -1) print_fmt ("pkt_pos", "%"PRId64, frame->pkt_pos); + else print_str_opt("pkt_pos", "N/A"); + if (frame->pkt_size != -1) print_val ("pkt_size", frame->pkt_size, unit_byte_str); + else print_str_opt("pkt_size", "N/A"); + + switch (stream->codecpar->codec_type) { + AVRational sar; + + case AVMEDIA_TYPE_VIDEO: + print_int("width", frame->width); + print_int("height", frame->height); + s = av_get_pix_fmt_name(frame->format); + if (s) print_str ("pix_fmt", s); + else print_str_opt("pix_fmt", "unknown"); + sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, frame); + if (sar.num) { + print_q("sample_aspect_ratio", sar, ':'); + } else { + print_str_opt("sample_aspect_ratio", "N/A"); + } + print_fmt("pict_type", "%c", av_get_picture_type_char(frame->pict_type)); + print_int("coded_picture_number", frame->coded_picture_number); + print_int("display_picture_number", frame->display_picture_number); + print_int("interlaced_frame", frame->interlaced_frame); + print_int("top_field_first", frame->top_field_first); + print_int("repeat_pict", frame->repeat_pict); + + print_color_range(w, frame->color_range); + print_color_space(w, frame->colorspace); + print_primaries(w, frame->color_primaries); + print_color_trc(w, frame->color_trc); + print_chroma_location(w, frame->chroma_location); + break; + + case AVMEDIA_TYPE_AUDIO: + s = av_get_sample_fmt_name(frame->format); + if (s) print_str ("sample_fmt", s); + else print_str_opt("sample_fmt", "unknown"); + print_int("nb_samples", frame->nb_samples); + print_int("channels", frame->channels); + if (frame->channel_layout) { + av_bprint_clear(&pbuf); + av_bprint_channel_layout(&pbuf, frame->channels, + frame->channel_layout); + print_str ("channel_layout", pbuf.str); + } else + print_str_opt("channel_layout", "unknown"); + break; + } + if (do_show_frame_tags) + show_tags(w, frame->metadata, SECTION_ID_FRAME_TAGS); + if (do_show_log) + show_log(w, SECTION_ID_FRAME_LOGS, SECTION_ID_FRAME_LOG, do_show_log); + if (frame->nb_side_data) { + writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_LIST); + for (i = 0; i < frame->nb_side_data; i++) { + AVFrameSideData *sd = frame->side_data[i]; + const char *name; + + writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA); + name = av_frame_side_data_name(sd->type); + print_str("side_data_type", name ? name : "unknown"); + if (sd->type == AV_FRAME_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); + print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + } else if (sd->type == AV_FRAME_DATA_GOP_TIMECODE && sd->size >= 8) { + char tcbuf[AV_TIMECODE_STR_SIZE]; + av_timecode_make_mpeg_tc_string(tcbuf, *(int64_t *)(sd->data)); + print_str("timecode", tcbuf); + } else if (sd->type == AV_FRAME_DATA_MASTERING_DISPLAY_METADATA) { + AVMasteringDisplayMetadata *metadata = (AVMasteringDisplayMetadata *)sd->data; + + if (metadata->has_primaries) { + print_q("red_x", metadata->display_primaries[0][0], '/'); + print_q("red_y", metadata->display_primaries[0][1], '/'); + print_q("green_x", metadata->display_primaries[1][0], '/'); + print_q("green_y", metadata->display_primaries[1][1], '/'); + print_q("blue_x", metadata->display_primaries[2][0], '/'); + print_q("blue_y", metadata->display_primaries[2][1], '/'); + + print_q("white_point_x", metadata->white_point[0], '/'); + print_q("white_point_y", metadata->white_point[1], '/'); + } + + if (metadata->has_luminance) { + print_q("min_luminance", metadata->min_luminance, '/'); + print_q("max_luminance", metadata->max_luminance, '/'); + } + } else if (sd->type == AV_FRAME_DATA_CONTENT_LIGHT_LEVEL) { + AVContentLightMetadata *metadata = (AVContentLightMetadata *)sd->data; + print_int("max_content", metadata->MaxCLL); + print_int("max_average", metadata->MaxFALL); + } else if (sd->type == AV_FRAME_DATA_ICC_PROFILE) { + AVDictionaryEntry *tag = av_dict_get(sd->metadata, "name", NULL, AV_DICT_MATCH_CASE); + if (tag) + print_str(tag->key, tag->value); + print_int("size", sd->size); + } + writer_print_section_footer(w); + } + writer_print_section_footer(w); + } + + writer_print_section_footer(w); + + av_bprint_finalize(&pbuf, NULL); + fflush(stdout); +} + +static av_always_inline int process_frame(WriterContext *w, + InputFile *ifile, + AVFrame *frame, AVPacket *pkt, + int *packet_new) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + AVCodecContext *dec_ctx = ifile->streams[pkt->stream_index].dec_ctx; + AVCodecParameters *par = ifile->streams[pkt->stream_index].st->codecpar; + AVSubtitle sub; + int ret = 0, got_frame = 0; + + clear_log(1); + if (dec_ctx && dec_ctx->codec) { + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + case AVMEDIA_TYPE_AUDIO: + if (*packet_new) { + ret = avcodec_send_packet(dec_ctx, pkt); + if (ret == AVERROR(EAGAIN)) { + ret = 0; + } else if (ret >= 0 || ret == AVERROR_EOF) { + ret = 0; + *packet_new = 0; + } + } + if (ret >= 0) { + ret = avcodec_receive_frame(dec_ctx, frame); + if (ret >= 0) { + got_frame = 1; + } else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + ret = 0; + } + } + break; + + case AVMEDIA_TYPE_SUBTITLE: + ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_frame, pkt); + *packet_new = 0; + break; + default: + *packet_new = 0; + } + } else { + *packet_new = 0; + } + + if (ret < 0) + return ret; + if (got_frame) { + int is_sub = (par->codec_type == AVMEDIA_TYPE_SUBTITLE); + nb_streams_frames[pkt->stream_index]++; + if (do_show_frames) + if (is_sub) + show_subtitle(w, &sub, ifile->streams[pkt->stream_index].st, fmt_ctx); + else + show_frame(w, frame, ifile->streams[pkt->stream_index].st, fmt_ctx); + if (is_sub) + avsubtitle_free(&sub); + } + return got_frame || *packet_new; +} + +static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level) +{ + av_log(log_ctx, log_level, "id:%d", interval->id); + + if (interval->has_start) { + av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "", + av_ts2timestr(interval->start, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " start:N/A"); + } + + if (interval->has_end) { + av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : ""); + if (interval->duration_frames) + av_log(log_ctx, log_level, "#%"PRId64, interval->end); + else + av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " end:N/A"); + } + + av_log(log_ctx, log_level, "\n"); +} + +static int read_interval_packets(WriterContext *w, InputFile *ifile, + const ReadInterval *interval, int64_t *cur_ts) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + AVPacket pkt; + AVFrame *frame = NULL; + int ret = 0, i = 0, frame_count = 0; + int64_t start = -INT64_MAX, end = interval->end; + int has_start = 0, has_end = interval->has_end && !interval->end_is_offset; + + av_init_packet(&pkt); + + av_log(NULL, AV_LOG_VERBOSE, "Processing read interval "); + log_read_interval(interval, NULL, AV_LOG_VERBOSE); + + if (interval->has_start) { + int64_t target; + if (interval->start_is_offset) { + if (*cur_ts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_ERROR, + "Could not seek to relative position since current " + "timestamp is not defined\n"); + ret = AVERROR(EINVAL); + goto end; + } + target = *cur_ts + interval->start; + } else { + target = interval->start; + } + + av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n", + av_ts2timestr(target, &AV_TIME_BASE_Q)); + if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n", + interval->start, av_err2str(ret)); + goto end; + } + } + + frame = av_frame_alloc(); + if (!frame) { + ret = AVERROR(ENOMEM); + goto end; + } + while (!av_read_frame(fmt_ctx, &pkt)) { + if (ifile->nb_streams > nb_streams) { + REALLOCZ_ARRAY_STREAM(nb_streams_frames, nb_streams, fmt_ctx->nb_streams); + REALLOCZ_ARRAY_STREAM(nb_streams_packets, nb_streams, fmt_ctx->nb_streams); + REALLOCZ_ARRAY_STREAM(selected_streams, nb_streams, fmt_ctx->nb_streams); + nb_streams = ifile->nb_streams; + } + if (selected_streams[pkt.stream_index]) { + AVRational tb = ifile->streams[pkt.stream_index].st->time_base; + + if (pkt.pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q); + + if (!has_start && *cur_ts != AV_NOPTS_VALUE) { + start = *cur_ts; + has_start = 1; + } + + if (has_start && !has_end && interval->end_is_offset) { + end = start + interval->end; + has_end = 1; + } + + if (interval->end_is_offset && interval->duration_frames) { + if (frame_count >= interval->end) + break; + } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) { + break; + } + + frame_count++; + if (do_read_packets) { + if (do_show_packets) + show_packet(w, ifile, &pkt, i++); + nb_streams_packets[pkt.stream_index]++; + } + if (do_read_frames) { + int packet_new = 1; + while (process_frame(w, ifile, frame, &pkt, &packet_new) > 0); + } + } + av_packet_unref(&pkt); + } + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + //Flush remaining frames that are cached in the decoder + for (i = 0; i < fmt_ctx->nb_streams; i++) { + pkt.stream_index = i; + if (do_read_frames) + while (process_frame(w, ifile, frame, &pkt, &(int){1}) > 0); + } + +end: + av_frame_free(&frame); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval "); + log_read_interval(interval, NULL, AV_LOG_ERROR); + } + return ret; +} + +static int read_packets(WriterContext *w, InputFile *ifile) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + int i, ret = 0; + int64_t cur_ts = fmt_ctx->start_time; + + if (read_intervals_nb == 0) { + ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; + ret = read_interval_packets(w, ifile, &interval, &cur_ts); + } else { + for (i = 0; i < read_intervals_nb; i++) { + ret = read_interval_packets(w, ifile, &read_intervals[i], &cur_ts); + if (ret < 0) + break; + } + } + + return ret; +} + +static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int in_program) +{ + AVStream *stream = ist->st; + AVCodecParameters *par; + AVCodecContext *dec_ctx; + char val_str[128]; + const char *s; + AVRational sar, dar; + AVBPrint pbuf; + const AVCodecDescriptor *cd; + int ret = 0; + const char *profile = NULL; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM); + + print_int("index", stream->index); + + par = stream->codecpar; + dec_ctx = ist->dec_ctx; + if (cd = avcodec_descriptor_get(par->codec_id)) { + print_str("codec_name", cd->name); + if (!do_bitexact) { + print_str("codec_long_name", + cd->long_name ? cd->long_name : "unknown"); + } + } else { + print_str_opt("codec_name", "unknown"); + if (!do_bitexact) { + print_str_opt("codec_long_name", "unknown"); + } + } + + if (!do_bitexact && (profile = avcodec_profile_name(par->codec_id, par->profile))) + print_str("profile", profile); + else { + if (par->profile != FF_PROFILE_UNKNOWN) { + char profile_num[12]; + snprintf(profile_num, sizeof(profile_num), "%d", par->profile); + print_str("profile", profile_num); + } else + print_str_opt("profile", "unknown"); + } + + s = av_get_media_type_string(par->codec_type); + if (s) print_str ("codec_type", s); + else print_str_opt("codec_type", "unknown"); +#if FF_API_LAVF_AVCTX + if (dec_ctx) + print_q("codec_time_base", dec_ctx->time_base, '/'); +#endif + + /* print AVI/FourCC tag */ + print_str("codec_tag_string", av_fourcc2str(par->codec_tag)); + print_fmt("codec_tag", "0x%04"PRIx32, par->codec_tag); + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + print_int("width", par->width); + print_int("height", par->height); + if (dec_ctx) { + print_int("coded_width", dec_ctx->coded_width); + print_int("coded_height", dec_ctx->coded_height); + } + print_int("has_b_frames", par->video_delay); + sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, NULL); + if (sar.den) { + print_q("sample_aspect_ratio", sar, ':'); + av_reduce(&dar.num, &dar.den, + par->width * sar.num, + par->height * sar.den, + 1024*1024); + print_q("display_aspect_ratio", dar, ':'); + } else { + print_str_opt("sample_aspect_ratio", "N/A"); + print_str_opt("display_aspect_ratio", "N/A"); + } + s = av_get_pix_fmt_name(par->format); + if (s) print_str ("pix_fmt", s); + else print_str_opt("pix_fmt", "unknown"); + print_int("level", par->level); + + print_color_range(w, par->color_range); + print_color_space(w, par->color_space); + print_color_trc(w, par->color_trc); + print_primaries(w, par->color_primaries); + print_chroma_location(w, par->chroma_location); + + if (par->field_order == AV_FIELD_PROGRESSIVE) + print_str("field_order", "progressive"); + else if (par->field_order == AV_FIELD_TT) + print_str("field_order", "tt"); + else if (par->field_order == AV_FIELD_BB) + print_str("field_order", "bb"); + else if (par->field_order == AV_FIELD_TB) + print_str("field_order", "tb"); + else if (par->field_order == AV_FIELD_BT) + print_str("field_order", "bt"); + else + print_str_opt("field_order", "unknown"); + +#if FF_API_PRIVATE_OPT + if (dec_ctx && dec_ctx->timecode_frame_start >= 0) { + char tcbuf[AV_TIMECODE_STR_SIZE]; + av_timecode_make_mpeg_tc_string(tcbuf, dec_ctx->timecode_frame_start); + print_str("timecode", tcbuf); + } else { + print_str_opt("timecode", "N/A"); + } +#endif + if (dec_ctx) + print_int("refs", dec_ctx->refs); + break; + + case AVMEDIA_TYPE_AUDIO: + s = av_get_sample_fmt_name(par->format); + if (s) print_str ("sample_fmt", s); + else print_str_opt("sample_fmt", "unknown"); + print_val("sample_rate", par->sample_rate, unit_hertz_str); + print_int("channels", par->channels); + + if (par->channel_layout) { + av_bprint_clear(&pbuf); + av_bprint_channel_layout(&pbuf, par->channels, par->channel_layout); + print_str ("channel_layout", pbuf.str); + } else { + print_str_opt("channel_layout", "unknown"); + } + + print_int("bits_per_sample", av_get_bits_per_sample(par->codec_id)); + break; + + case AVMEDIA_TYPE_SUBTITLE: + if (par->width) + print_int("width", par->width); + else + print_str_opt("width", "N/A"); + if (par->height) + print_int("height", par->height); + else + print_str_opt("height", "N/A"); + break; + } + + if (dec_ctx && dec_ctx->codec && dec_ctx->codec->priv_class && show_private_data) { + const AVOption *opt = NULL; + while (opt = av_opt_next(dec_ctx->priv_data,opt)) { + uint8_t *str; + if (opt->flags) continue; + if (av_opt_get(dec_ctx->priv_data, opt->name, 0, &str) >= 0) { + print_str(opt->name, str); + av_free(str); + } + } + } + + if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%x", stream->id); + else print_str_opt("id", "N/A"); + print_q("r_frame_rate", stream->r_frame_rate, '/'); + print_q("avg_frame_rate", stream->avg_frame_rate, '/'); + print_q("time_base", stream->time_base, '/'); + print_ts ("start_pts", stream->start_time); + print_time("start_time", stream->start_time, &stream->time_base); + print_ts ("duration_ts", stream->duration); + print_time("duration", stream->duration, &stream->time_base); + if (par->bit_rate > 0) print_val ("bit_rate", par->bit_rate, unit_bit_per_second_str); + else print_str_opt("bit_rate", "N/A"); +#if FF_API_LAVF_AVCTX + if (stream->codec->rc_max_rate > 0) print_val ("max_bit_rate", stream->codec->rc_max_rate, unit_bit_per_second_str); + else print_str_opt("max_bit_rate", "N/A"); +#endif + if (dec_ctx && dec_ctx->bits_per_raw_sample > 0) print_fmt("bits_per_raw_sample", "%d", dec_ctx->bits_per_raw_sample); + else print_str_opt("bits_per_raw_sample", "N/A"); + if (stream->nb_frames) print_fmt ("nb_frames", "%"PRId64, stream->nb_frames); + else print_str_opt("nb_frames", "N/A"); + if (nb_streams_frames[stream_idx]) print_fmt ("nb_read_frames", "%"PRIu64, nb_streams_frames[stream_idx]); + else print_str_opt("nb_read_frames", "N/A"); + if (nb_streams_packets[stream_idx]) print_fmt ("nb_read_packets", "%"PRIu64, nb_streams_packets[stream_idx]); + else print_str_opt("nb_read_packets", "N/A"); + if (do_show_data) + writer_print_data(w, "extradata", par->extradata, + par->extradata_size); + writer_print_data_hash(w, "extradata_hash", par->extradata, + par->extradata_size); + + /* Print disposition information */ +#define PRINT_DISPOSITION(flagname, name) do { \ + print_int(name, !!(stream->disposition & AV_DISPOSITION_##flagname)); \ + } while (0) + + if (do_show_stream_disposition) { + writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM_DISPOSITION : SECTION_ID_STREAM_DISPOSITION); + PRINT_DISPOSITION(DEFAULT, "default"); + PRINT_DISPOSITION(DUB, "dub"); + PRINT_DISPOSITION(ORIGINAL, "original"); + PRINT_DISPOSITION(COMMENT, "comment"); + PRINT_DISPOSITION(LYRICS, "lyrics"); + PRINT_DISPOSITION(KARAOKE, "karaoke"); + PRINT_DISPOSITION(FORCED, "forced"); + PRINT_DISPOSITION(HEARING_IMPAIRED, "hearing_impaired"); + PRINT_DISPOSITION(VISUAL_IMPAIRED, "visual_impaired"); + PRINT_DISPOSITION(CLEAN_EFFECTS, "clean_effects"); + PRINT_DISPOSITION(ATTACHED_PIC, "attached_pic"); + PRINT_DISPOSITION(TIMED_THUMBNAILS, "timed_thumbnails"); + writer_print_section_footer(w); + } + + if (do_show_stream_tags) + ret = show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS); + + if (stream->nb_side_data) { + print_pkt_side_data(w, stream->codecpar, stream->side_data, stream->nb_side_data, + SECTION_ID_STREAM_SIDE_DATA_LIST, + SECTION_ID_STREAM_SIDE_DATA); + } + + writer_print_section_footer(w); + av_bprint_finalize(&pbuf, NULL); + fflush(stdout); + + return ret; +} + +static int show_streams(WriterContext *w, InputFile *ifile) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_STREAMS); + for (i = 0; i < ifile->nb_streams; i++) + if (selected_streams[i]) { + ret = show_stream(w, fmt_ctx, i, &ifile->streams[i], 0); + if (ret < 0) + break; + } + writer_print_section_footer(w); + + return ret; +} + +static int show_program(WriterContext *w, InputFile *ifile, AVProgram *program) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_PROGRAM); + print_int("program_id", program->id); + print_int("program_num", program->program_num); + print_int("nb_streams", program->nb_stream_indexes); + print_int("pmt_pid", program->pmt_pid); + print_int("pcr_pid", program->pcr_pid); + print_ts("start_pts", program->start_time); + print_time("start_time", program->start_time, &AV_TIME_BASE_Q); + print_ts("end_pts", program->end_time); + print_time("end_time", program->end_time, &AV_TIME_BASE_Q); + if (do_show_program_tags) + ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); + if (ret < 0) + goto end; + + writer_print_section_header(w, SECTION_ID_PROGRAM_STREAMS); + for (i = 0; i < program->nb_stream_indexes; i++) { + if (selected_streams[program->stream_index[i]]) { + ret = show_stream(w, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], 1); + if (ret < 0) + break; + } + } + writer_print_section_footer(w); + +end: + writer_print_section_footer(w); + return ret; +} + +static int show_programs(WriterContext *w, InputFile *ifile) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_PROGRAMS); + for (i = 0; i < fmt_ctx->nb_programs; i++) { + AVProgram *program = fmt_ctx->programs[i]; + if (!program) + continue; + ret = show_program(w, ifile, program); + if (ret < 0) + break; + } + writer_print_section_footer(w); + return ret; +} + +static int show_chapters(WriterContext *w, InputFile *ifile) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + int i, ret = 0; + + writer_print_section_header(w, SECTION_ID_CHAPTERS); + for (i = 0; i < fmt_ctx->nb_chapters; i++) { + AVChapter *chapter = fmt_ctx->chapters[i]; + + writer_print_section_header(w, SECTION_ID_CHAPTER); + print_int("id", chapter->id); + print_q ("time_base", chapter->time_base, '/'); + print_int("start", chapter->start); + print_time("start_time", chapter->start, &chapter->time_base); + print_int("end", chapter->end); + print_time("end_time", chapter->end, &chapter->time_base); + if (do_show_chapter_tags) + ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); + writer_print_section_footer(w); + } + writer_print_section_footer(w); + + return ret; +} + +static int show_format(WriterContext *w, InputFile *ifile) +{ + AVFormatContext *fmt_ctx = ifile->fmt_ctx; + char val_str[128]; + int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; + int ret = 0; + + writer_print_section_header(w, SECTION_ID_FORMAT); + print_str_validate("filename", fmt_ctx->filename); + print_int("nb_streams", fmt_ctx->nb_streams); + print_int("nb_programs", fmt_ctx->nb_programs); + print_str("format_name", fmt_ctx->iformat->name); + if (!do_bitexact) { + if (fmt_ctx->iformat->long_name) print_str ("format_long_name", fmt_ctx->iformat->long_name); + else print_str_opt("format_long_name", "unknown"); + } + print_time("start_time", fmt_ctx->start_time, &AV_TIME_BASE_Q); + print_time("duration", fmt_ctx->duration, &AV_TIME_BASE_Q); + if (size >= 0) print_val ("size", size, unit_byte_str); + else print_str_opt("size", "N/A"); + if (fmt_ctx->bit_rate > 0) print_val ("bit_rate", fmt_ctx->bit_rate, unit_bit_per_second_str); + else print_str_opt("bit_rate", "N/A"); + print_int("probe_score", av_format_get_probe_score(fmt_ctx)); + if (do_show_format_tags) + ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); + + writer_print_section_footer(w); + fflush(stdout); + return ret; +} + +static void show_error(WriterContext *w, int err) +{ + char errbuf[128]; + const char *errbuf_ptr = errbuf; + + if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) + errbuf_ptr = strerror(AVUNERROR(err)); + + writer_print_section_header(w, SECTION_ID_ERROR); + print_int("code", err); + print_str("string", errbuf_ptr); + writer_print_section_footer(w); +} + +static int open_input_file(InputFile *ifile, const char *filename) +{ + int err, i; + AVFormatContext *fmt_ctx = NULL; + AVDictionaryEntry *t; + int scan_all_pmts_set = 0; + + fmt_ctx = avformat_alloc_context(); + if (!fmt_ctx) { + print_error(filename, AVERROR(ENOMEM)); + exit_program(1); + } + + fmt_ctx->flags |= AVFMT_FLAG_KEEP_SIDE_DATA; + + if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + if ((err = avformat_open_input(&fmt_ctx, filename, + iformat, &format_opts)) < 0) { + print_error(filename, err); + return err; + } + ifile->fmt_ctx = fmt_ctx; + if (scan_all_pmts_set) + av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { + av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); + return AVERROR_OPTION_NOT_FOUND; + } + + if (find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(fmt_ctx, codec_opts); + int orig_nb_streams = fmt_ctx->nb_streams; + + err = avformat_find_stream_info(fmt_ctx, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (err < 0) { + print_error(filename, err); + return err; + } + } + + av_dump_format(fmt_ctx, 0, filename, 0); + + ifile->streams = av_mallocz_array(fmt_ctx->nb_streams, + sizeof(*ifile->streams)); + if (!ifile->streams) + exit(1); + ifile->nb_streams = fmt_ctx->nb_streams; + + /* bind a decoder to each input stream */ + for (i = 0; i < fmt_ctx->nb_streams; i++) { + InputStream *ist = &ifile->streams[i]; + AVStream *stream = fmt_ctx->streams[i]; + AVCodec *codec; + + ist->st = stream; + + if (stream->codecpar->codec_id == AV_CODEC_ID_PROBE) { + av_log(NULL, AV_LOG_WARNING, + "Failed to probe codec for input stream %d\n", + stream->index); + continue; + } + + codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (!codec) { + av_log(NULL, AV_LOG_WARNING, + "Unsupported codec with id %d for input stream %d\n", + stream->codecpar->codec_id, stream->index); + continue; + } + { + AVDictionary *opts = filter_codec_opts(codec_opts, stream->codecpar->codec_id, + fmt_ctx, stream, codec); + + ist->dec_ctx = avcodec_alloc_context3(codec); + if (!ist->dec_ctx) + exit(1); + + err = avcodec_parameters_to_context(ist->dec_ctx, stream->codecpar); + if (err < 0) + exit(1); + + if (do_show_log) { + // For loging it is needed to disable at least frame threads as otherwise + // the log information would need to be reordered and matches up to contexts and frames + // That is in fact possible but not trivial + av_dict_set(&codec_opts, "threads", "1", 0); + } + + av_codec_set_pkt_timebase(ist->dec_ctx, stream->time_base); + ist->dec_ctx->framerate = stream->avg_frame_rate; + + if (avcodec_open2(ist->dec_ctx, codec, &opts) < 0) { + av_log(NULL, AV_LOG_WARNING, "Could not open codec for input stream %d\n", + stream->index); + exit(1); + } + + if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { + av_log(NULL, AV_LOG_ERROR, "Option %s for input stream %d not found\n", + t->key, stream->index); + return AVERROR_OPTION_NOT_FOUND; + } + } + } + + ifile->fmt_ctx = fmt_ctx; + return 0; +} + +static void close_input_file(InputFile *ifile) +{ + int i; + + /* close decoder for each stream */ + for (i = 0; i < ifile->nb_streams; i++) + if (ifile->streams[i].st->codecpar->codec_id != AV_CODEC_ID_NONE) + avcodec_free_context(&ifile->streams[i].dec_ctx); + + av_freep(&ifile->streams); + ifile->nb_streams = 0; + + avformat_close_input(&ifile->fmt_ctx); +} + +static int probe_file(WriterContext *wctx, const char *filename) +{ + InputFile ifile = { 0 }; + int ret, i; + int section_id; + + do_read_frames = do_show_frames || do_count_frames; + do_read_packets = do_show_packets || do_count_packets; + + ret = open_input_file(&ifile, filename); + if (ret < 0) + goto end; + +#define CHECK_END if (ret < 0) goto end + + nb_streams = ifile.fmt_ctx->nb_streams; + REALLOCZ_ARRAY_STREAM(nb_streams_frames,0,ifile.fmt_ctx->nb_streams); + REALLOCZ_ARRAY_STREAM(nb_streams_packets,0,ifile.fmt_ctx->nb_streams); + REALLOCZ_ARRAY_STREAM(selected_streams,0,ifile.fmt_ctx->nb_streams); + + for (i = 0; i < ifile.fmt_ctx->nb_streams; i++) { + if (stream_specifier) { + ret = avformat_match_stream_specifier(ifile.fmt_ctx, + ifile.fmt_ctx->streams[i], + stream_specifier); + CHECK_END; + else + selected_streams[i] = ret; + ret = 0; + } else { + selected_streams[i] = 1; + } + if (!selected_streams[i]) + ifile.fmt_ctx->streams[i]->discard = AVDISCARD_ALL; + } + + if (do_read_frames || do_read_packets) { + if (do_show_frames && do_show_packets && + wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER) + section_id = SECTION_ID_PACKETS_AND_FRAMES; + else if (do_show_packets && !do_show_frames) + section_id = SECTION_ID_PACKETS; + else // (!do_show_packets && do_show_frames) + section_id = SECTION_ID_FRAMES; + if (do_show_frames || do_show_packets) + writer_print_section_header(wctx, section_id); + ret = read_packets(wctx, &ifile); + if (do_show_frames || do_show_packets) + writer_print_section_footer(wctx); + CHECK_END; + } + + if (do_show_programs) { + ret = show_programs(wctx, &ifile); + CHECK_END; + } + + if (do_show_streams) { + ret = show_streams(wctx, &ifile); + CHECK_END; + } + if (do_show_chapters) { + ret = show_chapters(wctx, &ifile); + CHECK_END; + } + if (do_show_format) { + ret = show_format(wctx, &ifile); + CHECK_END; + } + +end: + if (ifile.fmt_ctx) + close_input_file(&ifile); + av_freep(&nb_streams_frames); + av_freep(&nb_streams_packets); + av_freep(&selected_streams); + + return ret; +} + +static void show_usage(void) +{ + av_log(NULL, AV_LOG_INFO, "Simple multimedia streams analyzer\n"); + av_log(NULL, AV_LOG_INFO, "usage: %s [OPTIONS] [INPUT_FILE]\n", program_name); + av_log(NULL, AV_LOG_INFO, "\n"); +} + +static void ffprobe_show_program_version(WriterContext *w) +{ + AVBPrint pbuf; + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + + writer_print_section_header(w, SECTION_ID_PROGRAM_VERSION); + print_str("version", FFMPEG_VERSION); + print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers", + program_birth_year, CONFIG_THIS_YEAR); + print_str("compiler_ident", CC_IDENT); + print_str("configuration", FFMPEG_CONFIGURATION); + writer_print_section_footer(w); + + av_bprint_finalize(&pbuf, NULL); +} + +#define SHOW_LIB_VERSION(libname, LIBNAME) \ + do { \ + if (CONFIG_##LIBNAME) { \ + unsigned int version = libname##_version(); \ + writer_print_section_header(w, SECTION_ID_LIBRARY_VERSION); \ + print_str("name", "lib" #libname); \ + print_int("major", LIB##LIBNAME##_VERSION_MAJOR); \ + print_int("minor", LIB##LIBNAME##_VERSION_MINOR); \ + print_int("micro", LIB##LIBNAME##_VERSION_MICRO); \ + print_int("version", version); \ + print_str("ident", LIB##LIBNAME##_IDENT); \ + writer_print_section_footer(w); \ + } \ + } while (0) + +static void ffprobe_show_library_versions(WriterContext *w) +{ + writer_print_section_header(w, SECTION_ID_LIBRARY_VERSIONS); + SHOW_LIB_VERSION(avutil, AVUTIL); + SHOW_LIB_VERSION(avcodec, AVCODEC); + SHOW_LIB_VERSION(avformat, AVFORMAT); + SHOW_LIB_VERSION(avdevice, AVDEVICE); + SHOW_LIB_VERSION(avfilter, AVFILTER); + SHOW_LIB_VERSION(swscale, SWSCALE); + SHOW_LIB_VERSION(swresample, SWRESAMPLE); + SHOW_LIB_VERSION(postproc, POSTPROC); + writer_print_section_footer(w); +} + +#define PRINT_PIX_FMT_FLAG(flagname, name) \ + do { \ + print_int(name, !!(pixdesc->flags & AV_PIX_FMT_FLAG_##flagname)); \ + } while (0) + +static void ffprobe_show_pixel_formats(WriterContext *w) +{ + const AVPixFmtDescriptor *pixdesc = NULL; + int i, n; + + writer_print_section_header(w, SECTION_ID_PIXEL_FORMATS); + while (pixdesc = av_pix_fmt_desc_next(pixdesc)) { + writer_print_section_header(w, SECTION_ID_PIXEL_FORMAT); + print_str("name", pixdesc->name); + print_int("nb_components", pixdesc->nb_components); + if ((pixdesc->nb_components >= 3) && !(pixdesc->flags & AV_PIX_FMT_FLAG_RGB)) { + print_int ("log2_chroma_w", pixdesc->log2_chroma_w); + print_int ("log2_chroma_h", pixdesc->log2_chroma_h); + } else { + print_str_opt("log2_chroma_w", "N/A"); + print_str_opt("log2_chroma_h", "N/A"); + } + n = av_get_bits_per_pixel(pixdesc); + if (n) print_int ("bits_per_pixel", n); + else print_str_opt("bits_per_pixel", "N/A"); + if (do_show_pixel_format_flags) { + writer_print_section_header(w, SECTION_ID_PIXEL_FORMAT_FLAGS); + PRINT_PIX_FMT_FLAG(BE, "big_endian"); + PRINT_PIX_FMT_FLAG(PAL, "palette"); + PRINT_PIX_FMT_FLAG(BITSTREAM, "bitstream"); + PRINT_PIX_FMT_FLAG(HWACCEL, "hwaccel"); + PRINT_PIX_FMT_FLAG(PLANAR, "planar"); + PRINT_PIX_FMT_FLAG(RGB, "rgb"); + PRINT_PIX_FMT_FLAG(PSEUDOPAL, "pseudopal"); + PRINT_PIX_FMT_FLAG(ALPHA, "alpha"); + writer_print_section_footer(w); + } + if (do_show_pixel_format_components && (pixdesc->nb_components > 0)) { + writer_print_section_header(w, SECTION_ID_PIXEL_FORMAT_COMPONENTS); + for (i = 0; i < pixdesc->nb_components; i++) { + writer_print_section_header(w, SECTION_ID_PIXEL_FORMAT_COMPONENT); + print_int("index", i + 1); + print_int("bit_depth", pixdesc->comp[i].depth); + writer_print_section_footer(w); + } + writer_print_section_footer(w); + } + writer_print_section_footer(w); + } + writer_print_section_footer(w); +} + +static int opt_format(void *optctx, const char *opt, const char *arg) +{ + iformat = av_find_input_format(arg); + if (!iformat) { + av_log(NULL, AV_LOG_ERROR, "Unknown input format: %s\n", arg); + return AVERROR(EINVAL); + } + return 0; +} + +static inline void mark_section_show_entries(SectionID section_id, + int show_all_entries, AVDictionary *entries) +{ + struct section *section = §ions[section_id]; + + section->show_all_entries = show_all_entries; + if (show_all_entries) { + SectionID *id; + for (id = section->children_ids; *id != -1; id++) + mark_section_show_entries(*id, show_all_entries, entries); + } else { + av_dict_copy(§ion->entries_to_show, entries, 0); + } +} + +static int match_section(const char *section_name, + int show_all_entries, AVDictionary *entries) +{ + int i, ret = 0; + + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) { + const struct section *section = §ions[i]; + if (!strcmp(section_name, section->name) || + (section->unique_name && !strcmp(section_name, section->unique_name))) { + av_log(NULL, AV_LOG_DEBUG, + "'%s' matches section with unique name '%s'\n", section_name, + (char *)av_x_if_null(section->unique_name, section->name)); + ret++; + mark_section_show_entries(section->id, show_all_entries, entries); + } + } + return ret; +} + +static int opt_show_entries(void *optctx, const char *opt, const char *arg) +{ + const char *p = arg; + int ret = 0; + + while (*p) { + AVDictionary *entries = NULL; + char *section_name = av_get_token(&p, "=:"); + int show_all_entries = 0; + + if (!section_name) { + av_log(NULL, AV_LOG_ERROR, + "Missing section name for option '%s'\n", opt); + return AVERROR(EINVAL); + } + + if (*p == '=') { + p++; + while (*p && *p != ':') { + char *entry = av_get_token(&p, ",:"); + if (!entry) + break; + av_log(NULL, AV_LOG_VERBOSE, + "Adding '%s' to the entries to show in section '%s'\n", + entry, section_name); + av_dict_set(&entries, entry, "", AV_DICT_DONT_STRDUP_KEY); + if (*p == ',') + p++; + } + } else { + show_all_entries = 1; + } + + ret = match_section(section_name, show_all_entries, entries); + if (ret == 0) { + av_log(NULL, AV_LOG_ERROR, "No match for section '%s'\n", section_name); + ret = AVERROR(EINVAL); + } + av_dict_free(&entries); + av_free(section_name); + + if (ret <= 0) + break; + if (*p) + p++; + } + + return ret; +} + +static int opt_show_format_entry(void *optctx, const char *opt, const char *arg) +{ + char *buf = av_asprintf("format=%s", arg); + int ret; + + if (!buf) + return AVERROR(ENOMEM); + + av_log(NULL, AV_LOG_WARNING, + "Option '%s' is deprecated, use '-show_entries format=%s' instead\n", + opt, arg); + ret = opt_show_entries(optctx, opt, buf); + av_free(buf); + return ret; +} + +static void opt_input_file(void *optctx, const char *arg) +{ + if (input_filename) { + av_log(NULL, AV_LOG_ERROR, + "Argument '%s' provided as input filename, but '%s' was already specified.\n", + arg, input_filename); + exit_program(1); + } + if (!strcmp(arg, "-")) + arg = "pipe:"; + input_filename = arg; +} + +static int opt_input_file_i(void *optctx, const char *opt, const char *arg) +{ + opt_input_file(optctx, arg); + return 0; +} + +void show_help_default(const char *opt, const char *arg) +{ + av_log_set_callback(log_callback_help); + show_usage(); + show_help_options(options, "Main options:", 0, 0, 0); + printf("\n"); + + show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); + show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM); +} + +/** + * Parse interval specification, according to the format: + * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]] + * INTERVALS ::= INTERVAL[,INTERVALS] +*/ +static int parse_read_interval(const char *interval_spec, + ReadInterval *interval) +{ + int ret = 0; + char *next, *p, *spec = av_strdup(interval_spec); + if (!spec) + return AVERROR(ENOMEM); + + if (!*spec) { + av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n"); + ret = AVERROR(EINVAL); + goto end; + } + + p = spec; + next = strchr(spec, '%'); + if (next) + *next++ = 0; + + /* parse first part */ + if (*p) { + interval->has_start = 1; + + if (*p == '+') { + interval->start_is_offset = 1; + p++; + } else { + interval->start_is_offset = 0; + } + + ret = av_parse_time(&interval->start, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p); + goto end; + } + } else { + interval->has_start = 0; + } + + /* parse second part */ + p = next; + if (p && *p) { + int64_t us; + interval->has_end = 1; + + if (*p == '+') { + interval->end_is_offset = 1; + p++; + } else { + interval->end_is_offset = 0; + } + + if (interval->end_is_offset && *p == '#') { + long long int lli; + char *tail; + interval->duration_frames = 1; + p++; + lli = strtoll(p, &tail, 10); + if (*tail || lli < 0) { + av_log(NULL, AV_LOG_ERROR, + "Invalid or negative value '%s' for duration number of frames\n", p); + goto end; + } + interval->end = lli; + } else { + interval->duration_frames = 0; + ret = av_parse_time(&us, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p); + goto end; + } + interval->end = us; + } + } else { + interval->has_end = 0; + } + +end: + av_free(spec); + return ret; +} + +static int parse_read_intervals(const char *intervals_spec) +{ + int ret, n, i; + char *p, *spec = av_strdup(intervals_spec); + if (!spec) + return AVERROR(ENOMEM); + + /* preparse specification, get number of intervals */ + for (n = 0, p = spec; *p; p++) + if (*p == ',') + n++; + n++; + + read_intervals = av_malloc_array(n, sizeof(*read_intervals)); + if (!read_intervals) { + ret = AVERROR(ENOMEM); + goto end; + } + read_intervals_nb = n; + + /* parse intervals */ + p = spec; + for (i = 0; p; i++) { + char *next; + + av_assert0(i < read_intervals_nb); + next = strchr(p, ','); + if (next) + *next++ = 0; + + read_intervals[i].id = i; + ret = parse_read_interval(p, &read_intervals[i]); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n", + i, p); + goto end; + } + av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval "); + log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE); + p = next; + } + av_assert0(i == read_intervals_nb); + +end: + av_free(spec); + return ret; +} + +static int opt_read_intervals(void *optctx, const char *opt, const char *arg) +{ + return parse_read_intervals(arg); +} + +static int opt_pretty(void *optctx, const char *opt, const char *arg) +{ + show_value_unit = 1; + use_value_prefix = 1; + use_byte_value_binary_prefix = 1; + use_value_sexagesimal_format = 1; + return 0; +} + +static void print_section(SectionID id, int level) +{ + const SectionID *pid; + const struct section *section = §ions[id]; + printf("%c%c%c", + section->flags & SECTION_FLAG_IS_WRAPPER ? 'W' : '.', + section->flags & SECTION_FLAG_IS_ARRAY ? 'A' : '.', + section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS ? 'V' : '.'); + printf("%*c %s", level * 4, ' ', section->name); + if (section->unique_name) + printf("/%s", section->unique_name); + printf("\n"); + + for (pid = section->children_ids; *pid != -1; pid++) + print_section(*pid, level+1); +} + +static int opt_sections(void *optctx, const char *opt, const char *arg) +{ + printf("Sections:\n" + "W.. = Section is a wrapper (contains other sections, no local entries)\n" + ".A. = Section contains an array of elements of the same type\n" + "..V = Section may contain a variable number of fields with variable keys\n" + "FLAGS NAME/UNIQUE_NAME\n" + "---\n"); + print_section(SECTION_ID_ROOT, 0); + return 0; +} + +static int opt_show_versions(const char *opt, const char *arg) +{ + mark_section_show_entries(SECTION_ID_PROGRAM_VERSION, 1, NULL); + mark_section_show_entries(SECTION_ID_LIBRARY_VERSION, 1, NULL); + return 0; +} + +#define DEFINE_OPT_SHOW_SECTION(section, target_section_id) \ + static int opt_show_##section(const char *opt, const char *arg) \ + { \ + mark_section_show_entries(SECTION_ID_##target_section_id, 1, NULL); \ + return 0; \ + } + +DEFINE_OPT_SHOW_SECTION(chapters, CHAPTERS) +DEFINE_OPT_SHOW_SECTION(error, ERROR) +DEFINE_OPT_SHOW_SECTION(format, FORMAT) +DEFINE_OPT_SHOW_SECTION(frames, FRAMES) +DEFINE_OPT_SHOW_SECTION(library_versions, LIBRARY_VERSIONS) +DEFINE_OPT_SHOW_SECTION(packets, PACKETS) +DEFINE_OPT_SHOW_SECTION(pixel_formats, PIXEL_FORMATS) +DEFINE_OPT_SHOW_SECTION(program_version, PROGRAM_VERSION) +DEFINE_OPT_SHOW_SECTION(streams, STREAMS) +DEFINE_OPT_SHOW_SECTION(programs, PROGRAMS) + +static const OptionDef real_options[] = { + CMDUTILS_COMMON_OPTIONS + { "f", HAS_ARG, {.func_arg = opt_format}, "force format", "format" }, + { "unit", OPT_BOOL, {&show_value_unit}, "show unit of the displayed values" }, + { "prefix", OPT_BOOL, {&use_value_prefix}, "use SI prefixes for the displayed values" }, + { "byte_binary_prefix", OPT_BOOL, {&use_byte_value_binary_prefix}, + "use binary prefixes for byte units" }, + { "sexagesimal", OPT_BOOL, {&use_value_sexagesimal_format}, + "use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units" }, + { "pretty", 0, {.func_arg = opt_pretty}, + "prettify the format of displayed values, make it more human readable" }, + { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, + "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)", "format" }, + { "of", OPT_STRING | HAS_ARG, {(void*)&print_format}, "alias for -print_format", "format" }, + { "select_streams", OPT_STRING | HAS_ARG, {(void*)&stream_specifier}, "select the specified streams", "stream_specifier" }, + { "sections", OPT_EXIT, {.func_arg = opt_sections}, "print sections structure and section information, and exit" }, + { "show_data", OPT_BOOL, {(void*)&do_show_data}, "show packets data" }, + { "show_data_hash", OPT_STRING | HAS_ARG, {(void*)&show_data_hash}, "show packets data hash" }, + { "show_error", 0, {(void*)&opt_show_error}, "show probing error" }, + { "show_format", 0, {(void*)&opt_show_format}, "show format/container info" }, + { "show_frames", 0, {(void*)&opt_show_frames}, "show frames info" }, + { "show_format_entry", HAS_ARG, {.func_arg = opt_show_format_entry}, + "show a particular entry from the format/container info", "entry" }, + { "show_entries", HAS_ARG, {.func_arg = opt_show_entries}, + "show a set of specified entries", "entry_list" }, +#if HAVE_THREADS + { "show_log", OPT_INT|HAS_ARG, {(void*)&do_show_log}, "show log" }, +#endif + { "show_packets", 0, {(void*)&opt_show_packets}, "show packets info" }, + { "show_programs", 0, {(void*)&opt_show_programs}, "show programs info" }, + { "show_streams", 0, {(void*)&opt_show_streams}, "show streams info" }, + { "show_chapters", 0, {(void*)&opt_show_chapters}, "show chapters info" }, + { "count_frames", OPT_BOOL, {(void*)&do_count_frames}, "count the number of frames per stream" }, + { "count_packets", OPT_BOOL, {(void*)&do_count_packets}, "count the number of packets per stream" }, + { "show_program_version", 0, {(void*)&opt_show_program_version}, "show ffprobe version" }, + { "show_library_versions", 0, {(void*)&opt_show_library_versions}, "show library versions" }, + { "show_versions", 0, {(void*)&opt_show_versions}, "show program and library versions" }, + { "show_pixel_formats", 0, {(void*)&opt_show_pixel_formats}, "show pixel format descriptions" }, + { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, + { "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, + { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, + { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, + { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" }, + { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, + { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + "read and decode the streams to fill missing information with heuristics" }, + { NULL, }, +}; + +static inline int check_section_show_entries(int section_id) +{ + int *id; + struct section *section = §ions[section_id]; + if (sections[section_id].show_all_entries || sections[section_id].entries_to_show) + return 1; + for (id = section->children_ids; *id != -1; id++) + if (check_section_show_entries(*id)) + return 1; + return 0; +} + +#define SET_DO_SHOW(id, varname) do { \ + if (check_section_show_entries(SECTION_ID_##id)) \ + do_show_##varname = 1; \ + } while (0) + +int main(int argc, char **argv) +{ + const Writer *w; + WriterContext *wctx; + char *buf; + char *w_name = NULL, *w_args = NULL; + int ret, i; + + init_dynload(); + +#if HAVE_THREADS + ret = pthread_mutex_init(&log_mutex, NULL); + if (ret != 0) { + goto end; + } +#endif + av_log_set_flags(AV_LOG_SKIP_REPEATED); + register_exit(ffprobe_cleanup); + + options = real_options; + parse_loglevel(argc, argv, options); + av_register_all(); + avformat_network_init(); + init_opts(); +#if CONFIG_AVDEVICE + avdevice_register_all(); +#endif + + show_banner(argc, argv, options); + parse_options(NULL, argc, argv, options, opt_input_file); + + if (do_show_log) + av_log_set_callback(log_callback); + + /* mark things to show, based on -show_entries */ + SET_DO_SHOW(CHAPTERS, chapters); + SET_DO_SHOW(ERROR, error); + SET_DO_SHOW(FORMAT, format); + SET_DO_SHOW(FRAMES, frames); + SET_DO_SHOW(LIBRARY_VERSIONS, library_versions); + SET_DO_SHOW(PACKETS, packets); + SET_DO_SHOW(PIXEL_FORMATS, pixel_formats); + SET_DO_SHOW(PIXEL_FORMAT_FLAGS, pixel_format_flags); + SET_DO_SHOW(PIXEL_FORMAT_COMPONENTS, pixel_format_components); + SET_DO_SHOW(PROGRAM_VERSION, program_version); + SET_DO_SHOW(PROGRAMS, programs); + SET_DO_SHOW(STREAMS, streams); + SET_DO_SHOW(STREAM_DISPOSITION, stream_disposition); + SET_DO_SHOW(PROGRAM_STREAM_DISPOSITION, stream_disposition); + + SET_DO_SHOW(CHAPTER_TAGS, chapter_tags); + SET_DO_SHOW(FORMAT_TAGS, format_tags); + SET_DO_SHOW(FRAME_TAGS, frame_tags); + SET_DO_SHOW(PROGRAM_TAGS, program_tags); + SET_DO_SHOW(STREAM_TAGS, stream_tags); + SET_DO_SHOW(PROGRAM_STREAM_TAGS, stream_tags); + SET_DO_SHOW(PACKET_TAGS, packet_tags); + + if (do_bitexact && (do_show_program_version || do_show_library_versions)) { + av_log(NULL, AV_LOG_ERROR, + "-bitexact and -show_program_version or -show_library_versions " + "options are incompatible\n"); + ret = AVERROR(EINVAL); + goto end; + } + + writer_register_all(); + + if (!print_format) + print_format = av_strdup("default"); + if (!print_format) { + ret = AVERROR(ENOMEM); + goto end; + } + w_name = av_strtok(print_format, "=", &buf); + if (!w_name) { + av_log(NULL, AV_LOG_ERROR, + "No name specified for the output format\n"); + ret = AVERROR(EINVAL); + goto end; + } + w_args = buf; + + if (show_data_hash) { + if ((ret = av_hash_alloc(&hash, show_data_hash)) < 0) { + if (ret == AVERROR(EINVAL)) { + const char *n; + av_log(NULL, AV_LOG_ERROR, + "Unknown hash algorithm '%s'\nKnown algorithms:", + show_data_hash); + for (i = 0; (n = av_hash_names(i)); i++) + av_log(NULL, AV_LOG_ERROR, " %s", n); + av_log(NULL, AV_LOG_ERROR, "\n"); + } + goto end; + } + } + + w = writer_get_by_name(w_name); + if (!w) { + av_log(NULL, AV_LOG_ERROR, "Unknown output format with name '%s'\n", w_name); + ret = AVERROR(EINVAL); + goto end; + } + + if ((ret = writer_open(&wctx, w, w_args, + sections, FF_ARRAY_ELEMS(sections))) >= 0) { + if (w == &xml_writer) + wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES; + + writer_print_section_header(wctx, SECTION_ID_ROOT); + + if (do_show_program_version) + ffprobe_show_program_version(wctx); + if (do_show_library_versions) + ffprobe_show_library_versions(wctx); + if (do_show_pixel_formats) + ffprobe_show_pixel_formats(wctx); + + if (!input_filename && + ((do_show_format || do_show_programs || do_show_streams || do_show_chapters || do_show_packets || do_show_error) || + (!do_show_program_version && !do_show_library_versions && !do_show_pixel_formats))) { + show_usage(); + av_log(NULL, AV_LOG_ERROR, "You have to specify one input file.\n"); + av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name); + ret = AVERROR(EINVAL); + } else if (input_filename) { + ret = probe_file(wctx, input_filename); + if (ret < 0 && do_show_error) + show_error(wctx, ret); + } + + writer_print_section_footer(wctx); + writer_close(&wctx); + } + +end: + av_freep(&print_format); + av_freep(&read_intervals); + av_hash_freep(&hash); + + uninit_opts(); + for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) + av_dict_free(&(sections[i].entries_to_show)); + + avformat_network_deinit(); + + return ret < 0; +} diff --git a/fftools/ffserver.c b/fftools/ffserver.c new file mode 100644 index 0000000000..d4885dfa0e --- /dev/null +++ b/fftools/ffserver.c @@ -0,0 +1,4026 @@ +/* + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * multiple format streaming server based on the FFmpeg libraries + */ + +#include "config.h" +#if !HAVE_CLOSESOCKET +#define closesocket close +#endif +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "libavformat/avformat.h" +/* FIXME: those are internal headers, ffserver _really_ shouldn't use them */ +#include "libavformat/rtpproto.h" +#include "libavformat/rtsp.h" +#include "libavformat/avio_internal.h" +#include "libavformat/internal.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/lfg.h" +#include "libavutil/dict.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mathematics.h" +#include "libavutil/random_seed.h" +#include "libavutil/rational.h" +#include "libavutil/parseutils.h" +#include "libavutil/opt.h" +#include "libavutil/time.h" + +#include <stdarg.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <fcntl.h> +#include <sys/ioctl.h> +#if HAVE_POLL_H +#include <poll.h> +#endif +#include <errno.h> +#include <time.h> +#include <sys/wait.h> +#include <signal.h> + +#include "cmdutils.h" +#include "ffserver_config.h" + +#define PATH_LENGTH 1024 + +const char program_name[] = "ffserver"; +const int program_birth_year = 2000; + +static const OptionDef options[]; + +enum HTTPState { + HTTPSTATE_WAIT_REQUEST, + HTTPSTATE_SEND_HEADER, + HTTPSTATE_SEND_DATA_HEADER, + HTTPSTATE_SEND_DATA, /* sending TCP or UDP data */ + HTTPSTATE_SEND_DATA_TRAILER, + HTTPSTATE_RECEIVE_DATA, + HTTPSTATE_WAIT_FEED, /* wait for data from the feed */ + HTTPSTATE_READY, + + RTSPSTATE_WAIT_REQUEST, + RTSPSTATE_SEND_REPLY, + RTSPSTATE_SEND_PACKET, +}; + +static const char * const http_state[] = { + "HTTP_WAIT_REQUEST", + "HTTP_SEND_HEADER", + + "SEND_DATA_HEADER", + "SEND_DATA", + "SEND_DATA_TRAILER", + "RECEIVE_DATA", + "WAIT_FEED", + "READY", + + "RTSP_WAIT_REQUEST", + "RTSP_SEND_REPLY", + "RTSP_SEND_PACKET", +}; + +#define IOBUFFER_INIT_SIZE 8192 + +/* timeouts are in ms */ +#define HTTP_REQUEST_TIMEOUT (15 * 1000) +#define RTSP_REQUEST_TIMEOUT (3600 * 24 * 1000) + +#define SYNC_TIMEOUT (10 * 1000) + +typedef struct RTSPActionServerSetup { + uint32_t ipaddr; + char transport_option[512]; +} RTSPActionServerSetup; + +typedef struct { + int64_t count1, count2; + int64_t time1, time2; +} DataRateData; + +/* context associated with one connection */ +typedef struct HTTPContext { + enum HTTPState state; + int fd; /* socket file descriptor */ + struct sockaddr_in from_addr; /* origin */ + struct pollfd *poll_entry; /* used when polling */ + int64_t timeout; + uint8_t *buffer_ptr, *buffer_end; + int http_error; + int post; + int chunked_encoding; + int chunk_size; /* 0 if it needs to be read */ + struct HTTPContext *next; + int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */ + int64_t data_count; + /* feed input */ + int feed_fd; + /* input format handling */ + AVFormatContext *fmt_in; + int64_t start_time; /* In milliseconds - this wraps fairly often */ + int64_t first_pts; /* initial pts value */ + int64_t cur_pts; /* current pts value from the stream in us */ + int64_t cur_frame_duration; /* duration of the current frame in us */ + int cur_frame_bytes; /* output frame size, needed to compute + the time at which we send each + packet */ + int pts_stream_index; /* stream we choose as clock reference */ + int64_t cur_clock; /* current clock reference value in us */ + /* output format handling */ + struct FFServerStream *stream; + /* -1 is invalid stream */ + int feed_streams[FFSERVER_MAX_STREAMS]; /* index of streams in the feed */ + int switch_feed_streams[FFSERVER_MAX_STREAMS]; /* index of streams in the feed */ + int switch_pending; + AVFormatContext *pfmt_ctx; /* instance of FFServerStream for one user */ + int last_packet_sent; /* true if last data packet was sent */ + int suppress_log; + DataRateData datarate; + int wmp_client_id; + char protocol[16]; + char method[16]; + char url[128]; + char clean_url[128*7]; + int buffer_size; + uint8_t *buffer; + int is_packetized; /* if true, the stream is packetized */ + int packet_stream_index; /* current stream for output in state machine */ + + /* RTSP state specific */ + uint8_t *pb_buffer; /* XXX: use that in all the code */ + AVIOContext *pb; + int seq; /* RTSP sequence number */ + + /* RTP state specific */ + enum RTSPLowerTransport rtp_protocol; + char session_id[32]; /* session id */ + AVFormatContext *rtp_ctx[FFSERVER_MAX_STREAMS]; + + /* RTP/UDP specific */ + URLContext *rtp_handles[FFSERVER_MAX_STREAMS]; + + /* RTP/TCP specific */ + struct HTTPContext *rtsp_c; + uint8_t *packet_buffer, *packet_buffer_ptr, *packet_buffer_end; +} HTTPContext; + +static HTTPContext *first_http_ctx; + +static FFServerConfig config = { + .nb_max_http_connections = 2000, + .nb_max_connections = 5, + .max_bandwidth = 1000, + .use_defaults = 1, +}; + +static void new_connection(int server_fd, int is_rtsp); +static void close_connection(HTTPContext *c); + +/* HTTP handling */ +static int handle_connection(HTTPContext *c); +static inline void print_stream_params(AVIOContext *pb, FFServerStream *stream); +static void compute_status(HTTPContext *c); +static int open_input_stream(HTTPContext *c, const char *info); +static int http_parse_request(HTTPContext *c); +static int http_send_data(HTTPContext *c); +static int http_start_receive_data(HTTPContext *c); +static int http_receive_data(HTTPContext *c); + +/* RTSP handling */ +static int rtsp_parse_request(HTTPContext *c); +static void rtsp_cmd_describe(HTTPContext *c, const char *url); +static void rtsp_cmd_options(HTTPContext *c, const char *url); +static void rtsp_cmd_setup(HTTPContext *c, const char *url, + RTSPMessageHeader *h); +static void rtsp_cmd_play(HTTPContext *c, const char *url, + RTSPMessageHeader *h); +static void rtsp_cmd_interrupt(HTTPContext *c, const char *url, + RTSPMessageHeader *h, int pause_only); + +/* SDP handling */ +static int prepare_sdp_description(FFServerStream *stream, uint8_t **pbuffer, + struct in_addr my_ip); + +/* RTP handling */ +static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, + FFServerStream *stream, + const char *session_id, + enum RTSPLowerTransport rtp_protocol); +static int rtp_new_av_stream(HTTPContext *c, + int stream_index, struct sockaddr_in *dest_addr, + HTTPContext *rtsp_c); +/* utils */ +static size_t htmlencode (const char *src, char **dest); +static inline void cp_html_entity (char *buffer, const char *entity); +static inline int check_codec_match(LayeredAVStream *ccf, AVStream *ccs, int stream); + +static const char *my_program_name; + +static int no_launch; +static int need_to_start_children; + +/* maximum number of simultaneous HTTP connections */ +static unsigned int nb_connections; + +static uint64_t current_bandwidth; + +/* Making this global saves on passing it around everywhere */ +static int64_t cur_time; + +static AVLFG random_state; + +static FILE *logfile = NULL; + +static void unlayer_stream(AVStream *st, LayeredAVStream *lst) +{ + avcodec_free_context(&st->codec); + avcodec_parameters_free(&st->codecpar); +#define COPY(a) st->a = lst->a; + COPY(index) + COPY(id) + COPY(codec) + COPY(codecpar) + COPY(time_base) + COPY(pts_wrap_bits) + COPY(sample_aspect_ratio) + COPY(recommended_encoder_configuration) +} + +static inline void cp_html_entity (char *buffer, const char *entity) { + if (!buffer || !entity) + return; + while (*entity) + *buffer++ = *entity++; +} + +/** + * Substitutes known conflicting chars on a text string with + * their corresponding HTML entities. + * + * Returns the number of bytes in the 'encoded' representation + * not including the terminating NUL. + */ +static size_t htmlencode (const char *src, char **dest) { + const char *amp = "&"; + const char *lt = "<"; + const char *gt = ">"; + const char *start; + char *tmp; + size_t final_size = 0; + + if (!src) + return 0; + + start = src; + + /* Compute needed dest size */ + while (*src != '\0') { + switch(*src) { + case 38: /* & */ + final_size += 5; + break; + case 60: /* < */ + case 62: /* > */ + final_size += 4; + break; + default: + final_size++; + } + src++; + } + + src = start; + *dest = av_mallocz(final_size + 1); + if (!*dest) + return 0; + + /* Build dest */ + tmp = *dest; + while (*src != '\0') { + switch(*src) { + case 38: /* & */ + cp_html_entity (tmp, amp); + tmp += 5; + break; + case 60: /* < */ + cp_html_entity (tmp, lt); + tmp += 4; + break; + case 62: /* > */ + cp_html_entity (tmp, gt); + tmp += 4; + break; + default: + *tmp = *src; + tmp += 1; + } + src++; + } + *tmp = '\0'; + + return final_size; +} + +static int64_t ffm_read_write_index(int fd) +{ + uint8_t buf[8]; + + if (lseek(fd, 8, SEEK_SET) < 0) + return AVERROR(EIO); + if (read(fd, buf, 8) != 8) + return AVERROR(EIO); + return AV_RB64(buf); +} + +static int ffm_write_write_index(int fd, int64_t pos) +{ + uint8_t buf[8]; + int i; + + for(i=0;i<8;i++) + buf[i] = (pos >> (56 - i * 8)) & 0xff; + if (lseek(fd, 8, SEEK_SET) < 0) + goto bail_eio; + if (write(fd, buf, 8) != 8) + goto bail_eio; + + return 8; + +bail_eio: + return AVERROR(EIO); +} + +static void ffm_set_write_index(AVFormatContext *s, int64_t pos, + int64_t file_size) +{ + av_opt_set_int(s, "server_attached", 1, AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(s, "ffm_write_index", pos, AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(s, "ffm_file_size", file_size, AV_OPT_SEARCH_CHILDREN); +} + +static char *ctime1(char *buf2, size_t buf_size) +{ + time_t ti; + char *p; + + ti = time(NULL); + p = ctime(&ti); + if (!p || !*p) { + *buf2 = '\0'; + return buf2; + } + av_strlcpy(buf2, p, buf_size); + p = buf2 + strlen(buf2) - 1; + if (*p == '\n') + *p = '\0'; + return buf2; +} + +static void http_vlog(const char *fmt, va_list vargs) +{ + static int print_prefix = 1; + char buf[32]; + + if (!logfile) + return; + + if (print_prefix) { + ctime1(buf, sizeof(buf)); + fprintf(logfile, "%s ", buf); + } + print_prefix = strstr(fmt, "\n") != NULL; + vfprintf(logfile, fmt, vargs); + fflush(logfile); +} + +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +static void http_log(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + http_vlog(fmt, vargs); + va_end(vargs); +} + +static void http_av_log(void *ptr, int level, const char *fmt, va_list vargs) +{ + static int print_prefix = 1; + AVClass *avc = ptr ? *(AVClass**)ptr : NULL; + if (level > av_log_get_level()) + return; + if (print_prefix && avc) + http_log("[%s @ %p]", avc->item_name(ptr), ptr); + print_prefix = strstr(fmt, "\n") != NULL; + http_vlog(fmt, vargs); +} + +static void log_connection(HTTPContext *c) +{ + if (c->suppress_log) + return; + + http_log("%s - - [%s] \"%s %s\" %d %"PRId64"\n", + inet_ntoa(c->from_addr.sin_addr), c->method, c->url, + c->protocol, (c->http_error ? c->http_error : 200), c->data_count); +} + +static void update_datarate(DataRateData *drd, int64_t count) +{ + if (!drd->time1 && !drd->count1) { + drd->time1 = drd->time2 = cur_time; + drd->count1 = drd->count2 = count; + } else if (cur_time - drd->time2 > 5000) { + drd->time1 = drd->time2; + drd->count1 = drd->count2; + drd->time2 = cur_time; + drd->count2 = count; + } +} + +/* In bytes per second */ +static int compute_datarate(DataRateData *drd, int64_t count) +{ + if (cur_time == drd->time1) + return 0; + + return ((count - drd->count1) * 1000) / (cur_time - drd->time1); +} + + +static void start_children(FFServerStream *feed) +{ + char *pathname; + char *slash; + int i; + size_t cmd_length; + + if (no_launch) + return; + + cmd_length = strlen(my_program_name); + + /** + * FIXME: WIP Safeguard. Remove after clearing all harcoded + * '1024' path lengths + */ + if (cmd_length > PATH_LENGTH - 1) { + http_log("Could not start children. Command line: '%s' exceeds " + "path length limit (%d)\n", my_program_name, PATH_LENGTH); + return; + } + + slash = strrchr(my_program_name, '/'); + if (!slash) { + pathname = av_mallocz(sizeof("ffmpeg")); + } else { + pathname = av_mallocz(slash - my_program_name + sizeof("ffmpeg")); + if (pathname != NULL) { + memcpy(pathname, my_program_name, slash - my_program_name); + } + } + if (!pathname) { + http_log("Could not allocate memory for children cmd line\n"); + return; + } + /* use "ffmpeg" in the path of current program. Ignore user provided path */ + + strcat(pathname, "ffmpeg"); + + for (; feed; feed = feed->next) { + + if (!feed->child_argv || feed->pid) + continue; + + feed->pid_start = time(0); + + feed->pid = fork(); + if (feed->pid < 0) { + http_log("Unable to create children: %s\n", strerror(errno)); + av_free (pathname); + exit(EXIT_FAILURE); + } + + if (feed->pid) + continue; + + /* In child */ + + http_log("Launch command line: "); + http_log("%s ", pathname); + + for (i = 1; feed->child_argv[i] && feed->child_argv[i][0]; i++) + http_log("%s ", feed->child_argv[i]); + http_log("\n"); + + for (i = 3; i < 256; i++) + close(i); + + if (!config.debug) { + if (!freopen("/dev/null", "r", stdin)) + http_log("failed to redirect STDIN to /dev/null\n;"); + if (!freopen("/dev/null", "w", stdout)) + http_log("failed to redirect STDOUT to /dev/null\n;"); + if (!freopen("/dev/null", "w", stderr)) + http_log("failed to redirect STDERR to /dev/null\n;"); + } + + signal(SIGPIPE, SIG_DFL); + execvp(pathname, feed->child_argv); + av_free (pathname); + _exit(1); + } + av_free (pathname); +} + +/* open a listening socket */ +static int socket_open_listen(struct sockaddr_in *my_addr) +{ + int server_fd, tmp; + + server_fd = socket(AF_INET,SOCK_STREAM,0); + if (server_fd < 0) { + perror ("socket"); + return -1; + } + + tmp = 1; + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp))) + av_log(NULL, AV_LOG_WARNING, "setsockopt SO_REUSEADDR failed\n"); + + my_addr->sin_family = AF_INET; + if (bind (server_fd, (struct sockaddr *) my_addr, sizeof (*my_addr)) < 0) { + char bindmsg[32]; + snprintf(bindmsg, sizeof(bindmsg), "bind(port %d)", + ntohs(my_addr->sin_port)); + perror (bindmsg); + goto fail; + } + + if (listen (server_fd, 5) < 0) { + perror ("listen"); + goto fail; + } + + if (ff_socket_nonblock(server_fd, 1) < 0) + av_log(NULL, AV_LOG_WARNING, "ff_socket_nonblock failed\n"); + + return server_fd; + +fail: + closesocket(server_fd); + return -1; +} + +/* start all multicast streams */ +static void start_multicast(void) +{ + FFServerStream *stream; + char session_id[32]; + HTTPContext *rtp_c; + struct sockaddr_in dest_addr = {0}; + int default_port, stream_index; + unsigned int random0, random1; + + default_port = 6000; + for(stream = config.first_stream; stream; stream = stream->next) { + + if (!stream->is_multicast) + continue; + + random0 = av_lfg_get(&random_state); + random1 = av_lfg_get(&random_state); + + /* open the RTP connection */ + snprintf(session_id, sizeof(session_id), "%08x%08x", random0, random1); + + /* choose a port if none given */ + if (stream->multicast_port == 0) { + stream->multicast_port = default_port; + default_port += 100; + } + + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr = stream->multicast_ip; + dest_addr.sin_port = htons(stream->multicast_port); + + rtp_c = rtp_new_connection(&dest_addr, stream, session_id, + RTSP_LOWER_TRANSPORT_UDP_MULTICAST); + if (!rtp_c) + continue; + + if (open_input_stream(rtp_c, "") < 0) { + http_log("Could not open input stream for stream '%s'\n", + stream->filename); + continue; + } + + /* open each RTP stream */ + for(stream_index = 0; stream_index < stream->nb_streams; + stream_index++) { + dest_addr.sin_port = htons(stream->multicast_port + + 2 * stream_index); + if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, NULL) >= 0) + continue; + + http_log("Could not open output stream '%s/streamid=%d'\n", + stream->filename, stream_index); + exit(1); + } + + rtp_c->state = HTTPSTATE_SEND_DATA; + } +} + +/* main loop of the HTTP server */ +static int http_server(void) +{ + int server_fd = 0, rtsp_server_fd = 0; + int ret, delay; + struct pollfd *poll_table, *poll_entry; + HTTPContext *c, *c_next; + + poll_table = av_mallocz_array(config.nb_max_http_connections + 2, + sizeof(*poll_table)); + if(!poll_table) { + http_log("Impossible to allocate a poll table handling %d " + "connections.\n", config.nb_max_http_connections); + return -1; + } + + if (config.http_addr.sin_port) { + server_fd = socket_open_listen(&config.http_addr); + if (server_fd < 0) + goto quit; + } + + if (config.rtsp_addr.sin_port) { + rtsp_server_fd = socket_open_listen(&config.rtsp_addr); + if (rtsp_server_fd < 0) { + closesocket(server_fd); + goto quit; + } + } + + if (!rtsp_server_fd && !server_fd) { + http_log("HTTP and RTSP disabled.\n"); + goto quit; + } + + http_log("FFserver started.\n"); + + start_children(config.first_feed); + + start_multicast(); + + for(;;) { + poll_entry = poll_table; + if (server_fd) { + poll_entry->fd = server_fd; + poll_entry->events = POLLIN; + poll_entry++; + } + if (rtsp_server_fd) { + poll_entry->fd = rtsp_server_fd; + poll_entry->events = POLLIN; + poll_entry++; + } + + /* wait for events on each HTTP handle */ + c = first_http_ctx; + delay = 1000; + while (c) { + int fd; + fd = c->fd; + switch(c->state) { + case HTTPSTATE_SEND_HEADER: + case RTSPSTATE_SEND_REPLY: + case RTSPSTATE_SEND_PACKET: + c->poll_entry = poll_entry; + poll_entry->fd = fd; + poll_entry->events = POLLOUT; + poll_entry++; + break; + case HTTPSTATE_SEND_DATA_HEADER: + case HTTPSTATE_SEND_DATA: + case HTTPSTATE_SEND_DATA_TRAILER: + if (!c->is_packetized) { + /* for TCP, we output as much as we can + * (may need to put a limit) */ + c->poll_entry = poll_entry; + poll_entry->fd = fd; + poll_entry->events = POLLOUT; + poll_entry++; + } else { + /* when ffserver is doing the timing, we work by + * looking at which packet needs to be sent every + * 10 ms (one tick wait XXX: 10 ms assumed) */ + if (delay > 10) + delay = 10; + } + break; + case HTTPSTATE_WAIT_REQUEST: + case HTTPSTATE_RECEIVE_DATA: + case HTTPSTATE_WAIT_FEED: + case RTSPSTATE_WAIT_REQUEST: + /* need to catch errors */ + c->poll_entry = poll_entry; + poll_entry->fd = fd; + poll_entry->events = POLLIN;/* Maybe this will work */ + poll_entry++; + break; + default: + c->poll_entry = NULL; + break; + } + c = c->next; + } + + /* wait for an event on one connection. We poll at least every + * second to handle timeouts */ + do { + ret = poll(poll_table, poll_entry - poll_table, delay); + if (ret < 0 && ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) { + goto quit; + } + } while (ret < 0); + + cur_time = av_gettime() / 1000; + + if (need_to_start_children) { + need_to_start_children = 0; + start_children(config.first_feed); + } + + /* now handle the events */ + for(c = first_http_ctx; c; c = c_next) { + c_next = c->next; + if (handle_connection(c) < 0) { + log_connection(c); + /* close and free the connection */ + close_connection(c); + } + } + + poll_entry = poll_table; + if (server_fd) { + /* new HTTP connection request ? */ + if (poll_entry->revents & POLLIN) + new_connection(server_fd, 0); + poll_entry++; + } + if (rtsp_server_fd) { + /* new RTSP connection request ? */ + if (poll_entry->revents & POLLIN) + new_connection(rtsp_server_fd, 1); + } + } + +quit: + av_free(poll_table); + return -1; +} + +/* start waiting for a new HTTP/RTSP request */ +static void start_wait_request(HTTPContext *c, int is_rtsp) +{ + c->buffer_ptr = c->buffer; + c->buffer_end = c->buffer + c->buffer_size - 1; /* leave room for '\0' */ + + c->state = is_rtsp ? RTSPSTATE_WAIT_REQUEST : HTTPSTATE_WAIT_REQUEST; + c->timeout = cur_time + + (is_rtsp ? RTSP_REQUEST_TIMEOUT : HTTP_REQUEST_TIMEOUT); +} + +static void http_send_too_busy_reply(int fd) +{ + char buffer[400]; + int len = snprintf(buffer, sizeof(buffer), + "HTTP/1.0 503 Server too busy\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<!DOCTYPE html>\n" + "<html><head><title>Too busy</title></head><body>\r\n" + "<p>The server is too busy to serve your request at " + "this time.</p>\r\n" + "<p>The number of current connections is %u, and this " + "exceeds the limit of %u.</p>\r\n" + "</body></html>\r\n", + nb_connections, config.nb_max_connections); + av_assert0(len < sizeof(buffer)); + if (send(fd, buffer, len, 0) < len) + av_log(NULL, AV_LOG_WARNING, + "Could not send too-busy reply, send() failed\n"); +} + + +static void new_connection(int server_fd, int is_rtsp) +{ + struct sockaddr_in from_addr; + socklen_t len; + int fd; + HTTPContext *c = NULL; + + len = sizeof(from_addr); + fd = accept(server_fd, (struct sockaddr *)&from_addr, + &len); + if (fd < 0) { + http_log("error during accept %s\n", strerror(errno)); + return; + } + if (ff_socket_nonblock(fd, 1) < 0) + av_log(NULL, AV_LOG_WARNING, "ff_socket_nonblock failed\n"); + + if (nb_connections >= config.nb_max_connections) { + http_send_too_busy_reply(fd); + goto fail; + } + + /* add a new connection */ + c = av_mallocz(sizeof(HTTPContext)); + if (!c) + goto fail; + + c->fd = fd; + c->poll_entry = NULL; + c->from_addr = from_addr; + c->buffer_size = IOBUFFER_INIT_SIZE; + c->buffer = av_malloc(c->buffer_size); + if (!c->buffer) + goto fail; + + c->next = first_http_ctx; + first_http_ctx = c; + nb_connections++; + + start_wait_request(c, is_rtsp); + + return; + + fail: + if (c) { + av_freep(&c->buffer); + av_free(c); + } + closesocket(fd); +} + +static void close_connection(HTTPContext *c) +{ + HTTPContext **cp, *c1; + int i, nb_streams; + AVFormatContext *ctx; + AVStream *st; + + /* remove connection from list */ + cp = &first_http_ctx; + while (*cp) { + c1 = *cp; + if (c1 == c) + *cp = c->next; + else + cp = &c1->next; + } + + /* remove references, if any (XXX: do it faster) */ + for(c1 = first_http_ctx; c1; c1 = c1->next) { + if (c1->rtsp_c == c) + c1->rtsp_c = NULL; + } + + /* remove connection associated resources */ + if (c->fd >= 0) + closesocket(c->fd); + if (c->fmt_in) { + /* close each frame parser */ + for(i=0;i<c->fmt_in->nb_streams;i++) { + st = c->fmt_in->streams[i]; + if (st->codec->codec) + avcodec_close(st->codec); + } + avformat_close_input(&c->fmt_in); + } + + /* free RTP output streams if any */ + nb_streams = 0; + if (c->stream) + nb_streams = c->stream->nb_streams; + + for(i=0;i<nb_streams;i++) { + ctx = c->rtp_ctx[i]; + if (ctx) { + av_write_trailer(ctx); + av_dict_free(&ctx->metadata); + av_freep(&ctx->streams[0]); + av_freep(&ctx); + } + ffurl_close(c->rtp_handles[i]); + } + + ctx = c->pfmt_ctx; + + if (ctx) { + if (!c->last_packet_sent && c->state == HTTPSTATE_SEND_DATA_TRAILER) { + /* prepare header */ + if (ctx->oformat && avio_open_dyn_buf(&ctx->pb) >= 0) { + av_write_trailer(ctx); + av_freep(&c->pb_buffer); + avio_close_dyn_buf(ctx->pb, &c->pb_buffer); + } + } + for(i=0; i<ctx->nb_streams; i++) + av_freep(&ctx->streams[i]); + av_freep(&ctx->streams); + av_freep(&ctx->priv_data); + } + + if (c->stream && !c->post && c->stream->stream_type == STREAM_TYPE_LIVE) + current_bandwidth -= c->stream->bandwidth; + + /* signal that there is no feed if we are the feeder socket */ + if (c->state == HTTPSTATE_RECEIVE_DATA && c->stream) { + c->stream->feed_opened = 0; + close(c->feed_fd); + } + + av_freep(&c->pb_buffer); + av_freep(&c->packet_buffer); + av_freep(&c->buffer); + av_free(c); + nb_connections--; +} + +static int handle_connection(HTTPContext *c) +{ + int len, ret; + uint8_t *ptr; + + switch(c->state) { + case HTTPSTATE_WAIT_REQUEST: + case RTSPSTATE_WAIT_REQUEST: + /* timeout ? */ + if ((c->timeout - cur_time) < 0) + return -1; + if (c->poll_entry->revents & (POLLERR | POLLHUP)) + return -1; + + /* no need to read if no events */ + if (!(c->poll_entry->revents & POLLIN)) + return 0; + /* read the data */ + read_loop: + if (!(len = recv(c->fd, c->buffer_ptr, 1, 0))) + return -1; + + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) + return -1; + break; + } + /* search for end of request. */ + c->buffer_ptr += len; + ptr = c->buffer_ptr; + if ((ptr >= c->buffer + 2 && !memcmp(ptr-2, "\n\n", 2)) || + (ptr >= c->buffer + 4 && !memcmp(ptr-4, "\r\n\r\n", 4))) { + /* request found : parse it and reply */ + if (c->state == HTTPSTATE_WAIT_REQUEST) + ret = http_parse_request(c); + else + ret = rtsp_parse_request(c); + + if (ret < 0) + return -1; + } else if (ptr >= c->buffer_end) { + /* request too long: cannot do anything */ + return -1; + } else goto read_loop; + + break; + + case HTTPSTATE_SEND_HEADER: + if (c->poll_entry->revents & (POLLERR | POLLHUP)) + return -1; + + /* no need to write if no events */ + if (!(c->poll_entry->revents & POLLOUT)) + return 0; + len = send(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) { + goto close_connection; + } + break; + } + c->buffer_ptr += len; + if (c->stream) + c->stream->bytes_served += len; + c->data_count += len; + if (c->buffer_ptr >= c->buffer_end) { + av_freep(&c->pb_buffer); + /* if error, exit */ + if (c->http_error) + return -1; + /* all the buffer was sent : synchronize to the incoming + * stream */ + c->state = HTTPSTATE_SEND_DATA_HEADER; + c->buffer_ptr = c->buffer_end = c->buffer; + } + break; + + case HTTPSTATE_SEND_DATA: + case HTTPSTATE_SEND_DATA_HEADER: + case HTTPSTATE_SEND_DATA_TRAILER: + /* for packetized output, we consider we can always write (the + * input streams set the speed). It may be better to verify + * that we do not rely too much on the kernel queues */ + if (!c->is_packetized) { + if (c->poll_entry->revents & (POLLERR | POLLHUP)) + return -1; + + /* no need to read if no events */ + if (!(c->poll_entry->revents & POLLOUT)) + return 0; + } + if (http_send_data(c) < 0) + return -1; + /* close connection if trailer sent */ + if (c->state == HTTPSTATE_SEND_DATA_TRAILER) + return -1; + /* Check if it is a single jpeg frame 123 */ + if (c->stream->single_frame && c->data_count > c->cur_frame_bytes && c->cur_frame_bytes > 0) { + close_connection(c); + } + break; + case HTTPSTATE_RECEIVE_DATA: + /* no need to read if no events */ + if (c->poll_entry->revents & (POLLERR | POLLHUP)) + return -1; + if (!(c->poll_entry->revents & POLLIN)) + return 0; + if (http_receive_data(c) < 0) + return -1; + break; + case HTTPSTATE_WAIT_FEED: + /* no need to read if no events */ + if (c->poll_entry->revents & (POLLIN | POLLERR | POLLHUP)) + return -1; + + /* nothing to do, we'll be waken up by incoming feed packets */ + break; + + case RTSPSTATE_SEND_REPLY: + if (c->poll_entry->revents & (POLLERR | POLLHUP)) + goto close_connection; + /* no need to write if no events */ + if (!(c->poll_entry->revents & POLLOUT)) + return 0; + len = send(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) { + goto close_connection; + } + break; + } + c->buffer_ptr += len; + c->data_count += len; + if (c->buffer_ptr >= c->buffer_end) { + /* all the buffer was sent : wait for a new request */ + av_freep(&c->pb_buffer); + start_wait_request(c, 1); + } + break; + case RTSPSTATE_SEND_PACKET: + if (c->poll_entry->revents & (POLLERR | POLLHUP)) { + av_freep(&c->packet_buffer); + return -1; + } + /* no need to write if no events */ + if (!(c->poll_entry->revents & POLLOUT)) + return 0; + len = send(c->fd, c->packet_buffer_ptr, + c->packet_buffer_end - c->packet_buffer_ptr, 0); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) { + /* error : close connection */ + av_freep(&c->packet_buffer); + return -1; + } + break; + } + c->packet_buffer_ptr += len; + if (c->packet_buffer_ptr >= c->packet_buffer_end) { + /* all the buffer was sent : wait for a new request */ + av_freep(&c->packet_buffer); + c->state = RTSPSTATE_WAIT_REQUEST; + } + break; + case HTTPSTATE_READY: + /* nothing to do */ + break; + default: + return -1; + } + return 0; + +close_connection: + av_freep(&c->pb_buffer); + return -1; +} + +static int extract_rates(char *rates, int ratelen, const char *request) +{ + const char *p; + + for (p = request; *p && *p != '\r' && *p != '\n'; ) { + if (av_strncasecmp(p, "Pragma:", 7) == 0) { + const char *q = p + 7; + + while (*q && *q != '\n' && av_isspace(*q)) + q++; + + if (av_strncasecmp(q, "stream-switch-entry=", 20) == 0) { + int stream_no; + int rate_no; + + q += 20; + + memset(rates, 0xff, ratelen); + + while (1) { + while (*q && *q != '\n' && *q != ':') + q++; + + if (sscanf(q, ":%d:%d", &stream_no, &rate_no) != 2) + break; + + stream_no--; + if (stream_no < ratelen && stream_no >= 0) + rates[stream_no] = rate_no; + + while (*q && *q != '\n' && !av_isspace(*q)) + q++; + } + + return 1; + } + } + p = strchr(p, '\n'); + if (!p) + break; + + p++; + } + + return 0; +} + +static int find_stream_in_feed(FFServerStream *feed, AVCodecParameters *codec, + int bit_rate) +{ + int i; + int best_bitrate = 100000000; + int best = -1; + + for (i = 0; i < feed->nb_streams; i++) { + AVCodecParameters *feed_codec = feed->streams[i]->codecpar; + + if (feed_codec->codec_id != codec->codec_id || + feed_codec->sample_rate != codec->sample_rate || + feed_codec->width != codec->width || + feed_codec->height != codec->height) + continue; + + /* Potential stream */ + + /* We want the fastest stream less than bit_rate, or the slowest + * faster than bit_rate + */ + + if (feed_codec->bit_rate <= bit_rate) { + if (best_bitrate > bit_rate || + feed_codec->bit_rate > best_bitrate) { + best_bitrate = feed_codec->bit_rate; + best = i; + } + continue; + } + if (feed_codec->bit_rate < best_bitrate) { + best_bitrate = feed_codec->bit_rate; + best = i; + } + } + return best; +} + +static int modify_current_stream(HTTPContext *c, char *rates) +{ + int i; + FFServerStream *req = c->stream; + int action_required = 0; + + /* Not much we can do for a feed */ + if (!req->feed) + return 0; + + for (i = 0; i < req->nb_streams; i++) { + AVCodecParameters *codec = req->streams[i]->codecpar; + + switch(rates[i]) { + case 0: + c->switch_feed_streams[i] = req->feed_streams[i]; + break; + case 1: + c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 2); + break; + case 2: + /* Wants off or slow */ + c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 4); +#ifdef WANTS_OFF + /* This doesn't work well when it turns off the only stream! */ + c->switch_feed_streams[i] = -2; + c->feed_streams[i] = -2; +#endif + break; + } + + if (c->switch_feed_streams[i] >= 0 && + c->switch_feed_streams[i] != c->feed_streams[i]) { + action_required = 1; + } + } + + return action_required; +} + +static void get_word(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + +#define SPACE_CHARS " \t\r\n" + + p = *pp; + p += strspn(p, SPACE_CHARS); + q = buf; + while (!av_isspace(*p) && *p != '\0') { + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + if (buf_size > 0) + *q = '\0'; + *pp = p; +} + +static FFServerIPAddressACL* parse_dynamic_acl(FFServerStream *stream, + HTTPContext *c) +{ + FILE* f; + char line[1024]; + char cmd[1024]; + FFServerIPAddressACL *acl = NULL; + int line_num = 0; + const char *p; + + f = fopen(stream->dynamic_acl, "r"); + if (!f) { + perror(stream->dynamic_acl); + return NULL; + } + + acl = av_mallocz(sizeof(FFServerIPAddressACL)); + if (!acl) { + fclose(f); + return NULL; + } + + /* Build ACL */ + while (fgets(line, sizeof(line), f)) { + line_num++; + p = line; + while (av_isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + continue; + ffserver_get_arg(cmd, sizeof(cmd), &p); + + if (!av_strcasecmp(cmd, "ACL")) + ffserver_parse_acl_row(NULL, NULL, acl, p, stream->dynamic_acl, + line_num); + } + fclose(f); + return acl; +} + + +static void free_acl_list(FFServerIPAddressACL *in_acl) +{ + FFServerIPAddressACL *pacl, *pacl2; + + pacl = in_acl; + while(pacl) { + pacl2 = pacl; + pacl = pacl->next; + av_freep(pacl2); + } +} + +static int validate_acl_list(FFServerIPAddressACL *in_acl, HTTPContext *c) +{ + enum FFServerIPAddressAction last_action = IP_DENY; + FFServerIPAddressACL *acl; + struct in_addr *src = &c->from_addr.sin_addr; + unsigned long src_addr = src->s_addr; + + for (acl = in_acl; acl; acl = acl->next) { + if (src_addr >= acl->first.s_addr && src_addr <= acl->last.s_addr) + return (acl->action == IP_ALLOW) ? 1 : 0; + last_action = acl->action; + } + + /* Nothing matched, so return not the last action */ + return (last_action == IP_DENY) ? 1 : 0; +} + +static int validate_acl(FFServerStream *stream, HTTPContext *c) +{ + int ret = 0; + FFServerIPAddressACL *acl; + + /* if stream->acl is null validate_acl_list will return 1 */ + ret = validate_acl_list(stream->acl, c); + + if (stream->dynamic_acl[0]) { + acl = parse_dynamic_acl(stream, c); + ret = validate_acl_list(acl, c); + free_acl_list(acl); + } + + return ret; +} + +/** + * compute the real filename of a file by matching it without its + * extensions to all the stream's filenames + */ +static void compute_real_filename(char *filename, int max_size) +{ + char file1[1024]; + char file2[1024]; + char *p; + FFServerStream *stream; + + av_strlcpy(file1, filename, sizeof(file1)); + p = strrchr(file1, '.'); + if (p) + *p = '\0'; + for(stream = config.first_stream; stream; stream = stream->next) { + av_strlcpy(file2, stream->filename, sizeof(file2)); + p = strrchr(file2, '.'); + if (p) + *p = '\0'; + if (!strcmp(file1, file2)) { + av_strlcpy(filename, stream->filename, max_size); + break; + } + } +} + +enum RedirType { + REDIR_NONE, + REDIR_ASX, + REDIR_RAM, + REDIR_ASF, + REDIR_RTSP, + REDIR_SDP, +}; + +/* parse HTTP request and prepare header */ +static int http_parse_request(HTTPContext *c) +{ + const char *p; + char *p1; + enum RedirType redir_type; + char cmd[32]; + char info[1024], filename[1024]; + char url[1024], *q; + char protocol[32]; + char msg[1024]; + char *encoded_msg = NULL; + const char *mime_type; + FFServerStream *stream; + int i; + char ratebuf[32]; + const char *useragent = 0; + + p = c->buffer; + get_word(cmd, sizeof(cmd), &p); + av_strlcpy(c->method, cmd, sizeof(c->method)); + + if (!strcmp(cmd, "GET")) + c->post = 0; + else if (!strcmp(cmd, "POST")) + c->post = 1; + else + return -1; + + get_word(url, sizeof(url), &p); + av_strlcpy(c->url, url, sizeof(c->url)); + + get_word(protocol, sizeof(protocol), (const char **)&p); + if (strcmp(protocol, "HTTP/1.0") && strcmp(protocol, "HTTP/1.1")) + return -1; + + av_strlcpy(c->protocol, protocol, sizeof(c->protocol)); + + if (config.debug) + http_log("%s - - New connection: %s %s\n", + inet_ntoa(c->from_addr.sin_addr), cmd, url); + + /* find the filename and the optional info string in the request */ + p1 = strchr(url, '?'); + if (p1) { + av_strlcpy(info, p1, sizeof(info)); + *p1 = '\0'; + } else + info[0] = '\0'; + + av_strlcpy(filename, url + ((*url == '/') ? 1 : 0), sizeof(filename)-1); + + for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) { + if (av_strncasecmp(p, "User-Agent:", 11) == 0) { + useragent = p + 11; + if (*useragent && *useragent != '\n' && av_isspace(*useragent)) + useragent++; + break; + } + p = strchr(p, '\n'); + if (!p) + break; + + p++; + } + + redir_type = REDIR_NONE; + if (av_match_ext(filename, "asx")) { + redir_type = REDIR_ASX; + filename[strlen(filename)-1] = 'f'; + } else if (av_match_ext(filename, "asf") && + (!useragent || av_strncasecmp(useragent, "NSPlayer", 8))) { + /* if this isn't WMP or lookalike, return the redirector file */ + redir_type = REDIR_ASF; + } else if (av_match_ext(filename, "rpm,ram")) { + redir_type = REDIR_RAM; + strcpy(filename + strlen(filename)-2, "m"); + } else if (av_match_ext(filename, "rtsp")) { + redir_type = REDIR_RTSP; + compute_real_filename(filename, sizeof(filename) - 1); + } else if (av_match_ext(filename, "sdp")) { + redir_type = REDIR_SDP; + compute_real_filename(filename, sizeof(filename) - 1); + } + + /* "redirect" request to index.html */ + if (!strlen(filename)) + av_strlcpy(filename, "index.html", sizeof(filename) - 1); + + stream = config.first_stream; + while (stream) { + if (!strcmp(stream->filename, filename) && validate_acl(stream, c)) + break; + stream = stream->next; + } + if (!stream) { + snprintf(msg, sizeof(msg), "File '%s' not found", url); + http_log("File '%s' not found\n", url); + goto send_error; + } + + c->stream = stream; + memcpy(c->feed_streams, stream->feed_streams, sizeof(c->feed_streams)); + memset(c->switch_feed_streams, -1, sizeof(c->switch_feed_streams)); + + if (stream->stream_type == STREAM_TYPE_REDIRECT) { + c->http_error = 301; + q = c->buffer; + snprintf(q, c->buffer_size, + "HTTP/1.0 301 Moved\r\n" + "Location: %s\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<!DOCTYPE html>\n" + "<html><head><title>Moved</title></head><body>\r\n" + "You should be <a href=\"%s\">redirected</a>.\r\n" + "</body></html>\r\n", + stream->feed_filename, stream->feed_filename); + q += strlen(q); + /* prepare output buffer */ + c->buffer_ptr = c->buffer; + c->buffer_end = q; + c->state = HTTPSTATE_SEND_HEADER; + return 0; + } + + /* If this is WMP, get the rate information */ + if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) { + if (modify_current_stream(c, ratebuf)) { + for (i = 0; i < FF_ARRAY_ELEMS(c->feed_streams); i++) { + if (c->switch_feed_streams[i] >= 0) + c->switch_feed_streams[i] = -1; + } + } + } + + if (c->post == 0 && stream->stream_type == STREAM_TYPE_LIVE) + current_bandwidth += stream->bandwidth; + + /* If already streaming this feed, do not let another feeder start */ + if (stream->feed_opened) { + snprintf(msg, sizeof(msg), "This feed is already being received."); + http_log("Feed '%s' already being received\n", stream->feed_filename); + goto send_error; + } + + if (c->post == 0 && config.max_bandwidth < current_bandwidth) { + c->http_error = 503; + q = c->buffer; + snprintf(q, c->buffer_size, + "HTTP/1.0 503 Server too busy\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<!DOCTYPE html>\n" + "<html><head><title>Too busy</title></head><body>\r\n" + "<p>The server is too busy to serve your request at " + "this time.</p>\r\n" + "<p>The bandwidth being served (including your stream) " + "is %"PRIu64"kbit/s, and this exceeds the limit of " + "%"PRIu64"kbit/s.</p>\r\n" + "</body></html>\r\n", + current_bandwidth, config.max_bandwidth); + q += strlen(q); + /* prepare output buffer */ + c->buffer_ptr = c->buffer; + c->buffer_end = q; + c->state = HTTPSTATE_SEND_HEADER; + return 0; + } + + if (redir_type != REDIR_NONE) { + const char *hostinfo = 0; + + for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) { + if (av_strncasecmp(p, "Host:", 5) == 0) { + hostinfo = p + 5; + break; + } + p = strchr(p, '\n'); + if (!p) + break; + + p++; + } + + if (hostinfo) { + char *eoh; + char hostbuf[260]; + + while (av_isspace(*hostinfo)) + hostinfo++; + + eoh = strchr(hostinfo, '\n'); + if (eoh) { + if (eoh[-1] == '\r') + eoh--; + + if (eoh - hostinfo < sizeof(hostbuf) - 1) { + memcpy(hostbuf, hostinfo, eoh - hostinfo); + hostbuf[eoh - hostinfo] = 0; + + c->http_error = 200; + q = c->buffer; + switch(redir_type) { + case REDIR_ASX: + snprintf(q, c->buffer_size, + "HTTP/1.0 200 ASX Follows\r\n" + "Content-type: video/x-ms-asf\r\n" + "\r\n" + "<ASX Version=\"3\">\r\n" + //"<!-- Autogenerated by ffserver -->\r\n" + "<ENTRY><REF HREF=\"http://%s/%s%s\"/></ENTRY>\r\n" + "</ASX>\r\n", hostbuf, filename, info); + q += strlen(q); + break; + case REDIR_RAM: + snprintf(q, c->buffer_size, + "HTTP/1.0 200 RAM Follows\r\n" + "Content-type: audio/x-pn-realaudio\r\n" + "\r\n" + "# Autogenerated by ffserver\r\n" + "http://%s/%s%s\r\n", hostbuf, filename, info); + q += strlen(q); + break; + case REDIR_ASF: + snprintf(q, c->buffer_size, + "HTTP/1.0 200 ASF Redirect follows\r\n" + "Content-type: video/x-ms-asf\r\n" + "\r\n" + "[Reference]\r\n" + "Ref1=http://%s/%s%s\r\n", hostbuf, filename, info); + q += strlen(q); + break; + case REDIR_RTSP: + { + char hostname[256], *p; + /* extract only hostname */ + av_strlcpy(hostname, hostbuf, sizeof(hostname)); + p = strrchr(hostname, ':'); + if (p) + *p = '\0'; + snprintf(q, c->buffer_size, + "HTTP/1.0 200 RTSP Redirect follows\r\n" + /* XXX: incorrect MIME type ? */ + "Content-type: application/x-rtsp\r\n" + "\r\n" + "rtsp://%s:%d/%s\r\n", hostname, ntohs(config.rtsp_addr.sin_port), filename); + q += strlen(q); + } + break; + case REDIR_SDP: + { + uint8_t *sdp_data; + int sdp_data_size; + socklen_t len; + struct sockaddr_in my_addr; + + snprintf(q, c->buffer_size, + "HTTP/1.0 200 OK\r\n" + "Content-type: application/sdp\r\n" + "\r\n"); + q += strlen(q); + + len = sizeof(my_addr); + + /* XXX: Should probably fail? */ + if (getsockname(c->fd, (struct sockaddr *)&my_addr, &len)) + http_log("getsockname() failed\n"); + + /* XXX: should use a dynamic buffer */ + sdp_data_size = prepare_sdp_description(stream, + &sdp_data, + my_addr.sin_addr); + if (sdp_data_size > 0) { + memcpy(q, sdp_data, sdp_data_size); + q += sdp_data_size; + *q = '\0'; + av_freep(&sdp_data); + } + } + break; + default: + abort(); + break; + } + + /* prepare output buffer */ + c->buffer_ptr = c->buffer; + c->buffer_end = q; + c->state = HTTPSTATE_SEND_HEADER; + return 0; + } + } + } + + snprintf(msg, sizeof(msg), "ASX/RAM file not handled"); + goto send_error; + } + + stream->conns_served++; + + /* XXX: add there authenticate and IP match */ + + if (c->post) { + /* if post, it means a feed is being sent */ + if (!stream->is_feed) { + /* However it might be a status report from WMP! Let us log the + * data as it might come handy one day. */ + const char *logline = 0; + int client_id = 0; + + for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) { + if (av_strncasecmp(p, "Pragma: log-line=", 17) == 0) { + logline = p; + break; + } + if (av_strncasecmp(p, "Pragma: client-id=", 18) == 0) + client_id = strtol(p + 18, 0, 10); + p = strchr(p, '\n'); + if (!p) + break; + + p++; + } + + if (logline) { + char *eol = strchr(logline, '\n'); + + logline += 17; + + if (eol) { + if (eol[-1] == '\r') + eol--; + http_log("%.*s\n", (int) (eol - logline), logline); + c->suppress_log = 1; + } + } + +#ifdef DEBUG + http_log("\nGot request:\n%s\n", c->buffer); +#endif + + if (client_id && extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) { + HTTPContext *wmpc; + + /* Now we have to find the client_id */ + for (wmpc = first_http_ctx; wmpc; wmpc = wmpc->next) { + if (wmpc->wmp_client_id == client_id) + break; + } + + if (wmpc && modify_current_stream(wmpc, ratebuf)) + wmpc->switch_pending = 1; + } + + snprintf(msg, sizeof(msg), "POST command not handled"); + c->stream = 0; + goto send_error; + } + if (http_start_receive_data(c) < 0) { + snprintf(msg, sizeof(msg), "could not open feed"); + goto send_error; + } + c->http_error = 0; + c->state = HTTPSTATE_RECEIVE_DATA; + return 0; + } + +#ifdef DEBUG + if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) + http_log("\nGot request:\n%s\n", c->buffer); +#endif + + if (c->stream->stream_type == STREAM_TYPE_STATUS) + goto send_status; + + /* open input stream */ + if (open_input_stream(c, info) < 0) { + snprintf(msg, sizeof(msg), "Input stream corresponding to '%s' not found", url); + goto send_error; + } + + /* prepare HTTP header */ + c->buffer[0] = 0; + av_strlcatf(c->buffer, c->buffer_size, "HTTP/1.0 200 OK\r\n"); + mime_type = c->stream->fmt->mime_type; + if (!mime_type) + mime_type = "application/x-octet-stream"; + av_strlcatf(c->buffer, c->buffer_size, "Pragma: no-cache\r\n"); + + /* for asf, we need extra headers */ + if (!strcmp(c->stream->fmt->name,"asf_stream")) { + /* Need to allocate a client id */ + + c->wmp_client_id = av_lfg_get(&random_state); + + av_strlcatf(c->buffer, c->buffer_size, "Server: Cougar 4.1.0.3923\r\nCache-Control: no-cache\r\nPragma: client-id=%d\r\nPragma: features=\"broadcast\"\r\n", c->wmp_client_id); + } + av_strlcatf(c->buffer, c->buffer_size, "Content-Type: %s\r\n", mime_type); + av_strlcatf(c->buffer, c->buffer_size, "\r\n"); + q = c->buffer + strlen(c->buffer); + + /* prepare output buffer */ + c->http_error = 0; + c->buffer_ptr = c->buffer; + c->buffer_end = q; + c->state = HTTPSTATE_SEND_HEADER; + return 0; + send_error: + c->http_error = 404; + q = c->buffer; + if (!htmlencode(msg, &encoded_msg)) { + http_log("Could not encode filename '%s' as HTML\n", msg); + } + snprintf(q, c->buffer_size, + "HTTP/1.0 404 Not Found\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<meta charset=\"UTF-8\">\n" + "<title>404 Not Found</title>\n" + "</head>\n" + "<body>%s</body>\n" + "</html>\n", encoded_msg? encoded_msg : "File not found"); + q += strlen(q); + /* prepare output buffer */ + c->buffer_ptr = c->buffer; + c->buffer_end = q; + c->state = HTTPSTATE_SEND_HEADER; + av_freep(&encoded_msg); + return 0; + send_status: + compute_status(c); + /* horrible: we use this value to avoid + * going to the send data state */ + c->http_error = 200; + c->state = HTTPSTATE_SEND_HEADER; + return 0; +} + +static void fmt_bytecount(AVIOContext *pb, int64_t count) +{ + static const char suffix[] = " kMGTP"; + const char *s; + + for (s = suffix; count >= 100000 && s[1]; count /= 1000, s++); + + avio_printf(pb, "%"PRId64"%c", count, *s); +} + +static inline void print_stream_params(AVIOContext *pb, FFServerStream *stream) +{ + int i, stream_no; + const char *type = "unknown"; + char parameters[64]; + LayeredAVStream *st; + AVCodec *codec; + + stream_no = stream->nb_streams; + + avio_printf(pb, "<table><tr><th>Stream<th>" + "type<th>kbit/s<th>codec<th>" + "Parameters\n"); + + for (i = 0; i < stream_no; i++) { + st = stream->streams[i]; + codec = avcodec_find_encoder(st->codecpar->codec_id); + + parameters[0] = 0; + + switch(st->codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + type = "audio"; + snprintf(parameters, sizeof(parameters), "%d channel(s), %d Hz", + st->codecpar->channels, st->codecpar->sample_rate); + break; + case AVMEDIA_TYPE_VIDEO: + type = "video"; + snprintf(parameters, sizeof(parameters), + "%dx%d, q=%d-%d, fps=%d", st->codecpar->width, + st->codecpar->height, st->codec->qmin, st->codec->qmax, + st->time_base.den / st->time_base.num); + break; + default: + abort(); + } + + avio_printf(pb, "<tr><td>%d<td>%s<td>%"PRId64 + "<td>%s<td>%s\n", + i, type, st->codecpar->bit_rate/1000, + codec ? codec->name : "", parameters); + } + + avio_printf(pb, "</table>\n"); +} + +static void clean_html(char *clean, int clean_len, char *dirty) +{ + int i, o; + + for (o = i = 0; o+10 < clean_len && dirty[i];) { + int len = strspn(dirty+i, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$-_.+!*(),?/ :;%"); + if (len) { + if (o + len >= clean_len) + break; + memcpy(clean + o, dirty + i, len); + i += len; + o += len; + } else { + int c = dirty[i++]; + switch (c) { + case '&': av_strlcat(clean+o, "&" , clean_len - o); break; + case '<': av_strlcat(clean+o, "<" , clean_len - o); break; + case '>': av_strlcat(clean+o, ">" , clean_len - o); break; + case '\'': av_strlcat(clean+o, "'" , clean_len - o); break; + case '\"': av_strlcat(clean+o, """ , clean_len - o); break; + default: av_strlcat(clean+o, "☹", clean_len - o); break; + } + o += strlen(clean+o); + } + } + clean[o] = 0; +} + +static void compute_status(HTTPContext *c) +{ + HTTPContext *c1; + FFServerStream *stream; + char *p; + time_t ti; + int i, len; + AVIOContext *pb; + + if (avio_open_dyn_buf(&pb) < 0) { + /* XXX: return an error ? */ + c->buffer_ptr = c->buffer; + c->buffer_end = c->buffer; + return; + } + + avio_printf(pb, "HTTP/1.0 200 OK\r\n"); + avio_printf(pb, "Content-type: text/html\r\n"); + avio_printf(pb, "Pragma: no-cache\r\n"); + avio_printf(pb, "\r\n"); + + avio_printf(pb, "<!DOCTYPE html>\n"); + avio_printf(pb, "<html><head><title>%s Status</title>\n", program_name); + if (c->stream->feed_filename[0]) + avio_printf(pb, "<link rel=\"shortcut icon\" href=\"%s\">\n", + c->stream->feed_filename); + avio_printf(pb, "</head>\n<body>"); + avio_printf(pb, "<h1>%s Status</h1>\n", program_name); + /* format status */ + avio_printf(pb, "<h2>Available Streams</h2>\n"); + avio_printf(pb, "<table>\n"); + avio_printf(pb, "<tr><th>Path<th>Served<br>Conns<th><br>bytes<th>Format<th>Bit rate<br>kbit/s<th>Video<br>kbit/s<th><br>Codec<th>Audio<br>kbit/s<th><br>Codec<th>Feed\n"); + stream = config.first_stream; + while (stream) { + char sfilename[1024]; + char *eosf; + + if (stream->feed == stream) { + stream = stream->next; + continue; + } + + av_strlcpy(sfilename, stream->filename, sizeof(sfilename) - 10); + eosf = sfilename + strlen(sfilename); + if (eosf - sfilename >= 4) { + if (strcmp(eosf - 4, ".asf") == 0) + strcpy(eosf - 4, ".asx"); + else if (strcmp(eosf - 3, ".rm") == 0) + strcpy(eosf - 3, ".ram"); + else if (stream->fmt && !strcmp(stream->fmt->name, "rtp")) { + /* generate a sample RTSP director if + * unicast. Generate an SDP redirector if + * multicast */ + eosf = strrchr(sfilename, '.'); + if (!eosf) + eosf = sfilename + strlen(sfilename); + if (stream->is_multicast) + strcpy(eosf, ".sdp"); + else + strcpy(eosf, ".rtsp"); + } + } + + avio_printf(pb, "<tr><td><a href=\"/%s\">%s</a> ", + sfilename, stream->filename); + avio_printf(pb, "<td> %d <td> ", + stream->conns_served); + // TODO: Investigate if we can make http bitexact so it always produces the same count of bytes + if (!config.bitexact) + fmt_bytecount(pb, stream->bytes_served); + + switch(stream->stream_type) { + case STREAM_TYPE_LIVE: { + int audio_bit_rate = 0; + int video_bit_rate = 0; + const char *audio_codec_name = ""; + const char *video_codec_name = ""; + const char *audio_codec_name_extra = ""; + const char *video_codec_name_extra = ""; + + for(i=0;i<stream->nb_streams;i++) { + LayeredAVStream *st = stream->streams[i]; + AVCodec *codec = avcodec_find_encoder(st->codecpar->codec_id); + + switch(st->codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + audio_bit_rate += st->codecpar->bit_rate; + if (codec) { + if (*audio_codec_name) + audio_codec_name_extra = "..."; + audio_codec_name = codec->name; + } + break; + case AVMEDIA_TYPE_VIDEO: + video_bit_rate += st->codecpar->bit_rate; + if (codec) { + if (*video_codec_name) + video_codec_name_extra = "..."; + video_codec_name = codec->name; + } + break; + case AVMEDIA_TYPE_DATA: + video_bit_rate += st->codecpar->bit_rate; + break; + default: + abort(); + } + } + + avio_printf(pb, "<td> %s <td> %d <td> %d <td> %s %s <td> " + "%d <td> %s %s", + stream->fmt->name, stream->bandwidth, + video_bit_rate / 1000, video_codec_name, + video_codec_name_extra, audio_bit_rate / 1000, + audio_codec_name, audio_codec_name_extra); + + if (stream->feed) + avio_printf(pb, "<td>%s", stream->feed->filename); + else + avio_printf(pb, "<td>%s", stream->feed_filename); + avio_printf(pb, "\n"); + } + break; + default: + avio_printf(pb, "<td> - <td> - " + "<td> - <td><td> - <td>\n"); + break; + } + stream = stream->next; + } + avio_printf(pb, "</table>\n"); + + stream = config.first_stream; + while (stream) { + + if (stream->feed != stream) { + stream = stream->next; + continue; + } + + avio_printf(pb, "<h2>Feed %s</h2>", stream->filename); + if (stream->pid) { + avio_printf(pb, "Running as pid %"PRId64".\n", (int64_t) stream->pid); + +#if defined(linux) + { + FILE *pid_stat; + char ps_cmd[64]; + + /* This is somewhat linux specific I guess */ + snprintf(ps_cmd, sizeof(ps_cmd), + "ps -o \"%%cpu,cputime\" --no-headers %"PRId64"", + (int64_t) stream->pid); + + pid_stat = popen(ps_cmd, "r"); + if (pid_stat) { + char cpuperc[10]; + char cpuused[64]; + + if (fscanf(pid_stat, "%9s %63s", cpuperc, cpuused) == 2) { + avio_printf(pb, "Currently using %s%% of the cpu. " + "Total time used %s.\n", + cpuperc, cpuused); + } + fclose(pid_stat); + } + } +#endif + + avio_printf(pb, "<p>"); + } + + print_stream_params(pb, stream); + stream = stream->next; + } + + /* connection status */ + avio_printf(pb, "<h2>Connection Status</h2>\n"); + + avio_printf(pb, "Number of connections: %d / %d<br>\n", + nb_connections, config.nb_max_connections); + + avio_printf(pb, "Bandwidth in use: %"PRIu64"k / %"PRIu64"k<br>\n", + current_bandwidth, config.max_bandwidth); + + avio_printf(pb, "<table>\n"); + avio_printf(pb, "<tr><th>#<th>File<th>IP<th>URL<th>Proto<th>State<th>Target " + "bit/s<th>Actual bit/s<th>Bytes transferred\n"); + c1 = first_http_ctx; + i = 0; + while (c1) { + int bitrate; + int j; + + bitrate = 0; + if (c1->stream) { + for (j = 0; j < c1->stream->nb_streams; j++) { + if (!c1->stream->feed) + bitrate += c1->stream->streams[j]->codecpar->bit_rate; + else if (c1->feed_streams[j] >= 0) + bitrate += c1->stream->feed->streams[c1->feed_streams[j]]->codecpar->bit_rate; + } + } + + i++; + p = inet_ntoa(c1->from_addr.sin_addr); + clean_html(c1->clean_url, sizeof(c1->clean_url), c1->url); + avio_printf(pb, "<tr><td><b>%d</b><td>%s%s<td>%s<td>%s<td>%s<td>%s" + "<td>", + i, c1->stream ? c1->stream->filename : "", + c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", + p, + c1->clean_url, + c1->protocol, http_state[c1->state]); + fmt_bytecount(pb, bitrate); + avio_printf(pb, "<td>"); + fmt_bytecount(pb, compute_datarate(&c1->datarate, c1->data_count) * 8); + avio_printf(pb, "<td>"); + fmt_bytecount(pb, c1->data_count); + avio_printf(pb, "\n"); + c1 = c1->next; + } + avio_printf(pb, "</table>\n"); + + if (!config.bitexact) { + /* date */ + ti = time(NULL); + p = ctime(&ti); + avio_printf(pb, "<hr>Generated at %s", p); + } + avio_printf(pb, "</body>\n</html>\n"); + + len = avio_close_dyn_buf(pb, &c->pb_buffer); + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; +} + +static int open_input_stream(HTTPContext *c, const char *info) +{ + char buf[128]; + char input_filename[1024]; + AVFormatContext *s = NULL; + int buf_size, i, ret; + int64_t stream_pos; + + /* find file name */ + if (c->stream->feed) { + strcpy(input_filename, c->stream->feed->feed_filename); + buf_size = FFM_PACKET_SIZE; + /* compute position (absolute time) */ + if (av_find_info_tag(buf, sizeof(buf), "date", info)) { + if ((ret = av_parse_time(&stream_pos, buf, 0)) < 0) { + http_log("Invalid date specification '%s' for stream\n", buf); + return ret; + } + } else if (av_find_info_tag(buf, sizeof(buf), "buffer", info)) { + int prebuffer = strtol(buf, 0, 10); + stream_pos = av_gettime() - prebuffer * (int64_t)1000000; + } else + stream_pos = av_gettime() - c->stream->prebuffer * (int64_t)1000; + } else { + strcpy(input_filename, c->stream->feed_filename); + buf_size = 0; + /* compute position (relative time) */ + if (av_find_info_tag(buf, sizeof(buf), "date", info)) { + if ((ret = av_parse_time(&stream_pos, buf, 1)) < 0) { + http_log("Invalid date specification '%s' for stream\n", buf); + return ret; + } + } else + stream_pos = 0; + } + if (!input_filename[0]) { + http_log("No filename was specified for stream\n"); + return AVERROR(EINVAL); + } + + /* open stream */ + ret = avformat_open_input(&s, input_filename, c->stream->ifmt, + &c->stream->in_opts); + if (ret < 0) { + http_log("Could not open input '%s': %s\n", + input_filename, av_err2str(ret)); + return ret; + } + + /* set buffer size */ + if (buf_size > 0) { + ret = ffio_set_buf_size(s->pb, buf_size); + if (ret < 0) { + http_log("Failed to set buffer size\n"); + return ret; + } + } + + s->flags |= AVFMT_FLAG_GENPTS; + c->fmt_in = s; + if (strcmp(s->iformat->name, "ffm") && + (ret = avformat_find_stream_info(c->fmt_in, NULL)) < 0) { + http_log("Could not find stream info for input '%s'\n", input_filename); + avformat_close_input(&s); + return ret; + } + + /* choose stream as clock source (we favor the video stream if + * present) for packet sending */ + c->pts_stream_index = 0; + for(i=0;i<c->stream->nb_streams;i++) { + if (c->pts_stream_index == 0 && + c->stream->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + c->pts_stream_index = i; + } + } + + if (c->fmt_in->iformat->read_seek) + av_seek_frame(c->fmt_in, -1, stream_pos, 0); + /* set the start time (needed for maxtime and RTP packet timing) */ + c->start_time = cur_time; + c->first_pts = AV_NOPTS_VALUE; + return 0; +} + +/* return the server clock (in us) */ +static int64_t get_server_clock(HTTPContext *c) +{ + /* compute current pts value from system time */ + return (cur_time - c->start_time) * 1000; +} + +/* return the estimated time (in us) at which the current packet must be sent */ +static int64_t get_packet_send_clock(HTTPContext *c) +{ + int bytes_left, bytes_sent, frame_bytes; + + frame_bytes = c->cur_frame_bytes; + if (frame_bytes <= 0) + return c->cur_pts; + + bytes_left = c->buffer_end - c->buffer_ptr; + bytes_sent = frame_bytes - bytes_left; + return c->cur_pts + (c->cur_frame_duration * bytes_sent) / frame_bytes; +} + + +static int http_prepare_data(HTTPContext *c) +{ + int i, len, ret; + AVFormatContext *ctx; + + av_freep(&c->pb_buffer); + switch(c->state) { + case HTTPSTATE_SEND_DATA_HEADER: + ctx = avformat_alloc_context(); + if (!ctx) + return AVERROR(ENOMEM); + c->pfmt_ctx = ctx; + av_dict_copy(&(c->pfmt_ctx->metadata), c->stream->metadata, 0); + + for(i=0;i<c->stream->nb_streams;i++) { + LayeredAVStream *src; + AVStream *st = avformat_new_stream(c->pfmt_ctx, NULL); + if (!st) + return AVERROR(ENOMEM); + + /* if file or feed, then just take streams from FFServerStream + * struct */ + if (!c->stream->feed || + c->stream->feed == c->stream) + src = c->stream->streams[i]; + else + src = c->stream->feed->streams[c->stream->feed_streams[i]]; + + unlayer_stream(c->pfmt_ctx->streams[i], src); //TODO we no longer copy st->internal, does this matter? + av_assert0(!c->pfmt_ctx->streams[i]->priv_data); + + if (src->codec->flags & AV_CODEC_FLAG_BITEXACT) + c->pfmt_ctx->flags |= AVFMT_FLAG_BITEXACT; + } + /* set output format parameters */ + c->pfmt_ctx->oformat = c->stream->fmt; + av_assert0(c->pfmt_ctx->nb_streams == c->stream->nb_streams); + + c->got_key_frame = 0; + + /* prepare header and save header data in a stream */ + if (avio_open_dyn_buf(&c->pfmt_ctx->pb) < 0) { + /* XXX: potential leak */ + return -1; + } + c->pfmt_ctx->pb->seekable = 0; + + /* + * HACK to avoid MPEG-PS muxer to spit many underflow errors + * Default value from FFmpeg + * Try to set it using configuration option + */ + c->pfmt_ctx->max_delay = (int)(0.7*AV_TIME_BASE); + + if ((ret = avformat_write_header(c->pfmt_ctx, NULL)) < 0) { + http_log("Error writing output header for stream '%s': %s\n", + c->stream->filename, av_err2str(ret)); + return ret; + } + av_dict_free(&c->pfmt_ctx->metadata); + + len = avio_close_dyn_buf(c->pfmt_ctx->pb, &c->pb_buffer); + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; + + c->state = HTTPSTATE_SEND_DATA; + c->last_packet_sent = 0; + break; + case HTTPSTATE_SEND_DATA: + /* find a new packet */ + /* read a packet from the input stream */ + if (c->stream->feed) + ffm_set_write_index(c->fmt_in, + c->stream->feed->feed_write_index, + c->stream->feed->feed_size); + + if (c->stream->max_time && + c->stream->max_time + c->start_time - cur_time < 0) + /* We have timed out */ + c->state = HTTPSTATE_SEND_DATA_TRAILER; + else { + AVPacket pkt; + redo: + ret = av_read_frame(c->fmt_in, &pkt); + if (ret < 0) { + if (c->stream->feed) { + /* if coming from feed, it means we reached the end of the + * ffm file, so must wait for more data */ + c->state = HTTPSTATE_WAIT_FEED; + return 1; /* state changed */ + } + if (ret == AVERROR(EAGAIN)) { + /* input not ready, come back later */ + return 0; + } + if (c->stream->loop) { + avformat_close_input(&c->fmt_in); + if (open_input_stream(c, "") < 0) + goto no_loop; + goto redo; + } else { + no_loop: + /* must send trailer now because EOF or error */ + c->state = HTTPSTATE_SEND_DATA_TRAILER; + } + } else { + int source_index = pkt.stream_index; + /* update first pts if needed */ + if (c->first_pts == AV_NOPTS_VALUE && pkt.dts != AV_NOPTS_VALUE) { + c->first_pts = av_rescale_q(pkt.dts, c->fmt_in->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q); + c->start_time = cur_time; + } + /* send it to the appropriate stream */ + if (c->stream->feed) { + /* if coming from a feed, select the right stream */ + if (c->switch_pending) { + c->switch_pending = 0; + for(i=0;i<c->stream->nb_streams;i++) { + if (c->switch_feed_streams[i] == pkt.stream_index) + if (pkt.flags & AV_PKT_FLAG_KEY) + c->switch_feed_streams[i] = -1; + if (c->switch_feed_streams[i] >= 0) + c->switch_pending = 1; + } + } + for(i=0;i<c->stream->nb_streams;i++) { + if (c->stream->feed_streams[i] == pkt.stream_index) { + AVStream *st = c->fmt_in->streams[source_index]; + pkt.stream_index = i; + if (pkt.flags & AV_PKT_FLAG_KEY && + (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + c->stream->nb_streams == 1)) + c->got_key_frame = 1; + if (!c->stream->send_on_key || c->got_key_frame) + goto send_it; + } + } + } else { + AVStream *ist, *ost; + send_it: + ist = c->fmt_in->streams[source_index]; + /* specific handling for RTP: we use several + * output streams (one for each RTP connection). + * XXX: need more abstract handling */ + if (c->is_packetized) { + /* compute send time and duration */ + if (pkt.dts != AV_NOPTS_VALUE) { + c->cur_pts = av_rescale_q(pkt.dts, ist->time_base, AV_TIME_BASE_Q); + c->cur_pts -= c->first_pts; + } + c->cur_frame_duration = av_rescale_q(pkt.duration, ist->time_base, AV_TIME_BASE_Q); + /* find RTP context */ + c->packet_stream_index = pkt.stream_index; + ctx = c->rtp_ctx[c->packet_stream_index]; + if(!ctx) { + av_packet_unref(&pkt); + break; + } + /* only one stream per RTP connection */ + pkt.stream_index = 0; + } else { + ctx = c->pfmt_ctx; + /* Fudge here */ + } + + if (c->is_packetized) { + int max_packet_size; + if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP) + max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; + else + max_packet_size = c->rtp_handles[c->packet_stream_index]->max_packet_size; + ret = ffio_open_dyn_packet_buf(&ctx->pb, + max_packet_size); + } else + ret = avio_open_dyn_buf(&ctx->pb); + + if (ret < 0) { + /* XXX: potential leak */ + return -1; + } + ost = ctx->streams[pkt.stream_index]; + + ctx->pb->seekable = 0; + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, ist->time_base, + ost->time_base); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, ist->time_base, + ost->time_base); + pkt.duration = av_rescale_q(pkt.duration, ist->time_base, + ost->time_base); + if ((ret = av_write_frame(ctx, &pkt)) < 0) { + http_log("Error writing frame to output for stream '%s': %s\n", + c->stream->filename, av_err2str(ret)); + c->state = HTTPSTATE_SEND_DATA_TRAILER; + } + + av_freep(&c->pb_buffer); + len = avio_close_dyn_buf(ctx->pb, &c->pb_buffer); + ctx->pb = NULL; + c->cur_frame_bytes = len; + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; + + if (len == 0) { + av_packet_unref(&pkt); + goto redo; + } + } + av_packet_unref(&pkt); + } + } + break; + default: + case HTTPSTATE_SEND_DATA_TRAILER: + /* last packet test ? */ + if (c->last_packet_sent || c->is_packetized) + return -1; + ctx = c->pfmt_ctx; + /* prepare header */ + if (avio_open_dyn_buf(&ctx->pb) < 0) { + /* XXX: potential leak */ + return -1; + } + c->pfmt_ctx->pb->seekable = 0; + av_write_trailer(ctx); + len = avio_close_dyn_buf(ctx->pb, &c->pb_buffer); + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; + + c->last_packet_sent = 1; + break; + } + return 0; +} + +/* should convert the format at the same time */ +/* send data starting at c->buffer_ptr to the output connection + * (either UDP or TCP) + */ +static int http_send_data(HTTPContext *c) +{ + int len, ret; + + for(;;) { + if (c->buffer_ptr >= c->buffer_end) { + ret = http_prepare_data(c); + if (ret < 0) + return -1; + else if (ret) + /* state change requested */ + break; + } else { + if (c->is_packetized) { + /* RTP data output */ + len = c->buffer_end - c->buffer_ptr; + if (len < 4) { + /* fail safe - should never happen */ + fail1: + c->buffer_ptr = c->buffer_end; + return 0; + } + len = (c->buffer_ptr[0] << 24) | + (c->buffer_ptr[1] << 16) | + (c->buffer_ptr[2] << 8) | + (c->buffer_ptr[3]); + if (len > (c->buffer_end - c->buffer_ptr)) + goto fail1; + if ((get_packet_send_clock(c) - get_server_clock(c)) > 0) { + /* nothing to send yet: we can wait */ + return 0; + } + + c->data_count += len; + update_datarate(&c->datarate, c->data_count); + if (c->stream) + c->stream->bytes_served += len; + + if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP) { + /* RTP packets are sent inside the RTSP TCP connection */ + AVIOContext *pb; + int interleaved_index, size; + uint8_t header[4]; + HTTPContext *rtsp_c; + + rtsp_c = c->rtsp_c; + /* if no RTSP connection left, error */ + if (!rtsp_c) + return -1; + /* if already sending something, then wait. */ + if (rtsp_c->state != RTSPSTATE_WAIT_REQUEST) + break; + if (avio_open_dyn_buf(&pb) < 0) + goto fail1; + interleaved_index = c->packet_stream_index * 2; + /* RTCP packets are sent at odd indexes */ + if (c->buffer_ptr[1] == 200) + interleaved_index++; + /* write RTSP TCP header */ + header[0] = '$'; + header[1] = interleaved_index; + header[2] = len >> 8; + header[3] = len; + avio_write(pb, header, 4); + /* write RTP packet data */ + c->buffer_ptr += 4; + avio_write(pb, c->buffer_ptr, len); + size = avio_close_dyn_buf(pb, &c->packet_buffer); + /* prepare asynchronous TCP sending */ + rtsp_c->packet_buffer_ptr = c->packet_buffer; + rtsp_c->packet_buffer_end = c->packet_buffer + size; + c->buffer_ptr += len; + + /* send everything we can NOW */ + len = send(rtsp_c->fd, rtsp_c->packet_buffer_ptr, + rtsp_c->packet_buffer_end - rtsp_c->packet_buffer_ptr, 0); + if (len > 0) + rtsp_c->packet_buffer_ptr += len; + if (rtsp_c->packet_buffer_ptr < rtsp_c->packet_buffer_end) { + /* if we could not send all the data, we will + * send it later, so a new state is needed to + * "lock" the RTSP TCP connection */ + rtsp_c->state = RTSPSTATE_SEND_PACKET; + break; + } else + /* all data has been sent */ + av_freep(&c->packet_buffer); + } else { + /* send RTP packet directly in UDP */ + c->buffer_ptr += 4; + ffurl_write(c->rtp_handles[c->packet_stream_index], + c->buffer_ptr, len); + c->buffer_ptr += len; + /* here we continue as we can send several packets + * per 10 ms slot */ + } + } else { + /* TCP data output */ + len = send(c->fd, c->buffer_ptr, + c->buffer_end - c->buffer_ptr, 0); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) + /* error : close connection */ + return -1; + else + return 0; + } + c->buffer_ptr += len; + + c->data_count += len; + update_datarate(&c->datarate, c->data_count); + if (c->stream) + c->stream->bytes_served += len; + break; + } + } + } /* for(;;) */ + return 0; +} + +static int http_start_receive_data(HTTPContext *c) +{ + int fd; + int ret; + int64_t ret64; + + if (c->stream->feed_opened) { + http_log("Stream feed '%s' was not opened\n", + c->stream->feed_filename); + return AVERROR(EINVAL); + } + + /* Don't permit writing to this one */ + if (c->stream->readonly) { + http_log("Cannot write to read-only file '%s'\n", + c->stream->feed_filename); + return AVERROR(EINVAL); + } + + /* open feed */ + fd = open(c->stream->feed_filename, O_RDWR); + if (fd < 0) { + ret = AVERROR(errno); + http_log("Could not open feed file '%s': %s\n", + c->stream->feed_filename, strerror(errno)); + return ret; + } + c->feed_fd = fd; + + if (c->stream->truncate) { + /* truncate feed file */ + ffm_write_write_index(c->feed_fd, FFM_PACKET_SIZE); + http_log("Truncating feed file '%s'\n", c->stream->feed_filename); + if (ftruncate(c->feed_fd, FFM_PACKET_SIZE) < 0) { + ret = AVERROR(errno); + http_log("Error truncating feed file '%s': %s\n", + c->stream->feed_filename, strerror(errno)); + return ret; + } + } else { + ret64 = ffm_read_write_index(fd); + if (ret64 < 0) { + http_log("Error reading write index from feed file '%s': %s\n", + c->stream->feed_filename, strerror(errno)); + return ret64; + } + c->stream->feed_write_index = ret64; + } + + c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), + FFM_PACKET_SIZE); + c->stream->feed_size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + /* init buffer input */ + c->buffer_ptr = c->buffer; + c->buffer_end = c->buffer + FFM_PACKET_SIZE; + c->stream->feed_opened = 1; + c->chunked_encoding = !!av_stristr(c->buffer, "Transfer-Encoding: chunked"); + return 0; +} + +static int http_receive_data(HTTPContext *c) +{ + HTTPContext *c1; + int len, loop_run = 0; + + while (c->chunked_encoding && !c->chunk_size && + c->buffer_end > c->buffer_ptr) { + /* read chunk header, if present */ + len = recv(c->fd, c->buffer_ptr, 1, 0); + + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) + /* error : close connection */ + goto fail; + return 0; + } else if (len == 0) { + /* end of connection : close it */ + goto fail; + } else if (c->buffer_ptr - c->buffer >= 2 && + !memcmp(c->buffer_ptr - 1, "\r\n", 2)) { + c->chunk_size = strtol(c->buffer, 0, 16); + if (c->chunk_size <= 0) { // end of stream or invalid chunk size + c->chunk_size = 0; + goto fail; + } + c->buffer_ptr = c->buffer; + break; + } else if (++loop_run > 10) + /* no chunk header, abort */ + goto fail; + else + c->buffer_ptr++; + } + + if (c->buffer_end > c->buffer_ptr) { + len = recv(c->fd, c->buffer_ptr, + FFMIN(c->chunk_size, c->buffer_end - c->buffer_ptr), 0); + if (len < 0) { + if (ff_neterrno() != AVERROR(EAGAIN) && + ff_neterrno() != AVERROR(EINTR)) + /* error : close connection */ + goto fail; + } else if (len == 0) + /* end of connection : close it */ + goto fail; + else { + av_assert0(len <= c->chunk_size); + c->chunk_size -= len; + c->buffer_ptr += len; + c->data_count += len; + update_datarate(&c->datarate, c->data_count); + } + } + + if (c->buffer_ptr - c->buffer >= 2 && c->data_count > FFM_PACKET_SIZE) { + if (c->buffer[0] != 'f' || + c->buffer[1] != 'm') { + http_log("Feed stream has become desynchronized -- disconnecting\n"); + goto fail; + } + } + + if (c->buffer_ptr >= c->buffer_end) { + FFServerStream *feed = c->stream; + /* a packet has been received : write it in the store, except + * if header */ + if (c->data_count > FFM_PACKET_SIZE) { + /* XXX: use llseek or url_seek + * XXX: Should probably fail? */ + if (lseek(c->feed_fd, feed->feed_write_index, SEEK_SET) == -1) + http_log("Seek to %"PRId64" failed\n", feed->feed_write_index); + + if (write(c->feed_fd, c->buffer, FFM_PACKET_SIZE) < 0) { + http_log("Error writing to feed file: %s\n", strerror(errno)); + goto fail; + } + + feed->feed_write_index += FFM_PACKET_SIZE; + /* update file size */ + if (feed->feed_write_index > c->stream->feed_size) + feed->feed_size = feed->feed_write_index; + + /* handle wrap around if max file size reached */ + if (c->stream->feed_max_size && + feed->feed_write_index >= c->stream->feed_max_size) + feed->feed_write_index = FFM_PACKET_SIZE; + + /* write index */ + if (ffm_write_write_index(c->feed_fd, feed->feed_write_index) < 0) { + http_log("Error writing index to feed file: %s\n", + strerror(errno)); + goto fail; + } + + /* wake up any waiting connections */ + for(c1 = first_http_ctx; c1; c1 = c1->next) { + if (c1->state == HTTPSTATE_WAIT_FEED && + c1->stream->feed == c->stream->feed) + c1->state = HTTPSTATE_SEND_DATA; + } + } else { + /* We have a header in our hands that contains useful data */ + AVFormatContext *s = avformat_alloc_context(); + AVIOContext *pb; + AVInputFormat *fmt_in; + int i; + + if (!s) + goto fail; + + /* use feed output format name to find corresponding input format */ + fmt_in = av_find_input_format(feed->fmt->name); + if (!fmt_in) + goto fail; + + pb = avio_alloc_context(c->buffer, c->buffer_end - c->buffer, + 0, NULL, NULL, NULL, NULL); + if (!pb) + goto fail; + + pb->seekable = 0; + + s->pb = pb; + if (avformat_open_input(&s, c->stream->feed_filename, fmt_in, NULL) < 0) { + av_freep(&pb); + goto fail; + } + + /* Now we have the actual streams */ + if (s->nb_streams != feed->nb_streams) { + avformat_close_input(&s); + av_freep(&pb); + http_log("Feed '%s' stream number does not match registered feed\n", + c->stream->feed_filename); + goto fail; + } + + for (i = 0; i < s->nb_streams; i++) { + LayeredAVStream *fst = feed->streams[i]; + AVStream *st = s->streams[i]; + avcodec_parameters_to_context(fst->codec, st->codecpar); + avcodec_parameters_from_context(fst->codecpar, fst->codec); + } + + avformat_close_input(&s); + av_freep(&pb); + } + c->buffer_ptr = c->buffer; + } + + return 0; + fail: + c->stream->feed_opened = 0; + close(c->feed_fd); + /* wake up any waiting connections to stop waiting for feed */ + for(c1 = first_http_ctx; c1; c1 = c1->next) { + if (c1->state == HTTPSTATE_WAIT_FEED && + c1->stream->feed == c->stream->feed) + c1->state = HTTPSTATE_SEND_DATA_TRAILER; + } + return -1; +} + +/********************************************************************/ +/* RTSP handling */ + +static void rtsp_reply_header(HTTPContext *c, enum RTSPStatusCode error_number) +{ + const char *str; + time_t ti; + struct tm *tm; + char buf2[32]; + + str = RTSP_STATUS_CODE2STRING(error_number); + if (!str) + str = "Unknown Error"; + + avio_printf(c->pb, "RTSP/1.0 %d %s\r\n", error_number, str); + avio_printf(c->pb, "CSeq: %d\r\n", c->seq); + + /* output GMT time */ + ti = time(NULL); + tm = gmtime(&ti); + strftime(buf2, sizeof(buf2), "%a, %d %b %Y %H:%M:%S", tm); + avio_printf(c->pb, "Date: %s GMT\r\n", buf2); +} + +static void rtsp_reply_error(HTTPContext *c, enum RTSPStatusCode error_number) +{ + rtsp_reply_header(c, error_number); + avio_printf(c->pb, "\r\n"); +} + +static int rtsp_parse_request(HTTPContext *c) +{ + const char *p, *p1, *p2; + char cmd[32]; + char url[1024]; + char protocol[32]; + char line[1024]; + int len; + RTSPMessageHeader header1 = { 0 }, *header = &header1; + + c->buffer_ptr[0] = '\0'; + p = c->buffer; + + get_word(cmd, sizeof(cmd), &p); + get_word(url, sizeof(url), &p); + get_word(protocol, sizeof(protocol), &p); + + av_strlcpy(c->method, cmd, sizeof(c->method)); + av_strlcpy(c->url, url, sizeof(c->url)); + av_strlcpy(c->protocol, protocol, sizeof(c->protocol)); + + if (avio_open_dyn_buf(&c->pb) < 0) { + /* XXX: cannot do more */ + c->pb = NULL; /* safety */ + return -1; + } + + /* check version name */ + if (strcmp(protocol, "RTSP/1.0")) { + rtsp_reply_error(c, RTSP_STATUS_VERSION); + goto the_end; + } + + /* parse each header line */ + /* skip to next line */ + while (*p != '\n' && *p != '\0') + p++; + if (*p == '\n') + p++; + while (*p != '\0') { + p1 = memchr(p, '\n', (char *)c->buffer_ptr - p); + if (!p1) + break; + p2 = p1; + if (p2 > p && p2[-1] == '\r') + p2--; + /* skip empty line */ + if (p2 == p) + break; + len = p2 - p; + if (len > sizeof(line) - 1) + len = sizeof(line) - 1; + memcpy(line, p, len); + line[len] = '\0'; + ff_rtsp_parse_line(NULL, header, line, NULL, NULL); + p = p1 + 1; + } + + /* handle sequence number */ + c->seq = header->seq; + + if (!strcmp(cmd, "DESCRIBE")) + rtsp_cmd_describe(c, url); + else if (!strcmp(cmd, "OPTIONS")) + rtsp_cmd_options(c, url); + else if (!strcmp(cmd, "SETUP")) + rtsp_cmd_setup(c, url, header); + else if (!strcmp(cmd, "PLAY")) + rtsp_cmd_play(c, url, header); + else if (!strcmp(cmd, "PAUSE")) + rtsp_cmd_interrupt(c, url, header, 1); + else if (!strcmp(cmd, "TEARDOWN")) + rtsp_cmd_interrupt(c, url, header, 0); + else + rtsp_reply_error(c, RTSP_STATUS_METHOD); + + the_end: + len = avio_close_dyn_buf(c->pb, &c->pb_buffer); + c->pb = NULL; /* safety */ + if (len < 0) + /* XXX: cannot do more */ + return -1; + + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; + c->state = RTSPSTATE_SEND_REPLY; + return 0; +} + +static int prepare_sdp_description(FFServerStream *stream, uint8_t **pbuffer, + struct in_addr my_ip) +{ + AVFormatContext *avc; + AVOutputFormat *rtp_format = av_guess_format("rtp", NULL, NULL); + AVDictionaryEntry *entry = av_dict_get(stream->metadata, "title", NULL, 0); + int i; + + *pbuffer = NULL; + + avc = avformat_alloc_context(); + if (!avc || !rtp_format) + return -1; + + avc->oformat = rtp_format; + av_dict_set(&avc->metadata, "title", + entry ? entry->value : "No Title", 0); + if (stream->is_multicast) { + snprintf(avc->filename, 1024, "rtp://%s:%d?multicast=1?ttl=%d", + inet_ntoa(stream->multicast_ip), + stream->multicast_port, stream->multicast_ttl); + } else + snprintf(avc->filename, 1024, "rtp://0.0.0.0"); + + for(i = 0; i < stream->nb_streams; i++) { + AVStream *st = avformat_new_stream(avc, NULL); + if (!st) + goto sdp_done; + avcodec_parameters_from_context(stream->streams[i]->codecpar, stream->streams[i]->codec); + unlayer_stream(st, stream->streams[i]); + } +#define PBUFFER_SIZE 2048 + *pbuffer = av_mallocz(PBUFFER_SIZE); + if (!*pbuffer) + goto sdp_done; + av_sdp_create(&avc, 1, *pbuffer, PBUFFER_SIZE); + + sdp_done: + av_freep(&avc->streams); + av_dict_free(&avc->metadata); + av_free(avc); + + return *pbuffer ? strlen(*pbuffer) : AVERROR(ENOMEM); +} + +static void rtsp_cmd_options(HTTPContext *c, const char *url) +{ + /* rtsp_reply_header(c, RTSP_STATUS_OK); */ + avio_printf(c->pb, "RTSP/1.0 %d %s\r\n", RTSP_STATUS_OK, "OK"); + avio_printf(c->pb, "CSeq: %d\r\n", c->seq); + avio_printf(c->pb, "Public: %s\r\n", + "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"); + avio_printf(c->pb, "\r\n"); +} + +static void rtsp_cmd_describe(HTTPContext *c, const char *url) +{ + FFServerStream *stream; + char path1[1024]; + const char *path; + uint8_t *content; + int content_length; + socklen_t len; + struct sockaddr_in my_addr; + + /* find which URL is asked */ + av_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + path = path1; + if (*path == '/') + path++; + + for(stream = config.first_stream; stream; stream = stream->next) { + if (!stream->is_feed && + stream->fmt && !strcmp(stream->fmt->name, "rtp") && + !strcmp(path, stream->filename)) { + goto found; + } + } + /* no stream found */ + rtsp_reply_error(c, RTSP_STATUS_NOT_FOUND); + return; + + found: + /* prepare the media description in SDP format */ + + /* get the host IP */ + len = sizeof(my_addr); + getsockname(c->fd, (struct sockaddr *)&my_addr, &len); + content_length = prepare_sdp_description(stream, &content, + my_addr.sin_addr); + if (content_length < 0) { + rtsp_reply_error(c, RTSP_STATUS_INTERNAL); + return; + } + rtsp_reply_header(c, RTSP_STATUS_OK); + avio_printf(c->pb, "Content-Base: %s/\r\n", url); + avio_printf(c->pb, "Content-Type: application/sdp\r\n"); + avio_printf(c->pb, "Content-Length: %d\r\n", content_length); + avio_printf(c->pb, "\r\n"); + avio_write(c->pb, content, content_length); + av_free(content); +} + +static HTTPContext *find_rtp_session(const char *session_id) +{ + HTTPContext *c; + + if (session_id[0] == '\0') + return NULL; + + for(c = first_http_ctx; c; c = c->next) { + if (!strcmp(c->session_id, session_id)) + return c; + } + return NULL; +} + +static RTSPTransportField *find_transport(RTSPMessageHeader *h, enum RTSPLowerTransport lower_transport) +{ + RTSPTransportField *th; + int i; + + for(i=0;i<h->nb_transports;i++) { + th = &h->transports[i]; + if (th->lower_transport == lower_transport) + return th; + } + return NULL; +} + +static void rtsp_cmd_setup(HTTPContext *c, const char *url, + RTSPMessageHeader *h) +{ + FFServerStream *stream; + int stream_index, rtp_port, rtcp_port; + char buf[1024]; + char path1[1024]; + const char *path; + HTTPContext *rtp_c; + RTSPTransportField *th; + struct sockaddr_in dest_addr; + RTSPActionServerSetup setup; + + /* find which URL is asked */ + av_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + path = path1; + if (*path == '/') + path++; + + /* now check each stream */ + for(stream = config.first_stream; stream; stream = stream->next) { + if (stream->is_feed || !stream->fmt || + strcmp(stream->fmt->name, "rtp")) { + continue; + } + /* accept aggregate filenames only if single stream */ + if (!strcmp(path, stream->filename)) { + if (stream->nb_streams != 1) { + rtsp_reply_error(c, RTSP_STATUS_AGGREGATE); + return; + } + stream_index = 0; + goto found; + } + + for(stream_index = 0; stream_index < stream->nb_streams; + stream_index++) { + snprintf(buf, sizeof(buf), "%s/streamid=%d", + stream->filename, stream_index); + if (!strcmp(path, buf)) + goto found; + } + } + /* no stream found */ + rtsp_reply_error(c, RTSP_STATUS_SERVICE); /* XXX: right error ? */ + return; + found: + + /* generate session id if needed */ + if (h->session_id[0] == '\0') { + unsigned random0 = av_lfg_get(&random_state); + unsigned random1 = av_lfg_get(&random_state); + snprintf(h->session_id, sizeof(h->session_id), "%08x%08x", + random0, random1); + } + + /* find RTP session, and create it if none found */ + rtp_c = find_rtp_session(h->session_id); + if (!rtp_c) { + /* always prefer UDP */ + th = find_transport(h, RTSP_LOWER_TRANSPORT_UDP); + if (!th) { + th = find_transport(h, RTSP_LOWER_TRANSPORT_TCP); + if (!th) { + rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); + return; + } + } + + rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id, + th->lower_transport); + if (!rtp_c) { + rtsp_reply_error(c, RTSP_STATUS_BANDWIDTH); + return; + } + + /* open input stream */ + if (open_input_stream(rtp_c, "") < 0) { + rtsp_reply_error(c, RTSP_STATUS_INTERNAL); + return; + } + } + + /* test if stream is OK (test needed because several SETUP needs + * to be done for a given file) */ + if (rtp_c->stream != stream) { + rtsp_reply_error(c, RTSP_STATUS_SERVICE); + return; + } + + /* test if stream is already set up */ + if (rtp_c->rtp_ctx[stream_index]) { + rtsp_reply_error(c, RTSP_STATUS_STATE); + return; + } + + /* check transport */ + th = find_transport(h, rtp_c->rtp_protocol); + if (!th || (th->lower_transport == RTSP_LOWER_TRANSPORT_UDP && + th->client_port_min <= 0)) { + rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); + return; + } + + /* setup default options */ + setup.transport_option[0] = '\0'; + dest_addr = rtp_c->from_addr; + dest_addr.sin_port = htons(th->client_port_min); + + /* setup stream */ + if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, c) < 0) { + rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); + return; + } + + /* now everything is OK, so we can send the connection parameters */ + rtsp_reply_header(c, RTSP_STATUS_OK); + /* session ID */ + avio_printf(c->pb, "Session: %s\r\n", rtp_c->session_id); + + switch(rtp_c->rtp_protocol) { + case RTSP_LOWER_TRANSPORT_UDP: + rtp_port = ff_rtp_get_local_rtp_port(rtp_c->rtp_handles[stream_index]); + rtcp_port = ff_rtp_get_local_rtcp_port(rtp_c->rtp_handles[stream_index]); + avio_printf(c->pb, "Transport: RTP/AVP/UDP;unicast;" + "client_port=%d-%d;server_port=%d-%d", + th->client_port_min, th->client_port_max, + rtp_port, rtcp_port); + break; + case RTSP_LOWER_TRANSPORT_TCP: + avio_printf(c->pb, "Transport: RTP/AVP/TCP;interleaved=%d-%d", + stream_index * 2, stream_index * 2 + 1); + break; + default: + break; + } + if (setup.transport_option[0] != '\0') + avio_printf(c->pb, ";%s", setup.transport_option); + avio_printf(c->pb, "\r\n"); + + + avio_printf(c->pb, "\r\n"); +} + + +/** + * find an RTP connection by using the session ID. Check consistency + * with filename + */ +static HTTPContext *find_rtp_session_with_url(const char *url, + const char *session_id) +{ + HTTPContext *rtp_c; + char path1[1024]; + const char *path; + char buf[1024]; + int s, len; + + rtp_c = find_rtp_session(session_id); + if (!rtp_c) + return NULL; + + /* find which URL is asked */ + av_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + path = path1; + if (*path == '/') + path++; + if(!strcmp(path, rtp_c->stream->filename)) return rtp_c; + for(s=0; s<rtp_c->stream->nb_streams; ++s) { + snprintf(buf, sizeof(buf), "%s/streamid=%d", + rtp_c->stream->filename, s); + if(!strncmp(path, buf, sizeof(buf))) + /* XXX: Should we reply with RTSP_STATUS_ONLY_AGGREGATE + * if nb_streams>1? */ + return rtp_c; + } + len = strlen(path); + if (len > 0 && path[len - 1] == '/' && + !strncmp(path, rtp_c->stream->filename, len - 1)) + return rtp_c; + return NULL; +} + +static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPMessageHeader *h) +{ + HTTPContext *rtp_c; + + rtp_c = find_rtp_session_with_url(url, h->session_id); + if (!rtp_c) { + rtsp_reply_error(c, RTSP_STATUS_SESSION); + return; + } + + if (rtp_c->state != HTTPSTATE_SEND_DATA && + rtp_c->state != HTTPSTATE_WAIT_FEED && + rtp_c->state != HTTPSTATE_READY) { + rtsp_reply_error(c, RTSP_STATUS_STATE); + return; + } + + rtp_c->state = HTTPSTATE_SEND_DATA; + + /* now everything is OK, so we can send the connection parameters */ + rtsp_reply_header(c, RTSP_STATUS_OK); + /* session ID */ + avio_printf(c->pb, "Session: %s\r\n", rtp_c->session_id); + avio_printf(c->pb, "\r\n"); +} + +static void rtsp_cmd_interrupt(HTTPContext *c, const char *url, + RTSPMessageHeader *h, int pause_only) +{ + HTTPContext *rtp_c; + + rtp_c = find_rtp_session_with_url(url, h->session_id); + if (!rtp_c) { + rtsp_reply_error(c, RTSP_STATUS_SESSION); + return; + } + + if (pause_only) { + if (rtp_c->state != HTTPSTATE_SEND_DATA && + rtp_c->state != HTTPSTATE_WAIT_FEED) { + rtsp_reply_error(c, RTSP_STATUS_STATE); + return; + } + rtp_c->state = HTTPSTATE_READY; + rtp_c->first_pts = AV_NOPTS_VALUE; + } + + /* now everything is OK, so we can send the connection parameters */ + rtsp_reply_header(c, RTSP_STATUS_OK); + /* session ID */ + avio_printf(c->pb, "Session: %s\r\n", rtp_c->session_id); + avio_printf(c->pb, "\r\n"); + + if (!pause_only) + close_connection(rtp_c); +} + +/********************************************************************/ +/* RTP handling */ + +static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, + FFServerStream *stream, + const char *session_id, + enum RTSPLowerTransport rtp_protocol) +{ + HTTPContext *c = NULL; + const char *proto_str; + + /* XXX: should output a warning page when coming + * close to the connection limit */ + if (nb_connections >= config.nb_max_connections) + goto fail; + + /* add a new connection */ + c = av_mallocz(sizeof(HTTPContext)); + if (!c) + goto fail; + + c->fd = -1; + c->poll_entry = NULL; + c->from_addr = *from_addr; + c->buffer_size = IOBUFFER_INIT_SIZE; + c->buffer = av_malloc(c->buffer_size); + if (!c->buffer) + goto fail; + nb_connections++; + c->stream = stream; + av_strlcpy(c->session_id, session_id, sizeof(c->session_id)); + c->state = HTTPSTATE_READY; + c->is_packetized = 1; + c->rtp_protocol = rtp_protocol; + + /* protocol is shown in statistics */ + switch(c->rtp_protocol) { + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: + proto_str = "MCAST"; + break; + case RTSP_LOWER_TRANSPORT_UDP: + proto_str = "UDP"; + break; + case RTSP_LOWER_TRANSPORT_TCP: + proto_str = "TCP"; + break; + default: + proto_str = "???"; + break; + } + av_strlcpy(c->protocol, "RTP/", sizeof(c->protocol)); + av_strlcat(c->protocol, proto_str, sizeof(c->protocol)); + + current_bandwidth += stream->bandwidth; + + c->next = first_http_ctx; + first_http_ctx = c; + return c; + + fail: + if (c) { + av_freep(&c->buffer); + av_free(c); + } + return NULL; +} + +/** + * add a new RTP stream in an RTP connection (used in RTSP SETUP + * command). If RTP/TCP protocol is used, TCP connection 'rtsp_c' is + * used. + */ +static int rtp_new_av_stream(HTTPContext *c, + int stream_index, struct sockaddr_in *dest_addr, + HTTPContext *rtsp_c) +{ + AVFormatContext *ctx; + AVStream *st; + char *ipaddr; + URLContext *h = NULL; + uint8_t *dummy_buf; + int max_packet_size; + void *st_internal; + + /* now we can open the relevant output stream */ + ctx = avformat_alloc_context(); + if (!ctx) + return -1; + ctx->oformat = av_guess_format("rtp", NULL, NULL); + + st = avformat_new_stream(ctx, NULL); + if (!st) + goto fail; + + st_internal = st->internal; + + if (!c->stream->feed || + c->stream->feed == c->stream) + unlayer_stream(st, c->stream->streams[stream_index]); + else + unlayer_stream(st, + c->stream->feed->streams[c->stream->feed_streams[stream_index]]); + av_assert0(st->priv_data == NULL); + av_assert0(st->internal == st_internal); + + /* build destination RTP address */ + ipaddr = inet_ntoa(dest_addr->sin_addr); + + switch(c->rtp_protocol) { + case RTSP_LOWER_TRANSPORT_UDP: + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: + /* RTP/UDP case */ + + /* XXX: also pass as parameter to function ? */ + if (c->stream->is_multicast) { + int ttl; + ttl = c->stream->multicast_ttl; + if (!ttl) + ttl = 16; + snprintf(ctx->filename, sizeof(ctx->filename), + "rtp://%s:%d?multicast=1&ttl=%d", + ipaddr, ntohs(dest_addr->sin_port), ttl); + } else { + snprintf(ctx->filename, sizeof(ctx->filename), + "rtp://%s:%d", ipaddr, ntohs(dest_addr->sin_port)); + } + + if (ffurl_open(&h, ctx->filename, AVIO_FLAG_WRITE, NULL, NULL) < 0) + goto fail; + c->rtp_handles[stream_index] = h; + max_packet_size = h->max_packet_size; + break; + case RTSP_LOWER_TRANSPORT_TCP: + /* RTP/TCP case */ + c->rtsp_c = rtsp_c; + max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; + break; + default: + goto fail; + } + + http_log("%s:%d - - \"PLAY %s/streamid=%d %s\"\n", + ipaddr, ntohs(dest_addr->sin_port), + c->stream->filename, stream_index, c->protocol); + + /* normally, no packets should be output here, but the packet size may + * be checked */ + if (ffio_open_dyn_packet_buf(&ctx->pb, max_packet_size) < 0) + /* XXX: close stream */ + goto fail; + + if (avformat_write_header(ctx, NULL) < 0) { + fail: + if (h) + ffurl_close(h); + av_free(st); + av_free(ctx); + return -1; + } + avio_close_dyn_buf(ctx->pb, &dummy_buf); + ctx->pb = NULL; + av_free(dummy_buf); + + c->rtp_ctx[stream_index] = ctx; + return 0; +} + +/********************************************************************/ +/* ffserver initialization */ + +/* FIXME: This code should use avformat_new_stream() */ +static LayeredAVStream *add_av_stream1(FFServerStream *stream, + AVCodecContext *codec, int copy) +{ + LayeredAVStream *fst; + + if(stream->nb_streams >= FF_ARRAY_ELEMS(stream->streams)) + return NULL; + + fst = av_mallocz(sizeof(*fst)); + if (!fst) + return NULL; + if (copy) { + fst->codec = avcodec_alloc_context3(codec->codec); + if (!fst->codec) { + av_free(fst); + return NULL; + } + avcodec_copy_context(fst->codec, codec); + } else + /* live streams must use the actual feed's codec since it may be + * updated later to carry extradata needed by them. + */ + fst->codec = codec; + + //NOTE we previously allocated internal & internal->avctx, these seemed uneeded though + fst->codecpar = avcodec_parameters_alloc(); + fst->index = stream->nb_streams; + fst->time_base = codec->time_base; + fst->pts_wrap_bits = 33; + fst->sample_aspect_ratio = codec->sample_aspect_ratio; + stream->streams[stream->nb_streams++] = fst; + return fst; +} + +/* return the stream number in the feed */ +static int add_av_stream(FFServerStream *feed, LayeredAVStream *st) +{ + LayeredAVStream *fst; + AVCodecContext *av, *av1; + int i; + + av = st->codec; + for(i=0;i<feed->nb_streams;i++) { + av1 = feed->streams[i]->codec; + if (av1->codec_id == av->codec_id && + av1->codec_type == av->codec_type && + av1->bit_rate == av->bit_rate) { + + switch(av->codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (av1->channels == av->channels && + av1->sample_rate == av->sample_rate) + return i; + break; + case AVMEDIA_TYPE_VIDEO: + if (av1->width == av->width && + av1->height == av->height && + av1->time_base.den == av->time_base.den && + av1->time_base.num == av->time_base.num && + av1->gop_size == av->gop_size) + return i; + break; + default: + abort(); + } + } + } + + fst = add_av_stream1(feed, av, 0); + if (!fst) + return -1; + if (st->recommended_encoder_configuration) + fst->recommended_encoder_configuration = + av_strdup(st->recommended_encoder_configuration); + return feed->nb_streams - 1; +} + +static void remove_stream(FFServerStream *stream) +{ + FFServerStream **ps; + ps = &config.first_stream; + while (*ps) { + if (*ps == stream) + *ps = (*ps)->next; + else + ps = &(*ps)->next; + } +} + +/* compute the needed AVStream for each file */ +static void build_file_streams(void) +{ + FFServerStream *stream; + AVFormatContext *infile; + int i, ret; + + /* gather all streams */ + for(stream = config.first_stream; stream; stream = stream->next) { + infile = NULL; + + if (stream->stream_type != STREAM_TYPE_LIVE || stream->feed) + continue; + + /* the stream comes from a file */ + /* try to open the file */ + /* open stream */ + + + /* specific case: if transport stream output to RTP, + * we use a raw transport stream reader */ + if (stream->fmt && !strcmp(stream->fmt->name, "rtp")) + av_dict_set(&stream->in_opts, "mpeg2ts_compute_pcr", "1", 0); + + if (!stream->feed_filename[0]) { + http_log("Unspecified feed file for stream '%s'\n", + stream->filename); + goto fail; + } + + http_log("Opening feed file '%s' for stream '%s'\n", + stream->feed_filename, stream->filename); + + ret = avformat_open_input(&infile, stream->feed_filename, + stream->ifmt, &stream->in_opts); + if (ret < 0) { + http_log("Could not open '%s': %s\n", stream->feed_filename, + av_err2str(ret)); + /* remove stream (no need to spend more time on it) */ + fail: + remove_stream(stream); + } else { + /* find all the AVStreams inside and reference them in + * 'stream' */ + if (avformat_find_stream_info(infile, NULL) < 0) { + http_log("Could not find codec parameters from '%s'\n", + stream->feed_filename); + avformat_close_input(&infile); + goto fail; + } + + for(i=0;i<infile->nb_streams;i++) + add_av_stream1(stream, infile->streams[i]->codec, 1); + + avformat_close_input(&infile); + } + } +} + +static inline +int check_codec_match(LayeredAVStream *ccf, AVStream *ccs, int stream) +{ + int matches = 1; + +/* FIXME: Missed check on AVCodecContext.flags */ +#define CHECK_CODEC(x) (ccf->codecpar->x != ccs->codecpar->x) + if (CHECK_CODEC(codec_id) || CHECK_CODEC(codec_type)) { + http_log("Codecs do not match for stream %d\n", stream); + matches = 0; + } else if (CHECK_CODEC(bit_rate)) { + http_log("Codec bitrates do not match for stream %d\n", stream); + matches = 0; + } else if (ccf->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (av_cmp_q(ccf->time_base, ccs->time_base) || + CHECK_CODEC(width) || CHECK_CODEC(height)) { + http_log("Codec width, height or framerate do not match for stream %d\n", stream); + matches = 0; + } + } else if (ccf->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + if (CHECK_CODEC(sample_rate) || + CHECK_CODEC(channels) || + CHECK_CODEC(frame_size)) { + http_log("Codec sample_rate, channels, frame_size do not match for stream %d\n", stream); + matches = 0; + } + } else { + http_log("Unknown codec type for stream %d\n", stream); + matches = 0; + } + + return matches; +} + +/* compute the needed AVStream for each feed */ +static int build_feed_streams(void) +{ + FFServerStream *stream, *feed; + int i, fd; + + /* gather all streams */ + for(stream = config.first_stream; stream; stream = stream->next) { + feed = stream->feed; + if (!feed) + continue; + + if (stream->is_feed) { + for(i=0;i<stream->nb_streams;i++) + stream->feed_streams[i] = i; + continue; + } + /* we handle a stream coming from a feed */ + for(i=0;i<stream->nb_streams;i++) + stream->feed_streams[i] = add_av_stream(feed, stream->streams[i]); + } + + /* create feed files if needed */ + for(feed = config.first_feed; feed; feed = feed->next_feed) { + + if (avio_check(feed->feed_filename, AVIO_FLAG_READ) > 0) { + AVFormatContext *s = NULL; + int matches = 0; + + /* See if it matches */ + + if (avformat_open_input(&s, feed->feed_filename, NULL, NULL) < 0) { + http_log("Deleting feed file '%s' as it appears " + "to be corrupt\n", + feed->feed_filename); + goto drop; + } + + /* set buffer size */ + if (ffio_set_buf_size(s->pb, FFM_PACKET_SIZE) < 0) { + http_log("Failed to set buffer size\n"); + avformat_close_input(&s); + goto bail; + } + + /* Now see if it matches */ + if (s->nb_streams != feed->nb_streams) { + http_log("Deleting feed file '%s' as stream counts " + "differ (%d != %d)\n", + feed->feed_filename, s->nb_streams, feed->nb_streams); + goto drop; + } + + matches = 1; + for(i=0;i<s->nb_streams;i++) { + AVStream *ss; + LayeredAVStream *sf; + + sf = feed->streams[i]; + ss = s->streams[i]; + + if (sf->index != ss->index || sf->id != ss->id) { + http_log("Index & Id do not match for stream %d (%s)\n", + i, feed->feed_filename); + matches = 0; + break; + } + + matches = check_codec_match (sf, ss, i); + if (!matches) + break; + } + +drop: + if (s) + avformat_close_input(&s); + + if (!matches) { + if (feed->readonly) { + http_log("Unable to delete read-only feed file '%s'\n", + feed->feed_filename); + goto bail; + } + unlink(feed->feed_filename); + } + } + + if (avio_check(feed->feed_filename, AVIO_FLAG_WRITE) <= 0) { + AVFormatContext *s = avformat_alloc_context(); + + if (!s) { + http_log("Failed to allocate context\n"); + goto bail; + } + + if (feed->readonly) { + http_log("Unable to create feed file '%s' as it is " + "marked readonly\n", + feed->feed_filename); + avformat_free_context(s); + goto bail; + } + + /* only write the header of the ffm file */ + if (avio_open(&s->pb, feed->feed_filename, AVIO_FLAG_WRITE) < 0) { + http_log("Could not open output feed file '%s'\n", + feed->feed_filename); + avformat_free_context(s); + goto bail; + } + s->oformat = feed->fmt; + for (i = 0; i<feed->nb_streams; i++) { + AVStream *st = avformat_new_stream(s, NULL); // FIXME free this + if (!st) { + http_log("Failed to allocate stream\n"); + goto bail; + } + unlayer_stream(st, feed->streams[i]); + } + if (avformat_write_header(s, NULL) < 0) { + http_log("Container doesn't support the required parameters\n"); + avio_closep(&s->pb); + s->streams = NULL; + s->nb_streams = 0; + avformat_free_context(s); + goto bail; + } + /* XXX: need better API */ + av_freep(&s->priv_data); + avio_closep(&s->pb); + s->streams = NULL; + s->nb_streams = 0; + avformat_free_context(s); + } + + /* get feed size and write index */ + fd = open(feed->feed_filename, O_RDONLY); + if (fd < 0) { + http_log("Could not open output feed file '%s'\n", + feed->feed_filename); + goto bail; + } + + feed->feed_write_index = FFMAX(ffm_read_write_index(fd), + FFM_PACKET_SIZE); + feed->feed_size = lseek(fd, 0, SEEK_END); + /* ensure that we do not wrap before the end of file */ + if (feed->feed_max_size && feed->feed_max_size < feed->feed_size) + feed->feed_max_size = feed->feed_size; + + close(fd); + } + return 0; + +bail: + return -1; +} + +/* compute the bandwidth used by each stream */ +static void compute_bandwidth(void) +{ + unsigned bandwidth; + int i; + FFServerStream *stream; + + for(stream = config.first_stream; stream; stream = stream->next) { + bandwidth = 0; + for(i=0;i<stream->nb_streams;i++) { + LayeredAVStream *st = stream->streams[i]; + switch(st->codec->codec_type) { + case AVMEDIA_TYPE_AUDIO: + case AVMEDIA_TYPE_VIDEO: + bandwidth += st->codec->bit_rate; + break; + default: + break; + } + } + stream->bandwidth = (bandwidth + 999) / 1000; + } +} + +static void handle_child_exit(int sig) +{ + pid_t pid; + int status; + time_t uptime; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + FFServerStream *feed; + + for (feed = config.first_feed; feed; feed = feed->next) { + if (feed->pid != pid) + continue; + + uptime = time(0) - feed->pid_start; + feed->pid = 0; + fprintf(stderr, + "%s: Pid %"PRId64" exited with status %d after %"PRId64" " + "seconds\n", + feed->filename, (int64_t) pid, status, (int64_t)uptime); + + if (uptime < 30) + /* Turn off any more restarts */ + ffserver_free_child_args(&feed->child_argv); + } + } + + need_to_start_children = 1; +} + +static void opt_debug(void) +{ + config.debug = 1; + snprintf(config.logfilename, sizeof(config.logfilename), "-"); +} + +void show_help_default(const char *opt, const char *arg) +{ + printf("usage: ffserver [options]\n" + "Hyper fast multi format Audio/Video streaming server\n"); + printf("\n"); + show_help_options(options, "Main options:", 0, 0, 0); +} + +static const OptionDef options[] = { + CMDUTILS_COMMON_OPTIONS + { "n", OPT_BOOL, {(void *)&no_launch }, "enable no-launch mode" }, + { "d", 0, {(void*)opt_debug}, "enable debug mode" }, + { "f", HAS_ARG | OPT_STRING, {(void*)&config.filename }, "use configfile instead of /etc/ffserver.conf", "configfile" }, + { NULL }, +}; + +int main(int argc, char **argv) +{ + struct sigaction sigact = { { 0 } }; + int cfg_parsed; + int ret = EXIT_FAILURE; + + init_dynload(); + + config.filename = av_strdup("/etc/ffserver.conf"); + + parse_loglevel(argc, argv, options); + av_register_all(); + avformat_network_init(); + + show_banner(argc, argv, options); + + my_program_name = argv[0]; + + parse_options(NULL, argc, argv, options, NULL); + + unsetenv("http_proxy"); /* Kill the http_proxy */ + + av_lfg_init(&random_state, av_get_random_seed()); + + sigact.sa_handler = handle_child_exit; + sigact.sa_flags = SA_NOCLDSTOP | SA_RESTART; + sigaction(SIGCHLD, &sigact, 0); + + if ((cfg_parsed = ffserver_parse_ffconfig(config.filename, &config)) < 0) { + fprintf(stderr, "Error reading configuration file '%s': %s\n", + config.filename, av_err2str(cfg_parsed)); + goto bail; + } + + /* open log file if needed */ + if (config.logfilename[0] != '\0') { + if (!strcmp(config.logfilename, "-")) + logfile = stdout; + else + logfile = fopen(config.logfilename, "a"); + av_log_set_callback(http_av_log); + } + + build_file_streams(); + + if (build_feed_streams() < 0) { + http_log("Could not setup feed streams\n"); + goto bail; + } + + compute_bandwidth(); + + /* signal init */ + signal(SIGPIPE, SIG_IGN); + + if (http_server() < 0) { + http_log("Could not start server\n"); + goto bail; + } + + ret=EXIT_SUCCESS; + +bail: + av_freep (&config.filename); + avformat_network_deinit(); + return ret; +} diff --git a/fftools/ffserver_config.c b/fftools/ffserver_config.c new file mode 100644 index 0000000000..54135be989 --- /dev/null +++ b/fftools/ffserver_config.c @@ -0,0 +1,1325 @@ +/* + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <float.h> +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/avstring.h" +#include "libavutil/pixdesc.h" +#include "libavutil/avassert.h" + +#include "cmdutils.h" +#include "ffserver_config.h" + +#define MAX_CHILD_ARGS 64 + +static int ffserver_save_avoption(const char *opt, const char *arg, int type, + FFServerConfig *config); +static void vreport_config_error(const char *filename, int line_num, + int log_level, int *errors, const char *fmt, + va_list vl); +static void report_config_error(const char *filename, int line_num, + int log_level, int *errors, const char *fmt, + ...); + +#define ERROR(...) report_config_error(config->filename, config->line_num,\ + AV_LOG_ERROR, &config->errors, __VA_ARGS__) +#define WARNING(...) report_config_error(config->filename, config->line_num,\ + AV_LOG_WARNING, &config->warnings, __VA_ARGS__) + +/* FIXME: make ffserver work with IPv6 */ +/* resolve host with also IP address parsing */ +static int resolve_host(struct in_addr *sin_addr, const char *hostname) +{ + + if (!ff_inet_aton(hostname, sin_addr)) { +#if HAVE_GETADDRINFO + struct addrinfo *ai, *cur; + struct addrinfo hints = { 0 }; + hints.ai_family = AF_INET; + if (getaddrinfo(hostname, NULL, &hints, &ai)) + return -1; + /* getaddrinfo returns a linked list of addrinfo structs. + * Even if we set ai_family = AF_INET above, make sure + * that the returned one actually is of the correct type. */ + for (cur = ai; cur; cur = cur->ai_next) { + if (cur->ai_family == AF_INET) { + *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr; + freeaddrinfo(ai); + return 0; + } + } + freeaddrinfo(ai); + return -1; +#else + struct hostent *hp; + hp = gethostbyname(hostname); + if (!hp) + return -1; + memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr)); +#endif + } + return 0; +} + +void ffserver_get_arg(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + int quote = 0; + + p = *pp; + q = buf; + + while (av_isspace(*p)) p++; + + if (*p == '\"' || *p == '\'') + quote = *p++; + + while (*p != '\0') { + if (quote && *p == quote || !quote && av_isspace(*p)) + break; + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + + *q = '\0'; + if (quote && *p == quote) + p++; + *pp = p; +} + +void ffserver_parse_acl_row(FFServerStream *stream, FFServerStream* feed, + FFServerIPAddressACL *ext_acl, + const char *p, const char *filename, int line_num) +{ + char arg[1024]; + FFServerIPAddressACL acl; + FFServerIPAddressACL *nacl; + FFServerIPAddressACL **naclp; + + ffserver_get_arg(arg, sizeof(arg), &p); + if (av_strcasecmp(arg, "allow") == 0) + acl.action = IP_ALLOW; + else if (av_strcasecmp(arg, "deny") == 0) + acl.action = IP_DENY; + else { + fprintf(stderr, "%s:%d: ACL action '%s' should be ALLOW or DENY.\n", + filename, line_num, arg); + goto bail; + } + + ffserver_get_arg(arg, sizeof(arg), &p); + + if (resolve_host(&acl.first, arg)) { + fprintf(stderr, + "%s:%d: ACL refers to invalid host or IP address '%s'\n", + filename, line_num, arg); + goto bail; + } + + acl.last = acl.first; + + ffserver_get_arg(arg, sizeof(arg), &p); + + if (arg[0]) { + if (resolve_host(&acl.last, arg)) { + fprintf(stderr, + "%s:%d: ACL refers to invalid host or IP address '%s'\n", + filename, line_num, arg); + goto bail; + } + } + + nacl = av_mallocz(sizeof(*nacl)); + if (!nacl) { + fprintf(stderr, "Failed to allocate FFServerIPAddressACL\n"); + goto bail; + } + + naclp = 0; + + acl.next = 0; + *nacl = acl; + + if (stream) + naclp = &stream->acl; + else if (feed) + naclp = &feed->acl; + else if (ext_acl) + naclp = &ext_acl; + else + fprintf(stderr, "%s:%d: ACL found not in <Stream> or <Feed>\n", + filename, line_num); + + if (naclp) { + while (*naclp) + naclp = &(*naclp)->next; + + *naclp = nacl; + } else + av_free(nacl); + +bail: + return; + +} + +/* add a codec and set the default parameters */ +static void add_codec(FFServerStream *stream, AVCodecContext *av, + FFServerConfig *config) +{ + LayeredAVStream *st; + AVDictionary **opts, *recommended = NULL; + char *enc_config; + + if(stream->nb_streams >= FF_ARRAY_ELEMS(stream->streams)) + return; + + opts = av->codec_type == AVMEDIA_TYPE_AUDIO ? + &config->audio_opts : &config->video_opts; + av_dict_copy(&recommended, *opts, 0); + av_opt_set_dict2(av->priv_data, opts, AV_OPT_SEARCH_CHILDREN); + av_opt_set_dict2(av, opts, AV_OPT_SEARCH_CHILDREN); + + if (av_dict_count(*opts)) + av_log(NULL, AV_LOG_WARNING, + "Something is wrong, %d options are not set!\n", + av_dict_count(*opts)); + + if (!config->stream_use_defaults) { + switch(av->codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (av->bit_rate == 0) + report_config_error(config->filename, config->line_num, + AV_LOG_ERROR, &config->errors, + "audio bit rate is not set\n"); + if (av->sample_rate == 0) + report_config_error(config->filename, config->line_num, + AV_LOG_ERROR, &config->errors, + "audio sample rate is not set\n"); + break; + case AVMEDIA_TYPE_VIDEO: + if (av->width == 0 || av->height == 0) + report_config_error(config->filename, config->line_num, + AV_LOG_ERROR, &config->errors, + "video size is not set\n"); + break; + default: + av_assert0(0); + } + goto done; + } + + /* stream_use_defaults = true */ + + /* compute default parameters */ + switch(av->codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (!av_dict_get(recommended, "b", NULL, 0)) { + av->bit_rate = 64000; + av_dict_set_int(&recommended, "b", av->bit_rate, 0); + WARNING("Setting default value for audio bit rate = %d. " + "Use NoDefaults to disable it.\n", + av->bit_rate); + } + if (!av_dict_get(recommended, "ar", NULL, 0)) { + av->sample_rate = 22050; + av_dict_set_int(&recommended, "ar", av->sample_rate, 0); + WARNING("Setting default value for audio sample rate = %d. " + "Use NoDefaults to disable it.\n", + av->sample_rate); + } + if (!av_dict_get(recommended, "ac", NULL, 0)) { + av->channels = 1; + av_dict_set_int(&recommended, "ac", av->channels, 0); + WARNING("Setting default value for audio channel count = %d. " + "Use NoDefaults to disable it.\n", + av->channels); + } + break; + case AVMEDIA_TYPE_VIDEO: + if (!av_dict_get(recommended, "b", NULL, 0)) { + av->bit_rate = 64000; + av_dict_set_int(&recommended, "b", av->bit_rate, 0); + WARNING("Setting default value for video bit rate = %d. " + "Use NoDefaults to disable it.\n", + av->bit_rate); + } + if (!av_dict_get(recommended, "time_base", NULL, 0)){ + av->time_base.den = 5; + av->time_base.num = 1; + av_dict_set(&recommended, "time_base", "1/5", 0); + WARNING("Setting default value for video frame rate = %d. " + "Use NoDefaults to disable it.\n", + av->time_base.den); + } + if (!av_dict_get(recommended, "video_size", NULL, 0)) { + av->width = 160; + av->height = 128; + av_dict_set(&recommended, "video_size", "160x128", 0); + WARNING("Setting default value for video size = %dx%d. " + "Use NoDefaults to disable it.\n", + av->width, av->height); + } + /* Bitrate tolerance is less for streaming */ + if (!av_dict_get(recommended, "bt", NULL, 0)) { + av->bit_rate_tolerance = FFMAX(av->bit_rate / 4, + (int64_t)av->bit_rate*av->time_base.num/av->time_base.den); + av_dict_set_int(&recommended, "bt", av->bit_rate_tolerance, 0); + WARNING("Setting default value for video bit rate tolerance = %d. " + "Use NoDefaults to disable it.\n", + av->bit_rate_tolerance); + } + + if (!av_dict_get(recommended, "rc_eq", NULL, 0)) { + av->rc_eq = av_strdup("tex^qComp"); + av_dict_set(&recommended, "rc_eq", "tex^qComp", 0); + WARNING("Setting default value for video rate control equation = " + "%s. Use NoDefaults to disable it.\n", + av->rc_eq); + } + if (!av_dict_get(recommended, "maxrate", NULL, 0)) { + av->rc_max_rate = av->bit_rate * 2; + av_dict_set_int(&recommended, "maxrate", av->rc_max_rate, 0); + WARNING("Setting default value for video max rate = %d. " + "Use NoDefaults to disable it.\n", + av->rc_max_rate); + } + + if (av->rc_max_rate && !av_dict_get(recommended, "bufsize", NULL, 0)) { + av->rc_buffer_size = av->rc_max_rate; + av_dict_set_int(&recommended, "bufsize", av->rc_buffer_size, 0); + WARNING("Setting default value for video buffer size = %d. " + "Use NoDefaults to disable it.\n", + av->rc_buffer_size); + } + break; + default: + abort(); + } + +done: + st = av_mallocz(sizeof(*st)); + if (!st) + return; + av_dict_get_string(recommended, &enc_config, '=', ','); + av_dict_free(&recommended); + st->recommended_encoder_configuration = enc_config; + st->codec = av; + st->codecpar = avcodec_parameters_alloc(); + avcodec_parameters_from_context(st->codecpar, av); + stream->streams[stream->nb_streams++] = st; +} + +static int ffserver_set_codec(AVCodecContext *ctx, const char *codec_name, + FFServerConfig *config) +{ + int ret; + AVCodec *codec = avcodec_find_encoder_by_name(codec_name); + if (!codec || codec->type != ctx->codec_type) { + report_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, + "Invalid codec name: '%s'\n", codec_name); + return 0; + } + if (ctx->codec_id == AV_CODEC_ID_NONE && !ctx->priv_data) { + if ((ret = avcodec_get_context_defaults3(ctx, codec)) < 0) + return ret; + ctx->codec = codec; + } + if (ctx->codec_id != codec->id) + report_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, + "Inconsistent configuration: trying to set '%s' " + "codec option, but '%s' codec is used previously\n", + codec_name, avcodec_get_name(ctx->codec_id)); + return 0; +} + +static int ffserver_opt_preset(const char *arg, int type, FFServerConfig *config) +{ + FILE *f=NULL; + char filename[1000], tmp[1000], tmp2[1000], line[1000]; + int ret = 0; + AVCodecContext *avctx; + const AVCodec *codec; + + switch(type) { + case AV_OPT_FLAG_AUDIO_PARAM: + avctx = config->dummy_actx; + break; + case AV_OPT_FLAG_VIDEO_PARAM: + avctx = config->dummy_vctx; + break; + default: + av_assert0(0); + } + codec = avcodec_find_encoder(avctx->codec_id); + + if (!(f = get_preset_file(filename, sizeof(filename), arg, 0, + codec ? codec->name : NULL))) { + av_log(NULL, AV_LOG_ERROR, "File for preset '%s' not found\n", arg); + return AVERROR(EINVAL); + } + + while(!feof(f)){ + int e= fscanf(f, "%999[^\n]\n", line) - 1; + if(line[0] == '#' && !e) + continue; + e|= sscanf(line, "%999[^=]=%999[^\n]\n", tmp, tmp2) - 2; + if(e){ + av_log(NULL, AV_LOG_ERROR, "%s: Invalid syntax: '%s'\n", filename, + line); + ret = AVERROR(EINVAL); + break; + } + if (!strcmp(tmp, "acodec") && avctx->codec_type == AVMEDIA_TYPE_AUDIO || + !strcmp(tmp, "vcodec") && avctx->codec_type == AVMEDIA_TYPE_VIDEO) + { + if (ffserver_set_codec(avctx, tmp2, config) < 0) + break; + } else if (!strcmp(tmp, "scodec")) { + av_log(NULL, AV_LOG_ERROR, "Subtitles preset found.\n"); + ret = AVERROR(EINVAL); + break; + } else if (ffserver_save_avoption(tmp, tmp2, type, config) < 0) + break; + } + + fclose(f); + + return ret; +} + +static AVOutputFormat *ffserver_guess_format(const char *short_name, + const char *filename, + const char *mime_type) +{ + AVOutputFormat *fmt = av_guess_format(short_name, filename, mime_type); + + if (fmt) { + AVOutputFormat *stream_fmt; + char stream_format_name[64]; + + snprintf(stream_format_name, sizeof(stream_format_name), "%s_stream", + fmt->name); + stream_fmt = av_guess_format(stream_format_name, NULL, NULL); + + if (stream_fmt) + fmt = stream_fmt; + } + + return fmt; +} + +static void vreport_config_error(const char *filename, int line_num, + int log_level, int *errors, const char *fmt, + va_list vl) +{ + av_log(NULL, log_level, "%s:%d: ", filename, line_num); + av_vlog(NULL, log_level, fmt, vl); + if (errors) + (*errors)++; +} + +static void report_config_error(const char *filename, int line_num, + int log_level, int *errors, + const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + vreport_config_error(filename, line_num, log_level, errors, fmt, vl); + va_end(vl); +} + +static int ffserver_set_int_param(int *dest, const char *value, int factor, + int min, int max, FFServerConfig *config, + const char *error_msg, ...) +{ + int tmp; + char *tailp; + if (!value || !value[0]) + goto error; + errno = 0; + tmp = strtol(value, &tailp, 0); + if (tmp < min || tmp > max) + goto error; + if (factor) { + if (tmp == INT_MIN || FFABS(tmp) > INT_MAX / FFABS(factor)) + goto error; + tmp *= factor; + } + if (tailp[0] || errno) + goto error; + if (dest) + *dest = tmp; + return 0; + error: + if (config) { + va_list vl; + va_start(vl, error_msg); + vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, error_msg, vl); + va_end(vl); + } + return AVERROR(EINVAL); +} + +static int ffserver_set_float_param(float *dest, const char *value, + float factor, float min, float max, + FFServerConfig *config, + const char *error_msg, ...) +{ + double tmp; + char *tailp; + if (!value || !value[0]) + goto error; + errno = 0; + tmp = strtod(value, &tailp); + if (tmp < min || tmp > max) + goto error; + if (factor) + tmp *= factor; + if (tailp[0] || errno) + goto error; + if (dest) + *dest = tmp; + return 0; + error: + if (config) { + va_list vl; + va_start(vl, error_msg); + vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, error_msg, vl); + va_end(vl); + } + return AVERROR(EINVAL); +} + +static int ffserver_save_avoption(const char *opt, const char *arg, int type, + FFServerConfig *config) +{ + static int hinted = 0; + int ret = 0; + AVDictionaryEntry *e; + const AVOption *o = NULL; + const char *option = NULL; + const char *codec_name = NULL; + char buff[1024]; + AVCodecContext *ctx; + AVDictionary **dict; + enum AVCodecID guessed_codec_id; + + switch (type) { + case AV_OPT_FLAG_VIDEO_PARAM: + ctx = config->dummy_vctx; + dict = &config->video_opts; + guessed_codec_id = config->guessed_video_codec_id != AV_CODEC_ID_NONE ? + config->guessed_video_codec_id : AV_CODEC_ID_H264; + break; + case AV_OPT_FLAG_AUDIO_PARAM: + ctx = config->dummy_actx; + dict = &config->audio_opts; + guessed_codec_id = config->guessed_audio_codec_id != AV_CODEC_ID_NONE ? + config->guessed_audio_codec_id : AV_CODEC_ID_AAC; + break; + default: + av_assert0(0); + } + + if (strchr(opt, ':')) { + //explicit private option + snprintf(buff, sizeof(buff), "%s", opt); + codec_name = buff; + if(!(option = strchr(buff, ':'))){ + report_config_error(config->filename, config->line_num, + AV_LOG_ERROR, &config->errors, + "Syntax error. Unmatched ':'\n"); + return -1; + + } + buff[option - buff] = '\0'; + option++; + if ((ret = ffserver_set_codec(ctx, codec_name, config)) < 0) + return ret; + if (!ctx->codec || !ctx->priv_data) + return -1; + } else { + option = opt; + } + + o = av_opt_find(ctx, option, NULL, type | AV_OPT_FLAG_ENCODING_PARAM, + AV_OPT_SEARCH_CHILDREN); + if (!o && + (!strcmp(option, "time_base") || !strcmp(option, "pixel_format") || + !strcmp(option, "video_size") || !strcmp(option, "codec_tag"))) + o = av_opt_find(ctx, option, NULL, 0, 0); + if (!o) { + report_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, "Option not found: '%s'\n", opt); + if (!hinted && ctx->codec_id == AV_CODEC_ID_NONE) { + hinted = 1; + report_config_error(config->filename, config->line_num, + AV_LOG_ERROR, NULL, "If '%s' is a codec private" + "option, then prefix it with codec name, for " + "example '%s:%s %s' or define codec earlier.\n", + opt, avcodec_get_name(guessed_codec_id) ,opt, + arg); + } + } else if ((ret = av_opt_set(ctx, option, arg, AV_OPT_SEARCH_CHILDREN)) < 0) { + report_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, "Invalid value for option %s (%s): %s\n", opt, + arg, av_err2str(ret)); + } else if ((e = av_dict_get(*dict, option, NULL, 0))) { + if ((o->type == AV_OPT_TYPE_FLAGS) && arg && + (arg[0] == '+' || arg[0] == '-')) + return av_dict_set(dict, option, arg, AV_DICT_APPEND); + report_config_error(config->filename, config->line_num, AV_LOG_ERROR, + &config->errors, "Redeclaring value of option '%s'." + "Previous value was: '%s'.\n", opt, e->value); + } else if (av_dict_set(dict, option, arg, 0) < 0) { + return AVERROR(ENOMEM); + } + return 0; +} + +static int ffserver_save_avoption_int(const char *opt, int64_t arg, + int type, FFServerConfig *config) +{ + char buf[22]; + snprintf(buf, sizeof(buf), "%"PRId64, arg); + return ffserver_save_avoption(opt, buf, type, config); +} + +static int ffserver_parse_config_global(FFServerConfig *config, const char *cmd, + const char **p) +{ + int val; + char arg[1024]; + if (!av_strcasecmp(cmd, "Port") || !av_strcasecmp(cmd, "HTTPPort")) { + if (!av_strcasecmp(cmd, "Port")) + WARNING("Port option is deprecated. Use HTTPPort instead.\n"); + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, 1, 65535, config, + "Invalid port: %s\n", arg); + if (val < 1024) + WARNING("Trying to use IETF assigned system port: '%d'\n", val); + config->http_addr.sin_port = htons(val); + } else if (!av_strcasecmp(cmd, "HTTPBindAddress") || + !av_strcasecmp(cmd, "BindAddress")) { + if (!av_strcasecmp(cmd, "BindAddress")) + WARNING("BindAddress option is deprecated. Use HTTPBindAddress " + "instead.\n"); + ffserver_get_arg(arg, sizeof(arg), p); + if (resolve_host(&config->http_addr.sin_addr, arg)) + ERROR("Invalid host/IP address: '%s'\n", arg); + } else if (!av_strcasecmp(cmd, "NoDaemon")) { + WARNING("NoDaemon option has no effect. You should remove it.\n"); + } else if (!av_strcasecmp(cmd, "RTSPPort")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, 1, 65535, config, + "Invalid port: %s\n", arg); + config->rtsp_addr.sin_port = htons(val); + } else if (!av_strcasecmp(cmd, "RTSPBindAddress")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (resolve_host(&config->rtsp_addr.sin_addr, arg)) + ERROR("Invalid host/IP address: %s\n", arg); + } else if (!av_strcasecmp(cmd, "MaxHTTPConnections")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, 1, 65535, config, + "Invalid MaxHTTPConnections: %s\n", arg); + config->nb_max_http_connections = val; + if (config->nb_max_connections > config->nb_max_http_connections) { + ERROR("Inconsistent configuration: MaxClients(%d) > " + "MaxHTTPConnections(%d)\n", config->nb_max_connections, + config->nb_max_http_connections); + } + } else if (!av_strcasecmp(cmd, "MaxClients")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, 1, 65535, config, + "Invalid MaxClients: '%s'\n", arg); + config->nb_max_connections = val; + if (config->nb_max_connections > config->nb_max_http_connections) { + ERROR("Inconsistent configuration: MaxClients(%d) > " + "MaxHTTPConnections(%d)\n", config->nb_max_connections, + config->nb_max_http_connections); + } + } else if (!av_strcasecmp(cmd, "MaxBandwidth")) { + int64_t llval; + char *tailp; + ffserver_get_arg(arg, sizeof(arg), p); + errno = 0; + llval = strtoll(arg, &tailp, 10); + if (llval < 10 || llval > 10000000 || tailp[0] || errno) + ERROR("Invalid MaxBandwidth: '%s'\n", arg); + else + config->max_bandwidth = llval; + } else if (!av_strcasecmp(cmd, "CustomLog")) { + if (!config->debug) { + ffserver_get_arg(config->logfilename, sizeof(config->logfilename), + p); + } + } else if (!av_strcasecmp(cmd, "LoadModule")) { + ERROR("Loadable modules are no longer supported\n"); + } else if (!av_strcasecmp(cmd, "NoDefaults")) { + config->use_defaults = 0; + } else if (!av_strcasecmp(cmd, "UseDefaults")) { + config->use_defaults = 1; + } else + ERROR("Incorrect keyword: '%s'\n", cmd); + return 0; +} + +static int ffserver_parse_config_feed(FFServerConfig *config, const char *cmd, + const char **p, FFServerStream **pfeed) +{ + FFServerStream *feed; + char arg[1024]; + av_assert0(pfeed); + feed = *pfeed; + if (!av_strcasecmp(cmd, "<Feed")) { + char *q; + FFServerStream *s; + feed = av_mallocz(sizeof(FFServerStream)); + if (!feed) + return AVERROR(ENOMEM); + ffserver_get_arg(feed->filename, sizeof(feed->filename), p); + q = strrchr(feed->filename, '>'); + if (*q) + *q = '\0'; + + for (s = config->first_feed; s; s = s->next) { + if (!strcmp(feed->filename, s->filename)) + ERROR("Feed '%s' already registered\n", s->filename); + } + + feed->fmt = av_guess_format("ffm", NULL, NULL); + /* default feed file */ + snprintf(feed->feed_filename, sizeof(feed->feed_filename), + "/tmp/%s.ffm", feed->filename); + feed->feed_max_size = 5 * 1024 * 1024; + feed->is_feed = 1; + feed->feed = feed; /* self feeding :-) */ + *pfeed = feed; + return 0; + } + av_assert0(feed); + if (!av_strcasecmp(cmd, "Launch")) { + int i; + + feed->child_argv = av_mallocz_array(MAX_CHILD_ARGS, sizeof(char *)); + if (!feed->child_argv) + return AVERROR(ENOMEM); + for (i = 0; i < MAX_CHILD_ARGS - 2; i++) { + ffserver_get_arg(arg, sizeof(arg), p); + if (!arg[0]) + break; + + feed->child_argv[i] = av_strdup(arg); + if (!feed->child_argv[i]) + return AVERROR(ENOMEM); + } + + feed->child_argv[i] = + av_asprintf("http://%s:%d/%s", + (config->http_addr.sin_addr.s_addr == INADDR_ANY) ? + "127.0.0.1" : inet_ntoa(config->http_addr.sin_addr), + ntohs(config->http_addr.sin_port), feed->filename); + if (!feed->child_argv[i]) + return AVERROR(ENOMEM); + } else if (!av_strcasecmp(cmd, "ACL")) { + ffserver_parse_acl_row(NULL, feed, NULL, *p, config->filename, + config->line_num); + } else if (!av_strcasecmp(cmd, "File") || + !av_strcasecmp(cmd, "ReadOnlyFile")) { + ffserver_get_arg(feed->feed_filename, sizeof(feed->feed_filename), p); + feed->readonly = !av_strcasecmp(cmd, "ReadOnlyFile"); + } else if (!av_strcasecmp(cmd, "Truncate")) { + ffserver_get_arg(arg, sizeof(arg), p); + /* assume Truncate is true in case no argument is specified */ + if (!arg[0]) { + feed->truncate = 1; + } else { + WARNING("Truncate N syntax in configuration file is deprecated. " + "Use Truncate alone with no arguments.\n"); + feed->truncate = strtod(arg, NULL); + } + } else if (!av_strcasecmp(cmd, "FileMaxSize")) { + char *p1; + double fsize; + + ffserver_get_arg(arg, sizeof(arg), p); + p1 = arg; + fsize = strtod(p1, &p1); + switch(av_toupper(*p1)) { + case 'K': + fsize *= 1024; + break; + case 'M': + fsize *= 1024 * 1024; + break; + case 'G': + fsize *= 1024 * 1024 * 1024; + break; + default: + ERROR("Invalid file size: '%s'\n", arg); + break; + } + feed->feed_max_size = (int64_t)fsize; + if (feed->feed_max_size < FFM_PACKET_SIZE*4) { + ERROR("Feed max file size is too small. Must be at least %d.\n", + FFM_PACKET_SIZE*4); + } + } else if (!av_strcasecmp(cmd, "</Feed>")) { + *pfeed = NULL; + } else { + ERROR("Invalid entry '%s' inside <Feed></Feed>\n", cmd); + } + return 0; +} + +static int ffserver_parse_config_stream(FFServerConfig *config, const char *cmd, + const char **p, + FFServerStream **pstream) +{ + char arg[1024], arg2[1024]; + FFServerStream *stream; + int val; + + av_assert0(pstream); + stream = *pstream; + + if (!av_strcasecmp(cmd, "<Stream")) { + char *q; + FFServerStream *s; + stream = av_mallocz(sizeof(FFServerStream)); + if (!stream) + return AVERROR(ENOMEM); + config->dummy_actx = avcodec_alloc_context3(NULL); + config->dummy_vctx = avcodec_alloc_context3(NULL); + if (!config->dummy_vctx || !config->dummy_actx) { + av_free(stream); + avcodec_free_context(&config->dummy_vctx); + avcodec_free_context(&config->dummy_actx); + return AVERROR(ENOMEM); + } + config->dummy_actx->codec_type = AVMEDIA_TYPE_AUDIO; + config->dummy_vctx->codec_type = AVMEDIA_TYPE_VIDEO; + ffserver_get_arg(stream->filename, sizeof(stream->filename), p); + q = strrchr(stream->filename, '>'); + if (q) + *q = '\0'; + + for (s = config->first_stream; s; s = s->next) { + if (!strcmp(stream->filename, s->filename)) + ERROR("Stream '%s' already registered\n", s->filename); + } + + stream->fmt = ffserver_guess_format(NULL, stream->filename, NULL); + if (stream->fmt) { + config->guessed_audio_codec_id = stream->fmt->audio_codec; + config->guessed_video_codec_id = stream->fmt->video_codec; + } else { + config->guessed_audio_codec_id = AV_CODEC_ID_NONE; + config->guessed_video_codec_id = AV_CODEC_ID_NONE; + } + config->stream_use_defaults = config->use_defaults; + *pstream = stream; + return 0; + } + av_assert0(stream); + if (!av_strcasecmp(cmd, "Feed")) { + FFServerStream *sfeed; + ffserver_get_arg(arg, sizeof(arg), p); + sfeed = config->first_feed; + while (sfeed) { + if (!strcmp(sfeed->filename, arg)) + break; + sfeed = sfeed->next_feed; + } + if (!sfeed) + ERROR("Feed with name '%s' for stream '%s' is not defined\n", arg, + stream->filename); + else + stream->feed = sfeed; + } else if (!av_strcasecmp(cmd, "Format")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (!strcmp(arg, "status")) { + stream->stream_type = STREAM_TYPE_STATUS; + stream->fmt = NULL; + } else { + stream->stream_type = STREAM_TYPE_LIVE; + /* JPEG cannot be used here, so use single frame MJPEG */ + if (!strcmp(arg, "jpeg")) { + strcpy(arg, "singlejpeg"); + stream->single_frame=1; + } + stream->fmt = ffserver_guess_format(arg, NULL, NULL); + if (!stream->fmt) + ERROR("Unknown Format: '%s'\n", arg); + } + if (stream->fmt) { + config->guessed_audio_codec_id = stream->fmt->audio_codec; + config->guessed_video_codec_id = stream->fmt->video_codec; + } + } else if (!av_strcasecmp(cmd, "InputFormat")) { + ffserver_get_arg(arg, sizeof(arg), p); + stream->ifmt = av_find_input_format(arg); + if (!stream->ifmt) + ERROR("Unknown input format: '%s'\n", arg); + } else if (!av_strcasecmp(cmd, "FaviconURL")) { + if (stream->stream_type == STREAM_TYPE_STATUS) + ffserver_get_arg(stream->feed_filename, + sizeof(stream->feed_filename), p); + else + ERROR("FaviconURL only permitted for status streams\n"); + } else if (!av_strcasecmp(cmd, "Author") || + !av_strcasecmp(cmd, "Comment") || + !av_strcasecmp(cmd, "Copyright") || + !av_strcasecmp(cmd, "Title")) { + char key[32]; + int i; + ffserver_get_arg(arg, sizeof(arg), p); + for (i = 0; i < strlen(cmd); i++) + key[i] = av_tolower(cmd[i]); + key[i] = 0; + WARNING("Deprecated '%s' option in configuration file. Use " + "'Metadata %s VALUE' instead.\n", cmd, key); + if (av_dict_set(&stream->metadata, key, arg, 0) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "Metadata")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_get_arg(arg2, sizeof(arg2), p); + if (av_dict_set(&stream->metadata, arg, arg2, 0) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "Preroll")) { + ffserver_get_arg(arg, sizeof(arg), p); + stream->prebuffer = atof(arg) * 1000; + } else if (!av_strcasecmp(cmd, "StartSendOnKey")) { + stream->send_on_key = 1; + } else if (!av_strcasecmp(cmd, "AudioCodec")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_codec(config->dummy_actx, arg, config); + } else if (!av_strcasecmp(cmd, "VideoCodec")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_codec(config->dummy_vctx, arg, config); + } else if (!av_strcasecmp(cmd, "MaxTime")) { + ffserver_get_arg(arg, sizeof(arg), p); + stream->max_time = atof(arg) * 1000; + } else if (!av_strcasecmp(cmd, "AudioBitRate")) { + float f; + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_float_param(&f, arg, 1000, -FLT_MAX, FLT_MAX, config, + "Invalid %s: '%s'\n", cmd, arg); + if (ffserver_save_avoption_int("b", (int64_t)lrintf(f), + AV_OPT_FLAG_AUDIO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "AudioChannels")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("ac", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "AudioSampleRate")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("ar", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoBitRateRange")) { + int minrate, maxrate; + char *dash; + ffserver_get_arg(arg, sizeof(arg), p); + dash = strchr(arg, '-'); + if (dash) { + *dash = '\0'; + dash++; + if (ffserver_set_int_param(&minrate, arg, 1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0 && + ffserver_set_int_param(&maxrate, dash, 1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0) { + if (ffserver_save_avoption_int("minrate", minrate, AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || + ffserver_save_avoption_int("maxrate", maxrate, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } + } else + ERROR("Incorrect format for VideoBitRateRange. It should be " + "<min>-<max>: '%s'.\n", arg); + } else if (!av_strcasecmp(cmd, "Debug")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("debug", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 || + ffserver_save_avoption("debug", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "Strict")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("strict", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 || + ffserver_save_avoption("strict", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoBufferSize")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 8*1024, 0, INT_MAX, config, + "Invalid %s: '%s'", cmd, arg); + if (ffserver_save_avoption_int("bufsize", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoBitRateTolerance")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 1000, INT_MIN, INT_MAX, config, + "Invalid %s: '%s'", cmd, arg); + if (ffserver_save_avoption_int("bt", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoBitRate")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 1000, INT_MIN, INT_MAX, config, + "Invalid %s: '%s'", cmd, arg); + if (ffserver_save_avoption_int("b", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoSize")) { + int ret, w, h; + ffserver_get_arg(arg, sizeof(arg), p); + ret = av_parse_video_size(&w, &h, arg); + if (ret < 0) + ERROR("Invalid video size '%s'\n", arg); + else { + if (w % 2 || h % 2) + WARNING("Image size is not a multiple of 2\n"); + if (ffserver_save_avoption("video_size", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } + } else if (!av_strcasecmp(cmd, "VideoFrameRate")) { + ffserver_get_arg(&arg[2], sizeof(arg) - 2, p); + arg[0] = '1'; arg[1] = '/'; + if (ffserver_save_avoption("time_base", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "PixelFormat")) { + enum AVPixelFormat pix_fmt; + ffserver_get_arg(arg, sizeof(arg), p); + pix_fmt = av_get_pix_fmt(arg); + if (pix_fmt == AV_PIX_FMT_NONE) + ERROR("Unknown pixel format: '%s'\n", arg); + else if (ffserver_save_avoption("pixel_format", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoGopSize")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("g", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoIntraOnly")) { + if (ffserver_save_avoption("g", "1", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoHighQuality")) { + if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "Video4MotionVector")) { + if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || //FIXME remove + ffserver_save_avoption("flags", "+mv4", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "AVOptionVideo") || + !av_strcasecmp(cmd, "AVOptionAudio")) { + int ret; + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_get_arg(arg2, sizeof(arg2), p); + if (!av_strcasecmp(cmd, "AVOptionVideo")) + ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_VIDEO_PARAM, + config); + else + ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_AUDIO_PARAM, + config); + if (ret < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "AVPresetVideo") || + !av_strcasecmp(cmd, "AVPresetAudio")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (!av_strcasecmp(cmd, "AVPresetVideo")) + ffserver_opt_preset(arg, AV_OPT_FLAG_VIDEO_PARAM, config); + else + ffserver_opt_preset(arg, AV_OPT_FLAG_AUDIO_PARAM, config); + } else if (!av_strcasecmp(cmd, "VideoTag")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (strlen(arg) == 4 && + ffserver_save_avoption_int("codec_tag", + MKTAG(arg[0], arg[1], arg[2], arg[3]), + AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "BitExact")) { + config->bitexact = 1; + if (ffserver_save_avoption("flags", "+bitexact", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "DctFastint")) { + if (ffserver_save_avoption("dct", "fastint", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "IdctSimple")) { + if (ffserver_save_avoption("idct", "simple", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "Qscale")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, INT_MIN, INT_MAX, config, + "Invalid Qscale: '%s'\n", arg); + if (ffserver_save_avoption("flags", "+qscale", AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || + ffserver_save_avoption_int("global_quality", FF_QP2LAMBDA * val, + AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoQDiff")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("qdiff", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoQMax")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("qmax", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "VideoQMin")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("qmin", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "LumiMask")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("lumi_mask", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "DarkMask")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (ffserver_save_avoption("dark_mask", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) + goto nomem; + } else if (!av_strcasecmp(cmd, "NoVideo")) { + config->no_video = 1; + } else if (!av_strcasecmp(cmd, "NoAudio")) { + config->no_audio = 1; + } else if (!av_strcasecmp(cmd, "ACL")) { + ffserver_parse_acl_row(stream, NULL, NULL, *p, config->filename, + config->line_num); + } else if (!av_strcasecmp(cmd, "DynamicACL")) { + ffserver_get_arg(stream->dynamic_acl, sizeof(stream->dynamic_acl), p); + } else if (!av_strcasecmp(cmd, "RTSPOption")) { + ffserver_get_arg(arg, sizeof(arg), p); + av_freep(&stream->rtsp_option); + stream->rtsp_option = av_strdup(arg); + } else if (!av_strcasecmp(cmd, "MulticastAddress")) { + ffserver_get_arg(arg, sizeof(arg), p); + if (resolve_host(&stream->multicast_ip, arg)) + ERROR("Invalid host/IP address: '%s'\n", arg); + stream->is_multicast = 1; + stream->loop = 1; /* default is looping */ + } else if (!av_strcasecmp(cmd, "MulticastPort")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, 1, 65535, config, + "Invalid MulticastPort: '%s'\n", arg); + stream->multicast_port = val; + } else if (!av_strcasecmp(cmd, "MulticastTTL")) { + ffserver_get_arg(arg, sizeof(arg), p); + ffserver_set_int_param(&val, arg, 0, INT_MIN, INT_MAX, config, + "Invalid MulticastTTL: '%s'\n", arg); + stream->multicast_ttl = val; + } else if (!av_strcasecmp(cmd, "NoLoop")) { + stream->loop = 0; + } else if (!av_strcasecmp(cmd, "</Stream>")) { + config->stream_use_defaults &= 1; + if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm")) { + if (config->dummy_actx->codec_id == AV_CODEC_ID_NONE) + config->dummy_actx->codec_id = config->guessed_audio_codec_id; + if (!config->no_audio && + config->dummy_actx->codec_id != AV_CODEC_ID_NONE) { + AVCodecContext *audio_enc = avcodec_alloc_context3(avcodec_find_encoder(config->dummy_actx->codec_id)); + add_codec(stream, audio_enc, config); + } + if (config->dummy_vctx->codec_id == AV_CODEC_ID_NONE) + config->dummy_vctx->codec_id = config->guessed_video_codec_id; + if (!config->no_video && + config->dummy_vctx->codec_id != AV_CODEC_ID_NONE) { + AVCodecContext *video_enc = avcodec_alloc_context3(avcodec_find_encoder(config->dummy_vctx->codec_id)); + add_codec(stream, video_enc, config); + } + } + av_dict_free(&config->video_opts); + av_dict_free(&config->audio_opts); + avcodec_free_context(&config->dummy_vctx); + avcodec_free_context(&config->dummy_actx); + config->no_video = 0; + config->no_audio = 0; + *pstream = NULL; + } else if (!av_strcasecmp(cmd, "File") || + !av_strcasecmp(cmd, "ReadOnlyFile")) { + ffserver_get_arg(stream->feed_filename, sizeof(stream->feed_filename), + p); + } else if (!av_strcasecmp(cmd, "UseDefaults")) { + if (config->stream_use_defaults > 1) + WARNING("Multiple UseDefaults/NoDefaults entries.\n"); + config->stream_use_defaults = 3; + } else if (!av_strcasecmp(cmd, "NoDefaults")) { + if (config->stream_use_defaults > 1) + WARNING("Multiple UseDefaults/NoDefaults entries.\n"); + config->stream_use_defaults = 2; + } else { + ERROR("Invalid entry '%s' inside <Stream></Stream>\n", cmd); + } + return 0; + nomem: + av_log(NULL, AV_LOG_ERROR, "Out of memory. Aborting.\n"); + av_dict_free(&config->video_opts); + av_dict_free(&config->audio_opts); + avcodec_free_context(&config->dummy_vctx); + avcodec_free_context(&config->dummy_actx); + return AVERROR(ENOMEM); +} + +static int ffserver_parse_config_redirect(FFServerConfig *config, + const char *cmd, const char **p, + FFServerStream **predirect) +{ + FFServerStream *redirect; + av_assert0(predirect); + redirect = *predirect; + + if (!av_strcasecmp(cmd, "<Redirect")) { + char *q; + redirect = av_mallocz(sizeof(FFServerStream)); + if (!redirect) + return AVERROR(ENOMEM); + + ffserver_get_arg(redirect->filename, sizeof(redirect->filename), p); + q = strrchr(redirect->filename, '>'); + if (*q) + *q = '\0'; + redirect->stream_type = STREAM_TYPE_REDIRECT; + *predirect = redirect; + return 0; + } + av_assert0(redirect); + if (!av_strcasecmp(cmd, "URL")) { + ffserver_get_arg(redirect->feed_filename, + sizeof(redirect->feed_filename), p); + } else if (!av_strcasecmp(cmd, "</Redirect>")) { + if (!redirect->feed_filename[0]) + ERROR("No URL found for <Redirect>\n"); + *predirect = NULL; + } else { + ERROR("Invalid entry '%s' inside <Redirect></Redirect>\n", cmd); + } + return 0; +} + +int ffserver_parse_ffconfig(const char *filename, FFServerConfig *config) +{ + FILE *f; + char line[1024]; + char cmd[64]; + const char *p; + FFServerStream **last_stream, *stream = NULL, *redirect = NULL; + FFServerStream **last_feed, *feed = NULL; + int ret = 0; + + av_assert0(config); + + f = fopen(filename, "r"); + if (!f) { + ret = AVERROR(errno); + av_log(NULL, AV_LOG_ERROR, + "Could not open the configuration file '%s'\n", filename); + return ret; + } + + config->first_stream = NULL; + config->first_feed = NULL; + config->errors = config->warnings = 0; + + last_stream = &config->first_stream; + last_feed = &config->first_feed; + + config->line_num = 0; + while (fgets(line, sizeof(line), f) != NULL) { + config->line_num++; + p = line; + while (av_isspace(*p)) + p++; + if (*p == '\0' || *p == '#') + continue; + + ffserver_get_arg(cmd, sizeof(cmd), &p); + + if (feed || !av_strcasecmp(cmd, "<Feed")) { + int opening = !av_strcasecmp(cmd, "<Feed"); + if (opening && (stream || feed || redirect)) { + ERROR("Already in a tag\n"); + } else { + ret = ffserver_parse_config_feed(config, cmd, &p, &feed); + if (ret < 0) + break; + if (opening) { + /* add in stream & feed list */ + *last_stream = feed; + *last_feed = feed; + last_stream = &feed->next; + last_feed = &feed->next_feed; + } + } + } else if (stream || !av_strcasecmp(cmd, "<Stream")) { + int opening = !av_strcasecmp(cmd, "<Stream"); + if (opening && (stream || feed || redirect)) { + ERROR("Already in a tag\n"); + } else { + ret = ffserver_parse_config_stream(config, cmd, &p, &stream); + if (ret < 0) + break; + if (opening) { + /* add in stream list */ + *last_stream = stream; + last_stream = &stream->next; + } + } + } else if (redirect || !av_strcasecmp(cmd, "<Redirect")) { + int opening = !av_strcasecmp(cmd, "<Redirect"); + if (opening && (stream || feed || redirect)) + ERROR("Already in a tag\n"); + else { + ret = ffserver_parse_config_redirect(config, cmd, &p, + &redirect); + if (ret < 0) + break; + if (opening) { + /* add in stream list */ + *last_stream = redirect; + last_stream = &redirect->next; + } + } + } else { + ffserver_parse_config_global(config, cmd, &p); + } + } + if (stream || feed || redirect) + ERROR("Missing closing </%s> tag\n", + stream ? "Stream" : (feed ? "Feed" : "Redirect")); + + fclose(f); + if (ret < 0) + return ret; + if (config->errors) + return AVERROR(EINVAL); + else + return 0; +} + +#undef ERROR +#undef WARNING + +void ffserver_free_child_args(void *argsp) +{ + int i; + char **args; + if (!argsp) + return; + args = *(char ***)argsp; + if (!args) + return; + for (i = 0; i < MAX_CHILD_ARGS; i++) + av_free(args[i]); + av_freep(argsp); +} diff --git a/fftools/ffserver_config.h b/fftools/ffserver_config.h new file mode 100644 index 0000000000..089b8484da --- /dev/null +++ b/fftools/ffserver_config.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FFTOOLS_FFSERVER_CONFIG_H +#define FFTOOLS_FFSERVER_CONFIG_H + +#define FFM_PACKET_SIZE 4096 + +#include "libavutil/dict.h" +#include "libavformat/avformat.h" +#include "libavformat/network.h" + +#define FFSERVER_MAX_STREAMS 20 + +/* each generated stream is described here */ +enum FFServerStreamType { + STREAM_TYPE_LIVE, + STREAM_TYPE_STATUS, + STREAM_TYPE_REDIRECT, +}; + +enum FFServerIPAddressAction { + IP_ALLOW = 1, + IP_DENY, +}; + +typedef struct FFServerIPAddressACL { + struct FFServerIPAddressACL *next; + enum FFServerIPAddressAction action; + /* These are in host order */ + struct in_addr first; + struct in_addr last; +} FFServerIPAddressACL; + +/** + * This holds the stream parameters for an AVStream, it cannot be a AVStream + * because AVStreams cannot be instanciated without a AVFormatContext, especially + * not outside libavformat. + * + * The fields of this struct have the same semantics as the fields of an AVStream. + */ +typedef struct LayeredAVStream { + int index; + int id; + AVCodecParameters *codecpar; + AVCodecContext *codec; + AVRational time_base; + int pts_wrap_bits; + AVRational sample_aspect_ratio; + char *recommended_encoder_configuration; +} LayeredAVStream; + +/* description of each stream of the ffserver.conf file */ +typedef struct FFServerStream { + enum FFServerStreamType stream_type; + char filename[1024]; /* stream filename */ + struct FFServerStream *feed; /* feed we are using (can be null if coming from file) */ + AVDictionary *in_opts; /* input parameters */ + AVDictionary *metadata; /* metadata to set on the stream */ + AVInputFormat *ifmt; /* if non NULL, force input format */ + AVOutputFormat *fmt; + FFServerIPAddressACL *acl; + char dynamic_acl[1024]; + int nb_streams; + int prebuffer; /* Number of milliseconds early to start */ + int64_t max_time; /* Number of milliseconds to run */ + int send_on_key; + LayeredAVStream *streams[FFSERVER_MAX_STREAMS]; + int feed_streams[FFSERVER_MAX_STREAMS]; /* index of streams in the feed */ + char feed_filename[1024]; /* file name of the feed storage, or + input file name for a stream */ + pid_t pid; /* Of ffmpeg process */ + time_t pid_start; /* Of ffmpeg process */ + char **child_argv; + struct FFServerStream *next; + unsigned bandwidth; /* bandwidth, in kbits/s */ + /* RTSP options */ + char *rtsp_option; + /* multicast specific */ + int is_multicast; + struct in_addr multicast_ip; + int multicast_port; /* first port used for multicast */ + int multicast_ttl; + int loop; /* if true, send the stream in loops (only meaningful if file) */ + char single_frame; /* only single frame */ + + /* feed specific */ + int feed_opened; /* true if someone is writing to the feed */ + int is_feed; /* true if it is a feed */ + int readonly; /* True if writing is prohibited to the file */ + int truncate; /* True if feeder connection truncate the feed file */ + int conns_served; + int64_t bytes_served; + int64_t feed_max_size; /* maximum storage size, zero means unlimited */ + int64_t feed_write_index; /* current write position in feed (it wraps around) */ + int64_t feed_size; /* current size of feed */ + struct FFServerStream *next_feed; +} FFServerStream; + +typedef struct FFServerConfig { + char *filename; + FFServerStream *first_feed; /* contains only feeds */ + FFServerStream *first_stream; /* contains all streams, including feeds */ + unsigned int nb_max_http_connections; + unsigned int nb_max_connections; + uint64_t max_bandwidth; + int debug; + int bitexact; + char logfilename[1024]; + struct sockaddr_in http_addr; + struct sockaddr_in rtsp_addr; + int errors; + int warnings; + int use_defaults; + // Following variables MUST NOT be used outside configuration parsing code. + enum AVCodecID guessed_audio_codec_id; + enum AVCodecID guessed_video_codec_id; + AVDictionary *video_opts; /* AVOptions for video encoder */ + AVDictionary *audio_opts; /* AVOptions for audio encoder */ + AVCodecContext *dummy_actx; /* Used internally to test audio AVOptions. */ + AVCodecContext *dummy_vctx; /* Used internally to test video AVOptions. */ + int no_audio; + int no_video; + int line_num; + int stream_use_defaults; +} FFServerConfig; + +void ffserver_get_arg(char *buf, int buf_size, const char **pp); + +void ffserver_parse_acl_row(FFServerStream *stream, FFServerStream* feed, + FFServerIPAddressACL *ext_acl, + const char *p, const char *filename, int line_num); + +int ffserver_parse_ffconfig(const char *filename, FFServerConfig *config); + +void ffserver_free_child_args(void *argsp); + +#endif /* FFTOOLS_FFSERVER_CONFIG_H */ |