TS封装格式
ts流最早应用于数字电视领域,其格式非常复杂包含的配置信息表多达十几个,视频格式主要是mpeg2。苹果公司发明的http live stream流媒体是基于ts文件的,不过他大大简化了传统的ts流,只需要2个最基本的配置表PAT和PMT,再加上音视频内容就可以了,hls流媒体视频编码的主要格式为h264/mpeg4,音频为aac/mp3。
ts文件分为三层:ts层(Transport Stream)、pes层(Packet Elemental Stream)、es层(Elementary Stream)。es层就是音视频数据,pes层是在音视频数据上加了时间戳等对数据帧的说明信息,ts层是在pes层上加入了数据流识别和传输的必要信息。
1.ts层
ts包大小固定为188字节,ts层分为三个部分:ts header、adaptation field、payload。ts header固定4个字节;adaptation field可能存在也可能不存在,主要作用是给不足188字节的数据做填充;payload是pes数据。
1.1.ts header
sync_byte | 8bit | 同步字节,固定为0x47 |
transport_error_indicator | 1bit | 传输错误指示符,表明在ts头的adapt域后由一个无用字节,通常都为0,这个字节算在adapt域长度内 |
payload_unit_start_indicator | 1bit | 负载单元起始标示符,一个完整的数据包开始时标记为1 |
transport_priority | 1bit | 传输优先级,0为低优先级,1为高优先级,通常取0 |
pid | 13bit | pid值(Packet ID号码,唯一的号码对应不同的包) |
transport_scrambling_control | 2bit | 传输加扰控制,00表示未加密 |
adaptation_field_control | 2bit | 是否包含自适应区,‘00’保留;‘01’为无自适应域,仅含有效负载;‘10’为仅含自适应域,无有效负载;‘11’为同时带有自适应域和有效负载。 |
continuity_counter | 4bit | 递增计数器,从0-f,起始值不一定取0,但必须是连续的 |
ts层的内容是通过PID值来标识的,主要内容包括:PAT表、PMT表、音频流、视频流。解析ts流要先找到PAT表,只要找到PAT就可以找到PMT,然后就可以找到音视频流了。PAT表的PID值固定为0。PAT表和PMT表需要定期插入ts流,因为用户随时可能加入ts流,这个间隔比较小,通常每隔几个视频帧就要加入PAT和PMT。PAT和PMT表是必须的,还可以加入其它表如SDT(业务描述表)等,不过hls流只要有PAT和PMT就可以播放了。
- PAT表:他主要的作用就是指明了PMT表的PID值。
- PMT表:他主要的作用就是指明了音视频流的PID值。
- 音频流/视频流:承载音视频内容。
表 |
PID 值 |
PAT |
0x0000 |
CAT |
0x0001 |
TSDT |
0x0002 |
EIT,ST |
0x0012 |
RST,ST |
0x0013 |
TDT,TOT,ST |
0x0014 |
下面以一个TS流的其中一个Packet中的Packet Header为例进行说明:
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
Packet(十六进制) |
4 |
7 |
0 |
7 |
e |
5 |
1 |
2 |
… |
||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
… |
Packet Header 信息 |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x07e5 |
6 |
7 |
8 |
… |
sync_byte=01000111, 就是0x47,这是DVB TS规定的同步字节,固定是0x47.
transport_error_indicator=0, 表示当前包没有发生传输错误.
payload_unit_start_indicator=0, 含义参考ISO13818-1标准文档
transport_priority=0, 表示当前包是低优先级.
PID=00111 11100101即0x07e5, Video PID
transport_scrambling_control=00, 表示节目没有加密
adaptation_field_control=01 即0x01,具体含义请参考ISO13818-1
continuity_counte=0010 即0x02,表示当前传送的相同类型的包是第3个
1.2.adaption
adaptation_field_length | 1B | 自适应域长度,后面的字节数 |
flag | 1B | 取0x50表示包含PCR或0x40表示不包含PCR |
PCR | 5B | Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)。 |
stuffing_bytes | xB | 填充字节,取值0xff |
自适应区的长度要包含传输错误指示符标识的一个字节。pcr是节目时钟参考,pcr、dts、pts都是对同一个系统时钟的采样值,pcr是递增的,因此可以将其设置为dts值,音频数据不需要pcr。如果没有字段,ipad是可以播放的,但vlc无法播放。打包ts流时PAT和PMT表是没有adaptation field的,不够的长度直接补0xff即可。视频流和音频流都需要加adaptation field,通常加在一个帧的第一个ts包和最后一个ts包里,中间的ts包不加。
1.3.PAT格式(Program Association Table,节目关联表)
PAT表定义了当前TS流中所有的节目,其PID为0x0000,它是PSI的根节点,要查寻找节目必须从PAT表开始查找。
table_id | 8b | PAT表固定为0x00 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
transport_stream_id | 16b | 传输流ID,固定为0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1,表示这个PAT表可以用,如果为0则要等待下一个PAT表 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
开始循环 | ||
program_number | 16b | 节目号为0x0000时表示这是NIT,节目号为0x0001时,表示这是PMT |
reserved | 3b | 固定为111 |
PID | 13b | 节目号对应内容的PID值 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
通过一段TS流中一个Packet分析PAT表,这里我们分析一段TS流其中一个Packet的Packet Data部分:
首先给出一个数据包,其数据如下:
Packet Header |
Packet Data |
0x47 0x40 0x00 0x10 |
0000 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff…… ff ff |
分析Packet Header如下表所示:
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
Packet(十六进制) |
4 |
7 |
4 |
0 |
0 |
0 |
1 |
0 |
… |
||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
… |
Packet Header Bits |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x0000 |
6 |
7 |
8 |
… |
根据包头数据格式,我们可以知晓整个数据包的属性,列表如下:
sync_byte |
0x47 |
固定同步字节 |
transport_error_indicator |
“0” |
没有传输错误 |
payload_unit_start_indicator |
“1” |
在前4个字节后会有一个调整字节。所以实际数据应该为去除第一个字节后的数据。即上面数据中红色部分不属于有效数据包。 |
transport_priority |
“0” |
传输优先级低 |
PID |
0x0000 |
PID=0x0000说明数据包是PAT表信息 |
transport_scrambling_control |
“00” |
未加密 |
adaptation_field_control |
“01” |
附加区域控制 |
continuity_counte |
“0000” |
包递增计数器 |
如上表所示,我们可以知道,首先Packet的Packet Data是PAT信息表,因为其PID为0x0000,并且在包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")。这样,Packet Data就应该是“00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff …… ff ff”。
Packet Data分析 |
|||||||||||||||||||||||||
第n个字节 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
… |
||||
Packet Data(除去开头的0x00) |
00 |
b0 |
11 |
00 |
01 |
c1 |
00 |
00 |
00 |
00 |
e0 |
1f |
00 |
01 |
e1 |
00 |
24 |
ac |
48 |
84 |
… |
||||
字段名 |
位 |
具体值 |
次序 |
说明 |
|||||||||||||||||||||
table_id |
8 |
0000 |
第1个字节 0000 0000B(0x00) |
PAT的table_id只能是0x00 |
|||||||||||||||||||||
section_syntax_indicator |
1 |
1 |
第2、3个字节 1011 0000 0001 0001B(0xb0 11) |
段语法标志位,固定为1 |
|||||||||||||||||||||
zero |
1 |
0 |
|
||||||||||||||||||||||
reserved |
2 |
11 |
|
||||||||||||||||||||||
section_length |
12 |
0000 0001 0001B=0x011=17 |
段长度为17字节 |
||||||||||||||||||||||
transport_stream_id |
16 |
0x0001 |
第4、5个字节 0x00 0x01 |
|
|||||||||||||||||||||
reserved |
2 |
11 |
第6个字节 1100 0001B(0xc1) |
|
|||||||||||||||||||||
version_number |
5 |
00000 |
一旦PAT有变化,版本号加1 |
||||||||||||||||||||||
current_next_indicator |
1 |
1 |
当前传送的PAT表可以使用,若为0则要等待下一个表 |
||||||||||||||||||||||
section_number |
8 |
0x00 |
第7个字节0x00 |
|
|||||||||||||||||||||
last_section_number |
8 |
0x00 |
第8个字节 0x00 |
|
|||||||||||||||||||||
开始循环 |
|||||||||||||||||||||||||
program_number |
16 |
0x0000-第一次 |
2个字节(0x00 00) |
节目号 |
|||||||||||||||||||||
reserved |
3 |
111 |
2个字节 1110 0000 0001 1111B(0xe0 1f) |
|
|||||||||||||||||||||
network_id(节目号为0时) program_map_PID(节目号为其他时) |
13 |
0 0000 0001 1111B=31 -第一次 |
节目号为0x0000时,表示这是NIT,PID=0x001f,即31 节目号为0x0001时,表示这是PMT,PID=0x100,即256 |
||||||||||||||||||||||
结束循环 |
|||||||||||||||||||||||||
CRC_32 |
32 |
-- |
4个字节 |
|
由以上几个表可以分析出PAT表和PMT表有着内在的联系。也就是之前提到的。PAT表描述了当前流的NIT(Network Information Table,网络信息表)中的PID、当前流中有多少不同类型的PMT表及每个PMT表对应的频道号。
1.4.PMT格式( Program Map Table,节目映射表 )
table_id | 8b | PMT表取值随意,0x02 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
program_number | 16b | 频道号码,表示当前的PMT关联到的频道,取值0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
reserved | 3b | 固定为111 |
PCR_PID | 13b | PCR(节目参考时钟)所在TS分组的PID,指定为视频PID |
reserved | 4b | 固定为1111 |
program_info_length | 12b | 节目描述信息,指定为0x000表示没有 |
开始循环 | ||
stream_type | 8b | 流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03 |
reserved | 3b | 固定为111 |
elementary_PID | 13b | 与stream_type对应的PID |
reserved | 4b | 固定为1111 |
ES_info_length | 12b | 描述信息,指定为0x000表示没有 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
通过一段TS流中一个Packet分析PMT表,通过分析一段TS流的数据包Packet来学习PMT表。下面给出了一段TS流数据中的一个Packet(十六进制数)
Packet Header |
Packet Data |
0x47 0x43 0xe8 0x12 |
00 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff…… ff ff |
首先解析Packet Header,分析如下:
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
Packet(十六进制) |
4 |
7 |
4 |
3 |
e |
8 |
1 |
2 |
… |
||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
… |
Packet Header Bits |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x03e8 |
6 |
7 |
8 |
… |
Packet Header分析 |
|||
|
Packet Header:0x47 0x40 0x00 0x10 |
||
1 |
sync_byte |
0x47 |
固定同步字节 |
2 |
transport_error_indicator |
“0” |
没有传输错误 |
3 |
payload_unit_start_indicator |
“1” |
在前4个字节后会有一个调整字节。所以实际数据应该为去除第一个字节后的数据。 |
4 |
transport_priority |
“0” |
传输优先级低 |
5 |
PID |
0x03e8 |
PID=0x03e8说明数据包是PMT表信息 |
6 |
transport_scrambling_control |
“00” |
未加密 |
7 |
adaptation_field_control |
“01” |
附加区域控制 |
8 |
continuity_counte |
“0010” |
包递增计数器 |
因为payload_unit_start_indicator=‘1’,在解析数据包的时候需要去除Packet Data的第一个字节。下面是对Packet Data的详细解析:
PMT表的Packet Data分析 |
|||||||||||||||||||||||||
第n个字节 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
… |
||||
Packet Data |
02 |
b0 |
12 |
00 |
01 |
c1 |
00 |
00 |
e3 |
e9 |
f0 |
00 |
1b |
e3 |
e9 |
f0 |
00 |
f0 |
1b |
e3 |
… |
||||
字段名 |
位数 |
具体值 |
次序 |
说明 |
|||||||||||||||||||||
table_id |
8 |
0x02 |
第1个字节 |
|
|||||||||||||||||||||
section_syntax_indicator |
1 |
1B |
第2、3个字节 1011 0000 0001 0010B=0xb012 |
段语法标志 |
|||||||||||||||||||||
zero |
1 |
0B |
|
||||||||||||||||||||||
reserved |
2 |
11B=0x03 |
|
||||||||||||||||||||||
section_length |
12 |
0000 0001 0010B=0x12 |
段长度,从program_number开始,到CRC_32(含)的字节总数 |
||||||||||||||||||||||
program_number |
16 |
0x0001 |
第4、5个字节 0x00 01 |
频道号码,表示当前的PMT关联到的频道 |
|||||||||||||||||||||
reserved |
2 |
11B=0x03 |
第6个字节 1100 0001B=0xc1 |
|
|||||||||||||||||||||
version_number |
5 |
00000B=0x00 |
版本号码,如果PMT内容有更新,则它会递增1通知解复用程序需要重新接收节目信息 |
||||||||||||||||||||||
current_next_indicator |
1 |
1B=0x01 |
当前未来标志符 |
||||||||||||||||||||||
section_number |
8 |
0x00 |
第7个字节0x00 |
当前段号码 |
|||||||||||||||||||||
last_section_number |
8 |
0x00 |
第8个字节 0x00 |
最后段号码,含义和PAT中的对应字段相同 |
|||||||||||||||||||||
reserved |
3 |
111B=0x07 |
第9、10个字节 1110 0011 1110 1001B=0xe3e9 |
|
|||||||||||||||||||||
PCR_PID |
13 |
000111110B=0x3e9 |
PCR(节目参考时钟)所在TS分组的PID |
||||||||||||||||||||||
reserved |
4 |
1111B=0x0f |
第11、12个字节 1111 0000 0000 0000=0xf000 |
|
|||||||||||||||||||||
program_info_length |
12 |
000000000000B=0x000 |
节目信息长度(之后的是N个描述符结构,一般可以忽略掉,这个字段就代表描述符总的长度,单位是Bytes)紧接着就是频道内部包含的节目类型和对应的PID号码了 |
||||||||||||||||||||||
stream_type |
8 |
0x1b |
第13个字节 0x1b |
流类型,标志是Video还是Audio还是其他数据 |
|||||||||||||||||||||
reserved |
3 |
111B=0x07 |
第14、15个字节 1110 0011 1110 1001B=0xe3e9 |
|
|||||||||||||||||||||
elementary_PID |
13 |
000111110 1001=0x3e9 |
该节目中包括的视频流,音频流等对应的TS分组的PID |
||||||||||||||||||||||
reserved |
4 |
1111B=0x0f |
第16、17个字节 1111 0000 0000 0000B=0xf000 |
|
|||||||||||||||||||||
ES_info_length |
12 |
0000 0000 0000=0x000 |
|
||||||||||||||||||||||
CRC |
32 |
—— |
—— |
|
1.4.pes层
pes层是在每一个视频/音频帧上加入了时间戳等信息,pes包内容很多,我们只留下最常用的。
pes start code | 3B | 开始码,固定为0x000001 |
stream id | 1B | 音频取值(0xc0-0xdf),通常为0xc0 视频取值(0xe0-0xef),通常为0xe0 |
pes packet length | 2B | 后面pes数据的长度,0表示长度不限制, 只有视频数据长度会超过0xffff |
flag | 1B | 通常取值0x80,表示数据不加密、无优先级、备份的数据 |
flag | 1B | 取值0x80表示只含有pts,取值0xc0表示含有pts和dts |
pes data length | 1B | 后面数据的长度,取值5或10 |
pts | 5B | 33bit值 |
dts | 5B | 33bit值 |
1.5.es层
es层就是音视频裸数据了,常用的音频编码格式为AAC,视频编码格式为H.264
2.打包H.264和AAC为TS
对于H.264视频而言,每一帧的时间长度为
frame_duration = 1000/fps
当fps为25时,一帧时间为40ms
对于AAC音频而言,每一帧的时间长度为
音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率(单位为s)
一帧 1024个 sample。采样率 Samplerate 44100KHz,每秒44100个sample, 所以根据公式 音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率
当前AAC一帧的播放时间是= 1024*1000000/44100= 22.32ms(单位为ms)
由此得到了每一帧数据的持续时间,音视频交叉存储在容器中:一个时间轴:
时间轴:0 22.32 40 44.62 66.96 80 89.16 111.48 120 ................
音 频 :0 22.32 44.62 66.96 89.16 111.48 ................
视 频 :0 40 80 120 ................
即视频的持续时间相加 和音频的持续时间相加作比较,谁小写入哪个。