mpegts.c中handle_packet() 函数代码注释
author: hjjdebug
date : 2025年 07月 14日 星期一 17:13:07 CST
descrip: mpegts.c中handle_packet() 函数代码注释
文章目录
- 1. handle_packet() 的功能
- 2. 上层调用接口
- 3. 代码注释
- 4. 概括归纳.
1. handle_packet() 的功能
ffmpeg 中函数调用链为: handle_packets → handle_packet,
hanle_packet 每次分析188字节
解析TS包头部信息,并根据PID(包标识符)将数据分发给对应的过滤器进行处理.
简单说就是解析,组包.
handle_packets 就是循环调用handle_packet 来处理一系列小包
可见handle_packet 是数据包处理的核心函数,承上启下.
其再上一层往往是是为了读取一个pkt
2. 上层调用接口
举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
3. 代码注释
来自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; //提取pidis_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 PIDis_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 fieldif (is_start){/* pointer field present */len = *p++; //这个len 一般为0if (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 PMTif (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;
}
4. 概括归纳.
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状态, 例如会添加过滤器等