恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
ISO/IEC 13818-1系统层深度解析:TS流、PSI表与PCR
首页
资讯中心
/
ISO/IEC 13818-1系统层深度解析:TS流、PSI表与PCR
ISO/IEC 13818-1系统层深度解析:TS流、PSI表与PCR
发布时间:2026/10/5 3:35:20
简介ISO/IEC 13818-1:2019是全球通用的音视频编码系统层国际标准由ISO与IEC联合发布面向多媒体开发工程师、编解码研究人员及系统集成人员。此PDF完整收录第七版英文正式文本共305页系统阐述系统架构、视频编码、音频编码、传输流与节目流封装、音视频信号处理及同步时序控制等核心规范同时涵盖专利权和商标权相关声明。资源为单一PDF文件压缩包大小约20.93MB便于离线查阅、检索与比对。目前已有328人学习使用。读者可获得该标准全文包括正式前言、范围、引用标准及全部条款有助于深入理解MPEG-2系统层的设计机理在播放器开发、流媒体封装、数字电视与通信系统设计中作为权威技术参考。1. ISO/IEC 13818-1:2019 是什么所有 TS 流背后那台黑匣子的总纲做数字电视前端的人大概都经历过这种时刻码流里某一路 PID 忽然解不出画面播放器表现出各种“玄学”花屏复用器那边却说信号一切正常。翻遍抓包文件最后能当裁判的往往只剩 ISO/IEC 13818-1:2019——《Information technology - Generic coding of moving pictures and associated audio information - Part 1: Systems》。这套标准的名字很长核心词是“Generic coding”和“Systems”它不定义视频编码算法也不管音频采样格式它只管一件事——一堆已经压缩好的视频、音频和辅助数据怎么被打包成能在带宽有限的信道里传输的比特流又怎么在接收端恢复出原来的时间关系。凡是用 MPEG-2 Transport Stream也就是我们常说的 TS 流做封装的地方DVB 广播前端、IPTV 平台、复用器、码流分析仪底层规则都在这 305 页里。它不是写给播放器开发者的入门手册而是写给那些需要和码流死磕的工程师的“总纲”PID 怎么分配、PSI 表怎么更新、PCR 按什么时钟基准发送、解码器的缓冲区水位怎么计算全部有明确说法。适合正在做复用、转码、流分析、机顶盒集成或者被 TS 流问题折磨到想换行的人。2. 系统层协议再拆解ES、PES、TS 三层搞不清后面全白看2.1 从 ES 到 PES 是“打包”从 PES 到 TS 是“切块”很多新人第一次解 TS 流是直接把一个 188 字节的包当成一个“视频帧”这从一开始就错了。TS 流里根本不存在“帧”的概念只有层层嵌套的容器。编码器输出的裸压缩数据叫 ES也就是 Elementary Stream它只是一串连续的视频或音频比特没有边界。要在系统层传输第一步是把它“打包”成 PESPacketized Elementary Stream。PES 的头部里塞了真正有用的东西stream_id 告诉你这一包是视频、音频还是私有数据PTS/DTS 标志位决定后面有没有时间戳PES_packet_length 给出这个包的字节数。需要注意PES_packet_length 在视频流里经常是 0表示长度不固定解码器必须依靠 TS 包的 payload_unit_start_indicator 来重新确定 PES 边界而不是靠这个长度字段。拿到 PES 之后第二步才是“切块”按 188 字节一包切成 TS 包。一个视频 PES 可能跨几十个 TS 包一个 TS 包也可能只装了一个 PES 的尾部。这种不对称正是初读标准时最晕的地方。我一般建议先不要把 ES、PES、TS 当成三种“文件格式”而是当成三层封装动作ES 是原材料PES 给原材料贴上标签TS 再按固定块大小装上传输容器。层与层之间靠头部字段关联不是靠物理边界对应。有经验的工程师还会提醒一点TS 包里的负载不一定都是 PES。它也可能是 PSI 表的 section是条件接收的 EMM/ECM是空包填充。判断负载是什么唯一办法是看 PID 和 payload_unit_start_indicator不能靠猜。后面会展开讲。2.2 188 字节的 TS 包里真正起作用的不是负载而是那 4 字节头一个 TS 包固定 188 字节其中绝大多数位置是负载但决定一切的是最前面的 4 字节头。这 4 字节丢了负载再完整也白搭。头部的字段排列顺序和含义如下表所示做码流分析时所有工具的输出最终都能映射回这十几个字段。字段位数作用sync_byte8固定 0x47用于字节对齐transport_error_indicator1传输层报错时置 1payload_unit_start_indicator1负载是否从 PES/section 的起始位置开始transport_priority1同 PID 包之间的优先级PID13标识该包属于哪一条流transport_scrambling_control2负载加扰模式adaptation_field_control2头部后是否跟 adaptation field、是否有负载continuity_counter4同 PID 包的连续性计数实际排查时最容易出问题的字段是 payload_unit_start_indicator 和 adaptation_field_control。payload_unit_start_indicator 等于 1代表当前 TS 包的负载是某个 PES 包或某个 section 的第一个字节这是重组 PES 和 PSI 表的锚点。很多解析器不看这个标志直接按包序号拼接结果在丢包或插入空包时整个流解析崩溃。adaptation_field_control 的取值也值得背下来“01”表示只有负载“10”表示只有 adaptation field“11”表示两者都有。PCR 往往就在 adaptation field 里如果解复用器的过滤逻辑为了省事把不含负载的包全部丢掉了PCR 就会断掉后续时间戳恢复必定出问题。这类问题在码流分析仪上看得清清楚楚但在自研播放器里往往表现得像“随机花屏”。continuity_counter 也有不少细节可以抠。它不是简单地每个包加一有负载的包按顺序递增重复发送的包不递增纯 adaptation field 的包不参与连续性检查。标准里关于这个小字段的说明分散在多个章节建议直接翻到讲 transport packet layer 的部分逐字读比在某些论坛里听二手经验可靠得多。我自己早期就吃过亏把“每个 PID 包计数必须连续”当成铁律写进检测工具结果遇到带 PCR 的空包就误报丢包最后一条干净流报了上百条 discontinuity纯属自己没读透。2.3 一份流却不是一种“格式”别把文件结构和传输结构混在一起还有一个常见的认知坑是把“TS 文件”和“TS 传输流”当成同一种东西。两者底层包结构一致但用途不同文件里可以包含任意长度的 PES甚至允许不完整的 PSI 表在真实传输链路上PAT 和 PMT 必须以不超过 100ms 的周期重复发送而且接收端要能在任意的中途位置进入并完成节目同步。标准里给的是后者也就是传输链路的规则集合。所以当你拿到一个 .ts 后缀的录像文件直接套用“PCR 必须间隔多少毫秒、PAT 必须重复多少次”这些规则去校验大概率会看到一堆离谱数据。这时候该做的不是怀疑标准错了而是先搞清楚文件到底是从实时流录下来的还是某个工具重新封装过的。常见做法是先看 PAT 和 PMT 的出现频率再看 PCR 是否连续最后才判断这条流是否合规。我在本地方案里一般会用 TSDuck 的 psi 打印工具先看一眼表结构再决定往哪个方向查。读这本 305 页的 PDF 时建议也别按页码顺序啃。它的章节组织相当工程化先讲 TS 包层再讲 PES 层然后进入 PSI 表、PCR、STD 模型。第一次读的人只需要把三块内容圈出来TS 头的字段定义、PAT/PMT/CAT 的 section 语法、PCR/PTS/DTS 的时钟关系。这三块通了剩下的描述符扩展、私有数据规则、条件接收相关章节都是用到再查。3. PSI 表是这套系统的灵魂PAT/PMT 才是你解析节目的真正入口3.1 PAT 永远在 PID 0先找到节目再到 PMT 映射TS 流里没有“文件目录”这种概念接收端想知道“这一串包里有哪些节目每个节目由哪些流组成”靠的是 PSIProgram Specific Information。PSI 的第一张表是 PATProgram Association Table固定占用 PID 0。任何一条符合规范的 TS 流PID 0 上都必须周期性出现 PAT。PAT 的负载是一组 “program_number 到 program_map_PID” 的映射。program_number 是节目号0 有特殊含义它不指向节目而是指向包含 NIT 的 PID这种情况多出现在实际广播流里非零的 program_number 对应当前流里的一个节目它告诉解析器想看这个节目就去 program_map_PID 指到的那个 PID 上收 PMT。PMTProgram Map Table才是真正描述节目组成的表。它带有 program_number、PCR_PID、节目级描述符以及一个流描述列表。每个条目包含 stream_type、elementary_PID 和该流对应的描述符。拿到 PMT 之后解析器才能把“PID 0x101 是视频stream_type 是 H.264”、“PID 0x102 是音频stream_type 是 AAC”这些信息确定下来。解析 PSI 的通用链路因此很固定先收 PID 0 上的 PAT拿到 PMT 的 PID再去那个 PID 上收 PMT拿到 PCR_PID 和各路流 PID。做前端解复用的人对这套“先找目录再找节目最后找流”的次序要形成肌肉记忆。很多自研播放器出问题追根溯源都是跳过了中间某一步直接用预设 PID 去解视频。3.2 table_id、section_syntax_indicator 与版本号更新表时靠这些字段PSI 表在传输时不是整张出现的而是被切成一个或多个 section。每个 section 都有独立的头部table_id 就是第一道区分标识。下面是几张关键表的速查表建议贴在手边。table_idPID表名作用0x000PAT节目号与 PMT PID 映射0x011CAT条件接收描述符0x02由 PAT 指定PMT节目组成与流 PID0x032TSDT传输流描述符0x40–0xFE用户自定义私有表业务自定义数据section 头部里除了 table_id还有几个字段是解析器的生命线。section_syntax_indicator 通常置 1表示 section 按长格式带 CRC 校验section_number 和 last_section_number 用于重组被拆成多段的表。PAT 在节目很多时会被拆成多个 section每个 section 最多带若干条节目映射必须等 last_section_number 之前的 section 全部收齐才能拿到完整的节目列表。PMT 一般一个节目一个 section但它也可能因为描述符太多而被拆开。version_number 和 current_next_indicator 是更新机制的核心。当节目增删或 PID 变化时编码端会把 version_number 加一再重新发送表。接收端通过比较 version_number 判断表是否更新如果值没变即使内容变了符合规范的接收端也可以不处理。current_next_indicator 为 1 表示当前表已经生效为 0 表示这张表是“下一版本”当前还没启用。不少工程师在写 EPG 更新逻辑时只看了 version_number忽略了 current_next_indicator结果把尚未生效的表提前应用导致短时间黑屏随后又恢复现象极其隐蔽。3.3 SI 表不在本标准但解 DVB/ATSC 的套路从 PSI 延伸这里需要划一条边界ISO/IEC 13818-1 只规定 PSI不规定 DVB 的 SI 表。NIT、SDT、EIT、BAT 这些实际广播里天天见到的表属于 ETSI EN 300 468 的范围ATSC 系统用的 PSIP 又是另一套。把两者混为一谈是很多工程师啃标准时反复碰壁的原因。但这不是说 SI 和 PSI 毫无关系。SI 表完全沿用了 PSI 的 section 机制也沿用了 descriptor 的通用结构只是 table_id 被分配到不同区间并增加了针对 EPG、网络信息等业务的描述符。理解了 PAT/PMT 和 section 语法之后再去读 EN 300 468本质上是“换一套表名学更多描述符”而已。实际落地时解析一条 DVB 直播流的完整链路是先收 PAT 找到 PMT再从 PMT 拿到视频/音频 PID 和 PCR_PID然后去 EIT 所在的 PID 上收集节目信息同时通过 NIT 了解当前频点属于哪个网络。这条链路里PSI 部分是 13818-1 的管辖范围SI 部分则要看 DVB 规范。边界清晰了排查问题时就能准确判断“是哪一层出了问题”而不是把所有锅都甩给码流。4. PCR、PTS、DTS时间戳系统才是没有回声的同步基准4.1 27MHz 与 90kHzPCR/PTS/DTS 的时钟换算TS 流的音视频同步靠的不是把音频和视频数据插在一起而是靠时间戳。标准里定义了两套时钟关系一套是 27MHz 的系统时钟用于 PCR另一套是 90kHz 的显示时钟用于 PTS 和 DTS。90kHz 是 27MHz 除以 300 得来的两者在数值上有确定的换算关系。字段层面的规则如下表这是解析时间戳时最常用到的一组常量。字段位数时钟频率作用PCR_base3390kHz系统时钟的基本计数值PCR_ext927MHz细粒度扩展计数值PTS3390kHz解码后展示时间DTS3390kHz送入解码器的时间PCR 的实际值不是简单地读两个字段然后拼接。标准规定的计算公式是PCR PCR_base × 300 PCR_ext。因为 PCR_ext 跑在 27MHz 上而 PCR_base 跑在 90kHz 上两者之间刚好差 300 倍。比如 PCR_base 12345、PCR_ext 100整体就是 12345×300100 3704500 个 27MHz tick换算成秒大约是 0.1372 秒。写代码做 PCR 转时间时把数值先转成 27MHz tick再统一除以 27000000会避免很多精度损失。PTS 和 DTS 只有 33 位工作在 90kHz 下这就意味着它们的最大值约为 26.5 小时。长时间的直播流必然会遇到 PTS 回绕也就是计数器从最大值翻回零。处理回绕不是靠“跳过那一帧”而是要在采集端做有符号差值计算或者做 33 位的模运算不能把差值当成普通无符号整数直接减。这一点在写长期运行的录播程序时尤其重要我见过不止一个项目在连续播放超过一天后突然出现时间跳变查到最后都是 33 位回绕没有处理。4.2 STD 缓冲模型为什么 PCR 不准会导致花屏而非声音不同步很多非系统层的开发者以为 PCR 只是“让音画同步”的参考其实 PCR 真正的作用是重建系统时钟让解码器能够正确管理缓冲区。标准里画了一套完整的 System Target Decoder 模型也就是 T-STDTS 包进入后先过传输缓冲再经过复用缓冲、解码缓冲最终进入解码器。每一级缓冲都有大小上限和下限编码器在打包时就必须保证这些缓冲不会上溢或下溢。这个模型解释了为什么 PCR 抖动在现象上往往表现为“花屏”而不是“声音对不上”。当 PCR 出现抖动解码器重建出的 27MHz 时钟就会忽快忽慢缓冲水位判断随之出错解码器可能提前或延后取走数据导致画面出现碎块、卡顿严重时直接黑屏。音频因为缓冲模型相对简单对时钟抖动的容忍度稍高所以表现出来得不明显。于是大家看到的现象就是——画面已经一塌糊涂音频还在正常走音画不同步反而是后话。实际维护复用器输出时PCR 的规律性是判断前端质量的重要指标。标准给出的 PCR 发送间隔指导值大致在 100ms 量级但实际广电前端为了给接收端留足余量通常会把间隔压得更短。PCR 的抖动容忍也有明确限值超出之后市面上的主流机顶盒大概率会出现解码异常。工程师在确认“PCR 是否有问题”时应该先算相邻 PCR 的间隔再算基于实际码率的理论间隔对比两者偏差而不是只看平均值。4.3 用 PCR 间隔与抖动检查流质量的最小手段本地排查时我一般先抓一段几百兆的 TS 文件然后用工具把 PCR 包单独过滤出来看时间间隔。在 TSDuck 的 analyze 输出里每个 PID 都会有一栏 PCR 统计包括总数、间隔、抖动等ffprobe 也能导出包级时间戳信息但需要自己写逻辑去算 PCR 差值输出落得比较原始。手动核对的具体步骤是三步。第一步确认 PCR_PID 和 PMT 中声明的一致这是最容易忽略的前提第二步在 PCR_PID 上过滤出含有 adaptation field 的包提取 PCR_base 和 PCR_ext按上面的公式换算成 27MHz tick第三步计算相邻两个 PCR 值的差值再除以该时间段内实际传输的 TS 包数乘以 188 字节的码率看两者是否吻合。差值明显偏大说明 PCR 包发送偏稀差值忽大忽小说明复用器的打包节奏不稳后面大概率伴随缓冲问题。这套方法不需要高级仪器只需要一个能导出包列表的工具和一张计算表。对于大部分 TS 流异常排查它已经足够把问题从“播放器玄学”推进到“PCR 具体超标多少”的程度。5. 啃 ISO/IEC 13818-1:2019 时最容易被坑的地方常有同事问我读这种几百页的国际标准到底哪些地方最容易因为自以为是而翻车我总结了几条带共性的踩坑记录按“现象 → 原因 → 解决”的顺序列出来每一条都来自实际做码流解析时反复遇到的类型。5.1 现象PAT 解析成功了PMT 却始终收不全播放器提示 no video原因把 section 的边界和 TS 包的边界画了等号。PAT 或 PMT 的 section 通常横跨多个 TS 包一个 184 字节负载的包往往只装下 section 的一部分。如果代码只在单个包内查找 table_id 并解析遇到 section 头部在包尾、内容延伸到下一个包时就会解析残缺数据得出的 PMT PID 或流 PID 全是错的。解决解析 PSI 时必须按 payload_unit_start_indicator 判定 section 起始再用 section_length 控制拼接范围将跨包数据累积到缓冲区后一次性解析。只要涉及 PAT/PMT一律走“拼 section → 校验 CRC → 再解析”的完整路径。5.2 现象PCR 间隔检查完全达标但画面依然频繁卡顿原因PMT 里声明的 PCR_PID 和实际携带 PCR 的 PID 不一致。有些复用器配置错误把 PCR 放在了视频 PID 上而 PMT 里却指向了另一个空 PID。接收端按照 PMT 的声明去找 PCR结果找到的 PID 上没有 PCR或者 PCR 包被解复用器当作 adaptation-only 空包丢掉。这时候统计上看到的“间隔正常”其实是统计了错误 PID 的数据。解决先单独过滤 PMT 声明的 PCR_PID打印每个含 adaptation field 的包确认里面是否真的有 PCR 字段。很多码流分析工具能直接显示每个 PID 的 PCR 数量这一步能快速分辨是“没发 PCR”还是“发错了 PID”。5.3 现象长播超过一天后时间跳变EPG 和录制文件都对不上原因PTS 是 33 位计数器溢出周期大约 26.5 小时。很多解析代码把 PTS 直接放到无符号 64 位整数里做差值运算回绕瞬间差值会变成一个巨大的正数或负数导致播放器误判时间跳变。解决比较两个 PTS 时先把差值定义为有符号 32 位量只取低 33 位参与计算再做符号扩展。也就是“模 2^33再映射到有符号区间”这样无论回绕发生在哪个方向差值都能算出正确相对值。5.4 现象从老项目切到 2019 版标准后原来某些描述符解析不出来了原因ISO/IEC 13818-1:2019 是整合了多轮修正案后的版本部分老的描述符被修订、替换或引入更严格的条件一些扩展描述符的 tag 号被重新分配或收编到新章节。拿旧项目里的 descriptor tag 表直接套新流自然会出现解析断层。解决优先按版本号锁定标准基准。工程上我一般会建一个“描述符 tag - 名称 - 版本来源”的小表遇到未知 tag 先查到底是私有描述符还是标准保留区不要一上来就按老 tag 表硬解。5.5 现象transport_private_data 总是解析错位长度对不上原因transport_private_data 位于 adaptation field 内部在 PCR 字段之后但它在标准里只定义了 transport_private_data_flag 和对应的 data_length 机制实际内容结构完全由业务自定义。很多实现把私有数据的字节偏移硬编码死了一旦复用器供应商调整内部结构解析就全部错位。解决先按 adaptation_field_length 跳到私有数据区再读它的 data_length按长度字段继续跳最后才进入真正的业务数据。任何“按固定偏移取字段”的做法在系统层面都是定时炸弹除非你能保证上一级复用器永远不会调整结构。5.6 现象加扰流里明明有 EMM/ECMCAS 却一直找不到密钥原因在加扰流里EMM/ECM 的 PID 不是从 PMT 拿的而是从 CAT 表里条件接收描述符中获取的。很多人第一反应是去 PMT 里找私有流 PID结果找不到正确的授权数据通道。解决PID 1 上收取 CAT解析其中的 CA_descriptor取得 CA_system_ID、EMM/ECM PID 和参数。把 CAS 的 PID 分配和节目流的 PID 分开管理这条规则在标准里写得很清楚但工程上混用的现象依然常见。6. 验证一环把读过的字段用标准里的规则做一次闭环标准读完了不等于会用。我自己的习惯是每读完一类规则就立刻找一条真实 TS 流做验证把字段对应到工具的原始输出上。这里给出一个最省事的闭环先拿一条录制的 TS 文件用 ffprobe 打印节目和流信息再用包级导出做 PCR 间隔验证。ffprobe -v trace -show_programs -show_streams input.ts ffprobe -show_packets -select_streams v:0 -show_data input.ts第一条命令的 output 里可以看到 programs 列表每个 program 下就是 PMT 解析出的流集合包括 stream_type、PID 和 PCR_PID第二条命令能导出视频 PID 的每个包里面带着 pts_time、dts_time以及原始包数据。把第二条输出里 PID 为 PCR_PID 的包单独筛出来按前面说的 PCR_base×300PCR_ext 计算再和 pts_time 对照基本就能验证自己对规则的理解对不对。如果发现工具输出的 PCR 间隔异常不要急着下结论先回到标准的原始定义逐字段核对很多时候问题出在工具本身的解析策略上。早期我做复用器兼容性排查时最深刻的教训就是“拿到新标准先通读再到流里找对应关系”。一本 305 页的 PDF翻完前面 30 页就犯困是常态但带着具体问题去读完全不一样看到一个 PCR 字段就想办法在抓包里把它标出来看到一个 continuity_counter 规则就写个小脚本验证。这套方法让我把“读标准”从背书变成了调试的一部分省下的时间远超多读的几遍。这么多年下来我仍然保留一个习惯凡是码流解析问题先在桌面上摆一条标准里对应的原文再摆一份工具输出两边贴在一起对比。没有这个习惯我大概还会在“PID 对不上”“PCR 跳变”“PTS 回绕”这些老坑里多翻几次车。希望帮到你。本文还有配套的精品资源点击获取