|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
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 file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 #include "MediaPluginReader.h" |
|
7 #include "mozilla/TimeStamp.h" |
|
8 #include "mozilla/dom/TimeRanges.h" |
|
9 #include "mozilla/gfx/Point.h" |
|
10 #include "MediaResource.h" |
|
11 #include "VideoUtils.h" |
|
12 #include "MediaPluginDecoder.h" |
|
13 #include "MediaPluginHost.h" |
|
14 #include "MediaDecoderStateMachine.h" |
|
15 #include "ImageContainer.h" |
|
16 #include "AbstractMediaDecoder.h" |
|
17 #include "gfx2DGlue.h" |
|
18 |
|
19 namespace mozilla { |
|
20 |
|
21 using namespace mozilla::gfx; |
|
22 |
|
23 typedef mozilla::layers::Image Image; |
|
24 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage; |
|
25 |
|
26 MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder, |
|
27 const nsACString& aContentType) : |
|
28 MediaDecoderReader(aDecoder), |
|
29 mType(aContentType), |
|
30 mPlugin(nullptr), |
|
31 mHasAudio(false), |
|
32 mHasVideo(false), |
|
33 mVideoSeekTimeUs(-1), |
|
34 mAudioSeekTimeUs(-1) |
|
35 { |
|
36 } |
|
37 |
|
38 MediaPluginReader::~MediaPluginReader() |
|
39 { |
|
40 ResetDecode(); |
|
41 } |
|
42 |
|
43 nsresult MediaPluginReader::Init(MediaDecoderReader* aCloneDonor) |
|
44 { |
|
45 return NS_OK; |
|
46 } |
|
47 |
|
48 nsresult MediaPluginReader::ReadMetadata(MediaInfo* aInfo, |
|
49 MetadataTags** aTags) |
|
50 { |
|
51 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
52 |
|
53 if (!mPlugin) { |
|
54 mPlugin = GetMediaPluginHost()->CreateDecoder(mDecoder->GetResource(), mType); |
|
55 if (!mPlugin) { |
|
56 return NS_ERROR_FAILURE; |
|
57 } |
|
58 } |
|
59 |
|
60 // Set the total duration (the max of the audio and video track). |
|
61 int64_t durationUs; |
|
62 mPlugin->GetDuration(mPlugin, &durationUs); |
|
63 if (durationUs) { |
|
64 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
65 mDecoder->SetMediaDuration(durationUs); |
|
66 } |
|
67 |
|
68 if (mPlugin->HasVideo(mPlugin)) { |
|
69 int32_t width, height; |
|
70 mPlugin->GetVideoParameters(mPlugin, &width, &height); |
|
71 nsIntRect pictureRect(0, 0, width, height); |
|
72 |
|
73 // Validate the container-reported frame and pictureRect sizes. This ensures |
|
74 // that our video frame creation code doesn't overflow. |
|
75 nsIntSize displaySize(width, height); |
|
76 nsIntSize frameSize(width, height); |
|
77 if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) { |
|
78 return NS_ERROR_FAILURE; |
|
79 } |
|
80 |
|
81 // Video track's frame sizes will not overflow. Activate the video track. |
|
82 mHasVideo = mInfo.mVideo.mHasVideo = true; |
|
83 mInfo.mVideo.mDisplay = displaySize; |
|
84 mPicture = pictureRect; |
|
85 mInitialFrame = frameSize; |
|
86 VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); |
|
87 if (container) { |
|
88 container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height), |
|
89 nullptr, |
|
90 mozilla::TimeStamp::Now()); |
|
91 } |
|
92 } |
|
93 |
|
94 if (mPlugin->HasAudio(mPlugin)) { |
|
95 int32_t numChannels, sampleRate; |
|
96 mPlugin->GetAudioParameters(mPlugin, &numChannels, &sampleRate); |
|
97 mHasAudio = mInfo.mAudio.mHasAudio = true; |
|
98 mInfo.mAudio.mChannels = numChannels; |
|
99 mInfo.mAudio.mRate = sampleRate; |
|
100 } |
|
101 |
|
102 *aInfo = mInfo; |
|
103 *aTags = nullptr; |
|
104 return NS_OK; |
|
105 } |
|
106 |
|
107 // Resets all state related to decoding, emptying all buffers etc. |
|
108 nsresult MediaPluginReader::ResetDecode() |
|
109 { |
|
110 if (mLastVideoFrame) { |
|
111 mLastVideoFrame = nullptr; |
|
112 } |
|
113 if (mPlugin) { |
|
114 GetMediaPluginHost()->DestroyDecoder(mPlugin); |
|
115 mPlugin = nullptr; |
|
116 } |
|
117 |
|
118 return NS_OK; |
|
119 } |
|
120 |
|
121 bool MediaPluginReader::DecodeVideoFrame(bool &aKeyframeSkip, |
|
122 int64_t aTimeThreshold) |
|
123 { |
|
124 // Record number of frames decoded and parsed. Automatically update the |
|
125 // stats counters using the AutoNotifyDecoded stack-based class. |
|
126 uint32_t parsed = 0, decoded = 0; |
|
127 AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); |
|
128 |
|
129 // Throw away the currently buffered frame if we are seeking. |
|
130 if (mLastVideoFrame && mVideoSeekTimeUs != -1) { |
|
131 mLastVideoFrame = nullptr; |
|
132 } |
|
133 |
|
134 ImageBufferCallback bufferCallback(mDecoder->GetImageContainer()); |
|
135 nsRefPtr<Image> currentImage; |
|
136 |
|
137 // Read next frame |
|
138 while (true) { |
|
139 MPAPI::VideoFrame frame; |
|
140 if (!mPlugin->ReadVideo(mPlugin, &frame, mVideoSeekTimeUs, &bufferCallback)) { |
|
141 // We reached the end of the video stream. If we have a buffered |
|
142 // video frame, push it the video queue using the total duration |
|
143 // of the video as the end time. |
|
144 if (mLastVideoFrame) { |
|
145 int64_t durationUs; |
|
146 mPlugin->GetDuration(mPlugin, &durationUs); |
|
147 durationUs = std::max<int64_t>(durationUs - mLastVideoFrame->mTime, 0); |
|
148 mVideoQueue.Push(VideoData::ShallowCopyUpdateDuration(mLastVideoFrame, |
|
149 durationUs)); |
|
150 mLastVideoFrame = nullptr; |
|
151 } |
|
152 return false; |
|
153 } |
|
154 mVideoSeekTimeUs = -1; |
|
155 |
|
156 if (aKeyframeSkip) { |
|
157 // Disable keyframe skipping for now as |
|
158 // stagefright doesn't seem to be telling us |
|
159 // when a frame is a keyframe. |
|
160 #if 0 |
|
161 if (!frame.mKeyFrame) { |
|
162 ++parsed; |
|
163 continue; |
|
164 } |
|
165 #endif |
|
166 aKeyframeSkip = false; |
|
167 } |
|
168 |
|
169 if (frame.mSize == 0) |
|
170 return true; |
|
171 |
|
172 currentImage = bufferCallback.GetImage(); |
|
173 int64_t pos = mDecoder->GetResource()->Tell(); |
|
174 IntRect picture = ToIntRect(mPicture); |
|
175 |
|
176 nsAutoPtr<VideoData> v; |
|
177 if (currentImage) { |
|
178 gfx::IntSize frameSize = currentImage->GetSize(); |
|
179 if (frameSize.width != mInitialFrame.width || |
|
180 frameSize.height != mInitialFrame.height) { |
|
181 // Frame size is different from what the container reports. This is legal, |
|
182 // and we will preserve the ratio of the crop rectangle as it |
|
183 // was reported relative to the picture size reported by the container. |
|
184 picture.x = (mPicture.x * frameSize.width) / mInitialFrame.width; |
|
185 picture.y = (mPicture.y * frameSize.height) / mInitialFrame.height; |
|
186 picture.width = (frameSize.width * mPicture.width) / mInitialFrame.width; |
|
187 picture.height = (frameSize.height * mPicture.height) / mInitialFrame.height; |
|
188 } |
|
189 |
|
190 v = VideoData::CreateFromImage(mInfo.mVideo, |
|
191 mDecoder->GetImageContainer(), |
|
192 pos, |
|
193 frame.mTimeUs, |
|
194 1, // We don't know the duration yet. |
|
195 currentImage, |
|
196 frame.mKeyFrame, |
|
197 -1, |
|
198 picture); |
|
199 } else { |
|
200 // Assume YUV |
|
201 VideoData::YCbCrBuffer b; |
|
202 b.mPlanes[0].mData = static_cast<uint8_t *>(frame.Y.mData); |
|
203 b.mPlanes[0].mStride = frame.Y.mStride; |
|
204 b.mPlanes[0].mHeight = frame.Y.mHeight; |
|
205 b.mPlanes[0].mWidth = frame.Y.mWidth; |
|
206 b.mPlanes[0].mOffset = frame.Y.mOffset; |
|
207 b.mPlanes[0].mSkip = frame.Y.mSkip; |
|
208 |
|
209 b.mPlanes[1].mData = static_cast<uint8_t *>(frame.Cb.mData); |
|
210 b.mPlanes[1].mStride = frame.Cb.mStride; |
|
211 b.mPlanes[1].mHeight = frame.Cb.mHeight; |
|
212 b.mPlanes[1].mWidth = frame.Cb.mWidth; |
|
213 b.mPlanes[1].mOffset = frame.Cb.mOffset; |
|
214 b.mPlanes[1].mSkip = frame.Cb.mSkip; |
|
215 |
|
216 b.mPlanes[2].mData = static_cast<uint8_t *>(frame.Cr.mData); |
|
217 b.mPlanes[2].mStride = frame.Cr.mStride; |
|
218 b.mPlanes[2].mHeight = frame.Cr.mHeight; |
|
219 b.mPlanes[2].mWidth = frame.Cr.mWidth; |
|
220 b.mPlanes[2].mOffset = frame.Cr.mOffset; |
|
221 b.mPlanes[2].mSkip = frame.Cr.mSkip; |
|
222 |
|
223 if (frame.Y.mWidth != mInitialFrame.width || |
|
224 frame.Y.mHeight != mInitialFrame.height) { |
|
225 |
|
226 // Frame size is different from what the container reports. This is legal, |
|
227 // and we will preserve the ratio of the crop rectangle as it |
|
228 // was reported relative to the picture size reported by the container. |
|
229 picture.x = (mPicture.x * frame.Y.mWidth) / mInitialFrame.width; |
|
230 picture.y = (mPicture.y * frame.Y.mHeight) / mInitialFrame.height; |
|
231 picture.width = (frame.Y.mWidth * mPicture.width) / mInitialFrame.width; |
|
232 picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height; |
|
233 } |
|
234 |
|
235 // This is the approximate byte position in the stream. |
|
236 v = VideoData::Create(mInfo.mVideo, |
|
237 mDecoder->GetImageContainer(), |
|
238 pos, |
|
239 frame.mTimeUs, |
|
240 1, // We don't know the duration yet. |
|
241 b, |
|
242 frame.mKeyFrame, |
|
243 -1, |
|
244 picture); |
|
245 } |
|
246 |
|
247 if (!v) { |
|
248 return false; |
|
249 } |
|
250 parsed++; |
|
251 decoded++; |
|
252 NS_ASSERTION(decoded <= parsed, "Expect to decode fewer frames than parsed in MediaPlugin..."); |
|
253 |
|
254 // Since MPAPI doesn't give us the end time of frames, we keep one frame |
|
255 // buffered in MediaPluginReader and push it into the queue as soon |
|
256 // we read the following frame so we can use that frame's start time as |
|
257 // the end time of the buffered frame. |
|
258 if (!mLastVideoFrame) { |
|
259 mLastVideoFrame = v; |
|
260 continue; |
|
261 } |
|
262 |
|
263 // Calculate the duration as the timestamp of the current frame minus the |
|
264 // timestamp of the previous frame. We can then return the previously |
|
265 // decoded frame, and it will have a valid timestamp. |
|
266 int64_t duration = v->mTime - mLastVideoFrame->mTime; |
|
267 mLastVideoFrame = VideoData::ShallowCopyUpdateDuration(mLastVideoFrame, duration); |
|
268 |
|
269 // We have the start time of the next frame, so we can push the previous |
|
270 // frame into the queue, except if the end time is below the threshold, |
|
271 // in which case it wouldn't be displayed anyway. |
|
272 if (mLastVideoFrame->GetEndTime() < aTimeThreshold) { |
|
273 mLastVideoFrame = nullptr; |
|
274 continue; |
|
275 } |
|
276 |
|
277 mVideoQueue.Push(mLastVideoFrame.forget()); |
|
278 |
|
279 // Buffer the current frame we just decoded. |
|
280 mLastVideoFrame = v; |
|
281 |
|
282 break; |
|
283 } |
|
284 |
|
285 return true; |
|
286 } |
|
287 |
|
288 bool MediaPluginReader::DecodeAudioData() |
|
289 { |
|
290 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
291 |
|
292 // This is the approximate byte position in the stream. |
|
293 int64_t pos = mDecoder->GetResource()->Tell(); |
|
294 |
|
295 // Read next frame |
|
296 MPAPI::AudioFrame source; |
|
297 if (!mPlugin->ReadAudio(mPlugin, &source, mAudioSeekTimeUs)) { |
|
298 return false; |
|
299 } |
|
300 mAudioSeekTimeUs = -1; |
|
301 |
|
302 // Ignore empty buffers which stagefright media read will sporadically return |
|
303 if (source.mSize == 0) |
|
304 return true; |
|
305 |
|
306 uint32_t frames = source.mSize / (source.mAudioChannels * |
|
307 sizeof(AudioDataValue)); |
|
308 |
|
309 typedef AudioCompactor::NativeCopy MPCopy; |
|
310 return mAudioCompactor.Push(pos, |
|
311 source.mTimeUs, |
|
312 source.mAudioSampleRate, |
|
313 frames, |
|
314 source.mAudioChannels, |
|
315 MPCopy(static_cast<uint8_t *>(source.mData), |
|
316 source.mSize, |
|
317 source.mAudioChannels)); |
|
318 } |
|
319 |
|
320 nsresult MediaPluginReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) |
|
321 { |
|
322 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
323 |
|
324 mVideoQueue.Reset(); |
|
325 mAudioQueue.Reset(); |
|
326 |
|
327 mAudioSeekTimeUs = mVideoSeekTimeUs = aTarget; |
|
328 |
|
329 return NS_OK; |
|
330 } |
|
331 |
|
332 MediaPluginReader::ImageBufferCallback::ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer) : |
|
333 mImageContainer(aImageContainer) |
|
334 { |
|
335 } |
|
336 |
|
337 void * |
|
338 MediaPluginReader::ImageBufferCallback::operator()(size_t aWidth, size_t aHeight, |
|
339 MPAPI::ColorFormat aColorFormat) |
|
340 { |
|
341 if (!mImageContainer) { |
|
342 NS_WARNING("No image container to construct an image"); |
|
343 return nullptr; |
|
344 } |
|
345 |
|
346 nsRefPtr<Image> image; |
|
347 switch(aColorFormat) { |
|
348 case MPAPI::RGB565: |
|
349 image = mozilla::layers::CreateSharedRGBImage(mImageContainer, |
|
350 nsIntSize(aWidth, aHeight), |
|
351 gfxImageFormat::RGB16_565); |
|
352 if (!image) { |
|
353 NS_WARNING("Could not create rgb image"); |
|
354 return nullptr; |
|
355 } |
|
356 |
|
357 mImage = image; |
|
358 return image->AsSharedImage()->GetBuffer(); |
|
359 case MPAPI::I420: |
|
360 return CreateI420Image(aWidth, aHeight); |
|
361 default: |
|
362 NS_NOTREACHED("Color format not supported"); |
|
363 return nullptr; |
|
364 } |
|
365 } |
|
366 |
|
367 uint8_t * |
|
368 MediaPluginReader::ImageBufferCallback::CreateI420Image(size_t aWidth, |
|
369 size_t aHeight) |
|
370 { |
|
371 mImage = mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR); |
|
372 PlanarYCbCrImage *yuvImage = static_cast<PlanarYCbCrImage *>(mImage.get()); |
|
373 |
|
374 if (!yuvImage) { |
|
375 NS_WARNING("Could not create I420 image"); |
|
376 return nullptr; |
|
377 } |
|
378 |
|
379 size_t frameSize = aWidth * aHeight; |
|
380 |
|
381 // Allocate enough for one full resolution Y plane |
|
382 // and two quarter resolution Cb/Cr planes. |
|
383 uint8_t *buffer = yuvImage->AllocateAndGetNewBuffer(frameSize * 3 / 2); |
|
384 |
|
385 mozilla::layers::PlanarYCbCrData frameDesc; |
|
386 |
|
387 frameDesc.mYChannel = buffer; |
|
388 frameDesc.mCbChannel = buffer + frameSize; |
|
389 frameDesc.mCrChannel = buffer + frameSize * 5 / 4; |
|
390 |
|
391 frameDesc.mYSize = IntSize(aWidth, aHeight); |
|
392 frameDesc.mCbCrSize = IntSize(aWidth / 2, aHeight / 2); |
|
393 |
|
394 frameDesc.mYStride = aWidth; |
|
395 frameDesc.mCbCrStride = aWidth / 2; |
|
396 |
|
397 frameDesc.mYSkip = 0; |
|
398 frameDesc.mCbSkip = 0; |
|
399 frameDesc.mCrSkip = 0; |
|
400 |
|
401 frameDesc.mPicX = 0; |
|
402 frameDesc.mPicY = 0; |
|
403 frameDesc.mPicSize = IntSize(aWidth, aHeight); |
|
404 |
|
405 yuvImage->SetDataNoCopy(frameDesc); |
|
406 |
|
407 return buffer; |
|
408 } |
|
409 |
|
410 already_AddRefed<Image> |
|
411 MediaPluginReader::ImageBufferCallback::GetImage() |
|
412 { |
|
413 return mImage.forget(); |
|
414 } |
|
415 |
|
416 } // namespace mozilla |