Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / cast / receiver / video_decoder.cc
blobf9b24a420a26a7d349fdacf0a43b32d6ea0dbcd0
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"
7 #include "base/bind.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_new/source/libvpx/vpx/vp8dx.h"
20 #include "third_party/libvpx_new/source/libvpx/vpx/vpx_decoder.h"
21 #include "ui/gfx/geometry/size.h"
23 namespace media {
24 namespace cast {
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> {
31 public:
32 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
33 Codec codec)
34 : cast_environment_(cast_environment),
35 codec_(codec),
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;
56 } else {
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,
66 FROM_HERE,
67 base::Bind(callback, decoded_frame, is_continuous));
70 protected:
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_;
80 const Codec codec_;
82 // Subclass' ctor is expected to set this to STATUS_INITIALIZED.
83 OperationalStatus operational_status_;
85 private:
86 bool seen_first_frame_;
87 uint32 last_frame_id_;
89 DISALLOW_COPY_AND_ASSIGN(ImplBase);
92 class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase {
93 public:
94 explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment)
95 : ImplBase(cast_environment, CODEC_VIDEO_VP8) {
96 if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
97 return;
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.
102 cfg.threads = 1;
104 DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC);
105 if (vpx_codec_dec_init(&context_,
106 vpx_codec_vp8_dx(),
107 &cfg,
108 VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) {
109 ImplBase::operational_status_ = STATUS_INVALID_CONFIGURATION;
110 return;
112 ImplBase::operational_status_ = STATUS_INITIALIZED;
115 private:
116 ~Vp8Impl() final {
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_,
123 data,
124 static_cast<unsigned int>(len),
125 NULL,
126 0) != VPX_CODEC_OK) {
127 return NULL;
130 vpx_codec_iter_t iter = NULL;
131 vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter);
132 if (!image)
133 return NULL;
134 if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) {
135 NOTREACHED();
136 return NULL;
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,
145 base::TimeDelta());
146 CopyYPlane(image->planes[VPX_PLANE_Y],
147 image->stride[VPX_PLANE_Y],
148 image->d_h,
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 {
170 public:
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)
175 return;
176 ImplBase::operational_status_ = STATUS_INITIALIZED;
179 private:
180 ~FakeImpl() final {}
182 scoped_refptr<VideoFrame> Decode(uint8* data, int len) final {
183 // Make sure this is a JSON string.
184 if (!len || data[0] != '{')
185 return NULL;
186 base::JSONReader reader;
187 scoped_ptr<base::Value> values(
188 reader.Read(base::StringPiece(reinterpret_cast<char*>(data), len)));
189 if (!values)
190 return NULL;
191 base::DictionaryValue* dict = NULL;
192 values->GetAsDictionary(&dict);
194 bool key = false;
195 int id = 0;
196 int ref = 0;
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);
209 #endif
211 VideoDecoder::VideoDecoder(
212 const scoped_refptr<CastEnvironment>& cast_environment,
213 Codec codec)
214 : cast_environment_(cast_environment) {
215 switch (codec) {
216 #ifndef OFFICIAL_BUILD
217 case CODEC_VIDEO_FAKE:
218 impl_ = new FakeImpl(cast_environment);
219 break;
220 #endif
221 case CODEC_VIDEO_VP8:
222 impl_ = new Vp8Impl(cast_environment);
223 break;
224 case CODEC_VIDEO_H264:
225 // TODO(miu): Need implementation.
226 NOTIMPLEMENTED();
227 break;
228 default:
229 NOTREACHED() << "Unknown or unspecified codec.";
230 break;
234 VideoDecoder::~VideoDecoder() {}
236 OperationalStatus VideoDecoder::InitializationResult() const {
237 if (impl_.get())
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);
249 return;
251 cast_environment_->PostTask(CastEnvironment::VIDEO,
252 FROM_HERE,
253 base::Bind(&VideoDecoder::ImplBase::DecodeFrame,
254 impl_,
255 base::Passed(&encoded_frame),
256 callback));
259 } // namespace cast
260 } // namespace media