WebRTC在视频编码过程中会进行QP检测,目的是让视频质量维持在可接受范围的前提下,调节整体视频表现,如分辨率、帧率。这里要注意的是,QP检测机制只是利用QP分析结果对分辨率、帧率进行调节,并不对编码QP做直接调整。说句题外话,通常编码器也不会对外提供QP设置接口,QP主要是由帧率、码率、复杂度等因素计算得到,这部分内容将会另写文章分享。
QP检测的主体代码在quality_scaler.cc的QualityScaler类中,后者作为observer注册到VideoStreamEncoder中,VideoStreamEncoder内完成了相关流程的控制。
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。
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();
}
// 传入回调参数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);
}
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;
}
// ...
}