Chromium Code Reviews| 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 const vpx_codec_flags_t kNoFlags = 0; | |
| 31 } // anonymous namespace | |
| 32 | |
| 33 // Inner class encapsulating all libvpx interactions and the encoding+delivery | |
| 34 // of received frames. This class is: | |
| 35 // - created and destroyed on its parent's thread (usually the main render | |
| 36 // thread), | |
| 37 // - receives VideoFrames and Run()s the callbacks on another thread (supposedly | |
| 38 // the render IO thread), which is cached on first frame arrival, | |
| 39 // - uses an internal |encoding_thread_| for libvpx interactions, notably for | |
| 40 // encoding (which might take some time). | |
| 41 // Only VP8 is supported for the time being. | |
| 42 class VideoTrackRecorder::VpxEncoder | |
| 43 : public base::RefCountedThreadSafe<VpxEncoder> { | |
| 44 public: | |
| 45 VpxEncoder(const OnEncodedVideoCB& on_encoded_video_callback); | |
|
miu
2015/08/18 02:08:35
Need explicit keyword here.
mcasas
2015/08/19 00:16:30
Done.
| |
| 46 | |
| 47 void StartFrameEncode(const scoped_refptr<VideoFrame>& frame, | |
| 48 const base::TimeTicks& capture_timestamp); | |
| 49 | |
| 50 private: | |
| 51 friend class base::RefCountedThreadSafe<VpxEncoder>; | |
| 52 virtual ~VpxEncoder(); | |
| 53 | |
| 54 void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame, | |
| 55 const base::TimeTicks& capture_timestamp); | |
| 56 | |
| 57 void OnFrameEncodeCompleted(const scoped_refptr<VideoFrame>& frame, | |
| 58 scoped_ptr<std::string> data, | |
| 59 const base::TimeTicks& capture_timestamp, | |
| 60 bool keyframe); | |
| 61 | |
| 62 void ConfigureVp8Encoding(const gfx::Size& size); | |
| 63 | |
| 64 // Returns true if |codec_config_| has been filled in at least once. | |
| 65 bool IsInitialized() const; | |
| 66 | |
| 67 // Estimate the frame duration from |frame| and |last_frame_timestamp_|. | |
| 68 base::TimeDelta CalculateFrameDuration( | |
| 69 const scoped_refptr<VideoFrame>& frame); | |
| 70 | |
| 71 // Used to check that we are destroyed on the same thread we were created. | |
| 72 base::ThreadChecker main_render_thread_checker_; | |
| 73 | |
| 74 // Task runner where frames to encode and reply callbacks must happen. | |
| 75 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; | |
| 76 | |
| 77 // This callback should be exercised on IO thread. | |
| 78 const OnEncodedVideoCB on_encoded_video_callback_; | |
| 79 | |
| 80 // Thread for encoding. Active as long as VpxEncoder exists. All variables | |
| 81 // below this are used in this thread. | |
| 82 base::Thread encoding_thread_; | |
| 83 // VP8 internal objects: configuration, encoder and Vpx Image wrapper. | |
| 84 vpx_codec_enc_cfg_t codec_config_; | |
| 85 vpx_codec_ctx_t encoder_; | |
| 86 | |
| 87 // The |VideoFrame::timestamp()| of the last encoded frame. This is used to | |
| 88 // predict the duration of the next frame. | |
| 89 base::TimeDelta last_frame_timestamp_; | |
| 90 }; | |
| 91 | |
| 92 VideoTrackRecorder::VpxEncoder::VpxEncoder( | |
| 93 const OnEncodedVideoCB& on_encoded_video_callback) | |
| 94 : on_encoded_video_callback_(on_encoded_video_callback), | |
| 95 encoding_thread_("EncodingThread") { | |
| 96 DCHECK(!on_encoded_video_callback_.is_null()); | |
| 97 | |
| 98 codec_config_.g_timebase.den = 0; // Not initialized. | |
| 99 | |
| 100 DCHECK(!encoding_thread_.IsRunning()); | |
| 101 encoding_thread_.Start(); | |
| 102 } | |
| 103 | |
| 104 void VideoTrackRecorder::VpxEncoder::StartFrameEncode( | |
| 105 const scoped_refptr<VideoFrame>& frame, | |
| 106 const base::TimeTicks& capture_timestamp) { | |
| 107 // Cache the thread sending frames on first frame arrival. | |
| 108 if (!origin_task_runner_.get()) | |
| 109 origin_task_runner_ = base::MessageLoop::current()->task_runner(); | |
| 110 DCHECK(origin_task_runner_->BelongsToCurrentThread()); | |
| 111 | |
| 112 encoding_thread_.task_runner()->PostTask(FROM_HERE, | |
| 113 base::Bind(&VpxEncoder::EncodeOnEncodingThread, | |
| 114 this, frame, capture_timestamp)); | |
| 115 } | |
| 116 | |
| 117 VideoTrackRecorder::VpxEncoder::~VpxEncoder() { | |
| 118 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
|
miu
2015/08/18 02:08:35
This class is ref-counted. Therefore, there's no
mcasas
2015/08/19 00:16:30
Several references are held to VpxEncoder:
1. Vide
miu
2015/08/19 19:43:45
It looks like #3 is the only remaining issue. And
mcasas
2015/08/20 00:37:04
Done.
| |
| 119 DCHECK(encoding_thread_.IsRunning()); | |
| 120 encoding_thread_.Stop(); | |
| 121 } | |
|
miu
2015/08/18 02:08:35
You need to call vpx_codec_destroy() if the encode
mcasas
2015/08/19 00:16:30
I've been digging down the vpx_codec_destroy() f p
Tom Finegan
2015/08/19 04:20:10
2 is fine unless you're going to end up calling vp
miu
2015/08/19 19:43:45
My concern is that the thread calling vpx_codec_en
Tom Finegan
2015/08/19 20:04:11
The main thread waits for all workers to complete
| |
| 122 | |
| 123 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread( | |
| 124 const scoped_refptr<VideoFrame>& frame, | |
| 125 const base::TimeTicks& capture_timestamp) { | |
| 126 TRACE_EVENT0("video", | |
| 127 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread"); | |
| 128 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); | |
| 129 | |
| 130 const gfx::Size frame_size = frame->visible_rect().size(); | |
| 131 if (!IsInitialized() || | |
| 132 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) { | |
| 133 ConfigureVp8Encoding(frame_size); | |
| 134 } | |
| 135 | |
| 136 vpx_image_t vpx_image; | |
| 137 vpx_image_t* const result = vpx_img_wrap(&vpx_image, | |
| 138 VPX_IMG_FMT_I420, | |
| 139 frame_size.width(), | |
| 140 frame_size.height(), | |
| 141 1 /* align */, | |
| 142 frame->data(VideoFrame::kYPlane)); | |
| 143 DCHECK_EQ(result, &vpx_image); | |
| 144 vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane); | |
| 145 vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane); | |
| 146 vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane); | |
| 147 vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane); | |
| 148 vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane); | |
| 149 vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane); | |
| 150 | |
| 151 const base::TimeDelta duration = CalculateFrameDuration(frame); | |
| 152 // Encode the frame. The presentation time stamp argument here is fixed to | |
| 153 // zero to force the encoder to base its single-frame bandwidth calculations | |
| 154 // entirely on |predicted_frame_duration|. | |
| 155 const vpx_codec_err_t ret = vpx_codec_encode(&encoder_, | |
| 156 &vpx_image, | |
| 157 0 /* pts */, | |
| 158 duration.InMicroseconds(), | |
| 159 kNoFlags, | |
| 160 VPX_DL_REALTIME); | |
| 161 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #" | |
| 162 << vpx_codec_error(&encoder_) << " -" | |
| 163 << vpx_codec_error_detail(&encoder_); | |
| 164 | |
| 165 scoped_ptr<std::string> data(new std::string); | |
| 166 bool keyframe = false; | |
| 167 vpx_codec_iter_t iter = NULL; | |
| 168 const vpx_codec_cx_pkt_t* pkt = NULL; | |
| 169 while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != NULL) { | |
| 170 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) | |
| 171 continue; | |
| 172 data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz); | |
| 173 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; | |
| 174 break; | |
| 175 } | |
| 176 origin_task_runner_->PostTask(FROM_HERE, | |
| 177 base::Bind(&VpxEncoder::OnFrameEncodeCompleted, | |
| 178 this, | |
| 179 frame, | |
| 180 base::Passed(&data), | |
| 181 capture_timestamp, | |
| 182 keyframe)); | |
| 183 } | |
| 184 | |
| 185 void VideoTrackRecorder::VpxEncoder::OnFrameEncodeCompleted( | |
| 186 const scoped_refptr<VideoFrame>& frame, | |
| 187 scoped_ptr<std::string> data, | |
| 188 const base::TimeTicks& capture_timestamp, | |
| 189 bool keyframe) { | |
| 190 DVLOG(1) << (keyframe ? "" : "non ") << "keyframe " | |
| 191 << capture_timestamp << " ms - " << data->length() << "B "; | |
| 192 DCHECK(origin_task_runner_->BelongsToCurrentThread()); | |
| 193 on_encoded_video_callback_.Run(frame, | |
| 194 base::StringPiece(*data), | |
| 195 capture_timestamp, | |
| 196 keyframe); | |
| 197 } | |
| 198 | |
| 199 void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding( | |
| 200 const gfx::Size& size) { | |
| 201 if (IsInitialized()) { | |
| 202 // TODO(mcasas): Workaround for certain bug. | |
|
miu
2015/08/18 02:08:35
What bug? ;)
mcasas
2015/08/19 00:16:30
Oops, sorry, both this TODO and the commented out
| |
| 203 DVLOG(1) << "Destroying/Re-Creating encoder for larger frame size: " | |
| 204 << gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString() | |
| 205 << " --> " << size.ToString(); | |
| 206 //vpx_codec_destroy(&encoder_); | |
|
miu
2015/08/18 02:08:35
Is this supposed to be commented out?
mcasas
2015/08/19 00:16:29
Acknowledged.
| |
| 207 } | |
| 208 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx(); | |
| 209 vpx_codec_enc_config_default(interface, &codec_config_, 0 /* reserved */); | |
| 210 | |
| 211 // Adjust default bit rate to account for the actual size. | |
| 212 codec_config_.rc_target_bitrate = size.width() * size.height() * | |
|
miu
2015/08/18 02:08:35
nit: size.GetArea() would be cleaner instead of wi
mcasas
2015/08/19 00:16:29
Done.
| |
| 213 codec_config_.rc_target_bitrate / | |
|
miu
2015/08/18 02:08:35
IMO, I'd be a bit nervous about the default codec
mcasas
2015/08/19 00:16:30
Done.
| |
| 214 codec_config_.g_w / codec_config_.g_h; | |
| 215 DCHECK(size.width()); | |
| 216 DCHECK(size.height()); | |
| 217 codec_config_.g_w = size.width(); | |
| 218 codec_config_.g_h = size.height(); | |
| 219 codec_config_.g_pass = VPX_RC_ONE_PASS; | |
| 220 | |
| 221 // Timebase is the smallest interval used by the stream, can be set to the | |
| 222 // frame rate or just to milliseconds. | |
| 223 codec_config_.g_timebase.num = 1; | |
| 224 codec_config_.g_timebase.den = base::Time::kMillisecondsPerSecond; | |
|
miu
2015/08/18 02:08:35
Microseconds please. Milliseconds is for people w
mcasas
2015/08/19 00:16:30
Done.
| |
| 225 | |
| 226 // Let the encoder decide where to place the Keyframes, between min and max. | |
| 227 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/ | |
| 228 // max distance out of necessity. Due to http://crbug.com/440223, decoding | |
|
miu
2015/08/18 02:08:35
Are you sure this is the same problem? If you use
mcasas
2015/08/19 00:16:30
I don't know. I assumed so due to the similar conf
Tom Finegan
2015/08/19 04:20:10
What's the question? Whether or not a file with 30
mcasas
2015/08/20 00:37:04
Acknowledged.
| |
| 229 // fails after 30,000 non-key frames, so force an "unnecessary" key-frame | |
| 230 // every 10,000 frames. | |
| 231 codec_config_.kf_mode = VPX_KF_AUTO; | |
| 232 codec_config_.kf_min_dist = 10000; | |
|
miu
2015/08/18 02:08:35
If this really is needed, I'd suggest setting the
mcasas
2015/08/19 00:16:29
Done.
| |
| 233 codec_config_.kf_max_dist = 10000; | |
| 234 | |
| 235 // Number of frames to consume before producing output. | |
| 236 codec_config_.g_lag_in_frames = 0; | |
| 237 | |
|
miu
2015/08/18 02:08:35
You may also want to set codec_config_.g_threads.
mcasas
2015/08/19 00:16:30
Done.
| |
| 238 const vpx_codec_err_t ret = vpx_codec_enc_init(&encoder_, interface, | |
| 239 &codec_config_, kNoFlags); | |
| 240 DCHECK_EQ(VPX_CODEC_OK, ret); | |
| 241 } | |
| 242 | |
| 243 bool VideoTrackRecorder::VpxEncoder::IsInitialized() const { | |
| 244 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); | |
| 245 return codec_config_.g_timebase.den != 0; | |
| 246 } | |
| 247 | |
| 248 base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration( | |
| 249 const scoped_refptr<VideoFrame>& frame) { | |
| 250 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); | |
| 251 | |
| 252 base::TimeDelta predicted_frame_duration; | |
| 253 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION, | |
| 254 &predicted_frame_duration) || | |
| 255 predicted_frame_duration <= base::TimeDelta()) { | |
| 256 // The source of the video frame did not provide the frame duration. Use | |
| 257 // the actual amount of time between the current and previous frame as a | |
| 258 // prediction for the next frame's duration. | |
| 259 // TODO(mcasas): This duration estimation could lead to artifacts if the | |
| 260 // cadence of the received stream is compromised (e.g. camera freeze, pause, | |
| 261 // remote packet loss). Investigate using GetFrameRate() in this case. | |
| 262 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_; | |
| 263 } | |
| 264 last_frame_timestamp_ = frame->timestamp(); | |
| 265 const base::TimeDelta kMinFrameDuration = | |
| 266 base::TimeDelta::FromMilliseconds(1); | |
| 267 return std::max(predicted_frame_duration, kMinFrameDuration); | |
|
miu
2015/08/18 02:08:35
Suggest you upper-bound this as well. It's possib
mcasas
2015/08/19 00:16:29
Done.
| |
| 268 } | |
| 269 | |
| 270 VideoTrackRecorder::VideoTrackRecorder( | |
| 271 const blink::WebMediaStreamTrack& track, | |
| 272 const OnEncodedVideoCB& on_encoded_video_callback) | |
| 273 : track_(track), | |
| 274 encoder_(new VpxEncoder(on_encoded_video_callback)) { | |
| 275 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 276 DCHECK(track.extraData()); | |
| 277 AddToVideoTrack(this, | |
| 278 base::Bind(&VpxEncoder::StartFrameEncode, encoder_), track_); | |
| 279 } | |
| 280 | |
| 281 VideoTrackRecorder::~VideoTrackRecorder() { | |
| 282 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 283 RemoveFromVideoTrack(this, track_); | |
| 284 } | |
| 285 | |
| 286 void VideoTrackRecorder::StartFrameEncodeForTesting( | |
| 287 const scoped_refptr<VideoFrame>& frame, | |
| 288 const base::TimeTicks& capture_timestamp) { | |
| 289 encoder_->StartFrameEncode(frame, capture_timestamp); | |
| 290 } | |
| 291 | |
| 292 } // namespace content | |
| OLD | NEW |