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