您的位置:首页 > 其它

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

2012-04-24 13:59 555 查看
文章转载自:罗索工作室 [http://www.rosoo.net]

5.渐入佳境

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

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

static int mpegts_read_header(***FormatContext *s,

***FormatParameters *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, ***_LOG_DEBUG, "tuning done\n");

#endif

s->ctx_flags |= ***FMTCTX_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, ***_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)

{

***FormatContext *s = ts->stream;

MpegTSFilter *tss;

int len, pid, cc, cc_ok, afc, is_start;

const uint8_t *p, *p_end;

##########################################################

获取该包的PID

##########################################################

pid = ***_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,待续......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: