split_commandline解析中间状态—ffmpeg.c源码分析
作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音
split_commandline()
函数的作用是把命令行的参数先解析到一个中间结构(OptionParseContext)里面,也就是 octx
变量。
本文我们一起来探索一下它是如何实现解析功能的,split_commandline()
函数的定义如下:
/**
* 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);
在 ffmpeg_opt.c
里面的实际传参如下:
static const OptionGroupDef groups[] = {
[GROUP_OUTFILE] = { "output url", NULL, OPT_OUTPUT },
[GROUP_INFILE] = { "input url", "i", OPT_INPUT },
};
split_commandline()
函数的内部流程图如下:
split_commandline()
函数的整体逻辑是这样的。首先是一个 while (optindex < argc) {...}
循环,不断处理命令行的参数。
在循环内部,会多次查找 命令行的参数 所对应的 struct OptionDef
,f
参数对应的 OptionDef
,如下:
查询 OptionDef
会最多会尝试 3 次:
1,从 ffmpeg_opt.c
定义的 options
数组里面找,这是 ffmpeg.exe
独有的 OptionDef
(选项)。
2,从 编解码库,容器库,重采样库,等等里面查找公共的 OptionDef
。调用的函数是 opt_default()
。
3,判断命令行参数,是否以 no 开头的,例如 nobenchmark
会转成 benchmark
,然后再从 ffmpeg_opt.c
定义的 options
数组里面找一次。
这是 FFmpeg 整个项目布尔参数的习惯,如果要取反,就在前面加个 no
。解析的时候会把 no
去掉,然后赋值为 0 ,如下:
/* 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"); //注意这里的 “0”
av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with "
"argument 0.\n", po->name, po->help);
continue;
}
一旦找到 命令行的参数 所对应的 struct OptionDef
,就会开始记录。
如果是在 ffmpeg.exe
的独立参数里面找到的 OptionDef
,可能会记录到下面两个地方:
1,octx->global_opts
,记录到全局组里面。
2,octx->cur_group
,记录到当前组里面。
如果是在编解码库,容器库等等,公共的组件库里面里面找到的 OptionDef
,可能会记录到下面这些地方,是通过 opt_default()
函数记录的。
1,codec_opts
,编解码器参数
2,format_opts
,容器层参数。
3,sws_dict
,图像转换
4,swr_opts
,重采样
无论是 octx->cur_group
当前组,还是 codec_opts
,format_opts
等等,他们都是一种中间的状态,当匹配到输入或者输出文件的时候,就会把这些中间信息,全部转移到输入组,或者是输出组里面。
这个转移操作,是通过 finish_group()
函数完成的,如下:
接下来讲一下,ffmpeg.exe 是怎么匹配到输入 跟 输出文件的。
输入文件的匹配代码如下:
/* 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;
}
上面的 groups
变量的定义如下:
static const OptionGroupDef groups[] = {
[GROUP_OUTFILE] = { "output url", NULL, OPT_OUTPUT },
[GROUP_INFILE] = { "input url", "i", OPT_INPUT },
};
而 opt 就是命令行字符。
所以逻辑比较简单,就是匹配 opt 是不是 i ,如果是 i, 就用 GET_ARG(arg)
获取输入文件的名称,存到 arg
里面,然后调 finish_group()
完成输入组的解析。
输出文件的匹配代码如下:
/* 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;
}
上面的 finish_group(octx, 0, opt);
里面的 0 应该替换成 GROUP_OUTFILE
宏,这样看起来会更容易理解。
上面的代码前面两个条件比较简单,就是匹配命令行参数,opt
的第一个字符如果不是 -
,而且第二个字符没有值,这个 opt
变量就是输出文件的文件名。
举个例子参数 "juren.mp4" ,就符合上面条件的判断。
不过有个 dashdash 变量不太容易看懂,这其实是一个转义操作,如果你的输出文件名命名,前面确实要是 -
,那就可以用 --
来转义,如下:
ffmpeg -i juren.mp4 -- -juren.flv
上面最后一个条件,dashdash+1 == optindex
,的意思是,只要匹配到 --
,那后面的参数就是输出文件名称。
其他命令也有类似习惯,比如 grep
搜索带 -
的字符串。
grep -r "-h"
上面这样是不行的,-h
会当成 grep
自己的参数配置,需要换成下面的命令。
grep -r -- "-h"
之前说过,一个输入组是可以有多个输入文件的,输出组同理。所以每调用一次 finish_group()
,它的内部都会扩展一次数组的大小,来容纳更多的输入文件,如下:
split_commandline()
函数执行完毕之后,所有的命令行参数都会解析进去中间结构体 OptionParseContext
里面。
OptionParseContext
里面有全局参数组,输入组,输出组。
理解了 split_commandline()
函数,也就理解了命令行参数解析模块的50%。
题外话:
opt_default()
函数是处理公共组件库的参数解析的,会把命令行参数赋值到 codec_opts
,format_opts
,sws_dict
,swr_opts
。定义如下:
int opt_default(void *optctx, const char *opt, const char *arg)
但是 opt_default()
内部是没用使用到 第一个参数 void *optctx
的,所以第一个参数是多余的。
感谢 NETINT(镕铭微电子) 赞助《FFmpeg原理》免费版一书的服务器费用,下面是 VPU 产品介绍