ffmpeg源码学习
最近在看 ffmpeg 源码,希望多了解下底层的东西, mplayer 源码太过庞大,不利于快速的分析关键代码。 ffmpeg 恰好满足需要。
主要的分析内容包括如下两个方面: demuxer 相关,解码器选取相关
1 、 demuxer 相关
1.1 ffmpeg 中选择 demuxer 的过程
av_open_input_file ( ** ) ->ff_probe_input_buffer ( ** ) -->av_probe_input_format2(***)-->read_probe (), 其中 read_probe 是函数指针,不同的 demuxer 基本都会实现自己的 read_probe ,举例来说, mpegts 对应的函数名是 mpegts_probe, 根据 read_probe 的返回值来判断,选择得分(返回值)最大的 demuxer 。
其中在 av_open_input_file 函数中首先调用 url_fopen ( ** )来判断参数中给定的视频流是 文件格式 ( file )还是网络流格式( rtsp 、 rtp 等),并调用相应的 open 函数来打开流,其中 url_alloc(**) 为格式探测函数, url_connect(**)—>uc->prot->url_open(**) 为相应的流打开函数,其中 uc->prot->url_open 为函数指针,存储选中格式的 open 函数。格式判断方法比较简单:判断文件名中是否有 “ : ” ,有再判断具体是哪种流,没有的话按照文件格式来处理。 格式探测完毕并打开流之后(将 uc->is_connected 设置为 1 ) ,url_open 函数结束。接下来执行 url_fdopen(**), 在此过程中通过 URLContext 数据结构(不同的格式会有不同的读取包的方式)来初始化 ByteIOContext , ByteIOContext 是 AVFormatContext 的成员,其成员包括流属性,已经对流的读取、写入、定位等函数指针。经过初始化此结构程序便可以读取实际的数据包来做进一步分析了。
下面以 mpegts 为例,看具体选择 demuxer 的过程。过程通过调用 read_probe ()来确定,在 mpegts 中是调用 libavformat/mpegts.c 中的 mpegts_probe ( ** ) -->analyze(***), 具体原理是读取一定数量的包(此 处是 2048Byte ,计算下来应该是 10 个包 204*10=2040 ),判断 0x47 出现的次数( 0x47 是 mpegts 的同步位),根据出现次数来计算分数(具体代码分析可参考引用的其他文章)。 如果匹配的话,肯定是满分,故而选择。
1.2psi 分析
选择完毕 demuxer ( format )之后,下一步打开视频流并分析。主要分析 PSI 信息,即包含的频道及各个频道关联的具体视频或音频流的信息等。具体过程如下:
av_open_input_stream ( ** ) -->read_header(), 其中 read_header 也是函数指针,在此处我们还以 mpegts 为例,实际调用的函数为 mpegts_read_header ( ** ),此过程主要 是分析 ts 流中有哪些视频流、音频流等参数(主要通过分析 ts 流中的 pat sdt 表等信息得出),调用过程为 :
mpegts_read_header(***)-->handle_packets(**)-->handle_packet(**) ,
其中在 mpegts_read_header ( ** )中首先通过 pegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1); mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1); 挂接 sdt 以及 pat 的处理函数,即 sdt 和 pat 是最初始的信息,在 pat 中才能得到 pmt 的 pid 以便进一步分析。在 handle_packets 中读取一个包然后调用 handle_packet(**) 处理。处理过程中调用 write_section_data
接收 pat 等信息,当一个完整的 pat 接收完毕后,调用 tss->section_cb(tss1, tss->section_buf, tss->section_h_size); 来解析,此处 section_cb 便是通过 mpegts_open_section_filter 挂接的 pat_cb( 以 pat 为例, sdt 分析方式一样 ) 。分析过程得到 pmt 的 pid 并通过 mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1); 将 pmt 的解析函数挂接上来,之后便可以读取 pmt 包并处理了。通过分析 pmt 便可以得到具体的媒体流并通过 add_pes_stream(ts, pid, pcr_pid); 函数来建立各个媒体流的信息,也得到了此 ts 中包含的媒体流的数量和 id ,在 pmt_cb 中还有一个很重要的函数是 mpegts_set_stream_info(st, pes, stream_type, prog_reg_desc); 此函数通过 mpegts_find_stream_type(st, pes->stream_type, MISC_types); 可以定位到每个媒体流的具体解码器(主要是存储 codec_id, 其他在后面初始化),之后解码的时候便可以通过 codec_id 来选择具体的解码器了。增加流的操作中会调用 tss = mpegts_open_pes_filter(ts, pid, mpegts_push_data, pes); 来挂接实际的解析 pes 的函数,之后便可以接收具体的 pes 包并解析实际的媒体数据。以上内容并不保证准确,需进一步确认。
经过此过程,便得到了基本的流信息( video audio 数量及相应的 ID ),在读包后分析 ID 即可相应处理,达到解复用的目的了。分析参数得到流信息的过程描述如下:主要是两个表 PAT 和 PMT ,其中 PAT 存 储的频道信息,一个频道便有一个 PMT ,而 PMT 存储的是此频道由哪些流组成(视频流 音频流 以及相应的 PID ),以及流的具体格式( h264 等),通过分析完每个 PMT 便得到了总的视频流及频道等信息。具体的关于sdt和pat、pmt的介绍可参照网上信息或查阅标准文档。
2 、解码器相关
2.1 解码器选择:
通过如上分析,已经得到了解码器的 codec_id ,根据 codec_id 选择即可