summaryrefslogtreecommitdiffstats
path: root/dom/media/directshow
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/directshow')
-rw-r--r--dom/media/directshow/AudioSinkFilter.cpp285
-rw-r--r--dom/media/directshow/AudioSinkFilter.h95
-rw-r--r--dom/media/directshow/AudioSinkInputPin.cpp195
-rw-r--r--dom/media/directshow/AudioSinkInputPin.h76
-rw-r--r--dom/media/directshow/DirectShowDecoder.cpp65
-rw-r--r--dom/media/directshow/DirectShowDecoder.h45
-rw-r--r--dom/media/directshow/DirectShowReader.cpp360
-rw-r--r--dom/media/directshow/DirectShowReader.h110
-rw-r--r--dom/media/directshow/DirectShowUtils.cpp369
-rw-r--r--dom/media/directshow/DirectShowUtils.h125
-rw-r--r--dom/media/directshow/SampleSink.cpp159
-rw-r--r--dom/media/directshow/SampleSink.h67
-rw-r--r--dom/media/directshow/SourceFilter.cpp683
-rw-r--r--dom/media/directshow/SourceFilter.h75
-rw-r--r--dom/media/directshow/moz.build41
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',
+]