• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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