Bug 830171 - Use SourceReader in async mode to better allow shutdown on MediaResource close. r=padenot

This commit is contained in:
Chris Pearce 2013-02-15 21:35:48 +13:00
parent f4778a33b6
commit 9e930cf22a
9 changed files with 328 additions and 38 deletions

View File

@ -25,6 +25,7 @@ CPPSRCS = \
WMFDecoder.cpp \
WMFReader.cpp \
WMFUtils.cpp \
WMFSourceReaderCallback.cpp \
$(NULL)
ifeq ($(OS_ARCH),WINNT)

View File

@ -10,6 +10,7 @@
#include <ole2.h>
#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<MediaResource> 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;
}

View File

@ -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<nsIThreadPool> 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<WMFSourceReaderCallback> 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

View File

@ -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<IMFAttributes> 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<IMFSample> sample;
hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,
0, // control flags
nullptr, // read stream index
&flags,
&timestampHns,
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<IMFSample> sample;
hr = mSourceReaderCallback->Wait(&flags, &timestampHns, 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<IMFMediaBuffer> 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<IMFSample> sample;
hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0, // control flags
nullptr, // read stream index
&flags,
&timestampHns,
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<IMFSample> sample;
hr = mSourceReaderCallback->Wait(&flags, &timestampHns, 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;
}

View File

@ -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<IMFSourceReader> mSourceReader;
RefPtr<WMFByteStream> mByteStream;
RefPtr<WMFSourceReaderCallback> mSourceReaderCallback;
// Region inside the video frame that makes up the picture. Pixels outside
// of this region should not be rendered.

View File

@ -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<WMFSourceReaderCallback*>(this), aInterface);
}
if (aIId == IID_IUnknown) {
return DoGetInterface(static_cast<WMFSourceReaderCallback*>(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

View File

@ -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_

View File

@ -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.

View File

@ -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