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"
26 scoped_ptr
<VideoDecoderVpx
> VideoDecoderVpx::CreateForVP8() {
27 return make_scoped_ptr(new VideoDecoderVpx(vpx_codec_vp8_dx()));
31 scoped_ptr
<VideoDecoderVpx
> VideoDecoderVpx::CreateForVP9() {
32 return make_scoped_ptr(new VideoDecoderVpx(vpx_codec_vp9_dx()));
35 VideoDecoderVpx::~VideoDecoderVpx() {}
37 void VideoDecoderVpx::Initialize(const webrtc::DesktopSize
& source_size
) {
38 // Nothing to do here; the codec handles resizing internally, and returns
39 // the source dimensions as part of the vpx_image_t.
42 bool VideoDecoderVpx::DecodePacket(const VideoPacket
& packet
) {
43 // Pass the packet to the codec to process.
44 vpx_codec_err_t ret
= vpx_codec_decode(
45 codec_
.get(), reinterpret_cast<const uint8
*>(packet
.data().data()),
46 packet
.data().size(), nullptr, 0);
47 if (ret
!= VPX_CODEC_OK
) {
48 const char* error
= vpx_codec_error(codec_
.get());
49 const char* error_detail
= vpx_codec_error_detail(codec_
.get());
50 LOG(ERROR
) << "Decoding failed:" << (error
? error
: "(NULL)") << "\n"
51 << "Details: " << (error_detail
? error_detail
: "(NULL)");
55 // Fetch the decoded video frame.
56 vpx_codec_iter_t iter
= nullptr;
57 image_
= vpx_codec_get_frame(codec_
.get(), &iter
);
59 LOG(ERROR
) << "No video frame decoded";
62 DCHECK(!image_size().is_empty());
64 // Determine which areas have been updated.
65 webrtc::DesktopRegion region
;
66 for (int i
= 0; i
< packet
.dirty_rects_size(); ++i
) {
67 Rect remoting_rect
= packet
.dirty_rects(i
);
68 region
.AddRect(webrtc::DesktopRect::MakeXYWH(
69 remoting_rect
.x(), remoting_rect
.y(),
70 remoting_rect
.width(), remoting_rect
.height()));
72 updated_region_
.AddRegion(region
);
74 // Process the frame shape, if supplied.
75 if (packet
.has_use_desktop_shape()) {
76 if (packet
.use_desktop_shape()) {
78 desktop_shape_
= make_scoped_ptr(new webrtc::DesktopRegion
);
79 desktop_shape_
->Clear();
80 for (int i
= 0; i
< packet
.desktop_shape_rects_size(); ++i
) {
81 Rect remoting_rect
= packet
.desktop_shape_rects(i
);
82 desktop_shape_
->AddRect(webrtc::DesktopRect::MakeXYWH(
83 remoting_rect
.x(), remoting_rect
.y(), remoting_rect
.width(),
84 remoting_rect
.height()));
87 desktop_shape_
.reset();
94 void VideoDecoderVpx::Invalidate(const webrtc::DesktopSize
& view_size
,
95 const webrtc::DesktopRegion
& region
) {
96 DCHECK(!view_size
.is_empty());
98 for (webrtc::DesktopRegion::Iterator
i(region
); !i
.IsAtEnd(); i
.Advance()) {
99 updated_region_
.AddRect(ScaleRect(i
.rect(), view_size
, image_size()));
103 void VideoDecoderVpx::RenderFrame(const webrtc::DesktopSize
& view_size
,
104 const webrtc::DesktopRect
& clip_area
,
107 webrtc::DesktopRegion
* output_region
) {
108 DCHECK(!image_size().is_empty());
109 DCHECK(!view_size
.is_empty());
111 // Early-return and do nothing if we haven't yet decoded any frames.
115 webrtc::DesktopRect source_clip
= webrtc::DesktopRect::MakeSize(image_size());
117 // VP8 only outputs I420 frames, but VP9 can also produce I444.
118 switch (image_
->fmt
) {
119 case VPX_IMG_FMT_I444
: {
120 // TODO(wez): Add scaling support to the I444 conversion path.
121 if (view_size
.equals(image_size())) {
122 for (webrtc::DesktopRegion::Iterator
i(updated_region_
);
123 !i
.IsAtEnd(); i
.Advance()) {
124 // Determine the scaled area affected by this rectangle changing.
125 webrtc::DesktopRect rect
= i
.rect();
126 rect
.IntersectWith(source_clip
);
127 rect
.IntersectWith(clip_area
);
131 int image_offset
= image_stride
* rect
.top() +
132 rect
.left() * VideoDecoder::kBytesPerPixel
;
133 int y_offset
= image_
->stride
[0] * rect
.top() + rect
.left();
134 int u_offset
= image_
->stride
[1] * rect
.top() + rect
.left();
135 int v_offset
= image_
->stride
[2] * rect
.top() + rect
.left();
136 libyuv::I444ToARGB(image_
->planes
[0] + y_offset
, image_
->stride
[0],
137 image_
->planes
[1] + u_offset
, image_
->stride
[1],
138 image_
->planes
[2] + v_offset
, image_
->stride
[2],
139 image_buffer
+ image_offset
, image_stride
,
140 rect
.width(), rect
.height());
142 output_region
->AddRect(rect
);
147 case VPX_IMG_FMT_I420
: {
148 // ScaleYUVToRGB32WithRect does not currently support up-scaling. We
149 // won't be asked to up-scale except during resizes or if page zoom is
150 // >100%, so we work-around the limitation by using the slower
152 // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can
154 if (!updated_region_
.is_empty() &&
155 (source_clip
.width() < view_size
.width() ||
156 source_clip
.height() < view_size
.height())) {
157 // We're scaling only |clip_area| into the |image_buffer|, so we need to
158 // work out which source rectangle that corresponds to.
159 webrtc::DesktopRect source_rect
=
160 ScaleRect(clip_area
, view_size
, image_size());
161 source_rect
= webrtc::DesktopRect::MakeLTRB(
162 RoundToTwosMultiple(source_rect
.left()),
163 RoundToTwosMultiple(source_rect
.top()),
165 source_rect
.bottom());
167 // If there were no changes within the clip source area then don't
169 webrtc::DesktopRegion
intersection(source_rect
);
170 intersection
.IntersectWith(updated_region_
);
171 if (intersection
.is_empty())
174 // Scale & convert the entire clip area.
175 int y_offset
= CalculateYOffset(source_rect
.left(), source_rect
.top(),
177 int uv_offset
= CalculateUVOffset(source_rect
.left(), source_rect
.top(),
180 image_
->planes
[0] + y_offset
, image_
->planes
[1] + uv_offset
,
181 image_
->planes
[2] + uv_offset
, image_buffer
, source_rect
.width(),
182 source_rect
.height(), clip_area
.width(), clip_area
.height(),
183 image_
->stride
[0], image_
->stride
[1], image_stride
, media::YV12
,
184 media::ROTATE_0
, media::FILTER_BILINEAR
);
186 output_region
->AddRect(clip_area
);
187 updated_region_
.Subtract(source_rect
);
191 for (webrtc::DesktopRegion::Iterator
i(updated_region_
);
192 !i
.IsAtEnd(); i
.Advance()) {
193 // Determine the scaled area affected by this rectangle changing.
194 webrtc::DesktopRect rect
= i
.rect();
195 rect
.IntersectWith(source_clip
);
198 rect
= ScaleRect(rect
, image_size(), view_size
);
199 rect
.IntersectWith(clip_area
);
203 ConvertAndScaleYUVToRGB32Rect(
204 image_
->planes
[0], image_
->planes
[1], image_
->planes
[2],
205 image_
->stride
[0], image_
->stride
[1], image_size(), source_clip
,
206 image_buffer
, image_stride
, view_size
, clip_area
, rect
);
208 output_region
->AddRect(rect
);
211 updated_region_
.Subtract(ScaleRect(clip_area
, view_size
, image_size()));
215 LOG(ERROR
) << "Unsupported image format:" << image_
->fmt
;
220 webrtc::DesktopRect scaled_clip_area
=
221 ScaleRect(clip_area
, view_size
, image_size());
222 updated_region_
.Subtract(scaled_clip_area
);
225 const webrtc::DesktopRegion
* VideoDecoderVpx::GetImageShape() {
226 return desktop_shape_
.get();
229 VideoDecoderVpx::VideoDecoderVpx(vpx_codec_iface_t
* codec
) : image_(nullptr) {
230 codec_
.reset(new vpx_codec_ctx_t
);
232 vpx_codec_dec_cfg config
;
236 vpx_codec_err_t ret
= vpx_codec_dec_init(codec_
.get(), codec
, &config
, 0);
237 CHECK_EQ(VPX_CODEC_OK
, ret
);
240 webrtc::DesktopSize
VideoDecoderVpx::image_size() const {
241 return image_
? webrtc::DesktopSize(image_
->d_w
, image_
->d_h
)
242 : webrtc::DesktopSize();
245 } // namespace remoting