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

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: Moved |track_index| and timestamp mgmt from VideoTrackRecorder into WebmMuxer 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 // 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698