ffmpeg.c源码中关于参数设置的流程

来源:互联网 发布:linux c gbk转utf8 编辑:程序博客网 时间:2024/05/16 00:51

终于开始了自己的处女作,对于ffmpeg本人还是个小学生,如果有写的不对的地方,还请不吝赐教,再次感谢!

ffmpeg的版本为3.1.3,是当前的最新版。

涉及到的文件有:

  1. ffmpeg.c ffmpeg.h
  2. cmdutils.c cmdutils.h
  3. 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,                       {              &copy_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 : 0int 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的命令行参数的分析也就告一段落了。

0 0
原创粉丝点击