xref: /aosp_15_r20/external/openscreen/cast/standalone_sender/streaming_av1_encoder.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
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