WebRTC视频码率控制(二)—— QP检测

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;
  }
  // ...
}

你可能感兴趣的:(视频编解码,流媒体,webrtc)