一、引言
从《音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现》可以知道:FFmpeg源码中使用handle_packet函数来处理一个transport packet(TS包),该函数的前半部分实现解析一个transport packet的TS Header。而在解析完TS Header后,handle_packet函数内部会调用write_section_data函数来把各个transport packet组合起来,并调用对应的解析PSI/SI表的方法:
/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{
//...
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end || !has_payload)
return 0;
if (pos >= 0) {
av_assert0(pos >= TS_PACKET_SIZE);
ts->pos47_full = pos - TS_PACKET_SIZE;
}
if (tss->type == MPEGTS_SECTION) {
if (is_start) {
/* pointer field present */
len = *p++;
if (len > p_end - p)
return 0;
if (len && cc_ok) {
/* write remaining section bytes */
write_section_data(ts, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return 0;
}
p += len;
if (p < p_end) {
write_section_data(ts, tss,
p, p_end - p, 1);
}
} else {
if (cc_ok) {
write_section_data(ts, tss,
p, p_end - p, 0);
}
}
// stop find_stream_info from waiting for more streams
// when all programs have received a PMT
if (ts->stream->ctx_flags & AVFMTCTX_NOHEADER && ts->scan_all_pmts <= 0) {
int i;
for (i = 0; i < ts->nb_prg; i++) {
if (!ts->prg[i].pmt_found)
break;
}
if (i == ts->nb_prg && ts->nb_prg > 0) {
av_log(ts->stream, AV_LOG_DEBUG, "All programs have pmt, headers found\n");
ts->stream->ctx_flags &= ~AVFMTCTX_NOHEADER;
}
}
}
//...
}
上述代码中,首先让指针p_end指向该transport packet的末尾:
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
如果已经读取到了该transport packet的末尾(p >= p_end)或者载荷不存在(!has_payload),handle_packet函数直接返回,不继续进行处理:
if (p >= p_end || !has_payload)
return 0;
如果该transport packet不是PES分组,是Section数据(tss->type == MPEGTS_SECTION),并且TS Header中的payload_unit_start_indicator属性的值为1(is_start为真),表示该transport packet是一个Section的首包,这时TS Header后面还会有一个长度为1字节的pointer_field属性,通过语句:len = *p++读取该pointer_field属性,让指针p指向该transport packet的有效数据。根据有没有pointer_field属性和pointer_field的值是多少,调用write_section_data函数并传入不同参数:
if (tss->type == MPEGTS_SECTION) {
if (is_start) {
/* pointer field present */
len = *p++;
if (len > p_end - p)
return 0;
if (len && cc_ok) {
/* write remaining section bytes */
write_section_data(ts, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return 0;
}
p += len;
if (p < p_end) {
write_section_data(ts, tss,
p, p_end - p, 1);
}
}
//...
}
二、write_section_data函数
(一)write_section_data函数的定义
write_section_data函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:
/**
* Assemble PES packets out of TS packets, and then call the "section_cb"
* function when they are complete.
*/
static void write_section_data(MpegTSContext *ts, MpegTSFilter *tss1,
const uint8_t *buf, int buf_size, int is_start)
{
MpegTSSectionFilter *tss = &tss1->u.section_filter;
uint8_t *cur_section_buf = NULL;
int len, offset;
if (is_start) {
memcpy(tss->section_buf, buf, buf_size);
tss->section_index = buf_size;
tss->section_h_size = -1;
tss->end_of_section_reached = 0;
} else {
if (tss->end_of_section_reached)
return;
len = MAX_SECTION_SIZE - tss->section_index;
if (buf_size < len)
len = buf_size;
memcpy(tss->section_buf + tss->section_index, buf, len);
tss->section_index += len;
}
offset = 0;
cur_section_buf = tss->section_buf;
while (cur_section_buf - tss->section_buf < MAX_SECTION_SIZE && cur_section_buf[0] != 0xff) {
/* compute section length if possible */
if (tss->section_h_size == -1 && tss->section_index - offset >= 3) {
len = (AV_RB16(cur_section_buf + 1) & 0xfff) + 3;
if (len > MAX_SECTION_SIZE)
return;
tss->section_h_size = len;
}
if (tss->section_h_size != -1 &&
tss->section_index >= offset + tss->section_h_size) {
int crc_valid = 1;
tss->end_of_section_reached = 1;
if (tss->check_crc) {
crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size);
if (tss->section_h_size >= 4)
tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4);
if (crc_valid) {
ts->crc_validity[ tss1->pid ] = 100;
}else if (ts->crc_validity[ tss1->pid ] > -10) {
ts->crc_validity[ tss1->pid ]--;
}else
crc_valid = 2;
}
if (crc_valid) {
tss->section_cb(tss1, cur_section_buf, tss->section_h_size);
if (crc_valid != 1)
tss->last_ver = -1;
}
cur_section_buf += tss->section_h_size;
offset += tss->section_h_size;
tss->section_h_size = -1;
} else {
tss->section_h_size = -1;
tss->end_of_section_reached = 0;
break;
}
}
}
该函数的作用是:把各个transport packet组合起来,并调用对应的解析PSI/SI表的方法。
形参ts:既是输入型参数也是输出型参数,指向一个MpegTSContext类型变量。
形参tss1:既是输入型参数也是输出型参数,指向一个MpegTSFilter类型变量。
形参buf:指针,输入型参数,指向某个transport packet去掉TS Header和pointer_field后的有效数据。
形参buf_size:输入型参数,该transport packet有效数据的长度,单位为字节。
形参is_start:输入型参数,该transport packet是否为一个Section的首包。值为1表示是,值为0表示否。
返回值:无
(二)write_section_data函数的内部实现
write_section_data函数中,首先判断该transport packet是否为一个Section的首包。如果是,通过语句:memcpy(tss->section_buf, buf, buf_size)将该transport packet去掉TS Header和pointer_field后的有效数据拷贝到tss->section_buf中。tss->section_buf存放一个Section的数据,一个Section可能包含一个或多个transport packet。tss->section_index为该Section的累计长度,通过语句:tss->section_index = buf_size让该Section的累计长度等于该transport packet有效数据的长度:
if (is_start) {
memcpy(tss->section_buf, buf, buf_size);
tss->section_index = buf_size;
tss->section_h_size = -1;
tss->end_of_section_reached = 0;
}
如果该transport packet不是一个Section的首包,并且还未到达该Section的末尾,通过语句:memcpy(tss->section_buf + tss->section_index, buf, len)将该transport packet的有效数据拼接到tss->section_buf的末尾。通过语句:tss->section_index += len让该Section的累计长度增加:
if (is_start) {
//...
} else {
if (tss->end_of_section_reached)
return;
len = MAX_SECTION_SIZE - tss->section_index;
if (buf_size < len)
len = buf_size;
memcpy(tss->section_buf + tss->section_index, buf, len);
tss->section_index += len;
}
读取该Section的Section Header中的section_length属性,section_length属性的值加3就是整个Section的长度,将整个Section的长度赋值给变量len和tss->section_h_size:
/* compute section length if possible */
if (tss->section_h_size == -1 && tss->section_index - offset >= 3) {
len = (AV_RB16(cur_section_buf + 1) & 0xfff) + 3;
if (len > MAX_SECTION_SIZE)
return;
tss->section_h_size = len;
}
如果已经读取到该Section的末尾,并且需要检查CRC校验(tss->check_crc为真),通过语句:crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size)判断该Section的CRC校验是否正确(关于av_crc函数用法可以参考:《FFmpeg源码中,计算CRC校验的实现》)。通过语句:tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4)获取到该Section的CRC校验:
if (tss->section_h_size != -1 &&
tss->section_index >= offset + tss->section_h_size) {
int crc_valid = 1;
tss->end_of_section_reached = 1;
if (tss->check_crc) {
crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size);
if (tss->section_h_size >= 4)
tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4);
if (crc_valid) {
ts->crc_validity[ tss1->pid ] = 100;
}else if (ts->crc_validity[ tss1->pid ] > -10) {
ts->crc_validity[ tss1->pid ]--;
}else
crc_valid = 2;
}
如果CRC校验正确,根据Section类型调用对应的解析PSI/SI表的方法。tss->section_cb是函数指针,指向不同PSI/SI表的解析函数,比如SDT表对应的解析函数是sdt_cb,PAT表对应的解析函数是pat_cb,PMT表对应的解析函数是pmt_cb:
if (crc_valid) {
tss->section_cb(tss1, cur_section_buf, tss->section_h_size);
if (crc_valid != 1)
tss->last_ver = -1;
}