前言
最近在做一些音视频领域的工作,这个领域基本绕不开 FFmpeg
,因此想对其源码进行一些研究,站着这个巨人的肩膀上,学习一下其设计思想以及实现思路,FFmpeg 很多人最开始接触的应该都是它的命令和 ffplay
,这篇我们就先分析下 ffmpeg 命令
的实现。
从问题出发
- FFmpeg的命令结构是什么样的
- 怎么实现任意功能、配置的随意组装的
- 倒放这种非线性的情况是怎么处理的
- 当不需要转码类似只需要转封装的话是如何处理的
命令结构
ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
流程推测
- 应该要有一个数据层,按照输入参数进行数据解析
- 将解码、转码、功能处理、编码四个模块抽象分开
- 按第一个环节数据解析的结构组装上述四个模块,形成一条或多条责任链的效果
- 开始处理
过程中应该有很多分支判断、参数设置之类的情况,在这里都不考虑,我们先只分析主链路。
主流程
首先 ffmpeg 命令的入口在于 fftools/ffmpeg.c
的 main
函数上,先从这个函数来看下整体的流程,代码用的是 ffmpeg 4.4
的版本,同时会省略一些无关紧要的内容:
int main(int argc, char **argv)
{
/* 一些前置的初始化动作 */
...
/* 数据解析函数,不过在这个函数里面还会去开启输入/输出文件流等处理 */
ret = ffmpeg_parse_options(argc, argv);
/* 一些数据判断、开启基准测试之类的处理 */
...
/* 开始做实际的文件转换,即按命令开始处理了 */
if (transcode() < 0)
...
/* 结束了,统计耗时等等 */
...
}
从上述代码可以看到核心的环节就是两部分:数据解析
+ 文件转换
,接下来我们将针对这两部分再深入进去看看。
接下去我们分析过程中使用的命令如下:
ffmpeg -i douyin_700x1240.mp4 -vf reverse -af areverse reversed.mp4
上述命令是将一个 douyin_700x1240.mp4
这个视频中的视频流于音频流同时倒放,生成 reversed.mp4
。
数据解析
在分析 ffmpeg_parse_options
函数之前,我们先介绍下 FFmpeg 命令中参数配置相关的几个主要的结构体:
OptionParseContext
typedef struct OptionParseContext {
OptionGroup global_opts; // 全局配置参数集
OptionGroupList *groups; // 输出/输入配置参数集
int nb_groups; // groups的长度
/* parsing state */
OptionGroup cur_group; // 下面讲解到 add_opt 的时候会提到
} OptionParseContext;
该对象用来存储被解析后的数据,全局参数,输入输出参数等。
OptionGroupList
/**
* A list of option groups that all have the same group type
* (e.g. input files or output files)
*/
typedef struct OptionGroupList {
const OptionGroupDef *group_def;
OptionGroup *groups;
int nb_groups;
} OptionGroupList;
上面的注释已经解释得比较清楚了。
OptionGroup
typedef struct OptionGroup {
const OptionGroupDef *group_def;
const char *arg;
Option *opts;
int nb_opts;
AVDictionary *codec_opts;
AVDictionary *format_opts;
AVDictionary *resample_opts;
AVDictionary *sws_dict;
AVDictionary *swr_opts;
} OptionGroup;
这里就是各类参数的存储地了,再往下就是单个配置参数的存储地 Option 了。
Option
/**
* An option extracted from the commandline.
* Cannot use AVDictionary because of options like -map which can be
* used multiple times.
*/
typedef struct Option {
const OptionDef *opt;
const char *key;
const char *val;
} Option;