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