blob: 0c125f02b36b8cc2a3eaac39dbb1c26072551071 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/vaapi/vp9_encoder.h"
#include <algorithm>
#include "base/bits.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/vp9_rate_control.h"
#include "third_party/libvpx/source/libvpx/vp9/ratectrl_rtc.h"
namespace media {
namespace {
// Keyframe period.
constexpr size_t kKFPeriod = 3000;
// Arbitrarily chosen bitrate window size for rate control, in ms.
constexpr int kCPBWindowSizeMs = 500;
// Quantization parameter. They are vp9 ac/dc indices and their ranges are
// 0-255. Based on WebRTC's defaults.
constexpr int kMinQP = 4;
// TODO(crbug.com/1060775): Relax this max quantization parameter upper bound
// so that our encoder and bitrate controller can select a higher value in the
// case a requested bitrate is small.
constexpr int kMaxQP = 112;
// This stands for 31 as a real ac value (see rfc 8.6.1 table
// ac_qlookup[3][256]). Note: This needs to be revisited once we have 10&12 bit
// encoder support.
constexpr int kDefaultQP = 24;
// filter level may affect on quality at lower bitrates; for now,
// we set a constant value (== 10) which is what other VA-API
// implementations like libyami and gstreamer-vaapi are using.
constexpr uint8_t kDefaultLfLevel = 10;
// Convert Qindex, whose range is 0-255, to the quantizer parameter used in
// libvpx vp9 rate control, whose range is 0-63.
// Cited from //third_party/libvpx/source/libvpx/vp9/encoder/vp9_quantize.cc.
int QindexToQuantizer(int q_index) {
constexpr int kQuantizerToQindex[] = {
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48,
52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100,
104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152,
156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204,
208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 249, 255,
};
for (size_t q = 0; q < base::size(kQuantizerToQindex); ++q) {
if (kQuantizerToQindex[q] >= q_index)
return q;
}
return base::size(kQuantizerToQindex) - 1;
}
// The return value is expressed as a percentage of the average. For example,
// to allocate no more than 4.5 frames worth of bitrate to a keyframe, the
// return value is 450.
uint32_t MaxSizeOfKeyframeAsPercentage(uint32_t optimal_buffer_size,
uint32_t max_framerate) {
// Set max to the optimal buffer level (normalized by target BR),
// and scaled by a scale_par.
// Max target size = scale_par * optimal_buffer_size * targetBR[Kbps].
// This value is presented in percentage of perFrameBw:
// perFrameBw = targetBR[Kbps] * 1000 / framerate.
// The target in % is as follows:
const double target_size_byte_per_frame = optimal_buffer_size * 0.5;
const uint32_t target_size_kbyte =
target_size_byte_per_frame * max_framerate / 1000;
const uint32_t target_size_kbyte_as_percent = target_size_kbyte * 100;
// Don't go below 3 times the per frame bandwidth.
constexpr uint32_t kMinIntraSizePercentage = 300u;
return std::max(kMinIntraSizePercentage, target_size_kbyte_as_percent);
}
libvpx::VP9RateControlRtcConfig CreateRCConfig(
const gfx::Size& encode_size,
const VP9Encoder::EncodeParams& encode_params) {
libvpx::VP9RateControlRtcConfig rc_cfg{};
rc_cfg.width = encode_size.width();
rc_cfg.height = encode_size.height();
rc_cfg.max_quantizer =
QindexToQuantizer(encode_params.scaling_settings.max_qp);
rc_cfg.min_quantizer =
QindexToQuantizer(encode_params.scaling_settings.min_qp);
// libvpx::VP9RateControlRtcConfig is kbps.
rc_cfg.target_bandwidth =
encode_params.bitrate_allocation.GetSumBps() / 1000.0;
// These default values come from
// //third_party/webrtc/modules/video_coding/codecs/vp9/vp9_impl.cc.
rc_cfg.buf_initial_sz = 500;
rc_cfg.buf_optimal_sz = 600;
rc_cfg.buf_sz = 1000;
rc_cfg.undershoot_pct = 50;
rc_cfg.overshoot_pct = 50;
rc_cfg.max_intra_bitrate_pct = MaxSizeOfKeyframeAsPercentage(
rc_cfg.buf_optimal_sz, encode_params.framerate);
rc_cfg.framerate = encode_params.framerate;
// Spatial layer variables.
rc_cfg.ss_number_layers = 1;
rc_cfg.max_quantizers[0] = rc_cfg.max_quantizer;
rc_cfg.min_quantizers[0] = rc_cfg.min_quantizer;
// TODO(crbug.com/1030199): Fill multiple temporal layers variables.
// Temporal layer variables.
rc_cfg.ts_number_layers = 1;
rc_cfg.scaling_factor_num[0] = 1;
rc_cfg.scaling_factor_den[0] = 1;
rc_cfg.layer_target_bitrate[0] = rc_cfg.target_bandwidth;
rc_cfg.ts_rate_decimator[0] = 1;
return rc_cfg;
}
} // namespace
VP9Encoder::EncodeParams::EncodeParams()
: kf_period_frames(kKFPeriod),
framerate(0),
cpb_window_size_ms(kCPBWindowSizeMs),
cpb_size_bits(0),
initial_qp(kDefaultQP),
scaling_settings(kMinQP, kMaxQP),
error_resilient_mode(false) {}
void VP9Encoder::set_rate_ctrl_for_testing(
std::unique_ptr<VP9RateControl> rate_ctrl) {
rate_ctrl_ = std::move(rate_ctrl);
}
void VP9Encoder::Reset() {
current_params_ = EncodeParams();
reference_frames_.Clear();
frame_num_ = 0;
InitializeFrameHeader();
}
VP9Encoder::VP9Encoder(std::unique_ptr<Accelerator> accelerator)
: accelerator_(std::move(accelerator)) {}
VP9Encoder::~VP9Encoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool VP9Encoder::Initialize(const VideoEncodeAccelerator::Config& config,
const AcceleratedVideoEncoder::Config& ave_config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (VideoCodecProfileToVideoCodec(config.output_profile) != kCodecVP9) {
DVLOGF(1) << "Invalid profile: " << GetProfileName(config.output_profile);
return false;
}
if (config.input_visible_size.IsEmpty()) {
DVLOGF(1) << "Input visible size could not be empty";
return false;
}
accelerator_->set_bitrate_control(ave_config.bitrate_control);
visible_size_ = config.input_visible_size;
coded_size_ = gfx::Size(base::bits::Align(visible_size_.width(), 16),
base::bits::Align(visible_size_.height(), 16));
Reset();
if (ave_config.bitrate_control ==
BitrateControl::kConstantQuantizationParameter) {
// |rate_ctrl_| might be injected for tests.
if (!rate_ctrl_) {
rate_ctrl_ = VP9RateControl::Create(
CreateRCConfig(visible_size_, current_params_));
}
if (!rate_ctrl_)
return false;
} else {
DCHECK(!rate_ctrl_) << "|rate_ctrl_| should only be configured when in "
"kConstantQuantizationParameter";
}
VideoBitrateAllocation initial_bitrate_allocation;
initial_bitrate_allocation.SetBitrate(0, 0, config.initial_bitrate);
return UpdateRates(initial_bitrate_allocation,
config.initial_framerate.value_or(
VideoEncodeAccelerator::kDefaultFramerate));
}
gfx::Size VP9Encoder::GetCodedSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!coded_size_.IsEmpty());
return coded_size_;
}
size_t VP9Encoder::GetMaxNumOfRefFrames() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return kVp9NumRefFrames;
}
ScalingSettings VP9Encoder::GetScalingSettings() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return current_params_.scaling_settings;
}
bool VP9Encoder::PrepareEncodeJob(EncodeJob* encode_job) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (encode_job->IsKeyframeRequested())
frame_num_ = 0;
if (frame_num_ == 0)
encode_job->ProduceKeyframe();
frame_num_++;
frame_num_ %= current_params_.kf_period_frames;
scoped_refptr<VP9Picture> picture = accelerator_->GetPicture(encode_job);
DCHECK(picture);
const bool keyframe = encode_job->IsKeyframeRequested();
UpdateFrameHeader(keyframe);
*picture->frame_hdr = current_frame_hdr_;
// Use last, golden and altref for references.
const std::array<bool, kVp9NumRefsPerFrame> ref_frames_used = {
!keyframe, !keyframe, !keyframe};
if (!accelerator_->SubmitFrameParameters(encode_job, current_params_, picture,
reference_frames_,
ref_frames_used)) {
LOG(ERROR) << "Failed submitting frame parameters";
return false;
}
UpdateReferenceFrames(picture);
return true;
}
void VP9Encoder::BitrateControlUpdate(uint64_t encoded_chunk_size_bytes) {
if (accelerator_->bitrate_control() !=
BitrateControl::kConstantQuantizationParameter ||
!rate_ctrl_) {
DLOG(ERROR) << __func__ << "() is called when no bitrate controller exists";
return;
}
DVLOGF(4) << "|encoded_chunk_size_bytes|=" << encoded_chunk_size_bytes;
rate_ctrl_->PostEncodeUpdate(encoded_chunk_size_bytes);
}
bool VP9Encoder::UpdateRates(const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (bitrate_allocation.GetSumBps() == 0 || framerate == 0)
return false;
if (current_params_.bitrate_allocation == bitrate_allocation &&
current_params_.framerate == framerate) {
return true;
}
VLOGF(2) << "New bitrate: " << bitrate_allocation.GetSumBps()
<< ", New framerate: " << framerate;
current_params_.bitrate_allocation = bitrate_allocation;
current_params_.framerate = framerate;
current_params_.cpb_size_bits =
current_params_.bitrate_allocation.GetSumBps() *
current_params_.cpb_window_size_ms / 1000;
if (!rate_ctrl_)
return true;
rate_ctrl_->UpdateRateControl(CreateRCConfig(visible_size_, current_params_));
return true;
}
void VP9Encoder::InitializeFrameHeader() {
current_frame_hdr_ = {};
DCHECK(!visible_size_.IsEmpty());
current_frame_hdr_.frame_width = visible_size_.width();
current_frame_hdr_.frame_height = visible_size_.height();
current_frame_hdr_.render_width = visible_size_.width();
current_frame_hdr_.render_height = visible_size_.height();
current_frame_hdr_.quant_params.base_q_idx = kDefaultQP;
current_frame_hdr_.loop_filter.level = kDefaultLfLevel;
current_frame_hdr_.show_frame = true;
}
void VP9Encoder::UpdateFrameHeader(bool keyframe) {
if (keyframe) {
current_frame_hdr_.frame_type = Vp9FrameHeader::KEYFRAME;
current_frame_hdr_.refresh_frame_flags = 0xff;
ref_frame_index_ = 0;
} else {
current_frame_hdr_.frame_type = Vp9FrameHeader::INTERFRAME;
current_frame_hdr_.ref_frame_idx[0] = ref_frame_index_;
current_frame_hdr_.ref_frame_idx[1] =
(ref_frame_index_ - 1) & (kVp9NumRefFrames - 1);
current_frame_hdr_.ref_frame_idx[2] =
(ref_frame_index_ - 2) & (kVp9NumRefFrames - 1);
ref_frame_index_ = (ref_frame_index_ + 1) % kVp9NumRefFrames;
current_frame_hdr_.refresh_frame_flags = 1 << ref_frame_index_;
}
if (!rate_ctrl_)
return;
libvpx::VP9FrameParamsQpRTC frame_params{};
frame_params.frame_type =
keyframe ? FRAME_TYPE::KEY_FRAME : FRAME_TYPE::INTER_FRAME;
rate_ctrl_->ComputeQP(frame_params);
// TODO(crbug.com/1030199): Fill temporal layer id.
current_frame_hdr_.quant_params.base_q_idx = rate_ctrl_->GetQP();
current_frame_hdr_.loop_filter.level = rate_ctrl_->GetLoopfilterLevel();
DVLOGF(4) << "|qp|=" << rate_ctrl_->GetQP()
<< ", |filter_level|=" << rate_ctrl_->GetLoopfilterLevel();
}
void VP9Encoder::UpdateReferenceFrames(scoped_refptr<VP9Picture> picture) {
reference_frames_.Refresh(picture);
}
} // namespace media