blob: a557b34e9c4b2eb83c2b175201291c55f405004c [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 "webmmuxstream.h"
#include "graphutil.h"
#include <vfwmsgs.h>
#include <cassert>
#ifdef _DEBUG
#include "odbgstream.h"
using std::endl;
using std::hex;
using std::dec;
#endif
//#define DEBUG_RECEIVE
//#define DEBUG_WAIT
//#define DEBUG_RECEIVE_MULTIPLE
namespace WebmMuxLib
{
Inpin::Inpin(Filter* p, const wchar_t* id) :
Pin(p, id, PINDIR_INPUT),
m_pStream(0)
{
m_hSample = CreateEvent(0, 0, 0, 0);
assert(m_hSample); //TODO
m_hStateChangeOrFlush = CreateEvent(0, 0, 0, 0);
assert(m_hStateChangeOrFlush); //TODO
}
Inpin::~Inpin()
{
BOOL b = CloseHandle(m_hStateChangeOrFlush);
assert(b);
b = CloseHandle(m_hSample);
assert(b);
}
void Inpin::Init()
{
m_bFlush = false;
m_bEndOfStream = false;
BOOL b = ResetEvent(m_hSample);
assert(b);
b = ResetEvent(m_hStateChangeOrFlush);
assert(b);
assert(m_pStream == 0);
if (!bool(m_pPinConnection))
return; //not connected, so nothing else to do
const HRESULT hr = OnInit();
hr;
assert(SUCCEEDED(hr));
assert(m_pStream);
}
//void Inpin::Stop()
//{
// if (m_pStream)
// m_pStream->Stop();
//}
void Inpin::Final()
{
OnFinal();
delete m_pStream;
m_pStream = 0;
const BOOL b = SetEvent(m_hStateChangeOrFlush); //transition to stopped
assert(b);
}
void Inpin::Run()
{
const BOOL b = SetEvent(m_hStateChangeOrFlush);
assert(b);
}
HRESULT Inpin::QueryInterface(const IID& iid, void** ppv)
{
if (ppv == 0)
return E_POINTER;
IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
if (iid == __uuidof(IUnknown))
pUnk = static_cast<IPin*>(this);
else if (iid == __uuidof(IPin))
pUnk = static_cast<IPin*>(this);
else if (iid == __uuidof(IMemInputPin))
pUnk = static_cast<IMemInputPin*>(this);
else
{
pUnk = 0;
return E_NOINTERFACE;
}
pUnk->AddRef();
return S_OK;
}
ULONG Inpin::AddRef()
{
return m_pFilter->AddRef();
}
ULONG Inpin::Release()
{
return m_pFilter->Release();
}
HRESULT Inpin::Connect(IPin*, const AM_MEDIA_TYPE*)
{
return E_UNEXPECTED; //for output pins only
}
HRESULT Inpin::QueryInternalConnections(
IPin** pa,
ULONG* pn)
{
if (pn == 0)
return E_POINTER;
if (*pn == 0)
{
if (pa == 0) //query for required number
{
*pn = 1;
return S_OK;
}
return S_FALSE; //means "insufficient number of array elements"
}
if (pa == 0)
{
*pn = 0;
return E_POINTER;
}
IPin*& pin = pa[0];
pin = &m_pFilter->m_outpin;
pin->AddRef();
*pn = 1;
return S_OK;
}
HRESULT Inpin::ReceiveConnection(
IPin* pin,
const AM_MEDIA_TYPE* pmt)
{
if ((pin == 0) || (pmt == 0))
return E_POINTER;
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (m_pFilter->m_state != State_Stopped)
return VFW_E_NOT_STOPPED;
if (bool(m_pPinConnection))
return VFW_E_ALREADY_CONNECTED;
m_connection_mtv.Clear();
hr = QueryAccept(pmt);
if (hr != S_OK)
return VFW_E_TYPE_NOT_ACCEPTED;
const AM_MEDIA_TYPE& mt = *pmt;
hr = OnReceiveConnection(pin, mt); //dispatch to subclass
if (FAILED(hr))
return hr;
hr = m_connection_mtv.Add(mt);
if (FAILED(hr))
return hr;
m_pPinConnection = pin;
return S_OK;
}
HRESULT Inpin::EndOfStream()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_WRONG_STATE; //?
if (m_bEndOfStream)
return S_FALSE;
m_bEndOfStream = true;
return OnEndOfStream();
}
HRESULT Inpin::BeginFlush()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
//TODO: check state?
//I think flush is supposed to flush EOS indicators
//too (they can be queued, just like sammples).
if (m_bFlush)
return S_FALSE;
if (m_pStream)
m_pStream->Flush();
//TODO: destroy stream, then re-create it during EndFlush
m_bFlush = true;
const BOOL b = SetEvent(m_hStateChangeOrFlush);
assert(b);
return S_OK;
}
HRESULT Inpin::EndFlush()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
const BOOL b = ResetEvent(m_hStateChangeOrFlush);
assert(b);
if (!m_bFlush)
return S_FALSE; //?
m_bFlush = false;
//TODO: it's not clear whether this should also clear
//the EOS status.
//TODO: re-create stream object
return S_OK;
}
HRESULT Inpin::NewSegment(
REFERENCE_TIME,
REFERENCE_TIME,
double)
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
//TODO: we could probably tell file object to render its
//current cluster (and start a new one)
return S_OK; //TODO
}
HRESULT Inpin::GetAllocator(IMemAllocator** p)
{
if (p)
*p = 0;
return VFW_E_NO_ALLOCATOR; //?
}
HRESULT Inpin::NotifyAllocator(
IMemAllocator* pAllocator,
BOOL)
{
if (pAllocator == 0)
return E_INVALIDARG;
ALLOCATOR_PROPERTIES props;
const HRESULT hr = pAllocator->GetProperties(&props);
hr;
assert(SUCCEEDED(hr));
#ifdef _DEBUG
wodbgstream os;
os << "mkvmux::inpin["
<< m_id
<< "]::NotifyAllocator: props.cBuffers="
<< props.cBuffers
<< " cbBuffer="
<< props.cbBuffer
<< " cbAlign="
<< props.cbAlign
<< " cbPrefix="
<< props.cbPrefix
<< endl;
#endif
return S_OK;
}
HRESULT Inpin::Receive(IMediaSample* pSample)
{
if (pSample == 0)
return E_INVALIDARG;
#ifdef DEBUG_RECEIVE
{
__int64 start_reftime, stop_reftime;
const HRESULT hr = pSample->GetTime(&start_reftime, &stop_reftime);
odbgstream os;
os << "mkvmux::inpin::receive: ";
if (hr == S_OK)
os << "start=" << double(start_reftime) / 10000000
<< "; stop=" << double(stop_reftime) / 10000000;
else if (hr == VFW_S_NO_STOP_TIME)
os << "start=" << double(start_reftime) / 10000000;
os << endl;
}
#endif
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
#ifdef DEBUG_RECEIVE
wodbgstream os;
os << L"inpin[" << m_id << "]::Receive: THREAD=0x"
<< hex << GetCurrentThreadId() << dec
<< endl;
#endif
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_NOT_RUNNING;
if (m_bEndOfStream)
return VFW_E_SAMPLE_REJECTED_EOS;
if (m_bFlush)
return S_FALSE;
hr = m_pStream->Receive(pSample);
if (hr != S_OK)
return hr;
const BOOL b = SetEvent(m_hSample); //notify other pin
assert(b);
return Wait(lock);
}
HRESULT Inpin::Wait(CLockable::Lock& lock)
{
//TODO:
//If pStream->Wait returns true, it implies that there's
//another stream. However, even without another pin
//(that is, pStream->Wait returns false), we must still
//wait if we're paused.
//If video stream is ahead of audio, then wait for audio.
//Audio stream will signal when new sample is delivered.
//If there's no audio stream connection, then don't wait.
//If audio stream is EOS, then don't wait.
//While we're waiting, we must wake up because:
// receipt of audio sample
// flush
// transition to stopped
// any other transition?
//We must signal receipt of this video sample, so audio
//streaming thread will wake up.
//How do we detect whether video stream is ahead of audio?
//If we wait, we must release the filter lock.
//If we transition to stopped, then wake up and release caller.
//If we're paused, then write frame, and block caller.
//If we transition from paused, then wake up and release caller.
const HANDLE hOther = GetOtherHandle();
enum { cHandles = 2 };
HANDLE hh[cHandles] = { m_hStateChangeOrFlush, hOther };
#ifdef DEBUG_WAIT
wodbgstream os;
#endif
for (;;)
{
if (m_bFlush)
{
#ifdef DEBUG_WAIT
os << L"inpin[" << m_id << "]::wait: FLUSH; EXITING" << endl;
#endif
return S_FALSE;
}
const FILTER_STATE state = m_pFilter->m_state;
if (state == State_Stopped)
{
#ifdef DEBUG_WAIT
os << L"inpin[" << m_id << "]::wait: STATE=STOPPED; EXITING"
<< endl;
#endif
return VFW_E_NOT_RUNNING;
}
if ((state == State_Running) && !m_pStream->Wait())
{
#ifdef DEBUG_WAIT
os << L"inpin["
<< m_id
<< "]::wait: STATE=RUNNING and !WAIT; EXITING; THREAD=0x"
<< hex << GetCurrentThreadId() << dec
<< endl;
#endif
return S_OK;
}
#ifdef DEBUG_WAIT
os << L"inpin[" << m_id << "]::wait: state="
<< state
<< "; WAITING; THREAD=0x"
<< hex << GetCurrentThreadId() << dec
<< endl;
#endif
assert(bool(m_pPinConnection));
assert(!m_bEndOfStream);
lock.Release();
DWORD index;
HRESULT hr = CoWaitForMultipleHandles(
0, //wait flags
INFINITE,
cHandles,
hh,
&index);
assert(hr == S_OK);
hr = lock.Seize(m_pFilter);
if (FAILED(hr)) //should never happen
return S_FALSE; //what else can we do?
}
}
//TODO: handle wait here, instead of in Receive?
HRESULT Inpin::ReceiveMultiple(
IMediaSample** pSamples,
long n, //in
long* pm) //out
{
if (pm == 0)
return E_POINTER;
long& m = *pm; //out
m = 0;
if (n <= 0)
return S_OK; //weird
if (pSamples == 0)
return E_INVALIDARG;
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
#ifdef DEBUG_RECEIVE_MULTIPLE
wodbgstream os;
os << L"inpin[" << m_id << "]::ReceiveMultiple(begin): THREAD=0x"
<< hex << GetCurrentThreadId() << dec
<< endl;
#endif
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_WRONG_STATE; //?
if (m_bEndOfStream)
return VFW_E_SAMPLE_REJECTED_EOS;
if (m_bFlush)
return S_FALSE;
for (long i = 0; i < n; ++i)
{
IMediaSample* const pSample = pSamples[i];
assert(pSample);
#ifdef DEBUG_RECEIVE_MULTIPLE
{
os << L"inpin[" << m_id << "]::ReceiveMultiple(cont'd):";
__int64 st, sp;
HRESULT hr = pSample->GetTime(&st, &sp);
if (hr == S_OK)
os << " st=" << st << " sp=" << sp;
else if (SUCCEEDED(hr))
os << " st=" << st;
else
os << " (NO TIME SET)";
os << "; PREROLL="
<< ((pSample->IsPreroll() == S_OK) ? "TRUE" : "FALSE");
os << endl;
}
#endif
const HRESULT hr = m_pStream->Receive(pSample);
if (hr != S_OK)
{
if (m > 0)
break;
return hr;
}
++m;
}
const BOOL b = SetEvent(m_hSample); //notify other pin
assert(b);
return Wait(lock);
}
HRESULT Inpin::ReceiveCanBlock()
{
return S_OK; //yes, we block caller
}
HRESULT Inpin::OnReceiveConnection(IPin*, const AM_MEDIA_TYPE&)
{
return S_OK;
}
HRESULT Inpin::OnEndOfStream()
{
#if 0
wodbgstream os;
os << L"inpin[" << m_id << L"]::EndOfStream (before calling stream)"
<< endl;
#endif
const int result = m_pStream->EndOfStream();
#if 0
os << L"inpin[" << m_id << L"]::EndOfStream (after calling stream);"
<< " result="
<< result
<< endl;
#endif
const BOOL b = SetEvent(m_hSample); //notify other pin
assert(b);
if (result <= 0)
return S_OK;
const HRESULT hr = m_pFilter->OnEndOfStream();
hr;
assert(SUCCEEDED(hr));
return S_OK;
}
HRESULT Inpin::ResetPosition()
{
//Filter locked by caller
if (!bool(m_pPinConnection))
return S_FALSE;
const GraphUtil::IMediaSeekingPtr pSeek(m_pPinConnection);
if (!bool(pSeek))
return S_FALSE;
LONGLONG tCurr = 0;
const HRESULT hr = pSeek->SetPositions(
&tCurr,
AM_SEEKING_AbsolutePositioning,
0,
AM_SEEKING_NoPositioning);
return hr;
}
} //end namespace WebmMuxLib