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