|
|
//
// 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 );
//
// Free the cache
//
for ( DWORD dwCurrDevice = 0; dwCurrDevice < dwNumDevices; dwCurrDevice++ ) { if( pwszHardwareIds[ dwCurrDevice ] ) { delete pwszHardwareIds[ dwCurrDevice ]; } }
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
//
|