1、简介
1.1、 综述
作为 Apple 提出的一种基于 HTTP 的协议,HLS(HTTP Live Streaming)用于解决实时音视频流的传
输。尤其是在移动端,由于 iOS /H5 不支持 flash,使得 HLS 成了移动端实时视频流传输的首选。HLS
经常用在直播领域,一些国内的直播云通常用 HLS 拉流(将视频流从服务器拉到客户端)。 HLS 值得诟
病之处就是其延迟严重,延迟通常在 10-30s 之间。
HLS(HTTP Live Streaming) 把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。
HLS 协议由三部分组成:HTTP、M3U8、TS。这三部分中,HTTP 是传输协议,M3U8 是索引文件,TS
是音视频的媒体信息。
1.2 、HLS 协议编码格式要求
视频的编码格式:H264
音频的编码格式:AAC、MP3、AC-3
视频的封装格式:ts
保存 ts 索引的 m3u8 文件
1.3 、HLS 协议优势
1、HLS 相对于 RTMP 来讲使用了标准的 HTTP 协议来传输数据,可以避免在一些特殊的网络环境下被屏蔽。
2、HLS 相对 RTMP 在服务器端做负载均衡要简单得多。因为 HLS 是基于无状态协议 HTTP 实现的,客户端只需要按照顺序使用下载存储在服务器的普通 ts 文件进行播放就可以。而 RTMP 是一种有状态协议,很难对视频服务器进行平滑扩展,因为需要为每一个播放视频流的客户端维护状态。
3、HLS 协议本身实现了码率自适应,在不同带宽情况下,设备可以自动切换到最适合自己码率的视频播放。
1.4 、HLS 协议劣势
1、HLS 协议在直播的视频延迟时间很难做到 10 s 以下延时,而 RTMP 协议的延时可以降到 1s 左右。
1.5 、框架图
2、m3u8文件
m3u8 文件是用文件方式对媒体文件进行描述,由一些列标签组成。
2.1 、单码率适配流m3u8文件
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:2
#EXT-X-TARGETDURATION:16
#EXTINF:14.357, no desc
livestream-2.ts
#EXTINF:15.617, no desc
livestream-3.ts
#EXTINF:14.358, no desc
livestream-4.ts
#EXTINF:15.618, no desc
livestream-5.ts
#EXTINF:11.130, no desc
livestream-6.ts
2.2 、多码率适配流m3u8文件
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
包含多种比特率的 Master Playlist。该文件是一个实际使用中的顶级 m3u8 文件,该文件中又定义了
http://example.com/low.m3u8 、 http://example.com/mid.m3u8 等 几 个 二 级文件 。 顶 级m3u8 文件主要是做码率适配的,二级 m3u8 才是真正的切片文件,客户端会默认选择码率最高的请求,如果发现码率达不到,会请求降低码率的流。客户端拿到二级 m3u8 文件后,会继续请求里面的文件,这时就可以进行播放了。
2.3 、Playlist file
一个 m3u 的 Playlist 就是一个由多个独立行组成的文本文件,每行由回车/换行区分。每一行可以是一个
URI、空白行或是一个 以 “#” 号开头的字符串,并且空格只能存在于一行中不同元素间的分隔。
一个 URI 表示一个媒体段或是 “variant Playlist file”(最多支持一层嵌套,即一个 m3u8 文件中嵌套另
一个 m3u8),以 “EXT” 开头的表示一个 “tag”,否则表示注释,直接忽略。
2.4 、Tags
1、
#EXTM3U :
每个 m3u8 文件第一行必须是这个 tag,如上面的两个示例。
2、
#EXT-X-VERSION:
m3u8文件版本号,比如#EXT-X-VERSION:3。
3、
#EXTINF :
指定每个媒体段(ts)的持续时间,这个仅对其后面的 URI 有效,
每两个媒体段 URI 间被这个 tag 分隔开其格式为: #EXTINF:<duration><title>
比如#EXTINF:14.357, no desc
duration:表示持续的时间(秒),"Durations MUST be integers if the protocol version of the Playlist file is less than 3",否则可以是浮点数。
4、
#EXT-X-BYTERANGE :
表示媒体段是一个媒体 URI 资源中的一段,只对其后的 media URI 有效,
格式: #EXT-X-BYTERANGE:<n>[@o]
n:表示这个区间的大小
o:表示在 URI 中的 offset
The EXT-X-BYTERANGE tag appeared in version 4 of the protocol
5、
#EXT-X-TARGETDURATION :
指定当前视频流中的单个切片(即 ts)文件的最大时长(秒)。
所以
#EXTINF :
中指定的时间长度必须小于或是等于这个最大值。这个 tag 在整个 Playlist 文件中只能出现1次(在嵌套的情况下,
一般有真正ts url 的 m3u8 才会出现该 tag)。格式: #EXT-XTARGETDURATION:<s>
s:表示最大的秒数。
6、
#EXT-X-MEDIA-SEQUENCE :
每一个 media URI 在 Playlist 中只有唯一的序号,相邻之间序号 +1。
格式:#EXT-X-MEDIA-SEQUENCE:<number>
一个 media URI 并不是必须要包含的,如果没有,默认为 0.
7、
#EXT-X-KEY :
表示怎么对 media segments 进行解码。其作用范围是下次该 tag 出现前的所有media URI。
格式为: #EXT-X-KEY:<attribute-list>
NONE 或者 AES-128。如果是 NONE,则 URI 以及 IV 属性必须不存在,如果是 AES-128(Advanced Encryption Standard),
则 URI 必须存在,IV 可以不存在。对于 AES-128 的情况,keytag 和 URI 属性共同表示了一个 key 文件,通过 URI 可以获得这个key,
如果没有 IV(Initialization Vector),则使用序列号作为 IV 进行编解码,将序列号的高位赋到 16 个字节的 buffer 中,
左边补 0;如果有 IV,则将该值当成 16 个字节的 16 进制数。
8、
#EXT-X-PROGRAM-DATE-TIME :
将一个绝对时间或是日期和一个媒体段中的第一个 sample 相关
联,只对下一个 media URI 有效,
格式: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MMDDThh:mm:ssZ>
例如: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
9、
#EXT-X-ALLOW-CACHE :
是否允许做 cache,这个可以在 Playlist 文件中任意地方出现,
并且最多只出现一次,作用效果是所有的媒体段。
格式: #EXT-X-ALLOW-CACHE:<YES|NO>
10、
#EXT-X-PLAYLIST-TYPE :
提供关于 Playlist 的可变性的信息,这个对整个 Playlist 文件有效,是可选的,
格式: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
VOD,即为点播视频,服务器不能改变 Playlist 文件,换句话说就是该视频全部的 ts 文件已经被生成好了。
EVENT,就是实时生成 m3u8 和 ts 文件。服务器不能改变或是删除 Playlist 文件中的任何部分,
但是可以向该文件中增加新的一行内容。它的索引文件一直处于动态变化中,
播放的时候需要不断下载二级index文件。
11、
#EXT-X-ENDLIST :
表示 m3u8 文件的结束,live m3u8 没有该 tag。它可以在 Playlist 中任意位置出现,
但是只能出现一个,格式: #EXT-X-ENDLIST
12、
#EXT-X-MEDIA :
被用来在 Playlist 中表示相同内容的不同语种/译文的版本,
比如可以通过使用 3个这种 tag 表示 3 种不同语音的音频,
或者用 2 个这个 tag 表示不同⻆度的 video。在 Playlist中,
这个标签是独立存在的,
其格式: #EXT-X-MEDIA:<attribute-list>
该属性列表中包含:URI、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、
AUTOSELECT。
URI:如果没有,则表示这个 tag 描述的可选择版本在主 PlayList 的 EXT-X-STREAM-INF 中存在。
TYPE:AUDIO and VIDEO。
GROUP-ID:具有相同 ID 的 MEDIAtag,组成一组样式。
LANGUAGE:identifies the primary language used in the rendition。
NAME:The value is a quoted-string containing a human-readable description of the rendition.
If the LANGUAGE attribute is present then this description SHOULD be in that language。
DEFAULT:YES 或是 NO,默认是 No,如果是 YES,则客户端会以这种选项来播放,除非用户自己进行选择。
AUTOSELECT:YES 或是 NO,默认是 No,如果是 YES,则客户端会根据当前播放环境来进行选择(用户没有根据自己偏好进行选择的前提下)。
The EXT-X-MEDIA tag appeared in version 4 of the protocol。
13、
#EXT-X-STREAM-INF :
指定一个包含多媒体信息的 media URI 作为 Playlist,
一般做 m3u8 的嵌套使用,
它只对紧跟后面的 URI 有效,
格式: #EXT-X-STREAM-INF:<attribute-list>
常用的属性如下:
BANDWIDTH:带宽,必须有。
PROGRAM-ID:该值是一个十进制整数,唯一地标识一个在 Playlist 文件范围内的特定的描述。一个 Playlist 文件中可能包含多个有相同 ID 的此 tag。
CODECS:指定流的编码类型,不是必须的。
RESOLUTION:分辨率。
AUDIO:这个值必须和 AUDIO 类别的 "EXT-X-MEDIA" 标签中 "GROUP-ID" 属性值相匹配。
VIDEO:同上。
14、
#EXT-X-DISCONTINUITY :
当遇到该 tag 的时候说明以下属性发生了变化:
file format :文件格式
number and type of tracks :轨道
encoding parameters :编码参数
encoding sequence :编码序号
timestamp sequence :时间戳序号
15、
#ZEN-TOTAL-DURATION :
表示这个 m3u8 所含 ts 的总时间长度
3、ts文件
ts 文件为传输流文件,视频编码主要格式为 H264/MPEG4,音频为 AAC/MP3。
ts 文件分为三层:
------ ts 层:Transport Stream,是在 pes 层的基础上加入数据流的识别和传输必须的信息。
------ pes 层: Packet Elemental Stream,是在音视频数据上加了时间戳等对数据帧的说明信息。
------ es 层:Elementary Stream,即音视频数据。
3.1 、ts文件结构
PAT(Program Association Table)节目关联表:主要的作用就是指明了 PMT 表的 PID 值。
PMT(Program Map Table)节目映射表:主要的作用就是指明了音视频流的 PID 值。
3.2、ts文件结构部分截图
刚开始的TS包是PAT(Program Association Table):节目关联表。
再跟的TS包是PMT(Program Map Table):节目映射表。
然后再跟视频、音频的TS包。
ts 包大小固定为 188 字节,ts 层分为三个部分:ts header、adaptation field、payload。
ts header :固定 4 个字节。
adaptation field : 可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充。
payload : pes 数据。
可以根据每个ts包的偏移量判断都是188字节。
3.3、ts层(ts header)
ts 层的内容是通过 PID 值来标识的,主要内容包括:PAT 表、PMT 表、音频流、视频流。
解析 ts 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到音视频流了。
PAT 表的和 PMT 表需要定期插入 ts 流,因为用户随时可能加入 ts 流,这个间隔比较小,
通常每隔几个视频帧就要加入 PAT和 PMT。
PAT 和 PMT 表是必须的,还可以加入其它表如 SDT(业务描述表)等,不过 hls 流只要有
PAT 和 PMT 就可以播放了。
3.4、ts层(adaptation field)
自适应区的长度要包含传输错误指示符标识的一个字节。pcr 是节目时钟参考,pcr、dts、pts 都是对同
一个系统时钟的采样值,pcr 是递增的,因此可以将其设置为 dts 值,音频数据不需要 pcr。如果没有字
段,ipad 是可以播放的,但 vlc 无法播放。打包 ts 流时 PAT 和 PMT 表是没有 adaptation field 的,
不够的长度直接补 0xff 即可。视频流和音频流都需要加 adaptation field,通常加在一个帧的第一个 ts
包和最后一个 ts 包中,中间的 ts 包不加。
adaptation field 详解:
flag 标志位:0x10就表示有PCR,下面视频流截图也是这个情况,0x50是random_access_indicator标志位和PCR_flag标志位都有。
3.4.1、视频流和音频流加 adaptation field 情况部分截图
1、视频帧:
I帧:第一个TS包和最后一个TS包有adaptation field,根据ts header 最后一个字节判断。
(第一个TS包要存PCR)
P帧:最后一个TS包有adaptation field,根据ts header 最后一个字节判断。
2、音频帧:
最后一个TS包有adaptation field,根据ts header 最后一个字节判断。
3.5、ts层(PAT)
PAT(Program Association Table)节目关联表:主要的作用就是指明了 PMT 表的 PID 值。
3.5.1、PAT情况截图
1、下面0x01字节表示有PMT。
2、下面0XF0 01的后13b就是PMT的PID(4097)。
(注意这个PID与ts header的pid的字节序是不同的)
3.6、ts层(PMT)
PMT(Program Map Table)节目映射表:主要的作用就是指明了音视频流的 PID 值。
3.6.1、PMT情况截图
1、下面0xE1 00字节后13b表示PCR的PID(256)。
2、下面0X0F 字节就是stream type AAC。
3、下面0xE1 01字节后13b就是elementary_PID (257)。
4、下面0X1B 字节就是stream type H264。
5、下面0xE1 00字节后13b就是elementary_PID (256)。
3.7、pes层
pes (Packet Elemental Stream)层是在每一个视频/音频帧上加上了时间戳等信息,pes 包内容项很多。
pes 层格式如下:
列举一些pes 包含的常用字段:
截图分析:
根据0x41字节中获取payload_unit_start_indicator 为1,表示这个TS包是有pes包头的(00 00 01 E0 01 FB 80 80 05 21 00 7F E9 B9 )然后后面才是实际的h264数据,由于后面还有两个payload_unit_start_indicator 为0的,表示这3个TS包合起来才是完整的一帧h264数据。
而且payload_unit_start_indicator 为0的TS包,是没有pes包头了,除去4个字节的ts包头就是h264数据了。如下图:
3.8、es层
es(Elementary Stream) 层就是音视频数据。
3.8.1、es层 (h264视频)
打包 h.264 数据时必须给视频数据加上一个 nalu(Network Abstraction Layer Unit),nalu 包括
nalu header 和 nalu type,nalu header 固定为 0x00000001(帧开始)或 0x000001(帧中)。
h.264 的数据是由 slice 组成的,slice 的内容包括:视频、sps、pps 等。nalu type 决定了后面的
h.264 数据内容。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| TYPE |
+-+-+-+-+-+-+-+-+
F:1bit,forbidden_zero_bit,h.264 规定必须取 0。
NRI:2bits,nal_ref_idc,取值为 0~3,指示这个 nalu 的重要性,I 帧、sps、pps 通常取 3,P 帧
常取 2,B 帧通常取 0
Type:5bits,取值如下:
打包 es 层数据时 pes 头和 es 数据之间要加入一个 type=9 的 nalu,关键帧 slice 前必须要加入
type=7 和 type=8 的 nalu,而且是紧邻的。
如下图所示:
3.8.2、es层 (aac音频)
打包aac音频必须加上一个adts(Audio Data Transport Stream)头,共7Byte,adts包括
fixed_header和variable_header两部分,各28bit。
fixed_header:
variable_header: