|
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 |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "WMFReader.h" |
|
8 #include "WMFDecoder.h" |
|
9 #include "WMFUtils.h" |
|
10 #include "WMFByteStream.h" |
|
11 #include "WMFSourceReaderCallback.h" |
|
12 #include "mozilla/ArrayUtils.h" |
|
13 #include "mozilla/dom/TimeRanges.h" |
|
14 #include "mozilla/dom/HTMLMediaElement.h" |
|
15 #include "mozilla/Preferences.h" |
|
16 #include "DXVA2Manager.h" |
|
17 #include "ImageContainer.h" |
|
18 #include "Layers.h" |
|
19 #include "mozilla/layers/LayersTypes.h" |
|
20 |
|
21 #ifndef MOZ_SAMPLE_TYPE_FLOAT32 |
|
22 #error We expect 32bit float audio samples on desktop for the Windows Media Foundation media backend. |
|
23 #endif |
|
24 |
|
25 #include "MediaDecoder.h" |
|
26 #include "VideoUtils.h" |
|
27 #include "gfx2DGlue.h" |
|
28 |
|
29 using namespace mozilla::gfx; |
|
30 using mozilla::layers::Image; |
|
31 using mozilla::layers::LayerManager; |
|
32 using mozilla::layers::LayersBackend; |
|
33 |
|
34 namespace mozilla { |
|
35 |
|
36 #ifdef PR_LOGGING |
|
37 extern PRLogModuleInfo* gMediaDecoderLog; |
|
38 #define DECODER_LOG(...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, (__VA_ARGS__)) |
|
39 #else |
|
40 #define DECODER_LOG(...) |
|
41 #endif |
|
42 |
|
43 // Uncomment to enable verbose per-sample logging. |
|
44 //#define LOG_SAMPLE_DECODE 1 |
|
45 |
|
46 WMFReader::WMFReader(AbstractMediaDecoder* aDecoder) |
|
47 : MediaDecoderReader(aDecoder), |
|
48 mSourceReader(nullptr), |
|
49 mAudioChannels(0), |
|
50 mAudioBytesPerSample(0), |
|
51 mAudioRate(0), |
|
52 mVideoWidth(0), |
|
53 mVideoHeight(0), |
|
54 mVideoStride(0), |
|
55 mAudioFrameSum(0), |
|
56 mAudioFrameOffset(0), |
|
57 mHasAudio(false), |
|
58 mHasVideo(false), |
|
59 mUseHwAccel(false), |
|
60 mMustRecaptureAudioPosition(true), |
|
61 mIsMP3Enabled(WMFDecoder::IsMP3Supported()), |
|
62 mCOMInitialized(false) |
|
63 { |
|
64 NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); |
|
65 MOZ_COUNT_CTOR(WMFReader); |
|
66 } |
|
67 |
|
68 WMFReader::~WMFReader() |
|
69 { |
|
70 NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); |
|
71 |
|
72 // Note: We must shutdown the byte stream before calling MFShutdown, else we |
|
73 // get assertion failures when unlocking the byte stream's work queue. |
|
74 if (mByteStream) { |
|
75 DebugOnly<nsresult> rv = mByteStream->Shutdown(); |
|
76 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to shutdown WMFByteStream"); |
|
77 } |
|
78 DebugOnly<HRESULT> hr = wmf::MFShutdown(); |
|
79 NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed"); |
|
80 MOZ_COUNT_DTOR(WMFReader); |
|
81 } |
|
82 |
|
83 bool |
|
84 WMFReader::InitializeDXVA() |
|
85 { |
|
86 if (!Preferences::GetBool("media.windows-media-foundation.use-dxva", false)) { |
|
87 return false; |
|
88 } |
|
89 MOZ_ASSERT(mDecoder->GetImageContainer()); |
|
90 |
|
91 // Extract the layer manager backend type so that we can determine |
|
92 // whether it's worthwhile using DXVA. If we're not running with a D3D |
|
93 // layer manager then the readback of decoded video frames from GPU to |
|
94 // CPU memory grinds painting to a halt, and makes playback performance |
|
95 // *worse*. |
|
96 MediaDecoderOwner* owner = mDecoder->GetOwner(); |
|
97 NS_ENSURE_TRUE(owner, false); |
|
98 |
|
99 dom::HTMLMediaElement* element = owner->GetMediaElement(); |
|
100 NS_ENSURE_TRUE(element, false); |
|
101 |
|
102 nsRefPtr<LayerManager> layerManager = |
|
103 nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); |
|
104 NS_ENSURE_TRUE(layerManager, false); |
|
105 |
|
106 LayersBackend backend = layerManager->GetCompositorBackendType(); |
|
107 if (backend != LayersBackend::LAYERS_D3D9 && |
|
108 backend != LayersBackend::LAYERS_D3D10 && |
|
109 backend != LayersBackend::LAYERS_D3D11) { |
|
110 return false; |
|
111 } |
|
112 |
|
113 mDXVA2Manager = DXVA2Manager::Create(); |
|
114 |
|
115 return mDXVA2Manager != nullptr; |
|
116 } |
|
117 |
|
118 nsresult |
|
119 WMFReader::Init(MediaDecoderReader* aCloneDonor) |
|
120 { |
|
121 NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); |
|
122 |
|
123 nsresult rv = WMFDecoder::LoadDLLs(); |
|
124 NS_ENSURE_SUCCESS(rv, rv); |
|
125 |
|
126 if (FAILED(wmf::MFStartup())) { |
|
127 NS_WARNING("Failed to initialize Windows Media Foundation"); |
|
128 return NS_ERROR_FAILURE; |
|
129 } |
|
130 |
|
131 mSourceReaderCallback = new WMFSourceReaderCallback(); |
|
132 |
|
133 // Must be created on main thread. |
|
134 mByteStream = new WMFByteStream(mDecoder->GetResource(), mSourceReaderCallback); |
|
135 rv = mByteStream->Init(); |
|
136 NS_ENSURE_SUCCESS(rv, rv); |
|
137 |
|
138 if (mDecoder->GetImageContainer() != nullptr && |
|
139 IsVideoContentType(mDecoder->GetResource()->GetContentType())) { |
|
140 mUseHwAccel = InitializeDXVA(); |
|
141 } else { |
|
142 mUseHwAccel = false; |
|
143 } |
|
144 |
|
145 return NS_OK; |
|
146 } |
|
147 |
|
148 bool |
|
149 WMFReader::HasAudio() |
|
150 { |
|
151 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
152 return mHasAudio; |
|
153 } |
|
154 |
|
155 bool |
|
156 WMFReader::HasVideo() |
|
157 { |
|
158 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
159 return mHasVideo; |
|
160 } |
|
161 |
|
162 static HRESULT |
|
163 ConfigureSourceReaderStream(IMFSourceReader *aReader, |
|
164 const DWORD aStreamIndex, |
|
165 const GUID& aOutputSubType, |
|
166 const GUID* aAllowedInSubTypes, |
|
167 const uint32_t aNumAllowedInSubTypes) |
|
168 { |
|
169 NS_ENSURE_TRUE(aReader, E_POINTER); |
|
170 NS_ENSURE_TRUE(aAllowedInSubTypes, E_POINTER); |
|
171 |
|
172 RefPtr<IMFMediaType> nativeType; |
|
173 RefPtr<IMFMediaType> type; |
|
174 HRESULT hr; |
|
175 |
|
176 // Find the native format of the stream. |
|
177 hr = aReader->GetNativeMediaType(aStreamIndex, 0, byRef(nativeType)); |
|
178 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
179 |
|
180 // Get the native output subtype of the stream. This denotes the uncompressed |
|
181 // type. |
|
182 GUID subType; |
|
183 hr = nativeType->GetGUID(MF_MT_SUBTYPE, &subType); |
|
184 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
185 |
|
186 // Ensure the input type of the media is in the allowed formats list. |
|
187 bool isSubTypeAllowed = false; |
|
188 for (uint32_t i = 0; i < aNumAllowedInSubTypes; i++) { |
|
189 if (aAllowedInSubTypes[i] == subType) { |
|
190 isSubTypeAllowed = true; |
|
191 break; |
|
192 } |
|
193 } |
|
194 if (!isSubTypeAllowed) { |
|
195 nsCString name = GetGUIDName(subType); |
|
196 DECODER_LOG("ConfigureSourceReaderStream subType=%s is not allowed to be decoded", name.get()); |
|
197 return E_FAIL; |
|
198 } |
|
199 |
|
200 // Find the major type. |
|
201 GUID majorType; |
|
202 hr = nativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType); |
|
203 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
204 |
|
205 // Define the output type. |
|
206 hr = wmf::MFCreateMediaType(byRef(type)); |
|
207 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
208 |
|
209 hr = type->SetGUID(MF_MT_MAJOR_TYPE, majorType); |
|
210 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
211 |
|
212 hr = type->SetGUID(MF_MT_SUBTYPE, aOutputSubType); |
|
213 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
214 |
|
215 // Set the uncompressed format. This can fail if the decoder can't produce |
|
216 // that type. |
|
217 return aReader->SetCurrentMediaType(aStreamIndex, nullptr, type); |
|
218 } |
|
219 |
|
220 // Returns the duration of the resource, in microseconds. |
|
221 HRESULT |
|
222 GetSourceReaderDuration(IMFSourceReader *aReader, |
|
223 int64_t& aOutDuration) |
|
224 { |
|
225 AutoPropVar var; |
|
226 HRESULT hr = aReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, |
|
227 MF_PD_DURATION, |
|
228 &var); |
|
229 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
230 |
|
231 // WMF stores duration in hundred nanosecond units. |
|
232 int64_t duration_hns = 0; |
|
233 hr = wmf::PropVariantToInt64(var, &duration_hns); |
|
234 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
235 |
|
236 aOutDuration = HNsToUsecs(duration_hns); |
|
237 |
|
238 return S_OK; |
|
239 } |
|
240 |
|
241 HRESULT |
|
242 GetSourceReaderCanSeek(IMFSourceReader* aReader, bool& aOutCanSeek) |
|
243 { |
|
244 NS_ENSURE_TRUE(aReader, E_FAIL); |
|
245 |
|
246 HRESULT hr; |
|
247 AutoPropVar var; |
|
248 hr = aReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, |
|
249 MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, |
|
250 &var); |
|
251 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
252 |
|
253 ULONG flags = 0; |
|
254 hr = wmf::PropVariantToUInt32(var, &flags); |
|
255 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
256 |
|
257 aOutCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK); |
|
258 |
|
259 return S_OK; |
|
260 } |
|
261 |
|
262 HRESULT |
|
263 WMFReader::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType) |
|
264 { |
|
265 NS_ENSURE_TRUE(aMediaType != nullptr, E_POINTER); |
|
266 HRESULT hr; |
|
267 |
|
268 // Verify that the video subtype is what we expect it to be. |
|
269 // When using hardware acceleration/DXVA2 the video format should |
|
270 // be NV12, which is DXVA2's preferred format. For software decoding |
|
271 // we use YV12, as that's easier for us to stick into our rendering |
|
272 // pipeline than NV12. NV12 has interleaved UV samples, whereas YV12 |
|
273 // is a planar format. |
|
274 GUID videoFormat; |
|
275 hr = aMediaType->GetGUID(MF_MT_SUBTYPE, &videoFormat); |
|
276 NS_ENSURE_TRUE(videoFormat == MFVideoFormat_NV12 || !mUseHwAccel, E_FAIL); |
|
277 NS_ENSURE_TRUE(videoFormat == MFVideoFormat_YV12 || mUseHwAccel, E_FAIL); |
|
278 |
|
279 nsIntRect pictureRegion; |
|
280 hr = GetPictureRegion(aMediaType, pictureRegion); |
|
281 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
282 |
|
283 UINT32 width = 0, height = 0; |
|
284 hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height); |
|
285 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
286 |
|
287 uint32_t aspectNum = 0, aspectDenom = 0; |
|
288 hr = MFGetAttributeRatio(aMediaType, |
|
289 MF_MT_PIXEL_ASPECT_RATIO, |
|
290 &aspectNum, |
|
291 &aspectDenom); |
|
292 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
293 |
|
294 // Calculate and validate the picture region and frame dimensions after |
|
295 // scaling by the pixel aspect ratio. |
|
296 nsIntSize frameSize = nsIntSize(width, height); |
|
297 nsIntSize displaySize = nsIntSize(pictureRegion.width, pictureRegion.height); |
|
298 ScaleDisplayByAspectRatio(displaySize, float(aspectNum) / float(aspectDenom)); |
|
299 if (!IsValidVideoRegion(frameSize, pictureRegion, displaySize)) { |
|
300 // Video track's frame sizes will overflow. Ignore the video track. |
|
301 return E_FAIL; |
|
302 } |
|
303 |
|
304 // Success! Save state. |
|
305 mInfo.mVideo.mDisplay = displaySize; |
|
306 GetDefaultStride(aMediaType, &mVideoStride); |
|
307 mVideoWidth = width; |
|
308 mVideoHeight = height; |
|
309 mPictureRegion = pictureRegion; |
|
310 |
|
311 DECODER_LOG("WMFReader frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d) display=(%d,%d) PAR=%d:%d", |
|
312 width, height, |
|
313 mVideoStride, |
|
314 mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height, |
|
315 displaySize.width, displaySize.height, |
|
316 aspectNum, aspectDenom); |
|
317 |
|
318 return S_OK; |
|
319 } |
|
320 |
|
321 HRESULT |
|
322 WMFReader::ConfigureVideoDecoder() |
|
323 { |
|
324 NS_ASSERTION(mSourceReader, "Must have a SourceReader before configuring decoders!"); |
|
325 |
|
326 // Determine if we have video. |
|
327 if (!mSourceReader || |
|
328 !SourceReaderHasStream(mSourceReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM)) { |
|
329 // No stream, no error. |
|
330 return S_OK; |
|
331 } |
|
332 |
|
333 if (!mDecoder->GetImageContainer()) { |
|
334 // We can't display the video, so don't bother to decode; disable the stream. |
|
335 return mSourceReader->SetStreamSelection(MF_SOURCE_READER_FIRST_VIDEO_STREAM, FALSE); |
|
336 } |
|
337 |
|
338 static const GUID MP4VideoTypes[] = { |
|
339 MFVideoFormat_H264 |
|
340 }; |
|
341 HRESULT hr = ConfigureSourceReaderStream(mSourceReader, |
|
342 MF_SOURCE_READER_FIRST_VIDEO_STREAM, |
|
343 mUseHwAccel ? MFVideoFormat_NV12 : MFVideoFormat_YV12, |
|
344 MP4VideoTypes, |
|
345 ArrayLength(MP4VideoTypes)); |
|
346 if (FAILED(hr)) { |
|
347 DECODER_LOG("Failed to configured video output"); |
|
348 return hr; |
|
349 } |
|
350 |
|
351 RefPtr<IMFMediaType> mediaType; |
|
352 hr = mSourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, |
|
353 byRef(mediaType)); |
|
354 if (FAILED(hr)) { |
|
355 NS_WARNING("Failed to get configured video media type"); |
|
356 return hr; |
|
357 } |
|
358 |
|
359 if (FAILED(ConfigureVideoFrameGeometry(mediaType))) { |
|
360 NS_WARNING("Failed configured video frame dimensions"); |
|
361 return hr; |
|
362 } |
|
363 |
|
364 DECODER_LOG("Successfully configured video stream"); |
|
365 |
|
366 mHasVideo = mInfo.mVideo.mHasVideo = true; |
|
367 |
|
368 return S_OK; |
|
369 } |
|
370 |
|
371 void |
|
372 WMFReader::GetSupportedAudioCodecs(const GUID** aCodecs, uint32_t* aNumCodecs) |
|
373 { |
|
374 MOZ_ASSERT(aCodecs); |
|
375 MOZ_ASSERT(aNumCodecs); |
|
376 |
|
377 if (mIsMP3Enabled) { |
|
378 GUID aacOrMp3 = MFMPEG4Format_Base; |
|
379 aacOrMp3.Data1 = 0x6D703461;// FOURCC('m','p','4','a'); |
|
380 static const GUID codecs[] = { |
|
381 MFAudioFormat_AAC, |
|
382 MFAudioFormat_MP3, |
|
383 aacOrMp3 |
|
384 }; |
|
385 *aCodecs = codecs; |
|
386 *aNumCodecs = ArrayLength(codecs); |
|
387 } else { |
|
388 static const GUID codecs[] = { |
|
389 MFAudioFormat_AAC |
|
390 }; |
|
391 *aCodecs = codecs; |
|
392 *aNumCodecs = ArrayLength(codecs); |
|
393 } |
|
394 } |
|
395 |
|
396 HRESULT |
|
397 WMFReader::ConfigureAudioDecoder() |
|
398 { |
|
399 NS_ASSERTION(mSourceReader, "Must have a SourceReader before configuring decoders!"); |
|
400 |
|
401 if (!mSourceReader || |
|
402 !SourceReaderHasStream(mSourceReader, MF_SOURCE_READER_FIRST_AUDIO_STREAM)) { |
|
403 // No stream, no error. |
|
404 return S_OK; |
|
405 } |
|
406 |
|
407 const GUID* codecs; |
|
408 uint32_t numCodecs = 0; |
|
409 GetSupportedAudioCodecs(&codecs, &numCodecs); |
|
410 |
|
411 HRESULT hr = ConfigureSourceReaderStream(mSourceReader, |
|
412 MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
|
413 MFAudioFormat_Float, |
|
414 codecs, |
|
415 numCodecs); |
|
416 if (FAILED(hr)) { |
|
417 NS_WARNING("Failed to configure WMF Audio decoder for PCM output"); |
|
418 return hr; |
|
419 } |
|
420 |
|
421 RefPtr<IMFMediaType> mediaType; |
|
422 hr = mSourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
|
423 byRef(mediaType)); |
|
424 if (FAILED(hr)) { |
|
425 NS_WARNING("Failed to get configured audio media type"); |
|
426 return hr; |
|
427 } |
|
428 |
|
429 mAudioRate = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_SAMPLES_PER_SECOND, 0); |
|
430 mAudioChannels = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_NUM_CHANNELS, 0); |
|
431 mAudioBytesPerSample = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_BITS_PER_SAMPLE, 16) / 8; |
|
432 |
|
433 mInfo.mAudio.mChannels = mAudioChannels; |
|
434 mInfo.mAudio.mRate = mAudioRate; |
|
435 mHasAudio = mInfo.mAudio.mHasAudio = true; |
|
436 |
|
437 DECODER_LOG("Successfully configured audio stream. rate=%u channels=%u bitsPerSample=%u", |
|
438 mAudioRate, mAudioChannels, mAudioBytesPerSample); |
|
439 |
|
440 return S_OK; |
|
441 } |
|
442 |
|
443 HRESULT |
|
444 WMFReader::CreateSourceReader() |
|
445 { |
|
446 HRESULT hr; |
|
447 |
|
448 RefPtr<IMFAttributes> attr; |
|
449 hr = wmf::MFCreateAttributes(byRef(attr), 1); |
|
450 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
451 |
|
452 hr = attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, mSourceReaderCallback); |
|
453 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
454 |
|
455 if (mUseHwAccel) { |
|
456 hr = attr->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, |
|
457 mDXVA2Manager->GetDXVADeviceManager()); |
|
458 if (FAILED(hr)) { |
|
459 DECODER_LOG("Failed to set DXVA2 D3D Device manager on source reader attributes"); |
|
460 mUseHwAccel = false; |
|
461 } |
|
462 } |
|
463 |
|
464 hr = wmf::MFCreateSourceReaderFromByteStream(mByteStream, attr, byRef(mSourceReader)); |
|
465 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
466 |
|
467 hr = ConfigureVideoDecoder(); |
|
468 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
469 |
|
470 hr = ConfigureAudioDecoder(); |
|
471 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
472 |
|
473 if (mUseHwAccel && mInfo.mVideo.mHasVideo) { |
|
474 RefPtr<IMFTransform> videoDecoder; |
|
475 hr = mSourceReader->GetServiceForStream(MF_SOURCE_READER_FIRST_VIDEO_STREAM, |
|
476 GUID_NULL, |
|
477 IID_IMFTransform, |
|
478 (void**)(IMFTransform**)(byRef(videoDecoder))); |
|
479 |
|
480 if (SUCCEEDED(hr)) { |
|
481 ULONG_PTR manager = ULONG_PTR(mDXVA2Manager->GetDXVADeviceManager()); |
|
482 hr = videoDecoder->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, |
|
483 manager); |
|
484 if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) { |
|
485 // Ignore MF_E_TRANSFORM_TYPE_NOT_SET. Vista returns this here |
|
486 // on some, perhaps all, video cards. This may be because activating |
|
487 // DXVA changes the available output types. It seems to be safe to |
|
488 // ignore this error. |
|
489 hr = S_OK; |
|
490 } |
|
491 } |
|
492 if (FAILED(hr)) { |
|
493 DECODER_LOG("Failed to set DXVA2 D3D Device manager on decoder hr=0x%x", hr); |
|
494 mUseHwAccel = false; |
|
495 } |
|
496 } |
|
497 return hr; |
|
498 } |
|
499 |
|
500 nsresult |
|
501 WMFReader::ReadMetadata(MediaInfo* aInfo, |
|
502 MetadataTags** aTags) |
|
503 { |
|
504 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
505 |
|
506 DECODER_LOG("WMFReader::ReadMetadata()"); |
|
507 HRESULT hr; |
|
508 |
|
509 const bool triedToInitDXVA = mUseHwAccel; |
|
510 if (FAILED(CreateSourceReader())) { |
|
511 mSourceReader = nullptr; |
|
512 if (triedToInitDXVA && !mUseHwAccel) { |
|
513 // We tried to initialize DXVA and failed. Try again to create the |
|
514 // IMFSourceReader but this time we won't use DXVA. Note that we |
|
515 // must recreate the IMFSourceReader from scratch, as on some systems |
|
516 // (AMD Radeon 3000) we cannot successfully reconfigure an existing |
|
517 // reader to not use DXVA after we've failed to configure DXVA. |
|
518 // See bug 987127. |
|
519 if (FAILED(CreateSourceReader())) { |
|
520 mSourceReader = nullptr; |
|
521 } |
|
522 } |
|
523 } |
|
524 |
|
525 if (!mSourceReader) { |
|
526 NS_WARNING("Failed to create IMFSourceReader"); |
|
527 return NS_ERROR_FAILURE; |
|
528 } |
|
529 |
|
530 if (mInfo.HasVideo()) { |
|
531 DECODER_LOG("Using DXVA: %s", (mUseHwAccel ? "Yes" : "No")); |
|
532 } |
|
533 |
|
534 // Abort if both video and audio failed to initialize. |
|
535 NS_ENSURE_TRUE(mInfo.HasValidMedia(), NS_ERROR_FAILURE); |
|
536 |
|
537 // Get the duration, and report it to the decoder if we have it. |
|
538 int64_t duration = 0; |
|
539 hr = GetSourceReaderDuration(mSourceReader, duration); |
|
540 if (SUCCEEDED(hr)) { |
|
541 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
542 mDecoder->SetMediaEndTime(duration); |
|
543 } |
|
544 // We can seek if we get a duration *and* the reader reports that it's |
|
545 // seekable. |
|
546 bool canSeek = false; |
|
547 if (FAILED(hr) || |
|
548 FAILED(GetSourceReaderCanSeek(mSourceReader, canSeek)) || |
|
549 !canSeek) { |
|
550 mDecoder->SetMediaSeekable(false); |
|
551 } |
|
552 |
|
553 *aInfo = mInfo; |
|
554 *aTags = nullptr; |
|
555 // aTags can be retrieved using techniques like used here: |
|
556 // http://blogs.msdn.com/b/mf/archive/2010/01/12/mfmediapropdump.aspx |
|
557 |
|
558 return NS_OK; |
|
559 } |
|
560 |
|
561 bool |
|
562 WMFReader::DecodeAudioData() |
|
563 { |
|
564 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
565 |
|
566 HRESULT hr; |
|
567 hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
|
568 0, // control flags |
|
569 0, // read stream index |
|
570 nullptr, |
|
571 nullptr, |
|
572 nullptr); |
|
573 |
|
574 if (FAILED(hr)) { |
|
575 DECODER_LOG("WMFReader::DecodeAudioData() ReadSample failed with hr=0x%x", hr); |
|
576 // End the stream. |
|
577 return false; |
|
578 } |
|
579 |
|
580 DWORD flags = 0; |
|
581 LONGLONG timestampHns = 0; |
|
582 RefPtr<IMFSample> sample; |
|
583 hr = mSourceReaderCallback->Wait(&flags, ×tampHns, byRef(sample)); |
|
584 if (FAILED(hr) || |
|
585 (flags & MF_SOURCE_READERF_ERROR) || |
|
586 (flags & MF_SOURCE_READERF_ENDOFSTREAM) || |
|
587 (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)) { |
|
588 DECODER_LOG("WMFReader::DecodeAudioData() ReadSample failed with hr=0x%x flags=0x%x", |
|
589 hr, flags); |
|
590 // End the stream. |
|
591 return false; |
|
592 } |
|
593 |
|
594 if (!sample) { |
|
595 // Not enough data? Try again... |
|
596 return true; |
|
597 } |
|
598 |
|
599 RefPtr<IMFMediaBuffer> buffer; |
|
600 hr = sample->ConvertToContiguousBuffer(byRef(buffer)); |
|
601 NS_ENSURE_TRUE(SUCCEEDED(hr), false); |
|
602 |
|
603 BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it. |
|
604 DWORD maxLength = 0, currentLength = 0; |
|
605 hr = buffer->Lock(&data, &maxLength, ¤tLength); |
|
606 NS_ENSURE_TRUE(SUCCEEDED(hr), false); |
|
607 |
|
608 uint32_t numFrames = currentLength / mAudioBytesPerSample / mAudioChannels; |
|
609 NS_ASSERTION(sizeof(AudioDataValue) == mAudioBytesPerSample, "Size calculation is wrong"); |
|
610 nsAutoArrayPtr<AudioDataValue> pcmSamples(new AudioDataValue[numFrames * mAudioChannels]); |
|
611 memcpy(pcmSamples.get(), data, currentLength); |
|
612 buffer->Unlock(); |
|
613 |
|
614 // We calculate the timestamp and the duration based on the number of audio |
|
615 // frames we've already played. We don't trust the timestamp stored on the |
|
616 // IMFSample, as sometimes it's wrong, possibly due to buggy encoders? |
|
617 |
|
618 // If this sample block comes after a discontinuity (i.e. a gap or seek) |
|
619 // reset the frame counters, and capture the timestamp. Future timestamps |
|
620 // will be offset from this block's timestamp. |
|
621 UINT32 discontinuity = false; |
|
622 sample->GetUINT32(MFSampleExtension_Discontinuity, &discontinuity); |
|
623 if (mMustRecaptureAudioPosition || discontinuity) { |
|
624 mAudioFrameSum = 0; |
|
625 hr = HNsToFrames(timestampHns, mAudioRate, &mAudioFrameOffset); |
|
626 NS_ENSURE_TRUE(SUCCEEDED(hr), false); |
|
627 mMustRecaptureAudioPosition = false; |
|
628 } |
|
629 |
|
630 int64_t timestamp; |
|
631 hr = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, mAudioRate, ×tamp); |
|
632 NS_ENSURE_TRUE(SUCCEEDED(hr), false); |
|
633 |
|
634 mAudioFrameSum += numFrames; |
|
635 |
|
636 int64_t duration; |
|
637 hr = FramesToUsecs(numFrames, mAudioRate, &duration); |
|
638 NS_ENSURE_TRUE(SUCCEEDED(hr), false); |
|
639 |
|
640 mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(), |
|
641 timestamp, |
|
642 duration, |
|
643 numFrames, |
|
644 pcmSamples.forget(), |
|
645 mAudioChannels)); |
|
646 |
|
647 #ifdef LOG_SAMPLE_DECODE |
|
648 DECODER_LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", |
|
649 timestamp, duration, currentLength); |
|
650 #endif |
|
651 |
|
652 return true; |
|
653 } |
|
654 |
|
655 HRESULT |
|
656 WMFReader::CreateBasicVideoFrame(IMFSample* aSample, |
|
657 int64_t aTimestampUsecs, |
|
658 int64_t aDurationUsecs, |
|
659 int64_t aOffsetBytes, |
|
660 VideoData** aOutVideoData) |
|
661 { |
|
662 NS_ENSURE_TRUE(aSample, E_POINTER); |
|
663 NS_ENSURE_TRUE(aOutVideoData, E_POINTER); |
|
664 |
|
665 *aOutVideoData = nullptr; |
|
666 |
|
667 HRESULT hr; |
|
668 RefPtr<IMFMediaBuffer> buffer; |
|
669 |
|
670 // Must convert to contiguous buffer to use IMD2DBuffer interface. |
|
671 hr = aSample->ConvertToContiguousBuffer(byRef(buffer)); |
|
672 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
673 |
|
674 // Try and use the IMF2DBuffer interface if available, otherwise fallback |
|
675 // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient, |
|
676 // but only some systems (Windows 8?) support it. |
|
677 BYTE* data = nullptr; |
|
678 LONG stride = 0; |
|
679 RefPtr<IMF2DBuffer> twoDBuffer; |
|
680 hr = buffer->QueryInterface(static_cast<IMF2DBuffer**>(byRef(twoDBuffer))); |
|
681 if (SUCCEEDED(hr)) { |
|
682 hr = twoDBuffer->Lock2D(&data, &stride); |
|
683 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
684 } else { |
|
685 hr = buffer->Lock(&data, nullptr, nullptr); |
|
686 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
687 stride = mVideoStride; |
|
688 } |
|
689 |
|
690 // YV12, planar format: [YYYY....][VVVV....][UUUU....] |
|
691 // i.e., Y, then V, then U. |
|
692 VideoData::YCbCrBuffer b; |
|
693 |
|
694 // Y (Y') plane |
|
695 b.mPlanes[0].mData = data; |
|
696 b.mPlanes[0].mStride = stride; |
|
697 b.mPlanes[0].mHeight = mVideoHeight; |
|
698 b.mPlanes[0].mWidth = mVideoWidth; |
|
699 b.mPlanes[0].mOffset = 0; |
|
700 b.mPlanes[0].mSkip = 0; |
|
701 |
|
702 // The V and U planes are stored 16-row-aligned, so we need to add padding |
|
703 // to the row heights to ensure the Y'CbCr planes are referenced properly. |
|
704 uint32_t padding = 0; |
|
705 if (mVideoHeight % 16 != 0) { |
|
706 padding = 16 - (mVideoHeight % 16); |
|
707 } |
|
708 uint32_t y_size = stride * (mVideoHeight + padding); |
|
709 uint32_t v_size = stride * (mVideoHeight + padding) / 4; |
|
710 uint32_t halfStride = (stride + 1) / 2; |
|
711 uint32_t halfHeight = (mVideoHeight + 1) / 2; |
|
712 uint32_t halfWidth = (mVideoWidth + 1) / 2; |
|
713 |
|
714 // U plane (Cb) |
|
715 b.mPlanes[1].mData = data + y_size + v_size; |
|
716 b.mPlanes[1].mStride = halfStride; |
|
717 b.mPlanes[1].mHeight = halfHeight; |
|
718 b.mPlanes[1].mWidth = halfWidth; |
|
719 b.mPlanes[1].mOffset = 0; |
|
720 b.mPlanes[1].mSkip = 0; |
|
721 |
|
722 // V plane (Cr) |
|
723 b.mPlanes[2].mData = data + y_size; |
|
724 b.mPlanes[2].mStride = halfStride; |
|
725 b.mPlanes[2].mHeight = halfHeight; |
|
726 b.mPlanes[2].mWidth = halfWidth; |
|
727 b.mPlanes[2].mOffset = 0; |
|
728 b.mPlanes[2].mSkip = 0; |
|
729 |
|
730 VideoData *v = VideoData::Create(mInfo.mVideo, |
|
731 mDecoder->GetImageContainer(), |
|
732 aOffsetBytes, |
|
733 aTimestampUsecs, |
|
734 aDurationUsecs, |
|
735 b, |
|
736 false, |
|
737 -1, |
|
738 ToIntRect(mPictureRegion)); |
|
739 if (twoDBuffer) { |
|
740 twoDBuffer->Unlock2D(); |
|
741 } else { |
|
742 buffer->Unlock(); |
|
743 } |
|
744 |
|
745 *aOutVideoData = v; |
|
746 |
|
747 return S_OK; |
|
748 } |
|
749 |
|
750 HRESULT |
|
751 WMFReader::CreateD3DVideoFrame(IMFSample* aSample, |
|
752 int64_t aTimestampUsecs, |
|
753 int64_t aDurationUsecs, |
|
754 int64_t aOffsetBytes, |
|
755 VideoData** aOutVideoData) |
|
756 { |
|
757 NS_ENSURE_TRUE(aSample, E_POINTER); |
|
758 NS_ENSURE_TRUE(aOutVideoData, E_POINTER); |
|
759 NS_ENSURE_TRUE(mDXVA2Manager, E_ABORT); |
|
760 NS_ENSURE_TRUE(mUseHwAccel, E_ABORT); |
|
761 |
|
762 *aOutVideoData = nullptr; |
|
763 HRESULT hr; |
|
764 |
|
765 nsRefPtr<Image> image; |
|
766 hr = mDXVA2Manager->CopyToImage(aSample, |
|
767 mPictureRegion, |
|
768 mDecoder->GetImageContainer(), |
|
769 getter_AddRefs(image)); |
|
770 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); |
|
771 NS_ENSURE_TRUE(image, E_FAIL); |
|
772 |
|
773 VideoData *v = VideoData::CreateFromImage(mInfo.mVideo, |
|
774 mDecoder->GetImageContainer(), |
|
775 aOffsetBytes, |
|
776 aTimestampUsecs, |
|
777 aDurationUsecs, |
|
778 image.forget(), |
|
779 false, |
|
780 -1, |
|
781 ToIntRect(mPictureRegion)); |
|
782 |
|
783 NS_ENSURE_TRUE(v, E_FAIL); |
|
784 *aOutVideoData = v; |
|
785 |
|
786 return S_OK; |
|
787 } |
|
788 |
|
789 bool |
|
790 WMFReader::DecodeVideoFrame(bool &aKeyframeSkip, |
|
791 int64_t aTimeThreshold) |
|
792 { |
|
793 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
794 |
|
795 // Record number of frames decoded and parsed. Automatically update the |
|
796 // stats counters using the AutoNotifyDecoded stack-based class. |
|
797 uint32_t parsed = 0, decoded = 0; |
|
798 AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); |
|
799 |
|
800 HRESULT hr; |
|
801 |
|
802 hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, |
|
803 0, // control flags |
|
804 0, // read stream index |
|
805 nullptr, |
|
806 nullptr, |
|
807 nullptr); |
|
808 if (FAILED(hr)) { |
|
809 DECODER_LOG("WMFReader::DecodeVideoData() ReadSample failed with hr=0x%x", hr); |
|
810 return false; |
|
811 } |
|
812 |
|
813 DWORD flags = 0; |
|
814 LONGLONG timestampHns = 0; |
|
815 RefPtr<IMFSample> sample; |
|
816 hr = mSourceReaderCallback->Wait(&flags, ×tampHns, byRef(sample)); |
|
817 |
|
818 if (flags & MF_SOURCE_READERF_ERROR) { |
|
819 NS_WARNING("WMFReader: Catastrophic failure reading video sample"); |
|
820 // Future ReadSample() calls will fail, so give up and report end of stream. |
|
821 return false; |
|
822 } |
|
823 |
|
824 if (FAILED(hr)) { |
|
825 // Unknown failure, ask caller to try again? |
|
826 return true; |
|
827 } |
|
828 |
|
829 if (!sample) { |
|
830 if ((flags & MF_SOURCE_READERF_ENDOFSTREAM)) { |
|
831 DECODER_LOG("WMFReader; Null sample after video decode, at end of stream"); |
|
832 return false; |
|
833 } |
|
834 DECODER_LOG("WMFReader; Null sample after video decode. Maybe insufficient data..."); |
|
835 return true; |
|
836 } |
|
837 |
|
838 if ((flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)) { |
|
839 DECODER_LOG("WMFReader: Video media type changed!"); |
|
840 RefPtr<IMFMediaType> mediaType; |
|
841 hr = mSourceReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, |
|
842 byRef(mediaType)); |
|
843 if (FAILED(hr) || |
|
844 FAILED(ConfigureVideoFrameGeometry(mediaType))) { |
|
845 NS_WARNING("Failed to reconfigure video media type"); |
|
846 return false; |
|
847 } |
|
848 } |
|
849 |
|
850 int64_t timestamp = HNsToUsecs(timestampHns); |
|
851 if (timestamp < aTimeThreshold) { |
|
852 return true; |
|
853 } |
|
854 int64_t offset = mDecoder->GetResource()->Tell(); |
|
855 int64_t duration = GetSampleDuration(sample); |
|
856 |
|
857 VideoData* v = nullptr; |
|
858 if (mUseHwAccel) { |
|
859 hr = CreateD3DVideoFrame(sample, timestamp, duration, offset, &v); |
|
860 } else { |
|
861 hr = CreateBasicVideoFrame(sample, timestamp, duration, offset, &v); |
|
862 } |
|
863 NS_ENSURE_TRUE(SUCCEEDED(hr) && v, false); |
|
864 |
|
865 parsed++; |
|
866 decoded++; |
|
867 mVideoQueue.Push(v); |
|
868 |
|
869 #ifdef LOG_SAMPLE_DECODE |
|
870 DECODER_LOG("Decoded video sample timestamp=%lld duration=%lld stride=%d height=%u flags=%u", |
|
871 timestamp, duration, mVideoStride, mVideoHeight, flags); |
|
872 #endif |
|
873 |
|
874 if ((flags & MF_SOURCE_READERF_ENDOFSTREAM)) { |
|
875 // End of stream. |
|
876 DECODER_LOG("End of video stream"); |
|
877 return false; |
|
878 } |
|
879 |
|
880 return true; |
|
881 } |
|
882 |
|
883 nsresult |
|
884 WMFReader::Seek(int64_t aTargetUs, |
|
885 int64_t aStartTime, |
|
886 int64_t aEndTime, |
|
887 int64_t aCurrentTime) |
|
888 { |
|
889 DECODER_LOG("WMFReader::Seek() %lld", aTargetUs); |
|
890 |
|
891 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
|
892 #ifdef DEBUG |
|
893 bool canSeek = false; |
|
894 GetSourceReaderCanSeek(mSourceReader, canSeek); |
|
895 NS_ASSERTION(canSeek, "WMFReader::Seek() should only be called if we can seek!"); |
|
896 #endif |
|
897 |
|
898 nsresult rv = ResetDecode(); |
|
899 NS_ENSURE_SUCCESS(rv, rv); |
|
900 |
|
901 // Mark that we must recapture the audio frame count from the next sample. |
|
902 // WMF doesn't set a discontinuity marker when we seek to time 0, so we |
|
903 // must remember to recapture the audio frame offset and reset the frame |
|
904 // sum on the next audio packet we decode. |
|
905 mMustRecaptureAudioPosition = true; |
|
906 |
|
907 AutoPropVar var; |
|
908 HRESULT hr = InitPropVariantFromInt64(UsecsToHNs(aTargetUs), &var); |
|
909 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); |
|
910 |
|
911 hr = mSourceReader->SetCurrentPosition(GUID_NULL, var); |
|
912 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); |
|
913 |
|
914 return NS_OK; |
|
915 } |
|
916 |
|
917 } // namespace mozilla |