| Index: webrtc/modules/video_coding/utility/quality_scaler.cc
|
| diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc
|
| index 0f63edca7d126d520fff5fd28eb39a8adf8ca66c..ba1d9789c783230b5cf48fad2699a723669ae411 100644
|
| --- a/webrtc/modules/video_coding/utility/quality_scaler.cc
|
| +++ b/webrtc/modules/video_coding/utility/quality_scaler.cc
|
| @@ -13,8 +13,11 @@
|
| #include <math.h>
|
|
|
| #include <algorithm>
|
| +#include <memory>
|
|
|
| #include "webrtc/base/checks.h"
|
| +#include "webrtc/base/logging.h"
|
| +#include "webrtc/base/task_queue.h"
|
|
|
| // TODO(kthelgason): Some versions of Android have issues with log2.
|
| // See https://code.google.com/p/android/issues/detail?id=212634 for details
|
| @@ -26,21 +29,9 @@ namespace webrtc {
|
|
|
| namespace {
|
| // Threshold constant used until first downscale (to permit fast rampup).
|
| -static const int kMeasureSecondsFastUpscale = 2;
|
| -static const int kMeasureSecondsUpscale = 5;
|
| -static const int kMeasureSecondsDownscale = 5;
|
| +static const int kMeasureMs = 2000;
|
| +static const float kSamplePeriodScaleFactor = 2.5;
|
| static const int kFramedropPercentThreshold = 60;
|
| -// Min width/height to downscale to, set to not go below QVGA, but with some
|
| -// margin to permit "almost-QVGA" resolutions, such as QCIF.
|
| -static const int kMinDownscaleDimension = 140;
|
| -// Initial resolutions corresponding to a bitrate. Aa bit above their actual
|
| -// values to permit near-VGA and near-QVGA resolutions to use the same
|
| -// mechanism.
|
| -static const int kVgaBitrateThresholdKbps = 500;
|
| -static const int kVgaNumPixels = 700 * 500; // 640x480
|
| -static const int kQvgaBitrateThresholdKbps = 250;
|
| -static const int kQvgaNumPixels = 400 * 300; // 320x240
|
| -
|
| // QP scaling threshold defaults:
|
| static const int kLowH264QpThreshold = 24;
|
| static const int kHighH264QpThreshold = 37;
|
| @@ -48,20 +39,13 @@ static const int kHighH264QpThreshold = 37;
|
| // bitstream range of [0, 127] and not the user-level range of [0,63].
|
| static const int kLowVp8QpThreshold = 29;
|
| static const int kHighVp8QpThreshold = 95;
|
| -} // namespace
|
| +const ScalingObserverInterface::ScaleReason scale_reason_ =
|
| + ScalingObserverInterface::ScaleReason::kQuality;
|
|
|
| -// Default values. Should immediately get set to something more sensible.
|
| -QualityScaler::QualityScaler()
|
| - : average_qp_(kMeasureSecondsUpscale * 30),
|
| - framedrop_percent_(kMeasureSecondsUpscale * 30),
|
| - low_qp_threshold_(-1) {}
|
| -
|
| -void QualityScaler::Init(VideoCodecType codec_type,
|
| - int initial_bitrate_kbps,
|
| - int width,
|
| - int height,
|
| - int fps) {
|
| - int low = -1, high = -1;
|
| +static VideoEncoder::QpThresholds CodecTypeToDefaultThresholds(
|
| + VideoCodecType codec_type) {
|
| + int low = -1;
|
| + int high = -1;
|
| switch (codec_type) {
|
| case kVideoCodecH264:
|
| low = kLowH264QpThreshold;
|
| @@ -74,138 +58,132 @@ void QualityScaler::Init(VideoCodecType codec_type,
|
| default:
|
| RTC_NOTREACHED() << "Invalid codec type for QualityScaler.";
|
| }
|
| - Init(low, high, initial_bitrate_kbps, width, height, fps);
|
| + return VideoEncoder::QpThresholds(low, high);
|
| }
|
| +} // namespace
|
|
|
| -void QualityScaler::Init(int low_qp_threshold,
|
| - int high_qp_threshold,
|
| - int initial_bitrate_kbps,
|
| - int width,
|
| - int height,
|
| - int fps) {
|
| - ClearSamples();
|
| - low_qp_threshold_ = low_qp_threshold;
|
| - high_qp_threshold_ = high_qp_threshold;
|
| - downscale_shift_ = 0;
|
| - fast_rampup_ = true;
|
| -
|
| - const int init_width = width;
|
| - const int init_height = height;
|
| - if (initial_bitrate_kbps > 0) {
|
| - int init_num_pixels = width * height;
|
| - if (initial_bitrate_kbps < kVgaBitrateThresholdKbps)
|
| - init_num_pixels = kVgaNumPixels;
|
| - if (initial_bitrate_kbps < kQvgaBitrateThresholdKbps)
|
| - init_num_pixels = kQvgaNumPixels;
|
| - while (width * height > init_num_pixels) {
|
| - ++downscale_shift_;
|
| - width /= 2;
|
| - height /= 2;
|
| - }
|
| +class QualityScaler::CheckQPTask : public rtc::QueuedTask {
|
| + public:
|
| + explicit CheckQPTask(QualityScaler* scaler) : scaler_(scaler) {
|
| + LOG(LS_INFO) << "Created CheckQPTask. Scheduling on queue...";
|
| + rtc::TaskQueue::Current()->PostDelayedTask(
|
| + std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
|
| }
|
| - UpdateTargetResolution(init_width, init_height);
|
| - ReportFramerate(fps);
|
| + void Stop() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + LOG(LS_INFO) << "Stopping QP Check task.";
|
| + stop_ = true;
|
| + }
|
| +
|
| + private:
|
| + bool Run() override {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + if (stop_)
|
| + return true; // TaskQueue will free this task.
|
| + scaler_->CheckQP();
|
| + rtc::TaskQueue::Current()->PostDelayedTask(
|
| + std::unique_ptr<rtc::QueuedTask>(this), scaler_->GetSamplingPeriodMs());
|
| + return false; // Retain the task in order to reuse it.
|
| + }
|
| +
|
| + QualityScaler* const scaler_;
|
| + bool stop_ = false;
|
| + rtc::SequencedTaskChecker task_checker_;
|
| +};
|
| +
|
| +QualityScaler::QualityScaler(ScalingObserverInterface* observer,
|
| + VideoCodecType codec_type)
|
| + : QualityScaler(observer, CodecTypeToDefaultThresholds(codec_type)) {}
|
| +
|
| +QualityScaler::QualityScaler(ScalingObserverInterface* observer,
|
| + VideoEncoder::QpThresholds thresholds)
|
| + : QualityScaler(observer, thresholds, kMeasureMs) {}
|
| +
|
| +// Protected ctor, should not be called directly.
|
| +QualityScaler::QualityScaler(ScalingObserverInterface* observer,
|
| + VideoEncoder::QpThresholds thresholds,
|
| + int64_t sampling_period)
|
| + : check_qp_task_(nullptr),
|
| + observer_(observer),
|
| + sampling_period_ms_(sampling_period),
|
| + fast_rampup_(true),
|
| + // Arbitrarily choose size based on 30 fps for 5 seconds.
|
| + average_qp_(5 * 30),
|
| + framedrop_percent_(5 * 30),
|
| + thresholds_(thresholds) {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + RTC_DCHECK(observer_ != nullptr);
|
| + check_qp_task_ = new CheckQPTask(this);
|
| }
|
|
|
| -// Report framerate(fps) to estimate # of samples.
|
| -void QualityScaler::ReportFramerate(int framerate) {
|
| - // Use a faster window for upscaling initially.
|
| - // This enables faster initial rampups without risking strong up-down
|
| - // behavior later.
|
| - num_samples_upscale_ = framerate * (fast_rampup_ ? kMeasureSecondsFastUpscale
|
| - : kMeasureSecondsUpscale);
|
| - num_samples_downscale_ = framerate * kMeasureSecondsDownscale;
|
| +QualityScaler::~QualityScaler() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + check_qp_task_->Stop();
|
| }
|
|
|
| -void QualityScaler::ReportQP(int qp) {
|
| - framedrop_percent_.AddSample(0);
|
| - average_qp_.AddSample(qp);
|
| +int64_t QualityScaler::GetSamplingPeriodMs() const {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + return fast_rampup_ ? sampling_period_ms_
|
| + : (sampling_period_ms_ * kSamplePeriodScaleFactor);
|
| }
|
|
|
| void QualityScaler::ReportDroppedFrame() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| framedrop_percent_.AddSample(100);
|
| }
|
|
|
| -void QualityScaler::OnEncodeFrame(int width, int height) {
|
| - // Should be set through InitEncode -> Should be set by now.
|
| - RTC_DCHECK_GE(low_qp_threshold_, 0);
|
| - if (target_res_.width != width || target_res_.height != height) {
|
| - UpdateTargetResolution(width, height);
|
| - }
|
| +void QualityScaler::ReportQP(int qp) {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + framedrop_percent_.AddSample(0);
|
| + average_qp_.AddSample(qp);
|
| +}
|
|
|
| +void QualityScaler::CheckQP() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + // Should be set through InitEncode -> Should be set by now.
|
| + RTC_DCHECK_GE(thresholds_.low, 0);
|
| + LOG(LS_INFO) << "Checking if average QP exceeds threshold";
|
| // Check if we should scale down due to high frame drop.
|
| - const auto drop_rate = framedrop_percent_.GetAverage(num_samples_downscale_);
|
| + const rtc::Optional<int> drop_rate = framedrop_percent_.GetAverage();
|
| if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
|
| - ScaleDown();
|
| + ReportQPHigh();
|
| return;
|
| }
|
|
|
| // Check if we should scale up or down based on QP.
|
| - const auto avg_qp_down = average_qp_.GetAverage(num_samples_downscale_);
|
| - if (avg_qp_down && *avg_qp_down > high_qp_threshold_) {
|
| - ScaleDown();
|
| + const rtc::Optional<int> avg_qp = average_qp_.GetAverage();
|
| + if (avg_qp && *avg_qp > thresholds_.high) {
|
| + ReportQPHigh();
|
| return;
|
| }
|
| - const auto avg_qp_up = average_qp_.GetAverage(num_samples_upscale_);
|
| - if (avg_qp_up && *avg_qp_up <= low_qp_threshold_) {
|
| + if (avg_qp && *avg_qp <= thresholds_.low) {
|
| // QP has been low. We want to try a higher resolution.
|
| - ScaleUp();
|
| + ReportQPLow();
|
| return;
|
| }
|
| }
|
|
|
| -void QualityScaler::ScaleUp() {
|
| - downscale_shift_ = std::max(0, downscale_shift_ - 1);
|
| +void QualityScaler::ReportQPLow() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + LOG(LS_INFO) << "QP has been low, asking for higher resolution.";
|
| ClearSamples();
|
| + observer_->ScaleUp(scale_reason_);
|
| }
|
|
|
| -void QualityScaler::ScaleDown() {
|
| - downscale_shift_ = std::min(maximum_shift_, downscale_shift_ + 1);
|
| +void QualityScaler::ReportQPHigh() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| + LOG(LS_INFO) << "QP has been high , asking for lower resolution.";
|
| ClearSamples();
|
| + observer_->ScaleDown(scale_reason_);
|
| // If we've scaled down, wait longer before scaling up again.
|
| if (fast_rampup_) {
|
| fast_rampup_ = false;
|
| - num_samples_upscale_ = (num_samples_upscale_ / kMeasureSecondsFastUpscale) *
|
| - kMeasureSecondsUpscale;
|
| }
|
| }
|
|
|
| -QualityScaler::Resolution QualityScaler::GetScaledResolution() const {
|
| - const int frame_width = target_res_.width >> downscale_shift_;
|
| - const int frame_height = target_res_.height >> downscale_shift_;
|
| - return Resolution{frame_width, frame_height};
|
| -}
|
| -
|
| -rtc::scoped_refptr<VideoFrameBuffer> QualityScaler::GetScaledBuffer(
|
| - const rtc::scoped_refptr<VideoFrameBuffer>& frame) {
|
| - Resolution res = GetScaledResolution();
|
| - const int src_width = frame->width();
|
| - const int src_height = frame->height();
|
| -
|
| - if (res.width == src_width && res.height == src_height)
|
| - return frame;
|
| - rtc::scoped_refptr<I420Buffer> scaled_buffer =
|
| - pool_.CreateBuffer(res.width, res.height);
|
| -
|
| - scaled_buffer->ScaleFrom(*frame);
|
| -
|
| - return scaled_buffer;
|
| -}
|
| -
|
| -void QualityScaler::UpdateTargetResolution(int width, int height) {
|
| - if (width < kMinDownscaleDimension || height < kMinDownscaleDimension) {
|
| - maximum_shift_ = 0;
|
| - } else {
|
| - maximum_shift_ = static_cast<int>(
|
| - log2(std::min(width, height) / kMinDownscaleDimension));
|
| - }
|
| - target_res_ = Resolution{width, height};
|
| -}
|
| -
|
| void QualityScaler::ClearSamples() {
|
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_);
|
| framedrop_percent_.Reset();
|
| average_qp_.Reset();
|
| }
|
| -
|
| -
|
| } // namespace webrtc
|
|
|