Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3555 lines
94 KiB

/*++
Copyright (c) 1998-1999 Microsoft Corporation
Module Name:
wavestrm.cpp
Abstract:
This module contains implementation of CWaveMSPStream.
Author:
Zoltan Szilagyi (zoltans) September 7, 1998
--*/
#include "stdafx.h"
#include <audevcod.h> // audio device error codes
#include <initguid.h>
#include <g711uids.h>
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Custom logging helper macro, usable only within this class.
//
#ifdef MSPLOG
#define STREAM_PREFIX(x) m_Direction == TD_RENDER ? \
"CWaveMSPStream(RENDER)::" x : \
"CWaveMSPStream(CAPTURE)::" x
#endif
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
CWaveMSPStream::CWaveMSPStream() : CMSPStream()
{
LOG((MSP_TRACE, STREAM_PREFIX("CWaveMSPStream entered.")));
m_fTerminalConnected = FALSE;
m_fHaveWaveID = FALSE;
m_dwSuspendCount = 0;
m_DesiredGraphState = State_Stopped;
m_ActualGraphState = State_Stopped;
m_pFilter = NULL;
m_pG711Filter = NULL;
LOG((MSP_TRACE, STREAM_PREFIX("CWaveMSPStream exited.")));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
CWaveMSPStream::~CWaveMSPStream()
{
LOG((MSP_TRACE, STREAM_PREFIX("~CWaveMSPStream entered.")));
LOG((MSP_TRACE, STREAM_PREFIX("~CWaveMSPStream exited.")));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FinalRelease
//
// Called on destruction of the stream object, before the destructor. Releases
// all of the stream's references to filters.
//
// Arguments: none
//
// Returns: nothing
//
void CWaveMSPStream::FinalRelease()
{
LOG((MSP_TRACE, STREAM_PREFIX("FinalRelease entered.")));
//
// At this point we should have no terminals selected, since
// Shutdown is supposed to be called before we are destructed.
//
_ASSERTE( 0 == m_Terminals.GetSize() );
//
// Remove our filter from the graph and release it.
//
if ( m_fHaveWaveID )
{
_ASSERTE( m_pFilter );
m_pIGraphBuilder->RemoveFilter( m_pFilter );
m_pFilter->Release();
}
if ( m_pG711Filter )
{
m_pIGraphBuilder->RemoveFilter( m_pG711Filter );
m_pG711Filter->Release();
}
//
// Call the base class method to clean up everything else.
//
CMSPStream::FinalRelease();
LOG((MSP_TRACE, STREAM_PREFIX("FinalRelease exited.")));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::get_Name
//
// This method returns the name of the stream. The stream name is a friendly
// name which the app can use for UI. The name is determined by the direction
// of the stream and is retrieved from the string table.
//
// Arguments:
// OUT ppName - Returns a BSTR containing the name. The caller is
// responsible for calling SysFreeString when it is done
// with this string.
//
// Returns HRESULT:
// S_OK - success
// E_POINTER - ppName argument is invalid
// E_UNEXPECTED - string cannot be loaded from string table
// E_OUTOFMEMORY - not enough memory to allocate return string
//
STDMETHODIMP CWaveMSPStream::get_Name (
OUT BSTR * ppName
)
{
LOG((MSP_TRACE, STREAM_PREFIX("get_Name - enter")));
//
// Check argument.
//
if ( IsBadWritePtr(ppName, sizeof(BSTR) ) )
{
LOG((MSP_TRACE, STREAM_PREFIX("get_Name - "
"bad return pointer - returning E_POINTER")));
return E_POINTER;
}
//
// Decide what string to return based on which stream this is.
//
ULONG ulID;
if ( m_Direction == TD_CAPTURE )
{
ulID = IDS_CAPTURE_STREAM;
}
else
{
ulID = IDS_RENDER_STREAM;
}
//
// Get the string from the string table.
//
const int ciAllocSize = 2048;
WCHAR wszName[ciAllocSize];
int iReturn = LoadStringW( _Module.GetModuleInstance(),
ulID,
wszName,
ciAllocSize - 1 );
if ( iReturn == 0 )
{
_ASSERTE( FALSE );
*ppName = NULL;
LOG((MSP_ERROR, STREAM_PREFIX("get_Name - "
"LoadString failed - returning E_UNEXPECTED")));
return E_UNEXPECTED;
}
//
// Convert to a BSTR and return the BSTR.
//
*ppName = SysAllocString(wszName);
if ( *ppName == NULL )
{
LOG((MSP_ERROR, STREAM_PREFIX("get_Name - "
"SysAllocString failed - returning E_OUTOFMEMORY")));
return E_OUTOFMEMORY;
}
LOG((MSP_TRACE, STREAM_PREFIX("get_Name - exit S_OK")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::SelectTerminal
//
// The app calls this method to indicate that the given terminal should be used
// on this stream. Only one terminal per stream is supported at this time.
// If the stream's desired graph state is not stopped, then a successful
// terminal selection causes the stream to attempt to regain the desired graph
// state. (The desired graph state is manipulated using ITStream::StartStream,
// etc.) If such a state change is unsuccessful, an event is fired but the
// SelectTerminal call still returns S_OK. This is to maintain consistency
// between synchronous and asynchronous streaming failures.
//
// Arguments:
// IN pTerminal - Pointer to the terminal to select.
//
// Returns HRESULT:
// S_OK - success
// TAPI_E_MAXTERMINALS - a terminal is already selected
// other - from CMSPStream::SelectTerminal
//
STDMETHODIMP CWaveMSPStream::SelectTerminal(
IN ITTerminal * pTerminal
)
{
LOG((MSP_TRACE, STREAM_PREFIX("SelectTerminal - enter")));
//
// We are going to access the terminal list -- grab the lock
//
CLock lock(m_lock);
//
// Reject if we already have a terminal selected.
//
if ( 0 != m_Terminals.GetSize() )
{
LOG((MSP_ERROR, STREAM_PREFIX("SelectTerminal - "
"exit TAPI_E_MAXTERMINALS")));
return TAPI_E_MAXTERMINALS;
}
//
// Use base class method to add it to our list of terminals.
//
HRESULT hr = CMSPStream::SelectTerminal(pTerminal);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SelectTerminal - "
"base class method failed - exit 0x%08x"), hr));
return hr;
}
//
// Re-pause or re-start the stream if needed.
//
// Note that our behavior does not depend on whether TAPI
// has suspended the stream. If TAPI has suspended the stream,
// that's handled with these methods.
//
// Also note that if an error occurs on trying to regain the
// desired graph state, we leave the terminal selected and
// do not return an error code. This is as it should be, for
// consistency with asynchronous failure cases.
//
if ( m_DesiredGraphState == State_Paused )
{
PauseStream();
}
else if ( m_DesiredGraphState == State_Running )
{
StartStream();
}
else
{
_ASSERTE( m_DesiredGraphState == State_Stopped );
hr = S_OK;
}
if ( FAILED(hr) )
{
LOG((MSP_WARN, STREAM_PREFIX("SelectTerminal - "
"can't regain old graph state "
"0x%08x - continuing anyway"), hr));
}
LOG((MSP_TRACE, STREAM_PREFIX("SelectTerminal - exit S_OK")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::UnselectTerminal
//
// The app calls this method to indicate that the given terminal should no
// longer be used on this stream. This is relatively easy to do if the
// terminal is not connected to the stream, as is the case for a stream whose
// desired graph state has never deviated from State_Stopped since the terminal
// was selected, or for a stream where connection with the terminal was
// attempted but the connection failed. In these cases the terminal is simply
// removed from the stream's list.
//
// However, if the terminal was successfully connected to the stream earlier
// ( m_fTerminalConnected == TRUE ) then the terminal must also be
// disconnected from the stream. This also requires that the stream's graph
// be stopped. Stopping the graph to disconnect the terminal does not affect
// the desired graph state, so that reselecting a terminal without any changes
// to the desired graph state will result in an attempt to regain the same
// desired graph state that was present before the terminal was unselected.
// (The desired graph state is manipulated using ITStream::StartStream,
// etc.)
//
// Another complication arises from the fact that the G711 codec filter may
// be present in the graph and conected. In order to assure that future
// connections can succeed, this method disconnects and removes the G711
// codec filter from the graph.
//
// Arguments:
// IN pTerminal - Pointer to the terminal to unselect.
//
// Returns HRESULT:
// S_OK - success
// other - from CMSPStream::UnselectTerminal
// or QI for ITTerminalControl
// or ITTerminalControl::DisconnectTerminal
//
STDMETHODIMP CWaveMSPStream::UnselectTerminal (
IN ITTerminal * pTerminal
)
{
LOG((MSP_TRACE, STREAM_PREFIX("UnselectTerminal - enter")));
//
// check the argument -- it has to be a reasonably good pointer
//
if (IsBadReadPtr(pTerminal, sizeof(ITTerminal)))
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"bad terminal pointer passed in. returning E_POINTER")));
return E_POINTER;
}
CLock lock(m_lock);
//
// check the argument -- it has to be in the array of terminals
//
if (m_Terminals.Find(pTerminal) < 0)
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"terminal [%p] is not selected on this stream. "
"returning TAPI_E_INVALIDTERMINAL"), pTerminal));
return TAPI_E_INVALIDTERMINAL;
}
//
// Add an extra reference to the terminal so it doesn't go away
// after we call CMSPStream::UnselectTerminal. We need it later
// in the function.
//
pTerminal->AddRef();
//
// Use base class method to remove terminal from our list of terminals.
//
// Note that if the graph won't stop or the terminal won't disconnect,
// then we will never be able to try again, as the terminal will be gone
// from our list. That's bad. If we just check here if we have the
// terminal selected and then do the base class unselection at the end,
// then we have a different problem -- we can potentially never release a
// terminal from our list that for some reason won't stop/disconnect.
// Therefore we should probably unselect it from our list even if stop/
// disconnect fails. But then we have the same problem we started with.
// Need to think more about how to solve this.
//
HRESULT hr = CMSPStream::UnselectTerminal(pTerminal);
if (FAILED(hr))
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"base class method failed - exit 0x%08x"), hr));
pTerminal->Release();
return hr;
}
//
// Stop the graph and disconnect the terminal if this call had it
// connected. (We need a stopped graph to disconnect properly, and
// couldn't have started the graph if the terminal isn't connected.)
//
if ( m_fTerminalConnected )
{
//
// At this point we need to make sure the stream is stopped.
// We can't use our own StopStream method because it
// (1) changes the desired graph state to Stopped and
// (2) does nothing if no terminal has been selected (which it now
// thinks is the case)
//
// Note also that our behavior does not depend on whether TAPI
// has suspended the stream. If TAPI has suspended the stream,
// it just means we're already stopped.
//
_ASSERTE( m_fHaveWaveID );
//
// Stop the stream via the base class method.
//
hr = CMSPStream::StopStream();
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"Stop failed - 0x%08x"), hr));
// don't return hr -- we really want to continue and
// disconnect if we can!
}
if ( m_ActualGraphState == State_Running )
{
FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);
}
m_ActualGraphState = State_Stopped;
//
// Get the ITTerminalControl interface.
//
ITTerminalControl * pTerminalControl;
hr = pTerminal->QueryInterface(IID_ITTerminalControl,
(void **) &pTerminalControl);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"QI for ITTerminalControl failed - exit 0x%08x"), hr));
pTerminal->Release();
return hr;
}
//
// Disconnect the terminal.
//
hr = pTerminalControl->DisconnectTerminal(m_pIGraphBuilder, 0);
pTerminalControl->Release();
m_fTerminalConnected = FALSE;
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("UnselectTerminal - "
"DisconnectTerminal failed - exit 0x%08x"), hr));
pTerminal->Release();
return hr;
}
//
// Our graph now contains our wave filter and possibly the G711
// codec. The G711 codec may or may not be connected to the wave
// filter. Alternatively, various other filters, pulled in as a
// side-effect of an earlier intelligent connection, may be connected
// to the wave filter. Clean this up.
//
hr = CleanFilterGraph();
if (FAILED(hr))
{
//
// the graph is in a bad state and can no longer be used.
// this is not very good, but we cannot rollback to the original
// state at this point, so we will have to live with this
//
LOG((MSP_ERROR,
STREAM_PREFIX("UnselectTerminal - CleanFilterGraph failed. hr = %lx"),
hr));
pTerminal->Release();
return hr;
}
}
pTerminal->Release();
pTerminal = NULL;
LOG((MSP_TRACE, STREAM_PREFIX("UnselectTerminal - exit")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::StartStream
//
// The app calls this method to indicate that the stream should start playing.
// Tapi3.dll also calls this method when the call is connected (so that
// streaming starts by default on a newly connected call).
//
// First, a new desired graph state is set on the stream. If there is no
// terminal selected, then this is all that happens, and the stream will start
// the next time a terminal is selected.
//
// If a terminal has been selected, then this method checks if the wave ID
// has been set. If the wave ID has not been set, this indicates a problem
// with the TSP or the transport filter. An event is fired and an error code
// is returned. If the wave ID has been set, then the terminal is connected
// (if this has not already occured) and, unless TAPI3.DLL has suspended the
// stream due to an outstanding TSPI call, the stream is started and an event
// is fired. (But the event is only fired if this is an actual Active/Inactive
// transition.)
//
// Arguments: none
//
// Returns HRESULT:
// S_OK - success
// E_FAIL - wave ID not set
// other - from ConnectTerminal
// or FireEvent
// or CMSPStream::StartStream
//
STDMETHODIMP CWaveMSPStream::StartStream (void)
{
LOG((MSP_TRACE, STREAM_PREFIX("StartStream - enter")));
CLock lock(m_lock);
m_DesiredGraphState = State_Running;
//
// Can't start the stream if no terminal has been selected.
//
if ( 0 == m_Terminals.GetSize() )
{
LOG((MSP_WARN, STREAM_PREFIX("StartStream - "
"no Terminal so nothing to do yet - exit S_OK")));
return S_OK;
}
//
// Can't start the stream if we don't know the waveid.
// (We create our filters on discovery of the waveid.)
// Here we fire a failure event, as this indicates an
// improperly-installed TSP or a failure during filter
// creation or setup, rendering this stream unusable.
//
if ( ! m_fHaveWaveID )
{
LOG((MSP_WARN, STREAM_PREFIX("StartStream - "
"no waveid - event + exit E_FAIL")));
FireEvent(CALL_STREAM_FAIL, E_FAIL, CALL_CAUSE_UNKNOWN);
return E_FAIL;
}
//
// Connect the terminal. This does nothing if this call already
// connected the terminal and fails if another call has the
// terminal connected.
//
HRESULT hr;
hr = ConnectTerminal(m_Terminals[0]);
if ( FAILED(hr) )
{
FireEvent(CALL_TERMINAL_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
LOG((MSP_ERROR, STREAM_PREFIX("StartStream - "
"our ConnectTerminal failed - exit 0x%08x"), hr));
return hr;
}
//
// If the stream is suspended, we're done. Since we've set the
// desired graph state to State_Running, the rest of this
// process will complete when TAPI resumes the stream.
//
if ( 0 != m_dwSuspendCount )
{
//
// By the way, this is quite normal as the suspend/resume happens
// behind the scenes -- use MSP_TRACE rather than MSP_WARN
//
LOG((MSP_TRACE, STREAM_PREFIX("StartStream - "
"stream is suspended so terminal is connected but we are not "
"running the graph yet - exit S_OK")));
return S_OK;
}
//
// Run the stream via the base class method.
//
hr = CMSPStream::StartStream();
if ( FAILED(hr) )
{
//
// Failed to run -- tell the app.
//
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);
LOG((MSP_ERROR, STREAM_PREFIX("StartStream - "
"Run failed - exit 0x%08x"), hr));
return hr;
}
//
// Fire event if this just made us active.
//
if ( m_ActualGraphState != State_Running )
{
m_ActualGraphState = State_Running;
HRESULT hr2 = FireEvent(CALL_STREAM_ACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);
if ( FAILED(hr2) )
{
LOG((MSP_ERROR, STREAM_PREFIX("StartStream - "
"FireEvent failed - exit 0x%08x"), hr2));
return hr2;
}
}
LOG((MSP_TRACE, STREAM_PREFIX("StartStream - exit S_OK")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::PauseStream
//
// The app calls this method to indicate that the stream should transition to
// the paused state.
//
// First, a new desired graph state is set on the stream. If there is no
// terminal selected, then this is all that happens, and the stream will pause
// the next time a terminal is selected.
//
// If a terminal has been selected, then this method checks if the wave ID
// has been set. If the wave ID has not been set, this indicates a problem
// with the TSP or the transport filter. An event is fired and an error code
// is returned. If the wave ID has been set, then the terminal is connected
// (if this has not already occured) and, unless TAPI3.DLL has suspended the
// stream due to an outstanding TSPI call, the stream is paused and an event
// is fired. (But the event is only fired if this is an actual Active/Inactive
// transition.)
//
// Arguments: none
//
// Returns HRESULT:
// S_OK - success
// E_FAIL - wave ID not set
// other - from ConnectTerminal
// or FireEvent
// or CMSPStream::StartStream
//
STDMETHODIMP CWaveMSPStream::PauseStream (void)
{
LOG((MSP_TRACE, STREAM_PREFIX("PauseStream - enter")));
CLock lock(m_lock);
m_DesiredGraphState = State_Paused;
//
// Can't pause the stream if no terminal has been selected.
//
if ( 0 == m_Terminals.GetSize() )
{
LOG((MSP_WARN, STREAM_PREFIX("PauseStream - "
"no Terminal so nothing to do yet - exit S_OK")));
return S_OK;
}
//
// Can't start the stream if we don't know the waveid.
// (We create our filters on discovery of the waveid.)
// Here we fire a failure event, as this indicates an
// improperly-installed TSP or a failure during filter
// creation or setup, rendering this stream unusable.
//
if ( ! m_fHaveWaveID )
{
LOG((MSP_WARN, STREAM_PREFIX("PauseStream - "
"no waveid - event + exit E_FAIL")));
FireEvent(CALL_STREAM_FAIL, E_FAIL, CALL_CAUSE_UNKNOWN);
return E_FAIL;
}
//
// Connect the terminal. This does nothing if this call already
// connected the terminal and fails if another call has the
// terminal connected.
//
HRESULT hr;
hr = ConnectTerminal(m_Terminals[0]);
if ( FAILED(hr) )
{
FireEvent(CALL_TERMINAL_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_CONNECT_FAIL);
LOG((MSP_ERROR, STREAM_PREFIX("StartStream - "
"our ConnectTerminal failed - exit 0x%08x"), hr));
return hr;
}
//
// If the stream is suspended, we're done. Since we've set the
// desired graph state to State_Paused, the rest of this
// process will complete when TAPI resumes the stream.
//
if ( 0 != m_dwSuspendCount )
{
//
// By the way, this is quite normal as the suspend/resume happens
// behind the scenes -- use MSP_TRACE rather than MSP_WARN
//
LOG((MSP_TRACE, STREAM_PREFIX("PauseStream - "
"stream is suspended so terminal is connected but we are not "
"pausing the graph yet - exit S_OK")));
return S_OK;
}
//
// Pause the stream via the base class method.
//
hr = CMSPStream::PauseStream();
if ( FAILED(hr) )
{
//
// Failed to pause -- tell the app.
//
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);
LOG((MSP_ERROR, STREAM_PREFIX("PauseStream - "
"Pause failed - exit 0x%08x"), hr));
return hr;
}
//
// Fire event if this just made us inactive.
//
if ( m_ActualGraphState == State_Running )
{
HRESULT hr2 = FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);
if ( FAILED(hr2) )
{
m_ActualGraphState = State_Paused;
LOG((MSP_ERROR, STREAM_PREFIX("PauseStream - "
"FireEvent failed - exit 0x%08x"), hr2));
return hr2;
}
}
m_ActualGraphState = State_Paused;
LOG((MSP_TRACE, STREAM_PREFIX("PauseStream - exit S_OK")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// ITStream::StopStream
//
// The app calls this method to indicate that the stream should transition to
// the stopped state. Tapi3.dll also calls this method when the call is
// disconnected (so that streaming stops by default on a disconnected call).
//
// First, a new desired graph state is set on the stream. If there is no
// terminal selected, then this is all that happens, and the stream will start
// the next time a terminal is selected.
//
// If a terminal has been selected, then this method checks if the wave ID
// has been set. If the wave ID has not been set, then there is nothing to do.
// If the wave ID has been set, then the stream is stopped and an event
// is fired. (But the event is only fired if this is an actual Active/Inactive
// transition.)
//
// Arguments: none
//
// Returns HRESULT:
// S_OK - success
// E_FAIL - wave ID not set
// other - from ConnectTerminal
// or FireEvent
// or CMSPStream::StartStream
//
STDMETHODIMP CWaveMSPStream::StopStream (void)
{
LOG((MSP_TRACE, STREAM_PREFIX("StopStream - enter")));
CLock lock(m_lock);
m_DesiredGraphState = State_Stopped;
//
// Nothing to do if we don't know our waveid.
//
if ( ! m_fHaveWaveID )
{
LOG((MSP_WARN, STREAM_PREFIX("StopStream - "
"no waveid - exit S_OK")));
return S_OK;
}
//
// Nothing to do if no terminal has been selected.
//
if ( 0 == m_Terminals.GetSize() )
{
LOG((MSP_WARN, STREAM_PREFIX("StopStream - "
"no Terminal - exit S_OK")));
return S_OK;
}
//
// Nothing special here if we are suspended. Stopping while suspended just
// means that we are already stopped. The StopStream call will do nothing
// and no event will be fired in this case.
//
//
// Stop the stream via the base class method.
//
HRESULT hr;
hr = CMSPStream::StopStream();
if ( FAILED(hr) )
{
//
// Failed to stop -- tell the app.
//
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);
m_DesiredGraphState = m_ActualGraphState;
LOG((MSP_ERROR, STREAM_PREFIX("StopStream - "
"Stop failed - exit 0x%08x"), hr));
return hr;
}
//
// Fire event if this just made us inactive.
//
if ( m_ActualGraphState == State_Running )
{
HRESULT hr2 = FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);
if ( FAILED(hr2) )
{
m_ActualGraphState = State_Stopped;
LOG((MSP_ERROR, STREAM_PREFIX("StopStream - "
"FireEvent failed - exit 0x%08x"), hr2));
return hr2;
}
}
m_ActualGraphState = State_Stopped;
LOG((MSP_TRACE, STREAM_PREFIX("StopStream - exit S_OK")));
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
HRESULT CWaveMSPStream::SetWaveID(DWORD dwWaveID)
{
LOG((MSP_TRACE, STREAM_PREFIX("SetWaveID - enter")));
CLock lock(m_lock);
//
// create the correct wave filter
//
HRESULT hr;
hr = CoCreateInstance(
(m_Direction == TD_RENDER) ? CLSID_AudioRecord :
CLSID_AudioRender,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void **) &m_pFilter
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"Filter creation failed - exit 0x%08x"), hr));
return hr;
}
//
// If this is a wavein filter, turn on sample dropping for
// live graphs. Ignore failure here.
//
if ( m_Direction == TD_RENDER )
{
SetLiveMode( TRUE, m_pFilter );
}
CComObject< CMyPropertyBag > * pMyBag;
IPropertyBag * pPropertyBag;
VARIANT var;
//
// create a propertybag
//
hr = CComObject< CMyPropertyBag >::CreateInstance( &pMyBag );
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"failed to create property bag - exit 0x%08x")));
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// create the variant for the propertybag
//
VariantInit( &var );
var.vt = VT_I4;
var.lVal = dwWaveID;
//
// get the correct interface
//
hr = pMyBag->QueryInterface(
IID_IPropertyBag,
(void **) &pPropertyBag
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"failed to get the proppag interface - exit 0x%08x")));
delete pMyBag;
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// save the variant in the property bag
//
hr = pPropertyBag->Write(
( (m_Direction == TD_RENDER) ? (L"WaveInId") :
(L"WaveOutID")),
&var
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"failed to write to the proppag interface - exit 0x%08x")));
pPropertyBag->Release();
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// get the IPersistPropertyBag interface
// and save the propertybag through it
//
IPersistPropertyBag * pPersistPropertyBag;
hr = m_pFilter->QueryInterface(
IID_IPersistPropertyBag,
(void **) &pPersistPropertyBag
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"failed to get the IPersisPropertyBag interface - exit 0x%08x")));
pPropertyBag->Release();
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// Load() tells the filter object to read in the
// properties it is interested in from the property bag
//
hr = pPersistPropertyBag->Load( pPropertyBag, NULL );
pPropertyBag->Release();
pPersistPropertyBag->Release();
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"failed to save device id - exit 0x%08x")));
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// Before adding a RENDER filter to the graph (ie, the wave filter on
// a CAPTURE stream), do SetDefaultSyncSource
// to enable drop-sample code in DirectShow which will prevent
// forever-increasing latency with mismatched wave clocks.
//
if ( m_Direction == TD_CAPTURE )
{
hr = m_pIGraphBuilder->SetDefaultSyncSource();
if ( FAILED(hr) )
{
LOG((MSP_WARN, STREAM_PREFIX("SetWaveID - "
"SetDefaultSyncSource failed 0x%08x - continuing anyway"), hr));
}
}
//
// Add the filter. Supply a name to make debugging easier.
//
WCHAR * pName = (m_Direction == TD_RENDER) ?
(L"The Stream's WaveIn (on line device)") :
(L"The Stream's WaveOut (on line device)");
hr = m_pIGraphBuilder->AddFilter(m_pFilter, pName);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SetWaveID - "
"AddFilter failed - exit 0x%08x"), hr));
m_pFilter->Release();
m_pFilter = NULL;
return hr;
}
//
// We now have the wave ID.
//
m_fHaveWaveID = TRUE;
LOG((MSP_TRACE, STREAM_PREFIX("SetWaveID - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Suspend the stream. Only TAPI itself can ask us to do this. Tapi asks us
// to do this when it's calling a tapi call control function, and the TSP
// requires that its wave devices be closed for the call control function to
// work.
//
HRESULT CWaveMSPStream::SuspendStream(void)
{
LOG((MSP_TRACE, STREAM_PREFIX("SuspendStream - enter")));
CLock lock(m_lock);
m_dwSuspendCount++;
if ( m_dwSuspendCount > 1 )
{
LOG((MSP_TRACE, STREAM_PREFIX("SuspendStream - "
"just bumping up suspend count - exit S_OK")));
return S_OK;
}
//
// We are now suspended. If we're actually not stopped (this
// implies wave ids present, terminal selected, etc.) then
// we need to close the wave device by stopping the stream
// if it's not stopped, send an event to the
// the app saying that the stream is inactive, and update
// m_ActualGraphState so that resuming the stream will fire a
// a stream active event. However we must not change our
// m_DesiredGraphState, as doing so would prevent us from being
// resumed to the correct state.
//
HRESULT hr = S_OK;
if ( m_ActualGraphState != State_Stopped )
{
//
// Stop the stream via the base class method.
//
hr = CMSPStream::StopStream();
//
// Send an event to the app.
//
HRESULT hr2;
if ( SUCCEEDED(hr) )
{
hr2 = FireEvent(CALL_STREAM_INACTIVE, hr, CALL_CAUSE_LOCAL_REQUEST);
}
else
{
hr2 = FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_UNKNOWN);
}
//
// Update the the actual graph state; desired graph state remains the
// same.
//
m_ActualGraphState = State_Stopped;
//
// Debug log only for failed FireEvent.
//
if ( FAILED(hr2) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SuspendStream - "
"FireEvent failed - 0x%08x"), hr2));
}
}
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("SuspendStream - "
"Stop failed - 0x%08x"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("SuspendStream - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Resume the stream after a SuspendStream. Only TAPI itself can ask us to do
// this. Tapi asks us to do this when it's finished calling a tapi call
// control function, and we can now start using our wave device again.
//
// Tapi does this on the (callback? / async events?) thread, so we post an
// async work item to our own thread to avoid blocking tapi's thread.
//
HRESULT CWaveMSPStream::ResumeStream (void)
{
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStream - enter")));
HRESULT hr;
this->AddRef();
hr = g_Thread.QueueWorkItem(
ResumeStreamWI, // the callback
this, // the context
FALSE // FALSE -> asynchronous
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ResumeStream - "
"failed to queue work item - exit 0x%08x"), hr));
this->Release();
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStream - queued async work item - " \
"exit S_OK")));
return S_OK;
}
DWORD WINAPI CWaveMSPStream::ResumeStreamWI (VOID * pContext)
{
CWaveMSPStream * pStream = (CWaveMSPStream *) pContext;
pStream->ResumeStreamAsync();
pStream->Release();
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Resume the stream after a SuspendStream. Only TAPI itself can ask us to do
// this. Tapi asks us to do this when it's finished calling a tapi call
// control function, and we can now start using our wave device again.
//
// This is the actual routine, processed on our worker thread (see above).
//
HRESULT CWaveMSPStream::ResumeStreamAsync (void)
{
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStreamAsync - enter")));
CLock lock(m_lock);
if ( 0 == m_dwSuspendCount )
{
LOG((MSP_ERROR, STREAM_PREFIX("ResumeStreamAsync - "
"resume count was already zero - exit E_UNEXPECTED")));
return S_OK;
}
m_dwSuspendCount--;
if ( 0 != m_dwSuspendCount )
{
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStreamAsync - "
"just decrementing suspend count - exit S_OK")));
return S_OK;
}
//
// We are no longer suspended. Try to regain the desired graph state.
// These methods fire all applicable events automatically.
//
HRESULT hr;
if ( m_DesiredGraphState == State_Paused )
{
hr = PauseStream();
}
else if ( m_DesiredGraphState == State_Running )
{
hr = StartStream();
}
else
{
_ASSERTE( m_DesiredGraphState == State_Stopped );
hr = S_OK;
}
if ( FAILED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStreamAsync - "
"can't regain old graph state - exit 0x%08x"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("ResumeStreamAsync - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// ProcessSoundDeviceEvent
//
// Called only from within ProcessGraphEvent. This function outputs some trace
// info indicating the details of the sound device event received, and fires
// appropriate events to the application.
//
HRESULT CWaveMSPStream::ProcessSoundDeviceEvent(
IN long lEventCode,
IN LONG_PTR lParam1,
IN LONG_PTR lParam2
)
{
LOG((MSP_EVENT, STREAM_PREFIX("ProcessSoundDeviceEvent - enter")));
#ifdef MSPLOG
//
// Spew some debug output to indicate what this is.
//
char * pszType;
switch ( lParam1 )
{
case SNDDEV_ERROR_Open:
pszType = "SNDDEV_ERROR_Open";
break;
case SNDDEV_ERROR_Close:
pszType = "SNDDEV_ERROR_Close";
break;
case SNDDEV_ERROR_GetCaps:
pszType = "SNDDEV_ERROR_GetCaps";
break;
case SNDDEV_ERROR_PrepareHeader:
pszType = "SNDDEV_ERROR_PrepareHeader";
break;
case SNDDEV_ERROR_UnprepareHeader:
pszType = "SNDDEV_ERROR_UnprepareHeader";
break;
case SNDDEV_ERROR_Reset:
pszType = "SNDDEV_ERROR_Reset";
break;
case SNDDEV_ERROR_Restart:
pszType = "SNDDEV_ERROR_Restart";
break;
case SNDDEV_ERROR_GetPosition:
pszType = "SNDDEV_ERROR_GetPosition";
break;
case SNDDEV_ERROR_Write:
pszType = "SNDDEV_ERROR_Write";
break;
case SNDDEV_ERROR_Pause:
pszType = "SNDDEV_ERROR_Pause";
break;
case SNDDEV_ERROR_Stop:
pszType = "USNDDEV_ERROR_Stop";
break;
case SNDDEV_ERROR_Start:
pszType = "SNDDEV_ERROR_Start";
break;
case SNDDEV_ERROR_AddBuffer:
pszType = "SNDDEV_ERROR_AddBuffer";
break;
case SNDDEV_ERROR_Query:
pszType = "SNDDEV_ERROR_Query";
break;
default:
pszType = "Unknown sound device call";
break;
}
LOG((MSP_EVENT, STREAM_PREFIX("ProcessSoundDeviceEvent - "
"EVENT DUMP: type = %s; "), pszType));
LOG((MSP_EVENT, STREAM_PREFIX("ProcessSoundDeviceEvent - "
"EVENT DUMP: this event is for a %s device"),
( lEventCode == EC_SNDDEV_IN_ERROR ) ? "capture" :
"render"));
//
// The rest of the info is dumped in FireEvent if logging is enabled.
//
#endif // ifdef MSPLOG
//
// Determine the error code to use when firing events.
//
HRESULT hr;
switch ( lParam2 )
{
case MMSYSERR_NOERROR: // no error
hr = S_OK;
break;
case MMSYSERR_ERROR: // unspecified error
case MMSYSERR_BADDB: // bad registry database
case MMSYSERR_KEYNOTFOUND: // registry key not found
case MMSYSERR_READERROR: // registry read error
case MMSYSERR_WRITEERROR: // registry write error
case MMSYSERR_DELETEERROR: // registry delete error
case MMSYSERR_VALNOTFOUND: // registry value not found
hr = E_FAIL;
break;
case MMSYSERR_ALLOCATED: // device already allocated
hr = TAPI_E_ALLOCATED;
break;
case MMSYSERR_NOMEM: // memory allocation error
hr = E_OUTOFMEMORY;
break;
case MMSYSERR_BADDEVICEID: // device ID out of range
case MMSYSERR_NOTENABLED: // driver failed enable
case MMSYSERR_INVALHANDLE: // device handle is invalid
case MMSYSERR_NODRIVER: // no device driver present
case MMSYSERR_NOTSUPPORTED: // function isn't supported
case MMSYSERR_BADERRNUM: // error value out of range
case MMSYSERR_INVALFLAG: // invalid flag passed
case MMSYSERR_INVALPARAM: // invalid parameter passed
case MMSYSERR_HANDLEBUSY: // handle being used simultaneously on another
// thread (eg callback)
case MMSYSERR_INVALIDALIAS: // specified alias not found
case MMSYSERR_NODRIVERCB: // driver does not call DriverCallback
case MMSYSERR_MOREDATA: // more data to be returned
default:
hr = E_UNEXPECTED; // these would appear to indicate a bug in the wave
// driver, Quartz, or Qcap)
break;
}
//
// If this event concerns our terminal, fire a terminal fail event to the
// app.
//
if ( ( m_Direction == TD_CAPTURE ) ==
( lEventCode == EC_SNDDEV_IN_ERROR ) )
{
FireEvent(CALL_TERMINAL_FAIL, hr, CALL_CAUSE_BAD_DEVICE);
}
//
// Fire a stream failed event to the app. Even if the event concerned a
// terminal and not the stream, since we currently have only one
// terminal per stream, the failure of a terminal results in the failure
// of a stream.
//
FireEvent(CALL_STREAM_FAIL, hr, CALL_CAUSE_BAD_DEVICE);
LOG((MSP_EVENT, STREAM_PREFIX("ProcessSoundDeviceEvent - exit S_OK")));
return S_OK;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// ProcessGraphEvent
//
// Sends an event to the app when we get an event from the filter graph.
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
HRESULT CWaveMSPStream::ProcessGraphEvent(
IN long lEventCode,
IN LONG_PTR lParam1,
IN LONG_PTR lParam2
)
{
LOG((MSP_EVENT, STREAM_PREFIX("ProcessGraphEvent - enter")));
HRESULT hr = S_OK;
switch (lEventCode)
{
case EC_COMPLETE:
hr = FireEvent(CALL_STREAM_INACTIVE,
(HRESULT) lParam1,
CALL_CAUSE_UNKNOWN);
break;
case EC_USERABORT:
hr = FireEvent(CALL_STREAM_INACTIVE, S_OK, CALL_CAUSE_UNKNOWN);
break;
case EC_ERRORABORT:
case EC_STREAM_ERROR_STOPPED:
case EC_STREAM_ERROR_STILLPLAYING:
case EC_ERROR_STILLPLAYING:
hr = FireEvent(CALL_STREAM_FAIL,
(HRESULT) lParam1,
CALL_CAUSE_UNKNOWN);
break;
case EC_SNDDEV_IN_ERROR:
case EC_SNDDEV_OUT_ERROR:
//
// async error accessing an audio device
//
ProcessSoundDeviceEvent(lEventCode, lParam1, lParam2);
break;
default:
LOG((MSP_EVENT, STREAM_PREFIX("ProcessGraphEvent - "
"ignoring event code %d"), lEventCode));
break;
}
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ProcessGraphEvent - "
"FireEvent failed - exit 0x%08x"), hr));
return hr;
}
LOG((MSP_EVENT, STREAM_PREFIX("ProcessGraphEvent - exit S_OK")));
return S_OK;
}
////////////////////////////////////////////////////////////////////////
//
// FireEvent
//
// Fires an event to the application. Does its own locking.
//
HRESULT CWaveMSPStream::FireEvent(
IN MSP_CALL_EVENT type,
IN HRESULT hrError,
IN MSP_CALL_EVENT_CAUSE cause
)
{
LOG((MSP_EVENT, STREAM_PREFIX("FireEvent - enter")));
//
// First, need to check if the call is shutting down. This is important
// because UnselectTerminal can fire an event, and UnselectTerminal can
// be called within ITStream::Shutdown. We can safely discard such
// events because there is nothing the app can do with them anyway.
//
// Note on locking: It is convenient to check the m_pMSPCall here
// and we don't use it until the end of the method, so we simply lock
// during the entire method. This could be optimized at the expense of
// some code complexity; note that we also need to lock while accessing
// m_Terminals.
//
CLock lock(m_lock);
if ( m_pMSPCall == NULL )
{
LOG((MSP_EVENT, STREAM_PREFIX("FireEvent - "
"call is shutting down; dropping event - exit S_OK")));
return S_OK;
}
//
// Create the event structure. Must use "new" as it will be
// "delete"d later.
//
MSPEVENTITEM * pEventItem = AllocateEventItem();
if (pEventItem == NULL)
{
LOG((MSP_ERROR, STREAM_PREFIX("FireEvent - "
"can't create MSPEVENTITEM structure - exit E_OUTOFMEMORY")));
return E_OUTOFMEMORY;
}
//
// Fill in the necessary fields for the event structure.
//
pEventItem->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);
pEventItem->MSPEventInfo.Event = ME_CALL_EVENT;
ITTerminal * pTerminal = NULL;
if ( 0 != m_Terminals.GetSize() )
{
_ASSERTE( 1 == m_Terminals.GetSize() );
pTerminal = m_Terminals[0];
pTerminal->AddRef();
}
ITStream * pStream = (ITStream *) this;
pStream->AddRef();
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Type = type;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Cause = cause;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pStream = pStream;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pTerminal = pTerminal;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.hrError = hrError;
#ifdef MSPLOG
//
// Spew some debug output to indicate what this is.
//
char * pszType;
DWORD dwLevel;
switch (type)
{
case CALL_NEW_STREAM:
pszType = "CALL_NEW_STREAM (unexpected)";
dwLevel = MSP_ERROR;
break;
case CALL_STREAM_FAIL:
pszType = "CALL_STREAM_FAIL";
dwLevel = MSP_INFO;
break;
case CALL_TERMINAL_FAIL:
pszType = "CALL_TERMINAL_FAIL";
dwLevel = MSP_INFO;
break;
case CALL_STREAM_NOT_USED:
pszType = "CALL_STREAM_NOT_USED (unexpected)";
dwLevel = MSP_ERROR;
break;
case CALL_STREAM_ACTIVE:
pszType = "CALL_STREAM_ACTIVE";
dwLevel = MSP_INFO;
break;
case CALL_STREAM_INACTIVE:
pszType = "CALL_STREAM_INACTIVE";
dwLevel = MSP_INFO;
break;
default:
pszType = "UNKNOWN EVENT TYPE";
dwLevel = MSP_ERROR;
break;
}
LOG((dwLevel, STREAM_PREFIX("FireEvent - "
"EVENT DUMP: type = %s"), pszType));
LOG((dwLevel, STREAM_PREFIX("FireEvent - "
"EVENT DUMP: pStream = %p"), pStream));
LOG((dwLevel, STREAM_PREFIX("FireEvent - "
"EVENT DUMP: pTerminal = %p"), pTerminal));
LOG((dwLevel, STREAM_PREFIX("FireEvent - "
"EVENT DUMP: hrError = %08x"), hrError));
#endif // ifdef MSPLOG
//
// Send the event to the app.
//
HRESULT hr = m_pMSPCall->HandleStreamEvent(pEventItem);
if (FAILED(hr))
{
LOG((MSP_ERROR, STREAM_PREFIX("FireEvent - "
"HandleStreamEvent failed - returning 0x%08x"), hr));
pStream->Release();
pTerminal->Release();
FreeEventItem(pEventItem);
return hr;
}
LOG((MSP_EVENT, STREAM_PREFIX("FireEvent - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// The rest of this file deals with the connection path.
// This could be pulled out into separate file in the future.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Create the G711 filter, which we will try to connect if direct
// connection fails.
//
HRESULT CWaveMSPStream::AddG711()
{
LOG((MSP_TRACE, STREAM_PREFIX("AddG711 - enter")));
//
// if we don't yet have the G711 filter, create it
//
HRESULT hr = S_OK;
if (NULL == m_pG711Filter)
{
hr = CoCreateInstance(
CLSID_G711Codec,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void **) &m_pG711Filter
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("AddG711 - Failed to create G711 codec: %lx"), hr));
//
// Method #2 for connection will not be available.
//
m_pG711Filter = NULL;
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("AddG711 - created filter [%p]"), m_pG711Filter));
}
//
// add the G711 filter to the graph
//
hr = m_pIGraphBuilder->AddFilter(
m_pG711Filter,
NULL
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("AddG711 - Failed to add G711 filter: %lx"), hr));
//
// If we couldn't add it to the graph, then it's useless to us.
// Method #2 for connection will not be available.
//
m_pG711Filter->Release();
m_pG711Filter = NULL;
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("AddG711 - finish")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// CWaveMSPStream::RemoveAllFilters
//
// this method cleans filter graph by removing and releasing all the filters
// if the graph cannot be cleaned, this method returns an error that indicates
// a failure and does not guarantee that the graph remains in its original
// state. in fact, in case of error, the caller should assume that the graph
// can no longer be used.
//
HRESULT CWaveMSPStream::RemoveAllFilters()
{
LOG((MSP_INFO, STREAM_PREFIX("RemoveAllFilters - enter.")));
//
// get an enumeration with all the filters in the filter graph
//
IEnumFilters *pFilterEnumeration = NULL;
HRESULT hr = m_pIGraphBuilder->EnumFilters( &pFilterEnumeration );
if (FAILED(hr))
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveAllFilters - failed to enumerate filters. hr = %lx"), hr));
return hr;
}
//
// we will keep the last error from RemoveFilter if it fails
//
HRESULT hrFIlterRemovalError = S_OK;
//
// walk through the enumeration and remove and release each filter
//
while (TRUE)
{
IBaseFilter *pFilter = NULL;
ULONG nFiltersFetched = 0;
hr = pFilterEnumeration->Next(1, &pFilter, &nFiltersFetched);
//
// did the enumeration fail?
//
if (FAILED(hr))
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveAllFilters - failed to fetch another filter. hr = %lx"), hr));
break;
}
//
// did we reach the end of the enumeration?
//
if ( hr != S_OK )
{
LOG((MSP_INFO,
STREAM_PREFIX("RemoveAllFilters - no more filters in the enumeration")));
//
// if there was an error removing a filter, keep it in hr
//
hr = hrFIlterRemovalError;
break;
}
LOG((MSP_INFO, STREAM_PREFIX("RemoveAllFilters - removing filter %p."), pFilter));
//
// we got a filter. remove it from the graph and then release it
//
hr = m_pIGraphBuilder->RemoveFilter( pFilter );
if (FAILED(hr))
{
//
// we failed to remove a filter from the graph. it is not safe to
// continue to use the graph. so we will continue to remove all
// other filters and then return the error
//
hrFIlterRemovalError = hr;
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveAllFilters - failed to remove filter [%p]. hr = %lx"),
pFilter, hr));
}
else
{
//
// reset enumeration -- the set of filters in the enumeration needs
// to be updated.
//
//
// note: we only need to reset enumeration if Remove succeeded.
// otherwise, we could get into an infinite loop trying to remove
// failing filter
//
hr = pFilterEnumeration->Reset();
if (FAILED(hr))
{
//
// log a message, but don't do anything else -- next() will most
// likely fail and that error will be handled
//
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveAllFilters - failed to reset enumeration. hr = %lx"),
hr));
}
}
pFilter->Release();
pFilter = NULL;
}
//
// done with the enumeration
//
pFilterEnumeration->Release();
pFilterEnumeration = NULL;
//
// note that an error returned from this method means that the graph could
// not be cleaned and is not guaranteed to be in useable state.
//
LOG((MSP_(hr), STREAM_PREFIX("RemoveAllFilters - finish. hr = %lx"), hr));
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// CWaveMSPStream::CleanFilterGraph
//
//
// this function removes all the filters from the filter graph and then readds
// the wave filter
//
// if the method returns a failure, the graph is in undefined state and cannot
// be used.
//
HRESULT CWaveMSPStream::CleanFilterGraph()
{
LOG((MSP_TRACE, STREAM_PREFIX("CleanFilterGraph - enter")));
//
// completely clean filter graph
//
HRESULT hr = RemoveAllFilters();
if (FAILED(hr))
{
LOG((MSP_ERROR,
STREAM_PREFIX("CleanFilterGraph - remove all filters 0x%x"), hr));
return hr;
}
//
// Add the wave filter back to the graph
//
hr = m_pIGraphBuilder->AddFilter( m_pFilter,
NULL );
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("CleanFilterGraph - failed to re-add filter: 0x%x"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("CleanFilterGraph - exit")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Remove and readd the terminal. This is only needed between a successful
// intelligent connection and the subsequent reconnection.
//
//////////////////////////////////////////////////////////////////////////////
HRESULT CWaveMSPStream::RemoveTerminal()
{
LOG((MSP_TRACE, STREAM_PREFIX("RemoveTerminal - enter")));
//
// verify the assumptin that we have exactly one terminal
//
if (1 != m_Terminals.GetSize() )
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveTerminal - expecting one terminal. have %d "),
m_Terminals.GetSize()));
_ASSERTE(FALSE);
return E_UNEXPECTED;
}
//
// Get the ITTerminalControl interface.
//
ITTerminalControl *pTerminalControl = NULL;
HRESULT hr = m_Terminals[0]->QueryInterface(IID_ITTerminalControl,
(void **) &pTerminalControl);
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveTerminal - QI for ITTerminalControl failed hr = 0x%x"), hr));
return hr;
}
//
// Disconnect the terminal (this also removes it from the filter graph)
//
hr = pTerminalControl->DisconnectTerminal(m_pIGraphBuilder, 0);
pTerminalControl->Release();
pTerminalControl = NULL;
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveTerminal - DisconnectTerminal failed hr = 0x%x"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("RemoveTerminal - exit")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
HRESULT CWaveMSPStream::ReAddTerminal()
{
LOG((MSP_TRACE, STREAM_PREFIX("ReAddTerminal - enter")));
//
// verify the assumptin that we have exactly one terminal
//
if (1 != m_Terminals.GetSize() )
{
LOG((MSP_ERROR,
STREAM_PREFIX("RemoveTerminal - expecting one terminal. have %d "),
m_Terminals.GetSize()));
_ASSERTE(FALSE);
return E_UNEXPECTED;
}
//
// Get the ITTerminalControl interface.
//
ITTerminalControl *pTerminalControl = NULL;
HRESULT hr = m_Terminals[0]->QueryInterface(IID_ITTerminalControl,
(void **) &pTerminalControl);
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("ReAddTerminal - QI for ITTerminalControl failed hr = 0x%x"), hr));
return hr;
}
//
// Find out how many pins the terminal has. If not one then bail as
// we have no idea what to do with multiple-pin terminals at this point.
//
DWORD dwNumPinsAvailable = 0;
hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
m_Direction,
&dwNumPinsAvailable,
NULL);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ReAddTerminal - "
"query for number of terminal pins failed 0x%x)"), hr));
pTerminalControl->Release();
pTerminalControl = NULL;
return hr;
}
if ( 1 != dwNumPinsAvailable )
{
LOG((MSP_ERROR,
STREAM_PREFIX("ReAddTerminal - unsupported number of terminal pins %ld ")));
pTerminalControl->Release();
pTerminalControl = NULL;
return E_INVALIDARG;
}
//
// Before adding a RENDER filter to the graph (ie, the terminal on
// a RENDER stream), do SetDefaultSyncSource
// to enable drop-sample code in DirectShow which will prevent
// forever-increasing latency with mismatched wave clocks.
//
if ( m_Direction == TD_RENDER )
{
hr = m_pIGraphBuilder->SetDefaultSyncSource();
if ( FAILED(hr) )
{
LOG((MSP_WARN,
STREAM_PREFIX(
"ReAddTerminal - SetDefaultSyncSource failed hr = 0x%x - continuing anyway"),
hr));
}
}
//
// Actually connect the terminal.
//
IPin *pTerminalPin = NULL;
hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
m_Direction,
&dwNumPinsAvailable,
&pTerminalPin);
pTerminalControl->Release();
pTerminalControl = NULL;
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("ReAddTerminal - ConnectTerminal on terminal failed hr = 0x%x"),
hr));
return hr;
}
//
// also try to check if the terminal returned a bad pin.
//
if ( IsBadReadPtr(pTerminalPin, sizeof(IPin)) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ReAddTerminal - "
"ConnectTerminal on terminal succeeded but returned a bad pin ")));
return E_FAIL;
}
pTerminalPin->Release();
pTerminalPin = NULL;
LOG((MSP_TRACE, STREAM_PREFIX("ReAddTerminal- exit")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DecideDesiredCaptureBufferSize
//
// This method must be called when the graph is connected. It uses the
// connection format on the passed-in pin to determine how many bytes should
// be used in each buffer to achieve DESIRED_BUFFER_SIZE_MS milliseconds of
// sound in each buffer.
//
// In the past, on slow machines with buggy wave drivers, small sample sizes
// have led to bad audio quality, invariably on devices that are not designed
// for interactive use anyway, where latency would be important. We've been
// successful in getting key wave drivers fixed to work well with small
// buffer sizes. It doesn't make sense to increase the default buffer size for
// the benefit of buggy drivers, as we want to achieve low latency with the
// good drivers, which is now pretty much all of them. If someone wants to use
// the WaveMSP for interactive calls and they have a really lousy driver, they
// need to get the driver fixed. Nevertheless it may be a good idea to make
// a registry value for this, for the rare case where fixing the driver may
// not be possible or convenient.
//
static const long DESIRED_BUFFER_SIZE_MS = 20; // milliseconds
HRESULT CWaveMSPStream::DecideDesiredCaptureBufferSize(IPin * pPin,
long * plDesiredSize)
{
LOG((MSP_TRACE, STREAM_PREFIX("DecideDesiredCaptureBufferSize - "
"enter")));
_ASSERTE( ! IsBadReadPtr(pPin, sizeof(IPin)) );
_ASSERTE( ! IsBadWritePtr(plDesiredSize, sizeof(long)) );
//
// Get the format being used for this pin's connection.
//
HRESULT hr;
AM_MEDIA_TYPE MediaType;
hr = pPin->ConnectionMediaType( & MediaType );
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("DecideDesiredCaptureBufferSize - "
"ConnectionMediaType failed - hr = 0x%08x"), hr));
return hr;
}
_ASSERTE( MediaType.formattype == FORMAT_WaveFormatEx );
_ASSERTE( MediaType.cbFormat >= sizeof(WAVEFORMATEX) );
//
// Calculate the desired capture buffer size.
//
*plDesiredSize = DESIRED_BUFFER_SIZE_MS *
((WAVEFORMATEX *) (MediaType.pbFormat) )->nChannels *
( ((WAVEFORMATEX *) (MediaType.pbFormat) )->nSamplesPerSec / 1000 ) *
( ((WAVEFORMATEX *) (MediaType.pbFormat) )->wBitsPerSample / 8 );
FreeMediaType( MediaType );
LOG((MSP_TRACE, STREAM_PREFIX("DecideDesiredCaptureBufferSize - "
"exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// ConfigureCapture
//
// This is a helper function that sets up the allocator properties on the
// capture filter, given the terminal's pin and our filter's pin. This
// involves determining if either of the pins belongs to an MST, since we
// don't want to set the default buffer size in the non-interactive case,
// but if the input pin belongs to an MST, we want to propagate the MST's
// properties to the output pin. If no MSTs are involved, then we set the
// passed-in default buffer size.
//
// We are already in a lock; no need to do locking here.
//
HRESULT CWaveMSPStream::ConfigureCapture(
IN IPin * pOutputPin,
IN IPin * pInputPin,
IN long lDefaultBufferSize
)
{
LOG((MSP_TRACE, STREAM_PREFIX("ConfigureCapture - enter")));
//
// If the output pin belongs to an MST, then we do not want
// to mess with its allocator properties.
//
HRESULT hr;
ITAllocatorProperties * pProperties;
hr = pOutputPin->QueryInterface(IID_ITAllocatorProperties,
(void **) &pProperties);
if ( SUCCEEDED(hr) )
{
pProperties->Release();
LOG((MSP_TRACE, STREAM_PREFIX("ConfigureCapture - "
"output pin is on an MST - not changing capture "
"allocator properties - exit S_OK")));
return S_OK;
}
//
// Ask the output pin for its buffer negotiation interface.
// This will be used to suggest allocator propreties on the
// output pin.
//
IAMBufferNegotiation * pNegotiation;
hr = pOutputPin->QueryInterface(IID_IAMBufferNegotiation,
(void **) &pNegotiation);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConfigureCapture - "
"IAMBufferNegotiation QI failed - exit 0x%08x"), hr));
return hr;
}
//
// If the input pin belongs to an MST and the MST divulges its
// allocator properties, then we just propage them to the output
// pin. Otherwise we set our own default properties on the
// output pin.
//
ALLOCATOR_PROPERTIES props;
hr = pInputPin->QueryInterface(IID_ITAllocatorProperties,
(void **) &pProperties);
if ( SUCCEEDED(hr) )
{
hr = pProperties->GetAllocatorProperties(&props);
pProperties->Release();
}
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("ConfigureCapture - "
"using downstream MST allocator properties")));
}
else
{
LOG((MSP_TRACE, STREAM_PREFIX("ConfigureCapture - "
"using our default allocator properties")));
props.cBuffers = 32; // we use 32 to avoid starvation
props.cbBuffer = lDefaultBufferSize;
props.cbAlign = -1; // means "default"
props.cbPrefix = -1; // means "default"
}
//
// "props" now contains the properties that we need to set.
// Suggest them to the output pin.
//
hr = pNegotiation->SuggestAllocatorProperties( &props );
pNegotiation->Release();
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConfigureCapture - "
"SuggestAllocatorProperties failed - exit 0x%08x"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("ConfigureCapture - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// This function is for debugging purposes only. It pops up a
// couple of message boxes telling you various information about
// media formats and allocator properties. It's called after
// connection has taken place. pPin is the output pin of the
// wavein filter.
//
HRESULT CWaveMSPStream::ExamineCaptureProperties(IPin *pPin)
{
LOG((MSP_TRACE, STREAM_PREFIX("ExamineCaptureProperties - enter")));
HRESULT hr;
IAMBufferNegotiation * pNegotiation = NULL;
hr = pPin->QueryInterface(IID_IAMBufferNegotiation,
(void **) &pNegotiation
);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ExamineCaptureProperties - "
"IAMBufferNegotiation QI failed on pin 0x%08x; hr = 0x%08x"),
pPin, hr));
return hr;
}
ALLOCATOR_PROPERTIES prop;
hr = pNegotiation->GetAllocatorProperties(&prop);
pNegotiation->Release();
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ExamineCaptureProperties - "
"GetAllocatorProperties failed; hr = 0x%08x"),
hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("GetAllocatorProperties info:\n"
"buffer count: %d\n"
"size of each buffer: %d bytes\n"
"alignment multiple: %d\n"
"each buffer has a prefix: %d bytes"),
prop.cBuffers,
prop.cbBuffer,
prop.cbAlign,
prop.cbPrefix
));
AM_MEDIA_TYPE MediaType;
hr = pPin->ConnectionMediaType( & MediaType );
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ExamineCaptureProperties - "
"ConnectionMediaType failed - hr = 0x%08x"), hr));
return hr;
}
//
// Check if this media type has a WAVE format.
//
if ( MediaType.formattype != FORMAT_WaveFormatEx )
{
//
// might want to print the format type guid here if we ever care
//
_ASSERTE( FALSE );
LOG((MSP_TRACE, STREAM_PREFIX("connected media type: NON-WAVE")));
}
else
{
_ASSERTE( MediaType.cbFormat >= sizeof(WAVEFORMATEX) );
LOG((MSP_TRACE, STREAM_PREFIX("connected media type:\n"
"sample size: %d bytes\n"
"format tag: %d\n"
"channels: %d\n"
"samples per second: %d\n"
"bits per sample: %d\n"),
MediaType.lSampleSize,
((WAVEFORMATEX *) (MediaType.pbFormat) )->wFormatTag,
((WAVEFORMATEX *) (MediaType.pbFormat) )->nChannels,
((WAVEFORMATEX *) (MediaType.pbFormat) )->nSamplesPerSec,
((WAVEFORMATEX *) (MediaType.pbFormat) )->wBitsPerSample
));
}
FreeMediaType( MediaType );
LOG((MSP_TRACE, STREAM_PREFIX("ExamineCaptureProperties - "
"exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// SetLiveMode
//
// If this is a wavein filter, tell it that it should do its best to
// counter the effects of mismatched clocks and drop samples when the
// latency gets too great. We really should do this on terminal
// selection dependin on whether we have at least one terminal selected on
// the stream that requires real-time performance, but this will have to
// do for now.
//
HRESULT CWaveMSPStream::SetLiveMode(BOOL fEnable, IBaseFilter * pFilter)
{
return S_OK;
#if 0
HRESULT hr;
IAMPushSource * pPushSource;
hr = pFilter->QueryInterface(
IID_IAMPushSource,
(void **) & pPushSource
);
if ( FAILED(hr) )
{
LOG((MSP_INFO, STREAM_PREFIX("SetLiveMode - "
"QI for IAMPushSource returned 0x%08x - continuing"), hr));
}
else
{
hr = pPushSource->SetLiveMode( fEnable );
if ( FAILED(hr) )
{
LOG((MSP_INFO, STREAM_PREFIX("SetLiveMode - "
"IAMPushSource::SetLiveMode( %d ) returned 0x%08x"
" - continuing"), fEnable, hr));
}
else
{
LOG((MSP_INFO, STREAM_PREFIX("SetLiveMode - "
"IAMPushSource::SetLiveMode( %d ) succeeded"
" - continuing"), fEnable, hr));
}
pPushSource->Release();
}
return hr;
#endif
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Add the terminal to the graph and connect it to our
// filters, if it is not already in use.
//
HRESULT CWaveMSPStream::ConnectTerminal(ITTerminal * pTerminal)
{
LOG((MSP_TRACE, STREAM_PREFIX("ConnectTerminal - enter")));
//
// If we've already connected the terminal on this stream, then
// there is nothing for us to do.
//
if ( m_fTerminalConnected )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"terminal already connected on this stream - exit S_OK")));
return S_OK;
}
//
// Get the ITTerminalControl interface.
//
ITTerminalControl * pTerminalControl = NULL;
HRESULT hr = m_Terminals[0]->QueryInterface(IID_ITTerminalControl,
(void **) &pTerminalControl);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"QI for ITTerminalControl failed - exit 0x%08x"), hr));
return hr;
}
//
// Find out how many pins the terminal has. If not one then bail as
// we have no idea what to do with multiple-pin terminals at this point.
//
DWORD dwNumPinsAvailable;
hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
m_Direction,
&dwNumPinsAvailable,
NULL);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"query for number of terminal pins failed - exit 0x%08x"), hr));
pTerminalControl->Release();
return hr;
}
if ( 1 != dwNumPinsAvailable )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"unsupported number of terminal pins - exit E_FAIL")));
pTerminalControl->Release();
return E_FAIL;
}
//
// Before adding a RENDER filter to the graph (ie, the terminal on
// a RENDER stream), do SetDefaultSyncSource
// to enable drop-sample code in DirectShow which will prevent
// forever-increasing latency with mismatched wave clocks.
//
if ( m_Direction == TD_RENDER )
{
hr = m_pIGraphBuilder->SetDefaultSyncSource();
if ( FAILED(hr) )
{
LOG((MSP_WARN, STREAM_PREFIX("ConnectTerminal - "
"SetDefaultSyncSource failed 0x%08x - continuing anyway"), hr));
}
}
//
// Actually connect the terminal.
//
IPin * pTerminalPin;
hr = pTerminalControl->ConnectTerminal(m_pIGraphBuilder,
m_Direction,
&dwNumPinsAvailable,
&pTerminalPin);
if ( FAILED(hr) )
{
pTerminalControl->Release();
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"ConnectTerminal on terminal failed - exit 0x%08x"), hr));
return hr;
}
//
// also try to check if the terminal returned a bad pin.
//
if ( IsBadReadPtr(pTerminalPin, sizeof(IPin)) )
{
pTerminalControl->Release();
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"ConnectTerminal on terminal succeeded but returned a bad pin "
"- returning E_POINTER")));
return E_POINTER;
}
//
// For a CAPTURE filter's pin, (ie, the terminal on a CAPTURE stream), get
// the filter and turn on sample dropping for live graphs. Ignore failure
// here. Note -- this will not work for multi-filter terminals. Luckily
// our interactive audio terminals are single-filter terminals.
// Multi-filter terminals can do this themselves.
//
if ( m_Direction == TD_CAPTURE )
{
PIN_INFO info;
hr = pTerminalPin->QueryPinInfo( & info );
if ( FAILED(hr) )
{
LOG((MSP_WARN, STREAM_PREFIX("ConnectTerminal - "
"get filter in preparation for SetLiveMode failed "
"0x%08x - continuing anyway"), hr));
}
else
{
SetLiveMode( TRUE, info.pFilter );
info.pFilter->Release();
}
}
//
// Now make the connection between our filters and the terminal's pin.
//
hr = ConnectToTerminalPin(pTerminalPin);
pTerminalPin->Release();
if ( FAILED(hr) )
{
pTerminalControl->DisconnectTerminal(m_pIGraphBuilder, 0);
pTerminalControl->Release();
//
// remove all the filters except for the wave filter
//
HRESULT hr2 = CleanFilterGraph();
if (FAILED(hr2))
{
LOG((MSP_ERROR,
STREAM_PREFIX("ConnectTerminal - CleanFilterGraph failed- exit 0x%x"),
hr2));
//
// filter graph may be in a bad shape, but there is nothing we can really do at this point.
//
}
LOG((MSP_ERROR, STREAM_PREFIX("ConnectTerminal - "
"ConnectToTerminalPin failed - exit 0x%08x"), hr));
return hr;
}
//
// Now we are actually connected. Update our state and perform postconnection
// (ignore postconnection error code).
//
m_fTerminalConnected = TRUE;
pTerminalControl->CompleteConnectTerminal();
pTerminalControl->Release();
LOG((MSP_TRACE, STREAM_PREFIX("ConnectTerminal - exit S_OK")));
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
//
void ShowMediaTypes(IEnumMediaTypes * pEnum)
{
//
// Look at each media type in the enumerator.
//
AM_MEDIA_TYPE * pMediaType;
while (pEnum->Next(1, &pMediaType, NULL) == S_OK)
{
//
// Check if this media type has a WAVE format.
//
if ( pMediaType->formattype != FORMAT_WaveFormatEx )
{
//
// might want to print the format type guid here if we ever care
//
LOG((MSP_TRACE, "Media Type: *** non-wave"));
}
else
{
LOG((MSP_TRACE,"Media Type: [format tag %d][%d channels]"
"[%d samples/sec][%d bits/sample]",
((WAVEFORMATEX *) (pMediaType->pbFormat) )->wFormatTag,
((WAVEFORMATEX *) (pMediaType->pbFormat) )->nChannels,
((WAVEFORMATEX *) (pMediaType->pbFormat) )->nSamplesPerSec,
((WAVEFORMATEX *) (pMediaType->pbFormat) )->wBitsPerSample
));
}
//
// Release the format info.
//
DeleteMediaType(pMediaType);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// ConnectUsingG711
//
// This method connects pOutputPin to pInputPin using the G711 codec and
// returns success if the connection was successful. If the connection was
// unsuccessful, it does its best to fully disconnect the filters and then
// returns a failure code.
//
// Assumptions:
// * direct connection has already failed
// * the g711 codec has been created and added to the graph
//
// Parameters:
// IN IPin * pOutputPin -- output pin on the capture filter or terminal
// IN IPin * pInputPin -- input pin on the render filter or terminal
//
//
HRESULT CWaveMSPStream::ConnectUsingG711(
IN IPin * pOutputPin,
IN IPin * pInputPin
)
{
HRESULT hr;
IPin * pG711InputPin = NULL;
hr = FindPinInFilter(
false, // want input pin
m_pG711Filter,
&pG711InputPin
);
if ( SUCCEEDED(hr) )
{
hr = m_pIGraphBuilder->ConnectDirect(
pOutputPin,
pG711InputPin,
NULL
);
// We don't release the G711's input pin here because we must
// hang onto it in order to break the connection if any of the
// subsequent steps fail.
if ( SUCCEEDED(hr) )
{
IPin * pG711OutputPin = NULL;
hr = FindPinInFilter(
true, // want output pin
m_pG711Filter,
&pG711OutputPin
);
if ( SUCCEEDED(hr) )
{
hr = m_pIGraphBuilder->ConnectDirect(
pG711OutputPin,
pInputPin,
NULL
);
pG711OutputPin->Release();
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("ConnectUsingG711 - G711 connection succeeded - exit S_OK")));
// Held onto this in case of failure... see above
pG711InputPin->Release();
return S_OK;
}
else
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - could not connect "
"G711 codec's output pin - %lx"), hr));
}
}
else
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - could not find "
"G711 codec's input pin - %lx"), hr));
}
if ( FAILED(hr) )
{
//
// The first G711 connection succeeded but something else
// subsequently failed. This means we must disconnect the left
// end of the G711 filter. Luckily, we held onto the G711 filter's
// input pin above. We must disconnect the them here, otherwise
// method #3 won't work.
//
HRESULT hr2;
hr2 = m_pIGraphBuilder->Disconnect(pOutputPin);
if ( FAILED(hr2) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - error undoing g711 "
"connection attempt - could not disconnect the "
"wave filter's output pin! hr = 0x%08x"), hr2));
}
hr2 = m_pIGraphBuilder->Disconnect(pG711InputPin);
if ( FAILED(hr2) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - error undoing g711 "
"connection attempt - could not disconnect the "
"g711 filter's input pin! hr = 0x%08x"), hr2));
}
//
// Now we no longer need to talk to the pin...
//
pG711InputPin->Release();
//
// And the G711 filter itself sticks around in the graph for next time.
//
}
}
else
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - could not connect "
"G711 codec's input pin - %lx"), hr));
}
}
else
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectUsingG711 - could not find "
"G711 codec's input pin - %lx"), hr));
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// TryToConnect
//
// This is a private helper method.
//
// This method connects an output pin to an input pin. It first tries
// direct connection; failing this, it adds the G711 filter to the graph
// and tries a G711 connection; failing that, it tries an intelligent
// connection (which can be disabled at compile time).
//
// Arguments:
// pOutputPin - IN - output pin on the capture filter or terminal
// pInputPin - IN - input pin on the render filter or terminal
// pfIntelligent - OUT - if NULL, then this parameter is ignored
// otherwise, BOOl value at this location is
// set to TRUE if intelligent connection was
// used, FALSE otherwise. Invalid if connection
// was unsuccessful.
//
// Return values:
// S_OK -- success
// various -- from other helpers and DShow methods
//
//
HRESULT CWaveMSPStream::TryToConnect(
IN IPin * pOutputPin,
IN IPin * pInputPin,
OUT BOOL * pfIntelligent
)
{
LOG((MSP_TRACE, STREAM_PREFIX("TryToConnect - enter")));
//
// Assume unintelligent connection unless we actually happen
// to use it.
//
if ( pfIntelligent != NULL )
{
_ASSERTE( ! IsBadWritePtr( pfIntelligent, sizeof( BOOL ) ) );
*pfIntelligent = FALSE;
}
HRESULT hr;
//
// Method 1: direct connection
//
hr = m_pIGraphBuilder->ConnectDirect(
pOutputPin,
pInputPin,
NULL
);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("TryToConnect: direct connection worked - exit S_OK")));
return S_OK;
}
LOG((MSP_ERROR, STREAM_PREFIX("TryToConnect - direct connection failed - %lx"), hr));
//
// Method 2: direct connection with G711 filter in between.
// If we haven't created and added the G711 filter to the graph yet,
// do so now.
//
hr = AddG711();
//
// If the AddG711 method worked, try to use the G711.
//
if (SUCCEEDED(hr) && m_pG711Filter)
{
hr = ConnectUsingG711(pOutputPin,
pInputPin);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("TryToConnect - "
"g711 connection worked - exit S_OK")));
return S_OK;
}
else
{
LOG((MSP_TRACE, STREAM_PREFIX("TryToConnect - "
"G711 connection failed - %lx"), hr));
}
}
else
{
LOG((MSP_ERROR, STREAM_PREFIX("TryToConnect - G711 codec does not exist. hr = %lx"), hr));
hr = E_FAIL;
}
//
// Method 3: intelligent connection, which may pull in who knows what
// other filters
//
#ifdef ALLOW_INTELLIGENT_CONNECTION
//
// Before intelligent connection, create the DShow filter mapper object if
// it doesn't already exist, and save it until the address is shut down.
// This will make all intelligent connects after the first much faster.
// No need to check the return code; if it fails, we just continue. The
// WaveMspCall object forwards this call to our address object.
//
// m_pMSPCall is valid here, because it is released in
// CMSPStream::ShutDown. ShutDown grabs the stream lock, releases
// m_pMSPCall, and unselects all terminals. The connection process starts
// with a StartStream or PauseStream, and those methods all grab the
// stream lock and return immediately if there are no terminals selected.
// Therefore, there is no danger of the call pointer being invalid during
// connection on a stream.
//
((CWaveMSPCall *) m_pMSPCall)->CreateFilterMapper();
hr = m_pIGraphBuilder->Connect(pOutputPin,
pInputPin);
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("TryToConnect - "
"intelligent connection failed - %lx"), hr));
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("TryToConnect - "
"intelligent connection worked - exit S_OK")));
if ( pfIntelligent != NULL )
{
*pfIntelligent = TRUE;
}
return S_OK;
#else // ALLOW_INTELLIGENT_CONNECTION
LOG((MSP_ERROR, STREAM_PREFIX("TryToConnect - NOTE: we never allow intelligent "
"connection - exit 0x%08x"), hr));
return hr;
#endif // ALLOW_INTELLIGENT_CONNECTION
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
HRESULT CWaveMSPStream::ConnectToTerminalPin(IPin * pTerminalPin)
{
LOG((MSP_TRACE, STREAM_PREFIX("ConnectToTerminalPin - enter")));
HRESULT hr = S_OK;
//
// Find our own filter's pin.
//
IPin * pMyPin;
hr = FindPin( &pMyPin );
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectToTerminalPin - "
"could not find pin - exit 0x%08x"), hr));
return hr; // we can't continue without this pin
}
// The OUTPUT pin from WAVEIN; the INPUT pin from WAVEOUT
IPin * pOutputPin = ( m_Direction == TD_RENDER ) ? pMyPin : pTerminalPin;
IPin * pInputPin = ( m_Direction == TD_CAPTURE ) ? pMyPin : pTerminalPin;
#ifdef MSPLOG
//
// In the interests of easier diagnosis, do some tracing of the formats
// that are available.
//
IEnumMediaTypes * pEnum;
hr = pOutputPin->EnumMediaTypes(&pEnum);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("Output pin media types:")));
ShowMediaTypes(pEnum);
pEnum->Release();
}
hr = pInputPin->EnumMediaTypes(&pEnum);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("Input pin media types:")));
ShowMediaTypes(pEnum);
pEnum->Release();
}
#endif // #ifdef MSPLOG
//
// Do a preliminary connection between the terminal and our filter,
// without having configured the capturer's allocator properties.
//
// fIntelligent is assigned TRUE if intelligent connection was used,
// FALSE otherwise -- only valid on success.
//
BOOL fIntelligent;
hr = TryToConnect(pOutputPin,
pInputPin,
& fIntelligent
);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("ConnectToTerminalPin - "
"preliminary connection succeeded")));
//
// Now that we are connected, find out the default buffer size we
// should use at the capture filter with interactive terminals.
// This can only be gleaned when the capture filter is connected, but
// we cannot make use of the information until we disconnect the
// filters.
//
long lDefaultBufferSize;
hr = DecideDesiredCaptureBufferSize(pOutputPin,
& lDefaultBufferSize);
if ( SUCCEEDED(hr) )
{
LOG((MSP_TRACE, STREAM_PREFIX("ConnectToTerminalPin - "
"default buffer size determination succeeded")));
//
// remove the terminal from the graph
//
hr = RemoveTerminal();
if (FAILED(hr))
{
LOG((MSP_ERROR,
STREAM_PREFIX("ConnectToTerminalPin - RemoveTerminal Failed hr=0x%x"), hr));
}
else
{
//
// clean filter graph by removing all the filters other than
// the wave filter
//
CleanFilterGraph();
//
// we can now re-add the terminal.
//
hr = ReAddTerminal();
if ( FAILED(hr) )
{
LOG((MSP_ERROR,
STREAM_PREFIX("ConnectToTerminalPin - ReAddTerminal failed - hr=0x%x"),
hr));
}
else
{
//
// Perform our settings on the capture filter.
// We don't need to bail if this fails -- we will just have worse
// latency / performance.
//
ConfigureCapture(pOutputPin,
pInputPin,
lDefaultBufferSize);
//
// Now do the actual connection between the terminal and our filter.
// Last argument is NULL because we don't care if it's intelligent
// this time
//
hr = TryToConnect(pOutputPin,
pInputPin,
NULL
);
}
}
}
}
#ifdef MSPLOG
if ( SUCCEEDED(hr) )
{
// Do some extra debug output.
// don't care if something fails in here...
ExamineCaptureProperties(pOutputPin);
}
#endif
pMyPin->Release();
if ( FAILED(hr) )
{
LOG((MSP_ERROR, STREAM_PREFIX("ConnectToTerminalPin - "
"could not connect to pin - exit 0x%08x"), hr));
//
// cleanup by removing all filters except for the wave filter
//
CleanFilterGraph();
return hr;
}
LOG((MSP_TRACE, STREAM_PREFIX("ConnectToTerminalPin - exit S_OK")));
return S_OK;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
HRESULT CWaveMSPStream::FindPinInFilter(
BOOL bWantOutputPin, // IN: if false, we want the input pin
IBaseFilter * pFilter, // IN: the filter to examine
IPin ** ppPin // OUT: the pin we found
)
{
HRESULT hr;
IEnumPins * pEnumPins;
*ppPin = NULL;
// enumerate the pins on the filter
hr = pFilter->EnumPins( &pEnumPins );
if ( FAILED(hr) )
{
return hr;
}
// go through the pins
while (TRUE)
{
PIN_DIRECTION pd;
hr = pEnumPins->Next( 1, ppPin, NULL );
if (S_OK != hr)
{
// didn't find a pin!
break;
}
// get the pin info
hr = (*ppPin)->QueryDirection( &pd );
// does it meet the criteria?
if (bWantOutputPin && (pd == PINDIR_OUTPUT))
{
// yes
break;
}
if ( ! bWantOutputPin && (pd == PINDIR_INPUT))
{
// yes
break;
}
(*ppPin)->Release();
*ppPin = NULL;
}
pEnumPins->Release();
if (NULL == *ppPin)
{
// error
return E_FAIL;
}
return S_OK;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// FindPin
//
// Finds the first pin in the filter that meets criteria.
// For bWaveIn == TRUE, the pin must be direction PINDIR_OUTPUT
// For bWaveIn == FALSE, the pin must be direction PINDIR_INPUT
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
HRESULT
CWaveMSPStream::FindPin(
IPin ** ppPin
)
{
return FindPinInFilter(m_Direction == TD_RENDER,
m_pFilter,
ppPin);
}
// eof