做音视频开发这些年,最容易让人一头雾水的概念就是“码流”,尤其PS和TS这两兄弟。白天还在跟同事一起抓包查TS拉流卡顿,晚上回家用播放器打开一个老旧的.mpg文件,里面其实也是PS封装。搞不清这两者的来龙去脉,排查问题只能靠瞎猜,所以我一直觉得,把PS和TS的结构吃透,是音视频从业者绕不开的基本功。这篇文章我就用我自己的理解方式,把这套东西拆开讲清楚。

先说结论:PS和TS都来自MPEG-2系统层标准(ISO/IEC 13818-1),两个不是互相替代的关系,而是针对完全不同的传输场景设计的两套封装方案。TS全称Transport Stream(传输流),天生为有损信道、广播、流媒体场景服务;PS全称Program Stream(节目流),则是为无差错环境下的本地存储和光盘播放准备的。理解了这句话,后面所有结构差异和选型问题都顺了。

1. 两种码流的出身背景,一个为传输而生,一个为存储而生

1.1 同一个家族,为什么长成两个样子

MPEG-2系统层标准在制定的时候,要同时应对两类完全不同的实际场景。一类是数字电视广播、卫星传输、IP网络拉流,这种环境里信号要经过调制解调、无线信道、路由器交换,中间随时可能丢包、误码;另一类是DVD光盘、本地录制文件,数据几乎不会出错,但要求存储紧凑、随机访问方便。

同一个编码器出来的音视频ES流(Elementary Stream,原始码流)本身没法直接用,必须先打包成PES包(Packetized Elementary Stream)。PES包给了数据一个基本的“信封”,里面带上了解码时间戳DTS和显示时间戳PTS。但从这里开始,两条路的岔口才真正出现:是继续把PES包切成固定大小的TS包,还是直接拼接成一个大PS流?标准的答案很干脆——都要,TS给广播和网络,PS给光盘和本地存储。

所以两兄弟的底层是一样的,区别在第二层封装策略。有一类问题常年被问:“TS和PS能互相转换吗?”答案是可以,而且代价很小,因为它们都基于PES。用FFmpeg一条命令就能把TS转成PS,或者反过来,前提是PES层的数据本身没有因为传输丢包而损坏。这也解释了为什么很多录播系统里,直播用TS收了,存文件的时候却转成PS,两头的好处都能吃到。

1.2 场景倒推设计:先搞清楚要为“错误”付出多少

TS的设计哲学,说白了就是“我预感到你会出错,所以我把数据切得很小、每包都带同步字、还定期重复关键信息,就算丢掉一部分也能快速恢复”。固定188字节的包大小,就是综合考虑信道纠错能力、缓冲器大小、同步找回速度之后的结果,这个数字不是拍脑袋定的,而是MPEG组织在大量实验后折中的产物。

PS的设计哲学刚好反过来,“我相信你不会出错,所以我把数据尽量串成大块,减少封装开销,把带宽留给真正的音视频数据”。所以PS使用可变长包,一个pack里可以塞好几组PES数据。这样做的代价是,如果PS流中间损坏了一个字节,整个流可能就要从下一个pack_start_code处重新找同步,恢复的代价远高于TS。所以在网络传输里用PS,一旦发生误码,基本就是一场灾难。

搞懂了这个底层逻辑,后续无论看结构还是排查问题,脑子里都会有一根弦:TS的所有机制都在围绕“抗错和同步”,PS的所有机制都在围绕“紧凑和顺序”。

2. TS码流核心拆解:188字节里藏着一套完整的生存法则

2.1 固定包长和同步字的双保险

打开任意一段TS流,按字节去翻,你会反复看到一个十六进制数0x47。这个就是TS包的同步字节(sync_byte),位于每个188字节包的第一个字节。0x47在ASCII里是字母“G”,它足够冷门,不容易在音视频数据里自然地频繁出现,所以被选中作为同步标记。

为什么包长要固定?因为接收端只要在比特流里找到连续几个0x47,就能推断出包边界,然后以188字节为单位向下扫描,即使中途出现误码导致某个0x47损坏,也只需要往后找下一个0x47就能重新对齐。标准建议连续检测5个0x47再确认同步,实际工程里我经常见到2到3个就切出同步的快速算法,这在低延迟场景里做首帧提速时很管用。

918行,继续。TS包长固定还有一层好处:很多播放器芯片和硬件解复用器在设计时,直接把188字节当成一个DMA单元去搬运,内存管理和网络收包都非常规整。后来DVB-S2定义了192字节的封装,其实是在188字节基础上扩充了4字节的FEC保护字段,底层对齐逻辑完全没变。理解191字节和188字节的对应关系,调卫星参数时特别有用。

2.2 TS包头的4字节,每个位都有用

TS头固定4字节,后面接adaptation field或者payload。我最常用的一种记忆方式,是把它拆成“1个同步字、1个错误标志、1个起始标志、1个优先级、13位PID、2位加扰、2位adaptation控制、4位连续性计数器”。

简单列一下关键字段的用途:

  • transport_error_indicator(1 bit):传输错误指示,通常由解调器或网络协议栈置位,告诉上层这个包已经坏了,别用。
  • payload_unit_start_indicator(1 bit):负载起始指示。等于1时,当前TS包的payload是一个新的PES包、PSI表或者私有分段的第一个字节。做解析时,这个位是判断数据边界的核心。
  • PID(13 bit):包标识,这是TS解析的“灵魂”。PAT的PID固定为0,PMT和音视频的PID都在表里声明。一台解码器要正确播放TS流,本质就是在做“PID的发现、匹配、分发”。
  • adaptation_field_control(2 bit):表示后面是只有payload,还是只有adaptation field,还是先adaptation field再payload。等于00是标准里禁止的状态。
  • continuity_counter(4 bit):每传一个同PID的有效负载包后加1,从0到15循环。解码端可以通过它判断是否发生了丢包。这个计数器在排查时非常关键,后面我会专门讲。

调试时想最快确认一路TS流有没有问题,第一步就是看PAT和PMT能否正常解析出来,第二步就是看连续性计数器是否连续,这两件事能排除掉一半以上“为什么卡顿”“为什么黑屏”的问题。

2.3 PSI信息表:PAT、PMT和PCR,TS流的“导航系统”

TS流里除了音视频数据,还必须混入一种特殊的数据,叫PSI(Program Specific Information,节目专用信息)。如果没有PSI,解码器就像拿到一本没目录的书,根本不知道现场里有哪些频道、哪个PID是视频、哪个PID是音频。

最基础的两张表:

PAT(Program Association Table,节目关联表)固定在PID 0上发送。它里面写入的是“节目号->PMT PID”的映射关系。一台机器解TS流的流程一般是:先抓PID 0的包解析PAT,拿到某个节目的PMT PID,再抓PMT包,拿到视频PID、音频PID、PCR PID,然后才开始真正的音视频数据接收和解析。

PMT(Program Map Table,节目映射表)里详细写了这个节目包含哪些流,每条流是什么类型(H.264用0x1B,H.265用0x24,AAC音频用0x0F,MPEG音频用0x03等),以及各自的PID。PMT是一个“菜单”,三张表之间的关系,我用一句话概括:PAT告诉你PMT在哪,PMT告诉你音视频在哪。

PCR(Program Clock Reference,节目参考时钟)通常也通过PMT里的PCR PID指定,物理上放在对应PID的数据包的adaptation field里。PCR的精度很高,33位90kHz的基值加9位27MHz扩展值。播放器拿它来恢复系统时钟,用它来纠正音视频同步。如果PCR间隔过大或者抖动严重,最直接的症状就是画面卡顿、声音忽快忽慢、外壳字幕和口型对不上。

还要强调一点,PSI表在TS流里是周期性重复发送的。为什么?因为新加入的终端可能在任何时间点开机,必须保证它能在一个较短等待时间内抓到完整的PAT/PMT。实际工程里100ms到500ms内至少重传一次,有的系统为了快速切台,20ms到40ms就发一次。

2.4 adaptation field是什么时候出现的,PES又怎么切进TS包

TS包的固定长度是188字节,但一个PES包动辄几千甚至几万字节,根本装不下,所以PES包要被“切碎”放进多个TS包的payload里。实现方式是:遇到一个新PES包起始时,把payload_unit_start_indicator置为1,第一个TS包的payload就从这个PES包的第一个字节开始。后续的TS包继续放这个PES的剩余数据,直到放完,下一个标记置为1,再开始一个新PES包。

那adaptation field又为什么会被引入?原因很多,最常见的有三点:

第一,PES包的长度往往不是188减去头长度的整数倍,最后一个TS包可能剩下几个字节放不下下一个PES,这时就用adaptation field去填充间隙,保证188字节整包不长不短。

第二,PCR需要单独加到码流里,但PCR不属于音视频PES数据,所以它只能被放在某个TS包的adaptation field里。PMT里指定的PCR PID,指的就是那些专门用来携带PCR的TS包,它们的PID和某个视频或音频PID相同,但包的adaptation field里额外带着PCR。

第三,需要在码流里插入私有数据、广告信令、加密授权信息时,也经常通过adaptation field实现。

所以你看,TS虽然只有188字节,但为了在各种网络和广播环境里存活,留了非常多的机制和占位。这是TS流的“代价”,也是它之所以在流媒体领域屹立不倒的原因。

3. PS码流核心结构:为连续、可靠的存储场景准备

3.1 Pack头、系统头和SCR:PS流自己的起点

如果你打开一个.mpg或.vob文件,会看到文件开头是一串固定的字节,典型的是00 00 01 BA。这就是PS流的pack起始码(pack_start_code)。PS流的基本单位叫pack,每个pack都由pack头开始,后面可以接多个PES包。

Pack头里最重要的信息是SCR(System Clock Reference,系统时钟参考),它的作用类似TS里的PCR,负责让播放器恢复系统时钟。SCR同样是90kHz基值加27MHz扩展,但因为它写在pack头里,粒度是按“每个pack一次”走的,整体精度比TS的PCR略粗。这对本地点播场景来说完全够用。

Pack头之后,PS流里会出现一个系统头(system header),起始码是00 00 01 BB。系统头描述了码流的最大码率(rate bound)、音频轨和视频轨的数量限制、以及每条流是不是固定码率等信息。很多播放器在解析PS时,会先读系统头来配置解码器,然后才开始读PES。如果你的PS封装工具把系统头写错了,典型表现就是播放器只听到声音不出画面,或者干脆提示“无法解析的文件”。

3.2 PSM程序流映射:PS流里的“PMT”

TS流有PAT+PMT来告知节目信息,PS流里也有对应物,叫PSM(Program Stream Map,程序流映射),起始码是00 00 01 BC。PSM的作用就是描述当前PS流包含哪些基本流、每个基本流的流类型和PID(PS里仍有PID,但主要作用变为流标识)。它的一组信息往往会在文件开头、中途和结尾重复出现,方便播放器在随机访问时重新定位节目结构。

PSM和PMT的区别在于:PMT必须和PAT配合,以PID0为入口去循环查找;PSM则是直接出现在PS流里的一个单元,结构相对简单。所以解析PS流的算法一般是这样:找pack头,校验系统头,找PSM确认流结构,再逐个PES包解出音视频数据。因为PS流本身是顺序化的,这个解析流程实现起来比TS简单很多,这也是DVD这么老的标准至今还能稳定工作的原因。

3.3 DVD里的PS封装,以及蓝光和录制里的例外

PS流最经典的落地场景是DVD的VOB文件。一张DVD的视频、音频、字幕都被封装进VOB文件,格式本质上就是PS流,只是加了一些DVD特定的导航信息和字幕子流。DVD播放机读盘时,激光头顺序读取VOB,播放器解析pack头、PES包、PSM,还原出画面。因为光盘读取本身有硬件校验纠错,几乎不出错,所以PS的可变长结构在这里发挥得很好。

这里要特别提一个容易混淆的点:蓝光碟用的M2TS文件,绝大多数人以为是PS,其实是TS流的变种。原因是蓝光时代视频码率大幅提高,播放中也需要随机访问和处理损坏数据,TS的固定包结构和抗错机制反而更适合大文件碟片。所以封装格式选型,永远取决于面向的具体信道特性,而不是“光盘就等于PS”这种刻板印象。

另外国内安防行业还有一种非常高频的PS使用场景:GB28181国标流和很多网络摄像机(IPC)到NVR的平台码流,经常走PS封装。原因很简单——这些流跑在UDP和可靠的内网传输上,网络质量好、丢包少,用PS可以减少封装开销、降低延迟。很多伙伴在对接国标平台时,抓到RTP载荷里全是PS,这跟TS反而少见是一样的逻辑。

4. 实战排查:一条码流怎么分析、怎么选型

4.1 先会选格式:哪个场景用TS,哪个场景用PS

我在项目里见过太多次因为选错封装导致的线上故障。最典型的例子是:录像文件从NVR下载回来,用播放器打不开,或者拖动进度条直接卡死,一查发现这台NVR用的PS封装,但播放器只做了TS解析,压根认不出。为了避免这种来回踩坑,我把选型建议整理成了一张表:

场景 推荐封装 原因
直播拉流(HTTP-FLV、HLS切片) TS 抗丢包、同步字恢复快、支持快速入播
数字电视广播(DVB、ATSC) TS 固定包长适合调制解调,误码后能快速同步
CDN分发、多路复用 TS 包结构规整,方便网络设备处理和分发
DVD碟片、本地录像文件 PS 封装开销小,适合无差错顺序读取
安防GB28181国标码流 PS 内网UDP传输稳定,PS开销低、延迟小
蓝光碟M2TS TS变种 高码率大文件兼容抗错需求

记住一个通用判断:你的数据信道可能出错,就选TS;你的数据信道不会出错、但要省带宽,就选PS。如果音视频数据已经在解封装之后了,那就没有必要纠结封装格式,直接操作ES流就好。

4.2 工具党上手:FFmpeg和tsduck怎么快速看码流

拿到一个TS或PS文件,先别急着用播放器试,先上工具看结构,效率高得多。我最常用的三招:

第一招,用FFmpeg看流信息:

ffprobe -show_programs -show_streams input.ts

这条命令能看到节目数、每条流的编码类型、PID、时长、分辨率等信息。如果PAT或PMT解析失败,ffprobe一般会直接报错或者输出异常,可以快速判断是不是结构性问题。

第二招,用FFmpeg转为另一种封装,验证PES层完整性:

ffmpeg -i input.ts -c copy output.mpg

如果这条命令能顺利跑完,说明PES数据基本完整,TS转PS没有问题。反过来也一样。这条命令不用重编码,瞬间就能测出流是否健康。

第三招,用tsduck工具族里的tsp或tspacket抓包。tsduck是TS分析神器,最常用的是:

tsp -I file input.ts -P continuity -P analyze -O drop

continuity插件会检查TS连续性计数器的跳变,analyze插件会输出PAT/PMT的解析结果。如果这两步骤都正常,那源端的问题基本很小;如果连续性计数器大量跳变,就要去查网络丢包了。

4.3 实操演示:把一个MP4分别转成TS和PS,看看包里有什么

为了把这套东西落到地上,我在本地做了一个小实验。准备一段H.264+AAC的MP4视频,分别执行:

ffmpeg -i demo.mp4 -c copy demo.ts
ffmpeg -i demo.mp4 -c copy demo.mpg

两个文件体积对比一下,PS很明显比TS小一点,这就是固定188字节封装多出的开销,通常在1%到3%之间。

再用ffprobe分别输出几行TS的原始包看看:

ffprobe -show_packets demo.ts | head -40

你能看到每个packet会带上dts、pts、pos这些字段。TS文件里PAT、PMT、PCR都是包夹杂在音视频PES中间;而PS文件的pack头主要是SCR和系统头,然后才是PES数据。这个观察,能帮你直观建立“TS是结构化的小容器、PS是顺序大容器”的认知。

如果手头有实时拉流的环境,还可以用Wireshark直接抓UDP端口数据,过滤条件写udp.port==接流的端口号,然后看原始载荷的前几个字节是不是0x47。如果是,你可以直接判定是TSoIP(TS over UDP),接下来再按TS格式去解析。这个排查方法我几乎每周都在用,效率非常高。

5. 常见问题与排查技巧实录

5.1 TS拉流只出声音不出画面,怎么定位

这个现象在安防平台和直播播放器里出现得特别多。多半不是PPP传输问题,而是播放器没能正确识别视频PID或者没能正确匹配编码格式。排查顺序我建议从前往后:

第一,ffprobe输入流文件,看视频流编码类型是否正常。如果里面有H.265视频,而播放器只支持H.264解码,那你看到的就不出画面。

第二,检查PMT里的stream_type和实际ES流是否一致。有的设备会故意把PID声明成MPEG-2视频,实际塞的却是H.264,播放器按错误的类型去解码自然什么都解不出来,这是设备厂家兼容性问题。

第三,检查视频PID是否连续,PES包头的起始标记是否正常。如果PES头没有被正确解析,解码器无法获得帧边界,也会黑屏。

5.2 音画不同步、声音忽快忽慢,优先查PCR

经常有现场反馈“声音是好的,但嘴型对不上”,或者“声音一直在但画面在跳”。这种情况我第一反应就是查PCR。

如果PCR间隔太长,播放器的时钟锁定会变得迟钝;如果PCR的抖动过大,播放器会反复调整播放速度,造成忽快忽慢的体验。排查时用tsduck的pcr插件看PCR间隔和抖动曲线,正常要求是PCR间隔在40ms到100ms以内,抖动尽量小于500ns。从源端编码器生成的TS一般不太会有PCR问题,更多的是服务器转封装时没有正确保留PCR导致。

另外,如果是边录边传的场景,音视频PTS和DTS本身与系统时钟没有对齐,即使PCR没问题,也依然会不同步。遇到这种情况,就需要在源端先检查采集中是否多次重置了时间戳。

5.3 PS文件无法拖进度条、播放器打不开,查PSM和系统头

PS文件有个非常典型的坑:很多简易录制工具生成的PS文件,只有pack头和PES数据,没有正确写系统头或者PSM。在顺序播放模式下问题不大,但一旦需要seek,播放器必须扫描全文件才能定位目标时间点,或者直接报错。

解决方向有两个。一是用FFmpeg重新封装一遍,让工具生成标准的PS头:

ffmpeg -i broken.mpg -c copy fixed.mpg

二是换一个支持“裸PS读取”的播放器,很多专业播放器在无PSM时也能自动探测流结构,这种兼容性处理在新老对接时格外重要。

5.4 解码器提示“码流已加密,请切换至本地配置页面设置密钥后重启预览”

这个提示在安防NVR和IPC平台上很常见,尤其是新出厂设备默认开启了码流加密。收到这个提示,重点排查三步:

先看设备端平台的码流加密开关,确认有没有在本地配置页面关闭流加密或者正确配置密钥数据。再去核对播放端是否导入了对应的密钥和解密库,很多平滑升级没引入密钥交换机制就会出现这个问题。最后看流里的加扰控制字段,TS流的transport_scrambling_control字段如果非零,说明TS流确实做了加扰,需要按条件接收方案处理,而PS流和私有流加密则可能有自己专有的算法。这个提示本身不是码流结构坏了,是安全鉴权没通过,不要把排查重心放在解复用层。

5.5 小技巧:用连续性计数器快速判断丢包

排查TS拉流卡顿时,除了看网络丢包统计,我还会抓一段流,检查每个PID的continuity_counter连续性。方法也很简单,拉3000个包,按PID分组,检查连续计数是否跳变。如果视频PID频繁跳变,说明这段链路的UDP丢包已经影响到媒体数据,即使播放器和解码器尽力纠错,也会出现马赛克、花屏、花絮。

这里的经验是:网络层的丢包和RTP序列号丢包统计往往只能反映“网络丢了多少包”,continuity_counter则能反映“这些丢包是否发生在关键媒体负载上”。两者配合使用,才能判断是不是要改FEC参数、加缓冲、切换到TCP拉流,或者直接走SRT这类具备重传机制的传输协议。这一串排查做完,基本能把问题范围从“播放器”缩小到“封装乱”再到“传输丢包”这三层,哪里有问题一目了然。

我在实际项目里最深的体会是,TS和PS本身并不难,难的是在链路出问题时快速判断该往哪一层去查。对码流结构越熟练,排查速度越快,生产环境的稳定性也越好。如果让我给新手一个建议,那就是别光看文档里那些冰冷字段,拿几段真实的TS录播、PS录像文件,按字节一层一层拆开看,把PID表、PCR、PES头都亲手翻一遍。这套手感练出来以后,再回头测播放器、调传输、评估服务器兼容性,你就知道问题大概率出在哪一行、哪一个包、哪一个字节上。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐