ffmpeg关于mpegts码流解析部分:
1、 首先来看main函数
通过av_register_all()来注册所有的编解码器、解复用器(这里只用到mpegts_demuxer)、注册所使用的协议(这里用到文件打开的协议file_protocol,类似的还有http_protocal,pipe_protocol,rtp_protocol,tcp_protocol,udp_protocol).
代码如下:
Void av_register_all()
{
Avcodec_register_all();// 注册所有的编解码器
REGISTER_MUXDEMUX(MPEGTS,mpegts);//注册的实质是把所有的解复用器也好还是复//用器也好都加入到一个链表里面
REGISTER_PROTOCOL(FILE,file);//协议的注册也是如此,都是加入到一个链表里面
}
2、 调用av_open_input_file(&ic,filename,iformat,0,ap);
a) 只是传参一个filename,其他的内容会根据不同的数据内容自动填充
b) 通过url_fopen(&pb,filename,URL_RDONLY)打开文件,在url_fopen中调用了url_open()来判断打开的流是文件流还是网络流,会根据不同的内容自动匹配协议xxx_protocol,然后会根据不同的协议会调用相应的url_open打开流。
这里用到URLProtocol file_protocol = {
"file",
file_open,
file_read,
file_write,
file_seek,
file_close,
};
c) 这里用到一个重要的结构体ByteIOContext封装了媒体流的细节,用它来承载媒体流信息。
d) 接着来探测文件格式。首先通过get_buffer()为pb->buf得到数据,然后通过av_probe_input_format2(pb,1,&score)来探测输入流格式,并返回得到的格式fmt。
这里得到的文件格式为AVInputFormat mpegts_demuxer = {
"mpegts",
NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),
sizeof(MpegTSContext),
mpegts_probe,
mpegts_read_header,
mpegts_read_packet,
mpegts_read_close,
read_seek,
mpegts_get_pcr,
AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,
};
e) 调用av_open_input_stream(ic_ptr,pb,filename,fmt,ap)来解析包头信息。
在其中调用ic->iformat->read_header(ic, ap)实质上是调用mpegts_read_header(ic,ap);
现在程序进入到核心的部分mpegts.c中,下面来依次分析mpegts.c中的各个函数。
1.在mpegts_read_header中,先读取1024字节来得到包大小
pos = url_ftell(pb);
len = get_buffer(pb, buf, sizeof(buf));
if (len != sizeof(buf))
goto fail;
ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
2.然后通过if(s->iformat==&mpegts_demuxer)来判断是解复用还是复用。先看解复用的情况。程序先挂载sdt表和pat表。
Mpegts_scan_sdt(ts);//挂在sdt表到ts->pids[pid]
Mpegts_set_service(ts);//挂在pat表到ts->pids[pid]
Handle_packets(ts,s->probesize);//真正处理包信息的地方在这里
3.进入到handle_packets()中来看。
For(;;)
{
Read_packet(pb,packet,ts->raw_packet_size);//得到一个包到packet
Handle_packet(ts,packet);//对包进行解析
}
4.下面分析handle_packets()
static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
pid = AV_RB16(packet + 1) & 0x1fff;//得到pid,pid=0代表是pat表
if(pid && discard_pid(ts, pid))
return;
is_start = packet[1] & 0x40;
tss = ts->pids[pid];
if (ts->auto_guess && tss == NULL && is_start) {
add_pes_stream(ts, pid, -1, 0);
tss = ts->pids[pid];
}
if (!tss)
return;
cc = (packet[3] & 0xf);
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
tss->last_cc = cc;
afc = (packet[3] >> 4) & 3;
p = packet + 4;
if (afc == 0)
return;
if (afc == 2)
return;
if (afc == 3) {
p += p[0] + 1;
}
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end)//指针到到负载部分
return;
ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;
if (tss->type == MPEGTS_SECTION) {//如果包类型是section
if (is_start) {
len = *p++;
if (p + len > p_end)
return;
if (len && cc_ok) {
write_section_data(s, tss,
p, len, 0);
if (!ts->pids[pid])
return;
}
p += len;
if (p < p_end) {
write_section_data(s, tss,//对section段进行解析
p, p_end - p, 1);
}
} else {
if (cc_ok) {
write_section_data(s, tss,
p, p_end - p, 0);
}
}
} else {
tss->u.pes_filter.pes_cb(tss,//这里先对section进行解析完之后才能进到这里对pes进行解析
p, p_end - p, is_start);
}
}
3.进入到write_section_data()
static void write_section_data(AVFormatContext *s, MpegTSFilter *tss1,
const uint8_t *buf, int buf_size, int is_start)
{
if (tss->section_h_size == -1 && tss->section_index >= 3) {
len = (AV_RB16(tss->section_buf + 1) & 0xfff) + 3;//得到段长度
}
if (tss->section_h_size != -1 && tss->section_index >= tss->section_h_size) {
tss->end_of_section_reached = 1;
if (!tss->check_crc ||
av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1,
tss->section_buf, tss->section_h_size) == 0)
{
tss->section_cb(tss1, tss->section_buf, tss->section_h_size);//这是个重要的函数,是对一个包中的负载进行解析,section_cb会根据包类型的不同来调用不同的函数
}
}
}
Section_cb是一个回调函数,调用的函数有pat_cb,sdt_cb,pmt_cb,
Table_id 00代表pat表,02代表pmt表
关于ffmpeg程序的结构层次关系:
URLProtocol、URLContext和ByteIOContext是ffmpeg文件操作的结构,
AVFormatContext是相当于容器之类的东西,会把解析到的信息都记录在这里
AVFormatContext{
AVInputFormat
ByteIOContext{
URLContext{
URLProtocol;
}
}
Void *priv_data//指向MpegtsContext结构体
}
对TS码流分析:
第一个包 负载为pat表
00000000h: 04 CA 63 A0 包头:47 4pid0 00 afc10 00 00 B0 section_length11 00 01 C1 00 ; .蔯燝@....?..?
00000010h: 00 sid00 00 network_pidE0 1F sid00 01 Epmt_pid1 00 24 AC 48 84 FF FF FF ; ...?..?$琀?
00000020h: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; 后面的省略
分析:pid=0,表明后面跟的负载为pat表
Afc=01,表明没有适应段
Section_length=17,表明此段后有17个字节,包括crc
Sid为节目
第二个包 负载为pmt表
000000c0h: 04 CA 63 A0 包头47 4pid1 00 10 00 段开始:table_id:02 B0 section_length:46program_number00 01 C1 section_number00 ; .蔯燝A....癋..?
000000d0h: last_sec_number00 F PCR_PID0 01 F0 program_info_length0C tag05 len04 bytes48 44 4D 56tag88 len04 0F FF FC ; .??..HDMV?.?
000000e0h: FC stream_type1B es_pidF0 11 Fes_info_len0 06 desc_tag28 desc_len04 64 00 29 BFstream_type81 es_pidF1 00 Fes_info_len0 ; ???(.d.)縼??
000000f0h: 12 desc_tag05 desc_len04 41 43 2D 33 desc_tag81 desc_len04 08 48 0E 00 desc_tag0Adesc_len04 6A ; ...AC-3?.H....j
00000100h: 70 6E 00 stream_type90 Fes_pid2 00 Fes_info_len0 06 desc_tag0A desc_len04 7A 68 6F 004C 3A ; pn.愹.?..zho.L:
00000110h: EA 06 FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ?未完待续。。。。