From 9e930cf22a2a9843b3aa2dc69d4556e9165fa236 Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Fri, 15 Feb 2013 21:35:48 +1300 Subject: [PATCH] Bug 830171 - Use SourceReader in async mode to better allow shutdown on MediaResource close. r=padenot --- content/media/wmf/Makefile.in | 1 + content/media/wmf/WMFByteStream.cpp | 25 ++- content/media/wmf/WMFByteStream.h | 9 +- content/media/wmf/WMFReader.cpp | 70 +++++--- content/media/wmf/WMFReader.h | 2 + content/media/wmf/WMFSourceReaderCallback.cpp | 152 ++++++++++++++++++ content/media/wmf/WMFSourceReaderCallback.h | 83 ++++++++++ content/media/wmf/WMFUtils.cpp | 10 ++ content/media/wmf/WMFUtils.h | 14 +- 9 files changed, 328 insertions(+), 38 deletions(-) create mode 100644 content/media/wmf/WMFSourceReaderCallback.cpp create mode 100644 content/media/wmf/WMFSourceReaderCallback.h diff --git a/content/media/wmf/Makefile.in b/content/media/wmf/Makefile.in index a5eccf4e9b48..b95035e6fc58 100644 --- a/content/media/wmf/Makefile.in +++ b/content/media/wmf/Makefile.in @@ -25,6 +25,7 @@ CPPSRCS = \ WMFDecoder.cpp \ WMFReader.cpp \ WMFUtils.cpp \ + WMFSourceReaderCallback.cpp \ $(NULL) ifeq ($(OS_ARCH),WINNT) diff --git a/content/media/wmf/WMFByteStream.cpp b/content/media/wmf/WMFByteStream.cpp index d83f06a269cf..bd1281a1be33 100644 --- a/content/media/wmf/WMFByteStream.cpp +++ b/content/media/wmf/WMFByteStream.cpp @@ -10,6 +10,7 @@ #include #include "WMFByteStream.h" +#include "WMFSourceReaderCallback.h" #include "WMFUtils.h" #include "MediaResource.h" #include "nsISeekableStream.h" @@ -27,16 +28,6 @@ PRLogModuleInfo* gWMFByteStreamLog = nullptr; #define LOG(...) #endif -HRESULT -DoGetInterface(IUnknown* aUnknown, void** aInterface) -{ - if (!aInterface) - return E_POINTER; - *aInterface = aUnknown; - aUnknown->AddRef(); - return S_OK; -} - // Thread pool listener which ensures that MSCOM is initialized and // deinitialized on the thread pool thread. We can call back into WMF // on this thread, so we need MSCOM working. @@ -103,8 +94,10 @@ public: nsRefPtr mResource; }; -WMFByteStream::WMFByteStream(MediaResource* aResource) - : mResourceMonitor("WMFByteStream.MediaResource"), +WMFByteStream::WMFByteStream(MediaResource* aResource, + WMFSourceReaderCallback* aSourceReaderCallback) + : mSourceReaderCallback(aSourceReaderCallback), + mResourceMonitor("WMFByteStream.MediaResource"), mResource(aResource), mReentrantMonitor("WMFByteStream.Data"), mOffset(0), @@ -112,6 +105,7 @@ WMFByteStream::WMFByteStream(MediaResource* aResource) { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); NS_ASSERTION(mResource, "Must have a valid media resource"); + NS_ASSERTION(mSourceReaderCallback, "Must have a source reader callback."); #ifdef PR_LOGGING if (!gWMFByteStreamLog) { @@ -176,8 +170,11 @@ WMFByteStream::Init() nsresult WMFByteStream::Shutdown() { - ReentrantMonitorAutoEnter mon(mReentrantMonitor); - mIsShutdown = true; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mIsShutdown = true; + } + mSourceReaderCallback->Cancel(); return NS_OK; } diff --git a/content/media/wmf/WMFByteStream.h b/content/media/wmf/WMFByteStream.h index 4e38962b5c04..c2f948e1e8d0 100644 --- a/content/media/wmf/WMFByteStream.h +++ b/content/media/wmf/WMFByteStream.h @@ -21,6 +21,7 @@ namespace mozilla { class MediaResource; class ReadRequest; +class WMFSourceReaderCallback; // Wraps a MediaResource around an IMFByteStream interface, so that it can // be used by the IMFSourceReader. Each WMFByteStream creates a WMF Work Queue @@ -37,7 +38,7 @@ class WMFByteStream MOZ_FINAL : public IMFByteStream , public IMFAttributes { public: - WMFByteStream(MediaResource* aResource); + WMFByteStream(MediaResource* aResource, WMFSourceReaderCallback* aCallback); ~WMFByteStream(); nsresult Init(); @@ -123,6 +124,12 @@ private: // Note this is pool is shared amongst all active WMFByteStreams. nsCOMPtr mThreadPool; + // Reference to the source reader's callback. We use this reference to + // notify threads waiting on a ReadSample() callback to stop waiting + // if the stream is closed, which happens when the media element is + // shutdown. + RefPtr mSourceReaderCallback; + // Monitor that ensures that multiple concurrent async reads are processed // in serial on a resource. This prevents concurrent async reads and seeks // from interleaving, to ensure that reads occur at the offset they're diff --git a/content/media/wmf/WMFReader.cpp b/content/media/wmf/WMFReader.cpp index 1f658b96092a..57eaf504bd07 100644 --- a/content/media/wmf/WMFReader.cpp +++ b/content/media/wmf/WMFReader.cpp @@ -8,6 +8,7 @@ #include "WMFDecoder.h" #include "WMFUtils.h" #include "WMFByteStream.h" +#include "WMFSourceReaderCallback.h" #ifndef MOZ_SAMPLE_TYPE_FLOAT32 #error We expect 32bit float audio samples on desktop for the Windows Media Foundation media backend. @@ -87,9 +88,10 @@ WMFReader::Init(MediaDecoderReader* aCloneDonor) return NS_ERROR_FAILURE; } - // Must be created on main thread. - mByteStream = new WMFByteStream(mDecoder->GetResource()); + mSourceReaderCallback = new WMFSourceReaderCallback(); + // Must be created on main thread. + mByteStream = new WMFByteStream(mDecoder->GetResource(), mSourceReaderCallback); return mByteStream->Init(); } @@ -441,7 +443,14 @@ WMFReader::ReadMetadata(VideoInfo* aInfo, LOG("WMFReader::ReadMetadata()"); HRESULT hr; - hr = wmf::MFCreateSourceReaderFromByteStream(mByteStream, NULL, byRef(mSourceReader)); + RefPtr attr; + hr = wmf::MFCreateAttributes(byRef(attr), 1); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, mSourceReaderCallback); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = wmf::MFCreateSourceReaderFromByteStream(mByteStream, attr, byRef(mSourceReader)); NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); hr = ConfigureVideoDecoder(); @@ -483,29 +492,41 @@ WMFReader::DecodeAudioData() { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); - DWORD flags; - LONGLONG timestampHns; HRESULT hr; - - RefPtr sample; hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, // control flags - nullptr, // read stream index - &flags, - ×tampHns, - byRef(sample)); + 0, // read stream index + nullptr, + nullptr, + nullptr); + if (FAILED(hr)) { + LOG("WMFReader::DecodeAudioData() ReadSample failed with hr=0x%x", hr); + // End the stream. + mAudioQueue.Finish(); + return false; + } + + DWORD flags = 0; + LONGLONG timestampHns = 0; + RefPtr sample; + hr = mSourceReaderCallback->Wait(&flags, ×tampHns, byRef(sample)); if (FAILED(hr) || (flags & MF_SOURCE_READERF_ERROR) || (flags & MF_SOURCE_READERF_ENDOFSTREAM) || (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)) { LOG("WMFReader::DecodeAudioData() ReadSample failed with hr=0x%x flags=0x%x", hr, flags); - // End of stream. + // End the stream. mAudioQueue.Finish(); return false; } + if (!sample) { + // Not enough data? Try again... + return true; + } + RefPtr buffer; hr = sample->ConvertToContiguousBuffer(byRef(buffer)); NS_ENSURE_TRUE(SUCCEEDED(hr), false); @@ -551,17 +572,26 @@ WMFReader::DecodeVideoFrame(bool &aKeyframeSkip, uint32_t parsed = 0, decoded = 0; AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); - DWORD flags; - LONGLONG timestampHns; HRESULT hr; - RefPtr sample; hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, // control flags - nullptr, // read stream index - &flags, - ×tampHns, - byRef(sample)); + 0, // read stream index + nullptr, + nullptr, + nullptr); + if (FAILED(hr)) { + LOG("WMFReader::DecodeVideoData() ReadSample failed with hr=0x%x", hr); + // End the stream. + mVideoQueue.Finish(); + return false; + } + + DWORD flags = 0; + LONGLONG timestampHns = 0; + RefPtr sample; + hr = mSourceReaderCallback->Wait(&flags, ×tampHns, byRef(sample)); + if (flags & MF_SOURCE_READERF_ERROR) { NS_WARNING("WMFReader: Catastrophic failure reading video sample"); // Future ReadSample() calls will fail, so give up and report end of stream. @@ -577,7 +607,7 @@ WMFReader::DecodeVideoFrame(bool &aKeyframeSkip, if (!sample) { if ((flags & MF_SOURCE_READERF_ENDOFSTREAM)) { LOG("WMFReader; Null sample after video decode, at end of stream"); - // End of stream. + // End the stream. mVideoQueue.Finish(); return false; } diff --git a/content/media/wmf/WMFReader.h b/content/media/wmf/WMFReader.h index 8c1d70e23204..4da5a685f047 100644 --- a/content/media/wmf/WMFReader.h +++ b/content/media/wmf/WMFReader.h @@ -12,6 +12,7 @@ namespace mozilla { class WMFByteStream; +class WMFSourceReaderCallback; // Decoder backend for reading H.264/AAC in MP4/M4A and MP3 audio files, // using Windows Media Foundation. @@ -53,6 +54,7 @@ private: RefPtr mSourceReader; RefPtr mByteStream; + RefPtr mSourceReaderCallback; // Region inside the video frame that makes up the picture. Pixels outside // of this region should not be rendered. diff --git a/content/media/wmf/WMFSourceReaderCallback.cpp b/content/media/wmf/WMFSourceReaderCallback.cpp new file mode 100644 index 000000000000..66ce5d7d1b6b --- /dev/null +++ b/content/media/wmf/WMFSourceReaderCallback.cpp @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WMFSourceReaderCallback.h" +#include "WMFUtils.h" + +namespace mozilla { + +#ifdef PR_LOGGING +extern PRLogModuleInfo* gMediaDecoderLog; +#define LOG(...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOG(...) +#endif + +// IUnknown Methods +STDMETHODIMP +WMFSourceReaderCallback::QueryInterface(REFIID aIId, void **aInterface) +{ + LOG("WMFSourceReaderCallback::QueryInterface %s", GetGUIDName(aIId).get()); + + if (aIId == IID_IMFSourceReaderCallback) { + return DoGetInterface(static_cast(this), aInterface); + } + if (aIId == IID_IUnknown) { + return DoGetInterface(static_cast(this), aInterface); + } + + *aInterface = NULL; + return E_NOINTERFACE; +} + +NS_IMPL_THREADSAFE_ADDREF(WMFSourceReaderCallback) +NS_IMPL_THREADSAFE_RELEASE(WMFSourceReaderCallback) + +WMFSourceReaderCallback::WMFSourceReaderCallback() + : mResultStatus(S_OK) + , mStreamFlags(0) + , mTimestamp(0) + , mSample(nullptr) + , mReadFinished(false) + , mMonitor("WMFSourceReaderCallback") +{ +} + +HRESULT +WMFSourceReaderCallback::NotifyReadComplete(HRESULT aReadStatus, + DWORD aStreamIndex, + DWORD aStreamFlags, + LONGLONG aTimestamp, + IMFSample *aSample) +{ + // Note: aSample can be NULL on success if more data is required! + ReentrantMonitorAutoEnter mon(mMonitor); + + if (mSample) { + // The WMFReader should have called Wait() to retrieve the last + // sample returned by the last ReadSample() call, but if we're + // aborting the read before Wait() is called the sample ref + // can be non-null. + mSample->Release(); + mSample = nullptr; + } + + if (SUCCEEDED(aReadStatus)) { + if (aSample) { + mTimestamp = aTimestamp; + mSample = aSample; + mSample->AddRef(); + } + } + + mResultStatus = aReadStatus; + mStreamFlags = aStreamFlags; + + // Set the sentinal value and notify the monitor, so that threads waiting + // in Wait() are awoken. + mReadFinished = true; + mon.NotifyAll(); + + return S_OK; +} + +STDMETHODIMP +WMFSourceReaderCallback::OnReadSample(HRESULT aReadStatus, + DWORD aStreamIndex, + DWORD aStreamFlags, + LONGLONG aTimestamp, + IMFSample *aSample) +{ + LOG("WMFSourceReaderCallback::OnReadSample() hr=0x%x flags=0x%x time=%lld sample=%p", + aReadStatus, aStreamFlags, aTimestamp, aSample); + return NotifyReadComplete(aReadStatus, + aStreamIndex, + aStreamFlags, + aTimestamp, + aSample); +} + +HRESULT +WMFSourceReaderCallback::Cancel() +{ + LOG("WMFSourceReaderCallback::Cancel()"); + return NotifyReadComplete(E_ABORT, + 0, + 0, + 0, + nullptr); +} + +STDMETHODIMP +WMFSourceReaderCallback::OnEvent(DWORD, IMFMediaEvent *) +{ + return S_OK; +} + +STDMETHODIMP +WMFSourceReaderCallback::OnFlush(DWORD) +{ + return S_OK; +} + +HRESULT +WMFSourceReaderCallback::Wait(DWORD* aStreamFlags, + LONGLONG* aTimeStamp, + IMFSample** aSample) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + LOG("WMFSourceReaderCallback::Wait() starting wait"); + while (!mReadFinished) { + mon.Wait(); + } + mReadFinished = false; + LOG("WMFSourceReaderCallback::Wait() done waiting"); + + *aStreamFlags = mStreamFlags; + *aTimeStamp = mTimestamp; + *aSample = mSample; + HRESULT hr = mResultStatus; + + mSample = nullptr; + mTimestamp = 0; + mStreamFlags = 0; + mResultStatus = S_OK; + + return hr; +} + +} // namespace mozilla diff --git a/content/media/wmf/WMFSourceReaderCallback.h b/content/media/wmf/WMFSourceReaderCallback.h new file mode 100644 index 000000000000..d01cd44f0f45 --- /dev/null +++ b/content/media/wmf/WMFSourceReaderCallback.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(WMFSourceReaderCallback_h_) +#define WMFSourceReaderCallback_h_ + +#include "WMF.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +// A listener which we pass into the IMFSourceReader upon creation which is +// notified when an asynchronous call to IMFSourceReader::ReadSample() +// completes. This allows us to abort ReadSample() operations when the +// WMFByteStream's underlying MediaResource is closed. This ensures that +// the decode threads don't get stuck in a synchronous ReadSample() call +// when the MediaResource is unexpectedly shutdown. +class WMFSourceReaderCallback : public IMFSourceReaderCallback +{ +public: + WMFSourceReaderCallback(); + + // IUnknown Methods. + STDMETHODIMP QueryInterface(REFIID aIId, LPVOID *aInterface); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IMFSourceReaderCallback methods + STDMETHODIMP OnReadSample(HRESULT hrStatus, + DWORD dwStreamIndex, + DWORD dwStreamFlags, + LONGLONG llTimestamp, + IMFSample *pSample); + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *); + STDMETHODIMP OnFlush(DWORD); + + // Causes the calling thread to block waiting for the + // IMFSourceReader::ReadSample() result callback to occur, or for the + // WMFByteStream to be closed. + HRESULT Wait(DWORD* aStreamFlags, + LONGLONG* aTimeStamp, + IMFSample** aSample); + + // Cancels Wait() calls. + HRESULT Cancel(); + +private: + + // Sets state to record the result of a read, and awake threads + // waiting in Wait(). + HRESULT NotifyReadComplete(HRESULT aReadStatus, + DWORD aStreamIndex, + DWORD aStreamFlags, + LONGLONG aTimestamp, + IMFSample *aSample); + + // Synchronizes all member data in this class, and Wait() blocks on + // and NotifyReadComplete() notifies this monitor. + ReentrantMonitor mMonitor; + + // Read result data. + HRESULT mResultStatus; + DWORD mStreamFlags; + LONGLONG mTimestamp; + IMFSample* mSample; + + // Sentinal. Set to true when a read result is returned. Wait() won't exit + // until this is set to true. + bool mReadFinished; + + // IUnknown ref counting. + nsAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + +}; + +} // namespace mozilla + +#endif // WMFSourceReaderCallback_h_ \ No newline at end of file diff --git a/content/media/wmf/WMFUtils.cpp b/content/media/wmf/WMFUtils.cpp index 44e14a1a9d3b..13bd8a502ffb 100644 --- a/content/media/wmf/WMFUtils.cpp +++ b/content/media/wmf/WMFUtils.cpp @@ -199,6 +199,16 @@ SourceReaderHasStream(IMFSourceReader* aReader, const DWORD aIndex) return FAILED(hr) ? false : true; } +HRESULT +DoGetInterface(IUnknown* aUnknown, void** aInterface) +{ + if (!aInterface) + return E_POINTER; + *aInterface = aUnknown; + aUnknown->AddRef(); + return S_OK; +} + namespace wmf { // Some SDK versions don't define the AAC decoder CLSID. diff --git a/content/media/wmf/WMFUtils.h b/content/media/wmf/WMFUtils.h index 372aa1f0a49a..d4097c0c1d79 100644 --- a/content/media/wmf/WMFUtils.h +++ b/content/media/wmf/WMFUtils.h @@ -11,7 +11,8 @@ namespace mozilla { -nsCString GetGUIDName(const GUID& guid); +nsCString +GetGUIDName(const GUID& guid); // Returns true if the reader has a stream with the specified index. // Index can be a specific index, or one of: @@ -45,15 +46,22 @@ private: // Converts from microseconds to hundreds of nanoseconds. // We use microseconds for our timestamps, whereas WMF uses // hundreds of nanoseconds. -inline int64_t UsecsToHNs(int64_t aUsecs) { +inline int64_t +UsecsToHNs(int64_t aUsecs) { return aUsecs * 10; } // Converts from hundreds of nanoseconds to microseconds. // We use microseconds for our timestamps, whereas WMF uses // hundreds of nanoseconds. -inline int64_t HNsToUsecs(int64_t hNanoSecs) { +inline int64_t +HNsToUsecs(int64_t hNanoSecs) { return hNanoSecs / 10; } +// Assigns aUnknown to *aInterface, and AddRef's it. +// Helper for MSCOM QueryInterface implementations. +HRESULT +DoGetInterface(IUnknown* aUnknown, void** aInterface); + } // namespace mozilla