Bug 836927 - Make WMFByteStream bug compatible with WMF's IMFByteStream implementation. r=padenot

This commit is contained in:
Chris Pearce 2013-02-05 10:34:23 +13:00
parent e12b195a7a
commit 35b36ead15
6 changed files with 172 additions and 95 deletions

View File

@ -132,6 +132,8 @@ public:
return NS_OK;
}
bool IsTransportSeekable() MOZ_OVERRIDE { return true; }
private:
const uint8_t * mBuffer;
uint32_t mLength;

View File

@ -946,7 +946,7 @@ public:
nsCOMPtr<MediaDecoderStateMachine> mDecoderStateMachine;
// Media data resource.
nsAutoPtr<MediaResource> mResource;
nsRefPtr<MediaResource> mResource;
// |ReentrantMonitor| for detecting when the video play state changes. A call
// to |Wait| on this monitor will block the thread until the next state

View File

@ -61,7 +61,8 @@ ChannelMediaResource::ChannelMediaResource(MediaDecoder* aDecoder,
mByteRangeDownloads(false),
mByteRangeFirstOpen(true),
mSeekOffsetMonitor("media.dashseekmonitor"),
mSeekOffset(-1)
mSeekOffset(-1),
mIsTransportSeekable(true)
{
#ifdef PR_LOGGING
if (!gMediaResourceLog) {
@ -308,6 +309,7 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
{
MutexAutoLock lock(mLock);
mIsTransportSeekable = seekable;
mChannelStatistics->Start();
}
@ -333,6 +335,13 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
return NS_OK;
}
bool
ChannelMediaResource::IsTransportSeekable()
{
MutexAutoLock lock(mLock);
return mIsTransportSeekable;
}
nsresult
ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
int64_t& aRangeStart,
@ -1301,6 +1310,7 @@ public:
return false;
}
virtual bool IsSuspended() { return false; }
virtual bool IsTransportSeekable() MOZ_OVERRIDE { return true; }
nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges);

View File

@ -193,6 +193,9 @@ inline MediaByteRange::MediaByteRange(TimestampedMediaByteRange& aByteRange)
class MediaResource
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaResource)
virtual ~MediaResource() {}
// The following can be called on the main thread only:
@ -323,6 +326,10 @@ public:
virtual nsresult ReadFromCache(char* aBuffer,
int64_t aOffset,
uint32_t aCount) = 0;
// Returns true if the resource can be seeked to unbuffered ranges, i.e.
// for an HTTP network stream this returns true if HTTP1.1 Byte Range
// requests are supported by the connection/server.
virtual bool IsTransportSeekable() = 0;
/**
* Create a resource, reading data from the channel. Call on main thread only.
@ -494,6 +501,7 @@ public:
virtual bool IsDataCachedToEndOfResource(int64_t aOffset);
virtual bool IsSuspendedByCache(MediaResource** aActiveResource);
virtual bool IsSuspended();
virtual bool IsTransportSeekable() MOZ_OVERRIDE;
class Listener MOZ_FINAL : public nsIStreamListener,
public nsIInterfaceRequestor,
@ -599,6 +607,10 @@ protected:
// Set to false once first byte range request has been made.
bool mByteRangeFirstOpen;
// True if the stream can seek into unbuffered ranged, i.e. if the
// connection supports byte range requests.
bool mIsTransportSeekable;
// For byte range requests, set to the offset requested in |Seek|.
// Used in |CacheClientSeek| to find the originally requested byte range.
// Read/Write on multiple threads; use |mSeekMonitor|.

View File

@ -72,10 +72,18 @@ static nsIThreadPool* sThreadPool = nullptr;
// the thread pool. This is read/write on the main thread only.
static int32_t sThreadPoolRefCnt = 0;
class ReleaseThreadPoolEvent MOZ_FINAL : public nsRunnable {
class ReleaseWMFByteStreamResourcesEvent MOZ_FINAL : public nsRunnable {
public:
ReleaseWMFByteStreamResourcesEvent(already_AddRefed<MediaResource> aResource)
: mResource(aResource) {}
virtual ~ReleaseWMFByteStreamResourcesEvent() {}
NS_IMETHOD Run() {
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
// Explicitly release the MediaResource reference. We *must* do this on
// the main thread, so we must explicitly release it here, we can't rely
// on the destructor to release it, since if this event runs before its
// dispatch call returns the destructor may run on the non-main thread.
mResource = nullptr;
NS_ASSERTION(sThreadPoolRefCnt > 0, "sThreadPoolRefCnt Should be non-negative");
sThreadPoolRefCnt--;
if (sThreadPoolRefCnt == 0) {
@ -91,11 +99,13 @@ public:
}
return NS_OK;
}
nsRefPtr<MediaResource> mResource;
};
WMFByteStream::WMFByteStream(MediaResource* aResource)
: mResource(aResource),
mReentrantMonitor("WMFByteStream"),
: mResourceMonitor("WMFByteStream.MediaResource"),
mResource(aResource),
mReentrantMonitor("WMFByteStream.Data"),
mOffset(0),
mIsShutdown(false)
{
@ -114,9 +124,11 @@ WMFByteStream::WMFByteStream(MediaResource* aResource)
WMFByteStream::~WMFByteStream()
{
MOZ_COUNT_DTOR(WMFByteStream);
// The WMFByteStream can be deleted from a WMF work queue thread, so we
// dispatch an event to the main thread to deref the thread pool.
nsCOMPtr<nsIRunnable> event = new ReleaseThreadPoolEvent();
// The WMFByteStream can be deleted from a thread pool thread, so we
// dispatch an event to the main thread to deref the thread pool and
// deref the MediaResource.
nsCOMPtr<nsIRunnable> event =
new ReleaseWMFByteStreamResourcesEvent(mResource.forget());
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
@ -152,11 +164,8 @@ WMFByteStream::Init()
nsresult
WMFByteStream::Shutdown()
{
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mIsShutdown = true;
}
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mIsShutdown = true;
return NS_OK;
}
@ -182,9 +191,9 @@ NS_IMPL_THREADSAFE_RELEASE(WMFByteStream)
// Stores data regarding an async read opreation.
class AsyncReadRequestState MOZ_FINAL : public IUnknown {
class AsyncReadRequest MOZ_FINAL : public IUnknown {
public:
AsyncReadRequestState(int64_t aOffset, BYTE* aBuffer, ULONG aLength)
AsyncReadRequest(int64_t aOffset, BYTE* aBuffer, ULONG aLength)
: mOffset(aOffset),
mBuffer(aBuffer),
mBufferLength(aLength),
@ -206,14 +215,14 @@ public:
NS_DECL_OWNINGTHREAD
};
NS_IMPL_THREADSAFE_ADDREF(AsyncReadRequestState)
NS_IMPL_THREADSAFE_RELEASE(AsyncReadRequestState)
NS_IMPL_THREADSAFE_ADDREF(AsyncReadRequest)
NS_IMPL_THREADSAFE_RELEASE(AsyncReadRequest)
// IUnknown Methods
STDMETHODIMP
AsyncReadRequestState::QueryInterface(REFIID aIId, void **aInterface)
AsyncReadRequest::QueryInterface(REFIID aIId, void **aInterface)
{
LOG("WMFByteStream::AsyncReadRequestState::QueryInterface %s", GetGUIDName(aIId).get());
LOG("AsyncReadRequest::QueryInterface %s", GetGUIDName(aIId).get());
if (aIId == IID_IUnknown) {
return DoGetInterface(static_cast<IUnknown*>(this), aInterface);
@ -223,23 +232,23 @@ AsyncReadRequestState::QueryInterface(REFIID aIId, void **aInterface)
return E_NOINTERFACE;
}
class PerformReadEvent MOZ_FINAL : public nsRunnable {
class ProcessReadRequestEvent MOZ_FINAL : public nsRunnable {
public:
PerformReadEvent(WMFByteStream* aStream,
IMFAsyncResult* aResult,
AsyncReadRequestState* aRequestState)
ProcessReadRequestEvent(WMFByteStream* aStream,
IMFAsyncResult* aResult,
AsyncReadRequest* aRequestState)
: mStream(aStream),
mResult(aResult),
mRequestState(aRequestState) {}
NS_IMETHOD Run() {
mStream->PerformRead(mResult, mRequestState);
mStream->ProcessReadRequest(mResult, mRequestState);
return NS_OK;
}
private:
RefPtr<WMFByteStream> mStream;
RefPtr<IMFAsyncResult> mResult;
RefPtr<AsyncReadRequestState> mRequestState;
RefPtr<AsyncReadRequest> mRequestState;
};
// IMFByteStream Methods
@ -256,12 +265,12 @@ WMFByteStream::BeginRead(BYTE *aBuffer,
LOG("WMFByteStream::BeginRead() mOffset=%lld tell=%lld length=%lu mIsShutdown=%d",
mOffset, mResource->Tell(), aLength, mIsShutdown);
if (mIsShutdown) {
return E_FAIL;
if (mIsShutdown || mOffset < 0) {
return E_INVALIDARG;
}
// Create an object to store our state.
RefPtr<AsyncReadRequestState> requestState = new AsyncReadRequestState(mOffset, aBuffer, aLength);
RefPtr<AsyncReadRequest> requestState = new AsyncReadRequest(mOffset, aBuffer, aLength);
// Create an IMFAsyncResult, this is passed back to the caller as a token to
// retrieve the number of bytes read.
@ -273,28 +282,31 @@ WMFByteStream::BeginRead(BYTE *aBuffer,
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
// Dispatch an event to perform the read in the thread pool.
nsCOMPtr<nsIRunnable> r = new PerformReadEvent(this, callersResult, requestState);
nsCOMPtr<nsIRunnable> r = new ProcessReadRequestEvent(this,
callersResult,
requestState);
nsresult rv = mThreadPool->Dispatch(r, NS_DISPATCH_NORMAL);
if (mResource->GetLength() > -1) {
mOffset = std::min<int64_t>(mOffset + aLength, mResource->GetLength());
} else {
mOffset += aLength;
}
return NS_SUCCEEDED(rv) ? S_OK : E_FAIL;
}
// Note: This is called on one of the thread pool's threads.
void
WMFByteStream::PerformRead(IMFAsyncResult* aResult, AsyncReadRequestState* aRequestState)
nsresult
WMFByteStream::Read(AsyncReadRequest* aRequestState)
{
ReentrantMonitorAutoEnter mon(mResourceMonitor);
// Ensure the read head is at the correct offset in the resource. It may not
// be if the SourceReader seeked.
if (mResource->Tell() != aRequestState->mOffset) {
nsresult rv = mResource->Seek(nsISeekableStream::NS_SEEK_SET,
aRequestState->mOffset);
if (NS_FAILED(rv)) {
// Let SourceReader know the read failed.
aResult->SetStatus(E_FAIL);
wmf::MFInvokeCallback(aResult);
LOG("WMFByteStream::Invoke() seek to read offset failed, aborting read");
return;
}
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ASSERTION(mResource->Tell() == aRequestState->mOffset, "State mismatch!");
@ -308,16 +320,36 @@ WMFByteStream::PerformRead(IMFAsyncResult* aResult, AsyncReadRequestState* aRequ
rv = mResource->Read(reinterpret_cast<char*>(buffer),
length,
reinterpret_cast<uint32_t*>(&bytesRead));
NS_ENSURE_SUCCESS(rv, rv);
totalBytesRead += bytesRead;
if (NS_FAILED(rv) || bytesRead == 0) {
if (bytesRead == 0) {
break;
}
}
aRequestState->mBytesRead = totalBytesRead;
return NS_OK;
}
// Record the number of bytes read, so the caller can retrieve
// it later.
aRequestState->mBytesRead = NS_SUCCEEDED(rv) ? totalBytesRead : 0;
aResult->SetStatus(S_OK);
// Note: This is called on one of the thread pool's threads.
void
WMFByteStream::ProcessReadRequest(IMFAsyncResult* aResult,
AsyncReadRequest* aRequestState)
{
if (mResource->GetLength() > -1 &&
aRequestState->mOffset > mResource->GetLength()) {
aResult->SetStatus(S_OK);
wmf::MFInvokeCallback(aResult);
LOG("WMFByteStream::Invoke() read offset greater than length, soft-failing read");
return;
}
nsresult rv = Read(aRequestState);
if (NS_FAILED(rv)) {
Shutdown();
aResult->SetStatus(E_ABORT);
} else {
aResult->SetStatus(S_OK);
}
LOG("WMFByteStream::Invoke() read %d at %lld finished rv=%x",
aRequestState->mBytesRead, aRequestState->mOffset, rv);
@ -357,26 +389,16 @@ WMFByteStream::EndRead(IMFAsyncResult* aResult, ULONG *aBytesRead)
if (FAILED(hr) || !unknown) {
return E_INVALIDARG;
}
AsyncReadRequestState* requestState =
static_cast<AsyncReadRequestState*>(unknown.get());
// Important: Only advance the read cursor if the caller hasn't seeked
// since it called BeginRead(). If it has seeked, we still must report
// the number of bytes read (in *aBytesRead), but we don't advance the
// read cursor; reads performed after the seek will do that. The SourceReader
// caller seems to keep track of which async read requested which range
// to be read, and does call SetCurrentPosition() before it calls EndRead().
if (mOffset == requestState->mOffset) {
mOffset += requestState->mBytesRead;
}
AsyncReadRequest* requestState =
static_cast<AsyncReadRequest*>(unknown.get());
// Report result.
*aBytesRead = requestState->mBytesRead;
LOG("WMFByteStream::EndRead() offset=%lld *aBytesRead=%u mOffset=%lld hr=0x%x eof=%d",
requestState->mOffset, *aBytesRead, mOffset, hr, (mOffset == mResource->GetLength()));
LOG("WMFByteStream::EndRead() offset=%lld *aBytesRead=%u mOffset=%lld status=0x%x hr=0x%x eof=%d",
requestState->mOffset, *aBytesRead, mOffset, aResult->GetStatus(), hr, IsEOS());
return S_OK;
return aResult->GetStatus();
}
STDMETHODIMP
@ -398,8 +420,12 @@ WMFByteStream::GetCapabilities(DWORD *aCapabilities)
{
LOG("WMFByteStream::GetCapabilities()");
NS_ENSURE_TRUE(aCapabilities, E_POINTER);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
bool seekable = mResource->IsTransportSeekable();
*aCapabilities = MFBYTESTREAM_IS_READABLE |
MFBYTESTREAM_IS_SEEKABLE;
MFBYTESTREAM_IS_SEEKABLE |
MFBYTESTREAM_IS_PARTIALLY_DOWNLOADED |
(!seekable ? MFBYTESTREAM_HAS_SLOW_SEEK : 0);
return S_OK;
}
@ -408,7 +434,14 @@ WMFByteStream::GetCurrentPosition(QWORD *aPosition)
{
NS_ENSURE_TRUE(aPosition, E_POINTER);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
*aPosition = mOffset;
// Note: Returning the length of stream as position when read
// cursor is < 0 seems to be the behaviour expected by WMF, but
// also note it doesn't seem to expect that the position is an
// unsigned value since if you seek to > length and read WMF
// expects the read to succeed after reading 0 bytes, but if you
// seek to < 0 and read, the read is expected to fails... So
// go figure...
*aPosition = mOffset < 0 ? mResource->GetLength() : mOffset;
LOG("WMFByteStream::GetCurrentPosition() %lld", mOffset);
return S_OK;
}
@ -417,18 +450,26 @@ STDMETHODIMP
WMFByteStream::GetLength(QWORD *aLength)
{
NS_ENSURE_TRUE(aLength, E_POINTER);
int64_t length = mResource->GetLength();
*aLength = length;
LOG("WMFByteStream::GetLength() %lld", length);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
*aLength = mResource->GetLength();
LOG("WMFByteStream::GetLength() %lld", *aLength);
return S_OK;
}
bool
WMFByteStream::IsEOS()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
return mResource->GetLength() > -1 &&
(mOffset < 0 ||
mOffset >= mResource->GetLength());
}
STDMETHODIMP
WMFByteStream::IsEndOfStream(BOOL *aEndOfStream)
{
NS_ENSURE_TRUE(aEndOfStream, E_POINTER);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
*aEndOfStream = (mOffset == mResource->GetLength());
*aEndOfStream = IsEOS();
LOG("WMFByteStream::IsEndOfStream() %d", *aEndOfStream);
return S_OK;
}
@ -450,16 +491,18 @@ WMFByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN aSeekOrigin,
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
if (mIsShutdown) {
return E_FAIL;
}
int64_t offset = mOffset;
if (aSeekOrigin == msoBegin) {
mOffset = aSeekOffset;
offset = aSeekOffset;
} else {
mOffset += aSeekOffset;
offset += aSeekOffset;
}
int64_t length = mResource->GetLength();
if (length > -1) {
mOffset = std::min<int64_t>(offset, length);
} else {
mOffset = offset;
}
if (aCurrentPosition) {
*aCurrentPosition = mOffset;
}
@ -474,25 +517,12 @@ WMFByteStream::SetCurrentPosition(QWORD aPosition)
LOG("WMFByteStream::SetCurrentPosition(%lld)",
aPosition);
if (mIsShutdown) {
return E_FAIL;
}
// Note: WMF calls SetCurrentPosition() sometimes after calling BeginRead()
// but before the read has finished, and thus before it's called EndRead().
// See comment in EndRead() for more details.
int64_t length = mResource->GetLength();
if (length >= 0 && aPosition > uint64_t(length)) {
// Despite the fact that the MSDN IMFByteStream::SetCurrentPosition()
// documentation says that we should return E_INVALIDARG when the seek
// position is after the length, if we do that IMFSourceReader::ReadSample()
// fails in some situations. So we just clamp the seek target to
// the EOS, and things seem to just work...
LOG("WMFByteStream::SetCurrentPosition(%lld) clamping position to eos (%lld)", aPosition, length);
aPosition = length;
if (length > -1) {
mOffset = std::min<int64_t>(aPosition, length);
} else {
mOffset = aPosition;
}
mOffset = aPosition;
return S_OK;
}

View File

@ -19,13 +19,19 @@ class nsIThreadPool;
namespace mozilla {
class MediaResource;
class AsyncReadRequestState;
class AsyncReadRequest;
// Wraps a MediaResource around an IMFByteStream interface, so that it can
// be used by the IMFSourceReader. Each WMFByteStream creates a WMF Work Queue
// on which blocking I/O is performed. The SourceReader requests reads
// asynchronously using {Begin,End}Read(). The synchronous I/O methods aren't
// used by the SourceReader, so they're not implemented on this class.
//
// Note: This implementation attempts to be bug-compatible with Windows Media
// Foundation's implementation of IMFByteStream. The behaviour of WMF's
// IMFByteStream was determined by creating it and testing the edge cases.
// For details see the test code at:
// https://github.com/cpearce/IMFByteStreamBehaviour/
class WMFByteStream MOZ_FINAL : public IMFByteStream
{
public:
@ -66,17 +72,34 @@ public:
STDMETHODIMP Write(const BYTE *, ULONG, ULONG *);
// We perform an async read operation in this callback implementation.
void PerformRead(IMFAsyncResult* aResult, AsyncReadRequestState* aRequestState);
// Processes an async read request, storing the result in aResult, and
// notifying the caller when the read operation is complete.
void ProcessReadRequest(IMFAsyncResult* aResult,
AsyncReadRequest* aRequestState);
private:
// Locks the MediaResource and performs the read. This is a helper
// for ProcessReadRequest().
nsresult Read(AsyncReadRequest* aRequestState);
// Returns true if the current position of the stream is at end of stream.
bool IsEOS();
// Reference to the thread pool in which we perform the reads asynchronously.
// Note this is pool is shared amongst all active WMFByteStreams.
nsCOMPtr<nsIThreadPool> mThreadPool;
// 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
// supposed to!
ReentrantMonitor mResourceMonitor;
// Resource we're wrapping. Note this object's methods are threadsafe,
// and we only call read/seek on the work queue's thread.
MediaResource* mResource;
// but because multiple reads can be processed concurrently in the thread
// pool we must hold mResourceMonitor whenever we seek+read to ensure that
// another read request's seek+read doesn't interleave.
nsRefPtr<MediaResource> mResource;
// Protects mOffset, which is accessed by the SourceReaders thread(s), and
// on the work queue thread.