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

1721 lines
49 KiB
C++

//
// Copyright (c) 1999 Microsoft Corporation
//
// HIDCOM.EXE -- exploratory USB Phone Console Application
//
// audio.cpp -- audio magic
//
// Zoltan Szilagyi, July - August, 1999
//
// Prioritized to-do list:
//
// * Convert printfs to debug tracing, and define debug tracing to
// printfs for HidCom.Exe use.
//
// * GetInstanceFromDeviceName should look only for audio devices.
// This should somewhat reduce the 2 sec wave enumeration time.
// Don't forget to remove timing debug output.
//
// * Consider changing FindWaveIdFromHardwareIdString and its helpers to take a
// devinst only and computer the hardware ids on the fly rather than storing
// them in arrays. This would slow it down but make the code simpler.
//
// * Small one-time memory leak: The static arrays of hardware ID
// strings are leaked. That's a few KB per process, no increase over
// time. If we make this a class then we'll just deallocate those
// arrays in the destructor.
// Also, for PNP events that cause us to recompute the mapping, we will
// need to destroy the array at some point if the wave devices change.
// So we need to augment the interface for this.
//
#include <wtypes.h>
#include <stdio.h>
#include "audio.h" // our own prototypes
#include <cfgmgr32.h> // CM_ functions
#include <setupapi.h> // SetupDi functions
#include <mmsystem.h> // wave functions
#include <initguid.h>
#include <devguid.h> // device guids
//
// mmddkp.h -- private winmm header file
// This is in nt\private\inc, but one must ssync and build in
// nt\private\genx\windows\inc before it will show up.
//
#include <mmddkp.h>
#include <crtdbg.h>
#define ASSERT _ASSERTE
#ifdef DBG
#define STATIC
#else
#define STATIC static
#endif
extern "C"
{
#include "mylog.h"
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
//
// Private helper functions
//
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// MapConfigRetToWin32
//
// This routine maps some CM error return codes to Win32 return codes, and
// maps everything else to the value specied by dwDefault. This function is
// adapted almost verbatim from the SetupAPI code.
//
// Arguments:
// CmReturnCode - IN - specifies the ConfigMgr return code to be mapped
// dwDefault - IN - specifies the default value to use if no explicit
// mapping applies
//
// Return values:
// Setup API (Win32) error code
//
STATIC DWORD
MapConfigRetToWin32(
IN CONFIGRET CmReturnCode,
IN DWORD dwDefault
)
{
switch(CmReturnCode) {
case CR_SUCCESS :
return NO_ERROR;
case CR_CALL_NOT_IMPLEMENTED :
return ERROR_CALL_NOT_IMPLEMENTED;
case CR_OUT_OF_MEMORY :
return ERROR_NOT_ENOUGH_MEMORY;
case CR_INVALID_POINTER :
return ERROR_INVALID_USER_BUFFER;
case CR_INVALID_DEVINST :
return ERROR_NO_SUCH_DEVINST;
case CR_INVALID_DEVICE_ID :
return ERROR_INVALID_DEVINST_NAME;
case CR_ALREADY_SUCH_DEVINST :
return ERROR_DEVINST_ALREADY_EXISTS;
case CR_INVALID_REFERENCE_STRING :
return ERROR_INVALID_REFERENCE_STRING;
case CR_INVALID_MACHINENAME :
return ERROR_INVALID_MACHINENAME;
case CR_REMOTE_COMM_FAILURE :
return ERROR_REMOTE_COMM_FAILURE;
case CR_MACHINE_UNAVAILABLE :
return ERROR_MACHINE_UNAVAILABLE;
case CR_NO_CM_SERVICES :
return ERROR_NO_CONFIGMGR_SERVICES;
case CR_ACCESS_DENIED :
return ERROR_ACCESS_DENIED;
case CR_NOT_DISABLEABLE:
return ERROR_NOT_DISABLEABLE;
default :
return dwDefault;
}
}
//////////////////////////////////////////////////////////////////////////////
//
// MapConfigRetToHResult
//
// This routine maps some CM error return codes to HRESULT return codes, and
// maps everything else to the HRESULT value E_FAIL.
//
// Arguments:
// CmReturnCode - IN - specifies the ConfigMgr return code to be mapped
//
// Return values:
// HRESULT error code
//
STATIC HRESULT
MapConfigRetToHResult(
IN CONFIGRET CmReturnCode
)
{
DWORD dwWin32Error;
HRESULT hr;
//
// Map configret --> win32
//
dwWin32Error = MapConfigRetToWin32(
CmReturnCode,
E_FAIL
);
//
// Map win32 --> HRESULT
// but don't try to map default E_FAIL, as it is not within the range for
// a normal win32 error code.
//
if ( dwWin32Error == E_FAIL )
{
hr = E_FAIL;
}
else
{
hr = HRESULT_FROM_WIN32( dwWin32Error );
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// CheckIfAncestor
//
// This function determines if one of the specified devnodes (the "proposed
// ancestor") is an ancestor of the other specified devnode (the "proposed
// descendant").
//
// The devnodes are arranged in a tree. If node A is an ancestor of node
// B, it just means that node A is either equal to node B, or has a child
// that is an ancestor of node B. This can also be stated in reverse --
// node C is a descendant of node D if C is equal to D, or if C's parent
// is a descendant of node D.
//
// The algorithm used here to determine ancestry is a straightforward
// application of the definition, although the recursion is removed.
//
// Arguments:
// dwDevInstProposedAncestor - IN - the proposed ancestor (see above)
// dwDevInstProposedDescendant - IN - the proposed descendant (see above)
// pfIsAncestor - OUT - returns bool value indicating if
// the pa is an ancestor of the pd
//
// Return values:
// S_OK - success
// others - from CM_Get_Parent
//
STATIC HRESULT
CheckIfAncestor(
IN DWORD dwDevInstProposedAnscestor,
IN DWORD dwDevInstProposedDescendant,
OUT BOOL * pfIsAncestor
)
{
ASSERT( ! IsBadWritePtr( pfIsAncestor, sizeof( BOOL ) ) );
DWORD dwCurrNode;
HRESULT hr;
//
// Initially, the current node is the proposed descendant.
//
dwCurrNode = dwDevInstProposedDescendant;
while ( TRUE )
{
//
// Check if this node is the proposed ancestor.
// If so, the proposed ancestor is an ancestor of the
// proposed descendant.
//
if ( dwCurrNode == dwDevInstProposedAnscestor )
{
*pfIsAncestor = TRUE;
hr = S_OK;
break;
}
//
// Replace the current node with the current node's parent.
//
CONFIGRET cr;
DWORD dwDevInstTemp;
cr = CM_Get_Parent(
& dwDevInstTemp, // out: parent's devinst dword
dwCurrNode, // in: child's devinst dword
0 // in: flags: must be zero
);
if ( cr == CR_NO_SUCH_DEVNODE )
{
//
// This means we've fallen off the top of the PNP tree -- the
// proposed ancestor is not found in the proposed descendant's
// parentage chain.
//
* pfIsAncestor = FALSE;
hr = S_OK;
break;
}
else if ( cr != CR_SUCCESS )
{
//
// Some other error occured.
//
hr = MapConfigRetToHResult( cr );
break;
}
dwCurrNode = dwDevInstTemp;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// FindClosestCommonAncestor
//
// Given a pair of devnodes identified by devinst DWORDs, this function
// finds the devinst DWORD for the closest common ancestor of the two
// devnodes.
//
// See CheckIfAncestor for a discussion of the concepts of ancestor and
// descendant. Then, devnode C is a common ancestor of devnodes A and B if C
// is an ancestor of A -AND- C is an ancestor of B. Any pair of devnodes has
// at least one common ancestor, that being the root of the PNP tree. A pair
// of devnodes may have more than one common ancestor. The set of common
// ancestors of A and B has one UNIQUE member, called the closest common
// ancestor, such that no other member of the set is a child of that node.
//
// You can compute the closest common ancestor of two nodes A and B by
// constructing a chain of nodes going from the root of the tree to A through
// all A's ancestors, and also doing the same for B. Comparing these chains
// side by side, they must be the same in at least the first node (the root).
// The closest common ancestor for A and B is the last node that is the same
// for both chains.
//
// The algorithm used here is an alternative, relatively stateless approach
// that can take more CPU time but uses less memory, doesn't involve any
// allocations, and is much easier to write (the last being the overriding
// consideration, as the PNP tree is in always shallow). The code simply walks
// up A's chain of ancestors, checking if each node is an ancestor of B. The
// first node for which this is true is the closest common ancestor of
// A and B.
//
// Arguments:
// dwDevInstOne - IN - the first node ('A' above)
// dwDevInstTwo - IN - the other node ('B' above)
// pdwDevInstResult - OUT - returns the closest common ancestor
//
// Return values:
// S_OK - success
// others - from CM_Get_Parent
//
STATIC HRESULT
FindClosestCommonAncestor(
IN DWORD dwDevInstOne,
IN DWORD dwDevInstTwo,
OUT DWORD * pdwDevInstResult
)
{
ASSERT( ! IsBadWritePtr( pdwDevInstResult, sizeof( DWORD ) ) );
HRESULT hr;
BOOL fIsAncestor;
DWORD dwDevInstCurr;
//
// For each node up the chain of #1's parents, starting from #1 itself...
//
dwDevInstCurr = dwDevInstOne;
while ( TRUE )
{
//
// Check if this node is also in the chain of #2's parents.
//
hr = CheckIfAncestor(
dwDevInstCurr,
dwDevInstTwo,
& fIsAncestor
);
if ( FAILED(hr) )
{
return hr;
}
if ( fIsAncestor )
{
*pdwDevInstResult = dwDevInstCurr;
return S_OK;
}
//
// Get the next node in the chain of #1's parents.
//
CONFIGRET cr;
DWORD dwDevInstTemp;
cr = CM_Get_Parent(
& dwDevInstTemp, // out: parent's devinst dword
dwDevInstCurr, // in: child's devinst dword
0 // in: flags: must be zero
);
if ( cr != CR_SUCCESS )
{
//
// dwDevInst has no parent, or some other error occured.
//
// This is always an error, because there must always
// be a common parent somewhere up the chain -- the root of the PNP
// tree!
//
return MapConfigRetToHResult( cr );
}
dwDevInstCurr = dwDevInstTemp;
}
}
//////////////////////////////////////////////////////////////////////////////
//
// TrimHardwareIdString
//
// This function strips off extraneous parts of the hardware ID string as
// it is expected to appear for USB devices. The remaining parts of the string
// are those that identify the vendor, product, and product revision, which
// are together used to match devices as belonging to the same composite or
// compound device.
//
// (Actually, for devices A and B, it's not just A and B that are compared,
// it's A and the closest common parent of A and B. This ensures that the case
// of multiple identical phones in the same system is handled correctly. This
// logic of course lives outside of this funtion, though.)
//
// As an example:
// "hid\Vid_04a6&Pid_00b9&Rev_0010&Mi_04&Col01"
// becomes "Vid_04a6&Pid_00b9&Rev_0010"
//
// Note that this function will routinely be applied to strings for non-USB
// devices that will not be in the same format; that's ok, since those strings
// will never match USB-generated strings, be they trimmed or not.
//
// Also, note that the hardware ID string as read from the registry actually
// consists of multiple concatenated null-terminated strings, all terminated
// by two consecutive null characters. This function just ignores strings
// beyond the first, as the first contains all the info we need.
//
// Arguments:
// wszHardwareId - IN - the string to trim (in place)
//
// Return values:
// TRUE - the string looked like a valid USB hardware ID
// FALSE - the string did not look like a valid USB hardware ID
//
STATIC BOOL
TrimHardwareIdString(
IN WCHAR * wszHardwareId
)
{
ASSERT( ! IsBadStringPtrW( wszHardwareId, (DWORD) -1 ) );
//
// "volatile" is needed, otherwise the compiler blatantly ignores the
// recalculation of dwSize after the first pass.
//
volatile DWORD dwSize;
DWORD dwCurrPos;
BOOL fValid = FALSE;
DWORD dwNumSeparators = 0;
//
// Strip off leading characters up to and including the first \ from the
// front. If there is no \ in the string, it is invalid.
//
dwSize = lstrlenW(wszHardwareId);
for ( dwCurrPos = 0; dwCurrPos < dwSize; dwCurrPos ++ )
{
if ( wszHardwareId[ dwCurrPos ] == L'\\' )
{
MoveMemory(
wszHardwareId, // dest
wszHardwareId + dwCurrPos + 1, // source
sizeof(WCHAR) * dwSize - dwCurrPos // size, in bytes
);
fValid = TRUE;
break;
}
}
if ( ! fValid )
{
return FALSE;
}
//
// Strip off trailing characters starting from the third &.
// A string with less than two & is rejected.
//
// Examples:
//
// Vid_04a6&Pid_00b9&Rev_0010&Mi_04&Col01
// becomes Vid_04a6&Pid_00b9&Rev_0010
//
// Vid_04a6&Pid_00b9&Rev_0010&Mi_04
// becomes Vid_04a6&Pid_00b9&Rev_0010
//
// CSC6835_DEV
// is rejected
//
//
// Must recompute size because we changed it above.
// (And note that dwSize is declared as 'volatile'.)
//
dwSize = lstrlenW(wszHardwareId);
for ( dwCurrPos = 0; dwCurrPos < dwSize; dwCurrPos ++ )
{
if ( wszHardwareId[ dwCurrPos ] == L'&' )
{
dwNumSeparators ++;
if ( dwNumSeparators == 3 )
{
wszHardwareId[ dwCurrPos ] = L'\0';
break;
}
}
}
if ( dwNumSeparators < 2 )
{
return FALSE;
}
else
{
return TRUE;
}
}
//////////////////////////////////////////////////////////////////////////////
//
// DevInstGetIdString
//
// This function retrieves an id string or string set for a particular
// devinst dword. The value is obtained from the registry, but the
// Configuration Manager API hides the detail of where in the registry this
// info lives.
//
// Arguments:
// dwDevInst - IN - the devinst dword for which we want info
// dwProperty - IN - the property to retrieve
// pwszIdString - OUT - returns "new"ed Unicode string or string set.
//
// Return values:
// S_OK - success
// E_OUTOFMEMORY - out of memory during string allocation
// E_UNEXPECTED - data type of returned ID is not string or mutli-string
// others - from CM_Get_DevNode_Registry_PropertyW
//
STATIC HRESULT
DevInstGetIdString(
IN DWORD dwDevInst,
IN DWORD dwProperty,
OUT WCHAR ** pwszIdString
)
{
const DWORD INITIAL_STRING_SIZE = 100;
CONFIGRET cr;
DWORD dwBufferSize = INITIAL_STRING_SIZE;
DWORD dwDataType = 0;
ASSERT( ! IsBadWritePtr( pwszIdString, sizeof( WCHAR * ) ) );
do
{
//
// Allocate a buffer to store the returned string.
//
*pwszIdString = new WCHAR[ dwBufferSize + 1 ];
if ( *pwszIdString == NULL )
{
return E_OUTOFMEMORY;
}
//
// Try to get the string in the registry; we may not have enough
// buffer space.
//
cr = CM_Get_DevNode_Registry_PropertyW(
dwDevInst, // IN DEVINST dnDevInst,
dwProperty, // IN ULONG ulProperty,
& dwDataType, // OUT PULONG pulRegDataType, OPT
(void *) *pwszIdString, // OUT PVOID Buffer, OPT
& dwBufferSize, // IN OUT PULONG pulLength,
0 // IN ULONG ulFlags -- must be zero
);
if ( cr == CR_SUCCESS )
{
if ( ( dwDataType != REG_MULTI_SZ ) && ( dwDataType != REG_SZ ) )
{
//
// Value available, but it is not a string ot multi-string. Ouch!
//
delete ( *pwszIdString );
return E_UNEXPECTED;
}
else
{
return S_OK;
}
}
else if ( cr != CR_BUFFER_SMALL )
{
//
// It's supposed to fail with this error code because we didn't pass in
// a buffer. Failed to get registry value type and length.
//
delete ( *pwszIdString );
return MapConfigRetToHResult( cr );
}
else // cr == CR_BUFFER_SMALL
{
delete ( *pwszIdString );
//
// the call filled in dwBufferSize with the needed value
//
}
}
while ( TRUE );
}
//////////////////////////////////////////////////////////////////////////////
//
// HardwareIdFromDevInst
//
// This function retrieves a trimmed hardware ID string for a particular
// devinst dword. The value is obtained from the helper function
// DevInstGetIdString(), and then trimmed using TrimHardwareIdString.
//
// Arguments:
// dwDevInst - IN - the devinst dword for which we want info
// pwszHardwareId - OUT - returns "new"ed Unicode string set
//
// Return values:
// S_OK - success
// E_FAIL - not a valid string in USB format
// others - from DevInstGetIdString()
//
STATIC HRESULT
HardwareIdFromDevInst(
IN DWORD dwDevInst,
OUT WCHAR ** pwszHardwareId
)
{
ASSERT( ! IsBadWritePtr(pwszHardwareId, sizeof( WCHAR * ) ) );
HRESULT hr;
BOOL fValid;
hr = DevInstGetIdString(
dwDevInst,
CM_DRP_HARDWAREID,
pwszHardwareId
);
if ( FAILED(hr) )
{
return hr;
}
// wprintf(L"*** HardwareIdFromDevInst: devinst 0x%08x, RAW hardwareID %s\n",
// dwDevInst, *pwszHardwareId);
fValid = TrimHardwareIdString( *pwszHardwareId );
if ( ! fValid )
{
delete ( * pwszHardwareId );
return E_FAIL;
}
// wprintf(L"HardwareIdFromDevInst: devinst 0x%08x, hardwareID %s\n",
// dwDevInst, *pwszHardwareId);
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// MatchHardwareIdInArray
//
// This function takes the devinst and hardware ID for a HID device and
// looks in an array of devinsts and hardware IDs for wave devices to find
// the correct wave id to use with the HID device. The correct wave id is
// the one whose hardware ID matches the HID device's hardware ID, and whose
// hardware ID matches the hardware ID for the closest common ancestor of
// itself and the HID device.
//
// Arguments:
// dwHidDevInst - IN - the devinst dword for the HID device
// wszHidHardwareId - IN - the trimmed hardware ID string for the
// HID device
// dwNumDevices - IN - the size of the arrays -- the number of
// wave ids on the system
// pwszHardwareIds - IN - array of trimmed hardware id strings for
// the wave devices, indexed by wave ids.
// Some entries may be NULL to mark them as
// invalid.
// pdwDevInsts - IN - array of devinsts for wave devices,
// indexed by wave ids. Some entries may be
// (DWORD) -1 to mark them as invalid.
// pdwMatchedWaveId - OUT - the wave id that matches the hid device
//
// Return values:
// S_OK - the devinst was matched
// E_FAIL - the devinst was not matched
//
STATIC HRESULT
MatchHardwareIdInArray(
IN DWORD dwHidDevInst,
IN WCHAR * wszHidHardwareId,
IN DWORD dwNumDevices,
IN WCHAR ** pwszHardwareIds,
IN DWORD * pdwDevInsts,
OUT DWORD * pdwMatchedWaveId
)
{
ASSERT( ! IsBadStringPtrW( wszHidHardwareId, (DWORD) -1 ) );
ASSERT( ! IsBadReadPtr( pwszHardwareIds,
sizeof( WCHAR * ) * dwNumDevices ) );
ASSERT( ! IsBadReadPtr( pdwDevInsts,
sizeof( DWORD ) * dwNumDevices ) );
ASSERT( ! IsBadWritePtr( pdwMatchedWaveId, sizeof(DWORD) ) );
//
// For each available wave id...
//
DWORD dwCurrWaveId;
for ( dwCurrWaveId = 0; dwCurrWaveId < dwNumDevices; dwCurrWaveId++ )
{
//
// If this particular wave device has the same stripped hardware
// ID string as what we are searching for, then we have a match.
// But non-USB devices have non-parsable hardware ID strings, so
// they are stored in the array as NULLs.
//
if ( pwszHardwareIds[ dwCurrWaveId ] != NULL )
{
ASSERT( ! IsBadStringPtrW( pwszHardwareIds[ dwCurrWaveId ], (DWORD) -1 ) );
if ( ! lstrcmpW( pwszHardwareIds[ dwCurrWaveId ], wszHidHardwareId ) )
{
//
// We have a match, but we must still verify if we're on the same
// device, not some other device that has the same hardwareID. This
// is to differentiate between multiple identical phones on the same
// system.
//
// Note: we could make the code more complex, but avoid some work in
// most cases, by only doing this if there is more than one match based
// on hardwareIDs alone.
//
DWORD dwCommonAncestor;
WCHAR * wszAncestorHardwareId;
HRESULT hr;
hr = FindClosestCommonAncestor(
dwHidDevInst,
pdwDevInsts[ dwCurrWaveId ],
& dwCommonAncestor
);
if ( SUCCEEDED(hr) )
{
//
// Get the hardware ID for the closest common ancestor.
//
hr = HardwareIdFromDevInst(
dwCommonAncestor,
& wszAncestorHardwareId
);
if ( SUCCEEDED(hr) )
{
//
// Check if they are the same. The closest common ancestor
// will be some sort of hub if the audio device is from
// some other identical phone other than the one whose HID
// device we are looking at.
//
BOOL fSame;
fSame = ! lstrcmpW( wszAncestorHardwareId,
wszHidHardwareId );
delete wszAncestorHardwareId;
if ( fSame )
{
*pdwMatchedWaveId = dwCurrWaveId;
return S_OK;
}
}
}
}
}
}
//
// No match.
//
return E_FAIL;
}
//////////////////////////////////////////////////////////////////////////////
//
// GetInstanceFromDeviceName
//
// This function retrieves a device instance identifier based on a device
// name string. This works for any device.
//
// Arguments:
// wszName - IN - the device name string
// pdwInstance - OUT - returns instance identifier
//
// Return values:
// S_OK
// various win32 errors from SetupDi fucntions
//
STATIC HRESULT
GetInstanceFromDeviceName(
IN WCHAR * wszName,
OUT DWORD * pdwInstance,
IN HDEVINFO hDevInfo
)
{
ASSERT( ! IsBadStringPtrW( wszName, (DWORD) -1 ) );
ASSERT( ! IsBadWritePtr( pdwInstance, sizeof(DWORD) ) );
//
// Get the interface data for this specific device
// (based on wszName).
//
BOOL fSuccess;
DWORD dwError;
SP_DEVICE_INTERFACE_DATA interfaceData;
interfaceData.cbSize = sizeof( SP_DEVICE_INTERFACE_DATA ); // required
fSuccess = SetupDiOpenDeviceInterfaceW(
hDevInfo, // device info set handle
wszName, // name of the device
0, // flags, reserved
& interfaceData // OUT: interface data
);
if ( ! fSuccess )
{
LOG((PHONESP_TRACE, "GetInstanceFromDeviceName - SetupDiOpenDeviceInterfaceW failed: %08x", GetLastError()));
//
// Need to clean up, but save the error code first, because
// the cleanup function calls SetLastError().
//
dwError = GetLastError();
return HRESULT_FROM_WIN32( dwError );
}
//
// Get the interface detail data from this interface data. This provides
// more detailed information,including the device instance DWORD that
// we seek.
//
SP_DEVINFO_DATA devinfoData;
devinfoData.cbSize = sizeof( SP_DEVINFO_DATA ); // required
fSuccess = SetupDiGetDeviceInterfaceDetail(
hDevInfo, // device info set handle
& interfaceData, // device interface data structure
NULL, // OPT ptr to dev name struct
0, // OPT avail size of dev name st
NULL, // OPT actual size of devname st
& devinfoData
);
if ( ! fSuccess )
{
//
// It is normal for the above function to fail with
// ERROR_INSUFFICIENT_BUFFER because we passed in NULL for the
// device interface detail data (device name) structure.
//
dwError = GetLastError();
if ( dwError != ERROR_INSUFFICIENT_BUFFER )
{
LOG((PHONESP_TRACE, "GetInstanceFromDeviceName - SetupDiGetDeviceInterfaceDetail failed: %08x", GetLastError()));
//
// Can't clean this up earlier, because it does SetLastError().
//
return HRESULT_FROM_WIN32( dwError );
}
}
*pdwInstance = devinfoData.DevInst;
//
// Can't clean this up earlier, because it does SetLastError().
//
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
//
//
// This function constructs an array of devinst DWORDs and an array of
// hardware ID strigs, both indexed by wave id, for either wave in or wave
// out devices. The devinsts are retrieved by (1) using undocumented calls
// to winmm to retrieve device name strings for each wave device, and (2)
// using SetupDi calls to retrieve a DevInst DWORD for each device name
// string (helper function GetInstanceFromDeviceName).
//
// The values are saved in an array because this process takes the bulk of the
// time in the HID --> audio mapping process, and therefore finding the mapping
// for several HID devices can be done in not much more time than for one HID
// device, just by reusing the array.
//
// Arguments:
// fRender - IN - if TRUE, look for wave out devices
// pdwNumDevices - OUT - returns number of wave devices found
// ppwszHardwareIds - OUT - returns "new"ed array of trimmed hardware id
// strings. The array is indexed by wave id. If
// a hardware id string cannot be determined for
// a particular wave id, then the string pointer
// in that position is set to NULL. Each string
// is "new"ed separately.
// ppdwDevInsts - OUT - returns "new"ed array of devinst DWORDs. The
// array is indexed by wave id. If a devinst
// cannot be determined for a particular wave id,
// then the DWORD in that position is set to
// (DWORD) -1.
//
// Return values:
// S_OK - success
// E_OUTOFMEMORY - not enough memory to allocate a device name string or
// the return array
//
STATIC HRESULT
ConstructWaveHardwareIdCache(
IN BOOL fRender,
OUT DWORD * pdwNumDevices,
OUT WCHAR *** ppwszHardwareIds,
OUT DWORD ** ppdwDevInsts
)
{
ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );
ASSERT( ! IsBadWritePtr( pdwNumDevices, sizeof( DWORD ) ) );
ASSERT( ! IsBadWritePtr( ppwszHardwareIds, sizeof( WCHAR ** ) ) );
ASSERT( ! IsBadWritePtr( ppdwDevInsts, sizeof( DWORD * ) ) );
//
// Get a device info list
//
HDEVINFO hDevInfo;
/*
hDevInfo = SetupDiGetClassDevs(
&GUID_DEVCLASS_MEDIA, // class GUID (which device classes?)
NULL, // optional enumerator to filter
NULL, // HWND (we have none)
( DIGCF_PRESENT | // only devices that are present
DIGCF_PROFILE ) // only devices in this hw profile
);
*/
hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_MEDIA, NULL);
if ( hDevInfo == NULL )
{
LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - SetupDiCreateDeviceInfoList failed: %08x", GetLastError()));
return HRESULT_FROM_WIN32(GetLastError());
}
//
// Find the number of available wave devices.
//
DWORD dwNumDevices;
DWORD dwCurrDevice;
if ( fRender )
{
dwNumDevices = waveOutGetNumDevs();
}
else
{
dwNumDevices = waveInGetNumDevs();
}
//
// Allocate space for the return arrays.
//
*pdwNumDevices = dwNumDevices;
*ppwszHardwareIds = new LPWSTR [ dwNumDevices ];
if ( (*ppwszHardwareIds) == NULL )
{
return E_OUTOFMEMORY;
}
*ppdwDevInsts = new DWORD [ dwNumDevices ];
if ( (*ppdwDevInsts) == NULL )
{
delete *ppwszHardwareIds;
*ppwszHardwareIds = NULL;
return E_OUTOFMEMORY;
}
//
// Loop over the available wave devices.
//
for ( dwCurrDevice = 0; dwCurrDevice < dwNumDevices; dwCurrDevice++ )
{
//
// For failure cases, we will return NULL string and -1 devinst
// for that wave id. Callers should compare against the NULL, not
// the -1.
//
(*ppwszHardwareIds) [ dwCurrDevice ] = NULL;
(*ppdwDevInsts) [ dwCurrDevice ] = -1;
//
// Get the size of the device path string.
//
MMRESULT mmresult;
ULONG ulSize;
if ( fRender )
{
mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACESIZE,
(DWORD_PTR) & ulSize,
0
);
}
else
{
mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACESIZE,
(DWORD_PTR) & ulSize,
0
);
}
if ( mmresult != MMSYSERR_NOERROR )
{
LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Could not get device string size for device %d; "
"error = %d", dwCurrDevice, mmresult));
}
else if ( ulSize == 0 )
{
LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Got zero device string size for device %d",
dwCurrDevice));
}
else
{
//
// Allocate space for the device path string.
//
WCHAR * wszDeviceName;
wszDeviceName = new WCHAR[ (ulSize / 2) + 1 ];
if ( wszDeviceName == NULL )
{
LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Out of memory in device string alloc for device %d;"
" requested size is %d\n", dwCurrDevice, ulSize));
delete *ppwszHardwareIds;
*ppwszHardwareIds = NULL;
delete *ppdwDevInsts;
*ppdwDevInsts = NULL;
return E_OUTOFMEMORY;
}
//
// Get the device path string from winmm.
//
if ( fRender )
{
mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACE,
(DWORD_PTR) wszDeviceName,
(DWORD_PTR) ulSize
);
}
else
{
mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACE,
(DWORD_PTR) wszDeviceName,
(DWORD_PTR) ulSize
);
}
if ( mmresult == MMSYSERR_NOERROR )
{
//
// Got the string. Now retrieve a devinst dword based on the
// string.
//
// wprintf(L"\tDevice name string for device %d is:\n"
// L"\t\t%ws\n",
// dwCurrDevice, wszDeviceName);
HRESULT hr;
DWORD dwInstance;
hr = GetInstanceFromDeviceName(
wszDeviceName,
& dwInstance,
hDevInfo
);
delete wszDeviceName;
if ( FAILED(hr) )
{
LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Can't get instance DWORD for device %d; "
"error 0x%08x\n",
dwCurrDevice, hr));
}
else
{
//
// Based on the devinst dword, retrieve a trimmed
// hardware id string.
//
// printf("\tInstance DWORD for device %d is "
// "0x%08x\n",
// dwCurrDevice, dwInstance);
WCHAR * wszHardwareId;
hr = HardwareIdFromDevInst(
dwInstance,
& wszHardwareId
);
if ( SUCCEEDED(hr) )
{
(*ppwszHardwareIds) [ dwCurrDevice ] = wszHardwareId;
(*ppdwDevInsts) [ dwCurrDevice ] = dwInstance;
}
}
}
}
}
SetupDiDestroyDeviceInfoList( hDevInfo );
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// FindWaveIdFromHardwareIdString
//
// This function finds the wave id for a device whose devinst and hardware id
// string are known.
//
// Constructing the mapping from waveid to devinst and hardwave id string
// takes some time, so the mapping is only constructed once for each
// direction (render/capture), via the helper function
// ConstructWaveHardwareIdCache().
//
// Thereafter, the helper function MatchHardwareIdInArray() is used to run
// the matching algorithm based on the already-computed arrays. See that
// function for a description of how the matching is done.
//
// Arguments:
// dwHidDevInst - IN - the devinst dword to match to a wave id
// wszHardwareId - IN - the hardware id string for the devinst
// fRender - IN - TRUE for wave out, FALSE for wave in
// pdwMatchedWaveId - OUT - the wave id associated with the devinst
//
// Return values:
// S_OK - success
// various errors from ConstructWaveHardwareIdCache() and
// MatchHardwareIdInArray() helper functions
//
STATIC HRESULT
FindWaveIdFromHardwareIdString(
IN DWORD dwHidDevInst,
IN WCHAR * wszHardwareId,
IN BOOL fRender,
OUT DWORD * pdwMatchedWaveId
)
{
ASSERT( ! IsBadStringPtrW( wszHardwareId, (DWORD) -1 ) );
ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );
ASSERT( ! IsBadWritePtr(pdwMatchedWaveId, sizeof(DWORD) ) );
DWORD dwNumDevices = 0;
WCHAR ** pwszHardwareIds = NULL;
DWORD * pdwDevInsts = NULL;
HRESULT hr;
//
// Need to construct cache of render device hardware IDs.
//
hr = ConstructWaveHardwareIdCache(
fRender,
& dwNumDevices,
& pwszHardwareIds,
& pdwDevInsts
);
if ( FAILED(hr) )
{
return hr;
}
//
// The cache is ready; use it to perform the rest of the matching
// algorithm.
//
hr = MatchHardwareIdInArray(
dwHidDevInst,
wszHardwareId,
dwNumDevices,
pwszHardwareIds,
pdwDevInsts,
pdwMatchedWaveId
);
delete pwszHardwareIds;
delete pdwDevInsts;
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//
// OutputDeviceInfo
//
// This function is for diagnostic purposes only.
//
// Given a devinst DWORD, this function prints the DeviceDesc string as well
// as the entire (untrimmed) hardware ID string set for the device. Example:
//
//
//
// Arguments:
// dwDesiredDevInst - IN - the devinst dword for which we want info
//
// Return values:
// none
//
STATIC void
OutputDeviceInfo(
DWORD dwDesiredDevInst
)
{
//
// Get and print the device description string.
//
HRESULT hr;
WCHAR * wszDeviceDesc;
WCHAR * wszHardwareId;
hr = DevInstGetIdString(
dwDesiredDevInst,
CM_DRP_DEVICEDESC,
& wszDeviceDesc
);
if ( FAILED(hr) )
{
LOG((PHONESP_TRACE, "OutputDeviceInfo - [can't get device description string - 0x%08x]", hr));
}
else
{
LOG((PHONESP_TRACE, "OutputDeviceInfo - [DeviceDesc: %ws]", wszDeviceDesc));
delete wszDeviceDesc;
}
//
// Get and print hardware ID string set.
//
hr = DevInstGetIdString(
dwDesiredDevInst,
CM_DRP_HARDWAREID,
& wszHardwareId
);
if ( FAILED(hr) )
{
LOG((PHONESP_TRACE, "OutputDeviceInfo - [can't get hardware id - 0x%08x]", hr));
}
else
{
//
// Print out all the values in the mutli-string.
//
WCHAR * wszCurr = wszHardwareId;
while ( wszCurr[0] != L'\0' )
{
LOG((PHONESP_TRACE, "OutputDeviceInfo - [HardwareId: %ws]", wszCurr));
wszCurr += lstrlenW(wszCurr) + 1;
}
delete wszHardwareId;
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
//
// Externally-callable functions
//
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// ExamineWaveDevices
//
// This function is for debugging purposes only. It enumerates audio devices
// using the Wave API and prints the device path string as well as the
// device instance DWORD for each render or capture device.
//
// Arguments:
// fRender - IN - true means examine wave out devices; false = wave in
//
// Return values:
// E_OUTOFMEMORY
// S_OK
//
HRESULT
ExamineWaveDevices(
IN BOOL fRender
)
{
ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );
DWORD dwNumDevices;
DWORD dwCurrDevice;
//
// Get a device info list
//
HDEVINFO hDevInfo;
/*
hDevInfo = SetupDiGetClassDevs(
&GUID_DEVCLASS_MEDIA, // class GUID (which device classes?)
NULL, // optional enumerator to filter
NULL, // HWND (we have none)
( DIGCF_PRESENT | // only devices that are present
DIGCF_PROFILE ) // only devices in this hw profile
);
*/
hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_MEDIA, NULL);
if ( hDevInfo == NULL )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - SetupDiCreateDeviceInfoList failed: %08x", GetLastError()));
return HRESULT_FROM_WIN32(GetLastError());
}
//
// Loop over the available wave devices.
//
if ( fRender )
{
dwNumDevices = waveOutGetNumDevs();
}
else
{
dwNumDevices = waveInGetNumDevs();
}
LOG((PHONESP_TRACE, "ExamineWaveDevices - Found %d audio %s devices.",
dwNumDevices,
fRender ? "render" : "capture"));
for ( dwCurrDevice = 0; dwCurrDevice < dwNumDevices; dwCurrDevice++ )
{
MMRESULT mmresult;
ULONG ulSize;
//
// Get the size of the device path string.
//
if ( fRender )
{
mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACESIZE,
(DWORD_PTR) & ulSize,
0
);
}
else
{
mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACESIZE,
(DWORD_PTR) & ulSize,
0
);
}
if ( mmresult != MMSYSERR_NOERROR )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Could not get device string size for device %d; "
"error = %d\n", dwCurrDevice, mmresult));
}
else if ( ulSize == 0 )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Got zero device string size for device %d\n",
dwCurrDevice));
}
else
{
//
// Allocate space for the device path string.
//
WCHAR * wszDeviceName;
wszDeviceName = new WCHAR[ (ulSize / 2) + 1 ];
if ( wszDeviceName == NULL )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Out of memory in device string alloc for device %d;"
" requested size is %d\n", dwCurrDevice, ulSize));
return E_OUTOFMEMORY;
}
//
// Get the device path string from winmm.
//
if ( fRender )
{
mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACE,
(DWORD_PTR) wszDeviceName,
ulSize
);
}
else
{
mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
DRV_QUERYDEVICEINTERFACE,
(DWORD_PTR) wszDeviceName,
ulSize
);
}
if ( mmresult == MMSYSERR_NOERROR )
{
//
// Got the string; print it and convert it to a
// devinst DWORD.
//
LOG((PHONESP_TRACE, "ExamineWaveDevices - Device name string for device %d is: %ws",
dwCurrDevice, wszDeviceName));
HRESULT hr;
DWORD dwInstance;
hr = GetInstanceFromDeviceName(
wszDeviceName,
& dwInstance,
hDevInfo
);
if ( FAILED(hr) )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Can't get instance DWORD for device %d; "
"error 0x%08x",
dwCurrDevice, hr));
}
else
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Instance DWORD for device %d is "
"0x%08x",
dwCurrDevice, dwInstance));
//
// Print various other info about this device.
//
OutputDeviceInfo( dwInstance );
WCHAR * wszHardwareId;
hr = HardwareIdFromDevInst(
dwInstance,
& wszHardwareId
);
if ( FAILED(hr) )
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Can't get hardware id string for device %d; "
"error 0x%08x",
dwCurrDevice, hr));
}
else
{
LOG((PHONESP_TRACE, "ExamineWaveDevices - Hardware ID for device %d is %ws\n",
dwCurrDevice, wszHardwareId));
delete wszHardwareId;
}
}
delete wszDeviceName;
}
}
}
SetupDiDestroyDeviceInfoList( hDevInfo );
return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
// DiscoverAssociatedWaveId
//
// This function searches for a wave device to match the HID device in the
// PNP tree location specified in the passed in SP_DEVICE_INTERFACE_DATA
// structure, obtained from the SetupKi API. It returns the wave id for
// the matched device.
//
// It uses the helper function FindWaveIdFromHardwareIdString() to search for
// the wave device based on a devinst DWORD and a hardware ID string. First,
// it must obtain the devinst for the device; it does this by calling a SetupDi
// function and looking up the devinst in a resulting structure. The hardware
// ID string is then retrieved from the registry and trimmed, using the helper
// function HardwareIdFromDevinst().
//
// See FindWaveIdFromHardwareIdString() for further comments on the search
// algorithm.
//
// Arguments:
// dwDevInst - IN - Device Instance of the HID device
// fRender - IN - TRUE for wave out, FALSE for wave in
// pdwWaveId - OUT - the wave id associated with this HID device
//
// Return values:
// S_OK - succeeded and matched wave id
// other from helper functions FindWaveIdFromHardwareIdString() or
// or HardwareIdFromDevinst()
//
HRESULT
DiscoverAssociatedWaveId(
IN DWORD dwDevInst,
IN BOOL fRender,
OUT DWORD * pdwWaveId
)
{
ASSERT( ! IsBadWritePtr(pdwWaveId, sizeof(DWORD) ) );
ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );
//
// We've got the device instance DWORD for the HID device.
// Use it to get the trimmed hardware ID string, which tells
// us the vendor, product, and revision numbers.
//
HRESULT hr;
WCHAR * wszHardwareId;
hr = HardwareIdFromDevInst(
dwDevInst,
& wszHardwareId
);
if ( FAILED(hr) )
{
return hr;
}
//
// Finally, use this information to choose a wave id.
//
hr = FindWaveIdFromHardwareIdString(
dwDevInst,
wszHardwareId,
fRender,
pdwWaveId
);
delete wszHardwareId;
if ( FAILED(hr) )
{
return hr;
}
return S_OK;
}
//
// eof
//