/*++ Copyright (c) 1998-1999 Microsoft Corporation Module Name: msptrmar.cpp Abstract: MSP base classes: implementation of audio render terminal. --*/ #include "precomp.h" #pragma hdrstop #include // Filter volume level ranges const long AX_MIN_VOLUME = -9640; // -10000; const long AX_MAX_VOLUME = 0; //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CAudioRenderTerminal::CAudioRenderTerminal() { m_TerminalDirection = TD_RENDER; m_TerminalType = TT_STATIC; m_szName[0] = L'\0'; // real name is copied in on creation m_bResourceReserved = false; LOG((MSP_TRACE, "CAudioRenderTerminal::CAudioRenderTerminal() finished")); } CAudioRenderTerminal::~CAudioRenderTerminal() { LOG((MSP_TRACE, "CAudioRenderTerminal::~CAudioRenderTerminal() finished")); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // This function determines if the terminal associated with a given // moniker is "good". A good terminal returns S_OK; a bad terminal returns an // error. // // A good terminal has the following properties: // * has a friendly name // * is not a WAVE_MAPPER terminal // * is not a DirectSound terminal (unless USE_DIRECT_SOUND is pound-defined) // static inline HRESULT TerminalAllowed(IMoniker * pMoniker) { HRESULT hr; CComPtr pBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if (FAILED(hr)) { LOG((MSP_ERROR, "audio render TerminalAllowed (IMoniker::BindToStorage) " "- returning %8x", hr)); return hr; } VARIANT var; // we make sure creation is not going to fail on // account of a nonexistent friendly name VariantInit(&var); var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, 0); if (FAILED(hr)) { LOG((MSP_ERROR, "audio render TerminalAllowed " "(IPropertyBag::Read on FriendlyName) - got %8x; skipping terminal", hr)); return hr; } // Fix for memory leak! SysFreeString(var.bstrVal); // NOTE: Magic code selects only wave devices VariantInit(&var); var.vt = VT_I4; hr = pBag->Read(L"WaveOutId", &var, 0); if (hr != S_OK) { #ifndef USE_DIRECT_SOUND // This is most likely a DirectSound terminal LOG((MSP_WARN, "audio render TerminalAllowed - " "this is a DirectSound terminal " "so we are skipping it - note that this is a routine " "occurance - returning %8x", hr)); #else // we do use DirectSound return S_OK; #endif } else if (var.lVal == WAVE_MAPPER) { // hack: if the value is equal to WAVE_MAPPER then don't use it.... hr = E_FAIL; // random failure code :) LOG((MSP_WARN, "audio render TerminalAllowed - " "this is a WAVE_MAPPER terminal " "so we are skipping it - note that this is a routine " "occurance - returning %8x", hr)); } return hr; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT CAudioRenderTerminal::CreateTerminal( IN CComPtr pMoniker, IN MSP_HANDLE htAddress, OUT ITTerminal **ppTerm ) { // Enable ATL string conversion macros. USES_CONVERSION; LOG((MSP_TRACE, "CAudioRenderTerminal::CreateTerminal - enter")); // // Validate the parameters // if ( MSPB_IsBadWritePtr(ppTerm, sizeof(ITTerminal *) ) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal : " "bad terminal pointer; returning E_POINTER")); return E_POINTER; } if ( IsBadReadPtr(pMoniker, sizeof(IMoniker) ) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal : " "bad moniker pointer; returning E_POINTER")); return E_POINTER; } // // We return a NULL terminal if we fail. // *ppTerm = NULL; HRESULT hr; // Refuse to work with DirectSound or WAVE_MAPPER terminals. // or if we can't read the friendlyName... if (FAILED(hr = TerminalAllowed(pMoniker))) return hr; // // Get the name for this filter out of the property bag. // CComPtr pBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal (IMoniker::BindToStorage) - returning %8x", hr)); return hr; } VARIANT var; VariantInit(&var); var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, 0); if (FAILED(hr)) { LOG((MSP_WARN, "CAudioRenderTerminal::CreateTerminal " "(IPropertyBag::Read) - got %8x - we are therefore skipping " "this terminal; note that this is fairly routine", hr)); return hr; } // // Create the terminal. // CMSPComObject *pLclTerm = new CMSPComObject; if (pLclTerm == NULL) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal - returning E_OUTOFMEMORY")); return E_OUTOFMEMORY; } // // Save some stuff in the terminal. // pLclTerm->m_pMoniker = pMoniker; lstrcpyn(pLclTerm->m_szName, OLE2T(var.bstrVal), MAX_PATH); SysFreeString(var.bstrVal); // // Get the ITTerminal interface that we were asked for. // hr = pLclTerm->_InternalQueryInterface(IID_ITTerminal, (void**)ppTerm); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal - " "Internal QI failed; returning 0x%08x", hr)); delete pLclTerm; *ppTerm = NULL; // just in case return hr; } // // Finish initializing the terminal. // hr = pLclTerm->Initialize(CLSID_SpeakersTerminal, TAPIMEDIATYPE_AUDIO, TD_RENDER, htAddress ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateTerminal - " "Initialize failed; returning 0x%08x", hr)); (*ppTerm)->Release(); *ppTerm = NULL; // just in case return hr; } LOG((MSP_TRACE, "CAudioRenderTerminal::CreateTerminal - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Create the filters used by this terminal HRESULT CAudioRenderTerminal::CreateFilters() { LOG((MSP_TRACE, "CAudioRenderTerminal::CreateFilters - enter")); HRESULT hr; // // We used to recreate the audio render filter every time, but we don't // have any real reason for doing so. Just return S_OK if the filter // has already been created. // if ( m_pIFilter != NULL ) { _ASSERTE( m_pIPin != NULL ); LOG((MSP_TRACE, "CAudioRenderTerminal::CreateFilters - " "filter already created - exit S_OK")); return S_OK; } _ASSERTE ( m_pIBasicAudio == NULL ); _ASSERTE ( m_pIPin == NULL ); // // Sanity checks. // if ( m_pMoniker == NULL ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateFilters - " "no moniker present - returning E_UNEXPECTED")); return E_UNEXPECTED; } // // Create a new instance of the filter. // hr = m_pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pIFilter); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateFilters - " "BindToObject failed; returning %8x", hr)); return hr; } // // Get the basic audio interface for the filter. If it doesn't exist, we // can live with that, but all our IBasicAudio methods will fail. // hr = m_pIFilter->QueryInterface(IID_IBasicAudio, (void **) &m_pIBasicAudio); if ( FAILED(hr) ) { LOG((MSP_WARN, "CAudioRenderTerminal::CreateFilters - " "QI for IBasicAudio failed: %8x", hr)); } hr = FindTerminalPin(); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::CreateFilters - " "FindTerminalPin failed; returning 0x%08x", hr)); m_pIFilter = NULL; // does an implicit release if ( m_pIBasicAudio ) { m_pIBasicAudio = NULL; // does an implicit release } return hr; } LOG((MSP_TRACE, "CAudioRenderTerminal::CreateFilters - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT CAudioRenderTerminal::FindTerminalPin( ) { LOG((MSP_TRACE, "CAudioRenderTerminal::FindTerminalPin - enter")); // // Sanity checks so we don't AV. // if (m_pIPin != NULL) { LOG((MSP_TRACE, "CAudioRenderTerminal::FindTerminalPin - " "we've already got a pin; exit E_UNEXPECTED")); return E_UNEXPECTED; } if (m_pIFilter == NULL) { LOG((MSP_TRACE, "CAudioRenderTerminal::FindTerminalPin - " "we don't have a filter; exit E_UNEXPECTED")); return E_UNEXPECTED; } HRESULT hr; CComPtr pIEnumPins; ULONG cFetched; // // Find the render pin for the filter. // hr = m_pIFilter->EnumPins(&pIEnumPins); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::FindTerminalPin - can't enum pins 0x%08x", hr)); return hr; } IPin * pIPin; // Enumerate all the pins and break on the // first pin that meets requirement. for (;;) { if (pIEnumPins->Next(1, &pIPin, &cFetched) != S_OK) { LOG((MSP_ERROR, "CAudioRenderTerminal::FindTerminalPin - can't get a pin %8x", hr)); return (hr == S_FALSE) ? E_FAIL : hr; } if (0 == cFetched) { LOG((MSP_ERROR, "CAudioRenderTerminal::FindTerminalPin - got zero pins")); return E_FAIL; } PIN_DIRECTION dir; if (FAILED(hr = pIPin->QueryDirection(&dir))) { LOG((MSP_ERROR, "CAudioRenderTerminal::FindTerminalPin - can't query pin direction %8x", hr)); pIPin->Release(); return hr; } if (PINDIR_INPUT == dir) { break; } pIPin->Release(); } m_pIPin = pIPin; LOG((MSP_TRACE, "CAudioRenderTerminal::FindTerminalPin - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HRESULT CAudioRenderTerminal::AddFiltersToGraph( ) { LOG((MSP_TRACE, "CAudioRenderTerminal::AddFiltersToGraph - enter")); if (m_pGraph == NULL) { LOG((MSP_ERROR, "CAudioRenderTerminal::AddFiltersToGraph - " "haven't got a filter graph; return E_UNEXPECTED")); return E_UNEXPECTED; } // // Create the filters if this is the first connection with this terminal. // HRESULT hr = CreateFilters(); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::AddFiltersToGraph - " "CreateFilters failed; returning 0x%08x", hr)); return hr; } // // Add the filter to the graph. // // A word about names: // If a filter has already been added with the same name (which will // happen if we have more than one audio render terminal in the same // graph) then that will return VFW_S_DUPLICATE_NAME, which is not // a failure. // hr = m_pGraph->AddFilter(m_pIFilter, WAVEOUT_NAME); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::AddFiltersToGraph - " "returning 0x%08x", hr)); return hr; } LOG((MSP_TRACE, "CAudioRenderTerminal::AddFiltersToGraph - exit S_OK")); return S_OK; } ////////////////////////////////////////////////////////////////////////////// // we override this here so we can do some stuff // right after the filter gets connected STDMETHODIMP CAudioRenderTerminal::CompleteConnectTerminal(void) { LOG((MSP_TRACE, "CAudioRenderTerminal::CompleteConnectTerminal - enter")); // By default, we need not unreserve later. m_bResourceReserved = false; // Don't clobber the base class' machinations (currently nothing...) HRESULT hr = CSingleFilterTerminal::CompleteConnectTerminal(); if (FAILED(hr)) { LOG((MSP_TRACE, "CAudioRenderTerminal::CompleteConnectTerminal: " "CSingleFilterTerminal method failed")); return hr; } // So here we are, after our filter has been added to the filter graph and connected up. // We need to use the filter's // IAMResourceControl::Reserve method to make sure the filter opens the waveOut device // now (and keeps it open). ////////////////////////////////////////////////////////////////////////// // we must inform the filter that we want it to grab the wave device. // We do this after connecting because the filter needs to negotiate the // media type before it can open a wave device. CComPtr pIResource; hr = m_pIFilter->QueryInterface(IID_IAMResourceControl, (void **) &pIResource); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::CompleteConnectTerminal - QI failed: %8x", hr)); // This is a nonesential operation so we do not fail. return S_OK; } // The QueryInterface didn't fail... hr = pIResource->Reserve(AMRESCTL_RESERVEFLAGS_RESERVE, NULL); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::CompleteConnectTerminal - " "device reservation failed: %8x", hr)); return hr; } else if (hr == S_FALSE) { // Well, in this case either another application is already using the wave out device, // or we are running half-duplex and we've got both wavein and waveout terminals // selected. LOG((MSP_ERROR, "CAudioRenderTerminal::CompleteConnectTerminal - " "device already in use: %8x", hr)); return hr; } // {if the driver is half-duplex} // We have succeeded in reserving, so we will want to unreserve later. m_bResourceReserved = true; LOG((MSP_TRACE, "CAudioRenderTerminal::CompleteConnectTerminal - exit S_OK")); return S_OK; } ////////////////////////////////////////////////////////////////////////////////// // We override this here so we can unreserve the resource when we are done. // removes filters from the filter graph and resets member variables // Disconnect may be called anytime after Connect succeeds (it need not be called // if CompleteConnect fails) STDMETHODIMP CAudioRenderTerminal::DisconnectTerminal( IN IGraphBuilder * pGraph, IN DWORD dwReserved ) { LOG((MSP_TRACE, "CAudioRenderTerminal::DisconnectTerminal - enter")); HRESULT hr; // // First call the base class method, to make sure we validate everything // and don't mess with our resource reservation unless this is a valid // disconnection (e.g., the filter graph pointersmatch). // hr = CSingleFilterTerminal::DisconnectTerminal(pGraph, dwReserved); if (FAILED(hr)) { LOG((MSP_TRACE, "CAudioRenderTerminal::DisconnectTerminal : " "CSingleFilterTerminal method failed; hr = %d", hr)); return hr; } // if we need to release the resource if (m_bResourceReserved) { CComPtr pIResource; hr = m_pIFilter->QueryInterface(IID_IAMResourceControl, (void **) &pIResource); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::DisconnectTerminal - QI failed: %8x", hr)); // This is a nonesential operation so we do not "return hr;" here. } else { // QueryInterface didn't fail, and we have reserved WaveOut, so we must // unreserve now. hr = pIResource->Reserve(AMRESCTL_RESERVEFLAGS_UNRESERVE, NULL); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::DisconnectTerminal - " "device unreservation failed: %8x", hr)); // no reason to completely die at this point, so we just continue } // if other things fail we may be called again, but we should not try // to unreserve again. m_bResourceReserved = false; } // {if QI succeeded} } // {if need to release resource} LOG((MSP_TRACE, "CAudioRenderTerminal::DisconnectTerminal - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // private helper method: static HRESULT RangeConvert(long lInput, long lInputMin, long lInputMax, long * plOutput, long lOutputMin, long lOutputMax) { _ASSERTE( lInputMin < lInputMax ); _ASSERTE( lOutputMin < lOutputMax ); _ASSERTE( ! MSPB_IsBadWritePtr(plOutput, sizeof(long)) ); if (lInput < lInputMin) { LOG((MSP_ERROR, "RangeConvert - value out of range - " "%d < %d; returning E_INVALIDARG", lInput, lInputMin)); return E_INVALIDARG; } if (lInput > lInputMax) { LOG((MSP_ERROR, "RangeConvert - value out of range - " "%d > %d; returning E_INVALIDARG", lInput, lInputMax)); return E_INVALIDARG; } // This is how much we are going to expand the range of the input. double dRangeWidthRatio = (double) (lOutputMax - lOutputMin) / (double) (lInputMax - lInputMin); *plOutput = (long) ((lInput - lInputMin) * dRangeWidthRatio) + lOutputMin; return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP CAudioRenderTerminal::get_Volume(long * plVolume) { CLock lock(m_CritSec); LOG((MSP_TRACE, "CAudioRenderTerminal::get_Volume - enter")); // // Parameter checks. // if ( MSPB_IsBadWritePtr(plVolume, sizeof(long)) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_Volume - " "bad pointer argument")); return E_POINTER; } if (m_pIBasicAudio == NULL) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_Volume - " "don't have necessary interface - exit E_FAIL")); return E_FAIL; } // // Let the filter do the work. // HRESULT hr = m_pIBasicAudio->get_Volume(plVolume); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_Volume - " "filter call failed: %08x", hr)); return hr; } // // Asjust the range of the value returned to match the range specified // by the TAPI APIs. // hr = RangeConvert(*plVolume, AX_MIN_VOLUME, AX_MAX_VOLUME, plVolume, 0, 0xFFFF); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_Volume - " "RangeConvert call failed: %08x", hr)); return hr; } LOG((MSP_TRACE, "CAudioRenderTerminal::get_Volume - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP CAudioRenderTerminal::put_Volume(long lVolume) { CLock lock(m_CritSec); LOG((MSP_TRACE, "CAudioRenderTerminal::put_Volume - enter")); if (m_pIBasicAudio == NULL) { LOG((MSP_ERROR, "CAudioRenderTerminal::put_Volume - " "don't have necessary interface - exit E_FAIL")); return E_FAIL; } // // Asjust the range of the value returned to match the range needed // by the WaveOut filter. // HRESULT hr = RangeConvert(lVolume, 0, 0xFFFF, &lVolume, AX_MIN_VOLUME, AX_MAX_VOLUME); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::put_Volume - " "RangeConvert call failed: %08x", hr)); return hr; } // // Let the filter do the work. // hr = m_pIBasicAudio->put_Volume(lVolume); if (FAILED(hr)) { LOG((MSP_ERROR, "CAudioRenderTerminal::put_Volume - " "filter call failed: %08x", hr)); return hr; } LOG((MSP_TRACE, "CAudioRenderTerminal::put_Volume - exit S_OK")); return S_OK; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP CAudioRenderTerminal::get_Balance(long * plBalance) { HRESULT hr = E_NOTIMPL; LOG((MSP_TRACE, "CAudioRenderTerminal::get_Balance - enter")); LOG((MSP_TRACE, "CAudioRenderTerminal::get_Balance - exit 0x%08x", hr)); return hr; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP CAudioRenderTerminal::put_Balance(long lBalance) { HRESULT hr = E_NOTIMPL; LOG((MSP_TRACE, "CAudioRenderTerminal::put_Balance - enter")); LOG((MSP_TRACE, "CAudioRenderTerminal::put_Balance - exit 0x%08x", hr)); return hr; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STDMETHODIMP CAudioRenderTerminal::get_WaveId( OUT long * plWaveId ) { LOG((MSP_TRACE, "CAudioRenderTerminal::get_WaveId - enter")); CLock lock(m_CritSec); // // Parameter checks. // if ( MSPB_IsBadWritePtr(plWaveId, sizeof(long)) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_WaveId - " "bad pointer argument")); return E_POINTER; } // // Check the moniker pointer. // if ( IsBadReadPtr( m_pMoniker, sizeof(IMoniker) ) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_WaveId - " "bad moniker pointer - exit E_UNEXPECTED")); return E_UNEXPECTED; } // // Get a property bag from the moniker. // IPropertyBag * pBag; HRESULT hr = m_pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **) &pBag); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_WaveId - " "can't get property bag - exit 0x%08x", hr)); return hr; } // // Get the ID from the property bag. // VARIANT var; var.vt = VT_I4; hr = pBag->Read( L"WaveOutId", &var, 0); pBag->Release(); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CAudioRenderTerminal::get_WaveId - " "can't read wave ID - exit 0x%08x", hr)); return hr; } *plWaveId = (long) var.lVal; LOG((MSP_TRACE, "CAudioRenderTerminal::get_WaveId - exit S_OK")); return S_OK; }