author: hjjdebug
date : 2025年 07月 14日 星期一 17:13:07 CST
descrip: mpegts.c中handle_packet() 函数代码注释
ffmpeg 中函数调用链为: handle_packets → handle_packet,
hanle_packet 每次分析188字节
解析TS包头部信息,并根据PID(包标识符)将数据分发给对应的过滤器进行处理.
简单说就是解析,组包.
handle_packets 就是循环调用handle_packet 来处理一系列小包
可见handle_packet 是数据包处理的核心函数,承上启下.
其再上一层往往是是为了读取一个pkt
举3个响当当的上层接口.
例如1: avformat_open_input.
0 in handle_packet of libavformat/mpegts.c:2950
1 in handle_packets of libavformat/mpegts.c:3231
2 in mpegts_read_header of libavformat/mpegts.c:3362
3 in avformat_open_input of libavformat/demux.c:316
例如2: avformat_find_stream_info
0 in handle_packet of libavformat/mpegts.c:2950
1 in handle_packets of libavformat/mpegts.c:3231
2 in mpegts_read_packet of libavformat/mpegts.c:3500
3 in ff_read_packet of libavformat/demux.c:576
4 in read_frame_internal of libavformat/demux.c:1264
5 in avformat_find_stream_info of libavformat/demux.c:2624
例如3: av_read_frame
0 in handle_packet of libavformat/mpegts.c:2950
1 in handle_packets of libavformat/mpegts.c:3231
2 in mpegts_read_packet of libavformat/mpegts.c:3500
3 in ff_read_packet of libavformat/demux.c:576
4 in read_frame_internal of libavformat/demux.c:1264
5 in av_read_frame of libavformat/demux.c:1473
来自ffmpeg6.1.1 libavformat/mpegts.c
static int handle_packet(MpegTSContext* ts, const uint8_t* packet, int64_t pos)
{
MpegTSFilter* tss;
int len, pid, cc, expected_cc, cc_ok, afc, is_start, is_discontinuity,
has_adaptation, has_payload;
const uint8_t *p, *p_end;
pid = AV_RB16(packet + 1) & 0x1fff; //提取pid
is_start = packet[1] & 0x40; //判断数据包起始位
tss = ts->pids[pid]; //获取过滤器
if (ts->auto_guess && !tss && is_start)
{
add_pes_stream(ts, pid, -1); //没有过滤器,添加pes 流
tss = ts->pids[pid];
}
if (!tss)
return 0;
if (is_start)
tss->discard = discard_pid(ts, pid); //判读pid 是否被抛弃
if (tss->discard)
return 0;
ts->current_pid = pid;
afc = (packet[3] >> 4) & 3;
if (afc == 0) /* reserved value */
return 0;
has_adaptation = afc & 2;
has_payload = afc & 1;
is_discontinuity = has_adaptation && packet[4] != 0 && /* with length > 0 */
(packet[5] & 0x80); /* and discontinuity indicated */
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
cc_ok = pid == 0x1FFF || // null packet PID
is_discontinuity || tss->last_cc < 0 || expected_cc == cc;
tss->last_cc = cc;
if (!cc_ok)
{
av_log(ts->stream, AV_LOG_DEBUG,
"Continuity check failed for pid %d expected %d got %d\n",
pid, expected_cc, cc);
if (tss->type == MPEGTS_PES)
{
PESContext* pc = tss->u.pes_filter.opaque;
pc->flags |= AV_PKT_FLAG_CORRUPT;
}
}
if (packet[1] & 0x80)
{ //检查错误标志
av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");
if (tss->type == MPEGTS_PES)
{
PESContext* pc = tss->u.pes_filter.opaque;
pc->flags |= AV_PKT_FLAG_CORRUPT;
}
}
p = packet + 4; //跳过头部字节
if (has_adaptation)
{
int64_t pcr_h;
int pcr_l;
if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)
tss->last_pcr = pcr_h * 300 + pcr_l;
/* skip adaptation field */
p += p[0] + 1; //跳过适配域字段
}
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end || !has_payload)
return 0;
if (pos >= 0) //pos 是当前的文件位置
{
av_assert0(pos >= TS_PACKET_SIZE);
ts->pos47_full = pos - TS_PACKET_SIZE;
}
if (tss->type == MPEGTS_SECTION)
{ //已经跳过了adaption field
if (is_start)
{
/* pointer field present */
len = *p++; //这个len 一般为0
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;
}
}
}
else
{
int ret;
// Note: The position here points actually behind the current packet.
if (tss->type == MPEGTS_PES)
{
if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
pos - ts->raw_packet_size))
< 0)
return ret;
}
}
return 0;
}
TS包头解析
通过AV_RB16(packet + 1) & 0x1fff提取PID,
判断是否为有效数据包起始(is_start = packet[1] & 0x40),
检查连续性计数器(CRC)确保数据完整性
检查错误标志
适配域处理
根据afc = (packet[3] >> 4) & 3的值跳过适配字段(adaptation field),
仅处理有效负载数据。当afc == 3时需跳过适配域长度p[0] + 1字节812。
PID路由机制
通过ts->pids[pid]查找对应的过滤器(MpegTSFilter * tss),
根据过滤器类型(MPEGTS_PES或MPEGTS_SECTION)调用相应的处理回调函数.
例如
PSI/SI表数据会调用 write_section_data(ts,tss,p,p_end_p,1); //1代表is_start
由tss->section_cb 去回调pat_cb, pmt_cb, sdt_cb, scte_data_cb. 依据section_cb 指针的值
PES流会触发tss->u.pes_filter.pes_cb,其指针是mpegts_push_data
各回调函数就不详细解释了.
简单介绍一下组包过程. pes 保留了状态,
switch (pes->state)
{
case MPEGTS_HEADER:
case MPEGTS_PESHEADER:
case MPEGTS_PESHEADER_FILL:
case MPEGTS_PAYLOAD:
case MPEGTS_SKIP:
}
根据不同的状态对小包进行处理,最后合成一个合格的pkt,
然后通过ts->stop_parse标志控制退出循环.
不仅pes_cb 可以组包, 发现scte_data_cb 也能组包. pat_cb,pmt_cb,sdt_cb是不会组包的,
它们会影响ts状态, 例如会添加过滤器等