video_decoder_vpx.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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
16extern "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
22namespace remoting {
23
24namespace {
25
26const uint32 kTransparentColor = 0;
27
28// Fills the rectangle |rect| with the given ARGB color |color| in |buffer|.
29void 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
45scoped_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(INFO) << "Cannot initialize codec.";
58    return scoped_ptr<VideoDecoderVpx>();
59  }
60
61  return scoped_ptr<VideoDecoderVpx>(new VideoDecoderVpx(codec.Pass()));
62}
63
64// static
65scoped_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(INFO) << "Cannot initialize codec.";
78    return scoped_ptr<VideoDecoderVpx>();
79  }
80
81  return scoped_ptr<VideoDecoderVpx>(new VideoDecoderVpx(codec.Pass()));
82}
83
84VideoDecoderVpx::~VideoDecoderVpx() {}
85
86void 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
94bool 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(INFO) << "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(INFO) << "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
148void 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
164void 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
280const webrtc::DesktopRegion* VideoDecoderVpx::GetImageShape() {
281  return &desktop_shape_;
282}
283
284VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec)
285    : codec_(codec.Pass()),
286      last_image_(NULL) {
287  DCHECK(codec_);
288}
289
290void 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