diff options
Diffstat (limited to 'dom/media/directshow')
-rw-r--r-- | dom/media/directshow/AudioSinkFilter.cpp | 285 | ||||
-rw-r--r-- | dom/media/directshow/AudioSinkFilter.h | 95 | ||||
-rw-r--r-- | dom/media/directshow/AudioSinkInputPin.cpp | 195 | ||||
-rw-r--r-- | dom/media/directshow/AudioSinkInputPin.h | 76 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowDecoder.cpp | 65 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowDecoder.h | 45 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowReader.cpp | 360 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowReader.h | 110 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowUtils.cpp | 369 | ||||
-rw-r--r-- | dom/media/directshow/DirectShowUtils.h | 125 | ||||
-rw-r--r-- | dom/media/directshow/SampleSink.cpp | 159 | ||||
-rw-r--r-- | dom/media/directshow/SampleSink.h | 67 | ||||
-rw-r--r-- | dom/media/directshow/SourceFilter.cpp | 683 | ||||
-rw-r--r-- | dom/media/directshow/SourceFilter.h | 75 | ||||
-rw-r--r-- | dom/media/directshow/moz.build | 41 |
15 files changed, 2750 insertions, 0 deletions
diff --git a/dom/media/directshow/AudioSinkFilter.cpp b/dom/media/directshow/AudioSinkFilter.cpp new file mode 100644 index 000000000..9f23c0e00 --- /dev/null +++ b/dom/media/directshow/AudioSinkFilter.cpp @@ -0,0 +1,285 @@ +/* -*- 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 "SampleSink.h" +#include "AudioSinkFilter.h" +#include "AudioSinkInputPin.h" +#include "VideoUtils.h" +#include "mozilla/Logging.h" + + +#include <initguid.h> +#include <wmsdkidl.h> + +#define DELETE_RESET(p) { delete (p) ; (p) = nullptr ;} + +DEFINE_GUID(CLSID_MozAudioSinkFilter, 0x1872d8c8, 0xea8d, 0x4c34, 0xae, 0x96, 0x69, 0xde, + 0xf1, 0x33, 0x7b, 0x33); + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +AudioSinkFilter::AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult) + : BaseFilter(aObjectName, CLSID_MozAudioSinkFilter), + mFilterCritSec("AudioSinkFilter::mFilterCritSec") +{ + (*aOutResult) = S_OK; + mInputPin = new AudioSinkInputPin(L"AudioSinkInputPin", + this, + &mFilterCritSec, + aOutResult); +} + +AudioSinkFilter::~AudioSinkFilter() +{ +} + +int +AudioSinkFilter::GetPinCount() +{ + return 1; +} + +BasePin* +AudioSinkFilter::GetPin(int aIndex) +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + return (aIndex == 0) ? static_cast<BasePin*>(mInputPin) : nullptr; +} + +HRESULT +AudioSinkFilter::Pause() +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + if (mState == State_Stopped) { + // Change the state, THEN activate the input pin. + mState = State_Paused; + if (mInputPin && mInputPin->IsConnected()) { + mInputPin->Active(); + } + } else if (mState == State_Running) { + mState = State_Paused; + } + return S_OK; +} + +HRESULT +AudioSinkFilter::Stop() +{ + CriticalSectionAutoEnter lockFilter(mFilterCritSec); + mState = State_Stopped; + if (mInputPin) { + mInputPin->Inactive(); + } + + GetSampleSink()->Flush(); + + return S_OK; +} + +HRESULT +AudioSinkFilter::Run(REFERENCE_TIME tStart) +{ + LOG("AudioSinkFilter::Run(%lld) [%4.2lf]", + RefTimeToUsecs(tStart), + double(RefTimeToUsecs(tStart)) / USECS_PER_S); + return media::BaseFilter::Run(tStart); +} + +HRESULT +AudioSinkFilter::GetClassID( OUT CLSID * pCLSID ) +{ + (* pCLSID) = CLSID_MozAudioSinkFilter; + return S_OK; +} + +HRESULT +AudioSinkFilter::QueryInterface(REFIID aIId, void **aInterface) +{ + if (aIId == IID_IMediaSeeking) { + *aInterface = static_cast<IMediaSeeking*>(this); + AddRef(); + return S_OK; + } + return mozilla::media::BaseFilter::QueryInterface(aIId, aInterface); +} + +ULONG +AudioSinkFilter::AddRef() +{ + return ::InterlockedIncrement(&mRefCnt); +} + +ULONG +AudioSinkFilter::Release() +{ + unsigned long newRefCnt = ::InterlockedDecrement(&mRefCnt); + if (!newRefCnt) { + delete this; + } + return newRefCnt; +} + +SampleSink* +AudioSinkFilter::GetSampleSink() +{ + return mInputPin->GetSampleSink(); +} + + +// IMediaSeeking implementation. +// +// Calls to IMediaSeeking are forwarded to the output pin that the +// AudioSinkInputPin is connected to, i.e. upstream towards the parser and +// source filters, which actually implement seeking. +#define ENSURE_CONNECTED_PIN_SEEKING \ + if (!mInputPin) { \ + return E_NOTIMPL; \ + } \ + RefPtr<IMediaSeeking> pinSeeking = mInputPin->GetConnectedPinSeeking(); \ + if (!pinSeeking) { \ + return E_NOTIMPL; \ + } + +HRESULT +AudioSinkFilter::GetCapabilities(DWORD* aCapabilities) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetCapabilities(aCapabilities); +} + +HRESULT +AudioSinkFilter::CheckCapabilities(DWORD* aCapabilities) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->CheckCapabilities(aCapabilities); +} + +HRESULT +AudioSinkFilter::IsFormatSupported(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->IsFormatSupported(aFormat); +} + +HRESULT +AudioSinkFilter::QueryPreferredFormat(GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->QueryPreferredFormat(aFormat); +} + +HRESULT +AudioSinkFilter::GetTimeFormat(GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::IsUsingTimeFormat(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->IsUsingTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::SetTimeFormat(const GUID* aFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetTimeFormat(aFormat); +} + +HRESULT +AudioSinkFilter::GetDuration(LONGLONG* aDuration) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetDuration(aDuration); +} + +HRESULT +AudioSinkFilter::GetStopPosition(LONGLONG* aStop) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetStopPosition(aStop); +} + +HRESULT +AudioSinkFilter::GetCurrentPosition(LONGLONG* aCurrent) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetCurrentPosition(aCurrent); +} + +HRESULT +AudioSinkFilter::ConvertTimeFormat(LONGLONG* aTarget, + const GUID* aTargetFormat, + LONGLONG aSource, + const GUID* aSourceFormat) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->ConvertTimeFormat(aTarget, + aTargetFormat, + aSource, + aSourceFormat); +} + +HRESULT +AudioSinkFilter::SetPositions(LONGLONG* aCurrent, + DWORD aCurrentFlags, + LONGLONG* aStop, + DWORD aStopFlags) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetPositions(aCurrent, + aCurrentFlags, + aStop, + aStopFlags); +} + +HRESULT +AudioSinkFilter::GetPositions(LONGLONG* aCurrent, + LONGLONG* aStop) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetPositions(aCurrent, aStop); +} + +HRESULT +AudioSinkFilter::GetAvailable(LONGLONG* aEarliest, + LONGLONG* aLatest) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetAvailable(aEarliest, aLatest); +} + +HRESULT +AudioSinkFilter::SetRate(double aRate) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->SetRate(aRate); +} + +HRESULT +AudioSinkFilter::GetRate(double* aRate) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetRate(aRate); +} + +HRESULT +AudioSinkFilter::GetPreroll(LONGLONG* aPreroll) +{ + ENSURE_CONNECTED_PIN_SEEKING + return pinSeeking->GetPreroll(aPreroll); +} + +} // namespace mozilla + diff --git a/dom/media/directshow/AudioSinkFilter.h b/dom/media/directshow/AudioSinkFilter.h new file mode 100644 index 000000000..85abdfccf --- /dev/null +++ b/dom/media/directshow/AudioSinkFilter.h @@ -0,0 +1,95 @@ +/* -*- 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(AudioSinkFilter_h_) +#define AudioSinkFilter_h_ + +#include "BaseFilter.h" +#include "DirectShowUtils.h" +#include "nsAutoPtr.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class AudioSinkInputPin; +class SampleSink; + +// Filter that acts as the end of the graph. Audio samples input into +// this filter block the calling thread, and the calling thread is +// unblocked when the decode thread extracts the sample. The samples +// input into this filter are stored in the SampleSink, where the blocking +// is implemented. The input pin owns the SampleSink. +class AudioSinkFilter: public mozilla::media::BaseFilter, + public IMediaSeeking +{ + +public: + AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult); + virtual ~AudioSinkFilter(); + + // Gets the input pin's sample sink. + SampleSink* GetSampleSink(); + + // IUnknown implementation. + STDMETHODIMP QueryInterface(REFIID aIId, void **aInterface); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // -------------------------------------------------------------------- + // CBaseFilter methods + int GetPinCount (); + mozilla::media::BasePin* GetPin ( IN int Index); + STDMETHODIMP Pause (); + STDMETHODIMP Stop (); + STDMETHODIMP GetClassID ( OUT CLSID * pCLSID); + STDMETHODIMP Run(REFERENCE_TIME tStart); + // IMediaSeeking Methods... + + // We defer to SourceFilter, but we must expose the interface on + // the output pins. Seeking commands come upstream from the renderers, + // but they must be actioned at the source filters. + STDMETHODIMP GetCapabilities(DWORD* aCapabilities); + STDMETHODIMP CheckCapabilities(DWORD* aCapabilities); + STDMETHODIMP IsFormatSupported(const GUID* aFormat); + STDMETHODIMP QueryPreferredFormat(GUID* aFormat); + STDMETHODIMP GetTimeFormat(GUID* aFormat); + STDMETHODIMP IsUsingTimeFormat(const GUID* aFormat); + STDMETHODIMP SetTimeFormat(const GUID* aFormat); + STDMETHODIMP GetDuration(LONGLONG* pDuration); + STDMETHODIMP GetStopPosition(LONGLONG* pStop); + STDMETHODIMP GetCurrentPosition(LONGLONG* aCurrent); + STDMETHODIMP ConvertTimeFormat(LONGLONG* aTarget, + const GUID* aTargetFormat, + LONGLONG aSource, + const GUID* aSourceFormat); + STDMETHODIMP SetPositions(LONGLONG* aCurrent, + DWORD aCurrentFlags, + LONGLONG* aStop, + DWORD aStopFlags); + STDMETHODIMP GetPositions(LONGLONG* aCurrent, + LONGLONG* aStop); + STDMETHODIMP GetAvailable(LONGLONG* aEarliest, + LONGLONG* aLatest); + STDMETHODIMP SetRate(double aRate); + STDMETHODIMP GetRate(double* aRate); + STDMETHODIMP GetPreroll(LONGLONG* aPreroll); + + // -------------------------------------------------------------------- + // class factory calls this + static IUnknown * CreateInstance (IN LPUNKNOWN punk, OUT HRESULT * phr); + +private: + CriticalSection mFilterCritSec; + + // Note: The input pin defers its refcounting to the sink filter, so when + // the input pin is addrefed, what actually happens is the sink filter is + // addrefed. + nsAutoPtr<AudioSinkInputPin> mInputPin; +}; + +} // namespace mozilla + +#endif // AudioSinkFilter_h_ diff --git a/dom/media/directshow/AudioSinkInputPin.cpp b/dom/media/directshow/AudioSinkInputPin.cpp new file mode 100644 index 000000000..85a6e3da3 --- /dev/null +++ b/dom/media/directshow/AudioSinkInputPin.cpp @@ -0,0 +1,195 @@ +/* -*- 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 "AudioSinkInputPin.h" +#include "AudioSinkFilter.h" +#include "SampleSink.h" +#include "mozilla/Logging.h" + +#include <wmsdkidl.h> + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +AudioSinkInputPin::AudioSinkInputPin(wchar_t* aObjectName, + AudioSinkFilter* aFilter, + mozilla::CriticalSection* aLock, + HRESULT* aOutResult) + : BaseInputPin(aObjectName, aFilter, aLock, aOutResult, aObjectName), + mSegmentStartTime(0) +{ + MOZ_COUNT_CTOR(AudioSinkInputPin); + mSampleSink = new SampleSink(); +} + +AudioSinkInputPin::~AudioSinkInputPin() +{ + MOZ_COUNT_DTOR(AudioSinkInputPin); +} + +HRESULT +AudioSinkInputPin::GetMediaType(int aPosition, MediaType* aOutMediaType) +{ + NS_ENSURE_TRUE(aPosition >= 0, E_INVALIDARG); + NS_ENSURE_TRUE(aOutMediaType, E_POINTER); + + if (aPosition > 0) { + return S_FALSE; + } + + // Note: We set output as PCM, as IEEE_FLOAT only works when using the + // MP3 decoder as an MFT, and we can't do that while using DirectShow. + aOutMediaType->SetType(&MEDIATYPE_Audio); + aOutMediaType->SetSubtype(&MEDIASUBTYPE_PCM); + aOutMediaType->SetType(&FORMAT_WaveFormatEx); + aOutMediaType->SetTemporalCompression(FALSE); + + return S_OK; +} + +HRESULT +AudioSinkInputPin::CheckMediaType(const MediaType* aMediaType) +{ + if (!aMediaType) { + return E_INVALIDARG; + } + + GUID majorType = *aMediaType->Type(); + if (majorType != MEDIATYPE_Audio && majorType != WMMEDIATYPE_Audio) { + return E_INVALIDARG; + } + + if (*aMediaType->Subtype() != MEDIASUBTYPE_PCM) { + return E_INVALIDARG; + } + + if (*aMediaType->FormatType() != FORMAT_WaveFormatEx) { + return E_INVALIDARG; + } + + // We accept the media type, stash its layout format! + WAVEFORMATEX* wfx = (WAVEFORMATEX*)(aMediaType->pbFormat); + GetSampleSink()->SetAudioFormat(wfx); + + return S_OK; +} + +AudioSinkFilter* +AudioSinkInputPin::GetAudioSinkFilter() +{ + return reinterpret_cast<AudioSinkFilter*>(mFilter); +} + +SampleSink* +AudioSinkInputPin::GetSampleSink() +{ + return mSampleSink; +} + +HRESULT +AudioSinkInputPin::SetAbsoluteMediaTime(IMediaSample* aSample) +{ + HRESULT hr; + REFERENCE_TIME start = 0, end = 0; + hr = aSample->GetTime(&start, &end); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + { + CriticalSectionAutoEnter lock(*mLock); + start += mSegmentStartTime; + end += mSegmentStartTime; + } + hr = aSample->SetMediaTime(&start, &end); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + return S_OK; +} + +HRESULT +AudioSinkInputPin::Receive(IMediaSample* aSample ) +{ + HRESULT hr; + NS_ENSURE_TRUE(aSample, E_POINTER); + + hr = BaseInputPin::Receive(aSample); + if (SUCCEEDED(hr) && hr != S_FALSE) { // S_FALSE == flushing + // Set the timestamp of the sample after being adjusted for + // seeking/segments in the "media time" attribute. When we seek, + // DirectShow starts a new "segment", and starts labeling samples + // from time=0 again, so we need to correct for this to get the + // actual timestamps after seeking. + hr = SetAbsoluteMediaTime(aSample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + hr = GetSampleSink()->Receive(aSample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + } + return S_OK; +} + +already_AddRefed<IMediaSeeking> +AudioSinkInputPin::GetConnectedPinSeeking() +{ + RefPtr<IPin> peer = GetConnected(); + if (!peer) + return nullptr; + RefPtr<IMediaSeeking> seeking; + peer->QueryInterface(static_cast<IMediaSeeking**>(getter_AddRefs(seeking))); + return seeking.forget(); +} + +HRESULT +AudioSinkInputPin::BeginFlush() +{ + HRESULT hr = media::BaseInputPin::BeginFlush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + GetSampleSink()->Flush(); + + return S_OK; +} + +HRESULT +AudioSinkInputPin::EndFlush() +{ + HRESULT hr = media::BaseInputPin::EndFlush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Reset the EOS flag, so that if we're called after a seek we still work. + GetSampleSink()->Reset(); + + return S_OK; +} + +HRESULT +AudioSinkInputPin::EndOfStream(void) +{ + HRESULT hr = media::BaseInputPin::EndOfStream(); + if (FAILED(hr) || hr == S_FALSE) { + // Pin is stil flushing. + return hr; + } + GetSampleSink()->SetEOS(); + + return S_OK; +} + + +HRESULT +AudioSinkInputPin::NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + CriticalSectionAutoEnter lock(*mLock); + // Record the start time of the new segment, so that we can store the + // correct absolute timestamp in the "media time" each incoming sample. + mSegmentStartTime = tStart; + return S_OK; +} + +} // namespace mozilla + diff --git a/dom/media/directshow/AudioSinkInputPin.h b/dom/media/directshow/AudioSinkInputPin.h new file mode 100644 index 000000000..80503c641 --- /dev/null +++ b/dom/media/directshow/AudioSinkInputPin.h @@ -0,0 +1,76 @@ +/* -*- 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(AudioSinkInputPin_h_) +#define AudioSinkInputPin_h_ + +#include "BaseInputPin.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +namespace media { + class MediaType; +} + +class AudioSinkFilter; +class SampleSink; + + +// Input pin for capturing audio output of a DirectShow filter graph. +// This is the input pin for the AudioSinkFilter. +class AudioSinkInputPin: public mozilla::media::BaseInputPin +{ +public: + AudioSinkInputPin(wchar_t* aObjectName, + AudioSinkFilter* aFilter, + mozilla::CriticalSection* aLock, + HRESULT* aOutResult); + virtual ~AudioSinkInputPin(); + + HRESULT GetMediaType (IN int iPos, OUT mozilla::media::MediaType * pmt); + HRESULT CheckMediaType (IN const mozilla::media::MediaType * pmt); + STDMETHODIMP Receive (IN IMediaSample *); + STDMETHODIMP BeginFlush() override; + STDMETHODIMP EndFlush() override; + + // Called when we start decoding a new segment, that happens directly after + // a seek. This captures the segment's start time. Samples decoded by the + // MP3 decoder have their timestamps offset from the segment start time. + // Storing the segment start time enables us to set each sample's MediaTime + // as an offset in the stream relative to the start of the stream, rather + // than the start of the segment, i.e. its absolute time in the stream. + STDMETHODIMP NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) override; + + STDMETHODIMP EndOfStream() override; + + // Returns the IMediaSeeking interface of the connected output pin. + // We forward seeking requests upstream from the sink to the source + // filters. + already_AddRefed<IMediaSeeking> GetConnectedPinSeeking(); + + SampleSink* GetSampleSink(); + +private: + AudioSinkFilter* GetAudioSinkFilter(); + + // Sets the media time on the media sample, relative to the segment + // start time. + HRESULT SetAbsoluteMediaTime(IMediaSample* aSample); + + nsAutoPtr<SampleSink> mSampleSink; + + // Synchronized by the filter lock; BaseInputPin::mLock. + REFERENCE_TIME mSegmentStartTime; +}; + +} // namespace mozilla + +#endif // AudioSinkInputPin_h_ diff --git a/dom/media/directshow/DirectShowDecoder.cpp b/dom/media/directshow/DirectShowDecoder.cpp new file mode 100644 index 000000000..da68b4daa --- /dev/null +++ b/dom/media/directshow/DirectShowDecoder.cpp @@ -0,0 +1,65 @@ +/* -*- 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 "DirectShowDecoder.h" +#include "DirectShowReader.h" +#include "DirectShowUtils.h" +#include "MediaDecoderStateMachine.h" +#include "mozilla/Preferences.h" +#include "mozilla/WindowsVersion.h" + +namespace mozilla { + +MediaDecoderStateMachine* DirectShowDecoder::CreateStateMachine() +{ + return new MediaDecoderStateMachine(this, new DirectShowReader(this)); +} + +/* static */ +bool +DirectShowDecoder::GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList) +{ + if (!IsEnabled()) { + return false; + } + + static char const *const mp3AudioCodecs[] = { + "mp3", + nullptr + }; + if (aType.EqualsASCII("audio/mpeg") || + aType.EqualsASCII("audio/mp3")) { + if (aCodecList) { + *aCodecList = mp3AudioCodecs; + } + return true; + } + + return false; +} + +/* static */ +bool +DirectShowDecoder::IsEnabled() +{ + return CanDecodeMP3UsingDirectShow() && + Preferences::GetBool("media.directshow.enabled"); +} + +DirectShowDecoder::DirectShowDecoder(MediaDecoderOwner* aOwner) + : MediaDecoder(aOwner) +{ + MOZ_COUNT_CTOR(DirectShowDecoder); +} + +DirectShowDecoder::~DirectShowDecoder() +{ + MOZ_COUNT_DTOR(DirectShowDecoder); +} + +} // namespace mozilla + diff --git a/dom/media/directshow/DirectShowDecoder.h b/dom/media/directshow/DirectShowDecoder.h new file mode 100644 index 000000000..c4d371fbf --- /dev/null +++ b/dom/media/directshow/DirectShowDecoder.h @@ -0,0 +1,45 @@ +/* -*- 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(DirectShowDecoder_h_) +#define DirectShowDecoder_h_ + +#include "MediaDecoder.h" + +namespace mozilla { + +// Decoder that uses DirectShow to playback MP3 files only. +class DirectShowDecoder : public MediaDecoder +{ +public: + + explicit DirectShowDecoder(MediaDecoderOwner* aOwner); + virtual ~DirectShowDecoder(); + + MediaDecoder* Clone(MediaDecoderOwner* aOwner) override { + if (!IsEnabled()) { + return nullptr; + } + return new DirectShowDecoder(aOwner); + } + + MediaDecoderStateMachine* CreateStateMachine() override; + + // Returns true if aType is a MIME type that we render with the + // DirectShow backend. If aCodecList is non null, + // it is filled with a (static const) null-terminated list of strings + // denoting the codecs we'll playback. Note that playback is strictly + // limited to MP3 only. + static bool GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList); + + // Returns true if the DirectShow backend is preffed on. + static bool IsEnabled(); +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/DirectShowReader.cpp b/dom/media/directshow/DirectShowReader.cpp new file mode 100644 index 000000000..cacf6f8de --- /dev/null +++ b/dom/media/directshow/DirectShowReader.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DirectShowReader.h" +#include "MediaDecoderReader.h" +#include "mozilla/RefPtr.h" +#include "DirectShowUtils.h" +#include "AudioSinkFilter.h" +#include "SourceFilter.h" +#include "SampleSink.h" +#include "VideoUtils.h" + +using namespace mozilla::media; + +namespace mozilla { + +// Windows XP's MP3 decoder filter. This is available on XP only, on Vista +// and later we can use the DMO Wrapper filter and MP3 decoder DMO. +const GUID DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER = +{ 0x38BE3000, 0xDBF4, 0x11D0, {0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D} }; + + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder) + : MediaDecoderReader(aDecoder), + mMP3FrameParser(aDecoder->GetResource()->GetLength()), +#ifdef DIRECTSHOW_REGISTER_GRAPH + mRotRegister(0), +#endif + mNumChannels(0), + mAudioRate(0), + mBytesPerSample(0) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + MOZ_COUNT_CTOR(DirectShowReader); +} + +DirectShowReader::~DirectShowReader() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + MOZ_COUNT_DTOR(DirectShowReader); +#ifdef DIRECTSHOW_REGISTER_GRAPH + if (mRotRegister) { + RemoveGraphFromRunningObjectTable(mRotRegister); + } +#endif +} + +// Try to parse the MP3 stream to make sure this is indeed an MP3, get the +// estimated duration of the stream, and find the offset of the actual MP3 +// frames in the stream, as DirectShow doesn't like large ID3 sections. +static nsresult +ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource) +{ + const uint32_t MAX_READ_SIZE = 4096; + + uint64_t offset = 0; + while (aParser->NeedsData() && !aParser->ParsedHeaders()) { + uint32_t bytesRead; + char buffer[MAX_READ_SIZE]; + nsresult rv = aResource->ReadAt(offset, buffer, + MAX_READ_SIZE, &bytesRead); + NS_ENSURE_SUCCESS(rv, rv); + + if (!bytesRead) { + // End of stream. + return NS_ERROR_FAILURE; + } + + aParser->Parse(reinterpret_cast<uint8_t*>(buffer), bytesRead, offset); + offset += bytesRead; + } + + return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +DirectShowReader::ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) +{ + MOZ_ASSERT(OnTaskQueue()); + HRESULT hr; + nsresult rv; + + // Create the filter graph, reference it by the GraphBuilder interface, + // to make graph building more convenient. + hr = CoCreateInstance(CLSID_FilterGraph, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IGraphBuilder, + reinterpret_cast<void**>(static_cast<IGraphBuilder**>(getter_AddRefs(mGraph)))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE); + + rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource()); + NS_ENSURE_SUCCESS(rv, rv); + + #ifdef DIRECTSHOW_REGISTER_GRAPH + hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + #endif + + // Extract the interface pointers we'll need from the filter graph. + hr = mGraph->QueryInterface(static_cast<IMediaControl**>(getter_AddRefs(mControl))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE); + + hr = mGraph->QueryInterface(static_cast<IMediaSeeking**>(getter_AddRefs(mMediaSeeking))); + NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE); + + // Build the graph. Create the filters we need, and connect them. We + // build the entire graph ourselves to prevent other decoders installed + // on the system being created and used. + + // Our source filters, wraps the MediaResource. + mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio); + NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE); + + rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset()); + NS_ENSURE_SUCCESS(rv, rv); + + hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource"); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // The MPEG demuxer. + RefPtr<IBaseFilter> demuxer; + hr = CreateAndAddFilter(mGraph, + CLSID_MPEG1Splitter, + L"MPEG1Splitter", + getter_AddRefs(demuxer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // Platform MP3 decoder. + RefPtr<IBaseFilter> decoder; + // Firstly try to create the MP3 decoder filter that ships with WinXP + // directly. This filter doesn't normally exist on later versions of + // Windows. + hr = CreateAndAddFilter(mGraph, + CLSID_MPEG_LAYER_3_DECODER_FILTER, + L"MPEG Layer 3 Decoder", + getter_AddRefs(decoder)); + if (FAILED(hr)) { + // Failed to create MP3 decoder filter. Try to instantiate + // the MP3 decoder DMO. + hr = AddMP3DMOWrapperFilter(mGraph, getter_AddRefs(decoder)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + } + + // Sink, captures audio samples and inserts them into our pipeline. + static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter"; + mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr); + NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE); + hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // Join the filters. + hr = ConnectFilters(mGraph, mSourceFilter, demuxer); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = ConnectFilters(mGraph, demuxer, decoder); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + WAVEFORMATEX format; + mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format); + NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE); + + mInfo.mAudio.mChannels = mNumChannels = format.nChannels; + mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec; + mInfo.mAudio.mBitDepth = format.wBitsPerSample; + mBytesPerSample = format.wBitsPerSample / 8; + + // Begin decoding! + hr = mControl->Run(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + DWORD seekCaps = 0; + hr = mMediaSeeking->GetCapabilities(&seekCaps); + mInfo.mMediaSeekable = SUCCEEDED(hr) && (AM_SEEKING_CanSeekAbsolute & seekCaps); + + int64_t duration = mMP3FrameParser.GetDuration(); + if (SUCCEEDED(hr)) { + mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration)); + } + + LOG("Successfully initialized DirectShow MP3 decoder."); + LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d", + mInfo.mAudio.mChannels, + mInfo.mAudio.mRate, + RefTimeToUsecs(duration), + mBytesPerSample); + + *aInfo = mInfo; + // Note: The SourceFilter strips ID3v2 tags out of the stream. + *aTags = nullptr; + + return NS_OK; +} + +inline float +UnsignedByteToAudioSample(uint8_t aValue) +{ + return aValue * (2.0f / UINT8_MAX) - 1.0f; +} + +bool +DirectShowReader::Finish(HRESULT aStatus) +{ + MOZ_ASSERT(OnTaskQueue()); + + LOG("DirectShowReader::Finish(0x%x)", aStatus); + // Notify the filter graph of end of stream. + RefPtr<IMediaEventSink> eventSink; + HRESULT hr = mGraph->QueryInterface(static_cast<IMediaEventSink**>(getter_AddRefs(eventSink))); + if (SUCCEEDED(hr) && eventSink) { + eventSink->Notify(EC_COMPLETE, aStatus, 0); + } + return false; +} + +class DirectShowCopy +{ +public: + DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample, + uint32_t aSamples, uint32_t aChannels) + : mSource(aSource) + , mBytesPerSample(aBytesPerSample) + , mSamples(aSamples) + , mChannels(aChannels) + , mNextSample(0) + { } + + uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples) + { + uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample); + uint32_t frames = maxSamples / mChannels; + size_t byteOffset = mNextSample * mBytesPerSample; + if (mBytesPerSample == 1) { + for (uint32_t i = 0; i < maxSamples; ++i) { + uint8_t *sample = mSource + byteOffset; + aBuffer[i] = UnsignedByteToAudioSample(*sample); + byteOffset += mBytesPerSample; + } + } else if (mBytesPerSample == 2) { + for (uint32_t i = 0; i < maxSamples; ++i) { + int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset); + aBuffer[i] = AudioSampleToFloat(*sample); + byteOffset += mBytesPerSample; + } + } + mNextSample += maxSamples; + return frames; + } + +private: + uint8_t * const mSource; + const uint32_t mBytesPerSample; + const uint32_t mSamples; + const uint32_t mChannels; + uint32_t mNextSample; +}; + +bool +DirectShowReader::DecodeAudioData() +{ + MOZ_ASSERT(OnTaskQueue()); + HRESULT hr; + + SampleSink* sink = mAudioSinkFilter->GetSampleSink(); + if (sink->AtEOS()) { + // End of stream. + return Finish(S_OK); + } + + // Get the next chunk of audio samples. This blocks until the sample + // arrives, or an error occurs (like the stream is shutdown). + RefPtr<IMediaSample> sample; + hr = sink->Extract(sample); + if (FAILED(hr) || hr == S_FALSE) { + return Finish(hr); + } + + int64_t start = 0, end = 0; + sample->GetMediaTime(&start, &end); + LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]", + RefTimeToSeconds(start), + RefTimeToSeconds(end)); + + LONG length = sample->GetActualDataLength(); + LONG numSamples = length / mBytesPerSample; + LONG numFrames = length / mBytesPerSample / mNumChannels; + + BYTE* data = nullptr; + hr = sample->GetPointer(&data); + NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr)); + + mAudioCompactor.Push(mDecoder->GetResource()->Tell(), + RefTimeToUsecs(start), + mInfo.mAudio.mRate, + numFrames, + mNumChannels, + DirectShowCopy(reinterpret_cast<uint8_t *>(data), + mBytesPerSample, + numSamples, + mNumChannels)); + return true; +} + +bool +DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) +{ + MOZ_ASSERT(OnTaskQueue()); + return false; +} + +RefPtr<MediaDecoderReader::SeekPromise> +DirectShowReader::Seek(SeekTarget aTarget, int64_t aEndTime) +{ + nsresult res = SeekInternal(aTarget.GetTime().ToMicroseconds()); + if (NS_FAILED(res)) { + return SeekPromise::CreateAndReject(res, __func__); + } else { + return SeekPromise::CreateAndResolve(aTarget.GetTime(), __func__); + } +} + +nsresult +DirectShowReader::SeekInternal(int64_t aTargetUs) +{ + HRESULT hr; + MOZ_ASSERT(OnTaskQueue()); + + LOG("DirectShowReader::Seek() target=%lld", aTargetUs); + + hr = mControl->Pause(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + nsresult rv = ResetDecode(); + NS_ENSURE_SUCCESS(rv, rv); + + LONGLONG seekPosition = UsecsToRefTime(aTargetUs); + hr = mMediaSeeking->SetPositions(&seekPosition, + AM_SEEKING_AbsolutePositioning, + nullptr, + AM_SEEKING_NoPositioning); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = mControl->Run(); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/directshow/DirectShowReader.h b/dom/media/directshow/DirectShowReader.h new file mode 100644 index 000000000..e1326d416 --- /dev/null +++ b/dom/media/directshow/DirectShowReader.h @@ -0,0 +1,110 @@ +/* -*- 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(DirectShowReader_h_) +#define DirectShowReader_h_ + +#include "windows.h" // HRESULT, DWORD +#include "MediaDecoderReader.h" +#include "MediaResource.h" +#include "mozilla/RefPtr.h" +#include "MP3FrameParser.h" + +// Add the graph to the Running Object Table so that we can connect +// to this graph with GraphEdit/GraphStudio. Note: on Vista and up you must +// also regsvr32 proppage.dll from the Windows SDK. +// See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx +// #define DIRECTSHOW_REGISTER_GRAPH + +struct IGraphBuilder; +struct IMediaControl; +struct IMediaSeeking; + +namespace mozilla { + +class AudioSinkFilter; +class SourceFilter; + +// Decoder backend for decoding MP3 using DirectShow. DirectShow operates as +// a filter graph. The basic design of the DirectShowReader is that we have +// a SourceFilter that wraps the MediaResource that connects to the +// MP3 decoder filter. The MP3 decoder filter "pulls" data as it requires it +// downstream on its own thread. When the MP3 decoder has produced a block of +// decoded samples, its thread calls downstream into our AudioSinkFilter, +// passing the decoded buffer in. The AudioSinkFilter inserts the samples into +// a SampleSink object. The SampleSink blocks the MP3 decoder's thread until +// the decode thread calls DecodeAudioData(), whereupon the SampleSink +// releases the decoded samples to the decode thread, and unblocks the MP3 +// decoder's thread. The MP3 decoder can then request more data from the +// SourceFilter, and decode more data. If the decode thread calls +// DecodeAudioData() and there's no decoded samples waiting to be extracted +// in the SampleSink, the SampleSink blocks the decode thread until the MP3 +// decoder produces a decoded sample. +class DirectShowReader : public MediaDecoderReader +{ +public: + DirectShowReader(AbstractMediaDecoder* aDecoder); + + virtual ~DirectShowReader(); + + bool DecodeAudioData() override; + bool DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) override; + + nsresult ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) override; + + RefPtr<SeekPromise> + Seek(SeekTarget aTarget, int64_t aEndTime) override; + + static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER; + +private: + // Notifies the filter graph that playback is complete. aStatus is + // the code to send to the filter graph. Always returns false, so + // that we can just "return Finish()" from DecodeAudioData(). + bool Finish(HRESULT aStatus); + + nsresult SeekInternal(int64_t aTime); + + // DirectShow filter graph, and associated playback and seeking + // control interfaces. + RefPtr<IGraphBuilder> mGraph; + RefPtr<IMediaControl> mControl; + RefPtr<IMediaSeeking> mMediaSeeking; + + // Wraps the MediaResource, and feeds undecoded data into the filter graph. + RefPtr<SourceFilter> mSourceFilter; + + // Sits at the end of the graph, removing decoded samples from the graph. + // The graph will block while this is blocked, i.e. it will pause decoding. + RefPtr<AudioSinkFilter> mAudioSinkFilter; + + // Some MP3s are variable bitrate, so DirectShow's duration estimation + // can make its duration estimation based on the wrong bitrate. So we parse + // the MP3 frames to get a more accuate estimate of the duration. + MP3FrameParser mMP3FrameParser; + +#ifdef DIRECTSHOW_REGISTER_GRAPH + // Used to add/remove the filter graph to the Running Object Table. You can + // connect GraphEdit/GraphStudio to the graph to observe and/or debug its + // topology and state. + DWORD mRotRegister; +#endif + + // Number of channels in the audio stream. + uint32_t mNumChannels; + + // Samples per second in the audio stream. + uint32_t mAudioRate; + + // Number of bytes per sample. Can be either 1 or 2. + uint32_t mBytesPerSample; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/DirectShowUtils.cpp b/dom/media/directshow/DirectShowUtils.cpp new file mode 100644 index 000000000..b2afa7528 --- /dev/null +++ b/dom/media/directshow/DirectShowUtils.cpp @@ -0,0 +1,369 @@ +/* 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 "DirectShowUtils.h" +#include "dmodshow.h" +#include "wmcodecdsp.h" +#include "dmoreg.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/RefPtr.h" +#include "nsPrintfCString.h" + +#define WARN(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get()) + +namespace mozilla { + +// Create a table which maps GUIDs to a string representation of the GUID. +// This is useful for debugging purposes, for logging the GUIDs of media types. +// This is only available when logging is enabled, i.e. not in release builds. +struct GuidToName { + const char* name; + const GUID guid; +}; + +#pragma push_macro("OUR_GUID_ENTRY") +#undef OUR_GUID_ENTRY +#define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + { #name, {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} }, + +static const GuidToName GuidToNameTable[] = { +#include <uuids.h> +}; + +#pragma pop_macro("OUR_GUID_ENTRY") + +const char* +GetDirectShowGuidName(const GUID& aGuid) +{ + const size_t len = ArrayLength(GuidToNameTable); + for (unsigned i = 0; i < len; i++) { + if (IsEqualGUID(aGuid, GuidToNameTable[i].guid)) { + return GuidToNameTable[i].name; + } + } + return "Unknown"; +} + +void +RemoveGraphFromRunningObjectTable(DWORD aRotRegister) +{ + RefPtr<IRunningObjectTable> runningObjectTable; + if (SUCCEEDED(GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)))) { + runningObjectTable->Revoke(aRotRegister); + } +} + +HRESULT +AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister) +{ + HRESULT hr; + + RefPtr<IMoniker> moniker; + RefPtr<IRunningObjectTable> runningObjectTable; + + hr = GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + const size_t STRING_LENGTH = 256; + WCHAR wsz[STRING_LENGTH]; + + StringCchPrintfW(wsz, + STRING_LENGTH, + L"FilterGraph %08x pid %08x", + (DWORD_PTR)aUnkGraph, + GetCurrentProcessId()); + + hr = CreateItemMoniker(L"!", wsz, getter_AddRefs(moniker)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = runningObjectTable->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, + aUnkGraph, + moniker, + aOutRotRegister); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +const char* +GetGraphNotifyString(long evCode) +{ +#define CASE(x) case x: return #x + switch(evCode) { + CASE(EC_ACTIVATE); // A video window is being activated or deactivated. + CASE(EC_BANDWIDTHCHANGE); // Not supported. + CASE(EC_BUFFERING_DATA); // The graph is buffering data, or has stopped buffering data. + CASE(EC_BUILT); // Send by the Video Control when a graph has been built. Not forwarded to applications. + CASE(EC_CLOCK_CHANGED); // The reference clock has changed. + CASE(EC_CLOCK_UNSET); // The clock provider was disconnected. + CASE(EC_CODECAPI_EVENT); // Sent by an encoder to signal an encoding event. + CASE(EC_COMPLETE); // All data from a particular stream has been rendered. + CASE(EC_CONTENTPROPERTY_CHANGED); // Not supported. + CASE(EC_DEVICE_LOST); // A Plug and Play device was removed or has become available again. + CASE(EC_DISPLAY_CHANGED); // The display mode has changed. + CASE(EC_END_OF_SEGMENT); // The end of a segment has been reached. + CASE(EC_EOS_SOON); // Not supported. + CASE(EC_ERROR_STILLPLAYING); // An asynchronous command to run the graph has failed. + CASE(EC_ERRORABORT); // An operation was aborted because of an error. + CASE(EC_ERRORABORTEX); // An operation was aborted because of an error. + CASE(EC_EXTDEVICE_MODE_CHANGE); // Not supported. + CASE(EC_FILE_CLOSED); // The source file was closed because of an unexpected event. + CASE(EC_FULLSCREEN_LOST); // The video renderer is switching out of full-screen mode. + CASE(EC_GRAPH_CHANGED); // The filter graph has changed. + CASE(EC_LENGTH_CHANGED); // The length of a source has changed. + CASE(EC_LOADSTATUS); // Notifies the application of progress when opening a network file. + CASE(EC_MARKER_HIT); // Not supported. + CASE(EC_NEED_RESTART); // A filter is requesting that the graph be restarted. + CASE(EC_NEW_PIN); // Not supported. + CASE(EC_NOTIFY_WINDOW); // Notifies a filter of the video renderer's window. + CASE(EC_OLE_EVENT); // A filter is passing a text string to the application. + CASE(EC_OPENING_FILE); // The graph is opening a file, or has finished opening a file. + CASE(EC_PALETTE_CHANGED); // The video palette has changed. + CASE(EC_PAUSED); // A pause request has completed. + CASE(EC_PLEASE_REOPEN); // The source file has changed. + CASE(EC_PREPROCESS_COMPLETE); // Sent by the WM ASF Writer filter when it completes the pre-processing for multipass encoding. + CASE(EC_PROCESSING_LATENCY); // Indicates the amount of time that a component is taking to process each sample. + CASE(EC_QUALITY_CHANGE); // The graph is dropping samples, for quality control. + //CASE(EC_RENDER_FINISHED); // Not supported. + CASE(EC_REPAINT); // A video renderer requires a repaint. + CASE(EC_SAMPLE_LATENCY); // Specifies how far behind schedule a component is for processing samples. + //CASE(EC_SAMPLE_NEEDED); // Requests a new input sample from the Enhanced Video Renderer (EVR) filter. + CASE(EC_SCRUB_TIME); // Specifies the time stamp for the most recent frame step. + CASE(EC_SEGMENT_STARTED); // A new segment has started. + CASE(EC_SHUTTING_DOWN); // The filter graph is shutting down, prior to being destroyed. + CASE(EC_SNDDEV_IN_ERROR); // A device error has occurred in an audio capture filter. + CASE(EC_SNDDEV_OUT_ERROR); // A device error has occurred in an audio renderer filter. + CASE(EC_STARVATION); // A filter is not receiving enough data. + CASE(EC_STATE_CHANGE); // The filter graph has changed state. + CASE(EC_STATUS); // Contains two arbitrary status strings. + CASE(EC_STEP_COMPLETE); // A filter performing frame stepping has stepped the specified number of frames. + CASE(EC_STREAM_CONTROL_STARTED); // A stream-control start command has taken effect. + CASE(EC_STREAM_CONTROL_STOPPED); // A stream-control stop command has taken effect. + CASE(EC_STREAM_ERROR_STILLPLAYING); // An error has occurred in a stream. The stream is still playing. + CASE(EC_STREAM_ERROR_STOPPED); // A stream has stopped because of an error. + CASE(EC_TIMECODE_AVAILABLE); // Not supported. + CASE(EC_UNBUILT); // Send by the Video Control when a graph has been torn down. Not forwarded to applications. + CASE(EC_USERABORT); // The user has terminated playback. + CASE(EC_VIDEO_SIZE_CHANGED); // The native video size has changed. + CASE(EC_VIDEOFRAMEREADY); // A video frame is ready for display. + CASE(EC_VMR_RECONNECTION_FAILED); // Sent by the VMR-7 and the VMR-9 when it was unable to accept a dynamic format change request from the upstream decoder. + CASE(EC_VMR_RENDERDEVICE_SET); // Sent when the VMR has selected its rendering mechanism. + CASE(EC_VMR_SURFACE_FLIPPED); // Sent when the VMR-7's allocator presenter has called the DirectDraw Flip method on the surface being presented. + CASE(EC_WINDOW_DESTROYED); // The video renderer was destroyed or removed from the graph. + CASE(EC_WMT_EVENT); // Sent by the WM ASF Reader filter when it reads ASF files protected by digital rights management (DRM). + CASE(EC_WMT_INDEX_EVENT); // Sent when an application uses the WM ASF Writer to index Windows Media Video files. + CASE(S_OK); // Success. + CASE(VFW_S_AUDIO_NOT_RENDERED); // Partial success; the audio was not rendered. + CASE(VFW_S_DUPLICATE_NAME); // Success; the Filter Graph Manager modified a filter name to avoid duplication. + CASE(VFW_S_PARTIAL_RENDER); // Partial success; some of the streams in this movie are in an unsupported format. + CASE(VFW_S_VIDEO_NOT_RENDERED); // Partial success; the video was not rendered. + CASE(E_ABORT); // Operation aborted. + CASE(E_OUTOFMEMORY); // Insufficient memory. + CASE(E_POINTER); // Null pointer argument. + CASE(VFW_E_CANNOT_CONNECT); // No combination of intermediate filters could be found to make the connection. + CASE(VFW_E_CANNOT_RENDER); // No combination of filters could be found to render the stream. + CASE(VFW_E_NO_ACCEPTABLE_TYPES); // There is no common media type between these pins. + CASE(VFW_E_NOT_IN_GRAPH); + + default: + return "Unknown Code"; + }; +#undef CASE +} + +HRESULT +CreateAndAddFilter(IGraphBuilder* aGraph, + REFGUID aFilterClsId, + LPCWSTR aFilterName, + IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aGraph, E_POINTER); + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + RefPtr<IBaseFilter> filter; + hr = CoCreateInstance(aFilterClsId, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)); + if (FAILED(hr)) { + // Object probably not available on this system. + WARN("CoCreateInstance failed, hr=%x", hr); + return hr; + } + + hr = aGraph->AddFilter(filter, aFilterName); + if (FAILED(hr)) { + WARN("AddFilter failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +HRESULT +CreateMP3DMOWrapperFilter(IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + // Create the wrapper filter. + RefPtr<IBaseFilter> filter; + hr = CoCreateInstance(CLSID_DMOWrapperFilter, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)); + if (FAILED(hr)) { + WARN("CoCreateInstance failed, hr=%x", hr); + return hr; + } + + // Query for IDMOWrapperFilter. + RefPtr<IDMOWrapperFilter> dmoWrapper; + hr = filter->QueryInterface(IID_IDMOWrapperFilter, + getter_AddRefs(dmoWrapper)); + if (FAILED(hr)) { + WARN("QueryInterface failed, hr=%x", hr); + return hr; + } + + hr = dmoWrapper->Init(CLSID_CMP3DecMediaObject, DMOCATEGORY_AUDIO_DECODER); + if (FAILED(hr)) { + // Can't instantiate MP3 DMO. It doesn't exist on Windows XP, we're + // probably hitting that. Don't log warning to console, this is an + // expected error. + WARN("dmoWrapper Init failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +HRESULT +AddMP3DMOWrapperFilter(IGraphBuilder* aGraph, + IBaseFilter **aOutFilter) +{ + NS_ENSURE_TRUE(aGraph, E_POINTER); + NS_ENSURE_TRUE(aOutFilter, E_POINTER); + HRESULT hr; + + // Create the wrapper filter. + RefPtr<IBaseFilter> filter; + hr = CreateMP3DMOWrapperFilter(getter_AddRefs(filter)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Add the wrapper filter to graph. + hr = aGraph->AddFilter(filter, L"MP3 Decoder DMO"); + if (FAILED(hr)) { + WARN("AddFilter failed, hr=%x", hr); + return hr; + } + + filter.forget(aOutFilter); + + return S_OK; +} + +bool +CanDecodeMP3UsingDirectShow() +{ + RefPtr<IBaseFilter> filter; + + // Can we create the MP3 demuxer filter? + if (FAILED(CoCreateInstance(CLSID_MPEG1Splitter, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter)))) { + return false; + } + + // Can we create either the WinXP MP3 decoder filter or the MP3 DMO decoder? + if (FAILED(CoCreateInstance(DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IBaseFilter, + getter_AddRefs(filter))) && + FAILED(CreateMP3DMOWrapperFilter(getter_AddRefs(filter)))) { + return false; + } + + // Else, we can create all of the components we need. Assume + // DirectShow is going to work... + return true; +} + +// Match a pin by pin direction and connection state. +HRESULT +MatchUnconnectedPin(IPin* aPin, + PIN_DIRECTION aPinDir, + bool *aOutMatches) +{ + NS_ENSURE_TRUE(aPin, E_POINTER); + NS_ENSURE_TRUE(aOutMatches, E_POINTER); + + // Ensure the pin is unconnected. + RefPtr<IPin> peer; + HRESULT hr = aPin->ConnectedTo(getter_AddRefs(peer)); + if (hr != VFW_E_NOT_CONNECTED) { + *aOutMatches = false; + return hr; + } + + // Ensure the pin is of the specified direction. + PIN_DIRECTION pinDir; + hr = aPin->QueryDirection(&pinDir); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + *aOutMatches = (pinDir == aPinDir); + return S_OK; +} + +// Return the first unconnected input pin or output pin. +already_AddRefed<IPin> +GetUnconnectedPin(IBaseFilter* aFilter, PIN_DIRECTION aPinDir) +{ + RefPtr<IEnumPins> enumPins; + + HRESULT hr = aFilter->EnumPins(getter_AddRefs(enumPins)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + // Test each pin to see if it matches the direction we're looking for. + RefPtr<IPin> pin; + while (S_OK == enumPins->Next(1, getter_AddRefs(pin), nullptr)) { + bool matches = FALSE; + if (SUCCEEDED(MatchUnconnectedPin(pin, aPinDir, &matches)) && + matches) { + return pin.forget(); + } + } + + return nullptr; +} + +HRESULT +ConnectFilters(IGraphBuilder* aGraph, + IBaseFilter* aOutputFilter, + IBaseFilter* aInputFilter) +{ + RefPtr<IPin> output = GetUnconnectedPin(aOutputFilter, PINDIR_OUTPUT); + NS_ENSURE_TRUE(output, E_FAIL); + + RefPtr<IPin> input = GetUnconnectedPin(aInputFilter, PINDIR_INPUT); + NS_ENSURE_TRUE(output, E_FAIL); + + return aGraph->Connect(output, input); +} + +} // namespace mozilla + +// avoid redefined macro in unified build +#undef WARN diff --git a/dom/media/directshow/DirectShowUtils.h b/dom/media/directshow/DirectShowUtils.h new file mode 100644 index 000000000..3bbc122fc --- /dev/null +++ b/dom/media/directshow/DirectShowUtils.h @@ -0,0 +1,125 @@ +/* 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/. */ + +#ifndef _DirectShowUtils_h_ +#define _DirectShowUtils_h_ + +#include <stdint.h> +#include "dshow.h" + +// XXXbz windowsx.h defines GetFirstChild, GetNextSibling, +// GetPrevSibling are macros, apparently... Eeevil. We have functions +// called that on some classes, so undef them. +#undef GetFirstChild +#undef GetNextSibling +#undef GetPrevSibling + +#include "DShowTools.h" +#include "mozilla/Logging.h" + +namespace mozilla { + +// Win32 "Event" wrapper. Must be paired with a CriticalSection to create a +// Java-style "monitor". +class Signal { +public: + + Signal(CriticalSection* aLock) + : mLock(aLock) + { + CriticalSectionAutoEnter lock(*mLock); + mEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + } + + ~Signal() { + CriticalSectionAutoEnter lock(*mLock); + CloseHandle(mEvent); + } + + // Lock must be held. + void Notify() { + SetEvent(mEvent); + } + + // Lock must be held. Check the wait condition before waiting! + HRESULT Wait() { + mLock->Leave(); + DWORD result = WaitForSingleObject(mEvent, INFINITE); + mLock->Enter(); + return result == WAIT_OBJECT_0 ? S_OK : E_FAIL; + } + +private: + CriticalSection* mLock; + HANDLE mEvent; +}; + +HRESULT +AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister); + +void +RemoveGraphFromRunningObjectTable(DWORD aRotRegister); + +const char* +GetGraphNotifyString(long evCode); + +// Creates a filter and adds it to a graph. +HRESULT +CreateAndAddFilter(IGraphBuilder* aGraph, + REFGUID aFilterClsId, + LPCWSTR aFilterName, + IBaseFilter **aOutFilter); + +HRESULT +AddMP3DMOWrapperFilter(IGraphBuilder* aGraph, + IBaseFilter **aOutFilter); + +// Connects the output pin on aOutputFilter to an input pin on +// aInputFilter, in aGraph. +HRESULT +ConnectFilters(IGraphBuilder* aGraph, + IBaseFilter* aOutputFilter, + IBaseFilter* aInputFilter); + +HRESULT +MatchUnconnectedPin(IPin* aPin, + PIN_DIRECTION aPinDir, + bool *aOutMatches); + +// Converts from microseconds to DirectShow "Reference Time" +// (hundreds of nanoseconds). +inline int64_t +UsecsToRefTime(const int64_t aUsecs) +{ + return aUsecs * 10; +} + +// Converts from DirectShow "Reference Time" (hundreds of nanoseconds) +// to microseconds. +inline int64_t +RefTimeToUsecs(const int64_t hRefTime) +{ + return hRefTime / 10; +} + +// Converts from DirectShow "Reference Time" (hundreds of nanoseconds) +// to seconds. +inline double +RefTimeToSeconds(const REFERENCE_TIME aRefTime) +{ + return double(aRefTime) / 10000000; +} + +const char* +GetDirectShowGuidName(const GUID& aGuid); + +// Returns true if we can instantiate an MP3 demuxer and decoder filters. +// Use this to detect whether MP3 support is installed. +bool +CanDecodeMP3UsingDirectShow(); + +} // namespace mozilla + +#endif diff --git a/dom/media/directshow/SampleSink.cpp b/dom/media/directshow/SampleSink.cpp new file mode 100644 index 000000000..fa5dc8d19 --- /dev/null +++ b/dom/media/directshow/SampleSink.cpp @@ -0,0 +1,159 @@ +/* -*- 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 "SampleSink.h" +#include "AudioSinkFilter.h" +#include "AudioSinkInputPin.h" +#include "VideoUtils.h" +#include "mozilla/Logging.h" + +using namespace mozilla::media; + +namespace mozilla { + +static LazyLogModule gDirectShowLog("DirectShowDecoder"); +#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +SampleSink::SampleSink() + : mMonitor("SampleSink"), + mIsFlushing(false), + mAtEOS(false) +{ + MOZ_COUNT_CTOR(SampleSink); +} + +SampleSink::~SampleSink() +{ + MOZ_COUNT_DTOR(SampleSink); +} + +void +SampleSink::SetAudioFormat(const WAVEFORMATEX* aInFormat) +{ + NS_ENSURE_TRUE(aInFormat, ); + ReentrantMonitorAutoEnter mon(mMonitor); + memcpy(&mAudioFormat, aInFormat, sizeof(WAVEFORMATEX)); +} + +void +SampleSink::GetAudioFormat(WAVEFORMATEX* aOutFormat) +{ + MOZ_ASSERT(aOutFormat); + ReentrantMonitorAutoEnter mon(mMonitor); + memcpy(aOutFormat, &mAudioFormat, sizeof(WAVEFORMATEX)); +} + +HRESULT +SampleSink::Receive(IMediaSample* aSample) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + while (true) { + if (mIsFlushing) { + return S_FALSE; + } + if (!mSample) { + break; + } + if (mAtEOS) { + return E_UNEXPECTED; + } + // Wait until the consumer thread consumes the sample. + mon.Wait(); + } + + if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) { + REFERENCE_TIME start = 0, end = 0; + HRESULT hr = aSample->GetMediaTime(&start, &end); + LOG("SampleSink::Receive() [%4.2lf-%4.2lf]", + (double)RefTimeToUsecs(start) / USECS_PER_S, + (double)RefTimeToUsecs(end) / USECS_PER_S); + } + + mSample = aSample; + // Notify the signal, to awaken the consumer thread in WaitForSample() + // if necessary. + mon.NotifyAll(); + return S_OK; +} + +HRESULT +SampleSink::Extract(RefPtr<IMediaSample>& aOutSample) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + // Loop until we have a sample, or we should abort. + while (true) { + if (mIsFlushing) { + return S_FALSE; + } + if (mSample) { + break; + } + if (mAtEOS) { + // Order is important here, if we have a sample, we should return it + // before reporting EOS. + return E_UNEXPECTED; + } + // Wait until the producer thread gives us a sample. + mon.Wait(); + } + aOutSample = mSample; + + if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) { + int64_t start = 0, end = 0; + mSample->GetMediaTime(&start, &end); + LOG("SampleSink::Extract() [%4.2lf-%4.2lf]", + (double)RefTimeToUsecs(start) / USECS_PER_S, + (double)RefTimeToUsecs(end) / USECS_PER_S); + } + + mSample = nullptr; + // Notify the signal, to awaken the producer thread in Receive() + // if necessary. + mon.NotifyAll(); + return S_OK; +} + +void +SampleSink::Flush() +{ + LOG("SampleSink::Flush()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mIsFlushing = true; + mSample = nullptr; + mon.NotifyAll(); +} + +void +SampleSink::Reset() +{ + LOG("SampleSink::Reset()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mIsFlushing = false; + mAtEOS = false; +} + +void +SampleSink::SetEOS() +{ + LOG("SampleSink::SetEOS()"); + ReentrantMonitorAutoEnter mon(mMonitor); + mAtEOS = true; + // Notify to unblock any threads waiting for samples in + // Extract() or Receive(). Now that we're at EOS, no more samples + // will come! + mon.NotifyAll(); +} + +bool +SampleSink::AtEOS() +{ + ReentrantMonitorAutoEnter mon(mMonitor); + return mAtEOS && !mSample; +} + +} // namespace mozilla + diff --git a/dom/media/directshow/SampleSink.h b/dom/media/directshow/SampleSink.h new file mode 100644 index 000000000..6a1af9fee --- /dev/null +++ b/dom/media/directshow/SampleSink.h @@ -0,0 +1,67 @@ +/* -*- 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(SampleSink_h_) +#define SampleSink_h_ + +#include "BaseFilter.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ReentrantMonitor.h" + +namespace mozilla { + +class SampleSink { +public: + SampleSink(); + virtual ~SampleSink(); + + // Sets the audio format of the incoming samples. The upstream filter + // calls this. This makes a copy. + void SetAudioFormat(const WAVEFORMATEX* aInFormat); + + // Copies the format of incoming audio samples into into *aOutFormat. + void GetAudioFormat(WAVEFORMATEX* aOutFormat); + + // Called when a sample is delivered by the DirectShow graph to the sink. + // The decode thread retrieves the sample by calling WaitForSample(). + // Blocks if there's already a sample waiting to be consumed by the decode + // thread. + HRESULT Receive(IMediaSample* aSample); + + // Retrieves a sample from the sample queue, blocking until one becomes + // available, or until an error occurs. Returns S_FALSE on EOS. + HRESULT Extract(RefPtr<IMediaSample>& aOutSample); + + // Unblocks any threads waiting in GetSample(). + // Clears mSample, which unblocks upstream stream. + void Flush(); + + // Opens up the sink to receive more samples in PutSample(). + // Clears EOS flag. + void Reset(); + + // Marks that we've reacehd the end of stream. + void SetEOS(); + + // Returns whether we're at end of stream. + bool AtEOS(); + +private: + // All data in this class is syncronized by mMonitor. + ReentrantMonitor mMonitor; + RefPtr<IMediaSample> mSample; + + // Format of the audio stream we're receiving. + WAVEFORMATEX mAudioFormat; + + bool mIsFlushing; + bool mAtEOS; +}; + +} // namespace mozilla + +#endif // SampleSink_h_ 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 diff --git a/dom/media/directshow/SourceFilter.h b/dom/media/directshow/SourceFilter.h new file mode 100644 index 000000000..d5ce2770e --- /dev/null +++ b/dom/media/directshow/SourceFilter.h @@ -0,0 +1,75 @@ +/* -*- 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(nsDirectShowSource_h___) +#define nsDirectShowSource_h___ + +#include "BaseFilter.h" +#include "BasePin.h" +#include "MediaType.h" + +#include "nsDeque.h" +#include "nsAutoPtr.h" +#include "DirectShowUtils.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class MediaResource; +class OutputPin; + + +// SourceFilter is an asynchronous DirectShow source filter which +// reads from an MediaResource, and supplies data via a pull model downstream +// using OutputPin. It us used to supply a generic byte stream into +// DirectShow. +// +// Implements: +// * IBaseFilter +// * IMediaFilter +// * IPersist +// * IUnknown +// +class DECLSPEC_UUID("5c2a7ad0-ba82-4659-9178-c4719a2765d6") +SourceFilter : public media::BaseFilter +{ +public: + + // Constructs source filter to deliver given media type. + SourceFilter(const GUID& aMajorType, const GUID& aSubType); + ~SourceFilter(); + + nsresult Init(MediaResource *aResource, int64_t aMP3Offset); + + // BaseFilter overrides. + // Only one output - the byte stream. + int GetPinCount() override { return 1; } + + media::BasePin* GetPin(int n) override; + + // Get's the media type we're supplying. + const media::MediaType* GetMediaType() const; + + uint32_t GetAndResetBytesConsumedCount(); + +protected: + + // Our async pull output pin. + nsAutoPtr<OutputPin> mOutputPin; + + // Type of byte stream we output. + media::MediaType mMediaType; + +}; + +} // namespace mozilla + +// For mingw __uuidof support +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(mozilla::SourceFilter, 0x5c2a7ad0,0xba82,0x4659,0x91,0x78,0xc4,0x71,0x9a,0x27,0x65,0xd6); +#endif + +#endif diff --git a/dom/media/directshow/moz.build b/dom/media/directshow/moz.build new file mode 100644 index 000000000..8a9b76200 --- /dev/null +++ b/dom/media/directshow/moz.build @@ -0,0 +1,41 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'AudioSinkFilter.h', + 'AudioSinkInputPin.h', + 'DirectShowDecoder.h', + 'DirectShowReader.h', + 'DirectShowUtils.h', +] + +UNIFIED_SOURCES += [ + 'DirectShowDecoder.cpp', + 'DirectShowUtils.cpp', + 'SourceFilter.cpp', +] + +SOURCES += [ + 'AudioSinkFilter.cpp', + 'AudioSinkInputPin.cpp', + 'DirectShowReader.cpp', + 'SampleSink.cpp', +] + +# If WebRTC isn't being built, we need to compile the DirectShow base classes so that +# they're available at link time. +if not CONFIG['MOZ_WEBRTC']: + SOURCES += [ + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseFilter.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseInputPin.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/BasePin.cpp', + '/media/webrtc/trunk/webrtc/modules/video_capture/windows/MediaType.cpp', + ] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/media/webrtc/trunk/webrtc/modules/video_capture/windows', +] |