为什么我会把命令行参数解析的文章放在后面,因为初学者使用 FFplay
,通常不会去用太多的命令参数,通常初学者接触到的第一条 FFplay
命令是这样的。
就是简简单单播放一个文件。
但是深入使用 FFplay
之后,必然会接触到各种各样的参数,你在工作中要验证一些问题的时候,也会用到各种各样的参数。因此理解 FFplay
命令行参数的解析逻辑是非常重要的。
命令行参数的解析逻辑,是通过一个 OptionDef
的数据结构定义的,如下:
OptionDef
其实是一个通用的数据结构,ffprobe.exe
,ffplay.exe
跟 ffprobe.exe
都是用的 OptionDef
数据结构来定义自己的命令行参数。
因此只要了解了 OptionDef
结构,基本上就成功了一半。
下面详细讲一下 OptionDef
里各个字段的含义。
1,const char *name
,命令行参数名称,也就是 -
符号后面的字符串。
2,int flags
,参数的属性,一共有 19 种属性,但是有些不是给 ffpaly.exe
用的,而是给 ffmpeg.exe
用的,等下会详细讲解各个值。
3,union {...} u
,这是一个联合体,可以是参数的值,或者是处理参数的函数,又或者是 OptionsContext
结构体的偏移值(off),通过偏移值找到结构体中的某个字段。
在 FFplay
播放器里面,union {...} u
只会是 参数的值 或者 是处理参数的函数,不会用到偏移值(off),off
是给 ffmpeg.exe
用的。
4,const char *help
,帮助信息。
5,const char *argname
,参数的英文解释,用来拼接,输出帮助信息的。
现在来讲一下 flags 字段分别有哪些属性,这个 flags 字段是最重要的。
1,#define HAS_ARG 0x0001
代表命令行参数是否有值,例如 -x 400
指定播放宽度为 400 就是有值的,400 就是一个值。而 -an
代表不播放音频,-an
命令行参数是没有值的。
2,#define OPT_BOOL 0x0002
代表命令行参数是否是布尔值,布尔值后面都是没有 value
的,例如 -an
就是一个布尔值,如果想取反,需要在前面加上 no
前缀,例如 -noan
OPT_BOOL 跟 HAS_ARG 是相反的两个属性。
提醒:C99 标准是没有布尔值的,命令行的布尔参数最后会转成 0 跟 1 赋值给变量。
3,#define OPT_STRING 0x0008
,代表命令行参数的值是一个字符串。
4,#define OPT_INT 0x0080
,代表命令行参数的值是一个32位整数(int)。
5,#define OPT_INT64 0x0400
,代表命令行参数的值是一个64位整数(int64)。
6,#define OPT_FLOAT 0x0100
,代表命令行参数的值是一个单精度浮点数(float)。
7,#define OPT_DOUBLE 0x20000
,代表命令行参数的值是双精度浮点数。
8,#define OPT_TIME 0x10000
,代表命令行参数的值是时间字符串格式,如果格式不对会报错。
9,#define OPT_EXPERT 0x0004
,只是一个标记,可以在 show_help()
输出帮助信息的时候指定 flags
为 OPT_EXPERT
,只显示这个标记相关的命令行参数。
10,#define OPT_VIDEO 0x0010
,同 OPT_EXPERT
,输出帮助信息用的。
11,#define OPT_AUDIO 0x0020
,同 OPT_EXPERT
,输出帮助信息用的。
12,#define OPT_SUBTITLE 0x0200
,同 OPT_EXPERT
,输出帮助信息用的。
13,#define OPT_DATA 0x1000
,同 OPT_EXPERT
,输出帮助信息用的。
14,#define OPT_EXIT 0x0800
,这个属性代表处理完这个命令行参数的时候是否自动退出程序 , 这个也是给 show_help()
用的,因为输入帮助信息之后,是需要退出程序的。
15,#define OPT_PERFILE 0x2000
,这个属性目前只有 ffmpeg.exe 在用,因为 ffmpeg.exe 支持多个输入文件跟多个输出文件,所以这个属性是标记命令行参数只作用于一个输入/输出文件的。OPT_PERFILE
是跟 OPT_OFFSET
或者 OPT_SPEC
一起使用的。
16,#define OPT_INPUT 0x40000
,代表命令行参数是作用于输入文件的,如果你把它用在输出文件前面,就会报错。
17,#define OPT_OUTPUT0x80000
,代表命令行参数是作用于输出文件的,如果你把它用在输入文件前面,就会报错。
18,#define OPT_OFFSET 0x4000
,代表命令行参数的值会赋值给 OptionsContext
结构体的某个字段,这个字段的位置通过 偏移(u.off)找到。
19,#define OPT_SPEC 0x8000
,代表命令行参数的值会赋值给 OptionsContext
结构体里面的 SpecifierOpt
数组。
上面的一级指针就是数组,然后下一个字段 nb_codec_tags
代表数组有多大。
因为 SpecifierOpt
是一个数组,所以有些命令行参数可以指定多个值,例如
这样会优先使用 h264_cuvid
硬件解码器解码,如果 ffmpeg.exe 没有编译硬件解码器,就会使用 h264_mf
在 ffplay.c
里面,是调 parse_options()
函数来解析命令行参数的,流程图如下:
其实命令行参数分为 2 种类型:
1,ffplay.c
里面定义的 OptionDef options[] = {...}
,这些参数是 ffplay
播放器独有的。
2,组件库里面的参数。组件库是指,编解码库,容器库,重采样库,滤镜库等等。这些库支持的参数都可以在命令行里指定。
无论是通用的,还是私有的参数,都可以通过命令行参数指定,如下:
上面的 probesize
是容器层通用参数,mp4
跟 flv
都有这个参数,但是 export_all
是 mp4
封装格式自己的私有参数。
下面来看一下 parse_option()
函数里面的重点代码,如下:
可以看到,一开始就是从 ffplay.c
定义的 options
去找命令行参数是否存在,如果找不到,po->name
就是空。
如果命令行参数前面有 no
,那就把 no
去掉,再找一遍。
如果命令行参数是 布尔类型,那 arg
就会置为 1 或者 0。
当在 ffplay.c
的 options
找不到这个命令行参数的时候,就会找 default
的 option
,在 ffplay.c
里面,default option
是一个函数调用。
opt_default()
函数,就是从各个组件库去找 参数的定义,如果也找不到,就会报错。
如果 从 ffplay.c
的 optiions
或者 组件库里面找到了命令行参数的定义,就会调 write_option()
来处理这个参数,重点如下:
write_option()
里面的比较复杂的地方就是 OPT_SPEC
的处理逻辑,但是幸运的是,ffplay
播放器不会用到这块逻辑,所以我们暂时不需要关注它。
OPT_SPEC
是给 ffmpeg.exe
用的。
在 ffplay
播放器里面,optctx
参数总是 NULL
,所以 dst
指针全都是指向一个全局变量的,不会指向 OptionsContext
结构体的某个字段。
当不能直接赋值给 dst
指针的时候,就会调 u.func_arg
函数来处理命令行参数,这样设计让命令行解析更加灵活。
最后,如果这个命令行参数的属性标记如果是 OPT_EXIT
,那解析完这个参数之后,就会立即退出程序。OPT_EXIT
通常是给帮助参数用的,因为输出帮助信息到控制台之后,确实是需要立即退出程序了。
总结,ffplay.c
解析命令行参数,只有两种逻辑,一是直接赋值给全局变量,二是调 u.func_arg
函数来处理。
ffprobe.exe
跟 ffplay.exe
的命令行参数解析逻辑是一样的,都是用的 parse_options()
。
但是 ffmpeg.exe
不是,ffmpeg.exe
用的是 ffmpeg_parse_options()
函数,稍微更复杂一些。
但是 parse_options()
跟 ffmpeg_parse_options()
内部都用了 parse_option()
。
注意 parse_options()
跟 parse_option()
,一个有 s ,一个没 s。