diff options
Diffstat (limited to 'Src/Plugins/Input/in_dshow/audioswitch.cpp')
-rw-r--r-- | Src/Plugins/Input/in_dshow/audioswitch.cpp | 1791 |
1 files changed, 1791 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_dshow/audioswitch.cpp b/Src/Plugins/Input/in_dshow/audioswitch.cpp new file mode 100644 index 00000000..032fbd71 --- /dev/null +++ b/Src/Plugins/Input/in_dshow/audioswitch.cpp @@ -0,0 +1,1791 @@ +#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); +} |