mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
1791 lines
48 KiB
C++
1791 lines
48 KiB
C++
#include <windows.h>
|
|
#include <AtlBase.h>
|
|
#include <streams.h>
|
|
#include <strsafe.h>
|
|
|
|
#include <qnetwork.h>
|
|
#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const.
|
|
#include "audioswitch.h"
|
|
|
|
// Implements the CAudioSwitchRenderer class
|
|
|
|
CAudioSwitchRenderer::CAudioSwitchRenderer(REFCLSID RenderClass, // CLSID for this renderer
|
|
TCHAR *pName, // Debug ONLY description
|
|
LPUNKNOWN pUnk, // Aggregated owner object
|
|
HRESULT *phr) : // General OLE return code
|
|
|
|
CBaseFilter(pName, pUnk, &m_InterfaceLock, RenderClass),
|
|
m_evComplete(TRUE),
|
|
m_bAbort(FALSE),
|
|
m_pPosition(NULL),
|
|
m_ThreadSignal(TRUE),
|
|
m_bStreaming(FALSE),
|
|
m_bEOS(FALSE),
|
|
m_bEOSDelivered(FALSE),
|
|
m_dwAdvise(0),
|
|
m_pQSink(NULL),
|
|
m_bRepaintStatus(TRUE),
|
|
m_SignalTime(0),
|
|
m_bInReceive(FALSE),
|
|
m_EndOfStreamTimer(0),
|
|
m_inputSelected(0)
|
|
{
|
|
for (int i = 0;i < 16;i++) m_pInputPin[i] = NULL;
|
|
for (int i = 0;i < 16;i++) m_pMediaSample[i] = NULL;
|
|
Ready();
|
|
#ifdef PERF
|
|
m_idBaseStamp = MSR_REGISTER("BaseRenderer: sample time stamp");
|
|
m_idBaseRenderTime = MSR_REGISTER("BaseRenderer: draw time (msec)");
|
|
m_idBaseAccuracy = MSR_REGISTER("BaseRenderer: Accuracy (msec)");
|
|
#endif
|
|
}
|
|
|
|
|
|
// Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
|
|
// object. The object is created when somebody queries us. These are standard
|
|
// control interfaces for seeking and setting start/stop positions and rates.
|
|
// We will probably also have made an input pin based on CAudioSwitchRendererInputPin
|
|
// that has to be deleted, it's created when an enumerator calls our GetPin
|
|
|
|
CAudioSwitchRenderer::~CAudioSwitchRenderer()
|
|
{
|
|
ASSERT(m_bStreaming == FALSE);
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
StopStreaming();
|
|
ClearPendingSample();
|
|
|
|
// Delete any IMediaPosition implementation
|
|
|
|
if (m_pPosition)
|
|
{
|
|
delete m_pPosition;
|
|
m_pPosition = NULL;
|
|
}
|
|
|
|
// Delete any input pin created
|
|
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
if (m_pInputPin[i])
|
|
{
|
|
delete m_pInputPin[i];
|
|
m_pInputPin[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// Release any Quality sink
|
|
|
|
ASSERT(m_pQSink == NULL);
|
|
}
|
|
|
|
|
|
// This returns the IMediaPosition and IMediaSeeking interfaces
|
|
|
|
HRESULT CAudioSwitchRenderer::GetMediaPositionInterface(REFIID riid, void **ppv)
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
if (m_pPosition)
|
|
{
|
|
return m_pPosition->NonDelegatingQueryInterface(riid, ppv);
|
|
}
|
|
|
|
HRESULT hr = NOERROR;
|
|
|
|
// Create implementation of this dynamically since sometimes we may
|
|
// never try and do a seek. The helper object implements a position
|
|
// control interface (IMediaPosition) which in fact simply takes the
|
|
// calls normally from the filter graph and passes them upstream
|
|
|
|
m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
|
|
CBaseFilter::GetOwner(),
|
|
(HRESULT *) & hr,
|
|
GetPin(m_inputSelected));
|
|
if (m_pPosition == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete m_pPosition;
|
|
m_pPosition = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
return GetMediaPositionInterface(riid, ppv);
|
|
}
|
|
|
|
|
|
// Overriden to say what interfaces we support and where
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::NonDelegatingQueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
// Do we have this interface
|
|
|
|
if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking)
|
|
{
|
|
return GetMediaPositionInterface(riid, ppv);
|
|
}
|
|
else
|
|
{
|
|
return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);
|
|
}
|
|
}
|
|
|
|
|
|
// This is called whenever we change states, we have a manual reset event that
|
|
// is signalled whenever we don't won't the source filter thread to wait in us
|
|
// (such as in a stopped state) and likewise is not signalled whenever it can
|
|
// wait (during paused and running) this function sets or resets the thread
|
|
// event. The event is used to stop source filter threads waiting in Receive
|
|
|
|
HRESULT CAudioSwitchRenderer::SourceThreadCanWait(BOOL bCanWait)
|
|
{
|
|
if (bCanWait == TRUE)
|
|
{
|
|
m_ThreadSignal.Reset();
|
|
}
|
|
else
|
|
{
|
|
m_ThreadSignal.Set();
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
// Dump the current renderer state to the debug terminal. The hardest part of
|
|
// the renderer is the window where we unlock everything to wait for a clock
|
|
// to signal it is time to draw or for the application to cancel everything
|
|
// by stopping the filter. If we get things wrong we can leave the thread in
|
|
// WaitForRenderTime with no way for it to ever get out and we will deadlock
|
|
|
|
void CAudioSwitchRenderer::DisplayRendererState()
|
|
{
|
|
DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime")));
|
|
|
|
// No way should this be signalled at this point
|
|
|
|
BOOL bSignalled = m_ThreadSignal.Check();
|
|
DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"), bSignalled));
|
|
|
|
// Now output the current renderer state variables
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"), m_State));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"), m_bAbort));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"), m_bStreaming));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"), m_dwAdvise));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"), m_pMediaSample[m_inputSelected]));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"), m_bEOS));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"), m_bEOSDelivered));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"), m_bRepaintStatus));
|
|
|
|
|
|
// Output the delayed end of stream timer information
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"), m_EndOfStreamTimer));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"), CDisp((LONGLONG)m_SignalTime)));
|
|
|
|
|
|
// Should never timeout during a flushing state
|
|
|
|
BOOL bFlushing = m_pInputPin[m_inputSelected]->IsFlushing();
|
|
DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"), bFlushing));
|
|
|
|
// Display the time we were told to start at
|
|
DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"), CDisp((LONGLONG)m_tStart.m_time)));
|
|
|
|
// Have we got a reference clock
|
|
if (m_pClock == NULL) return ;
|
|
|
|
// Get the current time from the wall clock
|
|
|
|
CRefTime CurrentTime, StartTime, EndTime;
|
|
m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime);
|
|
CRefTime Offset = CurrentTime - m_tStart;
|
|
|
|
// Display the current time from the clock
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"), CDisp((LONGLONG)CurrentTime.m_time)));
|
|
|
|
DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"), Offset.Millisecs()));
|
|
|
|
|
|
// Do we have a sample ready to render
|
|
if (m_pMediaSample[m_inputSelected] == NULL) return ;
|
|
|
|
m_pMediaSample[m_inputSelected]->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
|
|
DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"),
|
|
StartTime.Millisecs(), EndTime.Millisecs()));
|
|
|
|
// Calculate how long it is until it is due for rendering
|
|
CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
|
|
DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"), Wait.Millisecs()));
|
|
}
|
|
#endif
|
|
|
|
|
|
// Wait until the clock sets the timer event or we're otherwise signalled. We
|
|
// set an arbitrary timeout for this wait and if it fires then we display the
|
|
// current renderer state on the debugger. It will often fire if the filter's
|
|
// left paused in an application however it may also fire during stress tests
|
|
// if the synchronisation with application seeks and state changes is faulty
|
|
|
|
#define RENDER_TIMEOUT 10000
|
|
|
|
HRESULT CAudioSwitchRenderer::WaitForRenderTime()
|
|
{
|
|
HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent };
|
|
DWORD Result = WAIT_TIMEOUT;
|
|
|
|
// Wait for either the time to arrive or for us to be stopped
|
|
|
|
OnWaitStart();
|
|
while (Result == WAIT_TIMEOUT)
|
|
{
|
|
Result = WaitForMultipleObjects(2, WaitObjects, FALSE, RENDER_TIMEOUT);
|
|
|
|
#ifdef DEBUG
|
|
if (Result == WAIT_TIMEOUT) DisplayRendererState();
|
|
#endif
|
|
|
|
}
|
|
OnWaitEnd();
|
|
|
|
// We may have been awoken without the timer firing
|
|
|
|
if (Result == WAIT_OBJECT_0)
|
|
{
|
|
return VFW_E_STATE_CHANGED;
|
|
}
|
|
|
|
SignalTimerFired();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Poll waiting for Receive to complete. This really matters when
|
|
// Receive may set the palette and cause window messages
|
|
// The problem is that if we don't really wait for a renderer to
|
|
// stop processing we can deadlock waiting for a transform which
|
|
// is calling the renderer's Receive() method because the transform's
|
|
// Stop method doesn't know to process window messages to unblock
|
|
// the renderer's Receive processing
|
|
void CAudioSwitchRenderer::WaitForReceiveToComplete()
|
|
{
|
|
for (;;)
|
|
{
|
|
if (!m_bInReceive)
|
|
{
|
|
break;
|
|
}
|
|
|
|
MSG msg;
|
|
// Receive all interthread snedmessages
|
|
PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE);
|
|
|
|
Sleep(1);
|
|
}
|
|
|
|
// If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
|
|
// above just cleared the changebit which will cause some messaging
|
|
// calls to block (waitMessage, MsgWaitFor...) now.
|
|
// Post a dummy message to set the QS_POSTMESSAGE bit again
|
|
if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE)
|
|
{
|
|
// Send dummy message
|
|
PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
|
|
}
|
|
}
|
|
|
|
// A filter can have four discrete states, namely Stopped, Running, Paused,
|
|
// Intermediate. We are in an intermediate state if we are currently trying
|
|
// to pause but haven't yet got the first sample (or if we have been flushed
|
|
// in paused state and therefore still have to wait for a sample to arrive)
|
|
|
|
// This class contains an event called m_evComplete which is signalled when
|
|
// the current state is completed and is not signalled when we are waiting to
|
|
// complete the last state transition. As mentioned above the only time we
|
|
// use this at the moment is when we wait for a media sample in paused state
|
|
// If while we are waiting we receive an end of stream notification from the
|
|
// source filter then we know no data is imminent so we can reset the event
|
|
// This means that when we transition to paused the source filter must call
|
|
// end of stream on us or send us an image otherwise we'll hang indefinately
|
|
|
|
|
|
// Simple internal way of getting the real state
|
|
|
|
FILTER_STATE CAudioSwitchRenderer::GetRealState()
|
|
{
|
|
return m_State;
|
|
}
|
|
|
|
|
|
// The renderer doesn't complete the full transition to paused states until
|
|
// it has got one media sample to render. If you ask it for its state while
|
|
// it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::GetState(DWORD dwMSecs, FILTER_STATE *State)
|
|
{
|
|
CheckPointer(State, E_POINTER);
|
|
|
|
if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT)
|
|
{
|
|
*State = m_State;
|
|
return VFW_S_STATE_INTERMEDIATE;
|
|
}
|
|
*State = m_State;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// If we're pausing and we have no samples we don't complete the transition
|
|
// to State_Paused and we return S_FALSE. However if the m_bAbort flag has
|
|
// been set then all samples are rejected so there is no point waiting for
|
|
// one. If we do have a sample then return NOERROR. We will only ever return
|
|
// VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
|
|
// (calling GetState after either being stopped or Run will NOT return this)
|
|
|
|
HRESULT CAudioSwitchRenderer::CompleteStateChange(FILTER_STATE OldState)
|
|
{
|
|
// Allow us to be paused when disconnected
|
|
|
|
if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE)
|
|
{
|
|
Ready();
|
|
return S_OK;
|
|
}
|
|
|
|
// Have we run off the end of stream
|
|
|
|
if (IsEndOfStream() == TRUE)
|
|
{
|
|
Ready();
|
|
return S_OK;
|
|
}
|
|
|
|
// Make sure we get fresh data after being stopped
|
|
|
|
if (HaveCurrentSample() == TRUE)
|
|
{
|
|
if (OldState != State_Stopped)
|
|
{
|
|
Ready();
|
|
return S_OK;
|
|
}
|
|
}
|
|
NotReady();
|
|
return S_FALSE;
|
|
}
|
|
|
|
|
|
// When we stop the filter the things we do are:-
|
|
|
|
// Decommit the allocator being used in the connection
|
|
// Release the source filter if it's waiting in Receive
|
|
// Cancel any advise link we set up with the clock
|
|
// Any end of stream signalled is now obsolete so reset
|
|
// Allow us to be stopped when we are not connected
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::Stop()
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
|
|
// Make sure there really is a state change
|
|
|
|
if (m_State == State_Stopped)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// Is our input pin connected
|
|
|
|
if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE)
|
|
{
|
|
NOTE("Input pin is not connected");
|
|
m_State = State_Stopped;
|
|
return NOERROR;
|
|
}
|
|
|
|
CBaseFilter::Stop();
|
|
|
|
// If we are going into a stopped state then we must decommit whatever
|
|
// allocator we are using it so that any source filter waiting in the
|
|
// GetBuffer can be released and unlock themselves for a state change
|
|
|
|
if (m_pInputPin[m_inputSelected]->Allocator())
|
|
{
|
|
m_pInputPin[m_inputSelected]->Allocator()->Decommit();
|
|
}
|
|
|
|
// Cancel any scheduled rendering
|
|
|
|
SetRepaintStatus(TRUE);
|
|
StopStreaming();
|
|
SourceThreadCanWait(FALSE);
|
|
ResetEndOfStream();
|
|
CancelNotification();
|
|
|
|
// There should be no outstanding clock advise
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
|
|
Ready();
|
|
WaitForReceiveToComplete();
|
|
m_bAbort = FALSE;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// When we pause the filter the things we do are:-
|
|
|
|
// Commit the allocator being used in the connection
|
|
// Allow a source filter thread to wait in Receive
|
|
// Cancel any clock advise link (we may be running)
|
|
// Possibly complete the state change if we have data
|
|
// Allow us to be paused when we are not connected
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::Pause()
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
FILTER_STATE OldState = m_State;
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE);
|
|
|
|
// Make sure there really is a state change
|
|
|
|
if (m_State == State_Paused)
|
|
{
|
|
return CompleteStateChange(State_Paused);
|
|
}
|
|
|
|
// Has our input pin been connected
|
|
|
|
if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE)
|
|
{
|
|
NOTE("Input pin is not connected");
|
|
m_State = State_Paused;
|
|
return CompleteStateChange(State_Paused);
|
|
}
|
|
|
|
// Pause the base filter class
|
|
|
|
HRESULT hr = CBaseFilter::Pause();
|
|
if (FAILED(hr))
|
|
{
|
|
NOTE("Pause failed");
|
|
return hr;
|
|
}
|
|
|
|
// Enable EC_REPAINT events again
|
|
|
|
SetRepaintStatus(TRUE);
|
|
StopStreaming();
|
|
SourceThreadCanWait(TRUE);
|
|
CancelNotification();
|
|
ResetEndOfStreamTimer();
|
|
|
|
// If we are going into a paused state then we must commit whatever
|
|
// allocator we are using it so that any source filter can call the
|
|
// GetBuffer and expect to get a buffer without returning an error
|
|
|
|
if (m_pInputPin[m_inputSelected]->Allocator())
|
|
{
|
|
m_pInputPin[m_inputSelected]->Allocator()->Commit();
|
|
}
|
|
|
|
// There should be no outstanding advise
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE);
|
|
|
|
// When we come out of a stopped state we must clear any image we were
|
|
// holding onto for frame refreshing. Since renderers see state changes
|
|
// first we can reset ourselves ready to accept the source thread data
|
|
// Paused or running after being stopped causes the current position to
|
|
// be reset so we're not interested in passing end of stream signals
|
|
|
|
if (OldState == State_Stopped)
|
|
{
|
|
m_bAbort = FALSE;
|
|
ClearPendingSample();
|
|
}
|
|
return CompleteStateChange(OldState);
|
|
}
|
|
|
|
|
|
// When we run the filter the things we do are:-
|
|
|
|
// Commit the allocator being used in the connection
|
|
// Allow a source filter thread to wait in Receive
|
|
// Signal the render event just to get us going
|
|
// Start the base class by calling StartStreaming
|
|
// Allow us to be run when we are not connected
|
|
// Signal EC_COMPLETE if we are not connected
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::Run(REFERENCE_TIME StartTime)
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
FILTER_STATE OldState = m_State;
|
|
|
|
// Make sure there really is a state change
|
|
|
|
if (m_State == State_Running)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// Send EC_COMPLETE if we're not connected
|
|
|
|
if (m_pInputPin[m_inputSelected]->IsConnected() == FALSE)
|
|
{
|
|
NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)this);
|
|
m_State = State_Running;
|
|
return NOERROR;
|
|
}
|
|
|
|
Ready();
|
|
|
|
// Pause the base filter class
|
|
|
|
HRESULT hr = CBaseFilter::Run(StartTime);
|
|
if (FAILED(hr))
|
|
{
|
|
NOTE("Run failed");
|
|
return hr;
|
|
}
|
|
|
|
// Allow the source thread to wait
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE);
|
|
SourceThreadCanWait(TRUE);
|
|
SetRepaintStatus(FALSE);
|
|
|
|
// There should be no outstanding advise
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE);
|
|
|
|
// If we are going into a running state then we must commit whatever
|
|
// allocator we are using it so that any source filter can call the
|
|
// GetBuffer and expect to get a buffer without returning an error
|
|
|
|
if (m_pInputPin[m_inputSelected]->Allocator())
|
|
{
|
|
m_pInputPin[m_inputSelected]->Allocator()->Commit();
|
|
}
|
|
|
|
// When we come out of a stopped state we must clear any image we were
|
|
// holding onto for frame refreshing. Since renderers see state changes
|
|
// first we can reset ourselves ready to accept the source thread data
|
|
// Paused or running after being stopped causes the current position to
|
|
// be reset so we're not interested in passing end of stream signals
|
|
|
|
if (OldState == State_Stopped)
|
|
{
|
|
m_bAbort = FALSE;
|
|
ClearPendingSample();
|
|
}
|
|
return StartStreaming();
|
|
}
|
|
|
|
|
|
// Return the number of input pins we support
|
|
|
|
int CAudioSwitchRenderer::GetPinCount()
|
|
{
|
|
return 16;
|
|
}
|
|
|
|
|
|
// We only support one input pin and it is numbered zero
|
|
|
|
CBasePin *CAudioSwitchRenderer::GetPin(int n)
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
HRESULT hr = NOERROR;
|
|
ASSERT(n < 16 && n >= 0);
|
|
|
|
// Should only ever be called with zero
|
|
|
|
if (n > 16)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Create the input pin if not already done so
|
|
|
|
if (m_pInputPin[n] == NULL)
|
|
{
|
|
WCHAR t[256] = {0};
|
|
StringCchPrintfW(t, 256, L"In%d", n);
|
|
m_pInputPin[n] = new CAudioSwitchRendererInputPin(this, &hr, t);
|
|
}
|
|
return m_pInputPin[n];
|
|
}
|
|
|
|
|
|
// If "In" then return the IPin for our input pin, otherwise NULL and error
|
|
|
|
STDMETHODIMP CAudioSwitchRenderer::FindPin(LPCWSTR Id, IPin **ppPin)
|
|
{
|
|
CheckPointer(ppPin, E_POINTER);
|
|
|
|
int gotit = 0;
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
WCHAR t[256] = {0};
|
|
StringCchPrintfW(t, 256, L"In%d", i);
|
|
if (0 == lstrcmpW(Id, t))
|
|
{
|
|
gotit = 1;
|
|
*ppPin = GetPin(i);
|
|
ASSERT(*ppPin);
|
|
(*ppPin)->AddRef();
|
|
}
|
|
}
|
|
if (!gotit)
|
|
{
|
|
*ppPin = NULL;
|
|
return VFW_E_NOT_FOUND;
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Called when the input pin receives an EndOfStream notification. If we have
|
|
// not got a sample, then notify EC_COMPLETE now. If we have samples, then set
|
|
// m_bEOS and check for this on completing samples. If we're waiting to pause
|
|
// then complete the transition to paused state by setting the state event
|
|
|
|
HRESULT CAudioSwitchRenderer::EndOfStream()
|
|
{
|
|
// Ignore these calls if we are stopped
|
|
|
|
if (m_State == State_Stopped)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// If we have a sample then wait for it to be rendered
|
|
|
|
m_bEOS = TRUE;
|
|
if (m_pMediaSample[m_inputSelected])
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// If we are waiting for pause then we are now ready since we cannot now
|
|
// carry on waiting for a sample to arrive since we are being told there
|
|
// won't be any. This sets an event that the GetState function picks up
|
|
|
|
Ready();
|
|
|
|
// Only signal completion now if we are running otherwise queue it until
|
|
// we do run in StartStreaming. This is used when we seek because a seek
|
|
// causes a pause where early notification of completion is misleading
|
|
|
|
if (m_bStreaming)
|
|
{
|
|
SendEndOfStream();
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// When we are told to flush we should release the source thread
|
|
|
|
HRESULT CAudioSwitchRenderer::BeginFlush()
|
|
{
|
|
// If paused then report state intermediate until we get some data
|
|
|
|
if (m_State == State_Paused)
|
|
{
|
|
NotReady();
|
|
}
|
|
|
|
SourceThreadCanWait(FALSE);
|
|
CancelNotification();
|
|
ClearPendingSample();
|
|
// Wait for Receive to complete
|
|
WaitForReceiveToComplete();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// After flushing the source thread can wait in Receive again
|
|
|
|
HRESULT CAudioSwitchRenderer::EndFlush()
|
|
{
|
|
// Reset the current sample media time
|
|
if (m_pPosition) m_pPosition->ResetMediaTime();
|
|
|
|
// There should be no outstanding advise
|
|
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
SourceThreadCanWait(TRUE);
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// We can now send EC_REPAINTs if so required
|
|
|
|
HRESULT CAudioSwitchRenderer::CompleteConnect(IPin *pReceivePin)
|
|
{
|
|
SetRepaintStatus(TRUE);
|
|
m_bAbort = FALSE;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Called when we go paused or running
|
|
|
|
HRESULT CAudioSwitchRenderer::Active()
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Called when we go into a stopped state
|
|
|
|
HRESULT CAudioSwitchRenderer::Inactive()
|
|
{
|
|
if (m_pPosition)
|
|
{
|
|
m_pPosition->ResetMediaTime();
|
|
}
|
|
// People who derive from this may want to override this behaviour
|
|
// to keep hold of the sample in some circumstances
|
|
ClearPendingSample();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Tell derived classes about the media type agreed
|
|
|
|
HRESULT CAudioSwitchRenderer::SetMediaType(const CMediaType *pmt)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// When we break the input pin connection we should reset the EOS flags. When
|
|
// we are asked for either IMediaPosition or IMediaSeeking we will create a
|
|
// CPosPassThru object to handles media time pass through. When we're handed
|
|
// samples we store (by calling CPosPassThru::RegisterMediaTime) their media
|
|
// times so we can then return a real current position of data being rendered
|
|
|
|
HRESULT CAudioSwitchRenderer::BreakConnect()
|
|
{
|
|
// Do we have a quality management sink
|
|
|
|
if (m_pQSink)
|
|
{
|
|
m_pQSink->Release();
|
|
m_pQSink = NULL;
|
|
}
|
|
|
|
// Check we have a valid connection
|
|
|
|
int n = 0;
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
if (!m_pInputPin[i] || m_pInputPin[i]->IsConnected() == FALSE) { n++; continue; }
|
|
|
|
// Check we are stopped before disconnecting
|
|
if (m_State != State_Stopped && !m_pInputPin[i]->CanReconnectWhenActive())
|
|
{
|
|
return VFW_E_NOT_STOPPED;
|
|
}
|
|
}
|
|
|
|
if (n == 16) return S_FALSE;
|
|
|
|
SetRepaintStatus(FALSE);
|
|
ResetEndOfStream();
|
|
ClearPendingSample();
|
|
m_bAbort = FALSE;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Retrieves the sample times for this samples (note the sample times are
|
|
// passed in by reference not value). We return S_FALSE to say schedule this
|
|
// sample according to the times on the sample. We also return S_OK in
|
|
// which case the object should simply render the sample data immediately
|
|
|
|
HRESULT CAudioSwitchRenderer::GetSampleTimes(IMediaSample *pMediaSample,
|
|
REFERENCE_TIME *pStartTime,
|
|
REFERENCE_TIME *pEndTime)
|
|
{
|
|
ASSERT(m_dwAdvise == 0);
|
|
ASSERT(pMediaSample);
|
|
|
|
// If the stop time for this sample is before or the same as start time,
|
|
// then just ignore it (release it) and schedule the next one in line
|
|
// Source filters should always fill in the start and end times properly!
|
|
|
|
if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime)))
|
|
{
|
|
if (*pEndTime < *pStartTime)
|
|
{
|
|
return VFW_E_START_TIME_AFTER_END;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no time set in the sample... draw it now?
|
|
return S_OK;
|
|
}
|
|
|
|
// Can't synchronise without a clock so we return S_OK which tells the
|
|
// caller that the sample should be rendered immediately without going
|
|
// through the overhead of setting a timer advise link with the clock
|
|
|
|
if (m_pClock == NULL)
|
|
{
|
|
return S_OK;
|
|
}
|
|
return ShouldDrawSampleNow(pMediaSample, pStartTime, pEndTime);
|
|
}
|
|
|
|
|
|
// By default all samples are drawn according to their time stamps so we
|
|
// return S_FALSE. Returning S_OK means draw immediately, this is used
|
|
// by the derived video renderer class in its quality management.
|
|
|
|
HRESULT CAudioSwitchRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
|
|
REFERENCE_TIME *ptrStart,
|
|
REFERENCE_TIME *ptrEnd)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
|
|
// We must always reset the current advise time to zero after a timer fires
|
|
// because there are several possible ways which lead us not to do any more
|
|
// scheduling such as the pending image being cleared after state changes
|
|
|
|
void CAudioSwitchRenderer::SignalTimerFired()
|
|
{
|
|
m_dwAdvise = 0;
|
|
}
|
|
|
|
|
|
// Cancel any notification currently scheduled. This is called by the owning
|
|
// window object when it is told to stop streaming. If there is no timer link
|
|
// outstanding then calling this is benign otherwise we go ahead and cancel
|
|
// We must always reset the render event as the quality management code can
|
|
// signal immediate rendering by setting the event without setting an advise
|
|
// link. If we're subsequently stopped and run the first attempt to setup an
|
|
// advise link with the reference clock will find the event still signalled
|
|
|
|
HRESULT CAudioSwitchRenderer::CancelNotification()
|
|
{
|
|
ASSERT(m_dwAdvise == 0 || m_pClock);
|
|
DWORD_PTR dwAdvise = m_dwAdvise;
|
|
|
|
// Have we a live advise link
|
|
|
|
if (m_dwAdvise)
|
|
{
|
|
m_pClock->Unadvise(m_dwAdvise);
|
|
SignalTimerFired();
|
|
ASSERT(m_dwAdvise == 0);
|
|
}
|
|
|
|
// Clear the event and return our status
|
|
|
|
m_RenderEvent.Reset();
|
|
return (dwAdvise ? S_OK : S_FALSE);
|
|
}
|
|
|
|
|
|
// Responsible for setting up one shot advise links with the clock
|
|
// Return FALSE if the sample is to be dropped (not drawn at all)
|
|
// Return TRUE if the sample is to be drawn and in this case also
|
|
// arrange for m_RenderEvent to be set at the appropriate time
|
|
|
|
BOOL CAudioSwitchRenderer::ScheduleSample(IMediaSample *pMediaSample)
|
|
{
|
|
REFERENCE_TIME StartSample, EndSample;
|
|
|
|
// Is someone pulling our leg
|
|
|
|
if (pMediaSample == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Get the next sample due up for rendering. If there aren't any ready
|
|
// then GetNextSampleTimes returns an error. If there is one to be done
|
|
// then it succeeds and yields the sample times. If it is due now then
|
|
// it returns S_OK other if it's to be done when due it returns S_FALSE
|
|
|
|
HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
|
|
if (FAILED(hr))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// If we don't have a reference clock then we cannot set up the advise
|
|
// time so we simply set the event indicating an image to render. This
|
|
// will cause us to run flat out without any timing or synchronisation
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
|
|
return TRUE;
|
|
}
|
|
|
|
ASSERT(m_dwAdvise == 0);
|
|
ASSERT(m_pClock);
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
|
|
// We do have a valid reference clock interface so we can ask it to
|
|
// set an event when the image comes due for rendering. We pass in
|
|
// the reference time we were told to start at and also the current
|
|
// stream time which is the offset from the start reference time
|
|
|
|
hr = m_pClock->AdviseTime(
|
|
(REFERENCE_TIME) m_tStart, // Start run time
|
|
StartSample, // Stream time
|
|
(HEVENT)(HANDLE) m_RenderEvent, // Render notification
|
|
&m_dwAdvise); // Advise cookie
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// We could not schedule the next sample for rendering despite the fact
|
|
// we have a valid sample here. This is a fair indication that either
|
|
// the system clock is wrong or the time stamp for the sample is duff
|
|
|
|
ASSERT(m_dwAdvise == 0);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// This is called when a sample comes due for rendering. We pass the sample
|
|
// on to the derived class. After rendering we will initialise the timer for
|
|
// the next sample, NOTE signal that the last one fired first, if we don't
|
|
// do this it thinks there is still one outstanding that hasn't completed
|
|
|
|
HRESULT CAudioSwitchRenderer::Render(IMediaSample *pMediaSample)
|
|
{
|
|
// If the media sample is NULL then we will have been notified by the
|
|
// clock that another sample is ready but in the mean time someone has
|
|
// stopped us streaming which causes the next sample to be released
|
|
|
|
if (pMediaSample == NULL)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
// If we have stopped streaming then don't render any more samples, the
|
|
// thread that got in and locked us and then reset this flag does not
|
|
// clear the pending sample as we can use it to refresh any output device
|
|
|
|
if (m_bStreaming == FALSE)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
// Time how long the rendering takes
|
|
|
|
OnRenderStart(pMediaSample);
|
|
DoRenderSample(pMediaSample);
|
|
OnRenderEnd(pMediaSample);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Checks if there is a sample waiting at the renderer
|
|
|
|
BOOL CAudioSwitchRenderer::HaveCurrentSample()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
return (m_pMediaSample[m_inputSelected] == NULL ? FALSE : TRUE);
|
|
}
|
|
|
|
|
|
// Returns the current sample waiting at the video renderer. We AddRef the
|
|
// sample before returning so that should it come due for rendering the
|
|
// person who called this method will hold the remaining reference count
|
|
// that will stop the sample being added back onto the allocator free list
|
|
|
|
IMediaSample *CAudioSwitchRenderer::GetCurrentSample()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
if (m_pMediaSample[m_inputSelected])
|
|
{
|
|
m_pMediaSample[m_inputSelected]->AddRef();
|
|
}
|
|
return m_pMediaSample[m_inputSelected];
|
|
}
|
|
|
|
|
|
// Called when the source delivers us a sample. We go through a few checks to
|
|
// make sure the sample can be rendered. If we are running (streaming) then we
|
|
// have the sample scheduled with the reference clock, if we are not streaming
|
|
// then we have received an sample in paused mode so we can complete any state
|
|
// transition. On leaving this function everything will be unlocked so an app
|
|
// thread may get in and change our state to stopped (for example) in which
|
|
// case it will also signal the thread event so that our wait call is stopped
|
|
|
|
HRESULT CAudioSwitchRenderer::PrepareReceive(IMediaSample *pMediaSample)
|
|
{
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
m_bInReceive = TRUE;
|
|
|
|
// Check our flushing and filter state
|
|
|
|
HRESULT hr = m_pInputPin[m_inputSelected]->CBaseInputPin::Receive(pMediaSample);
|
|
|
|
if (hr != NOERROR)
|
|
{
|
|
m_bInReceive = FALSE;
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Has the type changed on a media sample. We do all rendering
|
|
// synchronously on the source thread, which has a side effect
|
|
// that only one buffer is ever outstanding. Therefore when we
|
|
// have Receive called we can go ahead and change the format
|
|
// Since the format change can cause a SendMessage we just don't
|
|
// lock
|
|
if (m_pInputPin[m_inputSelected]->SampleProps()->pMediaType)
|
|
{
|
|
m_pInputPin[m_inputSelected]->SetMediaType((CMediaType *)m_pInputPin[m_inputSelected]->SampleProps()->pMediaType);
|
|
}
|
|
|
|
|
|
CAutoLock cSampleLock(&m_RendererLock);
|
|
|
|
ASSERT(IsActive() == TRUE);
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsFlushing() == FALSE);
|
|
ASSERT(m_pInputPin[m_inputSelected]->IsConnected() == TRUE);
|
|
ASSERT(m_pMediaSample[m_inputSelected] == NULL);
|
|
|
|
// Return an error if we already have a sample waiting for rendering
|
|
// source pins must serialise the Receive calls - we also check that
|
|
// no data is being sent after the source signalled an end of stream
|
|
|
|
if (m_pMediaSample[m_inputSelected] || m_bEOS || m_bAbort)
|
|
{
|
|
Ready();
|
|
m_bInReceive = FALSE;
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Store the media times from this sample
|
|
if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);
|
|
|
|
// Schedule the next sample if we are streaming
|
|
|
|
if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE))
|
|
{
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
m_bInReceive = FALSE;
|
|
return VFW_E_SAMPLE_REJECTED;
|
|
}
|
|
|
|
// Store the sample end time for EC_COMPLETE handling
|
|
m_SignalTime = m_pInputPin[m_inputSelected]->SampleProps()->tStop;
|
|
|
|
// BEWARE we sometimes keep the sample even after returning the thread to
|
|
// the source filter such as when we go into a stopped state (we keep it
|
|
// to refresh the device with) so we must AddRef it to keep it safely. If
|
|
// we start flushing the source thread is released and any sample waiting
|
|
// will be released otherwise GetBuffer may never return (see BeginFlush)
|
|
|
|
m_pMediaSample[m_inputSelected] = pMediaSample;
|
|
m_pMediaSample[m_inputSelected]->AddRef();
|
|
|
|
if (m_bStreaming == FALSE)
|
|
{
|
|
SetRepaintStatus(TRUE);
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Called by the source filter when we have a sample to render. Under normal
|
|
// circumstances we set an advise link with the clock, wait for the time to
|
|
// arrive and then render the data using the PURE virtual DoRenderSample that
|
|
// the derived class will have overriden. After rendering the sample we may
|
|
// also signal EOS if it was the last one sent before EndOfStream was called
|
|
|
|
HRESULT CAudioSwitchRenderer::Receive(IMediaSample *pSample)
|
|
{
|
|
ASSERT(pSample);
|
|
|
|
// It may return VFW_E_SAMPLE_REJECTED code to say don't bother
|
|
|
|
HRESULT hr = PrepareReceive(pSample);
|
|
ASSERT(m_bInReceive == SUCCEEDED(hr));
|
|
if (FAILED(hr))
|
|
{
|
|
if (hr == VFW_E_SAMPLE_REJECTED)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// We realize the palette in "PrepareRender()" so we have to give away the
|
|
// filter lock here.
|
|
if (m_State == State_Paused)
|
|
{
|
|
PrepareRender();
|
|
// no need to use InterlockedExchange
|
|
m_bInReceive = FALSE;
|
|
{
|
|
// We must hold both these locks
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
if (m_State == State_Stopped)
|
|
return NOERROR;
|
|
m_bInReceive = TRUE;
|
|
}
|
|
Ready();
|
|
}
|
|
// Having set an advise link with the clock we sit and wait. We may be
|
|
// awoken by the clock firing or by a state change. The rendering call
|
|
// will lock the critical section and check we can still render the data
|
|
|
|
hr = WaitForRenderTime();
|
|
if (FAILED(hr))
|
|
{
|
|
m_bInReceive = FALSE;
|
|
return NOERROR;
|
|
}
|
|
|
|
PrepareRender();
|
|
|
|
// Set this here and poll it until we work out the locking correctly
|
|
// It can't be right that the streaming stuff grabs the interface
|
|
// lock - after all we want to be able to wait for this stuff
|
|
// to complete
|
|
m_bInReceive = FALSE;
|
|
|
|
// We must hold both these locks
|
|
CAutoLock cRendererLock(&m_InterfaceLock);
|
|
|
|
// since we gave away the filter wide lock, the sate of the filter could
|
|
// have chnaged to Stopped
|
|
if (m_State == State_Stopped)
|
|
return NOERROR;
|
|
|
|
CAutoLock cSampleLock(&m_RendererLock);
|
|
|
|
// Deal with this sample
|
|
|
|
Render(m_pMediaSample[m_inputSelected]);
|
|
ClearPendingSample();
|
|
SendEndOfStream();
|
|
CancelNotification();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// This is called when we stop or are inactivated to clear the pending sample
|
|
// We release the media sample interface so that they can be allocated to the
|
|
// source filter again, unless of course we are changing state to inactive in
|
|
// which case GetBuffer will return an error. We must also reset the current
|
|
// media sample to NULL so that we know we do not currently have an image
|
|
|
|
HRESULT CAudioSwitchRenderer::ClearPendingSample()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
if (m_pMediaSample[i])
|
|
{
|
|
m_pMediaSample[i]->Release();
|
|
m_pMediaSample[i] = NULL;
|
|
}
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
// Do the timer callback work
|
|
void CAudioSwitchRenderer::TimerCallback()
|
|
{
|
|
// Lock for synchronization (but don't hold this lock when calling
|
|
// timeKillEvent)
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
|
|
// See if we should signal end of stream now
|
|
|
|
if (m_EndOfStreamTimer)
|
|
{
|
|
m_EndOfStreamTimer = 0;
|
|
SendEndOfStream();
|
|
}
|
|
}
|
|
|
|
|
|
// If we are at the end of the stream signal the filter graph but do not set
|
|
// the state flag back to FALSE. Once we drop off the end of the stream we
|
|
// leave the flag set (until a subsequent ResetEndOfStream). Each sample we
|
|
// get delivered will update m_SignalTime to be the last sample's end time.
|
|
// We must wait this long before signalling end of stream to the filtergraph
|
|
|
|
#define TIMEOUT_DELIVERYWAIT 50
|
|
#define TIMEOUT_RESOLUTION 10
|
|
|
|
HRESULT CAudioSwitchRenderer::SendEndOfStream()
|
|
{
|
|
ASSERT(CritCheckIn(&m_RendererLock));
|
|
if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// If there is no clock then signal immediately
|
|
if (m_pClock == NULL)
|
|
{
|
|
return NotifyEndOfStream();
|
|
}
|
|
|
|
// How long into the future is the delivery time
|
|
|
|
REFERENCE_TIME Signal = m_tStart + m_SignalTime;
|
|
REFERENCE_TIME CurrentTime;
|
|
m_pClock->GetTime(&CurrentTime);
|
|
LONG Delay = LONG((Signal - CurrentTime) / 10000);
|
|
|
|
// Dump the timing information to the debugger
|
|
|
|
NOTE1("Delay until end of stream delivery %d", Delay);
|
|
NOTE1("Current %s", (LPCTSTR)CDisp((LONGLONG)CurrentTime));
|
|
NOTE1("Signal %s", (LPCTSTR)CDisp((LONGLONG)Signal));
|
|
|
|
// Wait for the delivery time to arrive
|
|
|
|
if (Delay < TIMEOUT_DELIVERYWAIT)
|
|
{
|
|
return NotifyEndOfStream();
|
|
}
|
|
|
|
// Signal a timer callback on another worker thread
|
|
|
|
m_EndOfStreamTimer = timeSetEvent((UINT) Delay, // Period of timer
|
|
TIMEOUT_RESOLUTION, // Timer resolution
|
|
EndOfStreamTimer, // Callback function
|
|
DWORD_PTR(this), // Used information
|
|
TIME_ONESHOT); // Type of callback
|
|
if (m_EndOfStreamTimer == 0)
|
|
{
|
|
return NotifyEndOfStream();
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Signals EC_COMPLETE to the filtergraph manager
|
|
|
|
HRESULT CAudioSwitchRenderer::NotifyEndOfStream()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
ASSERT(m_bEOS == TRUE);
|
|
ASSERT(m_bEOSDelivered == FALSE);
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
|
|
// Has the filter changed state
|
|
|
|
if (m_bStreaming == FALSE)
|
|
{
|
|
ASSERT(m_EndOfStreamTimer == 0);
|
|
return NOERROR;
|
|
}
|
|
|
|
// Reset the end of stream timer
|
|
m_EndOfStreamTimer = 0;
|
|
|
|
// If we've been using the IMediaPosition interface, set it's start
|
|
// and end media "times" to the stop position by hand. This ensures
|
|
// that we actually get to the end, even if the MPEG guestimate has
|
|
// been bad or if the quality management dropped the last few frames
|
|
|
|
if (m_pPosition) m_pPosition->EOS();
|
|
m_bEOSDelivered = TRUE;
|
|
NOTE("Sending EC_COMPLETE...");
|
|
return NotifyEvent(EC_COMPLETE, S_OK, (LONG_PTR)(IBaseFilter *)this);
|
|
}
|
|
|
|
|
|
// Reset the end of stream flag, this is typically called when we transfer to
|
|
// stopped states since that resets the current position back to the start so
|
|
// we will receive more samples or another EndOfStream if there aren't any. We
|
|
// keep two separate flags one to say we have run off the end of the stream
|
|
// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
|
|
// to the filter graph. We need the latter otherwise we can end up sending an
|
|
// EC_COMPLETE every time the source changes state and calls our EndOfStream
|
|
|
|
HRESULT CAudioSwitchRenderer::ResetEndOfStream()
|
|
{
|
|
ResetEndOfStreamTimer();
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
|
|
m_bEOS = FALSE;
|
|
m_bEOSDelivered = FALSE;
|
|
m_SignalTime = 0;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Kills any outstanding end of stream timer
|
|
|
|
void CAudioSwitchRenderer::ResetEndOfStreamTimer()
|
|
{
|
|
ASSERT(CritCheckOut(&m_RendererLock));
|
|
if (m_EndOfStreamTimer)
|
|
{
|
|
timeKillEvent(m_EndOfStreamTimer);
|
|
m_EndOfStreamTimer = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// This is called when we start running so that we can schedule any pending
|
|
// image we have with the clock and display any timing information. If we
|
|
// don't have any sample but we have queued an EOS flag then we send it. If
|
|
// we do have a sample then we wait until that has been rendered before we
|
|
// signal the filter graph otherwise we may change state before it's done
|
|
|
|
HRESULT CAudioSwitchRenderer::StartStreaming()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
if (m_bStreaming == TRUE)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
// Reset the streaming times ready for running
|
|
|
|
m_bStreaming = TRUE;
|
|
timeBeginPeriod(1);
|
|
OnStartStreaming();
|
|
|
|
// There should be no outstanding advise
|
|
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent, 0));
|
|
ASSERT(CancelNotification() == S_FALSE);
|
|
|
|
// If we have an EOS and no data then deliver it now
|
|
|
|
if (m_pMediaSample[m_inputSelected] == NULL)
|
|
{
|
|
return SendEndOfStream();
|
|
}
|
|
|
|
// Have the data rendered
|
|
|
|
ASSERT(m_pMediaSample[m_inputSelected]);
|
|
if (!ScheduleSample(m_pMediaSample[m_inputSelected]))
|
|
m_RenderEvent.Set();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// This is called when we stop streaming so that we can set our internal flag
|
|
// indicating we are not now to schedule any more samples arriving. The state
|
|
// change methods in the filter implementation take care of cancelling any
|
|
// clock advise link we have set up and clearing any pending sample we have
|
|
|
|
HRESULT CAudioSwitchRenderer::StopStreaming()
|
|
{
|
|
CAutoLock cRendererLock(&m_RendererLock);
|
|
m_bEOSDelivered = FALSE;
|
|
|
|
if (m_bStreaming == TRUE)
|
|
{
|
|
m_bStreaming = FALSE;
|
|
OnStopStreaming();
|
|
timeEndPeriod(1);
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// We have a boolean flag that is reset when we have signalled EC_REPAINT to
|
|
// the filter graph. We set this when we receive an image so that should any
|
|
// conditions arise again we can send another one. By having a flag we ensure
|
|
// we don't flood the filter graph with redundant calls. We do not set the
|
|
// event when we receive an EndOfStream call since there is no point in us
|
|
// sending further EC_REPAINTs. In particular the AutoShowWindow method and
|
|
// the DirectDraw object use this method to control the window repainting
|
|
|
|
void CAudioSwitchRenderer::SetRepaintStatus(BOOL bRepaint)
|
|
{
|
|
CAutoLock cSampleLock(&m_RendererLock);
|
|
m_bRepaintStatus = bRepaint;
|
|
}
|
|
|
|
|
|
// Pass the window handle to the upstream filter
|
|
|
|
void CAudioSwitchRenderer::SendNotifyWindow(IPin *pPin, HWND hwnd)
|
|
{
|
|
IMediaEventSink *pSink;
|
|
|
|
// Does the pin support IMediaEventSink
|
|
HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink, (void **) & pSink);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pSink->Notify(EC_NOTIFY_WINDOW, LONG_PTR(hwnd), 0);
|
|
pSink->Release();
|
|
}
|
|
NotifyEvent(EC_NOTIFY_WINDOW, LONG_PTR(hwnd), 0);
|
|
}
|
|
|
|
|
|
// Signal an EC_REPAINT to the filter graph. This can be used to have data
|
|
// sent to us. For example when a video window is first displayed it may
|
|
// not have an image to display, at which point it signals EC_REPAINT. The
|
|
// filtergraph will either pause the graph if stopped or if already paused
|
|
// it will call put_CurrentPosition of the current position. Setting the
|
|
// current position to itself has the stream flushed and the image resent
|
|
|
|
#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
|
|
|
|
void CAudioSwitchRenderer::SendRepaint()
|
|
{
|
|
CAutoLock cSampleLock(&m_RendererLock);
|
|
ASSERT(m_pInputPin[m_inputSelected]);
|
|
|
|
// We should not send repaint notifications when...
|
|
// - An end of stream has been notified
|
|
// - Our input pin is being flushed
|
|
// - The input pin is not connected
|
|
// - We have aborted a video playback
|
|
// - There is a repaint already sent
|
|
|
|
if (m_bAbort == FALSE)
|
|
{
|
|
if (m_pInputPin[m_inputSelected]->IsConnected() == TRUE)
|
|
{
|
|
if (m_pInputPin[m_inputSelected]->IsFlushing() == FALSE)
|
|
{
|
|
if (IsEndOfStream() == FALSE)
|
|
{
|
|
if (m_bRepaintStatus == TRUE)
|
|
{
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
IPin *pPin = (IPin *) m_pInputPin[i];
|
|
if (!pPin) continue;
|
|
NotifyEvent(EC_REPAINT, (LONG_PTR) pPin, 0);
|
|
SetRepaintStatus(FALSE);
|
|
RLOG("Sending repaint");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// When a video window detects a display change (WM_DISPLAYCHANGE message) it
|
|
// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
|
|
// filtergraph will stop everyone and reconnect our input pin. As we're then
|
|
// reconnected we can accept the media type that matches the new display mode
|
|
// since we may no longer be able to draw the current image type efficiently
|
|
|
|
BOOL CAudioSwitchRenderer::OnDisplayChange()
|
|
{
|
|
// Ignore if we are not connected yet
|
|
|
|
CAutoLock cSampleLock(&m_RendererLock);
|
|
int n = 0;
|
|
for (int i = 0;i < 16;i++)
|
|
if (!m_pInputPin[i] || m_pInputPin[i]->IsConnected() == FALSE) n++;
|
|
if (n == 16)
|
|
return FALSE;
|
|
|
|
RLOG("Notification of EC_DISPLAY_CHANGE");
|
|
|
|
// Pass our input pin as parameter on the event
|
|
|
|
for (int i = 0;i < 16;i++)
|
|
if (m_pInputPin[i] && m_pInputPin[i]->IsConnected())
|
|
{
|
|
IPin *pPin = (IPin *) m_pInputPin[i];
|
|
m_pInputPin[i]->AddRef();
|
|
NotifyEvent(EC_DISPLAY_CHANGED, (LONG_PTR) pPin, 0);
|
|
SetAbortSignal(TRUE);
|
|
ClearPendingSample();
|
|
m_pInputPin[i]->Release();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Called just before we start drawing.
|
|
// Store the current time in m_trRenderStart to allow the rendering time to be
|
|
// logged. Log the time stamp of the sample and how late it is (neg is early)
|
|
|
|
void CAudioSwitchRenderer::OnRenderStart(IMediaSample *pMediaSample)
|
|
{
|
|
#ifdef PERF
|
|
REFERENCE_TIME trStart, trEnd;
|
|
pMediaSample->GetTime(&trStart, &trEnd);
|
|
|
|
MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
|
|
|
|
m_pClock->GetTime(&m_trRenderStart);
|
|
MSR_INTEGER(0, (int)m_trRenderStart);
|
|
REFERENCE_TIME trStream;
|
|
trStream = m_trRenderStart - m_tStart; // convert reftime to stream time
|
|
MSR_INTEGER(0, (int)trStream);
|
|
|
|
const int trLate = (int)(trStream - trStart);
|
|
MSR_INTEGER(m_idBaseAccuracy, trLate / 10000); // dump in mSec
|
|
#endif
|
|
|
|
} // OnRenderStart
|
|
|
|
|
|
// Called directly after drawing an image.
|
|
// calculate the time spent drawing and log it.
|
|
|
|
void CAudioSwitchRenderer::OnRenderEnd(IMediaSample *pMediaSample)
|
|
{
|
|
#ifdef PERF
|
|
REFERENCE_TIME trNow;
|
|
m_pClock->GetTime(&trNow);
|
|
MSR_INTEGER(0, (int)trNow);
|
|
int t = (int)((trNow - m_trRenderStart) / 10000); // convert UNITS->msec
|
|
MSR_INTEGER(m_idBaseRenderTime, t);
|
|
#endif
|
|
} // OnRenderEnd
|
|
|
|
void CAudioSwitchRenderer::SetSelectedInput(int n)
|
|
{
|
|
if (m_inputSelected == n) return ;
|
|
if (n > 15 || n < 0) return ;
|
|
ClearPendingSample();
|
|
m_inputSelected = n;
|
|
GetSelectedPin()->NotifyMediaType();
|
|
}
|
|
|
|
int CAudioSwitchRenderer::GetSelectedInput()
|
|
{
|
|
return m_inputSelected;
|
|
}
|
|
|
|
int CAudioSwitchRenderer::GetConnectedInputsCount()
|
|
{
|
|
int n = 0;
|
|
for (int i = 0;i < 16;i++)
|
|
{
|
|
if (m_pInputPin[i] && m_pInputPin[i]->IsConnected()) n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Constructor must be passed the base renderer object
|
|
|
|
CAudioSwitchRendererInputPin::CAudioSwitchRendererInputPin(CAudioSwitchRenderer *pRenderer,
|
|
HRESULT *phr,
|
|
LPCWSTR pPinName) :
|
|
CBaseInputPin(NAME("Renderer pin"),
|
|
pRenderer,
|
|
&pRenderer->m_InterfaceLock,
|
|
(HRESULT *) phr,
|
|
pPinName)
|
|
{
|
|
m_pRenderer = pRenderer;
|
|
ASSERT(m_pRenderer);
|
|
}
|
|
|
|
|
|
// Signals end of data stream on the input pin
|
|
|
|
STDMETHODIMP CAudioSwitchRendererInputPin::EndOfStream()
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
if (m_pRenderer->GetSelectedPin() == this)
|
|
{
|
|
CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
|
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
|
|
|
// Make sure we're streaming ok
|
|
|
|
hr = CheckStreaming();
|
|
if (hr != NOERROR)
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
// Pass it onto the renderer
|
|
|
|
hr = m_pRenderer->EndOfStream();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CBaseInputPin::EndOfStream();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Signals start of flushing on the input pin - we do the final reset end of
|
|
// stream with the renderer lock unlocked but with the interface lock locked
|
|
// We must do this because we call timeKillEvent, our timer callback method
|
|
// has to take the renderer lock to serialise our state. Therefore holding a
|
|
// renderer lock when calling timeKillEvent could cause a deadlock condition
|
|
|
|
STDMETHODIMP CAudioSwitchRendererInputPin::BeginFlush()
|
|
{
|
|
if (m_pRenderer->GetSelectedPin() == this)
|
|
{
|
|
CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
|
{
|
|
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
|
CBaseInputPin::BeginFlush();
|
|
m_pRenderer->BeginFlush();
|
|
}
|
|
return m_pRenderer->ResetEndOfStream();
|
|
}
|
|
else return CBaseInputPin::BeginFlush();
|
|
}
|
|
|
|
|
|
// Signals end of flushing on the input pin
|
|
|
|
STDMETHODIMP CAudioSwitchRendererInputPin::EndFlush()
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
if (m_pRenderer->GetSelectedPin() == this)
|
|
{
|
|
CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
|
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
|
hr = m_pRenderer->EndFlush();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CBaseInputPin::EndFlush();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Pass the sample straight through to the renderer object
|
|
|
|
STDMETHODIMP CAudioSwitchRendererInputPin::Receive(IMediaSample *pSample)
|
|
{
|
|
if (m_pRenderer->GetSelectedPin() != this)
|
|
return NOERROR;
|
|
return m_pRenderer->Receive(pSample);
|
|
}
|
|
|
|
|
|
// Called when the input pin is disconnected
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::BreakConnect()
|
|
{
|
|
if (m_pRenderer->GetSelectedPin() == this)
|
|
{
|
|
HRESULT hr = m_pRenderer->BreakConnect();
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
return CBaseInputPin::BreakConnect();
|
|
}
|
|
|
|
|
|
// Called when the input pin is connected
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::CompleteConnect(IPin *pReceivePin)
|
|
{
|
|
if (m_pRenderer->GetSelectedPin() == this)
|
|
{
|
|
HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
return CBaseInputPin::CompleteConnect(pReceivePin);
|
|
}
|
|
|
|
|
|
// Give the pin id of our one and only pin
|
|
|
|
STDMETHODIMP CAudioSwitchRendererInputPin::QueryId(LPWSTR *Id)
|
|
{
|
|
CheckPointer(Id, E_POINTER);
|
|
|
|
*Id = (LPWSTR)CoTaskMemAlloc(8);
|
|
if (*Id == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
StringCbCopyW(*Id, 8, m_pName);
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// Will the filter accept this media type
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::CheckMediaType(const CMediaType *pmt)
|
|
{
|
|
return m_pRenderer->CheckMediaType(pmt);
|
|
}
|
|
|
|
|
|
// Called when we go paused or running
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::Active()
|
|
{
|
|
return m_pRenderer->Active();
|
|
}
|
|
|
|
|
|
// Called when we go into a stopped state
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::Inactive()
|
|
{
|
|
return m_pRenderer->Inactive();
|
|
}
|
|
|
|
|
|
// Tell derived classes about the media type agreed
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::SetMediaType(const CMediaType *pmt)
|
|
{
|
|
HRESULT hr = CBaseInputPin::SetMediaType(pmt);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
m_mt = *pmt;
|
|
if (m_pRenderer->GetSelectedPin() != this)
|
|
return NOERROR;
|
|
return m_pRenderer->SetMediaType(pmt);
|
|
}
|
|
|
|
HRESULT CAudioSwitchRendererInputPin::NotifyMediaType()
|
|
{
|
|
if (m_pRenderer->GetSelectedPin() != this)
|
|
return NOERROR;
|
|
return m_pRenderer->SetMediaType(&m_mt);
|
|
}
|