webrtc中的碼率控制

原文鏈接:https://blog.csdn.net/chinabinlang/article/details/78294464

本來想要自己寫一篇文章,但是網上已經有很好的文章了,所以這裏直接綜合轉載;

文章前面的部分是簡單總結,後面是轉載的文章;


名詞解釋:


GCC谷歌提出的擁塞控制算法(Google Congestion Control,簡稱GCC[1])來控制發送端碼率


TransportCC

REMB: Receiver Estimated Maximum Bitrate,  接收端最大接收碼率估測,接收端會估計本地接收的最大帶寬能力,並通過rtcp remb 消息返回給對端,這樣對端可以調整自己的發送端碼率,達到動態調整帶寬得目的

丟包率:

goog-remb:google實現了自己版本的remb;


協議文檔:https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03


NACK:丟包重傳


delay_based:  基於延時; recv端根據延時計算bitrate,remb返回到send端;

loss_base    : 基於丟包; send端接根據rtcp計算丟包率,計算bitrate;


接收延時   


RTT: 往返延時


RTX:

RED:


FEC:

UPL

FLEX


開啓DTX
DTX 是 Discontinuous Transmission的簡稱,這個特性是在用戶不說話時不傳輸語音,這樣可以節省點帶寬。默認WebRTC是不開啓這個特性的,要開啓DTX,只需要在a=ftmp這一行中加入usedtx=1就行


//從這裏看webrtc中的Fec類型;

//主要模塊:VCMNackFecMethod

//主要參數通過rtt判斷當前FEC模式;

enum class FecMechanism {
  RED,
  RED_AND_ULPFEC,
  FLEXFEC,
};


//從這看Rtcp中的Feedback類型;

// Used in RtcpFeedback struct.
enum class RtcpFeedbackType {
  CCM,
  NACK,
  REMB,  // "goog-remb"
  TRANSPORT_CC,
};



關於發送端的碼率具體參數在:VideoSendStream::OnBitrateUpdated



webrtc58中的相關代碼:

1:

classSendSideBandwidthEstimation

webrtc58\src\webrtc\modules\bitrate_controller\send_side_bandwidth_estimation.h


SendSideBandwidthEstimation具體實現了碼率計算的算法;

CurrentEstimate(int* bitrate, uint8_t* loss, int64_t* rtt)  獲取碼率,丟包率,往返延時;

uint32_t CapBitrateToThresholds(int64_t now_ms, uint32_t bitrate); 主要修真碼率範圍在最小和最大範圍之間,和參考incoming bandwidth;


  // Call periodically to update estimate.
  void UpdateEstimate(int64_t now_ms);


  // Call when we receive a RTCP message with TMMBR or REMB.
  void UpdateReceiverEstimate(int64_t now_ms, uint32_t bandwidth);
 TMMBR用於流控,請求發送端按指定的最大比特率傳輸數據流,通常用於網絡抖動情況下保證VOIP通信的流暢性(臨時降低質量)
 REMB接收端最大接收碼率估測,接收端會估計本地接收的最大帶寬能力,並通過rtcp remb 消息返回給對端,這樣對端可以調整自己的發送端碼率,達到動態調整帶寬得目的
   這裏可以看得出上述兩個RTCP的反饋都是用同一個接收算法處理;

  // Call when a new delay-based estimate is available.
  void UpdateDelayBasedEstimate(int64_t now_ms, uint32_t bitrate_bps);


  // Call when we receive a RTCP message with a ReceiveBlock. //其實就是基於丟包和rtt;
  void UpdateReceiverBlock(uint8_t fraction_loss,
                           int64_t rtt,
                           int number_of_packets,
                           int64_t now_ms);




2.

classBitrateControllerImpl: public BitrateController 

BitrateControllerImpl具體調聲明和調用了SendSideBandwidthEstimation相關函數,通過process實現執行UpdateEstimate;


BitrateControllerImpl中 通過 RtcpBandwidthObserverImpl獲取網絡 延時 和 丟包 等狀態被調用響應;


3.

//這個類的這個函數實現了網站狀態變化的計算;

void BitrateControllerImpl::MaybeTriggerOnNetworkChanged() {
  if (!observer_)
    return;


  uint32_t bitrate_bps;
  uint8_t fraction_loss;
  int64_t rtt;


  if (GetNetworkParameters(&bitrate_bps, &fraction_loss, &rtt))
    observer_->OnNetworkChanged(bitrate_bps, fraction_loss, rtt);
}



通過

RtcpBandwidthObserver* BitrateControllerImpl::CreateRtcpBandwidthObserver() {
  return new RtcpBandwidthObserverImpl(this);
}
創建實例;


4.

//具體就是實現了REMB 和  丟包 兩個bitrate獲取,然後 通過BitrateControllerImpl裏的相關設定參數,估算當前的碼率;

//但是這個類,僅僅是Observer,所以會在別的地方調用;

class BitrateControllerImpl::RtcpBandwidthObserverImpl
    : public RtcpBandwidthObserver{

 public:
  explicit RtcpBandwidthObserverImpl(BitrateControllerImpl* owner)
      : owner_(owner) {
  }


    。。。


}


5.

接下來,RTCP接收:


classRTCPReceiver ; 很重要;

這個類實現了RTCP的原始packet 解析;

也就是說所有rtcp packe,先到這裏解析;然後,在通過Observer分發到各個相關原始模塊;



//獲取接收rtcp pack,然後在裏面解析,然後出發相關回調函數Observer;

RTCPReceiver::IncomingPacket(const uint8_t* packet, size_t packet_size)

  //解析RTCP packet的函數;

.bool RTCPReceiver::ParseCompoundPacket(const uint8_t* packet_begin,
                                       const uint8_t* packet_end,
                                       PacketInformation* packet_information) 

{。。。

   switch (rtcp_block.type()) {
      case rtcp::SenderReport::kPacketType:
        HandleSenderReport(rtcp_block, packet_information);
        break;
      case rtcp::ReceiverReport::kPacketType:
        HandleReceiverReport(rtcp_block, packet_information);
        break;
      case rtcp::Sdes::kPacketType:
        HandleSdes(rtcp_block, packet_information);
        break;
      case rtcp::ExtendedReports::kPacketType:
        HandleXr(rtcp_block, packet_information);
        break;
      case rtcp::Bye::kPacketType:
        HandleBye(rtcp_block);
        break;
      case rtcp::Rtpfb::kPacketType:
        switch (rtcp_block.fmt()) {
          case rtcp::Nack::kFeedbackMessageType:
            HandleNack(rtcp_block, packet_information);
            break;
          case rtcp::Tmmbr::kFeedbackMessageType:
            HandleTmmbr(rtcp_block, packet_information);
            break;
          case rtcp::Tmmbn::kFeedbackMessageType:
            HandleTmmbn(rtcp_block, packet_information);
            break;
          case rtcp::RapidResyncRequest::kFeedbackMessageType:
            HandleSrReq(rtcp_block, packet_information);
            break;
          case rtcp::TransportFeedback::kFeedbackMessageType:
            HandleTransportFeedback(rtcp_block, packet_information);
            break;
          default:
            ++num_skipped_packets_;
            break;
        }
        break;
      case rtcp::Psfb::kPacketType:
        switch (rtcp_block.fmt()) {
          case rtcp::Pli::kFeedbackMessageType:
            HandlePli(rtcp_block, packet_information);
            break;
          case rtcp::Sli::kFeedbackMessageType:
            HandleSli(rtcp_block, packet_information);
            break;
          case rtcp::Rpsi::kFeedbackMessageType:
            HandleRpsi(rtcp_block, packet_information);
            break;
          case rtcp::Fir::kFeedbackMessageType:
            HandleFir(rtcp_block, packet_information);
            break;
          case rtcp::Remb::kFeedbackMessageType:
            HandlePsfbApp(rtcp_block, packet_information);
            break;
          default:
            ++num_skipped_packets_;
            break;
        }
        break;
      default:
        ++num_skipped_packets_;
        break;
    }


。。。


}



webrtc中的RTCP類型:

enum RTCPPacketType : uint32_t {
  kRtcpReport = 0x0001,
  kRtcpSr = 0x0002,
  kRtcpRr = 0x0004,
  kRtcpSdes = 0x0008,
  kRtcpBye = 0x0010,
  kRtcpPli = 0x0020,
  kRtcpNack = 0x0040,
  kRtcpFir = 0x0080,
  kRtcpTmmbr = 0x0100,
  kRtcpTmmbn = 0x0200,
  kRtcpSrReq = 0x0400,
  kRtcpXrVoipMetric = 0x0800,
  kRtcpApp = 0x1000,
  kRtcpSli = 0x4000,
  kRtcpRpsi = 0x8000,
  kRtcpRemb = 0x10000,
  kRtcpTransmissionTimeOffset = 0x20000,
  kRtcpXrReceiverReferenceTime = 0x40000,
  kRtcpXrDlrrReportBlock = 0x80000,
  kRtcpTransportFeedback = 0x100000,
  kRtcpXrTargetBitrate = 0x200000
};





//通過上述rtcp pack分析,然後回調函數相關observe;

void RTCPReceiver::TriggerCallbacksFromRtcpPacket(const PacketInformation& packet_information) 



通過上述分析,可以清楚的看到了RTCP的接收流程到網絡碼率計算的過程;簡單畫圖:


RTCPReceiver   ----->     RtcpBandwidthObserverImpl     ----->     BitrateControllerImpl     ----->     SendSideBandwidthEstimation



具體流程:

webrtc::SendSideBandwidthEstimation::UpdateDelayBasedEstimate
webrtc::BitrateControllerImpl::OnDelayBasedBweResult
webrtc::TransportFeedbackAdapter::OnTransportFeedback
webrtc::RTCPReceiver::TriggerCallbacksFromRtcpPacket
webrtc::RTCPReceiver::IncomingPacket
webrtc::ModuleRtpRtcpImpl::IncomingRtcpPacket
webrtc::internal::VideoSendStreamImpl::DeliverRtcp
webrtc::internal::VideoSendStream::DeliverRtcp
webrtc::internal::Call::DeliverRtcp
webrtc::internal::Call::DeliverPacket
cricket::WebRtcVideoChannel2::OnRtcpReceived
cricket::BaseChannel::OnPacketReceived








webrtc中已經建議將REMB的方式,修改爲: 2. Transport Feedback  + a=rtcp-fb:100 transport-cc;

REMB:只將 接收端 根據丟包延時計算的碼率返回到發送端,這樣發送碼率的計算邏輯在接收端實現;


Transport Feedback  + transport-cc; 

RTCP Feedback: 包含接收端延時;

transport-cc     : 根據返回的延時計算碼率;

將接收端的根據延時獲取碼率算法,放到發送端實現;





轉載如下文章:


WebRTC基於GCC的擁塞控制(上) - 算法分析

from:http://www.jianshu.com/p/0f7ee0e0b3be


WebRTC基於GCC的擁塞控制(下) - 實現分析

http://www.jianshu.com/p/5259a8659112


WebRTC的擁塞控制技術(Congestion Control)

http://www.jianshu.com/p/9061b6d0a901


WEBRTC 發送端擁塞控制

http://blog.csdn.net/doitsjz/article/details/73412056


webrtc中的帶寬自適應算法

http://blog.csdn.net/chenyefei/article/details/51896237





webrtc中rtcp反饋與碼率控制模塊分析

http://blog.csdn.net/mercy_pm/article/details/71474264





WebRTC的帶寬評估的新變化


http://blog.csdn.net/volvet/article/details/62237375








WebRTC基於GCC的擁塞控制(上) - 算法分析


實時流媒體應用的最大特點是實時性,而延遲是實時性的最大敵人。從媒體收發端來講,媒體數據的處理速度是造成延遲的重要原因;而從傳輸角度來講,網絡擁塞則是造成延遲的最主要原因。網絡擁塞可能造成數據包丟失,也可能造成數據傳輸時間變長,延遲增大。

擁塞控制是實時流媒體應用質量保證(QoS)的重要手段之一,它在緩解網絡擁堵、減小網絡延遲、平滑數據傳輸等質量保證方面發揮重要作用。WebRTC通控制發送端數據發送碼率來達到控制網絡擁塞的目的,其採用谷歌提出的擁塞控制算法(Google Congestion Control,簡稱GCC[1])來控制發送端碼率。

本文是關於WebRTC擁塞控制算法GCC的上半部分,主要集中於對算法的理論分析,力圖對WebRTC的QoS有一個全面直觀的認識。在下半部分,將深入WebRTC源代碼內部,仔細分析GCC的實現細節。

1 GCC算法綜述

Google關於GCC的RFC文檔在文獻[1],該RFC目前處於草案狀態,還沒有成爲IETF的正式RFC。此外,Google陸續發佈了一系列論文[2][3][4]來論述該算法的實現細節,以及其在Google Hangouts、WebRTC等產品中的應用。本文主要根據這些文檔資料,從理論上學習GCC算法。

GCC算法分兩部分:發送端基於丟包率的碼率控制和接收端基於延遲的碼率控制。如圖1所示。

圖1 GCC算法整體結構

基於丟包率的碼率控制運行在發送端,依靠RTCP RR報文進行工作。WebRTC在發送端收到來自接收端的RTCP RR報文,根據其Report Block中攜帶的丟包率信息,動態調整發送端碼率As。基於延遲的碼率控制運行在接收端,WebRTC根據數據包到達的時間延遲,通過到達時間濾波器,估算出網絡延遲m(t),然後經過過載檢測器判斷當前網絡的擁塞狀況,最後在碼率控制器根據規則計算出遠端估計最大碼率Ar。得到Ar之後,通過RTCP REMB報文返回發送端。發送端綜合As、Ar和預配置的上下限,計算出最終的目標碼率A,該碼率會作用到Encoder、RTP和PacedSender等模塊,控制發送端的碼率。

2 發送端基於丟包率的碼率控制

GCC算法在發送端基於丟包率控制發送碼率,其基本思想是:丟包率反映網絡擁塞狀況。如果丟包率很小或者爲0,說明網絡狀況良好,在不超過預設最大碼率的情況下,可以增大發送端碼率;反之如果丟包率變大,說明網絡狀況變差,此時應減少發送端碼率。在其它情況下,發送端碼率保持不變。

GCC使用的丟包率根據接收端RTP接收統計信息計算得到,通過RTCP RR報文中返回給發送端。RTCP RR報文統計接收端RTP接收信息,如Packet Loss,Jitter,DLSR等等,如圖2所示:

圖2 RTCP RR報文結構[5]

發送端收到RTCP RR報文並解析得到丟包率後,根據圖3公式計算髮送端碼率:當丟包率大於0.1時,說明網絡發生擁塞,此時降低發送端碼率;當丟包率小於0.02時,說明網絡狀況良好,此時增大發送端碼率;其他情況下,發送端碼率保持不變。

圖3 GCC基於丟包率的碼率計算公式[4]

最終碼率會作用於Encoder、RTP和PacedSender模塊,用以在編碼器內部調整碼率和平滑發送端發送速率。

3 接收端基於延遲的碼率控制

GCC算法在接收端基於數據包到達延遲估計發送碼率Ar,然後通過RTCP REMB報文反饋到發送端,發送端把Ar作爲最終目標碼率的上限值。其基本思想是: RTP數據包的到達時間延遲m(i)反映網絡擁塞狀況。當延遲很小時,說明網絡擁塞不嚴重,可以適當增大目標碼率;當延遲變大時,說明網絡擁塞變嚴重,需要減小目標碼率;當延遲維持在一個低水平時,目標碼率維持不變。

基於延時的擁塞控制由三個主要模塊組成:到達時間濾波器,過載檢查器和速率控制器;除此之外還有過載閾值自適應模塊和REMB報文生成模塊,如圖1所示。下面分別論述其工作過程。

3.1 到達時間濾波器(Arrival-time Filter)

該模塊用以計算相鄰相鄰兩個數據包組的網絡排隊延遲m(i)。數據包組定義爲一段時間內連續發送的數據包的集合。一系列數據包短時間裏連續發送,這段時間稱爲突發時間,建議突發時間爲5ms。不建議在突發時間內的包間隔時間做度量,而是把它們做爲一組來測量。通過相鄰兩個數據包組的發送時間和到達時間,計算得到組間延遲d (i)。組間延遲示意圖及計算公式如圖4所示:

圖4 組間延遲示意圖

T(i)是第i個數據包組中第一個數據包的發送時間,t(i)是第i個數據包組中最後一個數據包的到達時間。幀間延遲通過如下公式計算得到:

d(i) = t(i) – t(i-1) – (T(i) – T(i-1))    (3.1.1)

公式1.3.1是d(i)的觀測方程。另一方面,d(i)也可由如下狀態方程得到:

  1. d(i) = dL(i)/C(i) + w(i) (3.1.2)
  2. d(i) = dL(i)/C(i) + m(i) + v(i) (3.1.3)

其中dL(i)表示相鄰兩幀的長度差,C(i)表示網絡信道容量,m(i)表示網絡排隊延遲,v(i)表示零均值噪聲。m(i)即是我們要求得的網絡排隊延遲。通過Kalman Filter可以求得該值。具體計算過程請參考文獻[1][4][6]。

3.2 過載檢測器(Over-use Detector)


該模塊以到達時間濾波器計算得到的網絡排隊延遲m(i)爲輸入,結合當前閾值gamma_1,判斷當前網絡是否過載。判斷算法如圖5所示[2]。


圖5 過載檢測器僞代碼

算法基於當前網絡排隊延遲m(i)和當前閾值gamma_1判斷當前網絡擁塞狀況[2]:當m(i) > gamma_1時,算法計算處於當前狀態的持續時間t(ou) = t(ou) + delta(t),如果t(ou)大於設定閾值gamma_2(實際計算中設置爲10ms),並且m(i) > m(i-1),則發出網絡過載信號Overuse,同時重置t(ou)。如果m(i)小於m(i-1),即使高於閥值gamma_1也不需要發出過載信號。當m(i) < -gamma_1時,算法認爲當前網絡處於空閒狀態,發出網絡低載信號Underuse。當 – gamma_1 <= m(i) <= gamma_1是,算法認爲當前網絡使用率適中,發出保持信號Hold。算法隨着時間軸的計算過程可從圖6中看到。


圖6 時間軸上的過載檢測過程

需要注意的是,閥值gamma_1對算法的影響很大,並且閾值gamma_1是自適應性的。如果其是靜態值,會帶來一系列問題,詳見文獻[4]。所以gamma_1需要動態調整來達到良好的表現。這就是圖1中的Adaptive threshould模塊。閾值gamma_1動態更新的公式如下:

gamma_1(i) = gamma_1(i-1) + (t(i)-t(i-1)) * K(i) * (|m(i)|-gamma_1(i-1))    (3.2.4)

當|m(i)|>gamma_1(i-1)時增加gamma_1(i),反之減小gamma_1(i),而當|m(i)|– gamma_1(i) >15,建議gamma_1(i)不更新。K(i)爲更新系數,當|m(i)|<gamma_1(i-1)時K(i) = K_d,否則K(i) = K_u。同時建議gamma_1(i)控制在[6,600]區間。太小的值會導致探測器過於敏感。建議增加係數要大於減少係數K_u > K_d。文獻[1]給出的建議值如下:

  1. gamma_1(0) = 12.5 ms
  2. gamma_2 = 10 ms
  3. K_u = 0.01
  4. K_d = 0.00018

3.3 速率控制器(Remote Rate Controller)


該模塊以過載檢測器給出的當前網絡狀態s爲輸入,首先根據圖7所示的有限狀態機判斷當前碼率的變化趨勢,然後根據圖8所示的公式計算目標碼率Ar。


圖7 目標碼率Ar變化趨勢有限狀態機

當前網絡過載時,目標碼率處於Decrease狀態;當前網絡低載時,目標碼率處於Hold狀態;當網絡正常時,處於Decrease狀態時遷移到Hold狀態,處於Hold/Increase狀態時都遷移到Increase狀態。當判斷出碼率變化趨勢後,根據圖8所示公式進行計算目標碼率。


圖8 目標碼率Ar計算公式

當碼率變化趨勢爲Increase時,當前碼率爲上次碼率乘上係數1.05;當碼率變化趨勢爲Decrease,當前碼率爲過去500ms內的最大接收碼率乘上係數0.85。當碼率變化趨勢爲Hold時,當前碼率保持不變。目標碼率Ar計算得到之後,下一步把Ar封裝到REMB報文中發送回發送端。在REMB報文中,Ar被表示爲Ar = M * 2^Exp,其中M封裝在BR Mantissa域,佔18位;Exp封裝在BR Exp域,佔6位。REMB報文是Payload爲206的RTCP報文[7],格式如圖9所示。


圖9 REMB報文格式

REMB報文每秒發送一次,當Ar(i) < 0.97 * Ar(i-1)時則立即發送。

3.4 發送端目標碼率的確定


發送端最終目標碼率的確定結合了基於丟包率計算得到的碼率As和基於延遲計算得到的碼率Ar。此外,在實際實現中還會配置目標碼率的上限值和下限值。綜合以上因素,最終目標碼率確定如下:

    target_bitrate = max( min( min(As, Ar), Amax), Amin)        (3.4.1)

目標碼率確定之後,分別設置到Encoder模塊和PacedSender模塊。

4 總結


本文在廣泛調研WebRTC GCC算法的相關RFC和論文的基礎上,全面深入學習GCC算法的理論分析,以此爲契機力圖對WebRTC的QoS有一個全面直觀的認識。爲將來深入WebRTC源代碼內部分析GCC的實現細節奠定基礎。

參考文獻


[1] A Google Congestion Control Algorithm for Real-Time Communication.
draft-alvestrand-rmcat-congestion-03
[2] Understanding the Dynamic Behaviour of the Google Congestion Control for RTCWeb.
[3] Experimental Investigation of the Google Congestion Control for Real-Time Flows.
[4] Analysis and Design of the Google Congestion Control for Web Real-time Communication (WebRTC). MMSys’16, May 10-13, 2016, Klagenfurt, Austria
[5] RFC3550: RTP - A Transport Protocol for Real-Time Applications
[6] WebRTC視頻接收緩衝區基於KalmanFilter的延遲模型.
http://www.jianshu.com/p/bb34995c549a
[7] RTCP message for Receiver Estimated Maximum Bitrate. draft-alvestrand-rmcat-remb-03



作者:weizhenwei
鏈接:http://www.jianshu.com/p/0f7ee0e0b3be
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。





WebRTC基於GCC的擁塞控制(下) - 實現分析


本文在文章[1]的基礎上,從源代碼實現角度對WebRTC的GCC算法進行分析。主要內容包括: RTCP RR的數據源、報文構造和接收,接收端基於數據包到達延遲的碼率估計,發送端碼率的計算以及生效於目標模塊。

擁塞控制是實時流媒體應用的重要服務質量保證。通過本文和文章[1][2],從數學基礎、算法步驟到實現細節,對WebRTC的擁塞控制GCC算法有一個全面深入的理解,爲進一步學習WebRTC奠定良好基礎。

1 GCC算法框架再學習

本節內容基本上是文章[1]第1節的複習,目的是再次複習GCC算法的主要框架,梳理其算法流程中的數據流和控制流,以此作爲後續章節的行文提綱。GCC算法的數據流和控制流如圖1所示。

圖1 GCC算法數據流和控制流

對發送端來講,GCC算法主要負責兩件事:1)接收來自接收端的數據包信息反饋,包括來自RTCP RR報文的丟包率和來自RTCP REMB報文的接收端估計碼率,綜合本地的碼率配置信息,計算得到目標碼率A。2)把目標碼率A生效於目標模塊,包括PacedSender模塊,RTPSender模塊和ViEEncoder模塊等。

對於接收端來講,GCC算法主要負責兩件事:1)統計RTP數據包的接收信息,包括丟包數、接收RTP數據包的最高序列號等,構造RTCP RR報文,發送回發送端。2)針對每一個到達的RTP數據包,執行基於到達時間延遲的碼率估計算法,得到接收端估計碼率,構造RTCP REMB報文,發送回發送端。

由此可見,GCC算法由發送端和接收端配合共同實現,接收端負責碼率反饋數據的生成,發送端負責根據碼率反饋數據計算目標碼率,並生效於目標模塊。本文接下來基於本節所述的GCC算法的四項子任務,分別詳細分析之。

2 RTCP RR報文構造及收發

關於WebRTC上的RTP/RTCP協議的具體實現細節,可參考文章[3]。本節主要從RR報文的數據流角度,對其數據源、報文構造和收發進行分析。其數據源和報文構造如圖2所示,報文接收和作用於碼率控制模塊如圖3所示。

在數據接收端,RTP數據包從Network線程到達Worker線程,經過Call對象,VideoReceiveStream對象到達RtpStreamReceiver對象。在該對象中,主要執行三項任務:1)接收端碼率估計;2) 轉發RTP數據包到VCM模塊;3)接收端數據統計。其中1)是下一節的重點,2)是RTP數據包進一步組幀和解碼的地方;3)是統計RTP數據包接收信息,作爲RTCP RR報文和其他數據統計模塊的數據來源,是我們本節重點分析的部分。

在RtpStreamReceiver對象中,RTP數據包經過解析得到頭部信息,作爲輸入參數調用ReceiveStatistianImpl::IncomingPacket()。該函數中分別調用UpdateCounters()和NotifyRtpCallback(),前者用來更新對象內部的統計信息,如接收數據包計數等,後者用來更新RTP回調對象的統計信息,該信息用來作爲getStats調用的數據源。

圖2 RTCP RR報文數據源及報文構造

RTCP發送模塊在ModuleProcess線程中工作,RTCP報文週期性發送。當線程判斷需要發送RTCP報文時,調用SendRTCP()進行發送。接下來調用PrepareReport()準備各類型RTCP報文的數據。對於我們關心的RR報文,會調用AddReportBlock()獲取數據源並構造ReportBlock對象:該函數首先通過ReceiveStatistianImpl::GetStatistics()拿到類型爲RtcpStatistics的數據源,然後以此填充ReportBlock對象。GetStatistics()會調用CalculateRtcpStatistics()計算ReportBlock的每一項數據,包括丟包數、接收數據包最高序列號等。ReportBlock對象會在接下來的報文構造環節通過BuildRR()進行序列化。RTCP報文進行序列化之後,交給Network線程進行網絡層發送。

圖3 RTCP RR報文接收及反饋

在發送端(即RTCP報文接收端),RTCP報文經過Network線程到達Worker線程,最後到達ModuleRtpRtcpImpl模塊調用IncomingRtcpPacket()進行報文解析工作。解析完成以後,調用TriggerCallbacksFromRTCPPackets()反饋到回調模塊。在碼率估計方面,會反饋到BitrateController模塊。ReportBlock消息最終會到達BitrateControllerImpl對象,進行下一步的目標碼率確定。

至此,關於RTCP RR報文在擁塞控制中的執行流程分析完畢。

3 接收端基於延遲的碼率估計

接收端基於數據包到達延遲的碼率估計是整個GCC算法最複雜的部分,本節在分析WebRTC代碼的基礎上,闡述該部分的實現細節。

接收端基於延遲碼率估計的基本思想是:RTP數據包的到達時間延遲m(i)反映網絡擁塞狀況。當延遲很小時,說明網絡擁塞不嚴重,可以適當增大目標碼率;當延遲變大時,說明網絡擁塞變嚴重,需要減小目標碼率;當延遲維持在一個低水平時,目標碼率維持不變。其主要由三個模塊組成:到達時間濾波器,過載檢查器和速率控制器。

在實現上,WebRTC定義該模塊爲遠端碼率估計模塊RemoteBitrateEstimator,整個模塊的工作流程如圖4所示。需要注意的是,該模塊需要RTP報文擴展頭部abs-send-time的支持,用以記錄RTP數據包在發送端的絕對發送時間,詳細請參考文獻[4]。

圖4 GCC算法基於延遲的碼率估計

接收端收到RTP數據包後,經過一系列調用到RtpStreamReceiver對象,由該對象調用遠端碼率估計模塊的總控對象RemoteBitrateEstimatorAbsSendTime,由該對象的總控函數IncomingPacketInfo()負責整個碼率估計流程,如圖4所示,算法從左到右依次調用子對象的功能函數。

總控函數首先調用InterArrival::ComputeDeltas()函數,用以計算相鄰數據包組的到達時間相對延遲,該部分對應文章[1]的3.1節內容。在計算到達時間相對延遲時,用到了RTP報文頭部擴展abs-send-time。另外,實現細節上要注意數據包組的劃分,以及對亂序和突發時間的處理。

接下來算法調用OveruseEstimator::Update()函數,用以估計數據包的網絡延遲,該部分對應文章[1]的3.2節內容。對網絡延遲的估計用到了Kalman濾波,算法的具體細節請參考文章[2]。Kalman濾波的結果爲網絡延遲m(i),作爲下一階段網絡狀態檢測的輸入參數。

算法接着調用OveruseDetector::Detect(),用來檢測當前網絡的擁塞狀況,該部分對應文章[1]的3.2節內容。網絡狀態檢測用當前網絡延遲m(i)和閾值gamma_1進行比較,判斷出overuse,underuse和normal三種網絡狀態之一。在算法細節上,要注意overuse的判定相對複雜一些:當m(i) > gamma_1時,計算處於當前狀態的持續時間t(ou),如果t(ou) > gamma_2,並且m(i) > m(i-1),則發出網絡過載信號Overuse。如果m(i)小於m(i-1),即使高於閥值gamma_1也不需要發出過載信號。在判定網絡擁塞狀態之後,還要調用UpdateThreshold()更新閾值gamma_1。

算法接着調用AimdRateControl::Update()和UpdateBandwidthEstimate()函數,用以估計當前網絡狀態下的目標碼率Ar,該部分對應文章[1]的3.3節。算法基於當前網絡狀態和碼率變化趨勢有限狀態機,採用AIMD(Additive Increase Multiplicative Decrease)方法計算目標碼率,具體計算公式請參考文章[1]。需要注意的是,當算法處於開始階段時,會採用Multiplicative Increase方法快速增加碼率,以加快碼率估計速度。

此時,我們已經拿到接收端估計的目標碼率Ar。接下來以Ar爲參數調用VieRemb對象的OnReceiveBitrateChange()函數,發送REMB報文到發送端。REMB報文會推送到RTCP模塊,並設置REMB報文發送時間爲立即發送。關於REMB報文接下來的發送和接收流程,和第1節描述的RTCP報文一般處理流程是一樣的,即經過序列化發送到網絡,然後發送端收到以後,反序列化出描述結構,最後通過回調函數到達發送端碼率控制模塊BitrateControllerImpl。

至此,接收端基於延遲的碼率估計過程描述完畢。

4 發送端碼率計算及生效

在發送端,目標碼率計算和生效是異步進行的,即Worker線程從RTCP接收模塊經回調函數拿到丟包率和REMB碼率之後,計算得到目標碼率A;然後ModuleProcess線程異步把目標碼率A生效到目標模塊如PacedSender和ViEEncoder等。下面分別描述碼率計算和生效過程。

圖5 發送端碼率計算過程

碼率計算過程如圖5所示:Worker線程從RTCPReceiver模塊經過回調函數拿到RTCP RR報文和REMB報文的數據,到達BitrateController模塊。

RR報文中的丟包率會進入Update()函數中計算碼率,碼率計算公式如文章[1]第2節所述。

然後算法流程進入CapBitrateToThreshold()函數,和配置的最大最小碼率和遠端估計碼率進行比較後,確定最終目標碼率。

而REMB報文的接收端估計碼率Ar則直接進入CapBitrateToThreshold()函數參與目標碼率的確定。目標碼率由文章[1]的3.4節所示公式進行確定。

需要注意的是,RR報文和REMB報文一般不在同一個RTCP報文裏。

圖6 發送端碼率生效過程

發送端碼率生效過程如圖6所示:ModuleProcess線程調用擁塞控制總控對象CongestionController週期性從碼率控制模塊BitrateControllerImpl中獲取當前最新目標碼率A,然後判斷目標碼率是否有變化。若是,則把最新目標碼率設置到相關模塊中,主要包括PacedSender模塊,RTPSender模塊和ViEEncoder模塊。

對於PacedSender模塊,設置碼率主要是爲了平滑RTP數據包的發送速率,儘量避免數據包Burst造成碼率波動。對於RTPSender模塊,設置碼率是爲了給NACK模塊預留碼率,如果預留碼率過小,則在某些情況下對於NACK報文請求選擇不響應。對於ViEEncoder模塊,設置碼率有兩個用途:1)控制發送端丟幀策略,根據設定碼率和漏桶算法決定是否丟棄當前幀。2)控制編碼器內部碼率控制,設定碼率作爲參數傳輸到編碼器內部,參與內部碼率控制過程。

至此,發送端碼率計算和生效過程分析完畢。

5 總結

本文結合文章[1],深入WebRTC代碼內部,詳細分析了WebRTC的GCC算法的實現細節。通過本文,對WebRTC的代碼結構和擁塞控制實現細節有了更深層次的理解,爲進一步學習WebRTC奠定良好基礎。

參考文獻

[1] WebRTC基於GCC的擁塞控制(上) – 算法分析 http://www.jianshu.com/p/0f7ee0e0b3be [2] WebRTC視頻接收緩衝區基於KalmanFilter的延遲模型. http://www.jianshu.com/p/bb34995c549a [3] WebRTC中RTP/RTCP協議實現分析 http://www.jianshu.com/p/c84be6f3ddf3 [4] abs-send-time. https://webrtc.org/experiments/rtp-hdrext/abs-send-time/

作者:weizhenwei
鏈接:http://www.jianshu.com/p/5259a8659112
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。



WEBRTC 發送端擁塞控制


音視頻傳輸核心主要是通過發送端來控制服務質量,但服務質量的決策可根據發送端丟包率算法和接收端數據包延遲算法來計算實際的目標碼率,進而反饋給源端,即編碼端和RTP發送端,如下類圖:

                

數據流圖

                           

函數主要調用次順:

擁塞算法得到的碼率如何作用於編碼模塊和發送模塊

CongestionController::Process->

BitrateControllerImpl::Process-> WrappingBitrateEstimator::Process->

CongestionController::MaybeTriggerOnNetworkChanged()->

BitrateControllerImpl::GetNetworkParameters->CongestionController:Observer::OnNetworkChanged->Call::OnNetworkChanged(uint32_ttarget_bitrate_bps, uint8_t fraction_loss,int64_t rtt_ms)->BitrateAllocator::OnNetworkChanged[改變編碼碼率]|CongestionController::SetAllocatedSendBitrate[改變發送碼率]-> VideoSendStream::OnBitrateUpdated->PayloadRouter::SetTargetSendBitrate[ModuleRtpRtcpImpl::SetTargetSendBitrate進入RTP模塊]->ViEEncoder::OnBitrateUpdated[進入編碼模塊]-> VideoSender::SetChannelParameters[encoder_params_爲新的編碼參數]-> VideoSender::SetEncoderParameters->VCMGenericEncoder::SetEncoderParameters->H264EncoderImpl::SetRates->







WebRTC之帶寬控制部分學習(1) 

from: http://blog.csdn.net/u013160228/article/details/46392037

可以看到WebRTCd代碼分成了4個部分,目前爲止只看了talk裏的一些東西。

      1、 talk中的項目文件大多數都是以libjingle開頭的,可以看出libjingle是WebRTC中非常核心的一個東西啦。

       libjingle中開闢了兩個通道,分別爲會話通道和數據通道,一個用來會話建立管理等,一個用來傳輸數據。

       libjingle_media是用來渲染獲取視頻,libjingle_p2p和libjingle_peerconnection是不同的會話方式用到的庫吧,現在還沒有細看

       peerconnection_client和peerconnection_server則是可以直接運行的demo,設爲啓動項目就可以測試了,先運行server。

     2、由於項目研究的是關於帶寬估計的東西,所以我們看的文件主要有兩個,一個是上行帶寬的估計和控制,一個是下行帶寬的估計和控制

          上行帶寬的文件在webrtc/modules/bitrate_controller,下行帶寬的文件是remote_bitrate_estimator

         下面講一講Goolge的WebRTC中自帶的碼率控制:

                  

發送端發送RTP包,同時接收來自於接收端的RTCP反饋報告,整個擁塞控制算法分成了接收端和發送端兩個部分。接收端的控制算法是基於時延的算法,其目的是減小時延;發送端的控制算法是基於丟包率的算法,其目的是減少丟包。接收端的算法計算出一個速率Ar,然後將這個碼率反饋給發送端,用來限制發送端基於丟包率算法計算出來的發送速率。


發送端基於丟包率的控制方法在每一個tk時刻或者tr時刻啓動。tk表示第k個RTCP反饋報告的到達時間,tr表示第r個REMB信息到達發送端的時間,其中REMB信息中包含接收端反饋的Ar信息。RTCP包內包含丟包率fl(tk),發送端根據丟包率計算接下來的發送速率,具體計算方式圖公式1所示:






  接收端速率控制:

   

                接收端算法的目的就是計算出能保證低時延情況下的接收速率Ar,Ar的計算過程如圖2所示:


      

 下面介紹接收端也就是remote_bitrate_estimator中的幾個模塊算法

       

1) The arrival-time filter

該模塊的目的是爲了計算排隊時延m(ti),單向時延(one way delay variation)計算方式如下:dm(ti)= titi-1–(Ti –Ti-1 )                                (3)

       式中,Ti表示第i幀視頻發送的時間戳,ti 表示第i個視頻幀接收到的時間戳。單向時延是三個部分的總和,包括傳輸時間、排隊時延m(ti)、網絡抖動n(ti)。GCC算法中提出了下面這個公式:


  

式中,∆L(ti) = L(ti−L(ti−1),L(ti)是第i個視頻幀的長度,C(ti)是瓶頸鍊路容量估計,n(ti)是網絡抖動(高斯噪聲模型)。爲了將dm(ti− d(ti)的差值縮小到趨於零,用卡爾曼濾波器提取出狀態向量,具體工作方式如圖3所示:




The over-use detector

       每幀視頻收到的時刻,即ti時刻,該模塊都會產生一個s信號以驅動下一個碼率控制模塊。m(ti)大於閥值γ,信號爲overuse;當m(ti)小於閥值γ,信號爲underuse。


Remote rate controller

       該模塊的目的是爲了估算數接收速率Ar,其算法流程如圖2所示,由信號s決定碼率的調整,η ∈[1.005,1.3],α∈[0.8,0.95]分別爲增性係數和鹼性係數。

上調:Ar(ti)= ηAr(ti−1)

下調:Ar(ti)= αR(ti),其中R(ti)是最近500ms內的平均接收速率。



REMB Processing

       該模塊的功能是將上一個模塊計算出的速率Ar通過REMB信息發給客戶端。REMB信息發送間隔爲1s,但當Ar下降了3%,即Ar(ti) < 0.97Ar(ti−1)時,則立即發送REMB信息以調整碼率。






webrtc中的帶寬自適應算法

from: http://blog.csdn.net/chenyefei/article/details/51896237

webrtc中的帶寬自適應算法分爲兩種:

1, 發端帶寬控制, 原理是由rtcp中的丟包統計來動態的增加或減少帶寬,在減少帶寬時使用TFRC算法來增加平滑度。

2, 收端帶寬估算, 原理是並由收到rtp數據,估出帶寬; 用卡爾曼濾波,對每一幀的發送時間和接收時間進行分析, 從而得出網絡帶寬利用情況,修正估出的帶寬。


兩種算法相輔相成, 收端將估算的帶寬發送給發端, 發端結合收到的帶寬以及丟包率,調整發送的帶寬。


下面具體分析兩種算法:














webrtc中rtcp反饋與碼率控制模塊分析

http://blog.csdn.net/mercy_pm/article/details/71474264


0. 參考文檔

1 google congestion control

1. 簡介

webrtc的帶寬估計分爲兩部分,一部分爲發送端根據rtcp反饋信息進行反饋,另一部分爲接收端根據收到的rtp數據進行相應的碼率估計[1]。 
本文先分析發送端根據rtcp反饋信息進行碼率調整的部分代碼。

具體計算公式: 

2. 代碼結構

2.1 類關係

rtp_stream_receiver中有一個繼承自抽象類RtpRtcp的ModuleRtpRtcpImpl,ModuleRtpRtcpImpl中有一個rtcp_receiver。當有RTCP包到來時,逐層處理至rtcp_receiver,當包是rtcp receiver report包,則會將包解析,然後在ModuleRtpRtcpImpl中再次調用rtcp_receiver中的TriggerCallbacksFromRTCPPacket函數,觸發對應rtcp的一些事件,反饋觸發的主要是_cbRtcpBandwidthObserver的觀察者(RtcpBandwidthObserverImpl),這個觀察者收到對應的report block之後會計算成帶寬估計所需要的參數,並調用屬主bitratecontrolImpl類對帶寬進行估計,這裏會調用SendSideBandwidthEstimation中的UpdateReceiverBlock進行實際的帶寬評估。

2.2 調用關係圖

3. 代碼分析

3.1 HandleReportBlock

這個函數中最主要的部分就是RTT的計算,webrtc中對於RTT平滑的因子是一個線性增長的因子。

  1. /* 這個函數根據對應的report block生成了一個新的RTCPReportBlockInformation結構體,
  2. * 並計算出對應的RTT,多report block在調用點處執行循環。 */
  3. void RTCPReceiver::HandleReportBlock(
  4. const RTCPUtility::RTCPPacket& rtcpPacket,
  5. RTCPPacketInformation& rtcpPacketInformation,
  6. uint32_t remoteSSRC)
  7. EXCLUSIVE_LOCKS_REQUIRED(_criticalSectionRTCPReceiver) {
  8. // This will be called once per report block in the RTCP packet.
  9. // We filter out all report blocks that are not for us.
  10. // Each packet has max 31 RR blocks.
  11. //
  12. // We can calc RTT if we send a send report and get a report block back.
  13. // |rtcpPacket.ReportBlockItem.SSRC| is the SSRC identifier of the source to
  14. // which the information in this reception report block pertains.
  15. // Filter out all report blocks that are not for us.
  16. if (registered_ssrcs_.find(rtcpPacket.ReportBlockItem.SSRC) ==
  17. registered_ssrcs_.end()) {
  18. // This block is not for us ignore it.
  19. return;
  20. }
  21. RTCPReportBlockInformation* reportBlock =
  22. CreateOrGetReportBlockInformation(remoteSSRC,
  23. rtcpPacket.ReportBlockItem.SSRC);
  24. if (reportBlock == NULL) {
  25. LOG(LS_WARNING) << "Failed to CreateReportBlockInformation("
  26. << remoteSSRC << ")";
  27. return;
  28. }
  29. // 用於RTCP超時的計算。
  30. _lastReceivedRrMs = _clock->TimeInMilliseconds();
  31. // 其他字段的拷貝。
  32. const RTCPPacketReportBlockItem& rb = rtcpPacket.ReportBlockItem;
  33. reportBlock->remoteReceiveBlock.remoteSSRC = remoteSSRC;
  34. reportBlock->remoteReceiveBlock.sourceSSRC = rb.SSRC;
  35. reportBlock->remoteReceiveBlock.fractionLost = rb.FractionLost;
  36. reportBlock->remoteReceiveBlock.cumulativeLost =
  37. rb.CumulativeNumOfPacketsLost;
  38. if (rb.ExtendedHighestSequenceNumber >
  39. reportBlock->remoteReceiveBlock.extendedHighSeqNum) {
  40. // We have successfully delivered new RTP packets to the remote side after
  41. // the last RR was sent from the remote side.
  42. _lastIncreasedSequenceNumberMs = _lastReceivedRrMs;
  43. }
  44. reportBlock->remoteReceiveBlock.extendedHighSeqNum =
  45. rb.ExtendedHighestSequenceNumber;
  46. reportBlock->remoteReceiveBlock.jitter = rb.Jitter;
  47. reportBlock->remoteReceiveBlock.delaySinceLastSR = rb.DelayLastSR;
  48. reportBlock->remoteReceiveBlock.lastSR = rb.LastSR;
  49. if (rtcpPacket.ReportBlockItem.Jitter > reportBlock->remoteMaxJitter) {
  50. reportBlock->remoteMaxJitter = rtcpPacket.ReportBlockItem.Jitter;
  51. }
  52. int64_t rtt = 0;
  53. uint32_t send_time = rtcpPacket.ReportBlockItem.LastSR;
  54. // RFC3550, section 6.4.1, LSR field discription states:
  55. // If no SR has been received yet, the field is set to zero.
  56. // Receiver rtp_rtcp module is not expected to calculate rtt using
  57. // Sender Reports even if it accidentally can.
  58. if (!receiver_only_ && send_time != 0) {
  59. // 當RR在SR之前發送,send_time爲0.
  60. // delay計算:
  61. // Send SR Receive RR
  62. // | delay in RR |
  63. // | |<----------->| |
  64. // |<---------------------->| |<----------------------->|
  65. //
  66. // RTT = total_time - delay_in_RR
  67. // = receiver_rr_time - send_sr_time - delay_in_RR
  68. // 即使中間幾個SR丟包,但是如果RTT本身是平滑的,那麼RTT不會受到這幾個丟包的影響
  69. // 因爲SR->RR之間的delay可以精確計算。
  70. uint32_t delay = rtcpPacket.ReportBlockItem.DelayLastSR;
  71. // Local NTP time.
  72. uint32_t receive_time = CompactNtp(NtpTime(*_clock));
  73. // RTT in 1/(2^16) seconds.
  74. uint32_t rtt_ntp = receive_time - delay - send_time;
  75. // Convert to 1/1000 seconds (milliseconds).
  76. rtt = CompactNtpRttToMs(rtt_ntp);
  77. if (rtt > reportBlock->maxRTT) {
  78. // Store max RTT.
  79. reportBlock->maxRTT = rtt;
  80. }
  81. if (reportBlock->minRTT == 0) {
  82. // First RTT.
  83. reportBlock->minRTT = rtt;
  84. } else if (rtt < reportBlock->minRTT) {
  85. // Store min RTT.
  86. reportBlock->minRTT = rtt;
  87. }
  88. // Store last RTT.
  89. reportBlock->RTT = rtt;
  90. // store average RTT
  91. // RTT的平滑計算。
  92. // 如果這個塊是在CreateOrGetReportBlockInformation新生成的,
  93. // 則權重會從0開始隨着受到的report逐漸遞增。
  94. // srtt(i) = i/(i+1)*srtt(i-1) + 1/(i+1)*rtt + 0.5
  95. if (reportBlock->numAverageCalcs != 0) {
  96. float ac = static_cast<float>(reportBlock->numAverageCalcs);
  97. float newAverage =
  98. ((ac / (ac + 1)) * reportBlock->avgRTT) + ((1 / (ac + 1)) * rtt);
  99. reportBlock->avgRTT = static_cast<int64_t>(newAverage + 0.5f);
  100. } else {
  101. // First RTT.
  102. reportBlock->avgRTT = rtt;
  103. }
  104. reportBlock->numAverageCalcs++;
  105. }
  106. TRACE_COUNTER_ID1(TRACE_DISABLED_BY_DEFAULT("webrtc_rtp"), "RR_RTT", rb.SSRC,
  107. rtt);
  108. // 添加回rtcpPacketInformation,在ModuleRtpRtcpImpl中會使用這個進行事件回調。
  109. rtcpPacketInformation.AddReportInfo(*reportBlock);
  110. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

3.2 UpdateMinHistory

這個函數主要用於更新變量min_bitrate_history_,這個變量將會作用於上升區間,用來作爲基數,這裏簡單描述下。

  1. // Updates history of min bitrates.
  2. // After this method returns min_bitrate_history_.front().second contains the
  3. // min bitrate used during last kBweIncreaseIntervalMs.
  4. // 主要結合這個函數解釋下變量min_bitrate_history_
  5. // 這個變量的兩個維度,front記錄的是離當前最遠的時間,
  6. // 每個速率都是按照時間先後順序逐漸push到尾部。
  7. // 因此更新的時候,需要先將超時的元素從列表頭剔除。
  8. // 後一個維度是最小速率值,
  9. // 在相同的時間區間內,保留最小的速率值。
  10. // |-------Interval 1---------|----------Interval 2------|
  11. // | | |
  12. // |--t1 < t2 < t3 < t4 < t5--|--t1 < t2 < t3 < t4 < t5--|
  13. // 這樣的操作較爲簡單,不用在每次插入元素時去判斷對應的時間區域,再找到對應時間區間的最小值,用部分冗餘的內存換取操作的快捷。
  14. void SendSideBandwidthEstimation::UpdateMinHistory(int64_t now_ms) {
  15. // Remove old data points from history.
  16. // Since history precision is in ms, add one so it is able to increase
  17. // bitrate if it is off by as little as 0.5ms.
  18. while (!min_bitrate_history_.empty() &&
  19. now_ms - min_bitrate_history_.front().first + 1 >
  20. kBweIncreaseIntervalMs) {
  21. min_bitrate_history_.pop_front();
  22. }
  23. // Typical minimum sliding-window algorithm: Pop values higher than current
  24. // bitrate before pushing it.
  25. while (!min_bitrate_history_.empty() &&
  26. bitrate_ <= min_bitrate_history_.back().second) {
  27. min_bitrate_history_.pop_back();
  28. }
  29. min_bitrate_history_.push_back(std::make_pair(now_ms, bitrate_));
  30. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

3.3 UpdateEstimate

函數UpdateReceiverBlock會根據當前的report block對當前帶寬估計的一些變量進行相應的賦值,此外,只有當傳輸包的數量達到一定數量纔會再次觸發帶寬估計的調整。函數UpdateEstimate是主要用於帶寬估計的函數。

  1. void SendSideBandwidthEstimation::UpdateEstimate(int64_t now_ms) {
  2. // We trust the REMB and/or delay-based estimate during the first 2 seconds if
  3. // we haven't had any packet loss reported, to allow startup bitrate probing.
  4. if (last_fraction_loss_ == 0 && IsInStartPhase(now_ms)) {
  5. uint32_t prev_bitrate = bitrate_;
  6. // bwe_incoming_是remb更新的值,如果當前無丟包且在啓動階段,直接使用remb的值。
  7. if (bwe_incoming_ > bitrate_)
  8. bitrate_ = CapBitrateToThresholds(now_ms, bwe_incoming_);
  9. ...
  10. }
  11. }
  12. UpdateMinHistory(now_ms);
  13. // Only start updating bitrate when receiving receiver blocks.
  14. // TODO(pbos): Handle the case when no receiver report is received for a very
  15. // long time.
  16. if (time_last_receiver_block_ms_ != -1) {
  17. if (last_fraction_loss_ <= 5) {
  18. // Loss < 2%: Increase rate by 8% of the min bitrate in the last
  19. // kBweIncreaseIntervalMs.
  20. // Note that by remembering the bitrate over the last second one can
  21. // rampup up one second faster than if only allowed to start ramping
  22. // at 8% per second rate now. E.g.:
  23. // If sending a constant 100kbps it can rampup immediatly to 108kbps
  24. // whenever a receiver report is received with lower packet loss.
  25. // If instead one would do: bitrate_ *= 1.08^(delta time), it would
  26. // take over one second since the lower packet loss to achieve 108kbps.
  27. //TODO:tjl
  28. // 這裏與公式有一定不同:
  29. // 1. 係數不同,且附帶一定的修正值(向上取整加1kbps)
  30. // 2. 取的是上一個時間間隔之內最小值,比較平滑。
  31. bitrate_ = static_cast<uint32_t>(
  32. min_bitrate_history_.front().second * 1.08 + 0.5);
  33. // Add 1 kbps extra, just to make sure that we do not get stuck
  34. // (gives a little extra increase at low rates, negligible at higher
  35. // rates).
  36. bitrate_ += 1000;
  37. event_log_->LogBwePacketLossEvent(
  38. bitrate_, last_fraction_loss_,
  39. expected_packets_since_last_loss_update_);
  40. } else if (last_fraction_loss_ <= 26) {
  41. // Loss between 2% - 10%: Do nothing.
  42. } else {
  43. // Loss > 10%: Limit the rate decreases to once a kBweDecreaseIntervalMs +
  44. // rtt.
  45. if (!has_decreased_since_last_fraction_loss_ &&
  46. (now_ms - time_last_decrease_ms_) >=
  47. (kBweDecreaseIntervalMs + last_round_trip_time_ms_)) {
  48. time_last_decrease_ms_ = now_ms;
  49. // Reduce rate:
  50. // newRate = rate * (1 - 0.5*lossRate);
  51. // where packetLoss = 256*lossRate;
  52. //TODO:tjl
  53. // 當從未開始降低窗口值,且距離上一次衰減的時間差大於衰減週期加上rtt。
  54. // 其實當前貌似只有這個case下會對這兩個變量賦值。
  55. // 這裏的last_fraction_loss_是一次統計間隔(一定包數)之間的總丟包率。
  56. // 丟包率的單位是1/256,因此這裏是(1 - 丟包率/2) * 當前速率
  57. // 與公式相同。
  58. bitrate_ = static_cast<uint32_t>(
  59. (bitrate_ * static_cast<double>(512 - last_fraction_loss_)) /
  60. 512.0);
  61. has_decreased_since_last_fraction_loss_ = true;
  62. }
  63. event_log_->LogBwePacketLossEvent(
  64. bitrate_, last_fraction_loss_,
  65. expected_packets_since_last_loss_update_);
  66. }
  67. }
  68. // 在有效範圍內修正。
  69. bitrate_ = CapBitrateToThresholds(now_ms, bitrate_);
  70. }






WebRTC的帶寬評估的新變化


http://blog.csdn.net/volvet/article/details/62237375


帶寬評估(BWE)也許是WebRTC的視頻引擎中最關鍵的模塊了, 它將決定視頻通信中, 不引發網絡擁塞時最多可以產生的視頻數據量.

早期的帶寬評估算法比較簡陋, 大多是基於丟包來估計, 基本的策略是逐步增加發送的數據量, 直到檢測到丟包爲止. 爲了讓發送端獲悉網絡上的丟包信息, 可以使用標準的RTCP的RR來發送週期性的報告.

現代的帶寬評估算法則可以在網絡鏈路發生丟包以前就監測到網絡擁塞, 它可以通過偵測數據包接收的時延來預測未來可能的擁塞. 它是基於鏈路上的路由器都有一定的緩存, 在數據包開始被丟棄之前, 先發生數據在緩存裏堆積的事件, 所以時延相比於丟包, 對擁塞的反應更加靈敏. 現在幾個典型的算法有: Google Congest Control(https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02), 愛立信的SCEAM(https://github.com/EricssonResearch/scream) 和 MIT的SPROUT(http://aim.nms.lcs.mit.edu/papers/nsdi13-sprout.pdf). Mozilla的這篇文章講述了擁塞控制算法演變的歷史(https://blog.mozilla.org/webrtc/what-is-rmcat-congestion-control/)

WebRTC剛開始的時候是用接受端的帶寬評估來決定發送端的碼率的, 如上文, 接受端根據接受到的數據量和數據包時延的變化, 得到當前網絡帶寬的評估, 使用REMB 將評估的帶寬彙報給發送端. 另一個細節是, WebRTC的發送端除了使用REMB之外, 還會根據丟包情況來決定當前的視頻碼率, 僞代碼如下:

Sender pseudocode (send_side_bandwidth_estimation.cc): onFeedbackFromReceiver(lossRate): if (lossRate < 2%) video_bitrate *= 1.08 if (lossRate > 10%) video_bitrate *= (1 - 0.5*lossRate) if (video_bitrate > bwe) video_bitrate = bwe;

比較好的碼率控制方法是, 檢測到擁塞是,迅速下調碼率, 當網絡無擁塞是, 緩慢上調碼率.

最近的WebRTC已把帶寬評估整個搬到了發送端. 不過評估機制跟之前並沒有顯著的變化, 接受端需要把時延信息彙報給發送端, 這一機制引入了2個新的協議. 
1. Transport wide sequence number header extension. 
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 
2. Transport Feedback 
a=rtcp-fb:100 transport-cc

詳細可以參看: https://www.ietf.org/archive/id/draft-holmer-rmcat-transport-wide-cc-extensions-01.txt

The Original Post

http://www.rtcbits.com/2017/01/bandwidth-estimation-in-webrtc-and-new.html



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章