终于开始了自己的处女作,对于ffmpeg本人还是个小学生,如果有写的不对的地方,还请不吝赐教,再次感谢!
ffmpeg的版本为3.1.3,是当前的最新版。
涉及到的文件有:
- ffmpeg.c ffmpeg.h
- cmdutils.c cmdutils.h
- ffmpeg_opt.c ffmpeg_filter.c
其它文件如:
ffmpeg_cuvid.c ffmpeg_dxva2.c ffmpeg_qsv.c ffmpeg_vaapi.c ffmpeg_vdpau.c ffmpeg_videotoolbox.c
都是与硬件相关的,在编译时需要加对应的参数。不在此讨论范围之内。参考如下:
ffmpeg.h中
enum HWAccelID {
HWACCEL_NONE = 0,
HWACCEL_AUTO,
HWACCEL_VDPAU,
HWACCEL_DXVA2,
HWACCEL_VDA,
HWACCEL_VIDEOTOOLBOX,
HWACCEL_QSV,
HWACCEL_VAAPI,
HWACCEL_CUVID,
};
typedef struct HWAccel {
const char *name;
int (*init)(AVCodecContext *s);
enum HWAccelID id;
enum AVPixelFormat pix_fmt;
} HWAccel;
ffmpeg_opt.c中
const HWAccel hwaccels[] = {
#if HAVE_VDPAU_X11
{ "vdpau", vdpau_init, HWACCEL_VDPAU, AV_PIX_FMT_VDPAU },
#endif
#if HAVE_DXVA2_LIB
{ "dxva2", dxva2_init, HWACCEL_DXVA2, AV_PIX_FMT_DXVA2_VLD },
#endif
#if CONFIG_VDA
{ "vda", videotoolbox_init, HWACCEL_VDA, AV_PIX_FMT_VDA },
#endif
#if CONFIG_VIDEOTOOLBOX
{ "videotoolbox", videotoolbox_init, HWACCEL_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX },
#endif
#if CONFIG_LIBMFX
{ "qsv", qsv_init, HWACCEL_QSV, AV_PIX_FMT_QSV },
#endif
#if CONFIG_VAAPI
{ "vaapi", vaapi_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI },
#endif
#if CONFIG_CUVID
{ "cuvid", cuvid_init, HWACCEL_CUVID, AV_PIX_FMT_CUDA },
#endif
{ 0 },
};
int hwaccel_lax_profile_check = 0;
AVBufferRef *hw_device_ctx;
宏定义在config.h中都能找到。是否定义为1,是有编译选项决定的。有兴趣的可以进一步了解。再此不再赘述。
进入主题。
在main函数中,如下函数解析参数和处理输入和输出文件
/* parse options and open all input/output files */
ret = ffmpeg_parse_options(argc, argv);
if (ret < 0)
exit_program(1);
在此函数中:
int Cffmpeg::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将命令行参数进行分析,并存入octx中 */
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;
}
/* open input files设置输入文件相关的参数,并打开输入文件 */
ret = open_files(&octx.groups[GROUP_INFILE], "input");
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");
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error opening output files: ");
goto fail;
}
/* configure the complex filtergraphs 配置滤镜*/
ret = configure_complex_filters();
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error configuring complex filters.\n");
goto fail;
}
fail:
uninit_parse_context(&octx);//释放octx
if (ret < 0) {
av_strerror(ret, (char*)error, sizeof(error));
av_log(NULL, AV_LOG_FATAL, "%s\n", error);
}
return ret;
}
split_commandline()的参数解析如下:
1.OptionParseContext octx;用来将命令行中的参数通过分析分别存入此结构体的对应变量中。之后argc, argv就没用了。
2.options,定义了所有的参数、参数类型、参数是否有值、是放到全局变量中还是放到OptionsContext结构的某个变量中,或者是调用一个函数来处理参数的值。
定义如下:
#define OFFSET(x) offsetof(OptionsContext, x)
const OptionDef options[] = {
/* main options */
#include "cmdutils_common_opts.h"
{ "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]]" },
类似&file_overwrite参数的值放到全局变量file_overwrite中。
类似.off = OFFSET(format) 参数的值放到结构体OptionsContext的format中。这个结构体变量将在open_files中定义。
类似.func_arg = opt_map表示要调用opt_map函数处理此参数的值。
3.参数groups
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;
static const OptionGroupDef groups[] = {
[GROUP_OUTFILE] = { "output file", NULL, OPT_OUTPUT },
[GROUP_INFILE] = { "input file", "i", OPT_INPUT },
};
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 看是不是-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 不是输入结束,输出结束在options中找是否存在*/
po = find_option(options, opt);
if (po->name) {
if (po->flags & OPT_EXIT) {
/* optional argument, e.g. -h 遇到这个参数是要直接退出程序的,在write_option()中在最后有判断*/
arg = argv[optindex++];
} else if (po->flags & HAS_ARG) {//如果需要有参数的就获取参数
GET_ARG(arg);
} else {//不需要参数的就设置默认值为“1”
arg = "1";
}
/*根据先前在options中对参数的定义判断,是加到全局队列global_opts中,还是加到临时队列cur_group中。*/
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 如果没有在options中找到,那可能是某个库独特的参数在此处理*/
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 在options中所列的参数前加上-no的处理在此,但是得是OPT_BOOL的属性。值设为0.*/
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;
}
处理命令行参数的相关结构体关系如下图:
init_parse_context()函数的作用是将groups放到octx中的OptionGroupList *groups;中。nb_groups;设置为2.global_opts初始化。
其中global_opts是用来存放与输入输出文件无关的变量。
groups[0]存放于输出文件有关的参数
groups[1]存放于输入文件有关的参数
由于ffmpeg的命令行参数规则如下:
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
所以全局参数,输入参数,输出参数是相对分离开的。
在OptionParseContext中OptionGroup cur_group;变量就充当了临时存放一组数据的作用。会在finish_group()中移动到对应的队列中。可能是输入文件队列也可能是输出文件队列。全局的队列直接存入。是存入cur_group还是存入global_opts中在函数add_opt()中决定。
/*
* 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));
}
同时将全局变量sws_dict,swr_opts,codec_opts,format_opts,resample_opts,也放到了一起。这属于一组参数。
这些类型的参数在opt_default()中处理。
#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;
}
sws_dict:在libswscale\options.c中
swr_opts:在libswresample\options.c中
codec_opts:在libavcodec\options.c中
format_opts:在libavformat\options.c中
resample_opts:在libavresample\options.c中
从如下声明中就可以看出来
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
至此,split_commandline()结束。所有的命令行参数都已经放到了OptionParseContext octx中。
parse_optgroup()用来处理已经放到octx中的参数。在ffmpeg_parse_options()中和open_files()中都有调用。分别处理octx.global_opts全局参数、octx.groups[GROUP_INFILE]输入文件参数、octx.groups[GROUP_OUTFILE]输出文件参数。
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;
}
此函数就是逐个取出参数调用write_option()。write_option()根据预定好的设置规则。是设置到全局变量,还是到OptionsContext结构体的变量中。这个变量在open_files()中声明,并会传入此函数的第一个参数optctx中。了解此规则后,想要知道参数具体是设置到了哪里。只需要参考options定义就好了。但是大家有没有发现这里只处理了。OptionGroup结构体中的Option *opts;这一项。codec_opts,format_opts,resample_opts,sws_dict,swr_opts这些项还没有处理。这些都会在open_input_file()和open_output_file()中用到。这里不做解释。等到分析这两个函数的时候再具体说。
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;
}
之前提到过的。如果参数预制了OPT_EXIT标识,在write_option()中会退出程序。如上代码可知。
exit_program()很简单。如下
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);
}
就是执行一个函数指针指向的函数。此指针由register_exit()在main函数中设置。
int main(int argc, char **argv)
{
int ret;
int64_t ti;
init_dynload();
register_exit(ffmpeg_cleanup);
所以相当于执行ffmpeg_cleanup(),后退出。ffmpeg_cleanup()处理程序运行过程中的内存释放。
之后ffmpeg_parse_options()开始打开输入文件和输出文件,避免返回到上面看此函数代码。将部分代码贴于此。
/* 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;
}
/* configure the complex filtergraphs */
ret = configure_complex_filters();
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Error configuring complex filters.\n");
goto fail;
}
有两个open_files()分别将open_input_file()和open_output_file()作为回调传入。意思很明显,就是要进行输入文件和输出文件的初始化。
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;
}
就是调用了parse_optgroup()、open_input_file()和open_output_file()函数。后两个函数不在此讨论。
另外两个函数init_complex_filters()和configure_complex_filters(),是处理滤镜用的。在options中有如下定义:
{ "filter_complex", HAS_ARG | OPT_EXPERT, { .func_arg = opt_filter_complex },
"create a complex filtergraph", "graph_description" },
{ "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" },
对应的两个函数
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;
}
至于需要传入什么参数,得看libavfilter库的支持。还没有研究过。
ffmpeg_parse_options()最后要调用uninit_parse_context(&octx);释放存入octx的参数。命令行参数完成了它这一阶段的使命。
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();
}
至此,ffmpeg的命令行参数从main函数中传入,到分析出各个参数对,再到存入octx中,再到将值存入预先指定的变量中,最后释放内存。一系列动作执行完以后,ffmpeg的命令行参数的分析也就告一段落了。