• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 extern "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 
23 namespace remoting {
24 
25 namespace {
26 
27 const uint32 kTransparentColor = 0;
28 
29 // Fills the rectangle |rect| with the given ARGB color |color| in |buffer|.
FillRect(uint8 * buffer,int stride,const webrtc::DesktopRect & rect,uint32 color)30 void 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
CreateForVP8()46 scoped_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
CreateForVP9()66 scoped_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 
~VideoDecoderVpx()85 VideoDecoderVpx::~VideoDecoderVpx() {}
86 
Initialize(const webrtc::DesktopSize & screen_size)87 void 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 
DecodePacket(const VideoPacket & packet)95 bool 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 
Invalidate(const webrtc::DesktopSize & view_size,const webrtc::DesktopRegion & region)150 void 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 
RenderFrame(const webrtc::DesktopSize & view_size,const webrtc::DesktopRect & clip_area,uint8 * image_buffer,int image_stride,webrtc::DesktopRegion * output_region)166 void 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 
GetImageShape()326 const webrtc::DesktopRegion* VideoDecoderVpx::GetImageShape() {
327   return &desktop_shape_;
328 }
329 
VideoDecoderVpx(ScopedVpxCodec codec)330 VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec)
331     : codec_(codec.Pass()),
332       last_image_(NULL) {
333   DCHECK(codec_);
334 }
335 
UpdateImageShapeRegion(webrtc::DesktopRegion * new_desktop_shape)336 void 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