mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 23:37:33 +09:00
Drag DirectShow back from the dead, force H264 support
This commit is contained in:
parent
14b52e7fc8
commit
96bc206271
19 changed files with 3024 additions and 0 deletions
|
|
@ -34,6 +34,11 @@
|
||||||
#include "FlacDecoder.h"
|
#include "FlacDecoder.h"
|
||||||
#include "FlacDemuxer.h"
|
#include "FlacDemuxer.h"
|
||||||
|
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
#include "DirectShowDecoder.h"
|
||||||
|
#include "DirectShowReader.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "nsPluginHost.h"
|
#include "nsPluginHost.h"
|
||||||
#include "MediaPrefs.h"
|
#include "MediaPrefs.h"
|
||||||
|
|
||||||
|
|
@ -138,6 +143,14 @@ IsFlacSupportedType(const nsACString& aType,
|
||||||
return FlacDecoder::CanHandleMediaType(aType, aCodecs);
|
return FlacDecoder::CanHandleMediaType(aType, aCodecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
static bool
|
||||||
|
IsDirectShowSupportedType(const nsACString& aType)
|
||||||
|
{
|
||||||
|
return DirectShowDecoder::GetSupportedCodecs(aType, nullptr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static
|
static
|
||||||
CanPlayStatus
|
CanPlayStatus
|
||||||
CanHandleCodecsType(const MediaContentType& aType,
|
CanHandleCodecsType(const MediaContentType& aType,
|
||||||
|
|
@ -197,6 +210,11 @@ CanHandleCodecsType(const MediaContentType& aType,
|
||||||
if (IsFlacSupportedType(aType.GetMIMEType(), aType.GetCodecs())) {
|
if (IsFlacSupportedType(aType.GetMIMEType(), aType.GetCodecs())) {
|
||||||
return CANPLAY_YES;
|
return CANPLAY_YES;
|
||||||
}
|
}
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
if (IsDirectShowSupportedType(aType.GetMIMEType())) {
|
||||||
|
return CANPLAY_YES;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (!codecList) {
|
if (!codecList) {
|
||||||
return CANPLAY_MAYBE;
|
return CANPLAY_MAYBE;
|
||||||
}
|
}
|
||||||
|
|
@ -258,6 +276,11 @@ CanHandleMediaType(const MediaContentType& aType,
|
||||||
if (IsFlacSupportedType(aType.GetMIMEType())) {
|
if (IsFlacSupportedType(aType.GetMIMEType())) {
|
||||||
return CANPLAY_MAYBE;
|
return CANPLAY_MAYBE;
|
||||||
}
|
}
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
if (IsDirectShowSupportedType(aType.GetMIMEType())) {
|
||||||
|
return CANPLAY_MAYBE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return CANPLAY_NO;
|
return CANPLAY_NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,6 +370,13 @@ InstantiateDecoder(const nsACString& aType,
|
||||||
return decoder.forget();
|
return decoder.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
if (IsDirectShowSupportedType(aType)) {
|
||||||
|
decoder = new DirectShowDecoder(aOwner);
|
||||||
|
return decoder.forget();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,6 +423,11 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
|
||||||
decoderReader =
|
decoderReader =
|
||||||
new MediaFormatReader(aDecoder, new WebMDemuxer(aDecoder->GetResource()));
|
new MediaFormatReader(aDecoder, new WebMDemuxer(aDecoder->GetResource()));
|
||||||
}
|
}
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
else if (IsDirectShowSupportedType(aType)) {
|
||||||
|
decoderReader = new DirectShowReader(aDecoder);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return decoderReader;
|
return decoderReader;
|
||||||
}
|
}
|
||||||
|
|
@ -418,6 +453,9 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
||||||
IsAACSupportedType(aType) ||
|
IsAACSupportedType(aType) ||
|
||||||
IsWaveSupportedType(aType) ||
|
IsWaveSupportedType(aType) ||
|
||||||
IsFlacSupportedType(aType) ||
|
IsFlacSupportedType(aType) ||
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
IsDirectShowSupportedType(aType) ||
|
||||||
|
#endif
|
||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
290
dom/media/directshow/AudioSinkFilter.cpp
Normal file
290
dom/media/directshow/AudioSinkFilter.cpp
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
/* -*- 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ::VIDEOINFOHEADER*
|
||||||
|
AudioSinkFilter::GetVideoInfo() const
|
||||||
|
{
|
||||||
|
return mInputPin->GetVideoInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
96
dom/media/directshow/AudioSinkFilter.h
Normal file
96
dom/media/directshow/AudioSinkFilter.h
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* -*- 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();
|
||||||
|
const ::VIDEOINFOHEADER* GetVideoInfo() const;
|
||||||
|
|
||||||
|
// 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_
|
||||||
211
dom/media/directshow/AudioSinkInputPin.cpp
Normal file
211
dom/media/directshow/AudioSinkInputPin.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
/* -*- 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();
|
||||||
|
memset(&mVideoInfo, 0, sizeof(mVideoInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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->SetFormatType(&FORMAT_WaveFormatEx);
|
||||||
|
aOutMediaType->SetTemporalCompression(FALSE);
|
||||||
|
|
||||||
|
if (aPosition == 0) {
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
if (aPosition == 1) {
|
||||||
|
aOutMediaType->SetType(&MEDIATYPE_Video);
|
||||||
|
aOutMediaType->SetSubtype(&MEDIASUBTYPE_YUY2);
|
||||||
|
aOutMediaType->SetFormatType(&FORMAT_VideoInfo);
|
||||||
|
aOutMediaType->SetTemporalCompression(FALSE);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
return S_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT
|
||||||
|
AudioSinkInputPin::CheckMediaType(const MediaType* aMediaType)
|
||||||
|
{
|
||||||
|
if (!aMediaType) {
|
||||||
|
return E_INVALIDARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUID majorType = *aMediaType->Type();
|
||||||
|
if (majorType == MEDIATYPE_Video &&
|
||||||
|
(*aMediaType->Subtype() == MEDIASUBTYPE_YUY2 ||
|
||||||
|
*aMediaType->Subtype() == MEDIASUBTYPE_I420) &&
|
||||||
|
*aMediaType->FormatType() == FORMAT_VideoInfo) {
|
||||||
|
if (aMediaType->cbFormat >= sizeof(VIDEOINFOHEADER)) {
|
||||||
|
memcpy(&mVideoInfo, aMediaType->pbFormat, sizeof(VIDEOINFOHEADER));
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
78
dom/media/directshow/AudioSinkInputPin.h
Normal file
78
dom/media/directshow/AudioSinkInputPin.h
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* -*- 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();
|
||||||
|
const VIDEOINFOHEADER* GetVideoInfo() const { return &mVideoInfo; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
VIDEOINFOHEADER mVideoInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // AudioSinkInputPin_h_
|
||||||
78
dom/media/directshow/DirectShowDecoder.cpp
Normal file
78
dom/media/directshow/DirectShowDecoder.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* -*- 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char const *const h264VideoCodecs[] = {
|
||||||
|
"avc1",
|
||||||
|
"h264",
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
if ((aType.EqualsASCII("video/h264") ||
|
||||||
|
aType.EqualsASCII("video/avc")) &&
|
||||||
|
CanDecodeH264UsingDirectShow()) {
|
||||||
|
if (aCodecList) {
|
||||||
|
*aCodecList = h264VideoCodecs;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
bool
|
||||||
|
DirectShowDecoder::IsEnabled()
|
||||||
|
{
|
||||||
|
return Preferences::GetBool("media.directshow.enabled") &&
|
||||||
|
(CanDecodeMP3UsingDirectShow() || CanDecodeH264UsingDirectShow());
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectShowDecoder::DirectShowDecoder(MediaDecoderOwner* aOwner)
|
||||||
|
: MediaDecoder(aOwner)
|
||||||
|
{
|
||||||
|
MOZ_COUNT_CTOR(DirectShowDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectShowDecoder::~DirectShowDecoder()
|
||||||
|
{
|
||||||
|
MOZ_COUNT_DTOR(DirectShowDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mozilla
|
||||||
45
dom/media/directshow/DirectShowDecoder.h
Normal file
45
dom/media/directshow/DirectShowDecoder.h
Normal file
|
|
@ -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 the legacy DirectShow graph on systems without WMF.
|
||||||
|
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
|
||||||
448
dom/media/directshow/DirectShowReader.cpp
Normal file
448
dom/media/directshow/DirectShowReader.cpp
Normal file
|
|
@ -0,0 +1,448 @@
|
||||||
|
/* -*- 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),
|
||||||
|
mIsH264(aDecoder->GetResource()->GetContentType().EqualsLiteral("video/h264") ||
|
||||||
|
aDecoder->GetResource()->GetContentType().EqualsLiteral("video/avc"))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (mIsH264) {
|
||||||
|
return ReadH264Metadata(aInfo, aTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
DirectShowReader::ReadH264Metadata(MediaInfo* aInfo, MetadataTags** aTags)
|
||||||
|
{
|
||||||
|
HRESULT hr;
|
||||||
|
mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_H264);
|
||||||
|
NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_OUT_OF_MEMORY);
|
||||||
|
nsresult rv = mSourceFilter->Init(mDecoder->GetResource(), 0);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
hr = mGraph->AddFilter(mSourceFilter, L"Mozilla H.264 source");
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
RefPtr<IBaseFilter> decoder;
|
||||||
|
hr = AddH264DecoderFilter(mGraph, getter_AddRefs(decoder));
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
mAudioSinkFilter = new AudioSinkFilter(L"Mozilla H.264 video sink", &hr);
|
||||||
|
NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
hr = mGraph->AddFilter(mAudioSinkFilter, L"Mozilla H.264 video sink");
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
hr = ConnectFilters(mGraph, mSourceFilter, decoder);
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter);
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
const VIDEOINFOHEADER* vih = mAudioSinkFilter->GetVideoInfo();
|
||||||
|
int32_t width = abs(vih->bmiHeader.biWidth);
|
||||||
|
int32_t height = abs(vih->bmiHeader.biHeight);
|
||||||
|
NS_ENSURE_TRUE(width > 0 && height > 0, NS_ERROR_FAILURE);
|
||||||
|
mInfo.mVideo = VideoInfo(width, height);
|
||||||
|
mInfo.mVideo.mMimeType = "video/h264";
|
||||||
|
hr = mControl->Run();
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||||
|
*aInfo = mInfo;
|
||||||
|
*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());
|
||||||
|
if (!mIsH264) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RefPtr<IMediaSample> sample;
|
||||||
|
HRESULT hr = mAudioSinkFilter->GetSampleSink()->Extract(sample);
|
||||||
|
if (FAILED(hr) || hr == S_FALSE) {
|
||||||
|
return Finish(hr);
|
||||||
|
}
|
||||||
|
REFERENCE_TIME start = 0, end = 0;
|
||||||
|
sample->GetMediaTime(&start, &end);
|
||||||
|
BYTE* data = nullptr;
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(sample->GetPointer(&data)), Finish(E_FAIL));
|
||||||
|
LONG length = sample->GetActualDataLength();
|
||||||
|
int32_t width = mInfo.mVideo.mImage.width;
|
||||||
|
int32_t height = mInfo.mVideo.mImage.height;
|
||||||
|
int32_t stride = width * 2;
|
||||||
|
NS_ENSURE_TRUE(length >= stride * height, Finish(E_FAIL));
|
||||||
|
nsTArray<uint8_t> y;
|
||||||
|
nsTArray<uint8_t> u;
|
||||||
|
nsTArray<uint8_t> v;
|
||||||
|
NS_ENSURE_TRUE(y.SetLength(width * height) &&
|
||||||
|
u.SetLength((width / 2) * (height / 2)) &&
|
||||||
|
v.SetLength((width / 2) * (height / 2)),
|
||||||
|
Finish(E_OUTOFMEMORY));
|
||||||
|
for (int32_t row = 0; row < height; ++row) {
|
||||||
|
const uint8_t* src = data + row * stride;
|
||||||
|
for (int32_t x = 0; x < width; x += 2) {
|
||||||
|
int32_t p = row * width + x;
|
||||||
|
y[p] = src[0]; y[p + 1] = src[2];
|
||||||
|
if (!(row & 1)) {
|
||||||
|
u[(row / 2) * (width / 2) + x / 2] = src[1];
|
||||||
|
v[(row / 2) * (width / 2) + x / 2] = src[3];
|
||||||
|
}
|
||||||
|
src += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VideoData::YCbCrBuffer buffer;
|
||||||
|
buffer.mPlanes[0] = { y.Elements(), uint32_t(width), uint32_t(height), uint32_t(width), 0, 1 };
|
||||||
|
buffer.mPlanes[1] = { u.Elements(), uint32_t(width / 2), uint32_t(height / 2), uint32_t(width / 2), 0, 1 };
|
||||||
|
buffer.mPlanes[2] = { v.Elements(), uint32_t(width / 2), uint32_t(height / 2), uint32_t(width / 2), 0, 1 };
|
||||||
|
RefPtr<VideoData> video = VideoData::CreateAndCopyData(
|
||||||
|
mInfo.mVideo, mDecoder->GetImageContainer(), 0, RefTimeToUsecs(start),
|
||||||
|
RefTimeToUsecs(end - start), buffer, true, -1,
|
||||||
|
gfx::IntRect(0, 0, width, height));
|
||||||
|
NS_ENSURE_TRUE(video, Finish(E_OUTOFMEMORY));
|
||||||
|
VideoQueue().Push(video);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
112
dom/media/directshow/DirectShowReader.h
Normal file
112
dom/media/directshow/DirectShowReader.h
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* -*- 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: 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 legacy media 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);
|
||||||
|
nsresult ReadH264Metadata(MediaInfo* aInfo, MetadataTags** aTags);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
bool mIsH264;
|
||||||
|
|
||||||
|
// 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
|
||||||
458
dom/media/directshow/DirectShowUtils.cpp
Normal file
458
dom/media/directshow/DirectShowUtils.cpp
Normal file
|
|
@ -0,0 +1,458 @@
|
||||||
|
/* 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 "DirectShowReader.h"
|
||||||
|
#include "dmodshow.h"
|
||||||
|
#include "wmcodecdsp.h"
|
||||||
|
#include "dmoreg.h"
|
||||||
|
#include "mozilla/ArrayUtils.h"
|
||||||
|
#include "mozilla/RefPtr.h"
|
||||||
|
#include "nsPrintfCString.h"
|
||||||
|
#include <wchar.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CanDecodeH264UsingDirectShow()
|
||||||
|
{
|
||||||
|
RefPtr<ICreateDevEnum> devEnum;
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
|
||||||
|
CLSCTX_INPROC_SERVER,
|
||||||
|
IID_ICreateDevEnum,
|
||||||
|
reinterpret_cast<void**>(getter_AddRefs(devEnum)));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<IEnumMoniker> filters;
|
||||||
|
hr = devEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory,
|
||||||
|
getter_AddRefs(filters), 0);
|
||||||
|
if (hr != S_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<IMoniker> moniker;
|
||||||
|
ULONG fetched = 0;
|
||||||
|
while (filters->Next(1, getter_AddRefs(moniker), &fetched) == S_OK) {
|
||||||
|
RefPtr<IPropertyBag> bag;
|
||||||
|
if (SUCCEEDED(moniker->BindToStorage(nullptr, nullptr,
|
||||||
|
IID_IPropertyBag,
|
||||||
|
reinterpret_cast<void**>(getter_AddRefs(bag))))) {
|
||||||
|
VARIANT value;
|
||||||
|
VariantInit(&value);
|
||||||
|
if (SUCCEEDED(bag->Read(L"FriendlyName", &value, nullptr)) &&
|
||||||
|
value.vt == VT_BSTR && value.bstrVal &&
|
||||||
|
(wcsstr(value.bstrVal, L"H264") ||
|
||||||
|
wcsstr(value.bstrVal, L"H.264") ||
|
||||||
|
wcsstr(value.bstrVal, L"AVC"))) {
|
||||||
|
VariantClear(&value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
VariantClear(&value);
|
||||||
|
}
|
||||||
|
moniker = nullptr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT
|
||||||
|
AddH264DecoderFilter(IGraphBuilder* aGraph, IBaseFilter** aOutFilter)
|
||||||
|
{
|
||||||
|
NS_ENSURE_TRUE(aGraph && aOutFilter, E_POINTER);
|
||||||
|
*aOutFilter = nullptr;
|
||||||
|
RefPtr<ICreateDevEnum> devEnum;
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
|
||||||
|
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
|
||||||
|
reinterpret_cast<void**>(getter_AddRefs(devEnum)));
|
||||||
|
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||||
|
RefPtr<IEnumMoniker> filters;
|
||||||
|
hr = devEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory,
|
||||||
|
getter_AddRefs(filters), 0);
|
||||||
|
NS_ENSURE_TRUE(hr == S_OK, VFW_E_NOT_FOUND);
|
||||||
|
RefPtr<IMoniker> moniker;
|
||||||
|
ULONG fetched = 0;
|
||||||
|
while (filters->Next(1, getter_AddRefs(moniker), &fetched) == S_OK) {
|
||||||
|
RefPtr<IPropertyBag> bag;
|
||||||
|
VARIANT value;
|
||||||
|
VariantInit(&value);
|
||||||
|
bool h264 = false;
|
||||||
|
if (SUCCEEDED(moniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag,
|
||||||
|
reinterpret_cast<void**>(getter_AddRefs(bag)))) &&
|
||||||
|
SUCCEEDED(bag->Read(L"FriendlyName", &value, nullptr)) &&
|
||||||
|
value.vt == VT_BSTR && value.bstrVal) {
|
||||||
|
h264 = wcsstr(value.bstrVal, L"H264") ||
|
||||||
|
wcsstr(value.bstrVal, L"H.264") ||
|
||||||
|
wcsstr(value.bstrVal, L"AVC");
|
||||||
|
}
|
||||||
|
VariantClear(&value);
|
||||||
|
if (h264 && SUCCEEDED(moniker->BindToObject(nullptr, nullptr, IID_IBaseFilter,
|
||||||
|
reinterpret_cast<void**>(aOutFilter)))) {
|
||||||
|
hr = aGraph->AddFilter(*aOutFilter, L"DirectShow H.264 decoder");
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
(*aOutFilter)->Release();
|
||||||
|
*aOutFilter = nullptr;
|
||||||
|
}
|
||||||
|
moniker = nullptr;
|
||||||
|
}
|
||||||
|
return VFW_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
134
dom/media/directshow/DirectShowUtils.h
Normal file
134
dom/media/directshow/DirectShowUtils.h
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/* 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();
|
||||||
|
|
||||||
|
// Returns true when a registered DirectShow filter advertises H.264/AVC
|
||||||
|
// decoding. This works on Windows 2000 because it uses the legacy filter
|
||||||
|
// enumerator instead of Media Foundation.
|
||||||
|
bool
|
||||||
|
CanDecodeH264UsingDirectShow();
|
||||||
|
|
||||||
|
HRESULT
|
||||||
|
AddH264DecoderFilter(IGraphBuilder* aGraph, IBaseFilter** aOutFilter);
|
||||||
|
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif
|
||||||
159
dom/media/directshow/SampleSink.cpp
Normal file
159
dom/media/directshow/SampleSink.cpp
Normal file
|
|
@ -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
|
||||||
|
|
||||||
67
dom/media/directshow/SampleSink.h
Normal file
67
dom/media/directshow/SampleSink.h
Normal file
|
|
@ -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_
|
||||||
683
dom/media/directshow/SourceFilter.cpp
Normal file
683
dom/media/directshow/SourceFilter.cpp
Normal file
|
|
@ -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
|
||||||
75
dom/media/directshow/SourceFilter.h
Normal file
75
dom/media/directshow/SourceFilter.h
Normal file
|
|
@ -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
|
||||||
41
dom/media/directshow/moz.build
Normal file
41
dom/media/directshow/moz.build
Normal file
|
|
@ -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',
|
||||||
|
]
|
||||||
|
|
@ -42,6 +42,12 @@ DIRS += [
|
||||||
'standalone',
|
'standalone',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG['MOZ_DIRECTSHOW']:
|
||||||
|
DIRS += ['directshow']
|
||||||
|
LOCAL_INCLUDES += [
|
||||||
|
'/media/webrtc/trunk/webrtc/modules/video_capture/windows',
|
||||||
|
]
|
||||||
|
|
||||||
if CONFIG['MOZ_FMP4']:
|
if CONFIG['MOZ_FMP4']:
|
||||||
DIRS += ['fmp4']
|
DIRS += ['fmp4']
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,10 @@ pref("media.wave.play-stand-alone", false);
|
||||||
pref("media.hardware-video-decoding.enabled", true);
|
pref("media.hardware-video-decoding.enabled", true);
|
||||||
pref("media.hardware-video-decoding.force-enabled", false);
|
pref("media.hardware-video-decoding.force-enabled", false);
|
||||||
|
|
||||||
|
#ifdef MOZ_DIRECTSHOW
|
||||||
|
pref("media.directshow.enabled", true);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef MOZ_FMP4
|
#ifdef MOZ_FMP4
|
||||||
pref("media.mp4.enabled", true);
|
pref("media.mp4.enabled", true);
|
||||||
// Specifies whether the PDMFactory can create a test decoder that
|
// Specifies whether the PDMFactory can create a test decoder that
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||||
'crypt32',
|
'crypt32',
|
||||||
'shell32',
|
'shell32',
|
||||||
'ole32',
|
'ole32',
|
||||||
|
'strmiids',
|
||||||
'version',
|
'version',
|
||||||
'winspool',
|
'winspool',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue