流媒体学习之路(WebRTC)——GCC分析(3)
——
我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost
目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全配置,提供全面的可视化算法观察能力。
欢迎大家使用
——
文章目录
一、Ack模块(AcknowledgedBitrateEstimator)
1.1 模块简介
该模块会通过ack情况计算出对端接收数据的情况,通过经验值调整作为当前吞吐量的估计值用于计算下一次发送的码率。
该模块计算出来的吞吐量会作用到码率计算模块,是一个非常重要的模块。根据每一次feedback的数据量作为采样点,通过贝叶斯估计获得一个平滑的估计值。
贝叶斯估计补充:贝叶斯估计是统计学范畴里常用的参数估计方法,是基于先验采样。在经典的频率统计中,参数是固定的,样本统计量是随机变量。而在贝叶斯统计中,认为参数也是随机变量,服从某一概率分布的随机变量,贝叶斯统计的重点是研究参数的分布。
特点:首先要了解一定的先验,然后收集样本数据,根据样本数据的结果再进行调整,重新计算得到所谓的后验信息。
贝叶斯公式一般可以表示为:
上述公式中,B为先验数据、A为当前采样。
1.2 代码
void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector(
const std::vector<PacketResult>& packet_feedback_vector) {
RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),
packet_feedback_vector.end(),
PacketResult::ReceiveTimeOrder()));
// 根据feedback上层确认的seq记录的数据量,分别更新码率估计模块。
for (const auto& packet : packet_feedback_vector) {
if (alr_ended_time_ && packet.sent_packet.send_time > *alr_ended_time_) {
bitrate_estimator_->ExpectFastRateChange();
alr_ended_time_.reset();
}
DataSize acknowledged_estimate = packet.sent_packet.size;
acknowledged_estimate += packet.sent_packet.prior_unacked_data;
// 更新码率估计类
bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate,
in_alr_);
}
}
void BitrateEstimator::Update(Timestamp at_time, DataSize amount, bool in_alr) {
// noninitial_window_ms_ 存在码率估计的时候使用该参数,默认数值为:150ms;最小值:150ms;最大值:1000ms
int rate_window_ms = noninitial_window_ms_.Get();
// We use a larger window at the beginning to get a more stable sample that
// we can use to initialize the estimate.
// 初始值起始应该给比较大的计算时间等待初始化过程
if (bitrate_estimate_kbps_ < 0.f)
rate_window_ms = initial_window_ms_.Get();
bool is_small_sample = false;
// 更新数据
float bitrate_sample_kbps = UpdateWindow(at_time.ms(), amount.bytes(),
rate_window_ms, &is_small_sample);
if (bitrate_sample_kbps < 0.0f)
return;
if (bitrate_estimate_kbps_ < 0.0f) {
// This is the very first sample we get. Use it to initialize the estimate.
bitrate_estimate_kbps_ = bitrate_sample_kbps;
return;
}
// Optionally use higher uncertainty for very small samples to avoid dropping
// estimate and for samples obtained in ALR.
float scale = uncertainty_scale_;
// 检测是否处于小采样状态
if (is_small_sample && bitrate_sample_kbps < bitrate_estimate_kbps_) {
scale = small_sample_uncertainty_scale_;
} else if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) {
// Optionally use higher uncertainty for samples obtained during ALR.
scale = uncertainty_scale_in_alr_;
}
// Define the sample uncertainty as a function of how far away it is from the
// current estimate. With low values of uncertainty_symmetry_cap_ we add more
// uncertainty to increases than to decreases. For higher values we approach
// symmetry.
// 每次采样都会存在不确定的码率,因此每次都需要根据刻度计算码率。
float sample_uncertainty =
scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) /
(bitrate_estimate_kbps_ +
std::min(bitrate_sample_kbps,
uncertainty_symmetry_cap_.Get().kbps<float>()));
float sample_var = sample_uncertainty * sample_uncertainty;
// Update a bayesian estimate of the rate, weighting it lower if the sample
// uncertainty is large.
// The bitrate estimate uncertainty is increased with each update to model
// that the bitrate changes over time.
// 计算获得当前码率估计
float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;
bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ +
pred_bitrate_estimate_var * bitrate_sample_kbps) /
(sample_var + pred_bitrate_estimate_var);
bitrate_estimate_kbps_ =
std::max(bitrate_estimate_kbps_, estimate_floor_.Get().kbps<float>());
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
(sample_var + pred_bitrate_estimate_var);
BWE_TEST_LOGGING_PLOT(1, "acknowledged_bitrate", at_time.ms(),
bitrate_estimate_kbps_ * 1000);
}
float BitrateEstimator::UpdateWindow(int64_t now_ms,
int bytes,
int rate_window_ms,
bool* is_small_sample) {
RTC_DCHECK(is_small_sample != nullptr);
// Reset if time moves backwards.
if (now_ms < prev_time_ms_) {
prev_time_ms_ = -1;
sum_ = 0;
current_window_ms_ = 0;
}
if (prev_time_ms_ >= 0) {
current_window_ms_ += now_ms - prev_time_ms_;
// Reset if nothing has been received for more than a full window.
if (now_ms - prev_time_ms_ > rate_window_ms) {
sum_ = 0;
current_window_ms_ %= rate_window_ms;
}
}
prev_time_ms_ = now_ms;
float bitrate_sample = -1.0f;
// 确定当前是否进入了小采样
if (current_window_ms_ >= rate_window_ms) {
*is_small_sample = sum_ < small_sample_threshold_->bytes();
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
current_window_ms_ -= rate_window_ms;
sum_ = 0;
}
sum_ += bytes;
return bitrate_sample;
}
二、链路容量计算模块(LinkCapacityEstimator)
2.1 模块简介
链路容量预估模块对于网络剩余传输空间做预估,主要根据上述的ack模块计算出来的采样进行吞吐判断。
该模块对每个确认的数据包累加出对端接收的数据量。但是这些采样的数据是存在抖动的,上下抖动会导致整体码率不稳定,而且GCC算法本身表现出来就是发送码率。因此在使用链路容量模块时,webrtc选择使用其采样的最低码率(link capacity 顾明思议就是链路容量,在探测阶段没有探测到带宽瓶颈,就不会更新该字段)。
2.2 代码
LinkCapacityEstimator::LinkCapacityEstimator() {}
DataRate LinkCapacityEstimator::UpperBound() const {
if (estimate_kbps_.has_value())
return DataRate::KilobitsPerSec(estimate_kbps_.value() +
3 * deviation_estimate_kbps());
return DataRate::Infinity();
}
DataRate LinkCapacityEstimator::LowerBound() const {
if (estimate_kbps_.has_value())
return DataRate::KilobitsPerSec(
std::max(0.0, estimate_kbps_.value() - 3 * deviation_estimate_kbps()));
return DataRate::Zero();
}
void LinkCapacityEstimator::Reset() {
estimate_kbps_.reset();
}
void LinkCapacityEstimator::OnOveruseDetected(DataRate acknowledged_rate) {
Update(acknowledged_rate, 0.05);
}
void LinkCapacityEstimator::OnProbeRate(DataRate probe_rate) {
Update(probe_rate, 0.5);
}
void LinkCapacityEstimator::Update(DataRate capacity_sample, double alpha) {
double sample_kbps = capacity_sample.kbps();
if (!estimate_kbps_.has_value()) {
estimate_kbps_ = sample_kbps;
} else {
estimate_kbps_ = (1 - alpha) * estimate_kbps_.value() + alpha * sample_kbps;
}
// Estimate the variance of the link capacity estimate and normalize the
// variance with the link capacity estimate.
const double norm = std::max(estimate_kbps_.value(), 1.0);
double error_kbps = estimate_kbps_.value() - sample_kbps;
deviation_kbps_ =
(1 - alpha) * deviation_kbps_ + alpha * error_kbps * error_kbps / norm;
// 0.4 ~= 14 kbit/s at 500 kbit/s
// 2.5f ~= 35 kbit/s at 500 kbit/s
deviation_kbps_ = rtc::SafeClamp(deviation_kbps_, 0.4f, 2.5f);
}
bool LinkCapacityEstimator::has_estimate() const {
return estimate_kbps_.has_value();
}
DataRate LinkCapacityEstimator::estimate() const {
return DataRate::KilobitsPerSec(*estimate_kbps_);
}
double LinkCapacityEstimator::deviation_estimate_kbps() const {
// Calculate the max bit rate std dev given the normalized
// variance and the current throughput bitrate. The standard deviation will
// only be used if estimate_kbps_ has a value.
return sqrt(deviation_kbps_ * estimate_kbps_.value());
}
以上最关键的就是Updata函数,该函数中公式:
alpha 根据探测阶段、过量使用阶段分别设置不同的参数。
●使用带宽过量:0.05 —— 也就是使用历史参考值更多,因为此时已经到达了瓶颈上限;
●探测阶段:0.5 ——更多参考新的采样值。
采样完成后使用归一方差获得最终可以使用的链路容量值,这个方差大概为0.4 ~ 2.5之间,而0.4 ~ 2.5 在高斯分布之间可以包含99%概率分布数据。
三、应用受限判断器(AlrDetector)
3.1 简介
在此处再补充一个应用受限判断器的介绍,因为在带宽估计数据发送的过程中,难免会存在很多异常情况。例如:编码器输出不了更高的码率(性能影响、bug之类的)、带宽远大于 需要的码率等;
事实上在现代网络环境的传输过程中,必然会存在带宽远大编码器输出码率的情况。由此可知,在无探测的情况下我们无法准确知道整个网络的上限,因此我们需要做适当的适配。
3.2 头文件
Alr检测器中使用了IntervalBudget类,这个类在每次发送数据的时候调用并输入了时间戳,可以在最快的速度上反应出是否进入到alr状态。
struct AlrDetectorConfig {
// Sent traffic ratio as a function of network capacity used to determine
// application-limited region. ALR region start when bandwidth usage drops
// below kAlrStartUsageRatio and ends when it raises above
// kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment
// until BW adjustments of application limited region is fine tuned.
double bandwidth_usage_ratio = 0.65;
double start_budget_level_ratio = 0.80;
double stop_budget_level_ratio = 0.50;
std::unique_ptr<StructParametersParser> Parser();
};
// Application limited region detector is a class that utilizes signals of
// elapsed time and bytes sent to estimate whether network traffic is
// currently limited by the application's ability to generate traffic.
//
// AlrDetector provides a signal that can be utilized to adjust
// estimate bandwidth.
// Note: This class is not thread-safe.
class AlrDetector {
public:
AlrDetector(AlrDetectorConfig config, RtcEventLog* event_log);
explicit AlrDetector(const FieldTrialsView* key_value_config);
AlrDetector(const FieldTrialsView* key_value_config, RtcEventLog* event_log);
~AlrDetector();
void OnBytesSent(size_t bytes_sent, int64_t send_time_ms);
// Set current estimated bandwidth.
void SetEstimatedBitrate(int bitrate_bps);
// Returns time in milliseconds when the current application-limited region
// started or empty result if the sender is currently not application-limited.
absl::optional<int64_t> GetApplicationLimitedRegionStartTime() const;
private:
friend class GoogCcStatePrinter;
const AlrDetectorConfig conf_;
absl::optional<int64_t> last_send_time_ms_;
IntervalBudget alr_budget_;
absl::optional<int64_t> alr_started_time_ms_;
RtcEventLog* event_log_;
};
上述的配置中包含:start_budget_level_ratio 和 stop_budget_level_ratio两个计算参数。其中IntervalBudget::budget_ratio大于start_budget_level_ratio,表示剩余数据还有很多,发送码率低了,带宽没充分利用,Alr触发了,小于stop_budget_level_ratio时,alr_started_time_ms_重置,Alr停止。
3.3 cpp
namespace {
AlrDetectorConfig GetConfigFromTrials(const FieldTrialsView* key_value_config) {
RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled(*key_value_config));
absl::optional<AlrExperimentSettings> experiment_settings =
AlrExperimentSettings::CreateFromFieldTrial(
*key_value_config,
AlrExperimentSettings::kScreenshareProbingBweExperimentName);
if (!experiment_settings) {
experiment_settings = AlrExperimentSettings::CreateFromFieldTrial(
*key_value_config,
AlrExperimentSettings::kStrictPacingAndProbingExperimentName);
}
AlrDetectorConfig conf;
if (experiment_settings) {
conf.bandwidth_usage_ratio =
experiment_settings->alr_bandwidth_usage_percent / 100.0;
conf.start_budget_level_ratio =
experiment_settings->alr_start_budget_level_percent / 100.0;
conf.stop_budget_level_ratio =
experiment_settings->alr_stop_budget_level_percent / 100.0;
}
conf.Parser()->Parse(
key_value_config->Lookup("WebRTC-AlrDetectorParameters"));
return conf;
}
} // namespace
std::unique_ptr<StructParametersParser> AlrDetectorConfig::Parser() {
return StructParametersParser::Create( //
"bw_usage", &bandwidth_usage_ratio, //
"start", &start_budget_level_ratio, //
"stop", &stop_budget_level_ratio);
}
AlrDetector::AlrDetector(AlrDetectorConfig config, RtcEventLog* event_log)
: conf_(config), alr_budget_(0, true), event_log_(event_log) {}
AlrDetector::AlrDetector(const FieldTrialsView* key_value_config)
: AlrDetector(GetConfigFromTrials(key_value_config), nullptr) {}
AlrDetector::AlrDetector(const FieldTrialsView* key_value_config,
RtcEventLog* event_log)
: AlrDetector(GetConfigFromTrials(key_value_config), event_log) {}
AlrDetector::~AlrDetector() {}
void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {
if (!last_send_time_ms_.has_value()) {
last_send_time_ms_ = send_time_ms;
// Since the duration for sending the bytes is unknwon, return without
// updating alr state.
return;
}
int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;
last_send_time_ms_ = send_time_ms;
alr_budget_.UseBudget(bytes_sent);
alr_budget_.IncreaseBudget(delta_time_ms);
bool state_changed = false;
if (alr_budget_.budget_ratio() > conf_.start_budget_level_ratio &&
!alr_started_time_ms_) {
alr_started_time_ms_.emplace(rtc::TimeMillis());
state_changed = true;
} else if (alr_budget_.budget_ratio() < conf_.stop_budget_level_ratio &&
alr_started_time_ms_) {
state_changed = true;
alr_started_time_ms_.reset();
}
if (event_log_ && state_changed) {
event_log_->Log(
std::make_unique<RtcEventAlrState>(alr_started_time_ms_.has_value()));
}
}
void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
RTC_DCHECK(bitrate_bps);
int target_rate_kbps =
static_cast<double>(bitrate_bps) * conf_.bandwidth_usage_ratio / 1000;
alr_budget_.set_target_rate_kbps(target_rate_kbps);
}
absl::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
const {
return alr_started_time_ms_;
}
四、总结
本文解释了支持GCC做带宽计算的三个类。通过拥塞检测器之后我们需要使用上述的类对具体码率变化进行计算,得到目标码率应用到编码器。这样整体GCC从监测带宽变化到码率输出都已经完成,后续我们对GCC的码率数据输出的去处再做分析,希望大家可以继续关注。