FFMpeg对MPEG2 TS流解码的流程分析

来源:互联网 发布:手机淘宝模版售后服务 编辑:程序博客网 时间:2024/05/26 02:19

FFMpeg对MPEG2 TS流解码的流程分析[2]

落鹤生 发布于 2010-04-20 23:18 点击:次 来自:百度博客
5.渐入佳境 恩,前面的基础因该已近够了,有点像手剥洋葱头的感觉,我们来看看针对MPEG TS的相 应解析过程 我们后面的代码,主要集中在[libavformat/mpegts.c]里面,毛爷爷说:集中优势兵力打 围歼,恩,开始吧,蚂蚁啃骨头。 static int mpegts_read_header(AVFormatCo
TAG: TS流  IPTV  FFMPEG  MPEG2  MPEG2TS  视频解码  

5.渐入佳境
恩,前面的基础因该已近够了,有点像手剥洋葱头的感觉,我们来看看针对MPEG TS的相应解析过程

我们后面的代码,主要集中在[libavformat/mpegts.c]里面,毛爷爷说:集中优势兵力打围歼,恩,开始吧,蚂蚁啃骨头。

static int mpegts_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
MpegTSContext *ts = s->priv_data;
ByteIOContext *pb = s->pb;
uint8_t buf[1024];
int len;
int64_t pos;

......

/* read the first 1024 bytes to get packet size */
#####################################################################
【1】有了前面分析缓冲IO的经历,下面的代码就不是什么问题了:)
#####################################################################
pos = url_ftell(pb);
len = get_buffer(pb, buf, sizeof(buf));
if (len != sizeof(buf))
goto fail;
#####################################################################
【2】前面侦测文件格式时候其实已经知道TS包的大小了,这里又侦测一次,其实
有些多余,估计是因为解码框架的原因,已近侦测的包大小没能从前面被带过来,
可见框架虽好,却也会带来或多或少的一些不利影响
#####################################################################
ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
if (ts->raw_packet_size <= 0)
goto fail;
ts->stream = s;
ts->auto_guess = 0;

if (s->iformat == &mpegts_demuxer) {
/* normal demux */

/* first do a scaning to get all the services */
url_fseek(pb, pos, SEEK_SET);
##################################################################
【3】
##################################################################
mpegts_scan_sdt(ts);

##################################################################
【4】
##################################################################
mpegts_set_service(ts);

##################################################################
【5】
##################################################################
handle_packets(ts, s->probesize);
/* if could not find service, enable auto_guess */

ts->auto_guess = 1;

#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "tuning done\n");
#endif
s->ctx_flags |= AVFMTCTX_NOHEADER;
} else {
......
}

url_fseek(pb, pos, SEEK_SET);
return 0;
fail:
return -1;
}

这里简单说一下MpegTSContext *ts,从上面可以看到,其实这是为了解码不同容器格式所使用的私有数据,只有在相应的诸如mpegts.c 文件才可以使用的,这样,增加了这个库的模块化,而模块化的最大好处,则在于把问题集中到了一个很小的有限区域里面,如果你自己构造程序时候,不妨多参考其基本思想--这样的化,你之后的代码,还有你之后的生活,都将轻松许多。

【3】【4】其实调用的是同一个函数:mpegts_open_section_filter() 我们来看看意欲何为。

static
MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
SectionCallback *section_cb,
void *opaque,
int check_crc)
{
MpegTSFilter *filter;
MpegTSSectionFilter *sec;

#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%x\n", pid);
#endif
if (pid >= NB_PID_MAX || ts->pids[pid])
return NULL;
filter = av_mallocz(sizeof(MpegTSFilter));
if (!filter)
return NULL;
ts->pids[pid] = filter;
filter->type = MPEGTS_SECTION;
filter->pid = pid;
filter->last_cc = -1;
sec = &filter->u.section_filter;
sec->section_cb = section_cb;
sec->opaque = opaque;
sec->section_buf = av_malloc(MAX_SECTION_SIZE);
sec->check_crc = check_crc;
if (!sec->section_buf) {
av_free(filter);
return NULL;
}
return filter;
}

要完全明白这部分代码,其实需要分析作者对数据结构的定义:
依次为:

struct MpegTSContext;
|
V
struct MpegTSFilter;
|
V
+---------------+---------------+
|                               |
V                               V
MpegTSPESFilter        MpegTSSectionFilter

其实很简单,就是struct MpegTSContext;中有NB_PID_MAX(8192)个TS的Filter,而每个struct MpegTSFilter可能是PES的Filter或者Section的Filter。

我们先说为什么是8192,在前面的分析中:
给出过TS的语法结构:
Syntax                    No. of bits    Mnemonic
transport_packet(){      
sync_byte            8        bslbf
transport_error_indicator    1        bslbf
payload_unit_start_indicator    1        bslbf
transport_priority        1        bslbf
PID                13        uimsbf
transport_scrambling_control    2        bslbf
adaptation_field_control    2        bslbf
continuity_counter        4        uimsbf
if(adaptation_field_control=='10'
|| adaptation_field_control=='11'){      
adaptation_field()      
}      
if(adaptation_field_control=='01'
|| adaptation_field_control=='11') {      
for (i=0;i<N;i++){      
data_byte    8        bslbf
}      
}      
}      

而8192,则是 2^13=8192(PID)的最大数目,而为什么会有PES和Section的区分,请参考ISO/IEC-13818-1,我实在不太喜欢重复已有的东西.

可见【3】【4】,就是挂载了两个Section类型的过滤器,其实在TS的两种负载中,section是PES 的元数据,只有先解析了section,才能进一步解析PES数据,因此先挂上section的过滤器。

挂载上了两种 section过滤器,如下:
=========================================================================
PID                |Section Name           |Callback
=========================================================================
SDT_PID(0x0011)    |ServiceDescriptionTable|sdt_cb
|                       |
PAT_PID(0x0000)    |ProgramAssociationTable|pat_cb

既然自是挂上Callback,自然是在后面的地方使用,因此,我们还是继续

【5】处的代码看看是最重要的地方了,简单看来:
handle_packets()
|
+->read_packet()
|
+->handle_packet()
|
+->write_section_data()

read_packet()很简单,就是去找sync_byte(0x47),而看来handle_packet()才会是我们真正因该关注的地方了:)

这个函数很重要,我们贴出代码,以备分析:
/* handle one TS packet */
static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
AVFormatContext *s = ts->stream;
MpegTSFilter *tss;
int len, pid, cc, cc_ok, afc, is_start;
const uint8_t *p, *p_end;

##########################################################
获取该包的PID
##########################################################
pid = AV_RB16(packet + 1) & 0x1fff;
if(pid && discard_pid(ts, pid))
return;
##########################################################
是否是PES或者Section的开头(payload_unit_start_indicator)
##########################################################
is_start = packet[1] & 0x40;
tss = ts->pids[pid];

##########################################################
ts->auto_guess此时为0,因此不考虑下面的代码
##########################################################
if (ts->auto_guess && tss == NULL && is_start) {
add_pes_stream(ts, pid, -1, 0);
tss = ts->pids[pid];
}
if (!tss)
return;

##########################################################
代码说的很清楚,虽然检查,但不利用检查的结果
##########################################################
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
tss->last_cc = cc;

##########################################################
跳到adaptation_field_control
##########################################################
/* skip adaptation field */
afc = (packet[3] >> 4) & 3;
p = packet + 4;
if (afc == 0) /* reserved value */
return;
if (afc == 2) /* adaptation field only */
return;
if (afc == 3) {
/* skip adapation field */
p += p[0] + 1;
}

##########################################################
p已近到达TS包中的有效负载的地方
##########################################################
/* if past the end of packet, ignore */
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) {
if (is_start) {
#############################################################
针对Section,符合部分第一个字节为pointer field,该字段如果为0,
则表示后面紧跟着的是Section的开头,否则是某Section的End部分和
另一Section的开头,因此,这里的流程实际上由两个值is_start
(payload_unit_start_indicator)和len(pointer field)一起来决定
#############################################################
/* pointer field present */
len = *p++;
if (p + len > p_end)
return;
if (len && cc_ok) {
########################################################
1).is_start == 1
len > 0
负载部分由A Section的End部分和B Section的Start组成,把A的
End部分写入
########################################################
/* write remaining section bytes */
write_section_data(s, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return;
}
p += len;
if (p < p_end) {
########################################################
2).is_start == 1
len > 0
负载部分由A Section的End部分和B Section的Start组成,把B的Start部分写入
或者:
3).
is_start == 1
len == 0
负载部分仅是一个Section的Start部分,将其写入
########################################################
write_section_data(s, tss,
p, p_end - p, 1);
}
} else {
if (cc_ok) {
########################################################
4).is_start == 0
负载部分仅是一个Section的中间部分部分,将其写入
########################################################
write_section_data(s, tss,
p, p_end - p, 0);
}
}
} else {
##########################################################
如果是PES类型,直接调用其Callback,但显然,只有Section部分解析完成后才可能解析PES
##########################################################
tss->u.pes_filter.pes_cb(tss,
p, p_end - p, is_start);
}
}


write_section_data()函数则反复收集buffer中的数据,指导完成相关Section的重组过程,然后调用之前注册的两个section_cb:

后面我们将分析之前挂在的两个section_cb,待续......

(bohryan)
原创粉丝点击