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_decoder_vpx.h" 6 7 #include <math.h> 8 9 #include <algorithm> 10 11 #include "base/logging.h" 12 #include "media/base/media.h" 13 #include "media/base/yuv_convert.h" 14 #include "remoting/base/util.h" 15 #include "third_party/libyuv/include/libyuv/convert_argb.h" 16 17 extern "C" { 18 #define VPX_CODEC_DISABLE_COMPAT 1 19 #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" 20 #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" 21 } 22 23 namespace remoting { 24 25 namespace { 26 27 const uint32 kTransparentColor = 0; 28 29 // Fills the rectangle |rect| with the given ARGB color |color| in |buffer|. 30 void FillRect(uint8* buffer, 31 int stride, 32 const webrtc::DesktopRect& rect, 33 uint32 color) { 34 uint32* ptr = reinterpret_cast<uint32*>(buffer + (rect.top() * stride) + 35 (rect.left() * VideoDecoder::kBytesPerPixel)); 36 int width = rect.width(); 37 for (int height = rect.height(); height > 0; --height) { 38 std::fill(ptr, ptr + width, color); 39 ptr += stride / VideoDecoder::kBytesPerPixel; 40 } 41 } 42 43 } // namespace 44 45 // static 46 scoped_ptr<VideoDecoderVpx> VideoDecoderVpx::CreateForVP8() { 47 ScopedVpxCodec codec(new vpx_codec_ctx_t); 48 49 // TODO(hclam): Scale the number of threads with number of cores of the 50 // machine. 51 vpx_codec_dec_cfg config; 52 config.w = 0; 53 config.h = 0; 54 config.threads = 2; 55 vpx_codec_err_t ret = 56 vpx_codec_dec_init(codec.get(), vpx_codec_vp8_dx(), &config, 0); 57 if (ret != VPX_CODEC_OK) { 58 LOG(ERROR) << "Cannot initialize codec."; 59 return scoped_ptr<VideoDecoderVpx>(); 60 } 61 62 return scoped_ptr<VideoDecoderVpx>(new VideoDecoderVpx(codec.Pass())); 63 } 64 65 // static 66 scoped_ptr<VideoDecoderVpx> VideoDecoderVpx::CreateForVP9() { 67 ScopedVpxCodec codec(new vpx_codec_ctx_t); 68 69 // TODO(hclam): Scale the number of threads with number of cores of the 70 // machine. 71 vpx_codec_dec_cfg config; 72 config.w = 0; 73 config.h = 0; 74 config.threads = 2; 75 vpx_codec_err_t ret = 76 vpx_codec_dec_init(codec.get(), vpx_codec_vp9_dx(), &config, 0); 77 if (ret != VPX_CODEC_OK) { 78 LOG(ERROR) << "Cannot initialize codec."; 79 return scoped_ptr<VideoDecoderVpx>(); 80 } 81 82 return scoped_ptr<VideoDecoderVpx>(new VideoDecoderVpx(codec.Pass())); 83 } 84 85 VideoDecoderVpx::~VideoDecoderVpx() {} 86 87 void VideoDecoderVpx::Initialize(const webrtc::DesktopSize& screen_size) { 88 DCHECK(!screen_size.is_empty()); 89 90 screen_size_ = screen_size; 91 92 transparent_region_.SetRect(webrtc::DesktopRect::MakeSize(screen_size_)); 93 } 94 95 bool VideoDecoderVpx::DecodePacket(const VideoPacket& packet) { 96 DCHECK(!screen_size_.is_empty()); 97 98 // Do the actual decoding. 99 vpx_codec_err_t ret = vpx_codec_decode( 100 codec_.get(), reinterpret_cast<const uint8*>(packet.data().data()), 101 packet.data().size(), NULL, 0); 102 if (ret != VPX_CODEC_OK) { 103 const char* error = vpx_codec_error(codec_.get()); 104 const char* error_detail = vpx_codec_error_detail(codec_.get()); 105 LOG(ERROR) << "Decoding failed:" << (error ? error : "(NULL)") << "\n" 106 << "Details: " << (error_detail ? error_detail : "(NULL)"); 107 return false; 108 } 109 110 // Gets the decoded data. 111 vpx_codec_iter_t iter = NULL; 112 vpx_image_t* image = vpx_codec_get_frame(codec_.get(), &iter); 113 if (!image) { 114 LOG(ERROR) << "No video frame decoded"; 115 return false; 116 } 117 last_image_ = image; 118 119 webrtc::DesktopRegion region; 120 for (int i = 0; i < packet.dirty_rects_size(); ++i) { 121 Rect remoting_rect = packet.dirty_rects(i); 122 region.AddRect(webrtc::DesktopRect::MakeXYWH( 123 remoting_rect.x(), remoting_rect.y(), 124 remoting_rect.width(), remoting_rect.height())); 125 } 126 127 updated_region_.AddRegion(region); 128 129 // Update the desktop shape region. 130 webrtc::DesktopRegion desktop_shape_region; 131 if (packet.has_use_desktop_shape()) { 132 for (int i = 0; i < packet.desktop_shape_rects_size(); ++i) { 133 Rect remoting_rect = packet.desktop_shape_rects(i); 134 desktop_shape_region.AddRect(webrtc::DesktopRect::MakeXYWH( 135 remoting_rect.x(), remoting_rect.y(), 136 remoting_rect.width(), remoting_rect.height())); 137 } 138 } else { 139 // Fallback for the case when the host didn't include the desktop shape 140 // region. 141 desktop_shape_region = 142 webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(screen_size_)); 143 } 144 145 UpdateImageShapeRegion(&desktop_shape_region); 146 147 return true; 148 } 149 150 void VideoDecoderVpx::Invalidate(const webrtc::DesktopSize& view_size, 151 const webrtc::DesktopRegion& region) { 152 DCHECK(!view_size.is_empty()); 153 154 for (webrtc::DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) { 155 updated_region_.AddRect(ScaleRect(i.rect(), view_size, screen_size_)); 156 } 157 158 // Updated areas outside of the new desktop shape region should be made 159 // transparent, not repainted. 160 webrtc::DesktopRegion difference = updated_region_; 161 difference.Subtract(desktop_shape_); 162 updated_region_.Subtract(difference); 163 transparent_region_.AddRegion(difference); 164 } 165 166 void VideoDecoderVpx::RenderFrame(const webrtc::DesktopSize& view_size, 167 const webrtc::DesktopRect& clip_area, 168 uint8* image_buffer, 169 int image_stride, 170 webrtc::DesktopRegion* output_region) { 171 DCHECK(!screen_size_.is_empty()); 172 DCHECK(!view_size.is_empty()); 173 174 // Early-return and do nothing if we haven't yet decoded any frames. 175 if (!last_image_) 176 return; 177 178 webrtc::DesktopRect source_clip = 179 webrtc::DesktopRect::MakeWH(last_image_->d_w, last_image_->d_h); 180 181 // VP8 only outputs I420 frames, but VP9 can also produce I444. 182 switch (last_image_->fmt) { 183 case VPX_IMG_FMT_I444: { 184 // TODO(wez): Add scaling support to the I444 conversion path. 185 if (view_size.equals(screen_size_)) { 186 for (webrtc::DesktopRegion::Iterator i(updated_region_); 187 !i.IsAtEnd(); i.Advance()) { 188 // Determine the scaled area affected by this rectangle changing. 189 webrtc::DesktopRect rect = i.rect(); 190 rect.IntersectWith(source_clip); 191 rect.IntersectWith(clip_area); 192 if (rect.is_empty()) 193 continue; 194 195 int image_offset = image_stride * rect.top() + 196 rect.left() * VideoDecoder::kBytesPerPixel; 197 int y_offset = last_image_->stride[0] * rect.top() + rect.left(); 198 int u_offset = last_image_->stride[1] * rect.top() + rect.left(); 199 int v_offset = last_image_->stride[2] * rect.top() + rect.left(); 200 libyuv::I444ToARGB(last_image_->planes[0] + y_offset, 201 last_image_->stride[0], 202 last_image_->planes[1] + u_offset, 203 last_image_->stride[1], 204 last_image_->planes[2] + v_offset, 205 last_image_->stride[2], 206 image_buffer + image_offset, image_stride, 207 rect.width(), rect.height()); 208 209 output_region->AddRect(rect); 210 } 211 } 212 break; 213 } 214 case VPX_IMG_FMT_I420: { 215 // ScaleYUVToRGB32WithRect does not currently support up-scaling. We 216 // won't be asked to up-scale except during resizes or if page zoom is 217 // >100%, so we work-around the limitation by using the slower 218 // ScaleYUVToRGB32. 219 // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can 220 // up-scale. 221 if (!updated_region_.is_empty() && 222 (source_clip.width() < view_size.width() || 223 source_clip.height() < view_size.height())) { 224 // We're scaling only |clip_area| into the |image_buffer|, so we need to 225 // work out which source rectangle that corresponds to. 226 webrtc::DesktopRect source_rect = 227 ScaleRect(clip_area, view_size, screen_size_); 228 source_rect = webrtc::DesktopRect::MakeLTRB( 229 RoundToTwosMultiple(source_rect.left()), 230 RoundToTwosMultiple(source_rect.top()), 231 source_rect.right(), 232 source_rect.bottom()); 233 234 // If there were no changes within the clip source area then don't 235 // render. 236 webrtc::DesktopRegion intersection(source_rect); 237 intersection.IntersectWith(updated_region_); 238 if (intersection.is_empty()) 239 return; 240 241 // Scale & convert the entire clip area. 242 int y_offset = CalculateYOffset(source_rect.left(), source_rect.top(), 243 last_image_->stride[0]); 244 int uv_offset = CalculateUVOffset(source_rect.left(), source_rect.top(), 245 last_image_->stride[1]); 246 ScaleYUVToRGB32(last_image_->planes[0] + y_offset, 247 last_image_->planes[1] + uv_offset, 248 last_image_->planes[2] + uv_offset, 249 image_buffer, 250 source_rect.width(), 251 source_rect.height(), 252 clip_area.width(), 253 clip_area.height(), 254 last_image_->stride[0], 255 last_image_->stride[1], 256 image_stride, 257 media::YV12, 258 media::ROTATE_0, 259 media::FILTER_BILINEAR); 260 261 output_region->AddRect(clip_area); 262 updated_region_.Subtract(source_rect); 263 return; 264 } 265 266 for (webrtc::DesktopRegion::Iterator i(updated_region_); 267 !i.IsAtEnd(); i.Advance()) { 268 // Determine the scaled area affected by this rectangle changing. 269 webrtc::DesktopRect rect = i.rect(); 270 rect.IntersectWith(source_clip); 271 if (rect.is_empty()) 272 continue; 273 rect = ScaleRect(rect, screen_size_, view_size); 274 rect.IntersectWith(clip_area); 275 if (rect.is_empty()) 276 continue; 277 278 ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0], 279 last_image_->planes[1], 280 last_image_->planes[2], 281 last_image_->stride[0], 282 last_image_->stride[1], 283 screen_size_, 284 source_clip, 285 image_buffer, 286 image_stride, 287 view_size, 288 clip_area, 289 rect); 290 291 output_region->AddRect(rect); 292 } 293 294 updated_region_.Subtract(ScaleRect(clip_area, view_size, screen_size_)); 295 break; 296 } 297 default: { 298 LOG(ERROR) << "Unsupported image format:" << last_image_->fmt; 299 return; 300 } 301 } 302 303 for (webrtc::DesktopRegion::Iterator i(transparent_region_); 304 !i.IsAtEnd(); i.Advance()) { 305 // Determine the scaled area affected by this rectangle changing. 306 webrtc::DesktopRect rect = i.rect(); 307 rect.IntersectWith(source_clip); 308 if (rect.is_empty()) 309 continue; 310 rect = ScaleRect(rect, screen_size_, view_size); 311 rect.IntersectWith(clip_area); 312 if (rect.is_empty()) 313 continue; 314 315 // Fill the rectange with transparent pixels. 316 FillRect(image_buffer, image_stride, rect, kTransparentColor); 317 output_region->AddRect(rect); 318 } 319 320 webrtc::DesktopRect scaled_clip_area = 321 ScaleRect(clip_area, view_size, screen_size_); 322 updated_region_.Subtract(scaled_clip_area); 323 transparent_region_.Subtract(scaled_clip_area); 324 } 325 326 const webrtc::DesktopRegion* VideoDecoderVpx::GetImageShape() { 327 return &desktop_shape_; 328 } 329 330 VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec) 331 : codec_(codec.Pass()), 332 last_image_(NULL) { 333 DCHECK(codec_); 334 } 335 336 void VideoDecoderVpx::UpdateImageShapeRegion( 337 webrtc::DesktopRegion* new_desktop_shape) { 338 // Add all areas that have been updated or become transparent to the 339 // transparent region. Exclude anything within the new desktop shape. 340 transparent_region_.AddRegion(desktop_shape_); 341 transparent_region_.AddRegion(updated_region_); 342 transparent_region_.Subtract(*new_desktop_shape); 343 344 // Add newly exposed areas to the update region and limit updates to the new 345 // desktop shape. 346 webrtc::DesktopRegion difference = *new_desktop_shape; 347 difference.Subtract(desktop_shape_); 348 updated_region_.AddRegion(difference); 349 updated_region_.IntersectWith(*new_desktop_shape); 350 351 // Set the new desktop shape region. 352 desktop_shape_.Swap(new_desktop_shape); 353 } 354 355 } // namespace remoting 356