TS码流分析工具实战:从PAT/PMT到EPG与字幕排查
简介一款面向DVB/TS学习与调试的码流分析工具以树形结构逐层解析PAT、PMT、SDT、EIT以及Subtitle的PES包节点层级与SI/PSI数据结构一一对应适合刚接触数字电视协议的新人快速掌握TS帧结构也可用于现场码流问题定位。相比同类工具还额外解析LCN与Subtitle数据双击Subtitle的PES节点就能在右侧看到字幕图片的仿真显示同时支持仿真搜台结果展示、EPG仿真浏览双击EIT表中的eventid可弹出该事件的详细数据解析码流大小不受限制。解析完成后可将PAT、PMT、SDT、NIT或所有EIT表导出为网页文件打开parsing_result.html即可离线查看便于整理或对比分析。资源包共30个文件主体为可直接运行的exe程序并包含用于界面展示的js脚本、便于二次学习的c源码、gif图标、db数据及html结果模板压缩后仅161KB轻量便携。已有1912人学习下载适合想通过实际样例理解PSI/SI表结构、字幕及EPG处理流程的数字电视初学者和运维调试人员。1. 拿到黑匣子 TS 流先做码流分析再谈播放多数从业者第一次面对 TS 码流都是因为播放器黑屏或花屏然后开始用十六进制工具手动找 0x47。TS 结构说复杂也复杂188 字节一个包包头里藏着 PIDPID 又指向 PAT、PMT、PCR 和各路音视频任何一个环节错位都会让复用器或机顶盒直接罢工。这套码流分析工具解决的核心问题就是帮你把 TS 里的 PSI/SI、EPG、字幕和音视频轨以可视化的方式拆开三分钟定位到到底是 PID 映射错了还是 section 重复了还是 PCR 抖动太大。它适合复用流开发、EPG 联调、字幕兼容性排查以及做 DVB 接收端验证的工程师。下面从最基础的包头解析开始逐步把这套工具用出价值。2. 解析 PAT 与 PMT从 TS 包头到节目映射的第一次还原拿到任何一个 .ts 文件我一般不会急着拖进播放器。先把包头结构看明白再让工具去还原 PAT 和 PMT这一步决定了后面所有分析的准确性。TS 包里真正影响解析的字段其实很少大部分时间我们只关心 sync、PID 和 continuity_counter还有 payload_unit_start_indicator 这个标志位。2.1 TS 包头只有四个关键字段0x47、PID、载荷标志与 continuity_counterTS 包固定 188 字节理想情况下每隔 188 字节就会出现一个 0x47 同步字节。如果连续多个包的 0x47 都能对上说明文件是干净的 TS如果只在文件头出现一次后面全乱那大概率是录制工具出了问题或者文件根本是裸 ES 流被直接改了后缀名。工具里一般都有 sync 检查功能有的叫重新同步有的叫码流扫描本质上就是按 188 字节步长统计 0x47 的命中率。PID 是 13 位字段位于包头第 2 到第 3 个字节的高 13 位。PID 0x0000 固定是 PAT 表0x0010 是 NIT0x0011 是 SDT0x0012 是 EIT0x0014 是 TDT/TOT0x1FFF 是空包。每当我说先扫一遍 PID就是让工具统计每个 PID 出现的包数量然后按包数排序。这能很快看出视频流、音频流、字幕流分别占了多少带宽也能发现有没有 PID 重复复用。# 扫描 TS 包头统计所有 PID 的包数和命中率 ts_analysis --input live.ts --sync --pids这个命令做两件事--sync 参数让工具按 188 字节连续查找 0x47遇到连续丢同步会打印告警--pids 参数会把每个 PID 的包数输出到终端。我一般会先看有没有大量包落在 0x1FFF 空包上如果空包占比超过 30%说明复用器做了很多填充文件里无效内容偏多。选定 PID 之后下一步才是真正解析表结构。还需要关注包头里的 payload_unit_start_indicator。当这个标志位为 1 时表示当前包的有效载荷是某个 PSI/SI section 的第一个字节。PSI 表是分 section 传输的一个 section 可能跨多个 TS 包所以做表关联时不能只看 PID必须找到每个 section 的起始包否则会解析出一堆残缺数据。很多分析工具默认会丢弃不完整的 section只显示能通过 CRC 校验的表。2.2 先扫 PATprogram_number 与 PMT_PID 的映射关系PAT 表的 table_id 固定为 0x00永远从 PID 0x0000 传输。PAT 的作用是告诉解复用器这个传输流里有哪些节目每个节目的 PMT 拿一个单独的 PID 发出去。我见到最多的失败场景是拿到一个抓包文件播放器不出画但 PID 0x0000 上明明有数据这时 PAT 解析结果就是关键。用工具导出 PAT 时输出里会出现 transport_stream_id、program_number、PMT_PID 三个值。transport_stream_id 是传输流自身的唯一标识通常由运营商分配program_number 是节目号0x0000 开头的条目表示实际不存在的节目用于填充。重点看 program_number 不为 0 的那几行每一个都对应一路可切换的节目。比如一行写着 program_number1PMT_PID0x0100就代表节目 1 的 PMT 在 PID 0x0100 上发送。# 导出 PID 0x0000 上的 PAT 全部分区 ts_analysis --input live.ts --dump 0x0000导出后先核对 section 数量。正常情况下 PAT section 只有一个如果看到多个 section 且 CRC 都通过说明这个传输流里有超过 1024 个节目PAT 被拆成了多段。此时要把每个 section 的 PMT_PID 都记录下来不能只看第一个。遇到过某运营商在同一个 TS 里塞了三套节目第二套节目的 PAT section 起始位置和第一套紧挨着如果工具不支持多 section 导出很容易漏掉一套。2.3 再进 PMTstream_type、PCR_PID 与节目信息登记表拿到 PMT_PID 后切到对应 PIDPMT 的 table_id 为 0x02。PMT 里最重要的字段是 PCR_PID 和一组流描述每个流描述由 stream_type、elementary_PID、ES_info 组成。PCR_PID 是整个节目时间恢复的基准一般指向视频 PID但也见过指向音频或专用数据 PID 的理论上全节目所有 PID 的时间戳都应以它为准。# 解析节目 1 的 PMT列出全部流类型与 PID ts_analysis --input live.ts --pmt --program 1工具输出会类似这样program_number1PCR_PID0x0100stream_type0x1Belementary_PID0x0100stream_type0x0Felementary_PID0x0101stream_type0x06elementary_PID0x0102。看到 0x1B 基本可以认定视频是 H.2640x0F 是 AAC 音频0x06 是私有 PES 流字幕大概率藏在这里。把 PMT 内容和 PAT 里的 program_number 对照就知道这个 TS 的真正骨架是什么。要看懂 PMT还需要理解 ES_info 里的 descriptor。VLC 或 ffprobe 能显示出视频的 profile、音频的采样率靠的就是这些 descriptor。视频流常见 descriptor_tag 是 0x02 的 video descriptor音频是 0x03 或 0x04字幕是 0x59。我通常会把 PMT 里每个流对应的 descriptor 全部展开确认字幕 descriptor 里的小语种字段是否和目标地区一致。后续字幕分析还会在专门章节展开。3. 深入 PSI/SI 表NIT、SDT、EIT 到 EPG 的逐表拆解TS 流里除了 PAT/PMT 这套 PSI 表还有一堆 SI 表。SI 表的存在让机顶盒可以显示网络名、节目名、节目时间表也就是 EPG。这套分析工具对 SI 表的支持程度直接决定你能不能快速排查节目名乱码和EPG 不刷新这类问题。3.1 NIT 与 SDT网络拓扑与服务名的第一层信息NIT 的 table_id 是 0x40固定 PID 0x0010。NIT 里装着 network_id、传输流描述符和网络名称描述符。如果你手里是一个完整的 DVB 抓包打开 NIT 能看到运营商网络标识和频点信息。当你说这个 TS 是哪家网络的时实际上就是看 network_id 和 NIT 里的 network_name。SDT 的 table_id 是 0x42固定 PID 0x0011。SDT 里 original_network_id 和 transport_stream_id 一起定位某个具体传输流service_descriptor 则提供 service_name 和 service_type。service_type 0x01 是数字电视0x02 是数字广播0x19 是高清电视。很多调试问题最终都落在 SDT 的 service_name 编码上DVB 标准允许用 UTF-8 或 ISO-6937一旦机顶盒固件不支持某种编码节目名就会显示成乱码。table_id表名称高位 PID主要载荷0x40NIT0x0010网络标识、频点、运营商信息0x42SDT0x0011服务名、服务类型、LCN0x4EEIT present/following0x0012当前与后续事件0x50-0x6FEIT schedule0x0012多日 EPG 事件表0x70TDT0x0014UTC 时间0x73TOT0x0014UTC 时间与本地时移# 导出全部 NIT/SDT 相关 section ts_analysis --input live.ts --tables 0x40,0x42输出中如果 SDT 的 section 里出现重复 service_name或者同一 service_id 出现两次说明复用器在做节目调度时发生了 PID 复用冲突。这种情况机顶盒往往只认第一个 service_name后面的被忽略表现为切换节目后偶发黑屏。3.2 EIT 与 EPG事件表解析与 descriptor 处理EIT 表用于承载 EPGtable_id 从 0x4E 到 0x6F。0x4E 表示当前和后续事件0x50 到 0x6F 是计划表。EIT 的固定 PID 是 0x0012不过实际网络里经常能看到 EIT 被分到多个 PID 上做负载均衡解析时不能只盯一个 PID。工具导出 EIT 时通常会把 event_id、start_time、duration、running_status 和 descriptor 一起列出来。EPG 时间和时长是这里最容易出问题的地方。EIT 里的 start_time 是 UTC 时间采用 MJD 加 BCD 编码的方式duration 是小时分钟秒的 BCD 编码。工具如果能自动转换成北京时间一般会提示已读 TDT/TOT 做时区修正。如果你发现 EPG 显示的时间和实际播放时间差 8 小时十有八九是只看了 EIT 原始 UTC没有结合 TOT 的 local_time_offset 做偏移。短事件描述符 short_event_descriptor 是 EPG 真正展示的内容里面包含事件名称和一段简介。在 DVB 系统里这个描述符的字符编码经常不统一有的用 UTF-8有的用 ISO-6937还有直接用 GB2312 的私有编码。工具提供原始字节视图时我会先看事件名的编码声明再决定是否依赖它对字符串做解码。3.3 TDT/TOTUTC 时间基准与本地时移的校准逻辑TDT 的 table_id 是 0x70TOT 是 0x73两者都在 PID 0x0014 上传输。TDT 只带 UTC 时间TOT 多了一个本地时区偏移描述符。EPG 想要正确显示本地时间必须把 TDT/TOT 和 EIT 放在一起解析顺序不能颠倒。如果 TOT 里没有 local_time_offset机顶盒默认按 UTC 显示EPG 就会和实际节目错位一小时甚至更多。# 同时检查 TDT 和 TOT输出时区标记 ts_analysis --input live.ts --tables 0x70,0x73我遇到过一个比较隐蔽的情况码流里 TDT 正常TOT 的 local_time_offset 也带了 08:00但 EIT 的 start_time 本身已经被复用器从 UTC 改成了北京时间再叠加时移就会变成 16 小时偏差。这类问题只靠单个表看不出来必须把 EIT 和 TOT 两条记录对齐后人工确认一次。这也是码流分析工具比普通十六进制查看器强的地方它能把时间字段统一换算成 Unix 时间戳方便跨表交叉验证。4. 字幕与音视频轨判定从 stream_type 到 PES 层的逐步下探PAT 和 PMT 只是完成了 PID 层面的映射音视频能不能正常播放还要看 ES 层和 PES 层。这个分析工具在解析完 PMT 后会把每个流单独列出来视频流归为 VC 类目音频流按编码类型归类私有流里再细分字幕和 Teletext。这一章重点讲怎么用工具判定流类型以及为什么有些标记为视频的 PID 里没有任何有效载荷。4.1 stream_type视频编码类型的第一个判定依据PMT 里的 stream_type 是理解整个节目组成最关键的数字。stream_type0x02 代表 MPEG-2 视频0x1B 代表 H.264/AVC0x24 代表 HEVC0x01 是 MPEG-1 视频。注意这些值是 DVB 和 MPEG-2 系统标准约定的实际运营中有些运营商用 0x80 到 0xFF 之间的私有值表示 Dolby 或 AC-3这时不能只看 stream_type还要看 ES_info 里的 descriptor。我在调试一个视频兼容性问题时曾遇到 PMT 里写的是 0x1B但 0x0100 这个 PID 里实际载荷是 HEVC 的 NAL。工具对 H.264 和 HEVC 的起始码识别规则不同如果默认按 H.264 解析HEVC 的 NAL 会出现大量解析错误。解决办法是在工具里手动指定视频编码类型或者让工具先做 ES 层自动探测再和 PMT 里的 stream_type 对比。分析工具通常会给每个流标注声明的类型和探测到的类型两者不一致时就要警惕复用器配置错误。# 查看所有视频流声明类型与实测类型 ts_analysis --input live.ts --video --show-claimed-typeVC 类目下我一般会关注两个字段编码类型和分辨率描述符。MPEG-2 和 H.264 的 profile 信息不在 PES 头里而在 ES 的序列头或 SPS 中工具基本是通过解析 SPS 得到宽高和帧率。如果工具显示的宽高和预想不符先确认是否读到了正确的 SPS而不是被 PMT 里的 descriptor 误导。4.2 字幕 PID 藏在 PMT 的私有流里descriptor_tag 0x59DVB 字幕通常作为私有 PES 流传输stream_type 是 0x06elementary_PID 指向一个独立的字幕 PID。要确认这个 PID 是不是字幕要看 PMT 的 ES_info 里有没有 descriptor_tag0x59 的 subtitling descriptor。描述符里包含 language、subtitling_type、composition_page_id 和 ancillary_page_id。composite_page_id 用于标识字幕合成页很多播放器只认 composition page如果码流里只有 ancillary page字幕就显示不出来。# 列出 PMT 中所有 0x59 字幕描述符 ts_analysis --input live.ts --pmt --descriptors --tag 0x59Teletext 字幕则使用 descriptor_tag 为 0x56 的 descriptor里面存放着 magazine 和 page 编号。这两种字幕经常出现在同一个 TS 里PID 还不一样。分析时先分清是 DVB 字幕还是 Teletext再去选对应的解码方式。工具里如果看到 0x06 流但没有 0x59 也没有 0x56就不要把它当字幕流它更可能是或多或少的私有数据。字幕 PID 在 PES 层上也有明显特征。DVB 字幕 PES 的 stream_id 是 0xBD表示 private_stream_1。PES 有效载荷的第一个字节是 subtitling segment 的 sync_byte 0x0F后面跟着 segment_type0x20 是 composition segment0x22 是 ancillary segment。用工具打开字幕 PID 的 PES 头一眼就能确认这是不是真正的 DVB 字幕流。4.3 PES 层解析PTS/DTS 与字幕显示时间的对应关系PES 层里最常调的就是 PTS 和 DTS。视频 PES 往往同时带 PTS 和 DTS音频和字幕一般只有 PTS。PTS 是 33 位时钟频率 90kHz换算成秒直接除以 90000 即可。字幕显示的时机依赖 PTS 和视频帧的同步如果字幕 PID 的 PTS 序列和时间基准偏差过大播放器会有字幕延迟或提前消失。# 查看字幕 PID 的 PES 头提取 PTS 与 payload 类型 ts_analysis --input live.ts --pes --pid 0x0102 --head 32这个命令把每个 PES 包的前 32 个字节打印出来。重点看 PTS 是否单调递增如果连续两个 PTS 回退说明字幕流被重复插入或 PID 复用异常。还要看 payload 里的 segment_type 分布正常字幕在一秒内会有 composition 和 ancillary 交替出现纯 ancillary 字幕无法渲染。工具里可以直接统计各 segment_type 的包数这个统计比肉眼翻十六进制靠谱得多。音频轨的判定也可以用类似方式。stream_type0x0F 表示 ADTS 格式的 AAC0x11 表示 LATM 格式的 AAC。ADTS 每包有 7 字节固定头LATM 没有固定头所以分析工具判断音频类型时往往需要做帧同步探测。如果 PMT 写 0x0F但 PES 载荷里找不到 ADTS 头就很可能录到的是裸 AAC 文件被强行封装成 TS这时候播放器没声就一点也不奇怪了。5. 码流分析避坑五个常见误判与排查记录工具再顺手判断错了方向也会白忙半天。这里整理几个我实际踩过、也看别人踩过的坑每个都按现象、原因、解决三步走供你在分析时对号入座。5.1 PID 0x0000 有数据但 PAT 解析不出节目表现象是工具能统计到 PAT 的包数但展开 PAT 时节目列表为空或者只剩 program_number0 的填充项。第一次遇到时我以为文件损坏后来对比原始码流才发现 PAT 里实际有节目只是 section 的 CRC 校验失败工具默认丢弃了坏 section。原因多数是抓录设备在传输过程中丢字节导致 TS 包错位后整个 PAT section 内容错乱。还有一种情况是 PAT 的位置被另外一段重复数据占据工具用固定 PID 找 PAT但收到的 PAT section 里 table_id 不是 0x00。解决方法是先用 sync 重新对齐再单独导出 PID 0x0000 的原始十六进制手动检查 table_id 是否 0x00。如果确认是抓录丢包就回到源头设备调整抓包参数这种文件用任何工具都不可能完全恢复。5.2 continuity_counter 连续跳变被误报成丢包现象是分析工具提示某个 PID 丢包严重continuity_counter 从 5 直接跳到 9。我第一反应是网络丢包但把对应包的 adaptation_field_control 调出来看发现跳变的包全是 adaptation-only 包不含有效载荷。原因在于 continuity_counter 只对含有效载荷的包递增。TS 协议里 adaptation_field_control10 的包是纯适配包没有 payload计数可以保持不变而 adaptation_field_control11 的包既有适配字段又有载荷计数必须递增。有些复用器会插入大量 adaptation-only 包做 PCR 定位如果工具没处理这个规则就会把计数跳变误判为丢包。解决方法是把分析的包过滤条件改成只看 payload 不为空的包或者开启工具里忽略无载荷包的选项再统计一次丢包率。5.3 PMT 里有字幕 PID 但播放器始终不显示字幕现象是分析工具能在字幕 PID 上抓到大量 PES 包segment_type 统计也有 composition 段但播放器就是不显示字幕。我一开始怀疑是播放器字幕开关没打开后来把字幕 PID 的页面信息全部列出来发现 composition_page_id 和 ancillary_page_id 都正常但 language 字段是空的。原因通常是复用器在 subtitling descriptor 里没有填写 language code播放器会把空 language 当成未知字幕拒绝渲染。解决这个问题分两种路径如果只是想验证码流本身用工具把 composition page 里的 text 数据导出人工确认字幕内容存在如果是做产品验收就要推动复用器侧把 language 字段补齐否则即使码流结构完全合规终端设备也不会展示。5.4 EPG 事件时间与播出时间不一致现象是 EIT 解析出来的事件开始时间比实际播出时间早 8 小时。原因几乎都是 EIT 里的 start_time 用的是 UTC 时间而显示端没有结合 TOT 的 local_time_offset 做转换。但也有反例就是复用器已经将 EIT 时间本地化分析工具再叠加一次时移结果反而错得更离谱。解决时先在工具里分别查看 TDT、TOT、EIT 三张表的原始时间字段确认 EIT 的 MJD 是标准 UTC 还是本地时间再看 TOT 的 offset 是多少。如果工具支持时区表达式就把 local_time_offset 的 country 和 time_offset 一并导出。从那以后我每次分析 EPG都会强制把三张表导出到同一个文件里做时间轴对齐不再单独看某个表。5.5 PCR 间隔看似正常但抖动曲线锯齿严重现象是工具统计到 PCR 间隔没有超过阈值但绘制 PCR 时间戳曲线时能看到明显锯齿。在看门狗逻辑里这类问题会导致接收端的时钟恢复表现差表现为画面卡顿但不对时间戳做致命告警。原因多数是码流经过多级复用器或网络抖动缓冲PCR 在到达接收端前已经被重新打上时间戳。分析工具在离线文件上看到的原始 PCR 其实已经经过了抓录设备的时钟偏差直接用逐包比较 PCR 的方式很容易得到错误结论。解决方法是先把 PCR 值做一次线性回归用回归残差代替原始时间戳差分。工具里如果有PCR smoothing或滤波选项建议打开后重新生成曲线再判断真实抖动是否在可接受范围内。如果平滑后仍然出现周期大跳变才能确认是复用器 PCR 时钟源有问题。6. 进阶用法用 PID 过滤与导出做交叉验证工具装好只是开始真正提升效率的是把过滤和导出组合起来。我常用的验证流程是先过滤出关键 PID 并导出包数量再把 PCR 曲线和字幕 PTS 分开检查最后把 PAT/PMT/SDT/EIT 的解析结果合并到一张 CSV 里做关联。这套流程能把结构问题、时间问题和内容问题拆成三个独立维度逐个击破。# 过滤出视频 PID 的前 100 个包并打印十六进制 ts_analysis --input live.ts --filter 0x0100 --count 100 --hex这个命令最有用的地方不是看内容而是确认过滤后的包是否完整。如果过滤后的包列表里出现大量 payload_unit_start_indicator 为 1 但前一个 section 未结束的包说明 TS 包在录制时已经错位后续所有基于 PID 的统计都不可信。做字幕分析时我会把字幕 PID 的 PES 头导成 CSV重点看 PTS 的单调性再结合视频 PID 的 PTS 做差值确认字幕不是只存在于码流里而是真的能对齐。# 导出 PCR 间隔到 CSV用外部脚本画分布图 ts_analysis --input live.ts --pcr --pid 0x0100 --csv pcr.csvPCR 导出的 CSV 一般包含三列包序号、PCR 原始值、相邻 PCR 间隔。先用脚本把间隔大于 100ms 的行标红再看这些行是否集中在某些固定区间。如果集中在 I 帧附近说明视频编码器在 I 帧前的编码延迟导致 PCR 插入不够密集这是编码参数问题如果均匀分布在整个文件里则更可能是复用器插包策略的问题。两种情况的整改方向不一样不导出数据只看曲线容易把问题归类错。我还习惯用过滤条件验证 EPG 描述符的编码问题。把 EIT PID 的所有 section 导出为文本后用 UTF-8 和 ISO-6937 分别解码 short_event_descriptor对比哪个能正常还原中文节目名。很多运营商宣称用的是 UTF-8实际复用时却按本地私有编码发送只有用工具解开原始 descriptor 才能发现。从上次 EPG 时间出错的教训后我每次拿到新 TS 文件都强制走一遍固定流程sync 扫描确认文件干净PAT/PMT 确认节目映射再按视频、音频、字幕三个维度的 PID 单独导出数据做交叉验证。这套工具帮我省掉了大量手算和肉眼对十六进制的时间至少在码流分析这件事上少走了不少弯路。希望帮到你。本文还有配套的精品资源点击获取