当前位置:   article > 正文

open_files打开输入输出文件_open files

open files

FFmpeg 的社群来了,想加入微信社群的朋友请购买《FFmpeg原理》VIP版 电子书,里有更高级的内容与答疑服务。


在 ffmpeg.exe 里面打开输入输出文件,是由 open_files() 函数来完成的,如下:

open_files() 函数的定义如下:

  1. static int open_files(OptionGroupList *l, const char *inout,
  2. int (*open_file)(OptionsContext*, const char*))

参数解析如下:

1,OptionGroupList *l,输入/输出文件信息列表,

octx.groups[GROUP_INFILE]octx.groups[GROUP_OUTFILE]OptionParseContext octx 是在 split_commandline() 里面提前处理好的。

2,const char *inout,标记字符,用来输出日志的。

3,int (*open_file)(OptionsContext*, const char*),函数指针,指向真正的打开输入文件函数,或者是打开输出文件的函数。


open_files() 函数的流程如下:

open_files() 函数的逻辑就是 for 循环遍历 OptionGroupList 列表,OptionGroupList->nb_groups 就是文件的数量,如果命令行指定有 3 个输入文件,OptionGroupList->nb_groups 就等于 3

init_options() 函数是 初始化 OptionsContext o 的,设置一些字段的默认值。

然后调 parse_optfroup() 函数把 OptionGroup g 里面的参数解析到 OptionsContext o 结构,然后用 open_file() 函数打开文件。

open_file 是一个函数指针,可能指向 open_input_file() ,也可能指向 open_output_file()


我们需要先学习一下 struct OptionsContext 的结构,OptionsContext 保存的是单个输入或者输出文件的信息。

据本人统计,OptionsContext 里面的字段有50个以上,非常庞大,如下:

  1. typedef struct OptionsContext {
  2. OptionGroup *g;
  3. /* input/output options */
  4. int64_t start_time;
  5. int64_t start_time_eof;
  6. int seek_timestamp;
  7. const char *format;
  8. SpecifierOpt *codec_names;
  9. int nb_codec_names;
  10. SpecifierOpt *audio_channels;
  11. int nb_audio_channels;
  12. ...省略...
  13. }

这 50 多个字段可以分为 3 种类型。

1,OptionGroup *gOptionsContext 本身就是从 OptionGroup 里面的 Option 解析过来,这里又保存一下 OptionGroup ,是因为要用到里面的 codec_opts ,format_opts,这些组件库相关的参数,是没有解析到 OptionsContext 里面的。

2,int64_t start_time ,int seek_timestampconst char *format,这些,我把他们称为单字段,都是普通类型。

3,SpecifierOpt数组字段,这是最重要,也是比较复杂的字段。数组字段是一个数组,可以有多个的。


parse_optgroup() 函数把 OptionGroup 转成 OptionsContext 的流程如下:

可以看到,Option 里面原本都是 key - value 的字符串结构,经过 parse_optgroup() 处理之后,就变成了,右边那种 int64 ,intSpecifierOpt 的结构。

至于 AVDictionary 相关的结构是没有进行转换的,因为 AVDictionary 可以直接传给 FFmpeg 的 API 函数,所以 open_files() 会把 OptionGroup 直接赋值给 OptionsContext 的 g 字段,通过 OptionsContext->g->codec_opts 的方式,即可提取命令行指定的编解码参数。


下面来具体分析一下 parse_optgroup() 函数是如何把 OptionGroup 里面的 key - value 转成 OptionsContext 特定的字段的。

不过与其说是 parse_optgroup() 做的转换,不如说是 write_option() 函数做的转换。

parse_optgroup() 的逻辑比较简单,只是在循环里面调了一下 write_option() ,如下:


下面来分析一下 write_option() 是如何做转换的,定义如下:

  1. static int write_option(void *optctx, const OptionDef *po, const char *opt,
  2. const char *arg)

write_option() 的第一步是确定目标位置,这个 key-value 要保存到哪里,如下:

可以看到,是通过 flags 标记来判断要保存到全局变量,还是保存到 OptionsContext 结构,全局变量的解析流程之前已经讲过了,本文主要关注 解析到 OptionsContext 结构的流程。

可以看到通过 optctx + po->u.off 就能定位到 OptionsContext 里面的的字段,这个 u.off 是通过一个宏计算出来的,如下:

#define OFFSET(x) offsetof(OptionsContext, x)

OptionsContext 里面的 intint64double,等等字段的更新都比较简单,就是通过偏移定位到字段,然后赋值就行了。

但是 OptionsContext 里面 SpecifierOpt 类型的字段赋值比较复杂。SpecifierOpt 结构体的定义如下:

  1. typedef struct SpecifierOpt {
  2. char *specifier; /**< stream/chapter/program/... specifier */
  3. union {
  4. uint8_t *str;
  5. int i;
  6. int64_t i64;
  7. uint64_t ui64;
  8. float f;
  9. double dbl;
  10. } u;
  11. } SpecifierOpt;

可以看到 SpecifierOpt 只有两个字段,

1,char *specifier,会是 a 或者 v,区分这是音频还是视频的选项,或者留空字符,代表不区分音视频。

2,union {...} u,一个联合体,用来适应各种各样的数据类型。uint8_t 实际上是字符串,因为一个 char 也是占 8 位。


OptionsContext 里面使用 SpecifierOpt 的时候,是以数组的方式使用的,后面会跟一个数组的长度,如下:

  1. SpecifierOpt *codec_names; //指针就是数组
  2. int nb_codec_names; //数组长度

codec_names 是指编解码器的名称,所以 ffmpeg.exe 可以指定多个解码器来解码文件,如下:

ffmpeg.exe -c:v h264_cuvid -c:v h264_mf -i juren.mp4 juren.flv

这样会优先使用 h264_cuvid 硬件解码器解码,如果 ffmpeg.exe 没有编译硬件解码器,就会使用 h264_mf


下面讲解一下 SpecifierOpt 的解析代码,如下:

变量 so 直接指向 OptionsContext 里面 SpecifierOpt 类型的某个字段了。

下面的代码,实际是为了提取出 a 或者 v 字符, str 等于 a 或者 v,代表这是音频还是视频的选项。

  1. char *p = strchr(opt, ':');
  2. char *str;
  3. ...省略..
  4. str = av_strdup(p ? p + 1 : "");

例如 -c:v ,-c:a,可以指定音频或者视频的编码器。上面的代码这样操作之后,就可以把 str 指向 a 或者 v。


变量 so 一开始其实是一个 NULL 指针,需要用 grow_array() 函数来动态扩容。

最后把 dst 指向 SpecifierOpt 里面的 u 联合体字段,这个其实比较通用,

SpecifierOpt 的 u 跟,OptionDef 的 u 其实是有点类似,可以通用后面的赋值逻辑

  1. typedef struct SpecifierOpt {
  2. ...省略...
  3. union {
  4. uint8_t *str;
  5. int i;
  6. int64_t i64;
  7. uint64_t ui64;
  8. float f;
  9. double dbl;
  10. } u;
  11. } SpecifierOpt;
  1. typedef struct OptionDef {
  2. ...省略...
  3. union {
  4. void *dst_ptr;
  5. int (*func_arg)(void *, const char *, const char *);
  6. size_t off;
  7. } u;
  8. ...省略...
  9. } OptionDef;

至此,parse_option() 把 OptionGroup 转成 OptionsContext 的逻辑分析完毕了。

得到 OptionsContext 之后,就会调指针函数 open_file() 去打开输入或者输出文件。

open_file 是一个指针函数,可能指向 open_input_file() ,也可能指向 open_output_file()

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/278430
推荐阅读
相关标签
  

闽ICP备14008679号