OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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 "content/renderer/media_recorder/vpx_encoder.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <string> |
| 9 |
| 10 #include "base/bind.h" |
| 11 #include "base/sys_info.h" |
| 12 #include "base/threading/thread.h" |
| 13 #include "base/trace_event/trace_event.h" |
| 14 #include "media/base/video_frame.h" |
| 15 #include "ui/gfx/geometry/size.h" |
| 16 |
| 17 using media::VideoFrame; |
| 18 using media::VideoFrameMetadata; |
| 19 |
| 20 namespace content { |
| 21 |
| 22 void VpxEncoder::VpxCodecDeleter::operator()(vpx_codec_ctx_t* codec) { |
| 23 if (!codec) |
| 24 return; |
| 25 vpx_codec_err_t ret = vpx_codec_destroy(codec); |
| 26 CHECK_EQ(ret, VPX_CODEC_OK); |
| 27 delete codec; |
| 28 } |
| 29 |
| 30 static int GetNumberOfThreadsForEncoding() { |
| 31 // Do not saturate CPU utilization just for encoding. On a lower-end system |
| 32 // with only 1 or 2 cores, use only one thread for encoding. On systems with |
| 33 // more cores, allow half of the cores to be used for encoding. |
| 34 return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2); |
| 35 } |
| 36 |
| 37 // static |
| 38 void VpxEncoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread, |
| 39 ScopedVpxCodecCtxPtr encoder) { |
| 40 DCHECK(encoding_thread->IsRunning()); |
| 41 encoding_thread->Stop(); |
| 42 // Both |encoding_thread| and |encoder| will be destroyed at end-of-scope. |
| 43 } |
| 44 |
| 45 VpxEncoder::VpxEncoder( |
| 46 bool use_vp9, |
| 47 const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback, |
| 48 int32_t bits_per_second) |
| 49 : VideoTrackRecorder::Encoder(on_encoded_video_callback, bits_per_second), |
| 50 use_vp9_(use_vp9) { |
| 51 codec_config_.g_timebase.den = 0; // Not initialized. |
| 52 alpha_codec_config_.g_timebase.den = 0; // Not initialized. |
| 53 DCHECK(encoding_thread_->IsRunning()); |
| 54 } |
| 55 |
| 56 VpxEncoder::~VpxEncoder() { |
| 57 main_task_runner_->PostTask( |
| 58 FROM_HERE, |
| 59 base::Bind(&VpxEncoder::ShutdownEncoder, base::Passed(&encoding_thread_), |
| 60 base::Passed(&encoder_))); |
| 61 } |
| 62 |
| 63 bool VpxEncoder::CanEncodeAlphaChannel() { |
| 64 return true; |
| 65 } |
| 66 |
| 67 void VpxEncoder::EncodeOnEncodingTaskRunner(scoped_refptr<VideoFrame> frame, |
| 68 base::TimeTicks capture_timestamp) { |
| 69 TRACE_EVENT0("video", "VpxEncoder::EncodeOnEncodingTaskRunner"); |
| 70 DCHECK(encoding_task_runner_->BelongsToCurrentThread()); |
| 71 |
| 72 const gfx::Size frame_size = frame->visible_rect().size(); |
| 73 const base::TimeDelta duration = EstimateFrameDuration(frame); |
| 74 const media::WebmMuxer::VideoParameters video_params(frame); |
| 75 |
| 76 if (!IsInitialized(codec_config_) || |
| 77 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) { |
| 78 ConfigureEncoderOnEncodingTaskRunner(frame_size, &codec_config_, &encoder_); |
| 79 } |
| 80 |
| 81 const bool frame_has_alpha = frame->format() == media::PIXEL_FORMAT_YV12A; |
| 82 if (frame_has_alpha && (!IsInitialized(alpha_codec_config_) || |
| 83 gfx::Size(alpha_codec_config_.g_w, |
| 84 alpha_codec_config_.g_h) != frame_size)) { |
| 85 ConfigureEncoderOnEncodingTaskRunner(frame_size, &alpha_codec_config_, |
| 86 &alpha_encoder_); |
| 87 u_plane_stride_ = media::VideoFrame::RowBytes( |
| 88 VideoFrame::kUPlane, frame->format(), frame_size.width()); |
| 89 v_plane_stride_ = media::VideoFrame::RowBytes( |
| 90 VideoFrame::kVPlane, frame->format(), frame_size.width()); |
| 91 v_plane_offset_ = media::VideoFrame::PlaneSize( |
| 92 frame->format(), VideoFrame::kUPlane, frame_size) |
| 93 .GetArea(); |
| 94 alpha_dummy_planes_.resize( |
| 95 v_plane_offset_ + media::VideoFrame::PlaneSize( |
| 96 frame->format(), VideoFrame::kVPlane, frame_size) |
| 97 .GetArea()); |
| 98 // It is more expensive to encode 0x00, so use 0x80 instead. |
| 99 std::fill(alpha_dummy_planes_.begin(), alpha_dummy_planes_.end(), 0x80); |
| 100 } |
| 101 // If we introduced a new alpha frame, force keyframe. |
| 102 const bool force_keyframe = frame_has_alpha && !last_frame_had_alpha_; |
| 103 last_frame_had_alpha_ = frame_has_alpha; |
| 104 |
| 105 std::unique_ptr<std::string> data(new std::string); |
| 106 bool keyframe = false; |
| 107 DoEncode(encoder_.get(), frame_size, frame->data(VideoFrame::kYPlane), |
| 108 frame->visible_data(VideoFrame::kYPlane), |
| 109 frame->stride(VideoFrame::kYPlane), |
| 110 frame->visible_data(VideoFrame::kUPlane), |
| 111 frame->stride(VideoFrame::kUPlane), |
| 112 frame->visible_data(VideoFrame::kVPlane), |
| 113 frame->stride(VideoFrame::kVPlane), duration, force_keyframe, |
| 114 data.get(), &keyframe); |
| 115 |
| 116 std::unique_ptr<std::string> alpha_data(new std::string); |
| 117 if (frame_has_alpha) { |
| 118 bool alpha_keyframe = false; |
| 119 DoEncode(alpha_encoder_.get(), frame_size, frame->data(VideoFrame::kAPlane), |
| 120 frame->visible_data(VideoFrame::kAPlane), |
| 121 frame->stride(VideoFrame::kAPlane), alpha_dummy_planes_.data(), |
| 122 u_plane_stride_, alpha_dummy_planes_.data() + v_plane_offset_, |
| 123 v_plane_stride_, duration, keyframe, alpha_data.get(), |
| 124 &alpha_keyframe); |
| 125 DCHECK_EQ(keyframe, alpha_keyframe); |
| 126 } |
| 127 frame = nullptr; |
| 128 |
| 129 origin_task_runner_->PostTask( |
| 130 FROM_HERE, |
| 131 base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_, |
| 132 video_params, base::Passed(&data), base::Passed(&alpha_data), |
| 133 capture_timestamp, keyframe)); |
| 134 } |
| 135 |
| 136 void VpxEncoder::DoEncode(vpx_codec_ctx_t* const encoder, |
| 137 const gfx::Size& frame_size, |
| 138 uint8_t* const data, |
| 139 uint8_t* const y_plane, |
| 140 int y_stride, |
| 141 uint8_t* const u_plane, |
| 142 int u_stride, |
| 143 uint8_t* const v_plane, |
| 144 int v_stride, |
| 145 const base::TimeDelta& duration, |
| 146 bool force_keyframe, |
| 147 std::string* const output_data, |
| 148 bool* const keyframe) { |
| 149 DCHECK(encoding_task_runner_->BelongsToCurrentThread()); |
| 150 |
| 151 vpx_image_t vpx_image; |
| 152 vpx_image_t* const result = |
| 153 vpx_img_wrap(&vpx_image, VPX_IMG_FMT_I420, frame_size.width(), |
| 154 frame_size.height(), 1 /* align */, data); |
| 155 DCHECK_EQ(result, &vpx_image); |
| 156 vpx_image.planes[VPX_PLANE_Y] = y_plane; |
| 157 vpx_image.planes[VPX_PLANE_U] = u_plane; |
| 158 vpx_image.planes[VPX_PLANE_V] = v_plane; |
| 159 vpx_image.stride[VPX_PLANE_Y] = y_stride; |
| 160 vpx_image.stride[VPX_PLANE_U] = u_stride; |
| 161 vpx_image.stride[VPX_PLANE_V] = v_stride; |
| 162 |
| 163 const vpx_codec_flags_t flags = force_keyframe ? VPX_EFLAG_FORCE_KF : 0; |
| 164 // Encode the frame. The presentation time stamp argument here is fixed to |
| 165 // zero to force the encoder to base its single-frame bandwidth calculations |
| 166 // entirely on |predicted_frame_duration|. |
| 167 const vpx_codec_err_t ret = |
| 168 vpx_codec_encode(encoder, &vpx_image, 0 /* pts */, |
| 169 duration.InMicroseconds(), flags, VPX_DL_REALTIME); |
| 170 DCHECK_EQ(ret, VPX_CODEC_OK) |
| 171 << vpx_codec_err_to_string(ret) << ", #" << vpx_codec_error(encoder) |
| 172 << " -" << vpx_codec_error_detail(encoder); |
| 173 |
| 174 *keyframe = false; |
| 175 vpx_codec_iter_t iter = NULL; |
| 176 const vpx_codec_cx_pkt_t* pkt = NULL; |
| 177 while ((pkt = vpx_codec_get_cx_data(encoder, &iter)) != NULL) { |
| 178 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) |
| 179 continue; |
| 180 output_data->assign(static_cast<char*>(pkt->data.frame.buf), |
| 181 pkt->data.frame.sz); |
| 182 *keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; |
| 183 break; |
| 184 } |
| 185 } |
| 186 |
| 187 void VpxEncoder::ConfigureEncoderOnEncodingTaskRunner( |
| 188 const gfx::Size& size, |
| 189 vpx_codec_enc_cfg_t* codec_config, |
| 190 ScopedVpxCodecCtxPtr* encoder) { |
| 191 DCHECK(encoding_task_runner_->BelongsToCurrentThread()); |
| 192 if (IsInitialized(*codec_config)) { |
| 193 // TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less- |
| 194 // than-or-equal than the old size, in terms of area, the existing encoder |
| 195 // instance could be reused after changing |codec_config->{g_w,g_h}|. |
| 196 DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: " |
| 197 << gfx::Size(codec_config->g_w, codec_config->g_h).ToString() |
| 198 << " --> " << size.ToString() << (use_vp9_ ? " vp9" : " vp8"); |
| 199 encoder->reset(); |
| 200 } |
| 201 |
| 202 const vpx_codec_iface_t* codec_interface = |
| 203 use_vp9_ ? vpx_codec_vp9_cx() : vpx_codec_vp8_cx(); |
| 204 vpx_codec_err_t result = vpx_codec_enc_config_default( |
| 205 codec_interface, codec_config, 0 /* reserved */); |
| 206 DCHECK_EQ(VPX_CODEC_OK, result); |
| 207 |
| 208 DCHECK_EQ(320u, codec_config->g_w); |
| 209 DCHECK_EQ(240u, codec_config->g_h); |
| 210 DCHECK_EQ(256u, codec_config->rc_target_bitrate); |
| 211 // Use the selected bitrate or adjust default bit rate to account for the |
| 212 // actual size. Note: |rc_target_bitrate| units are kbit per second. |
| 213 if (bits_per_second_ > 0) { |
| 214 codec_config->rc_target_bitrate = bits_per_second_ / 1000; |
| 215 } else { |
| 216 codec_config->rc_target_bitrate = size.GetArea() * |
| 217 codec_config->rc_target_bitrate / |
| 218 codec_config->g_w / codec_config->g_h; |
| 219 } |
| 220 // Both VP8/VP9 configuration should be Variable BitRate by default. |
| 221 DCHECK_EQ(VPX_VBR, codec_config->rc_end_usage); |
| 222 if (use_vp9_) { |
| 223 // Number of frames to consume before producing output. |
| 224 codec_config->g_lag_in_frames = 0; |
| 225 |
| 226 // DCHECK that the profile selected by default is I420 (magic number 0). |
| 227 DCHECK_EQ(0u, codec_config->g_profile); |
| 228 } else { |
| 229 // VP8 always produces frames instantaneously. |
| 230 DCHECK_EQ(0u, codec_config->g_lag_in_frames); |
| 231 } |
| 232 |
| 233 DCHECK(size.width()); |
| 234 DCHECK(size.height()); |
| 235 codec_config->g_w = size.width(); |
| 236 codec_config->g_h = size.height(); |
| 237 codec_config->g_pass = VPX_RC_ONE_PASS; |
| 238 |
| 239 // Timebase is the smallest interval used by the stream, can be set to the |
| 240 // frame rate or to e.g. microseconds. |
| 241 codec_config->g_timebase.num = 1; |
| 242 codec_config->g_timebase.den = base::Time::kMicrosecondsPerSecond; |
| 243 |
| 244 // Let the encoder decide where to place the Keyframes, between min and max. |
| 245 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/ |
| 246 // max distance out of necessity. |
| 247 // Note that due to http://crbug.com/440223, it might be necessary to force a |
| 248 // key frame after 10,000frames since decoding fails after 30,000 non-key |
| 249 // frames. |
| 250 // Forcing a keyframe in regular intervals also allows seeking in the |
| 251 // resulting recording with decent performance. |
| 252 codec_config->kf_mode = VPX_KF_AUTO; |
| 253 codec_config->kf_min_dist = 0; |
| 254 codec_config->kf_max_dist = 100; |
| 255 |
| 256 codec_config->g_threads = GetNumberOfThreadsForEncoding(); |
| 257 |
| 258 // Number of frames to consume before producing output. |
| 259 codec_config->g_lag_in_frames = 0; |
| 260 |
| 261 encoder->reset(new vpx_codec_ctx_t); |
| 262 const vpx_codec_err_t ret = vpx_codec_enc_init( |
| 263 encoder->get(), codec_interface, codec_config, 0 /* flags */); |
| 264 DCHECK_EQ(VPX_CODEC_OK, ret); |
| 265 |
| 266 if (use_vp9_) { |
| 267 // Values of VP8E_SET_CPUUSED greater than 0 will increase encoder speed at |
| 268 // the expense of quality up to a maximum value of 8 for VP9, by tuning the |
| 269 // target time spent encoding the frame. Go from 8 to 5 (values for real |
| 270 // time encoding) depending on the amount of cores available in the system. |
| 271 const int kCpuUsed = |
| 272 std::max(5, 8 - base::SysInfo::NumberOfProcessors() / 2); |
| 273 result = vpx_codec_control(encoder->get(), VP8E_SET_CPUUSED, kCpuUsed); |
| 274 DLOG_IF(WARNING, VPX_CODEC_OK != result) << "VP8E_SET_CPUUSED failed"; |
| 275 } |
| 276 } |
| 277 |
| 278 bool VpxEncoder::IsInitialized(const vpx_codec_enc_cfg_t& codec_config) const { |
| 279 DCHECK(encoding_task_runner_->BelongsToCurrentThread()); |
| 280 return codec_config.g_timebase.den != 0; |
| 281 } |
| 282 |
| 283 base::TimeDelta VpxEncoder::EstimateFrameDuration( |
| 284 const scoped_refptr<VideoFrame>& frame) { |
| 285 DCHECK(encoding_task_runner_->BelongsToCurrentThread()); |
| 286 |
| 287 using base::TimeDelta; |
| 288 TimeDelta predicted_frame_duration; |
| 289 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION, |
| 290 &predicted_frame_duration) || |
| 291 predicted_frame_duration <= TimeDelta()) { |
| 292 // The source of the video frame did not provide the frame duration. Use |
| 293 // the actual amount of time between the current and previous frame as a |
| 294 // prediction for the next frame's duration. |
| 295 // TODO(mcasas): This duration estimation could lead to artifacts if the |
| 296 // cadence of the received stream is compromised (e.g. camera freeze, pause, |
| 297 // remote packet loss). Investigate using GetFrameRate() in this case. |
| 298 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_; |
| 299 } |
| 300 last_frame_timestamp_ = frame->timestamp(); |
| 301 // Make sure |predicted_frame_duration| is in a safe range of values. |
| 302 const TimeDelta kMaxFrameDuration = TimeDelta::FromSecondsD(1.0 / 8); |
| 303 const TimeDelta kMinFrameDuration = TimeDelta::FromMilliseconds(1); |
| 304 return std::min(kMaxFrameDuration, |
| 305 std::max(predicted_frame_duration, kMinFrameDuration)); |
| 306 } |
| 307 |
| 308 } // namespace content |
OLD | NEW |