| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2015 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/video_track_recorder.h" |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/threading/thread.h" |
| 10 #include "base/time/time.h" |
| 11 #include "base/trace_event/trace_event.h" |
| 12 #include "content/child/child_process.h" |
| 13 #include "media/base/bind_to_current_loop.h" |
| 14 #include "media/base/video_frame.h" |
| 15 |
| 16 extern "C" { |
| 17 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide |
| 18 // backwards compatibility for legacy applications using the library. |
| 19 #define VPX_CODEC_DISABLE_COMPAT 1 |
| 20 #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h" |
| 21 #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h" |
| 22 } |
| 23 |
| 24 using media::VideoFrame; |
| 25 using media::VideoFrameMetadata; |
| 26 |
| 27 namespace content { |
| 28 |
| 29 namespace { |
| 30 |
| 31 const vpx_codec_flags_t kNoFlags = 0; |
| 32 |
| 33 static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) { |
| 34 double frame_rate = 0.0f; |
| 35 base::IgnoreResult(video_frame->metadata()->GetDouble( |
| 36 VideoFrameMetadata::FRAME_RATE, &frame_rate)); |
| 37 return frame_rate; |
| 38 } |
| 39 |
| 40 } // anonymous namespace |
| 41 |
| 42 // Inner class encapsulating all libvpx interactions and the encoding+delivery |
| 43 // of received frames. This class is: |
| 44 // - created and destroyed on its parent's thread (usually the main render |
| 45 // thread), |
| 46 // - receives VideoFrames and Run()s the callbacks on another thread (supposedly |
| 47 // the render IO thread), which is cached on first frame arrival, |
| 48 // - uses an internal |encoding_thread_| for libvpx interactions, notably for |
| 49 // encoding (which might take some time). |
| 50 // Only VP8 is supported for the time being. |
| 51 class VideoTrackRecorder::VpxEncoder |
| 52 : public base::RefCountedThreadSafe<VpxEncoder> { |
| 53 public: |
| 54 VpxEncoder(const OnFirstFrameCB& on_first_frame_callback); |
| 55 |
| 56 void StartFrameEncode(const scoped_refptr<VideoFrame>& frame, |
| 57 const base::TimeTicks& capture_timestamp); |
| 58 |
| 59 private: |
| 60 friend class base::RefCountedThreadSafe<VpxEncoder>; |
| 61 virtual ~VpxEncoder(); |
| 62 |
| 63 void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame, |
| 64 const base::TimeTicks& capture_timestamp); |
| 65 |
| 66 void OnFrameEncodeCompleted(scoped_ptr<std::string> data, |
| 67 base::TimeDelta timestamp, |
| 68 bool keyframe); |
| 69 |
| 70 void ConfigureVp8Encoding(const gfx::Size& size); |
| 71 |
| 72 // Returns true if |codec_config_| has been filled in at least once. |
| 73 bool IsInitialized() const; |
| 74 |
| 75 // Estimate the frame duration from |frame| and |last_frame_timestamp_|. |
| 76 base::TimeDelta CalculateFrameDuration( |
| 77 const scoped_refptr<VideoFrame>& frame); |
| 78 |
| 79 // Used to check that we are destroyed on the same thread we were created. |
| 80 base::ThreadChecker main_render_thread_checker_; |
| 81 |
| 82 // Task runner where frames to encode and reply callbacks must happen. |
| 83 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; |
| 84 |
| 85 // Used to ping |on_first_frame_callback_|. Used on IO thread only. |
| 86 bool first_frame_received_; |
| 87 // Callbacks that should be exercised on IO thread. |
| 88 const OnFirstFrameCB on_first_frame_callback_; |
| 89 OnEncodedVideoCB on_encoded_video_callback_; |
| 90 |
| 91 // Thread for encoding. Active as long as VpxEncoder exists. All variables |
| 92 // below this are used in this thread. |
| 93 base::Thread encoding_thread_; |
| 94 // VP8 internal objects: configuration, encoder and Vpx Image wrapper. |
| 95 vpx_codec_enc_cfg_t codec_config_; |
| 96 vpx_codec_ctx_t encoder_; |
| 97 // Origin of times for frame timestamps. |
| 98 base::TimeTicks first_frame_timestamp_; |
| 99 // The |VideoFrame::timestamp()| of the last encoded frame. This is used to |
| 100 // predict the duration of the next frame. |
| 101 base::TimeDelta last_frame_timestamp_; |
| 102 }; |
| 103 |
| 104 VideoTrackRecorder::VpxEncoder::VpxEncoder( |
| 105 const OnFirstFrameCB& on_first_frame_callback) |
| 106 : first_frame_received_(false), |
| 107 on_first_frame_callback_(on_first_frame_callback), |
| 108 encoding_thread_("EncodingThread") { |
| 109 DCHECK(!on_first_frame_callback_.is_null()); |
| 110 |
| 111 codec_config_.g_timebase.den = 0; // Not initialized. |
| 112 |
| 113 DCHECK(!encoding_thread_.IsRunning()); |
| 114 encoding_thread_.Start(); |
| 115 } |
| 116 |
| 117 void VideoTrackRecorder::VpxEncoder::StartFrameEncode( |
| 118 const scoped_refptr<VideoFrame>& frame, |
| 119 const base::TimeTicks& capture_timestamp) { |
| 120 if (!first_frame_received_) { |
| 121 // Cache the thread sending frames on first frame arrival. |
| 122 DCHECK(!origin_task_runner_.get()); |
| 123 origin_task_runner_ = base::MessageLoop::current()->task_runner(); |
| 124 |
| 125 on_encoded_video_callback_ = |
| 126 on_first_frame_callback_.Run(frame->visible_rect().size(), |
| 127 GetFrameRate(frame)); |
| 128 first_frame_timestamp_ = capture_timestamp; |
| 129 first_frame_received_ = true; |
| 130 } |
| 131 DCHECK(origin_task_runner_->BelongsToCurrentThread()); |
| 132 encoding_thread_.task_runner()->PostTask(FROM_HERE, |
| 133 base::Bind(&VpxEncoder::EncodeOnEncodingThread, |
| 134 this, frame, capture_timestamp)); |
| 135 } |
| 136 |
| 137 VideoTrackRecorder::VpxEncoder::~VpxEncoder() { |
| 138 DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| 139 DCHECK(encoding_thread_.IsRunning()); |
| 140 encoding_thread_.Stop(); |
| 141 } |
| 142 |
| 143 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread( |
| 144 const scoped_refptr<VideoFrame>& frame, |
| 145 const base::TimeTicks& capture_timestamp) { |
| 146 TRACE_EVENT0("video", |
| 147 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread"); |
| 148 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); |
| 149 |
| 150 const gfx::Size frame_size = frame->visible_rect().size(); |
| 151 if (!IsInitialized() || |
| 152 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) { |
| 153 ConfigureVp8Encoding(frame_size); |
| 154 } |
| 155 |
| 156 vpx_image_t vpx_image; |
| 157 vpx_image_t* const result = vpx_img_wrap(&vpx_image, |
| 158 VPX_IMG_FMT_I420, |
| 159 frame_size.width(), |
| 160 frame_size.height(), |
| 161 1 /* align */, |
| 162 frame->data(VideoFrame::kYPlane)); |
| 163 DCHECK_EQ(result, &vpx_image); |
| 164 vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane); |
| 165 vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane); |
| 166 vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane); |
| 167 vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane); |
| 168 vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane); |
| 169 vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane); |
| 170 |
| 171 const base::TimeDelta duration = CalculateFrameDuration(frame); |
| 172 // Encode the frame. The presentation time stamp argument here is fixed to |
| 173 // zero to force the encoder to base its single-frame bandwidth calculations |
| 174 // entirely on |predicted_frame_duration|. |
| 175 const vpx_codec_err_t ret = vpx_codec_encode(&encoder_, |
| 176 &vpx_image, |
| 177 0 /* pts */, |
| 178 duration.InMicroseconds(), |
| 179 kNoFlags, |
| 180 VPX_DL_REALTIME); |
| 181 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #" |
| 182 << vpx_codec_error(&encoder_) << " -" |
| 183 << vpx_codec_error_detail(&encoder_); |
| 184 |
| 185 scoped_ptr<std::string> data(new std::string); |
| 186 bool keyframe = false; |
| 187 vpx_codec_iter_t iter = NULL; |
| 188 const vpx_codec_cx_pkt_t* pkt = NULL; |
| 189 while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != NULL) { |
| 190 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) |
| 191 continue; |
| 192 data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz); |
| 193 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; |
| 194 break; |
| 195 } |
| 196 const int timestamp = |
| 197 (capture_timestamp - first_frame_timestamp_).InMilliseconds(); |
| 198 origin_task_runner_->PostTask(FROM_HERE, |
| 199 base::Bind(&VpxEncoder::OnFrameEncodeCompleted, |
| 200 this, |
| 201 base::Passed(&data), |
| 202 base::TimeDelta::FromMilliseconds(timestamp), |
| 203 keyframe)); |
| 204 } |
| 205 |
| 206 void VideoTrackRecorder::VpxEncoder::OnFrameEncodeCompleted( |
| 207 scoped_ptr<std::string> data, |
| 208 base::TimeDelta timestamp, |
| 209 bool keyframe) { |
| 210 DVLOG(1) << (keyframe ? "" : "non ") << "keyframe " |
| 211 << timestamp.InMilliseconds() << " ms - " << data->length() << "B "; |
| 212 DCHECK(origin_task_runner_->BelongsToCurrentThread()); |
| 213 on_encoded_video_callback_.Run(base::StringPiece(*data), timestamp, keyframe); |
| 214 } |
| 215 |
| 216 void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding( |
| 217 const gfx::Size& size) { |
| 218 if (IsInitialized()) { |
| 219 // TODO(mcasas): Workaround for certain bug. |
| 220 DVLOG(1) << "Destroying/Re-Creating encoder for larger frame size: " |
| 221 << gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString() |
| 222 << " --> " << size.ToString(); |
| 223 //vpx_codec_destroy(&encoder_); |
| 224 } |
| 225 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx(); |
| 226 vpx_codec_enc_config_default(interface, &codec_config_, 0 /* reserved */); |
| 227 |
| 228 // Adjust default bit rate to account for the actual size. |
| 229 codec_config_.rc_target_bitrate = size.width() * size.height() * |
| 230 codec_config_.rc_target_bitrate / |
| 231 codec_config_.g_w / codec_config_.g_h; |
| 232 DCHECK(size.width()); |
| 233 DCHECK(size.height()); |
| 234 codec_config_.g_w = size.width(); |
| 235 codec_config_.g_h = size.height(); |
| 236 codec_config_.g_pass = VPX_RC_ONE_PASS; |
| 237 |
| 238 // Timebase is the smallest interval used by the stream, can be set to the |
| 239 // frame rate or just to milliseconds. |
| 240 codec_config_.g_timebase.num = 1; |
| 241 codec_config_.g_timebase.den = base::Time::kMillisecondsPerSecond; |
| 242 |
| 243 // Let the encoder decide where to place the Keyframes, between min and max. |
| 244 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/ |
| 245 // max distance out of necessity. Due to http://crbug.com/440223, decoding |
| 246 // fails after 30,000 non-key frames, so force an "unnecessary" key-frame |
| 247 // every 10,000 frames. |
| 248 codec_config_.kf_mode = VPX_KF_AUTO; |
| 249 codec_config_.kf_min_dist = 10000; |
| 250 codec_config_.kf_max_dist = 10000; |
| 251 |
| 252 // Number of frames to consume before producing output. |
| 253 codec_config_.g_lag_in_frames = 0; |
| 254 |
| 255 const vpx_codec_err_t ret = vpx_codec_enc_init(&encoder_, interface, |
| 256 &codec_config_, kNoFlags); |
| 257 DCHECK_EQ(VPX_CODEC_OK, ret); |
| 258 } |
| 259 |
| 260 bool VideoTrackRecorder::VpxEncoder::IsInitialized() const { |
| 261 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); |
| 262 return codec_config_.g_timebase.den != 0; |
| 263 } |
| 264 |
| 265 base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration( |
| 266 const scoped_refptr<VideoFrame>& frame) { |
| 267 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); |
| 268 |
| 269 base::TimeDelta predicted_frame_duration; |
| 270 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION, |
| 271 &predicted_frame_duration) || |
| 272 predicted_frame_duration <= base::TimeDelta()) { |
| 273 // The source of the video frame did not provide the frame duration. Use |
| 274 // the actual amount of time between the current and previous frame as a |
| 275 // prediction for the next frame's duration. |
| 276 // TODO(mcasas): This duration estimation could lead to artifacts if the |
| 277 // cadence of the received stream is compromised (e.g. camera freeze, pause, |
| 278 // remote packet loss). Investigate using GetFrameRate() in this case. |
| 279 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_; |
| 280 } |
| 281 last_frame_timestamp_ = frame->timestamp(); |
| 282 const base::TimeDelta kMinFrameDuration = |
| 283 base::TimeDelta::FromMilliseconds(1); |
| 284 return std::max(predicted_frame_duration, kMinFrameDuration); |
| 285 } |
| 286 |
| 287 VideoTrackRecorder::VideoTrackRecorder( |
| 288 const blink::WebMediaStreamTrack& track, |
| 289 const OnFirstFrameCB& on_first_frame_cb) |
| 290 : track_(track), |
| 291 encoder_(new VpxEncoder(on_first_frame_cb)) { |
| 292 DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| 293 DCHECK(track.extraData()); |
| 294 AddToVideoTrack(this, |
| 295 base::Bind(&VpxEncoder::StartFrameEncode, encoder_), track_); |
| 296 } |
| 297 |
| 298 VideoTrackRecorder::~VideoTrackRecorder() { |
| 299 DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| 300 RemoveFromVideoTrack(this, track_); |
| 301 } |
| 302 |
| 303 void VideoTrackRecorder::StartFrameEncodeForTesting( |
| 304 const scoped_refptr<VideoFrame>& frame, |
| 305 const base::TimeTicks& capture_timestamp) { |
| 306 encoder_->StartFrameEncode(frame, capture_timestamp); |
| 307 } |
| 308 |
| 309 } // namespace content |
| OLD | NEW |