// // 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 #include #include "audio.h" // our own prototypes #include // CM_ functions #include // SetupDi functions #include // wave functions #include #include // 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 #include #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 //