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 |