前言
- 在
webrtc
中带宽探测是属于拥塞控制中重要的一部分,在webrtc
实现中主要由ProbeController
、ProbeBitrateEstimator
、BitrateProber
三大模块组成。 -
ProbeController
模块负责生成ProbeClusterConfig
也就是探测源信号或者说判断是否需要探测。 -
ProbeBitrateEstimator
模块基于twcc feedback
依据ProbeClusterConfig
源完成对探测带宽的估计计算。 - 而
BitrateProber
结合Pacing
模块根据ProbeClusterConfig
完成探测包的发送。 - 本文着重介绍
ProbeController
模块的实现原理。其综合框架为:
探测场景介绍
-
ProbeController
模块是整个探测的logic
管理器,并对场景进行分类如下: - 初始阶段,需要进行带宽探测。
- 应用编码设置画质码率约束更改,向拥塞控制框架
allocate
申请码率,会触发探测。 -
GoogCcNetworkController
模块在综合基于delay_based
、lossbased
、以及ack
码率后会设置目标估计码率到ProbeController
,此时也有可能会触发带宽探测。 - 过载恢复后会触发探测。
- 定时周期性探测,默认
25Ms
。 - 以上场景,如逻辑上需要探测,则会在
ProbeController
模块生成ProbeClusterConfig
探测源数据。
初始阶段探测
- 初始阶段带宽探测的触发流程如上图,其代码流程如下:
void RtpTransportControllerSend::MaybeCreateControllers() {
...
initial_config_.constraints.at_time =
Timestamp::Millis(clock_->TimeInMilliseconds());
initial_config_.stream_based_config = streams_config_;
...
UpdateControllerWithTimeInterval();
...
}
void RtpTransportControllerSend::UpdateControllerWithTimeInterval() {
RTC_DCHECK(controller_);
ProcessInterval msg;
msg.at_time = Timestamp::Millis(clock_->TimeInMilliseconds());
PostUpdates(controller_->OnProcessInterval(msg));
}
- 从代码来看初始流程发送在模块初始化阶段,由
RtpTransportControllerSend
模块发起,经由GoogCcNetworkController
模块触发。
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
ProcessInterval msg) {
NetworkControlUpdate update;
if (initial_config_) {
// 在这里触发初始探测
update.probe_cluster_configs =
ResetConstraints(initial_config_->constraints);
....
initial_config_.reset();
}
return update;
}
- 在上图中探测的最终实现在
ProbeController::SetBitrates
函数中。
std::vector<ProbeClusterConfig> ProbeController::SetBitrates(
DataRate min_bitrate,
DataRate start_bitrate,
DataRate max_bitrate,
Timestamp at_time) {
// 设置初始码率,并设置估计码率为初始码率
if (start_bitrate > DataRate::Zero()) {
start_bitrate_ = start_bitrate;
estimated_bitrate_ = start_bitrate;
} else if (start_bitrate_.IsZero()) {
start_bitrate_ = min_bitrate;
}
// The reason we use the variable `old_max_bitrate_pbs` is because we
// need to set `max_bitrate_` before we call InitiateProbing.
// 默认情况下max_bitrate不会被设置,为-1,所以最大探测带宽为5Mbps(kDefaultMaxProbingBitrate)
DataRate old_max_bitrate = max_bitrate_;
max_bitrate_ =
max_bitrate.IsFinite() ? max_bitrate : kDefaultMaxProbingBitrate;
// 默认state_为kInit,所以初始状态会进行探测
switch (state_) {
case State::kInit:
if (network_available_)
return InitiateExponentialProbing(at_time);
break;
case State::kWaitingForProbingResult:
break;
// 更新目标需求码率
case State::kProbingComplete:
// If the new max bitrate is higher than both the old max bitrate and the
// estimate then initiate probing.
if (!estimated_bitrate_.IsZero() && old_max_bitrate < max_bitrate_ &&
estimated_bitrate_ < max_bitrate_) {
return InitiateProbing(at_time, {max_bitrate_}, false);
}
break;
}
return std::vector<ProbeClusterConfig>();
}
std::vector<ProbeClusterConfig> ProbeController::InitiateExponentialProbing(
Timestamp at_time) {
...
// When probing at 1.8 Mbps ( 6x 300), this represents a threshold of
// 1.2 Mbps to continue probing.
// 默认first_exponential_probe_scale为3.0,
std::vector<DataRate> probes = {config_.first_exponential_probe_scale *
start_bitrate_};
// 默认second_exponential_probe_scale为6.0
// 初始阶段使用指数探测
if (config_.second_exponential_probe_scale &&
config_.second_exponential_probe_scale.GetOptional().value() > 0) {
probes.push_back(config_.second_exponential_probe_scale.Value() *
start_bitrate_);
}
return InitiateProbing(at_time, probes, true);
}
- 上述函数会生成两个探测码率,第一个为
3.0
倍start_bitrate_
,第二个为6.0
倍start_bitrate_
。 - 由此可看出,
webrtc
的start_bitrate
不宜设置太高,如果设置太高很容易出现探测带宽太高,造成丢包现象,应该根据实际业务,当前目标画质进行配置。 - 最终调用
InitiateProbing
生成ProbeClusterConfig
集合,这里会生成两个探测族源数据。
std::vector<ProbeClusterConfig> ProbeController::InitiateProbing(
Timestamp now,
std::vector<DataRate> bitrates_to_probe,
bool probe_further) {
// 1) 新特性,确保目标码率在瓶颈链路带宽或者当前估计码率水位线以下
// 期望最大探测带宽为max_probe_rate
// 最大估计链路带宽为std::min(network_estimate, estimated_bitrate_),默认由于network_estimate未实现所以这里得到的是estimated_bitrate_
// 当前估计带宽
// skip_if_estimate_larger_than_fraction_of_max为一个小于1的因子
// 意思是当前估计带宽estimated_bitrate_已经大于skip_if_estimate_larger_than_fraction_of_max * max_probe_rate则完成探测
if (config_.skip_if_estimate_larger_than_fraction_of_max > 0) {
// 这里得到链路容量带宽上限(bottleneck link bandwidh),由于network_estimate_为空得到的是正无穷
DataRate network_estimate = network_estimate_
? network_estimate_->link_capacity_upper
: DataRate::PlusInfinity();
// 得到最大期望探测带宽
DataRate max_probe_rate =
max_total_allocated_bitrate_.IsZero()
? max_bitrate_
: std::min(max_total_allocated_bitrate_, max_bitrate_);
// estimated_bitrate_ > config_.skip_if_estimate_larger_than_fraction_of_max * max_probe_rate 探测完成
//
if (std::min(network_estimate, estimated_bitrate_ > ) >
config_.skip_if_estimate_larger_than_fraction_of_max * max_probe_rate) {
state_ = State::kProbingComplete;
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
return {};
}
}
// 2) 限制最大探测码率
// max_total_allocated_bitrate_为应用编码模块向拥塞控制模块allocate的最大码率
// 默认情况下如果应用握手阶段未设置当前session最大码率约束那么max_bitrate_为默认为5Mbps
// 最大探测码率限制在std::min(max_probe_bitrate, max_total_allocated_bitrate_ * 2)以内
// 也就是说如果你的应用需求码率比较高比如几十Mbps,那么在初始化阶段一定要修改max_bitrate_
DataRate max_probe_bitrate = max_bitrate_;
if (max_total_allocated_bitrate_ > DataRate::Zero()) {
// If a max allocated bitrate has been configured, allow probing up to 2x
// that rate. This allows some overhead to account for bursty streams,
// which otherwise would have to ramp up when the overshoot is already in
// progress.
// It also avoids minor quality reduction caused by probes often being
// received at slightly less than the target probe bitrate.
max_probe_bitrate =
std::min(max_probe_bitrate, max_total_allocated_bitrate_ * 2);
}
// 3) 新特性引入拥塞控制状态对探测进行调整,limit_probe_target_rate_to_loss_bwe和not_probe_if_delay_increased默认未开启
// 如果当前的拥塞状态为过载,码率处于下降状态(kLossLimitedBweDecreasing),则取消探测
// 如果当前延迟增加kDelayBasedLimitedDelayIncreased或kRttBasedBackOffHighRtt,同理也取消探测
// 如果当前正处于升码率状态,如aimd模块判定当前为轻载kLossLimitedBweIncreasing此时调整estimate_capped_bitrate(估计码率的上限)
// 对于低延迟场景,着重体验的场景,对丢包和延迟容忍度都极低的业务场景,建议默认开启
// 默认lossbased v2未开启,所以这里默认会是kDelayBasedLimited
DataRate estimate_capped_bitrate = DataRate::PlusInfinity();
if (config_.limit_probe_target_rate_to_loss_bwe) {
switch (bandwidth_limited_cause_) {
case BandwidthLimitedCause::kLossLimitedBweDecreasing:
// If bandwidth estimate is decreasing because of packet loss, do not
// send probes.
return {};
//
case BandwidthLimitedCause::kLossLimitedBweIncreasing:
estimate_capped_bitrate =
std::min(max_probe_bitrate,
estimated_bitrate_ * config_.loss_limited_probe_scale);
break;
case BandwidthLimitedCause::kDelayBasedLimited:
break;
default:
break;
}
}
if (config_.not_probe_if_delay_increased &&
(bandwidth_limited_cause_ ==
BandwidthLimitedCause::kDelayBasedLimitedDelayIncreased ||
bandwidth_limited_cause_ ==
BandwidthLimitedCause::kRttBasedBackOffHighRtt)) {
return {};
}
// 4) 新特性,默认未开启,这里假设设置了一个探测间隔,并且network_estimate_网络状态估计不为空
// 截止当前版本源码network_estimate_依然为空
if (config_.network_state_estimate_probing_interval->IsFinite() &&
network_estimate_ && network_estimate_->link_capacity_upper.IsFinite()) {
if (network_estimate_->link_capacity_upper.IsZero()) {
RTC_LOG(LS_INFO) << "Not sending probe, Network state estimate is zero";
return {};
}
// config_.network_state_probe_scale默认为1.0
// 这里是调整估计上限码率取estimate_capped_bitrate、max_probe_bitrate、和链路容量上限码率 * config_.network_state_probe_scale
// 的最小值,这里也是一个保守操作,避免因探测不当导致码率溢出,超过最大链路容量
estimate_capped_bitrate = std::min(
{estimate_capped_bitrate, max_probe_bitrate,
std::max(estimated_bitrate_, network_estimate_->link_capacity_upper *
config_.network_state_probe_scale)});
}
// 5) 生成探测族源数据,基于用户预设的探测码率、estimate_capped_bitrate以及max_probe_bitrate
std::vector<ProbeClusterConfig> pending_probes;
for (DataRate bitrate : bitrates_to_probe) {
RTC_DCHECK(!bitrate.IsZero());
// 保守设置,取最小值,避免探测溢出超过瓶颈链接容量
// 取estimate_capped_bitrate、max_probe_bitrate和bitrate的最小值
bitrate = std::min(bitrate, estimate_capped_bitrate);
if (bitrate > max_probe_bitrate) {
bitrate = max_probe_bitrate;
probe_further = false;
}
ProbeClusterConfig config;
config.at_time = now;
config.target_data_rate = bitrate;
// 若用户设置了探测间隔,则使用用户设置,否则使用默认15Ms
if (network_estimate_ &&
config_.network_state_estimate_probing_interval->IsFinite()) {
config.target_duration = config_.network_state_probe_duration;
} else {
config.target_duration = config_.min_probe_duration;
}
// 最小探测包个数默认5个
config.target_probe_count = config_.min_probe_packets_sent;
// 设置探测ID,在探测码率估计模块中会使用到
config.id = next_probe_cluster_id_;
next_probe_cluster_id_++;
MaybeLogProbeClusterCreated(event_log_, config);
// 插入到探测源数据集合当中
pending_probes.push_back(config);
}
// 设置本次生成探测包的时间戳为Now,后面会用到
time_last_probing_initiated_ = now;
// 如果目标码率设置很大,每次探测未到达目标设置可以设置probe_further更进一步探测
if (probe_further) {
state_ = State::kWaitingForProbingResult;
// Dont expect probe results to be larger than a fraction of the actual
// probe rate.
// further_probe_threshold = 0.7
// 假设需要继续上探,这里设置了下一次上探的最小码率为min_bitrate_to_probe_further_
min_bitrate_to_probe_further_ =
std::min(estimate_capped_bitrate, (*(bitrates_to_probe.end() - 1))) *
config_.further_probe_threshold;
} else {
// 否则设置状态为State::kProbingComplete,这里表示不需要等到探测成功就可以设置状态
state_ = State::kProbingComplete;
// 设置为正无穷大...
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
}
return pending_probes;
}
- 该函数负责生成
std::vector<ProbeClusterConfig>
探测族源数据信息,包括探测ID、探测包数量,最小探测间隔等信息。 - 在新版本的
webrtc
中引入了几个新特性,其中特性1)
,属于保守探测,判断当前的估计带宽是否已经大于最大探测码率的某个比例,如果是则表示探测完成,这样的好处有,其一是避免带宽浪费,其二是也可以降低一些不必要的丢包。 - 特性
3)
引入拥塞控制状态进行调整和控制,当识别到强求延迟增加、处于过载降码率的情况下直接放弃探测。
设置目标码率触发探测
-
设置目标码率约束的模块基本调用流程如下图:
- 实际上初始化探测和用户层设置目标码率的约束复用一个函数入口栈
ProbeController::SetBitrates()
。 - 假设在应用初始化的时候通过
sdp
设置了目标码率约束等信息,那么在初始阶段这些码率约束信息也会被带入。 - 这里不同的地方在于,假设用户更新目标码率信息的时候,此时的
ProbeController
模块的状态机处于kProbingComplete
状态,那有可能会继续触发探测。 - 比较尴尬的是假设此时状态机的状态为
kWaitingForProbingResult
,也就是上次探测的因为各种原因处于等待状态,那么会被直接返回,好在这个函数的调用会更新max_bitrate_
,否则会出现bug
。。
std::vector<ProbeClusterConfig> ProbeController::SetBitrates(
DataRate min_bitrate,
DataRate start_bitrate,
DataRate max_bitrate,
Timestamp at_time) {
....
DataRate old_max_bitrate = max_bitrate_;
max_bitrate_ =
max_bitrate.IsFinite() ? max_bitrate : kDefaultMaxProbingBitrate;
switch (state_) {
....
case State::kProbingComplete:
// If the new max bitrate is higher than both the old max bitrate and the
// estimate then initiate probing.
// 这里的条件是当前带宽估计码率不为0,并且当前的估计码率比用户预设的要小,则进行探测,这里并没有进行指数探测
// 并且是不进行更多探测further为false.
if (!estimated_bitrate_.IsZero() && old_max_bitrate < max_bitrate_ &&
estimated_bitrate_ < max_bitrate_) {
return InitiateProbing(at_time, {max_bitrate_}, false);
}
break;
}
return std::vector<ProbeClusterConfig>();
}
- 最终是调用
InitiateProbing()
函数生成探测族源数据,经由BitrateProber
结合Pacing
模块根据ProbeClusterConfig
完成探测包的发送。 - 注意这里是设置的拥塞控制框架的约束。
各路stream allocate 带宽
-
OnMaxTotalAllocatedBitrate
函数在默认实现中只要当处于alr
应用受限阶段才会触发。 - 这个函数一般在用户切换画质,对编码器流级别的码率进行更新的时候会用到。
- 在初始化阶段假设app层手动设置了编码器的
BitrateAllocation
,在初始化探测后也会进行探测。
std::vector<ProbeClusterConfig> ProbeController::OnMaxTotalAllocatedBitrate(
DataRate max_total_allocated_bitrate,
Timestamp at_time) {
// 这里表示触发了应用受限
const bool in_alr = alr_start_time_.has_value();
const bool allow_allocation_probe = in_alr;
// 探测条件为:
// 1) 当前状态为State::kProbingComplete
// 2) 前后两次max_total_allocated_bitrate不相等
// 3) 当前估计带宽小于最大码率约束
// 4) 当前估计带宽小于编码模块申请的最大码率
// 5) 当前处于应用受限状态
if (config_.probe_on_max_allocated_bitrate_change &&
state_ == State::kProbingComplete &&
max_total_allocated_bitrate != max_total_allocated_bitrate_ &&
estimated_bitrate_ < max_bitrate_ &&
estimated_bitrate_ < max_total_allocated_bitrate &&
allow_allocation_probe) {
max_total_allocated_bitrate_ = max_total_allocated_bitrate;
if (!config_.first_allocation_probe_scale)
return std::vector<ProbeClusterConfig>();
// 第一次探测为max_total_allocated_bitrate的一倍
DataRate first_probe_rate = max_total_allocated_bitrate *
config_.first_allocation_probe_scale.Value();
// 默认为正无穷
DataRate probe_cap = config_.allocation_probe_max.Get();
first_probe_rate = std::min(first_probe_rate, probe_cap);
std::vector<DataRate> probes = {first_probe_rate};
// 第二次探测为max_total_allocated_bitrate的两倍
if (config_.second_allocation_probe_scale) {
DataRate second_probe_rate =
max_total_allocated_bitrate *
config_.second_allocation_probe_scale.Value();
second_probe_rate = std::min(second_probe_rate, probe_cap);
if (second_probe_rate > first_probe_rate)
probes.push_back(second_probe_rate);
}
return InitiateProbing(at_time, probes,
config_.allocation_allow_further_probing.Get());
}
max_total_allocated_bitrate_ = max_total_allocated_bitrate;
return std::vector<ProbeClusterConfig>();
}
-
本文总结了该探测可能触发的两个经典场景如下逻辑流程图:
- 场景1为当初始化的时候我们手动对编码器进行了一些配置,比如限制编码器最大码率等。
- 场景2其实和
google webrtc
原生的设计有点出入,我们在正常的业务场景当中,实际上我们是不希望造成带宽浪费的,同时可能客户端也会涉及到各种不同的画质档位如标清、高清、超清、蓝光这些,而每一个档位我们希望拥塞控制引擎的最大上限也不会是网络实际带宽那么大,我们更希望的是这个带宽上限更加接近我们的画质档位,同时每个不同画质档位会想拥塞框架引擎申请一个合理的码率上限。 - 实际应用中
OnMaxTotalAllocatedBitrate()
函数的实现逻辑可能需要进行稍加修改,因为默认实现是一定要处于alr
状态下才进行上探,有时候我们直接从标清切换到蓝光,业务上压根不会这么考虑。 - 所以如果对这一块逻辑思路不是很清楚,我们在实际应用中很有可能就会出现切换档位,码率确上不去的问题。
网络中断或者恢复触发探测
std::vector<ProbeClusterConfig> ProbeController::OnNetworkAvailability(
NetworkAvailability msg) {
network_available_ = msg.network_available;
if (!network_available_ && state_ == State::kWaitingForProbingResult) {
state_ = State::kProbingComplete;
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
}
if (network_available_ && state_ == State::kInit && !start_bitrate_.IsZero())
return InitiateExponentialProbing(msg.at_time);
return std::vector<ProbeClusterConfig>();
}
- 网络有效、并且状态为初始状态的情况下才会触发探测。
设置估计码率
void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(
NetworkControlUpdate* update,
Timestamp at_time) {
...
DataRate loss_based_target_rate = bandwidth_estimation_->target_rate();
.....
auto probes = probe_controller_->SetEstimatedBitrate(
loss_based_target_rate,
GetBandwidthLimitedCause(
bandwidth_estimation_->loss_based_state(),
bandwidth_estimation_->IsRttAboveLimit(),
delay_based_bwe_->last_state(),
probe_controller_->DontProbeIfDelayIncreased()),
at_time);
update->probe_cluster_configs.insert(update->probe_cluster_configs.end(),
probes.begin(), probes.end());
...
}
}
-
GoogCcNetworkController
模块每次拥塞事件,估计完码率后调用,会将估计码率告知给ProbeController
模块。 - 在当前较新的版本中会将当前的拥塞状态传递给
ProbeController
模块
std::vector<ProbeClusterConfig> ProbeController::SetEstimatedBitrate(
DataRate bitrate,
BandwidthLimitedCause bandwidth_limited_cause,
Timestamp at_time) {
//记录当前拥塞状态
// kLossLimitedBweIncreasing:丢包率在某个阈值以下,码率递增状态(需要基于LossbasedV2)
// kLossLimitedBweDecreasing:丢包在某个阈值以上,码率递减状态(需要基于LossbasedV2)
// kDelayBasedLimitedDelayIncreased:基于delay_based,kBwOverusing或kBwUnderusing
// kRttBasedBackOffHighRtt:基于RttBasedBackoff模块,当前RTT大于某个阈值默认3秒
bandwidth_limited_cause_ = bandwidth_limited_cause;
// 如果当前的估计码率突然低于上次估计码率的0.66倍,kBitrateDropThreshold为0.66
// 则设置码率瞬降的时间戳为at_time,并记录码率瞬降前的码率为上一次的估计码率
if (bitrate < kBitrateDropThreshold * estimated_bitrate_) {
time_of_last_large_drop_ = at_time;
bitrate_before_last_large_drop_ = estimated_bitrate_;
}
// 更新估计码率
estimated_bitrate_ = bitrate;
// 如果当前状态为kWaitingForProbingResult才有机会触发探测
if (state_ == State::kWaitingForProbingResult) {
// Continue probing if probing results indicate channel has greater
// capacity.
DataRate network_state_estimate_probe_further_limit =
config_.network_state_estimate_probing_interval->IsFinite() &&
network_estimate_
? network_estimate_->link_capacity_upper *
config_.further_probe_threshold
: DataRate::PlusInfinity();
RTC_LOG(LS_INFO) << "Measured bitrate: " << bitrate
<< " Minimum to probe further: "
<< min_bitrate_to_probe_further_ << " upper limit: "
<< network_state_estimate_probe_further_limit;
// 当前的估计码率大于min_bitrate_to_probe_further_(最后一次估计码率的0.7倍)
if (bitrate > min_bitrate_to_probe_further_ &&
bitrate <= network_state_estimate_probe_further_limit) {
return InitiateProbing(
at_time, {config_.further_exponential_probe_scale * bitrate}, true);
}
}
return {};
}
- 首先是更新
estimated_bitrate_
当前的估计码率 - 其次是若需要继续探测,并且当前的估计码率比
min_bitrate_to_probe_further_
大0.7
倍的上次估计码率。
过载恢复进行探测
NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(
TransportPacketsFeedback report) {
....
recovered_from_overuse = result.recovered_from_overuse;
if (recovered_from_overuse) {
probe_controller_->SetAlrStartTimeMs(alr_start_time);
auto probes = probe_controller_->RequestProbe(report.feedback_time);
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
probes.begin(), probes.end());
}
....
return update;
}
- 当基于
DelayBased
模块检测到过载恢复的时候,会触发带宽探测,为了更快的提升码率。
std::vector<ProbeClusterConfig> ProbeController::RequestProbe(
Timestamp at_time) {
// Called once we have returned to normal state after a large drop in
// estimated bandwidth. The current response is to initiate a single probe
// session (if not already probing) at the previous bitrate.
//
// If the probe session fails, the assumption is that this drop was a
// real one from a competing flow or a network change.
bool in_alr = alr_start_time_.has_value();
// 这里是3秒间隔阈值,距离应用受限结束的时间为3秒以内
bool alr_ended_recently =
(alr_end_time_.has_value() &&
at_time - alr_end_time_.value() < kAlrEndedTimeout);
// 通过"WebRTC-BweRapidRecoveryExperiment/Enabled"开启in_rapid_recovery_experiment_,默认为false
// 1) 当前处于应用首先状态
// 2) 当前处于距离应用受限结束的时间为3秒以内
// 3) 开启快速回复的试验
// 4) 当前的状态机的状态处于kProbingComplete,也就是探测完成
if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) {
if (state_ == State::kProbingComplete) {
// 上一中记录了码率瞬降前的估计码率,kProbeFractionAfterDrop=0.85
// 这里得到一个建议的探测码率,为码率瞬降前的码率的0.85倍
DataRate suggested_probe =
kProbeFractionAfterDrop * bitrate_before_last_large_drop_;
// kProbeUncertainty = 0.05
// 这里是得到一个最小期望探测码率,为0.95倍的建议探测码率
DataRate min_expected_probe_result =
(1 - kProbeUncertainty) * suggested_probe;
// 距离上次码率瞬降的时间间隔
TimeDelta time_since_drop = at_time - time_of_last_large_drop_;
// 距离上次探测降低的时间间隔,Ret()的时候为设置last_bwe_drop_probing_time_为Now
// 所以假设过载前没有出现drop_probing,那这里得到的time_since_probe会是一个无穷,意思和上次因为bwe降低导致的上探之间的间隔
TimeDelta time_since_probe = at_time - last_bwe_drop_probing_time_;
// kMinTimeBetweenAlrProbes = TimeDelta::Seconds(5)
// kBitrateDropTimeout = TimeDelta::Seconds(5)
// 5) 最小期望探测码率比当前的估计码率大
// 6) 上次码率因过载等导致巨降的时间到当前的间隔小于5秒
// 7) 和上次因为过载等码率导致降低恢复后上探的时间间隔要大于5秒,这里也说明了当过载恢复的时候,主动上探的间隔至少5秒
if (min_expected_probe_result > estimated_bitrate_ &&
time_since_drop < kBitrateDropTimeout &&
time_since_probe > kMinTimeBetweenAlrProbes) {
RTC_LOG(LS_INFO) << "Detected big bandwidth drop, start probing.";
// Track how often we probe in response to bandwidth drop in ALR.
....
// 设置该变量,这就说明了,这个变量一般都是在过载恢复的时候,主动上探的时候对其赋值
last_bwe_drop_probing_time_ = at_time;
return InitiateProbing(at_time, {suggested_probe}, false);
}
}
}
return std::vector<ProbeClusterConfig>();
}
周期性探测
- 周期性探测只在
alr
应用受限状态下会触发。 - 比如在某些静态页面,如果一直处于静态页面,此时编码出来的数据量极少,如果长时间处于这种状态,就会导致
Ack
码率计算偏低,从而错误的估计出较低的目标码率,所以需要有alr
应用受限处理,来避免这种误判 - 当应用受限的时候会适当的补充一下
padding
包来进行发送,从而确保带宽估计的准确性。
NetworkControlUpdate GoogCcNetworkController::OnProcessInterval(
ProcessInterval msg) {
NetworkControlUpdate update;
...
absl::optional<int64_t> start_time_ms =
alr_detector_->GetApplicationLimitedRegionStartTime();
probe_controller_->SetAlrStartTimeMs(start_time_ms);
auto probes = probe_controller_->Process(msg.at_time);
update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),
probes.begin(), probes.end());
....
return update;
}
bool ProbeController::TimeForAlrProbe(Timestamp at_time) const {
if (enable_periodic_alr_probing_ && alr_start_time_) {
Timestamp next_probe_time =
std::max(*alr_start_time_, time_last_probing_initiated_) +
config_.alr_probing_interval;
return at_time >= next_probe_time;
}
return false;
}
-
TimeForAlrProbe
用于计算因alr
探测的时间间隔阈值,只有当前时间和上次探测或者alr
开始的时间间隔超过某个阈值的时候才让开始探测。 -
config_.alr_probing_interval
的默认值为5秒。 - 这说明只有当前时间和上次探测或者
alr
开始的时间间隔超过5
秒的时候才让开始探测。
std::vector<ProbeClusterConfig> ProbeController::Process(Timestamp at_time) {
// InitiateProbing正常生成探测族源数据的时候会对time_last_probing_initiated_进行赋值
// 如果当前时间 - 上一次生成探测源数据的间隔大于1秒,kMaxWaitingTimeForProbingResult为一秒,则判定为超时
// 此时需要设置状态机的状态为State::kProbingComplete,以便后续逻辑能正常处理
if (at_time - time_last_probing_initiated_ >
kMaxWaitingTimeForProbingResult) {
if (state_ == State::kWaitingForProbingResult) {
RTC_LOG(LS_INFO) << "kWaitingForProbingResult: timeout";
state_ = State::kProbingComplete;
min_bitrate_to_probe_further_ = DataRate::PlusInfinity();
}
}
// 如果当前的估计码率为0或者当前状态不为kProbingComplete则忽略这次定时处理
if (estimated_bitrate_.IsZero() || state_ != State::kProbingComplete) {
return {};
}
// 距离上次探测或alr的间隔5秒以上
if (TimeForAlrProbe(at_time) || TimeForNetworkStateProbe(at_time)) {
// alr的上探系数为2.0,在上次估计码率的基础上进行2.0倍探测
return InitiateProbing(
at_time, {estimated_bitrate_ * config_.alr_probe_scale}, true);
}
return std::vector<ProbeClusterConfig>();
}
- 周期性探测能使得码率估计更加稳定,但是也有弊病,实际上带宽探测有点浪费带宽,同时假设配置不合理,也会造成一定的额外丢包。
- 所以在实际的应用中,可能需要大家正确的优化该模块,尽量和实际的业务场景进行匹配,尽量降低带宽浪费和避免额外丢包。
总结
- 本文通过深入学习
ProbeController
模块,深入学习webrtc
中探测模块的逻辑控制。 - 同时通过本文的学习,也能认识到在
google
原生的实现过程中,主要还是基于低码率场景的,对于高码率场景有些策略或者是参数配置,还是有点不大友好,需要进行调整。