/*++

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