blob: 62cf9c00cf0ec344df4d4f7dca99c6458b34dbc1 [file]
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include "webmmuxfilter.h"
#include "cenumpins.h"
#include "graphutil.h"
#include "webmtypes.h"
#include <new>
#include <cassert>
#include <vfwmsgs.h>
#include <uuids.h>
#include <evcode.h>
#ifdef _DEBUG
#include "odbgstream.h"
using std::endl;
#endif
using std::wstring;
namespace WebmMuxLib
{
HRESULT CreateInstance(
IClassFactory* pClassFactory,
IUnknown* pOuter,
const IID& iid,
void** ppv)
{
if (ppv == 0)
return E_POINTER;
*ppv = 0;
if ((pOuter != 0) && (iid != __uuidof(IUnknown)))
return E_INVALIDARG;
Filter* p = new (std::nothrow) Filter(pClassFactory, pOuter);
if (p == 0)
return E_OUTOFMEMORY;
assert(p->m_nondelegating.m_cRef == 0);
const HRESULT hr = p->m_nondelegating.QueryInterface(iid, ppv);
if (SUCCEEDED(hr))
{
assert(*ppv);
assert(p->m_nondelegating.m_cRef == 1);
return S_OK;
}
assert(*ppv == 0);
assert(p->m_nondelegating.m_cRef == 0);
delete p;
p = 0;
return hr;
}
#pragma warning(disable:4355) //'this' ptr in member init list
Filter::Filter(IClassFactory* pClassFactory, IUnknown* pOuter)
: m_pClassFactory(pClassFactory),
m_nondelegating(this),
m_pOuter(pOuter ? pOuter : &m_nondelegating),
m_state(State_Stopped),
m_clock(0),
m_inpin_video(this),
m_inpin_audio(this),
m_outpin(this)
{
m_pClassFactory->LockServer(TRUE);
const HRESULT hr = CLockable::Init();
hr;
assert(SUCCEEDED(hr));
m_info.pGraph = 0;
m_info.achName[0] = L'\0';
#ifdef _DEBUG
odbgstream os;
os << "mkvmux::ctor" << endl;
#endif
}
#pragma warning(default:4355)
Filter::~Filter()
{
#ifdef _DEBUG
odbgstream os;
os << "mkvmux::dtor" << endl;
#endif
m_pClassFactory->LockServer(FALSE);
}
Filter::nondelegating_t::nondelegating_t(Filter* p)
: m_pFilter(p),
m_cRef(0) //see CreateInstance
{
}
Filter::nondelegating_t::~nondelegating_t()
{
}
HRESULT Filter::nondelegating_t::QueryInterface(
const IID& iid,
void** ppv)
{
if (ppv == 0)
return E_POINTER;
IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
if (iid == __uuidof(IUnknown))
{
pUnk = this; //must be nondelegating
}
else if ((iid == __uuidof(IBaseFilter)) ||
(iid == __uuidof(IMediaFilter)) ||
(iid == __uuidof(IPersist)))
{
pUnk = static_cast<IBaseFilter*>(m_pFilter);
}
else if (iid == __uuidof(IMediaSeeking))
{
pUnk = static_cast<IMediaSeeking*>(m_pFilter);
}
else if (iid == __uuidof(IAMFilterMiscFlags))
{
pUnk = static_cast<IAMFilterMiscFlags*>(m_pFilter);
}
else if (iid == __uuidof(IWebmMux))
{
pUnk = static_cast<IWebmMux*>(m_pFilter);
}
else
{
#if 0
wodbgstream os;
os << "mp3source::filter::QI: iid=" << IIDStr(iid) << std::endl;
#endif
pUnk = 0;
return E_NOINTERFACE;
}
pUnk->AddRef();
return S_OK;
}
ULONG Filter::nondelegating_t::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG Filter::nondelegating_t::Release()
{
if (LONG n = InterlockedDecrement(&m_cRef))
return n;
delete m_pFilter;
return 0;
}
HRESULT Filter::QueryInterface(const IID& iid, void** ppv)
{
return m_pOuter->QueryInterface(iid, ppv);
}
ULONG Filter::AddRef()
{
return m_pOuter->AddRef();
}
ULONG Filter::Release()
{
return m_pOuter->Release();
}
HRESULT Filter::GetClassID(CLSID* p)
{
if (p == 0)
return E_POINTER;
*p = CLSID_WebmMux;
return S_OK;
}
HRESULT Filter::Stop()
{
//Stop is a synchronous operation: when it completes,
//the filter is stopped.
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
#if 0
odbgstream os;
os << "mux::filter::stop (begin)" << endl;
#endif
switch (m_state)
{
case State_Paused:
case State_Running:
//m_inpin_video.Stop();
//m_inpin_audio.Stop();
m_outpin.Final(); //close mkv file if req'd
m_inpin_audio.Final();
m_inpin_video.Final();
break;
case State_Stopped:
default:
break;
}
m_state = State_Stopped;
#if 0
os << "mux::filter::stop (end)" << endl;
#endif
return S_OK;
}
HRESULT Filter::Pause()
{
//Unlike Stop(), Pause() can be asynchronous (that's why you have
//GetState()). We could use that here to build the samples index.
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
#if 0
odbgstream os;
os << "mux::filter::pause" << endl;
#endif
switch (m_state)
{
case State_Stopped:
m_inpin_video.Init();
m_inpin_audio.Init();
m_outpin.Init();
break;
case State_Running:
case State_Paused:
default:
break;
}
m_state = State_Paused;
return S_OK;
}
HRESULT Filter::Run(REFERENCE_TIME start)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
#if 0
odbgstream os;
os << "mux::filter::run" << endl;
#endif
switch (m_state)
{
case State_Stopped:
m_inpin_video.Init();
m_inpin_audio.Init();
m_outpin.Init();
break;
case State_Paused:
case State_Running:
default:
m_inpin_video.Run();
m_inpin_audio.Run();
break;
}
m_start = start;
m_state = State_Running;
return S_OK;
}
HRESULT Filter::GetState(
DWORD /* timeout */ ,
FILTER_STATE* p)
{
if (p == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
*p = m_state;
return S_OK;
}
HRESULT Filter::SetSyncSource(
IReferenceClock* clock)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (m_clock)
m_clock->Release();
m_clock = clock;
if (m_clock)
m_clock->AddRef();
return S_OK;
}
HRESULT Filter::GetSyncSource(
IReferenceClock** pclock)
{
if (pclock == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
IReferenceClock*& clock = *pclock;
clock = m_clock;
if (clock)
clock->AddRef();
return S_OK;
}
HRESULT Filter::EnumPins(IEnumPins** pp)
{
IPin* const pa[3] = { &m_inpin_video, &m_inpin_audio, &m_outpin };
return CEnumPins::CreateInstance(pa, 3, pp);
}
HRESULT Filter::FindPin(
LPCWSTR id,
IPin** pp)
{
if (pp == 0)
return E_POINTER;
IPin*& p = *pp;
p = 0;
if (id == 0)
return E_INVALIDARG;
enum { n = 3 };
Pin* const pins[n] =
{
&m_inpin_video,
&m_inpin_audio,
&m_outpin
};
for (int i = 0; i < n; ++i)
{
Pin* const pin = pins[i];
if (wcscmp(id, pin->m_id) == 0)
{
p = pin;
p->AddRef();
return S_OK;
}
}
return VFW_E_NOT_FOUND;
}
HRESULT Filter::QueryFilterInfo(FILTER_INFO* p)
{
if (p == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
enum { size = sizeof(p->achName)/sizeof(WCHAR) };
const errno_t e = wcscpy_s(p->achName, size, m_info.achName);
assert(e == 0);
e;
p->pGraph = m_info.pGraph;
if (p->pGraph)
p->pGraph->AddRef();
return S_OK;
}
HRESULT Filter::JoinFilterGraph(
IFilterGraph *pGraph,
LPCWSTR name)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
//NOTE:
//No, do not adjust reference counts here!
//Read the docs for the reasons why.
//ENDNOTE.
m_info.pGraph = pGraph;
if (name == 0)
m_info.achName[0] = L'\0';
else
{
enum { size = sizeof(m_info.achName)/sizeof(WCHAR) };
const errno_t e = wcscpy_s(m_info.achName, size, name);
e;
assert(e == 0); //TODO
}
return S_OK;
}
HRESULT Filter::QueryVendorInfo(LPWSTR* pstr)
{
if (pstr == 0)
return E_POINTER;
wchar_t*& str = *pstr;
str = 0;
return E_NOTIMPL;
}
HRESULT Filter::GetCapabilities(DWORD* pdw)
{
if (pdw == 0)
return E_POINTER;
//enum AM_SEEKING_SeekingCapabilities
// { AM_SEEKING_CanSeekAbsolute = 0x1,
// AM_SEEKING_CanSeekForwards = 0x2,
// AM_SEEKING_CanSeekBackwards = 0x4,
// AM_SEEKING_CanGetCurrentPos = 0x8,
// AM_SEEKING_CanGetStopPos = 0x10,
// AM_SEEKING_CanGetDuration = 0x20,
// AM_SEEKING_CanPlayBackwards = 0x40,
// AM_SEEKING_CanDoSegments = 0x80,
// AM_SEEKING_Source = 0x100
// } AM_SEEKING_SEEKING_CAPABILITIES;
DWORD& dwResult = *pdw;
dwResult = AM_SEEKING_CanGetCurrentPos |
AM_SEEKING_CanGetDuration;
#if 0
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
//if (m_state == State_Stopped)
// dwResult |= AM_SEEKING_CanSeekAbsolute;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
DWORD dw;
hr = pSeek->GetCapabilities(&dw);
if (SUCCEEDED(hr))
dwResult |= dw;
return S_OK;
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
DWORD dw;
hr = pSeek->GetCapabilities(&dw);
if (SUCCEEDED(hr))
dwResult |= dw;
return S_OK;
}
}
#endif
return S_OK;
}
HRESULT Filter::CheckCapabilities(DWORD* pdw)
{
if (pdw == 0)
return E_POINTER;
DWORD& dw = *pdw;
const DWORD dwRequested = dw;
if (dwRequested == 0)
return E_INVALIDARG;
DWORD dwActual;
HRESULT hr = GetCapabilities(&dwActual);
if (FAILED(hr))
return hr;
if (dwActual == 0)
return E_FAIL;
dw &= dwActual;
if (dw == 0)
return E_FAIL;
return (dw == dwRequested) ? S_OK : S_FALSE;
}
HRESULT Filter::IsFormatSupported(const GUID* p)
{
#if 0
//See my notes for other IMediaSeeking member functions.
//Because we handle GetCurrentPosition here, by this
//filter directly, I don't think it's meaningful to
//attempt to support other time formats besides MEDIA_TIME.
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->IsFormatSupported(p);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->IsFormatSupported(p);
}
}
#endif
if (p == 0)
return E_POINTER;
const GUID& fmt = *p;
if (fmt == TIME_FORMAT_MEDIA_TIME)
return S_OK;
return S_FALSE;
}
HRESULT Filter::QueryPreferredFormat(GUID* p)
{
#if 0
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->QueryPreferredFormat(p);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->QueryPreferredFormat(p);
}
}
#endif
if (p == 0)
return E_POINTER;
*p = TIME_FORMAT_MEDIA_TIME;
return S_OK;
}
HRESULT Filter::GetTimeFormat(GUID* p)
{
#if 0
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->GetTimeFormat(p);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->GetTimeFormat(p);
}
}
#endif
if (p == 0)
return E_POINTER;
*p = TIME_FORMAT_MEDIA_TIME;
return S_OK;
}
HRESULT Filter::IsUsingTimeFormat(const GUID* p)
{
#if 0
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->IsUsingTimeFormat(p);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->IsUsingTimeFormat(p);
}
}
#endif
if (p == 0)
return E_INVALIDARG;
return (*p == TIME_FORMAT_MEDIA_TIME) ? S_OK : S_FALSE;
}
HRESULT Filter::SetTimeFormat(const GUID* p)
{
#if 0
//TODO: see my comments below in GetCurrentPosition.
//We might have to NOT pass this request downstream,
//because we handle GetCurrentPosition locally, and
//all the filters need to agree on what the curent
//time format is.
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->SetTimeFormat(p);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->SetTimeFormat(p);
}
}
#endif
if (p == 0)
return E_INVALIDARG;
const GUID& g = *p;
if (g == TIME_FORMAT_MEDIA_TIME)
return S_OK;
return E_INVALIDARG;
}
HRESULT Filter::GetDuration(LONGLONG* p)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_pPinConnection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Release();
hr = pSeek->GetDuration(p);
//if (SUCCEEDED(hr))
//{
// odbgstream os;
// os << "mkvmux::filter::GetDuration: vt=" << *p << endl;
//}
return hr;
}
}
if (IPin* pin = m_inpin_audio.m_pPinConnection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Release();
hr = pSeek->GetDuration(p);
//if (SUCCEEDED(hr))
//{
// odbgstream os;
// os << "mkvmux::filter::GetDuration: at=" << *p << endl;
//}
return hr;
}
}
return E_FAIL;
}
HRESULT Filter::GetStopPosition(LONGLONG* p)
{
if (p == 0)
return E_POINTER;
LONGLONG& t = *p;
t = _I64_MAX; //?
return S_OK;
}
HRESULT Filter::GetCurrentPosition(LONGLONG* p)
{
if (p == 0)
return E_POINTER;
LONGLONG& reftime = *p;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
//muxers always return their own position, without
//forwarding this request upstream. Apps should
//always call the filter directly to get the
//current position, instead of calling the FGM.
//
//The only snag here is if the app changes the
//time format. We forward that change upstream.
//But if the app calls GetCurrentPosition, it's
//handled here, by this filter, which ignores any
//change in time format and always returns MEDIA_TIME
//units.
//
//Maybe this isn't a real problem, though. It might
//be the case that GetCurrentPosition is always
//filter-specific. If the app wants to know the
//"current position" then he might have to call a specific
//filter directly. But if that's true (and it is
//for this muxer filter), then if this muxer were
//to support other time formats (e.g FRAME), then
//it would have to cache the requested time format.
//This does get weird, though, if this filter supported
//formats different from the upstead filters, then
//you'd have different parts of the graph using
//different time formats. That probably wouldn't
//work.
//
//Maybe the solution is to simply reject all requests
//for media formats that's aren't for MEDIA_TIME. This
//is a muxer, after all, so we're very time-based
//anyway. It would be nice, however, to support
//frame-based requests, allowing the client to seek
//to, say, the 100th frame, and to request what is the
//number of the frame mostly recently written
//(a request that would be handled by this filter).
//Another possibility is to take the intersection
//of the formats supported by both this muxer
//and the upstream filter.
return m_outpin.GetCurrentPosition(reftime);
}
HRESULT Filter::ConvertTimeFormat(
LONGLONG* ptgt,
const GUID* ptgtfmt,
LONGLONG src,
const GUID* psrcfmt)
{
#if 0
Lock lock;
HRESULT hr = lock.Enter(this);
if (FAILED(hr))
return hr;
if (IPin* pin = m_inpin_video.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->ConvertTimeFormat(ptgt, ptgtfmt, src, psrcfmt);
}
}
if (IPin* pin = m_inpin_audio.m_connection)
{
const GraphUtil::IMediaSeekingPtr pSeek(pin);
if (bool(pSeek))
{
lock.Leave();
return pSeek->ConvertTimeFormat(ptgt, ptgtfmt, src, psrcfmt);
}
}
#endif
if (ptgt == 0)
return E_POINTER;
LONGLONG& tgt = *ptgt;
const GUID& tgtfmt = ptgtfmt ? *ptgtfmt : TIME_FORMAT_MEDIA_TIME;
const GUID& srcfmt = psrcfmt ? *psrcfmt : TIME_FORMAT_MEDIA_TIME;
if (tgtfmt != TIME_FORMAT_MEDIA_TIME)
return E_INVALIDARG;
if (srcfmt != TIME_FORMAT_MEDIA_TIME)
return E_INVALIDARG;
tgt = src;
return S_OK;
}
HRESULT Filter::SetPositions(
LONGLONG* pCurr,
DWORD dwCurr_,
LONGLONG* pStop,
DWORD dwStop_)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
if (dwCurrPos == AM_SEEKING_NoPositioning)
{
if (dwCurr_ & AM_SEEKING_ReturnTime)
{
if (pCurr == 0)
return E_POINTER;
hr = m_outpin.GetCurrentPosition(*pCurr);
if (FAILED(hr))
return hr;
}
if (dwStopPos == AM_SEEKING_NoPositioning)
{
if (dwStop_ & AM_SEEKING_ReturnTime)
{
if (pStop == 0)
return E_POINTER;
LONGLONG& tStop = *pStop;
tStop = _I64_MAX; //TODO
}
return S_FALSE; //no position change
}
if (pStop == 0)
return E_INVALIDARG;
LONGLONG& tStop = *pStop;
tStop;
return E_FAIL; //TODO: support setting stop pos
}
//Check for errors first, before changing any state.
if (pCurr == 0)
return E_INVALIDARG;
switch (dwCurrPos)
{
case AM_SEEKING_IncrementalPositioning:
default:
return E_INVALIDARG; //applies only to stop pos
case AM_SEEKING_AbsolutePositioning:
case AM_SEEKING_RelativePositioning:
break;
}
switch (dwStopPos)
{
case AM_SEEKING_NoPositioning:
if (((dwStop_ & AM_SEEKING_ReturnTime) != 0) && (pStop == 0))
return E_POINTER;
break;
case AM_SEEKING_AbsolutePositioning:
case AM_SEEKING_RelativePositioning:
case AM_SEEKING_IncrementalPositioning:
return E_FAIL;
}
//Args have now been vetted.
assert(pCurr); //vetted above
LONGLONG& tCurr = *pCurr;
if (m_state != State_Stopped)
return VFW_E_NOT_STOPPED;
switch (dwCurrPos)
{
case AM_SEEKING_IncrementalPositioning: //applies only to stop pos
default:
assert(false);
return E_FAIL;
case AM_SEEKING_AbsolutePositioning:
{
if (tCurr > 0)
return E_INVALIDARG;
break;
}
case AM_SEEKING_RelativePositioning:
{
//We're stopped, so we assume "curr time" is 0.
if (tCurr > 0)
return E_INVALIDARG;
break;
}
}
hr = m_inpin_video.ResetPosition();
if (FAILED(hr))
return hr;
hr = m_inpin_audio.ResetPosition();
if (FAILED(hr))
return hr;
if (dwCurr_ & AM_SEEKING_ReturnTime)
tCurr = 0;
if (dwStop_ & AM_SEEKING_ReturnTime)
{
assert(pStop); //we checked this above
LONGLONG& tStop = *pStop;
tStop = _I64_MAX; //TODO
}
return S_OK;
}
HRESULT Filter::GetPositions(
LONGLONG* pCurrPos,
LONGLONG* pStopPos)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (pCurrPos == 0)
return E_POINTER;
LONGLONG& reftime = *pCurrPos;
hr = m_outpin.GetCurrentPosition(reftime);
if (FAILED(hr))
return hr;
if (pStopPos)
*pStopPos = _I64_MAX; //?
return S_OK;
}
HRESULT Filter::GetAvailable(
LONGLONG* pEarliest,
LONGLONG* pLatest)
{
if ((pEarliest == 0) || (pLatest == 0))
return E_POINTER;
*pEarliest = 0;
*pLatest = 0; //?
return S_OK;
}
HRESULT Filter::SetRate(double r)
{
//TODO: still need to look at MKV support for this
if (r == 1)
return S_OK;
if (r <= 0)
return E_INVALIDARG;
return E_NOTIMPL; //TODO: better return here?
}
HRESULT Filter::GetRate(double* p)
{
//TODO: still need to look at MKV support for this
if (p == 0)
return E_POINTER;
*p = 1;
return S_OK;
}
HRESULT Filter::GetPreroll(LONGLONG* p)
{
if (p == 0)
return E_POINTER;
*p = 0; //?
return S_OK;
}
ULONG Filter::GetMiscFlags()
{
return AM_FILTER_MISC_FLAGS_IS_RENDERER;
}
HRESULT Filter::SetWritingApp(const wchar_t* str)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (str == 0)
m_ctx.m_writing_app.clear();
else if (wcslen(str) > 126) //1-byte u-int max
return E_INVALIDARG;
else
m_ctx.m_writing_app = str;
return S_OK;
}
HRESULT Filter::GetWritingApp(wchar_t** p)
{
if (p == 0)
return E_POINTER;
wchar_t*& str = *p;
str = 0;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
const wstring& app = m_ctx.m_writing_app;
if (app.empty())
return S_OK;
const size_t len = app.length(); //wchar strlen
const size_t buflen = len + 1; //wchar strlen + wchar null
const size_t cb = buflen * sizeof(wchar_t); //total bytes
str = (wchar_t*)CoTaskMemAlloc(cb);
if (str == 0)
return E_OUTOFMEMORY;
const errno_t e = wcscpy_s(str, buflen, app.c_str());
e;
assert(e == 0);
return S_OK;
}
HRESULT Filter::SetMuxMode(WebmMuxMode mux_mode)
{
Lock lock;
int imux_mode = mux_mode;
if (imux_mode < kWebmMuxModeDefault && mux_mode > kWebmMuxModeLive)
return E_INVALIDARG;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (m_state != State_Stopped)
return VFW_E_NOT_STOPPED;
m_ctx.SetLiveMuxMode(mux_mode == kWebmMuxModeLive);
return S_OK;
}
HRESULT Filter::GetMuxMode(WebmMuxMode* pMuxMode)
{
if (pMuxMode == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (m_ctx.GetLiveMuxMode())
*pMuxMode = kWebmMuxModeLive;
else
*pMuxMode = kWebmMuxModeDefault;
return S_OK;
}
HRESULT Filter::OnEndOfStream()
{
#if 1
//NOTE: it appears that you must do both of send EOS downstream
//to the file writer, and act as renderer (as described below):
if (IPin* pin = m_outpin.m_pPinConnection)
{
const HRESULT hr = pin->EndOfStream();
hr;
assert(SUCCEEDED(hr));
}
//"End-of-Stream Notifications"
//http://msdn.microsoft.com/en-us/library/dd375604(VS.85).aspx
//The key to getting this to work is to treat this muxer as
//as renderer. We do that by supporting both IMediaSeeking and
//IAMFilterMiscFlags. As the API states:
//
//(begin quote)
//To determine the number of streams, the Filter Graph Manager
//counts the number of filters that support seeking (through
//IMediaSeeking or IMediaPosition) and have a rendered input pin,
//which is defined as an input pin with no corresponding outputs.
//The Filter Graph Manager determines whether a pin is rendered
//in one of two ways:
//
//(1) The pin's IPin::QueryInternalConnections method returns zero
// in the nPin parameter.
//(2) The filter exposes the IAMFilterMiscFlags interface and
// returns the AM_FILTER_MISC_FLAGS_IS_RENDERER flag.
//(end quote)
//
//See also:
//"IPin::QueryInternalConnections Method"
//http://msdn.microsoft.com/en-us/library/dd390431(VS.85).aspx
//
//(begin quote)
//This method has another use that is now deprecated: The Filter
//Graph Manager treats a filter as being a renderer filter if at
//least one input pin implements this method but returns zero in nPin.
//If you are writing a new renderer filter, however, you should
//implement the IAMFilterMiscFlags interface instead of using this
//method to indicate that the filter is a renderer.
//(end quote)
#if 0
odbgstream os;
os << "muxer::filter::OnEndOfStream: sending EC_COMPLETE" << endl;
#endif
_COM_SMARTPTR_TYPEDEF(IMediaEventSink, __uuidof(IMediaEventSink));
const IMediaEventSinkPtr pSink(m_info.pGraph);
assert(bool(pSink));
const HRESULT hr = pSink->Notify(EC_COMPLETE, S_OK, 0);
hr;
assert(SUCCEEDED(hr));
#else
//TODO: I thought that sending EOS should be enough, but apparently
//the EC_COMPLETE is not being delivered to the main loop.
//I don't know why. As a workaround, just manually send
//a user event, so we know something gets delivered.
_COM_SMARTPTR_TYPEDEF(IMediaEventSink, __uuidof(IMediaEventSink));
const IMediaEventSinkPtr pSink(m_info.pGraph);
assert(bool(pSink));
const HRESULT hr = pSink->Notify(EC_USER + 0x100, 0, 0);
hr;
#endif
return S_OK;
}
} //end namespace WebmMuxLib