深入浅出WebRTC—LossBasedBweV2

news2024/9/24 7:21:19

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);
  // 找到收益值最大的Candidate
  if (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_estimate
	current_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_;

  // 两组报文时间差要达到阈值才能创建一个完整的 observation
  if (observation_duration <= TimeDelta::Zero() ||
      observation_duration < config_->observation_duration_lower_bound) {
    return false;
  }

  // 更新
  last_send_time_most_recent_observation_ = last_send_time;

  // 创建 oberservation
  Observation 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_++;

  // 保存 observation
  observations_[observation.id % config_->observation_window_size] =
      observation;

  // 重置 partial
  partial_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.75
    channel_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.coloradmin.cn/o/1944234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SSIS_SQLITE

1.安装 SQLite ODBC 驱动程序 2.添加SQLite数据源 在“用户DSN”或“系统DSN”选项卡中&#xff0c;点击“添加”。选择“SQLite3 ODBC Driver”&#xff0c;然后点击“完成”。在弹出的配置窗口中&#xff0c;设置数据源名称&#xff08;DSN&#xff09;&#xff0c;并指定S…

python实现特征检测算法4

python实现Richardson-Lucy 反卷积算法 Richardson-Lucy 反卷积算法算法原理Python实现详细解释Richardson-Lucy算法的优缺点应用领域Richardson-Lucy 反卷积算法 Richardson-Lucy反卷积算法是一种迭代算法,用于恢复因成像系统中的点扩散函数(PSF)导致的模糊图像。该算法最…

Spring MVC 应用分层

1. 类名使⽤⼤驼峰⻛格&#xff0c;但以下情形例外&#xff1a;DO/BO/DTO/VO/AO 2. ⽅法名、参数名、成员变量、局部变量统⼀使⽤⼩驼峰⻛格 3. 包名统⼀使⽤⼩写&#xff0c;点分隔符之间有且仅有⼀个⾃然语义的英语单词. 常⻅命名命名⻛格介绍 ⼤驼峰: 所有单词⾸字⺟…

【LLM-推理】Self-Refine:使用feedback迭代修正LLM的Output

来源&#xff1a; https://selfrefine.info/ 1.论文速读(摘要引言) 本文主要提出了Self-Refine策略&#xff0c;旨在通过一个LLM不断refine修正LLM的输出&#xff0c;使其在无需额外训练的情况下&#xff0c;在下游任务产生更好的效果。 该方法的直观Insight&#xff1a;我们…

7.23 字符串简单中等 520 125 14 34

520 Detect Capital 思路&#xff1a; 题目&#xff1a;判定word &#xff1a;if the usage of capitals in it is right.遍历所有的string&#xff1a; 两种情况&#xff1a; 首字母capitals–>判定第二个字母是否大写–>所有字母大写 otherwise 除第一个以外全部小写&a…

Github Desktop 关于将本地文件夹设置为新仓库的 使用笔记

实际要达到的结果: 将UE5工程同步到Github,工程太大,我们只需要将必要的工程文件夹同步即可,缓存等一些不必要的文件夹则不需要同步 最终效果预览: 1. 将本地文件夹设置为新仓库 将本地文件夹作为仓库一般你是没有这个仓库的,所以你需要新建一个仓库 如果忽略某些不必要的文…

视触觉传感器在矿物/岩石识别中的应用探索

人工智能推动矿物/岩石自动识别技术的发展&#xff0c;该技术减少了人工成本和对个人经验的依赖。随着仪器仪表的数字化&#xff0c;图像识别发挥着越来越重要的作用。清华大学联合中国地质大学近期在期刊Advanced Intelligent Systems&#xff08;JCR Q1, 影响因子7.4&#xf…

JAVA.4.继承

1.特点 java只支持单继承&#xff0c;一个儿子继承一个父亲 但可以多层继承&#xff0c;a继承b&#xff0c;b继承c b是a的直接父类&#xff0c;c是a的间接父类 每个类都直接或者简介继承Object&#xff0c;不写继承就默认继承它 2.注意事项 构造方法 父类的构造方法&#…

生成式 AI 的发展方向:Chat 还是 Agent?

生成式 AI 的发展方向&#xff0c;是 Chat 还是 Agent&#xff1f; 随着生成式 AI 技术的不断进步&#xff0c;关于其未来发展方向的讨论也愈发激烈。究竟生成式 AI 的未来是在对话系统&#xff08;Chat&#xff09;中展现智慧&#xff0c;还是在自主代理&#xff08;Agent&am…

GO内存分配详解

文章目录 GO内存分配详解一. 物理内存(Physical Memory)和虚拟内存(Virtual Memory)二. 内存分配器三. TCMalloc线程内存(thread memory)页堆(page heap)四. Go内存分配器mspanmcachemcentralmheap五. 对象分配流程六. Go虚拟内存ArenaGO内存分配详解 这篇文章中我将抽丝剥茧,…

LINUX之MMC子系统分析

目录 1. 概念1.1 MMC卡1.2 SD卡1.3 SDIO 2. 总线协议2.1 协议2.2 一般协议2.3 写数据2.4 读数据2.5 卡模式2.5.1 SD卡模式2.5.2 eMMC模式 2.6 命令2.6.1 命令类2.6.2 详细命令 2.7 应答2.8 寄存器2.8.1 OCR2.8.2 CID2.8.3 CSD2.8.4 RCA2.8.5 扩展CSD 3. 关键结构3.1 struct sdh…

Llama 3.1要来啦?!测试性能战胜GPT-4o

哎呀&#xff0c;Meta声称将于今晚发布的Llama 3.1&#xff0c;数小时前就在Hugging Face上泄露出来了&#xff1f;泄露的人很有可能是Meta员工&#xff1f; 还是先来看泄露出来的llama3.1吧。新的Llama 3.1模型包括8B、70B、405B三个版本。 而经过网友测试&#xff0c;该base…

K8s 核心组件——API Server

1. Kubernetes API Server 概述 1.1 基本概念 Kubernetes API Server&#xff08;API Server&#xff09;是 Kubernetes 的核心组件之一&#xff0c;负责暴露 Kubernetes API 给用户和客户端&#xff0c;接收和处理来自客户端的请求&#xff0c;并将其存储到 etcd 中。Kubern…

Unity3D之TextMeshPro使用

文章目录 1. TextMeshPro简介2. TextMeshPro创建3. TextMeshPro脚本中调用4. TextMeshPro字体设置及中文支持过程中出现的一些问题 1. TextMeshPro简介 【官网文档】https://docs.unity.cn/cn/2020.3/Manual/com.unity.textmeshpro.html TextMeshPro 是 Unity 的最终文本解决…

java 集合框架-collection(单列集合)

在编程语言中&#xff0c;我们必然少不了存储数据的容器&#xff0c;虽然我们有数组&#xff0c;但是数组是连续的开辟处一块连续的内存空间&#xff0c;如果数据过大会无法存储完&#xff0c;数据量小&#xff0c;会浪费空间&#xff0c;所以我们需要使用集合存储数据&#xf…

Qt创建自定义组件并且promote to之后导致编译错误(CMake)

创建自定组件并且加入到全局(勾选"Global include"选项)后&#xff0c;重新编译&#xff0c;元对象编译器生成的ui_xxxx.h文件中会新加入自定义组件的头文件&#xff1a; 如图所示&#xff0c;编译器提示找不到自定义组件的头文件&#xff1a; Solution: 在CMakeL…

leetcode日记(48)排列序列

这道题想到了规律就不算难&#xff0c;列了好几个示例想出的规律&#xff0c;试着排序几个就会了 class Solution { public:string getPermutation(int n, int k) {string result;int m1;int i1;for(i;i<n;i) m*i;i--;int pm/i;string s;for(int j0;j<n;j) s.append(to_…

NCRE3 2-1 网络总体设计基本方法

这部分记忆的比较多 概览 设计网络建设总体目标确定网络系统方案设计原则网络系统总体设计设计网络拓扑结构进行网络设备选型网络系统安全设计 设计网络建设总体目标 这部分视频没说到 确定网络系统方案设计原则 这部分视频没说到 网络系统总体设计 核心层网络结构设计 …

“机器说人话”-AI 时代的物联网

万物互联的物联网愿景已经提了许多年了&#xff0c;但是实际效果并不理想&#xff0c;除了某些厂商自己的产品生态中的产品实现了互联之外&#xff0c;就连手机控制空调&#xff0c;电视机和调光灯都没有实现。感觉小米做的好一点&#xff0c;而华为的鸿蒙的全场景&#xff0c;…

以flask为后端的博客项目——星云小窝

以flask为后端的博客项目——星云小窝 文章目录 以flask为后端的博客项目——星云小窝前言一、星云小窝项目——项目介绍&#xff08;一&#xff09;二、星云小窝项目——项目启动&#xff08;二&#xff09;三、星云小窝项目——项目结构&#xff08;三&#xff09;四、谈论一…