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.
4
5#include "media/cast/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/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"
22
23namespace media {
24namespace cast {
25
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.
29class 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        cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED),
37        seen_first_frame_(false) {}
38
39  CastInitializationStatus InitializationResult() const {
40    return cast_initialization_status_;
41  }
42
43  void DecodeFrame(scoped_ptr<EncodedFrame> encoded_frame,
44                   const DecodeFrameCallback& callback) {
45    DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED);
46
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;
55      }
56    } else {
57      seen_first_frame_ = true;
58    }
59    last_frame_id_ = encoded_frame->frame_id;
60
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));
68  }
69
70 protected:
71  friend class base::RefCountedThreadSafe<ImplBase>;
72  virtual ~ImplBase() {}
73
74  virtual void RecoverBecauseFramesWereDropped() {}
75
76  // Note: Implementation of Decode() is allowed to mutate |data|.
77  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0;
78
79  const scoped_refptr<CastEnvironment> cast_environment_;
80  const Codec codec_;
81
82  // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED.
83  CastInitializationStatus cast_initialization_status_;
84
85 private:
86  bool seen_first_frame_;
87  uint32 last_frame_id_;
88
89  DISALLOW_COPY_AND_ASSIGN(ImplBase);
90};
91
92class 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::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
97      return;
98
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;
103
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::cast_initialization_status_ =
110          STATUS_INVALID_VIDEO_CONFIGURATION;
111      return;
112    }
113    ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
114  }
115
116 private:
117  virtual ~Vp8Impl() {
118    if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED)
119      CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_));
120  }
121
122  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
123    if (len <= 0 || vpx_codec_decode(&context_,
124                                     data,
125                                     static_cast<unsigned int>(len),
126                                     NULL,
127                                     0) != VPX_CODEC_OK) {
128      return NULL;
129    }
130
131    vpx_codec_iter_t iter = NULL;
132    vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter);
133    if (!image)
134      return NULL;
135    if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) {
136      NOTREACHED();
137      return NULL;
138    }
139    DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL)
140        << "Should have only decoded exactly one frame.";
141
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,
146                                frame_size,
147                                gfx::Rect(frame_size),
148                                frame_size,
149                                base::TimeDelta());
150    CopyYPlane(image->planes[VPX_PLANE_Y],
151               image->stride[VPX_PLANE_Y],
152               image->d_h,
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;
163  }
164
165  // VPX decoder context (i.e., an instantiation).
166  vpx_codec_ctx_t context_;
167
168  DISALLOW_COPY_AND_ASSIGN(Vp8Impl);
169};
170
171#ifndef OFFICIAL_BUILD
172// A fake video decoder that always output 2x2 black frames.
173class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase {
174 public:
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)
179      return;
180    ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
181  }
182
183 private:
184  virtual ~FakeImpl() {}
185
186  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
187    // Make sure this is a JSON string.
188    if (!len || data[0] != '{')
189      return NULL;
190    base::JSONReader reader;
191    scoped_ptr<base::Value> values(
192        reader.Read(base::StringPiece(reinterpret_cast<char*>(data), len)));
193    if (!values)
194      return NULL;
195    base::DictionaryValue* dict = NULL;
196    values->GetAsDictionary(&dict);
197
198    bool key = false;
199    int id = 0;
200    int ref = 0;
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));
207  }
208
209  int last_decoded_id_;
210
211  DISALLOW_COPY_AND_ASSIGN(FakeImpl);
212};
213#endif
214
215VideoDecoder::VideoDecoder(
216    const scoped_refptr<CastEnvironment>& cast_environment,
217    Codec codec)
218    : cast_environment_(cast_environment) {
219  switch (codec) {
220#ifndef OFFICIAL_BUILD
221    case CODEC_VIDEO_FAKE:
222      impl_ = new FakeImpl(cast_environment);
223      break;
224#endif
225    case CODEC_VIDEO_VP8:
226      impl_ = new Vp8Impl(cast_environment);
227      break;
228    case CODEC_VIDEO_H264:
229      // TODO(miu): Need implementation.
230      NOTIMPLEMENTED();
231      break;
232    default:
233      NOTREACHED() << "Unknown or unspecified codec.";
234      break;
235  }
236}
237
238VideoDecoder::~VideoDecoder() {}
239
240CastInitializationStatus VideoDecoder::InitializationResult() const {
241  if (impl_.get())
242    return impl_->InitializationResult();
243  return STATUS_UNSUPPORTED_VIDEO_CODEC;
244}
245
246void VideoDecoder::DecodeFrame(
247    scoped_ptr<EncodedFrame> encoded_frame,
248    const DecodeFrameCallback& callback) {
249  DCHECK(encoded_frame.get());
250  DCHECK(!callback.is_null());
251  if (!impl_.get() ||
252      impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) {
253    callback.Run(make_scoped_refptr<VideoFrame>(NULL), false);
254    return;
255  }
256  cast_environment_->PostTask(CastEnvironment::VIDEO,
257                              FROM_HERE,
258                              base::Bind(&VideoDecoder::ImplBase::DecodeFrame,
259                                         impl_,
260                                         base::Passed(&encoded_frame),
261                                         callback));
262}
263
264}  // namespace cast
265}  // namespace media
266