|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 #include "MediaData.h" |
|
7 #include "MediaInfo.h" |
|
8 #ifdef MOZ_OMX_DECODER |
|
9 #include "GrallocImages.h" |
|
10 #include "mozilla/layers/TextureClient.h" |
|
11 #endif |
|
12 #include "VideoUtils.h" |
|
13 #include "ImageContainer.h" |
|
14 |
|
15 #ifdef MOZ_WIDGET_GONK |
|
16 #include <cutils/properties.h> |
|
17 #endif |
|
18 |
|
19 namespace mozilla { |
|
20 |
|
21 using namespace mozilla::gfx; |
|
22 using layers::ImageContainer; |
|
23 using layers::PlanarYCbCrImage; |
|
24 using layers::PlanarYCbCrData; |
|
25 |
|
26 void |
|
27 AudioData::EnsureAudioBuffer() |
|
28 { |
|
29 if (mAudioBuffer) |
|
30 return; |
|
31 mAudioBuffer = SharedBuffer::Create(mFrames*mChannels*sizeof(AudioDataValue)); |
|
32 |
|
33 AudioDataValue* data = static_cast<AudioDataValue*>(mAudioBuffer->Data()); |
|
34 for (uint32_t i = 0; i < mFrames; ++i) { |
|
35 for (uint32_t j = 0; j < mChannels; ++j) { |
|
36 data[j*mFrames + i] = mAudioData[i*mChannels + j]; |
|
37 } |
|
38 } |
|
39 } |
|
40 |
|
41 static bool |
|
42 ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane) |
|
43 { |
|
44 return aPlane.mWidth <= PlanarYCbCrImage::MAX_DIMENSION && |
|
45 aPlane.mHeight <= PlanarYCbCrImage::MAX_DIMENSION && |
|
46 aPlane.mWidth * aPlane.mHeight < MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT && |
|
47 aPlane.mStride > 0; |
|
48 } |
|
49 |
|
50 #ifdef MOZ_WIDGET_GONK |
|
51 static bool |
|
52 IsYV12Format(const VideoData::YCbCrBuffer::Plane& aYPlane, |
|
53 const VideoData::YCbCrBuffer::Plane& aCbPlane, |
|
54 const VideoData::YCbCrBuffer::Plane& aCrPlane) |
|
55 { |
|
56 return |
|
57 aYPlane.mWidth % 2 == 0 && |
|
58 aYPlane.mHeight % 2 == 0 && |
|
59 aYPlane.mWidth / 2 == aCbPlane.mWidth && |
|
60 aYPlane.mHeight / 2 == aCbPlane.mHeight && |
|
61 aCbPlane.mWidth == aCrPlane.mWidth && |
|
62 aCbPlane.mHeight == aCrPlane.mHeight; |
|
63 } |
|
64 |
|
65 static bool |
|
66 IsInEmulator() |
|
67 { |
|
68 char propQemu[PROPERTY_VALUE_MAX]; |
|
69 property_get("ro.kernel.qemu", propQemu, ""); |
|
70 return !strncmp(propQemu, "1", 1); |
|
71 } |
|
72 |
|
73 #endif |
|
74 |
|
75 VideoData::VideoData(int64_t aOffset, int64_t aTime, int64_t aDuration, int64_t aTimecode) |
|
76 : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration), |
|
77 mTimecode(aTimecode), |
|
78 mDuplicate(true), |
|
79 mKeyframe(false) |
|
80 { |
|
81 MOZ_COUNT_CTOR(VideoData); |
|
82 NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration."); |
|
83 } |
|
84 |
|
85 VideoData::VideoData(int64_t aOffset, |
|
86 int64_t aTime, |
|
87 int64_t aDuration, |
|
88 bool aKeyframe, |
|
89 int64_t aTimecode, |
|
90 IntSize aDisplay) |
|
91 : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration), |
|
92 mDisplay(aDisplay), |
|
93 mTimecode(aTimecode), |
|
94 mDuplicate(false), |
|
95 mKeyframe(aKeyframe) |
|
96 { |
|
97 MOZ_COUNT_CTOR(VideoData); |
|
98 NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration."); |
|
99 } |
|
100 |
|
101 VideoData::~VideoData() |
|
102 { |
|
103 MOZ_COUNT_DTOR(VideoData); |
|
104 } |
|
105 |
|
106 size_t |
|
107 VideoData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
108 { |
|
109 size_t size = aMallocSizeOf(this); |
|
110 |
|
111 // Currently only PLANAR_YCBCR has a well defined function for determining |
|
112 // it's size, so reporting is limited to that type. |
|
113 if (mImage && mImage->GetFormat() == ImageFormat::PLANAR_YCBCR) { |
|
114 const mozilla::layers::PlanarYCbCrImage* img = |
|
115 static_cast<const mozilla::layers::PlanarYCbCrImage*>(mImage.get()); |
|
116 size += img->SizeOfIncludingThis(aMallocSizeOf); |
|
117 } |
|
118 |
|
119 return size; |
|
120 } |
|
121 |
|
122 /* static */ |
|
123 VideoData* VideoData::ShallowCopyUpdateDuration(VideoData* aOther, |
|
124 int64_t aDuration) |
|
125 { |
|
126 VideoData* v = new VideoData(aOther->mOffset, |
|
127 aOther->mTime, |
|
128 aDuration, |
|
129 aOther->mKeyframe, |
|
130 aOther->mTimecode, |
|
131 aOther->mDisplay); |
|
132 v->mImage = aOther->mImage; |
|
133 return v; |
|
134 } |
|
135 |
|
136 /* static */ |
|
137 VideoData* VideoData::ShallowCopyUpdateTimestamp(VideoData* aOther, |
|
138 int64_t aTimestamp) |
|
139 { |
|
140 NS_ENSURE_TRUE(aOther, nullptr); |
|
141 VideoData* v = new VideoData(aOther->mOffset, |
|
142 aTimestamp, |
|
143 aOther->GetEndTime() - aTimestamp, |
|
144 aOther->mKeyframe, |
|
145 aOther->mTimecode, |
|
146 aOther->mDisplay); |
|
147 v->mImage = aOther->mImage; |
|
148 return v; |
|
149 } |
|
150 |
|
151 /* static */ |
|
152 void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, |
|
153 VideoInfo& aInfo, |
|
154 const YCbCrBuffer &aBuffer, |
|
155 const IntRect& aPicture, |
|
156 bool aCopyData) |
|
157 { |
|
158 if (!aVideoImage) { |
|
159 return; |
|
160 } |
|
161 const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; |
|
162 const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; |
|
163 const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2]; |
|
164 |
|
165 PlanarYCbCrData data; |
|
166 data.mYChannel = Y.mData + Y.mOffset; |
|
167 data.mYSize = IntSize(Y.mWidth, Y.mHeight); |
|
168 data.mYStride = Y.mStride; |
|
169 data.mYSkip = Y.mSkip; |
|
170 data.mCbChannel = Cb.mData + Cb.mOffset; |
|
171 data.mCrChannel = Cr.mData + Cr.mOffset; |
|
172 data.mCbCrSize = IntSize(Cb.mWidth, Cb.mHeight); |
|
173 data.mCbCrStride = Cb.mStride; |
|
174 data.mCbSkip = Cb.mSkip; |
|
175 data.mCrSkip = Cr.mSkip; |
|
176 data.mPicX = aPicture.x; |
|
177 data.mPicY = aPicture.y; |
|
178 data.mPicSize = aPicture.Size(); |
|
179 data.mStereoMode = aInfo.mStereoMode; |
|
180 |
|
181 aVideoImage->SetDelayedConversion(true); |
|
182 if (aCopyData) { |
|
183 aVideoImage->SetData(data); |
|
184 } else { |
|
185 aVideoImage->SetDataNoCopy(data); |
|
186 } |
|
187 } |
|
188 |
|
189 /* static */ |
|
190 VideoData* VideoData::Create(VideoInfo& aInfo, |
|
191 ImageContainer* aContainer, |
|
192 Image* aImage, |
|
193 int64_t aOffset, |
|
194 int64_t aTime, |
|
195 int64_t aDuration, |
|
196 const YCbCrBuffer& aBuffer, |
|
197 bool aKeyframe, |
|
198 int64_t aTimecode, |
|
199 const IntRect& aPicture) |
|
200 { |
|
201 if (!aImage && !aContainer) { |
|
202 // Create a dummy VideoData with no image. This gives us something to |
|
203 // send to media streams if necessary. |
|
204 nsAutoPtr<VideoData> v(new VideoData(aOffset, |
|
205 aTime, |
|
206 aDuration, |
|
207 aKeyframe, |
|
208 aTimecode, |
|
209 aInfo.mDisplay.ToIntSize())); |
|
210 return v.forget(); |
|
211 } |
|
212 |
|
213 // The following situation should never happen unless there is a bug |
|
214 // in the decoder |
|
215 if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth || |
|
216 aBuffer.mPlanes[1].mHeight != aBuffer.mPlanes[2].mHeight) { |
|
217 NS_ERROR("C planes with different sizes"); |
|
218 return nullptr; |
|
219 } |
|
220 |
|
221 // The following situations could be triggered by invalid input |
|
222 if (aPicture.width <= 0 || aPicture.height <= 0) { |
|
223 NS_WARNING("Empty picture rect"); |
|
224 return nullptr; |
|
225 } |
|
226 if (!ValidatePlane(aBuffer.mPlanes[0]) || !ValidatePlane(aBuffer.mPlanes[1]) || |
|
227 !ValidatePlane(aBuffer.mPlanes[2])) { |
|
228 NS_WARNING("Invalid plane size"); |
|
229 return nullptr; |
|
230 } |
|
231 |
|
232 // Ensure the picture size specified in the headers can be extracted out of |
|
233 // the frame we've been supplied without indexing out of bounds. |
|
234 CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width); |
|
235 CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height); |
|
236 if (!xLimit.isValid() || xLimit.value() > aBuffer.mPlanes[0].mStride || |
|
237 !yLimit.isValid() || yLimit.value() > aBuffer.mPlanes[0].mHeight) |
|
238 { |
|
239 // The specified picture dimensions can't be contained inside the video |
|
240 // frame, we'll stomp memory if we try to copy it. Fail. |
|
241 NS_WARNING("Overflowing picture rect"); |
|
242 return nullptr; |
|
243 } |
|
244 |
|
245 nsAutoPtr<VideoData> v(new VideoData(aOffset, |
|
246 aTime, |
|
247 aDuration, |
|
248 aKeyframe, |
|
249 aTimecode, |
|
250 aInfo.mDisplay.ToIntSize())); |
|
251 #ifdef MOZ_WIDGET_GONK |
|
252 const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; |
|
253 const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; |
|
254 const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2]; |
|
255 #endif |
|
256 |
|
257 if (!aImage) { |
|
258 // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR |
|
259 // format. |
|
260 #ifdef MOZ_WIDGET_GONK |
|
261 if (IsYV12Format(Y, Cb, Cr) && !IsInEmulator()) { |
|
262 v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); |
|
263 } |
|
264 #endif |
|
265 if (!v->mImage) { |
|
266 v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR); |
|
267 } |
|
268 } else { |
|
269 v->mImage = aImage; |
|
270 } |
|
271 |
|
272 if (!v->mImage) { |
|
273 return nullptr; |
|
274 } |
|
275 NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR || |
|
276 v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR, |
|
277 "Wrong format?"); |
|
278 PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get()); |
|
279 |
|
280 if (!aImage) { |
|
281 VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, |
|
282 true /* aCopyData */); |
|
283 } else { |
|
284 VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, |
|
285 false /* aCopyData */); |
|
286 } |
|
287 |
|
288 #ifdef MOZ_WIDGET_GONK |
|
289 if (!videoImage->IsValid() && !aImage && IsYV12Format(Y, Cb, Cr)) { |
|
290 // Failed to allocate gralloc. Try fallback. |
|
291 v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR); |
|
292 if (!v->mImage) { |
|
293 return nullptr; |
|
294 } |
|
295 videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get()); |
|
296 VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, |
|
297 true /* aCopyData */); |
|
298 } |
|
299 #endif |
|
300 return v.forget(); |
|
301 } |
|
302 |
|
303 /* static */ |
|
304 VideoData* VideoData::Create(VideoInfo& aInfo, |
|
305 ImageContainer* aContainer, |
|
306 int64_t aOffset, |
|
307 int64_t aTime, |
|
308 int64_t aDuration, |
|
309 const YCbCrBuffer& aBuffer, |
|
310 bool aKeyframe, |
|
311 int64_t aTimecode, |
|
312 const IntRect& aPicture) |
|
313 { |
|
314 return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer, |
|
315 aKeyframe, aTimecode, aPicture); |
|
316 } |
|
317 |
|
318 /* static */ |
|
319 VideoData* VideoData::Create(VideoInfo& aInfo, |
|
320 Image* aImage, |
|
321 int64_t aOffset, |
|
322 int64_t aTime, |
|
323 int64_t aDuration, |
|
324 const YCbCrBuffer& aBuffer, |
|
325 bool aKeyframe, |
|
326 int64_t aTimecode, |
|
327 const IntRect& aPicture) |
|
328 { |
|
329 return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer, |
|
330 aKeyframe, aTimecode, aPicture); |
|
331 } |
|
332 |
|
333 /* static */ |
|
334 VideoData* VideoData::CreateFromImage(VideoInfo& aInfo, |
|
335 ImageContainer* aContainer, |
|
336 int64_t aOffset, |
|
337 int64_t aTime, |
|
338 int64_t aDuration, |
|
339 const nsRefPtr<Image>& aImage, |
|
340 bool aKeyframe, |
|
341 int64_t aTimecode, |
|
342 const IntRect& aPicture) |
|
343 { |
|
344 nsAutoPtr<VideoData> v(new VideoData(aOffset, |
|
345 aTime, |
|
346 aDuration, |
|
347 aKeyframe, |
|
348 aTimecode, |
|
349 aInfo.mDisplay.ToIntSize())); |
|
350 v->mImage = aImage; |
|
351 return v.forget(); |
|
352 } |
|
353 |
|
354 #ifdef MOZ_OMX_DECODER |
|
355 /* static */ |
|
356 VideoData* VideoData::Create(VideoInfo& aInfo, |
|
357 ImageContainer* aContainer, |
|
358 int64_t aOffset, |
|
359 int64_t aTime, |
|
360 int64_t aDuration, |
|
361 mozilla::layers::TextureClient* aBuffer, |
|
362 bool aKeyframe, |
|
363 int64_t aTimecode, |
|
364 const IntRect& aPicture) |
|
365 { |
|
366 if (!aContainer) { |
|
367 // Create a dummy VideoData with no image. This gives us something to |
|
368 // send to media streams if necessary. |
|
369 nsAutoPtr<VideoData> v(new VideoData(aOffset, |
|
370 aTime, |
|
371 aDuration, |
|
372 aKeyframe, |
|
373 aTimecode, |
|
374 aInfo.mDisplay.ToIntSize())); |
|
375 return v.forget(); |
|
376 } |
|
377 |
|
378 // The following situations could be triggered by invalid input |
|
379 if (aPicture.width <= 0 || aPicture.height <= 0) { |
|
380 NS_WARNING("Empty picture rect"); |
|
381 return nullptr; |
|
382 } |
|
383 |
|
384 // Ensure the picture size specified in the headers can be extracted out of |
|
385 // the frame we've been supplied without indexing out of bounds. |
|
386 CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width); |
|
387 CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height); |
|
388 if (!xLimit.isValid() || !yLimit.isValid()) |
|
389 { |
|
390 // The specified picture dimensions can't be contained inside the video |
|
391 // frame, we'll stomp memory if we try to copy it. Fail. |
|
392 NS_WARNING("Overflowing picture rect"); |
|
393 return nullptr; |
|
394 } |
|
395 |
|
396 nsAutoPtr<VideoData> v(new VideoData(aOffset, |
|
397 aTime, |
|
398 aDuration, |
|
399 aKeyframe, |
|
400 aTimecode, |
|
401 aInfo.mDisplay.ToIntSize())); |
|
402 |
|
403 v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); |
|
404 if (!v->mImage) { |
|
405 return nullptr; |
|
406 } |
|
407 NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR, |
|
408 "Wrong format?"); |
|
409 typedef mozilla::layers::GrallocImage GrallocImage; |
|
410 GrallocImage* videoImage = static_cast<GrallocImage*>(v->mImage.get()); |
|
411 GrallocImage::GrallocData data; |
|
412 |
|
413 data.mPicSize = aPicture.Size(); |
|
414 data.mGraphicBuffer = aBuffer; |
|
415 |
|
416 videoImage->SetData(data); |
|
417 |
|
418 return v.forget(); |
|
419 } |
|
420 #endif // MOZ_OMX_DECODER |
|
421 |
|
422 } // namespace mozilla |