blob: 2463f23dc0b99991ec53ba114387e285f1adb46f [file] [log] [blame]
// Copyright 2018 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/vp8_encoder.h"
#include "base/bits.h"
#define DVLOGF(level) DVLOG(level) << __func__ << "(): "
namespace media {
namespace {
// Keyframe period.
const size_t kKFPeriod = 3000;
// Arbitrarily chosen bitrate window size for rate control, in ms.
const int kCPBWindowSizeMs = 1500;
// Based on WebRTC's defaults.
const int kMinQP = 4;
const int kMaxQP = 112;
const int kDefaultQP = (3 * kMinQP + kMaxQP) / 4;
} // namespace
VP8Encoder::EncodeParams::EncodeParams()
: kf_period_frames(kKFPeriod),
framerate(0),
cpb_window_size_ms(kCPBWindowSizeMs),
cpb_size_bits(0),
initial_qp(kDefaultQP),
min_qp(kMinQP),
max_qp(kMaxQP),
error_resilient_mode(false) {}
void VP8Encoder::Reset() {
current_params_ = EncodeParams();
reference_frames_.Clear();
frame_num_ = 0;
InitializeFrameHeader();
}
VP8Encoder::VP8Encoder(std::unique_ptr<Accelerator> accelerator)
: accelerator_(std::move(accelerator)) {}
VP8Encoder::~VP8Encoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool VP8Encoder::Initialize(const gfx::Size& visible_size,
VideoCodecProfile profile,
uint32_t initial_bitrate,
uint32_t initial_framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX);
DCHECK(!visible_size.IsEmpty());
// 4:2:0 format has to be 2-aligned.
DCHECK_EQ(visible_size.width() % 2, 0);
DCHECK_EQ(visible_size.height() % 2, 0);
visible_size_ = visible_size;
coded_size_ = gfx::Size(base::bits::Align(visible_size_.width(), 16),
base::bits::Align(visible_size_.height(), 16));
Reset();
VideoBitrateAllocation initial_bitrate_allocation;
initial_bitrate_allocation.SetBitrate(0, 0, initial_bitrate);
return UpdateRates(initial_bitrate_allocation, initial_framerate);
}
gfx::Size VP8Encoder::GetCodedSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!coded_size_.IsEmpty());
return coded_size_;
}
size_t VP8Encoder::GetBitstreamBufferSize() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!coded_size_.IsEmpty());
return coded_size_.GetArea();
}
size_t VP8Encoder::GetMaxNumOfRefFrames() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return kNumVp8ReferenceBuffers;
}
bool VP8Encoder::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<VP8Picture> picture = accelerator_->GetPicture(encode_job);
DCHECK(picture);
UpdateFrameHeader(encode_job->IsKeyframeRequested());
*picture->frame_hdr = current_frame_hdr_;
if (!accelerator_->SubmitFrameParameters(encode_job, current_params_, picture,
reference_frames_)) {
LOG(ERROR) << "Failed submitting frame parameters";
return false;
}
UpdateReferenceFrames(picture);
return true;
}
bool VP8Encoder::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;
}
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;
return true;
}
void VP8Encoder::InitializeFrameHeader() {
current_frame_hdr_ = {};
DCHECK(!visible_size_.IsEmpty());
current_frame_hdr_.width = visible_size_.width();
current_frame_hdr_.height = visible_size_.height();
current_frame_hdr_.quantization_hdr.y_ac_qi = current_params_.initial_qp;
current_frame_hdr_.show_frame = true;
// TODO(sprang): Make this dynamic. Value based on reference implementation
// in libyami (https://github.com/intel/libyami).
current_frame_hdr_.loopfilter_hdr.level = 19;
}
void VP8Encoder::UpdateFrameHeader(bool keyframe) {
current_frame_hdr_.frame_type =
keyframe ? Vp8FrameHeader::KEYFRAME : Vp8FrameHeader::INTERFRAME;
}
void VP8Encoder::UpdateReferenceFrames(scoped_refptr<VP8Picture> picture) {
if (current_frame_hdr_.IsKeyframe()) {
current_frame_hdr_.refresh_last = true;
current_frame_hdr_.refresh_golden_frame = true;
current_frame_hdr_.refresh_alternate_frame = true;
current_frame_hdr_.copy_buffer_to_golden =
Vp8FrameHeader::NO_GOLDEN_REFRESH;
current_frame_hdr_.copy_buffer_to_alternate =
Vp8FrameHeader::NO_ALT_REFRESH;
} else {
// TODO(sprang): Add temporal layer support.
current_frame_hdr_.refresh_last = true;
current_frame_hdr_.refresh_golden_frame = false;
current_frame_hdr_.refresh_alternate_frame = false;
current_frame_hdr_.copy_buffer_to_golden =
Vp8FrameHeader::COPY_LAST_TO_GOLDEN;
current_frame_hdr_.copy_buffer_to_alternate =
Vp8FrameHeader::COPY_GOLDEN_TO_ALT;
}
reference_frames_.Refresh(picture);
}
} // namespace media