WebRTC在视频编码过程中会进行QP检测,目的是让视频质量维持在可接受范围的前提下,调节整体视频表现,如分辨率、帧率。这里要注意的是,QP检测机制只是利用QP分析结果对分辨率、帧率进行调节,并不对编码QP做直接调整。说句题外话,通常编码器也不会对外提供QP设置接口,QP主要是由帧率、码率、复杂度等因素计算得到,这部分内容将会另写文章分享。
QP检测的主体代码在quality_scaler.cc的QualityScaler类中,后者作为observer注册到VideoStreamEncoder中,VideoStreamEncoder内完成了相关流程的控制。
1 初始化与配置
与CPU使用度检测类似,初始化过程发生在编码器重新创建的时候(流初始化,或者编码格式变化):
void VideoStreamEncoder::ReconfigureEncoder() {
// ...
ConfigureQualityScaler(info);
}
void VideoStreamEncoder::ConfigureQualityScaler(
const VideoEncoder::EncoderInfo& encoder_info) {
RTC_DCHECK_RUN_ON(&encoder_queue_);
const auto scaling_settings = encoder_info.scaling_settings;
// 允许进行QP检测的主要条件是,degradation_preference允许调整分辨率
// 换句话说,摄像头流允许,而共享屏幕不允许。
const bool quality_scaling_allowed =
IsResolutionScalingEnabled(degradation_preference_) &&
scaling_settings.thresholds;
if (quality_scaling_allowed) {
if (quality_scaler_ == nullptr) {
//...
AdaptationObserverInterface* observer = this;
quality_scaler_ = std::make_unique<QualityScaler>(
&encoder_queue_, observer,
experimental_thresholds ? *experimental_thresholds
: *(scaling_settings.thresholds));
has_seen_first_significant_bwe_change_ = false;
initial_framedrop_ = 0;
}
} else {
quality_scaler_.reset(nullptr);
initial_framedrop_ = kMaxInitialFramedrop;
}
// ...
}
经过初始化后,QP阈值的下限、上限默认分别为:24、37。
2 检测的开启与终止
在QualityScaler构造时开启检测,在所创建的线程中默认每2秒检测一次:
QualityScaler::QualityScaler(rtc::TaskQueue* task_queue,
AdaptationObserverInterface* observer,
VideoEncoder::QpThresholds thresholds,
int64_t sampling_period_ms) {
// ...
check_qp_task_ = RepeatingTaskHandle::DelayedStart(
task_queue->Get(), TimeDelta::ms(GetSamplingPeriodMs()), [this]() {
CheckQp();
return TimeDelta::ms(GetSamplingPeriodMs());
});
// ...
}
与此相应,在QualityScaler析构时终止检测:
QualityScaler::~QualityScaler() {
RTC_DCHECK_RUN_ON(&task_checker_);
check_qp_task_.Stop();
}
3 样本数据采集
每帧编码结束后,都会记录QP。这里的QP指的是帧级QP,准确地说是指last_slice_qp,当为单Slice编码时,二者相同。
// 传入回调参数EncodedImage中携带的QP
VideoStreamEncoder::RunPostEncode(EncodedImage encoded_image,
int64_t time_sent_us, ...) {
// ...
if (quality_scaler_ && encoded_image.qp_ >= 0)
quality_scaler_->ReportQp(encoded_image.qp_, time_sent_us);
// ...
}
void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
// ...
average_qp_.AddSample(qp);
if (qp_smoother_high_)
qp_smoother_high_->Add(qp, time_sent_us);
if (qp_smoother_low_)
qp_smoother_low_->Add(qp, time_sent_us);
}
4 计算过程
由上可知,QP检测到后,分别被添加到三个数据集 average_qp_、qp_smoother_high_、qp_smoother_low_ 中,在样本数据添加后立即完成滤波计算。其中average_qp_使用滑窗滤波rtc::MovingAverage,qp_smoother_high_和qp_smoother_low_ 使用指数滤波rtc::ExpFilter,均起到平滑数据的作用。
5 反馈调节
在定时检测中完成反馈:
void QualityScaler::CheckQp() {
// ...
// Check if we should scale up or down based on QP.
const absl::optional<int> avg_qp_high =
qp_smoother_high_ ? qp_smoother_high_->GetAvg()
: average_qp_.GetAverageRoundedDown();
const absl::optional<int> avg_qp_low =
qp_smoother_low_ ? qp_smoother_low_->GetAvg()
: average_qp_.GetAverageRoundedDown();
if (avg_qp_high && avg_qp_low) {
if (*avg_qp_high > thresholds_.high) {
ReportQpHigh();
return;
}
if (*avg_qp_low <= thresholds_.low) {
// QP has been low. We want to try a higher resolution.
ReportQpLow();
return;
}
}
}
// 当QP过低时,通过AdaptUp对编码进行升级
void QualityScaler::ReportQpLow() {
// ...
observer_->AdaptUp(AdaptationObserverInterface::AdaptReason::kQuality);
// ...
}
// 当QP过高时,通过AdaptDown对编码进行降级
void QualityScaler::ReportQpHigh() {
// ...
if (observer_->AdaptDown(
AdaptationObserverInterface::AdaptReason::kQuality)) {
ClearSamples();
} else {
adapt_failed_ = true;
}
// ...
}