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/time/time.h" | |
| 10 #include "content/child/child_process.h" | |
| 11 #include "media/base/bind_to_current_loop.h" | |
| 12 #include "media/base/video_frame.h" | |
| 13 | |
| 14 extern "C" { | |
| 15 #define VPX_CODEC_DISABLE_COMPAT 1 | |
| 16 #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h" | |
| 17 #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h" | |
| 18 } | |
| 19 | |
| 20 using media::VideoFrame; | |
| 21 using media::VideoFrameMetadata; | |
| 22 | |
| 23 namespace content { | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 // Originally from remoting/codec/scoped_vpx_codec.h | |
| 28 struct VpxCodecDeleter { | |
| 29 void operator()(vpx_codec_ctx_t* codec) { | |
| 30 if (!codec) | |
| 31 return; | |
| 32 const vpx_codec_err_t ret = vpx_codec_destroy(codec); | |
| 33 CHECK_EQ(VPX_CODEC_OK, ret) << "Failed to destroy codec"; | |
|
Tom Finegan
2015/07/23 22:18:39
Won't this kill a tab? Killing a tab for a weird v
mcasas
2015/07/24 15:00:09
Yes, changed.
Btw this comes from [1], eternal gl
| |
| 34 delete codec; | |
| 35 } | |
| 36 }; | |
| 37 | |
| 38 const vpx_codec_flags_t kNoFlags = 0; | |
| 39 const uint32_t kDefaultFrameDurationInMilliseconds = 30u; | |
| 40 | |
| 41 static scoped_ptr<vpx_image_t> InitVp8Encoding(const gfx::Size& size, | |
| 42 vpx_codec_ctx_t* codec_context) { | |
|
Tom Finegan
2015/07/23 22:18:39
A static method in an anon namespace seems unneces
mcasas
2015/07/24 15:00:10
It would but content rules "encourage" putting sta
Tom Finegan
2015/07/24 20:56:16
Ah, ok. I was just going on my admittedly foggy me
mcasas
2015/07/27 11:41:00
Acknowledged.
| |
| 43 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx(); | |
| 44 scoped_ptr<vpx_codec_enc_cfg_t> codec_config(new vpx_codec_enc_cfg_t); | |
| 45 vpx_codec_enc_config_default(interface, codec_config.get(), 0 /* reserved */); | |
| 46 | |
| 47 // Adjust default bit rate to account for the actual size. | |
| 48 codec_config->rc_target_bitrate = size.width() * size.height() * | |
| 49 codec_config->rc_target_bitrate / | |
| 50 codec_config->g_w / codec_config->g_h; | |
| 51 DCHECK(size.width()); | |
| 52 DCHECK(size.height()); | |
| 53 codec_config->g_w = size.width(); | |
| 54 codec_config->g_h = size.height(); | |
| 55 codec_config->g_pass = VPX_RC_ONE_PASS; | |
| 56 | |
| 57 // Millisecond granurality. | |
| 58 codec_config->g_timebase.num = 1; | |
| 59 codec_config->g_timebase.den = base::Time::kMillisecondsPerSecond * 90; | |
|
Tom Finegan
2015/07/23 22:18:38
90000? RTP?
mcasas
2015/07/24 15:00:09
Might have taken it inadvertently, removed :)
| |
| 60 | |
| 61 // Let the encoder decide where to place the Keyframes, between min and max. | |
| 62 // Due to http://crbug.com/440223, decoding fails after 30,000 non-key | |
| 63 // frames, so force an "unnecessary" key-frame every 10,000 frames. | |
| 64 codec_config->kf_mode = VPX_KF_AUTO; | |
| 65 codec_config->kf_min_dist = 10000; | |
| 66 codec_config->kf_max_dist = 10000; | |
|
Tom Finegan
2015/07/23 22:18:39
note that you can end up with more keyframes than
mcasas
2015/07/24 15:00:09
Hmmkay, should I add a note?
Tom Finegan
2015/07/24 20:56:16
Doesn't hurt to add something like, "When kf_mode
| |
| 67 | |
|
Tom Finegan
2015/07/23 22:18:39
One more config flag that should always be set by
mcasas
2015/07/24 15:00:10
Done.
| |
| 68 const vpx_codec_err_t ret = vpx_codec_enc_init(codec_context, interface, | |
| 69 codec_config.get(), kNoFlags); | |
| 70 DCHECK_EQ(VPX_CODEC_OK, ret); | |
| 71 | |
| 72 scoped_ptr<vpx_image_t> image(new vpx_image_t()); | |
| 73 vpx_image_t* const returned_image_ptr = | |
| 74 vpx_img_wrap(image.get(), VPX_IMG_FMT_I420, size.width(), size.height(), | |
| 75 1 /* align */, nullptr /* img_data */); | |
| 76 DCHECK_EQ(image.get(), returned_image_ptr); | |
| 77 return image.Pass(); | |
| 78 } | |
| 79 | |
| 80 } // anonymous namespace | |
| 81 | |
| 82 // Inner class holding all libvpx configuration and encoding received frames | |
| 83 class VideoTrackRecorder::VpxEncoder | |
| 84 : public base::RefCountedThreadSafe<VpxEncoder> { | |
| 85 public: | |
| 86 VpxEncoder(scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, | |
| 87 const OnFirstFrameCB& on_first_frame_callback, | |
| 88 const OnEncodedVideoCB& on_encoded_video_callback); | |
| 89 | |
| 90 void EncodeOnIo(const scoped_refptr<VideoFrame>& frame, | |
| 91 const base::TimeTicks& estimated_capture_time); | |
| 92 | |
| 93 bool configuration_done() const { return configuration_done_; } | |
| 94 | |
| 95 private: | |
| 96 friend class base::RefCountedThreadSafe<VpxEncoder>; | |
| 97 virtual ~VpxEncoder(); | |
| 98 | |
| 99 // Used to DCHECK that we are destructed where we were constructed. | |
| 100 base::ThreadChecker main_render_thread_checker_; | |
| 101 const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; | |
| 102 | |
| 103 const OnFirstFrameCB on_first_frame_callback_; | |
| 104 const OnEncodedVideoCB on_encoded_video_callback_; | |
| 105 uint64_t track_index_; | |
| 106 | |
| 107 // VP8 internal objects: codec configuration and Vpx Image wrapper. | |
| 108 scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> codec_; | |
| 109 scoped_ptr<vpx_image_t> vpx_image_; | |
| 110 | |
| 111 // Configuration has to happen on first frame arrival. | |
| 112 bool configuration_done_; | |
| 113 | |
| 114 // Origin of times for frame timestamps. | |
| 115 base::TimeTicks timestamp_base_; | |
| 116 }; | |
| 117 | |
| 118 VideoTrackRecorder::VpxEncoder::VpxEncoder( | |
| 119 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, | |
| 120 const OnFirstFrameCB& on_first_frame_callback, | |
| 121 const OnEncodedVideoCB& on_encoded_video_callback) | |
| 122 : io_task_runner_(io_task_runner), | |
| 123 on_first_frame_callback_(on_first_frame_callback), | |
| 124 on_encoded_video_callback_(on_encoded_video_callback), | |
| 125 track_index_(0), | |
| 126 codec_(new vpx_codec_ctx_t), | |
| 127 configuration_done_(false) { | |
| 128 DCHECK(!on_encoded_video_callback_.is_null()); | |
| 129 } | |
| 130 | |
| 131 VideoTrackRecorder::VpxEncoder::~VpxEncoder() { | |
| 132 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 133 } | |
| 134 | |
| 135 void VideoTrackRecorder::VpxEncoder::EncodeOnIo( | |
| 136 const scoped_refptr<VideoFrame>& frame, | |
| 137 const base::TimeTicks& estimated_capture_time) { | |
| 138 DVLOG(3) << __FUNCTION__ << " " << frame->coded_size().ToString(); | |
| 139 DCHECK(io_task_runner_->BelongsToCurrentThread()); | |
| 140 | |
| 141 vpx_enc_frame_flags_t flags = kNoFlags; | |
| 142 double frame_rate = 0.0f; | |
| 143 if (!configuration_done_) { | |
| 144 vpx_image_ = InitVp8Encoding(frame->coded_size(), codec_.get()); | |
| 145 DCHECK(vpx_image_.get()); | |
| 146 timestamp_base_ = estimated_capture_time; | |
| 147 base::IgnoreResult(frame->metadata()->GetDouble( | |
| 148 VideoFrameMetadata::FRAME_RATE, &frame_rate)); | |
| 149 track_index_ = | |
| 150 on_first_frame_callback_.Run(frame->coded_size(), frame_rate); | |
| 151 | |
| 152 flags |= VPX_EFLAG_FORCE_KF; | |
| 153 configuration_done_ = true; | |
| 154 } | |
| 155 | |
| 156 DCHECK(frame->visible_data(VideoFrame::kYPlane)); | |
| 157 vpx_image_->planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane); | |
| 158 vpx_image_->planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane); | |
| 159 vpx_image_->planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane); | |
| 160 vpx_image_->stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane); | |
| 161 vpx_image_->stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane); | |
| 162 vpx_image_->stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane); | |
| 163 | |
| 164 // TODO(mcasas): Use VideoFrameMetadata::FRAME_DURATION if available. | |
| 165 uint32_t duration = kDefaultFrameDurationInMilliseconds; | |
| 166 base::IgnoreResult(frame->metadata()->GetDouble( | |
| 167 VideoFrameMetadata::FRAME_RATE, &frame_rate)); | |
| 168 if (frame_rate) | |
| 169 duration = base::Time::kMillisecondsPerSecond / frame_rate; | |
| 170 const int timestamp = | |
| 171 (estimated_capture_time - timestamp_base_).InMilliseconds(); | |
| 172 const vpx_codec_err_t ret = | |
| 173 vpx_codec_encode(codec_.get(), vpx_image_.get(), timestamp, duration, | |
| 174 flags, VPX_DL_REALTIME); | |
| 175 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #" | |
| 176 << vpx_codec_error(codec_.get()) << " -" | |
| 177 << vpx_codec_error_detail(codec_.get()); | |
| 178 | |
| 179 std::string data; | |
| 180 bool keyframe = false; | |
| 181 vpx_codec_iter_t iter = NULL; | |
| 182 const vpx_codec_cx_pkt_t* pkt = NULL; | |
| 183 while ((pkt = vpx_codec_get_cx_data(codec_.get(), &iter)) != NULL) { | |
| 184 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) | |
| 185 continue; | |
| 186 data.append(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz); | |
| 187 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; | |
| 188 break; | |
| 189 } | |
| 190 DVLOG(1) << timestamp << " - " << data.length() << "B " | |
| 191 << (keyframe ? "" : "non") << " keyframe"; | |
| 192 on_encoded_video_callback_.Run(track_index_, | |
| 193 base::StringPiece(data), | |
| 194 base::TimeDelta::FromMilliseconds(timestamp), | |
| 195 keyframe); | |
| 196 } | |
| 197 | |
| 198 VideoTrackRecorder::VideoTrackRecorder( | |
| 199 const blink::WebMediaStreamTrack& track, | |
| 200 const OnFirstFrameCB& on_first_frame_cb, | |
| 201 const OnEncodedVideoCB& on_encoded_video_cb) | |
| 202 : track_(track), | |
| 203 encoder_(new VpxEncoder(ChildProcess::current()->io_task_runner(), | |
| 204 on_first_frame_cb, | |
| 205 on_encoded_video_cb)) { | |
| 206 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 207 DCHECK(track.extraData()); | |
| 208 AddToVideoTrack( | |
| 209 this, | |
| 210 base::Bind(&VideoTrackRecorder::VpxEncoder::EncodeOnIo, encoder_), | |
| 211 track_); | |
| 212 } | |
| 213 | |
| 214 VideoTrackRecorder::~VideoTrackRecorder() { | |
| 215 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 216 RemoveFromVideoTrack(this, track_); | |
| 217 } | |
| 218 | |
| 219 void VideoTrackRecorder::EncodeOnIoForTesting( | |
| 220 const scoped_refptr<VideoFrame>& frame, | |
| 221 const base::TimeTicks& estimated_capture_time) { | |
| 222 encoder_->EncodeOnIo(frame, estimated_capture_time); | |
| 223 } | |
| 224 | |
| 225 bool VideoTrackRecorder::IsEncoderConfigurationDoneForTesting() const { | |
| 226 return encoder_->configuration_done(); | |
| 227 } | |
| 228 | |
| 229 } // namespace content | |
| OLD | NEW |