日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

webrtc中的码率控制

發布時間:2023/12/16 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 webrtc中的码率控制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

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


名詞解釋:


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)也可由如下狀態方程得到:

  • d(i) = dL(i)/C(i) + w(i) (3.1.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]給出的建議值如下:

  • gamma_1(0) = 12.5 ms
  • gamma_2 = 10 ms
  • K_u = 0.01
  • 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)=?ti–ti-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平滑的因子是一個線性增長的因子。

  • /* 這個函數根據對應的report block生成了一個新的RTCPReportBlockInformation結構體,
  • * 并計算出對應的RTT,多report block在調用點處執行循環。 */
  • void RTCPReceiver::HandleReportBlock(
  • const RTCPUtility::RTCPPacket& rtcpPacket,
  • RTCPPacketInformation& rtcpPacketInformation,
  • uint32_t remoteSSRC)
  • EXCLUSIVE_LOCKS_REQUIRED(_criticalSectionRTCPReceiver) {
  • // This will be called once per report block in the RTCP packet.
  • // We filter out all report blocks that are not for us.
  • // Each packet has max 31 RR blocks.
  • //
  • // We can calc RTT if we send a send report and get a report block back.
  • // |rtcpPacket.ReportBlockItem.SSRC| is the SSRC identifier of the source to
  • // which the information in this reception report block pertains.
  • // Filter out all report blocks that are not for us.
  • if (registered_ssrcs_.find(rtcpPacket.ReportBlockItem.SSRC) ==
  • registered_ssrcs_.end()) {
  • // This block is not for us ignore it.
  • return;
  • }
  • RTCPReportBlockInformation* reportBlock =
  • CreateOrGetReportBlockInformation(remoteSSRC,
  • rtcpPacket.ReportBlockItem.SSRC);
  • if (reportBlock == NULL) {
  • LOG(LS_WARNING) << "Failed to CreateReportBlockInformation("
  • << remoteSSRC << ")";
  • return;
  • }
  • // 用于RTCP超時的計算。
  • _lastReceivedRrMs = _clock->TimeInMilliseconds();
  • // 其他字段的拷貝。
  • const RTCPPacketReportBlockItem& rb = rtcpPacket.ReportBlockItem;
  • reportBlock->remoteReceiveBlock.remoteSSRC = remoteSSRC;
  • reportBlock->remoteReceiveBlock.sourceSSRC = rb.SSRC;
  • reportBlock->remoteReceiveBlock.fractionLost = rb.FractionLost;
  • reportBlock->remoteReceiveBlock.cumulativeLost =
  • rb.CumulativeNumOfPacketsLost;
  • if (rb.ExtendedHighestSequenceNumber >
  • reportBlock->remoteReceiveBlock.extendedHighSeqNum) {
  • // We have successfully delivered new RTP packets to the remote side after
  • // the last RR was sent from the remote side.
  • _lastIncreasedSequenceNumberMs = _lastReceivedRrMs;
  • }
  • reportBlock->remoteReceiveBlock.extendedHighSeqNum =
  • rb.ExtendedHighestSequenceNumber;
  • reportBlock->remoteReceiveBlock.jitter = rb.Jitter;
  • reportBlock->remoteReceiveBlock.delaySinceLastSR = rb.DelayLastSR;
  • reportBlock->remoteReceiveBlock.lastSR = rb.LastSR;
  • if (rtcpPacket.ReportBlockItem.Jitter > reportBlock->remoteMaxJitter) {
  • reportBlock->remoteMaxJitter = rtcpPacket.ReportBlockItem.Jitter;
  • }
  • int64_t rtt = 0;
  • uint32_t send_time = rtcpPacket.ReportBlockItem.LastSR;
  • // RFC3550, section 6.4.1, LSR field discription states:
  • // If no SR has been received yet, the field is set to zero.
  • // Receiver rtp_rtcp module is not expected to calculate rtt using
  • // Sender Reports even if it accidentally can.
  • if (!receiver_only_ && send_time != 0) {
  • // 當RR在SR之前發送,send_time為0.
  • // delay計算:
  • // Send SR Receive RR
  • // | delay in RR |
  • // | |<----------->| |
  • // |<---------------------->| |<----------------------->|
  • //
  • // RTT = total_time - delay_in_RR
  • // = receiver_rr_time - send_sr_time - delay_in_RR
  • // 即使中間幾個SR丟包,但是如果RTT本身是平滑的,那么RTT不會受到這幾個丟包的影響
  • // 因為SR->RR之間的delay可以精確計算。
  • uint32_t delay = rtcpPacket.ReportBlockItem.DelayLastSR;
  • // Local NTP time.
  • uint32_t receive_time = CompactNtp(NtpTime(*_clock));
  • // RTT in 1/(2^16) seconds.
  • uint32_t rtt_ntp = receive_time - delay - send_time;
  • // Convert to 1/1000 seconds (milliseconds).
  • rtt = CompactNtpRttToMs(rtt_ntp);
  • if (rtt > reportBlock->maxRTT) {
  • // Store max RTT.
  • reportBlock->maxRTT = rtt;
  • }
  • if (reportBlock->minRTT == 0) {
  • // First RTT.
  • reportBlock->minRTT = rtt;
  • } else if (rtt < reportBlock->minRTT) {
  • // Store min RTT.
  • reportBlock->minRTT = rtt;
  • }
  • // Store last RTT.
  • reportBlock->RTT = rtt;
  • // store average RTT
  • // RTT的平滑計算。
  • // 如果這個塊是在CreateOrGetReportBlockInformation新生成的,
  • // 則權重會從0開始隨著受到的report逐漸遞增。
  • // srtt(i) = i/(i+1)*srtt(i-1) + 1/(i+1)*rtt + 0.5
  • if (reportBlock->numAverageCalcs != 0) {
  • float ac = static_cast<float>(reportBlock->numAverageCalcs);
  • float newAverage =
  • ((ac / (ac + 1)) * reportBlock->avgRTT) + ((1 / (ac + 1)) * rtt);
  • reportBlock->avgRTT = static_cast<int64_t>(newAverage + 0.5f);
  • } else {
  • // First RTT.
  • reportBlock->avgRTT = rtt;
  • }
  • reportBlock->numAverageCalcs++;
  • }
  • TRACE_COUNTER_ID1(TRACE_DISABLED_BY_DEFAULT("webrtc_rtp"), "RR_RTT", rb.SSRC,
  • rtt);
  • // 添加回rtcpPacketInformation,在ModuleRtpRtcpImpl中會使用這個進行事件回調。
  • rtcpPacketInformation.AddReportInfo(*reportBlock);
  • }
    • 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_,這個變量將會作用于上升區間,用來作為基數,這里簡單描述下。

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

  • void SendSideBandwidthEstimation::UpdateEstimate(int64_t now_ms) {
  • // We trust the REMB and/or delay-based estimate during the first 2 seconds if
  • // we haven't had any packet loss reported, to allow startup bitrate probing.
  • if (last_fraction_loss_ == 0 && IsInStartPhase(now_ms)) {
  • uint32_t prev_bitrate = bitrate_;
  • // bwe_incoming_是remb更新的值,如果當前無丟包且在啟動階段,直接使用remb的值。
  • if (bwe_incoming_ > bitrate_)
  • bitrate_ = CapBitrateToThresholds(now_ms, bwe_incoming_);
  • ...
  • }
  • }
  • UpdateMinHistory(now_ms);
  • // Only start updating bitrate when receiving receiver blocks.
  • // TODO(pbos): Handle the case when no receiver report is received for a very
  • // long time.
  • if (time_last_receiver_block_ms_ != -1) {
  • if (last_fraction_loss_ <= 5) {
  • // Loss < 2%: Increase rate by 8% of the min bitrate in the last
  • // kBweIncreaseIntervalMs.
  • // Note that by remembering the bitrate over the last second one can
  • // rampup up one second faster than if only allowed to start ramping
  • // at 8% per second rate now. E.g.:
  • // If sending a constant 100kbps it can rampup immediatly to 108kbps
  • // whenever a receiver report is received with lower packet loss.
  • // If instead one would do: bitrate_ *= 1.08^(delta time), it would
  • // take over one second since the lower packet loss to achieve 108kbps.
  • //TODO:tjl
  • // 這里與公式有一定不同:
  • // 1. 系數不同,且附帶一定的修正值(向上取整加1kbps)
  • // 2. 取的是上一個時間間隔之內最小值,比較平滑。
  • bitrate_ = static_cast<uint32_t>(
  • min_bitrate_history_.front().second * 1.08 + 0.5);
  • // Add 1 kbps extra, just to make sure that we do not get stuck
  • // (gives a little extra increase at low rates, negligible at higher
  • // rates).
  • bitrate_ += 1000;
  • event_log_->LogBwePacketLossEvent(
  • bitrate_, last_fraction_loss_,
  • expected_packets_since_last_loss_update_);
  • } else if (last_fraction_loss_ <= 26) {
  • // Loss between 2% - 10%: Do nothing.
  • } else {
  • // Loss > 10%: Limit the rate decreases to once a kBweDecreaseIntervalMs +
  • // rtt.
  • if (!has_decreased_since_last_fraction_loss_ &&
  • (now_ms - time_last_decrease_ms_) >=
  • (kBweDecreaseIntervalMs + last_round_trip_time_ms_)) {
  • time_last_decrease_ms_ = now_ms;
  • // Reduce rate:
  • // newRate = rate * (1 - 0.5*lossRate);
  • // where packetLoss = 256*lossRate;
  • //TODO:tjl
  • // 當從未開始降低窗口值,且距離上一次衰減的時間差大于衰減周期加上rtt。
  • // 其實當前貌似只有這個case下會對這兩個變量賦值。
  • // 這里的last_fraction_loss_是一次統計間隔(一定包數)之間的總丟包率。
  • // 丟包率的單位是1/256,因此這里是(1 - 丟包率/2) * 當前速率
  • // 與公式相同。
  • bitrate_ = static_cast<uint32_t>(
  • (bitrate_ * static_cast<double>(512 - last_fraction_loss_)) /
  • 512.0);
  • has_decreased_since_last_fraction_loss_ = true;
  • }
  • event_log_->LogBwePacketLossEvent(
  • bitrate_, last_fraction_loss_,
  • expected_packets_since_last_loss_update_);
  • }
  • }
  • // 在有效范圍內修正。
  • bitrate_ = CapBitrateToThresholds(now_ms, bitrate_);
  • }





  • 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



    總結

    以上是生活随笔為你收集整理的webrtc中的码率控制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。