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

Side by Side Diff: content/renderer/media_recorder/vpx_encoder.cc

Issue 2793303003: Refactor VideoTrackRecorder into smaller classes (Closed)
Patch Set: Rebase Created 3 years, 8 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
« no previous file with comments | « content/renderer/media_recorder/vpx_encoder.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 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_recorder/vpx_encoder.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/sys_info.h"
12 #include "base/threading/thread.h"
13 #include "base/trace_event/trace_event.h"
14 #include "media/base/video_frame.h"
15 #include "ui/gfx/geometry/size.h"
16
17 using media::VideoFrame;
18 using media::VideoFrameMetadata;
19
20 namespace content {
21
22 void VpxEncoder::VpxCodecDeleter::operator()(vpx_codec_ctx_t* codec) {
23 if (!codec)
24 return;
25 vpx_codec_err_t ret = vpx_codec_destroy(codec);
26 CHECK_EQ(ret, VPX_CODEC_OK);
27 delete codec;
28 }
29
30 static int GetNumberOfThreadsForEncoding() {
31 // Do not saturate CPU utilization just for encoding. On a lower-end system
32 // with only 1 or 2 cores, use only one thread for encoding. On systems with
33 // more cores, allow half of the cores to be used for encoding.
34 return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
35 }
36
37 // static
38 void VpxEncoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
39 ScopedVpxCodecCtxPtr encoder) {
40 DCHECK(encoding_thread->IsRunning());
41 encoding_thread->Stop();
42 // Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
43 }
44
45 VpxEncoder::VpxEncoder(
46 bool use_vp9,
47 const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
48 int32_t bits_per_second)
49 : VideoTrackRecorder::Encoder(on_encoded_video_callback, bits_per_second),
50 use_vp9_(use_vp9) {
51 codec_config_.g_timebase.den = 0; // Not initialized.
52 alpha_codec_config_.g_timebase.den = 0; // Not initialized.
53 DCHECK(encoding_thread_->IsRunning());
54 }
55
56 VpxEncoder::~VpxEncoder() {
57 main_task_runner_->PostTask(
58 FROM_HERE,
59 base::Bind(&VpxEncoder::ShutdownEncoder, base::Passed(&encoding_thread_),
60 base::Passed(&encoder_)));
61 }
62
63 bool VpxEncoder::CanEncodeAlphaChannel() {
64 return true;
65 }
66
67 void VpxEncoder::EncodeOnEncodingTaskRunner(scoped_refptr<VideoFrame> frame,
68 base::TimeTicks capture_timestamp) {
69 TRACE_EVENT0("video", "VpxEncoder::EncodeOnEncodingTaskRunner");
70 DCHECK(encoding_task_runner_->BelongsToCurrentThread());
71
72 const gfx::Size frame_size = frame->visible_rect().size();
73 const base::TimeDelta duration = EstimateFrameDuration(frame);
74 const media::WebmMuxer::VideoParameters video_params(frame);
75
76 if (!IsInitialized(codec_config_) ||
77 gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
78 ConfigureEncoderOnEncodingTaskRunner(frame_size, &codec_config_, &encoder_);
79 }
80
81 const bool frame_has_alpha = frame->format() == media::PIXEL_FORMAT_YV12A;
82 if (frame_has_alpha && (!IsInitialized(alpha_codec_config_) ||
83 gfx::Size(alpha_codec_config_.g_w,
84 alpha_codec_config_.g_h) != frame_size)) {
85 ConfigureEncoderOnEncodingTaskRunner(frame_size, &alpha_codec_config_,
86 &alpha_encoder_);
87 u_plane_stride_ = media::VideoFrame::RowBytes(
88 VideoFrame::kUPlane, frame->format(), frame_size.width());
89 v_plane_stride_ = media::VideoFrame::RowBytes(
90 VideoFrame::kVPlane, frame->format(), frame_size.width());
91 v_plane_offset_ = media::VideoFrame::PlaneSize(
92 frame->format(), VideoFrame::kUPlane, frame_size)
93 .GetArea();
94 alpha_dummy_planes_.resize(
95 v_plane_offset_ + media::VideoFrame::PlaneSize(
96 frame->format(), VideoFrame::kVPlane, frame_size)
97 .GetArea());
98 // It is more expensive to encode 0x00, so use 0x80 instead.
99 std::fill(alpha_dummy_planes_.begin(), alpha_dummy_planes_.end(), 0x80);
100 }
101 // If we introduced a new alpha frame, force keyframe.
102 const bool force_keyframe = frame_has_alpha && !last_frame_had_alpha_;
103 last_frame_had_alpha_ = frame_has_alpha;
104
105 std::unique_ptr<std::string> data(new std::string);
106 bool keyframe = false;
107 DoEncode(encoder_.get(), frame_size, frame->data(VideoFrame::kYPlane),
108 frame->visible_data(VideoFrame::kYPlane),
109 frame->stride(VideoFrame::kYPlane),
110 frame->visible_data(VideoFrame::kUPlane),
111 frame->stride(VideoFrame::kUPlane),
112 frame->visible_data(VideoFrame::kVPlane),
113 frame->stride(VideoFrame::kVPlane), duration, force_keyframe,
114 data.get(), &keyframe);
115
116 std::unique_ptr<std::string> alpha_data(new std::string);
117 if (frame_has_alpha) {
118 bool alpha_keyframe = false;
119 DoEncode(alpha_encoder_.get(), frame_size, frame->data(VideoFrame::kAPlane),
120 frame->visible_data(VideoFrame::kAPlane),
121 frame->stride(VideoFrame::kAPlane), alpha_dummy_planes_.data(),
122 u_plane_stride_, alpha_dummy_planes_.data() + v_plane_offset_,
123 v_plane_stride_, duration, keyframe, alpha_data.get(),
124 &alpha_keyframe);
125 DCHECK_EQ(keyframe, alpha_keyframe);
126 }
127 frame = nullptr;
128
129 origin_task_runner_->PostTask(
130 FROM_HERE,
131 base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_,
132 video_params, base::Passed(&data), base::Passed(&alpha_data),
133 capture_timestamp, keyframe));
134 }
135
136 void VpxEncoder::DoEncode(vpx_codec_ctx_t* const encoder,
137 const gfx::Size& frame_size,
138 uint8_t* const data,
139 uint8_t* const y_plane,
140 int y_stride,
141 uint8_t* const u_plane,
142 int u_stride,
143 uint8_t* const v_plane,
144 int v_stride,
145 const base::TimeDelta& duration,
146 bool force_keyframe,
147 std::string* const output_data,
148 bool* const keyframe) {
149 DCHECK(encoding_task_runner_->BelongsToCurrentThread());
150
151 vpx_image_t vpx_image;
152 vpx_image_t* const result =
153 vpx_img_wrap(&vpx_image, VPX_IMG_FMT_I420, frame_size.width(),
154 frame_size.height(), 1 /* align */, data);
155 DCHECK_EQ(result, &vpx_image);
156 vpx_image.planes[VPX_PLANE_Y] = y_plane;
157 vpx_image.planes[VPX_PLANE_U] = u_plane;
158 vpx_image.planes[VPX_PLANE_V] = v_plane;
159 vpx_image.stride[VPX_PLANE_Y] = y_stride;
160 vpx_image.stride[VPX_PLANE_U] = u_stride;
161 vpx_image.stride[VPX_PLANE_V] = v_stride;
162
163 const vpx_codec_flags_t flags = force_keyframe ? VPX_EFLAG_FORCE_KF : 0;
164 // Encode the frame. The presentation time stamp argument here is fixed to
165 // zero to force the encoder to base its single-frame bandwidth calculations
166 // entirely on |predicted_frame_duration|.
167 const vpx_codec_err_t ret =
168 vpx_codec_encode(encoder, &vpx_image, 0 /* pts */,
169 duration.InMicroseconds(), flags, VPX_DL_REALTIME);
170 DCHECK_EQ(ret, VPX_CODEC_OK)
171 << vpx_codec_err_to_string(ret) << ", #" << vpx_codec_error(encoder)
172 << " -" << vpx_codec_error_detail(encoder);
173
174 *keyframe = false;
175 vpx_codec_iter_t iter = NULL;
176 const vpx_codec_cx_pkt_t* pkt = NULL;
177 while ((pkt = vpx_codec_get_cx_data(encoder, &iter)) != NULL) {
178 if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
179 continue;
180 output_data->assign(static_cast<char*>(pkt->data.frame.buf),
181 pkt->data.frame.sz);
182 *keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
183 break;
184 }
185 }
186
187 void VpxEncoder::ConfigureEncoderOnEncodingTaskRunner(
188 const gfx::Size& size,
189 vpx_codec_enc_cfg_t* codec_config,
190 ScopedVpxCodecCtxPtr* encoder) {
191 DCHECK(encoding_task_runner_->BelongsToCurrentThread());
192 if (IsInitialized(*codec_config)) {
193 // TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less-
194 // than-or-equal than the old size, in terms of area, the existing encoder
195 // instance could be reused after changing |codec_config->{g_w,g_h}|.
196 DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: "
197 << gfx::Size(codec_config->g_w, codec_config->g_h).ToString()
198 << " --> " << size.ToString() << (use_vp9_ ? " vp9" : " vp8");
199 encoder->reset();
200 }
201
202 const vpx_codec_iface_t* codec_interface =
203 use_vp9_ ? vpx_codec_vp9_cx() : vpx_codec_vp8_cx();
204 vpx_codec_err_t result = vpx_codec_enc_config_default(
205 codec_interface, codec_config, 0 /* reserved */);
206 DCHECK_EQ(VPX_CODEC_OK, result);
207
208 DCHECK_EQ(320u, codec_config->g_w);
209 DCHECK_EQ(240u, codec_config->g_h);
210 DCHECK_EQ(256u, codec_config->rc_target_bitrate);
211 // Use the selected bitrate or adjust default bit rate to account for the
212 // actual size. Note: |rc_target_bitrate| units are kbit per second.
213 if (bits_per_second_ > 0) {
214 codec_config->rc_target_bitrate = bits_per_second_ / 1000;
215 } else {
216 codec_config->rc_target_bitrate = size.GetArea() *
217 codec_config->rc_target_bitrate /
218 codec_config->g_w / codec_config->g_h;
219 }
220 // Both VP8/VP9 configuration should be Variable BitRate by default.
221 DCHECK_EQ(VPX_VBR, codec_config->rc_end_usage);
222 if (use_vp9_) {
223 // Number of frames to consume before producing output.
224 codec_config->g_lag_in_frames = 0;
225
226 // DCHECK that the profile selected by default is I420 (magic number 0).
227 DCHECK_EQ(0u, codec_config->g_profile);
228 } else {
229 // VP8 always produces frames instantaneously.
230 DCHECK_EQ(0u, codec_config->g_lag_in_frames);
231 }
232
233 DCHECK(size.width());
234 DCHECK(size.height());
235 codec_config->g_w = size.width();
236 codec_config->g_h = size.height();
237 codec_config->g_pass = VPX_RC_ONE_PASS;
238
239 // Timebase is the smallest interval used by the stream, can be set to the
240 // frame rate or to e.g. microseconds.
241 codec_config->g_timebase.num = 1;
242 codec_config->g_timebase.den = base::Time::kMicrosecondsPerSecond;
243
244 // Let the encoder decide where to place the Keyframes, between min and max.
245 // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
246 // max distance out of necessity.
247 // Note that due to http://crbug.com/440223, it might be necessary to force a
248 // key frame after 10,000frames since decoding fails after 30,000 non-key
249 // frames.
250 // Forcing a keyframe in regular intervals also allows seeking in the
251 // resulting recording with decent performance.
252 codec_config->kf_mode = VPX_KF_AUTO;
253 codec_config->kf_min_dist = 0;
254 codec_config->kf_max_dist = 100;
255
256 codec_config->g_threads = GetNumberOfThreadsForEncoding();
257
258 // Number of frames to consume before producing output.
259 codec_config->g_lag_in_frames = 0;
260
261 encoder->reset(new vpx_codec_ctx_t);
262 const vpx_codec_err_t ret = vpx_codec_enc_init(
263 encoder->get(), codec_interface, codec_config, 0 /* flags */);
264 DCHECK_EQ(VPX_CODEC_OK, ret);
265
266 if (use_vp9_) {
267 // Values of VP8E_SET_CPUUSED greater than 0 will increase encoder speed at
268 // the expense of quality up to a maximum value of 8 for VP9, by tuning the
269 // target time spent encoding the frame. Go from 8 to 5 (values for real
270 // time encoding) depending on the amount of cores available in the system.
271 const int kCpuUsed =
272 std::max(5, 8 - base::SysInfo::NumberOfProcessors() / 2);
273 result = vpx_codec_control(encoder->get(), VP8E_SET_CPUUSED, kCpuUsed);
274 DLOG_IF(WARNING, VPX_CODEC_OK != result) << "VP8E_SET_CPUUSED failed";
275 }
276 }
277
278 bool VpxEncoder::IsInitialized(const vpx_codec_enc_cfg_t& codec_config) const {
279 DCHECK(encoding_task_runner_->BelongsToCurrentThread());
280 return codec_config.g_timebase.den != 0;
281 }
282
283 base::TimeDelta VpxEncoder::EstimateFrameDuration(
284 const scoped_refptr<VideoFrame>& frame) {
285 DCHECK(encoding_task_runner_->BelongsToCurrentThread());
286
287 using base::TimeDelta;
288 TimeDelta predicted_frame_duration;
289 if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
290 &predicted_frame_duration) ||
291 predicted_frame_duration <= TimeDelta()) {
292 // The source of the video frame did not provide the frame duration. Use
293 // the actual amount of time between the current and previous frame as a
294 // prediction for the next frame's duration.
295 // TODO(mcasas): This duration estimation could lead to artifacts if the
296 // cadence of the received stream is compromised (e.g. camera freeze, pause,
297 // remote packet loss). Investigate using GetFrameRate() in this case.
298 predicted_frame_duration = frame->timestamp() - last_frame_timestamp_;
299 }
300 last_frame_timestamp_ = frame->timestamp();
301 // Make sure |predicted_frame_duration| is in a safe range of values.
302 const TimeDelta kMaxFrameDuration = TimeDelta::FromSecondsD(1.0 / 8);
303 const TimeDelta kMinFrameDuration = TimeDelta::FromMilliseconds(1);
304 return std::min(kMaxFrameDuration,
305 std::max(predicted_frame_duration, kMinFrameDuration));
306 }
307
308 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/media_recorder/vpx_encoder.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698