diff options
Diffstat (limited to 'dom/media/directshow/SourceFilter.cpp')
-rw-r--r-- | dom/media/directshow/SourceFilter.cpp | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/dom/media/directshow/SourceFilter.cpp b/dom/media/directshow/SourceFilter.cpp new file mode 100644 index 000000000..4c5a0882c --- /dev/null +++ b/dom/media/directshow/SourceFilter.cpp @@ -0,0 +1,683 @@ +/* -*- 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 "SourceFilter.h" +#include "MediaResource.h" +#include "mozilla/RefPtr.h" +#include "DirectShowUtils.h" +#include "MP3FrameParser.h" +#include "mozilla/Logging.h" +#include <algorithm> + +using namespace mozilla::media; + +namespace mozilla { + +// Define to trace what's on... +//#define DEBUG_SOURCE_TRACE 1 + +#if defined (DEBUG_SOURCE_TRACE) +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define DIRECTSHOW_LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#else +#define DIRECTSHOW_LOG(...) +#endif + +static HRESULT +DoGetInterface(IUnknown* aUnknown, void** aInterface) +{ + if (!aInterface) + return E_POINTER; + *aInterface = aUnknown; + aUnknown->AddRef(); + return S_OK; +} + +// Stores details of IAsyncReader::Request(). +class ReadRequest { +public: + + ReadRequest(IMediaSample* aSample, + DWORD_PTR aDwUser, + uint32_t aOffset, + uint32_t aCount) + : mSample(aSample), + mDwUser(aDwUser), + mOffset(aOffset), + mCount(aCount) + { + MOZ_COUNT_CTOR(ReadRequest); + } + + ~ReadRequest() { + MOZ_COUNT_DTOR(ReadRequest); + } + + RefPtr<IMediaSample> mSample; + DWORD_PTR mDwUser; + uint32_t mOffset; + uint32_t mCount; +}; + +// A wrapper around media resource that presents only a partition of the +// underlying resource to the caller to use. The partition returned is from +// an offset to the end of stream, and this object deals with ensuring +// the offsets and lengths etc are translated from the reduced partition +// exposed to the caller, to the absolute offsets of the underlying stream. +class MediaResourcePartition { +public: + MediaResourcePartition(MediaResource* aResource, + int64_t aDataStart) + : mResource(aResource), + mDataOffset(aDataStart) + {} + + int64_t GetLength() { + int64_t len = mResource.GetLength(); + if (len == -1) { + return len; + } + return std::max<int64_t>(0, len - mDataOffset); + } + nsresult ReadAt(int64_t aOffset, char* aBuffer, + uint32_t aCount, uint32_t* aBytes) + { + return mResource.ReadAt(aOffset + mDataOffset, + aBuffer, + aCount, + aBytes); + } + int64_t GetCachedDataEnd() { + int64_t tell = mResource.GetResource()->Tell(); + int64_t dataEnd = + mResource.GetResource()->GetCachedDataEnd(tell) - mDataOffset; + return dataEnd; + } +private: + // MediaResource from which we read data. + MediaResourceIndex mResource; + int64_t mDataOffset; +}; + + +// Output pin for SourceFilter, which implements IAsyncReader, to +// allow downstream filters to pull/read data from it. Downstream pins +// register to read data using Request(), and asynchronously wait for the +// reads to complete using WaitForNext(). They may also synchronously read +// using SyncRead(). This class is a delegate (tear off) of +// SourceFilter. +// +// We can expose only a segment of the MediaResource to the filter graph. +// This is used to strip off the ID3v2 tags from the stream, as DirectShow +// has trouble parsing some headers. +// +// Implements: +// * IAsyncReader +// * IPin +// * IQualityControl +// * IUnknown +// +class DECLSPEC_UUID("18e5cfb2-1015-440c-a65c-e63853235894") +OutputPin : public IAsyncReader, + public BasePin +{ +public: + + OutputPin(MediaResource* aMediaResource, + SourceFilter* aParent, + CriticalSection& aFilterLock, + int64_t aMP3DataStart); + virtual ~OutputPin(); + + // IUnknown + // Defer to ref counting to BasePin, which defers to owning nsBaseFilter. + STDMETHODIMP_(ULONG) AddRef() override { return BasePin::AddRef(); } + STDMETHODIMP_(ULONG) Release() override { return BasePin::Release(); } + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override; + + // BasePin Overrides. + // Determines if the pin accepts a specific media type. + HRESULT CheckMediaType(const MediaType* aMediaType) override; + + // Retrieves a preferred media type, by index value. + HRESULT GetMediaType(int aPosition, MediaType* aMediaType) override; + + // Releases the pin from a connection. + HRESULT BreakConnect(void) override; + + // Determines whether a pin connection is suitable. + HRESULT CheckConnect(IPin* aPin) override; + + + // IAsyncReader overrides + + // The RequestAllocator method requests an allocator during the + // pin connection. + STDMETHODIMP RequestAllocator(IMemAllocator* aPreferred, + ALLOCATOR_PROPERTIES* aProps, + IMemAllocator** aActual) override; + + // The Request method queues an asynchronous request for data. Downstream + // will call WaitForNext() when they want to retrieve the result. + STDMETHODIMP Request(IMediaSample* aSample, DWORD_PTR aUserData) override; + + // The WaitForNext method waits for the next pending read request + // to complete. This method fails if the graph is flushing. + // Defers to SyncRead/5. + STDMETHODIMP WaitForNext(DWORD aTimeout, + IMediaSample** aSamples, + DWORD_PTR* aUserData) override; + + // The SyncReadAligned method performs a synchronous read. The method + // blocks until the request is completed. Defers to SyncRead/5. This + // method does not fail if the graph is flushing. + STDMETHODIMP SyncReadAligned(IMediaSample* aSample) override; + + // The SyncRead method performs a synchronous read. The method blocks + // until the request is completed. Defers to SyncRead/5. This + // method does not fail if the graph is flushing. + STDMETHODIMP SyncRead(LONGLONG aPosition, LONG aLength, BYTE* aBuffer) override; + + // The Length method retrieves the total length of the stream. + STDMETHODIMP Length(LONGLONG* aTotal, LONGLONG* aAvailable) override; + + // IPin Overrides + STDMETHODIMP BeginFlush(void) override; + STDMETHODIMP EndFlush(void) override; + + uint32_t GetAndResetBytesConsumedCount(); + +private: + + // Protects thread-shared data/structures (mFlushCount, mPendingReads). + // WaitForNext() also waits on this monitor + CriticalSection& mPinLock; + + // Signal used with mPinLock to implement WaitForNext(). + Signal mSignal; + + // The filter that owns us. Weak reference, as we're a delegate (tear off). + SourceFilter* mParentSource; + + MediaResourcePartition mResource; + + // Counter, inc'd in BeginFlush(), dec'd in EndFlush(). Calls to this can + // come from multiple threads and can interleave, hence the counter. + int32_t mFlushCount; + + // Number of bytes that have been read from the output pin since the last + // time GetAndResetBytesConsumedCount() was called. + uint32_t mBytesConsumed; + + // Deque of ReadRequest* for reads that are yet to be serviced. + // nsReadRequest's are stored on the heap, popper must delete them. + nsDeque mPendingReads; + + // Flags if the downstream pin has QI'd for IAsyncReader. We refuse + // connection if they don't query, as it means they're assuming that we're + // a push filter, and we're not. + bool mQueriedForAsyncReader; + +}; + +// For mingw __uuidof support +#ifdef __CRT_UUID_DECL +} +__CRT_UUID_DECL(mozilla::OutputPin, 0x18e5cfb2,0x1015,0x440c,0xa6,0x5c,0xe6,0x38,0x53,0x23,0x58,0x94); +namespace mozilla { +#endif + +OutputPin::OutputPin(MediaResource* aResource, + SourceFilter* aParent, + CriticalSection& aFilterLock, + int64_t aMP3DataStart) + : BasePin(static_cast<BaseFilter*>(aParent), + &aFilterLock, + L"MozillaOutputPin", + PINDIR_OUTPUT), + mPinLock(aFilterLock), + mSignal(&mPinLock), + mParentSource(aParent), + mResource(aResource, aMP3DataStart), + mFlushCount(0), + mBytesConsumed(0), + mQueriedForAsyncReader(false) +{ + MOZ_COUNT_CTOR(OutputPin); + DIRECTSHOW_LOG("OutputPin::OutputPin()"); +} + +OutputPin::~OutputPin() +{ + MOZ_COUNT_DTOR(OutputPin); + DIRECTSHOW_LOG("OutputPin::~OutputPin()"); +} + +HRESULT +OutputPin::BreakConnect() +{ + mQueriedForAsyncReader = false; + return BasePin::BreakConnect(); +} + +STDMETHODIMP +OutputPin::QueryInterface(REFIID aIId, void** aInterface) +{ + if (aIId == IID_IAsyncReader) { + mQueriedForAsyncReader = true; + return DoGetInterface(static_cast<IAsyncReader*>(this), aInterface); + } + + if (aIId == __uuidof(OutputPin)) { + AddRef(); + *aInterface = this; + return S_OK; + } + + return BasePin::QueryInterface(aIId, aInterface); +} + +HRESULT +OutputPin::CheckConnect(IPin* aPin) +{ + // Our connection is only suitable if the downstream pin knows + // that we're asynchronous (i.e. it queried for IAsyncReader). + return mQueriedForAsyncReader ? S_OK : S_FALSE; +} + +HRESULT +OutputPin::CheckMediaType(const MediaType* aMediaType) +{ + const MediaType *myMediaType = mParentSource->GetMediaType(); + + if (IsEqualGUID(aMediaType->majortype, myMediaType->majortype) && + IsEqualGUID(aMediaType->subtype, myMediaType->subtype) && + IsEqualGUID(aMediaType->formattype, myMediaType->formattype)) + { + DIRECTSHOW_LOG("OutputPin::CheckMediaType() Match: major=%s minor=%s TC=%d FSS=%d SS=%u", + GetDirectShowGuidName(aMediaType->majortype), + GetDirectShowGuidName(aMediaType->subtype), + aMediaType->TemporalCompression(), + aMediaType->bFixedSizeSamples, + aMediaType->SampleSize()); + return S_OK; + } + + DIRECTSHOW_LOG("OutputPin::CheckMediaType() Failed to match: major=%s minor=%s TC=%d FSS=%d SS=%u", + GetDirectShowGuidName(aMediaType->majortype), + GetDirectShowGuidName(aMediaType->subtype), + aMediaType->TemporalCompression(), + aMediaType->bFixedSizeSamples, + aMediaType->SampleSize()); + return S_FALSE; +} + +HRESULT +OutputPin::GetMediaType(int aPosition, MediaType* aMediaType) +{ + if (!aMediaType) + return E_POINTER; + + if (aPosition == 0) { + aMediaType->Assign(mParentSource->GetMediaType()); + return S_OK; + } + return VFW_S_NO_MORE_ITEMS; +} + +static inline bool +IsPowerOf2(int32_t x) { + return ((-x & x) != x); +} + +STDMETHODIMP +OutputPin::RequestAllocator(IMemAllocator* aPreferred, + ALLOCATOR_PROPERTIES* aProps, + IMemAllocator** aActual) +{ + // Require the downstream pin to suggest what they want... + if (!aPreferred) return E_POINTER; + if (!aProps) return E_POINTER; + if (!aActual) return E_POINTER; + + // We only care about alignment - our allocator will reject anything + // which isn't power-of-2 aligned, so so try a 4-byte aligned allocator. + ALLOCATOR_PROPERTIES props; + memcpy(&props, aProps, sizeof(ALLOCATOR_PROPERTIES)); + if (aProps->cbAlign == 0 || IsPowerOf2(aProps->cbAlign)) { + props.cbAlign = 4; + } + + // Limit allocator's number of buffers. We know that the media will most + // likely be bound by network speed, not by decoding speed. We also + // store the incoming data in a Gecko stream, if we don't limit buffers + // here we'll end up duplicating a lot of storage. We must have enough + // space for audio key frames to fit in the first batch of buffers however, + // else pausing may fail for some downstream decoders. + if (props.cBuffers > BaseFilter::sMaxNumBuffers) { + props.cBuffers = BaseFilter::sMaxNumBuffers; + } + + // The allocator properties that are actually used. We don't store + // this, we need it for SetProperties() below to succeed. + ALLOCATOR_PROPERTIES actualProps; + HRESULT hr; + + if (aPreferred) { + // Play nice and prefer the downstream pin's preferred allocator. + hr = aPreferred->SetProperties(&props, &actualProps); + if (SUCCEEDED(hr)) { + aPreferred->AddRef(); + *aActual = aPreferred; + return S_OK; + } + } + + // Else downstream hasn't requested a specific allocator, so create one... + + // Just create a default allocator. It's highly unlikely that we'll use + // this anyway, as most parsers insist on using their own allocators. + RefPtr<IMemAllocator> allocator; + hr = CoCreateInstance(CLSID_MemoryAllocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IMemAllocator, + getter_AddRefs(allocator)); + if(FAILED(hr) || (allocator == nullptr)) { + NS_WARNING("Can't create our own DirectShow allocator."); + return hr; + } + + // See if we can make it suitable + hr = allocator->SetProperties(&props, &actualProps); + if (SUCCEEDED(hr)) { + // We need to release our refcount on pAlloc, and addref + // it to pass a refcount to the caller - this is a net nothing. + allocator.forget(aActual); + return S_OK; + } + + NS_WARNING("Failed to pick an allocator"); + return hr; +} + +STDMETHODIMP +OutputPin::Request(IMediaSample* aSample, DWORD_PTR aDwUser) +{ + if (!aSample) return E_FAIL; + + CriticalSectionAutoEnter lock(*mLock); + NS_ASSERTION(!mFlushCount, "Request() while flushing"); + + if (mFlushCount) + return VFW_E_WRONG_STATE; + + REFERENCE_TIME refStart = 0, refEnd = 0; + if (FAILED(aSample->GetTime(&refStart, &refEnd))) { + NS_WARNING("Sample incorrectly timestamped"); + return VFW_E_SAMPLE_TIME_NOT_SET; + } + + // Convert reference time to bytes. + uint32_t start = (uint32_t)(refStart / 10000000); + uint32_t end = (uint32_t)(refEnd / 10000000); + + uint32_t numBytes = end - start; + + ReadRequest* request = new ReadRequest(aSample, + aDwUser, + start, + numBytes); + // Memory for |request| is free when it's popped from the completed + // reads list. + + // Push this onto the queue of reads to be serviced. + mPendingReads.Push(request); + + // Notify any threads blocked in WaitForNext() which are waiting for mPendingReads + // to become non-empty. + mSignal.Notify(); + + return S_OK; +} + +STDMETHODIMP +OutputPin::WaitForNext(DWORD aTimeout, + IMediaSample** aOutSample, + DWORD_PTR* aOutDwUser) +{ + NS_ASSERTION(aTimeout == 0 || aTimeout == INFINITE, + "Oops, we don't handle this!"); + + *aOutSample = nullptr; + *aOutDwUser = 0; + + LONGLONG offset = 0; + LONG count = 0; + BYTE* buf = nullptr; + + { + CriticalSectionAutoEnter lock(*mLock); + + // Wait until there's a pending read to service. + while (aTimeout && mPendingReads.GetSize() == 0 && !mFlushCount) { + // Note: No need to guard against shutdown-during-wait here, as + // typically the thread doing the pull will have already called + // Request(), so we won't Wait() here anyway. SyncRead() will fail + // on shutdown. + mSignal.Wait(); + } + + nsAutoPtr<ReadRequest> request(reinterpret_cast<ReadRequest*>(mPendingReads.PopFront())); + if (!request) + return VFW_E_WRONG_STATE; + + *aOutSample = request->mSample; + *aOutDwUser = request->mDwUser; + + offset = request->mOffset; + count = request->mCount; + buf = nullptr; + request->mSample->GetPointer(&buf); + NS_ASSERTION(buf != nullptr, "Invalid buffer!"); + + if (mFlushCount) { + return VFW_E_TIMEOUT; + } + } + + return SyncRead(offset, count, buf); +} + +STDMETHODIMP +OutputPin::SyncReadAligned(IMediaSample* aSample) +{ + { + // Ignore reads while flushing. + CriticalSectionAutoEnter lock(*mLock); + if (mFlushCount) { + return S_FALSE; + } + } + + if (!aSample) + return E_FAIL; + + REFERENCE_TIME lStart = 0, lEnd = 0; + if (FAILED(aSample->GetTime(&lStart, &lEnd))) { + NS_WARNING("Sample incorrectly timestamped"); + return VFW_E_SAMPLE_TIME_NOT_SET; + } + + // Convert reference time to bytes. + int32_t start = (int32_t)(lStart / 10000000); + int32_t end = (int32_t)(lEnd / 10000000); + + int32_t numBytes = end - start; + + // If the range extends off the end of stream, truncate to the end of stream + // as per IAsyncReader specificiation. + int64_t streamLength = mResource.GetLength(); + if (streamLength != -1) { + // We know the exact length of the stream, fail if the requested offset + // is beyond it. + if (start > streamLength) { + return VFW_E_BADALIGN; + } + + // If the end of the chunk to read is off the end of the stream, + // truncate it to the end of the stream. + if ((start + numBytes) > streamLength) { + numBytes = (uint32_t)(streamLength - start); + } + } + + BYTE* buf=0; + aSample->GetPointer(&buf); + + return SyncRead(start, numBytes, buf); +} + +STDMETHODIMP +OutputPin::SyncRead(LONGLONG aPosition, + LONG aLength, + BYTE* aBuffer) +{ + MOZ_ASSERT(!NS_IsMainThread()); + NS_ENSURE_TRUE(aPosition >= 0, E_FAIL); + NS_ENSURE_TRUE(aLength > 0, E_FAIL); + NS_ENSURE_TRUE(aBuffer, E_POINTER); + + DIRECTSHOW_LOG("OutputPin::SyncRead(%lld, %d)", aPosition, aLength); + { + // Ignore reads while flushing. + CriticalSectionAutoEnter lock(*mLock); + if (mFlushCount) { + return S_FALSE; + } + } + + uint32_t totalBytesRead = 0; + nsresult rv = mResource.ReadAt(aPosition, + reinterpret_cast<char*>(aBuffer), + aLength, + &totalBytesRead); + if (NS_FAILED(rv)) { + return E_FAIL; + } + if (totalBytesRead > 0) { + CriticalSectionAutoEnter lock(*mLock); + mBytesConsumed += totalBytesRead; + } + return (totalBytesRead == aLength) ? S_OK : S_FALSE; +} + +STDMETHODIMP +OutputPin::Length(LONGLONG* aTotal, LONGLONG* aAvailable) +{ + HRESULT hr = S_OK; + int64_t length = mResource.GetLength(); + if (length == -1) { + hr = VFW_S_ESTIMATED; + // Don't have a length. Just lie, it seems to work... + *aTotal = INT32_MAX; + } else { + *aTotal = length; + } + if (aAvailable) { + *aAvailable = mResource.GetCachedDataEnd(); + } + + DIRECTSHOW_LOG("OutputPin::Length() len=%lld avail=%lld", *aTotal, *aAvailable); + + return hr; +} + +STDMETHODIMP +OutputPin::BeginFlush() +{ + CriticalSectionAutoEnter lock(*mLock); + mFlushCount++; + mSignal.Notify(); + return S_OK; +} + +STDMETHODIMP +OutputPin::EndFlush(void) +{ + CriticalSectionAutoEnter lock(*mLock); + mFlushCount--; + return S_OK; +} + +uint32_t +OutputPin::GetAndResetBytesConsumedCount() +{ + CriticalSectionAutoEnter lock(*mLock); + uint32_t bytesConsumed = mBytesConsumed; + mBytesConsumed = 0; + return bytesConsumed; +} + +SourceFilter::SourceFilter(const GUID& aMajorType, + const GUID& aSubType) + : BaseFilter(L"MozillaDirectShowSource", __uuidof(SourceFilter)) +{ + MOZ_COUNT_CTOR(SourceFilter); + mMediaType.majortype = aMajorType; + mMediaType.subtype = aSubType; + + DIRECTSHOW_LOG("SourceFilter Constructor(%s, %s)", + GetDirectShowGuidName(aMajorType), + GetDirectShowGuidName(aSubType)); +} + +SourceFilter::~SourceFilter() +{ + MOZ_COUNT_DTOR(SourceFilter); + DIRECTSHOW_LOG("SourceFilter Destructor()"); +} + +BasePin* +SourceFilter::GetPin(int n) +{ + if (n == 0) { + NS_ASSERTION(mOutputPin != 0, "GetPin with no pin!"); + return static_cast<BasePin*>(mOutputPin); + } else { + return nullptr; + } +} + +// Get's the media type we're supplying. +const MediaType* +SourceFilter::GetMediaType() const +{ + return &mMediaType; +} + +nsresult +SourceFilter::Init(MediaResource* aResource, int64_t aMP3Offset) +{ + DIRECTSHOW_LOG("SourceFilter::Init()"); + + mOutputPin = new OutputPin(aResource, + this, + mLock, + aMP3Offset); + NS_ENSURE_TRUE(mOutputPin != nullptr, NS_ERROR_FAILURE); + + return NS_OK; +} + +uint32_t +SourceFilter::GetAndResetBytesConsumedCount() +{ + return mOutputPin->GetAndResetBytesConsumedCount(); +} + + +} // namespace mozilla |