一、引言
由《音视频入门基础:MPEG2-TS专题(14)——PAT简介》可以知道,PAT表(Program association table)由一个或多个段(Program association section,即组成PAT表的段)组成。当某个PAT表非常大时,只要接收到一个Program association section的完整数据就可以进行解析,而不需要接收到完整的表时才开始解析工作,解析完各个Program association section后再把这些信息汇总起来。FFmpeg源码中,通过pat_cb函数解析Program association section。
二、pat_cb函数定义
pat_cb函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:
static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
{
MpegTSContext *ts = filter->u.section_filter.opaque;
MpegTSSectionFilter *tssf = &filter->u.section_filter;
SectionHeader h1, *h = &h1;
const uint8_t *p, *p_end;
int sid, pmt_pid;
int nb_prg = 0;
AVProgram *program;
av_log(ts->stream, AV_LOG_TRACE, "PAT:\n");
hex_dump_debug(ts->stream, section, section_len);
p_end = section + section_len - 4;
p = section;
if (parse_section_header(h, &p, p_end) < 0)
return;
if (h->tid != PAT_TID)
return;
if (!h->current_next)
return;
if (ts->skip_changes)
return;
if (skip_identical(h, tssf))
return;
ts->id = h->id;
for (;;) {
sid = get16(&p, p_end);
if (sid < 0)
break;
pmt_pid = get16(&p, p_end);
if (pmt_pid < 0)
break;
pmt_pid &= 0x1fff;
if (pmt_pid == ts->current_pid)
break;
av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x pid=0x%x\n", sid, pmt_pid);
if (sid == 0x0000) {
/* NIT info */
} else {
MpegTSFilter *fil = ts->pids[pmt_pid];
struct Program *prg;
program = av_new_program(ts->stream, sid);
if (program) {
program->program_num = sid;
program->pmt_pid = pmt_pid;
}
if (fil)
if ( fil->type != MPEGTS_SECTION
|| fil->pid != pmt_pid
|| fil->u.section_filter.section_cb != pmt_cb)
mpegts_close_filter(ts, ts->pids[pmt_pid]);
if (!ts->pids[pmt_pid])
mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1);
prg = add_program(ts, sid);
if (prg) {
unsigned prg_idx = prg - ts->prg;
if (prg->nb_pids && prg->pids[0] != pmt_pid)
clear_program(prg);
add_pid_to_program(prg, pmt_pid);
if (prg_idx > nb_prg)
FFSWAP(struct Program, ts->prg[nb_prg], ts->prg[prg_idx]);
if (prg_idx >= nb_prg)
nb_prg++;
} else
nb_prg = 0;
}
}
ts->nb_prg = nb_prg;
if (sid < 0) {
int i,j;
for (j=0; j<ts->stream->nb_programs; j++) {
for (i = 0; i < ts->nb_prg; i++)
if (ts->prg[i].id == ts->stream->programs[j]->id)
break;
if (i==ts->nb_prg && !ts->skip_clear)
clear_avprogram(ts, ts->stream->programs[j]->id);
}
}
}
该函数的作用是:解析TS流中的Program association section,提取出里面的属性。
形参filter:输出型参数,指向一个MpegTSFilter类型变量。执行pat_cb函数后,数组((MpegTSContext *)(filter->u.section_filter.opaque))->prg会得到从Program association section中解析出来的属性。
形参section:输入型参数。存放一个Program association section(PAT section)的数据,即“将一个或多个包含PAT表信息的transport packet去掉它们TS Header和pointer_field后的有效数据组合起来后”的数据。
section_len:输入型参数。该Program association section的长度,单位为字节。
返回值:无
三、pat_cb函数的内部实现分析
pat_cb函数中首先通过下面语句让指针p_end指向该Program association section中有效数据的末尾,即CRC_32属性的开头;让指针p指向Program association section的开头:
const uint8_t *p, *p_end;
//...
p_end = section + section_len - 4;
p = section;
通过parse_section_header函数解析Section Header,这样指针h就会得到从Section Header中解析出来的属性。关于parse_section_header函数的用法可以参考:《音视频入门基础:MPEG2-TS专题(13)——FFmpeg源码中,解析Section Header的实现》:
if (parse_section_header(h, &p, p_end) < 0)
return;
宏PAT_TID定义如下:
#define PAT_TID 0x00 /* Program Association section */
判断SectionHeader中的table_id属性是否为PAT_TID(0x00),PAT表的table_id固定为0x00,如果不是,表示这不是PAT表,pat_cb函数直接返回:
if (h->tid != PAT_TID)
return;
判断SectionHeader中的current_next_indicator属性的值,如果值为1,表示该PAT当前有效,pat_cb函数继续往下执行;值为0表示下一个PAT有效,pat_cb函数直接返回:
if (!h->current_next)
return;
读取SectionHeader中的program_number属性:
for (;;) {
sid = get16(&p, p_end);
if (sid < 0)
break;
//...
}
读取SectionHeader中的network_PID或program_map_PID属性:
pmt_pid = get16(&p, p_end);
if (pmt_pid < 0)
break;
pmt_pid &= 0x1fff;
如果读取到的network_PID或program_map_PID的值等于当前Section的PID,由于network_PID是NIT的PID,program_map_PID是PMT的PID,当前Section的PID是PAT表的PID,所以这时表示出错了,通过break语句跳出循环:
if (pmt_pid == ts->current_pid)
break;
如果program_number的值为0x0000,变量pmt_pid的值为network_PID(NIT的PID);否则变量pmt_pid的值为program_map_PID(PMT的PID):
if (sid == 0x0000) {
/* NIT info */
} else {
//...
}
数组ts->stream->programs中的每个元素都对应一个PMT表的信息。通过下面代码让ts->stream->programs[i]->program_num赋值为program_number属性的值,让ts->stream->programs[i]->pmt_pid赋值为program_map_PID属性的值,i为该节目是TS流中的第几个节目。使得后续可以通过遍历ts->stream->programs找到对应的PMT表的信息:
MpegTSFilter *fil = ts->pids[pmt_pid];
struct Program *prg;
program = av_new_program(ts->stream, sid);
if (program) {
program->program_num = sid;
program->pmt_pid = pmt_pid;
}
让ts->prg[ts->nb_prg]->id赋值为program_number属性的值:
prg = add_program(ts, sid);
让ts->prg[ts->nb_prg]->pid[nb_pids]赋值为program_map_PID属性的值:
add_pid_to_program(prg, pmt_pid);