| OLD | NEW |
| (Empty) |
| 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/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 | |
| 23 namespace media { | |
| 24 namespace 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. | |
| 29 class VideoDecoder::ImplBase | |
| 30 : public base::RefCountedThreadSafe<VideoDecoder::ImplBase> { | |
| 31 public: | |
| 32 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, | |
| 33 transport::VideoCodec 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<transport::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 transport::VideoCodec 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 | |
| 92 class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase { | |
| 93 public: | |
| 94 explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment) | |
| 95 : ImplBase(cast_environment, transport::kVp8) { | |
| 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); | |
| 154 CopyUPlane(image->planes[VPX_PLANE_U], | |
| 155 image->stride[VPX_PLANE_U], | |
| 156 (image->d_h + 1) / 2, | |
| 157 decoded_frame); | |
| 158 CopyVPlane(image->planes[VPX_PLANE_V], | |
| 159 image->stride[VPX_PLANE_V], | |
| 160 (image->d_h + 1) / 2, | |
| 161 decoded_frame); | |
| 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. | |
| 173 class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase { | |
| 174 public: | |
| 175 explicit FakeImpl(const scoped_refptr<CastEnvironment>& cast_environment) | |
| 176 : ImplBase(cast_environment, transport::kFakeSoftwareVideo), | |
| 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 base::JSONReader reader; | |
| 188 scoped_ptr<base::Value> values(reader.Read( | |
| 189 base::StringPiece(reinterpret_cast<char*>(data), len))); | |
| 190 base::DictionaryValue* dict = NULL; | |
| 191 values->GetAsDictionary(&dict); | |
| 192 | |
| 193 bool key = false; | |
| 194 int id = 0; | |
| 195 int ref = 0; | |
| 196 dict->GetBoolean("key", &key); | |
| 197 dict->GetInteger("id", &id); | |
| 198 dict->GetInteger("ref", &ref); | |
| 199 DCHECK(id == last_decoded_id_ + 1); | |
| 200 last_decoded_id_ = id; | |
| 201 return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2)); | |
| 202 } | |
| 203 | |
| 204 int last_decoded_id_; | |
| 205 | |
| 206 DISALLOW_COPY_AND_ASSIGN(FakeImpl); | |
| 207 }; | |
| 208 #endif | |
| 209 | |
| 210 VideoDecoder::VideoDecoder( | |
| 211 const scoped_refptr<CastEnvironment>& cast_environment, | |
| 212 const FrameReceiverConfig& video_config) | |
| 213 : cast_environment_(cast_environment) { | |
| 214 switch (video_config.codec.video) { | |
| 215 #ifndef OFFICIAL_BUILD | |
| 216 case transport::kFakeSoftwareVideo: | |
| 217 impl_ = new FakeImpl(cast_environment); | |
| 218 break; | |
| 219 #endif | |
| 220 case transport::kVp8: | |
| 221 impl_ = new Vp8Impl(cast_environment); | |
| 222 break; | |
| 223 case transport::kH264: | |
| 224 // TODO(miu): Need implementation. | |
| 225 NOTIMPLEMENTED(); | |
| 226 break; | |
| 227 default: | |
| 228 NOTREACHED() << "Unknown or unspecified codec."; | |
| 229 break; | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 VideoDecoder::~VideoDecoder() {} | |
| 234 | |
| 235 CastInitializationStatus VideoDecoder::InitializationResult() const { | |
| 236 if (impl_) | |
| 237 return impl_->InitializationResult(); | |
| 238 return STATUS_UNSUPPORTED_VIDEO_CODEC; | |
| 239 } | |
| 240 | |
| 241 void VideoDecoder::DecodeFrame( | |
| 242 scoped_ptr<transport::EncodedFrame> encoded_frame, | |
| 243 const DecodeFrameCallback& callback) { | |
| 244 DCHECK(encoded_frame.get()); | |
| 245 DCHECK(!callback.is_null()); | |
| 246 if (!impl_ || impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) { | |
| 247 callback.Run(make_scoped_refptr<VideoFrame>(NULL), false); | |
| 248 return; | |
| 249 } | |
| 250 cast_environment_->PostTask(CastEnvironment::VIDEO, | |
| 251 FROM_HERE, | |
| 252 base::Bind(&VideoDecoder::ImplBase::DecodeFrame, | |
| 253 impl_, | |
| 254 base::Passed(&encoded_frame), | |
| 255 callback)); | |
| 256 } | |
| 257 | |
| 258 } // namespace cast | |
| 259 } // namespace media | |
| OLD | NEW |