HLS 協(xié)議詳解
HLS 概述
HLS 全稱是 HTTP Live Streaming, 是一個(gè)由 Apple 公司實(shí)現(xiàn)的基于 HTTP 的媒體流傳輸協(xié)議. 他跟 DASH 協(xié)議的原理非常類似. 通過(guò)將整條流切割成一個(gè)小的可以通過(guò) HTTP 下載的媒體文件, 然后提供一個(gè)配套的媒體列表文件, 提供給客戶端, 讓客戶端順序地拉取這些媒體文件播放, 來(lái)實(shí)現(xiàn)看上去是在播放一條流的效果.
由于傳輸層協(xié)議只需要標(biāo)準(zhǔn)的 HTTP 協(xié)議, HLS 可以方便的透過(guò)防火墻或者代理服務(wù)器, 而且可以很方便的利用 CDN 進(jìn)行分發(fā)加速, 并且客戶端實(shí)現(xiàn)起來(lái)也很方便.
HLS 目前廣泛地應(yīng)用于點(diǎn)播和直播領(lǐng)域.
在 HTML5 頁(yè)面上使用 HLS 非常簡(jiǎn)單:
直接:
<video src="example.m3u8" controls></video>
或者:
<video controls>
<source src="example.m3u8"></source>
</video>
下面, 我將會(huì)概括性地介紹 HLS 協(xié)議的方方面面(暫時(shí)不包括 AES 加密部分的內(nèi)容), 配合 HLS 的 RFC 食用效果更佳.
HLS 協(xié)議詳解
上面是 HLS 整體架構(gòu)圖, 可以看出, 總共有三個(gè)部分: Server, CDN, Client.
其實(shí), HLS 協(xié)議的主要內(nèi)容是關(guān)于 M3U8 這個(gè)文本協(xié)議的, 其實(shí)生成與解析都非常簡(jiǎn)單. 為了更加直接地說(shuō)明這一點(diǎn), 我下面舉兩個(gè)簡(jiǎn)單的例子:
簡(jiǎn)單的 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 通過(guò) URI(RFC3986) 指向的一個(gè) Playlist 來(lái)表示一個(gè)媒體流.
- 一個(gè) Playlist 可以是一個(gè) Media Playlist 或者 Master Playlist, 使用 UTF-8 編碼的文本文件, 包含一些 URI 跟描述性的 tags.
- 一個(gè) Media Playlist 包含一個(gè) Media Segments 列表,當(dāng)順序播放時(shí), 能播放整個(gè)完整的流.
- 要想播放這個(gè) Playlist, 客戶端需要首先下載他, 然后播放里面的每一個(gè) Media Segment.
- 更加復(fù)雜的情況是, Playlist 是一個(gè) Master Playlist, 包含一個(gè) Variant Stream 集合, 通常每個(gè) Variant Stream 里面是同一個(gè)流的多個(gè)不同版本(如: 分辨率, 碼率不同).
HLS Media Segments
- 每一個(gè) Media Segment 通過(guò)一個(gè) URI 指定, 可能包含一個(gè) byte range.
- 每一個(gè) Media Segment 的 duration 通過(guò)
EXTINF
tag 指定. - 每一個(gè) Media Segment 有一個(gè)唯一的整數(shù) Media Segment Number.
- 有些媒體格式需要一個(gè) format-specific sequence 來(lái)初始化一個(gè) parser, 在 Media Segment 被 parse 之前. 這個(gè)字段叫做 Media Initialization Section, 通過(guò)
EXT-X-MAP
tag 來(lái)指定.
支持的 Media Segment 格式
MPEG-2 Transport Streams
- 即最常見(jiàn)的 TS 文件.
- RFC: ISO_13818.
- Media Initialization Section: PAT(Program Association Table) 跟 PMT(Program Map Table).
- 每個(gè) TS segment 必須值含一個(gè) MPEG-2 Program.
- 每一個(gè) TS segment 包含一個(gè) PAT 和 PMT, 最好在 segment 的開(kāi)始處, 或者通過(guò)一個(gè)
EXT-X-MAP
tag 來(lái)指定.
Fragmented MPEG-4
- 即常提到的 fMP4.
- RFC: ISOBMFF.
- Media Initialization Section:
ftyp
box(包含一個(gè)高于ios6
的 brand),ftyp
box 必須緊跟在moov
box 之后.moov
box 必須包含一個(gè)trak
box(對(duì)于每個(gè) fMP4 segment 里面的traf
box, 包含匹配的track_ID
). 每個(gè)trak
box 應(yīng)該包含一個(gè) sample table, 但是他的 sample count 必須為 0.mvhd
box 跟tkhd
的 duration 必須為 0.mvex
box 必須跟在上一個(gè)trak
box 后面. - 不像普通的 MP4 文件包含一個(gè)
moov
box(包含 sample tables) 和一個(gè)mdat
box(包含對(duì)應(yīng)的 samples), 一個(gè) fMP4 包含一個(gè)moof
box (包含 sample table 的子集), 和一個(gè)mdat
box(包含對(duì)應(yīng)的 samples). - 在每一個(gè) fMP4 segment 里面, 每一個(gè)
traf
box 必須包含一個(gè)tfdt
box, fMP4 segment 必須使用 movie-fragment relative addressing. fMP4 segments 絕對(duì)不能使用外部的 data references. - 每一個(gè) fMP4 segment 必須有一個(gè)
EXT-X-MAP
tag.
Packed Audio
- 一個(gè) Packed Audio Segment 包含編碼的 audio samples 和 ID3 tags. 簡(jiǎn)單的打包到一起, 包含最小的 framing, 并且沒(méi)有 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].
- 一個(gè) Packed Audio Segment 沒(méi)有 Media Initialization Section.
- 每一個(gè) Packed Audio Segment 必須在他的第一個(gè) sample 指定 timestamp 通過(guò)一個(gè) ID3 PRIV tag.
- ID3 PRIV owner identifier 必須是
com.apple.streaming.transportStreamTimestamp
. - ID3 payload 必須是一個(gè) 33-bit MPEG-2 Program Elementary Stream timestamp 的大端 eight-octet number, 高 31 為設(shè)置為 0.
WebVTT
- 一個(gè) WebVTT Segment 是一個(gè) WebVTT 文件的一個(gè) section, WebVTT Segment 包含 subtitles.
- Media Initialization Section: WebVTT header.
- 每一個(gè) WebVTT Segment 必須有以一個(gè) WebVTT header 開(kāi)始, 或者有一個(gè)
EXT-X-MAP
tag 來(lái)指定. - 每一個(gè) WebVTT header 應(yīng)該有一個(gè)
X-TIMESTAMP-MAP
來(lái)保證音視頻同步.
HLS Playlists
- Playlist 文件的格式是起源于 M3U, 并且繼承兩個(gè) tag:
EXTM3U
和EXTINF
- 下面的 tags 通過(guò)
BNF-style
語(yǔ)法來(lái)指定. - 一個(gè) Playlist 文件必須通過(guò) URI(.m3u8 或 m3u) 或者 HTTP Content-Type 來(lái)識(shí)別(application/vnd.apple.mpegurl 或 audio/mpegurl).
- 換行符可以用
\n
或者\r\n
. - 以
#
開(kāi)頭的是 tag 或者注釋, 以#EXT
開(kāi)頭的是 tag, 其余的為注釋, 在解析時(shí)應(yīng)該忽略. - Playlist 里面的 URI 可以用絕對(duì)地址或者相對(duì)地址, 如果使用相對(duì)地址, 那么是相對(duì)于 Playlist 文件的地址.
Attribute Lists
- 有的 tags 的值是 Attribute Lists.
- 一個(gè) Attribute List 是一個(gè)用逗號(hào)分隔的 attribute/value 對(duì)列表.
- 格式為:
AttributeName=AttributeValue
.
Basic Tags
Basic Tags 可以用在 Media Playlist 和 Master Playlist 里面.
-
EXTM3U
: 必須在文件的第一行, 標(biāo)識(shí)是一個(gè) Extended M3U Playlist 文件. -
EXT-X-VERSION
: 表示 Playlist 兼容的版本.
Media Segment Tags
每一個(gè) Media Segment 通過(guò)一系列的 Media Segment tags 跟一個(gè) URI 來(lái)指定. 有的 Media Segment tags 只應(yīng)用與下一個(gè) segment, 有的則是應(yīng)用所有下面的 segments. 一個(gè) Media Segment tag 只能出現(xiàn)在 Media Playlist 里面.
-
EXTINF
: 用于指定 Media Segment 的 duration -
EXT-X-BYTERANGE
: 用于指定 URI 的sub-range
-
EXT-X-DISCONTINUITY
: 表示不連續(xù). -
EXT-X-KEY
: 表示 Media Segment 已加密, 該值用于解密. -
EXT-X-MAP
: 用于指定 Media Initialization Section. -
EXT-X-PROGRAM-DATE-TIME
: 和 Media Segment 的第一個(gè) sample 一起來(lái)確定時(shí)間戳. -
EXT-X-DATERANGE
: 將一個(gè)時(shí)間范圍和一組屬性鍵值對(duì)結(jié)合到一起.
Media Playlist Tags
Media Playlist tags 描述 Media Playlist 的全局參數(shù). 同樣地, Media Playlist tags 只能出現(xiàn)在 Media Playlist 里面.
-
EXT-X-TARGETDURATION
: 用于指定最大的 Media Segment duration. -
EXT-X-MEDIA-SEQUENCE
: 用于指定第一個(gè) Media Segment 的 Media Sequence Number. -
EXT-X-DISCONTINUITY-SEQUENCE
: 用于不同 Variant Stream 之間同步. -
EXT-X-ENDLIST
: 表示結(jié)束. -
EXT-X-PLAYLIST-TYPE
: 可選, 指定整個(gè) Playlist 的類型. -
EXT-X-I-FRAMES-ONLY
: 表示每個(gè) Media Segment 描述一個(gè)單一的 I-frame.
Master Playlist Tags
Master Playlist tags 定義 Variant Streams, Renditions 和 其他顯示的全局參數(shù). Master Playlist tags 只能出現(xiàn)在 Master Playlist 中.
-
EXT-X-MEDIA
: 用于關(guān)聯(lián)同一個(gè)內(nèi)容的多個(gè) Media Playlist 的多種 renditions. -
EXT-X-STREAM-INF
: 用于指定一個(gè) Variant Stream. -
EXT-X-I-FRAME-STREAM-INF
: 用于指定一個(gè) Media Playlist 包含媒體的 I-frames. -
EXT-X-SESSION-DATA
: 存放一些 session 數(shù)據(jù). -
EXT-X-SESSION-KEY
: 用于解密.
Media or Master Playlist Tags
這里的 tags 可以出現(xiàn)在 Media Playlist 或者 Master Playlist 中. 但是如果同時(shí)出現(xiàn)在同一個(gè) Master Playlist 和 Media Playlist 中時(shí), 必須為相同值.
-
EXT-X-INDEPENDENT-SEGMENTS
: 表示每個(gè) Media Segment 可以獨(dú)立解碼. -
EXT-X-START
: 標(biāo)識(shí)一個(gè)優(yōu)選的點(diǎn)來(lái)播放這個(gè) Playlist.
服務(wù)器端與客戶端邏輯
以下流程僅供參考, 其實(shí)不同的播放器客戶端以及服務(wù)器端的拉取規(guī)則都有很多細(xì)節(jié)差異.
服務(wù)器端邏輯
- 將媒體源切片成 Media Segment, 應(yīng)該優(yōu)先從可以高效解碼的時(shí)間點(diǎn)來(lái)進(jìn)行切片(如: I-frame).
- 為每一個(gè) Media Segment 生成 URI.
- Server 需要支持 “gzip” 方式壓縮文本內(nèi)容.
- 創(chuàng)建一個(gè) Media Playlist 索引文件,
EXT-X-VERSION
不要高于他需要的版本, 來(lái)提供更好的兼容性. - Server 不能隨便修改 Media Playlist, 除了 Append 文本到文件末尾, 按順序移除 Media Segment URIs, 增長(zhǎng)
EXT-X-MEDIA-SEQUENCE
和EXT-X-DISCONTINUITY-SEQUENCE
, 添加EXT-X-ENDLIST
到文件尾. - 在最后添加
EXT-X-ENDLIST
tag, 來(lái)減少 Client reload Playlist 的次數(shù). - 注意點(diǎn)播與直播服務(wù)器不同的地方是, 直播的 m3u8 文件會(huì)不斷更新, 而點(diǎn)播的 m3u8 文件是不會(huì)變的, 只需要客戶端在開(kāi)始時(shí)請(qǐng)求一次即可.
客戶端邏輯
- 客戶端通過(guò) URI 獲取 Playlist. 如果是 Master Playlist, 客戶端可以選擇一個(gè) Variant Stream 來(lái)播放.
- 客戶端檢查
EXT-X-VERSION
版本是否滿足. - 客戶端應(yīng)該忽略不可識(shí)別的 tags, 忽略不可識(shí)別的屬性鍵值對(duì).
- 加載 Media Playlist file.
- 播放 Media Playlist file.
- 重加載 Media Playlist file.
- 決定下一次要加載的 Media Segment.
HLS 的優(yōu)勢(shì)
- 客戶端支持簡(jiǎn)單, 只需要支持 HTTP 請(qǐng)求即可, HTTP 協(xié)議無(wú)狀態(tài), 只需要按順序下載媒體片段即可.
- 使用 HTTP 協(xié)議網(wǎng)絡(luò)兼容性好, HTTP 數(shù)據(jù)包也可以方便地通過(guò)防火墻或者代理服務(wù)器, CDN 支持良好.
- Apple 的全系列產(chǎn)品支持, 由于 HLS 是蘋果提出的, 所以在 Apple 的全系列產(chǎn)品包括 iphone, ipad, safari 都不需要安裝任何插件就可以原生支持播放 HLS, 現(xiàn)在, Android 也加入了對(duì) HLS 的支持.
- 自帶多碼率自適應(yīng), Apple 在提出 HLS 時(shí), 就已經(jīng)考慮了碼流自適應(yīng)的問(wèn)題.
HLS 的劣勢(shì)
- 相比 RTMP 這類長(zhǎng)連接協(xié)議, 延時(shí)較高, 難以用到互動(dòng)直播場(chǎng)景.
- 對(duì)于點(diǎn)播服務(wù)來(lái)說(shuō), 由于 TS 切片通常較小, 海量碎片在文件分發(fā), 一致性緩存, 存儲(chǔ)等方面都有較大挑戰(zhàn).
改進(jìn)的 HLS 技術(shù)
由于客戶端每次請(qǐng)求 TS 或 M3U8 有可能都是一個(gè)新的連接請(qǐng)求, 所以, 我們無(wú)法有效的標(biāo)識(shí)客戶端, 一旦出現(xiàn)問(wèn)題, 基本無(wú)法有效的定位問(wèn)題, 所以, 一般工業(yè)級(jí)的服務(wù)器都會(huì)對(duì)傳統(tǒng)的 HLS 做一些改進(jìn).
這里主要介紹網(wǎng)宿的 Variant HLS 與又拍云的 HLS+.
網(wǎng)宿的 Variant HLS
首先, 我們可以下載一條網(wǎng)宿的 M3U8 文件:
wget http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8
然后, 打開(kāi)下載得到的 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
可以看出這是一個(gè) Master Playlist, 里面嵌套了一層 M3U8, 同時(shí)可以看出網(wǎng)宿采用 wsSession
來(lái)標(biāo)識(shí)一條播放連接.
又拍云的 HLS+
Variant HLS
首先, 我們可以下載一條又拍云的 M3U8 文件:
wget http://uplive.b0.upaiyun.com/live/loading.m3u8
然后, 打開(kāi)下載得到的 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 方式來(lái)標(biāo)識(shí)一條 HLS 連接, 可以看出, 又拍云使用 uuid 來(lái)表示一條 HLS 連接.
HTTP 302
首先, 以 HTTP 302 方式來(lái)請(qǐng)求播放地址.
? 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
打開(kāi) playlist 內(nèi)容:
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
在跳轉(zhuǎn)之后的地址存放真正的 playlist, 同時(shí), 也能夠?qū)?uuid 加入到了連接上.
總地來(lái)說(shuō), 不管通過(guò)哪種方式, 最終我們都能通過(guò)一個(gè)唯一的 id 來(lái)標(biāo)識(shí)一條流, 這樣在排查問(wèn)題時(shí)就可以根據(jù)這個(gè) id 來(lái)定位播放過(guò)程中的問(wèn)題.
HLS 延時(shí)分析
HLS 理論延時(shí) = 1 個(gè)切片的時(shí)長(zhǎng) + 0-1個(gè) td (td 是 EXT-X-TARGETDURATION, 可簡(jiǎn)單理解為播放器取片的間隔時(shí)間) + 0-n 個(gè)啟動(dòng)切片(蘋果官方建議是請(qǐng)求到 3 個(gè)片之后才開(kāi)始播放) + 播放器最開(kāi)始請(qǐng)求的片的網(wǎng)絡(luò)延時(shí)(網(wǎng)絡(luò)連接耗時(shí))
為了追求低延時(shí)效果, 可以將切片切的更小, 取片間隔做的更小, 播放器未取到 3 個(gè)片就啟動(dòng)播放. 但是, 這些優(yōu)化方式都會(huì)增加 HLS 不穩(wěn)定和出現(xiàn)錯(cuò)誤的風(fēng)險(xiǎn).
Demo
- M3U8 golang library
- HLS downloader: 讀取一個(gè) m3u8 URL, 下載為 TS 文件.
Refs
轉(zhuǎn)自:http://akagi201.org/post/hls-explained/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io