MPEG-TS 基础:包结构、PID、PAT、PMT

做协议分析的人都知道,MPEG-TS 是数字电视、广电、甚至 IP 流里绕不开的东西。说白了,它就是个固定长度的“集装箱”——每个箱子 188 字节,不多不少。我刚开始接触 TS 时,看着一堆十六进制数完全懵圈,后来真正去抓了一次流,才明白它为什么这么设计。

TS 包结构——188 字节的“硬规矩”

每个 TS 包最前面 4 个字节是包头,后面 184 字节是负载。我把头部字段列个表,你对照抓包工具看就清楚了:

字段 长度(bit) 作用
同步字节 8 固定 0x47,定位包起始
传输错误指示 1 1 表示当前包有错误,解码器应丢弃
有效载荷单元起始指示 1 1 表示负载是 PES 或 PSI 的起始
传输优先级 1 用于流中优先级区分
PID 13 包标识符,核心字段
传输加扰控制 2 00 未加扰,其他表示加扰模式
自适应字段控制 2 01 仅负载,10 仅自适应,11 两者都有
连续计数器 4 0~15 循环,检测丢包
关键点:49 字节(1 字节同步 + 3 字节其他)固定,你抓到的第一个字节必须是 0x47,否则要么协议不对,要么同步丢了。

PID——每路流的“身份证”

13 位 PID,范围 0~8191。其中 0x0000 是 PAT(节目关联表),0x0001 是 CAT,0x1FFF 是空包。其余 PID 可以分配给视频、音频、字幕等。我记得有一次调试机顶盒,发现特定频道黑屏,一查 PID 表才发现视频 PID 写错了……嗯,从那以后我抓流第一件事就是先过滤 PAT。

我个人习惯在 Wireshark 里直接加 ts.pid == 0x0000 过滤,快速定位 PAT 包。你可以试试看。

PAT & PMT——节目信息的“导航地图”

PAT(Program Association Table)承载在 PID=0x0000 的 TS 包中。它本身不包含节目内容,而是告诉你:每个节目的 PMT 在哪个 PID 上。PMT(Program Map Table)则进一步列出每个节目的音视频流 PID、编码格式等信息。

我曾经在工作中遇到一个奇葩问题:某节目只有音频没有视频。抓包后发现 PMT 里确实有两个流,但视频流的 PID 始终没有数据包。最后发现是编码器配置错了。你想想看,如果 PAT/PMT 不完整,解码器根本不知道该去哪个 PID 拿数据。

实操小技巧:抓流时先确认 PAT 周期(通常每 100ms 一次),再解析 PMT。如果长时间收不到 PAT,说明传输流损坏或黑屏。

整体关系——看一眼这张图就懂了

TS 流中 PAT/PMT 与音视频流的关系 TS 流 -> PAT -> PMT -> 音视频流 TS 包流 PID=0x0000 PAT 表 节目号 → PMT_PID 解析 PMT_PID PMT - 节目1 视频PID: 0x0100 音频PID: 0x0101 PMT - 节目2 视频PID: 0x0200 音频PID: 0x0201 实际抓流顺序: 1. 找到 PID=0x0000 的 TS 包 → 2. 解析出 PAT → 3. 根据节目号拿到 PMT 的 PID → 4. 解析对应 PMT → 5. 得到音视频流 PID → 6. 过滤这些 PID 的视频/音频包

实战中要注意的几个坑

  • PAT/PMT 必须周期性发送:标准要求每 100ms 内至少一次。我曾遇到一个编码器配置了“仅开头发一次 PAT”,结果解码器在切换频道时直接黑屏。
  • PID 冲突:同一 TS 流内所有 PID 必须唯一。如果两个节目不小心用了同一个视频 PID,解码器会疯掉。
  • 空包问题:PID=0x1FFF 的空包可以忽略,但有些设备会用空包做填充速率匹配,不要误以为这些包有数据。
避坑指南:我曾经在抓一个直播流时,发现解析出的 PMT 列表里竟然有 50 多个节目,但实际只有 3 个有效。原来编码器没做清理,残留的历史 PMT 还在 PAT 里。如果你用程序自动解析,千万要加节目号验证,否则白白浪费时间。

小结

记住一句话:没有 PAT,你连节目在哪都不知道;没有 PMT,你连音视频数据在哪都不知道。理解 TS 包结构 + PID 筛选 + PAT/PMT 解析,就等于拿到了 TS 流的钥匙。动手抓一次流,跟着 PAT 走一遍,你会发现这东西其实没那么玄乎。