前一阵子研究转码的时候看了FFmpeg的源代码。由于ffmpeg.c的代码相对比较长,而且其中有相当一部分是AVFilter有关的代码(这一部分一直不太熟),因此之前学习FFmpeg的时候一直也没有好好看一下其源代码。最近正好看了看AVFilter的知识,顺便就看了下FFmpeg的源代码,在这里画图理一下它的结构。目前好多地方还没有弄明白,等到以后慢慢完善了。 先说明一下自己画的结构图的规则:图中仅画出了比较重要的函数之间的调用关系。粉红色的函数是FFmpeg编解码类库(libavcodec,libavformat等)的API。绿色的函数是FFmpeg的libavfilter的API。其他不算很重要的函数就不再列出了。 PS:有一部分代码可能和ffmpeg.c有一些出入。因为本文使用的ffmpeg.c的代码是移植到VC之后的代码。 在看ffmpeg.c的代码之前,最好先看一下简单的代码了解FFmpeg解码,编码的关键API: 100行代码实现最简单的基于FFMPEG+SDL的视频播放器 最简单的基于FFmpeg+SDL的音频播放器 最简单的基于FFMPEG的视频编码器(YUV编码为H.264) 最简单的基于FFMPEG的音频编码器(PCM编码为AAC) 最简单的基于FFmpeg的转码程序 函数调用结构图FFmpeg的总体函数调用结构图如下图所示
main() main()是FFmpeg的主函数。 调用了如下函数 av_register_all():注册所有编码器和解码器。 show_banner():打印输出FFmpeg版本信息(编译时间,编译选项,类库信息等)。 parse_options():解析输入的命令。 transcode():转码。 exit_progam():退出和清理。 parse_options() parse_options()解析全部输入选项。即将输入命令“ffmpeg -i xxx.mpg -vcodec libx264 yyy.mkv”中的“-i”,“-vcodec”这样的命令解析出来。其函数调用结构如下图所示。 注:定义位于cmdutils.c中。
调用了如下函数: parse_option():解析一个输入选项。具体的解析步骤不再赘述。parse_options()会循环调用parse_option()直到所有选项解析完毕。FFmpeg的每一个选项信息存储在一个OptionDef结构体中。定义如下: typedef struct OptionDef { const char *name; int flags; #define HAS_ARG 0x0001 #define OPT_BOOL 0x0002 #define OPT_EXPERT 0x0004 #define OPT_STRING 0x0008 #define OPT_VIDEO 0x0010 #define OPT_AUDIO 0x0020 #define OPT_INT 0x0080 #define OPT_FLOAT 0x0100 #define OPT_SUBTITLE 0x0200 #define OPT_INT64 0x0400 #define OPT_EXIT 0x0800 #define OPT_DATA 0x1000 #define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only). implied by OPT_OFFSET or OPT_SPEC */ #define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */ #define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt. Implies OPT_OFFSET. Next element after the offset is an int containing element count in the array. */ #define OPT_TIME 0x10000 #define OPT_DOUBLE 0x20000 union { void *dst_ptr; int (*func_arg)(void *, const char *, const char *); size_t off; } u; const char *help; const char *argname; } OptionDef; 其中的重要字段: name:用于存储选项的名称。例如“i”,“f”,“codec”等等。 flags:存储选项值的类型。例如:HAS_ARG(包含选项值),OPT_STRING(选项值为字符串类型),OPT_TIME(选项值为时间类型。 u:存储该选项的处理函数。 help:选项的说明信息。 FFmpeg使用一个名称为options,类型为OptionDef的数组存储所有的选项。有一部分通用选项存储在cmdutils_common_opts.h中。cmdutils_common_opts.h内容如下: { "L" , OPT_EXIT, {(void*)show_license}, "show license" }, { "h" , OPT_EXIT, {(void*) show_help}, "show help", "topic" }, { "?" , OPT_EXIT, {(void*)show_help}, "show help", "topic" }, { "help" , OPT_EXIT, {(void*)show_help}, "show help", "topic" }, { "-help" , OPT_EXIT, {(void*)show_help}, "show help", "topic" }, { "version" , OPT_EXIT, {(void*)show_version}, "show version" }, { "formats" , OPT_EXIT, {(void*)show_formats }, "show available formats" }, { "codecs" , OPT_EXIT, {(void*)show_codecs }, "show available codecs" }, { "decoders" , OPT_EXIT, {(void*)show_decoders }, "show available decoders" }, { "encoders" , OPT_EXIT, {(void*)show_encoders }, "show available encoders" }, { "bsfs" , OPT_EXIT, {(void*)show_bsfs }, "show available bit stream filters" }, { "protocols" , OPT_EXIT, {(void*)show_protocols}, "show available protocols" }, { "filters" , OPT_EXIT, {(void*)show_filters }, "show available filters" }, { "pix_fmts" , OPT_EXIT, {(void*)show_pix_fmts }, "show available pixel formats" }, { "layouts" , OPT_EXIT, {(void*)show_layouts }, "show standard channel layouts" }, { "sample_fmts", OPT_EXIT, {(void*)show_sample_fmts }, "show available audio sample formats" }, { "loglevel" , HAS_ARG, {(void*)opt_loglevel}, "set libav* logging level", "loglevel" }, { "v", HAS_ARG, {(void*)opt_loglevel}, "set libav* logging level", "loglevel" }, { "debug" , HAS_ARG, {(void*)opt_codec_debug}, "set debug flags", "flags" }, { "fdebug" , HAS_ARG, {(void*)opt_codec_debug}, "set debug flags", "flags" }, { "report" , 0, {(void*)opt_report}, "generate a report" }, { "max_alloc" , HAS_ARG, {(void*) opt_max_alloc}, "set maximum size of a single allocated block", "bytes" }, { "cpuflags" , HAS_ARG | OPT_EXPERT, {(void*) opt_cpuflags}, "force specific cpu flags", "flags" }, options数组的定义位于ffmpeg_opt.c中: const OptionDef options[] = { /* main options */ #include "cmdutils_common_opts.h"//包含了cmdutils_common_opts.h中的选项 { "f", HAS_ARG | OPT_STRING | OPT_OFFSET, { (void*)OFFSET(format) }, "force format", "fmt" }, { "i", HAS_ARG | OPT_PERFILE, { (void*) opt_input_file }, "input file name", "filename" }, { "y", OPT_BOOL, { &file_overwrite }, "overwrite output files" }, { "n", OPT_BOOL, { &no_file_overwrite }, "do not overwrite output files" }, { "c", HAS_ARG | OPT_STRING | OPT_SPEC,{ (void*) OFFSET(codec_names) }, "codec name", "codec" }, { "codec", HAS_ARG | OPT_STRING | OPT_SPEC,{(void*) OFFSET(codec_names) }, "codec name", "codec" }, { "pre", HAS_ARG | OPT_STRING | OPT_SPEC,{ (void*) OFFSET(presets) }, "preset name", "preset" }, { "map", HAS_ARG | OPT_EXPERT | OPT_PERFILE, { (void*) opt_map }, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, { "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE, {(void*)opt_map_channel }, "map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" }, { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC,{ (void*)OFFSET(metadata_map) }, "set metadata information of outfile from infile", "outfile[,metadata]:infile[,metadata]" }, { "map_chapters", HAS_ARG | OPT_INT | OPT_EXPERT | OPT_OFFSET, { (void*) OFFSET(chapters_input_file) }, "set chapters mapping", "input_file_index" }, { "t", HAS_ARG | OPT_TIME | OPT_OFFSET,{(void*) OFFSET(recording_time) }, "record or transcode \"duration\" seconds of audio/video", "duration" }, { "fs",HAS_ARG | OPT_INT64 | OPT_OFFSET, { (void*) OFFSET(limit_filesize) }, "set the limit file size in bytes", "limit_size" }, { "ss",HAS_ARG | OPT_TIME | OPT_OFFSET,{ (void*) OFFSET(start_time) }, "set the start time offset", "time_off" }, …//选项太多,不一一列出 }; 在这里,例举一个选项的OptionDef结构体:输入 { "i",HAS_ARG | OPT_PERFILE, { (void*) opt_input_file }, "input file name", "filename" } 在这个结构体中,可以看出选项的名称为“i”,选项包含选项值(HAS_ARG),选项的处理函数是opt_input_file(),选项的说明是“input file name”。下面可以详细看一下选项的处理函数opt_input_file()。该函数的定义位于ffmpeg_opt.c文件中。可以看出,调用了avformat_alloc_context()初始化了AVFormatContext结构体,调用了avformat_open_input()函数打开了“-i”选项指定的文件。此外,调用了avformat_find_stream_info()等完成了一些初始化操作。此外,调用了av_dump_format()打印输出输入文件信息。 static int opt_input_file(void *optctx, const char *opt, const char *filename) { //略… /* open the input file with generic avformat function */ err = avformat_open_input(&ic, filename, file_iformat, &format_opts); if (err < 0) { print_error(filename, err); exit(1); } //略… /* Set AVCodecContext options for avformat_find_stream_info */ opts = setup_find_stream_info_opts(ic, codec_opts); orig_nb_streams = ic->nb_streams; /* If not enough info to get the stream parameters, we decode the first frames to get it. (used in mpeg case for example) */ ret = avformat_find_stream_info(ic, opts); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); avformat_close_input(&ic); exit(1); } //略… /* dump the file content */ av_dump_format(ic, nb_input_files, filename, 0); //略… return 0; } 再例举一个输出文件处理函数opt_output_file()。这里需要注意,输出文件的处理并不包含在OptionDef类型的数组options中。因为FFmpeg中指定输出文件时并不包含选项名称,这是一个比较特殊的地方。一般的选项格式是“-名称 值”,例如指定输入文件的时候,选项格式是“-i xxx.flv”。而指定输出文件的时候,直接指定“值”即可,这是新手可能容易搞混的地方。 例如,最简单的转码命令如下(输出文件前面不包含选项): ffmpeg -i xxx.mpg xxx.mkv 而不是 ffmpeg -i xxx.mpeg -o xxx.mkv 下面简单看一下opt_output_file()函数的定义。该函数的定义同样位于ffmpeg_opt.c文件中。这个函数的定义特别长,完成了输出视频的初始化工作。在这里就不列出代码了。该函数首先调用avformat_alloc_output_context2()初始化AVFormatContext结构体。而后根据媒体类型的不同,分别调用new_video_stream(),new_audio_stream(),new_subtitle_stream()等创建不同的AVStream。实际上上述的几个创建AVStream的函数调用了new_output_stream()。而new_output_stream()又调用了FFmpeg类库的API函数avformat_new_stream()。 void opt_output_file(void *optctx, const char *filename) { //略… err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); if (!oc) { print_error(filename, err); exit(1); } //略… new_video_stream(); … new_audio_stream(); … new_subtitle_stream (); //略… } transcode() transcode()的功能是转码。其函数调用结构如下图所示。 调用了如下函数 transcode_init():转码的初始化工作。 check_keyboard_interaction():检测键盘操作。例如转码的过程中按下“Q”键之后,会退出转码。 transcode_step():进行转码。 print_report():打印转码信息,输出到屏幕上。 flush_encoder():输出编码器中剩余的帧。 其中check_keyboard_interaction(),transcode_step(),print_report()三个函数位于一个循环之中会不断地执行。 下面简单介绍两个重要的函数transcode_init()和transcode_step()。 transcode_init() transcode_init()调用了以下几个重要的函数: av_dump_format():在屏幕上打印输出格式信息。注意是输出格式的信息,输入格式的信息的打印是在parse_options()函数执行过程中调用opt_input_file()的时候打印到屏幕上的。 init_input_stream():其中调用了avcodec_open2()打开编码器。 avformat_write_header():写输出文件的文件头。 transcode_step() transcode_step()调用了如下函数: process_input():完成解码工作。 transcode_from_filter():未分析。 reap_filters():完成编码工作。 process_input() process_input()主要完成了解码的工作。其函数调用结构如下图所示。
process_input()调用了如下函数: get_input_packet():获取一帧压缩编码数据,即一个AVPacket。其中调用了av_read_frame()。 output_packet():解码压缩编码的数据并将之送至AVFilterContext。 output_packet()调用了如下函数: decode_video():解码一帧视频(一个AVPacket)。 decode_audio():解码音频(并不一定是一帧,是一个AVPacket)。 do_streamcopy():如果不需要重新编码的话,则调用此函数,一般用于封装格式之间的转换。速度比转码快很多。 decode_video()调用了如下函数: avcodec_decode_video2():解码一帧视频。 rate_emu_sleep():要求按照帧率处理数据的时候调用,可以避免FFmpeg处理速度过快。常用于网络实时流的处理(RTP/RTMP流的推送)。 configure_filtergraph():设置AVFilterGraph。 av_buffersrc_add_frame():将解码后的数据(一个AVFrame)送至AVFilterContext。 decode_audio()调用的函数和decode_video()基本一样。唯一的不同在于其解码音频的函数是avcodec_decode_audio4() configure_filtergraph() 未分析。
reap_filters()reap_filters()主要完成了编码的工作。其函数调用结构如下图所示。
reap_filters()调用了如下函数 av_buffersink_get_buffer_ref():从AVFilterContext中取出一帧解码后的数据(结构为AVFilterBufferRef,可以转换为AVFrame)。 avfilter_copy_buf_props():AVFilterBufferRef转换为AVFrame。 do_audio_out():编码音频。 do_video_out():编码视频。 avfilter_unref_buffer():释放资源。 do_video_out()调用了如下函数 avcodec_encode_video2():编码一帧视频。 write_frame():写入编码后的视频压缩数据。 write_frame()调用了如下函数: av_bitstream_filter_filter():使用AVBitStreamFilter的时候,会调用此函数进行处理。 av_interleaved_write_frame():写入压缩编码数据。 do_audio_out()调用的函数与do_video_out()基本上一样。唯一的不同在于视频编码函数avcodec_encode_video2()变成了音频编码函数avcodec_encode_audio2()。 exit_program()exit_program()主要完成了清理工作。调用关系如下图所示。
调用了如下函数: avfilter_graph_free():释放AVFilterGraph。 avformat_free_context():释放输出文件的AVFormatContext。 av_bitstream_filter_close():关闭AVBitStreamFilter。 avformat_close_input():关闭输入文件。
原文链接:https://blog.csdn.net/leixiaohua1020/article/details/39760711 |
|