MPEG-TS 封装的核心要求

假设一个 IDR 帧数据为 5000 字节:
| 包序号 | 内容 | 数据量 |
| 包1 | TS头 + 适配域(PCR) + PES头(14) + AUD(4) + 数据 | ~150 字节数据 |
| 包2 | TS头 + 数据 | 184 字节数据 |
| 包3 | TS头 + 数据 | 184 字节数据 |
| ... | ... | ... |
| 包N | TS头 + 数据 | 剩余数据 |
|---|
一、 MPEG-TS封装流程
1.等待第一个完整的 IDR 帧及其参数集就绪
IDR 帧(Instantaneous Decoding Refresh)是 H.264/H.265 视频中的关键帧,具有以下特性:
| 特性 | 说明 |
|---|---|
| 独立解码 | 不依赖任何前面的帧,可以独立解码出完整画面 |
| 刷新参考帧 | 解码器收到 IDR 帧后会清空所有参考帧缓存 |
| 随机访问点 | 用户切台、Seek 时从 IDR 帧开始解码 |
没有 IDR 帧,解码器无法开始解码。如果直接发送 P 帧或 B 帧,解码器会因为缺少参考帧而无法解码,导致花屏或黑屏。
SPS(Sequence Parameter Set)和 PPS(Picture Parameter Set)包含解码所需的参数:
| 参数集 | 包含的关键信息 |
|---|---|
| SPS | 分辨率、帧率、编码档次(Profile)、级别(Level)等 |
| PPS | 熵编码模式、量化参数初始值、片组信息等 |
| VPS (H.265) | 视频参数集,包含更高层级的编码信息 |
没有 SPS/PPS,解码器不知道如何解码视频数据,即使收到 IDR 帧也无法正确解析。解码器从开始接收到能够正常显示画面,需要经历以下过程:
收到 PAT → 找到 PMT → 找到视频 PID → 等待 IDR 帧 → 等待 SPS/PPS → 开始解码
如果缺少 SPS/PPS:解码器无法初始化,画面无法解码,如果缺少 IDR 帧:解码器没有解码起点,只能等下一个 IDR
2.写PES负载数据
如何将一个完整的 PES 负载数据(如包含 SPS、PPS、IDR 帧数据)分片封装成多个 188 字节的 TS 包。
-
清空 TS 包缓冲区 (m_tsBuffer)
-
判断是否需要写 PCR(第一帧视频帧,每100ms插入PRC数据)
-
计算开销:PES头(14) + AUD(视频4字节)
| 开销类型 | 字节数 | 说明 |
|---|---|---|
| PES 头 | 14 | 包含起始码、流ID、PTS/DTS 等 |
| AUD (视频) | 4 | 访问单元分隔符,H.264/H.265 需要 |
- 决定是否需要适配域(PCR/关键帧标志/填充)
适配域结构:
| 字段 | 字节数 | 说明 |
|---|---|---|
| 适配域长度 | 1 | 后续字节数 |
| 标志位 | 1 | 指示是否包含 PCR、RAI 等 |
| PCR(可选) | 6 | 节目时钟参考 |
| 填充字节 | N | 0xFF 填充 |
-
计算基础头部大小
-
计算可用空间
-
计算可复制的数据量,核心逻辑逻辑:只有当可用空间足够容纳 PES 头 + AUD 时,才复制数据。
-
计算已使用空间(used = 基础头部(6/12) + PES头+AUD(18) + 负载数据(copySize))
-
计算填充字节数,stuffingBytes = 188 - used,剩余空间用0xff填充
-
写入 TS 头,核心逻辑和填充字段上篇文章已经解析

5.写pes头部信息
TS 封装中最核心的部分,负责将 PES 负载数据分片写入 TS 包。根据是否为PES第一个包标志,分为第一个包和后续包两种处理方式。
if (m_pesFirstPacket) {
// ========== 第一个 TS 包 ==========
// 包含:TS头 + 适配域 + PES头(14B) + AUD(视频4B) + 部分负载
} else {
// ========== 后续 TS 包 ==========
// 包含:TS头 + 适配域 + 负载数据
}
| 参数 | 说明 |
|---|---|
streamId |
流类型(0xE0视频 / 0xC0音频) |
m_pesPts |
PTS 时间戳(90kHz) |
0 |
PES 长度设为 0(表示未指定长度) |
headerSize |
TS 头大小(用于定位写入位置) |
isAudioStream |
是否音频流 |
-
写入音频的PES,头部 (14字节)
-
视频特有:写入 AUD(4个字节)(访问单元分隔符)
// H264_AUD 通常定义为
const uint8_t H264_AUD[] = {0x00, 0x00, 0x00, 0x01, 0x09, 0xF0};
// 实际是:起始码(4) + NAL头(1) + 类型(1) = 6字节? -
写部分负载数据
视频数据第一个包整体布局结构:

音频数据第一个包整体布局结构:

后续包结构:没有 PES 头
-
没有 AUD
-
直接从
headerSize偏移处开始写数据
