淺析 HLS 流媒體協議

zuozewei發表於2020-12-06

一、前言

在最近工作中接觸到了視訊點播和直播業務,也瞭解到了一些流媒體的後端技術,這段時間希望將瞭解到的一些知識總結下來,這篇文章主要介紹 HLS 流媒體協議 的基礎知識。

二、常見流媒體協議

常用的流媒體協議主要有 HTTP 漸進下載和基於 RTSP/RTP 的實時流媒體協議,這兩種協議是完全不同的實現方式。主要區別如下:

  • 一種是分段漸近下載,一種是基於實時流來實現播放;
  • 協議不同,HTTP 協議的漸近下載意味著可以在一臺普通的 HTTP 的應用伺服器上就可以直接提供視訊點播和直播服務;
  • 延遲有差異,HTTP 漸近下載的方式的延遲理論上會略高於實時流媒體協議的播放;
  • 漸近下載會生成索引檔案,所以需要考慮儲存,對 I/O 要求較高。

三、HLS 協議介紹

HLS 協議是由 Apple 公司提出並推廣開來的,以下是來一段維基百科的定義:

HTTP Live Streaming(縮寫是HLS)是一個由蘋果公司提出的基於HTTP的流媒體網路傳輸協議。是蘋果公司QuickTime X和iPhone軟體系統的一部分。它的工作原理是把整個流分成一個個小的基於HTTP的檔案來下載,每次只下載一些。當媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應不同的資料速率。在開始一個流媒體會話時,客戶端會下載一個包含後設資料的extended M3U (m3u8)playlist檔案,用於尋找可用的媒體流。
HLS只請求基本的 HTTP 報文,與實時傳輸協議(RTP)不同,HLS可以穿過任何允許HTTP資料通過的防火牆或者代理伺服器。它也很容易使用內容分發網路來傳輸媒體流。
蘋果公司把HLS協議作為一個網際網路草案(逐步提交),在第一階段中已作為一個非正式的標準提交到IETF。但是,即使蘋果偶爾地提交一些小的更新,IETF卻沒有關於制定此標準的有關進一步的動作。[1]

在網上已經有很多關於 HLS 的資料,本文主要按照我的理解整理下 HLS 流媒體協議基礎知識。

HLS 協議格式要求:

  • 視訊的封裝格式 TS(流媒體檔案);
  • 儲存 TS 索引的 M3U8 檔案;
  • 視訊的編碼格式:H264 (只要 MPEG-TS 支援,基本都可以,只是有些格式不是免費的;音訊類似);
  • 音訊的編碼格式:AAC、MP3、AC-3。

HLS 協議優勢:

  • 使用標準 HTTP 傳輸資料,具有較好的網路穿透及防遮蔽性,更易於內容分發網路傳輸;
  • HLS 協議本身是支援位元速率自適應的,客戶端可以根據實際網路狀況切換到合適的位元速率播放;
  • HLS 內容釋出服務更簡單,對系統裝置要求較低,更容易實現負載均衡,並且 HLS 是無狀態協議的 HTTP,客戶端只需要下載即可。

HLS 協議劣勢:

  • 延時較大,尤其是在直播的情況下,很難做到 10s 以內的延時(不排除網上各種改進版本及演算法);
  • 內容生成時對編碼端效能要求較高。

四、HlS 系統架構

下圖來自 Apple 官網:

HlS 系統架構

HLS 支援直播或者點播,同時支援加密和認證。從概念上來說,HTTP通常包括三部分:伺服器端、釋出端、客戶端。

1、HLS 伺服器端

伺服器端主要負責將輸入的媒體資料進行編碼、封裝,並將封裝之後的檔案切片,以滿足釋出端的要求。其輸出可以是音視訊原始資料,也可以是編碼之後的資料,也可以是封裝好的 TS 資料。這也輸入最終會通過分片工具切分成釋出端需要的格式。這裡涉及三部分:

  • 多媒體編碼器 (Media Encoder):多媒體編碼器主要把採集自音視訊裝置的實時訊號編碼,封裝。編碼中必須選擇客戶端支援的格式,比如 h264 視訊+ aac 音訊。目前 HLS 支援的封裝格式是 MPEG-TS 或者 MPEG 基本流(MPEG-ES,僅支援純音訊)。編碼完成之後,編碼器可以把封裝之後的格式通過本地網路或者其他機制傳遞給分片工具(segmenter)。

  • 分片工具(segmenter):按照輸入源的不同,通常分為流分片器、檔案分片器。顧名思義,二者主要區別在於輸入的檔案格式上。 流分片器輸出的是從本地網路滴入的 MPEG-TS 流,而檔案分片器處理的是封裝好的 TS檔案。它們的工作原理類似:將 MPEG-TS 切分成一系列等時長的媒體檔案,但保證這些小的分片是可以無縫重建的,播放時音視訊是連續的。分片工具還會建立索引檔案(M3U8),其中包含指向單獨媒體檔案的索引資訊。每當分片器完成一個新的媒體檔案,它將更新索引檔案。該索引用於記錄媒體檔案的位置及可訪問性。在此過程中,分片工具可以加密每個分片,併為其建立金鑰檔案。

2、HLS 分發端

HLS 分發端較為簡單,只要使用標準的網路伺服器即可。它們負責接受客戶端請求,並將處理好的多媒體檔案和資源傳送給客戶端。如果併發量較大,可能需要邊緣網路或其他內容分發網路。
分發系統是一個 web 伺服器或者 web 快取系統,它們能夠通過 HTTP 向客戶端傳送媒體檔案及索引檔案。多數情況下,分發內容之前無需額外配置伺服器、模組,僅需很少的配置就在 web 伺服器上正常工作。

3、HLS 客戶端

客戶端負責選擇合適的請求資源,下載器資源,然後解碼顯示(整成播放器的功能)。
客戶端從獲取索引檔案開始,通常使用給定的 URL 來識別該流的資訊。這個索引檔案一般給出了可用媒體檔案、解密金鑰和其他可選流的位置。客戶端選定流之後,就開始順序下載每個可用的媒體檔案。每個檔案中包含特定流的連續分片。只要客戶端下載到足夠的資料,就可以開始解碼資料並顯示了。
如果需要,客戶端負責讀取所有解密金鑰、認證或為使用者提供用於認證或解密的介面。
客戶端可以一直持續這個過程,直到它遇到索引檔案中的 #EXT-X-ENDLIST 標籤;若不存在該標籤,則表示該索引檔案是一個直播源,客戶端需要定期更新索引檔案,重複上述過程。

較為常用的 HLS 系統中,使用硬編碼器將輸入的音訊編碼為 AAC、將輸入的視訊編碼為 h264,並將二者複用到 MPEG-TS 中,之後使用分片工具將其切分為一系列小的 TS 檔案;這些檔案將可以放到 web 伺服器上。分片工具同時會建立並維護一個索引檔案(HLS 中稱為 M3U8),其中包含可用媒體檔案的列表。索引檔案的URL會在 web 伺服器上釋出。客戶端可以讀取該索引檔案,然後順序請求列出的媒體檔案,這些分片可以無縫播放。

4、小結

左下方的 inputs 的視訊源是什麼格式都無所謂,他與 server 之間的通訊協議也可以任意(比如RTMP),總之只要把視訊資料傳輸到伺服器上即可。這個視訊在 server 伺服器上被轉換成 HLS 格式的視訊(TS 和 M3U8 檔案)檔案。細拆分來看 server 裡面的 Media encoder 的是一個轉碼模組負責將視訊源中的視訊資料轉碼到目標編碼格式(H264)的視訊資料,視訊源的編碼格式可以是任何的視訊編碼格式。轉碼成 H264 視訊資料之後,在 stream segmenter 模組將視訊切片,切片的結果就是 index file(m3u8)和 ts 檔案了。圖中的 Distribution 其實只是一個普通的 HTTP 檔案伺服器,然後客戶端只需要訪問一級 index 檔案的路徑就會自動播放 HLS 視訊流了。

下圖是一個簡單概括的流媒體播放實現時序圖:
HLS播放實現時序圖
簡單描述 HLS 的工作原理是將整個流分成一系列小的基於 HTTP 的檔案下載,每個下載將載入整個潛在的無限制傳輸流中的一小部分。由於片段之間的分段間隔時間非常短,所以看起來是一條完整的播放流,實現的重點是對於視訊檔案的分割。同時,HLS 還支援多位元速率的切換,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應不同的資料速率。多清晰度就是這樣實現的。
為了播放視訊,客戶端首先需要獲得播放列表檔案,也就是根據 HLS 生成的片段列表,該列表中包含每個流媒體的檔案,客戶端以類似輪詢的方式不斷重複載入播放列表檔案並將片段追加實現流媒體的播放。

五、示例分析

playlist.m3u8 的請求。這實際上是指向其他索引的指標,這些塊需要作為流媒體的一部分進行下載。m3u8 檔案本質說其實是採用了編碼是 UTF-8 的 m3u 檔案。
它只是一個純索引檔案,一個檔案片段的列表,客戶單開啟它並不是播放它,而是根據它裡面的檔案片段找到視訊檔案的網路地址進行播放。

這裡請求一個 m3u8 檔案開啟看一下究竟是什麼:

curl http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/playlist.m3u8
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-STREAM-INF:BANDWIDTH=3128000,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=1280x720
chunklist_w1690990834_b3128000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1778000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=852x480
chunklist_w1690990834_b1778000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1048000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=640x360
chunklist_w1690990834_b1048000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=738000,CODECS="avc1.4d0015,mp4a.40.2",RESOLUTION=428x240
chunklist_w1690990834_b738000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=528000,CODECS="avc1.4d000d,mp4a.40.2",RESOLUTION=312x176
chunklist_w1690990834_b528000.m3u8

我們分析該 m3u8 檔案:

  • #EXTM3U:擴充套件標記 ,意思是我是 m3u 檔案
  • #EXT-X-VERSION:版本
  • #EXT-X-STREAM-INF:指定一個包含多媒體資訊的 media URI 作為 PlayList,一般做 M3U8 的巢狀使用,它只對緊跟後面的 URI 有效,
  • #EXT-X-STREAM-INF:有以下屬性:
    • BANDWIDTH:頻寬
    • CODECS:不是必須的。
    • RESOLUTION:解析度。

第一個塊是 chunklist_w1057647775_b3128000 還是個 m3u8 檔案,之後可以看到每個後續塊。每個塊都將顯示客戶端要下載的媒體 URI。
同時可以觀察發現,這其實是不同清晰度的 m3u8 檔案,客戶端根據網路或者選項去選擇不同的清晰度的 m3u8 檔案。
上面的 m3u8 檔案為一級 m3u8 檔案,這兩個 m3u8 就稱為二級索引檔案,那麼我們就順著二級索引檔案繼續檢視:

curl http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/chunklist_w570392994_b3128000.m3u8
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:6.0,
media_w570392994_b3128000_0.ts
#EXTINF:6.0,
media_w570392994_b3128000_1.ts
#EXTINF:6.0,
media_w570392994_b3128000_2.ts
#EXTINF:6.0,
media_w570392994_b3128000_3.ts
#EXTINF:6.0,
media_w570392994_b3128000_4.ts
#EXTINF:6.0,
media_w570392994_b3128000_5.ts
#EXTINF:6.0,
media_w570392994_b3128000_6.ts
#EXTINF:6.0,
media_w570392994_b3128000_7.ts
#EXTINF:6.0,
media_w570392994_b3128000_8.ts
#EXTINF:6.0,
media_w570392994_b3128000_9.ts
#EXTINF:6.0,
media_w570392994_b3128000_10.ts
#EXTINF:6.0,
media_w570392994_b3128000_11.ts
#EXTINF:6.0,
media_w570392994_b3128000_12.ts
#EXTINF:6.0,
media_w570392994_b3128000_13.ts
#EXTINF:6.0,
media_w570392994_b3128000_14.ts
#EXTINF:6.0,
media_w570392994_b3128000_15.ts
#EXTINF:6.0,
media_w570392994_b3128000_16.ts
#EXTINF:6.0,
media_w570392994_b3128000_17.ts
#EXTINF:6.0,
media_w570392994_b3128000_18.ts
#EXTINF:6.0,
media_w570392994_b3128000_19.ts
#EXTINF:6.0,
media_w570392994_b3128000_20.ts
#EXTINF:6.0,
media_w570392994_b3128000_21.ts
#EXTINF:2.66,
media_w570392994_b3128000_22.ts
#EXT-X-ENDLIST

我們分析該二級索引檔案:

  • #EXT-X-VERSION : 版本
  • #EXT-X-TARGETDURATION:指定最大的流片段時間長(秒),也就是說這些 ts 切片的時長不能大於這個值;
  • #EXTINF: 指定每個流片段(ts)的持續時間(秒),僅對其後面的 URI 有效,title 是下載資源的 URI;
  • #EXT-X-ENDLIST: 結束列表,這個標誌同時也說明當前的流是一個非直播流。

這裡我們看到了真正播放的流片段,即 ts 片,客戶端拿到的就是這個 ts 片,然後不斷下載請求到該片段並連續播放。
有些人可能要問了,那 ts 檔案又到底是個什麼東西呢,那就下載來看看,拿著其中的一個 ts 檔案瀏覽器開啟儲存到本地
發現儲存到本地的檔案就可以直接開啟,其實就是真正的流媒體檔案,但是這個檔案只是片段,大概只有 6s 的時間。
在這裡插入圖片描述

請求下該 “media_w570392994_b3128000_0.ts”流片段:

curl -I http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/media_w570392994_b528000_0.ts
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Expose-Headers: Date
Server: WowzaStreamingEngine/4.7.5.01
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: HEAD, GET, POST
Access-Control-Allow-Headers: Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control, Range
Date: Sat, 05 Dec 2020 18:05:53 GMT
Content-Type: video/MP2T
Content-Length: 426008

六、總結

在這裡插入圖片描述
播放 HLS 視訊流的邏輯其實非常簡單,先下載一級 Index file,它裡面記錄了二級索引檔案(Alternate-A、Alternate-B、Alternate-C)的地址,然後客戶端再去下載二級索引檔案,二級索引檔案中又記錄了 TS 檔案的下載地址,這樣客戶端就可以按順序下載 TS 流媒體檔案並連續播放。

參考資料:

  • https://blog.csdn.net/phachon/article/details/52524596
  • https://www.flood.io/blog/load-testing-hls-with-ruby-jmeter
  • https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1
  • https://www.jianshu.com/p/426425cad08a

相關文章