網絡擁塞是基於IP協議的數據報交換網絡中常見的一種網絡傳輸問題,它對網絡傳輸的質量有嚴重的影響,網絡擁塞是導致網絡吞吐降低,網絡丟包等的主要原因之一,這些問題使得上層應用無法有效的利用網絡帶寬獲得高質量的網絡傳輸效果。特別是在通信領域,網絡擁塞導致的丟包,延遲,抖動等問題,嚴重的影響了通信質量,如果不能很好的解決這些問題,一個通信產品就無法在現實環境中正常使用。在這方面WebRTC中的網絡擁塞控制算法給我們提供了一個可供參考的實現,本篇文章會盡量詳細的介紹WebRTC中的擁塞控制算法---GCC的實現方式。
相關閱讀推薦
《聊聊WebRTC網關服務器1:如何選擇服務端端口方案?》
《聊聊WebRTC網關服務器2:如何選擇PeerConnection方案?》
WebRTC簡介
WebRTC是一個Web端的實時通信解決方案,它可以做到在不借助外部插件的情況下,在瀏覽器中實現點對點的實時通信。WebRTC已經由W3C和IETF標准化,最早推出和支持這項技術的瀏覽器是Chrome, 其他主流瀏覽器也正在陸續支持。Chrome中集成的WebRTC代碼已全部開源,同時Chrome提供了一套LibWebRTC的代碼庫,使得這套RTC架構可以移植到其他APP當中,提供實時通信功能。

GCC算法概述
本文主要介紹的是WebRTC的擁塞控制算法,WebRTC的傳輸層是基於UDP協議,在此之上,使用的是標准的RTP/RTCP協議封裝媒體流。RTP/RTCP本身提供很多機制來保證傳輸的可靠性,比如RR/SR, NACK,PLI,FIR, FEC,REMB等,同時WebRTC還擴展了RTP/RTCP協議,來提供一些額外的保障,比如Transport-CCFeedback, RTP Transport-wide-cc extension,RTP abs-sendtime extension等,其中一些后文會詳細介紹。
GCC算法主要分成兩個部分,一個是基於丟包的擁塞控制,一個是基於延遲的擁塞控制。在早期的實現當中,這兩個擁塞控制算法分別是在發送端和接收端實現的,接收端的擁塞控制算法所計算出的估計帶寬,會通過RTCP的remb反饋到發送端,發送端綜合兩個控制算法的結果得到一個最終的發送碼率,並以此碼率發送數據包。下圖便是展現的該種實現方式:

從圖中可以看到,Loss-Based Controller在發送端負責基於丟包的擁塞控制,它的輸入比較簡單,只需要根據從接收端反饋的丟包率,就可以做帶寬估算;上圖右側比較復雜,做的是基於延遲的帶寬估計,這也是本文后面主要介紹的部分。在最近的WebRTC實現中,GCC把它的兩種擁塞控制算法都移到了發送端來實現,但是兩種算法本身並沒有改變,只是在發送端需要計算延遲,因而需要一些額外的feedback信息,為此WebRTC擴展了RTCP協議,其中最主要的是增加了Transport-CC Feedback,該包攜帶了接收端接收到的每個媒體包的到達時間。
基於延遲的擁塞控制比較復雜,WebRTC使用延遲梯度來判斷網絡的擁塞程度,延遲梯段的概念后文會詳細介紹;
其算法分為幾個部分:
l 到達時間濾波器
l 過載檢測器
l 速率控制器
在獲得兩個擁塞控制算法分別結算到的發送碼率之后,GCC最終的發送碼率取的是兩種算法的最小值。下面我們詳細介紹WebRTC的擁塞控制算法GCC。
(一)基於丟包的帶寬估計
基於丟包的擁塞控制比較簡單,其基本思想是根據丟包的多少來判斷網絡的擁塞程度,丟包越多則認為網絡越擁塞,那么我們就要降低發送速率來緩解網絡擁塞;如果沒有丟包,這說明網絡狀況很好,這時候就可以提高發送碼率,向上探測是否有更多的帶寬可用。實現該算法有兩點:一是獲得接收端的丟包率,一是確定降低碼率和提升碼率的閾值。
WebRTC通過RTCP協議的Receive Report反饋包來獲取接收端的丟包率。Receive Report包中有一個lost fraction字段,包含了接收端的丟包率,如下圖所示。

另外,WebRTC通過以下公式來估算發送碼率,式中 As(tk) 即為 tk 時刻的帶寬估計值,fl(tk)即為 tk 時刻的丟包率:

------ (1)
簡單來說,當丟包率大於10%時則認為網絡有擁塞,此時根據丟包率降低帶寬,丟包率越高帶寬降的越多;當丟包率小於2%時,則認為網絡狀況很好,此時向上提高5%的帶寬以探測是否有更多帶寬可用;2%到10%之間的丟包率,則會保持當前碼率不變,這樣可以避免一些網絡固有的丟包被錯判為網絡擁塞而導致降低碼率,而這部分的丟包則需要通過其他的如NACK或FEC等手段來恢復。
(二)基於延遲梯度的帶寬估計
WebRTC實現的基於延遲梯度的帶寬估計有兩種版本:
l 最早一種是在接受端實現,評估的帶寬結果通過RTCP REMB消息反饋到發送端。在此種實現中,為了准確計算延遲梯度,WebRTC添加了一種RTP擴展頭部abs-send-time, 用來表示每個RTP包的精確發送時間,從而避免發送端延遲給網絡傳播延遲的估計帶來誤差。這種模式也是RFC和google的paper中描述的模式。
l 在新近的WebRTC的實現中,所有的帶寬估計都放在了發送端,也就說發送端除了做基於丟包的帶寬估計,同時也做基於延遲梯度的帶寬估計。為了能夠在接受端做基於延遲梯度的帶寬估計,WebRTC擴展了RTP/RTCP協議,其一是增加了RTP擴展頭部,添加了一個session級別的sequence number, 目的是基於一個session做反饋信息的統計,而不緊緊是一條音頻流或視頻流;其二是增加了一個RTCP反饋信息transport-cc-feedback,該消息負責反饋接受端收到的所有媒體包的到達時間。接收端根據包間的接受延遲和發送間隔可以計算出延遲梯度,從而估計帶寬。
關於如何根據延遲梯度推斷當前網絡狀況, 后面會分幾點詳細展開講, 總體來說分為以下幾個步驟:
l 到達時間濾波器
l 過載檢測器
l 速率控制器
其過程就是,到達時間濾波器根據包間的到達時延和發送間隔,計算出延遲變化,這里會用到卡爾曼濾波對延遲變化做平滑以消除網絡噪音帶來的誤差;延遲變化會作為過載檢測器的輸入,由過載檢測器判斷當前網絡的狀態,有三種網絡狀態返回overuse/underuse/normal,檢測的依據是比較延遲變化和一個閾值,其中該閾值非常關鍵且是動態調整的。最后根據網絡狀態的變化,速率控制器根據一個帶寬估計公式計算帶寬估計值。
(三)到達時間濾波器
前面多次提到WebRTC使用延遲梯度來判斷網絡擁塞狀況,那什么是延遲梯度,為什么延遲梯度可以作為判斷網絡擁塞的依據,我們在這里詳細介紹,首先來看以下,延遲梯度是怎樣計算出來的:
1. 延遲梯度的計算

如上圖所示,用兩個數據包的到達時間間隔減去他們的發送時間間隔,就可以得到一個延遲的變化,這里我們稱這個延遲的變化為單向延遲梯度(one way delay gradient),其公式可記為:

(2)
那么為什么延遲梯度可以用來判斷網絡擁塞的呢,如下面兩圖所示:


左邊這幅圖的場景是理想狀況下的網絡傳輸,沒有任何擁塞,按我們上面提到的公式(2)來計算,這種場景下,所計算到的延遲梯度應該為0。而右邊這幅圖的場景則是發送擁塞時的狀況,當包在t2時刻到達時,該報在網絡中經歷過一次因擁塞導致的排隊,這導致他的到達時間比原本要完,此時計算出的延遲梯度就為一個較大的值,通過這個值,我們就能判斷當前網絡正處在擁塞狀態。
在WebRTC的具體實現中,還有一些細節來保證延遲梯度計算的准確性,總結如下:
l 由於延遲梯度的測量精度很小,為了避免網絡噪音帶來的誤差,利用了卡爾曼濾波來平滑延遲梯度的測量結果。
l WebRTC的實現中,並不是單純的測量單個數據包彼此之間的延遲梯度,而是將數據包按發送時間間隔和到達時間間隔分組,計算組間的整體延遲梯度。分組規則是:
1) 發送時間間隔小於5ms的數據包被歸為一組,這是由於WebRTC的發送端實現了一個平滑發送模塊,該模塊的發送間隔是5ms發送一批數據包。
2) 到達時間間隔小於5ms的數據包被歸為一組,這是由於在wifi網絡下,某些wifi設備的轉發模式是,在某個固定時間片內才有機會轉發數據包,這個時間片的間隔可能長達100ms,造成的結果是100ms的數據包堆積,並在發送時形成burst,這個busrt內的所有數據包就會被視為一組。
l 為了計算延遲梯度,除了接收端要反饋每個媒體包的接受狀態,同時發送端也要記錄每個媒體包的發送狀態,記錄其發送的時間值。在這個情況下abs-send-time擴展不再需要。
2. transport-cc-feedback消息
l 該消息是對RTCP的一個擴展,專門用於在GCC中反饋數據包的接受情況。這里有兩點需要注意:
l 該消息的發送速率如何確定,按RFC[2]中的說明,可以是收到每個frame發送一次,另外也指出可以是一個RTT的時間發送一次,實際WebRTC的實現中大約估計了一個發送帶寬的5%這樣一個發送速率。
l 如果這個數據包丟失怎么辦,RFC[2]和WebRTC實現中都是直接忽略,這里涉及的問題是,忽略該包對計算延遲梯度影響不大,只是相當於數據包的分組跨度更大了,丟失的包對計算沒有太大影響,但另一個問題是,發送端需要計算接受端的接受速率,當feedback丟失時,會認為相應的數據包都丟失了,這會影響接受速率的計算,這個值在后續計算估計帶寬中會用到,從而導致一定誤差。
具體消息格式如下:

如上圖所示,紅框之前的字段是RTCP包的通用字段,紅框中的字段為transport-cc的具體內容,其中前四個字段分別表示:
l base sequence number:當前包攜帶的媒體包的接受信息是從哪個包開始的
l packet status count:當前包攜帶了幾個媒體包的接受信息
l reference time:一個基准時間,計算該包中每個媒體包的到達時間都要基於這個基准時間計算
l fb pkt. count:第幾個transport-cc包
在此之后,是兩類信息:多個packet chunk字段和多個recv delta字段。其中pcaket chunk具體含義如下:
如下兩圖所示, 表示媒體包到達狀態的結構有兩種編碼方式, 其中 T 表示chunk type;0表示RunLength Chunk, 1表示Status Vector Chunk.
1)Run LengthChunk

這種表示方式是用於,當我們連續收到多個數據包,他們都有相同的到達狀態,就可以用這種編碼方式。其中S表示的是到達狀態,Run Length表示有多少個連續的包屬於這一到達狀態。
到達狀態有三種:
00 Packet not received
01 Packet received, small delta (所謂small detal是指能用一個字節表示的數值)
10 Packet received, large ornegative delta (large即是能用兩個字節表示的數值)
2) Status Vector Chunk

這種表示方式用於每個數據包都需要自己的狀態表示碼,當然還是上面提到的那三種狀態。但是這里的S就不是上面的意思,這里的S指的是symbol list的編碼方式,s = 0時,表示symbollist的每一個bit能表示一個數據包的到達狀態,s = 1時表示每兩個bit表示一個數據包的狀態。
s = 0 時
0 Packet not received
1 Packet received , small detal
s = 1 時
同 Run Length Chunk
最后,對於每一個狀態為Packet received 的數據包的延遲依次填入|recv delta|字段,到達狀態為1的,recv delta占用一個字節,到達狀態為2的,recv delta占用兩個字節可以看出以上編碼的目的是為了盡量減少該數據包的大小,因為每個媒體包都需要反饋他的接受狀態。
(四)過載檢測器
到達時間濾波器計算出每組數據包的延遲梯度之后,就要據此判斷當前的網絡擁塞狀態,通過和某個閾值的比較,高過某個閾值就認為時網絡擁塞,低於某個閾值就認為網路狀態良好,因此如何確定閾值就至關重要。這就是過載檢測器的主要工作,它主要有兩部分,一部分是確定閾值的大小,另一部分就是依據延遲梯度和閾值的判斷,估計出當前的網絡狀態,一共有三種網絡狀態: overuse underuse normal,我們先看網絡狀態的判斷。
1. 網絡狀態判斷
判斷依據入下圖所示:

其中表示的是計算出的延遲梯,表示的是一個判斷閾值,這個閾值是自適應的, 后面還會介紹他是怎么動態調整的,這里先只看如何根據這兩個值判斷當前網絡狀態。
從上圖可以看出,這里的判斷方法是:

這樣計算的依據是,網絡發生擁塞時,數據包會在中間網絡設備中排隊等待轉發,這會造成延遲梯度的增長,當網絡流量回落時,網絡設備快速消耗(轉發)其發送隊列中的數據包,而后續的包排隊時間更短,這時延遲梯度減小或為負值。
這里了需要說明的是:
l 在實際WebRTC的實現中,雖然每個數據包組(前面提到了如何分組)的到達都會觸發這個探測過程,但是使用的m(ti)這個值並不是直接使用每組數據到來時的計算值,而是將這個值放大了60倍。這么做的目的可能是m(ti)這個值通常情況下很小,理想網絡下基本為0,放大該值可以使該算法不會應為太靈敏而波動太大。
l 在判斷是否overuse時,不會一旦超過閾值就改變當前狀態,而是要滿足延遲梯度大於閾值至少持續100ms,才會將當前網絡狀態判斷為overuse。
2. 自適應閾值
上節提到的閾值值,它是判斷當前網絡狀況的依據,所以如何確定它的值也就非常重要了。雖然理想狀況下,網絡的延遲梯度是0,但是實際的網絡中,不同轉發路徑其延遲梯度還是有波動的,波動的大小也是不一樣的,這就導致如果設置固定的 太大可能無法探測到擁塞,太小又太敏感,導致速率了變化很大。同時,另外一個問題是,實驗中顯示固定的值會導致在和TCP鏈接的競爭中,自己被餓死的現象(TCP是基於丟包的擁塞控制),因此WebRTC使用了一種自適應的閾值調節算法,具體如下:
(1) 自適應算法

------(3)
上面的公式就是GCC提出的閾值自適應算法,其中:
,每組數據包會觸發一次探測,同時更新一次閾值,這里
的意義就是距上次更新閾值時的時間間隔。
是一個變化率,或者叫增長率,當然也有可能是負增長,增長的基值是:當前的延遲梯度和上一個閾值的差值---
。其具體的取值如下:

------(4)
其中:ku = 0.01; kd = 0.00018
從這個式子中可以看出,當延遲梯度減小時,閾值會以一個更慢的速率減小; 延遲梯度增加時,閾值也會以一個更慢的速度增加;不過相對而言,閾值的減小速度要小於增加速度。
(五)速率控制器
速率控制器主要實現了一個狀態機的變遷,並根據當前狀態來計算當前的可用碼率,狀態機如下圖所示:

速率控制器根據過載探測器輸出的信號(overuse underusenormal)驅動速率控制狀態機, 從而估算出當前的網絡速率。從上圖可以看出,當網絡擁塞時,會收到overuse信號,狀態機進入“decrease”狀態,發送速率降低;當網絡中排隊的數據包被快速釋放時,會受到underuse信號,狀態機進入“hold”狀態。網絡平穩時,收到normal信號,狀態機進入“increase”狀態,開始探測是否可以增加發送速率。
在Google的paper[3]中,計算帶寬的公式如下:

------(5)
其中 = 1.05, =0.85。從該式中可以看到,當需要Increase時,以前一次的估算碼率乘以1.05作為當前碼率;當需要Decrease時,以當前估算的接受端碼率(Rr(ti))乘以0.85作為當前碼率;Hold狀態不改變碼率。
最后,將基於丟包的碼率估計值和基於延遲的碼率估計值作比較,其中最小的碼率估價值將作為最終的發送碼率。
以上便是WebRTC中的擁塞控制算法的主要內容,其算法也一直還在演進當中,每個版本都有會有一些改進加入。其他還有一些主題這里沒有覆蓋到,比如平滑發送,可以避免突發流量; padding包等用來探測帶寬的策略。應該說WebRTC的這套機制能覆蓋大部分的網絡場景,但是從我們測試來看有一些特殊場景,比如抖動或者丟包比較高的情況下,其帶寬利用率還是不夠理想,但總體來說效果還是很不錯的。
另外,想要獲取更多產品干貨、技術干貨,記得關注網易雲信博客。
Reference
[1] https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02
[2] https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
[3] Analysis and Design of the Google CongestionControl for Web Real-time Communication
