WebRTC GCC拥塞控制算法详解

本文详细介绍了WebRTC的GCC(Google Congestion Control)拥塞控制算法,包括其核心思想、发送端和接收端的工作流程。GCC通过预测可用带宽控制发送速率,依赖丢包率和延迟进行带宽调整。发送端利用RR包计算丢包率和RTT,结合丢包和延迟调整带宽。接收端则通过延迟变化检测网络状况,调整码率。GCC的接收端使用到达时间滤波器、过载检测器和卡尔曼滤波器等方法来检测网络延迟变化,以反馈给发送端进行带宽调整。
摘要由CSDN通过智能技术生成

1、WebRTC版本


m74

2、GCC的概念


GCC全称Google Congest Control,所谓拥塞控制,就是控制数据发送的速率避免网络的拥塞。可以对比TCP的拥塞控制算法,由于WebRTC使用基于UDP的RTP来传输媒体数据,需要一个拥塞控制算法来保证基本的Qos。

*注:本文不涉及Transport CC,只是早期版本的GCC。

3、GCC的思想


GCC核心思想就是通过预测可用带宽来控制发送的速率,会结合发送端和接收端两端各自估测的带宽来综合计算,其中发送端的带宽估测主要依赖于丢包率(其实也有延迟),接收端的带宽估测依赖于延迟(的变化)。

换句话说,GCC主要是依靠丢包、延迟、抖动等网络参数来预估当前的可用带宽进而控制发送的速率从而避免网络拥塞引起的丢包、延迟、抖动等现象,是一个反馈的过程。

由于WebRTC还有NACK、FEC等策略来解决丢包问题,实际上发送端的带宽估测对较小程度的丢包来说并不太敏感,反而是接收端的带宽估测对延迟的抖动有较大的灵敏度。GCC的接收端通过一系列算法检测当前网络延迟是否有变化,延迟变大的话,在考虑并消除掉数据尺寸变化的影响后,可以认为是网络路由的拥塞,需要降低码率,否则在延迟变小的情况下,认为网络空闲,可以提高码率。所以在延迟抖动较大的情况下,即使没有丢包,GCC也会进行较大程度的带宽调整。

也就是说,延迟如果稳定的话,即使值较大,也并不影响带宽的估测,反过来如果平均延迟比较小,但是出现较多较大的抖动,则会迅速将估测带宽调低。

4、发送端


主要实现:SendSideBandwidthEstimation

4.1 基本流程

发送端核心的带宽估测逻辑是SendSideBandwidthEstimation::UpdateEstimate这个函数,主要有两个地方会触发:

  • 收到RR包,受限于RR包的频率,大概1秒1次,更新了丢包率、RTT之后调用;

  • 定时器,25ms一次,kUpdateIntervalMs=25,这个应该是为了防止RR包丢失或者不及时,更迅速灵敏的进行调整;

SendSideBandwidthEstimation::SetSendBitrate设置初始的预估带宽为300kbps,SendSideBandwidthEstimation::UpdateEstimate函数会根据丢包、RTT以及当前的估测带宽来调整下个时刻的估测带宽。

4.2 计算丢包率/RTT

通过SR(Sender Report)、RR(Receiver Report)包计算。

SR包结构:

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=SR=200 | length |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| SSRC of sender |

+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

sender | NTP timestamp, most significant word |

info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| NTP timestamp, least significant word |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| RTP timestamp |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| sender's packet count |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| sender's octet count |

+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

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 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RR包结构

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 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

4.2.1 丢包率

接收端构造RR包,发送端收到并解析RR包计算丢包率。主要通过以下3个字段:

  1. fraction lost,丢包率,发送两次RR包这两次统计间隔之间的丢包率,以小数形式的丢包率*255,乘以255主要是为了降低精度到8位;

  1. cumulative number of packets lost,目前时刻的累计丢包数,24位;

  1. extended highest sequence number received,目前时刻收到的的最大序列号,32位,低16位为接收端收到的最大序列号,高16位为序列号循环的次数。

4.2.1.1 接收端统计

接收端通过函数

StreamStatisticianImpl::OnRtpPacket ->

StreamStatisticianImpl::UpdateCounters

处理每个RTP包,更新计数器等统计信息:

StreamDataCounters StreamStatisticianImpl::UpdateCounters(

const RtpPacketReceived& packet) {

rtc::CritScope cs(&stream_lock_);

RTC_DCHECK_EQ(ssrc_, packet.Ssrc());

int64_t now_ms = clock_->TimeInMilliseconds();

incoming_bitrate_.Update(packet.size(), now_ms);

receive_counters_.transmitted.AddPacket(packet); // 更新接收到的包数,用于计算丢包

int64_t sequence_number =

seq_unwrapper_.UnwrapWithoutUpdate(packet.SequenceNumber());

if (!ReceivedRtpPacket()) { // 如果收到第一个RTP包

received_seq_first_ = sequence_number;

last_report_seq_max_ = sequence_number - 1;

receive_counters_.first_packet_time_ms = now_ms;

} else if (UpdateOutOfOrder(packet, sequence_number, now_ms)) {

// 判断包是否是乱序的包,如果是乱序的,可能重传的包,如果是重传的包

// 则更新receive_counters_.transmitted重传包计数器,用于计算丢包

return receive_counters_;

}

// 更新接收到的最大序列号

received_seq_max_ = sequence_number;

seq_unwrapper_.UpdateLast(sequence_number);

// 如果收到了一个更新的RTP包并且超过1个包有序,则更新抖动

if (packet.Timestamp() != last_received_timestamp_ &&

(receive_counters_.transmitted.packets -

receive_counters_.retransmitted.packets) > 1) {

UpdateJitter(packet, now_ms);

}

last_received_timestamp_ = packet.Timestamp();

last_receive_time_ms_ = now_ms;

return receive_counters_;

}

接收端通过函数

StreamStatisticianImpl::GetActiveStatisticsAndReset ->

StreamStatisticianImpl::CalculateRtcpStatistics

计算一次统计值,用于填充RR包:

RtcpStatistics StreamStatisticianImpl::CalculateRtcpStatistics() {

RtcpStatistics stats;

// 上个时刻到当前时刻期望接收到的包数 = 当前时刻接收到的最大序列号 - 上个时刻的接收到的最大序列号

int64_t exp_since_last = received_seq_max_ - last_report_seq_max_;

RTC_DCHECK_GE(exp_since_last, 0);

// 上个时刻到当前时刻实际接收到的包数

// 先计算上个时刻到当前时刻接收到的不包含重传包的包数 = 当前时刻接收到的不包含重传包的包数 - 上个时刻接收到的不包含重传包的包数

uint32_t rec_since_last = (receive_counters_.transmitted.packets -

receive_counters_.retransmitted.packets) -

last_report_inorder_packets_;

// 再计算上个时刻到当前时刻接收到的重传包的包数

uint32_t retransmitted_packets =

receive_counters_.retransmitted.packets - last_report_old_packets_;

// 上个时刻到当前时刻实际接收到的包数 = 上个时刻到当前时刻接收到的不包含重传包的包数 + 上个时刻到当前时刻接收到的重传包的包数

// 实际结果就是两个时刻的receive_counters_.transmitted.packets相减

rec_since_last += retransmitted_packets;

int32_t missing = 0;

if (exp_since_last > rec_since_last) {

// 上个时刻到当前时刻的丢包数 = 上个时刻到当前时刻期望接收到的包数 - 上个时刻到当前时刻实际接收到的包数

missing = (exp_since_last - rec_since_last);

}

uint8_t local_fraction_lost = 0;

if (exp_since_last) {

// 上个时刻到当前时刻的丢包率 = 上个时刻到当前时刻的丢包数 / 上个时刻到当前时刻期望接收到的包数

// *255是为了将值限制在(0,255)范围内,降低精度以8位存储

local_fraction_lost = static_cast<uint8_t>(255 * missing / exp_since_last);

}

stats.fraction_lost = local_fraction_lost;

// 更新并设置累计丢包数

cumulative_loss_ += missing;

stats.packets_lost = cumulative_loss_;

// 设置接收到的最大序列号

stats.extended_highest_sequence_number =

static_cast<uint32_t>(received_seq_max_);

// 设置抖动

stats.jitter = jitter_q4_ >> 4;

// Store this report.

last_reported_statistics_ = stats;

// 保存当前时刻收到的有序包的包数

last_report_inorder_packets_ = receive_counters_.transmitted.packets -

receive_counters_.retransmitted.packets;

// 保存当前时刻收到的重传包的包数

last_report_old_packets_ = receive_counters_.retransmitted.packets;

// 保存当前时刻收到的最大序列号

last_report_seq_max_ = received_seq_max_;

BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "cumulative_loss_pkts",

clock_->TimeInMilliseconds(),

cumulative_loss_, ssrc_);

BWE_TEST_LOGGING_PLOT_WITH_SSRC(

1, "received_seq_max_pkts", clock_->TimeInMilliseconds(),

(received_seq_max_ - received_seq_first_), ssrc_);

return stats;

}

上面代码中的3个值对应RR包中的3个字段:

  • stats.fraction_lost,发送两次RR包这两次统计间隔之间的丢包率;

  • stats.packets_lost,累计丢包数;

  • stats.extended_highest_sequence_number,目前时刻收到的的最大序列号。

总结:接收端的基本算法就是统计上个时刻到当前时刻的丢包率,总的丢包数,收到过的最大序列号,填入RR包发给发送端。

4.2.1.2 发送端计算

发送端通过函数

BitrateControllerImpl::OnReceivedRtcpReceiverReport ->

SendSideBandwidthEstimation::UpdateReceiverBlock ->

SendSideBandwidthEstimation::UpdatePacketsLost

处理RR包中的Report Block,计算上个时刻到当前时刻的丢包率。

BitrateControllerImpl::OnReceivedRtcpReceiverReport:累计音视频等所有流的Report Block的总丢包,计算总的丢包率。

void BitrateControllerImpl::OnReceivedRtcpReceiverReport(

const ReportBlockList& report_blocks,

int64_t rtt,

int64_t now_ms) {

if (report_blocks.empty())

return;

{

rtc::CritScope cs(&critsect_);

int fraction_lost_aggregate = 0;

int total_number_of_packets = 0;

// 遍历所有的Report Block,可能有音频、视频等不同流的Report Block

for (const RTCPReportBlock& report_block : report_blocks) {

// 通过SSRC查找到Report Block对应的流

std::map<uint32_t, uint32_t>::iterator seq_num_it =

ssrc_to_last_received_extended_high_seq_num_.find(

report_block.source_ssrc);

int number_of_packets = 0;

if (seq_num_it != ssrc_to_last_received_extended_high_seq_num_.end()) {

// 跟接收端类似:这里计算的是单个流的,

// 上个时刻到当前时刻期望的包数 = 当前时刻接的最大序列号 - 上个时刻的到的最大序列号

number_of_packets =

report_block.extended_highest_sequence_number - seq_num_it->second;

}

// 上个时刻到当前时刻的丢包数 = 上个时刻到当前时刻期望的包数 * 上个时刻到当前时刻的丢包率

// 注意这里的丢包率还是(0, 255)范围,所有的流的丢包数累加

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值