HLS 全称是 HTTP Live Streaming, 是一个由 Apple 公司实现的基于 HTTP 的媒体流传输协议. 他跟 DASH 协议的原理很是相似. 经过将整条流切割成一个小的能够经过 HTTP 下载的媒体文件, 而后提供一个配套的媒体列表文件, 提供给客户端, 让客户端顺序地拉取这些媒体文件播放, 来实现看上去是在播放一条流的效果.html
因为传输层协议只须要标准的 HTTP 协议, HLS 能够方便的透过防火墙或者代理服务器, 并且能够很方便的利用 CDN 进行分发加速, 而且客户端实现起来也很方便.ios
HLS 目前普遍地应用于点播和直播领域.git
在 HTML5 页面上使用 HLS 很是简单:github
直接:golang
<video src="example.m3u8" controls></video>
或者:缓存
<video controls> <source src="example.m3u8"></source> </video>
下面, 我将会归纳性地介绍 HLS 协议的方方面面(暂时不包括 AES 加密部分的内容), 配合 HLS 的 RFC 食用效果更佳.服务器
上面是 HLS 总体架构图, 能够看出, 总共有三个部分: Server, CDN, Client.网络
其实, HLS 协议的主要内容是关于 M3U8 这个文本协议的, 其实生成与解析都很是简单. 为了更加直接地说明这一点, 我下面举两个简单的例子:session
简单的 Media Playlist:架构
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:8 #EXT-X-MEDIA-SEQUENCE:2680 #EXTINF:7.975, https://priv.example.com/fileSequence2680.ts #EXTINF:7.941, https://priv.example.com/fileSequence2681.ts #EXTINF:7.975, https://priv.example.com/fileSequence2682.ts
包含多种比特率的 Master Playlist:
#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
HLS 经过 URI(RFC3986) 指向的一个 Playlist 来表示一个媒体流.
一个 Playlist 能够是一个 Media Playlist 或者 Master Playlist, 使用 UTF-8 编码的文本文件, 包含一些 URI 跟描述性的 tags.
一个 Media Playlist 包含一个 Media Segments 列表,当顺序播放时, 能播放整个完整的流.
要想播放这个 Playlist, 客户端须要首先下载他, 而后播放里面的每个 Media Segment.
更加复杂的状况是, Playlist 是一个 Master Playlist, 包含一个 Variant Stream 集合, 一般每一个 Variant Stream 里面是同一个流的多个不一样版本(如: 分辨率, 码率不一样).
每个 Media Segment 经过一个 URI 指定, 可能包含一个 byte range.
每个 Media Segment 的 duration 经过 EXTINF
tag 指定.
每个 Media Segment 有一个惟一的整数 Media Segment Number.
有些媒体格式须要一个 format-specific sequence 来初始化一个 parser, 在 Media Segment 被 parse 以前. 这个字段叫作 Media Initialization Section, 经过 EXT-X-MAP
tag 来指定.
即最多见的 TS 文件.
RFC: ISO_13818.
Media Initialization Section: PAT(Program Association Table) 跟 PMT(Program Map Table).
每一个 TS segment 必须值含一个 MPEG-2 Program.
每个 TS segment 包含一个 PAT 和 PMT, 最好在 segment 的开始处, 或者经过一个 EXT-X-MAP
tag 来指定.
即常提到的 fMP4.
RFC: ISOBMFF.
Media Initialization Section: ftyp
box(包含一个高于 ios6
的 brand), ftyp
box 必须紧跟在 moov
box 以后. moov
box 必须包含一个 trak
box(对于每一个 fMP4 segment 里面的 traf
box, 包含匹配的 track_ID
). 每一个 trak
box 应该包含一个 sample table, 可是他的 sample count 必须为 0. mvhd
box 跟 tkhd
的 duration 必须为 0. mvex
box 必须跟在上一个 trak
box 后面.
不像普通的 MP4 文件包含一个 moov
box(包含 sample tables) 和一个 mdat
box(包含对应的 samples), 一个 fMP4 包含一个 moof
box (包含 sample table 的子集), 和一个 mdat
box(包含对应的 samples).
在每个 fMP4 segment 里面, 每个 traf
box 必须包含一个 tfdt
box, fMP4 segment 必须使用 movie-fragment relative addressing. fMP4 segments 绝对不能使用外部的 data references.
每个 fMP4 segment 必须有一个 EXT-X-MAP
tag.
一个 Packed Audio Segment 包含编码的 audio samples 和 ID3 tags. 简单的打包到一块儿, 包含最小的 framing, 而且没有 per-sample timestamp.
支持的 Packed Audio: AAC with ADTS framing [ISO_13818_7], MP3 [ISO_13818_3], AC-3 [AC_3], Enhanced AC-3 [AC_3].
一个 Packed Audio Segment 没有 Media Initialization Section.
每个 Packed Audio Segment 必须在他的第一个 sample 指定 timestamp 经过一个 ID3 PRIV tag.
ID3 PRIV owner identifier 必须是 com.apple.streaming.transportStreamTimestamp
.
ID3 payload 必须是一个 33-bit MPEG-2 Program Elementary Stream timestamp 的大端 eight-octet number, 高 31 为设置为 0.
一个 WebVTT Segment 是一个 WebVTT 文件的一个 section, WebVTT Segment 包含 subtitles.
Media Initialization Section: WebVTT header.
每个 WebVTT Segment 必须有以一个 WebVTT header 开始, 或者有一个 EXT-X-MAP
tag 来指定.
每个 WebVTT header 应该有一个 X-TIMESTAMP-MAP
来保证音视频同步.
Playlist 文件的格式是起源于 M3U, 而且继承两个 tag: EXTM3U
和 EXTINF
下面的 tags 经过 BNF-style
语法来指定.
一个 Playlist 文件必须经过 URI(.m3u8 或 m3u) 或者 HTTP Content-Type 来识别(application/vnd.apple.mpegurl 或 audio/mpegurl).
换行符能够用 \n
或者 \r\n
.
以 #
开头的是 tag 或者注释, 以 #EXT
开头的是 tag, 其他的为注释, 在解析时应该忽略.
Playlist 里面的 URI 能够用绝对地址或者相对地址, 若是使用相对地址, 那么是相对于 Playlist 文件的地址.
有的 tags 的值是 Attribute Lists.
一个 Attribute List 是一个用逗号分隔的 attribute/value 对列表.
格式为: AttributeName=AttributeValue
.
Basic Tags 能够用在 Media Playlist 和 Master Playlist 里面.
EXTM3U
: 必须在文件的第一行, 标识是一个 Extended M3U Playlist 文件.
EXT-X-VERSION
: 表示 Playlist 兼容的版本.
每个 Media Segment 经过一系列的 Media Segment tags 跟一个 URI 来指定. 有的 Media Segment tags 只应用与下一个 segment, 有的则是应用全部下面的 segments. 一个 Media Segment tag 只能出如今 Media Playlist 里面.
EXTINF
: 用于指定 Media Segment 的 duration
EXT-X-BYTERANGE
: 用于指定 URI 的 sub-range
EXT-X-DISCONTINUITY
: 表示不连续.
EXT-X-KEY
: 表示 Media Segment 已加密, 该值用于解密.
EXT-X-MAP
: 用于指定 Media Initialization Section.
EXT-X-PROGRAM-DATE-TIME
: 和 Media Segment 的第一个 sample 一块儿来肯定时间戳.
EXT-X-DATERANGE
: 将一个时间范围和一组属性键值对结合到一块儿.
Media Playlist tags 描述 Media Playlist 的全局参数. 一样地, Media Playlist tags 只能出如今 Media Playlist 里面.
EXT-X-TARGETDURATION
: 用于指定最大的 Media Segment duration.
EXT-X-MEDIA-SEQUENCE
: 用于指定第一个 Media Segment 的 Media Sequence Number.
EXT-X-DISCONTINUITY-SEQUENCE
: 用于不一样 Variant Stream 之间同步.
EXT-X-ENDLIST
: 表示结束.
EXT-X-PLAYLIST-TYPE
: 可选, 指定整个 Playlist 的类型.
EXT-X-I-FRAMES-ONLY
: 表示每一个 Media Segment 描述一个单一的 I-frame.
Master Playlist tags 定义 Variant Streams, Renditions 和 其余显示的全局参数. Master Playlist tags 只能出如今 Master Playlist 中.
EXT-X-MEDIA
: 用于关联同一个内容的多个 Media Playlist 的多种 renditions.
EXT-X-STREAM-INF
: 用于指定一个 Variant Stream.
EXT-X-I-FRAME-STREAM-INF
: 用于指定一个 Media Playlist 包含媒体的 I-frames.
EXT-X-SESSION-DATA
: 存放一些 session 数据.
EXT-X-SESSION-KEY
: 用于解密.
这里的 tags 能够出如今 Media Playlist 或者 Master Playlist 中. 可是若是同时出如今同一个 Master Playlist 和 Media Playlist 中时, 必须为相同值.
EXT-X-INDEPENDENT-SEGMENTS
: 表示每一个 Media Segment 能够独立解码.
EXT-X-START
: 标识一个优选的点来播放这个 Playlist.
如下流程仅供参考, 其实不一样的播放器客户端以及服务器端的拉取规则都有不少细节差别.
将媒体源切片成 Media Segment, 应该优先从能够高效解码的时间点来进行切片(如: I-frame).
为每个 Media Segment 生成 URI.
Server 须要支持 “gzip” 方式压缩文本内容.
建立一个 Media Playlist 索引文件, EXT-X-VERSION
不要高于他须要的版本, 来提供更好的兼容性.
Server 不能随便修改 Media Playlist, 除了 Append 文本到文件末尾, 按顺序移除 Media Segment URIs, 增加 EXT-X-MEDIA-SEQUENCE
和 EXT-X-DISCONTINUITY-SEQUENCE
, 添加 EXT-X-ENDLIST
到文件尾.
在最后添加 EXT-X-ENDLIST
tag, 来减小 Client reload Playlist 的次数.
注意点播与直播服务器不一样的地方是, 直播的 m3u8 文件会不断更新, 而点播的 m3u8 文件是不会变的, 只须要客户端在开始时请求一次便可.
客户端经过 URI 获取 Playlist. 若是是 Master Playlist, 客户端能够选择一个 Variant Stream 来播放.
客户端检查 EXT-X-VERSION
版本是否知足.
客户端应该忽略不可识别的 tags, 忽略不可识别的属性键值对.
加载 Media Playlist file.
播放 Media Playlist file.
重加载 Media Playlist file.
决定下一次要加载的 Media Segment.
客户端支持简单, 只须要支持 HTTP 请求便可, HTTP 协议无状态, 只须要按顺序下载媒体片断便可.
使用 HTTP 协议网络兼容性好, HTTP 数据包也能够方便地经过防火墙或者代理服务器, CDN 支持良好.
Apple 的全系列产品支持, 因为 HLS 是苹果提出的, 因此在 Apple 的全系列产品包括 iphone, ipad, safari 都不须要安装任何插件就能够原生支持播放 HLS, 如今, Android 也加入了对 HLS 的支持.
自带多码率自适应, Apple 在提出 HLS 时, 就已经考虑了码流自适应的问题.
相比 RTMP 这类长链接协议, 延时较高, 难以用到互动直播场景.
对于点播服务来讲, 因为 TS 切片一般较小, 海量碎片在文件分发, 一致性缓存, 存储等方面都有较大挑战.
因为客户端每次请求 TS 或 M3U8 有可能都是一个新的链接请求, 因此, 咱们没法有效的标识客户端, 一旦出现问题, 基本没法有效的定位问题, 因此, 通常工业级的服务器都会对传统的 HLS 作一些改进.
这里主要介绍网宿的 Variant HLS 与又拍云的 HLS+.
首先, 咱们能够下载一条网宿的 M3U8 文件:
wget http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8
而后, 打开下载获得的 playlist 文件:
#EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=781000 http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8?wsSession=0105cb4e8fe63bccab511a4a-149017212774715&wsIPSercert=b80d38c068c9e3634a7ebb2f2bbf9b89&wsMonitor=-1
能够看出这是一个 Master Playlist, 里面嵌套了一层 M3U8, 同时能够看出网宿采用 wsSession
来标识一条播放链接.
首先, 咱们能够下载一条又拍云的 M3U8 文件:
wget http://uplive.b0.upaiyun.com/live/loading.m3u8
而后, 打开下载获得的 playlist 文件:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-ALLOW-CACHE:YES #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-TARGETDURATION:1 #EXTINF:0.998, no desc http://183.158.35.12:8080/uplive.b0.upaiyun.com/live/loading-0.ts?shp_uuid=e4989f34fcab282e21ef1fd2980284cb&shp_ts=1490172420851&shp_cid=17906&shp_pid=3370578&shp_sip0=127.0.0.1&shp_sip1=183.158.35.12&domain=uplive.b0.upaiyun.com&shp_seqno=0
能够看出又拍云的 HLS+ 也支持这种 Variant HLS 方式来标识一条 HLS 链接, 能够看出, 又拍云使用 uuid 来表示一条 HLS 链接.
首先, 以 HTTP 302 方式来请求播放地址.
❯ curl -v http://uplive.b0.upaiyun.com/live/loading.m3u8\?shp_identify\=302 -o playlist % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 183.158.35.59... * TCP_NODELAY set * Connected to uplive.b0.upaiyun.com (183.158.35.59) port 80 (#0) > GET /live/loading.m3u8?shp_identify=302 HTTP/1.1 > Host: uplive.b0.upaiyun.com > User-Agent: curl/7.51.0 > Accept: */* > < HTTP/1.1 302 Found < Server: marco/0.26 < Date: Wed, 22 Mar 2017 08:54:11 GMT < Content-Type: text/plain; charset=utf-8 < Content-Length: 259 < Connection: keep-alive < Access-Control-Allow-Methods: GET < Access-Control-Allow-Origin: * < Location: http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_identify=302 < { [259 bytes data] * Curl_http_done: called premature == 0 100 259 100 259 0 0 4813 0 --:--:-- --:--:-- --:--:-- 4886 * Connection #0 to host uplive.b0.upaiyun.com left intact
打开 playlist 内容:
Redirect to http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_identify=302
在跳转以后的地址存放真正的 playlist, 同时, 也可以将 uuid 加入到了链接上.
总地来讲, 无论经过哪一种方式, 最终咱们都能经过一个惟一的 id 来标识一条流, 这样在排查问题时就能够根据这个 id 来定位播放过程当中的问题.
HLS 理论延时 = 1 个切片的时长 + 0-1个 td (td 是 EXT-X-TARGETDURATION, 可简单理解为播放器取片的间隔时间) + 0-n 个启动切片(苹果官方建议是请求到 3 个片以后才开始播放) + 播放器最开始请求的片的网络延时(网络链接耗时)
为了追求低延时效果, 能够将切片切的更小, 取片间隔作的更小, 播放器未取到 3 个片就启动播放. 可是, 这些优化方式都会增长 HLS 不稳定和出现错误的风险.
HLS downloader: 读取一个 m3u8 URL, 下载为 TS 文件.
流媒体协议—HLS&version=12020010&nettype=WIFI&fontScale=100&pass_ticket=weWVu89q646SDwy9dCFmksdsH21wbzfJgMDMyh4p88A%3D)