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层上加入了数据流识别和传输的必要信息。
ts包大小固定为188字节,ts层分为三个部分:ts header、adaptation field、payload。ts header固定4个字节;adaptation field可能存在也可能不存在,主要作用是给不足188字节的数据做填充;payload是pes数据。
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就可以播放了。
PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的。如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video、Audio或其他业务信息)。下表给出了一些表的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个
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包不加。
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表对应的频道号。
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 | —— | —— |
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值 |
pts是显示时间戳、dts是解码时间戳,视频数据两种时间戳都需要,音频数据的pts和dts相同,所以只需要pts。有pts和dts两种时间戳是B帧引起的,I帧和P帧的pts等于dts。如果一个视频没有B帧,则pts永远和dts相同。从文件中顺序读取视频帧,取出的帧顺序和dts顺序相同。dts算法比较简单,初始值 + 增量即可,pts计算比较复杂,需要在dts的基础上加偏移量。
音频的pes中只有pts(同dts),视频的I、P帧两种时间戳都要有,视频B帧只要pts(同dts)。打包pts和dts就需要知道视频帧类型,但是通过容器格式我们是无法判断帧类型的,必须解析h.264内容才可以获取帧类型。
es层就是音视频裸数据了,常用的音频编码格式为AAC,视频编码格式为H.264
对于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)
理论上的音视频(播放)同步是这样的:
由此得到了每一帧数据的持续时间,音视频交叉存储在容器中:一个时间轴:
时间轴:022.324044.6266.968089.16111.48120................
音频:0 22.32 44.62 66.9689.16 111.48................
视 频:04080120................
即视频的持续时间相加 和音频的持续时间相加作比较,谁小写入哪个。
(自己的方法)
音频数据(AAC 48k) 21.33 42.44 63.99 85.32
视频数据(H264 25fps) 40 80
时间轴 ------------------------------------------->
(ts容器)循环做(写一帧视频,然后写一帧音频,然后视频的时间减去音频的时间,如果大于一帧音频的时间,就多写一帧音频,知道视频多出来的时间小于一帧音频)
使用Elecard Stream Analyzer 可以分析TS流