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/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 cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED
),
37 seen_first_frame_(false) {}
39 CastInitializationStatus
InitializationResult() const {
40 return cast_initialization_status_
;
43 void DecodeFrame(scoped_ptr
<EncodedFrame
> encoded_frame
,
44 const DecodeFrameCallback
& callback
) {
45 DCHECK_EQ(cast_initialization_status_
, STATUS_VIDEO_INITIALIZED
);
47 COMPILE_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_VIDEO_INITIALIZED.
83 CastInitializationStatus cast_initialization_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::cast_initialization_status_
!= STATUS_VIDEO_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::cast_initialization_status_
=
110 STATUS_INVALID_VIDEO_CONFIGURATION
;
113 ImplBase::cast_initialization_status_
= STATUS_VIDEO_INITIALIZED
;
117 ~Vp8Impl() override
{
118 if (ImplBase::cast_initialization_status_
== STATUS_VIDEO_INITIALIZED
)
119 CHECK_EQ(VPX_CODEC_OK
, vpx_codec_destroy(&context_
));
122 scoped_refptr
<VideoFrame
> Decode(uint8
* data
, int len
) override
{
123 if (len
<= 0 || vpx_codec_decode(&context_
,
125 static_cast<unsigned int>(len
),
127 0) != VPX_CODEC_OK
) {
131 vpx_codec_iter_t iter
= NULL
;
132 vpx_image_t
* const image
= vpx_codec_get_frame(&context_
, &iter
);
135 if (image
->fmt
!= VPX_IMG_FMT_I420
&& image
->fmt
!= VPX_IMG_FMT_YV12
) {
139 DCHECK(vpx_codec_get_frame(&context_
, &iter
) == NULL
)
140 << "Should have only decoded exactly one frame.";
142 const gfx::Size
frame_size(image
->d_w
, image
->d_h
);
143 // Note: Timestamp for the VideoFrame will be set in VideoReceiver.
144 const scoped_refptr
<VideoFrame
> decoded_frame
=
145 VideoFrame::CreateFrame(VideoFrame::YV12
,
147 gfx::Rect(frame_size
),
150 CopyYPlane(image
->planes
[VPX_PLANE_Y
],
151 image
->stride
[VPX_PLANE_Y
],
153 decoded_frame
.get());
154 CopyUPlane(image
->planes
[VPX_PLANE_U
],
155 image
->stride
[VPX_PLANE_U
],
156 (image
->d_h
+ 1) / 2,
157 decoded_frame
.get());
158 CopyVPlane(image
->planes
[VPX_PLANE_V
],
159 image
->stride
[VPX_PLANE_V
],
160 (image
->d_h
+ 1) / 2,
161 decoded_frame
.get());
162 return decoded_frame
;
165 // VPX decoder context (i.e., an instantiation).
166 vpx_codec_ctx_t context_
;
168 DISALLOW_COPY_AND_ASSIGN(Vp8Impl
);
171 #ifndef OFFICIAL_BUILD
172 // A fake video decoder that always output 2x2 black frames.
173 class VideoDecoder::FakeImpl
: public VideoDecoder::ImplBase
{
175 explicit FakeImpl(const scoped_refptr
<CastEnvironment
>& cast_environment
)
176 : ImplBase(cast_environment
, CODEC_VIDEO_FAKE
),
177 last_decoded_id_(-1) {
178 if (ImplBase::cast_initialization_status_
!= STATUS_VIDEO_UNINITIALIZED
)
180 ImplBase::cast_initialization_status_
= STATUS_VIDEO_INITIALIZED
;
184 ~FakeImpl() override
{}
186 scoped_refptr
<VideoFrame
> Decode(uint8
* data
, int len
) override
{
187 // Make sure this is a JSON string.
188 if (!len
|| data
[0] != '{')
190 base::JSONReader reader
;
191 scoped_ptr
<base::Value
> values(
192 reader
.Read(base::StringPiece(reinterpret_cast<char*>(data
), len
)));
195 base::DictionaryValue
* dict
= NULL
;
196 values
->GetAsDictionary(&dict
);
201 dict
->GetBoolean("key", &key
);
202 dict
->GetInteger("id", &id
);
203 dict
->GetInteger("ref", &ref
);
204 DCHECK(id
== last_decoded_id_
+ 1);
205 last_decoded_id_
= id
;
206 return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2));
209 int last_decoded_id_
;
211 DISALLOW_COPY_AND_ASSIGN(FakeImpl
);
215 VideoDecoder::VideoDecoder(
216 const scoped_refptr
<CastEnvironment
>& cast_environment
,
218 : cast_environment_(cast_environment
) {
220 #ifndef OFFICIAL_BUILD
221 case CODEC_VIDEO_FAKE
:
222 impl_
= new FakeImpl(cast_environment
);
225 case CODEC_VIDEO_VP8
:
226 impl_
= new Vp8Impl(cast_environment
);
228 case CODEC_VIDEO_H264
:
229 // TODO(miu): Need implementation.
233 NOTREACHED() << "Unknown or unspecified codec.";
238 VideoDecoder::~VideoDecoder() {}
240 CastInitializationStatus
VideoDecoder::InitializationResult() const {
242 return impl_
->InitializationResult();
243 return STATUS_UNSUPPORTED_VIDEO_CODEC
;
246 void VideoDecoder::DecodeFrame(
247 scoped_ptr
<EncodedFrame
> encoded_frame
,
248 const DecodeFrameCallback
& callback
) {
249 DCHECK(encoded_frame
.get());
250 DCHECK(!callback
.is_null());
252 impl_
->InitializationResult() != STATUS_VIDEO_INITIALIZED
) {
253 callback
.Run(make_scoped_refptr
<VideoFrame
>(NULL
), false);
256 cast_environment_
->PostTask(CastEnvironment::VIDEO
,
258 base::Bind(&VideoDecoder::ImplBase::DecodeFrame
,
260 base::Passed(&encoded_frame
),