LL-DASH CMAF 低延遲直播

語言: CN / TW / HK

使用 DASH 直播時一般會有幾十秒的直播延遲,對於互動直播這麼高的延遲根本互不動。要降低直播延遲一般會減少視訊分段時長。

上圖中展示了不同時長的視訊片段對應的延遲,減小視訊片段時長雖然可以降低延遲,但是也會增加資源消耗和視訊位元速率,而且就算使用 1 秒的視訊分段,延遲也會比下面介紹的 LLDASH 方案高。

介紹

LLDASH(Low Latency DASH)最早在 2017 年提出併成立工作組,在 2019 年 DVB 釋出了 DVB-DASH with low latency 規範,基於 DVB 和 DASH IF 聯合開發的這份報告 DVB and DASH-IF in 2017 on Low-Latency DASH 在 2020 年 DASH IF 釋出了 Low-latency Modes for DASH 規範。

DVB(Digital Video Broadcasting)數字視訊廣播,是一系列數字電視國際開放標準,由 DVB Project 維護。DVB Project 是一個由 300 多個成員組成的工業組織,它是由歐洲電信標準化組織、歐洲電子標準化組織和歐洲廣播聯盟聯合組成的聯合專家組發起的。

DASH IF(DASH Industry Forum)DASH 行業論壇,它主要由流媒體公司組成,如 Akamai、谷歌、微軟等。DASH IF 主要標準化互操作性,促進 MPEG-DASH 發展,並幫助其從規範過渡到真正的業務。

所以目前一共有 DVB 和 DASH IF 兩套 LLDASH 規範,這兩套低延遲方案非常相似只有一點不同,由於 DASH IF 較晚釋出所以在 DASH IF 規範中也說明了與 DVB 不同的部分。而且這兩個規範是完全向下相容普通 DASH 直播的。

CMAF

雖然 MPEG-DASH 規範並沒有限制內容格式,但是兩種 LLDASH 規範中都是使用 CMAF 格式。這容易讓人產生 CMAF 和低延遲劃等號的誤解,CMAF 本身並不會降低延遲,例如 HLS 支援 MPEG-TS 和 CMAF 兩種格式,如果將普通 HLS 直播 MPEG-TS 分片換成 CMAF 分片,這並不會降低直播延遲。CMAF 最大的作用是統一播放格式,從而節省儲存空間。不過 CMAF 提供了一些工具使低延遲 DASH 成為可能。

原理

LLDASH 與上篇文章介紹的 LHLS 非常相似,都是將一個分片變成一個個小 Chunk,這些小 Chunk 可以在分片完全生成之前被播放器使用 HTTP/1.1 的 Chunked transfer encoding 功能下載並快取,從而降低直播延遲。

如上圖所示,普通 DASH 直播中一個 MP4 分段只有完全編碼後才能輸出被請求。LLDASH 中將視訊片段分割為一個個小 Chunk,編碼器可以每生成一個 Chunk 就輸出,傳遞給播放器快取播放。

CMAF 中 ftyp 和 moov 盒子組成初始化分段,每一個 Chunk 由 moof 和 mdat 盒子組成。播放器會先請求初始化分段,然後請求最新的媒體分段,伺服器會將分段的一個個 Chunk 返回給播放器播放。

播放器請求拉流時,可能如上圖所示,一個視訊片段被分為 3 個 Chunk。當前播放器傳送請求給伺服器時,視訊片段還沒被完全生成,伺服器會保持連線不斷開,每當生成一個 Chunk 就立馬推送給播放器。

規範實現

對於使用 DASH IF 低延遲規範的 MPD,應該新增 http://www.dashif.org/guidelines/low-latency-live-v5 到 MPD@profiles 屬性中進行標識,下面是一個符合 DASH IF 低延遲規範的 MPD 例子。

```

<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" availabilityStartTime="1970-01-01T00:00:00Z" id="Config part of url maybe?" maxSegmentDuration="PT8S" minBufferTime="PT1S" minimumUpdatePeriod="P100Y" profiles="urn:mpeg:dash:profile:full:2011,http://www.dashif.org/guidelines/low-latency-live-v5" publishTime="2021-09-14T05:25:57Z" timeShiftBufferDepth="PT5M" type="dynamic"

https://livesim.dashif.org/livesim/sts_1631597157/sid_a736b022/chunkdur_1/ato_7/testpic4_8s/ ```

判斷是否是 Chunked 低延遲

DASH IF 低延遲規範中定義了兩種低延遲直播方法,一種是不使用上面提到的 Chunked transfer encoding 功能,而是將媒體分段切分的非常短來降低延遲,這種方法可以普通的 DASH 直播並沒有什麼區別,這裡不再過多介紹。

另一種低延遲方法就是使用上面提到的 HTTP Chunked transfer encoding 功能,也就是當一個媒體還沒完全生成好時,播放器可以請求該分段下載並快取已經建立的 Chunk,而不是 404 請求報錯。這個就是下面要介紹的低延遲模式。

有兩種方法可以判斷是否是低延遲模式。

  1. 根據 SegmentTemplate@availabilityTimeComplete 屬性。DASH IF 低延遲規範中對於 Chunked AdaptationSet,需要將 availabilityTimeComplete 屬性設定為 false,所以如果 availabilityTimeComplete 為 false 時,則可以認為該媒體流是低延遲模式。
  2. DVB 還定義了描述低延遲的 EssentialProperty 和 SupplementalProperty 元素,如果存在其中一個,並且它的 schemeIdUri 屬性等於 urn:dvb:dash:lowlatency:critical:2019,value 屬性等於 true,則也可以認為該媒體流是低延遲模式。

延遲和播放速率

LLDASH 中定義了播放延遲和播放速率資訊,這些資訊在 ServiceDescription 元素中。

<ServiceDescription id="0"> <Scope schemeIdUri="urn:dvb:dash:lowlatency:scope:2019" /> <Latency target="3000" max="6000" min="1500" /> <PlaybackRate max="1.5" min="0.5" /> </ServiceDescription>

  • Latency 元素定義直播延遲相關資訊,單位是毫秒。Latency@target 為直播目標延遲。 Latency@max 為允許的最大延遲,當延遲超過該值時播放器應該直接 seek 到延遲位置。如果當前延遲小於 Latency@min 播放器應該慢放。
  • PlaybackRate 元素定義了最大和最小播放速率,正常速率是 1。當延遲超過目標延遲時播放器會進行快放追趕,當快取過少時播放器可能進行慢放。

DVB 低延遲規範中還定義了 Scope 元素,它的 schemeIdUri 屬性為 urn:dvb:dash:lowlatency:scope:2019,DASH IF 規範中沒有定義該元素。

時鐘同步

為了獲取準確的媒體分段和直播延遲,LLDASH 規範定義 MPD 中最少存在一個 UTCTiming 元素,用於客戶端與服務端時鐘同步。UTCTiming@schemeIdUri 屬性需要是下面 3 箇中的一個。

  • urn:mpeg:dash:utc:http-xsdate:2014
  • urn:mpeg:dash:utc:http-iso:2014
  • urn:mpeg:dash:utc:http-ntp:2014 (瀏覽器中不支援)

UTCTiming@value 屬性是伺服器時間服務地址。

<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014" value="https://time.example.com" />

一些老規範中使用的是 2012,播放器也應該支援下面兩個 schemeIdUri。

  • urn:mpeg:dash:utc:http-xsdate:2012
  • urn:mpeg:dash:utc:http-iso:2012

如果 MPD 中沒有 UTCTiming 元素或者時鐘同步服務不可訪問時,播放器可以降級為使用本地時鐘。

媒體分段

LLDASH 中定義了兩種獲取媒體分段的方法。

  1. SegmentTemplate@media(使用 $Number$) 加 SegmentTemplate@duration
  2. SegmentTemplate@media($Time$ 和 $Number$)加 SegmentTimeline 元素

DVB-DASH 更推薦使用第一種方法。DASH IF 沒有做推薦。

SegmentTemplate + SegmentTemplate@duration

下面是使用第一種方法的例子。

<Representation id="0" mimeType="video/mp4" codecs="avc1.42c028" bandwidth="6000000" width="1920" height="1080" sar="1:1"> <SegmentTemplate timescale="1000000" duration="2002000" availabilityTimeOffset="1.969" availabilityTimeComplete="false" initialization="1630889228/init-stream_$RepresentationID$.m4s" media="1630889228/chunk-stream_$RepresentationID$-$Number%05d$.m4s" startNumber="1" ></SegmentTemplate> </Representation>

上面例子中我們首先將 SegmentTemplate@initialization 中的 $RepresentationID$ 替換成 Representation@id 獲取到初始化分段的地址 1630889228/init-stream_0.m4s。(初始化分段還可能用其他方式提供,如使用 Initialization 元素。)

獲取到初始化分段 URL 後,還需要確定第一個媒體分段的 URL。假設 NOW 變數是與伺服器時鐘同步後的當前時間。那麼我們就可以用下面式子獲取到符合目標延遲的最新完整分段地址的 $Number$。

targetNumber = Math.floor( ((NOW - MPD@availabilityStartTime - Latency@target) / 1000 - Period@start) / (SegmentTemplate@duration / SegmentTemplate@timescale) ) + SegmentTemplate@startNumber

然後再把 SegmentTemplate@media 中的 $Number$ 替換成 targetNumber,就構造好了第一個媒體分段的 URL 了。

SegmentTemplate + SegmentTimeline

<AdaptationSet id="0" mimeType="video/mp4" width="512" height="288" par="16:9" frameRate="30" segmentAlignment="true" startWithSAP="1"> <SegmentTemplate timescale="90000" media="segment_ctvideo_cfm4s_rid$RepresentationID$_cs$Time$_w743518253_mpd.m4s" initialization="segment_ctvideo_cfm4s_rid$RepresentationID$_cinit_w743518253_mpd.m4s" > <SegmentTimeline> <S t="1614755160" d="900000"/> <S d="900000"/> <S d="900000"/> <S d="900000"/> <S d="900000"/> </SegmentTimeline> </SegmentTemplate> <Representation id="p0va0br601091" codecs="avc1.42c015" sar="1:1" bandwidth="601091" /> </AdaptationSet>

SegmentTimeline 用來表示各個媒體分段的媒體時間和時長,用來替換SegmentTemplate@duration 屬性。SegmentTimeline 有一堆 S 子元素,S 元素主要有 S@t、S@d 和 S@r 三個屬性。

| 屬性名 | 描述 | | ---- | ----------------------------------------------------- | | S@t | 分段媒體時間,如果不存在則等於它上一個分段 Sp 的 Sp@t + Sp@d | | S@d | 分段時長 | | S@r | 與該分段相同時長分段的的重複次數,預設為 0,如果為負數則表示重複到下一個 S 元素或 Period 結束 |

和第一種方法一樣首先我們需要去請求初始化分段,這裡不在詳細介紹。

要計算出符合目標延遲的最新完整分段的地址,首先需要計算出目標 S@t 值,再在 SegmentTimeline 找到這個分段並計算出它的 URL。

targetT = (NOW - MPD@availabilityStartTime - Latency@target) / 1000 - Period@start + (SegmentTemplate@presentationTimeOffset / SegmentTemplate@timescale)

計算出目標 targetT 後,我們就可以迭代 SegmentTimeline 的 S 元素,如果存在 S@r 屬性則需要進行展開,找到 (S@t + S@d) / SegmentTemplate@timescale > targetT 的 S 元素,這個 S 元素就是我們要找的目標 S 元素。

然後把 SegmentTemplate@media 中的 $Time$ 替換成目標 S 元素的 S@t 屬性,$Number$ 替換成目標 S 元素在展開後的 SegmentTimeline 元素中的下標再加上 SegmentTemplate@startNumber。這樣就構造好了目標媒體分段的 URL 了。

Resync

Resync 被定義與 MPEG DASH ISO/IEC 23009-1:2020/Amd.1 規範中。它定義了分段的同步點資訊,通過它播放器可以進行快速隨機訪問,可是實現類似與 LLHLS 中的 EXT-X-PART。

它可以放在 AdaptationSet 或 Representation 元素下。

<Resync type="2" dT="1000000" dImin="0.1" dImax="0.15” marker="TRUE"/>

  • type 等於 2 表示可以被隨機訪問,0 表示 CMAF Chunk 不保證能隨機訪問
  • dT 表示在 timescale 下最大隨機訪問點的時間距離。
  • dImin 表示最小兩個隨機點之間的位元組距離 dImin * bandwidth
  • dImax 表示最大兩個隨機點之間的位元組距離 dImax * bandwidth
  • marker 為 true,表示播放器可以解析 CMAF 盒子找到同步點

低延遲 ABR 演算法

ABR(自適應位元速率)是 DASH 播放器的一個關鍵功能,它可以讓視訊在複雜的網路條件下動態切換位元速率和播放速率,而不是中斷播放降低使用者體驗。

在低延遲下,一些基於頻寬估算的 ABR 演算法都不太好用。這是因為使用 Chunked transfer encoding 時,一個視訊分段並沒有被完全生成,對於一個 5 秒的視訊分段,一個 http 請求可能需要花費 5 秒,這個時間並不是準確的下載時間。在 2020 年 Twitch 和 ACM 合作舉辦了低延遲下的 ABR 演算法大挑戰 Adaptation Algorithms for Near-Second Latency。比賽的第一名是 Unified Streaming 的 L2A-LL(Learn2Adapt-LowLatency) 演算法 ,第二名是新加坡國立大學的 LoL(Low-on-Latency)演算法。由於 Twitch 播放器不是開源的,比賽是基於 dash.js 播放器,目前 dash.js 也集成了這兩種 ABR 演算法。

總結

LLDASH 和 LHLS 非常相似,都是使用 HTTP/1.1 的 Chunked transfer encoding 功能來降低延遲提供 1 到 6 秒的低延遲直播,而且可以複用現有的 CDN 網路支援大規模使用者觀看直播。不過 Chunked transfer encoding 功能需要瀏覽器支援 fetch API,所以在 IE 上會降級為使用 XHR 的普通 DASH 直播。