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

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 (except param piggybacking and threading stuff) 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
31 const vpx_codec_flags_t kNoFlags = 0;
32
33 static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) {
34 double frame_rate = 0.0f;
35 base::IgnoreResult(video_frame->metadata()->GetDouble(
36 VideoFrameMetadata::FRAME_RATE, &frame_rate));
37 return frame_rate;
38 }
39
40 } // anonymous namespace
41
42 // Inner class holding all libvpx configuration and encoding received frames.
43 // This class is created and destroyed on its parent's thread: the main render
44 // thread. It receives VideoFrames and must Run() the callbacks on IO thread,
45 // and finally uses an internal |encoding_thread_| for libvpx interactions,
46 // notably for encoding (which might take some time). All this forces a bit of
47 // hopping back and forth between threads.
48 // Only VP8 is supported for the time being.
49 // TODO(mcasas): Consider not getting |io_task_runner| in ctor and instead
50 // caching it on first StartFrameEncode().
51 class VideoTrackRecorder::VpxEncoder
52 : public base::RefCountedThreadSafe<VpxEncoder> {
53 public:
54 VpxEncoder(const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
55 const OnFirstFrameCB& on_first_frame_callback,
56 const OnEncodedVideoCB& on_encoded_video_callback);
57
58 void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
59 const base::TimeTicks& capture_timestamp);
60
61 private:
62 friend class base::RefCountedThreadSafe<VpxEncoder>;
63 virtual ~VpxEncoder();
64
65 void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
66 const base::TimeTicks& capture_timestamp);
67
68 void OnFrameEncodeCompleted(scoped_ptr<std::string> data,
69 base::TimeDelta timestamp,
70 bool keyframe);
71
72 void ConfigureVp8Encoding(const gfx::Size& size);
73 // Returns true if |codec_config_| has been filled in at least once.
74 bool IsInitialized() const;
75
76 base::TimeDelta CalculateFrameDuration(
77 const scoped_refptr<VideoFrame>& frame);
78
79 // Used to check that we are destroyed on the same thread we were created.
80 base::ThreadChecker main_render_thread_checker_;
81
82 // Render IO thread task runner for posting tasks and DCHECK()s.
83 const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
84
85 // Used to ping |on_first_frame_callback_|. Used on IO thread only.
86 bool first_frame_received_;
87 // Written once on IO thread and used (read) from capture thread.
88 uint64_t track_index_;
89 // Callbacks that should be exercised on IO thread.
90 const OnFirstFrameCB on_first_frame_callback_;
91 const OnEncodedVideoCB on_encoded_video_callback_;
92
93 // Thread for encoding. Active as long as VpxEncoder exists. All variables
94 // below this are used in this thread.
95 base::Thread encoding_thread_;
96 // VP8 internal objects: configuration, encoder and Vpx Image wrapper.
97 vpx_codec_enc_cfg_t codec_config_;
98 vpx_codec_ctx_t encoder_;
99 // Origin of times for frame timestamps.
100 base::TimeTicks first_frame_timestamp_;
101 // The |VideoFrame::timestamp()| of the last encoded frame. This is used to
102 // predict the duration of the next frame.
103 base::TimeDelta last_frame_timestamp_;
104 };
105
106 VideoTrackRecorder::VpxEncoder::VpxEncoder(
107 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
108 const OnFirstFrameCB& on_first_frame_callback,
109 const OnEncodedVideoCB& on_encoded_video_callback)
110 : io_task_runner_(io_task_runner),
111 first_frame_received_(false),
112 track_index_(0),
113 on_first_frame_callback_(on_first_frame_callback),
114 on_encoded_video_callback_(on_encoded_video_callback),
115 encoding_thread_("EncodingThread") {
116 DCHECK(!on_first_frame_callback_.is_null());
117 DCHECK(!on_encoded_video_callback_.is_null());
118
119 codec_config_.g_timebase.den = 0; // Not initialized.
120
121 DCHECK(!encoding_thread_.IsRunning());
122 encoding_thread_.Start();
123 }
124
125 void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
126 const scoped_refptr<VideoFrame>& frame,
127 const base::TimeTicks& capture_timestamp) {
128 DCHECK(io_task_runner_->BelongsToCurrentThread());
129
130 if (!first_frame_received_) {
131 track_index_ = on_first_frame_callback_.Run(frame->visible_rect().size(),
132 GetFrameRate(frame));
133 first_frame_timestamp_ = capture_timestamp;
134 first_frame_received_ = true;
135 }
136 encoding_thread_.task_runner()->PostTask(FROM_HERE,
137 base::Bind(&VpxEncoder::EncodeOnEncodingThread,
138 this, frame, capture_timestamp));
139 }
140
141 VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
142 DCHECK(main_render_thread_checker_.CalledOnValidThread());
143 DCHECK(encoding_thread_.IsRunning());
144 encoding_thread_.Stop();
145 }
146
147 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
148 const scoped_refptr<VideoFrame>& frame,
149 const base::TimeTicks& capture_timestamp) {
150 TRACE_EVENT0("video",
151 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
152 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
153
154 const gfx::Size frame_size = frame->visible_rect().size();
155 if (!IsInitialized() ||
156 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
157 ConfigureVp8Encoding(frame_size);
158 }
159
160 vpx_image_t vpx_image;
161 vpx_image_t* const result = vpx_img_wrap(&vpx_image,
162 VPX_IMG_FMT_I420,
163 frame_size.width(),
164 frame_size.height(),
165 1 /* align */,
166 frame->data(VideoFrame::kYPlane));
167 DCHECK_EQ(result, &vpx_image);
168 vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane);
169 vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane);
170 vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane);
171 vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane);
172 vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane);
173 vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane);
174
175 const base::TimeDelta duration = CalculateFrameDuration(frame);
176 DVLOG(1) << duration.InMicroseconds();
177 // Encode the frame. The presentation time stamp argument here is fixed to
178 // zero to force the encoder to base its single-frame bandwidth calculations
179 // entirely on |predicted_frame_duration|.
180 const vpx_codec_err_t ret = vpx_codec_encode(&encoder_,
181 &vpx_image,
182 0 /* pts */,
183 duration.InMicroseconds(),
184 kNoFlags,
185 VPX_DL_REALTIME);
186 DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #"
187 << vpx_codec_error(&encoder_) << " -"
188 << vpx_codec_error_detail(&encoder_);
189
190 scoped_ptr<std::string> data(new std::string);
191 bool keyframe = false;
192 vpx_codec_iter_t iter = NULL;
193 const vpx_codec_cx_pkt_t* pkt = NULL;
194 while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != NULL) {
195 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
196 continue;
197 data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz);
198 keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
199 break;
200 }
201 const int timestamp =
202 (capture_timestamp - first_frame_timestamp_).InMilliseconds();
203 io_task_runner_->PostTask(FROM_HERE,
204 base::Bind(&VpxEncoder::OnFrameEncodeCompleted,
205 this,
206 base::Passed(&data),
207 base::TimeDelta::FromMilliseconds(timestamp),
208 keyframe ));
209 }
210
211 void VideoTrackRecorder::VpxEncoder::OnFrameEncodeCompleted(
212 scoped_ptr<std::string> data,
213 base::TimeDelta timestamp,
214 bool keyframe) {
215 DVLOG(1) << (keyframe ? "" : "non ") << "keyframe "
216 << timestamp.InMilliseconds() << " ms - " << data->length() << "B ";
217 DCHECK(io_task_runner_->BelongsToCurrentThread());
218 on_encoded_video_callback_.Run(track_index_, base::StringPiece(*data),
219 timestamp, keyframe);
220 }
221
222 void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding(
223 const gfx::Size& size) {
224 if (IsInitialized()) {
225 // TODO(mcasas): Workaround for certain bug.
226 DVLOG(1) << "Destroying/Re-Creating encoder for larger frame size: "
227 << gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString()
228 << " --> " << size.ToString();
229 //vpx_codec_destroy(&encoder_);
230 }
231 const vpx_codec_iface_t* interface = vpx_codec_vp8_cx();
232 vpx_codec_enc_config_default(interface, &codec_config_, 0 /* reserved */);
233
234 // Adjust default bit rate to account for the actual size.
235 codec_config_.rc_target_bitrate = size.width() * size.height() *
236 codec_config_.rc_target_bitrate /
237 codec_config_.g_w / codec_config_.g_h;
238 DCHECK(size.width());
239 DCHECK(size.height());
240 codec_config_.g_w = size.width();
241 codec_config_.g_h = size.height();
242 codec_config_.g_pass = VPX_RC_ONE_PASS;
243
244 // Timebase is the smallest interval used by the stream, can be set to the
245 // frame rate or just to milliseconds.
246 codec_config_.g_timebase.num = 1;
247 codec_config_.g_timebase.den = base::Time::kMillisecondsPerSecond;
248
249 // Let the encoder decide where to place the Keyframes, between min and max.
250 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
251 // max distance out of necessity. Due to http://crbug.com/440223, decoding
252 // fails after 30,000 non-key frames, so force an "unnecessary" key-frame
253 // every 10,000 frames.
254 codec_config_.kf_mode = VPX_KF_AUTO;
255 codec_config_.kf_min_dist = 10000;
256 codec_config_.kf_max_dist = 10000;
257
258 // Number of frames to consume before producing output.
259 codec_config_.g_lag_in_frames = 0;
260
261 const vpx_codec_err_t ret = vpx_codec_enc_init(&encoder_, interface,
262 &codec_config_, kNoFlags);
263 DCHECK_EQ(VPX_CODEC_OK, ret);
264 }
265
266 bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
267 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
268 return codec_config_.g_timebase.den != 0;
269 }
270
271 base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
272 const scoped_refptr<VideoFrame>& frame) {
273 DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
274
275 base::TimeDelta predicted_frame_duration;
276 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
277 &predicted_frame_duration) ||
278 predicted_frame_duration <= base::TimeDelta()) {
279 // The source of the video frame did not provide the frame duration. Use
280 // the actual amount of time between the current and previous frame as a
281 // prediction for the next frame's duration.
Niklas Enbom 2015/07/30 19:46:47 Drive by shooting. This (using last frames duratio
mcasas 2015/07/31 11:26:27 Done.
282 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_;
283 }
284 last_frame_timestamp_ = frame->timestamp();
285 const base::TimeDelta kMinFrameDuration =
286 base::TimeDelta::FromMilliseconds(1);
287 return std::max(predicted_frame_duration, kMinFrameDuration);
288 }
289
290 VideoTrackRecorder::VideoTrackRecorder(
291 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
292 const blink::WebMediaStreamTrack& track,
293 const OnFirstFrameCB& on_first_frame_cb,
294 const OnEncodedVideoCB& on_encoded_video_cb)
295 : track_(track),
296 encoder_(new VpxEncoder(io_task_runner,
297 on_first_frame_cb,
298 on_encoded_video_cb)) {
299 DCHECK(main_render_thread_checker_.CalledOnValidThread());
300 DCHECK(track.extraData());
301 AddToVideoTrack(this,
302 base::Bind(&VpxEncoder::StartFrameEncode, encoder_), track_);
303 }
304
305 VideoTrackRecorder::~VideoTrackRecorder() {
306 DCHECK(main_render_thread_checker_.CalledOnValidThread());
307 RemoveFromVideoTrack(this, track_);
308 }
309
310 void VideoTrackRecorder::StartFrameEncodeForTesting(
311 const scoped_refptr<VideoFrame>& frame,
312 const base::TimeTicks& capture_timestamp) {
313 encoder_->StartFrameEncode(frame, capture_timestamp);
314 }
315
316 } // 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