HLS低延遲直播(LL-HLS)原理與落地實作

分類:技術與進階


前陣子想幫朋友弄個小範圍的直播,就找了幾個哥們一起看比賽。一開始用的是 RTMP + Flash 那一套,結果現在瀏覽器都拋棄 Flash 了,還要裝外掛超麻煩。後來切到 HLS,相容性倒是好了,但延遲直接飆到十幾秒,群組裡的訊息跟畫面差了半個進球,體驗簡直崩潰。於是開始研究怎麼幫 HLS 減肥,就摸到了 LL-HLS(低延遲 HLS)。

老實說,LL-HLS 剛出來的時候我其實不太當一回事,感覺蘋果又在畫大餅。但實際動手試了之後發現,延遲確實能壓到 3-5 秒,跟之前 RTMP 差不多,而且完全基於 HTTP 協定,不用開一些奇怪的連接埠,CDN 也很好搭配。來聊一下我折騰過後的一些理解和踩過的坑吧。

LL-HLS 核心思路:把片段再切更小

傳統 HLS 延遲高是因為資料是按 ts 或 m4s 片段組織的,一個片段通常 6 秒,加上播放器快取,延遲輕鬆就 10-15 秒。LL-HLS 的做法是把每個片段進一步切成更小的 Partial Segment(分塊),比方說 0.5 秒一塊。m3u8 裡多了一個 #EXT-X-PART 標籤來標記這些分塊,而且檔頭會提前渲染,讓播放器在片段還沒完全產生的時候就開始下載小塊。

還有一個關鍵是 Preload Hint(預載提示)。伺服器會在 m3u8 裡加入一個特殊的 #EXT-X-PRELOAD-HINT,告訴播放器「那塊還沒結束的資料我馬上就能給你」,播放器收到後就會持續用 HTTP 長輪詢或分塊傳輸的方式去拉這塊資料,只要伺服器新的 chunk 來了,馬上就能拿到更新。這樣就把之前的等待時間壓縮到毫秒等級。

另外蘋果還推薦使用 HTTP/2 推播或多路複用(多工),不過我自己實際測試下來,只要分塊夠小,HTTP/1.1 的長輪詢也能跑得動,就是連線數會多一點。

落地實作:從伺服器端到播放器一路都是坑

原理看起來不太複雜,但實際動手搭建還是有很多地方要注意。

伺服器端:我們常用的 Nginx 自帶的 nginx-rtmp-module 雖然可以切出 HLS,但對 LL-HLS 的支援很有限(舊版本基本上不支援)。我後來換成 Nginx 官方提供的 HLS 模組加上修補程式,或者直接用開源方案像是 lal(一位大神寫的 Go 串流媒體伺服器),它原生支援 LL-HLS。也可以用 ffmpeg 加上 -hls_init_time 0.5-hls_playlist_type event 來推流,但 ffmpeg 的 LL-HLS 功能還在完善中,有時候分塊時間會不準確。

分塊大小與延遲平衡:Partial Segment 時長設太小(例如 0.2 秒)會大幅增加伺服器端的計算壓力和網路開銷,延遲是降下來了,但卡頓就上去了。我試過 0.5 秒是一個平衡點,搭配預載提示,整體延遲能控制在 3-4 秒,一般使用者幾乎感覺不到。這個參數要在伺服器端的切片引擎裡設定好,例如在 lal 裡設定 fragment_duration = 1000(毫秒),然後 part_duration = 500

客戶端支援:這個最坑。Safari 和 iOS 的 HLS 原生支援 LL-HLS,開箱即用。但其他瀏覽器就得靠第三方播放器。hls.js 從 1.0 開始加入了 LL-HLS 支援,但版本升級過程中常常有一些 bug,像是遇到 #EXT-X-PRELOAD-HINT 會直接不解析。我測試的時候常用 M3U8Player(https://m3u8player.link/)這個線上播放器,丟個 LL-HLS 的 m3u8 連結進去就能看到延遲表現,也方便給朋友們驗證,算是一個偷懶小工具。

還有一個坑是時間同步。LL-HLS 很依賴伺服器與客戶端的時鐘同步,因為預載提示裡帶有 SN(序號)和 PART 資訊,如果時間偏差太大,播放器會誤判哪個 block 是最新的,導致重複請求或者永遠追不上進度。我自己的環境中直接用 NTP 鎖定伺服器時間,客戶端反正一般都是瀏覽器,自然同步問題不大。

快取的坑:CDN 快取如果不關掉的話,分塊會被快取,播放器拿到的永遠是最舊的那一塊,延遲又爆回十幾秒。所以我特別弄了一套 API 自動給 m3u8 檔名加上隨機參數,並在 CDN 上設定不要快取 m3u8 和最近幾秒的 ts/m4s 檔案。這一點很容易被忽略。

總結(其實也沒啥好總結的)

折騰了一圈之後,LL-HLS 確實能讓 HLS 的延遲降到可用水準,但部署成本比一般 HLS 高出一些。不過好處是相容性依然很好,而且現在各大雲端服務商也都開始支援了。如果你也在弄直播又不想用 WebRTC 那套複雜的信令,LL-HLS 是一個值得嘗試的方向。接下來我準備在伺服器上弄個自動化指令碼,一鍵產生 LL-HLS 串流,這樣以後再找朋友看比賽就不用聽他們罵延遲了。