1 // Copyright 2021 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 "cast/standalone_sender/streaming_av1_encoder.h"
6
7 #include <aom/aomcx.h>
8
9 #include <chrono>
10 #include <cmath>
11 #include <utility>
12
13 #include "cast/standalone_sender/streaming_encoder_util.h"
14 #include "cast/streaming/encoded_frame.h"
15 #include "cast/streaming/environment.h"
16 #include "cast/streaming/sender.h"
17 #include "util/chrono_helpers.h"
18 #include "util/osp_logging.h"
19 #include "util/saturate_cast.h"
20
21 namespace openscreen {
22 namespace cast {
23
24 // TODO(issuetracker.google.com/issues/155336511): Fix the declarations and then
25 // remove this:
26 using openscreen::operator<<; // For std::chrono::duration pretty-printing.
27
28 namespace {
29
30 constexpr int kBytesPerKilobyte = 1024;
31
32 // Lower and upper bounds to the frame duration passed to aom_codec_encode(), to
33 // ensure sanity. Note that the upper-bound is especially important in cases
34 // where the video paused for some lengthy amount of time.
35 constexpr Clock::duration kMinFrameDuration = milliseconds(1);
36 constexpr Clock::duration kMaxFrameDuration = milliseconds(125);
37
38 // Highest/lowest allowed encoding speed set to the encoder.
39 constexpr int kHighestEncodingSpeed = 9;
40 constexpr int kLowestEncodingSpeed = 0;
41
42 } // namespace
43
StreamingAv1Encoder(const Parameters & params,TaskRunner * task_runner,Sender * sender)44 StreamingAv1Encoder::StreamingAv1Encoder(const Parameters& params,
45 TaskRunner* task_runner,
46 Sender* sender)
47 : StreamingVideoEncoder(params, task_runner, sender) {
48 ideal_speed_setting_ = kHighestEncodingSpeed;
49 encode_thread_ = std::thread([this] { ProcessWorkUnitsUntilTimeToQuit(); });
50
51 OSP_DCHECK(params_.codec == VideoCodec::kAv1);
52 const auto result = aom_codec_enc_config_default(aom_codec_av1_cx(), &config_,
53 AOM_USAGE_REALTIME);
54 OSP_CHECK_EQ(result, AOM_CODEC_OK);
55
56 // This is set to non-zero in ConfigureForNewFrameSize() later, to flag that
57 // the encoder has been initialized.
58 config_.g_threads = 0;
59
60 // Set the timebase to match that of openscreen::Clock::duration.
61 config_.g_timebase.num = Clock::duration::period::num;
62 config_.g_timebase.den = Clock::duration::period::den;
63
64 // |g_pass| and |g_lag_in_frames| must be "one pass" and zero, respectively,
65 // because of the way the libaom API is used.
66 config_.g_pass = AOM_RC_ONE_PASS;
67 config_.g_lag_in_frames = 0;
68
69 // Rate control settings.
70 config_.rc_dropframe_thresh = 0; // The encoder may not drop any frames.
71 config_.rc_resize_mode = 0;
72 config_.rc_end_usage = AOM_CBR;
73 config_.rc_target_bitrate = target_bitrate_ / kBytesPerKilobyte;
74 config_.rc_min_quantizer = params_.min_quantizer;
75 config_.rc_max_quantizer = params_.max_quantizer;
76
77 // The reasons for the values chosen here (rc_*shoot_pct and rc_buf_*_sz) are
78 // lost in history. They were brought-over from the legacy Chrome Cast
79 // Streaming Sender implemenation.
80 config_.rc_undershoot_pct = 100;
81 config_.rc_overshoot_pct = 15;
82 config_.rc_buf_initial_sz = 500;
83 config_.rc_buf_optimal_sz = 600;
84 config_.rc_buf_sz = 1000;
85
86 config_.kf_mode = AOM_KF_DISABLED;
87 }
88
~StreamingAv1Encoder()89 StreamingAv1Encoder::~StreamingAv1Encoder() {
90 {
91 std::unique_lock<std::mutex> lock(mutex_);
92 target_bitrate_ = 0;
93 cv_.notify_one();
94 }
95 encode_thread_.join();
96 }
97
GetTargetBitrate() const98 int StreamingAv1Encoder::GetTargetBitrate() const {
99 // Note: No need to lock the |mutex_| since this method should be called on
100 // the same thread as SetTargetBitrate().
101 return target_bitrate_;
102 }
103
SetTargetBitrate(int new_bitrate)104 void StreamingAv1Encoder::SetTargetBitrate(int new_bitrate) {
105 // Ensure that, when bps is converted to kbps downstream, that the encoder
106 // bitrate will not be zero.
107 new_bitrate = std::max(new_bitrate, kBytesPerKilobyte);
108
109 std::unique_lock<std::mutex> lock(mutex_);
110 // Only assign the new target bitrate if |target_bitrate_| has not yet been
111 // used to signal the |encode_thread_| to end.
112 if (target_bitrate_ > 0) {
113 target_bitrate_ = new_bitrate;
114 }
115 }
116
EncodeAndSend(const VideoFrame & frame,Clock::time_point reference_time,std::function<void (Stats)> stats_callback)117 void StreamingAv1Encoder::EncodeAndSend(
118 const VideoFrame& frame,
119 Clock::time_point reference_time,
120 std::function<void(Stats)> stats_callback) {
121 WorkUnit work_unit;
122
123 // TODO(jophba): The |VideoFrame| struct should provide the media timestamp,
124 // instead of this code inferring it from the reference timestamps, since: 1)
125 // the video capturer's clock may tick at a different rate than the system
126 // clock; and 2) to reduce jitter.
127 if (start_time_ == Clock::time_point::min()) {
128 start_time_ = reference_time;
129 work_unit.rtp_timestamp = RtpTimeTicks();
130 } else {
131 work_unit.rtp_timestamp = RtpTimeTicks::FromTimeSinceOrigin(
132 reference_time - start_time_, sender_->rtp_timebase());
133 if (work_unit.rtp_timestamp <= last_enqueued_rtp_timestamp_) {
134 OSP_LOG_WARN << "VIDEO[" << sender_->ssrc()
135 << "] Dropping: RTP timestamp is not monotonically "
136 "increasing from last frame.";
137 return;
138 }
139 }
140 if (sender_->GetInFlightMediaDuration(work_unit.rtp_timestamp) >
141 sender_->GetMaxInFlightMediaDuration()) {
142 OSP_LOG_WARN << "VIDEO[" << sender_->ssrc()
143 << "] Dropping: In-flight media duration would be too high.";
144 return;
145 }
146
147 Clock::duration frame_duration = frame.duration;
148 if (frame_duration <= Clock::duration::zero()) {
149 // The caller did not provide the frame duration in |frame|.
150 if (reference_time == start_time_) {
151 // Use the max for the first frame so libaom will spend extra effort on
152 // its quality.
153 frame_duration = kMaxFrameDuration;
154 } else {
155 // Use the actual amount of time between the current and previous frame as
156 // a prediction for the next frame's duration.
157 frame_duration =
158 (work_unit.rtp_timestamp - last_enqueued_rtp_timestamp_)
159 .ToDuration<Clock::duration>(sender_->rtp_timebase());
160 }
161 }
162 work_unit.duration =
163 std::max(std::min(frame_duration, kMaxFrameDuration), kMinFrameDuration);
164
165 last_enqueued_rtp_timestamp_ = work_unit.rtp_timestamp;
166
167 work_unit.image = CloneAsAv1Image(frame);
168 work_unit.reference_time = reference_time;
169 work_unit.stats_callback = std::move(stats_callback);
170 const bool force_key_frame = sender_->NeedsKeyFrame();
171 {
172 std::unique_lock<std::mutex> lock(mutex_);
173 needs_key_frame_ |= force_key_frame;
174 encode_queue_.push(std::move(work_unit));
175 cv_.notify_one();
176 }
177 }
178
DestroyEncoder()179 void StreamingAv1Encoder::DestroyEncoder() {
180 OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());
181
182 if (is_encoder_initialized()) {
183 aom_codec_destroy(&encoder_);
184 // Flag that the encoder is not initialized. See header comments for
185 // is_encoder_initialized().
186 config_.g_threads = 0;
187 }
188 }
189
ProcessWorkUnitsUntilTimeToQuit()190 void StreamingAv1Encoder::ProcessWorkUnitsUntilTimeToQuit() {
191 OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());
192
193 for (;;) {
194 WorkUnitWithResults work_unit{};
195 bool force_key_frame;
196 int target_bitrate;
197 {
198 std::unique_lock<std::mutex> lock(mutex_);
199 if (target_bitrate_ <= 0) {
200 break; // Time to end this thread.
201 }
202 if (encode_queue_.empty()) {
203 cv_.wait(lock);
204 if (encode_queue_.empty()) {
205 continue;
206 }
207 }
208 static_cast<WorkUnit&>(work_unit) = std::move(encode_queue_.front());
209 encode_queue_.pop();
210 force_key_frame = needs_key_frame_;
211 needs_key_frame_ = false;
212 target_bitrate = target_bitrate_;
213 }
214
215 // Clock::now() is being called directly, instead of using a
216 // dependency-injected "now function," since actual wall time is being
217 // measured.
218 const Clock::time_point encode_start_time = Clock::now();
219 PrepareEncoder(work_unit.image->d_w, work_unit.image->d_h, target_bitrate);
220 EncodeFrame(force_key_frame, work_unit);
221 ComputeFrameEncodeStats(Clock::now() - encode_start_time, target_bitrate,
222 work_unit);
223 UpdateSpeedSettingForNextFrame(work_unit.stats);
224
225 main_task_runner_->PostTask(
226 [this, results = std::move(work_unit)]() mutable {
227 SendEncodedFrame(std::move(results));
228 });
229 }
230
231 DestroyEncoder();
232 }
233
PrepareEncoder(int width,int height,int target_bitrate)234 void StreamingAv1Encoder::PrepareEncoder(int width,
235 int height,
236 int target_bitrate) {
237 OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());
238
239 const int target_kbps = target_bitrate / kBytesPerKilobyte;
240
241 // Translate the |ideal_speed_setting_| into the AOME_SET_CPUUSED setting and
242 // the minimum quantizer to use.
243 int speed;
244 int min_quantizer;
245 if (ideal_speed_setting_ > kHighestEncodingSpeed) {
246 speed = kHighestEncodingSpeed;
247 const double remainder = ideal_speed_setting_ - speed;
248 min_quantizer = rounded_saturate_cast<int>(
249 remainder / kEquivalentEncodingSpeedStepPerQuantizerStep +
250 params_.min_quantizer);
251 min_quantizer = std::min(min_quantizer, params_.max_cpu_saver_quantizer);
252 } else {
253 speed = std::max(rounded_saturate_cast<int>(ideal_speed_setting_),
254 kLowestEncodingSpeed);
255 min_quantizer = params_.min_quantizer;
256 }
257
258 if (static_cast<int>(config_.g_w) != width ||
259 static_cast<int>(config_.g_h) != height) {
260 DestroyEncoder();
261 }
262
263 if (!is_encoder_initialized()) {
264 config_.g_threads = params_.num_encode_threads;
265 config_.g_w = width;
266 config_.g_h = height;
267 config_.rc_target_bitrate = target_kbps;
268 config_.rc_min_quantizer = min_quantizer;
269
270 encoder_ = {};
271 const aom_codec_flags_t flags = 0;
272
273 const auto init_result =
274 aom_codec_enc_init(&encoder_, aom_codec_av1_cx(), &config_, flags);
275 OSP_CHECK_EQ(init_result, AOM_CODEC_OK);
276
277 // Raise the threshold for considering macroblocks as static. The default is
278 // zero, so this setting makes the encoder less sensitive to motion. This
279 // lowers the probability of needing to utilize more CPU to search for
280 // motion vectors.
281 const auto ctl_result =
282 aom_codec_control(&encoder_, AOME_SET_STATIC_THRESHOLD, 1);
283 OSP_CHECK_EQ(ctl_result, AOM_CODEC_OK);
284
285 // Ensure the speed will be set (below).
286 current_speed_setting_ = ~speed;
287 } else if (static_cast<int>(config_.rc_target_bitrate) != target_kbps ||
288 static_cast<int>(config_.rc_min_quantizer) != min_quantizer) {
289 config_.rc_target_bitrate = target_kbps;
290 config_.rc_min_quantizer = min_quantizer;
291 const auto update_config_result =
292 aom_codec_enc_config_set(&encoder_, &config_);
293 OSP_CHECK_EQ(update_config_result, AOM_CODEC_OK);
294 }
295
296 if (current_speed_setting_ != speed) {
297 const auto ctl_result =
298 aom_codec_control(&encoder_, AOME_SET_CPUUSED, speed);
299 OSP_CHECK_EQ(ctl_result, AOM_CODEC_OK);
300 current_speed_setting_ = speed;
301 }
302 }
303
EncodeFrame(bool force_key_frame,WorkUnitWithResults & work_unit)304 void StreamingAv1Encoder::EncodeFrame(bool force_key_frame,
305 WorkUnitWithResults& work_unit) {
306 OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());
307
308 // The presentation timestamp argument here is fixed to zero to force the
309 // encoder to base its single-frame bandwidth calculations entirely on
310 // |frame_duration| and the target bitrate setting.
311 const aom_codec_pts_t pts = 0;
312 const aom_enc_frame_flags_t flags = force_key_frame ? AOM_EFLAG_FORCE_KF : 0;
313 const auto encode_result = aom_codec_encode(
314 &encoder_, work_unit.image.get(), pts, work_unit.duration.count(), flags);
315 OSP_CHECK_EQ(encode_result, AOM_CODEC_OK);
316
317 const aom_codec_cx_pkt_t* pkt;
318 for (aom_codec_iter_t iter = nullptr;;) {
319 pkt = aom_codec_get_cx_data(&encoder_, &iter);
320 // aom_codec_get_cx_data() returns null once the "iteration" is complete.
321 // However, that point should never be reached because a
322 // AOM_CODEC_CX_FRAME_PKT must be encountered before that.
323 OSP_CHECK(pkt);
324 if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
325 break;
326 }
327 }
328
329 // A copy of the payload data is being made here. That's okay since it has to
330 // be copied at some point anyway, to be passed back to the main thread.
331 auto* const begin = static_cast<const uint8_t*>(pkt->data.frame.buf);
332 auto* const end = begin + pkt->data.frame.sz;
333 work_unit.payload.assign(begin, end);
334 work_unit.is_key_frame = !!(pkt->data.frame.flags & AOM_FRAME_IS_KEY);
335 }
336
ComputeFrameEncodeStats(Clock::duration encode_wall_time,int target_bitrate,WorkUnitWithResults & work_unit)337 void StreamingAv1Encoder::ComputeFrameEncodeStats(
338 Clock::duration encode_wall_time,
339 int target_bitrate,
340 WorkUnitWithResults& work_unit) {
341 OSP_DCHECK_EQ(std::this_thread::get_id(), encode_thread_.get_id());
342
343 Stats& stats = work_unit.stats;
344
345 // Note: stats.frame_id is set later, in SendEncodedFrame().
346 stats.rtp_timestamp = work_unit.rtp_timestamp;
347 stats.encode_wall_time = encode_wall_time;
348 stats.frame_duration = work_unit.duration;
349 stats.encoded_size = work_unit.payload.size();
350
351 constexpr double kBytesPerBit = 1.0 / CHAR_BIT;
352 constexpr double kSecondsPerClockTick =
353 1.0 / Clock::to_duration(seconds(1)).count();
354 const double target_bytes_per_clock_tick =
355 target_bitrate * (kBytesPerBit * kSecondsPerClockTick);
356 stats.target_size = target_bytes_per_clock_tick * work_unit.duration.count();
357
358 // The quantizer the encoder used. This is the result of the AV1 encoder
359 // taking a guess at what quantizer value would produce an encoded frame size
360 // as close to the target as possible.
361 const auto get_quantizer_result = aom_codec_control(
362 &encoder_, AOME_GET_LAST_QUANTIZER_64, &stats.quantizer);
363 OSP_CHECK_EQ(get_quantizer_result, AOM_CODEC_OK);
364
365 // Now that the frame has been encoded and the number of bytes is known, the
366 // perfect quantizer value (i.e., the one that should have been used) can be
367 // determined.
368 stats.perfect_quantizer = stats.quantizer * stats.space_utilization();
369 }
370
SendEncodedFrame(WorkUnitWithResults results)371 void StreamingAv1Encoder::SendEncodedFrame(WorkUnitWithResults results) {
372 OSP_DCHECK(main_task_runner_->IsRunningOnTaskRunner());
373
374 EncodedFrame frame;
375 frame.frame_id = sender_->GetNextFrameId();
376 if (results.is_key_frame) {
377 frame.dependency = EncodedFrame::KEY_FRAME;
378 frame.referenced_frame_id = frame.frame_id;
379 } else {
380 frame.dependency = EncodedFrame::DEPENDS_ON_ANOTHER;
381 frame.referenced_frame_id = frame.frame_id - 1;
382 }
383 frame.rtp_timestamp = results.rtp_timestamp;
384 frame.reference_time = results.reference_time;
385 frame.data = absl::Span<uint8_t>(results.payload);
386
387 if (sender_->EnqueueFrame(frame) != Sender::OK) {
388 // Since the frame will not be sent, the encoder's frame dependency chain
389 // has been broken. Force a key frame for the next frame.
390 std::unique_lock<std::mutex> lock(mutex_);
391 needs_key_frame_ = true;
392 }
393
394 if (results.stats_callback) {
395 results.stats.frame_id = frame.frame_id;
396 results.stats_callback(results.stats);
397 }
398 }
399
400 // static
CloneAsAv1Image(const VideoFrame & frame)401 StreamingAv1Encoder::Av1ImageUniquePtr StreamingAv1Encoder::CloneAsAv1Image(
402 const VideoFrame& frame) {
403 OSP_DCHECK_GE(frame.width, 0);
404 OSP_DCHECK_GE(frame.height, 0);
405 OSP_DCHECK_GE(frame.yuv_strides[0], 0);
406 OSP_DCHECK_GE(frame.yuv_strides[1], 0);
407 OSP_DCHECK_GE(frame.yuv_strides[2], 0);
408
409 constexpr int kAlignment = 32;
410 Av1ImageUniquePtr image(aom_img_alloc(nullptr, AOM_IMG_FMT_I420, frame.width,
411 frame.height, kAlignment));
412 OSP_CHECK(image);
413
414 CopyPlane(frame.yuv_planes[0], frame.yuv_strides[0], frame.height,
415 image->planes[AOM_PLANE_Y], image->stride[AOM_PLANE_Y]);
416 CopyPlane(frame.yuv_planes[1], frame.yuv_strides[1], (frame.height + 1) / 2,
417 image->planes[AOM_PLANE_U], image->stride[AOM_PLANE_U]);
418 CopyPlane(frame.yuv_planes[2], frame.yuv_strides[2], (frame.height + 1) / 2,
419 image->planes[AOM_PLANE_V], image->stride[AOM_PLANE_V]);
420
421 return image;
422 }
423
424 } // namespace cast
425 } // namespace openscreen
426