| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/cast/video_receiver/video_decoder.h" | 5 #include "media/cast/video_receiver/video_decoder.h" |
| 6 | 6 |
| 7 #include <stdint.h> | |
| 8 | |
| 9 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/bind_helpers.h" |
| 9 #include "base/location.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/message_loop/message_loop.h" | 11 #include "base/stl_util.h" |
| 12 #include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" | 12 #include "media/base/video_util.h" |
| 13 #include "media/cast/cast_defines.h" |
| 14 #include "media/cast/cast_environment.h" |
| 15 // VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide |
| 16 // backwards compatibility for legacy applications using the library. |
| 17 #define VPX_CODEC_DISABLE_COMPAT 1 |
| 18 #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" |
| 19 #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" |
| 20 #include "ui/gfx/size.h" |
| 13 | 21 |
| 14 namespace media { | 22 namespace media { |
| 15 namespace cast { | 23 namespace cast { |
| 16 | 24 |
| 17 VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config, | 25 // Base class that handles the common problem of detecting dropped frames, and |
| 18 scoped_refptr<CastEnvironment> cast_environment) | 26 // then invoking the Decode() method implemented by the subclasses to convert |
| 19 : codec_(video_config.codec), vp8_decoder_() { | 27 // the encoded payload data into a usable video frame. |
| 28 class VideoDecoder::ImplBase |
| 29 : public base::RefCountedThreadSafe<VideoDecoder::ImplBase> { |
| 30 public: |
| 31 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, |
| 32 transport::VideoCodec codec) |
| 33 : cast_environment_(cast_environment), |
| 34 codec_(codec), |
| 35 cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED), |
| 36 seen_first_frame_(false) {} |
| 37 |
| 38 CastInitializationStatus InitializationResult() const { |
| 39 return cast_initialization_status_; |
| 40 } |
| 41 |
| 42 void DecodeFrame(scoped_ptr<transport::EncodedVideoFrame> encoded_frame, |
| 43 const DecodeFrameCallback& callback) { |
| 44 DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED); |
| 45 |
| 46 if (encoded_frame->codec != codec_) { |
| 47 NOTREACHED(); |
| 48 cast_environment_->PostTask( |
| 49 CastEnvironment::MAIN, |
| 50 FROM_HERE, |
| 51 base::Bind(callback, scoped_refptr<VideoFrame>(NULL), false)); |
| 52 } |
| 53 |
| 54 COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_), |
| 55 size_of_frame_id_types_do_not_match); |
| 56 bool is_continuous = true; |
| 57 if (seen_first_frame_) { |
| 58 const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_; |
| 59 if (frames_ahead > 1) { |
| 60 RecoverBecauseFramesWereDropped(); |
| 61 is_continuous = false; |
| 62 } |
| 63 } else { |
| 64 seen_first_frame_ = true; |
| 65 } |
| 66 last_frame_id_ = encoded_frame->frame_id; |
| 67 |
| 68 const scoped_refptr<VideoFrame> decoded_frame = Decode( |
| 69 reinterpret_cast<uint8*>(string_as_array(&encoded_frame->data)), |
| 70 static_cast<int>(encoded_frame->data.size())); |
| 71 cast_environment_->PostTask( |
| 72 CastEnvironment::MAIN, |
| 73 FROM_HERE, |
| 74 base::Bind(callback, decoded_frame, is_continuous)); |
| 75 } |
| 76 |
| 77 protected: |
| 78 friend class base::RefCountedThreadSafe<ImplBase>; |
| 79 virtual ~ImplBase() {} |
| 80 |
| 81 virtual void RecoverBecauseFramesWereDropped() {} |
| 82 |
| 83 // Note: Implementation of Decode() is allowed to mutate |data|. |
| 84 virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0; |
| 85 |
| 86 const scoped_refptr<CastEnvironment> cast_environment_; |
| 87 const transport::VideoCodec codec_; |
| 88 |
| 89 // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED. |
| 90 CastInitializationStatus cast_initialization_status_; |
| 91 |
| 92 private: |
| 93 bool seen_first_frame_; |
| 94 uint32 last_frame_id_; |
| 95 |
| 96 DISALLOW_COPY_AND_ASSIGN(ImplBase); |
| 97 }; |
| 98 |
| 99 class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase { |
| 100 public: |
| 101 explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment) |
| 102 : ImplBase(cast_environment, transport::kVp8) { |
| 103 if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED) |
| 104 return; |
| 105 |
| 106 vpx_codec_dec_cfg_t cfg = {0}; |
| 107 // TODO(miu): Revisit this for typical multi-core desktop use case. This |
| 108 // feels like it should be 4 or 8. |
| 109 cfg.threads = 1; |
| 110 |
| 111 DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC); |
| 112 if (vpx_codec_dec_init(&context_, |
| 113 vpx_codec_vp8_dx(), |
| 114 &cfg, |
| 115 VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) { |
| 116 ImplBase::cast_initialization_status_ = |
| 117 STATUS_INVALID_VIDEO_CONFIGURATION; |
| 118 return; |
| 119 } |
| 120 ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED; |
| 121 } |
| 122 |
| 123 private: |
| 124 virtual ~Vp8Impl() { |
| 125 if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED) |
| 126 CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_)); |
| 127 } |
| 128 |
| 129 virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE { |
| 130 if (len <= 0 || vpx_codec_decode(&context_, |
| 131 data, |
| 132 static_cast<unsigned int>(len), |
| 133 NULL, |
| 134 0) != VPX_CODEC_OK) { |
| 135 return NULL; |
| 136 } |
| 137 |
| 138 vpx_codec_iter_t iter = NULL; |
| 139 vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter); |
| 140 if (!image) |
| 141 return NULL; |
| 142 if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) { |
| 143 NOTREACHED(); |
| 144 return NULL; |
| 145 } |
| 146 DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL) |
| 147 << "Should have only decoded exactly one frame."; |
| 148 |
| 149 const gfx::Size frame_size(image->d_w, image->d_h); |
| 150 // Note: Timestamp for the VideoFrame will be set in VideoReceiver. |
| 151 const scoped_refptr<VideoFrame> decoded_frame = |
| 152 VideoFrame::CreateFrame(VideoFrame::YV12, |
| 153 frame_size, |
| 154 gfx::Rect(frame_size), |
| 155 frame_size, |
| 156 base::TimeDelta()); |
| 157 CopyYPlane(image->planes[VPX_PLANE_Y], |
| 158 image->stride[VPX_PLANE_Y], |
| 159 image->d_h, |
| 160 decoded_frame); |
| 161 CopyUPlane(image->planes[VPX_PLANE_U], |
| 162 image->stride[VPX_PLANE_U], |
| 163 (image->d_h + 1) / 2, |
| 164 decoded_frame); |
| 165 CopyVPlane(image->planes[VPX_PLANE_V], |
| 166 image->stride[VPX_PLANE_V], |
| 167 (image->d_h + 1) / 2, |
| 168 decoded_frame); |
| 169 return decoded_frame; |
| 170 } |
| 171 |
| 172 // VPX decoder context (i.e., an instantiation). |
| 173 vpx_codec_ctx_t context_; |
| 174 |
| 175 DISALLOW_COPY_AND_ASSIGN(Vp8Impl); |
| 176 }; |
| 177 |
| 178 VideoDecoder::VideoDecoder( |
| 179 const scoped_refptr<CastEnvironment>& cast_environment, |
| 180 const VideoReceiverConfig& video_config) |
| 181 : cast_environment_(cast_environment) { |
| 20 switch (video_config.codec) { | 182 switch (video_config.codec) { |
| 21 case transport::kVp8: | 183 case transport::kVp8: |
| 22 vp8_decoder_.reset(new Vp8Decoder(cast_environment)); | 184 impl_ = new Vp8Impl(cast_environment); |
| 23 break; | 185 break; |
| 24 case transport::kH264: | 186 case transport::kH264: |
| 187 // TODO(miu): Need implementation. |
| 25 NOTIMPLEMENTED(); | 188 NOTIMPLEMENTED(); |
| 26 break; | 189 break; |
| 190 default: |
| 191 NOTREACHED() << "Unknown or unspecified codec."; |
| 192 break; |
| 27 } | 193 } |
| 28 } | 194 } |
| 29 | 195 |
| 30 VideoDecoder::~VideoDecoder() {} | 196 VideoDecoder::~VideoDecoder() {} |
| 31 | 197 |
| 32 bool VideoDecoder::DecodeVideoFrame( | 198 CastInitializationStatus VideoDecoder::InitializationResult() const { |
| 33 const transport::EncodedVideoFrame* encoded_frame, | 199 if (impl_) |
| 34 const base::TimeTicks render_time, | 200 return impl_->InitializationResult(); |
| 35 const VideoFrameDecodedCallback& frame_decoded_cb) { | 201 return STATUS_UNSUPPORTED_VIDEO_CODEC; |
| 36 DCHECK(encoded_frame->codec == codec_) << "Invalid codec"; | |
| 37 DCHECK_GT(encoded_frame->data.size(), UINT64_C(0)) << "Empty video frame"; | |
| 38 return vp8_decoder_->Decode(encoded_frame, render_time, frame_decoded_cb); | |
| 39 } | 202 } |
| 40 | 203 |
| 204 void VideoDecoder::DecodeFrame( |
| 205 scoped_ptr<transport::EncodedVideoFrame> encoded_frame, |
| 206 const DecodeFrameCallback& callback) { |
| 207 DCHECK(encoded_frame.get()); |
| 208 DCHECK(!callback.is_null()); |
| 209 if (!impl_ || impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) { |
| 210 callback.Run(make_scoped_refptr<VideoFrame>(NULL), false); |
| 211 return; |
| 212 } |
| 213 cast_environment_->PostTask(CastEnvironment::VIDEO, |
| 214 FROM_HERE, |
| 215 base::Bind(&VideoDecoder::ImplBase::DecodeFrame, |
| 216 impl_, |
| 217 base::Passed(&encoded_frame), |
| 218 callback)); |
| 219 } |
| 220 |
| 41 } // namespace cast | 221 } // namespace cast |
| 42 } // namespace media | 222 } // namespace media |
| OLD | NEW |