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 "media/cast/video_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/stl_util.h"
13 #include "base/values.h"
14 #include "media/base/video_util.h"
15 #include "media/cast/cast_defines.h"
16 #include "media/cast/cast_environment.h"
17 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
18 // backwards compatibility for legacy applications using the library.
19 #define VPX_CODEC_DISABLE_COMPAT 1
20 #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
21 #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"
22 #include "ui/gfx/size.h"
27 // Base class that handles the common problem of detecting dropped frames, and
28 // then invoking the Decode() method implemented by the subclasses to convert
29 // the encoded payload data into a usable video frame.
30 class VideoDecoder::ImplBase
31 : public base::RefCountedThreadSafe<VideoDecoder::ImplBase> {
33 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
34 transport::VideoCodec codec)
35 : cast_environment_(cast_environment),
37 cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED),
38 seen_first_frame_(false) {}
40 CastInitializationStatus InitializationResult() const {
41 return cast_initialization_status_;
44 void DecodeFrame(scoped_ptr<transport::EncodedVideoFrame> encoded_frame,
45 const DecodeFrameCallback& callback) {
46 DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED);
48 if (encoded_frame->codec != codec_) {
50 cast_environment_->PostTask(
51 CastEnvironment::MAIN,
53 base::Bind(callback, scoped_refptr<VideoFrame>(NULL), false));
56 COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_),
57 size_of_frame_id_types_do_not_match);
58 bool is_continuous = true;
59 if (seen_first_frame_) {
60 const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_;
61 if (frames_ahead > 1) {
62 RecoverBecauseFramesWereDropped();
63 is_continuous = false;
66 seen_first_frame_ = true;
68 last_frame_id_ = encoded_frame->frame_id;
70 const scoped_refptr<VideoFrame> decoded_frame = Decode(
71 reinterpret_cast<uint8*>(string_as_array(&encoded_frame->data)),
72 static_cast<int>(encoded_frame->data.size()));
73 cast_environment_->PostTask(
74 CastEnvironment::MAIN,
76 base::Bind(callback, decoded_frame, is_continuous));
80 friend class base::RefCountedThreadSafe<ImplBase>;
81 virtual ~ImplBase() {}
83 virtual void RecoverBecauseFramesWereDropped() {}
85 // Note: Implementation of Decode() is allowed to mutate |data|.
86 virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0;
88 const scoped_refptr<CastEnvironment> cast_environment_;
89 const transport::VideoCodec codec_;
91 // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED.
92 CastInitializationStatus cast_initialization_status_;
95 bool seen_first_frame_;
96 uint32 last_frame_id_;
98 DISALLOW_COPY_AND_ASSIGN(ImplBase);
101 class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase {
103 explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment)
104 : ImplBase(cast_environment, transport::kVp8) {
105 if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
108 vpx_codec_dec_cfg_t cfg = {0};
109 // TODO(miu): Revisit this for typical multi-core desktop use case. This
110 // feels like it should be 4 or 8.
113 DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC);
114 if (vpx_codec_dec_init(&context_,
117 VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) {
118 ImplBase::cast_initialization_status_ =
119 STATUS_INVALID_VIDEO_CONFIGURATION;
122 ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
127 if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED)
128 CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_));
131 virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
132 if (len <= 0 || vpx_codec_decode(&context_,
134 static_cast<unsigned int>(len),
136 0) != VPX_CODEC_OK) {
140 vpx_codec_iter_t iter = NULL;
141 vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter);
144 if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) {
148 DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL)
149 << "Should have only decoded exactly one frame.";
151 const gfx::Size frame_size(image->d_w, image->d_h);
152 // Note: Timestamp for the VideoFrame will be set in VideoReceiver.
153 const scoped_refptr<VideoFrame> decoded_frame =
154 VideoFrame::CreateFrame(VideoFrame::YV12,
156 gfx::Rect(frame_size),
159 CopyYPlane(image->planes[VPX_PLANE_Y],
160 image->stride[VPX_PLANE_Y],
163 CopyUPlane(image->planes[VPX_PLANE_U],
164 image->stride[VPX_PLANE_U],
165 (image->d_h + 1) / 2,
167 CopyVPlane(image->planes[VPX_PLANE_V],
168 image->stride[VPX_PLANE_V],
169 (image->d_h + 1) / 2,
171 return decoded_frame;
174 // VPX decoder context (i.e., an instantiation).
175 vpx_codec_ctx_t context_;
177 DISALLOW_COPY_AND_ASSIGN(Vp8Impl);
180 #ifndef OFFICIAL_BUILD
181 // A fake video decoder that always output 2x2 black frames.
182 class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase {
184 explicit FakeImpl(const scoped_refptr<CastEnvironment>& cast_environment)
185 : ImplBase(cast_environment, transport::kFakeSoftwareVideo),
186 last_decoded_id_(-1) {
187 if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
189 ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
193 virtual ~FakeImpl() {}
195 virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
196 base::JSONReader reader;
197 scoped_ptr<base::Value> values(reader.Read(
198 base::StringPiece(reinterpret_cast<char*>(data), len)));
199 base::DictionaryValue* dict = NULL;
200 values->GetAsDictionary(&dict);
205 dict->GetBoolean("key", &key);
206 dict->GetInteger("id", &id);
207 dict->GetInteger("ref", &ref);
208 DCHECK(id == last_decoded_id_ + 1);
209 last_decoded_id_ = id;
210 return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2));
213 int last_decoded_id_;
215 DISALLOW_COPY_AND_ASSIGN(FakeImpl);
219 VideoDecoder::VideoDecoder(
220 const scoped_refptr<CastEnvironment>& cast_environment,
221 const VideoReceiverConfig& video_config)
222 : cast_environment_(cast_environment) {
223 switch (video_config.codec) {
224 #ifndef OFFICIAL_BUILD
225 case transport::kFakeSoftwareVideo:
226 impl_ = new FakeImpl(cast_environment);
229 case transport::kVp8:
230 impl_ = new Vp8Impl(cast_environment);
232 case transport::kH264:
233 // TODO(miu): Need implementation.
237 NOTREACHED() << "Unknown or unspecified codec.";
242 VideoDecoder::~VideoDecoder() {}
244 CastInitializationStatus VideoDecoder::InitializationResult() const {
246 return impl_->InitializationResult();
247 return STATUS_UNSUPPORTED_VIDEO_CODEC;
250 void VideoDecoder::DecodeFrame(
251 scoped_ptr<transport::EncodedVideoFrame> encoded_frame,
252 const DecodeFrameCallback& callback) {
253 DCHECK(encoded_frame.get());
254 DCHECK(!callback.is_null());
255 if (!impl_ || impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) {
256 callback.Run(make_scoped_refptr<VideoFrame>(NULL), false);
259 cast_environment_->PostTask(CastEnvironment::VIDEO,
261 base::Bind(&VideoDecoder::ImplBase::DecodeFrame,
263 base::Passed(&encoded_frame),