Chromium Code Reviews| 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 59bc60596609f94fa56fe2eca88a510b431ffa02..3fefc4e67905e3d3cf5a7cba7cdaf0adc5d46a86 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,12 @@ 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 auto scale_reason_ = ScalingObserverInterface::ScaleReason::kQuality; |
|
magjed_webrtc
2016/11/10 13:07:00
Not allowed to use auto for non-local variables
kthelgason
2016/11/10 15:38:27
Done.
|
| -// 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 QualityScaler::QPThresholds CodecTypeToDefaultThresholds( |
| + VideoCodecType codec_type) { |
| + int low = -1; |
| + int high = -1; |
| switch (codec_type) { |
| case kVideoCodecH264: |
| low = kLowH264QpThreshold; |
| @@ -74,138 +57,137 @@ void QualityScaler::Init(VideoCodecType codec_type, |
| default: |
| RTC_NOTREACHED() << "Invalid codec type for QualityScaler."; |
| } |
| - Init(low, high, initial_bitrate_kbps, width, height, fps); |
| + return QualityScaler::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..."; |
| + const auto q = rtc::TaskQueue::Current(); |
| + q->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)) {} |
| + |
| +// Protected ctor, for testing purposes only. |
| +QualityScaler::QualityScaler(ScalingObserverInterface* observer, |
| + QPThresholds thresholds, |
| + int64_t sampling_period) |
| + : QualityScaler(observer, thresholds) { |
| + check_qp_task_->Stop(); |
|
magjed_webrtc
2016/11/10 13:07:00
I would like to avoid the 'check_qp_task_->Stop()'
kthelgason
2016/11/10 15:38:27
Thanks, good point. Done.
|
| + sampling_period_ms_ = sampling_period; |
| + 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(ScalingObserverInterface* observer, |
| + QPThresholds thresholds) |
| + : check_qp_task_(nullptr), |
| + observer_(observer), |
| + sampling_period_ms_(kMeasureMs), |
| + 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); |
| } |
| -void QualityScaler::ReportQP(int qp) { |
| - framedrop_percent_.AddSample(0); |
| - average_qp_.AddSample(qp); |
| +QualityScaler::~QualityScaler() { |
| + check_qp_task_->Stop(); |
| + check_qp_task_ = nullptr; |
|
magjed_webrtc
2016/11/10 13:07:00
no need to set it to nullptr in the dtor.
kthelgason
2016/11/10 15:38:27
Done.
|
| +} |
| + |
| +int64_t QualityScaler::GetSamplingPeriodMs() const { |
| + RTC_DCHECK_CALLED_SEQUENTIALLY(&task_checker_); |
| + return sampling_period_ms_; |
|
magjed_webrtc
2016/11/10 13:07:00
Can you make |sampling_period_ms_| const and retur
kthelgason
2016/11/10 15:38:27
Done.
|
| } |
| -void QualityScaler::ReportDroppedFrame() { |
| +void QualityScaler::ReportDroppedFrame() const { |
| + 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) const { |
| + 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 auto drop_rate = framedrop_percent_.GetAverage(); |
|
magjed_webrtc
2016/11/10 13:07:00
Don't use auto here.
kthelgason
2016/11/10 15:38:27
Done.
|
| 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 auto 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; |
| + sampling_period_ms_ *= kSamplePeriodScaleFactor; |
| } |
| } |
| -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 |