1 简述
ffmpeg.exe 是一个非常快速的音频视频转换工具, 我们可以通过它进行格式,编码转换,还可以加滤镜。它可以读取普通文件,网络文件,甚至抓取设备数据。ffmpeg 转码的过程如下图所示:
ffmpeg 涉及的c 文件有三个:ffmpeg.c , ffmpeg_opt.c,cmdutils.c ,有关ffmpeg.exe 框架,引用雷神画的流程图:
从上图我们可以看到,main() 函数中调用的函数有:
1)av_register_all()
2)show_banner() :打印ffmpeg 版本,编译器版本,已配置的编解码器,以及动态库的版本信息。
3)parse_options():解析命令行传入的参数,并根据传入的参数,去打开相应的stream,创建该输出的流。
4)transcode() : 转码,还支持滤镜的添加
5)exit_program()
下面我们围绕show_banner(), parse_option(), transcode() 三个函数来介绍ffmpeg.
2 show_banner()
这函数在cmdutils.c 文件中实现的,我们每次使用FFMPEG 进行转码时,都会有下面的输出, 这些都是在show_banner() 函数中输出的。
函数十分简单,直接上源码。
void show_banner(int argc, char **argv, const OptionDef *options)
{
int idx = locate_option(argc, argv, options, "version");
if (hide_banner || idx)
return;
print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO);
print_all_libs_info(INDENT|SHOW_CONFIG, AV_LOG_INFO);
print_all_libs_info(INDENT|SHOW_VERSION, AV_LOG_INFO);
}
3 ffmpeg_parse_options()
这函数的实现是在ffmpeg_opt.c 中, 解析拆解命令行传下来的参数,并打开输入的stream, 创建要输出的stream, 并创建每一个视频,音频的编解码器资源。
int ffmpeg_parse_options(int argc, char **argv)
{
......
/* split the commandline into an internal representation */
ret = split_commandline(&octx, argc, argv, options, groups,
FF_ARRAY_ELEMS(groups));
.......
/* apply global options */
ret = parse_optgroup(NULL, &octx.global_opts);
......
/* configure terminal and setup signal handlers */
term_init();
........
/* open input files */
ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
........
/* create the complex filtergraphs */
ret = init_complex_filters();
..........
/* open output files */
ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
........
check_filter_outputs();
........
}
当我们在终端输入 ffmpeg.exe -help, 就会显示很多关于ffmpeg 使用的帮助信息出来。这又是怎么实现的呢?
在ffmpeg_opt.c 中有定义一个options 数组, 它存放所有 ffmpeg 命令的相关参数。
#define OFFSET(x) offsetof(OptionsContext, x)
const OptionDef options[] = {
/* main options */
CMDUTILS_COMMON_OPTIONS //它实际是一个宏,记录common option, 如help option
{ "f",//命令参数 HAS_ARG | OPT_STRING | OPT_OFFSET |
OPT_INPUT | OPT_OUTPUT,//命令参数类型 { .off = OFFSET(format) },//一个union, 成员有void *, 函数指针,size_t off
"force format", // 帮助信息 "fmt" //参数名字 },
{ "y", OPT_BOOL, { &file_overwrite },
"overwrite output files" },
{ "n", OPT_BOOL, { &no_file_overwrite },
"never overwrite output files" },
...........
在上面关于options 定义中,有个CMDUTILS_COMMON_OPTIONS 的宏, 它是common option, 宏定义如下:
#define CMDUTILS_COMMON_OPTIONS \
{ "L", OPT_EXIT, { .func_arg = show_license }, "show license" }, \
{ "h", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "?", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "-help", OPT_EXIT, { .func_arg = show_help }, "show help", "topic" }, \
{ "version", OPT_EXIT, { .func_arg = show_version }, "show version" }, \
{ "buildconf", OPT_EXIT, { .func_arg = show_buildconf }, "show build configuration" }, \
{ "formats", OPT_EXIT, { .func_arg = show_formats }, "show available formats" }, \
{ "muxers", OPT_EXIT, { .func_arg = show_muxers }, "show available muxers" },
当我们输入ffmpeg.exe -help 时, 它实际会跑到show_help() 这个函数中, 那么它又是怎么跑到的呢?
main() ---> ffmpeg_parse_options() ---> parse_optgroup() --> write_option() --> po->u.func_arg() ( show_help() )
它 的大体流程就是这样的,func_arg 实际是个指针函数, 指向的函数在定义options 就已经定好了, 另外关于ffmpeg.exe -formats ; ffmpeg -codecs 等等流程都是一样的。
4 ffmpeg转码实现
假设我们在consle 输入 "ffmpeg.exe -i test.mp4 -vcodec libx264 out.flv" , 那么会发生什么呢?
step 1: 打开输入的stream
step 2: 创建输出stream
step 3: 打开解码器
step 4: 打开编码器
step 5: 解码
step 6:编码
在transcode() 函数中,调用transcode_init() 初始化编解码器,然后循环调用transcode_step() 进行一帧一帧解码
/*
* The following code is the main loop of the file converter
*/
static int transcode(void)
{
........
ret = transcode_init();//初始化编解码器
/* 循环调用transcode_step() 解码,编码。并解码编码完一帧后,会调用print_report() 打印相关信息 */
while (!received_sigterm) {
int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */
if (stdin_interaction)
if (check_keyboard_interaction(cur_time) < 0)
break;
ret = transcode_step();
if (ret < 0 && ret != AVERROR_EOF) {
av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));
break;
}
print_report(0, timer_start, cur_time);
}
................
}