Home | History | Annotate | Download | only in codec
      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