blob: 7ef7c575ba4b699210fca7c4e40f0b557a54d440 [file] [log] [blame]
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
#include <math.h>
#include <algorithm>
// TODO(kthelgason): Some versions of Android have issues with log2.
// See https://code.google.com/p/android/issues/detail?id=212634 for details
#if defined(WEBRTC_ANDROID)
#define log2(x) (log(x) / log(2))
#endif
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 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
} // namespace
// QP thresholds are chosen to be high enough to be hit in practice when quality
// is good, but also low enough to not cause a flip-flop behavior (e.g. going up
// in resolution shouldn't give so bad quality that we should go back down).
const int QualityScaler::kLowVp8QpThreshold = 29;
const int QualityScaler::kBadVp8QpThreshold = 95;
#if defined(WEBRTC_IOS)
const int QualityScaler::kLowH264QpThreshold = 32;
const int QualityScaler::kBadH264QpThreshold = 42;
#else
const int QualityScaler::kLowH264QpThreshold = 24;
const int QualityScaler::kBadH264QpThreshold = 37;
#endif
// 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(int low_qp_threshold,
int high_qp_threshold,
int initial_bitrate_kbps,
int width,
int height,
int fps) {
low_qp_threshold_ = low_qp_threshold;
high_qp_threshold_ = high_qp_threshold;
downscale_shift_ = 0;
fast_rampup_ = true;
ClearSamples();
ReportFramerate(fps);
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;
}
}
UpdateTargetResolution(init_width, init_height);
ReportFramerate(fps);
}
// 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;
average_qp_ =
MovingAverage(std::max(num_samples_upscale_, num_samples_downscale_));
framedrop_percent_ =
MovingAverage(std::max(num_samples_upscale_, num_samples_downscale_));
}
void QualityScaler::ReportQP(int qp) {
framedrop_percent_.AddSample(0);
average_qp_.AddSample(qp);
}
void QualityScaler::ReportDroppedFrame() {
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);
}
// Check if we should scale down due to high frame drop.
const auto drop_rate = framedrop_percent_.GetAverage(num_samples_downscale_);
if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
ScaleDown();
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();
return;
}
const auto avg_qp_up = average_qp_.GetAverage(num_samples_upscale_);
if (avg_qp_up && *avg_qp_up <= low_qp_threshold_) {
// QP has been low. We want to try a higher resolution.
ScaleUp();
return;
}
}
void QualityScaler::ScaleUp() {
downscale_shift_ = std::max(0, downscale_shift_ - 1);
ClearSamples();
}
void QualityScaler::ScaleDown() {
downscale_shift_ = std::min(maximum_shift_, downscale_shift_ + 1);
ClearSamples();
// 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() {
framedrop_percent_.Reset();
average_qp_.Reset();
}
} // namespace webrtc