1. 程式人生 > >如何實現1080P延遲低於500ms的實時超清直播傳輸技術【圖文版本】

如何實現1080P延遲低於500ms的實時超清直播傳輸技術【圖文版本】

最近由於公司業務關係,需要一個在公網上能實時互動超清視訊的架構和技術方案。眾所周知,視訊直播用 CDN + RTMP 就可以滿足絕大部分視訊直播業務,我們也接觸了和測試了幾家 CDN 提供的方案,單人直播沒有問題,一旦涉及到多人互動延遲非常大,無法進行正常的互動交談。對於我們做線上教育的企業來說沒有互動的直播是毫無意義的,所以我們決定自己來構建一個超清晰(1080P)實時視訊的傳輸方案。

先來解釋下什麼是實時視訊,實時視訊就是視訊影象從產生到消費完成整個過程人感覺不到延遲,只要符合這個要求的視訊業務都可以稱為實時視訊。關於視訊的實時性歸納為三個等級:

  • 偽實時:視訊消費延遲超過 3 秒,單向觀看實時,通用架構是 CDN + RTMP + HLS,現在基本上所有的直播都是這類技術。

  • 準實時: 視訊消費延遲 1 ~ 3 秒,能進行雙方互動但互動有障礙。有些直播網站通過 TCP/UDP + FLV 已經實現了這類技術,YY 直播屬於這類技術。

  • 真實時:視訊消費延遲 < 1秒,平均 500 毫秒。這類技術是真正的實時技術,人和人交談沒有明顯延遲感。QQ、微信、Skype 和 WebRTC 等都已經實現了這類技術。

市面上大部分真實時視訊都是 480P 或者 480P 以下的實時傳輸方案,用於線上教育和線上教學有一定困難,而且有時候流暢度是個很大的問題。在實現超清晰實時視訊我們做了大量嘗試性的研究和探索,在這裡會把大部分細節分享出來。

要實時就要縮短延遲,要縮短延遲就要知道延遲是怎麼產生的,視訊從產生、編碼、傳輸到最後播放消費,各個環節都會產生延遲,總體歸納為下圖:

成像延遲,一般的技術是毫無為力的,涉及到 CCD 相關的硬體,現在市面上最好的 CCD,一秒鐘 50 幀,成像延遲也在 20 毫秒左右,一般的 CCD 只有 20 ~ 25 幀左右,成像延遲 40 ~ 50 毫秒。

編碼延遲,和編碼器有關係,在接下來的小結介紹,一般優化的空間比較小。

我們著重針對網路延遲和播放緩衝延遲來進行設計,在介紹整個技術細節之前先來了解下視訊編碼和網路傳輸相關的知識和特點。

一、視訊編碼那些事

我們知道從 CCD 採集到的影象格式一般的 RGB 格式的(BMP),這種格式的儲存空間非常大,它是用三個位元組描述一個畫素的顏色值,如果是 1080P 解析度的影象空間:1920 x 1080 x 3 = 6MB,就算轉換成 JPG 也有近 200KB,如果是每秒 12 幀用 JPG 也需要近 2.4MB/S 的頻寬,這頻寬在公網上傳輸是無法接受的。

視訊編碼器就是為了解決這個問題的,它會根據前後影象的變化做運動檢測,通過各種壓縮把變化的傳送到對方,1080P 進行過  H.264 編碼後頻寬也就在 200KB/S ~ 300KB/S 左右。在我們的技術方案裡面我們採用 H.264 作為預設編碼器(也在研究 H.265)。

1.1 H.264 編碼

前面提到視訊編碼器會根據影象的前後變化進行選擇性壓縮,因為剛開始接收端是沒有收到任何影象,那麼編碼器在開始壓縮的視訊時需要做個全量壓縮,這個全量壓縮在 H.264 中 I 幀,後面的視訊影象根據這個I幀來做增量壓縮,這些增量壓縮幀叫做 P 幀,H.264 為了防止丟包和減小頻寬還引入一種雙向預測編碼的 B 幀,B 幀以前面的 I 或 P 幀和後面的 P 幀為參考幀。H.264 為了防止中間 P 幀丟失視訊影象會一直錯誤它引入分組序列(GOP)編碼,也就是隔一段時間發一個全量 I 幀,上一個 I 幀與下一個 I 幀之間為一個分組 GOP。它們之間的關係如下圖:

1.2 馬賽克、卡頓和秒開

前面提到如果 GOP 分組中的P幀丟失會造成解碼端的影象發生錯誤,其實這個錯誤表現出來的就是馬賽克。因為中間連續的運動資訊丟失了,H.264 在解碼的時候會根據前面的參考幀來補齊,但是補齊的並不是真正的運動變化後的資料,這樣就會出現顏色色差的問題,這就是所謂的馬賽克現象,如圖:

這種現象不是我們想看到的。為了避免這類問題的發生,一般如果發現 P 幀或者 I 幀丟失,就不顯示本 GOP 內的所有幀,直到下一個 I 幀來後重新重新整理影象。但是 I 幀是按照幀週期來的,需要一個比較長的時間週期,如果在下一個 I 幀來之前不顯示後來的影象,那麼視訊就靜止不動了,這就是出現了所謂的卡頓現象。如果連續丟失的視訊幀太多造成解碼器無幀可解,也會造成嚴重的卡頓現象。視訊解碼端的卡頓現象和馬賽克現象都是因為丟幀引起的,最好的辦法就是讓幀儘量不丟

知道 H.264 的原理和分組編碼技術後所謂的秒開技術就比較簡單了,只要傳送方從最近一個 GOP 的 I 幀開發傳送給接收方,接收方就可以正常解碼完成的影象並立即顯示。但這會在視訊連線開始的時候多發一些幀資料造成播放延遲,只要在接收端播放的時候儘量讓過期的幀資料只解碼不顯示,直到當前視訊幀在播放時間範圍之內即可。

1.3 編碼延遲與位元速率

前面四個延遲裡面我們提到了編碼延遲,編碼延遲就是從 CCD 出來的 RGB 資料經過 H.264 編碼器編碼後出來的幀資料過程的時間。我們在一個 8 核 CPU 的普通客戶機測試了最新版本 X.264 的各個解析度的延遲,資料如下:

從上面可以看出,超清視訊的編碼延遲會達到 50ms,解決編碼延遲的問題只能去優化編碼器核心讓編碼的運算更快,我們也正在進行方面的工作。

在 1080P 解析度下,視訊編碼位元速率會達到 300KB/S,單個 I 幀資料大小達到 80KB,單個 P 幀可以達到 30KB,這對網路實時傳輸造成嚴峻的挑戰。

二、網路傳輸質量因素

實時互動視訊一個關鍵的環節就是網路傳輸技術,不管是早期 VoIP,還是現階段流行的視訊直播,其主要手段是通過 TCP/IP 協議來進行通訊。但是 IP 網路本來就是不可靠的傳輸網路,在這樣的網路傳輸視訊很容易造成卡頓現象和延遲。先來看看 IP 網路傳輸的幾個影響網路傳輸質量關鍵因素。

2.1 TCP 和 UDP

對直播有過了解的人都會認為做視訊傳輸首選的就是 TCP + RTMP,其實這是比較片面的。在大規模實時多媒體傳輸網路中,TCP 和 RTMP 都不佔優勢。TCP 是個擁塞公平傳輸的協議,它的擁塞控制都是為了保證網路的公平性而不是快速到達,我們知道,TCP 層只有順序到對應的報文才會提示應用層讀資料,如果中間有報文亂序或者丟包都會在 TCP 做等待,所以 TCP 的傳送視窗緩衝和重發機制在網路不穩定的情況下會造成延遲不可控,而且傳輸鏈路層級越多延遲會越大。

關於 TCP 的原理

關於 TCP 重發延遲

在實時傳輸中使用 UDP 更加合理,UDP 避免了 TCP 繁重的三次握手、四次揮手和各種繁雜的傳輸特性,只需要在 UDP 上做一層簡單的鏈路 QoS 監測和報文重發機制,實時性會比 TCP 好,這一點從 RTP 和 DDCP 協議可以證明這一點,我們正式參考了這兩個協議來設計自己的通訊協議。

2.2 延遲

要評估一個網路通訊質量的好壞和延遲一個重要的因素就是 Round-Trip Time(網路往返延遲),也就是 RTT。評估兩端之間的 RTT 方法很簡單,大致如下:

  1. 傳送端方一個帶本地時間戳 T1 的 ping 報文到接收端;

  2. 接收端收到 ping 報文,以 ping 中的時間戳 T1 構建一個攜帶 T1 的 pong 報文發往傳送端;

  3. 傳送端接收到接收端發了的 pong 時,獲取本地的時間戳 T2,用 T2 – T1 就是本次評測的 RTT。

示意圖如下:

上面步驟的探測週期可以設為 1 秒一次。為了防止網路突發延遲增大,我們採用了借鑑了 TCP 的 RTT 遺忘衰減的演算法來計算,假設原來的 RTT 值為 rtt,本次探測的 RTT 值為 keep_rtt。那麼新的 RTT 為:

new_rtt = (7 * rtt + keep_rtt) / 8

可能每次探測出來的 keep_rtt 會不一樣,我們需要會計算一個 RTT 的修正值 rtt_var,演算法如下:

new_rtt_var = (rtt_var * 3 + abs(rtt – keep_rtt)) / 4 

rtt_var 其實就是網路抖動的時間差值。

如果 RTT 太大,表示網路延遲很大。我們在端到端之間的網路路徑同時保持多條並且實時探測其網路狀態,如果 RTT 超出延遲範圍會進行傳輸路徑切換(本地網路擁塞除外)。

2.3 抖動和亂序

UDP 除了延遲外,還會出現網路抖動。什麼是抖動呢?舉個例子,假如我們每秒傳送 10 幀視訊幀,傳送方與接收方的延遲為 50MS,每幀資料用一個 UDP 報文來承載,那麼傳送方傳送資料的頻率是 100ms 一個數據報文,表示第一個報文傳送時刻 0ms, T2 表示第二個報文傳送時刻 100ms . . .,如果是理想狀態下接收方接收到的報文的時刻依次是(50ms, 150ms, 250ms, 350ms….),但由於傳輸的原因接收方收到的報文的相對時刻可能是(50ms, 120ms, 240ms, 360ms ….),接收方實際接收報文的時刻和理想狀態時刻的差值就是抖動。如下示意圖:

我們知道視訊必須按照嚴格是時間戳來播放,否則的就會出現視訊動作加快或者放慢的現象,如果我們按照接收到視訊資料就立即播放,那麼這種加快和放慢的現象會非常頻繁和明顯。也就是說網路抖動會嚴重影響視訊播放的質量,一般為了解決這個問題會設計一個視訊播放緩衝區,通過緩衝接收到的視訊幀,再按視訊幀內部的時間戳來播放既可以了。

UDP 除了小範圍的抖動以外,還是出現大範圍的亂序現象,就是後發的報文先於先發的報文到達接收方。亂序會造成視訊幀順序錯亂,一般解決的這個問題會在視訊播放緩衝區裡做一個先後排序功能讓先發送的報文先進行播放。

播放緩衝區的設計非常講究,如果緩衝過多幀資料會造成不必要的延遲,如果緩衝幀資料過少,會因為抖動和亂序問題造成播放無資料可以播的情況發生,會引起一定程度的卡頓。關於播放緩衝區內部的設計細節我們在後面的小節中詳細介紹。

2.4 丟包

UDP 在傳輸過程還會出現丟包,丟失的原因有多種,例如:網路出口不足、中間網路路由擁堵、socket 收發緩衝區太小、硬體問題、傳輸損耗問題等等。在基於 UDP 視訊傳輸過程中,丟包是非常頻繁發生的事情,丟包會造成視訊解碼器丟幀,從而引起視訊播放卡頓。這也是大部分視訊直播用 TCP 和 RTMP 的原因,因為 TCP 底層有自己的重傳機制,可以保證在網路正常的情況下視訊在傳輸過程不丟。基於 UDP 丟包補償方式一般有以下幾種:

報文冗餘

報文冗餘很好理解,就是一個報文在傳送的時候傳送 2 次或者多次。這個做的好處是簡單而且延遲小,壞處就是需要額外 N 倍(N 取決於傳送的次數)的頻寬。

FEC

 Forward Error Correction,即向前糾錯演算法,常用的演算法有糾刪碼技術(EC),在分散式儲存系統中比較常見。最簡單的就是 A B 兩個報文進行 XOR(與或操作)得到 C,同時把這三個報文發往接收端,如果接收端只收到 AC,通過 A 和 C 的 XOR 操作就可以得到 B 操作。這種方法相對增加的額外頻寬比較小,也能防止一定的丟包,延遲也比較小,通常用於實時語音傳輸上。對於  1080P 300KB/S 位元速率的超清晰視訊,哪怕是增加 20% 的額外頻寬都是不可接受的,所以視訊傳輸不太建議採用 FEC 機制。

丟包重傳

丟包重傳有兩種方式,一種是 push 方式,一種是 pull 方式。Push 方式是傳送方沒有收到接收方的收包確認進行週期性重傳,TCP 用的是 push 方式。pull 方式是接收方發現報文丟失後傳送一個重傳請求給傳送方,讓傳送方重傳丟失的報文。丟包重傳是按需重傳,比較適合視訊傳輸的應用場景,不會增加太對額外的頻寬,但一旦丟包會引來至少一個 RTT 的延遲。

2.5 MTU 和最大 UDP

IP 網定義單個 IP 報文最大的大小,常用 MTU 情況如下:

超通道 65535

16Mb/s 令牌環 179144

Mb/s 令牌環 4464

FDDI 4352

乙太網 1500

IEEE 802.3/802.2 1492

X.25 576

點對點(低時延)296

紅色的是 Internet 使用的上網方式,其中 X.25 是個比較老的上網方式,主要是利用 ISDN 或者電話線上網的裝置,也不排除有些家用路由器沿用 X.25 標準來設計。所以我們必須清晰知道每個使用者端的 MTU 多大,簡單的辦法就是在初始化階段用各種大小的 UDP 報文來探測 MTU 的大小。MTU 的大小會影響到我們視訊幀分片的大小,視訊幀分片的大小其實就是單個 UDP 報文最大承載的資料大小。

分片大小 = MTU – IP 頭大小 – UDP 頭大小 – 協議頭大小;

IP 頭大小 = 20 位元組, UDP 頭大小 = 8 位元組。

為了適應網路路由器小包優先的特性,我們如果得到的分片大小超過 800 時,會直接預設成 800 大小的分片。

三、傳輸模型

我們根據視訊編碼和網路傳輸得到特性對 1080P 超清視訊的實時傳輸設計了一個自己的傳輸模型,這個模型包括一個根據網路狀態自動位元速率的編解碼器物件、一個網路傳送模組、一個網路接收模組和一個 UDP 可靠到達的協議模型。各個模組的關係示意圖如下:

3.1 通訊協議

先來看通訊協議,我們定義的通訊協議分為三個階段:接入協商階段、傳輸階段、斷開階段。

接入協商階段:

主要是傳送端發起一個視訊傳輸接入請求,攜帶本地的視訊的當前狀態、起始幀序號、時間戳和 MTU 大小等,接收方在收到這個請求後,根據請求中視訊資訊初始化本地的接收通道,並對本地 MTU 和傳送端 MTU 進行比較取兩者中較小的回送給傳送方, 讓傳送方按協商後的 MTU 來分片。示意圖如下:

傳輸階段:

傳輸階段有幾個協議,一個測試量 RTT 的 PING/PONG 協議、攜帶視訊幀分片的資料協議、資料反饋協議和傳送端同步糾正協議。其中資料反饋協議是由接收反饋給傳送方的,攜帶接收方已經接收到連續幀的報文 ID、幀 ID 和請求重傳的報文 ID 序列。同步糾正協議是由傳送端主動丟棄傳送視窗緩衝區中的報文後要求接收方同步到當前傳送視窗位置,防止在傳送主動丟棄幀資料後接收方一直要求傳送方重發丟棄的資料。示意圖如下:

斷開階段:

就一個斷開請求和一個斷開確認,傳送方和接收方都可以發起斷開請求。

3.2 傳送

傳送主要包括視訊幀分片演算法、傳送視窗緩衝區、擁塞判斷演算法、過期幀丟棄演算法和重傳。先一個個來介紹。

幀分片

前面我們提到 MTU 和視訊幀大小,在 1080P 下大部分視訊幀的大小都大於 UDP 的 MTU 大小,那麼就需要對幀進行分片,分片的方法很簡單,按照先連線過程協商後的 MTU 大小來確定分片大小(確定分片大小的演算法在 MTU 小節已經介紹過),然後將 幀資料按照分片大小切分成若干份,每一份分片以 segment 報文形式發往接收方。

重傳

重傳比較簡單,我們採用 pull 方式來實現重傳,當接收方發生丟包,如果丟包的時刻 T1 + rtt_var< 接收方當前的時刻 T2,就認為是丟包了,這個時候就會把所有滿足這個條件丟失的報文 ID 構建一個 segment ack 反饋給傳送方,傳送方收到這個反饋根據 ID 到重發視窗緩衝區中查詢對應的報文重發即可。

為什麼要間隔一個 rtt_var 才認為是丟包了?因為報文是有可能亂序到達,所有要等待一個抖動週期後認為丟失的報文還沒有來才確認是報文丟失了,如果檢測到丟包立即傳送反饋要求重傳,有可能會讓傳送端多發資料,造成頻寬讓費和網路擁塞。

傳送視窗緩衝區

傳送視窗緩衝區儲存這所有正在傳送且沒有得到傳送方連續 ID 確認的報文。當接收方反饋最新的連續報文 ID,傳送視窗緩衝就會刪除所有小於最新反饋連續的報文 ID,傳送視窗緩衝區緩衝的報文都是為了重發而存在。這裡解釋下接收方反饋的連續的報文 ID,舉個例子,假如傳送方傳送了 1. 2. 3. 4. 5,接收方收到 1.2. 4. 5。這個時候最小連續 ID = 2,如果後面又來了 3,那麼接收方最小連續 ID = 5。

擁塞判斷

我們把當前時間戳記為 curr_T,把傳送視窗緩衝區中最老的報文的時間戳記為 oldest_T,它們之間的間隔記為 delay,那麼

delay = curr_T - oldest_T

在編碼器請求傳送模組傳送新的視訊幀時,如果 delay > 擁塞閾值 Tn,我們就認為網路擁塞了,這個時候會根據最近 20 秒接收端確認收到的資料大小計算一個頻寬值,並把這個頻寬值反饋給編碼器,編碼器收到反饋後,會根據頻寬調整編碼位元速率。如果多次發生要求降低位元速率的反饋,我們會縮小影象的解析度來保證視訊的流暢性和實時性。Tn 的值可以通過 rtt 和 rtt_var 來確定。

但是網路可能階段性擁塞,過後卻恢復正常,我們設計了一個定時器來定時檢查傳送方的重發報文數量和 delay,如果發現恢復正常,會逐步增大編碼器編碼位元速率,讓視訊恢復到指定的解析度和清晰度。

過期幀丟棄

在網路擁塞時可能傳送視窗緩衝區中有很多報文正在傳送,為了緩解擁塞和減少延遲我們會對整個緩衝區做檢查,如果有超過一定閾值時間的 H.264 GOP 分組存在,我們會將這個 GOP 所有幀的報文從視窗緩衝區移除。並將它下一個 GOP 分組的 I 的幀 ID 和報文 ID 通過 wnd sync 協議同步到接收端上,接收端接收到這個協議,會將最新連續 ID 設定成同步過來的 ID。這裡必須要說明的是如果頻繁出現過期幀丟棄的動作會造成卡頓,說明當前網路不適合傳輸高解析度視訊,可以直接將視訊設成更小的解析度

3.3 接收

接收主要包括丟包管理、播放緩衝區、緩衝時間評估和播放控制,都是圍繞播放緩衝區來實現的,一個個來介紹。

丟包管理

丟包管理包括丟包檢測和丟失報文 ID 管理兩部分。丟包檢測過程大致是這樣的,假設播放緩衝區的最大報文 ID 為 max_id,網路上新收到的報文 ID 為 new_id,如果 max_id + 1 < new_id,那麼可能發生丟包,就會將 [max_id + 1, new_id -1] 區間中所有的 ID 和當前時刻作為 K/V 對加入到丟包管理器當中。如果 new_id < max_id,那麼就將丟包管理中的 new_id 對應的 K/V 對刪除,表示丟失的報文已經收到。當收包反饋條件滿足時,會掃描整個丟包管理,將達到請求重傳的丟包 ID 加入到 segment ack 反饋訊息中併發往傳送方請求重傳,如果 ID 被請求了重傳,會將當前時刻設定為 K/V 對中,增加對應報文的重傳計數器 count,這個掃描過程會統計對包管理器中單個重發最多報文的重發次數 resend_count。

緩衝時間評估

在前面的抖動與亂序小節中我們提到播放端有個緩衝區,這個緩衝區過大時延遲就大,緩衝區過小時又會出現卡頓現象,我們針對這個問題設計了一個緩衝時間評估的演算法。緩衝區評估先會算出一個 cache timer,cache timer 是通過掃描對包管理得到的 resend count 和 rtt 得到的,我們知道從請求重傳報文到接收方收到重傳的報文的時間間隔是一個 RTT 週期,所以 cache timer 的計算方式如下。

cache timer = (2 * resend_count+ 1) * (rtt + rtt_var) / 2

有可能 cache timer 計算出來很小(小於視訊幀之間間隔時間 frame timer),那麼 cache timer = frame timer,也就是說網路再好,緩衝區緩衝區至少 1 幀視訊的資料,否則緩衝區是毫無意義的。

如果單位時間內沒有丟包重傳發生,那麼 cache timer 會做適當的縮小,這樣做的好處是當網路間歇性波動造成 cache timer 很大,恢復正常後 cache timer 也能恢復到相對小位置,縮減不必要的緩衝區延遲。

播放緩衝區

我們設計的播放緩衝區是按幀 ID 為索引的有序迴圈陣列,陣列內部的單元是視訊幀的具體資訊:幀 ID、分片數、幀型別等。緩衝區有兩個狀態:waiting 和 playing,waiting 狀態表示緩衝區處於緩衝狀態,不能進行視訊播放直到緩衝區中的幀資料達到一定的閾值。Playing 狀態表示緩衝區進入播放狀態,播放模組可以從中取出幀進行解碼播放。我們來介紹下這兩個狀態的切換關係:

  1. 當緩衝區建立時會被初始化成 waiting 狀態。

  2. 當緩衝區中緩衝的最新幀與最老幀的時間戳間隔 > cache timer 時,進入 playing 狀態並更當前時刻設成播放絕對時間戳 play ts。

  3. 當緩衝區處於 playing 狀態且緩衝區是沒有任何幀資料,進入 waiting 狀態直到觸發第 2 步。

播放緩衝區的目的就是防止抖動和應對丟包重傳,讓視訊流能按照採集時的頻率進行播放,播放緩衝區的設計極其複雜,需要考慮的因素很多,實現的時候需要慎重。

播放控制

接收端最後一個環節就是播放控制,播放控制就是從緩衝區中拿出有效的視訊幀進行解碼播放。但是怎麼拿?什麼時候拿?我們知道視訊是按照視訊幀從傳送端攜帶過來的相對時間戳來做播放,我們每一幀視訊都有一個相對時間戳 TS,根據幀與幀之間的 TS 的差值就可以知道上一幀和下一幀播放的時間間隔,假如上一幀播放的絕對時間戳為 prev_play_ts,相對時間戳為 prev_ts,當前系統時間戳為 curr_play_ts,當前緩衝區中最小序號幀的相對時間戳為  frame_ts,只要滿足:

Prev_play_ts + (frame_ts – prev_ts) < curr_play_ts 且這一幀資料是所有的報文都收齊了

這兩個條件就可以進行解碼播放,取出幀資料後將 Prev_play_ts = cur_play_ts,但更新 prev_ts 有些講究,為了防止緩衝延遲問題我們做了特殊處理。

如果 frame_ts + cache timer < 緩衝區中最大幀的 ts,表明緩衝的時延太長,則 prev_ts = 緩衝區中最大幀的 ts - cache timer。 否則 prev_ts = frame_ts。

四、測量

再好的模型也需要有合理的測量方式來驗證,在多媒體這種具有時效性的傳輸領域尤其如此。一般在實驗室環境我們採用 netem 來進行模擬公網的各種情況進行測試,如果在模擬環境已經達到一個比較理想的狀態後會組織相關人員在公網上進行測試。下面來介紹怎麼來測試我們整個傳輸模型的。

4.1 netem 模擬測試

Netem 是 Linux 核心提供的一個網路模擬工具,可以設定延遲、丟包、抖動、亂序和包損壞等,基本能模擬公網大部分網路情況。

關於 netem 可以訪問它的官網

我們在實驗環境搭建了一個基於伺服器和客戶端模式的測試環境,下面是測試環境的拓撲關係圖:

我們利用 Linux 來做一個路由器,伺服器和收發端都連線到這個路由器上,伺服器負責客戶端的登記、資料轉發、資料緩衝等,相當於一個簡易的流媒體伺服器。Sender 負責媒體編碼和傳送,receiver 負責接收和媒體播放。為了測試延遲,我們把 sender 和 receiver 執行在同一個 PC 機器上,在 sender 從 CCD 獲取到 RGB 影象時打一個時間戳,並把這個時間戳記錄在這一幀資料的報文發往 server 和 receiver,receiver 收到並解碼顯示這幀資料時,通過記錄的時間戳可以得到整個過程的延遲。我們的測試用例是用 1080P 位元速率為 300KB/S 視訊流,在 router 用 netem 上模擬了以下幾種網路狀態:

  1. 環路延遲 10m,無丟包,無抖動,無亂序
  2. 環路延遲 30ms,丟包 0.5%,抖動 5ms, 2% 亂序
  3. 環路延遲 60ms,丟包 1%,抖動 20ms, 3% 亂序,0.1% 包損壞
  4. 環路延遲 100ms,丟包 4%,抖動 50ms, 4% 亂序,0.1% 包損壞
  5. 環路延遲 200ms,丟包 10%,抖動 70ms, 5% 亂序,0.1% 包損壞
  6. 環路延遲 300ms,丟包 15%,抖動 100ms, 5% 亂序,0.1% 包損壞

因為傳輸機制採用的是可靠到達,那麼檢驗傳輸機制有效的引數就是視訊延遲,我們統計 2 分鐘週期內最大延遲,以下是各種情況的延遲曲線圖:

從上圖可以看出,如果網路控制在環路延遲在 200ms 丟包在 10% 以下,可以讓視訊延遲在 500ms 毫秒以下,這並不是一個對網路質量要求很苛刻的條件。所以我們在後臺的媒體服務部署時,儘量讓客戶端到媒體伺服器之間的網路滿足這個條件,如果網路環路延遲在 300ms 丟包 15% 時,依然可以做到小於 1 秒的延遲,基本能滿足雙向互動交流。

4.2 公網測試

公網測試相對比較簡單,我們將 Server 部署到 UCloud 雲上,傳送端用的是上海電信 100M 公司寬頻,接收端用的是河北聯通 20M 小區寬頻,環路延遲在 60ms 左右。總體測試下來 1080P 在接收端觀看視訊流暢自然,無抖動,無卡頓,延遲統計平均在 180ms 左右。

五、坑

在整個 1080P 超清視訊的傳輸技術實現過程中,我們遇到過比較多的坑。大致如下:

Socket 緩衝區問題

我們前期開發階段都是使用 socket 預設的緩衝區大小,由於 1080P 影象幀的資料非常巨大(關鍵幀超過 80KB),我們發現在在內網測試沒有設定丟包的網路環境發現接收端有嚴重的丟包,經查證是 socket 收發緩衝區太小造成丟包的,後來我們把 socket 緩衝區設定到 128KB 大小,問題解決了。

H.264 B 幀延遲問題

前期我們為了節省傳輸頻寬和防丟包開了 B 幀編碼,由於 B 幀是前後雙向預測編碼的,會在編碼期滯後幾個幀間隔時間,引起了超過 100ms 的編碼延時,後來我們為了實時性乾脆把 B 幀編碼選項去掉。

Push 方式丟包重傳

在設計階段我們曾經使用傳送端主動 push 方式來解決丟包重傳問題,在測試過程發現在丟包頻繁發生的情況下至少增加了 20% 的頻寬消耗,而且容易帶來延遲和網路擁塞。後來幾經論證用現在的 pull 模式來進行丟包重傳。

Segment 記憶體問題

在設計階段我們對每個視訊緩衝區中的幀資訊都是動態分配記憶體物件的,由於 1080P 在傳輸過程中每秒會發送 400 - 500 個 UDP 報文,在 PC 端長時間執行容易出現記憶體碎片,在伺服器端出現莫名其妙的 clib 假記憶體洩露和併發問題。我們實現了一個 memory slab 管理頻繁申請和釋放記憶體的問題。

音訊和視訊資料傳輸問題

在早期的設計之中我們借鑑了 FLV 的方式將音訊和視訊資料用同一套傳輸演算法傳輸,好處就是容易實現,但在網路波動的情況下容易引起聲音卡頓,也無法根據音訊的特性優化傳輸。後來我們把音訊獨立出來,針對音訊的特性設計了一套低延遲高質量的音訊傳輸體系,定點對音訊進行傳輸優化。

後續的工作是重點放在媒體器多點分佈、多點併發傳輸、P2P 分發演算法的探索上,儘量減少延遲和服務頻寬成本,讓傳輸變的更高效和更低廉。

Q&A

提問:在優化到 500ms 方案中,哪一塊是最關鍵的?

袁榮喜:主要是丟包重傳 擁塞和播放緩衝這三者之間的協調工作最為關鍵,要兼顧延遲控制和視訊流暢性。

提問:多方視訊和單方有哪些區別,用到了 CDN 推流嗎?

袁榮喜:我們公司是做線上教育的,很多場景需要老師和學生交談,用 CDN 推流方式延遲很大,我們這個視訊主要是解決多方通訊之間交談延遲的問題。我們現在觀看放也有用 CDN 推流,但只是單純的觀看。我們也在研發基於 UDP 的觀看端分發協議,目前這部分工作還沒有完成。

參考閱讀