HLS(HTTP Live Streaming)全称为http直播流,是一个由Apple公司提出的基于HTTP的媒体流传输协议,用来实现实时音视频流的传输。
服务器将码流切割成一个个小的(通常是10秒一个)可以通过HTTP下载的媒体分片(segment),并提供一个配套的媒体列表文件(M3U8文件);客户端下载媒体列表文件,并按照规范解析文件,获取每个媒体分片的地址并按顺序下载,从而实现看起来播放一个码流的效果。
这其中,HTTP是网络协议,媒体播放列表为Apple官方定义的M3U8文件,媒体文件格式为TS或fMP4。
M3U8文件,HLS的媒体描述文件,UTF-8编码。
可以是一个主列表(Master Playlist),也可以是一个媒体列表(Media Playlist)。
如下图为列表和媒体文件的关系,共两级列表。
用来表示多个不同码率码流的媒体播放列表url及相关信息。
如下:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000,CODECS="avc1.640028,mp4a.40.2"
http: //example.com/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000,CODECS="avc1.640028,mp4a.40.2"
http: //example.com/mid.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000,CODECS="avc1.640028,mp4a.40.2"
http: //example.com/hi.m3u8
TAG以及形式 | 意义 |
---|---|
#EXTM3U | 表示该文件一个拓展的M3U8文件,必须放在第一行。 |
#EXT-X-STREAM-INF: | 用于指定一个Variants Stream,指定一个包含多媒体信息的Media URI作为playlist 一般做M3U8的嵌套使用,它只对紧跟后面的URI有效。 attribute-list有以下参数: · BANDWIDTH: 要求的带宽,该参数必须有,实现码率自适应的关键参数。 · PROGRAM-ID: 十进制整数,唯一地标识playlist文件范围内的特定描述。 · CODECS: 指定视频和音频编码类型,非必须的。 · RESOLUTION: 分辨率,非必须的。 |
用来表示同一码率码流的一系列切片url及相关信息。
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD // 播放列表类型
#EXT-X-VERSION:3 // 版本信息
#EXT-X-TARGETDURATION:11 // 每个分片的目标时长
#EXT-X-MEDIA-SEQUENCE:0 // 该文件第一个分片的序号
#EXTINF:10.922578, // 分片实际时长
test000.ts // 第一个分片文件,序号为0
#EXTINF:9.929578, // 第二个分片实际时长
test001.ts // 第二个分片文件,序号为1
...
#EXT-X-ENDLIST // 该列表结束标志
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:12616381
#EXT-X-KEY:METHOD=AES-128,URI="3M_key",IV=0x4F1E7B58678D094361DF3F0FFDEDD333
#EXTINF:10,
720p_aes_0.ts
#EXT-X-KEY:METHOD=AES-128,URI="3M_key",IV=0x4F1E7B58678D094361DF3F0FFDEDD333
#EXTINF:10,
720p_aes_1.ts
#EXT-X-ENDLIST
EXT-X-PLAYLIST-TYPE为EVENT,而且列表末尾没有EXT-X-ENDLIST tag!
#EXTM3U
#EXT-X-PLAYLIST-TYPE:EVENT
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.922578,
test000.ts
#EXTINF:9.929578,
test001.ts
...
TAG以及形式 | 意义 |
---|---|
#EXTM3U | 一个拓展的M3U8文件,必须放在第一行,以此TAG来说明该文件为M3U8文件 |
#EXT-X-PLAYLIST-TYPE | 播放列表类型。有两个取值:VOD --> 点播;EVENT --> 直播。 |
#EXT-X-VERSION: | 协议的版本号,n为3,HLS v3;n为4,HLS v4 |
#EXT-X-TARGETDURATION: | 该列表中每个切片的最大时长 |
#EXT-X-MEDIA-SEQUENCE: | 该文件第一个切片的序号。如没有,则默认为0 |
#EXTINF:duration(float),title | 一个切片的实际时长和文件名,文件名与url拼接就可以得到切片url。 |
#EXT-X-KEY | 解密信息,有三个参数: • METHOD:加密算法,hls协议规定三个枚举值:NONE, AES-128(CBC), SAMPLE-AES • URI:解密key相对资源路径 • IV:解密偏移量 |
#EXT-X-ENDLIST | M3U8文件结束符,直播场景没有这个tag |
播放列表中有以下几种不同的URL生成规则。
直接给出URL
也就是绝对路径,拿到之后直接用来请求既可。如:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXTINF:9.009,
http ://media.example.com/first.ts
#EXTINF:9.009,
http ://media.example.com/second.ts
#EXTINF:3.003,
http ://media.example.com/third.ts
#EXT-X-ENDLIST
那我们直接请求http://media.example.com/first.ts,就可以拿到切片了
单文件名相对路径
只给出文件名,说明资源文件与m3u8文件放在一个目录,需要进行url拼接。如:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXTINF:9.009,
first.ts
#EXTINF:9.009,
second.ts
#EXTINF:3.003,
third.ts
#EXT-X-ENDLIST
拼接规则:将请求该m3u8文件的url去掉m3u8文件名后,再将切片文件名拼接上去,就是正确url了。
如该m3u8的url为:http://media.example.com/index.m3u8
那么每个切片url前缀就是:http://media.example.com/
所以第一个切片url就是:http://media.example.com/first.ts
带文件路径的相对路径
给出资源文件的路径,说明资源文件与m3u8文件不放在同一个目录,需要进行url拼接,如:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
1000k/hls/index.m3u8
或者:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
/1000k/hls/index.m3u8
拼接规则同2)。
如该m3u8的url为:http://media.example.com/index.m3u8
那么每个媒体播放列表url前缀就是:http://media.example.com/
所以该媒体播放列表的url就是:http://media.example.com/1000k/hls/index.m3u8
还有一种:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
/15467_73a719b2/1000k/hls/index.m3u8
而请求该m3u8的url是:
http://media.example.com/123/15467_73a719b2/index.m3u8
可以看到文件路径和请求url有重复的地方,这种情况需要去重。
按2)的拼接规则,那么就是:
http://media.example.com/123/15467_73a719b2/15467_73a719b2/1000k/hls/index.m3u8
显然不对,需要去重,所以应该是:
http://media.example.com/123/15467_73a719b2/1000k/hls/index.m3u8
双斜杆相对位置:
双斜杆后面一般直接就是域名,如:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
//douban.donghongzuida.com/20210109/15467_73a719b2/1000k/hls/index.m3u8
拼接规则:直接添加请求该m3u8列表的协议。
如该m3u8为http://media.example.com/index.m3u8,
那么就是:http://douban.donghongzuida.com/20210109/15467_73a719b2/1000k/hls/index.m3u8
如该m3u8为https://media.example.com/index.m3u8,
那么就是:https://douban.donghongzuida.com/20210109/15467_73a719b2/1000k/hls/index.m3u8
HLS高版本中支持了ISO profile,也就是支持fragment MP4,为此HLS新增了EXT-X-MAP这个tag来标识。
如:
#EXTM3U
#EXT-X-TARGETDURATION:15
#EXT-X-ALLOW-CACHE:YES
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-VERSION:6
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-MAP:URI="init-v1-a1.mp4"
#EXTINF:13.000,
seg-1-v1-a1.m4s
#EXTINF:12.000,
seg-2-v1-a1.m4s
#EXT-X-ENDLIST
fragment mp4的moov和mdat是分开的,在hls中,承载moov的切片叫做init segment;而承载mdat的切片叫做media segment。
而为了区分两者,就有了EXT-X-MAP这个tag,用以表示init segment的url。meidia segment中可能有audio和video fragement。
而对于TS profile,也可以使用EXT-X-MAP,其init segment就是PAT和PMT的packet,这可以提高ts的负载率。
低延时HLS的实现
1>第三方公司提出的LHLS方案
HTTP 1.1 分块传输(chunk)
参考链接:https://www.theoplayer.com/low-latency-hls-streaming
2> apple官方提出的low-latency HLS方案
最大的改进是将最新的那个切片分解成更小的切片,也就是请求的数据颗粒度更小了,可访问切片的生成时间减小,时延减小。
为此,特别新增了#EXT-X-PART这个tag.
如:
#EXTINF:6.003,
LLHLS_Video1_67750710.mp4
#EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:20:29.482Z
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.0.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.1.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.2.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.3.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.4.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.5.mp4",INDEPENDENT=YES
可见LLHLS_Video1_67750710.mp4这个切片被切分成更小的切片,这样就不用等待该切片整个生成之后,再去访问了。
搭建HLS服务器。
使用nginx或Apache2搭建Http server即可…
如何在hls播放过程中插播广告?
同一个广告视频应当可以插入到任意hls片源的任意切片前面,因此无法保证广告视频的编码格式和码率等信息和HLS片源是一致的,这会影响客户端播放器的正常播放,因此有必要告知播放器广告插入在hls片源插入的时间点以及广告的metadata等信息。HLS专门设置了#EXT-X-DISCONTIUITY这个TAG来表明前面切片和后面切片是完全不同的,需要播放器做出对应的处理。播放器检测的这个tag之后就需要重新初始化解码器,因为切片的编码类型发送变化。
例子:
一个普通的M3u8文件:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.922578,
test000.ts
#EXTINF:9.929578,
test001.ts
...
如果想在原视频开头前插入广告:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
ad0.ts
#EXTINF:8.0,
ad1.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.922578,
test000.ts
#EXTINF:9.929578,
test001.ts
...
如果想在原视频中间插入广告:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.922578,
test000.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.0,
ad0.ts
#EXTINF:8.0,
ad1.ts
#EXT-X-DISCONTINUITY
#EXTINF:9.929578,
test001.ts
...
码率切换策略:
综合考虑网络带宽和buffer水位。
当前带宽小于当前播放码率且buffer中的数据也不充足(不足够一个切片)的时候,降到对应的码率。
当前带宽大于当前播放码率且buffer中的数据充足(大于一个切片)时,升到对应的码率。
怎么计算当前buffer中的切片数量?
通过buffer中数据量和上一个已下载的切片平均比特率来计算当前buffer中是否足够一个切片的时长(targetduration)
直播时,playlist请求的间隔是多少?
答:一般是间隔一个切片的时长更新一次播放列表,以减少服务器负担。
直播时,第一个播放片段的选择?
答:HLS协议规定直播时,客户端应该选择距离播放列表中最后一个切片大于两个目标时长的切片作为首个切片进行播放。
First_req_segment_sequence <= last_segment_in_list_sequence – 3。
为了兼顾实时性和流畅度,一般而言,客户端应该从m3u8文件中倒数第三个或倒数第四个segment开始播放。
如何判断播放源是直播还是点播?
答:有两种方法进行判断
1>判断媒体播放列表是否存在#EXT-X-ENDLIST标签,有,点播;无,直播。
2> 判断#EXT-X-PLAYLIST-TYPE的类型:
#EXT-X-PLAYLIST-TYPE: VOD // 点播源
#EXT-X-PLAYLIST-TYPE: EVENT // 直播源
如何判断某个M3U8文件是主播放列表还是媒体播放列表?
答:通过列表中是否存在EXT-X-STREAM-INF,存在,主m3u8列表;不存在,媒体播放列表。
直播时,如何判断当前请求的m3u8文件相比上一次请求的m3u8文件,是否有新的segment url加入?
答:一个效率比较高的方法是比较上一次请求的m3u8文件和当前m3u8文件的md5码是否一样。一样,无更新,可跳过解析;不一样,有更新。
如果m3u8列表请求不到,downloader会怎么处理?
答:retry两次,每次retry等待时长为10秒,如果还请求不到的话,就返回error给player。
为什么HLS的实时性差?
答:两个原因:
1>采用切片式的媒体形式,颗粒度大,切片需要编码完成才可以被请求,一个切片的时延不可避免。
2>采用http短连接进行数据请求,需要不断跟服务器请求响应,每次请求需要耗费一个RTT时间,交互时间长。