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