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 #define VPX_CODEC_DISABLE_COMPAT 1 | |
|
miu
2015/07/27 22:30:09
Is this legal? You're changing a build-time confi
mcasas
2015/07/31 11:26:27
I guess so, is used all over the codebase [1] incl
miu
2015/08/04 04:06:30
Weird. Well, okay I guess. ;-)
| |
| 18 #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h" | |
| 19 #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h" | |
| 20 } | |
| 21 | |
| 22 using media::VideoFrame; | |
| 23 using media::VideoFrameMetadata; | |
| 24 | |
| 25 namespace content { | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // Originally from remoting/codec/scoped_vpx_codec.h | |
| 30 struct VpxCodecDeleter { | |
| 31 void operator()(vpx_codec_ctx_t* codec) { | |
| 32 if (!codec) | |
| 33 return; | |
| 34 const vpx_codec_err_t ret = vpx_codec_destroy(codec); | |
| 35 DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to destroy codec"; | |
| 36 delete codec; | |
| 37 } | |
| 38 }; | |
| 39 | |
| 40 const vpx_codec_flags_t kNoFlags = 0; | |
| 41 const uint32_t kDefaultFrameDurationInMilliseconds = 30u; | |
| 42 | |
| 43 static scoped_ptr<vpx_image_t> InitVp8Encoding(const gfx::Size& size, | |
| 44 vpx_codec_ctx_t* codec_context) { | |
| 45 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx(); | |
| 46 scoped_ptr<vpx_codec_enc_cfg_t> codec_config(new vpx_codec_enc_cfg_t); | |
| 47 vpx_codec_enc_config_default(interface, codec_config.get(), 0 /* reserved */); | |
| 48 | |
| 49 // Adjust default bit rate to account for the actual size. | |
| 50 codec_config->rc_target_bitrate = size.width() * size.height() * | |
| 51 codec_config->rc_target_bitrate / | |
| 52 codec_config->g_w / codec_config->g_h; | |
| 53 DCHECK(size.width()); | |
| 54 DCHECK(size.height()); | |
| 55 codec_config->g_w = size.width(); | |
| 56 codec_config->g_h = size.height(); | |
| 57 codec_config->g_pass = VPX_RC_ONE_PASS; | |
| 58 | |
| 59 // Timebase is the smallest interval used by the stream, can be set to the | |
| 60 // frame rate or just to milliseconds. | |
| 61 codec_config->g_timebase.num = 1; | |
| 62 codec_config->g_timebase.den = base::Time::kMillisecondsPerSecond; | |
| 63 | |
| 64 // Let the encoder decide where to place the Keyframes, between min and max. | |
| 65 // Due to http://crbug.com/440223, decoding fails after 30,000 non-key | |
| 66 // frames, so force an "unnecessary" key-frame every 10,000 frames. | |
| 67 codec_config->kf_mode = VPX_KF_AUTO; | |
| 68 codec_config->kf_min_dist = 10000; | |
| 69 codec_config->kf_max_dist = 10000; | |
| 70 | |
| 71 // Number of frames to consume before producing output. | |
| 72 codec_config->g_lag_in_frames = 0; | |
| 73 | |
| 74 const vpx_codec_err_t ret = vpx_codec_enc_init(codec_context, interface, | |
| 75 codec_config.get(), kNoFlags); | |
| 76 DCHECK_EQ(VPX_CODEC_OK, ret); | |
| 77 | |
| 78 scoped_ptr<vpx_image_t> image(new vpx_image_t()); | |
| 79 vpx_image_t* const returned_image_ptr = | |
| 80 vpx_img_wrap(image.get(), VPX_IMG_FMT_I420, size.width(), size.height(), | |
| 81 1 /* align */, nullptr /* img_data */); | |
| 82 DCHECK_EQ(image.get(), returned_image_ptr); | |
| 83 return image.Pass(); | |
| 84 } | |
| 85 | |
| 86 static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) { | |
| 87 double frame_rate = 0.0f; | |
| 88 base::IgnoreResult(video_frame->metadata()->GetDouble( | |
| 89 VideoFrameMetadata::FRAME_RATE, &frame_rate)); | |
| 90 return frame_rate; | |
| 91 } | |
| 92 | |
| 93 } // anonymous namespace | |
| 94 | |
| 95 // Inner class holding all libvpx configuration and encoding received frames. | |
| 96 // This class is created and destroyed on its parent's thread: the main render | |
| 97 // thread. It receives VideoFrames and must Run() the callbacks on IO thread, | |
| 98 // and finally uses an internal |encoding_thread_| for libvpx interactions, | |
| 99 // notably for encoding (which might take some time). All this forces a bit of | |
| 100 // hopping back and forth between threads. | |
| 101 // Only VP8 is supported for the time being. | |
| 102 // TODO(mcasas): Consider not getting |io_task_runner| in ctor and instead | |
| 103 // caching it on first EncodeOnIo(). | |
| 104 class VideoTrackRecorder::VpxEncoder | |
| 105 : public base::RefCountedThreadSafe<VpxEncoder> { | |
| 106 public: | |
| 107 VpxEncoder(const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, | |
| 108 const OnFirstFrameCB& on_first_frame_callback, | |
| 109 const OnEncodedVideoCB& on_encoded_video_callback); | |
| 110 | |
| 111 void EncodeOnIo(const scoped_refptr<VideoFrame>& frame, | |
| 112 const base::TimeTicks& capture_timestamp); | |
| 113 | |
| 114 private: | |
| 115 friend class base::RefCountedThreadSafe<VpxEncoder>; | |
| 116 virtual ~VpxEncoder(); | |
| 117 | |
| 118 void FinishConfigurationOnEncodingThread(const gfx::Size frame_size, | |
| 119 const base::TimeTicks& timestamp); | |
| 120 | |
| 121 void EncodeOnEncodingThread(bool force_keyframe, | |
| 122 const scoped_refptr<VideoFrame>& frame, | |
| 123 const base::TimeTicks& capture_timestamp); | |
| 124 | |
| 125 void FinishEncodingOnIo(scoped_ptr<std::string> data, | |
| 126 base::TimeDelta timestamp, | |
| 127 bool keyframe); | |
| 128 | |
| 129 // Used to check that we are destroyed on the same thread we were created. | |
| 130 base::ThreadChecker main_render_thread_checker_; | |
| 131 | |
| 132 // Render IO thread task runner for posting tasks and DCHECK()s. | |
| 133 const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; | |
| 134 | |
| 135 // Configuration happens on first frame arrival. Used on IO thread only. | |
|
miu
2015/07/27 22:30:09
This won't work. MediaStreamVideoSinks must handl
mcasas
2015/07/31 11:26:27
Done. Shamelessly taken over big chunks of the cod
| |
| 136 bool configuration_done_; | |
| 137 // Written once on IO thread and used (read) from capture thread. | |
| 138 uint64_t track_index_; | |
| 139 // Callbacks that should be exercised on IO thread. | |
| 140 const OnFirstFrameCB on_first_frame_callback_; | |
| 141 const OnEncodedVideoCB on_encoded_video_callback_; | |
| 142 | |
| 143 // Thread for encoding. Active between first frame reception and class | |
| 144 // destruction. All variables below this are used in this thread. | |
| 145 base::Thread encoding_thread_; | |
| 146 // VP8 internal objects: codec configuration and Vpx Image wrapper. | |
| 147 scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> codec_; | |
| 148 scoped_ptr<vpx_image_t> vpx_image_; | |
| 149 // Origin of times for frame timestamps. | |
| 150 base::TimeTicks timestamp_base_; | |
|
miu
2015/07/27 22:30:09
How about first_frame_timestamp_?
mcasas
2015/07/31 11:26:27
Done.
| |
| 151 }; | |
| 152 | |
| 153 VideoTrackRecorder::VpxEncoder::VpxEncoder( | |
| 154 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, | |
| 155 const OnFirstFrameCB& on_first_frame_callback, | |
| 156 const OnEncodedVideoCB& on_encoded_video_callback) | |
| 157 : io_task_runner_(io_task_runner), | |
| 158 configuration_done_(false), | |
| 159 track_index_(0), | |
| 160 on_first_frame_callback_(on_first_frame_callback), | |
| 161 on_encoded_video_callback_(on_encoded_video_callback), | |
| 162 encoding_thread_("EncodingThread"), | |
| 163 codec_(new vpx_codec_ctx_t) { | |
| 164 DCHECK(!on_first_frame_callback_.is_null()); | |
| 165 DCHECK(!on_encoded_video_callback_.is_null()); | |
| 166 DCHECK(!encoding_thread_.IsRunning()); | |
| 167 encoding_thread_.Start(); | |
| 168 } | |
| 169 | |
| 170 VideoTrackRecorder::VpxEncoder::~VpxEncoder() { | |
| 171 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 172 DCHECK(encoding_thread_.IsRunning()); | |
| 173 encoding_thread_.Stop(); | |
| 174 } | |
| 175 | |
| 176 void VideoTrackRecorder::VpxEncoder::EncodeOnIo( | |
|
miu
2015/07/27 22:30:09
Ah! So the encoding doesn't actually happen on th
mcasas
2015/07/31 11:26:27
Good suggestion, done.
| |
| 177 const scoped_refptr<VideoFrame>& frame, | |
| 178 const base::TimeTicks& capture_timestamp) { | |
| 179 DCHECK(io_task_runner_->BelongsToCurrentThread()); | |
| 180 | |
| 181 bool force_keyframe = false; | |
| 182 if (!configuration_done_) { | |
| 183 track_index_ = on_first_frame_callback_.Run(frame->coded_size(), | |
|
miu
2015/07/27 22:30:09
This must be the visible_rect().size(), not coded_
mcasas
2015/07/31 11:26:27
Done.
| |
| 184 GetFrameRate(frame)); | |
| 185 encoding_thread_.task_runner()->PostTask(FROM_HERE, | |
| 186 base::Bind(&VpxEncoder::FinishConfigurationOnEncodingThread, | |
| 187 this, frame->coded_size(), capture_timestamp)); | |
| 188 force_keyframe = true; | |
| 189 configuration_done_ = true; | |
| 190 } | |
| 191 encoding_thread_.task_runner()->PostTask(FROM_HERE, | |
| 192 base::Bind(&VpxEncoder::EncodeOnEncodingThread, | |
| 193 this, force_keyframe, frame, capture_timestamp)); | |
| 194 } | |
| 195 | |
| 196 void VideoTrackRecorder::VpxEncoder::FinishConfigurationOnEncodingThread( | |
| 197 const gfx::Size frame_size, | |
| 198 const base::TimeTicks& timestamp) { | |
| 199 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); | |
| 200 timestamp_base_ = timestamp; | |
| 201 | |
| 202 vpx_image_ = InitVp8Encoding(frame_size, codec_.get()); | |
| 203 DCHECK(vpx_image_.get()); | |
| 204 } | |
| 205 | |
| 206 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread( | |
| 207 bool force_keyframe, | |
|
miu
2015/07/27 22:30:09
You don't need to force a key frame. VP8 will aut
mcasas
2015/07/31 11:26:27
Done.
| |
| 208 const scoped_refptr<VideoFrame>& frame, | |
| 209 const base::TimeTicks& capture_timestamp) { | |
| 210 TRACE_EVENT0("video", | |
| 211 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread"); | |
| 212 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread()); | |
| 213 | |
| 214 DCHECK(frame->visible_data(VideoFrame::kYPlane)); | |
| 215 vpx_image_->planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane); | |
| 216 vpx_image_->planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane); | |
| 217 vpx_image_->planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane); | |
| 218 vpx_image_->stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane); | |
| 219 vpx_image_->stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane); | |
| 220 vpx_image_->stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane); | |
| 221 | |
| 222 // TODO(mcasas): Use VideoFrameMetadata::FRAME_DURATION if available. | |
|
miu
2015/07/27 22:30:09
Quality and target bitrate will be all wrong for m
mcasas
2015/07/31 11:26:27
Done.
| |
| 223 uint32_t duration = kDefaultFrameDurationInMilliseconds; | |
| 224 const double frame_rate = GetFrameRate(frame); | |
| 225 if (frame_rate) | |
| 226 duration = base::Time::kMillisecondsPerSecond / frame_rate; | |
| 227 const int timestamp = (capture_timestamp - timestamp_base_).InMilliseconds(); | |
| 228 const vpx_enc_frame_flags_t flags = | |
| 229 kNoFlags | (force_keyframe ? VPX_EFLAG_FORCE_KF : 0u); | |
| 230 const vpx_codec_err_t ret = vpx_codec_encode(codec_.get(), vpx_image_.get(), | |
| 231 timestamp, duration, flags, VPX_DL_REALTIME); | |
| 232 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #" | |
| 233 << vpx_codec_error(codec_.get()) << " -" | |
| 234 << vpx_codec_error_detail(codec_.get()); | |
| 235 | |
| 236 scoped_ptr<std::string> data(new std::string); | |
| 237 bool keyframe = false; | |
| 238 vpx_codec_iter_t iter = NULL; | |
| 239 const vpx_codec_cx_pkt_t* pkt = NULL; | |
| 240 while ((pkt = vpx_codec_get_cx_data(codec_.get(), &iter)) != NULL) { | |
| 241 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) | |
| 242 continue; | |
| 243 data->append(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz); | |
|
miu
2015/07/27 22:30:09
Use assign() instead of append(). We don't want t
mcasas
2015/07/31 11:26:27
Done.
| |
| 244 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; | |
| 245 break; | |
| 246 } | |
| 247 io_task_runner_->PostTask(FROM_HERE, | |
| 248 base::Bind(&VpxEncoder::FinishEncodingOnIo, | |
| 249 this, | |
| 250 base::Passed(&data), | |
| 251 base::TimeDelta::FromMilliseconds(timestamp), | |
| 252 keyframe )); | |
| 253 } | |
| 254 | |
| 255 void VideoTrackRecorder::VpxEncoder::FinishEncodingOnIo( | |
| 256 scoped_ptr<std::string> data, | |
| 257 base::TimeDelta timestamp, | |
| 258 bool keyframe) { | |
| 259 DVLOG(1) << (keyframe ? "" : "non ") << "keyframe " | |
| 260 << timestamp.InMilliseconds() << " ms - " << data->length() << "B "; | |
| 261 DCHECK(io_task_runner_->BelongsToCurrentThread()); | |
| 262 on_encoded_video_callback_.Run(track_index_, base::StringPiece(*data), | |
| 263 timestamp, keyframe); | |
| 264 } | |
| 265 | |
| 266 VideoTrackRecorder::VideoTrackRecorder( | |
| 267 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, | |
| 268 const blink::WebMediaStreamTrack& track, | |
| 269 const OnFirstFrameCB& on_first_frame_cb, | |
| 270 const OnEncodedVideoCB& on_encoded_video_cb) | |
| 271 : track_(track), | |
| 272 encoder_(new VpxEncoder(io_task_runner, | |
| 273 on_first_frame_cb, | |
| 274 on_encoded_video_cb)) { | |
| 275 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 276 DCHECK(track.extraData()); | |
| 277 AddToVideoTrack(this, base::Bind(&VpxEncoder::EncodeOnIo, encoder_), track_); | |
| 278 } | |
| 279 | |
| 280 VideoTrackRecorder::~VideoTrackRecorder() { | |
| 281 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 282 RemoveFromVideoTrack(this, track_); | |
| 283 } | |
| 284 | |
| 285 void VideoTrackRecorder::EncodeOnIoForTesting( | |
| 286 const scoped_refptr<VideoFrame>& frame, | |
| 287 const base::TimeTicks& capture_timestamp) { | |
| 288 encoder_->EncodeOnIo(frame, capture_timestamp); | |
| 289 } | |
| 290 | |
| 291 } // namespace content | |
| OLD | NEW |