mirror of https://github.com/tongzx/nt5src
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
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
|