原地址已经找不到了,暂时不能贴上来。
本文主要的分析内容是 demuxer流程
1. Demuxer 相关
1.1. ffmpeg 中选择 demuxer 的过程
(1)Input Method
av_open_input_file()
-> avio_open()
--> url_open()
---> url_alloc
---> url_connect
在 av_open_input_file() 函数中首先调用 avio_open() 来判断参数中给定的视频流是文件格式(file)还是网络流格式(rtsp、tcp、http),并调用相应的url_open 函数来打开流。在url_open()函数中, url_alloc()为格式探测函数(查找出是哪个协议,url_alloc_for_protocol()将URLContext结构体填充满);url_connect()-->uc->prot->url_open() 为相应的流打开函数,uc->prot->url_open() 为函数指针,存储选中格式(即url_alloc()探测出来的函数格式)的open函数,以TCP的url_open()为例,会先建立Socket连接。格式判断方法比较简单:判断文件名中是否有“:”,若有,再判断具体是哪种流;没有的话按照文件格式来处理。 格式探测完毕并打开流之后(将 uc->is_connected 设置为1,意味与本地文件或非本地资源建立起连接),avio_open ()函数结束。
(2)Create Buffer for Input
->avio_open()
--> url_fdopen()
---> url_get_max_packet_size()
---> ffio_init_context()
avio_open()接下来执行 url_fdopen(), 在此过程首先初始化一个Buffer,接着通过 URLContext 数据结构(不同的格式会有不同的读取包的方式),函数ffio_init_context()来初始化AVIOContext , AVIOContext 是 AVFormatContext 的成员,其成员包括流属性,已经对流的读取、写入、定位等函数指针。经过初始化此结构程序便可以读取实际的数据包来做进一步分析了。 将URLProtocol中的url_read、url_write、url_seek写到AVIOContext(),之后就主要使用这个结构体。
(3) Choose 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 。
下面以 mpegts 为例,具体选择 demuxer 的过程。先调用av_probe_input_buffer ()->av_probe_input_format2()来确定,在 mpegts 中是调用libavformat/mpegts.c 中的 mpegts_probe-->analyze(), 具体原理是读取一定数量的包(此处是 2048Byte ,计算下来应该是 10 个包 204*10=2040 ),判断 0x47 出现的次数( 0x47 是 mpegts 的同步位),根据出现次数来计算分数。 如果匹配的话,分数接近满分,故而选择。目前,TS包的大小有三种标准:188,192, 204,其中188的标准用得最多。
(4)Protocol of FFMPEG Support
FFMPEG支持网络协议有Contat、PIPE、gopher、http、RTMP(network及扩展t、e、te、s)、Md5、MMSt、MMSh(Over http)、rtmp、rtp、Tcp、Udp。(在全工程中查找URLProtocol得到结果)
typedef struct URLProtocol {
const char *name;
int (*url_open)(URLContext *h, const char *url, int flags);
int (*url_read)(URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
struct URLProtocol *next;
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
} URLProtocol;
1.2. TS结构分析
(1) PSI 表
TS流中PSI(Program Specific Infromation,节目特定信息)组成:
* 节目关联表(PAT)Program Associate Table
* 条件接收表(CAT)Condition Accept Table
* 节目映射表(PMT)Program Mapped Table
* 网络信息表(NIT)Network Information Table
(2) SI表
TS流中的SI(Service Information,服务信息)组成:
l 业务群关联表(BAT)Bevy Associate Table
l 业务描述表(SDT)XX Describe Table
(3) PID and TID
PID信息:
PAT_PID = 0x0000
SDT_PID = 0x0011
Table信息:
#define PAT_TID 0x00
#define PMT_TID 0x02
#define SDT_TID 0x42
(4) Stream_type类型表:
#define STREAM_TYPE_VIDEO_MPEG1 0x01
#define STREAM_TYPE_VIDEO_MPEG2 0x02
#define STREAM_TYPE_AUDIO_MPEG1 0x03
#define STREAM_TYPE_AUDIO_MPEG2 0x04
#define STREAM_TYPE_PRIVATE_SECTION 0x05
#define STREAM_TYPE_PRIVATE_DATA 0x06
#define STREAM_TYPE_AUDIO_AAC 0x0f
#define STREAM_TYPE_AUDIO_AAC_LATM 0x11
#define STREAM_TYPE_VIDEO_MPEG4 0x10
#define STREAM_TYPE_VIDEO_H264 0x1b
#define STREAM_TYPE_VIDEO_VC1 0xea
#define STREAM_TYPE_VIDEO_DIRAC 0xd1
#define STREAM_TYPE_AUDIO_AC3 0x81
#define STREAM_TYPE_AUDIO_DTS 0x8a
1.3. PSI 分析
av_open_input_file()->av_open_input_stream ()-->read_header()
mpegts_read_header()-->handle_packets()-->handle_packet()
选择完毕 demuxer (format)之后,下一步打开视频流并分析。主要分析 PSI 信息,即包含的频道及各个频道关联的具体视频或音频流的信息等。具体需调用read_header函数指针。 以 mpegts 为例,实际调用的函数为 mpegts_read_header(),此过程主要是分析TS 流中有哪些视频流、音频流、字幕流及私有数据流(主要通过分析 ts 流中的 pat sdt 表等信息得出)。
在mpegts_read_header()中,首先通过 mpegts_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() 处理一个188字节的packet。处理过程中调用 write_section_data, 接收 pat 等信息,当一个完整的 pat 接收完毕后,调用 tss->section_cb()来解析,此处 section_cb 便是通过 mpegts_open_section_filter 挂接的 pat_cb( 以 pat 为例, sdt 分析方式一样 ) 。分析过程得到 pmt 的 pid 并通过 mpegts_open_section_filter(… pmt_pid, pmt_cb… ); 将 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 包并解析实际的媒体数据。
PS:MpegTSFilter *pid[NB_PID_MAX] 而NB_PID_MAZ == 8192
经过此过程,便得到了基本的流信息( video audio 数量及相应的 ID ),在读包后分析 ID 即可相应处理,达到解复用的目的了。分析参数得到流信息的方法如下:主要是两个表 PAT 和 PMT ,其中 PAT 存 储的频道信息,一个频道便有一个 PMT ,而 PMT 存储的是此频道由哪些流组成(视频流 音频流 以及相应的 PID ),以及流的具体格式( MPEG2、h264等),通过分析每个 PMT 便得到了总的视频流及频道等信息。具体的关于sdt和pat、pmt的介绍可参照网上信息或查阅标准文档。