/*++ 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 // audio device error codes #include #include /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // 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