深入浅出WebRTC—LossBasedBweV2

WebRTC 同时使用基于丢包的带宽估计算法和基于延迟的带宽估计算法那,能够实现更加全面和准确的带宽评估和控制。基于丢包的带宽估计算法主要依据网络中的丢包情况来动态调整带宽估计,以适应网络状况的变化。本文主要讲解最新 LossBasedBweV2 的实现。

1. 静态结构

LossBasedBweV2 的静态结构比较简单,如下图所示。LossBasedBweV2 被包含在 SendSideBandwidthEstimation 之中,GoogCcNetworkController 不直接与 LossBasedBweV2 打交道,而是通过 SendSideBandwidthEstimation 获取最终带宽估计值。LossBasedBweV2 静态结构虽然简单,但其内部实现一点都不简单,做好心理准备。

2. 重要属性

1)current_best_estimate_

从候选估计值中选择的当前最佳估计值,包含带宽估计值和链路固有丢包率。

struct ChannelParameters {// 链路固有丢包率(非带宽受限导致的丢包率)double inherent_loss = 0.0;// 丢包限制下的带宽DataRate loss_limited_bandwidth = DataRate::MinusInfinity();
};

2)observations_

历史观测值集合。一个 Observation 代表一个观测值:发送码率和丢包率,估计算法中会用到。

struct Observation {bool IsInitialized() const { return id != -1; }// 报文总数int num_packets = 0;// 丢包数量int num_lost_packets = 0;// 接收数量int num_received_packets = 0;// 根据观察时间计算DataRate sending_rate = DataRate::MinusInfinity();// 报文总大小DataSize size = DataSize::Zero();// 丢包总大小DataSize lost_size = DataSize::Zero();int id = -1;
};

3)loss_based_result_

基于丢包的带宽估计值和状态。

struct Result {// 估算的带宽DataRate bandwidth_estimate = DataRate::Zero();// 如果处于kIncreasing状态,则需要做带宽探测LossBasedState state = LossBasedState::kDelayBasedEstimate;
};enum class LossBasedState {// 使用丢包估计带宽,正在增加码率kIncreasing = 0,// 使用丢包估计带宽,正在使用padding增加带宽(探测)kIncreaseUsingPadding = 1,// 使用丢包估计带宽,由于丢包增大,正在降低码率kDecreasing = 2,// 使用延迟估计带宽kDelayBasedEstimate = 3
};

3. 重要方法

1)SetAcknowledgedBitrate

设置 ACK 码率,ACK 码率在很多地方都会被用到,比如计算基于丢包带宽估计值的上限和下限,生成候选者带宽,

2)SetMinMaxBitrate

设置基于丢包带宽估计的上限值和下限值。

3)UpdateBandwidthEstimate

SendSideBandwidthEstimation 调用此接口,传入 TransportFeedback、延迟估计带宽和 ALR 状态等参数。

4)GetLossBasedResult

获取基于丢包带宽估计结果。

4. 源码分析

4.1. UpdateBandwidthEstimate

UpdateBandwidthEstimate 是丢包估计的主函数,代码非常多,其主体流程如下图所示:

4.1.1. 搜索最佳候选者

搜索最佳候选者的逻辑如下图所示,解释如下:

1)基于 TransportFeedback 构建观测值,每一组观测值设置了最小观测时长。如果产生了新的观测值,则进人新一轮的带宽估计。

2)使用一定算法生成一系列候选者(candidate),只需确定候选者带宽即可。

3)基于观测数据,使用牛顿方法计算候选者的最优固有丢包率。

4)基于观测数据,对每个候选者计算目标函数值,取目标函数值最大者为最佳候选者。

// 尝试将新的观测数据加入到历史数据中,如果没有产生新的observation则返回
if (!PushBackObservation(packet_results)) {return;
}// 初始化最佳带宽估计,如果没有有效的丢包限制带宽估计,则使用基于延迟的估计
if (!IsValid(current_best_estimate_.loss_limited_bandwidth)) {if (!IsValid(delay_based_estimate)) {return;}current_best_estimate_.loss_limited_bandwidth = delay_based_estimate;loss_based_result_ = {.bandwidth_estimate = delay_based_estimate,.state = LossBasedState::kDelayBasedEstimate};
}ChannelParameters best_candidate = current_best_estimate_;
double objective_max = std::numeric_limits<double>::lowest();// 生成并遍历所有candidate,找到最优candidate
for (ChannelParameters candidate : GetCandidates(in_alr)) {// 使用牛顿法搜索最优固有丢包率NewtonsMethodUpdate(candidate);// 基于带宽和固有丢包率计算收益值const double candidate_objective = GetObjective(candidate);// 找到收益值最大的Candidateif (candidate_objective > objective_max) {objective_max = candidate_objective;best_candidate = candidate;}
}

4.1.2. 调整丢包限制带宽

通过算法计算得到的最佳候选者还不可靠,需要进行调整。 在丢包限制状态下,如果带宽增加过快则限制带宽增长,并使用爬坡因子来调整带宽估计。增加带宽过快可能会再次引发丢包。

// best_candidate 的估计带宽与其固有丢包率是匹配的,如果 best_candidate 的估计带宽大于
// 上一次的估计带宽,但真实丢包率大于 best_candidate 的固有丢包率,那么有理由认为 
// best_candidate 的估计带宽是不可靠的。
if (GetAverageReportedLossRatio() > best_candidate.inherent_loss &&config_->not_increase_if_inherent_loss_less_than_average_loss &&current_best_estimate_.loss_limited_bandwidth <best_candidate.loss_limited_bandwidth) {best_candidate.loss_limited_bandwidth =current_best_estimate_.loss_limited_bandwidth;
}// 下面这一坨都是在调整best_candidate.loss_limited_bandwidth
if (IsInLossLimitedState()) {if (recovering_after_loss_timestamp_.IsFinite() &&recovering_after_loss_timestamp_ + config_->delayed_increase_window >last_send_time_most_recent_observation_ &&best_candidate.loss_limited_bandwidth > bandwidth_limit_in_current_window_) {best_candidate.loss_limited_bandwidth = bandwidth_limit_in_current_window_;}bool increasing_when_loss_limited = IsEstimateIncreasingWhenLossLimited(/*old_estimate=*/current_best_estimate_.loss_limited_bandwidth,/*new_estimate=*/best_candidate.loss_limited_bandwidth);// Bound the best candidate by the acked bitrate.if (increasing_when_loss_limited && IsValid(acknowledged_bitrate_)) {// 爬坡因子double rampup_factor = config_->bandwidth_rampup_upper_bound_factor;// 使用更保守的爬坡因子if (IsValid(last_hold_info_.rate) &&acknowledged_bitrate_ <config_->bandwidth_rampup_hold_threshold * last_hold_info_.rate) {rampup_factor = config_->bandwidth_rampup_upper_bound_factor_in_hold;}// 保证带宽估计值不会低于当前的最佳估计值。// 同时,限制在不超过新计算的候选值和基于 ACK 码率计算的增长上限之间的较小值。best_candidate.loss_limited_bandwidth =std::max(current_best_estimate_.loss_limited_bandwidth,std::min(best_candidate.loss_limited_bandwidth,rampup_factor * (*acknowledged_bitrate_)));// 为了避免估计值长时间停滞导致算法无法切换到kIncreasing,这里将带宽估计增加1kbps。if (loss_based_result_.state == LossBasedState::kDecreasing &&best_candidate.loss_limited_bandwidth ==current_best_estimate_.loss_limited_bandwidth) {best_candidate.loss_limited_bandwidth =current_best_estimate_.loss_limited_bandwidth +DataRate::BitsPerSec(1);}}
}

4.1.3. 计算有界带宽估计

取丢包估计带宽和延迟估计带宽的较小者,并将估计值限制在合理范围,获得 bounded_bandwidth_estimate。

// 施加了范围限制的带宽估计值
DataRate bounded_bandwidth_estimate = DataRate::PlusInfinity();if (IsValid(delay_based_estimate_)) {// 取丢包估计带宽和延迟估计带宽中的较小者bounded_bandwidth_estimate =std::max(GetInstantLowerBound(),std::min({best_candidate.loss_limited_bandwidth,GetInstantUpperBound(), delay_based_estimate_}));
} else {// 没有延迟估计值,则使用丢包估计值bounded_bandwidth_estimate = std::max(GetInstantLowerBound(), std::min(best_candidate.loss_limited_bandwidth, GetInstantUpperBound()));
}

4.1.4. 更新当前最佳估计

根据配置和估计结果更新当前最佳估计值。

if (config_->bound_best_candidate && bounded_bandwidth_estimate < best_candidate.loss_limited_bandwidth) {// 如果配置了对 best_candidate 进行约束,则限制 // best_candidate.loss_limited_bandwidth 不能大于 bounded_bandwidth_estimatecurrent_best_estimate_.loss_limited_bandwidth = bounded_bandwidth_estimate;current_best_estimate_.inherent_loss = 0;
} else {// 没有配置就等于筛选出来的最优值current_best_estimate_ = best_candidate;
}

4.1.5. 设置带宽估计结果

获取 bounded_bandwidth_estimate 后,接下来需要更新 loss_based_result.state,并设置估计带宽。以下代码逻辑异常复杂,条件一大堆,是 LossBasedBweV2 最难理解的部分。

// 当前是在 kDecreasing 状态,此次丢包估计带宽低于延迟估计带宽,不允许估计带宽
// 立即上升到可能引起丢包的水平。
if (loss_based_result_.state == LossBasedState::kDecreasing && last_hold_info_.timestamp > last_send_time_most_recent_observation_ && bounded_bandwidth_estimate < delay_based_estimate_) {loss_based_result_.bandwidth_estimate =std::min(last_hold_info_.rate, bounded_bandwidth_estimate);return; // 直接返回,状态保持LossBasedState::kDecreasing
}// 带宽增加
if (IsEstimateIncreasingWhenLossLimited(/*old_estimate=*/loss_based_result_.bandwidth_estimate,/*new_estimate=*/bounded_bandwidth_estimate) &&CanKeepIncreasingState(bounded_bandwidth_estimate) &&bounded_bandwidth_estimate < delay_based_estimate_ &&bounded_bandwidth_estimate < max_bitrate_) {if (config_->padding_duration > TimeDelta::Zero() &&bounded_bandwidth_estimate > last_padding_info_.padding_rate) {// 开启一个新的填充周期last_padding_info_.padding_rate = bounded_bandwidth_estimate;last_padding_info_.padding_timestamp =last_send_time_most_recent_observation_;}loss_based_result_.state = config_->padding_duration > TimeDelta::Zero()? LossBasedState::kIncreaseUsingPadding: LossBasedState::kIncreasing;
} 
// 带宽减少
else if (bounded_bandwidth_estimate < delay_based_estimate_ &&bounded_bandwidth_estimate < max_bitrate_) {if (loss_based_result_.state != LossBasedState::kDecreasing &&config_->hold_duration_factor > 0) {last_hold_info_ = {.timestamp = last_send_time_most_recent_observation_ +last_hold_info_.duration,.duration =std::min(kMaxHoldDuration, last_hold_info_.duration *config_->hold_duration_factor),.rate = bounded_bandwidth_estimate};}last_padding_info_ = PaddingInfo();loss_based_result_.state = LossBasedState::kDecreasing;
} else {// 如果以上条件都不满足,表明基于延迟的估计应该被采纳,// 或者当前状态需要重置以避免带宽被错误地限制在低水平。last_hold_info_ = {.timestamp = Timestamp::MinusInfinity(),.duration = kInitHoldDuration,.rate = DataRate::PlusInfinity()};last_padding_info_ = PaddingInfo();loss_based_result_.state = LossBasedState::kDelayBasedEstimate;
}// 更新丢包限制的评估带宽
loss_based_result_.bandwidth_estimate = bounded_bandwidth_estimate;

4.2. 相关算法

基于丢包带宽估计的核心问题可以表述为:带宽和固有丢包率是链路的两个属性,现在我们有一组观测值,每个观测值记录了码率和丢包率,如何通过这些观测值反推链路的带宽和固有丢包率?

WebRTC 假定链路丢包符合二项分布,先生成一组候选者(candidate),根据经验设置候选者的带宽,然后用牛顿方法在观测值上搜索候选者的最优固有丢包率,最后用候选者带宽和固有丢包率计算一个收益函数,取收益函数最大的候选者作为最佳估计。

4.2.1. 搜集观测值

Observation 基于 TransportFeedback 生成,收集足够时长报文成为一个观测值。

bool LossBasedBweV2::PushBackObservation(rtc::ArrayView<const PacketResult> packet_results) {if (packet_results.empty()) {return false;}// 获取报文数组的统计信息PacketResultsSummary packet_results_summary =GetPacketResultsSummary(packet_results);// 累加报文数量partial_observation_.num_packets += packet_results_summary.num_packets;// 累加丢包数量partial_observation_.num_lost_packets +=packet_results_summary.num_lost_packets;// 累加报文大小partial_observation_.size += packet_results_summary.total_size;// 累加丢包大小partial_observation_.lost_size += packet_results_summary.lost_size;// This is the first packet report we have received.if (!IsValid(last_send_time_most_recent_observation_)) {last_send_time_most_recent_observation_ =packet_results_summary.first_send_time;}// 报文组中最晚发包时间const Timestamp last_send_time = packet_results_summary.last_send_time;// 距离上一组 last_send_time 时间差const TimeDelta observation_duration =last_send_time - last_send_time_most_recent_observation_;// 两组报文时间差要达到阈值才能创建一个完整的 observationif (observation_duration <= TimeDelta::Zero() ||observation_duration < config_->observation_duration_lower_bound) {return false;}// 更新last_send_time_most_recent_observation_ = last_send_time;// 创建 oberservationObservation observation;observation.num_packets = partial_observation_.num_packets;observation.num_lost_packets = partial_observation_.num_lost_packets;observation.num_received_packets =observation.num_packets - observation.num_lost_packets;observation.sending_rate =GetSendingRate(partial_observation_.size / observation_duration);observation.lost_size = partial_observation_.lost_size;observation.size = partial_observation_.size;observation.id = num_observations_++;// 保存 observationobservations_[observation.id % config_->observation_window_size] =observation;// 重置 partialpartial_observation_ = PartialObservation();CalculateInstantUpperBound();return true;
}

4.2.2. 生成候选者

搜索最佳后选择之前,需要生成一系列候选者。由于是有限集合搜索,候选者带宽的选取要考虑上界、下界以及分布的合理性,以形成一个有效的搜索空间,获得准确的搜索结果。

std::vector<LossBasedBweV2::ChannelParameters> LossBasedBweV2::GetCandidates(bool in_alr) const {// 当前的最佳带宽估计中提取信息ChannelParameters best_estimate = current_best_estimate_;// 用于存储即将生成的候选带宽std::vector<DataRate> bandwidths;// 基于当前最佳估计带宽和生成因子生成一系列候选带宽值: 1.02, 1.0, 0.95// 新的带宽在当前最佳估计带宽左右的概率比较高(带宽不会瞬变)for (double candidate_factor : config_->candidate_factors) {bandwidths.push_back(candidate_factor *best_estimate.loss_limited_bandwidth);}// ACK码率是链路容量的一个真实测量值,添加一个基于ACK码率但进行了回退因子调整的候选带宽if (acknowledged_bitrate_.has_value() &&config_->append_acknowledged_rate_candidate) {if (!(config_->not_use_acked_rate_in_alr && in_alr) ||(config_->padding_duration > TimeDelta::Zero() &&last_padding_info_.padding_timestamp + config_->padding_duration >=last_send_time_most_recent_observation_)) {bandwidths.push_back(*acknowledged_bitrate_ *config_->bandwidth_backoff_lower_bound_factor);}}// 满足以下条件,延迟估计带宽也作为带宽候选者之一// 1)延迟估计带宽有效// 2)配置允许// 3)延迟估计带宽高于当前最佳估计丢包限制带宽if (IsValid(delay_based_estimate_) &&config_->append_delay_based_estimate_candidate) {if (delay_based_estimate_ > best_estimate.loss_limited_bandwidth) {bandwidths.push_back(delay_based_estimate_);}}// 满足以下条件,当前带宽上界也作为带宽候选者之一// 1)处于ALR状态// 2)配置允许时// 3)最佳估计丢包限制带宽大于当前带宽上界if (in_alr && config_->append_upper_bound_candidate_in_alr &&best_estimate.loss_limited_bandwidth > GetInstantUpperBound()) {bandwidths.push_back(GetInstantUpperBound());}// 计算一个候选带宽的上界,用于限制生成的候选带宽值不超过这个上界。const DataRate candidate_bandwidth_upper_bound =GetCandidateBandwidthUpperBound();std::vector<ChannelParameters> candidates;candidates.resize(bandwidths.size());for (size_t i = 0; i < bandwidths.size(); ++i) {ChannelParameters candidate = best_estimate;// 丢包限制带宽设置为当前最佳估计的丢包限制带宽与候选带宽值、上界之间的最小值candidate.loss_limited_bandwidth =std::min(bandwidths[i], std::max(best_estimate.loss_limited_bandwidth,candidate_bandwidth_upper_bound));// 使用最佳估计的丢包率candidate.inherent_loss = GetFeasibleInherentLoss(candidate);candidates[i] = candidate;}return candidates;
}

4.2.3. 牛顿方法

牛顿方法要解决的问题是,在候选者的估计带宽下,基于当前观测值,求最大似然概率下的固有丢包率。可以这么理解,已知当前链路的带宽,测得一组观测值,观测值描述了收发数据和丢包情况,现在需要计算一个最优的固定丢包率,使得当前观测值出现的联合概率最大。

观测数据可以简化描述为:在一段时间内统计,丢失了 n 个报文,接收到 m 个报文。假设链路的固有丢包率为p,由于观测结果属于二项分布,其概率密度函数可以表示为:

现在测得一组观测数据,要求链路固有丢包率的最大似然概率。我们可以将 k 次观测数据的似然函数相乘,得到联合似然函数,因为每次实验是独立的:

直接最大化上述似然函数可能比较复杂,可以先对似然函数取自然对数,转换为对数似然函数,方便计算:

由于 ln(C_{m_i+n_i}^{n_i}) 不依赖于 p,在求导时会消失,因此在最大化对数似然函数时可以忽略这一项。对 ln(L(p)) 关于 p 求导,并令导数等于0,可以找到 p 的最大似然估计值 \hat{p}

理论上,代入观测数据就可以求得最优固有丢包率。但这里不能这么计算,原因有两个:

1)这里的丢包率并不是固有丢包率 inherent_loss,而是丢包概率 loss_probability,loss_probability 除 inherent_loss 之外,还包括发送速率超出链路带宽导致的丢包。

2)即使计算得到 loss_probability 的最大似然估计值,仍然不能直接求得 inherent_loss 的最大似然估计值,因为 inherent_loss 与 loss_probability 之间并不是简单的线性关系,如下所示。

double GetLossProbability(double inherent_loss, DataRate loss_limited_bandwidth,DataRate sending_rate) {if (inherent_loss < 0.0 || inherent_loss > 1.0) {inherent_loss = std::min(std::max(inherent_loss, 0.0), 1.0);}double loss_probability = inherent_loss;// 如果发送速率大于丢包限制带宽,真实丢包率会更高if (IsValid(sending_rate) && IsValid(loss_limited_bandwidth) && (sending_rate > loss_limited_bandwidth)) {loss_probability += (1 - inherent_loss) *(sending_rate - loss_limited_bandwidth) / sending_rate;}// 限制范围[1.0e-6, 1.0 - 1.0e-6]return std::min(std::max(loss_probability, 1.0e-6), 1.0 - 1.0e-6);
}

既然如此,WebRTC 就通过计算似然函数的一阶导数和二阶导数,然后使用牛顿方法来搜索 inherent_loss 的最优值。代码如下所示,标准的牛顿方法。

void LossBasedBweV2::NewtonsMethodUpdate(ChannelParameters& channel_parameters) const {// 没有可用的观测值if (num_observations_ <= 0) {return;}// 指定带宽下,根据观测值,求得最大似然丢包率for (int i = 0; i < config_->newton_iterations; ++i) {// 计算一阶导数和二阶导数const Derivatives derivatives = GetDerivatives(channel_parameters);// 基于一阶导数和二阶导数进行迭代搜索,newton_step_size = 0.75channel_parameters.inherent_loss -=config_->newton_step_size * derivatives.first / derivatives.second;// 固有丢包率的界限约束channel_parameters.inherent_loss = GetFeasibleInherentLoss(channel_parameters);}
}

一阶导数和二阶导数的计算如下所示,不过这里有两个需要注意的点:

1)这里计算的并不是 inherent_loss 而是 loss_probability 的最大似然函数的导数,由于 loss_probability 是 inherent_loss 的函数,根据链式法则,使用 loss_probability 的导数来计算 inherent_loss 的最优值是有效的。

2)这里的一阶导数和二阶导数是多个观测值计算的累加值,由于多个观测值之间是独立同分布的,所以,这也是没问题的。

LossBasedBweV2::Derivatives LossBasedBweV2::GetDerivatives(const ChannelParameters& channel_parameters) const {Derivatives derivatives;for (const Observation& observation : observations_) {// 无效的观测值if (!observation.IsInitialized()) {continue;}// 计算在给定通道参数下的丢包概率,如果发送速率超过丢包限制带宽,// 则很可能会产生链路拥塞,从而导致真实丢包率高于链路固有丢包率double loss_probability = GetLossProbability(channel_parameters.inherent_loss,channel_parameters.loss_limited_bandwidth, observation.sending_rate);// 施加一个时间权重,距当前时间越近,数据越“新鲜”,权重越高double temporal_weight =temporal_weights_[(num_observations_ - 1) - observation.id];// 基于丢失和接收到的数据量分别计算一阶导数和二阶导数的累加项if (config_->use_byte_loss_rate) {// derivatives.first += w*((lost/p) - (total-lost)/(1-p))derivatives.first +=temporal_weight *((ToKiloBytes(observation.lost_size) / loss_probability) -(ToKiloBytes(observation.size - observation.lost_size) /(1.0 - loss_probability)));// derivatives.second -= w*((lost/p^2) + (total-lost)/(1-p)^2)derivatives.second -=temporal_weight *((ToKiloBytes(observation.lost_size) /std::pow(loss_probability, 2)) +(ToKiloBytes(observation.size - observation.lost_size) /std::pow(1.0 - loss_probability, 2)));// 基于丢失和接收到的数据包数量分别计算一阶导数和二阶导数的累加项} else {derivatives.first +=temporal_weight *((observation.num_lost_packets / loss_probability) -(observation.num_received_packets / (1.0 - loss_probability)));derivatives.second -=temporal_weight *((observation.num_lost_packets / std::pow(loss_probability, 2)) +(observation.num_received_packets /std::pow(1.0 - loss_probability, 2)));}}// 理论上,二阶导数应为负(表示带宽估计函数的凸性),// 若出现非预期的正值,进行校正,以避免数学异常或不合理的进一步计算。if (derivatives.second >= 0.0) {derivatives.second = -1.0e-6;}return derivatives;
}

4.2.4. 目标函数

经牛顿方法搜索后的固有丢包率,加上链路带宽,带入目标函数进行计算,目标值越大则结果越可信。

目标函数分为两部分,第一部分是似然概率,代表了模型对观测数据的解释能力,其中 w_i 是时间权重因子,数据越“新鲜”权重越高。

目标函数的第二部分是高带宽偏置,鼓励算法探索更高带宽的潜在收益,其他项相同的前提下,带宽越高越受青睐。其中 w_i 是时间权重因子,数据越“新鲜”权重越高。

double LossBasedBweV2::GetObjective(const ChannelParameters& channel_parameters) const {double objective = 0.0;// 计算高带宽偏置,鼓励探索更高带宽const double high_bandwidth_bias =GetHighBandwidthBias(channel_parameters.loss_limited_bandwidth);for (const Observation& observation : observations_) {if (!observation.IsInitialized()) {continue;}// 考虑发送码率高于限制码率情况导致的拥塞丢包double loss_probability = GetLossProbability(channel_parameters.inherent_loss,channel_parameters.loss_limited_bandwidth, observation.sending_rate);// 应用一个时间权重给每个观测,新近的观测通常会有更大的影响double temporal_weight =temporal_weights_[(num_observations_ - 1) - observation.id];if (config_->use_byte_loss_rate) {// 固有丢包率收益objective +=temporal_weight *((ToKiloBytes(observation.lost_size) * std::log(loss_probability)) +(ToKiloBytes(observation.size - observation.lost_size) *std::log(1.0 - loss_probability)));// 带宽收益objective +=temporal_weight * high_bandwidth_bias * ToKiloBytes(observation.size);} else {objective +=temporal_weight *((observation.num_lost_packets * std::log(loss_probability)) +(observation.num_received_packets *std::log(1.0 - loss_probability)));objective +=temporal_weight * high_bandwidth_bias * observation.num_packets;}}return objective;
}

5. 总结

与带宽一样,固有丢包率(inherent loss)也是网路链路的一个属性,而且是动态变化的。当观察到丢包的时候,我们如何判断这是由于链路固有丢包率导致的丢包还是由于网络拥塞导致的丢包?除非我们知道链路的固有丢包率和带宽,但显然这是无法办到的。WebRTC 为解决这个问题打开了一扇窗,其思路是建立网络丢包的二项式分布模型,通过搜集足够多的观测值,构造目标函数,使用牛顿方法去搜索链路带宽和固有丢包率的最佳组合。然后对这个最佳组合进行必要的校正与调整。不过,从 WebRTC 的实现来看,调整算法太过复杂,有理由相信通过算法得到的估计值可靠性不是非常高,如何优化和简化这一部分的实现逻辑是一个挑战。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/382734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux操作系统之线程

1.线程概念 线程是一个轻量级进程,每一个线程都属于一个进程 进程是操作系统资源分配的最小单位,而线程是CPU任务调度的最小单位 线程是一个任务执行的过程,包括创建,调度,消亡 创建:线程空间位于进程空间,进程中的线程,栈区独立,并共享进程中的数据区,文本区,堆区 调度:宏观…

在 Android 上实现语音命令识别:详细指南

在 Android 上实现语音命令识别:详细指南 语音命令识别在现代 Android 应用中变得越来越普遍。它允许用户通过自然语言与设备进行交互,从而提升用户体验。本文将详细介绍如何在 Android 上实现语音命令识别,包括基本实现、带有占位槽位的命令处理,以及相关的配置和调试步骤…

【JavaScript】箭头函数

具体讲解 之前写 this 的指向时就提到过箭头函数&#xff0c;但是由于其比较复杂&#xff0c;还是单独开一篇来讲箭头函数。 箭头函数&#xff0c;箭头函数不能作为构造函数&#xff0c;没有原型 prototype&#xff0c;不能 new。 在箭头函数中&#xff0c;this 关键字指向的是…

刷机维修进阶教程-----何谓“tee损坏” 指纹丢失 掉帧 传感器失效?详细修复步骤教程

TEE损坏指的是安卓机型中Key Attestation密钥认证所依赖的可信应用中的证书库被破坏了。然后拒绝为指纹密匙认证提供服务。加密的密匙由TEE负责管理。tee损坏只影响当前机型的密匙认证。不影响加密。通俗的理解。如果你机型维修或者刷机或者解锁或者格机 全檫除分区等等后有异常…

vue3 + ts 报错:Parsing error: Unexpected token : eslint

报错&#xff1a;Parsing error: Unexpected token : eslint 解决&#xff1a; 在 .eslintrc.json 文件中加入 "parser": "babel/eslint-parser"配置 “parser”: “babel/eslint-parser” 告诉 ESLint 在检查代码之前&#xff0c;先使用 Babel 的解析器…

Chapter17 表面着色器——Shader入门精要学习

Chapter17 表面着色器 一、编译指令1.表面函数2.光照函数3.其他可选参数 二、两个结构体1.Input 结构体&#xff1a;数据来源2.SurfaceOutput 三、Unity背后做了什么四、表面着色器的缺点 一、编译指令 作用&#xff1a;指明该表面着色器的 表面函数 和 光照函数&#xff0c;并…

Google Chrome 浏览器在链接上点右键的快捷键

如今&#xff0c;越来越多的软件都懒得设个快捷键&#xff0c;就算设置了连个下划线也懒得加了。 谷歌浏览器右键 > 链接另存为... 和 复制链接地址 的快捷键 (如图)

微信小程序开发(百货商战)实战项目的购物车和个人中心的创建

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

一款异次元小清新风格的响应式wordpress个人博客主题

一款异次元小清新风格的响应式个人博客主题。这是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#x…

iOS开发设计模式篇第一篇MVC设计模式

目录 1. 引言 2.概念 1.Model 1.职责 2.实现 3.和Controller通信 1.Contrller直接访问Model 2.通过委托(Delegate)模式 3.通知 4.KVO 4.设计的建议 2.View 1.职责 2.实现 3.和Controller通信 1. 目标-动作&#xff08;Target-Action&#xff09;模式 2…

Jolt路线图

1. 引言 a16z crypto团队2024年7月更新了其Jolt路线图&#xff1a; 主要分为3大维度&#xff1a; 1&#xff09;链上验证维度&#xff1a; 1.1&#xff09;Zeromorph&#xff1a;见Aztec Labs团队2023年论文 Zeromorph: Zero-Knowledge Multilinear-Evaluation Proofs from…

什么是PCB流锡槽焊盘/C型焊盘,如何设计?-捷配笔记

在PCB进行机器组装器件时&#xff08;如波峰焊&#xff09;&#xff0c;为了防止部分需要二次焊接的元器件的焊盘堵孔&#xff0c;就需要在PCB焊盘上面开个过锡槽&#xff0c;以便过波峰焊时&#xff0c;这些焊锡会流掉。开流锡槽就是在焊盘裸铜&#xff08;敷锡&#xff09;部…

【MySQL进阶之路 | 高级篇】事务的ACID特性

1. 数据库事务概述 事务是数据库区别于文件系统的重要特性之一&#xff0c;当我们有了事务就会让数据库始终保持一致性&#xff0c;同时我们还能通过事务的机制恢复到某个时间点&#xff0c;这样可以保证给已提交到数据库的修改不会因为系统崩溃而丢失。 1.1 基本概念 事务&…

微前端--single-spa

微前端 使用微前端的挑战&#xff1a; 子应用切换&#xff0c;应用相互隔离&#xff0c;互补干扰&#xff0c;子应用之前的通信&#xff0c;多个子应用并存&#xff0c;用户状态的存储&#xff0c;免登。 常用技术方案 路由分发式微前端 通过http服务的反向代理 http {serv…

高翔【自动驾驶与机器人中的SLAM技术】学习笔记(二)——带着问题的学习;一刷感受;环境搭建

按照作者在读者寄语中的说法&#xff1a;我们得榨干这本书的知识。 带着问题 为了更好的学习&#xff0c;我们最好带着问题去探索。 第一&#xff1a;核心问题与基础知识 如上图&#xff1a;这本书介绍了SLAM相关的核心问题和基础知识。王谷博士给我们做了梳理&#xff1a;…

人工智能学习笔记 - 初级篇Ⅱ - 图形可视化 - 第5节-设置刻度、刻度标签和网格

微信公众号&#xff1a;御风研墨 关注可了解更多。问题或建议&#xff0c;请公众号留言 文章目录 设置刻度、刻度标签和网格应用背景准备工作操作步骤工作原理补充说明最后 设置刻度、刻度标签和网格 应用背景 在数据可视化中&#xff0c;合理设置刻度、刻度标签和网格是提高…

【BUG】已解决:libpng warning: iccp: known incorrect sRGB profile

已解决&#xff1a;libpng warning: iccp: known incorrect sRGB profile 目录 已解决&#xff1a;libpng warning: iccp: known incorrect sRGB profile 【常见模块错误】 错误原因&#xff1a; 原因分析 解决方案 具体步骤 欢迎来到英杰社区https://bbs.csdn.net/topics…

MySQL第四次作业

&#xff08;一&#xff09;建立数据库及表 &#xff08;二&#xff09;处理表 1.修改student 表中年龄(sage)字段属性&#xff0c;数据类型由int 改变为smallint 2.为Course表中Cno 课程号字段设置索引,并查看索引 3.为SC表建立按学号(sno)和课程号(cno)组合的升序的主键索引…

python每日学习12:pandas库的用法(1)

python每日学习12&#xff1a;pandas库的用法&#xff08;1&#xff09; 安装 pip install pandas设定系统环境 import pandas as pd #设定自由列表输出最多为 10 行 pd.options.display.max_rows 10 # 显示当前 Pandas 版本号 pd.__version__进入jupyter notebook 页面 在终端…

乐室预约小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;公告信息管理&#xff0c;乐室信息管理&#xff0c;乐器分类管理&#xff0c;乐器管理&#xff0c;乐器知识管理&#xff0c;用户管理&#xff0c;用户预约管理&#xff0c;取消预约管理&#xff0c;用…