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.
5 #include "content/renderer/media/video_track_recorder.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 "media/base/video_frame.h"
16 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
17 // backwards compatibility for legacy applications using the library.
18 #define VPX_CODEC_DISABLE_COMPAT 1
19 #include "third_party/libvpx_new/source/libvpx/vpx/vp8cx.h"
20 #include "third_party/libvpx_new/source/libvpx/vpx/vpx_encoder.h"
23 using media::VideoFrame
;
24 using media::VideoFrameMetadata
;
30 const vpx_codec_flags_t kNoFlags
= 0;
32 // Originally from remoting/codec/scoped_vpx_codec.h.
33 // TODO(mcasas): Refactor into a common location.
34 struct VpxCodecDeleter
{
35 void operator()(vpx_codec_ctx_t
* codec
) {
38 vpx_codec_err_t ret
= vpx_codec_destroy(codec
);
39 CHECK_EQ(ret
, VPX_CODEC_OK
);
44 typedef scoped_ptr
<vpx_codec_ctx_t
, VpxCodecDeleter
> ScopedVpxCodecCtxPtr
;
46 void OnFrameEncodeCompleted(
47 const VideoTrackRecorder::OnEncodedVideoCB
& on_encoded_video_cb
,
48 const scoped_refptr
<VideoFrame
>& frame
,
49 scoped_ptr
<std::string
> data
,
50 base::TimeTicks capture_timestamp
,
52 DVLOG(1) << (keyframe
? "" : "non ") << "keyframe "<< data
->length() << "B, "
53 << capture_timestamp
<< " ms";
54 on_encoded_video_cb
.Run(frame
, base::StringPiece(*data
), capture_timestamp
,
58 } // anonymous namespace
60 // Inner class encapsulating all libvpx interactions and the encoding+delivery
61 // of received frames. Limitation: Only VP8 is supported for the time being.
62 // This class must be ref-counted because the MediaStreamVideoTrack will hold a
63 // reference to it, via the callback MediaStreamVideoSink passes along, and it's
64 // unknown when exactly it will release that reference. This class:
65 // - is created and destroyed on its parent's thread (usually the main Render
67 // - receives VideoFrames and Run()s the callbacks on |origin_task_runner_|,
68 // which is cached on first frame arrival, and is supposed to be the render IO
69 // thread, but this is not enforced;
70 // - uses an internal |encoding_thread_| for libvpx interactions, notably for
71 // encoding (which might take some time).
72 class VideoTrackRecorder::VpxEncoder final
73 : public base::RefCountedThreadSafe
<VpxEncoder
> {
75 static void ShutdownEncoder(scoped_ptr
<base::Thread
> encoding_thread
,
76 ScopedVpxCodecCtxPtr encoder
);
78 explicit VpxEncoder(const OnEncodedVideoCB
& on_encoded_video_callback
);
80 void StartFrameEncode(const scoped_refptr
<VideoFrame
>& frame
,
81 base::TimeTicks capture_timestamp
);
84 friend class base::RefCountedThreadSafe
<VpxEncoder
>;
87 void EncodeOnEncodingThread(const scoped_refptr
<VideoFrame
>& frame
,
88 base::TimeTicks capture_timestamp
);
90 void ConfigureVp8Encoding(const gfx::Size
& size
);
92 // Returns true if |codec_config_| has been filled in at least once.
93 bool IsInitialized() const;
95 // Estimate the frame duration from |frame| and |last_frame_timestamp_|.
96 base::TimeDelta
CalculateFrameDuration(
97 const scoped_refptr
<VideoFrame
>& frame
);
99 // Used to shutdown properly on the same thread we were created.
100 const scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner_
;
102 // Task runner where frames to encode and reply callbacks must happen.
103 scoped_refptr
<base::SingleThreadTaskRunner
> origin_task_runner_
;
105 // This callback should be exercised on IO thread.
106 const OnEncodedVideoCB on_encoded_video_callback_
;
108 // Thread for encoding. Active for the lifetime of VpxEncoder. All variables
109 // below this are used in this thread.
110 scoped_ptr
<base::Thread
> encoding_thread_
;
111 // VP8 internal objects: configuration and encoder.
112 vpx_codec_enc_cfg_t codec_config_
;
113 // |encoder_| is a special scoped pointer to guarantee proper destruction.
114 // Again, it should only be accessed on |encoding_thread_|.
115 ScopedVpxCodecCtxPtr encoder_
;
117 // The |VideoFrame::timestamp()| of the last encoded frame. This is used to
118 // predict the duration of the next frame.
119 base::TimeDelta last_frame_timestamp_
;
121 DISALLOW_COPY_AND_ASSIGN(VpxEncoder
);
125 void VideoTrackRecorder::VpxEncoder::ShutdownEncoder(
126 scoped_ptr
<base::Thread
> encoding_thread
,
127 ScopedVpxCodecCtxPtr encoder
) {
128 DCHECK(encoding_thread
->IsRunning());
129 encoding_thread
->Stop();
130 // Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
133 VideoTrackRecorder::VpxEncoder::VpxEncoder(
134 const OnEncodedVideoCB
& on_encoded_video_callback
)
135 : main_task_runner_(base::MessageLoop::current()->task_runner()),
136 on_encoded_video_callback_(on_encoded_video_callback
),
137 encoding_thread_(new base::Thread("EncodingThread")) {
138 DCHECK(!on_encoded_video_callback_
.is_null());
140 codec_config_
.g_timebase
.den
= 0; // Not initialized.
142 DCHECK(!encoding_thread_
->IsRunning());
143 encoding_thread_
->Start();
146 VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
147 main_task_runner_
->PostTask(FROM_HERE
,
148 base::Bind(&VpxEncoder::ShutdownEncoder
,
149 base::Passed(&encoding_thread_
),
150 base::Passed(&encoder_
)));
153 void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
154 const scoped_refptr
<VideoFrame
>& frame
,
155 base::TimeTicks capture_timestamp
) {
156 // Cache the thread sending frames on first frame arrival.
157 if (!origin_task_runner_
.get())
158 origin_task_runner_
= base::MessageLoop::current()->task_runner();
159 DCHECK(origin_task_runner_
->BelongsToCurrentThread());
161 encoding_thread_
->task_runner()->PostTask(
162 FROM_HERE
, base::Bind(&VpxEncoder::EncodeOnEncodingThread
,
163 this, frame
, capture_timestamp
));
166 void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
167 const scoped_refptr
<VideoFrame
>& frame
,
168 base::TimeTicks capture_timestamp
) {
169 TRACE_EVENT0("video",
170 "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
171 DCHECK(encoding_thread_
->task_runner()->BelongsToCurrentThread());
173 const gfx::Size frame_size
= frame
->visible_rect().size();
174 if (!IsInitialized() ||
175 gfx::Size(codec_config_
.g_w
, codec_config_
.g_h
) != frame_size
) {
176 ConfigureVp8Encoding(frame_size
);
179 vpx_image_t vpx_image
;
180 vpx_image_t
* const result
= vpx_img_wrap(&vpx_image
,
185 frame
->data(VideoFrame::kYPlane
));
186 DCHECK_EQ(result
, &vpx_image
);
187 vpx_image
.planes
[VPX_PLANE_Y
] = frame
->visible_data(VideoFrame::kYPlane
);
188 vpx_image
.planes
[VPX_PLANE_U
] = frame
->visible_data(VideoFrame::kUPlane
);
189 vpx_image
.planes
[VPX_PLANE_V
] = frame
->visible_data(VideoFrame::kVPlane
);
190 vpx_image
.stride
[VPX_PLANE_Y
] = frame
->stride(VideoFrame::kYPlane
);
191 vpx_image
.stride
[VPX_PLANE_U
] = frame
->stride(VideoFrame::kUPlane
);
192 vpx_image
.stride
[VPX_PLANE_V
] = frame
->stride(VideoFrame::kVPlane
);
194 const base::TimeDelta duration
= CalculateFrameDuration(frame
);
195 // Encode the frame. The presentation time stamp argument here is fixed to
196 // zero to force the encoder to base its single-frame bandwidth calculations
197 // entirely on |predicted_frame_duration|.
198 const vpx_codec_err_t ret
= vpx_codec_encode(encoder_
.get(),
201 duration
.InMicroseconds(),
204 DCHECK_EQ(ret
, VPX_CODEC_OK
) << vpx_codec_err_to_string(ret
) << ", #"
205 << vpx_codec_error(encoder_
.get()) << " -"
206 << vpx_codec_error_detail(encoder_
.get());
208 scoped_ptr
<std::string
> data(new std::string
);
209 bool keyframe
= false;
210 vpx_codec_iter_t iter
= NULL
;
211 const vpx_codec_cx_pkt_t
* pkt
= NULL
;
212 while ((pkt
= vpx_codec_get_cx_data(encoder_
.get(), &iter
)) != NULL
) {
213 if (pkt
->kind
!= VPX_CODEC_CX_FRAME_PKT
)
215 data
->assign(static_cast<char*>(pkt
->data
.frame
.buf
), pkt
->data
.frame
.sz
);
216 keyframe
= (pkt
->data
.frame
.flags
& VPX_FRAME_IS_KEY
) != 0;
219 origin_task_runner_
->PostTask(FROM_HERE
,
220 base::Bind(OnFrameEncodeCompleted
,
221 on_encoded_video_callback_
,
228 void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding(
229 const gfx::Size
& size
) {
230 if (IsInitialized()) {
231 // TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less-
232 // than-or-equal than the old size, in terms of area, the existing encoder
233 // instance could be reused after changing |codec_config_.{g_w,g_h}|.
234 DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: "
235 << gfx::Size(codec_config_
.g_w
, codec_config_
.g_h
).ToString()
236 << " --> " << size
.ToString();
240 const vpx_codec_iface_t
* interface
= vpx_codec_vp8_cx();
241 const vpx_codec_err_t result
= vpx_codec_enc_config_default(interface
,
244 DCHECK_EQ(VPX_CODEC_OK
, result
);
246 // Adjust default bit rate to account for the actual size.
247 DCHECK_EQ(320u, codec_config_
.g_w
);
248 DCHECK_EQ(240u, codec_config_
.g_h
);
249 DCHECK_EQ(256u, codec_config_
.rc_target_bitrate
);
250 codec_config_
.rc_target_bitrate
= size
.GetArea() *
251 codec_config_
.rc_target_bitrate
/ codec_config_
.g_w
/ codec_config_
.g_h
;
253 DCHECK(size
.width());
254 DCHECK(size
.height());
255 codec_config_
.g_w
= size
.width();
256 codec_config_
.g_h
= size
.height();
257 codec_config_
.g_pass
= VPX_RC_ONE_PASS
;
259 // Timebase is the smallest interval used by the stream, can be set to the
260 // frame rate or to e.g. microseconds.
261 codec_config_
.g_timebase
.num
= 1;
262 codec_config_
.g_timebase
.den
= base::Time::kMicrosecondsPerSecond
;
264 // Let the encoder decide where to place the Keyframes, between min and max.
265 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
266 // max distance out of necessity.
267 // Note that due to http://crbug.com/440223, it might be necessary to force a
268 // key frame after 10,000frames since decoding fails after 30,000 non-key
270 codec_config_
.kf_mode
= VPX_KF_AUTO
;
271 codec_config_
.kf_min_dist
= 0;
272 codec_config_
.kf_max_dist
= 30000;
274 // Do not saturate CPU utilization just for encoding. On a lower-end system
275 // with only 1 or 2 cores, use only one thread for encoding. On systems with
276 // more cores, allow half of the cores to be used for encoding.
277 codec_config_
.g_threads
=
278 std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
280 // Number of frames to consume before producing output.
281 codec_config_
.g_lag_in_frames
= 0;
284 encoder_
.reset(new vpx_codec_ctx_t
);
285 const vpx_codec_err_t ret
= vpx_codec_enc_init(encoder_
.get(), interface
,
286 &codec_config_
, kNoFlags
);
287 DCHECK_EQ(VPX_CODEC_OK
, ret
);
290 bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
291 DCHECK(encoding_thread_
->task_runner()->BelongsToCurrentThread());
292 return codec_config_
.g_timebase
.den
!= 0;
295 base::TimeDelta
VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
296 const scoped_refptr
<VideoFrame
>& frame
) {
297 DCHECK(encoding_thread_
->task_runner()->BelongsToCurrentThread());
299 using base::TimeDelta
;
300 TimeDelta predicted_frame_duration
;
301 if (!frame
->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION
,
302 &predicted_frame_duration
) ||
303 predicted_frame_duration
<= TimeDelta()) {
304 // The source of the video frame did not provide the frame duration. Use
305 // the actual amount of time between the current and previous frame as a
306 // prediction for the next frame's duration.
307 // TODO(mcasas): This duration estimation could lead to artifacts if the
308 // cadence of the received stream is compromised (e.g. camera freeze, pause,
309 // remote packet loss). Investigate using GetFrameRate() in this case.
310 predicted_frame_duration
= frame
->timestamp() - last_frame_timestamp_
;
312 last_frame_timestamp_
= frame
->timestamp();
313 // Make sure |predicted_frame_duration| is in a safe range of values.
314 const TimeDelta kMaxFrameDuration
= TimeDelta::FromSecondsD(1.0 / 8);
315 const TimeDelta kMinFrameDuration
= TimeDelta::FromMilliseconds(1);
316 return std::min(kMaxFrameDuration
, std::max(predicted_frame_duration
,
320 VideoTrackRecorder::VideoTrackRecorder(
321 const blink::WebMediaStreamTrack
& track
,
322 const OnEncodedVideoCB
& on_encoded_video_callback
)
324 encoder_(new VpxEncoder(on_encoded_video_callback
)) {
325 DCHECK(main_render_thread_checker_
.CalledOnValidThread());
326 DCHECK(!track_
.isNull());
327 DCHECK(track_
.extraData());
329 // StartFrameEncode() will be called on Render IO thread.
330 AddToVideoTrack(this,
331 base::Bind(&VideoTrackRecorder::VpxEncoder::StartFrameEncode
,
336 VideoTrackRecorder::~VideoTrackRecorder() {
337 DCHECK(main_render_thread_checker_
.CalledOnValidThread());
338 RemoveFromVideoTrack(this, track_
);
342 void VideoTrackRecorder::OnVideoFrameForTesting(
343 const scoped_refptr
<media::VideoFrame
>& frame
,
344 base::TimeTicks timestamp
) {
345 encoder_
->StartFrameEncode(frame
, timestamp
);
348 } // namespace content