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.
5 #include "remoting/codec/video_decoder_vpx.h"
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"
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"
27 const uint32 kTransparentColor
= 0;
29 // Fills the rectangle |rect| with the given ARGB color |color| in |buffer|.
30 void FillRect(uint8
* buffer
,
32 const webrtc::DesktopRect
& rect
,
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
;
46 scoped_ptr
<VideoDecoderVpx
> VideoDecoderVpx::CreateForVP8() {
47 ScopedVpxCodec
codec(new vpx_codec_ctx_t
);
49 // TODO(hclam): Scale the number of threads with number of cores of the
51 vpx_codec_dec_cfg config
;
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
>();
62 return scoped_ptr
<VideoDecoderVpx
>(new VideoDecoderVpx(codec
.Pass()));
66 scoped_ptr
<VideoDecoderVpx
> VideoDecoderVpx::CreateForVP9() {
67 ScopedVpxCodec
codec(new vpx_codec_ctx_t
);
69 // TODO(hclam): Scale the number of threads with number of cores of the
71 vpx_codec_dec_cfg config
;
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
>();
82 return scoped_ptr
<VideoDecoderVpx
>(new VideoDecoderVpx(codec
.Pass()));
85 VideoDecoderVpx::~VideoDecoderVpx() {}
87 void VideoDecoderVpx::Initialize(const webrtc::DesktopSize
& screen_size
) {
88 DCHECK(!screen_size
.is_empty());
90 screen_size_
= screen_size
;
92 transparent_region_
.SetRect(webrtc::DesktopRect::MakeSize(screen_size_
));
95 bool VideoDecoderVpx::DecodePacket(const VideoPacket
& packet
) {
96 DCHECK(!screen_size_
.is_empty());
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)");
110 // Gets the decoded data.
111 vpx_codec_iter_t iter
= NULL
;
112 vpx_image_t
* image
= vpx_codec_get_frame(codec_
.get(), &iter
);
114 LOG(ERROR
) << "No video frame decoded";
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()));
127 updated_region_
.AddRegion(region
);
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()));
139 // Fallback for the case when the host didn't include the desktop shape
141 desktop_shape_region
=
142 webrtc::DesktopRegion(webrtc::DesktopRect::MakeSize(screen_size_
));
145 UpdateImageShapeRegion(&desktop_shape_region
);
150 void VideoDecoderVpx::Invalidate(const webrtc::DesktopSize
& view_size
,
151 const webrtc::DesktopRegion
& region
) {
152 DCHECK(!view_size
.is_empty());
154 for (webrtc::DesktopRegion::Iterator
i(region
); !i
.IsAtEnd(); i
.Advance()) {
155 updated_region_
.AddRect(ScaleRect(i
.rect(), view_size
, screen_size_
));
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
);
166 void VideoDecoderVpx::RenderFrame(const webrtc::DesktopSize
& view_size
,
167 const webrtc::DesktopRect
& clip_area
,
170 webrtc::DesktopRegion
* output_region
) {
171 DCHECK(!screen_size_
.is_empty());
172 DCHECK(!view_size
.is_empty());
174 // Early-return and do nothing if we haven't yet decoded any frames.
178 webrtc::DesktopRect source_clip
=
179 webrtc::DesktopRect::MakeWH(last_image_
->d_w
, last_image_
->d_h
);
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
);
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());
209 output_region
->AddRect(rect
);
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
219 // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can
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()),
232 source_rect
.bottom());
234 // If there were no changes within the clip source area then don't
236 webrtc::DesktopRegion
intersection(source_rect
);
237 intersection
.IntersectWith(updated_region_
);
238 if (intersection
.is_empty())
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
,
251 source_rect
.height(),
254 last_image_
->stride
[0],
255 last_image_
->stride
[1],
259 media::FILTER_BILINEAR
);
261 output_region
->AddRect(clip_area
);
262 updated_region_
.Subtract(source_rect
);
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
);
273 rect
= ScaleRect(rect
, screen_size_
, view_size
);
274 rect
.IntersectWith(clip_area
);
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],
291 output_region
->AddRect(rect
);
294 updated_region_
.Subtract(ScaleRect(clip_area
, view_size
, screen_size_
));
298 LOG(ERROR
) << "Unsupported image format:" << last_image_
->fmt
;
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
);
310 rect
= ScaleRect(rect
, screen_size_
, view_size
);
311 rect
.IntersectWith(clip_area
);
315 // Fill the rectange with transparent pixels.
316 FillRect(image_buffer
, image_stride
, rect
, kTransparentColor
);
317 output_region
->AddRect(rect
);
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
);
326 const webrtc::DesktopRegion
* VideoDecoderVpx::GetImageShape() {
327 return &desktop_shape_
;
330 VideoDecoderVpx::VideoDecoderVpx(ScopedVpxCodec codec
)
331 : codec_(codec
.Pass()),
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
);
344 // Add newly exposed areas to the update region and limit updates to the new
346 webrtc::DesktopRegion difference
= *new_desktop_shape
;
347 difference
.Subtract(desktop_shape_
);
348 updated_region_
.AddRegion(difference
);
349 updated_region_
.IntersectWith(*new_desktop_shape
);
351 // Set the new desktop shape region.
352 desktop_shape_
.Swap(new_desktop_shape
);
355 } // namespace remoting