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/cdm/ppapi/external_clear_key/libvpx_cdm_video_decoder.h"
6
7#include "base/logging.h"
8#include "base/memory/scoped_ptr.h"
9#include "media/base/buffers.h"
10#include "media/base/limits.h"
11
12// Include libvpx header files.
13// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
14// backwards compatibility for legacy applications using the library.
15#define VPX_CODEC_DISABLE_COMPAT 1
16extern "C" {
17// Note: vpx_decoder.h must be first or compile will fail.
18#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
19#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"  // NOLINT
20}
21
22// Enable USE_COPYPLANE_WITH_LIBVPX to use |CopyPlane()| instead of memcpy to
23// copy video frame data.
24// #define USE_COPYPLANE_WITH_LIBVPX 1
25
26namespace media {
27
28static const int kDecodeThreads = 2;
29
30LibvpxCdmVideoDecoder::LibvpxCdmVideoDecoder(CdmHost* host)
31    : is_initialized_(false),
32      host_(host),
33      vpx_codec_(NULL),
34      vpx_image_(NULL) {
35}
36
37LibvpxCdmVideoDecoder::~LibvpxCdmVideoDecoder() {
38  Deinitialize();
39}
40
41bool LibvpxCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) {
42  DVLOG(1) << "Initialize()";
43
44  if (!IsValidOutputConfig(config.format, config.coded_size)) {
45    LOG(ERROR) << "Initialize(): invalid video decoder configuration.";
46    return false;
47  }
48
49  if (is_initialized_) {
50    LOG(ERROR) << "Initialize(): Already initialized.";
51    return false;
52  }
53
54  vpx_codec_ = new vpx_codec_ctx();
55  vpx_codec_dec_cfg_t vpx_config = {0};
56  vpx_config.w = config.coded_size.width;
57  vpx_config.h = config.coded_size.height;
58  vpx_config.threads = kDecodeThreads;
59
60  vpx_codec_err_t status = vpx_codec_dec_init(vpx_codec_,
61                                              vpx_codec_vp8_dx(),
62                                              &vpx_config,
63                                              0);
64  if (status != VPX_CODEC_OK) {
65    LOG(ERROR) << "InitializeLibvpx(): vpx_codec_dec_init failed, ret="
66               << status;
67    delete vpx_codec_;
68    vpx_codec_ = NULL;
69  }
70
71  is_initialized_ = true;
72  return true;
73}
74
75void LibvpxCdmVideoDecoder::Deinitialize() {
76  DVLOG(1) << "Deinitialize()";
77
78  if (vpx_codec_) {
79    vpx_codec_destroy(vpx_codec_);
80    vpx_codec_ = NULL;
81  }
82
83  is_initialized_ = false;
84}
85
86void LibvpxCdmVideoDecoder::Reset() {
87  DVLOG(1) << "Reset()";
88}
89
90// static
91bool LibvpxCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format,
92                                                const cdm::Size& data_size) {
93  return ((format == cdm::kYv12 || format == cdm::kI420) &&
94          (data_size.width % 2) == 0 && (data_size.height % 2) == 0 &&
95          data_size.width > 0 && data_size.height > 0 &&
96          data_size.width <= limits::kMaxDimension &&
97          data_size.height <= limits::kMaxDimension &&
98          data_size.width * data_size.height <= limits::kMaxCanvas);
99}
100
101cdm::Status LibvpxCdmVideoDecoder::DecodeFrame(
102    const uint8_t* compressed_frame,
103    int32_t compressed_frame_size,
104    int64_t timestamp,
105    cdm::VideoFrame* decoded_frame) {
106  DVLOG(1) << "DecodeFrame()";
107  DCHECK(decoded_frame);
108
109  // Pass |compressed_frame| to libvpx.
110  void* user_priv = reinterpret_cast<void*>(&timestamp);
111  vpx_codec_err_t status = vpx_codec_decode(vpx_codec_,
112                                            compressed_frame,
113                                            compressed_frame_size,
114                                            user_priv,
115                                            0);
116  if (status != VPX_CODEC_OK) {
117    LOG(ERROR) << "DecodeFrameLibvpx(): vpx_codec_decode failed, status="
118               << status;
119    return cdm::kDecodeError;
120  }
121
122  // Gets pointer to decoded data.
123  vpx_codec_iter_t iter = NULL;
124  vpx_image_ = vpx_codec_get_frame(vpx_codec_, &iter);
125  if (!vpx_image_)
126    return cdm::kNeedMoreData;
127
128  if (vpx_image_->user_priv != reinterpret_cast<void*>(&timestamp)) {
129    LOG(ERROR) << "DecodeFrameLibvpx() invalid output timestamp.";
130    return cdm::kDecodeError;
131  }
132  decoded_frame->SetTimestamp(timestamp);
133
134  if (!CopyVpxImageTo(decoded_frame)) {
135    LOG(ERROR) << "DecodeFrameLibvpx() could not copy vpx image to output "
136               << "buffer.";
137    return cdm::kDecodeError;
138  }
139
140  return cdm::kSuccess;
141}
142
143bool LibvpxCdmVideoDecoder::CopyVpxImageTo(cdm::VideoFrame* cdm_video_frame) {
144  DCHECK(cdm_video_frame);
145  DCHECK_EQ(vpx_image_->fmt, VPX_IMG_FMT_I420);
146  DCHECK_EQ(vpx_image_->d_w % 2, 0U);
147  DCHECK_EQ(vpx_image_->d_h % 2, 0U);
148
149  const int y_size = vpx_image_->stride[VPX_PLANE_Y] * vpx_image_->d_h;
150  const int uv_rows = vpx_image_->d_h / 2;
151  const int u_size = vpx_image_->stride[VPX_PLANE_U] * uv_rows;
152  const int v_size = vpx_image_->stride[VPX_PLANE_V] * uv_rows;
153  const int space_required = y_size + u_size + v_size;
154
155  DCHECK(!cdm_video_frame->FrameBuffer());
156  cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required));
157  if (!cdm_video_frame->FrameBuffer()) {
158    LOG(ERROR) << "CopyVpxImageTo() CdmHost::Allocate failed.";
159    return false;
160  }
161  cdm_video_frame->FrameBuffer()->SetSize(space_required);
162
163  memcpy(cdm_video_frame->FrameBuffer()->Data(),
164         vpx_image_->planes[VPX_PLANE_Y],
165         y_size);
166  memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size,
167         vpx_image_->planes[VPX_PLANE_U],
168         u_size);
169  memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size + u_size,
170         vpx_image_->planes[VPX_PLANE_V],
171         v_size);
172
173  cdm_video_frame->SetFormat(cdm::kYv12);
174
175  cdm::Size video_frame_size;
176  video_frame_size.width = vpx_image_->d_w;
177  video_frame_size.height = vpx_image_->d_h;
178  cdm_video_frame->SetSize(video_frame_size);
179
180  cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0);
181  cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size);
182  cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane,
183                                    y_size + u_size);
184
185  cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane,
186                              vpx_image_->stride[VPX_PLANE_Y]);
187  cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane,
188                              vpx_image_->stride[VPX_PLANE_U]);
189  cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane,
190                              vpx_image_->stride[VPX_PLANE_V]);
191
192  return true;
193}
194
195}  // namespace media
196