📡 MPEG-2 TS传输流详解:流媒体核心技术完全指南

9942 字
50 分钟
📡 MPEG-2 TS传输流详解:流媒体核心技术完全指南

📡 MPEG-2 TS(Transport Stream)传输流详解#

一、基本定义与核心定位#

TS(Transport Stream,传输流) 是MPEG-2标准(ISO/IEC 13818-1,MPEG-2 Systems)定义的一种面向不可靠传输环境设计的音视频封装格式,全称为MPEG-2 TS。

核心设计目标:

  • 在有噪声/丢包的传输信道(如地面广播、卫星、有线电视、IP网络)中实现稳定可靠的音视频传输
  • 支持多路节目复用(多套电视节目在同一传输流中)
  • 提供强容错能力和快速同步恢复机制
  • 满足实时性要求,适合直播等低延迟场景

二、TS包详细结构(188字节固定长度)#

TS包是传输的最小单元,理解其结构是掌握TS流的第一步。

每个TS包结构如下:

┌─────────────┬─────────────────┬─────────────────────┐
│ TS Header │ Adaptation Field│ Payload (有效载荷) │
│ (4字节固定) │ (可选,0-184字节)│ (剩余字节) │
└─────────────┴─────────────────┴─────────────────────┘
总长度固定188字节:4(Header) + 0~184(AF) + 184~0(Payload)

1, TS包头(4字节,固定)#

TS包头是整个TS系统的”导航灯塔”,结构如下(按位定义):

字段位数取值/含义核心作用
同步字节8固定为0x47标识TS包起始,用于接收端同步
传输错误指示10=无错,1=有错标记包在传输中是否出错,出错包应丢弃
净荷单元起始指示10=非起始,1=起始标记当前包是否为PES包或PSI Section的起始位置
传输优先级10=普通,1=高优先级用于QoS控制,优先传输重要数据
PID130x0000~0x1FFFPacket Identifier,包标识符,用于区分不同流(视频/音频/PAT/PMT等)
传输加扰控制200=未加扰,01/10/11=加扰模式标识内容是否加密及加密方式
适配字段控制2见下表指示包中是否包含适配字段和有效载荷
连续计数器40~15循环检测包丢失或顺序错误,同一PID的连续包计数器应递增(模16)

适配字段控制(adaptation_field_control)取值:

值含义说明
00保留ISO/IEC保留,不应使用
01仅有效载荷最常见,无适配字段,184字节全部为有效载荷
10仅适配字段无有效载荷,用于填充或仅传输PCR
11适配字段+有效载荷两者都有,适配字段用于填充+PCR,剩余为有效载荷

关键PID值(预定义):

PID用途说明
0x0000PAT表必选,节目关联表
0x0001CAT表条件接收表(有加密节目时存在)
0x0002TSDT表传输流描述表
0x0010NIT表网络信息表(实际PID由PAT指定,此为DVB默认值)
0x1FFF空包(Null Packet)填充用,接收端应直接丢弃

2. 适配字段(Adaptation Field,可选)#

适配字段主要功能:

  • 填充数据,确保TS包固定188字节长度(PES数据不足188字节时用填充补齐)
  • 插入PCR(Program Clock Reference,节目时钟参考) 时间戳
  • 提供其他传输控制信息(如随机访问指示、不连续指示)

适配字段结构(长度可变,首字节为长度标识):

┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ AF长度 │ 标记位 │ PCR │ OPCR │ 填充字节 │ ... │ │
│ (1字节) │ (1字节) │ (6字节) │ (6字节) │ (可变) │ (其他) │ │
│ │ │ (可选) │ (可选) │ (可选) │ (可选) │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
AF长度字段值 = 后续所有字段的总字节数(不含自身)
当仅含AF长度+标记位时,AF长度=0(最小适配字段为2字节:长度+标记位)

标记位(flags,1字节)详细定义:

位字段名含义
7discontinuity_indicator1=系统时钟不连续或计数器不连续
6random_access_indicator1=当前包包含随机访问点(如I帧起始)
5elementary_stream_priority_indicator1=该包中的ES数据为高优先级
4PCR_flag1=适配字段中包含PCR
3OPCR_flag1=适配字段中包含OPCR(原始PCR,用于级联复用器)
2splicing_point_flag1=包含拼接点倒计数
1transport_private_data_flag1=包含私有数据
0adaptation_field_extension_flag1=包含适配字段扩展

PCR的核心作用与精确结构:

PCR携带编码端系统时钟(STC,System Time Clock) 信息,接收端通过PCR恢复与编码端同步的本地时钟,是PTS/DTS时间戳的参考基准。

PCR由两部分组成,共6字节(48位):

PCR结构(6字节,48位):
┌──────────────────────────────────────────┬──────┬──────────┐
│ PCR_base (33位) │ 保留 │ PCR_ext │
│ 基于90kHz时钟 │ 6位 │ (9位) │
│ │ │ 基于27MHz│
└──────────────────────────────────────────┴──────┴──────────┘
PCR = PCR_base × 300 + PCR_extension
PCR_base:33位,基于27MHz时钟÷300 = 90kHz
PCR_extension:9位,基于27MHz时钟的精细部分,范围0~299
PCR完整精度:27MHz(约37ns分辨率)
PCR_base精度:90kHz(约11.1μs分辨率)

PCR关键参数:

  • 系统时钟频率:27MHz(±30ppm容差)
  • PCR_base精度:90kHz(与PTS/DTS同频)
  • PCR完整精度:27MHz(约37ns分辨率)
  • PCR最大间隔:100ms(MPEG-2标准要求)
  • PCR抖动容限:±500ns(DVB标准)

PCR时钟恢复原理:

  1. 编码端以27MHz时钟驱动STC,周期性将当前STC值编码为PCR插入适配字段
  2. 接收端通过锁相环(PLL)或类似算法,将本地27MHz振荡器锁定到PCR
  3. 本地STC与PTS/DTS比较,控制解码和显示时机
  4. PCR间隔不超过100ms,确保时钟不会严重漂移

3. 有效载荷(Payload)#

有效载荷携带实际数据,分为两类:

  1. PES包数据:音频/视频等媒体数据,由PES包分片而来
  2. PSI/SI Section数据:节目特定信息/服务信息,用于描述流结构

PES包与TS包的映射关系:

  • 一个PES包通常包含一帧完整媒体数据(如一个I帧、一帧音频)
  • 一个PES包可被拆分为多个TS包传输(因PES包长度可变,通常远大于188字节)
  • TS包的净荷单元起始指示位(payload_start_indicator)=1时,表示当前TS包是一个新PES包的开始
  • PES包的第一个字节必须位于TS包有效载荷的第一个字节(payload_start_indicator=1时)

空包(Null Packet)机制:

PID=0x1FFF的TS包为空包,其有效载荷无意义,核心作用包括:

  • 维持恒定比特率(CBR):TS流通常需要恒定比特率传输(如DVB广播),当有效数据不足时插入空包填充
  • 复用器速率匹配:多路节目复用时,各路速率波动,空包用于平滑总比特率
  • 缓冲管理:保持解码器缓冲区的恒定输入速率,避免缓冲区下溢或上溢

空包处理规则:

  • 接收端应直接丢弃空包,不做任何处理
  • 空包的连续计数器也应递增,但接收端通常不检查
  • 空包的适配字段控制通常为01(仅有效载荷),有效载荷全为0xFF

三、分层封装结构(ES→PES→TS)#

在理解TS包结构后,我们来看媒体数据是如何被封装进TS包的。TS流采用三层封装模型,从原始媒体数据到最终传输流的完整链路:

层级名称英文全称核心功能数据单元
1基本流Elementary Stream (ES)原始压缩媒体数据(H.264/HEVC视频,AAC/MP3音频)编码帧(如I/P/B帧、音频采样帧)
2分组基本流Packetized Elementary Stream (PES)为ES添加时间戳、媒体类型标识,分组化可变长度PES包(通常包含一帧完整数据)
3传输流Transport Stream (TS)固定长度打包,添加传输控制信息,支持多节目复用固定188字节TS包(可扩展至204字节含FEC)

封装流程:原始媒体数据→ES→PES(添加PTS/DTS)→TS(固定长度打包+传输控制信息)→传输→解包→PES→ES→解码播放

PES包详细结构#

PES包是ES和TS之间的中间层,理解其结构对TS流解析至关重要。

┌──────────────┬──────────────┬──────────────┬──────────────┬───────────────┐
│ PES Header │ PES Optional │ PTS/DTS │ 其他可选 │ PES Payload │
│ (6字节固定) │ Header │ (5/10字节) │ 字段 │ (ES数据) │
│ │ (可变长度) │ │ │ │
└──────────────┴──────────────┴──────────────┴──────────────┴───────────────┘

PES包头(6字节固定部分):

字段位数取值/含义核心作用
起始码前缀24固定0x000001标识PES包起始,与ES起始码格式一致
stream_id8见下表标识PES包承载的流类型
PES包长度160~65535PES包剩余长度(不含起始码前缀和stream_id),0=未指定长度(视频流常用)

stream_id关键取值:

stream_id流类型
0xE0视频流(MPEG-1/2/4/H.264/HEVC)
0xC0音频流(MPEG-1/2/AAC)
0xBD私有流1(AC-3/DTS音频、字幕等)
0xBE私有流2
0xBCprogram_stream_map
0xFFprogram_stream_directory

PES可选头(可变长度):

字段位数核心作用
2位标记2固定’10’,标识MPEG-2 PES
PES加扰控制2标识PES层加扰方式
PES优先级10=普通,1=高优先级
数据对齐指示11=有效载荷从对齐点开始
版权11=有版权
原始或拷贝11=原始数据
PTS_DTS_flags200=无PTS/DTS,01=禁止,10=仅有PTS,11=PTS+DTS
ESCR标志11=存在ESCR(ES时钟参考)
ES速率标志11=存在ES速率
DSM特技模式11=存在DSM特技模式信息
附加复制信息11=存在附加复制信息
CRC标志11=存在PES CRC
扩展标志11=存在PES扩展
PES头数据长度8后续可选字段的总字节数

PTS/DTS编码格式(33位时间戳):

PTS和DTS都是33位值,基于90kHz时钟,编码时分散存储在5字节中:

PTS编码(5字节,33位有效):
4位标记 '0010' | PTS[32..30] | 1位标记 '1' | PTS[29..15] | 1位标记 '1' | PTS[14..0] | 1位标记 '1'
DTS编码(5字节,33位有效):
4位标记 '0001' | DTS[32..30] | 1位标记 '1' | DTS[29..15] | 1位标记 '1' | DTS[14..0] | 1位标记 '1'

33位时间戳的取值范围:0 ~ 2^33-1 = 8589934591 在90kHz时钟下,最大表示时间:8589934591 / 90000 ≈ 95443秒 ≈ 26.5小时 超过此范围后时间戳回绕(wrap around),接收端需要处理回绕逻辑。

ES流映射到PES的规则:

  • 视频ES:每个访问单元(Access Unit,即一个编码帧)封装为一个PES包,I帧起始的PES包的payload_start_indicator=1
  • 音频ES:每个音频帧封装为一个PES包
  • PES包长度:视频流通常设为0(未指定),音频流通常指定具体长度

四、PSI/SI表系统(节目导航核心)#

PSI(Program Specific Information,节目特定信息)和SI(Service Information,服务信息)是TS流的”导航系统”,使接收端能够正确识别和解析多路节目。

1. PSI Section分段机制#

PSI表不是直接放入TS包的,而是通过Section(段) 机制分段传输,这是理解PSI解析的关键前提。

Section结构:

┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ table_id │ section_ │ 版本/当前 │ section_ │ Section │
│ (1字节) │ syntax_ │ 下一个指示 │ number/ │ 数据 │
│ │ indicator │ │ last_section │ │
│ │ + section_ │ │ number │ │
│ │ length │ │ │ │
│ │ (2字节) │ │ │ │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
+ CRC32

Section关键特性:

  • Section长度可变,最大1024字节(PSI Section)或4096字节(private Section)
  • 一个Section可能跨多个TS包传输(Section长度>TS包有效载荷时)
  • 一个TS包的有效载荷中也可能包含多个Section(Section较短时)
  • Section末尾有CRC32校验(4字节),用于验证Section完整性
  • Section通过版本号机制(version_number,5位,0~31循环)标识表是否更新

Section与TS包的映射规则:

  • 当payload_start_indicator=1时,TS包有效载荷的第一个字节为pointer_field(1字节),指示Section数据起始的偏移量
  • pointer_field=0表示Section数据紧跟pointer_field之后
  • pointer_field>0表示pointer_field后有若干填充字节,然后才是Section数据

2. 核心PSI表#

表名英文全称PIDtable_id核心功能
PATProgram Association Table0x00000x00节目关联表,列出所有节目及其对应的PMT PID
PMTProgram Map Table可变(由PAT指定)0x02节目映射表,列出特定节目的所有组件(视频PID、音频PID、字幕PID等)及对应的编码类型
CATConditional Access Table0x00010x01条件接收表,描述加密系统信息(如CAS系统、EMM PID)
TSDTTS Description Table0x00020x03传输流描述表,提供TS流的整体信息

PAT表详细结构:

PAT Section:
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ table_id │ section_ │ transport│ version/ │ section_ │ 节目列表 │ CRC32 │
│ =0x00 │ length │ _stream_ │ current_ │ number/ │ (循环) │ (4字节) │
│ (1字节) │ (2字节) │ id │ next │ last_ │ │ │
│ │ │ (2字节) │ (1字节) │ section │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
节目列表每项4字节:
┌──────────────────────┬──────────────────────┐
│ program_number │ PMT PID / NIT PID │
│ (2字节) │ (13位PID + 3位保留) │
└──────────────────────┴──────────────────────┘
program_number=0 → 该PID指向NIT
program_number≠0 → 该PID指向对应节目的PMT

PMT表详细结构:

PMT Section:
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ table_id │ section_ │ program_ │ version/ │ PCR_PID │ 节目描述 │ 基本流 │ CRC32 │
│ =0x02 │ length │ number │ current_ │ (13位) │ (可变) │ 列表 │ (4字节) │
│ (1字节) │ (2字节) │ (2字节) │ next │ │ │ (循环) │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
基本流列表每项:
┌──────────┬──────────┬──────────┬──────────┐
│ stream_ │ 基本流 │ ES信息 │ │
│ type │ PID │ (可变) │ │
│ (1字节) │ (13位) │ │ │
└──────────┴──────────┴──────────┴──────────┘

stream_type关键取值:

stream_type编码格式说明
0x01MPEG-1 Video
0x02MPEG-2 Video
0x03MPEG-1 Audio
0x04MPEG-2 Audio
0x1BH.264/AVC Video最常见的高清编码
0x24HEVC/H.265 Video4K/UHD编码
0x0FAAC Audio
0x06PES私有数据字幕、AC-3/DTS音频等
0x81AC-3 AudioATSC标准

PAT→PMT→音视频流的导航流程:

  1. 接收端首先解析PID=0x0000的PAT表,获取所有节目号及对应PMT的PID
  2. 根据节目号找到对应的PMT PID,解析PMT表
  3. 从PMT中获取当前节目所有媒体流的PID(视频PID、音频PID等)和编码格式(stream_type)
  4. 从PMT中获取PCR_PID,用于时钟恢复
  5. 筛选出对应PID的TS包,重组为PES包,解码播放

PSI表版本更新机制:

  • 每个PSI Section都有version_number(5位,0~31循环)
  • 当表内容发生变化时,version_number递增1(模32)
  • current_next_indicator=1表示当前生效,=0表示预发布(下一版本生效)
  • 接收端应持续监测PAT/PMT的版本号变化,及时更新节目映射关系

2.5 PAT/PMT 的数量、位置与周期性发送#

PAT 表#

项目说明
数量通常 1 个,PID 固定为 0x0000
内容列出当前 TS 流中所有节目号及其对应的 PMT PID
内容变化时版本号递增,但 PID 始终为 0x0000

PMT 表#

项目说明
数量每个节目 1 个 PMT 表
PID不固定,由 PAT 表指定(如 0x0100、0x0200 等)
内容描述该节目的所有组件:视频 PID、音频 PID、字幕 PID、PCR PID、stream_type 等

举例:一个 TS 流有 100 个频道(节目),则有:

  • 1 个 PAT 表(PID = 0x0000)
  • 100 个 PMT 表(PID 各不相同,如 0x0100、0x0200、…、0x6300)

周期性发送机制#

PAT/PMT 不是单独存放,而是分散在整个 TS 流中周期性循环发送:

TS流持续发送示例(时间轴从左到右):
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ PAT包 │ 音视频包 │ PMT包1 │ 音视频包 │ 音视频包 │ PMT包2 │ 音视频包 │ PAT包 │
│(PID=0) │ │(PID=x) │ │ │(PID=y) │ │(PID=0) │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
↑ ↑ ↑ ↑
└────────────────────────同一PAT表的重复发送(周期性)────────────────────────┘

关键机制:

  1. PSI Section 封装 — PAT/PMT 数据以 Section 形式封装,可跨多个 TS 包传输
  2. 多 Section 复用 — 同一 PID 的 TS 包有效载荷可能包含多个 Section(通过 pointer_field 区分)
  3. 周期性发送 — 发送间隔通常 100ms~1s,确保接收端能快速获取
  4. 接收端解析流程:
    1. 找到 PID=0x0000 的 TS 包 → 解析 PAT → 获取所有 PMT PID
    2. 根据 PMT PID 找到各节目的 PMT 表 → 获取视频/音频/字幕 PID
    3. 开始接收对应 PID 的音视频 TS 包

一个 Section 可能跨多个 TS 包:

PES包数据(很大)→ 被分割成多个TS包:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ TS包1 │ TS包2 │ TS包3 │ TS包4 │ TS包5 │
│ PUSI=1 │ PUSI=0 │ PUSI=0 │ PUSI=0 │ PUSI=0 │
│ Section │ Section │ Section │ Section │ Section │
│ Part 1 │ Part 2 │ Part 3 │ Part 4 │ Part 5 │
└─────────┴─────────┴─────────┴─────────┴─────────┘
↑
payload_unit_start_indicator=1 表示新Section开始

一个 TS 包可能包含多个 Section:

单个TS包有效载荷(短Section紧凑排列):
┌────────┬─────────────────┬────────┬─────────────────┐
│Pointer│ Section 1 Header │ Data 1 │ Section 2 ... │
│Field │ (table_id=0x00) │ │ (同一PID下一个) │
└────────┴─────────────────┴────────┴─────────────────┘

实际查看 PSI 结构#

Terminal window
# 使用 tsduck 查看 PSI 信息
tsinfo input.ts
# 使用 FFprobe 查看节目信息
ffprobe -show_programs input.ts
# 详细解析 PSI 表
tsdump input.ts

3. SI表(扩展信息)#

SI表提供额外的服务信息,主要用于数字电视广播(DVB标准定义):

表名英文全称PID核心功能
NITNetwork Information Table0x0010(DVB默认)描述传输网络参数(频点、调制方式等)
SDTService Description Table0x0011提供节目名称、类型、运行状态等信息
EITEvent Information Table0x0012提供节目单、当前/后续节目信息(EPG数据源)
TDTTime and Date Table0x0014提供当前UTC时间和日期
TOTTime Offset Table0x0014提供本地时间偏移信息
BATBouquet Association Table0x0011节目组关联表,运营商节目包划分

4. 条件接收(CA)与加扰机制#

条件接收系统(CAS,Conditional Access System)是TS流中实现付费电视、内容保护的核心机制。

加扰与加密的区别:

  • 加扰(Scrambling):对音视频内容本身进行扰乱处理,使未授权用户无法正常观看,使用控制字(CW,Control Word) 作为密钥
  • 加密(Encryption):对CW进行加密传输,确保只有授权用户能获取CW,使用业务密钥(SK)加密

CAS系统工作流程:

编码端:
原始音视频 → 加扰器(使用CW) → 加扰后的TS流 → 复用器 → 传输
CW(控制字,通常64位) → 加密器(使用SK) → ECM(Entitlement Control Message)
授权信息 → 加密器(使用PDK) → EMM(Entitlement Management Message)
ECM流 → 分配PID(由PMT中CA描述符指定) → 复用到TS流
EMM流 → 分配PID(由CAT表指定) → 复用到TS流
接收端:
TS流 → 解复用 → 提取ECM/EMM → 智能卡
智能卡:EMM(用PDK解密)→ 获取授权 → ECM(用SK解密)→ 获取CW
CW → 解扰器 → 原始音视频 → 解码播放

关键概念:

术语全称说明
CWControl Word控制字,加扰/解扰的直接密钥,通常64位,每隔5~20秒更换一次
SKService Key业务密钥,用于加密CW,按频道/节目包分配
PDKPersonal Distribution Key个人分配密钥,用于加密EMM,每张智能卡唯一
ECMEntitlement Control Message授权控制消息,携带加密后的CW和访问条件,周期性发送
EMMEntitlement Management Message授权管理消息,携带用户授权信息,周期性发送

CAT表的作用:

  • PID=0x0001,列出TS流中使用的所有CAS系统
  • 每个CA描述符包含:CA_system_ID(标识CAS厂商)、EMM PID(该系统的EMM流PID)
  • 接收端根据CAT找到对应CAS系统的EMM PID,提取EMM数据

PMT中的CA描述符:

  • PMT的节目级和ES级均可包含CA描述符
  • 节目级CA描述符:整个节目的ECM PID
  • ES级CA描述符:特定基本流的ECM PID

五、时间同步机制(PCR+PTS+DTS)#

TS流通过三层时间戳机制确保精准音视频同步:

时间戳英文全称位置作用精度
PCRProgram Clock ReferenceTS适配字段恢复系统时钟,作为PTS/DTS的时间基准27MHz(base为90kHz,ext为27MHz精细部分)
PTSPresentation Time StampPES包头指示媒体帧应该何时呈现(显示/播放)给用户90kHz
DTSDecoding Time StampPES包头指示媒体帧应该何时解码(仅视频B帧需要,I/P帧PTS=DTS)90kHz

同步原理:

  1. 编码端:

    • 所有时间戳基于同一系统时钟(STC,27MHz) 生成
    • PCR = STC当前值(27MHz精度)
    • PTS/DTS = STC当前值 ÷ 300(转换为90kHz精度,33位编码)
    • 视频I/P/B帧需要PTS和DTS(B帧解码顺序与显示顺序不同)
    • 音频帧通常只需要PTS(解码后立即播放)
  2. 接收端:

    • 通过PCR恢复本地STC(27MHz),与编码端保持同步
    • 本地STC ÷ 300 得到90kHz时钟,与PTS/DTS比较
    • 当本地STC ÷ 300 ≥ PTS时,显示该帧
    • 当本地STC ÷ 300 ≥ DTS时,解码该帧
    • 即使部分包丢失,也能通过下一个PCR快速重新同步

PTS/DTS与B帧的关系:

视频编码中,B帧的解码顺序与显示顺序不同,导致DTS和PTS值不同:

显示顺序:I B B P B B P B B P
PTS值: 0 2 3 5 7 8 10 12 13 15
解码顺序:I P B B P B B P B B
DTS值: 0 3 1 2 6 4 5 9 7 8
说明:编码器先编码I帧和P帧(作为B帧的参考帧),再编码B帧
因此P帧的DTS(3)早于其PTS(5),中间的B帧DTS(1,2)在其后

时间戳回绕(Wrap Around)处理:

  • PTS/DTS为33位无符号整数,最大值2^33-1
  • 在90kHz时钟下,约26.5小时回绕一次
  • 接收端必须处理回绕:当新时间戳与旧时间戳差值超过2^32时,判定为回绕

六、TS流比特率与速率控制#

1. 比特率计算#

TS流的比特率可通过多种方式确定:

通过PCR计算:

比特率 = (TS包数 × 188 × 8) / (PCR差值 / 27000000)
其中:
TS包数 = 两个PCR之间的TS包总数(含空包)
PCR差值 = 后一个PCR值 - 前一个PCR值(27MHz单位)
27000000 = 27MHz时钟频率

通过TS头信息:

  • 适配字段中可选的transport_rate字段直接指示比特率
  • 但该字段在实际TS流中很少使用

2. CBR与VBR#

特性CBR(恒定比特率)VBR(可变比特率)
比特率恒定不变随内容复杂度变化
空包使用大量使用空包填充较少使用空包
典型场景数字电视广播(DVB/ATSC)IPTV、网络直播
带宽利用效率较低(简单场景浪费带宽)效率较高(按需分配)
缓冲管理简单(恒定输入速率)复杂(需处理速率波动)
复用简单(固定时隙分配)复杂(统计复用)

统计复用(Statistical Multiplexing):

  • 多路VBR节目共享固定带宽
  • 复用器根据各路节目的瞬时复杂度动态分配比特率
  • 复杂场景(运动剧烈)的节目分配更多带宽,简单场景分配较少
  • 整体带宽利用率显著高于固定时隙分配

3. 缓冲区模型#

MPEG-2标准定义了T-STD(Transport Stream System Target Decoder) 缓冲区模型,用于规定解码器的缓冲行为:

传输 → 传输缓冲区(TB) → 基本流缓冲区(EB) → 解码器 → 显示
(512字节) (可变大小)
关键参数:
TB大小:512字节(所有流共用)
EB大小:由profile/level决定(视频通常几Mbit,音频通常几Kbit)
缓冲区上溢:输入速率过快,需通过PCR控制
缓冲区下溢:输入速率过慢,需插入空包维持

七、关键特性与优势#

1. 固定长度包结构(188字节)#

  • 最核心特性,使接收端易于同步和错误检测
  • 丢包后仅影响单个包,不破坏整个流结构
  • 便于硬件实现和高速处理
  • 扩展版本:204字节(188+16字节FEC前向纠错码),进一步提升抗干扰能力

2. 强容错与恢复能力#

  • 每个TS包独立可识别(同步字节0x47),即使流中出现错误也能快速重新同步
  • 连续计数器检测包丢失或顺序错误
  • 适配字段中的discontinuity指示帮助接收端处理流中断情况
  • CRC32校验确保PSI Section的完整性

3. 多节目复用能力#

  • 单TS流可承载多个独立节目(如多套电视频道)
  • 每个节目有独立的PMT表,通过PID区分不同节目组件
  • 支持动态添加/移除节目(如数字电视中的频道切换)
  • 统计复用优化带宽利用率

4. 灵活的传输控制#

  • PID机制实现精准流识别,可灵活选择需要的节目组件
  • 适配字段提供丰富的传输控制信息
  • 支持条件接收(加密),通过CAT表和EMM/ECM流实现付费内容管理

5. 实时性保障#

  • 固定包结构和高效解析流程,适合低延迟场景
  • PCR间隔通常不超过100ms,确保快速时钟同步
  • 适合直播、视频会议等实时应用

八、与PS流及其他容器格式的对比#

1. 与PS流(Program Stream)的核心区别#

特性TS流(Transport Stream)PS流(Program Stream)
包长度固定188字节可变长度(通常较大)
应用场景实时传输(广播、IPTV、直播)存储介质(DVD、蓝光、本地文件)
容错能力强,适合不可靠信道弱,适合无错误环境
节目支持支持多路节目复用通常用于单路节目
同步恢复快速(基于同步字节和PCR)较慢(依赖完整包结构)
典型应用DVB、ATSC、IPTV、HLS切片、RTSP直播DVD视频、本地媒体文件

2. PS流(Program Stream)详解#

PS 流是 MPEG-2 标准的另一核心封装格式,与 TS 流同属 MPEG-2 系统层,但设计目标截然不同。

2.1 核心定位与设计目标#

PS(Program Stream,节目流) 是 MPEG-2 标准(ISO/IEC 13818-1,MPEG-2 Systems)定义的面向可靠传输环境设计的音视频封装格式。

核心设计目标:

  • 在无错误/低错误环境(如光盘、硬盘、本地文件)中实现音视频同步播放
  • 为单路节目提供高效的封装和存储方案
  • 简化解码器设计,无需复杂同步恢复机制
  • 支持本地媒体文件的随机访问(快进、快退、定位播放)

2.2 PS包结构(可变长度)#

PS 包是 PS 流的基本单元,与 TS 包的固定 188 字节不同,PS 包长度可变:

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ PS Header │ PES Packet │ PES Packet │ ... │
│ (14字节固定) │ (可变长度) │ (可变长度) │ │
└──────────────┴──────────────┴──────────────┴──────────────┘

PS包头(14字节固定部分):

字段位数取值/含义核心作用
起始码32固定0x000001BA标识PS包起始
系统时钟参考(SCR)3333位值恢复系统时钟,与TS的PCR类似但位置不同
SCR扩展99位值SCR的精细部分,基于27MHz时钟
节目 multiplexer rate2222位值指示比特率(字节/秒)
保留5-填充
PS头数据长度8后续可选字段的总字节数标识可选字段长度

SCR(System Clock Reference)结构:

SCR结构(6字节,42位):
┌────────────────────────────────┬──────────┬──────────────────┐
│ SCR_base (33位) │ 保留 │ SCR_extension │
│ 基于90kHz时钟 │ 6位 │ (9位) │
│ │ │ 基于27MHz时钟 │
└────────────────────────────────┴──────────┴──────────────────┘
SCR = SCR_base × 300 + SCR_extension

SCR_base 精度为 90kHz,SCR_extension 精度为 27MHz,与 PCR 的时钟恢复机制完全一致,但 SCR 位于 PS 包头而非适配字段中。

2.3 ES→PES→PS封装流程#

PS 流的封装链路与 TS 流完全相同:

原始媒体数据 → ES(编码帧) → PES(添加PTS/DTS) → PS(可变长度打包+ SCR)

与 TS 流的核心封装差异:

特性TS流PS流
打包长度固定188字节可变长度
传输控制信息位置适配字段(Adaptation Field)PS包头(SCR)
多节目支持原生支持多节目复用通常单节目
最小封装单元TS包(188字节)PS包(可变)

2.4 适用场景#

场景格式原因
DVD视频PS光盘读取错误率低,PS可变长度更高效,适合本地存储和随机访问
蓝光光盘PS(BD-ROM)同上,蓝光标准基于MPEG-2 PS衍生的BDAV格式
数字广播电视TS广播信道有噪声/丢包,TS的固定包长和强容错是必要特性
IPTVTSIP网络传输不可靠,TS的快速同步恢复是关键
HLS直播TSHLS基于TS切片,兼容广播级传输需求
本地文件播放PS 或 MP4无传输错误,PS的随机访问和高效存储更合适
视频点播(VOD)MP4(HLS/DASH)MP4的moov atom元数据支持偏移播放,HLS/DASH基于TS切片

2.5 何时选TS、何时选PS#

选择 TS 流的情况:

  • 传输环境有噪声、丢包、或需要强容错
  • 直播、广播、IPTV等实时传输场景
  • 需要多路节目复用(多套电视频道)
  • 带宽资源有限,需要高效同步恢复

选择 PS 流的情况:

  • 存储介质无传输错误(光盘、硬盘)
  • 单路节目封装,本地播放为主
  • 需要高效的随机访问能力
  • 追求更低的封装开销(无可变长度打包浪费)

实际混合场景: 有些系统会混合使用——例如 DVD 中的视频是 PS 封装,但其中的字幕、菜单等数据可能使用 TS 封装思路设计。在广播电视的前端编码中,也常将 PS 录制内容转换为 TS 进行传输。

3. 与其他常见容器格式的对比#

| 特性 | TS | PS | MP4 | FLV | MKV | |------|-----|-----|-----|-----| | 包/单元长度 | 固定188字节 | 可变(Atom/Box结构) | 可变(Tag结构) | 可变(EBML结构) | | 实时性 | 强(流式解析) | 弱(依赖moov atom) | 中(流式但延迟较高) | 弱(需索引) | | 容错能力 | 强(独立包) | 弱(atom损坏影响全局) | 中 | 弱 | | 多节目复用 | 原生支持 | 不支持 | 不支持 | 不支持 | | 时间戳精度 | 90kHz(PTS/DTS)+ 27MHz(PCR) | 1ms(通常) | 1ms | 1ms | | 元数据 | PSI/SI表 | moov atom | script tag | EBML header | | 加密/DRM | CAS/条件接收 | FairPlay/Widevine | 不原生支持 | 不原生支持 | | 适用场景 | 广播/直播/传输 | 点播/存储/下载 | 直播(旧平台) | 存储/归档 | | 标准组织 | ISO/IEC (MPEG-2) | ISO/IEC (MPEG-4) | Adobe | Matroska |

MP4的moov atom问题: MP4文件的元数据(moov atom)通常位于文件末尾,播放器必须下载整个moov atom才能开始播放。对于点播场景,可通过qt-faststart等工具将moov atom移至文件头部实现快速启动,但本质上MP4不适合实时流式传输。

FLV的局限性: FLV曾是网络直播的主流格式,但存在以下问题:不支持HEVC/H.265(需扩展)、时间戳精度有限、不支持多节目、Adobe已停止维护。目前逐步被HLS+TS/CMAF替代。

九、典型应用场景#

  1. 数字电视广播:DVB(欧洲)、ATSC(北美)、ISDB(日本)等标准的核心传输格式
  2. IPTV:基于IP网络的电视服务,广泛采用TS流作为传输格式
  3. 视频直播:
    • 网络直播平台的直播流封装
    • HLS(HTTP Live Streaming)协议的ts切片文件(HLS将TS流分割为2~10秒的小文件)
    • RTSP/RTP直播的常用封装格式
  4. 监控系统:安防摄像头、NVR等设备的视频传输和存储
  5. 卫星传输:高抗干扰要求的卫星电视广播
  6. 有线电视:数字有线电视网络的标准传输格式

TS over IP / RTP封装#

在实际开发中,TS流通过IP网络传输是非常常见的场景,RFC 2250定义了RTP承载MPEG-2 TS的标准。

RTP+TS封装格式:

┌──────────────┬──────────────┬──────────────┬─────┬──────────────┐
│ IP Header │ UDP Header │ RTP Header │ ... │ TS Packets │
│ (20字节) │ (8字节) │ (12字节) │ │ (N×188字节) │
└──────────────┴──────────────┴──────────────┴─────┴──────────────┘
RTP Payload:通常包含7个TS包(7×188=1316字节)
原因:1316 + 12(RTP) + 8(UDP) + 20(IP) = 1356字节 < 1500字节(MTU)
避免IP分片,确保传输效率

RTP头中的关键信息:

  • RTP序号:检测丢包和排序
  • RTP时间戳:基于90kHz时钟(与PTS同频),指示第一个TS包的发送时间
  • M标记位:通常不用(RFC 2250未定义特殊含义)

TS over IP的MTU考虑:

  • 以太网MTU=1500字节,扣除IP+UDP+RTP头后,有效载荷最大约1460字节
  • 7个TS包=1316字节,是最常见的打包数量
  • 8个TS包=1504字节,超过MTU,会触发IP分片,应避免
  • 在MTU更大的网络(如jumbo frame)中,可打包更多TS包

TS over UDP vs TS over TCP:

特性UDPTCP
延迟低高(重传机制)
可靠性不保证保证
适用场景实时直播文件传输、VOD
典型协议RTP/UDPHTTP/TCP(HLS)

十、C++流媒体开发关键要点#

1. TS包解析流程(接收端)#

void parse_ts_packet(uint8_t* ts_packet) {
if (ts_packet[0] != 0x47) {
return;
}
bool transport_error = (ts_packet[1] & 0x80) != 0;
bool payload_start = (ts_packet[1] & 0x40) != 0;
bool priority = (ts_packet[1] & 0x20) != 0;
uint16_t pid = ((ts_packet[1] & 0x1F) << 8) | ts_packet[2];
uint8_t scrambling_control = (ts_packet[3] >> 6) & 0x03;
uint8_t adaptation_field_control = (ts_packet[3] >> 4) & 0x03;
uint8_t continuity_counter = ts_packet[3] & 0x0F;
if (transport_error) {
return;
}
if (pid == 0x1FFF) {
return;
}
int pos = 4;
if (adaptation_field_control & 0x02) {
uint8_t af_length = ts_packet[pos++];
if (af_length > 0) {
uint8_t flags = ts_packet[pos];
if (flags & 0x10) {
uint64_t pcr_base = ((uint64_t)(ts_packet[pos + 1]) << 25) |
((uint64_t)(ts_packet[pos + 2]) << 17) |
((uint64_t)(ts_packet[pos + 3]) << 9) |
((uint64_t)(ts_packet[pos + 4]) << 1) |
((ts_packet[pos + 5] >> 7) & 0x01);
uint16_t pcr_ext = (((ts_packet[pos + 5] & 0x01) << 8) |
ts_packet[pos + 6]);
uint64_t pcr = pcr_base * 300 + pcr_ext;
update_system_clock(pcr);
}
}
pos += af_length;
}
if (adaptation_field_control & 0x01) {
if (pid == 0x0000) {
parse_pat(ts_packet + pos, 188 - pos, payload_start);
} else if (is_pmt_pid(pid)) {
parse_pmt(ts_packet + pos, 188 - pos, payload_start);
} else if (is_video_pid(pid) || is_audio_pid(pid)) {
add_to_pes_buffer(pid, ts_packet + pos, 188 - pos, payload_start);
if (is_pes_complete(pid)) {
PesPacket* pes = get_complete_pes(pid);
decode_pes(pes);
}
}
}
}

2. PAT/PMT解析#

struct ProgramInfo {
uint16_t program_number;
uint16_t pmt_pid;
};
struct StreamInfo {
uint8_t stream_type;
uint16_t elementary_pid;
std::vector<uint8_t> es_info;
};
std::vector<ProgramInfo> program_map;
void parse_pat(uint8_t* data, int length, bool payload_start) {
int offset = 0;
if (payload_start) {
uint8_t pointer_field = data[offset++];
offset += pointer_field;
}
uint8_t table_id = data[offset++];
if (table_id != 0x00) return;
uint16_t section_length = ((data[offset] & 0x0F) << 8) | data[offset + 1];
offset += 2;
uint16_t transport_stream_id = (data[offset] << 8) | data[offset + 1];
offset += 2;
uint8_t version_number = (data[offset] >> 1) & 0x1F;
bool current_next = data[offset] & 0x01;
offset++;
uint8_t section_number = data[offset++];
uint8_t last_section_number = data[offset++];
program_map.clear();
int program_data_end = offset + section_length - 5 - 4;
while (offset < program_data_end) {
ProgramInfo info;
info.program_number = (data[offset] << 8) | data[offset + 1];
info.pmt_pid = ((data[offset + 2] & 0x1F) << 8) | data[offset + 3];
offset += 4;
if (info.program_number != 0) {
program_map.push_back(info);
}
}
}
void parse_pmt(uint8_t* data, int length, bool payload_start) {
int offset = 0;
if (payload_start) {
uint8_t pointer_field = data[offset++];
offset += pointer_field;
}
uint8_t table_id = data[offset++];
if (table_id != 0x02) return;
uint16_t section_length = ((data[offset] & 0x0F) << 8) | data[offset + 1];
offset += 2;
uint16_t program_number = (data[offset] << 8) | data[offset + 1];
offset += 2;
uint8_t version_number = (data[offset] >> 1) & 0x1F;
offset++;
uint8_t section_number = data[offset++];
uint8_t last_section_number = data[offset++];
uint16_t pcr_pid = ((data[offset] & 0x1F) << 8) | data[offset + 1];
offset += 2;
uint16_t program_info_length = ((data[offset] & 0x0F) << 8) | data[offset + 1];
offset += 2 + program_info_length;
int stream_end = offset + section_length - 9 - 4;
while (offset < stream_end) {
StreamInfo si;
si.stream_type = data[offset++];
si.elementary_pid = ((data[offset] & 0x1F) << 8) | data[offset + 1];
offset += 2;
uint16_t es_info_length = ((data[offset] & 0x0F) << 8) | data[offset + 1];
offset += 2;
if (es_info_length > 0) {
si.es_info.assign(data + offset, data + offset + es_info_length);
offset += es_info_length;
}
}
}

3. PES重组状态机#

enum class PesState { WAITING_START, COLLECTING, COMPLETE };
struct PesBuffer {
PesState state = PesState::WAITING_START;
std::vector<uint8_t> data;
uint16_t expected_length = 0;
};
std::map<uint16_t, PesBuffer> pes_buffers;
void add_to_pes_buffer(uint16_t pid, uint8_t* data, int length, bool payload_start) {
auto& buf = pes_buffers[pid];
if (payload_start) {
if (buf.state == PesState::COLLECTING && buf.data.size() > 0) {
process_complete_pes(pid, buf.data);
}
buf.data.clear();
buf.state = PesState::COLLECTING;
if (length >= 6) {
uint8_t stream_id = data[3];
buf.expected_length = ((data[4] << 8) | data[5]);
}
}
if (buf.state == PesState::COLLECTING) {
buf.data.insert(buf.data.end(), data, data + length);
if (buf.expected_length > 0) {
uint16_t pes_payload_size = buf.expected_length + 3;
if (buf.data.size() >= pes_payload_size + 6) {
buf.state = PesState::COMPLETE;
process_complete_pes(pid, buf.data);
buf.data.clear();
buf.state = PesState::WAITING_START;
}
}
}
}
void process_complete_pes(uint16_t pid, const std::vector<uint8_t>& pes_data) {
if (pes_data.size() < 9) return;
uint8_t stream_id = pes_data[3];
uint16_t pes_packet_length = (pes_data[4] << 8) | pes_data[5];
uint8_t pts_dts_flags = (pes_data[7] >> 6) & 0x03;
uint8_t pes_header_data_length = pes_data[8];
int64_t pts = -1, dts = -1;
int offset = 9;
if (pts_dts_flags >= 2) {
pts = ((int64_t)(pes_data[offset] & 0x0E) << 29) |
((int64_t)pes_data[offset + 1] << 22) |
((int64_t)(pes_data[offset + 2] & 0xFE) << 14) |
((int64_t)pes_data[offset + 3] << 7) |
((int64_t)(pes_data[offset + 4] & 0xFE) >> 1);
offset += 5;
if (pts_dts_flags == 3) {
dts = ((int64_t)(pes_data[offset] & 0x0E) << 29) |
((int64_t)pes_data[offset + 1] << 22) |
((int64_t)(pes_data[offset + 2] & 0xFE) << 14) |
((int64_t)pes_data[offset + 3] << 7) |
((int64_t)(pes_data[offset + 4] & 0xFE) >> 1);
}
}
int es_data_offset = 9 + pes_header_data_length;
int es_data_length = pes_data.size() - es_data_offset;
decode_es(pid, stream_id, pts, dts, pes_data.data() + es_data_offset, es_data_length);
}

4. 同步恢复机制#

#define SYNC_BYTE 0x47
#define TS_PACKET_SIZE 188
int find_sync_byte(uint8_t* buffer, int length) {
for (int i = 0; i <= length - TS_PACKET_SIZE; i++) {
if (buffer[i] == SYNC_BYTE) {
bool sync_ok = true;
for (int j = 1; j < 4 && sync_ok; j++) {
if (i + j * TS_PACKET_SIZE < length) {
if (buffer[i + j * TS_PACKET_SIZE] != SYNC_BYTE) {
sync_ok = false;
}
}
}
if (sync_ok) return i;
}
}
return -1;
}
bool check_continuity(uint16_t pid, uint8_t new_cc) {
static std::map<uint16_t, uint8_t> last_cc;
auto it = last_cc.find(pid);
if (it != last_cc.end()) {
uint8_t expected = (it->second + 1) & 0x0F;
if (new_cc != expected) {
return false;
}
}
last_cc[pid] = new_cc;
return true;
}

5. 关键开发要点#

  1. 同步恢复机制:

    • 实现基于0x47同步字节的滑动窗口检测(至少连续4个188字节间隔出现0x47才确认同步)
    • 处理连续计数器中断,检测包丢失
    • 同步丢失后进入搜索模式,重新定位同步字节
  2. PSI表解析:

    • 优先解析PAT→PMT,建立节目与PID的映射关系
    • 处理PSI Section的分段(Section可能跨多个TS包)和更新
    • 监测version_number变化,及时更新节目映射
    • 验证CRC32确保Section完整性
  3. PCR处理:

    • 实现高精度PCR时钟恢复(PLL或线性插值),误差控制在几微秒内
    • 处理PCR间隔和不连续性(discontinuity_indicator),避免时钟漂移
    • PCR抖动补偿:使用缓冲和平滑算法消除网络传输引入的PCR抖动
  4. PES重组:

    • 根据payload_start_indicator识别PES包起始
    • 处理PES包跨多个TS包的情况,正确重组完整PES包
    • 解析PES包头中的PTS/DTS时间戳
    • 处理PES包长度为0的情况(视频流常见)
  5. 错误处理策略:

    • 忽略传输错误指示置位的TS包
    • 处理PID错误、包顺序错误等异常情况
    • 实现流中断后的快速恢复机制
    • CRC32校验失败的PSI Section应丢弃
  6. 性能优化:

    • 使用内存池管理TS包和PES包,减少内存分配开销
    • 采用SIMD指令(如SSE/AVX)加速TS包解析和同步字节搜索
    • 多线程处理:解析线程、解码线程、渲染线程分离
    • PID过滤:尽早过滤不需要的PID,减少后续处理负担

十一、扩展应用与C++开发工具#

1. 常用TS处理工具库#

工具库功能适用场景
libavformat (FFmpeg)完整的TS读写、复用/解复用音视频转码、播放器开发
libts轻量级TS解析库嵌入式设备、资源受限场景
GStreamer流媒体框架,支持TS插件复杂流媒体应用、多格式处理
tsduck专业TS分析工具库广播级TS流分析、监控

2. 常用调试与分析命令#

Terminal window
# 使用FFprobe分析TS流
ffprobe -show_streams -show_programs input.ts
# 使用tsduck分析TS流结构
tsinfo input.ts
# 分析PSI/SI表
tsdump input.ts
# 分析PCR间隔和抖动
tsanalyze input.ts
# 使用FFmpeg将MP4转为TS
ffmpeg -i input.mp4 -c copy -f mpegts output.ts
# 生成HLS切片
ffmpeg -i input.mp4 -c copy -f hls -hls_time 10 -hls_list_size 0 output.m3u8
# 将RTMP流转为TS流
ffmpeg -i rtmp://server/live/stream -c copy -f mpegts output.ts

3. 典型应用案例#

  1. IPTV机顶盒:

    • 解析TS流,提取多节目信息
    • 实现频道切换(快速PID过滤)
    • 音视频同步播放
  2. 直播流媒体服务器:

    • 将RTMP/HTTP流封装为TS流
    • 支持HLS切片(生成.ts切片文件和.m3u8索引)
    • 多比特率自适应流(ABR)实现
  3. 视频监控系统:

    • 实时封装摄像头视频为TS流
    • 支持多路视频复用传输
    • 存储TS流文件,支持快速回放

总结#

MPEG-2 TS作为流媒体传输领域的基石技术,以其固定包长、强容错性、多节目复用和精准同步等特性,成为广播电视、IPTV、直播等领域的标准封装格式。对于C++流媒体开发者而言,深入理解TS流的结构和机制,掌握高效的解析和处理方法,是开发高性能、高可靠性流媒体应用的核心能力。

TS流的设计理念——在不可靠环境中追求可靠传输——对于今天的网络直播、5G视频传输等场景依然具有重要指导意义,其核心技术思想也被后续的SRT、WebRTC等传输协议所借鉴。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
📡 MPEG-2 TS传输流详解:流媒体核心技术完全指南
https://xutopia.top/blog/mpeg2-ts-transport-stream-guide/
作者
xutopia77
发布于
2026-04-09
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
xutopia77
以 C/C++ 为根基做系统与音视频,用全栈能力把想法落地,同时把思考写成文字。
分类
标签
站点统计
文章
48
分类
12
标签
22
总字数
95,084
运行时长
0 天
最后活动
0 天前
文章目录