Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: content/renderer/media/video_track_recorder.cc

Issue 1233033002: MediaStream: Adding VideoTrackRecorder class and unittests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: tomfinegan@ comments, added a capture thread Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698