两个经常用到的宏定义
MATCH_PER_STREAM_OPT
#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);\
}\
}
//例子
MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st);
{
int i, ret;\
for (i = 0; i < o->nb_ts_scale; i++) {\
char *spec = o->ts_scale[i].specifier;\
if ((ret = check_stream_specifier(ic, st, spec)) > 0)\
ist->ts_scale = o->name[i].u.dbl;\
else if (ret < 0)\
exit_program(1);\
}\
}
typedef struct SpecifierOpt {
char *specifier; /**< stream/chapter/program/... specifier */
union {
uint8_t *str;
int i;
int64_t i64;
float f;
double dbl;
} u;
} SpecifierOpt;
从上面的例子中可以看出这个宏定义是在取OptionsContext结构体中定义为类型SpecifierOpt的变量所存储的值。显然它是以最后一个参数的值为准的。
参数含义:
name:OptionsContext中的一个成员变量,并用它加前缀nb_,找到这个变量链表的对应个数。
type:表示此变量在SpecifierOpt类型的变量中存放在union u中的那个变量中。
outvar:返回值存放的变量。
fmtctx, st:check_stream_specifier函数的传入参数。此函数用来检查命令行出入的格式是否正确。
MATCH_PER_TYPE_OPT
#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;\
}\
}
//例子
MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v");
{\
int i;\
for (i = 0; i < o->nb_codec_names; i++) {\
char *spec = o->codec_names[i].specifier;\
if (!strcmp(spec, "v"))\
video_codec_name = o->codec_names[i].u.str;\
}\
}
根据上面的例子可以看出,与前一个宏定义不同,这次不是找出最后一个符合格式标准的值。而是从中挑选一个spec和传入参数mediatype相同的项。mediatype一般是”v”(视频流),”a”(音频流),”s”(字幕流),”d”(数据流)。和流类型相关的。
write_option
要理解怎么取值,就要明白当初是怎么放进去的。这就涉及到一个函数write_option
在解析命令行参数的时候有两处调用关系是这样的。具体流程可以参数open_input_file()函数介绍
ffmpeg_parse_options()->parse_optgroup()->write_option():这条路是设置全局参数的
open_files()->parse_optgroup()->write_option():这条路里面分别设置输入和输出文件的参数
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;//如果数据是SpecifierOpt **类型,对应的存入链表成员个数的变量地址,
if (po->flags & OPT_SPEC) {//在OptionsContext中对应为SpecifierOpt类型的变量
SpecifierOpt **so = (SpecifierOpt **)dst;
char *p = (char *)strchr(opt, ':');
char *str;
/*将偏移对应的下一个地址给dstcount。记录数量. 例如: SpecifierOpt *codec_names; int nb_codec_names;*/
dstcount = (int *)(so + 1);//
*so = (SpecifierOpt *)grow_array(*so, sizeof(**so), dstcount, *dstcount + 1);//dstcount的值就是在这里加1的。
str = av_strdup(p ? p + 1 : "");
if (!str)
return AVERROR(ENOMEM);
//将冒号后面的存入此,一般为:v :a :s :d
(*so)[*dstcount - 1].specifier = str;
dst = &(*so)[*dstcount - 1].u;
}
//下面都是根据option[]中预先定义的参数处理方式进行处理,有的是直接赋值,有的是调用预定义的函数。
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;
}
}
#ifdef EXIT_PROGRAM
if (po->flags & OPT_EXIT)//如果参数标识中定义了OPT_EXIT,退出程序
exit_program(0);
#endif
return 0;
}