1)前言
- WebRtc基于發(fā)送端的動(dòng)態(tài)碼率調(diào)控主要分成兩大塊纫塌,其中一部分是基于丟包率的碼率控制肛真,另一部分是基于延遲的碼率控制盾碗。
- 本文主要分析WebRtc中基于丟包率的碼率控制苇倡。
- WebRtc中基于丟包率的碼率控制的實(shí)現(xiàn)原理是基于發(fā)送端接收對(duì)端反饋過(guò)來(lái)的RR或SR報(bào)文扔涧,并對(duì)報(bào)文的發(fā)送者報(bào)告塊進(jìn)行解析,解析其RTT和丟包率近弟。
- 如果丟包率比較大說(shuō)明網(wǎng)絡(luò)狀態(tài)不大好缅糟,將丟包信息和RTT更新到
GoogCcNetworkController
模塊,評(píng)估新的發(fā)送碼率祷愉。 - 最后在
RtpTransportControllerSend
模塊中將新評(píng)估出的碼率作用到pacer
模塊窗宦。
2)RTCP報(bào)文接收大致流程
- 在WebRTC RTP/RTCP協(xié)議分析(一)一文中有分析RTCP報(bào)文的接收流程,那篇文章是基于m76版本的分支進(jìn)行分析的二鳄,而本文是基于m79版本進(jìn)行分析赴涵,在分析過(guò)程中發(fā)現(xiàn)函數(shù)調(diào)用棧有些出入。
- 其調(diào)用流程大致如下:
- 上圖忽略從網(wǎng)絡(luò)部分得到RTCP包的業(yè)務(wù)邏輯订讼,直接從Call模塊說(shuō)起髓窜。
- 同時(shí)在Call模塊收到RTCP報(bào)文后會(huì)進(jìn)行一系列的處理,本文業(yè)務(wù)邏輯圖也未畫(huà)出欺殿。
- 對(duì)于音頻流RTCP報(bào)文的處理邏輯在第三步寄纵,有一些變化如下圖:
- 從上圖可以看出對(duì)于音頻流
RCPReceiver
模塊在調(diào)用TriggerCallbacksFromRtcpPacket函數(shù)觸發(fā)回調(diào)的時(shí)候首先是將報(bào)文送給VoERtcpObserver
塊進(jìn)行處理鳖敷。 - 而根據(jù)上圖得知
VoERtcpObserver
和RtpTransportControllerSend
都是RtcpBandwidthObserver
的子類。 - 最終在
VoERtcpObserver
中先調(diào)用OnReceivedRtcpReceiverReport將報(bào)告塊作用到GoogCcNetworkController
模塊程拭,后經(jīng)過(guò)相應(yīng)處理作用到編碼器哄陶。
3)RTCP報(bào)文RTT計(jì)算和丟包統(tǒng)計(jì)
- SR或RR報(bào)文的RTT信息和丟包信息包含在發(fā)送者報(bào)告塊,定義在RFC3550中,以RR報(bào)文為例哺壶,如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P| RC | PT=RR=201 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report | SSRC_1 (SSRC of first source) |
block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1 | fraction lost | cumulative number of packets lost |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extended highest sequence number received |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| interarrival jitter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| last SR (LSR) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| delay since last SR (DLSR) |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report | SSRC_2 (SSRC of second source) |
block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2 : ... :
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| profile-specific extensions |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 報(bào)告塊的解析代碼如下:
void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block,
PacketInformation* packet_information,
uint32_t remote_ssrc) {
// 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.
// |report_block.source_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_.count(report_block.source_ssrc()) == 0)
return;
last_received_rb_ms_ = clock_->TimeInMilliseconds();
ReportBlockData* report_block_data =
&received_report_blocks_[report_block.source_ssrc()][remote_ssrc];
RTCPReportBlock rtcp_report_block;
rtcp_report_block.sender_ssrc = remote_ssrc;
rtcp_report_block.source_ssrc = report_block.source_ssrc();
rtcp_report_block.fraction_lost = report_block.fraction_lost();
rtcp_report_block.packets_lost = report_block.cumulative_lost_signed();
if (report_block.extended_high_seq_num() >
report_block_data->report_block().extended_highest_sequence_number) {
// We have successfully delivered new RTP packets to the remote side after
// the last RR was sent from the remote side.
last_increased_sequence_number_ms_ = clock_->TimeInMilliseconds();
}
rtcp_report_block.extended_highest_sequence_number =
report_block.extended_high_seq_num();
rtcp_report_block.jitter = report_block.jitter();
rtcp_report_block.delay_since_last_sender_report =
report_block.delay_since_last_sr();
rtcp_report_block.last_sender_report_timestamp = report_block.last_sr();
report_block_data->SetReportBlock(rtcp_report_block, rtc::TimeUTCMicros());
int64_t rtt_ms = 0;
uint32_t send_time_ntp = report_block.last_sr();
// 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.
// TODO(nisse): Use this way to determine the RTT only when |receiver_only_|
// is false. However, that currently breaks the tests of the
// googCaptureStartNtpTimeMs stat for audio receive streams. To fix, either
// delete all dependencies on RTT measurements for audio receive streams, or
// ensure that audio receive streams that need RTT and stats that depend on it
// are configured with an associated audio send stream.
if (send_time_ntp != 0) {
uint32_t delay_ntp = report_block.delay_since_last_sr();
// Local NTP time.
uint32_t receive_time_ntp =
CompactNtp(TimeMicrosToNtp(clock_->TimeInMicroseconds()));
// RTT in 1/(2^16) seconds.
uint32_t rtt_ntp = receive_time_ntp - delay_ntp - send_time_ntp;
// Convert to 1/1000 seconds (milliseconds).
rtt_ms = CompactNtpRttToMs(rtt_ntp);
report_block_data->AddRoundTripTimeSample(rtt_ms);
packet_information->rtt_ms = rtt_ms;
}
packet_information->report_blocks.push_back(
report_block_data->report_block());
packet_information->report_block_datas.push_back(*report_block_data);
}
- 該函數(shù)的核心的作用是求得RTT時(shí)間屋吨,單位為1/(2^16) seconds.,計(jì)算公式為receive_time_ntp(當(dāng)前ntp) - delay_ntp(對(duì)端收到SR后發(fā)送SR或RR之間的延遲) - send_time_ntp(對(duì)端在發(fā)送SR或者RR之前收到發(fā)送者報(bào)告的時(shí)間)山宾。
- 另外獲取上一次和本次之間的丟包率至扰,以及總的丟包數(shù)。
- RTT計(jì)算公式如下:
[10 Nov 1995 11:33:25.125 UTC] [10 Nov 1995 11:33:36.5 UTC]
n SR(n) A=b710:8000 (46864.500 s)
---------------------------------------------------------------->
v ^
ntp_sec =0xb44db705 v ^ dlsr=0x0005:4000 ( 5.250s)
ntp_frac=0x20000000 v ^ lsr =0xb705:2000 (46853.125s)
(3024992005.125 s) v ^
r v ^ RR(n)
---------------------------------------------------------------->
|<-DLSR->|
(5.250 s)
A 0xb710:8000 (46864.500 s)
DLSR -0x0005:4000 ( 5.250 s)
LSR -0xb705:2000 (46853.125 s)
-------------------------------
delay 0x0006:2000 ( 6.125 s)
- 最后是將信息封裝到PacketInformation結(jié)構(gòu)當(dāng)中资锰,然后調(diào)用RTCPReceiver::TriggerCallbacksFromRtcpPacket函數(shù)進(jìn)行回調(diào)處理敢课。
4)RTCP觸發(fā)回調(diào)
void RTCPReceiver::TriggerCallbacksFromRtcpPacket(
const PacketInformation& packet_information) {
....
if (rtcp_bandwidth_observer_) {
RTC_DCHECK(!receiver_only_);
if ((packet_information.packet_type_flags & kRtcpSr) ||
(packet_information.packet_type_flags & kRtcpRr)) {
int64_t now_ms = clock_->TimeInMilliseconds();
rtcp_bandwidth_observer_->OnReceivedRtcpReceiverReport(
packet_information.report_blocks, packet_information.rtt_ms, now_ms);
}
}
.....
if ((packet_information.packet_type_flags & kRtcpSr) ||
(packet_information.packet_type_flags & kRtcpRr)) {
rtp_rtcp_->OnReceivedRtcpReportBlocks(packet_information.report_blocks);
}
}
- 保留相關(guān)的,改函數(shù)通過(guò)調(diào)用
RtcpBandwidthObserver
模塊的OnReceivedRtcpReceiverReport函數(shù),來(lái)傳遞信息。 - 根據(jù)上面的業(yè)務(wù)圖,
RtcpBandwidthObserver
模塊的最終實(shí)現(xiàn)為RtpTransportControllerSend
绷杜,在創(chuàng)建RTCPReceiver實(shí)例的時(shí)候會(huì)實(shí)例化rtcp_bandwidth_observer_直秆,
class RTCPReceiver {
public:
.....
private:
RtcpBandwidthObserver* const rtcp_bandwidth_observer_;
}
-
RtpTransportControllerSend
模塊的OnReceivedRtcpReceiverReport函數(shù)對(duì)RTCP 反饋信息的處理主要分成兩個(gè)步驟 - 其一是調(diào)用OnReceivedRtcpReceiverReportBlocks函數(shù)封裝TransportLossReport結(jié)構(gòu)消息,并最后調(diào)用PostUpdates(controller_->OnTransportLossReport(msg))鞭盟,首先將TransportLossReport消息傳遞給
GoogCcNetworkController
模塊進(jìn)行碼率估計(jì)圾结,其次是調(diào)用PostUpdates來(lái)將新估計(jì)得碼率值作用到pacer模塊 - 其二是封裝RoundTripTimeUpdate消息,并調(diào)用PostUpdates(controller_->OnRoundTripTimeUpdate(report));齿诉,
GoogCcNetworkController
模塊接收消息后先計(jì)算基于RTT時(shí)間延遲的碼率筝野,最后調(diào)用PostUpdates來(lái)刷新碼率,作用到發(fā)送模塊
5)TransportLossReport和RoundTripTimeUpdate結(jié)構(gòu)封裝
task_queue_.PostTask([this, report_blocks, now_ms]() {
RTC_DCHECK_RUN_ON(&task_queue_);
OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms);
});
task_queue_.PostTask([this, now_ms, rtt_ms]() {
RTC_DCHECK_RUN_ON(&task_queue_);
RoundTripTimeUpdate report;
report.receive_time = Timestamp::ms(now_ms);
report.round_trip_time = TimeDelta::ms(rtt_ms);
report.smoothed = false;
if (controller_ && !report.round_trip_time.IsZero())
PostUpdates(controller_->OnRoundTripTimeUpdate(report));
});
- 傳遞過(guò)來(lái)的now_ms參數(shù)為當(dāng)前ntp時(shí)間粤剧,也就是收到該rtcp sr或rr報(bào)文的時(shí)間歇竟。
- 同時(shí)這兩個(gè)步驟使用的是同一個(gè)task_queue_任務(wù)隊(duì)列,這說(shuō)明先進(jìn)行基于丟包的碼率估計(jì)抵恋,然后再進(jìn)行基于延遲的碼率估計(jì)焕议。
5.1)TransportLossReport封裝
- 通過(guò)OnReceivedRtcpReceiverReportBlocks函數(shù)對(duì)report_blocks消息進(jìn)行再封裝,將其封裝成TransportLossReport格式弧关,其定義如下
#network_types.h
struct TransportLossReport {
/*表示當(dāng)前接收到該消息的ntp時(shí)間*/
Timestamp receive_time = Timestamp::PlusInfinity();
/*上一次接收到SR或者RR報(bào)文的時(shí)間*/
Timestamp start_time = Timestamp::PlusInfinity();
/*當(dāng)前接收到該消息的ntp時(shí)間*/
Timestamp end_time = Timestamp::PlusInfinity();
/*在上一次處理和本次處理時(shí)間差范圍內(nèi)也就是end_time - start_time 之間的丟包數(shù)量*/
uint64_t packets_lost_delta = 0;
/*在上一次處理和本次處理時(shí)間差范圍內(nèi)也就是end_time - start_time 之間實(shí)際發(fā)送成功的包數(shù)*/
uint64_t packets_received_delta = 0;
};
void RtpTransportControllerSend::OnReceivedRtcpReceiverReportBlocks(
const ReportBlockList& report_blocks,
int64_t now_ms) {
if (report_blocks.empty())
return;
int total_packets_lost_delta = 0;
int total_packets_delta = 0;
// Compute the packet loss from all report blocks.
for (const RTCPReportBlock& report_block : report_blocks) {
auto it = last_report_blocks_.find(report_block.source_ssrc);
if (it != last_report_blocks_.end()) {
auto number_of_packets = report_block.extended_highest_sequence_number -
it->second.extended_highest_sequence_number;
total_packets_delta += number_of_packets;
auto lost_delta = report_block.packets_lost - it->second.packets_lost;
total_packets_lost_delta += lost_delta;
}
last_report_blocks_[report_block.source_ssrc] = report_block;
}
// Can only compute delta if there has been previous blocks to compare to. If
// not, total_packets_delta will be unchanged and there's nothing more to do.
if (!total_packets_delta)
return;
int packets_received_delta = total_packets_delta - total_packets_lost_delta;
// To detect lost packets, at least one packet has to be received. This check
// is needed to avoid bandwith detection update in
// VideoSendStreamTest.SuspendBelowMinBitrate
if (packets_received_delta < 1)
return;
Timestamp now = Timestamp::ms(now_ms);
TransportLossReport msg;
msg.packets_lost_delta = total_packets_lost_delta;
msg.packets_received_delta = packets_received_delta;
msg.receive_time = now;
msg.start_time = last_report_block_time_;
msg.end_time = now;
if (controller_)
PostUpdates(controller_->OnTransportLossReport(msg));
last_report_block_time_ = now;
}
首先根據(jù)報(bào)告塊得出盅安,上一次和本次時(shí)間戳之內(nèi)總共的發(fā)包數(shù)量number_of_packets,并對(duì)不同SSR進(jìn)行累加得出total_packets_delta梯醒。
計(jì)算total_packets_lost_delta宽堆,上一次總丟包數(shù)量減去本次總丟包數(shù)量腌紧,從而可知total_packets_lost_delta表示的是msg.end_time - msg.start_time時(shí)間差之間的丟包數(shù)量茸习。
根據(jù)total_packets_delta計(jì)算packets_received_delta,使用total_packets_delta - total_packets_lost_delta獲得壁肋,在msg.end_time - msg.start_time時(shí)間差之間的總發(fā)包數(shù)量減去本段時(shí)間差之內(nèi)總丟包數(shù)号胚,最終得出的就是實(shí)際發(fā)送成功的包數(shù)量籽慢。
封裝TransportLossReport并觸發(fā)OnTransportLossReport將TransportLossReport消息傳遞給
GoogCcNetworkController
模塊。為使分析邏輯清晰猫胁,在后面再確切分析
GoogCcNetworkController
模塊對(duì)TransportLossReport消息的處理和對(duì)碼率的估計(jì)箱亿。
5.2)RoundTripTimeUpdate封裝
struct RoundTripTimeUpdate {
/*表示當(dāng)前接收到該消息的ntp時(shí)間*/
Timestamp receive_time = Timestamp::PlusInfinity();
/*rtt時(shí)間*/
TimeDelta round_trip_time = TimeDelta::PlusInfinity();
bool smoothed = false;
};
- 封裝RoundTripTimeUpdate結(jié)構(gòu)
- 調(diào)用controller_->OnRoundTripTimeUpdate(report),將消息傳遞給
GoogCcNetworkController
模塊弃秆,進(jìn)行碼率估計(jì)并得出新碼率届惋。 - 調(diào)用PostUpdates更新pacer模塊的實(shí)時(shí)發(fā)送碼率。
6)丟包對(duì)GoogCcNetworkController模塊的影響
NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport(
TransportLossReport msg) {
if (packet_feedback_only_)//默認(rèn)為false,這里可以通過(guò)配置該變量讓碼率估計(jì)只依據(jù)packet_feedback_only_
return NetworkControlUpdate();
int64_t total_packets_delta =
msg.packets_received_delta + msg.packets_lost_delta;
bandwidth_estimation_->UpdatePacketsLost(
msg.packets_lost_delta, total_packets_delta, msg.receive_time);
return NetworkControlUpdate();
}
total_packets_delta為實(shí)際發(fā)送成功(對(duì)端收到的包) + 丟包數(shù) 菠赚。
調(diào)用SendSideBandwidthEstimation::UpdatePacketsLost更新當(dāng)前時(shí)間差范圍內(nèi)的總發(fā)包數(shù)和丟包信息脑豹。
返回NetworkControlUpdate(),從這里可以看出在此時(shí)只是直接new 了一個(gè)NetworkControlUpdate,并未對(duì)其中的成員進(jìn)行賦值衡查,所以由此可知瘩欺,經(jīng)由RR或者SR報(bào)文的丟包情況對(duì)
GoogCcNetworkController
模塊的真實(shí)作用是更新了SendSideBandwidthEstimation
模塊中的發(fā)包數(shù)量和丟包數(shù)量,而在RtpTransportControllerSend
模塊該階段的最后調(diào)用棧中調(diào)用的PostUpdates函數(shù)實(shí)際上會(huì)直接返回拌牲,不會(huì)做任何事情俱饿。
void SendSideBandwidthEstimation::UpdatePacketsLost(int packets_lost,
int number_of_packets,
Timestamp at_time) {
last_loss_feedback_ = at_time;
if (first_report_time_.IsInfinite())
first_report_time_ = at_time;
// Check sequence number diff and weight loss report
if (number_of_packets > 0) {
// Accumulate reports.
lost_packets_since_last_loss_update_ += packets_lost;
expected_packets_since_last_loss_update_ += number_of_packets;
// Don't generate a loss rate until it can be based on enough packets.
if (expected_packets_since_last_loss_update_ < kLimitNumPackets)
return;
has_decreased_since_last_fraction_loss_ = false;
int64_t lost_q8 = lost_packets_since_last_loss_update_ << 8;
int64_t expected = expected_packets_since_last_loss_update_;
last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);
// Reset accumulators.
lost_packets_since_last_loss_update_ = 0;
expected_packets_since_last_loss_update_ = 0;
last_loss_packet_report_ = at_time;
UpdateEstimate(at_time);
}
UpdateUmaStatsPacketsLost(at_time, packets_lost);
}
- lost_packets_since_last_loss_update_表示上截止上一次基于丟包的回調(diào)的丟包數(shù)量,這兩將上一次處理RR或者SR報(bào)告時(shí)的丟包數(shù)和本次接收到的丟包數(shù)進(jìn)行累加塌忽。
- expected_packets_since_last_loss_update_期望發(fā)送成功的總包數(shù)拍埠,發(fā)送多少對(duì)方收到多少,這里其實(shí)就是自上一次處理基于丟包碼率估計(jì)到本次處理時(shí)間差之間總共的發(fā)包數(shù)量土居。
- 判斷expected_packets_since_last_loss_update_ < kLimitNumPackets(默認(rèn)20)械拍,從這可以看出,如果在上一次處理丟包碼率估計(jì)到本次處理丟包估計(jì)之間的時(shí)間差內(nèi)如果總發(fā)包數(shù)小于20個(gè)装盯,則直接返回坷虑,如果大于20個(gè)則求出last_fraction_loss_ = 上一次處理到本次處理時(shí)間差內(nèi)的總丟包數(shù) * 256 / 本段時(shí)間內(nèi)總發(fā)包數(shù),然后和255 取最小值埂奈。
- 假設(shè)本段時(shí)間內(nèi)丟包率為1迄损,那肯定大于255,理論上丟包率肯定是小于1的账磺,所以這里得到的丟包率是2^8 * 丟包數(shù) / 發(fā)包數(shù)芹敌。
- 如果發(fā)包數(shù)大于20的話計(jì)算出丟包率后需要進(jìn)行清除。
- 由以上可知垮抗,這段代碼的時(shí)間差為 大于發(fā)送20個(gè)數(shù)據(jù)包的時(shí)間差氏捞,可以理解成每相隔20個(gè)數(shù)據(jù)包的時(shí)間差會(huì)進(jìn)行一次丟包統(tǒng)計(jì)。
- 調(diào)用UpdateEstimate進(jìn)行碼率估計(jì)冒版。
constexpr TimeDelta kBweIncreaseInterval = TimeDelta::Millis<1000>();
constexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis<5000>();
constexpr TimeDelta kBweDecreaseInterval = TimeDelta::Millis<300>();
constexpr float kDefaultLowLossThreshold = 0.02f;
constexpr float kDefaultHighLossThreshold = 0.1f;
constexpr DataRate kDefaultBitrateThreshold = DataRate::Zero();
void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
.........
UpdateMinHistory(at_time);
.......
/*是否啟用WebRTC-Bwe-LossBasedControl,正常情況除非用戶自己配置否則不啟用*/
if (loss_based_bandwidth_estimation_.Enabled()) {
loss_based_bandwidth_estimation_.Update(
at_time, min_bitrate_history_.front().second, last_round_trip_time_);
DataRate new_bitrate = MaybeRampupOrBackoff(current_target_, at_time);
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_;
if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {
// We only care about loss above a given bitrate threshold.
//上面分析到last_fraction_loss_位丟包率*2^8
float loss = last_fraction_loss_ / 256.0f;
// We only make decisions based on loss when the bitrate is above a
// threshold. This is a crude way of handling loss which is uncorrelated
// to congestion.
if (current_target_ < bitrate_threshold_ || loss <= low_loss_threshold_) {
// Loss < 2%: Increase rate by 8% of the min bitrate in the last
// kBweIncreaseInterval.
// 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 immediately to 108kbps
// whenever a receiver report is received with lower packet loss.
// If instead one would do: current_bitrate_ *= 1.08^(delta time),
// it would take over one second since the lower packet loss to achieve
// 108kbps.
DataRate new_bitrate =
DataRate::bps(min_bitrate_history_.front().second.bps() * 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).
new_bitrate += DataRate::bps(1000);
UpdateTargetBitrate(new_bitrate, at_time);
return;
} else if (current_target_ > bitrate_threshold_) {
if (loss <= high_loss_threshold_) {
// Loss between 2% - 10%: Do nothing.
} else {
// Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval
// + rtt.
if (!has_decreased_since_last_fraction_loss_ &&
(at_time - time_last_decrease_) >=
(kBweDecreaseInterval + last_round_trip_time_)) {
time_last_decrease_ = at_time;
// Reduce rate:
// newRate = rate * (1 - 0.5*lossRate);
// where packetLoss = 256*lossRate;
DataRate new_bitrate =
DataRate::bps((current_target_.bps() *
static_cast<double>(512 - last_fraction_loss_)) /
512.0);
has_decreased_since_last_fraction_loss_ = true;
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
}
}
}
// TODO(srte): This is likely redundant in most cases.
ApplyTargetLimits(at_time);
}
以上代碼刪除和TWCC延遲相關(guān)部分液茎,保留和基于RR或SR丟包統(tǒng)計(jì)相關(guān)部分代碼。
獲取本次處理丟包統(tǒng)計(jì)和上次處理丟包統(tǒng)計(jì)的時(shí)間差time_since_loss_packet_report = at_time - last_loss_packet_report_;
首先調(diào)用UpdateMinHistory以當(dāng)前時(shí)間做為參數(shù)來(lái)更新min_bitrate_history_集合。
void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) {
// 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() &&
at_time - min_bitrate_history_.front().first + TimeDelta::ms(1) >
kBweIncreaseInterval) {
min_bitrate_history_.pop_front();
}
// Typical minimum sliding-window algorithm: Pop values higher than current
// bitrate before pushing it.
while (!min_bitrate_history_.empty() &&
current_target_ <= min_bitrate_history_.back().second) {
min_bitrate_history_.pop_back();
}
min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
}
std::deque<std::pair<Timestamp, DataRate> > min_bitrate_history_;
min_bitrate_history_是一個(gè)以時(shí)間為key,碼率為值得一個(gè)鍵值對(duì)的容器捆等。
刪除歷史舊的數(shù)據(jù)滞造。以當(dāng)前時(shí)間和隊(duì)列的第一個(gè)元素的時(shí)間差進(jìn)行比較,刪除1s以前保存的碼率栋烤。
刪除歷史舊的數(shù)據(jù)谒养。以碼率為查詢條件,如果當(dāng)前的碼率比歷史數(shù)據(jù)中的碼率還要小則刪除明郭。
以當(dāng)前時(shí)間和當(dāng)前碼率構(gòu)建std::make_pair买窟,將其插入到容器尾部,現(xiàn)假設(shè)每次處理的時(shí)間間隔為100ms,那么從時(shí)間的角度來(lái)看薯定,該隊(duì)列能保存前10個(gè)碼率蔑祟。
bitrate_threshold_
默認(rèn)情況下為0,可以通過(guò)“WebRTC-BweLossExperiment”進(jìn)行配置沉唠,low_loss_threshold_
的默認(rèn)值為0.02f,high_loss_threshold_
的默認(rèn)值為0.1f疆虚。首先判斷time_since_loss_packet_report也就是兩次評(píng)估處理的時(shí)間間隔小于1.2*5000等于6000ms的情況下,分成兩種情況進(jìn)行碼率估計(jì)满葛,一種是當(dāng)前的丟包率小于等于2%的時(shí)候径簿,那么會(huì)以歷史最小碼率以8%的幅度進(jìn)行遞增。額外再加上1kbps嘀韧。
如果當(dāng)前的丟包率介于2% - 10%之間則維持當(dāng)前碼率篇亭。
如果丟包率大于10%,說(shuō)明網(wǎng)絡(luò)比較差锄贷,需要降低碼率译蒂,碼率下降的公式為 新碼率 = (當(dāng)前碼率 * (512 - 256 * 丟包率)) / 512,當(dāng)然這個(gè)計(jì)算是有條件的谊却,條件就是(當(dāng)前時(shí)間 - 上一次開(kāi)始遞減的時(shí)間) > 上一次的rtt時(shí)間 + 300ms ,也就是說(shuō)得到遞減的碼率在一定的時(shí)間范圍內(nèi)有效柔昼。has_decreased_since_last_fraction_loss_的值在UpdatePacketsLost回調(diào)中被設(shè)置成false,在碼率遞減后設(shè)置成true,每次處理RR或者SR丟包統(tǒng)計(jì)時(shí)進(jìn)行置false,在碼率遞減后設(shè)置成true炎辨。
最終碼率新增或遞減得到新碼率捕透,調(diào)用UpdateTargetBitrate對(duì)碼率進(jìn)行更新。從
GoogCcNetworkController
模塊到SendSideBandwidthEstimation
的大致流程如下:
void SendSideBandwidthEstimation::UpdateTargetBitrate(DataRate new_bitrate,
Timestamp at_time) {
new_bitrate = std::min(new_bitrate, GetUpperLimit());
if (new_bitrate < min_bitrate_configured_) {
MaybeLogLowBitrateWarning(new_bitrate, at_time);
new_bitrate = min_bitrate_configured_;
}
current_target_ = new_bitrate;
MaybeLogLossBasedEvent(at_time);
link_capacity_.OnRateUpdate(acknowledged_rate_, current_target_, at_time);
}
- 最終記錄新碼率為current_target_碴萧。
- 調(diào)用LinkCapacityTracker::OnRateUpdate進(jìn)行更新乙嘀,后續(xù)進(jìn)行分析,此文不進(jìn)行分析破喻。
7)RTT對(duì)GoogCcNetworkController模塊的影響
-
GoogCcNetworkController
模塊在收到rtt的回調(diào)后其處理邏輯如下圖:
- 一方面將RTT信息作用到
DelayBasedBwe
模塊虎谢。 - 另一方面講RTT信息作用到
SendSideBandwidthEstimation
模塊。 - 本文主要分析
SendSideBandwidthEstimation
相關(guān)的信息曹质。
void SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) {
// Update RTT if we were able to compute an RTT based on this RTCP.
// FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.
if (rtt > TimeDelta::Zero())
last_round_trip_time_ = rtt;
if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {
uma_rtt_state_ = kDone;
RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialRtt", rtt.ms<int>(), 0, 2000, 50);
}
}
- UpdateRtt的核心作用就是更新last_round_trip_time_的值婴噩,該值在上面碼率遞減的分析過(guò)程中有用到擎场,其來(lái)源就是基于此。
8)總結(jié)
- 本文意在分析webrtc基于丟包率的動(dòng)態(tài)碼率調(diào)控原理和其實(shí)現(xiàn)的業(yè)務(wù)邏輯讳推,包含對(duì)應(yīng)的函數(shù)調(diào)用棧顶籽,以及基于丟包率對(duì)碼率控制(上調(diào)或者下降)的核心算法原理玩般。
- 根據(jù)上文的分析得出银觅,當(dāng)網(wǎng)絡(luò)環(huán)境良好的情況下,webrtc給出的指標(biāo)是丟包率小于2%10%之間坏为,當(dāng)丟包率小于2%的時(shí)候可以適當(dāng)?shù)脑黾哟a率究驴,其增加的公式為以碼率的8%進(jìn)行遞增同時(shí)附加1kbps,而當(dāng)丟包率在2%10%之間的時(shí)候匀伏,保持碼率平穩(wěn)不增加也不減少洒忧。
- 當(dāng)丟包率超過(guò)10%的時(shí)候,對(duì)碼率以 (當(dāng)前碼率 * (512 - 256 * 丟包率)) / 512的幅度進(jìn)行遞減够颠,并且確保遞減的新碼率在一定的時(shí)間范圍內(nèi)有效熙侍。