1郭宝、前言
實時視頻直播是很多技術團隊及架構師關注的問題,在實時性方面廷蓉,大部分直播是準實時的——存在 1-3 秒延遲全封。本文由袁榮喜分享其將1080P高清實時視屏直播延遲控制在 500ms 的背后的技術挑戰(zhàn)以及實踐結論等,期待與各同行共同討論桃犬、學習和進步刹悴。
2、內容概述
最近由于公司業(yè)務關系疫萤,需要一個在公網(wǎng)上能實時互動超清視頻的架構和技術方案颂跨。眾所周知,視頻直播用 CDN + RTMP 就可以滿足絕大部分視頻直播業(yè)務扯饶,我們也接觸了和測試了幾家 CDN 提供的方案恒削,單人直播沒有問題,一旦涉及到多人互動延遲非常大尾序,無法進行正常的互動交談钓丰。對于我們做在線教育的企業(yè)來說沒有互動的直播是毫無意義的,所以我們決定自己來構建一個超清晰(1080P)實時視頻的傳輸方案每币。
先來解釋下什么是實時視頻携丁,實時視頻就是視頻圖像從產生到消費完成整個過程人感覺不到延遲,只要符合這個要求的視頻業(yè)務都可以稱為實時視頻兰怠。
關于視頻的實時性歸納為三個等級:
偽實時:視頻消費延遲超過 3 秒梦鉴,單向觀看實時,通用架構是 CDN + RTMP + HLS揭保,現(xiàn)在基本上所有的直播都是這類技術肥橙。
準實時: 視頻消費延遲 1 ~ 3 秒,能進行雙方互動但互動有障礙秸侣。有些直播網(wǎng)站通過 TCP/UDP + FLV 已經(jīng)實現(xiàn)了這類技術存筏,YY 直播屬于這類技術宠互。
真實時:視頻消費延遲 < 1秒,平均 500 毫秒椭坚。這類技術是真正的實時技術予跌,人和人交談沒有明顯延遲感。QQ善茎、微信券册、Skype 和 WebRTC 等都已經(jīng)實現(xiàn)了這類技術。
市面上大部分真實時視頻都是 480P 或者 480P 以下的實時傳輸方案垂涯,用于在線教育和線上教學有一定困難汁掠,而且有時候流暢度是個很大的問題。在實現(xiàn)超清晰實時視頻我們做了大量嘗試性的研究和探索集币,在這里會把大部分細節(jié)分享出來。
3翠忠、縮短視頻直播延遲從何入手鞠苟?
要實時就要縮短延遲,要縮短延遲就要知道延遲是怎么產生的秽之,視頻從產生当娱、編碼、傳輸?shù)阶詈蟛シ畔M考榨,各個環(huán)節(jié)都會產生延遲跨细,總體歸納為下圖:
成像延遲:一般的技術是毫無為力的,涉及到 CCD 相關的硬件河质,現(xiàn)在市面上最好的 CCD冀惭,一秒鐘 50 幀,成像延遲也在 20 毫秒左右掀鹅,一般的 CCD 只有 20 ~ 25 幀左右散休,成像延遲 40 ~ 50 毫秒。
編碼延遲:和編碼器有關系乐尊,在接下來的小結介紹戚丸,一般優(yōu)化的空間比較小。
我們著重針對網(wǎng)絡延遲和播放緩沖延遲來進行設計扔嵌,在介紹整個技術細節(jié)之前先來了解下視頻編碼和網(wǎng)絡傳輸相關的知識和特點限府。
4、視頻編碼那些事
我們知道從 CCD 采集到的圖像格式一般的 RGB 格式的(BMP)痢缎,這種格式的存儲空間非常大胁勺,它是用三個字節(jié)描述一個像素的顏色值,如果是 1080P 分辨率的圖像空間:1920 x 1080 x 3 = 6MB牺弄,就算轉換成 JPG 也有近 200KB姻几,如果是每秒 12 幀用 JPG 也需要近 2.4MB/S 的帶寬宜狐,這帶寬在公網(wǎng)上傳輸是無法接受的。
視頻編碼器就是為了解決這個問題的蛇捌,它會根據(jù)前后圖像的變化做運動檢測抚恒,通過各種壓縮把變化的發(fā)送到對方,1080P 進行過 H.264 編碼后帶寬也就在 200KB/S ~ 300KB/S 左右络拌。在我們的技術方案里面我們采用 H.264 作為默認編碼器(也在研究 H.265)俭驮。
1H.264 編碼
前面提到視頻編碼器會根據(jù)圖像的前后變化進行選擇性壓縮,因為剛開始接收端是沒有收到任何圖像春贸,那么編碼器在開始壓縮的視頻時需要做個全量壓縮混萝,這個全量壓縮在 H.264 中 I 幀,后面的視頻圖像根據(jù)這個I幀來做增量壓縮萍恕,這些增量壓縮幀叫做 P 幀逸嘀,H.264 為了防止丟包和減小帶寬還引入一種雙向預測編碼的 B 幀,B 幀以前面的 I 或 P 幀和后面的 P 幀為參考幀允粤。H.264 為了防止中間 P 幀丟失視頻圖像會一直錯誤它引入分組序列(GOP)編碼崭倘,也就是隔一段時間發(fā)一個全量 I 幀,上一個 I 幀與下一個 I 幀之間為一個分組 GOP类垫。
它們之間的關系如下圖:
PS:在實時視頻當中最好不要加入 B 幀司光,因為 B 幀是雙向預測,需要根據(jù)后面的視頻幀來編碼悉患,這會增大編解碼延遲残家。
2馬賽克、卡頓和秒開
前面提到如果 GOP 分組中的P幀丟失會造成解碼端的圖像發(fā)生錯誤,其實這個錯誤表現(xiàn)出來的就是馬賽克售躁。因為中間連續(xù)的運動信息丟失了坞淮,H.264 在解碼的時候會根據(jù)前面的參考幀來補齊,但是補齊的并不是真正的運動變化后的數(shù)據(jù)迂求,這樣就會出現(xiàn)顏色色差的問題碾盐,這就是所謂的馬賽克現(xiàn)象,如圖:
這種現(xiàn)象不是我們想看到的揩局。為了避免這類問題的發(fā)生毫玖,一般如果發(fā)現(xiàn) P 幀或者 I 幀丟失,就不顯示本 GOP 內的所有幀凌盯,直到下一個 I 幀來后重新刷新圖像付枫。但是 I 幀是按照幀周期來的,需要一個比較長的時間周期驰怎,如果在下一個 I 幀來之前不顯示后來的圖像阐滩,那么視頻就靜止不動了,這就是出現(xiàn)了所謂的卡頓現(xiàn)象县忌。如果連續(xù)丟失的視頻幀太多造成解碼器無幀可解掂榔,也會造成嚴重的卡頓現(xiàn)象继效。視頻解碼端的卡頓現(xiàn)象和馬賽克現(xiàn)象都是因為丟幀引起的,最好的辦法就是讓幀盡量不丟装获。
知道 H.264 的原理和分組編碼技術后所謂的秒開技術就比較簡單了瑞信,只要發(fā)送方從最近一個 GOP 的 I 幀開發(fā)發(fā)送給接收方,接收方就可以正常解碼完成的圖像并立即顯示穴豫。但這會在視頻連接開始的時候多發(fā)一些幀數(shù)據(jù)造成播放延遲凡简,只要在接收端播放的時候盡量讓過期的幀數(shù)據(jù)只解碼不顯示,直到當前視頻幀在播放時間范圍之內即可精肃。
3編碼延遲與碼率
前面四個延遲里面我們提到了編碼延遲秤涩,編碼延遲就是從 CCD 出來的 RGB 數(shù)據(jù)經(jīng)過 H.264 編碼器編碼后出來的幀數(shù)據(jù)過程的時間。我們在一個 8 核 CPU 的普通客戶機測試了最新版本 X.264 的各個分辨率的延遲司抱,數(shù)據(jù)如下:
從上面可以看出筐眷,超清視頻的編碼延遲會達到 50ms,解決編碼延遲的問題只能去優(yōu)化編碼器內核讓編碼的運算更快习柠,我們也正在進行方面的工作浊竟。
在 1080P 分辨率下,視頻編碼碼率會達到 300KB/S津畸,單個 I 幀數(shù)據(jù)大小達到 80KB,單個 P 幀可以達到 30KB必怜,這對網(wǎng)絡實時傳輸造成嚴峻的挑戰(zhàn)肉拓。
5、網(wǎng)絡傳輸質量因素
實時互動視頻一個關鍵的環(huán)節(jié)就是網(wǎng)絡傳輸技術,不管是早期 VoIP梳庆,還是現(xiàn)階段流行的視頻直播暖途,其主要手段是通過 TCP/IP 協(xié)議來進行通信。但是 IP 網(wǎng)絡本來就是不可靠的傳輸網(wǎng)絡膏执,在這樣的網(wǎng)絡傳輸視頻很容易造成卡頓現(xiàn)象和延遲驻售。先來看看 IP 網(wǎng)絡傳輸?shù)膸讉€影響網(wǎng)絡傳輸質量關鍵因素。
1TCP 和 UDP
對直播有過了解的人都會認為做視頻傳輸首選的就是 TCP + RTMP更米,其實這是比較片面的欺栗。在大規(guī)模實時多媒體傳輸網(wǎng)絡中,TCP 和 RTMP 都不占優(yōu)勢征峦。TCP 是個擁塞公平傳輸?shù)膮f(xié)議迟几,它的擁塞控制都是為了保證網(wǎng)絡的公平性而不是快速到達,我們知道栏笆,TCP 層只有順序到對應的報文才會提示應用層讀數(shù)據(jù)类腮,如果中間有報文亂序或者丟包都會在 TCP 做等待,所以 TCP 的發(fā)送窗口緩沖和重發(fā)機制在網(wǎng)絡不穩(wěn)定的情況下會造成延遲不可控蛉加,而且傳輸鏈路層級越多延遲會越大蚜枢。
關于 TCP 的原理:http://www.52im.net/thread-513-1-1.html
關于 TCP 重發(fā)延遲:http://blog.jobbole.com/85508/
在實時傳輸中使用 UDP 更加合理缸逃,UDP 避免了 TCP 繁重的三次握手、四次揮手和各種繁雜的傳輸特性厂抽,只需要在 UDP 上做一層簡單的鏈路 QoS 監(jiān)測和報文重發(fā)機制需频,實時性會比 TCP 好,這一點從 RTP 和 DDCP 協(xié)議可以證明這一點修肠,我們正式參考了這兩個協(xié)議來設計自己的通信協(xié)議贺辰。
2延遲
要評估一個網(wǎng)絡通信質量的好壞和延遲一個重要的因素就是 Round-Trip Time(網(wǎng)絡往返延遲),也就是 RTT。
評估兩端之間的 RTT 方法很簡單嵌施,大致如下:
發(fā)送端方一個帶本地時間戳 T1 的 ping 報文到接收端饲化;
接收端收到 ping 報文,以 ping 中的時間戳 T1 構建一個攜帶 T1 的 pong 報文發(fā)往發(fā)送端吗伤;
發(fā)送端接收到接收端發(fā)了的 pong 時吃靠,獲取本地的時間戳 T2,用 T2 – T1 就是本次評測的 RTT足淆。
示意圖如下:
上面步驟的探測周期可以設為 1 秒一次巢块。為了防止網(wǎng)絡突發(fā)延遲增大,我們采用了借鑒了 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 其實就是網(wǎng)絡抖動的時間差值。
如果 RTT 太大靠欢,表示網(wǎng)絡延遲很大廊敌。我們在端到端之間的網(wǎng)絡路徑同時保持多條并且實時探測其網(wǎng)絡狀態(tài),如果 RTT 超出延遲范圍會進行傳輸路徑切換(本地網(wǎng)絡擁塞除外)门怪。
3抖動和亂序
UDP 除了延遲外骡澈,還會出現(xiàn)網(wǎng)絡抖動。
什么是抖動呢掷空?舉個例子肋殴,假如我們每秒發(fā)送 10 幀視頻幀,發(fā)送方與接收方的延遲為 50MS坦弟,每幀數(shù)據(jù)用一個 UDP 報文來承載疼电,那么發(fā)送方發(fā)送數(shù)據(jù)的頻率是 100ms 一個數(shù)據(jù)報文,表示第一個報文發(fā)送時刻 0ms减拭, T2 表示第二個報文發(fā)送時刻 100ms . . .蔽豺,如果是理想狀態(tài)下接收方接收到的報文的時刻依次是(50ms, 150ms, 250ms, 350ms….),但由于傳輸?shù)脑蚪邮辗绞盏降膱笪牡南鄬r刻可能是(50ms, 120ms, 240ms, 360ms ….)拧粪,接收方實際接收報文的時刻和理想狀態(tài)時刻的差值就是抖動修陡。
如下示意圖:
我們知道視頻必須按照嚴格是時間戳來播放沧侥,否則的就會出現(xiàn)視頻動作加快或者放慢的現(xiàn)象,如果我們按照接收到視頻數(shù)據(jù)就立即播放魄鸦,那么這種加快和放慢的現(xiàn)象會非常頻繁和明顯宴杀。也就是說網(wǎng)絡抖動會嚴重影響視頻播放的質量,一般為了解決這個問題會設計一個視頻播放緩沖區(qū)拾因,通過緩沖接收到的視頻幀旺罢,再按視頻幀內部的時間戳來播放既可以了。
UDP 除了小范圍的抖動以外绢记,還是出現(xiàn)大范圍的亂序現(xiàn)象扁达,就是后發(fā)的報文先于先發(fā)的報文到達接收方。亂序會造成視頻幀順序錯亂蠢熄,一般解決的這個問題會在視頻播放緩沖區(qū)里做一個先后排序功能讓先發(fā)送的報文先進行播放跪解。
播放緩沖區(qū)的設計非常講究,如果緩沖過多幀數(shù)據(jù)會造成不必要的延遲签孔,如果緩沖幀數(shù)據(jù)過少叉讥,會因為抖動和亂序問題造成播放無數(shù)據(jù)可以播的情況發(fā)生,會引起一定程度的卡頓饥追。關于播放緩沖區(qū)內部的設計細節(jié)我們在后面的小節(jié)中詳細介紹图仓。
4丟包
UDP 在傳輸過程還會出現(xiàn)丟包,丟失的原因有多種但绕,例如:網(wǎng)絡出口不足透绩、中間網(wǎng)絡路由擁堵、socket 收發(fā)緩沖區(qū)太小壁熄、硬件問題、傳輸損耗問題等等碳竟。在基于 UDP 視頻傳輸過程中草丧,丟包是非常頻繁發(fā)生的事情,丟包會造成視頻解碼器丟幀莹桅,從而引起視頻播放卡頓昌执。這也是大部分視頻直播用 TCP 和 RTMP 的原因,因為 TCP 底層有自己的重傳機制诈泼,可以保證在網(wǎng)絡正常的情況下視頻在傳輸過程不丟懂拾。基于 UDP 丟包補償方式一般有以下幾種:
報文冗余:
報文冗余很好理解铐达,就是一個報文在發(fā)送的時候發(fā)送 2 次或者多次岖赋。這個做的好處是簡單而且延遲小,壞處就是需要額外 N 倍(N 取決于發(fā)送的次數(shù))的帶寬瓮孙。
FEC:
Forward Error Correction唐断,即向前糾錯算法选脊,常用的算法有糾刪碼技術(EC),在分布式存儲系統(tǒng)中比較常見脸甘。最簡單的就是 A B 兩個報文進行 XOR(與或操作)得到 C恳啥,同時把這三個報文發(fā)往接收端,如果接收端只收到 AC,通過 A 和 C 的 XOR 操作就可以得到 B 操作丹诀。這種方法相對增加的額外帶寬比較小魄咕,也能防止一定的丟包夺姑,延遲也比較小,通常用于實時語音傳輸上。對于 1080P 300KB/S 碼率的超清晰視頻做盅,哪怕是增加 20% 的額外帶寬都是不可接受的,所以視頻傳輸不太建議采用 FEC 機制溪烤。
丟包重傳:
丟包重傳有兩種方式县昂,一種是 push 方式,一種是 pull 方式棍弄。Push 方式是發(fā)送方?jīng)]有收到接收方的收包確認進行周期性重傳望薄,TCP 用的是 push 方式。pull 方式是接收方發(fā)現(xiàn)報文丟失后發(fā)送一個重傳請求給發(fā)送方呼畸,讓發(fā)送方重傳丟失的報文痕支。丟包重傳是按需重傳,比較適合視頻傳輸?shù)膽脠鼍奥粫黾犹珜︻~外的帶寬卧须,但一旦丟包會引來至少一個 RTT 的延遲。
5MTU 和最大 UDP
IP 網(wǎng)定義單個 IP 報文最大的大小儒陨,常用 MTU 情況如下:
超通道 65535
16Mb/s 令牌環(huán) 179144
Mb/s 令牌環(huán) 4464
FDDI 4352
以太網(wǎng) 1500
IEEE 802.3/802.2 1492
X.25 576
點對點(低時延)296
紅色的是 Internet 使用的上網(wǎng)方式花嘶,其中 X.25 是個比較老的上網(wǎng)方式,主要是利用 ISDN 或者電話線上網(wǎng)的設備蹦漠,也不排除有些家用路由器沿用 X.25 標準來設計椭员。所以我們必須清晰知道每個用戶端的 MTU 多大,簡單的辦法就是在初始化階段用各種大小的 UDP 報文來探測 MTU 的大小笛园。MTU 的大小會影響到我們視頻幀分片的大小隘击,視頻幀分片的大小其實就是單個 UDP 報文最大承載的數(shù)據(jù)大小。
分片大小 = MTU – IP 頭大小 – UDP 頭大小 – 協(xié)議頭大小;IP 頭大小 = 20 字節(jié)研铆, UDP 頭大小 = 8 字節(jié)埋同。
為了適應網(wǎng)絡路由器小包優(yōu)先的特性,我們如果得到的分片大小超過 800 時棵红,會直接默認成 800 大小的分片凶赁。
6、傳輸模型
我們根據(jù)視頻編碼和網(wǎng)絡傳輸?shù)玫教匦詫?1080P 超清視頻的實時傳輸設計了一個自己的傳輸模型,這個模型包括一個根據(jù)網(wǎng)絡狀態(tài)自動碼率的編解碼器對象哟冬、一個網(wǎng)絡發(fā)送模塊楼熄、一個網(wǎng)絡接收模塊和一個 UDP 可靠到達的協(xié)議模型。
各個模塊的關系示意圖如下:
1通信協(xié)議
先來看通信協(xié)議浩峡,我們定義的通信協(xié)議分為三個階段:接入?yún)f(xié)商階段可岂、傳輸階段、斷開階段翰灾。
接入?yún)f(xié)商階段:
主要是發(fā)送端發(fā)起一個視頻傳輸接入請求缕粹,攜帶本地的視頻的當前狀態(tài)、起始幀序號纸淮、時間戳和 MTU 大小等平斩,接收方在收到這個請求后,根據(jù)請求中視頻信息初始化本地的接收通道咽块,并對本地 MTU 和發(fā)送端 MTU 進行比較取兩者中較小的回送給發(fā)送方绘面, 讓發(fā)送方按協(xié)商后的 MTU 來分片。示意圖如下:
傳輸階段:
傳輸階段有幾個協(xié)議侈沪,一個測試量 RTT 的 PING/PONG 協(xié)議揭璃、攜帶視頻幀分片的數(shù)據(jù)協(xié)議、數(shù)據(jù)反饋協(xié)議和發(fā)送端同步糾正協(xié)議亭罪。其中數(shù)據(jù)反饋協(xié)議是由接收反饋給發(fā)送方的瘦馍,攜帶接收方已經(jīng)接收到連續(xù)幀的報文 ID、幀 ID 和請求重傳的報文 ID 序列应役。同步糾正協(xié)議是由發(fā)送端主動丟棄發(fā)送窗口緩沖區(qū)中的報文后要求接收方同步到當前發(fā)送窗口位置情组,防止在發(fā)送主動丟棄幀數(shù)據(jù)后接收方一直要求發(fā)送方重發(fā)丟棄的數(shù)據(jù)。示意圖如下:
斷開階段:
就一個斷開請求和一個斷開確認箩祥,發(fā)送方和接收方都可以發(fā)起斷開請求院崇。
2發(fā)送
發(fā)送主要包括視頻幀分片算法、發(fā)送窗口緩沖區(qū)袍祖、擁塞判斷算法底瓣、過期幀丟棄算法和重傳。先一個個來介紹盲泛。
幀分片:
前面我們提到 MTU 和視頻幀大小,在 1080P 下大部分視頻幀的大小都大于 UDP 的 MTU 大小键耕,那么就需要對幀進行分片寺滚,分片的方法很簡單,按照先連接過程協(xié)商后的 MTU 大小來確定分片大星邸(確定分片大小的算法在 MTU 小節(jié)已經(jīng)介紹過)村视,然后將 幀數(shù)據(jù)按照分片大小切分成若干份,每一份分片以 segment 報文形式發(fā)往接收方酒奶。
重傳:
重傳比較簡單蚁孔,我們采用 pull 方式來實現(xiàn)重傳奶赔,當接收方發(fā)生丟包,如果丟包的時刻 T1 + rtt_var< 接收方當前的時刻 T2杠氢,就認為是丟包了站刑,這個時候就會把所有滿足這個條件丟失的報文 ID 構建一個 segment ack 反饋給發(fā)送方,發(fā)送方收到這個反饋根據(jù) ID 到重發(fā)窗口緩沖區(qū)中查找對應的報文重發(fā)即可鼻百。
為什么要間隔一個 rtt_var 才認為是丟包了绞旅?因為報文是有可能亂序到達,所有要等待一個抖動周期后認為丟失的報文還沒有來才確認是報文丟失了温艇,如果檢測到丟包立即發(fā)送反饋要求重傳因悲,有可能會讓發(fā)送端多發(fā)數(shù)據(jù),造成帶寬讓費和網(wǎng)絡擁塞勺爱。
發(fā)送窗口緩沖區(qū):
發(fā)送窗口緩沖區(qū)保存這所有正在發(fā)送且沒有得到發(fā)送方連續(xù) ID 確認的報文晃琳。當接收方反饋最新的連續(xù)報文 ID,發(fā)送窗口緩沖就會刪除所有小于最新反饋連續(xù)的報文 ID琐鲁,發(fā)送窗口緩沖區(qū)緩沖的報文都是為了重發(fā)而存在卫旱。這里解釋下接收方反饋的連續(xù)的報文 ID,舉個例子绣否,假如發(fā)送方發(fā)送了 1. 2. 3. 4. 5誊涯,接收方收到 1.2. 4. 5。這個時候最小連續(xù) ID = 2蒜撮,如果后面又來了 3暴构,那么接收方最小連續(xù) ID = 5。
擁塞判斷:
我們把當前時間戳記為 curr_T段磨,把發(fā)送窗口緩沖區(qū)中最老的報文的時間戳記為 oldest_T取逾,它們之間的間隔記為 delay,那么:
delay = curr_T - oldest_T
在編碼器請求發(fā)送模塊發(fā)送新的視頻幀時苹支,如果 delay > 擁塞閾值 Tn砾隅,我們就認為網(wǎng)絡擁塞了,這個時候會根據(jù)最近 20 秒接收端確認收到的數(shù)據(jù)大小計算一個帶寬值债蜜,并把這個帶寬值反饋給編碼器晴埂,編碼器收到反饋后,會根據(jù)帶寬調整編碼碼率寻定。如果多次發(fā)生要求降低碼率的反饋儒洛,我們會縮小圖像的分辨率來保證視頻的流暢性和實時性。Tn 的值可以通過 rtt 和 rtt_var 來確定狼速。
但是網(wǎng)絡可能階段性擁塞琅锻,過后卻恢復正常,我們設計了一個定時器來定時檢查發(fā)送方的重發(fā)報文數(shù)量和 delay,如果發(fā)現(xiàn)恢復正常恼蓬,會逐步增大編碼器編碼碼率惊完,讓視頻恢復到指定的分辨率和清晰度。
過期幀丟棄:
在網(wǎng)絡擁塞時可能發(fā)送窗口緩沖區(qū)中有很多報文正在發(fā)送处硬,為了緩解擁塞和減少延遲我們會對整個緩沖區(qū)做檢查小槐,如果有超過一定閾值時間的 H.264 GOP 分組存在,我們會將這個 GOP 所有幀的報文從窗口緩沖區(qū)移除郁油。并將它下一個 GOP 分組的 I 的幀 ID 和報文 ID 通過 wnd sync 協(xié)議同步到接收端上本股,接收端接收到這個協(xié)議,會將最新連續(xù) ID 設置成同步過來的 ID桐腌。這里必須要說明的是如果頻繁出現(xiàn)過期幀丟棄的動作會造成卡頓拄显,說明當前網(wǎng)絡不適合傳輸高分辨率視頻,可以直接將視頻設成更小的分辨率
3接收
接收主要包括丟包管理案站、播放緩沖區(qū)躬审、緩沖時間評估和播放控制,都是圍繞播放緩沖區(qū)來實現(xiàn)的蟆盐,一個個來介紹承边。
丟包管理:
丟包管理包括丟包檢測和丟失報文 ID 管理兩部分。丟包檢測過程大致是這樣的石挂,假設播放緩沖區(qū)的最大報文 ID 為 max_id博助,網(wǎng)絡上新收到的報文 ID 為 new_id,如果 max_id + 1 < new_id痹愚,那么可能發(fā)生丟包富岳,就會將 [max_id + 1, new_id -1] 區(qū)間中所有的 ID 和當前時刻作為 K/V 對加入到丟包管理器當中。如果 new_id < max_id拯腮,那么就將丟包管理中的 new_id 對應的 K/V 對刪除窖式,表示丟失的報文已經(jīng)收到。當收包反饋條件滿足時动壤,會掃描整個丟包管理萝喘,將達到請求重傳的丟包 ID 加入到 segment ack 反饋消息中并發(fā)往發(fā)送方請求重傳,如果 ID 被請求了重傳琼懊,會將當前時刻設置為 K/V 對中阁簸,增加對應報文的重傳計數(shù)器 count,這個掃描過程會統(tǒng)計對包管理器中單個重發(fā)最多報文的重發(fā)次數(shù) resend_count哼丈。
緩沖時間評估:
在前面的抖動與亂序小節(jié)中我們提到播放端有個緩沖區(qū)启妹,這個緩沖區(qū)過大時延遲就大,緩沖區(qū)過小時又會出現(xiàn)卡頓現(xiàn)象削祈,我們針對這個問題設計了一個緩沖時間評估的算法翅溺。緩沖區(qū)評估先會算出一個 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,也就是說網(wǎng)絡再好羹饰,緩沖區(qū)緩沖區(qū)至少 1 幀視頻的數(shù)據(jù)伊滋,否則緩沖區(qū)是毫無意義的。
如果單位時間內沒有丟包重傳發(fā)生队秩,那么 cache timer 會做適當?shù)目s小笑旺,這樣做的好處是當網(wǎng)絡間歇性波動造成 cache timer 很大,恢復正常后 cache timer 也能恢復到相對小位置馍资,縮減不必要的緩沖區(qū)延遲筒主。
播放緩沖區(qū):
我們設計的播放緩沖區(qū)是按幀 ID 為索引的有序循環(huán)數(shù)組,數(shù)組內部的單元是視頻幀的具體信息:幀 ID鸟蟹、分片數(shù)乌妙、幀類型等。緩沖區(qū)有兩個狀態(tài):waiting 和 playing建钥,waiting 狀態(tài)表示緩沖區(qū)處于緩沖狀態(tài)藤韵,不能進行視頻播放直到緩沖區(qū)中的幀數(shù)據(jù)達到一定的閾值。Playing 狀態(tài)表示緩沖區(qū)進入播放狀態(tài)熊经,播放模塊可以從中取出幀進行解碼播放泽艘。我們來介紹下這兩個狀態(tài)的切換關系:
當緩沖區(qū)創(chuàng)建時會被初始化成 waiting 狀態(tài)。
當緩沖區(qū)中緩沖的最新幀與最老幀的時間戳間隔 > cache timer 時奈搜,進入 playing 狀態(tài)并更當前時刻設成播放絕對時間戳 play ts悉盆。
當緩沖區(qū)處于 playing 狀態(tài)且緩沖區(qū)是沒有任何幀數(shù)據(jù),進入 waiting 狀態(tài)直到觸發(fā)第 2 步馋吗。
播放緩沖區(qū)的目的就是防止抖動和應對丟包重傳焕盟,讓視頻流能按照采集時的頻率進行播放,播放緩沖區(qū)的設計極其復雜宏粤,需要考慮的因素很多脚翘,實現(xiàn)的時候需要慎重。
播放控制:
接收端最后一個環(huán)節(jié)就是播放控制绍哎,播放控制就是從緩沖區(qū)中拿出有效的視頻幀進行解碼播放来农。但是怎么拿?什么時候拿崇堰?我們知道視頻是按照視頻幀從發(fā)送端攜帶過來的相對時間戳來做播放沃于,我們每一幀視頻都有一個相對時間戳 TS涩咖,根據(jù)幀與幀之間的 TS 的差值就可以知道上一幀和下一幀播放的時間間隔,假如上一幀播放的絕對時間戳為 prev_play_ts繁莹,相對時間戳為 prev_ts檩互,當前系統(tǒng)時間戳為 curr_play_ts,當前緩沖區(qū)中最小序號幀的相對時間戳為 frame_ts咨演,只要滿足:
Prev_play_ts + (frame_ts – prev_ts) < curr_play_ts 且這一幀數(shù)據(jù)是所有的報文都收齊了
這兩個條件就可以進行解碼播放闸昨,取出幀數(shù)據(jù)后將 Prev_play_ts = cur_play_ts,但更新 prev_ts 有些講究薄风,為了防止緩沖延遲問題我們做了特殊處理饵较。
如果 frame_ts + cache timer < 緩沖區(qū)中最大幀的 ts,表明緩沖的時延太長遭赂,則 prev_ts = 緩沖區(qū)中最大幀的 ts - cache timer循诉。 否則 prev_ts = frame_ts。
7撇他、量化與測量方法
再好的模型也需要有合理的測量方式來驗證打洼,在多媒體這種具有時效性的傳輸領域尤其如此。一般在實驗室環(huán)境我們采用 netem 來進行模擬公網(wǎng)的各種情況進行測試逆粹,如果在模擬環(huán)境已經(jīng)達到一個比較理想的狀態(tài)后會組織相關人員在公網(wǎng)上進行測試募疮。下面來介紹怎么來測試我們整個傳輸模型的。
1netem 模擬測試
Netem 是 Linux 內核提供的一個網(wǎng)絡模擬工具僻弹,可以設置延遲阿浓、丟包、抖動蹋绽、亂序和包損壞等芭毙,基本能模擬公網(wǎng)大部分網(wǎng)絡情況。關于 netem 可以訪問它的官網(wǎng):https://wiki.linuxfoundation.org/networking/netem
我們在實驗環(huán)境搭建了一個基于服務器和客戶端模式的測試環(huán)境卸耘,下面是測試環(huán)境的拓撲關系圖:
我們利用 Linux 來做一個路由器退敦,服務器和收發(fā)端都連接到這個路由器上,服務器負責客戶端的登記蚣抗、數(shù)據(jù)轉發(fā)侈百、數(shù)據(jù)緩沖等,相當于一個簡易的流媒體服務器翰铡。Sender 負責媒體編碼和發(fā)送钝域,receiver 負責接收和媒體播放。為了測試延遲锭魔,我們把 sender 和 receiver 運行在同一個 PC 機器上例证,在 sender 從 CCD 獲取到 RGB 圖像時打一個時間戳,并把這個時間戳記錄在這一幀數(shù)據(jù)的報文發(fā)往 server 和 receiver迷捧,receiver 收到并解碼顯示這幀數(shù)據(jù)時织咧,通過記錄的時間戳可以得到整個過程的延遲胀葱。我們的測試用例是用 1080P 碼率為 300KB/S 視頻流,在 router 用 netem 上模擬了以下幾種網(wǎng)絡狀態(tài):
環(huán)路延遲 10m笙蒙,無丟包巡社,無抖動,無亂序
環(huán)路延遲 30ms手趣,丟包 0.5%,抖動 5ms, 2% 亂序
環(huán)路延遲 60ms肥荔,丟包 1%绿渣,抖動 20ms, 3% 亂序,0.1% 包損壞
環(huán)路延遲 100ms燕耿,丟包 4%中符,抖動 50ms, 4% 亂序,0.1% 包損壞
環(huán)路延遲 200ms誉帅,丟包 10%淀散,抖動 70ms, 5% 亂序,0.1% 包損壞
環(huán)路延遲 300ms蚜锨,丟包 15%档插,抖動 100ms, 5% 亂序,0.1% 包損壞
因為傳輸機制采用的是可靠到達亚再,那么檢驗傳輸機制有效的參數(shù)就是視頻延遲郭膛,我們統(tǒng)計 2 分鐘周期內最大延遲,以下是各種情況的延遲曲線圖:
從上圖可以看出氛悬,如果網(wǎng)絡控制在環(huán)路延遲在 200ms 丟包在 10% 以下则剃,可以讓視頻延遲在 500ms 毫秒以下,這并不是一個對網(wǎng)絡質量要求很苛刻的條件如捅。所以我們在后臺的媒體服務部署時棍现,盡量讓客戶端到媒體服務器之間的網(wǎng)絡滿足這個條件,如果網(wǎng)路環(huán)路延遲在 300ms 丟包 15% 時镜遣,依然可以做到小于 1 秒的延遲己肮,基本能滿足雙向互動交流。
2公網(wǎng)測試
公網(wǎng)測試相對比較簡單悲关,我們將 Server 部署到 UCloud 云上朴肺,發(fā)送端用的是上海電信 100M 公司寬帶,接收端用的是河北聯(lián)通 20M 小區(qū)寬帶坚洽,環(huán)路延遲在 60ms 左右戈稿。總體測試下來 1080P 在接收端觀看視頻流暢自然讶舰,無抖動鞍盗,無卡頓需了,延遲統(tǒng)計平均在 180ms 左右。
8般甲、我所遇到的坑
在整個 1080P 超清視頻的傳輸技術實現(xiàn)過程中肋乍,我們遇到過比較多的坑。大致如下敷存。
Socket 緩沖區(qū)問題:
我們前期開發(fā)階段都是使用 socket 默認的緩沖區(qū)大小墓造,由于 1080P 圖像幀的數(shù)據(jù)非常巨大(關鍵幀超過 80KB),我們發(fā)現(xiàn)在在內網(wǎng)測試沒有設置丟包的網(wǎng)絡環(huán)境發(fā)現(xiàn)接收端有嚴重的丟包锚烦,經(jīng)查證是 socket 收發(fā)緩沖區(qū)太小造成丟包的觅闽,后來我們把 socket 緩沖區(qū)設置到 128KB 大小,問題解決了涮俄。
H.264 B 幀延遲問題:
前期我們?yōu)榱斯?jié)省傳輸帶寬和防丟包開了 B 幀編碼蛉拙,由于 B 幀是前后雙向預測編碼的,會在編碼期滯后幾個幀間隔時間彻亲,引起了超過 100ms 的編碼延時孕锄,后來我們?yōu)榱藢崟r性干脆把 B 幀編碼選項去掉。
Push 方式丟包重傳:
在設計階段我們曾經(jīng)使用發(fā)送端主動 push 方式來解決丟包重傳問題苞尝,在測試過程發(fā)現(xiàn)在丟包頻繁發(fā)生的情況下至少增加了 20% 的帶寬消耗畸肆,而且容易帶來延遲和網(wǎng)絡擁塞。后來幾經(jīng)論證用現(xiàn)在的 pull 模式來進行丟包重傳宙址。
Segment 內存問題:
在設計階段我們對每個視頻緩沖區(qū)中的幀信息都是動態(tài)分配內存對象的恼除,由于 1080P 在傳輸過程中每秒會發(fā)送 400 - 500 個 UDP 報文曼氛,在 PC 端長時間運行容易出現(xiàn)內存碎片餐抢,在服務器端出現(xiàn)莫名其妙的 clib 假內存泄露和并發(fā)問題顽冶。我們實現(xiàn)了一個 memory slab 管理頻繁申請和釋放內存的問題贸人。
音頻和視頻數(shù)據(jù)傳輸問題:
在早期的設計之中我們借鑒了 FLV 的方式將音頻和視頻數(shù)據(jù)用同一套傳輸算法傳輸十拣,好處就是容易實現(xiàn),但在網(wǎng)絡波動的情況下容易引起聲音卡頓糠溜,也無法根據(jù)音頻的特性優(yōu)化傳輸承匣。后來我們把音頻獨立出來零聚,針對音頻的特性設計了一套低延遲高質量的音頻傳輸體系袍暴,定點對音頻進行傳輸優(yōu)化。
后續(xù)的工作是重點放在媒體器多點分布隶症、多點并發(fā)傳輸政模、P2P 分發(fā)算法的探索上,盡量減少延遲和服務帶寬成本,讓傳輸變的更高效和更低廉蚂会。
另外還有一些關于c++ Linux后臺服務器開發(fā)的一些知識點分享:Linux淋样,Nginx,MySQL胁住,Redis习蓬,P2P纽什,K8S,Docker躲叼,TCP/IP芦缰,協(xié)程,DPDK枫慷,webrtc让蕾,音視頻等等視頻。
喜歡的朋友可以后臺私信【1】獲取學習視頻
附上一份音視頻學習課程大綱給大家