1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/gpu/vaapi/vp8_encoder.h"
6
7 #include "base/bits.h"
8 #include "media/gpu/macros.h"
9
10 namespace media {
11
12 namespace {
13 // Keyframe period.
14 constexpr size_t kKFPeriod = 3000;
15
16 // Arbitrarily chosen bitrate window size for rate control, in ms.
17 constexpr int kCPBWindowSizeMs = 1500;
18
19 // Quantization parameter. They are vp8 ac/dc indices and their ranges are
20 // 0-127. Based on WebRTC's defaults.
21 constexpr int kMinQP = 4;
22 // b/110059922, crbug.com/1001900: Tuned 112->117 for bitrate issue in a lower
23 // resolution (180p).
24 constexpr int kMaxQP = 117;
25 // This stands for 32 as a real ac value (see rfc 14.1. table ac_qlookup).
26 constexpr int kDefaultQP = 28;
27 } // namespace
28
EncodeParams()29 VP8Encoder::EncodeParams::EncodeParams()
30 : kf_period_frames(kKFPeriod),
31 framerate(0),
32 cpb_window_size_ms(kCPBWindowSizeMs),
33 cpb_size_bits(0),
34 initial_qp(kDefaultQP),
35 scaling_settings(kMinQP, kMaxQP),
36 error_resilient_mode(false) {}
37
Reset()38 void VP8Encoder::Reset() {
39 current_params_ = EncodeParams();
40 reference_frames_.Clear();
41 frame_num_ = 0;
42
43 InitializeFrameHeader();
44 }
45
VP8Encoder(std::unique_ptr<Accelerator> accelerator)46 VP8Encoder::VP8Encoder(std::unique_ptr<Accelerator> accelerator)
47 : accelerator_(std::move(accelerator)) {}
48
~VP8Encoder()49 VP8Encoder::~VP8Encoder() {
50 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
51 }
52
Initialize(const VideoEncodeAccelerator::Config & config,const AcceleratedVideoEncoder::Config & ave_config)53 bool VP8Encoder::Initialize(const VideoEncodeAccelerator::Config& config,
54 const AcceleratedVideoEncoder::Config& ave_config) {
55 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
56 if (VideoCodecProfileToVideoCodec(config.output_profile) != kCodecVP8) {
57 DVLOGF(1) << "Invalid profile: " << GetProfileName(config.output_profile);
58 return false;
59 }
60
61 if (config.input_visible_size.IsEmpty()) {
62 DVLOGF(1) << "Input visible size could not be empty";
63 return false;
64 }
65
66 visible_size_ = config.input_visible_size;
67 coded_size_ = gfx::Size(base::bits::Align(visible_size_.width(), 16),
68 base::bits::Align(visible_size_.height(), 16));
69
70 Reset();
71
72 VideoBitrateAllocation initial_bitrate_allocation;
73 initial_bitrate_allocation.SetBitrate(0, 0, config.initial_bitrate);
74 return UpdateRates(initial_bitrate_allocation,
75 config.initial_framerate.value_or(
76 VideoEncodeAccelerator::kDefaultFramerate));
77 }
78
GetCodedSize() const79 gfx::Size VP8Encoder::GetCodedSize() const {
80 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
81 DCHECK(!coded_size_.IsEmpty());
82
83 return coded_size_;
84 }
85
GetMaxNumOfRefFrames() const86 size_t VP8Encoder::GetMaxNumOfRefFrames() const {
87 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
88
89 return kNumVp8ReferenceBuffers;
90 }
91
GetScalingSettings() const92 ScalingSettings VP8Encoder::GetScalingSettings() const {
93 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
94
95 return current_params_.scaling_settings;
96 }
97
PrepareEncodeJob(EncodeJob * encode_job)98 bool VP8Encoder::PrepareEncodeJob(EncodeJob* encode_job) {
99 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
100
101 if (encode_job->IsKeyframeRequested())
102 frame_num_ = 0;
103
104 if (frame_num_ == 0)
105 encode_job->ProduceKeyframe();
106
107 frame_num_++;
108 frame_num_ %= current_params_.kf_period_frames;
109
110 scoped_refptr<VP8Picture> picture = accelerator_->GetPicture(encode_job);
111 DCHECK(picture);
112
113 UpdateFrameHeader(encode_job->IsKeyframeRequested());
114 *picture->frame_hdr = current_frame_hdr_;
115
116 // We only use |last_frame| for a reference frame. This follows the behavior
117 // of libvpx encoder in chromium webrtc use case.
118 std::array<bool, kNumVp8ReferenceBuffers> ref_frames_used{true, false, false};
119
120 if (current_frame_hdr_.IsKeyframe()) {
121 // A driver should ignore |ref_frames_used| values if keyframe is requested.
122 // But we fill false in |ref_frames_used| just in case.
123 std::fill(std::begin(ref_frames_used), std::end(ref_frames_used), false);
124 }
125
126 if (!accelerator_->SubmitFrameParameters(encode_job, current_params_, picture,
127 reference_frames_,
128 ref_frames_used)) {
129 LOG(ERROR) << "Failed submitting frame parameters";
130 return false;
131 }
132
133 UpdateReferenceFrames(picture);
134 return true;
135 }
136
UpdateRates(const VideoBitrateAllocation & bitrate_allocation,uint32_t framerate)137 bool VP8Encoder::UpdateRates(const VideoBitrateAllocation& bitrate_allocation,
138 uint32_t framerate) {
139 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
140
141 if (bitrate_allocation.GetSumBps() == 0 || framerate == 0)
142 return false;
143
144 if (current_params_.bitrate_allocation == bitrate_allocation &&
145 current_params_.framerate == framerate) {
146 return true;
147 }
148 VLOGF(2) << "New bitrate: " << bitrate_allocation.GetSumBps()
149 << ", New framerate: " << framerate;
150
151 current_params_.bitrate_allocation = bitrate_allocation;
152 current_params_.framerate = framerate;
153
154 current_params_.cpb_size_bits =
155 current_params_.bitrate_allocation.GetSumBps() *
156 current_params_.cpb_window_size_ms / 1000;
157
158 return true;
159 }
160
InitializeFrameHeader()161 void VP8Encoder::InitializeFrameHeader() {
162 current_frame_hdr_ = {};
163 DCHECK(!visible_size_.IsEmpty());
164 current_frame_hdr_.width = visible_size_.width();
165 current_frame_hdr_.height = visible_size_.height();
166 current_frame_hdr_.quantization_hdr.y_ac_qi = kDefaultQP;
167 current_frame_hdr_.show_frame = true;
168 // TODO(sprang): Make this dynamic. Value based on reference implementation
169 // in libyami (https://github.com/intel/libyami).
170
171 // A VA-API driver recommends to set forced_lf_adjustment on keyframe.
172 // Set loop_filter_adj_enable to 1 here because forced_lf_adjustment is read
173 // only when a macroblock level loop filter adjustment.
174 current_frame_hdr_.loopfilter_hdr.loop_filter_adj_enable = 1;
175
176 // Set mb_no_skip_coeff to 1 that some decoders (e.g. kepler) could not decode
177 // correctly a stream encoded with mb_no_skip_coeff=0. It also enables an
178 // encoder to produce a more optimized stream than when mb_no_skip_coeff=0.
179 current_frame_hdr_.mb_no_skip_coeff = 1;
180 }
181
UpdateFrameHeader(bool keyframe)182 void VP8Encoder::UpdateFrameHeader(bool keyframe) {
183 if (keyframe) {
184 current_frame_hdr_.frame_type = Vp8FrameHeader::KEYFRAME;
185 current_frame_hdr_.refresh_last = true;
186 current_frame_hdr_.refresh_golden_frame = true;
187 current_frame_hdr_.refresh_alternate_frame = true;
188 current_frame_hdr_.copy_buffer_to_golden =
189 Vp8FrameHeader::NO_GOLDEN_REFRESH;
190 current_frame_hdr_.copy_buffer_to_alternate =
191 Vp8FrameHeader::NO_ALT_REFRESH;
192 } else {
193 current_frame_hdr_.frame_type = Vp8FrameHeader::INTERFRAME;
194 // TODO(sprang): Add temporal layer support.
195 current_frame_hdr_.refresh_last = true;
196 current_frame_hdr_.refresh_golden_frame = false;
197 current_frame_hdr_.refresh_alternate_frame = false;
198 current_frame_hdr_.copy_buffer_to_golden =
199 Vp8FrameHeader::COPY_LAST_TO_GOLDEN;
200 current_frame_hdr_.copy_buffer_to_alternate =
201 Vp8FrameHeader::COPY_GOLDEN_TO_ALT;
202 }
203 }
204
UpdateReferenceFrames(scoped_refptr<VP8Picture> picture)205 void VP8Encoder::UpdateReferenceFrames(scoped_refptr<VP8Picture> picture) {
206 reference_frames_.Refresh(picture);
207 }
208
209 } // namespace media
210