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
17extern "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
23namespace remoting {
24
25namespace {
26
27const uint32 kTransparentColor = 0;
28
29// Fills the rectangle |rect| with the given ARGB color |color| in |buffer|.
30void 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
46scoped_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
66scoped_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
85VideoDecoderVpx::~VideoDecoderVpx() {}
86
87void 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
95bool 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
150void 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
166void 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
326const webrtc::DesktopRegion* VideoDecoderVpx::GetImageShape() {
327  return &desktop_shape_;
328}
329
330VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec)
331    : codec_(codec.Pass()),
332      last_image_(NULL) {
333  DCHECK(codec_);
334}
335
336void 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