Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / media / cast / video_receiver / video_decoder.cc
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.
4
5 #include "media/cast/video_receiver/video_decoder.h"
6
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/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"
23
24 namespace media {
25 namespace cast {
26
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> {
32  public:
33   ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
34            transport::VideoCodec codec)
35       : cast_environment_(cast_environment),
36         codec_(codec),
37         cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED),
38         seen_first_frame_(false) {}
39
40   CastInitializationStatus InitializationResult() const {
41     return cast_initialization_status_;
42   }
43
44   void DecodeFrame(scoped_ptr<transport::EncodedVideoFrame> encoded_frame,
45                    const DecodeFrameCallback& callback) {
46     DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED);
47
48     if (encoded_frame->codec != codec_) {
49       NOTREACHED();
50       cast_environment_->PostTask(
51           CastEnvironment::MAIN,
52           FROM_HERE,
53           base::Bind(callback, scoped_refptr<VideoFrame>(NULL), false));
54     }
55
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;
64       }
65     } else {
66       seen_first_frame_ = true;
67     }
68     last_frame_id_ = encoded_frame->frame_id;
69
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,
75         FROM_HERE,
76         base::Bind(callback, decoded_frame, is_continuous));
77   }
78
79  protected:
80   friend class base::RefCountedThreadSafe<ImplBase>;
81   virtual ~ImplBase() {}
82
83   virtual void RecoverBecauseFramesWereDropped() {}
84
85   // Note: Implementation of Decode() is allowed to mutate |data|.
86   virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0;
87
88   const scoped_refptr<CastEnvironment> cast_environment_;
89   const transport::VideoCodec codec_;
90
91   // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED.
92   CastInitializationStatus cast_initialization_status_;
93
94  private:
95   bool seen_first_frame_;
96   uint32 last_frame_id_;
97
98   DISALLOW_COPY_AND_ASSIGN(ImplBase);
99 };
100
101 class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase {
102  public:
103   explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment)
104       : ImplBase(cast_environment, transport::kVp8) {
105     if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
106       return;
107
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.
111     cfg.threads = 1;
112
113     DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC);
114     if (vpx_codec_dec_init(&context_,
115                            vpx_codec_vp8_dx(),
116                            &cfg,
117                            VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) {
118       ImplBase::cast_initialization_status_ =
119           STATUS_INVALID_VIDEO_CONFIGURATION;
120       return;
121     }
122     ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
123   }
124
125  private:
126   virtual ~Vp8Impl() {
127     if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED)
128       CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_));
129   }
130
131   virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
132     if (len <= 0 || vpx_codec_decode(&context_,
133                                      data,
134                                      static_cast<unsigned int>(len),
135                                      NULL,
136                                      0) != VPX_CODEC_OK) {
137       return NULL;
138     }
139
140     vpx_codec_iter_t iter = NULL;
141     vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter);
142     if (!image)
143       return NULL;
144     if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) {
145       NOTREACHED();
146       return NULL;
147     }
148     DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL)
149         << "Should have only decoded exactly one frame.";
150
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,
155                                 frame_size,
156                                 gfx::Rect(frame_size),
157                                 frame_size,
158                                 base::TimeDelta());
159     CopyYPlane(image->planes[VPX_PLANE_Y],
160                image->stride[VPX_PLANE_Y],
161                image->d_h,
162                decoded_frame);
163     CopyUPlane(image->planes[VPX_PLANE_U],
164                image->stride[VPX_PLANE_U],
165                (image->d_h + 1) / 2,
166                decoded_frame);
167     CopyVPlane(image->planes[VPX_PLANE_V],
168                image->stride[VPX_PLANE_V],
169                (image->d_h + 1) / 2,
170                decoded_frame);
171     return decoded_frame;
172   }
173
174   // VPX decoder context (i.e., an instantiation).
175   vpx_codec_ctx_t context_;
176
177   DISALLOW_COPY_AND_ASSIGN(Vp8Impl);
178 };
179
180 #ifndef OFFICIAL_BUILD
181 // A fake video decoder that always output 2x2 black frames.
182 class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase {
183  public:
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)
188       return;
189     ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
190   }
191
192  private:
193   virtual ~FakeImpl() {}
194
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);
201
202     bool key = false;
203     int id = 0;
204     int ref = 0;
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));
211   }
212
213   int last_decoded_id_;
214
215   DISALLOW_COPY_AND_ASSIGN(FakeImpl);
216 };
217 #endif
218
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);
227       break;
228 #endif
229     case transport::kVp8:
230       impl_ = new Vp8Impl(cast_environment);
231       break;
232     case transport::kH264:
233       // TODO(miu): Need implementation.
234       NOTIMPLEMENTED();
235       break;
236     default:
237       NOTREACHED() << "Unknown or unspecified codec.";
238       break;
239   }
240 }
241
242 VideoDecoder::~VideoDecoder() {}
243
244 CastInitializationStatus VideoDecoder::InitializationResult() const {
245   if (impl_)
246     return impl_->InitializationResult();
247   return STATUS_UNSUPPORTED_VIDEO_CODEC;
248 }
249
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);
257     return;
258   }
259   cast_environment_->PostTask(CastEnvironment::VIDEO,
260                               FROM_HERE,
261                               base::Bind(&VideoDecoder::ImplBase::DecodeFrame,
262                                          impl_,
263                                          base::Passed(&encoded_frame),
264                                          callback));
265 }
266
267 }  // namespace cast
268 }  // namespace media