1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
|
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/video_track_recorder.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/sys_info.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "ui/gfx/geometry/size.h"
extern "C" {
// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
// backwards compatibility for legacy applications using the library.
#define VPX_CODEC_DISABLE_COMPAT 1
#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
#include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
}
using media::VideoFrame;
using media::VideoFrameMetadata;
namespace content {
namespace {
const vpx_codec_flags_t kNoFlags = 0;
// Originally from remoting/codec/scoped_vpx_codec.h.
// TODO(mcasas): Refactor into a common location.
struct VpxCodecDeleter {
void operator()(vpx_codec_ctx_t* codec) {
if (!codec)
return;
vpx_codec_err_t ret = vpx_codec_destroy(codec);
CHECK_EQ(ret, VPX_CODEC_OK);
delete codec;
}
};
typedef scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> ScopedVpxCodecCtxPtr;
void OnFrameEncodeCompleted(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_cb,
const scoped_refptr<VideoFrame>& frame,
scoped_ptr<std::string> data,
base::TimeTicks capture_timestamp,
bool keyframe) {
DVLOG(1) << (keyframe ? "" : "non ") << "keyframe "<< data->length() << "B, "
<< capture_timestamp << " ms";
on_encoded_video_cb.Run(frame, std::move(data), capture_timestamp, keyframe);
}
} // anonymous namespace
// Inner class encapsulating all libvpx interactions and the encoding+delivery
// of received frames. Limitation: Only VP8 is supported for the time being.
// This class must be ref-counted because the MediaStreamVideoTrack will hold a
// reference to it, via the callback MediaStreamVideoSink passes along, and it's
// unknown when exactly it will release that reference. This class:
// - is created and destroyed on its parent's thread (usually the main Render
// thread);
// - receives VideoFrames and Run()s the callbacks on |origin_task_runner_|,
// which is cached on first frame arrival, and is supposed to be the render IO
// thread, but this is not enforced;
// - uses an internal |encoding_thread_| for libvpx interactions, notably for
// encoding (which might take some time).
class VideoTrackRecorder::VpxEncoder final
: public base::RefCountedThreadSafe<VpxEncoder> {
public:
static void ShutdownEncoder(scoped_ptr<base::Thread> encoding_thread,
ScopedVpxCodecCtxPtr encoder);
VpxEncoder(bool use_vp9,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second);
void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
base::TimeTicks capture_timestamp);
void set_paused(bool paused) { paused_ = paused; }
private:
friend class base::RefCountedThreadSafe<VpxEncoder>;
~VpxEncoder();
void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
base::TimeTicks capture_timestamp);
void ConfigureEncoding(const gfx::Size& size);
// Returns true if |codec_config_| has been filled in at least once.
bool IsInitialized() const;
// Estimate the frame duration from |frame| and |last_frame_timestamp_|.
base::TimeDelta CalculateFrameDuration(
const scoped_refptr<VideoFrame>& frame);
// While |paused_|, frames are not encoded.
bool paused_;
// Force usage of VP9 for encoding, instead of VP8 which is the default.
const bool use_vp9_;
// This callback should be exercised on IO thread.
const OnEncodedVideoCB on_encoded_video_callback_;
// Target bitrate or video encoding. If 0, a standard bitrate is used.
const int32_t bits_per_second_;
// Used to shutdown properly on the same thread we were created.
const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
// Task runner where frames to encode and reply callbacks must happen.
scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
// Thread for encoding. Active for the lifetime of VpxEncoder. All variables
// below this are used in this thread.
scoped_ptr<base::Thread> encoding_thread_;
// VP8 internal objects: configuration and encoder.
vpx_codec_enc_cfg_t codec_config_;
// |encoder_| is a special scoped pointer to guarantee proper destruction.
// Again, it should only be accessed on |encoding_thread_|.
ScopedVpxCodecCtxPtr encoder_;
// The |VideoFrame::timestamp()| of the last encoded frame. This is used to
// predict the duration of the next frame.
base::TimeDelta last_frame_timestamp_;
DISALLOW_COPY_AND_ASSIGN(VpxEncoder);
};
// static
void VideoTrackRecorder::VpxEncoder::ShutdownEncoder(
scoped_ptr<base::Thread> encoding_thread,
ScopedVpxCodecCtxPtr encoder) {
DCHECK(encoding_thread->IsRunning());
encoding_thread->Stop();
// Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
}
VideoTrackRecorder::VpxEncoder::VpxEncoder(
bool use_vp9,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: paused_(false),
use_vp9_(use_vp9),
on_encoded_video_callback_(on_encoded_video_callback),
bits_per_second_(bits_per_second),
main_task_runner_(base::MessageLoop::current()->task_runner()),
encoding_thread_(new base::Thread("EncodingThread")) {
DCHECK(!on_encoded_video_callback_.is_null());
codec_config_.g_timebase.den = 0; // Not initialized.
DCHECK(!encoding_thread_->IsRunning());
encoding_thread_->Start();
}
VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
main_task_runner_->PostTask(FROM_HERE,
base::Bind(&VpxEncoder::ShutdownEncoder,
base::Passed(&encoding_thread_),
base::Passed(&encoder_)));
}
void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
const scoped_refptr<VideoFrame>& frame,
base::TimeTicks capture_timestamp) {
// Cache the thread sending frames on first frame arrival.
if (!origin_task_runner_.get())
origin_task_runner_ = base::MessageLoop::current()->task_runner();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
if (paused_)
return;
encoding_thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&VpxEncoder::EncodeOnEncodingThread,
this, frame, capture_timestamp));
}
void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
const scoped_refptr<VideoFrame>& video_frame,
base::TimeTicks capture_timestamp) {
TRACE_EVENT0("video",
"VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
video_frame->format() == media::PIXEL_FORMAT_YV12 ||
video_frame->format() == media::PIXEL_FORMAT_YV12A)) {
NOTREACHED();
return;
}
scoped_refptr<media::VideoFrame> frame = video_frame;
// Drop alpha channel since we do not support it yet.
if (frame->format() == media::PIXEL_FORMAT_YV12A)
frame = media::WrapAsI420VideoFrame(video_frame);
const gfx::Size frame_size = frame->visible_rect().size();
if (!IsInitialized() ||
gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
ConfigureEncoding(frame_size);
}
vpx_image_t vpx_image;
vpx_image_t* const result = vpx_img_wrap(&vpx_image,
VPX_IMG_FMT_I420,
frame_size.width(),
frame_size.height(),
1 /* align */,
frame->data(VideoFrame::kYPlane));
DCHECK_EQ(result, &vpx_image);
vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane);
vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane);
vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane);
vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane);
vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane);
vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane);
const base::TimeDelta duration = CalculateFrameDuration(frame);
// Encode the frame. The presentation time stamp argument here is fixed to
// zero to force the encoder to base its single-frame bandwidth calculations
// entirely on |predicted_frame_duration|.
const vpx_codec_err_t ret = vpx_codec_encode(encoder_.get(),
&vpx_image,
0 /* pts */,
duration.InMicroseconds(),
kNoFlags,
VPX_DL_REALTIME);
DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #"
<< vpx_codec_error(encoder_.get()) << " -"
<< vpx_codec_error_detail(encoder_.get());
scoped_ptr<std::string> data(new std::string);
bool keyframe = false;
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t* pkt = NULL;
while ((pkt = vpx_codec_get_cx_data(encoder_.get(), &iter)) != NULL) {
if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
continue;
data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz);
keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
break;
}
origin_task_runner_->PostTask(FROM_HERE,
base::Bind(OnFrameEncodeCompleted,
on_encoded_video_callback_,
frame,
base::Passed(&data),
capture_timestamp,
keyframe));
}
void VideoTrackRecorder::VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
if (IsInitialized()) {
// TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less-
// than-or-equal than the old size, in terms of area, the existing encoder
// instance could be reused after changing |codec_config_.{g_w,g_h}|.
DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: "
<< gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString()
<< " --> " << size.ToString() << (use_vp9_ ? " vp9" : " vp8");
encoder_.reset();
}
const vpx_codec_iface_t* interface =
use_vp9_ ? vpx_codec_vp9_cx() : vpx_codec_vp8_cx();
vpx_codec_err_t result =
vpx_codec_enc_config_default(interface, &codec_config_, 0 /* reserved */);
DCHECK_EQ(VPX_CODEC_OK, result);
DCHECK_EQ(320u, codec_config_.g_w);
DCHECK_EQ(240u, codec_config_.g_h);
DCHECK_EQ(256u, codec_config_.rc_target_bitrate);
// Use the selected bitrate or adjust default bit rate to account for the
// actual size.
if (bits_per_second_ > 0) {
codec_config_.rc_target_bitrate = bits_per_second_;
} else {
codec_config_.rc_target_bitrate = size.GetArea() *
codec_config_.rc_target_bitrate /
codec_config_.g_w / codec_config_.g_h;
}
// Both VP8/VP9 configuration should be Variable BitRate by default.
DCHECK_EQ(VPX_VBR, codec_config_.rc_end_usage);
if (use_vp9_) {
// Number of frames to consume before producing output.
codec_config_.g_lag_in_frames = 0;
// DCHECK that the profile selected by default is I420 (magic number 0).
DCHECK_EQ(0u, codec_config_.g_profile);
// Values of VP8E_SET_CPUUSED greater than 0 will increase encoder speed at
// the expense of quality up to a maximum value of 16 for VP9, by tuning the
// target time spent encoding the frame. Go from 12 to 6 depending on the
// amount of cores available in the system.
const int kCpuUsed = std::max(6, 13 - base::SysInfo::NumberOfProcessors());
result = vpx_codec_control(encoder_.get(), VP8E_SET_CPUUSED, kCpuUsed);
DLOG_IF(WARNING, VPX_CODEC_OK != result) << "VP8E_SET_CPUUSED failed";
} else {
// VP8 always produces frames instantaneously.
DCHECK_EQ(0u, codec_config_.g_lag_in_frames);
}
DCHECK(size.width());
DCHECK(size.height());
codec_config_.g_w = size.width();
codec_config_.g_h = size.height();
codec_config_.g_pass = VPX_RC_ONE_PASS;
// Timebase is the smallest interval used by the stream, can be set to the
// frame rate or to e.g. microseconds.
codec_config_.g_timebase.num = 1;
codec_config_.g_timebase.den = base::Time::kMicrosecondsPerSecond;
// Let the encoder decide where to place the Keyframes, between min and max.
// In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
// max distance out of necessity.
// Note that due to http://crbug.com/440223, it might be necessary to force a
// key frame after 10,000frames since decoding fails after 30,000 non-key
// frames.
codec_config_.kf_mode = VPX_KF_AUTO;
codec_config_.kf_min_dist = 0;
codec_config_.kf_max_dist = 30000;
// Do not saturate CPU utilization just for encoding. On a lower-end system
// with only 1 or 2 cores, use only one thread for encoding. On systems with
// more cores, allow half of the cores to be used for encoding.
codec_config_.g_threads =
std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
// Number of frames to consume before producing output.
codec_config_.g_lag_in_frames = 0;
DCHECK(!encoder_);
encoder_.reset(new vpx_codec_ctx_t);
const vpx_codec_err_t ret = vpx_codec_enc_init(encoder_.get(), interface,
&codec_config_, kNoFlags);
DCHECK_EQ(VPX_CODEC_OK, ret);
}
bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
return codec_config_.g_timebase.den != 0;
}
base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
const scoped_refptr<VideoFrame>& frame) {
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
using base::TimeDelta;
TimeDelta predicted_frame_duration;
if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
&predicted_frame_duration) ||
predicted_frame_duration <= TimeDelta()) {
// The source of the video frame did not provide the frame duration. Use
// the actual amount of time between the current and previous frame as a
// prediction for the next frame's duration.
// TODO(mcasas): This duration estimation could lead to artifacts if the
// cadence of the received stream is compromised (e.g. camera freeze, pause,
// remote packet loss). Investigate using GetFrameRate() in this case.
predicted_frame_duration = frame->timestamp() - last_frame_timestamp_;
}
last_frame_timestamp_ = frame->timestamp();
// Make sure |predicted_frame_duration| is in a safe range of values.
const TimeDelta kMaxFrameDuration = TimeDelta::FromSecondsD(1.0 / 8);
const TimeDelta kMinFrameDuration = TimeDelta::FromMilliseconds(1);
return std::min(kMaxFrameDuration, std::max(predicted_frame_duration,
kMinFrameDuration));
}
VideoTrackRecorder::VideoTrackRecorder(
bool use_vp9,
const blink::WebMediaStreamTrack& track,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: track_(track),
encoder_(
new VpxEncoder(use_vp9, on_encoded_video_callback, bits_per_second)) {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
DCHECK(!track_.isNull());
DCHECK(track_.extraData());
// StartFrameEncode() will be called on Render IO thread.
AddToVideoTrack(this,
base::Bind(&VideoTrackRecorder::VpxEncoder::StartFrameEncode,
encoder_),
track_);
}
VideoTrackRecorder::~VideoTrackRecorder() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
RemoveFromVideoTrack(this, track_);
track_.reset();
}
void VideoTrackRecorder::Pause() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
DCHECK(encoder_);
encoder_->set_paused(true);
}
void VideoTrackRecorder::Resume() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
DCHECK(encoder_);
encoder_->set_paused(false);
}
void VideoTrackRecorder::OnVideoFrameForTesting(
const scoped_refptr<media::VideoFrame>& frame,
base::TimeTicks timestamp) {
encoder_->StartFrameEncode(frame, timestamp);
}
} // namespace content
|