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

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: miu@s comments; rebase; reverted unrelated code cleanups 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/sys_info.h"
10 #include "base/threading/thread.h"
11 #include "base/time/time.h"
12 #include "base/trace_event/trace_event.h"
13 #include "content/child/child_process.h"
14 #include "media/base/bind_to_current_loop.h"
15 #include "media/base/video_frame.h"
16
17 extern "C" {
18 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
19 // backwards compatibility for legacy applications using the library.
20 #define VPX_CODEC_DISABLE_COMPAT 1
21 #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
22 #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
23 }
24
25 using media::VideoFrame;
26 using media::VideoFrameMetadata;
27
28 namespace content {
29
30 namespace {
31 const vpx_codec_flags_t kNoFlags = 0;
32 } // anonymous namespace
33
34 // Inner class encapsulating all libvpx interactions and the encoding+delivery
35 // of received frames. This class is:
36 // - created and destroyed on its parent's thread (usually the main render
37 // thread),
38 // - receives VideoFrames and Run()s the callbacks on another thread (supposedly
39 // the render IO thread), which is cached on first frame arrival,
40 // - uses an internal |encoding_thread_| for libvpx interactions, notably for
41 // encoding (which might take some time).
42 // Only VP8 is supported for the time being.
43 class VideoTrackRecorder::VpxEncoder
44 : public base::RefCountedThreadSafe<VpxEncoder> {
45 public:
46 explicit VpxEncoder(const OnEncodedVideoCB& on_encoded_video_callback);
47
48 void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
49 const base::TimeTicks& capture_timestamp);
50
51 private:
52 friend class base::RefCountedThreadSafe<VpxEncoder>;
53 virtual ~VpxEncoder();
54
55 void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
56 const base::TimeTicks& capture_timestamp);
57
58 void OnFrameEncodeCompleted(const scoped_refptr<VideoFrame>& frame,
59 scoped_ptr<std::string> data,
60 const base::TimeTicks& capture_timestamp,
61 bool keyframe);
62
63 void ConfigureVp8Encoding(const gfx::Size& size);
64
65 // Returns true if |codec_config_| has been filled in at least once.
66 bool IsInitialized() const;
67
68 // Estimate the frame duration from |frame| and |last_frame_timestamp_|.
69 base::TimeDelta CalculateFrameDuration(
70 const scoped_refptr<VideoFrame>& frame);
71
72 // Used to check that we are destroyed on the same thread we were created.
73 base::ThreadChecker main_render_thread_checker_;
74
75 // Task runner where frames to encode and reply callbacks must happen.
76 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
77
78 // This callback should be exercised on IO thread.
79 const OnEncodedVideoCB on_encoded_video_callback_;
80
81 // Thread for encoding. Active as long as VpxEncoder exists. All variables
82 // below this are used in this thread.
83 base::Thread encoding_thread_;
84 // VP8 internal objects: configuration, encoder and Vpx Image wrapper.
85 vpx_codec_enc_cfg_t codec_config_;
86 vpx_codec_ctx_t encoder_;
87
88 // The |VideoFrame::timestamp()| of the last encoded frame. This is used to
89 // predict the duration of the next frame.
90 base::TimeDelta last_frame_timestamp_;
91 };
92
93 VideoTrackRecorder::VpxEncoder::VpxEncoder(
94 const OnEncodedVideoCB& on_encoded_video_callback)
95 : on_encoded_video_callback_(on_encoded_video_callback),
96 encoding_thread_("EncodingThread") {
97 DCHECK(!on_encoded_video_callback_.is_null());
98
99 codec_config_.g_timebase.den = 0; // Not initialized.
100
101 DCHECK(!encoding_thread_.IsRunning());
102 encoding_thread_.Start();
103 }
104
105 void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
106 const scoped_refptr<VideoFrame>& frame,
107 const base::TimeTicks& capture_timestamp) {
108 // Cache the thread sending frames on first frame arrival.
109 if (!origin_task_runner_.get())
110 origin_task_runner_ = base::MessageLoop::current()->task_runner();
111 DCHECK(origin_task_runner_->BelongsToCurrentThread());
112
113 encoding_thread_.task_runner()->PostTask(FROM_HERE,
114 base::Bind(&VpxEncoder::EncodeOnEncodingThread,
115 this, frame, capture_timestamp));
116 }
117
118 VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
119 DCHECK(main_render_thread_checker_.CalledOnValidThread());
120 DCHECK(encoding_thread_.IsRunning());
121 encoding_thread_.Stop();
122 vpx_codec_destroy(&encoder_);
123 }
124
125 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
126 const scoped_refptr<VideoFrame>& frame,
127 const base::TimeTicks& capture_timestamp) {
128 TRACE_EVENT0("video",
129 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
130 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
131
132 const gfx::Size frame_size = frame->visible_rect().size();
133 if (!IsInitialized() ||
134 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
135 ConfigureVp8Encoding(frame_size);
136 }
137
138 vpx_image_t vpx_image;
139 vpx_image_t* const result = vpx_img_wrap(&vpx_image,
140 VPX_IMG_FMT_I420,
141 frame_size.width(),
142 frame_size.height(),
143 1 /* align */,
144 frame->data(VideoFrame::kYPlane));
145 DCHECK_EQ(result, &vpx_image);
146 vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane);
147 vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane);
148 vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane);
149 vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane);
150 vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane);
151 vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane);
152
153 const base::TimeDelta duration = CalculateFrameDuration(frame);
154 // Encode the frame. The presentation time stamp argument here is fixed to
155 // zero to force the encoder to base its single-frame bandwidth calculations
156 // entirely on |predicted_frame_duration|.
157 const vpx_codec_err_t ret = vpx_codec_encode(&encoder_,
158 &vpx_image,
159 0 /* pts */,
160 duration.InMicroseconds(),
161 kNoFlags,
162 VPX_DL_REALTIME);
163 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #"
164 << vpx_codec_error(&encoder_) << " -"
165 << vpx_codec_error_detail(&encoder_);
166
167 scoped_ptr<std::string> data(new std::string);
168 bool keyframe = false;
169 vpx_codec_iter_t iter = NULL;
170 const vpx_codec_cx_pkt_t* pkt = NULL;
171 while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != NULL) {
172 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
173 continue;
174 data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz);
175 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
176 break;
177 }
178 origin_task_runner_->PostTask(FROM_HERE,
179 base::Bind(&VpxEncoder::OnFrameEncodeCompleted,
180 this,
miu 2015/08/19 19:43:45 You can get rid of ref-counting if OnFrameEncodeCo
mcasas 2015/08/20 00:37:04 Done.
181 frame,
182 base::Passed(&data),
183 capture_timestamp,
184 keyframe));
185 }
186
187 void VideoTrackRecorder::VpxEncoder::OnFrameEncodeCompleted(
188 const scoped_refptr<VideoFrame>& frame,
189 scoped_ptr<std::string> data,
190 const base::TimeTicks& capture_timestamp,
191 bool keyframe) {
192 DVLOG(1) << (keyframe ? "" : "non ") << "keyframe "
193 << capture_timestamp << " ms - " << data->length() << "B ";
194 DCHECK(origin_task_runner_->BelongsToCurrentThread());
195 on_encoded_video_callback_.Run(frame,
196 base::StringPiece(*data),
197 capture_timestamp,
198 keyframe);
199 }
200
201 void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding(
202 const gfx::Size& size) {
203 if (IsInitialized()) {
204 // VP8 quirk: If the new |size| is strictly less-than-or-equal than the old
205 // size, in terms of area, the existing encoder instance _could_ continue.
miu 2015/08/19 19:43:45 I don't understand. This comment is explaining so
mcasas 2015/08/20 00:37:04 Done.
206 DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: "
207 << gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString()
208 << " --> " << size.ToString();
209 vpx_codec_destroy(&encoder_);
210 }
211
212 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx();
213 const vpx_codec_err_t result = vpx_codec_enc_config_default(interface,
214 &codec_config_,
215 0 /* reserved */);
216 DCHECK_EQ(VPX_CODEC_OK, result);
217
218 // Adjust default bit rate to account for the actual size.
219 DCHECK_EQ(320u, codec_config_.g_w);
220 DCHECK_EQ(240u, codec_config_.g_h);
221 DCHECK_EQ(256u, codec_config_.rc_target_bitrate);
222 codec_config_.rc_target_bitrate = size.GetArea() *
223 codec_config_.rc_target_bitrate / codec_config_.g_w / codec_config_.g_h;
224
225 DCHECK(size.width());
226 DCHECK(size.height());
227 codec_config_.g_w = size.width();
228 codec_config_.g_h = size.height();
229 codec_config_.g_pass = VPX_RC_ONE_PASS;
230
231 // Timebase is the smallest interval used by the stream, can be set to the
232 // frame rate or to e.g. microseconds.
233 codec_config_.g_timebase.num = 1;
234 codec_config_.g_timebase.den = base::Time::kMicrosecondsPerSecond;
235
236 // Let the encoder decide where to place the Keyframes, between min and max.
237 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
238 // max distance out of necessity.
239 // Note that due to http://crbug.com/440223, it might be necessary to force a
240 // key frame after 10,000frames since decoding fails after 30,000 non-key
241 // frames.
242 codec_config_.kf_mode = VPX_KF_AUTO;
243 codec_config_.kf_min_dist = 0;
244 codec_config_.kf_max_dist = 30000;
245
246 // Do not saturate CPU utilization just for encoding. On a lower-end system
247 // with only 1 or 2 cores, use only one thread for encoding. On systems with
248 // more cores, allow half of the cores to be used for encoding.
249 codec_config_.g_threads =
250 std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
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 using base::TimeDelta;
270 TimeDelta predicted_frame_duration;
271 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
272 &predicted_frame_duration) ||
273 predicted_frame_duration <= TimeDelta()) {
274 // The source of the video frame did not provide the frame duration. Use
275 // the actual amount of time between the current and previous frame as a
276 // prediction for the next frame's duration.
277 // TODO(mcasas): This duration estimation could lead to artifacts if the
278 // cadence of the received stream is compromised (e.g. camera freeze, pause,
279 // remote packet loss). Investigate using GetFrameRate() in this case.
280 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_;
281 }
282 last_frame_timestamp_ = frame->timestamp();
283 // Make sure |predicted_frame_duration| is in a safe range of values.
284 const TimeDelta kMaxFrameDuration = TimeDelta::FromSecondsD(1.0 / 8);
285 const TimeDelta kMinFrameDuration = TimeDelta::FromMilliseconds(1);
286 return std::min(kMaxFrameDuration, std::max(predicted_frame_duration,
287 kMinFrameDuration));
288 }
289
290 VideoTrackRecorder::VideoTrackRecorder(
291 const blink::WebMediaStreamTrack& track,
292 const OnEncodedVideoCB& on_encoded_video_callback)
293 : track_(track),
294 encoder_(new VpxEncoder(on_encoded_video_callback)),
295 weak_factory_(this) {
296 DCHECK(main_render_thread_checker_.CalledOnValidThread());
297 DCHECK(track.extraData());
298 AddToVideoTrack(this,
299 media::BindToCurrentLoop(
300 base::Bind(&VideoTrackRecorder::OnVideoFrame,
301 weak_factory_.GetWeakPtr())),
302 track_);
303 }
304
305 VideoTrackRecorder::~VideoTrackRecorder() {
306 DCHECK(main_render_thread_checker_.CalledOnValidThread());
307 RemoveFromVideoTrack(this, track_);
308 weak_factory_.InvalidateWeakPtrs();
309 track_.reset();
310 }
311
312 void VideoTrackRecorder::OnVideoFrame(const scoped_refptr<VideoFrame>& frame,
313 const base::TimeTicks& timestamp) {
314 DCHECK(main_render_thread_checker_.CalledOnValidThread());
315 if (!track_.isNull())
miu 2015/08/19 19:43:45 This should be DCHECK(!track_.isNull()). Or, bett
mcasas 2015/08/20 00:37:05 Done.
316 encoder_->StartFrameEncode(frame, timestamp);
317 }
318
319 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/media/video_track_recorder.h ('k') | content/renderer/media/video_track_recorder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698