1.HLS综述
谈HLS
就不得不谈苹果,谈苹果就不得不提乔帮主。HLS
就是“HTTP Live Streaming
”的缩写,它诞生自2009
年,QuickTime
和iPhone3GS
黄金搭档下的一个标准,一个意在颠覆流媒体产业的新协议。
它的工作原理简单来说就是把一段视频流,分成一个个小的基于HTTP
的文件来下载。当媒体流正在播放时,客户端可以根据当前网络环境,方便地在不同的码率流中做切换,以实现更好的观影体验。
HLS
的出现是为了解决苹果原生环境中的流媒体播放,这个协议可以方便地让Mac
和iPhone
播放视频流,不依赖Adobe
,更不用去管什么标准委员会。依赖自己,永远是最大力量的保障。
HLS(HTTP Live Streaming)
把整个流分成一个个小的基于 HTTP
的文件来下载,每次只下载一些。HLS
协议由三部分组成:HTTP
、M3U8
、TS
。这三部分中,HTTP
是传输协议,M3U8
是索引文件,TS
是音视频的媒体信息。
HLS
提供一个 m3u8
地址,Apple
的 Safari
浏览器直接就能打开 m3u8
地址,譬如:
http://demo.srs.com/live/livestream.m3u8
Android
不能直接打开,需要使用 html5
的 video
标签,然后在浏览器中打开这个页面即可,譬如:
<!-- livestream.html -->
<video width="640" height="360"
autoplay controls autobuffer
src="http://demo.srs.com/live/livestream.m3u8"
type="application/vnd.apple.mpegurl">
</video>
HLS
的 m3u8
,是一个 TS
的列表,也就是告诉浏览器可以播放这些 TS
文件,譬如:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:64
#EXT-X-TARGETDURATION:12
#EXTINF:11.550
livestream-64.TS
#EXTINF:5.250
livestream-65.TS
#EXTINF:7.700
livestream-66.TS
#EXTINF:6.850
livestream-67.TS
有几个关键的参数,这些参数在 SRS
的配置文件中都有配置项:
-
EXT-X-TARGETDURATION
:所有切片的最大时长。有些Apple
设备这个参数不正确会无法播放。SRS
会自动计算出TS
文件的最大时长,然后更新m3u8
时会自动更新这个值。用户不必自己配置。 -
EXTINF
:TS
切片的实际时长,SRS
提供配置项hls_fragment
,但实际上的TS
时长还受gop
影响。 -
TS
文件的数目:SRS
可配置hls_window
,指定m3u8
中保存多少个切片,SRS
会自动清理旧的切片 -
livestream-67.TS
:SRS
会自动维护TS
切片的文件名,在编码器重推之后,这个编号会继续增长,保证流的连续性。直到SRS
重启,这个编号才重置为0
。
譬如,每个 TS
切片为 10
秒,窗口为 60
秒,那么 m3u8
中会保存 6
个 TS
切片。
每一个 .m3u8
文件,分别对应若干个 TS
文件,这些 TS
文件才是真正存放视频的数据,m3u8
文件只是存放了一些 TS
文件的配置信息和相关路径,当视频播放时,.m3u8
是动态改变的,video
标签会解析这个文件,并找到对应的 TS
文件来播放,所以一般为了加快速度,.m3u8
放在 web
服务器上,TS
文件放在 CDN
上。
.m3u8
文件,其实就是以 utf-8
编码的 m3u
文件,这个文件本身不能播放,只是存放了播放信息的文本文件。
HLS 整体框架图:Server、CDN 和 Client
HLS 协议编码格式要求
- 视频的编码格式:
H264
- 音频的编码格式:
AAC
、MP3
、AC-3
- 视频的封装格式:
TS
- 保存
TS
索引的m3u8
文件
HLS 协议优势
-
HLS
相对于RTMP
来讲使用了标准的HTTP
协议来传输数据,可以避免在一些特殊的网络环境下被屏蔽。 -
HLS
相比RTMP
在服务器端做负载均衡要简单得多。因为HLS
是基于无状态协议HTTP
实现的,客户端只需要按照顺序使用下载存储在服务器的普通 TS 文件进行播放就可以。而RTMP
是一种有状态协议,很难对视频服务器进行平滑扩展,因为需要为每一个播放视频流的客户端维护状态。 -
HLS
协议本身实现了码率自适应,在不同带宽情况下,设备可以自动切换到最适合自己码率的视频播放。
HLS 协议缺点
-
HLS
协议在直播的视频延迟时间很难做到10 s
以下延时,而RTMP
协议的延时可以降到3s-4s
左右。
2. HLS 之 M3U8
m3u8
文件是用文件方式对媒体文件进行描述,由一些列标签组成。
m3u8 文件示例 1:单码率适配流
#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
该 m3u8
文件只是一个简单的 Media Playlist
。
m3u8 文件示例 2:多码率适配流
#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
文件,该文件中又定义了
等几个二级文件。顶级 m3u8
文件主要是做码率适配的,二级 m3u8
才是真正的切片文件,客户端会默认选择码率最高的请求,如果发现码率达不到,会请求降低码率的流。客户端拿到二级 m3u8
文件后,会继续请求里面的文件,这时就可以进行播放了。
2.1 基础概念
2.1.1 Playlist file
一个 m3u
的 Playlist
就是一个由多个独立行组成的文本文件,每行由回车/换行区分。每一行可以是一个 URL
、空白行或是一个 以 #
号开头的字符串,并且空格只能存在于一行中不同元素间的分隔。
一个 URL
表示一个媒体段或是 variant Playlist file
(最多支持一层嵌套,即一个 m3u8
文件中嵌套另一个 m3u8
),以 "EXT"
开头的表示一个 "tag"
,否则表示注释,直接忽略。
2.1.2 Tags
#EXTM3U:
每个m3u8
文件第一行必须是这个tag
,如上面的两个示例。#EXTINF:
指定每个媒体段(TS
)的持续时间,这个仅对其后面的URL
有效,每两个媒体段URL
间被这个tag
分隔开
其格式为:#EXTINF:<duration>,<title>
-
duration
:表示持续的时间(秒),objectivec Durations MUST be integers if the protocol version of the Playlist file is less than 3
,否则可以是浮点数。
#EXT-X-BYTERANGE:
表示媒体段是一个媒体URL
资源中的一段,只对其后的media URL
有效,格式为:#EXT-X-BYTERANGE:<n>[@o]
-
n
:表示这个区间的大小 -
o
:表示在URL
中的offset
The EXT-X-BYTERANGE tag appeared in version 4 of the protocol
#EXT-X-TARGETDURATION:
指定当前视频流中的单个切片(即TS
)文件的最大时长(秒)。所以#EXTINF
中指定的时间长度必须小于或是等于这个最大值。这个tag
在整个Playlist
文件中只能出现一次(在嵌套的情况下,一般有真正TS url
的m3u8
才会出现该tag
)。格式为:#EXT-X-TARGETDURATION:<s>
-
s
:表示最大的秒数。
#EXT-X-MEDIA-SEQUENCE:
每一个media
URL
在Playlist
中只有唯一的序号,相邻之间序号+1
。
格式为:#EXT-X-MEDIA-SEQUENCE:<number>
。一个media URL
并不是必须要包含的,如果没有,默认为0
.#EXT-X-KEY:
表示怎么对media segmenTS
进行解码。其作用范围是下次该tag
出现前的所有media URL
。
格式为:#EXT-X-KEY:<attribute-list>
-
NONE
或者AES-128
。如果是NONE
,则URL
以及IV
属性必须不存在,如果是AES-128
(Advanced Encryption Standard
),则URL
必须存在,IV
可以不存在。 - 对于
AES-128
的情况,keytag
和URL
属性共同表示了一个key
文件,通过URL
可以获得这个key
,如果没有IV
(Initialization Vector
),则使用序列号作为IV
进行编解码,将序列号的高位赋到16
个字节的buffer
中,左边补0
;如果有IV
,则将该值当成16
个字节的16
进制数。
#EXT-X-PROGRAM-DATE-TIME:
将一个绝对时间或是日期和一个媒体段中的第一个sample
相关联,只对下一个media URL
有效,格式如下:#EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
例如:#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
#EXT-X-ALLOW-CACHE:
是否允许做cache
,这个可以在 Playlist 文件中任意地方出现,并且最多只出现一次,作用效果是所有的媒体段。格式如下:#EXT-X-ALLOW-CACHE:<YES|NO>
#EXT-X-PLAYLIST-TYPE:
提供关于Playlist
的可变性的信息,这个对整个 Playlist 文件有效,是可选的,格式如下:#EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
-
VOD
,即为点播视频,服务器不能改变Playlist
文件,换句话说就是该视频全部的TS
文件已经被生成好了。 -
EVENT
,就是实时生成m3u8
和TS
文件。服务器不能改变或是删除Playlist
文件中的任何部分,但是可以向该文件中增加新的一行内容。它的索引文件一直处于动态变化中,播放的时候需要不断下载二级index
文件。
#EXT-X-ENDLIST:
表示m3u8
文件的结束,live m3u8
没有该tag
。它可以在Playlist
中任意位置出现,但是只能出现一个,格式如下:#EXT-X-ENDLIST
#EXT-X-MEDIA:
被用来在Playlist
中表示相同内容的不同语种/译文的版本,比如可以通过使用3
个这种tag
表示3
种不同语音的音频,或者用2
个这个tag
表示不同角度的video
。在Playlist
中,这个标签是独立存在的,其格式如下:#EXT-X-MEDIA:<attribute-list>
- 该属性列表中包含:
URL、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、AUTOSELECT
。 -
URL
:如果没有,则表示这个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
。
#EXT-X-STREAM-INF:
指定一个包含多媒体信息的media URL
作为Playlist
,一般做m3u8
的嵌套使用,它只对紧跟后面的URL
有效,格式如下:#EXT-X-STREAM-INF:<attribute-list>
- 常用的属性如下:
-
BANDWIDTH
:带宽,必须有 -
PROGRAM-ID
:该值是一个十进制整数,唯一地标识一个在Playlist
文件范围内的特定的描述。一个Playlist
文件中可能包含多个有相同ID
的此tag
-
CODECS
:指定流的编码类型,不是必须的 -
RESOLUTION
:分辨率 -
AUDIO
:这个值必须和AUDIO
类别的EXT-X-MEDIA
标签中GROUP-ID
属性值相匹配 -
VIDEO
:同上
#EXT-X-DISCONTINUITY:
当遇到该 tag 的时候说明以下属性发生了变化:
file format
number and type of tracks
encoding parameters
encoding sequence
timestamp sequence
#ZEN-TOTAL-DURATION:
表示这个m3u8
所含TS
的总时间长度
3. HLS 之 TS
TS
文件为传输流文件,视频编码主要格式为 H264/MPEG4
,音频为 AAC/MP3
。TS
文件分为三层:
-
TS
层:Transport Stream
,是在 pes 层的基础上加入数据流的识别和传输必须的信息。 -
pes
层:Packet Elemental Stream
,是在音视频数据上加了时间戳等对数据帧的说明信息。 es
层:Elementary Stream
,即音视频数据。
3.1 TS 层:Transport Stream
TS
包大小固定为 188
字节,TS
层分为三个部分:TS header
、adaptation field
、payload
。TS header
固定 4
个字节;adaptation field
可能存在也可能不存在,主要作用是给不足 188
字节的数据做填充;payload
是 pes
数据。
3.1.1 TS header
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
值。 - 音频流/视频流:承载音视频内容。
3.1.2 adaptation field
自适应区的长度要包含传输错误指示符标识的一个字节。pcr
是节目时钟参考,pcr
、dTS
、pTS
都是对同一个系统时钟的采样值,pcr
是递增的,因此可以将其设置为 dTS
值,音频数据不需要 pcr
。如果没有字段,ipad 是可以播放的,但 vlc
无法播放。打包 TS
流时 PAT
和 PMT
表是没有 adaptation field
的,不够的长度直接补 0xff
即可。视频流和音频流都需要加 adaptation field
,通常加在一个帧的第一个 TS
包和最后一个 TS
包里,中间的 TS
包不加。如下图所示:
PAT 格式如下图
PMT 格式如下图
3.2 pes 层:Packet Elemental Stream
pes
层是在每一个视频/音频帧上加入了时间戳等信息,pes
包内容很多,这里只留下最常用的。
pes 层格式如下图:
pes 层内容如下图:
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
内容才可以获取帧类型。
举例说明:
. I P B B B P
读取顺序: 1 2 3 4 5 6
dTS 顺序: 1 2 3 4 5 6
pTS 顺序: 1 5 3 2 4 6
点播视频 dTS 算法:dTS = 初始值 + 90000 / video_frame_rate
,初始值可以随便指定,但是最好不要取 0
,video_frame_rate
就是帧率,比如 23
、30
。
pTS
和 dTS
是以 timestamp
为单位的,1s = 90000 time scale
,一帧就应该是 90000/video_frame_rate
个 timescale
。
用一帧的 timescale
除以采样频率就可以转换为一帧的播放时长。
点播音频 dTS 算法:dTS = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate
,audio_samples_per_frame
这个值与编解码相关,AAC
取值 1024
,mp3
取值 1158
,audio_sample_rate
是采样率,比如 24000
、41000
. AAC
一般解码出来是每声道 1024
个 sample
,也就是说一帧的时长为 1024/sample_rate
秒。所以每一帧时间戳依次0
,1024/sample_rate
, …, 1024*n/sample_rate
秒。
注:直播视频的 dTS
和 pTS
应该直接用直播数据流中的时间,不应该按公式计算。
3.3 es 层:Elementary Stream
ES
层指的就是音视频数据。这里只介绍 h.264
视频和 AAC
音频。
3.3.1 h.264 视频
打包 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
,而且是紧邻的。如下图所示: