1 // Copyright 2013 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 "remoting/codec/video_encoder_vpx.h"
6
7 #include "base/bind.h"
8 #include "base/logging.h"
9 #include "base/sys_info.h"
10 #include "base/time/time.h"
11 #include "media/base/yuv_convert.h"
12 #include "remoting/base/util.h"
13 #include "remoting/proto/video.pb.h"
14 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
15 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
16 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
17
18 extern "C" {
19 #define VPX_CODEC_DISABLE_COMPAT 1
20 #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
21 #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
22 }
23
24 namespace remoting {
25
26 namespace {
27
28 // Defines the dimension of a macro block. This is used to compute the active
29 // map for the encoder.
30 const int kMacroBlockSize = 16;
31
CreateVP8Codec(const webrtc::DesktopSize & size)32 ScopedVpxCodec CreateVP8Codec(const webrtc::DesktopSize& size) {
33 ScopedVpxCodec codec(new vpx_codec_ctx_t);
34
35 // Configure the encoder.
36 vpx_codec_enc_cfg_t config;
37 const vpx_codec_iface_t* algo = vpx_codec_vp8_cx();
38 CHECK(algo);
39 vpx_codec_err_t ret = vpx_codec_enc_config_default(algo, &config, 0);
40 if (ret != VPX_CODEC_OK)
41 return ScopedVpxCodec();
42
43 config.rc_target_bitrate = size.width() * size.height() *
44 config.rc_target_bitrate / config.g_w / config.g_h;
45 config.g_w = size.width();
46 config.g_h = size.height();
47 config.g_pass = VPX_RC_ONE_PASS;
48
49 // Value of 2 means using the real time profile. This is basically a
50 // redundant option since we explicitly select real time mode when doing
51 // encoding.
52 config.g_profile = 2;
53
54 // Using 2 threads gives a great boost in performance for most systems with
55 // adequate processing power. NB: Going to multiple threads on low end
56 // windows systems can really hurt performance.
57 // http://crbug.com/99179
58 config.g_threads = (base::SysInfo::NumberOfProcessors() > 2) ? 2 : 1;
59 config.rc_min_quantizer = 20;
60 config.rc_max_quantizer = 30;
61 config.g_timebase.num = 1;
62 config.g_timebase.den = 20;
63
64 if (vpx_codec_enc_init(codec.get(), algo, &config, 0))
65 return ScopedVpxCodec();
66
67 // Value of 16 will have the smallest CPU load. This turns off subpixel
68 // motion search.
69 if (vpx_codec_control(codec.get(), VP8E_SET_CPUUSED, 16))
70 return ScopedVpxCodec();
71
72 // Use the lowest level of noise sensitivity so as to spend less time
73 // on motion estimation and inter-prediction mode.
74 if (vpx_codec_control(codec.get(), VP8E_SET_NOISE_SENSITIVITY, 0))
75 return ScopedVpxCodec();
76
77 return codec.Pass();
78 }
79
80 } // namespace
81
82 // static
CreateForVP8()83 scoped_ptr<VideoEncoderVpx> VideoEncoderVpx::CreateForVP8() {
84 return scoped_ptr<VideoEncoderVpx>(
85 new VideoEncoderVpx(base::Bind(&CreateVP8Codec)));
86 }
87
~VideoEncoderVpx()88 VideoEncoderVpx::~VideoEncoderVpx() {}
89
Encode(const webrtc::DesktopFrame & frame)90 scoped_ptr<VideoPacket> VideoEncoderVpx::Encode(
91 const webrtc::DesktopFrame& frame) {
92 DCHECK_LE(32, frame.size().width());
93 DCHECK_LE(32, frame.size().height());
94
95 base::Time encode_start_time = base::Time::Now();
96
97 if (!codec_ ||
98 !frame.size().equals(webrtc::DesktopSize(image_->w, image_->h))) {
99 bool ret = Initialize(frame.size());
100 // TODO(hclam): Handle error better.
101 CHECK(ret) << "Initialization of encoder failed";
102 }
103
104 // Convert the updated capture data ready for encode.
105 webrtc::DesktopRegion updated_region;
106 PrepareImage(frame, &updated_region);
107
108 // Update active map based on updated region.
109 PrepareActiveMap(updated_region);
110
111 // Apply active map to the encoder.
112 vpx_active_map_t act_map;
113 act_map.rows = active_map_height_;
114 act_map.cols = active_map_width_;
115 act_map.active_map = active_map_.get();
116 if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) {
117 LOG(ERROR) << "Unable to apply active map";
118 }
119
120 // Do the actual encoding.
121 vpx_codec_err_t ret = vpx_codec_encode(codec_.get(), image_.get(),
122 last_timestamp_,
123 1, 0, VPX_DL_REALTIME);
124 DCHECK_EQ(ret, VPX_CODEC_OK)
125 << "Encoding error: " << vpx_codec_err_to_string(ret) << "\n"
126 << "Details: " << vpx_codec_error(codec_.get()) << "\n"
127 << vpx_codec_error_detail(codec_.get());
128
129 // TODO(hclam): Apply the proper timestamp here.
130 last_timestamp_ += 50;
131
132 // Read the encoded data.
133 vpx_codec_iter_t iter = NULL;
134 bool got_data = false;
135
136 // TODO(hclam): Make sure we get exactly one frame from the packet.
137 // TODO(hclam): We should provide the output buffer to avoid one copy.
138 scoped_ptr<VideoPacket> packet(new VideoPacket());
139
140 while (!got_data) {
141 const vpx_codec_cx_pkt_t* vpx_packet =
142 vpx_codec_get_cx_data(codec_.get(), &iter);
143 if (!vpx_packet)
144 continue;
145
146 switch (vpx_packet->kind) {
147 case VPX_CODEC_CX_FRAME_PKT:
148 got_data = true;
149 packet->set_data(vpx_packet->data.frame.buf, vpx_packet->data.frame.sz);
150 break;
151 default:
152 break;
153 }
154 }
155
156 // Construct the VideoPacket message.
157 packet->mutable_format()->set_encoding(VideoPacketFormat::ENCODING_VP8);
158 packet->mutable_format()->set_screen_width(frame.size().width());
159 packet->mutable_format()->set_screen_height(frame.size().height());
160 packet->set_capture_time_ms(frame.capture_time_ms());
161 packet->set_encode_time_ms(
162 (base::Time::Now() - encode_start_time).InMillisecondsRoundedUp());
163 if (!frame.dpi().is_zero()) {
164 packet->mutable_format()->set_x_dpi(frame.dpi().x());
165 packet->mutable_format()->set_y_dpi(frame.dpi().y());
166 }
167 for (webrtc::DesktopRegion::Iterator r(updated_region); !r.IsAtEnd();
168 r.Advance()) {
169 Rect* rect = packet->add_dirty_rects();
170 rect->set_x(r.rect().left());
171 rect->set_y(r.rect().top());
172 rect->set_width(r.rect().width());
173 rect->set_height(r.rect().height());
174 }
175
176 return packet.Pass();
177 }
178
VideoEncoderVpx(const InitializeCodecCallback & init_codec)179 VideoEncoderVpx::VideoEncoderVpx(const InitializeCodecCallback& init_codec)
180 : init_codec_(init_codec),
181 active_map_width_(0),
182 active_map_height_(0),
183 last_timestamp_(0) {
184 }
185
Initialize(const webrtc::DesktopSize & size)186 bool VideoEncoderVpx::Initialize(const webrtc::DesktopSize& size) {
187 codec_.reset();
188
189 image_.reset(new vpx_image_t());
190 memset(image_.get(), 0, sizeof(vpx_image_t));
191
192 image_->fmt = VPX_IMG_FMT_YV12;
193
194 // libvpx seems to require both to be assigned.
195 image_->d_w = size.width();
196 image_->w = size.width();
197 image_->d_h = size.height();
198 image_->h = size.height();
199
200 // libvpx should derive this from|fmt| but currently has a bug:
201 // https://code.google.com/p/webm/issues/detail?id=627
202 image_->x_chroma_shift = 1;
203 image_->y_chroma_shift = 1;
204
205 // Initialize active map.
206 active_map_width_ = (image_->w + kMacroBlockSize - 1) / kMacroBlockSize;
207 active_map_height_ = (image_->h + kMacroBlockSize - 1) / kMacroBlockSize;
208 active_map_.reset(new uint8[active_map_width_ * active_map_height_]);
209
210 // YUV image size is 1.5 times of a plane. Multiplication is performed first
211 // to avoid rounding error.
212 const int y_plane_size = image_->w * image_->h;
213 const int uv_width = (image_->w + 1) / 2;
214 const int uv_height = (image_->h + 1) / 2;
215 const int uv_plane_size = uv_width * uv_height;
216 const int yuv_image_size = y_plane_size + uv_plane_size * 2;
217
218 // libvpx may try to access memory after the buffer (it still
219 // doesn't use it) - it copies the data in 16x16 blocks:
220 // crbug.com/119633 . Here we workaround that problem by adding
221 // padding at the end of the buffer. Overreading to U and V buffers
222 // is safe so the padding is necessary only at the end.
223 //
224 // TODO(sergeyu): Remove this padding when the bug is fixed in libvpx.
225 const int active_map_area = active_map_width_ * kMacroBlockSize *
226 active_map_height_ * kMacroBlockSize;
227 const int padding_size = active_map_area - y_plane_size;
228 const int buffer_size = yuv_image_size + padding_size;
229
230 yuv_image_.reset(new uint8[buffer_size]);
231
232 // Reset image value to 128 so we just need to fill in the y plane.
233 memset(yuv_image_.get(), 128, yuv_image_size);
234
235 // Fill in the information for |image_|.
236 unsigned char* image = reinterpret_cast<unsigned char*>(yuv_image_.get());
237 image_->planes[0] = image;
238 image_->planes[1] = image + y_plane_size;
239 image_->planes[2] = image + y_plane_size + uv_plane_size;
240 image_->stride[0] = image_->w;
241 image_->stride[1] = uv_width;
242 image_->stride[2] = uv_width;
243
244 // Initialize the codec.
245 codec_ = init_codec_.Run(size);
246
247 return codec_;
248 }
249
PrepareImage(const webrtc::DesktopFrame & frame,webrtc::DesktopRegion * updated_region)250 void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame,
251 webrtc::DesktopRegion* updated_region) {
252 if (frame.updated_region().is_empty()) {
253 updated_region->Clear();
254 return;
255 }
256
257 // Align the region to macroblocks, to avoid encoding artefacts.
258 // This also ensures that all rectangles have even-aligned top-left, which
259 // is required for ConvertRGBToYUVWithRect() to work.
260 std::vector<webrtc::DesktopRect> aligned_rects;
261 for (webrtc::DesktopRegion::Iterator r(frame.updated_region());
262 !r.IsAtEnd(); r.Advance()) {
263 const webrtc::DesktopRect& rect = r.rect();
264 aligned_rects.push_back(AlignRect(webrtc::DesktopRect::MakeLTRB(
265 rect.left(), rect.top(), rect.right(), rect.bottom())));
266 }
267 DCHECK(!aligned_rects.empty());
268 updated_region->Clear();
269 updated_region->AddRects(&aligned_rects[0], aligned_rects.size());
270
271 // Clip back to the screen dimensions, in case they're not macroblock aligned.
272 // The conversion routines don't require even width & height, so this is safe
273 // even if the source dimensions are not even.
274 updated_region->IntersectWith(
275 webrtc::DesktopRect::MakeWH(image_->w, image_->h));
276
277 // Convert the updated region to YUV ready for encoding.
278 const uint8* rgb_data = frame.data();
279 const int rgb_stride = frame.stride();
280 const int y_stride = image_->stride[0];
281 DCHECK_EQ(image_->stride[1], image_->stride[2]);
282 const int uv_stride = image_->stride[1];
283 uint8* y_data = image_->planes[0];
284 uint8* u_data = image_->planes[1];
285 uint8* v_data = image_->planes[2];
286 for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd();
287 r.Advance()) {
288 const webrtc::DesktopRect& rect = r.rect();
289 ConvertRGB32ToYUVWithRect(
290 rgb_data, y_data, u_data, v_data,
291 rect.left(), rect.top(), rect.width(), rect.height(),
292 rgb_stride, y_stride, uv_stride);
293 }
294 }
295
PrepareActiveMap(const webrtc::DesktopRegion & updated_region)296 void VideoEncoderVpx::PrepareActiveMap(
297 const webrtc::DesktopRegion& updated_region) {
298 // Clear active map first.
299 memset(active_map_.get(), 0, active_map_width_ * active_map_height_);
300
301 // Mark updated areas active.
302 for (webrtc::DesktopRegion::Iterator r(updated_region); !r.IsAtEnd();
303 r.Advance()) {
304 const webrtc::DesktopRect& rect = r.rect();
305 int left = rect.left() / kMacroBlockSize;
306 int right = (rect.right() - 1) / kMacroBlockSize;
307 int top = rect.top() / kMacroBlockSize;
308 int bottom = (rect.bottom() - 1) / kMacroBlockSize;
309 DCHECK_LT(right, active_map_width_);
310 DCHECK_LT(bottom, active_map_height_);
311
312 uint8* map = active_map_.get() + top * active_map_width_;
313 for (int y = top; y <= bottom; ++y) {
314 for (int x = left; x <= right; ++x)
315 map[x] = 1;
316 map += active_map_width_;
317 }
318 }
319 }
320
321 } // namespace remoting
322