news 2026/10/22 22:36:40

MPEG-2 Systems从TS/PS到PAT/PMT的码流解析与排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MPEG-2 Systems从TS/PS到PAT/PMT的码流解析与排查

简介:ISO/IEC 13818-1:2019 是国际标准化组织与国际电工委员会联合发布的 MPEG-2 系统层国际标准,面向音视频编码、多媒体传输及协议开发相关技术人员。该 PDF 包含完整英文版 305 页正文,系统阐述系统架构、编码格式、音频与视频信号处理、同步与时序控制等核心规范,并涉及专利权与商标权问题,可作为多媒体系统设计与协议核对的权威依据。资源包内共 1 个文件,类型为 PDF,压缩后约 20.93 MB;该版本为第七版正式文本,整合了 2018 年修正案,替代旧版内容。目前已有 328 人学习下载,适合需要查阅标准原文、开展编码器/解码器调试或完成科研引用的工程师与研究者使用。无论是验证传输流语法,还是理解系统时钟与同步机制,这份完整版标准都能提供直接、详实的条款支撑。

1. 为什么搞流媒体的人绕不开 ISO/IEC 13818-1:2019 这份 305 页 PDF

上次接手一个 IPTV 直播流黑屏的工单,业务层日志全正常,我用 ffprobe 按 PID 逐个看,发现视频 PES 的 PTS 字段解析出来一塌糊涂。翻出 ISO/IEC 13818-1:2019 一对照,定位到是某个透传设备把 PES_header_data_length 改写了。所以对做封装、播放器、数字电视和 OTT 的工程师来说,MPEG-2 Systems 这份标准不是拿来背的,是拿来查的。它由 ISO 与 IEC 联合发布,第七版(2019),与 ITU-T H.222.0 是同一文本,定义了传输流(TS)、节目流(PS)、PES 封装、PSI/SI 表、时间戳与缓冲模型。这份 PDF 适合码流分析、协议解析、播放器兼容性调试的人反复查阅。

2. TS 和 PS 怎么选:从多节目复用到准无损环境的两条路线

2.1 系统层管什么:压缩层之外的公共骨架

ISO/IEC 13818-1 这部分的职责边界很清晰:视频压缩编码归 Part 2,音频归 Part 3,这里只定义系统层。系统层把视频、音频、字幕、私有数据打包成可复用、可同步、可缓冲的单一码流。摘要里提到的六个基本功能——多路压缩流解码同步、交织成单流、解码启动缓冲初始化、连续缓冲管理、时间标识、复用与信令——全部落在系统层。

注意一个细节:标准支持承载的编码格式远不止 MPEG-2 视频。从摘要正文可以看到,它明确列了 ISO/IEC 14496-2、H.264/14496-10、H.265/23008-2、JPEG 2000/15444-1 Annex M,音频侧有 13818-7、14496-3。这就是为什么现在很多 DVB 流里跑 H.265 仍然用 MPEG-2 TS 封装——系统层和压缩层是解耦的,TS 只是容器。

解码器拿到 TS 流之后的动作是固定的:先找同步字节,再解析 PSI 表,建立 PID 与节目元素的映射,然后按时间戳送显。压缩层怎么解码不是这层管的事,但打包格式不对,压缩层再厉害也解不出像样的画面。

2.2 都叫 MPEG-2 系统层,TS 和 PS 差在哪

TS(Transport Stream)和 PS(Program Stream)是系统层提供的两种复用形式。标准原文对两者的定位说得很直白:PS 适用于几乎无错环境,支持软件处理;TS 更适用于容易出错的传输环境。

从结构上拆,TS 是固定长度 188 字节的包,每个包 4 字节头加负载,包头里有 PID、adaptation_field_control、continuity_counter 等字段;PS 是变长包,以 pack_start_code 0x000001BA 起始,内部嵌 system_header、PES 和描述符。前者天生抗丢包和误码,后者依赖信道干净,换来的好处是结构简单、适合文件存储和本地播放。

多节目复用能力是两者最大的分水岭。TS 能把多个节目、多个独立时基的流复用在一条物理通道里,每个节目有自己的 PMT 和 PCR;PS 只能承载单一时基下的一个节目内容。这是选型时首先要明确的边界。

对比项Transport StreamProgram Stream
包结构固定 188 字节变长 pack
适用环境有误码、易丢包几乎无错
多节目复用支持,独立时基不支持
典型应用DVB/ATSC/ISDB、OTT 直播、TS over RTPDVD/VOB、本地文件、部分监控录像
起播方式依赖 PAT/PMT 建立映射依赖 pack 头和 system_header

2.3 工程选型的实际判断

我自己做选型时一般就三个问题:链路干净吗?要复几路?需要随机访问吗?

信道是广播网、IPTV 组播或公网 RTP,一律 TS,别犹豫。188 字节定长包让丢包检测、FEC 前向纠错、PID 过滤都很好做。如果是做蓝光、DVD 这类文件型产品,或者录制归档,PS 更合适,文件随机访问友好,结构开销也比 TS 头小得多。还有一种混合场景:编码器输出 PS 到本地,传输前转成 TS,标准第 2.8 节专门讲了与 ISO/IEC 11172 的兼容,就是考虑这类环节。

2.4 七版演进透露的行业信号

这份第七版替代了 2018 年第六版,同时纳入了 2018/Amd.1 修正案。从 1995 年 H.222.0 首版到现在,版本史里能看到行业需求的迭代:2000 年前后补 MPEG-4 数据承载,2006 年前后补 DASH 的参考,2014 年后持续加 H.265 和元数据承载。判断一个封装协议有没有生命力,看它有没有在不断吸纳新编码格式就够了——MPEG-2 TS 至今还是数字电视事实标准,不是因为老,而是因为扩展机制设计得够稳。

3. 从 PES 到 PAT/PMT:逐字节解析节目映射关系

3.1 PES:压缩码流进入系统层的第一个包装

PES 是压缩流和复用层之间的中间容器。标准规定,PES 由 packet_start_code_prefix(3 字节 0x000001)、stream_id(1 字节)和 PES_packet_length 起始。stream_id 的高位有明确分工:0xC0~0xDF 是音频,0xE0~0xEF 是视频,还有其他值对应私有流、字幕等。只看 stream_id 就能判断 PID 里装的是什么类型。

PES 头里值得注意的字段:PTS_DTS_flags 决定有没有 PTS 和 DTS;PES_header_data_length 给出扩展头长度;data_alignment_indicator 表示负载是否从访问单元起点开始。很多解析器翻车,就是把 PES_header_data_length 当成固定值,遇到视频流 PES_packet_length 为 0 的情况直接算错偏移。

PES 不是 TS 专属,PS 里同样有。区别在于承载方式:TS 把 PES 切成 184 字节以下的块装进 TS 包,PS 把 PES 放进 pack 负载。所以解析 PES 的逻辑应该独立于外层传输,单独封装成函数。

3.2 PAT 和 PMT:节目与 PID 的两级映射表

TS 的解码入口是 PAT(Program Association Table),固定 PID 0。PAT 本身是一张 section,每个表项把 program_number 映射到 PMT 的 PID。拿到 PAT 之后,再去对应 PID 上收 PMT(Program Map Table),PMT 里才列出真正的音视频 PID。所以链路是 PAT → PMT → 音视频 PID,一级比一级具体。

PMT 的 table_id 是 0x02,里面包含 PCR_PID 字段——这个字段定义了该节目 PCR 所在的 PID,通常指向视频流,但不强制。随后是 program descriptor 循环和 elementary stream 循环,每个流用 stream_type 标明编码格式,比如 0x1B 是 H.264,0x24 是 H.265,0x0F 是 AAC。排查黑屏时,先确认这三层映射有没有断开,比追业务日志快得多。

3.3 用 Python 快速验证 PAT/PMT 解析

下面这段脚本从 TS 文件里抽 PAT 并打印字段:

#!/usr/bin/env python3 # 解析 TS 文件中的 PAT 表项,确认节目到 PMT PID 的映射 import sys TS_PACKET = 188 SYNC = 0x47 def read_section(payload): # 第一个字节是 pointer_field,跳过它到达 section 起点 offset = 1 + payload[0] if payload[offset] != 0x00: # PAT 的 table_id 固定为 0x00 return # section_length 只占低 10 位,注意屏蔽高 4 位 sec_len = ((payload[offset + 1] & 0x0F) << 8) | payload[offset + 2] if sec_len <= 0: return print(f"section_length={sec_len}") i = offset + 8 # 跳过 section 头部的固定字段 end = offset + 3 + sec_len - 4 # 末尾 4 字节是 CRC while i < end: prog_num = (payload[i] << 8) | payload[i + 1] if prog_num == 0: # program_number=0 指向 NIT,属于网络信息表 nit_pid = ((payload[i + 2] & 0x1F) << 8) | payload[i + 3] print(f"NIT -> PID=0x{nit_pid:X}") else: pmt_pid = ((payload[i + 2] & 0x1F) << 8) | payload[i + 3] print(f"program_number={prog_num} -> PMT PID=0x{pmt_pid:X}") i += 4 def main(ts_path): with open(ts_path, "rb") as f: while True: packet = f.read(TS_PACKET) if len(packet) < TS_PACKET: break if packet[0] != SYNC: continue pid = ((packet[1] & 0x1F) << 8) | packet[2] if pid != 0: # 只收 PID=0 的 PAT continue # 从 TS 包头第 4 字节取 adaptation_field_control afc = (packet[3] >> 4) & 0x03 if afc == 2: # 只有 adaptation field,没有负载 continue offset = 4 if afc == 3: # 头部有 adaptation field,需要跳过 offset += 1 + packet[4] read_section(packet[offset:]) break if __name__ == "__main__": main(sys.argv[1])

逻辑说明:TS 包头的第 1 字节必须是 0x47 同步字,PID 由第 2 字节低 5 位和第 3 字节组合而成。PAT 包的 PUSI(payload_unit_start_indicator)为 1,所以负载第一个字节是 pointer_field,指向 section 实际起点。PAT 的每个表项固定 4 字节,前两字节是 program_number,后两字节是 PMT PID。这个脚本只处理第一个 PAT section,实际产品里还要遍历所有 section 和 version 变化。

3.4 PMT 解析的扩展

PAT 能定位到 PMT PID 之后,PMT 自身的解析结构是:table_id=0x02,紧接着是 section_length,之后有 program_number、version_number、PCR_PID,然后是描述符循环长度和流循环。每个流条目也是 4 字节起步:stream_type(1 字节)、elementary_PID(2 字节)、ES_info_length(1 字节),后面跟该流的描述符。

解剖 PMT 最有价值的两个点:一是 PCR_PID 与视频 PID 是否一致,如果指向了音频 PID,很多解码器会行为异常;二是 stream_type 与 PID 里的实际编码格式是否匹配,转码后 PID 没更新是常见事故。

4. 时钟与缓冲模型:PCR、PTS/DTS 与 T-STD 的配合边界

4.1 PCR:解码端时基恢复的锚点

PCR(Program Clock Reference)是 27MHz 时钟的采样值,由 33 位 base 和 9 位 extension 组成,base 的单位是 90kHz,extension 是 27MHz 的余数。按标准公式PCR = base * 300 + extension换算到 27MHz 刻度。解码端靠 PCR 恢复系统时钟,音视频的 PTS/DTS 都以这个时钟为基准,PCR 抖动直接表现为音画不同步。

PCR 在 TS 里的位置不固定,它藏在 adaptation_field 里。adaptation_field_control=2 或 3 且 PCR_flag 置位时,adaptation 字段的第 1 字节起连续 6 字节是 PCR。提取逻辑如下:

# 假设 af 是从 TS 包 adaptation_field 起点开始的数据 # af[0] 是 flag 字节,bit4 是 PCR_flag if af[0] & 0x10: # PCR_base 占 33 位,PCR_ext 占 9 位 pcr_base = (af[1] << 25) | (af[2] << 17) | (af[3] << 9) | (af[4] << 1) | (af[5] >> 7) pcr_ext = ((af[5] & 0x01) << 8) | af[6] pcr_27mhz = pcr_base * 300 + pcr_ext print(f"PCR={pcr_27mhz} ({pcr_base}.{pcr_ext})")

参数说明:af[0] 的 bit4 对应 PCR_flag,置 1 才说明 PCR 字段存在。PCR_base 从 af[1] 开始,跨 4.5 字节;af[5] 的最高位是 base 的最后一位,最低位是 extension 的第一位;af[6] 是 extension 的低 8 位。这个位级操作没法偷懒,很多解析器就是在这少移了一位,算出来的 PCR 差 300 倍。

4.2 PTS 和 DTS:两套时间戳解决视频重排问题

PTS 是显示时间戳(Presentation Time Stamp),DTS 是解码时间戳(Decoding Time Stamp)。没有 B 帧的时候两者相等;有 B 帧时,编码顺序和显示顺序不一致,DTS 先于 PTS,解码器必须同时拿到两套时间才能正确重排。音频只有 PTS,且通常和 DTS 一致。

PTS/DTS 都是 33 位,单位是 90kHz,封装在 PES 头里,由 PTS_DTS_flags 控制:为 2 时只有 PTS,为 3 时 PTS 和 DTS 都有。提取 5 字节的位操作如下:

# b 是 PES 头里 5 字节的 pts 字段,要从 '0011' 前缀之后开始取值 pts = (((b[0] >> 1) & 0x07) << 30) | (b[1] << 22) | ((b[2] >> 1) << 15) | (b[3] << 7) | (b[4] >> 1) # 单位是 90kHz,转换成秒 pts_sec = pts / 90000.0 print(f"PTS={pts} ({pts_sec:.3f}s)")

注意 b[0] 的高 4 位是固定标识,后 3 位是 PTS 最高有效位;b[2] 和 b[4] 的最低 1 位是 marker_bit,取值前必须右移丢弃。PTS 缺失时播放器只能按到达顺序渲染,遇到 B 帧序列就会花屏或卡顿,这是判断播放器兼容性问题时的首要排查点。

4.3 T-STD 缓冲模型:系统目标解码器不是玄学

T-STD(Transport Stream System Target Decoder)是标准定义的系统目标解码器模型,描述 TS 流进入解码器后经过各级缓冲的约束。它规定解码器必须有传输缓冲、复用缓冲、主缓冲等层级,并对每个层级的大小和输入速率做了限制。为什么 PCR 间隔不能太大?为什么码率抖动不能太夸张?都能从 T-STD 的缓冲模型推出答案。

实际工程里,T-STD 的约束往往由复用器来保证。复用器插入 PCR、切分 PES 到 TS 包时,必须保证缓冲不溢出、不读空。标准第 2.4 节的可复用流语义部分就是干这个的。自己做码流分析时,T-STD 不需完整实现,但要知道缓冲模型的存在——它解释了为什么同样的视频,有的复用器在小缓冲设备上卡顿,有的没事。

5. 避坑手册:解析 MPEG-2 Systems 码流的五个常见翻车现场

5.1 continuity_counter 连续跳变:先分清丢包还是重复包

现象:播放过程中偶发卡顿、马赛克,按 PID 统计发现 continuity_counter(CC)不连续。

原因:CC 是 4 位计数器,0~15 循环,每个 PID 独立计数。网络抖动或解码器缓冲溢出会导致丢包;但也有网络重传机制会让同一个包重复到达,CC 呈现相邻递增但负载相同的形态。

解决:不要见到 CC 不连续就报丢包。先比对负载内容,如果连续两个包的 PID、CC 相同且负载完全相同,这是重复包,按规范应丢弃后者;如果 CC 跳变且负载不同,才是真丢包。tsduck 的 continuity 分析器会分别统计这两类,建议先跑一遍再下结论。

5.2 PCR 间隔超标:音画慢慢偏掉的头号元凶

现象:直播流长时间运行后音画逐渐不同步,轻微到几乎察觉不到,但过一小时就明显。

原因:PCR 插入间隔太长,解码端 27MHz 时钟校正不及时,时间戳累积漂移。PCR 间隔问题很难在短时间测试里暴露,属于典型的“跑久了才翻车”的坑。

解决:工程上常见做法是 PCR 间隔控制在 40ms 以内,最坏不要超过 100ms,这是 DVB 和 ATSC 的通行建议。检查复用器配置里 PCR_PID 的插入策略,保证每个节目每 40ms 至少有一个携带 PCR 的包。

5.3 PMT 里的 PCR_PID 写错:解码器黑屏或反复尝试

现象:流能识别,PID 也能看到,但解码器一直黑屏,日志里反复出现“waiting for PTS”或“PCR not found”。

原因:PMT 里 PCR_PID 指向了不存在的 PID,或者错误指向了音频 PID。解码器找不到有效 PCR 就无法恢复系统时钟,PTS/DTS 失去参照,进入等待状态。

解决:逐层核对 PAT → PMT → PCR_PID。最直接的办法是解析 PMT 后打印全部字段,确认 PCR_PID 和视频 PID 是否一致,以及该 PID 上是否真的有带 PCR 的包。曾有项目因为复用器配置界面把 PCR 来源选成音频,黑屏折腾了两天,最后就是一行配置的问题。

5.4 PES_packet_length 与真实负载不符:ffprobe 报错的来源

现象:ffprobe 显示 invalid PES header 或 duration 抖动,但流能播放。

原因:标准规定视频流 PES_packet_length 可以置 0,表示长度由后续 TS 包边界决定;某些工具或中间设备会把该字段改写成实际值,一旦透传或切包不精确,长度就和负载对不上。

解决:解析 TS 里的 PES 时不要依赖 PES_packet_length 判断负载边界,要依靠 TS 包的 PUSI 标志识别 PES 起点。视频 PES 长度字段置 0 是合法的,把它当错误报出来反而是解析器自己的问题。

5.5 PS 流的 pack 头和 system_header 缺失:文件播放器直接罢工

现象:拿到一个 .mpg 或 .vob 文件,播放器打不开,部分播放器黑屏有声或弹格式不支持。

原因:PS 流以 pack_start_code 0x000001BA 为起始,system_header 的起始码是 0x000001BB。有些工具从 TS 抽取裸流后直接改了扩展名,PS 结构根本不存在。

解决:先检查文件头部是否有 0x000001BA。缺失的话不能硬改扩展名,需要重新用封装工具按 PS 标准生成。PS 的 PES 对 PTS 的要求也比 TS 严格,很多播放器遇到 PTS 缺失会拒绝渲染,这是做 DVD 兼容时要额外注意的点。

6. 把排查沉淀成习惯:tsduck 与 ffprobe 的配合

6.1 两个命令的定位差异

ffprobe 适合看单条流的时间戳和帧信息,tsduck 适合看容器层级的完整结构与连续性问题。排查 MPEG-2 Systems 问题时,我一般先用 tsduck 验证容器健康度,再用 ffprobe 深入看视频流内部:

# 检查 PID 统计、continuity_counter 连续率和 PAT/PMT 结构 tsp -I file input.ts -P continuity -P pcr -O drop # 用 ffprobe 查看视频流的 PTS/DTS 明细 ffprobe -show_packets -select_streams v input.ts

其中-P continuity输出每个 PID 的 CC 跳变统计和重复包计数,-P pcr分析 PCR 间隔最大值、最小值和抖动范围。两条命令跑完,容器层有没有问题基本就能定性。

6.2 固定阈值,形成基线

排查了这么多年,我把几条硬指标设成了默认基线:PCR 间隔不超过 100ms,PTS 差值不超过 0.5s,CC 不连续率按节目不超过 1%。每个新项目启动前先跑一遍,把输出保存下来,后续出问题直接对比基线。这方法治标也治本——流媒体问题大多是性价比高的“玄学”,其实是基线没建立。从那以后我每次处理黑屏、音画不同步的工单,都强制走一遍容器层初检再碰业务层,极少再在协议层浪费时间。这份 ISO/IEC 13818-1:2019 标准,值得每个做音视频封装的工程师存一份在本地,排查时逐字段核对。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 22:35:57

大厂Java面试复盘:从HashMap连环追问看后端基本功

前阵子收到一位读者发来的面试复盘&#xff0c;他面的是某互联网大厂Java后端岗。这位读者给自己起了个帖子里用的网名叫“谢飞机”&#xff0c;因为那天的面试节奏就像坐飞机——面试官从头到尾坐在对面&#xff0c;语气平静&#xff0c;问题一个接一个往下压&#xff0c;四十…

作者头像 李华
网站建设 2026/10/22 22:36:13

t3code:纯文本标签驱动的代码片段管理与复用工具

1. 项目缘起与核心定位第一次看到"t3code"这个名字&#xff0c;我的直觉是&#xff1a;这大概率是一个跟代码生成、代码工具链或者某种轻量级编码框架相关的东西。事实也确实如此。t3code 本质上是一个面向开发者的代码片段管理与快速复用工具&#xff0c;它的核心目…

作者头像 李华
网站建设 2026/10/22 22:36:39

串口服务器深度实践:RS232/485兼容性与Modbus TCP桥接关键解析

1. 为什么一台“黑盒子”能接住三十年前的设备&#xff0c;却让现代工程师半夜改配置&#xff1f;你有没有见过这样的场景&#xff1a;某高校实验室角落里&#xff0c;一台贴着“2003年购入”标签的示波器&#xff0c;面板上还残留着褪色的胶带印&#xff1b;旁边是崭新的工业数…

作者头像 李华
网站建设 2026/10/16 1:03:40

DDR3初始化与校准全链路实战指南

1. 为什么DDR3初始化不是“插上就能用”的简单事很多人第一次接触内存调试&#xff0c;会下意识认为&#xff1a;把DDR3颗粒焊到板子上&#xff0c;写好控制器寄存器配置&#xff0c;通电一按复位键&#xff0c;内存就该立刻响应读写命令了——就像插U盘进电脑那样自然。但现实…

作者头像 李华
网站建设 2026/10/22 14:05:49

程序员外包接单避坑指南:四年经验总结的平台筛选与需求识别策略

自己做程序员外包接单这件事&#xff0c;前前后后摸爬滚打了四年多。从一开始在各大接单平台上像个无头苍蝇一样乱撞&#xff0c;被坑过、被骗过、也被离谱需求折磨到怀疑人生&#xff0c;到现在基本能在一个小时内判断一个平台值不值得投入时间、一个需求能不能接。这中间花出…

作者头像 李华
网站建设 2026/10/16 1:03:45

Android视频转换工具实战:Java+FFmpeg的转码实现与兼容适配

去年接了个私活&#xff0c;对方想给公司内部做一个批量的视频处理工具&#xff0c;把各种不同设备录出来的视频统一转成便于统一管理和分享的格式&#xff0c;重点是Android手机上能跑。当时拿到需求的第一反应是&#xff1a;这活儿在Android上用纯Java写&#xff0c;代码量会…

作者头像 李华