流媒体协议之 HLS

流媒体(Streaming media)是指将一连串的多媒体资料压缩后,经过互联网分段发送资料,在互联网上即时传输影音以供观赏的一种技术与过程,此技术使得资料数据包得以像流水一样发送,如果不使用此技术,就必须在使用前下载整个媒体文件。实时流媒体是指互联网内容的实时传输,就像电视直播通过电视信号在电波上播放内容一样。互联网流媒体直播需要一种形式的源媒体(如摄像机、音频接口、屏幕捕捉软件)、将内容数字化的编码器、媒体发布者和内容传输网络来分发和传递内容。

HLS 简介

HTTP Live Streaming,缩写为 HLS,是由苹果公司提出基于 HTTP 的流媒体网络传输协议。是苹果公司 QuickTime X 和 iPhone 软件系统的一部分。它的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的扩展 M3U (m3u8) 播放列表文件,用于寻找可用的媒体流。
—— https://zh.wikipedia.org/wiki/HTTP_Live_Streaming

上面是维基百科对 HLS 的解释,其实不难理解,当我们在爱奇艺或者腾讯视频中追剧、看电影或者刷 B 站,都是随时可以调整进度的,有时候一个视频特别大,比如一个高清的电影要是下载下来得十多个 GB 甚至高达上百 GB,但是在观看视频的时候却可以手动拖动进度条调整播放进度,很显然不可能把整个视频都下载下来再进行调整,所以在这种场景下,服务器分段发送资料就显得尤为重要,只发送用户当前即将要观看的一小段视频,而不是传输整个视频。

HLS 支持以下内容:

  • 直播和预录内容(视频点播或 VOD
  • 具有不同码率的多个备用流
  • 根据网络带宽变化对流进行智能切换
  • 媒体加密和用户身份验证

下图显示了 HTTP Live Stream 的组件:

所以用比较通俗的话来讲,HLS 的基本实现原理为将一个大的媒体文件进行分片,将该分片文件资源路径记录于 m3u8 文件内,所以 m3u8 文件是记录索引的纯文本文件,打开它时播放软件并不是播放它,而是根据它的索引找到对应的音视频文件的网络地址进行在线播放,而且 m3u8 文件其中附带一些额外描述(比如该资源的多带宽信息・・・)用于提供给客户端,客户端依据该 m3u8 文件即可获取对应的媒体资源,进行播放,如下图所示:

我们观看视频的时候,往往可以根据网速选择 360P、720P、1080P 等清晰度,其实切换清晰度的本质就是切换不同的流而已,对于爱奇艺这类视频网站往往也会准备不同码率的媒体流,用于给用户在网络情况较差的时候也能有流畅的观看体验:

m3u8 文件内容

m3u8 文件实质是一个视频点播列表,或者实时播放列表、或者是一个主播放列表,或者活动播放列表。无论是哪种播放列表,其内部文字使用的都是 UTF-8 编码。在看 m3u8 的文件内容的时候需要注意以下几点:

1、m3u8 文件都是 UTF-8 编码。

2、m3u8 文件的每一行要么是一个 URL,要么是空行,要么就是以 # 开头的字符串。不能出现空白字符,除了显示声明的元素。m3u8 文件中以 # 开头的字符串要么是注释,要么就是标签。标签以 #EXT 开头,大小写敏感。

点播列表

当 m3u8 文件作为点播列表时,其内部信息记录的是一系列媒体片段资源的 URL,客户端只需按顺序下载这些片段资源,依次进行播放即可,下面的代码是视频点播播放列表的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://example.com/movie1/fileSequenceA.ts
#EXTINF:10.0,
http://example.com/movie1/fileSequenceB.ts
#EXTINF:10.0,
http://example.com/movie1/fileSequenceC.ts
#EXTINF:9.0,
http://example.com/movie1/fileSequenceD.ts
#EXT-X-ENDLIST

以下是视频点播播放列表示例中使用的标签:

EXTM3U: 表明该文件是一个扩展的 M3U 文件,即 m3u8 文件,每个 M3U 文件必须将该标签放置在第一行。

EXT-X-PLAYLIST-TYPE: 提供适用于整个播放列表文件的可变性信息。该标签的值可能是 EVENTVOD。如果标签存在并且值为 EVENT,则服务器不得更改或删除播放列表文件的任何部分(尽管它可以在其后添加行)。如果标签存在并且值为 VOD,则播放列表文件不得更改。

EXT-X-TARGETDURATION: 指定每个视频段最大的时长(以秒为单位)

EXT-X-VERSION: 表示播放列表文件的兼容版本。播放列表媒体及其服务器必须遵守定义该协议版本的 HTTP Live Streaming 规范的 IETF Internet-Draft 最新版本的所有规定。

EXT-X-MEDIA-SEQUENCE: 指示出现在播放列表文件中的第一个 URL 的序列号。播放列表中的每个媒体文件 URL 都有一个唯一的整数序列号。URL 的序列号比其前面的 URL 的序列号高 1,媒体序号与文件名无关。

EXTINF: 记录标记,描述由紧随其后的 URL 标识的媒体文件。每个媒体文件 URL 必须带有一个 EXTINF 标记。该标签包含一个 duration 属性,该属性是一个整数或浮点数,以十进制位置表示法指定视频段的时长(以秒为单位)。该值必须小于或等于 EXT-X-TARGETDURATION 中规定的时间。

重点:建议始终使用浮点型指定时长,这可以让客户端在定位流时,减少四舍五入错误。但是如果兼容版本号 EXT-X-VERSION 小于 3,那么必须使用整型。

EXT-X-ENDLIST:表示不会再有媒体文件添加到播放列表文件,也就是 m3u8 文件的结尾。点播列表类型的 m3u8 通常会带有 EXT-X-ENDLIST 标签,因为其视频片段不会改变;而直播类型的 m3u8 初始化时一般不会有 EXT-X-ENDLIST 标签,暗示有新的文件会添加到播放列表末尾,因此也需要客户端定时获取该 m3u8 文件,以获取新的媒体片段资源,直到访问到 EXT-X-ENDLIST 标签才停止。

上面的播放列表示例为媒体文件播放列表条目使用了完整路径名,虽然这是可以的,但使用相对路径名更好一些。因为相对路径名比绝对路径名更容易移植,并且相对于播放列表文件的 URL,对单个播放列表条目使用完整路径名通常会比使用相对路径名产生更多文本。下面是与上面相同的播放列表的相对路径表示的 m3u8 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
fileSequenceA.ts
#EXTINF:10.0,
fileSequenceB.ts
#EXTINF:10.0,
fileSequenceC.ts
#EXTINF:9.0,
fileSequenceD.ts
#EXT-X-ENDLIST

实时播放列表

实时播放列表通常用于直播场景,通过在创建新媒体文件并在其可用时从文件中删除媒体 URI 来更新索引文件,这个时候 m3u8 文件中就不存在 EXT-X-ENDLIST 标签,新的视频媒体文件就可以被添加到 m3u8 文件中,下面是一个实时播放列表(直播)的 m3u8 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:10.0,
fileSequence1.ts
#EXTINF:10.0,
fileSequence2.ts
#EXTINF:10.0,
fileSequence3.ts
#EXTINF:10.0,
fileSequence4.ts
#EXTINF:10.0,
fileSequence5.ts

这里面在点播列表里出现的标签就不再介绍了,如 #EXTM3U#EXT-X-TARGETDURATION#EXT-X-VERSION#EXTINF#EXT-X-MEDIA-SEQUENCE 等标签。

对于 EXT-X-MEDIA-SEQUENCE 标签需要注意一点:从播放列表文件中删除的每个媒体 URI,标记值必须增加 1。媒体 URI 必须按照它们在播放列表中出现的顺序从播放列表文件中删除。更新的索引文件将移动窗口呈现为连续的流。看下面两个示例就明白了,从上面的 m3u8 更新到下面的 m3u8 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:2
#EXTINF:10.0,
fileSequence2.ts
#EXTINF:10.0,
fileSequence3.ts
#EXTINF:10.00,
fileSequence4.ts
#EXTINF:10.00,
fileSequence5.ts
#EXTINF:10.0,
fileSequence6.ts

随着添加新媒体 URI,播放列表将继续更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:4
#EXTINF:10.00,
fileSequence4.ts
#EXTINF:10.00,
fileSequence5.ts
#EXTINF:10.0,
fileSequence6.ts,
#EXTINF:10.0,
fileSequence7.ts,
#EXTINF:10.0,
fileSequence8.ts,
#EXTINF:10.0,
fileSequence9.ts

活动播放列表

#EXT-X-PLAYLIST-TYPE 的值为 EVENT 的时候,那么就表示这是一个活动播放列表。当允许用户搜索事件中的任何点(例如音乐会或体育赛事)时,通常会使用事件播放列表。当 #EXT-X-PLAYLIST-TYPE 的值为 EVENT 的时候,就无法从播放列表中删除任何内容;只能在文件末尾附加新的句段。新的段将添加到文件的末尾,直到事件结束为止,此时将添加 EXT-X-ENDLIST 标签。以下示例显示了用新的媒体 URI 更新的播放列表,并且该事件已结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#EXTM3U
#EXT-X-PLAYLIST-TYPE:EVENT
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
fileSequence0.ts
#EXTINF:10.0,
fileSequence1.ts
#EXTINF:10.0,
fileSequence2.ts
#EXTINF:10.0,
fileSequence3.ts
#EXTINF:10.0,
fileSequence4.ts

// List of files between 4 and 120 go here.

#EXTINF:10.0,
fileSequence120.ts
#EXTINF:10.0,
fileSequence121.ts
#EXT-X-ENDLIST

主播放列表

提供多个播放列表文件以提供相同内容的不同码率,主播放列表描述了内容的所有可用的不同码率的媒体源。每个媒体源都是具有特定码率的版本,并包含在单独的播放列表中。客户端根据测得的网速切换到最合适的媒体源。调整客户端的播放器以最大程度地减少播放停顿,从而为用户提供最佳的流媒体体验。

主播放列表不会重新读取。客户端解析完主播放列表后,便会假设不同码率的媒体源未发生变化。一旦客户端在各个媒体源播放列表之一上看到 EXT-X-ENDLIST 标签,流就会结束。以下示例显示了一个主播放列表,它定义了五个不同的媒体源:

1
2
3
4
5
6
7
8
9
10
11
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/lo_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/hi_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/high/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5"
http://example.com/audio/index.m3u8

同样的,在之前出现的标签在这里不在赘述。

EXT-X-STREAM-INF: 该属性指定了一个媒体源,该属性值提供了该备份源的相关信息。

AVERAGE-BANDWIDTH:(可选,但建议使用)表示该媒体流的平均速率,单位:每秒传输的比特数。
BANDWIDTH:(必需)表示该媒体流的最大速率,单位:每秒传输的比特数。
FRAME-RATE:(可选,但建议使用)一个浮点值,它描述该媒体流中的最大帧率。
HDCP-LEVEL:(可选)指示使用的加密类型。有效值为 TYPE-0NONE。输出受 HDCP 保护的话需要设置 TYPE-0,否则流无法播放。
RESOLUTION:(可选,但建议使用)可选的显示尺寸,以像素为单位,以该尺寸显示播放列表中的所有视频。任何包含视频的流都应包含此参数。
VIDEO-RANGE:(需要,取决于编码)值为 SDRPQ 的字符串。如果未指定传输特性代码 1、16 或 18,则必须省略此参数。
CODECS:(可选,但建议使用)带引号的字符串,其中包含用逗号分隔的格式列表,其中每种格式均指定播放列表文件中媒体片段中存在的媒体样本类型。有效格式标识符是 RFC 6381 [RFC6381] 定义的 ISO 文件格式名称空间中的标识符。

尽管 CODECS 参数是可选的,但每个 EXT-X-STREAM-INF 标记都应包含该属性。 此属性提供解码特定流所需的编解码器的完整列表。 它允许客户端区分仅是音频的变体和同时具有音频和视频的媒体源,则客户端在切换流时会利用此信息来提供更好的用户体验。

参考资料

1、Example Playlists for HTTP Live Streaming

2、HTTP Live Streaming

2、https://zh.wikipedia.org/wiki/HTTP_Live_Streaming