音视频封装 - TS 封装格式
TS 全称是 MPEG2-TS,MPEG2-TS 是一种标准容器格式,传输与存储音视频、节目与系统信息协议数据,广泛应用于数字广播系统,我们日常数字机顶盒接收到的就是 TS(Transport Stream,传输流)流。
概念
首先需要先分辨 TS 传输流中几个基本概念:
- ES(- Elementary Stream):基本流,直接从编码器出来的数据流,可以是编码过的音频、视频或其他连续码流
- PES(- Packetized Elementary Streams):- PES流是- ES流经过- PES打包器处理后形成的数据流,在这个过程中完成了将- ES流分组、加入包头信息(- PTS、- DTS等)操作。- PES流的基本单位是- PES包,- PES包由包头和 payload 组成
- PS流 (- Program Stream):节目流,PS 流由 PS 包组成,而一个 PS 包又由若干个- PES包组成。一个 PS 包由具有同一时间基准的一个或多个- PES包复合合成。
- TS流 (- Transport Stream):传输流,- TS流由固定长度(- 188字节)的- TS包组成,- TS包是对- PES包的另一种封装方式,同样由具有同一时间基准的一个或多个- PES包复合合成。PS 包是不固定长度,而- TS包为固定长度。
TS 文件
TS 文件分为三次:TS 层、PES 层、ES 层。ES 层就是音视频数据,PES 层是在音视频数据上加了时间戳等数据帧的说明信息,TS 层是在 PES 层上加入了数据流识别和传输的必要信息。
+-+-+-+-+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  TS   |  =  |  Packet 1 |  Packet 2 |  Packet 3 |    ...    | Packet n-1|  Packet n |
+-+-+-+-+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
One Packet:          4bytes              184bytes
+-+-+-+-+-+    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Packet  | =  | Packet header |       Packet data       |
+-+-+-+-+-+    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+TS 层
TS 包大小固定为 188 字节,TS 层分为三个部分:TS header、adaptation field、payload:
- TS header固定- 4个字节
- adaptation field可能存在也可能不存在,主要作用是给不足- 188字节的数据做填充
- payload是- PES数据- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TS header | adaptation field | payload(PES) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
TS header
|0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       ①       |②|③|④|             ⑤           | ⑥ | ⑦ |   ⑧   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| 序号 | 名称 | 大小 | 说明 | 
|---|---|---|---|
| 1 | sync_byte | 8bit | 同步字节,固定为 0x47 | 
| 2 | transport_error_indicator | 1bit | 传输错误指示符,表明在 TS 头的 adapt 域后由一个无用字节,通常都为 0,这个字节算在 adapt 域长度内 | 
| 3 | payload_unit_start_indicator | 1bit | 负载单元起始标示符,一个完整的数据包开始时标记为 1 | 
| 4 | transport_priority | 1bit | 传输优先级,0 为低优先级,1 为高优先级,通常取 0 | 
| 5 | PID | 13bit | PID 值 | 
| 6 | transport_scrambling_control | 2bit | 传输加扰控制,00 表示未加密 | 
| 7 | adaptation_field_control | 2bit | 是否包含自适应区,'00'保留;'01'为无自适应域,仅含有效负载;'10'为仅含自适应域,无有效负载;'11'为同时带有自适应域和有效负载。 | 
| 8 | continuity_counter | 4bit | 递增计数器,从 0-f,起始值不一定取 0,但必须是连续的 | 
TS 层的内容是通过 PID 值来标识的,主要内容包括:PAT 表、PMT 表、音频流、视频流。解析 TS 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到音视频流了。PAT 表和 PMT 表需要定期插入 TS 流,因为用户随时可能加入 TS 流,这个间隔比较小,通常每隔几个视频帧就要加入 PAT 和 PMT。PAT 和 PMT 表是必须的,还可以加入其它表如 SDT(业务描述表)等,不过 HLS 流只要有 PAT 和 PMT 就可以播放了。
- PAT表:他主要的作用就是指明了- PMT表的- PID值。
- PMT表:他主要的作用就是指明了音视频流的- PID值。
- 音频流 / 视频流:承载音视频内容。
PID 是 TS 流中唯一识别标志,Packet Data 是什么内容就是由 PID 决定的。下表给出了一些表的 PID 值:
| 表类型 | PID值 | 
|---|---|
| PAT | 0x0000 | 
| CAT | 0x0001 | 
| TSDT | 0x0002 | 
| EIT,ST | 0x0012 | 
| RST,ST | 0x0013 | 
adaptation field
| 名称 | 大小 | 说明 | 
|---|---|---|
| adaptation_field_length | 8bit | 自适应域长度,后面的字节数(从 flag 开始) | 
| flag | 8bit | 取 0x50 表示包含 PCR 或 0x40 表示不包含 PCR | 
| PCR | 40bit | Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟 STC(System Time Clock)。 | 
| stuffing_bytes | xbit | 填充字节,取值 0xFF | 
自适应区的长度要包含传输错误指示符标识的一个字节。PCR 是节目时钟参考,PCR、DTS、PTS 都是对同一个系统时钟的采样值,pcr 是递增的,因此可以将其设置为 DTS 值,音频数据不需要 PCR。如果没有字段,打包 TS 流时 PAT 和 PMT 表是没有 adaptation field 的,不够的长度直接补 0xFF 即可。视频流和音频流都需要加 adaptation field,通常加在一个帧的第一个 TS 包和最后一个 TS 包里,中间的 TS 包不加。
PAT
Program Association Table 节目关联表,每个 TS 流对应一张,用来描述该 TS 流中有多少个节目。
- TS流中中,- PAT包重复实现,大约- 0.5秒出现一个,保证实时解码性
- 表示 PAT表的TS包PID值为0,便于识别
- PAT的 payload 中传送特殊- PID的列表,每个- PID对应一个节目(对应一张- PMT表)
- PAT表是- TS流的基础,任何一个- TS流解析寻找节目都是从- PAT表开始查找
typedef struct TS_PAT_Program {
    unsigned program_number       :16;  // 节目号,为 0x0000 时表示这是 NIT,节目号为 0x0001 时, 表示这是 PMT
    unsigned reserved             :3    // 保留,固定为 111
    unsigned program_map_PID      :13;  // 节目号对应内容的 PID 值
} TS_PAT_Program;
typedef struct TS_PAT {
    unsigned table_id                     : 8;  // 固定 0x00 ,标志是该表是 PAT
    unsigned section_syntax_indicator     : 1;  // 段语法标志位,固定为 1
    unsigned zero                         : 1;  // 0
    unsigned reserved_1                   : 2;  // 保留位,固定为 11
    unsigned section_length               : 12; // 段长度,表示从下一个字段开始到 CRC32(含) 之间有用的字节数
    unsigned transport_stream_id          : 16; // TS 流 ID,一般为 0x0001,区别于一个网络中其它多路复用的流
    unsigned reserved_2                   : 2;  // 保留位,固定为 11
    unsigned version_number               : 5;  // PAT 版本号,固定为 00000,如果 PAT 有变化则版本号加 1
    unsigned current_next_indicator       : 1;  // 固定为 1,表示这个 PAT 表有效,如果为 0 则要等待下一个 PAT 表
    unsigned section_number               : 8;  // 分段的号码。PAT 可能分为多段传输,第一段为 00,以后每个分段加 1,最多可能有 256 个分段
    unsigned last_section_number          : 8;  // 最后一个分段的号码
    std::vector<TS_PAT_Program> program;
    unsigned CRC_32                       : 32; // CRC32 校验码
} TS_PAT;PMT
Program Map Table,节目映射表,该表的 PID 是由 PAT 表 提供给出的。表征一路节目所有流信息。包含:
- 当前节目中包含的所有 Video数据的PID
- 当前节目中包含的所有 Audio数据的PID
- 与当前节目关联在一起的其他数据的 PID(如数字广播, 数据通讯等使用的PID)
如果 TS 流中包含多个节目,那么就会有多个 PMT 表。只要我们处理了 PMT 表,那么我们就可以获取该节目中所有的流信息,如当前节目包含多少个 Video、多少个 Audio 和其他数据及每种数据对用的流 PID 分别是多少。
typedef struct TS_PMT_Stream {
    unsigned stream_type              : 8;  // 指示本节目流的类型,H.264 编码对应 0x1b,AAC 编码对应 0x0f,MP3 编码对应 0x03
    unsigned reserved1                : 3;  // 保留位,固定为 111
    unsigned elementary_PID           : 13; // 指示该流的 PID 值
    unsigned reserved2                : 3;  // 保留位,固定为 1111
    unsigned ES_info_length           : 12; // 前两位 bit 为 00,指示跟随其后的描述相关节目元素的字节数
    std::vector<unsigned> descriptor;
} TS_PMT_Stream;
typedef struct TS_PMT {
    unsigned table_id                     : 8;  // 取值随意,一般使用 0x02, 表示 PMT 表
    unsigned section_syntax_indicator     : 1;  // 段语法标志位,固定为 1
    unsigned zero                         : 1;  // 固定为 0
    unsigned reserved_1                   : 2;  // 保留位,固定为 11
    unsigned section_length               : 12; // 段长度,表示从下一个字段开始到 CRC32(含) 之间有用的字节数
    unsigned program_number               : 16; // 当前 PMT 表映射到的节目号,1、2、3
    unsigned reserved_2                   : 2;  // 保留位,固定为 11
    unsigned version_number               : 5;  // PMT 版本号码,固定为 00000,如果 PAT 有变化则版本号加 1
    unsigned current_next_indicator       : 1;  // 发送的 PMT 表 是当前有效还是下一个 PMT 有效
    unsigned section_number               : 8;  // 分段的号码。PMT 可能分为多段传输,第一段为 00,以后每个分段加 1,最多可能有 256 个分段
    unsigned last_section_number          : 8;  // 分段数
    unsigned reserved_3                   : 3;  // 保留位,固定为 111
    unsigned PCR_PID                      : 13; // 指明 TS 包的 PID 值,该 TS 包含有 PCR 同步时钟,
    unsigned reserved_4                   : 4;  // 预留位,固定为 1111
    unsigned program_info_length          : 12; // 前 2bit 为 00,该域指出跟随其后对节目信息的描述的字节数。
    std::vector<TS_PMT_Stream> PMT_Stream;
    unsigned reserved_5                   : 3;  // 保留位,0x07
    unsigned reserved_6                   : 4;  // 保留位,0x0F
    unsigned CRC_32                       : 32; // CRC32 校验码
} TS_PMT;