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