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 选择即可