windows-nt/Source/XPSP1/NT/net/tapi/skywalker/mspbase/msptrmar.cpp
2020-09-26 16:20:57 +08:00

881 lines
24 KiB
C++

/*++
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 <mmsystem.h>
// 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<IPropertyBag> 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<IMoniker> 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<IPropertyBag> 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<CAudioRenderTerminal> *pLclTerm = new CMSPComObject<CAudioRenderTerminal>;
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<IEnumPins> 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 <IAMResourceControl> 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 <IAMResourceControl> 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;
}