ffmpeg 源码分析 命令参数篇(二)

接上一篇,接着分析 命令参数 从 OptionGroup 类型的数组列表中的 arg 和 *opts 是如何转移到 OptionsContext 中的,不过 OptionsContext的变量也是 临时变量

这里要先看一下 全局变量 options的 定义(ffmpeg_opt.c),只看其中的几个元素,便于理解.

#define OFFSET(x) offsetof(OptionsContext, x)
const OptionDef options[] = {


{ "to", HAS_ARG | OPT_TIME | OPT_OFFSET | OPT_OUTPUT, 
 { .off = OFFSET(stop_time) },   
 "record or transcode stop time", "time_stop"
  },

{ "vframes",   OPT_VIDEO | HAS_ARG  | OPT_PERFILE | OPT_OUTPUT, 
   { .func_arg = opt_video_frames },  
   "set the number of video frames to output", "number" },
 
 
}

这里列举了两个元素 一个是 带有 { .off = OFFSET(stop_time) } ,另一个是带有
{ .func_arg = opt_video_frames },

{ .off = OFFSET(stop_time) } 的意思是 命令参数的值 要存放在 OptionsContext 中的 stop_time 变量中
{ .func_arg = opt_video_frames } 的意思是 命令参数值 作为参数被
opt_video_frames函数调用处理

主要有三个函数处理完成的:
open_files
parse_optgroup
write_option

先看一下 ffmpeg_parse_options 函数 ,主要里面的注释

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 */
	//这里的参数为&octx.groups[GROUP_INFILE] 意味着要处理输入选项
	//从open_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 */
	//这里的参数为&octx.groups[GROUP_OUTFILE] 意味着要处理输出选项
    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;
}

这个函数中有个调用
ret = open_files(&octx.groups[GROUP_INFILE], “input”, open_input_file);
这个调用是要处理输入选项的参数,也就是把 输入选项的参数 转移到 OptionsContext中

static int open_files(OptionGroupList *l, const char *inout,
                      int (*open_file)(OptionsContext*, const char*))
{
    int i, ret;
	//由于要适应多个输入源或者多个输出流及文件 所以nb_groups 有可能是多个
    for (i = 0; i < l->nb_groups; i++) {
        OptionGroup *g = &l->groups[i];
        OptionsContext o;
        init_options(&o);//初始化
		//下面这句很重要 ,因为 o 会在 open_input_file 或者 open_output_file 中被临时操作
		//o.g 其实指向的是 groups 数组列表中的一个
        o.g = g;
		//要对 g 中的 opts 数据 进行解析,并转移了,转移到o中
        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);
		//o已经被上面的parse_optgroup函数赋值过,g->arg 为 输入源或者输出流及文件名
		//下面的open_file 实际上 open_input_file 或 open_output_file
        ret = open_file(&o, g->arg);//open_input_file or open_output_file
		//释放o
        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;
}

在 oen_files 函数中有个for 循环,意思是 ,一个输入源一个输入源的处理

OptionGroup *g = &l->groups[i];
OptionsContext o; //先创建一个变量
init_options(&o); //初始化
//下面这句很重要 ,因为 o 会在 open_input_file 或者 open_output_file 中被临时操作 (注意o.g 其实指向的是 groups 数组列表中的一个)
o.g = g; //先让o.g 指向 真正存储着参数数据的地址
//开始解析了
ret = parse_optgroup(&o, g);

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);
	//对OptionGroup 中的 opts 逐一取出参数,分析
    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);

		//此函数是将 Option中 key 和 val 转换成 OptionsContext 中的变量数据
		//便于在以后的逻辑中使用
		//此处的 o->opt 是在上面的 add_opt 调用赋值的,实际上是全局变量options中的某一个元素 
		//是根据上面的find_option 查出来的
		//key是参数的name ,val为参数的value ;(比如:-c copy key 为 "c" val 为 "copy" )
        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;
}

parse_optgroup 函数里的for 循环,是每次取出参数数据,进行处理
write_option 函数是真正向 OptionsContext 类型变量赋值的函数

ret = write_option(optctx, o->opt, o->key, o->val);
此处的 optctx 就是 OptionsContext 的变量

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*/
	//这句很关键,po->u.off 为 OptionsContext 中变量 位置
	//po->u.dst_ptr 为 OptionsContext 中的 函数指针
    void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?
                (uint8_t *)optctx + po->u.off : po->u.dst_ptr;

	//下面的代码就是对 OptionsContext中的变量进行赋值了
    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;
}

注意一下 write_option函数中 u.func_arg,它是函数地址,在 最开始的 options 定义中已经赋值了,此处就是 执行相应的函数,传入的参数就是 命令参数数据

到此为止,已经把 命令参数数据 从 OptionGroup 类型的数组列表中的 arg 和 opts 转移到OptionsContext 中了

下一篇 再分析 把数据 如何从 OptionsContext 中 转移到 字典表中 AVDictionary,
又从字典表转移到 AVFormatContext 中去的

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值