video_decoder_vpx.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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(ERROR) << "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(ERROR) << "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    const char* error = vpx_codec_error(codec_.get());
103    const char* error_detail = vpx_codec_error_detail(codec_.get());
104    LOG(ERROR) << "Decoding failed:" << (error ? error : "(NULL)") << "\n"
105               << "Details: " << (error_detail ? error_detail : "(NULL)");
106    return false;
107  }
108
109  // Gets the decoded data.
110  vpx_codec_iter_t iter = NULL;
111  vpx_image_t* image = vpx_codec_get_frame(codec_.get(), &iter);
112  if (!image) {
113    LOG(ERROR) << "No video frame decoded";
114    return false;
115  }
116  last_image_ = image;
117
118  webrtc::DesktopRegion region;
119  for (int i = 0; i < packet.dirty_rects_size(); ++i) {
120    Rect remoting_rect = packet.dirty_rects(i);
121    region.AddRect(webrtc::DesktopRect::MakeXYWH(
122        remoting_rect.x(), remoting_rect.y(),
123        remoting_rect.width(), remoting_rect.height()));
124  }
125
126  updated_region_.AddRegion(region);
127
128  // Update the desktop shape region.
129  webrtc::DesktopRegion desktop_shape_region;
130  if (packet.has_use_desktop_shape()) {
131    for (int i = 0; i < packet.desktop_shape_rects_size(); ++i) {
132      Rect remoting_rect = packet.desktop_shape_rects(i);
133      desktop_shape_region.AddRect(webrtc::DesktopRect::MakeXYWH(
134          remoting_rect.x(), remoting_rect.y(),
135          remoting_rect.width(), remoting_rect.height()));
136    }
137  } else {
138    // Fallback for the case when the host didn't include the desktop shape
139    // region.
140    desktop_shape_region =
141        webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(screen_size_));
142  }
143
144  UpdateImageShapeRegion(&desktop_shape_region);
145
146  return true;
147}
148
149void VideoDecoderVpx::Invalidate(const webrtc::DesktopSize& view_size,
150                                 const webrtc::DesktopRegion& region) {
151  DCHECK(!view_size.is_empty());
152
153  for (webrtc::DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) {
154    updated_region_.AddRect(ScaleRect(i.rect(), view_size, screen_size_));
155  }
156
157  // Updated areas outside of the new desktop shape region should be made
158  // transparent, not repainted.
159  webrtc::DesktopRegion difference = updated_region_;
160  difference.Subtract(desktop_shape_);
161  updated_region_.Subtract(difference);
162  transparent_region_.AddRegion(difference);
163}
164
165void VideoDecoderVpx::RenderFrame(const webrtc::DesktopSize& view_size,
166                                  const webrtc::DesktopRect& clip_area,
167                                  uint8* image_buffer,
168                                  int image_stride,
169                                  webrtc::DesktopRegion* output_region) {
170  DCHECK(!screen_size_.is_empty());
171  DCHECK(!view_size.is_empty());
172
173  // Early-return and do nothing if we haven't yet decoded any frames.
174  if (!last_image_)
175    return;
176
177  webrtc::DesktopRect source_clip =
178      webrtc::DesktopRect::MakeWH(last_image_->d_w, last_image_->d_h);
179
180  // ScaleYUVToRGB32WithRect does not currently support up-scaling.  We won't
181  // be asked to up-scale except during resizes or if page zoom is >100%, so
182  // we work-around the limitation by using the slower ScaleYUVToRGB32.
183  // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can up-scale.
184  if (!updated_region_.is_empty() &&
185      (source_clip.width() < view_size.width() ||
186       source_clip.height() < view_size.height())) {
187    // We're scaling only |clip_area| into the |image_buffer|, so we need to
188    // work out which source rectangle that corresponds to.
189    webrtc::DesktopRect source_rect =
190        ScaleRect(clip_area, view_size, screen_size_);
191    source_rect = webrtc::DesktopRect::MakeLTRB(
192        RoundToTwosMultiple(source_rect.left()),
193        RoundToTwosMultiple(source_rect.top()),
194        source_rect.right(),
195        source_rect.bottom());
196
197    // If there were no changes within the clip source area then don't render.
198    webrtc::DesktopRegion intersection(source_rect);
199    intersection.IntersectWith(updated_region_);
200    if (intersection.is_empty())
201      return;
202
203    // Scale & convert the entire clip area.
204    int y_offset = CalculateYOffset(source_rect.left(), source_rect.top(),
205                                    last_image_->stride[0]);
206    int uv_offset = CalculateUVOffset(source_rect.left(), source_rect.top(),
207                                      last_image_->stride[1]);
208    ScaleYUVToRGB32(last_image_->planes[0] + y_offset,
209                    last_image_->planes[1] + uv_offset,
210                    last_image_->planes[2] + uv_offset,
211                    image_buffer,
212                    source_rect.width(),
213                    source_rect.height(),
214                    clip_area.width(),
215                    clip_area.height(),
216                    last_image_->stride[0],
217                    last_image_->stride[1],
218                    image_stride,
219                    media::YV12,
220                    media::ROTATE_0,
221                    media::FILTER_BILINEAR);
222
223    output_region->AddRect(clip_area);
224    updated_region_.Subtract(source_rect);
225    return;
226  }
227
228  for (webrtc::DesktopRegion::Iterator i(updated_region_);
229       !i.IsAtEnd(); i.Advance()) {
230    // Determine the scaled area affected by this rectangle changing.
231    webrtc::DesktopRect rect = i.rect();
232    rect.IntersectWith(source_clip);
233    if (rect.is_empty())
234      continue;
235    rect = ScaleRect(rect, screen_size_, view_size);
236    rect.IntersectWith(clip_area);
237    if (rect.is_empty())
238      continue;
239
240    ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0],
241                                  last_image_->planes[1],
242                                  last_image_->planes[2],
243                                  last_image_->stride[0],
244                                  last_image_->stride[1],
245                                  screen_size_,
246                                  source_clip,
247                                  image_buffer,
248                                  image_stride,
249                                  view_size,
250                                  clip_area,
251                                  rect);
252
253    output_region->AddRect(rect);
254  }
255
256  updated_region_.Subtract(ScaleRect(clip_area, view_size, screen_size_));
257
258  for (webrtc::DesktopRegion::Iterator i(transparent_region_);
259       !i.IsAtEnd(); i.Advance()) {
260    // Determine the scaled area affected by this rectangle changing.
261    webrtc::DesktopRect rect = i.rect();
262    rect.IntersectWith(source_clip);
263    if (rect.is_empty())
264      continue;
265    rect = ScaleRect(rect, screen_size_, view_size);
266    rect.IntersectWith(clip_area);
267    if (rect.is_empty())
268      continue;
269
270    // Fill the rectange with transparent pixels.
271    FillRect(image_buffer, image_stride, rect, kTransparentColor);
272    output_region->AddRect(rect);
273  }
274
275  webrtc::DesktopRect scaled_clip_area =
276      ScaleRect(clip_area, view_size, screen_size_);
277  updated_region_.Subtract(scaled_clip_area);
278  transparent_region_.Subtract(scaled_clip_area);
279}
280
281const webrtc::DesktopRegion* VideoDecoderVpx::GetImageShape() {
282  return &desktop_shape_;
283}
284
285VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec)
286    : codec_(codec.Pass()),
287      last_image_(NULL) {
288  DCHECK(codec_);
289}
290
291void VideoDecoderVpx::UpdateImageShapeRegion(
292    webrtc::DesktopRegion* new_desktop_shape) {
293  // Add all areas that have been updated or become transparent to the
294  // transparent region. Exclude anything within the new desktop shape.
295  transparent_region_.AddRegion(desktop_shape_);
296  transparent_region_.AddRegion(updated_region_);
297  transparent_region_.Subtract(*new_desktop_shape);
298
299  // Add newly exposed areas to the update region and limit updates to the new
300  // desktop shape.
301  webrtc::DesktopRegion difference = *new_desktop_shape;
302  difference.Subtract(desktop_shape_);
303  updated_region_.AddRegion(difference);
304  updated_region_.IntersectWith(*new_desktop_shape);
305
306  // Set the new desktop shape region.
307  desktop_shape_.Swap(new_desktop_shape);
308}
309
310}  // namespace remoting
311