简介: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 Stream | Program Stream |
|---|---|---|
| 包结构 | 固定 188 字节 | 变长 pack |
| 适用环境 | 有误码、易丢包 | 几乎无错 |
| 多节目复用 | 支持,独立时基 | 不支持 |
| 典型应用 | DVB/ATSC/ISDB、OTT 直播、TS over RTP | DVD/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 标准,值得每个做音视频封装的工程师存一份在本地,排查时逐字段核对。希望帮到你。
本文还有配套的精品资源,点击获取