1 // Copyright 2014 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 "media/cast/receiver/video_decoder.h"
8 #include "base/bind_helpers.h"
9 #include "base/json/json_reader.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/values.h"
13 #include "media/base/video_util.h"
14 #include "media/cast/cast_defines.h"
15 #include "media/cast/cast_environment.h"
16 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
17 // backwards compatibility for legacy applications using the library.
18 #define VPX_CODEC_DISABLE_COMPAT 1
19 #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
20 #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"
21 #include "ui/gfx/geometry/size.h"
26 // Base class that handles the common problem of detecting dropped frames, and
27 // then invoking the Decode() method implemented by the subclasses to convert
28 // the encoded payload data into a usable video frame.
29 class VideoDecoder::ImplBase
30 : public base::RefCountedThreadSafe
<VideoDecoder::ImplBase
> {
32 ImplBase(const scoped_refptr
<CastEnvironment
>& cast_environment
,
34 : cast_environment_(cast_environment
),
36 operational_status_(STATUS_UNINITIALIZED
),
37 seen_first_frame_(false) {}
39 OperationalStatus
InitializationResult() const {
40 return operational_status_
;
43 void DecodeFrame(scoped_ptr
<EncodedFrame
> encoded_frame
,
44 const DecodeFrameCallback
& callback
) {
45 DCHECK_EQ(operational_status_
, STATUS_INITIALIZED
);
47 static_assert(sizeof(encoded_frame
->frame_id
) == sizeof(last_frame_id_
),
48 "size of frame_id types do not match");
49 bool is_continuous
= true;
50 if (seen_first_frame_
) {
51 const uint32 frames_ahead
= encoded_frame
->frame_id
- last_frame_id_
;
52 if (frames_ahead
> 1) {
53 RecoverBecauseFramesWereDropped();
54 is_continuous
= false;
57 seen_first_frame_
= true;
59 last_frame_id_
= encoded_frame
->frame_id
;
61 const scoped_refptr
<VideoFrame
> decoded_frame
= Decode(
62 encoded_frame
->mutable_bytes(),
63 static_cast<int>(encoded_frame
->data
.size()));
64 cast_environment_
->PostTask(
65 CastEnvironment::MAIN
,
67 base::Bind(callback
, decoded_frame
, is_continuous
));
71 friend class base::RefCountedThreadSafe
<ImplBase
>;
72 virtual ~ImplBase() {}
74 virtual void RecoverBecauseFramesWereDropped() {}
76 // Note: Implementation of Decode() is allowed to mutate |data|.
77 virtual scoped_refptr
<VideoFrame
> Decode(uint8
* data
, int len
) = 0;
79 const scoped_refptr
<CastEnvironment
> cast_environment_
;
82 // Subclass' ctor is expected to set this to STATUS_INITIALIZED.
83 OperationalStatus operational_status_
;
86 bool seen_first_frame_
;
87 uint32 last_frame_id_
;
89 DISALLOW_COPY_AND_ASSIGN(ImplBase
);
92 class VideoDecoder::Vp8Impl
: public VideoDecoder::ImplBase
{
94 explicit Vp8Impl(const scoped_refptr
<CastEnvironment
>& cast_environment
)
95 : ImplBase(cast_environment
, CODEC_VIDEO_VP8
) {
96 if (ImplBase::operational_status_
!= STATUS_UNINITIALIZED
)
99 vpx_codec_dec_cfg_t cfg
= {0};
100 // TODO(miu): Revisit this for typical multi-core desktop use case. This
101 // feels like it should be 4 or 8.
104 DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC
);
105 if (vpx_codec_dec_init(&context_
,
108 VPX_CODEC_USE_POSTPROC
) != VPX_CODEC_OK
) {
109 ImplBase::operational_status_
= STATUS_INVALID_CONFIGURATION
;
112 ImplBase::operational_status_
= STATUS_INITIALIZED
;
117 if (ImplBase::operational_status_
== STATUS_INITIALIZED
)
118 CHECK_EQ(VPX_CODEC_OK
, vpx_codec_destroy(&context_
));
121 scoped_refptr
<VideoFrame
> Decode(uint8
* data
, int len
) final
{
122 if (len
<= 0 || vpx_codec_decode(&context_
,
124 static_cast<unsigned int>(len
),
126 0) != VPX_CODEC_OK
) {
130 vpx_codec_iter_t iter
= NULL
;
131 vpx_image_t
* const image
= vpx_codec_get_frame(&context_
, &iter
);
134 if (image
->fmt
!= VPX_IMG_FMT_I420
&& image
->fmt
!= VPX_IMG_FMT_YV12
) {
138 DCHECK(vpx_codec_get_frame(&context_
, &iter
) == NULL
)
139 << "Should have only decoded exactly one frame.";
141 const gfx::Size
frame_size(image
->d_w
, image
->d_h
);
142 // Note: Timestamp for the VideoFrame will be set in VideoReceiver.
143 const scoped_refptr
<VideoFrame
> decoded_frame
= VideoFrame::CreateFrame(
144 PIXEL_FORMAT_YV12
, frame_size
, gfx::Rect(frame_size
), frame_size
,
146 CopyYPlane(image
->planes
[VPX_PLANE_Y
],
147 image
->stride
[VPX_PLANE_Y
],
149 decoded_frame
.get());
150 CopyUPlane(image
->planes
[VPX_PLANE_U
],
151 image
->stride
[VPX_PLANE_U
],
152 (image
->d_h
+ 1) / 2,
153 decoded_frame
.get());
154 CopyVPlane(image
->planes
[VPX_PLANE_V
],
155 image
->stride
[VPX_PLANE_V
],
156 (image
->d_h
+ 1) / 2,
157 decoded_frame
.get());
158 return decoded_frame
;
161 // VPX decoder context (i.e., an instantiation).
162 vpx_codec_ctx_t context_
;
164 DISALLOW_COPY_AND_ASSIGN(Vp8Impl
);
167 #ifndef OFFICIAL_BUILD
168 // A fake video decoder that always output 2x2 black frames.
169 class VideoDecoder::FakeImpl
: public VideoDecoder::ImplBase
{
171 explicit FakeImpl(const scoped_refptr
<CastEnvironment
>& cast_environment
)
172 : ImplBase(cast_environment
, CODEC_VIDEO_FAKE
),
173 last_decoded_id_(-1) {
174 if (ImplBase::operational_status_
!= STATUS_UNINITIALIZED
)
176 ImplBase::operational_status_
= STATUS_INITIALIZED
;
182 scoped_refptr
<VideoFrame
> Decode(uint8
* data
, int len
) final
{
183 // Make sure this is a JSON string.
184 if (!len
|| data
[0] != '{')
186 base::JSONReader reader
;
187 scoped_ptr
<base::Value
> values(
188 reader
.Read(base::StringPiece(reinterpret_cast<char*>(data
), len
)));
191 base::DictionaryValue
* dict
= NULL
;
192 values
->GetAsDictionary(&dict
);
197 dict
->GetBoolean("key", &key
);
198 dict
->GetInteger("id", &id
);
199 dict
->GetInteger("ref", &ref
);
200 DCHECK(id
== last_decoded_id_
+ 1);
201 last_decoded_id_
= id
;
202 return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2));
205 int last_decoded_id_
;
207 DISALLOW_COPY_AND_ASSIGN(FakeImpl
);
211 VideoDecoder::VideoDecoder(
212 const scoped_refptr
<CastEnvironment
>& cast_environment
,
214 : cast_environment_(cast_environment
) {
216 #ifndef OFFICIAL_BUILD
217 case CODEC_VIDEO_FAKE
:
218 impl_
= new FakeImpl(cast_environment
);
221 case CODEC_VIDEO_VP8
:
222 impl_
= new Vp8Impl(cast_environment
);
224 case CODEC_VIDEO_H264
:
225 // TODO(miu): Need implementation.
229 NOTREACHED() << "Unknown or unspecified codec.";
234 VideoDecoder::~VideoDecoder() {}
236 OperationalStatus
VideoDecoder::InitializationResult() const {
238 return impl_
->InitializationResult();
239 return STATUS_UNSUPPORTED_CODEC
;
242 void VideoDecoder::DecodeFrame(
243 scoped_ptr
<EncodedFrame
> encoded_frame
,
244 const DecodeFrameCallback
& callback
) {
245 DCHECK(encoded_frame
.get());
246 DCHECK(!callback
.is_null());
247 if (!impl_
.get() || impl_
->InitializationResult() != STATUS_INITIALIZED
) {
248 callback
.Run(make_scoped_refptr
<VideoFrame
>(NULL
), false);
251 cast_environment_
->PostTask(CastEnvironment::VIDEO
,
253 base::Bind(&VideoDecoder::ImplBase::DecodeFrame
,
255 base::Passed(&encoded_frame
),