From: Ross Bencina Date: Sat, 27 Aug 2016 07:51:40 +0000 (+1000) Subject: Merge of old SVN hotplug branch into current git HEAD. Includes hotplug support for... X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=89f8f75a27ddfda9f9bcefcf96ac5c510a046b80;p=portaudio Merge of old SVN hotplug branch into current git HEAD. Includes hotplug support for DirectSound, WDM/KS. Both are working running in debugger but WDM/KS seems to be crashing in stand-alone build. I removed the use of struct _PaInternalInfo in hotplug/pa_front.c to reduce the size of the change. pa_win_dsound.c involved the most intricate changes due to other changes that had been made in the mean time. I've also initialized function ptrs ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos so this should build on all platforms. --- diff --git a/build/msvc/portaudio.def b/build/msvc/portaudio.def index bdaa8ee..2534daf 100644 --- a/build/msvc/portaudio.def +++ b/build/msvc/portaudio.def @@ -3,7 +3,7 @@ EXPORTS ; Pa_GetVersion @1 Pa_GetVersionText @2 -Pa_GetErrorText @3 +Pa_GetErrorText @3 Pa_Initialize @4 Pa_Terminate @5 Pa_GetHostApiCount @6 @@ -35,6 +35,8 @@ Pa_GetStreamReadAvailable @31 Pa_GetStreamWriteAvailable @32 Pa_GetSampleSize @33 Pa_Sleep @34 +Pa_UpdateAvailableDeviceList @35 +Pa_SetDevicesChangedCallback @36 PaAsio_GetAvailableBufferSizes @50 PaAsio_ShowControlPanel @51 PaUtil_InitializeX86PlainConverters @52 diff --git a/build/msvc/portaudio.vcproj b/build/msvc/portaudio.vcproj index e5a648b..e9f3078 100644 --- a/build/msvc/portaudio.vcproj +++ b/build/msvc/portaudio.vcproj @@ -1827,6 +1827,10 @@ RelativePath="..\..\src\os\win\pa_win_wdmks_utils.c" > + + diff --git a/include/portaudio.h b/include/portaudio.h index 3bb3b31..09d6aa8 100644 --- a/include/portaudio.h +++ b/include/portaudio.h @@ -155,9 +155,17 @@ const char *Pa_GetErrorText( PaError errorCode ); Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not required to be fully nested. - Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + If Pa_Initialize() returns an error code, Pa_Terminate() should NOT be called. + @note The device list returned by Pa_GetDeviceCount() et al. is frozen + at the time Pa_Initialize() is called, and not updated dynamically, even if + hardware devices (e.g. USB or firewire devices) are connected or disconnected + during the course of the program's lifetime. If it is required to re-scan the + list of devices, one must call Pa_UpdateAvailableDeviceList() or, completely + uninitialize PortAudio using Pa_Terminate(), and then reinitialize it by calling + Pa_Initialize(). + @return paNoError if successful, otherwise an error code indicating the cause of failure. @@ -431,6 +439,20 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void ); */ PaDeviceIndex Pa_GetDefaultOutputDevice( void ); +/** Update the list of available devices to match currently available hardware devices. + + PortAudio's list of available devices is usually frozen at the time Pa_Initialize() is + called. Pa_UpdateAvailableDeviceList() may be called to refresh PortAudio's list + of available devices at any time while PortAudio is initialized. + + @note Open streams will not be affected by calls to this function, but + any previously returned PaDeviceInfo pointers are invalidated once this + function is called. + + @return an error code indicating whether the device refresh was successful. + */ +PaError Pa_UpdateAvailableDeviceList( void ); + /** The type used to represent monotonic time in seconds. PaTime is used for the fields of the PaStreamCallbackTimeInfo argument to the @@ -1201,6 +1223,13 @@ PaError Pa_GetSampleSize( PaSampleFormat format ); void Pa_Sleep( long msec ); +/* FIXME: document these and put them with other device functions */ +typedef void PaDevicesChangedCallback( void *userData ); + +PaError Pa_SetDevicesChangedCallback( void *userData, PaStreamFinishedCallback* devicesChangedCallback ); + + + #ifdef __cplusplus } diff --git a/src/common/pa_front.c b/src/common/pa_front.c index 0632710..f530bd9 100644 --- a/src/common/pa_front.c +++ b/src/common/pa_front.c @@ -81,6 +81,10 @@ #include "pa_svnrevision.h" #endif +extern void PaUtil_InitializeHotPlug(); +extern void PaUtil_TerminateHotPlug(); + + /** * This is incremented if we make incompatible API changes. * This version scheme is based loosely on http://semver.org/ @@ -161,6 +165,8 @@ static int deviceCount_ = 0; PaUtilStreamRepresentation *firstOpenStream_ = NULL; +PaDevicesChangedCallback* devicesChangedCallback_ = NULL; +void* devicesChangedCallbackUserData_ = NULL; #define PA_IS_INITIALISED_ (initializationCount_ != 0) @@ -371,6 +377,9 @@ PaError Pa_Initialize( void ) PaUtil_InitializeClock(); PaUtil_ResetTraceMessages(); + /* Initialize hot plug here, so all its internal info is setup */ + PaUtil_InitializeHotPlug(); + result = InitializeHostApis(); if( result == paNoError ) ++initializationCount_; @@ -396,6 +405,8 @@ PaError Pa_Terminate( void ) TerminateHostApis(); + PaUtil_TerminateHotPlug(); + PaUtil_DumpTraceMessages(); } result = paNoError; @@ -744,6 +755,117 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void ) } +PaError Pa_UpdateAvailableDeviceList( void ) +{ + PaError result = paNoError; + void **scanResults = NULL; + int *deviceCounts = NULL; + int i = 0; + + PA_LOGAPI_ENTER( "Pa_UpdateAvailableDeviceList" ); + if( !PA_IS_INITIALISED_ ) + { + result = paNotInitialized; + goto done; + } + + /* Allocate data structures used in 2-stage commit */ + scanResults = (void **) PaUtil_AllocateMemory( sizeof(void*) * hostApisCount_ ); + if( !scanResults ) + { + result = paInsufficientMemory; + goto done; + } + + deviceCounts = ( int * ) PaUtil_AllocateMemory( sizeof( int ) * hostApisCount_ ); + if( !deviceCounts ) + { + result = paInsufficientMemory; + goto done; + } + + /* Phase 1: Perform a scan of new devices */ + for( i = 0 ; i < hostApisCount_ ; ++i ) + { + PaUtilHostApiRepresentation *hostApi = hostApis_[i]; + if( hostApi->ScanDeviceInfos == NULL ) + continue; + + PA_DEBUG(( "Scanning new device list for host api %d.\n",i)); + if( hostApi->ScanDeviceInfos( hostApi, i, &scanResults[ i ], &deviceCounts[ i ] ) != paNoError ) + break; + + } + + /* Check the result of the scan operation */ + if( i < hostApisCount_ ) + { + /* If failure, rollback the scan changes back to original state */ + int j = 0; + for( j = 0 ; j < i ; ++j ) + { + PaUtilHostApiRepresentation *hostApi = hostApis_[j]; + if( hostApi->DisposeDeviceInfos == NULL ) + continue; + + PA_DEBUG(( "Performing rollback for device list scan for host api %d.\n",i)); + hostApi->DisposeDeviceInfos( hostApi, scanResults[ j ], deviceCounts[ j ] ); + } + } + else + { + int baseDeviceIndex = 0; + deviceCount_ = 0; + + /* Otherwise, commit the scan changes to each back-end */ + for( i = 0 ; i < hostApisCount_ ; ++i ) + { + PaUtilHostApiRepresentation *hostApi = hostApis_[i]; + if( hostApi->CommitDeviceInfos == NULL ) + { + /* Not yet implemented for this backend. Just + assume that the baseDeviceIndex and the deviceCount_ are + incremented according to the values in the info */ + baseDeviceIndex += hostApi->info.deviceCount; + deviceCount_ += hostApi->info.deviceCount; + continue; + } + + PA_DEBUG(( "Committing device list scan for host api %d.\n",i)); + if( hostApi->CommitDeviceInfos( hostApi, i, scanResults[ i ], deviceCounts[ i ] ) != paNoError ) + { + PA_DEBUG(( "Committing failed (shouldn't happen) %d.\n",i)); + result = paInternalError; + goto done; + } + + assert( hostApi->info.defaultInputDevice < hostApi->info.deviceCount ); + assert( hostApi->info.defaultOutputDevice < hostApi->info.deviceCount ); + + hostApi->privatePaFrontInfo.baseDeviceIndex = baseDeviceIndex; + + if( hostApi->info.defaultInputDevice != paNoDevice ) + hostApi->info.defaultInputDevice += baseDeviceIndex; + + if( hostApi->info.defaultOutputDevice != paNoDevice ) + hostApi->info.defaultOutputDevice += baseDeviceIndex; + + baseDeviceIndex += hostApi->info.deviceCount; + deviceCount_ += hostApi->info.deviceCount; + } + } + +done: + + if( scanResults ) + PaUtil_FreeMemory( scanResults ); + + if( deviceCounts ) + PaUtil_FreeMemory( deviceCounts ); + + return result; +} + const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) { int hostSpecificDeviceIndex; @@ -1807,3 +1929,29 @@ PaError Pa_GetSampleSize( PaSampleFormat format ) return (PaError) result; } +extern void PaUtil_LockHotPlug(); +extern void PaUtil_UnlockHotPlug(); + + +PaError Pa_SetDevicesChangedCallback( void *userData, PaStreamFinishedCallback* devicesChangedCallback ) +{ + PaUtil_LockHotPlug(); + devicesChangedCallback_ = devicesChangedCallback; + devicesChangedCallbackUserData_ = userData; + PaUtil_UnlockHotPlug(); + return paNoError; +} + +/* Called whenever a OS audio device change has been detected */ +void PaUtil_DevicesChanged(unsigned state, void* pData) +{ + (void)state; + (void)pData; + PaUtil_LockHotPlug(); + if (devicesChangedCallback_) + { + (devicesChangedCallback_)(devicesChangedCallbackUserData_); + } + PaUtil_UnlockHotPlug(); +} + diff --git a/src/common/pa_hostapi.h b/src/common/pa_hostapi.h index 54b527e..79e3e88 100644 --- a/src/common/pa_hostapi.h +++ b/src/common/pa_hostapi.h @@ -322,6 +322,37 @@ typedef struct PaUtilHostApiRepresentation { const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); + + /** + Collect current native device information and compute a new device count. + This method should not result in the host API making any changes to its + internal data structures, instead it is the first step in a two-stage commit + process which will be completed only if all host API scans return with no errors. + + Some time later the caller will either (1) pass scanResults and newDeviceCount + to CommitDeviceInfos() to complete the transaction, or (2) pass scanResults + and newDeviceCount to DisposeDeviceInfos() to abort the transaction. + */ + PaError (*ScanDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **scanResults, int *newDeviceCount ); + + /** + Replace the previous device information with the device information + passed in as an argument via scanResults. This is the second/final stage of + the two-stage transaction required for updating the available device information. + This function should free any resources associated with the previous device + information. + @FIXME: this function should probably not return an error code as it should not be + permitted to fail. + */ + PaError (*CommitDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount ); + + /** + Free device information previously returned by ScanDeviceInfos(). This function + will be called in cases where the update available device info transaction + failed for some reason. + */ + PaError (*DisposeDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ); + } PaUtilHostApiRepresentation; diff --git a/src/hostapi/alsa/pa_linux_alsa.c b/src/hostapi/alsa/pa_linux_alsa.c index f286591..dbe2d04 100644 --- a/src/hostapi/alsa/pa_linux_alsa.c +++ b/src/hostapi/alsa/pa_linux_alsa.c @@ -757,6 +757,9 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; /** If AlsaErrorHandler is to be used, do not forget to unregister callback pointer in Terminate function. diff --git a/src/hostapi/asihpi/pa_linux_asihpi.c b/src/hostapi/asihpi/pa_linux_asihpi.c index f5a5290..ab76d3c 100644 --- a/src/hostapi/asihpi/pa_linux_asihpi.c +++ b/src/hostapi/asihpi/pa_linux_asihpi.c @@ -771,6 +771,9 @@ PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &hpiHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, diff --git a/src/hostapi/asio/pa_asio.cpp b/src/hostapi/asio/pa_asio.cpp index f230d87..47c5761 100644 --- a/src/hostapi/asio/pa_asio.cpp +++ b/src/hostapi/asio/pa_asio.cpp @@ -1356,6 +1356,9 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &asioHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, diff --git a/src/hostapi/coreaudio/pa_mac_core.c b/src/hostapi/coreaudio/pa_mac_core.c index 4b7b43c..4e35266 100644 --- a/src/hostapi/coreaudio/pa_mac_core.c +++ b/src/hostapi/coreaudio/pa_mac_core.c @@ -825,6 +825,9 @@ PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIn (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &auhalHostApi->callbackStreamInterface, CloseStream, StartStream, diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index 35fac5f..0a40797 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -189,6 +189,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); +static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount ); +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount ); +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount ); static PaError CloseStream( PaStream* stream ); static PaError StartStream( PaStream *stream ); static PaError StopStream( PaStream *stream ); @@ -312,6 +315,12 @@ typedef struct PaWinDsStream } PaWinDsStream; +typedef struct PaWinDsScanDeviceInfosResults{ /* used for tranferring device infos during scanning / rescanning */ + PaDeviceInfo **deviceInfos; + PaDeviceIndex defaultInputDevice; + PaDeviceIndex defaultOutputDevice; +} PaWinDsScanDeviceInfosResults; + /* Set minimal latency based on the current OS version. * NT has higher latency. @@ -455,7 +464,7 @@ typedef struct DSDeviceNameAndGUIDVector{ int count; int free; - DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() + DSDeviceNameAndGUID *items; /* Allocated using LocalAlloc() */ } DSDeviceNameAndGUIDVector; typedef struct DSDeviceNamesAndGUIDs{ @@ -762,38 +771,15 @@ static double defaultSampleRateSearchOrder_[] = ** The device will not be added to the device list if any errors are encountered. */ static PaError AddOutputDeviceInfoFromDirectSound( - PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface ) + PaWinDsDeviceInfo *winDsDeviceInfo, char *name, LPGUID lpGUID, char *pnpInterface ) { - PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; - PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount]; PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo; HRESULT hr; - LPDIRECTSOUND lpDirectSound; + LPDIRECTSOUND lpDirectSound = NULL; DSCAPS caps; - int deviceOK = TRUE; PaError result = paNoError; int i; - /* Copy GUID to the device info structure. Set pointer. */ - if( lpGUID == NULL ) - { - winDsDeviceInfo->lpGUID = NULL; - } - else - { - memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); - winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; - } - - if( lpGUID ) - { - if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) || - IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) ) - { - PA_DEBUG(("BLACKLISTED: %s \n",name)); - return paNoError; - } - } /* Create a DirectSound object for the specified GUID Note that using CoCreateInstance doesn't work on windows CE. @@ -836,7 +822,8 @@ static PaError AddOutputDeviceInfoFromDirectSound( lpGUID->Data4[6], lpGUID->Data4[7])); - deviceOK = FALSE; + result = paUnanticipatedHostError; + goto error; } else { @@ -847,7 +834,9 @@ static PaError AddOutputDeviceInfoFromDirectSound( if( hr != DS_OK ) { DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr )); - deviceOK = FALSE; + + result = paUnanticipatedHostError; + goto error; } else { @@ -856,154 +845,161 @@ static PaError AddOutputDeviceInfoFromDirectSound( if( caps.dwFlags & DSCAPS_EMULDRIVER ) { /* If WMME supported, then reject Emulated drivers because they are lousy. */ - deviceOK = FALSE; + result = paInvalidDevice; + goto error; } #endif - if( deviceOK ) + deviceInfo->maxInputChannels = 0; + winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; + + /* DS output capabilities only indicate supported number of channels + using two flags which indicate mono and/or stereo. + We assume that stereo devices may support more than 2 channels + (as is the case with 5.1 devices for example) and so + set deviceOutputChannelCountIsKnown to 0 (unknown). + In this case OpenStream will try to open the device + when the user requests more than 2 channels, rather than + returning an error. + */ + if( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) { - deviceInfo->maxInputChannels = 0; - winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; - - /* DS output capabilities only indicate supported number of channels - using two flags which indicate mono and/or stereo. - We assume that stereo devices may support more than 2 channels - (as is the case with 5.1 devices for example) and so - set deviceOutputChannelCountIsKnown to 0 (unknown). - In this case OpenStream will try to open the device - when the user requests more than 2 channels, rather than - returning an error. - */ - if( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) - { - deviceInfo->maxOutputChannels = 2; - winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0; - } - else - { - deviceInfo->maxOutputChannels = 1; - winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; - } + deviceInfo->maxOutputChannels = 2; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0; + } + else + { + deviceInfo->maxOutputChannels = 1; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; + } - /* Guess channels count from speaker configuration. We do it only when - pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined. - */ + /* Guess channels count from speaker configuration. We do it only when + pnpInterface is NULL or when PAWIN_USE_WDMKS_DEVICE_INFO is undefined. + */ #ifdef PAWIN_USE_WDMKS_DEVICE_INFO - if( !pnpInterface ) + if( !pnpInterface ) #endif + { + DWORD spkrcfg; + if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) ) { - DWORD spkrcfg; - if( SUCCEEDED(IDirectSound_GetSpeakerConfig( lpDirectSound, &spkrcfg )) ) + int count = 0; + switch (DSSPEAKER_CONFIG(spkrcfg)) { - int count = 0; - switch (DSSPEAKER_CONFIG(spkrcfg)) - { - case DSSPEAKER_HEADPHONE: count = 2; break; - case DSSPEAKER_MONO: count = 1; break; - case DSSPEAKER_QUAD: count = 4; break; - case DSSPEAKER_STEREO: count = 2; break; - case DSSPEAKER_SURROUND: count = 4; break; - case DSSPEAKER_5POINT1: count = 6; break; - case DSSPEAKER_7POINT1: count = 8; break; + case DSSPEAKER_HEADPHONE: count = 2; break; + case DSSPEAKER_MONO: count = 1; break; + case DSSPEAKER_QUAD: count = 4; break; + case DSSPEAKER_STEREO: count = 2; break; + case DSSPEAKER_SURROUND: count = 4; break; + case DSSPEAKER_5POINT1: count = 6; break; + case DSSPEAKER_7POINT1: count = 8; break; #ifndef DSSPEAKER_7POINT1_SURROUND #define DSSPEAKER_7POINT1_SURROUND 0x00000008 #endif - case DSSPEAKER_7POINT1_SURROUND: count = 8; break; + case DSSPEAKER_7POINT1_SURROUND: count = 8; break; #ifndef DSSPEAKER_5POINT1_SURROUND #define DSSPEAKER_5POINT1_SURROUND 0x00000009 #endif - case DSSPEAKER_5POINT1_SURROUND: count = 6; break; - } - if( count ) - { - deviceInfo->maxOutputChannels = count; - winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; - } + case DSSPEAKER_5POINT1_SURROUND: count = 6; break; } - } - -#ifdef PAWIN_USE_WDMKS_DEVICE_INFO - if( pnpInterface ) - { - int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0 ); - if( count > 0 ) + if( count ) { deviceInfo->maxOutputChannels = count; winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; } } + } + +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO + if( pnpInterface ) + { + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 0 ); + if( count > 0 ) + { + deviceInfo->maxOutputChannels = count; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; + } + } #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ - /* initialize defaultSampleRate */ + /* initialize defaultSampleRate */ - if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) - { - /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */ - deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) + { + /* initialize to caps.dwMaxSecondarySampleRate incase none of the standard rates match */ + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; - for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) - { - if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate - && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ) - { - deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; - break; - } - } - } - else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) { - if( caps.dwMinSecondarySampleRate == 0 ) - { - /* - ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! - ** But it supports continuous sampling. - ** So fake range of rates, and hope it really supports it. - */ - deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ - - DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name )); - } - else + if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate + && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ) { - deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; + break; } } - else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + } + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) { - /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. - ** But we know that they really support a range of rates! - ** So when we see a ridiculous set of rates, assume it is a range. + /* + ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + ** But it supports continuous sampling. + ** So fake range of rates, and hope it really supports it. */ - deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ - DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name )); + deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%s\n", name )); } - else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + else + { + deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + ** But we know that they really support a range of rates! + ** So when we see a ridiculous set of rates, assume it is a range. + */ + deviceInfo->defaultSampleRate = 48000.0f; /* assume 48000 as the default */ + DBUG(("PA - Sample rate range used instead of two odd values for device #%s\n", name )); + } + else deviceInfo->defaultSampleRate = caps.dwMaxSecondarySampleRate; - //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate ); - // dwFlags | DSCAPS_CONTINUOUSRATE + //printf( "min %d max %d\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate ); + // dwFlags | DSCAPS_CONTINUOUSRATE - deviceInfo->defaultLowInputLatency = 0.; - deviceInfo->defaultHighInputLatency = 0.; + deviceInfo->defaultLowInputLatency = 0.; + deviceInfo->defaultHighInputLatency = 0.; - deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); - deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2; - } + deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); + deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2; } IDirectSound_Release( lpDirectSound ); } - if( deviceOK ) + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) { - deviceInfo->name = name; - - if( lpGUID == NULL ) - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; - - hostApi->info.deviceCount++; + winDsDeviceInfo->lpGUID = NULL; + } + else + { + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; } + deviceInfo->name = name; + + return result; + +error: + if( lpDirectSound ) + IDirectSound_Release( lpDirectSound ); + return result; } @@ -1016,27 +1012,14 @@ static PaError AddOutputDeviceInfoFromDirectSound( ** The device will not be added to the device list if any errors are encountered. */ static PaError AddInputDeviceInfoFromDirectSoundCapture( - PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface ) + PaWinDsDeviceInfo *winDsDeviceInfo, char *name, LPGUID lpGUID, char *pnpInterface ) { - PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; - PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount]; PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo; HRESULT hr; - LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture = NULL; DSCCAPS caps; - int deviceOK = TRUE; PaError result = paNoError; - /* Copy GUID to the device info structure. Set pointer. */ - if( lpGUID == NULL ) - { - winDsDeviceInfo->lpGUID = NULL; - } - else - { - winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; - memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); - } hr = paWinDsDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); @@ -1053,7 +1036,8 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture( if( hr != DS_OK ) { DBUG(("Cannot create Capture for %s. Result = 0x%x\n", name, hr )); - deviceOK = FALSE; + result = paUnanticipatedHostError; + goto error; } else { @@ -1064,7 +1048,8 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture( if( hr != DS_OK ) { DBUG(("Cannot GetCaps() for Capture device %s. Result = 0x%x\n", name, hr )); - deviceOK = FALSE; + result = paUnanticipatedHostError; + goto error; } else { @@ -1072,45 +1057,44 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture( if( caps.dwFlags & DSCAPS_EMULDRIVER ) { /* If WMME supported, then reject Emulated drivers because they are lousy. */ - deviceOK = FALSE; + result = paInvalidDevice; + goto error; } #endif - if( deviceOK ) - { - deviceInfo->maxInputChannels = caps.dwChannels; - winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; + deviceInfo->maxInputChannels = caps.dwChannels; + winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; - deviceInfo->maxOutputChannels = 0; - winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; + deviceInfo->maxOutputChannels = 0; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 1; #ifdef PAWIN_USE_WDMKS_DEVICE_INFO - if( pnpInterface ) + if( pnpInterface ) + { + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1 ); + if( count > 0 ) { - int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( pnpInterface, /* isInput= */ 1 ); - if( count > 0 ) - { - deviceInfo->maxInputChannels = count; - winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; - } + deviceInfo->maxInputChannels = count; + winDsDeviceInfo->deviceInputChannelCountIsKnown = 1; } + } #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ /* constants from a WINE patch by Francois Gouget, see: - http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html +http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html - --- - Date: Fri, 14 May 2004 10:38:12 +0200 (CEST) - From: Francois Gouget - To: Ross Bencina - Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library +--- +Date: Fri, 14 May 2004 10:38:12 +0200 (CEST) +From: Francois Gouget +To: Ross Bencina +Subject: Re: Permission to use wine 48/96 wave patch in BSD licensed library - [snip] +[snip] - I give you permission to use the patch below under the BSD license. - http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html +I give you permission to use the patch below under the BSD license. +http://www.winehq.com/hypermail/wine-patches/2003/01/0290.html - [snip] +[snip] */ #ifndef WAVE_FORMAT_48M08 #define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ @@ -1123,59 +1107,67 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture( #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif - /* defaultSampleRate */ - if( caps.dwChannels == 2 ) - { - if( caps.dwFormats & WAVE_FORMAT_4S16 ) - deviceInfo->defaultSampleRate = 44100.0; - else if( caps.dwFormats & WAVE_FORMAT_48S16 ) - deviceInfo->defaultSampleRate = 48000.0; - else if( caps.dwFormats & WAVE_FORMAT_2S16 ) - deviceInfo->defaultSampleRate = 22050.0; - else if( caps.dwFormats & WAVE_FORMAT_1S16 ) - deviceInfo->defaultSampleRate = 11025.0; - else if( caps.dwFormats & WAVE_FORMAT_96S16 ) - deviceInfo->defaultSampleRate = 96000.0; - else - deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ - } - else if( caps.dwChannels == 1 ) - { - if( caps.dwFormats & WAVE_FORMAT_4M16 ) - deviceInfo->defaultSampleRate = 44100.0; - else if( caps.dwFormats & WAVE_FORMAT_48M16 ) - deviceInfo->defaultSampleRate = 48000.0; - else if( caps.dwFormats & WAVE_FORMAT_2M16 ) - deviceInfo->defaultSampleRate = 22050.0; - else if( caps.dwFormats & WAVE_FORMAT_1M16 ) - deviceInfo->defaultSampleRate = 11025.0; - else if( caps.dwFormats & WAVE_FORMAT_96M16 ) - deviceInfo->defaultSampleRate = 96000.0; - else - deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ - } - else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ + /* defaultSampleRate */ + if( caps.dwChannels == 2 ) + { + if( caps.dwFormats & WAVE_FORMAT_4S16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48S16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2S16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1S16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96S16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ + } + else if( caps.dwChannels == 1 ) + { + if( caps.dwFormats & WAVE_FORMAT_4M16 ) + deviceInfo->defaultSampleRate = 44100.0; + else if( caps.dwFormats & WAVE_FORMAT_48M16 ) + deviceInfo->defaultSampleRate = 48000.0; + else if( caps.dwFormats & WAVE_FORMAT_2M16 ) + deviceInfo->defaultSampleRate = 22050.0; + else if( caps.dwFormats & WAVE_FORMAT_1M16 ) + deviceInfo->defaultSampleRate = 11025.0; + else if( caps.dwFormats & WAVE_FORMAT_96M16 ) + deviceInfo->defaultSampleRate = 96000.0; + else + deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ + } + else deviceInfo->defaultSampleRate = 48000.0; /* assume 48000 as the default */ - deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); - deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2; + deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); + deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2; - deviceInfo->defaultLowOutputLatency = 0.; - deviceInfo->defaultHighOutputLatency = 0.; - } + deviceInfo->defaultLowOutputLatency = 0.; + deviceInfo->defaultHighOutputLatency = 0.; } IDirectSoundCapture_Release( lpDirectSoundCapture ); } - if( deviceOK ) + /* Copy GUID to the device info structure. Set pointer. */ + if( lpGUID == NULL ) { - deviceInfo->name = name; + winDsDeviceInfo->lpGUID = NULL; + } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + } - if( lpGUID == NULL ) - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + deviceInfo->name = name; + + return result; - hostApi->info.deviceCount++; - } +error: + if( lpDirectSoundCapture ) + IDirectSoundCapture_Release( lpDirectSoundCapture ); return result; } @@ -1185,18 +1177,12 @@ static PaError AddInputDeviceInfoFromDirectSoundCapture( PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) { PaError result = paNoError; - int i, deviceCount; PaWinDsHostApiRepresentation *winDsHostApi; - DSDeviceNamesAndGUIDs deviceNamesAndGUIDs; - PaWinDsDeviceInfo *deviceInfoArray; + int deviceCount; + void *scanResults = 0; PaWinDs_InitializeDSoundEntryPoints(); - /* initialise guid vectors so they can be safely deleted on error */ - deviceNamesAndGUIDs.winDsHostApi = NULL; - deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL; - deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL; - winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); if( !winDsHostApi ) { @@ -1224,109 +1210,25 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde (*hostApi)->info.type = paDirectSound; (*hostApi)->info.name = "Windows DirectSound"; + /* these are all updated by CommitDeviceInfos() */ (*hostApi)->info.deviceCount = 0; (*hostApi)->info.defaultInputDevice = paNoDevice; (*hostApi)->info.defaultOutputDevice = paNoDevice; + (*hostApi)->deviceInfos = 0; - -/* DSound - enumerate devices to count them and to gather their GUIDs */ - - result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations ); - if( result != paNoError ) - goto error; - - result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations ); - if( result != paNoError ) - goto error; - - paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs ); - - paWinDsDSoundEntryPoints.DirectSoundEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs ); - - if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError ) - { - result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError; - goto error; - } - - if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError ) - { - result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError; - goto error; - } - - deviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + deviceNamesAndGUIDs.outputNamesAndGUIDs.count; - -#ifdef PAWIN_USE_WDMKS_DEVICE_INFO - if( deviceCount > 0 ) - { - deviceNamesAndGUIDs.winDsHostApi = winDsHostApi; - FindDevicePnpInterfaces( &deviceNamesAndGUIDs ); - } -#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ - - if( deviceCount > 0 ) - { - /* allocate array for pointers to PaDeviceInfo structs */ - (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( - winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); - if( !(*hostApi)->deviceInfos ) - { - result = paInsufficientMemory; - goto error; - } - - /* allocate all PaDeviceInfo structs in a contiguous block */ - deviceInfoArray = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( - winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount ); - if( !deviceInfoArray ) - { - result = paInsufficientMemory; - goto error; - } - - for( i=0; i < deviceCount; ++i ) - { - PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - deviceInfo->name = 0; - (*hostApi)->deviceInfos[i] = deviceInfo; - } - - for( i=0; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count; ++i ) - { - result = AddInputDeviceInfoFromDirectSoundCapture( winDsHostApi, - deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name, - deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, - deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface ); - if( result != paNoError ) - goto error; - } - - for( i=0; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count; ++i ) - { - result = AddOutputDeviceInfoFromDirectSound( winDsHostApi, - deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name, - deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, - deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface ); - if( result != paNoError ) - goto error; - } - } - - result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); + result = ScanDeviceInfos( &winDsHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount ); if( result != paNoError ) goto error; - result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - if( result != paNoError ) - goto error; + /* FIXME for now we ignore the result of CommitDeviceInfos(), it should probably be an atomic non-failing operation */ + CommitDeviceInfos( &winDsHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount ); - (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; + (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; + (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, @@ -1342,9 +1244,6 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde return result; error: - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi ); return result; @@ -1489,6 +1388,232 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, return paFormatIsSupported; } +/***********************************************************************************/ +static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + PaWinDsDeviceInfo *deviceInfoArray; + PaError result = paNoError; + PaWinDsScanDeviceInfosResults *outArgument = 0; + DSDeviceNamesAndGUIDs deviceNamesAndGUIDs; + int i = 0; + int maximumNewDeviceCount = 0; + + /* Check preconditions */ + if( scanResults == NULL || newDeviceCount == NULL ) + return paInternalError; + + /* initialize the out params */ + *scanResults = NULL; + *newDeviceCount = 0; + + /* initialise guid vectors so they can be safely deleted on error */ + deviceNamesAndGUIDs.winDsHostApi = NULL; + deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL; + deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL; + + result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + deviceNamesAndGUIDs.winDsHostApi = winDsHostApi; + + /* DSound - enumerate devices to count them and to gather their GUIDs and device names */ + + paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs ); + + if( deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError != paNoError ) + { + result = deviceNamesAndGUIDs.inputNamesAndGUIDs.enumerationError; + goto error; + } + + paWinDsDSoundEntryPoints.DirectSoundEnumerateW( (LPDSENUMCALLBACKW)CollectGUIDsProcW, (void *)&deviceNamesAndGUIDs.outputNamesAndGUIDs ); + + if( deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError != paNoError ) + { + result = deviceNamesAndGUIDs.outputNamesAndGUIDs.enumerationError; + goto error; + } + + maximumNewDeviceCount = deviceNamesAndGUIDs.inputNamesAndGUIDs.count + + deviceNamesAndGUIDs.outputNamesAndGUIDs.count; + +#ifdef PAWIN_USE_WDMKS_DEVICE_INFO + if( maximumNewDeviceCount > 0 ) + { + FindDevicePnpInterfaces( &deviceNamesAndGUIDs ); + } +#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ + + if( maximumNewDeviceCount > 0 ) + { + /* Allocate the out param for all the info we need */ + outArgument = (PaWinDsScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsScanDeviceInfosResults) ); + if( !outArgument ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate array for pointers to PaDeviceInfo structs */ + outArgument->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo*) * maximumNewDeviceCount ); + if( !outArgument->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all PaDeviceInfo structs in a contiguous block */ + deviceInfoArray = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * maximumNewDeviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i = 0 ; i < maximumNewDeviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + + outArgument->deviceInfos[ i ] = deviceInfo; + } + + for( i = 0 ; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count ; ++i ) + { + PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*)outArgument->deviceInfos[*newDeviceCount]; + + result = AddInputDeviceInfoFromDirectSoundCapture( winDsDeviceInfo, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface ); + if( result == paNoError ) + { + if( deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID == NULL ) + outArgument->defaultInputDevice = *newDeviceCount; + (*newDeviceCount)++; + } + /* ignore error results here and just skip the device */ + } + + for( i = 0 ; i < deviceNamesAndGUIDs.outputNamesAndGUIDs.count ; ++i ) + { + PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*)outArgument->deviceInfos[*newDeviceCount]; + + result = AddOutputDeviceInfoFromDirectSound( winDsDeviceInfo, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface ); + if( result == paNoError ) + { + if( deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID == NULL ) + outArgument->defaultOutputDevice = *newDeviceCount; + (*newDeviceCount)++; + } + /* ignore error results here and just skip the device */ + } + } + + result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); + if( result != paNoError ) + goto error; + + *scanResults = outArgument; + return result; + +error: + TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); + TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); + + if( outArgument ) + { + if( outArgument->deviceInfos ) + { + if( outArgument->deviceInfos[0] ) + { + PaUtil_GroupFreeMemory( winDsHostApi->allocations, outArgument->deviceInfos[0] ); + } + + PaUtil_GroupFreeMemory( winDsHostApi->allocations, outArgument->deviceInfos ); + } + } + return result; +} + +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + PaError result = paNoError; + int i = 0; + + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + /* Free any old memory which might be in the device info */ + if( hostApi->deviceInfos ) + { + PaUtil_GroupFreeMemory( winDsHostApi->allocations, hostApi->deviceInfos[0] ); + PaUtil_GroupFreeMemory( winDsHostApi->allocations, hostApi->deviceInfos ); + hostApi->deviceInfos = NULL; + } + + if( scanResults != NULL ) + { + PaWinDsScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinDsScanDeviceInfosResults * ) scanResults; + + if( deviceCount > 0 ) + { + /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ + hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; + + hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; + hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; + + hostApi->info.deviceCount = deviceCount; + } + + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); + } + + return result; +} + +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + if( scanResults != NULL ) + { + PaWinDsScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinDsScanDeviceInfosResults * ) scanResults; + + if( scanDeviceInfosResults->deviceInfos ) + { + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */ + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos ); + } + + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); + } + + return paNoError; +} + +/***********************************************************************************/ #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, diff --git a/src/hostapi/jack/pa_jack.c b/src/hostapi/jack/pa_jack.c index a800f8e..ab6af78 100644 --- a/src/hostapi/jack/pa_jack.c +++ b/src/hostapi/jack/pa_jack.c @@ -756,6 +756,9 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface, CloseStream, StartStream, diff --git a/src/hostapi/oss/pa_unix_oss.c b/src/hostapi/oss/pa_unix_oss.c index 51e9630..b0ce4c3 100644 --- a/src/hostapi/oss/pa_unix_oss.c +++ b/src/hostapi/oss/pa_unix_oss.c @@ -256,6 +256,9 @@ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PA_ENSURE( BuildDeviceList( ossHostApi ) ); diff --git a/src/hostapi/skeleton/pa_hostapi_skeleton.c b/src/hostapi/skeleton/pa_hostapi_skeleton.c index 6edc22c..dfb3aa9 100644 --- a/src/hostapi/skeleton/pa_hostapi_skeleton.c +++ b/src/hostapi/skeleton/pa_hostapi_skeleton.c @@ -209,6 +209,9 @@ PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiI (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 6e62a4a..67618c8 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -1516,6 +1516,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, diff --git a/src/hostapi/wdmks/pa_win_wdmks.c b/src/hostapi/wdmks/pa_win_wdmks.c index 129b9e2..608c1c3 100644 --- a/src/hostapi/wdmks/pa_win_wdmks.c +++ b/src/hostapi/wdmks/pa_win_wdmks.c @@ -3853,11 +3853,10 @@ PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; - /* In preparation for hotplug (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; - */ + PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, GetStreamTime, GetStreamCpuLoad, diff --git a/src/hostapi/wmme/pa_win_wmme.c b/src/hostapi/wmme/pa_win_wmme.c index 422c867..4f4c872 100644 --- a/src/hostapi/wmme/pa_win_wmme.c +++ b/src/hostapi/wmme/pa_win_wmme.c @@ -1155,6 +1155,9 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = 0; + (*hostApi)->CommitDeviceInfos = 0; + (*hostApi)->DisposeDeviceInfos = 0; PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, diff --git a/src/os/win/pa_win_hotplug.c b/src/os/win/pa_win_hotplug.c new file mode 100644 index 0000000..9257c0c --- /dev/null +++ b/src/os/win/pa_win_hotplug.c @@ -0,0 +1,315 @@ + +#include "pa_util.h" +#include "pa_debugprint.h" +#include "pa_allocation.h" + +#include "pa_win_wdmks_utils.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment( lib, "setupapi.lib" ) +#endif + + +/* Implemented in pa_front.c + @param first 0 = unknown, 1 = insertion, 2 = removal + @param second Host specific device change info (in windows it is the (unicode) device path) +*/ +extern void PaUtil_DevicesChanged(unsigned, void*); + +/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */ +#if !defined(__CYGWIN__) && !defined(_WIN32_WCE) +#define CREATE_THREAD_FUNCTION (HANDLE)_beginthreadex +#define PA_THREAD_FUNC static unsigned WINAPI +#else +#define CREATE_THREAD_FUNCTION CreateThread +#define PA_THREAD_FUNC static DWORD WINAPI +#endif + +typedef struct PaHotPlugDeviceInfo +{ + wchar_t name[MAX_PATH]; + struct PaHotPlugDeviceInfo* next; +} PaHotPlugDeviceInfo; + +typedef struct PaHotPlugDeviceEventHandlerInfo +{ + HANDLE hWnd; + HANDLE hMsgThread; + HANDLE hNotify; + CRITICAL_SECTION lock; + PaUtilAllocationGroup* cacheAllocGroup; + PaHotPlugDeviceInfo* cache; + +} PaHotPlugDeviceEventHandlerInfo; + + +static BOOL RemoveDeviceFromCache(PaHotPlugDeviceEventHandlerInfo* pInfo, const wchar_t* name) +{ + if (pInfo->cache != NULL) + { + PaHotPlugDeviceInfo* lastEntry = 0; + PaHotPlugDeviceInfo* entry = pInfo->cache; + while (entry != NULL) + { + if (_wcsicmp(entry->name, name) == 0) + { + if (lastEntry) + { + lastEntry->next = entry->next; + } + else + { + pInfo->cache = NULL; + } + PaUtil_GroupFreeMemory(pInfo->cacheAllocGroup, entry); + return TRUE; + } + + lastEntry = entry; + entry = entry->next; + } + } + return FALSE; +} + +static void InsertDeviceIntoCache(PaHotPlugDeviceEventHandlerInfo* pInfo, const wchar_t* name) +{ + PaHotPlugDeviceInfo** ppEntry = NULL; + + /* Remove it first (if possible) so we don't accidentally get duplicates */ + RemoveDeviceFromCache(pInfo, name); + + if (pInfo->cache == NULL) + { + ppEntry = &pInfo->cache; + } + else + { + PaHotPlugDeviceInfo* entry = pInfo->cache; + while (entry->next != NULL) + { + entry = entry->next; + } + ppEntry = &entry->next; + } + + *ppEntry = (PaHotPlugDeviceInfo*)PaUtil_GroupAllocateMemory(pInfo->cacheAllocGroup, sizeof(PaHotPlugDeviceInfo)); + wcsncpy((*ppEntry)->name, name, MAX_PATH-1); + (*ppEntry)->next = NULL; +} + +static BOOL IsDeviceAudio(const wchar_t* deviceName) +{ + int channelCnt = 0; + channelCnt += PaWin_WDMKS_QueryFilterMaximumChannelCount((void*)deviceName, 1); + channelCnt += PaWin_WDMKS_QueryFilterMaximumChannelCount((void*)deviceName, 0); + return (channelCnt > 0); +} + +static void PopulateCacheWithAvailableAudioDevices(PaHotPlugDeviceEventHandlerInfo* pInfo) +{ + HDEVINFO handle = NULL; + const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + (MAX_PATH * sizeof(WCHAR)); + SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)PaUtil_AllocateMemory(sizeInterface); + + if (devInterfaceDetails) + { + devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); + + /* Open a handle to search for devices (filters) */ + handle = SetupDiGetClassDevsW(&KSCATEGORY_AUDIO,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if( handle != NULL ) + { + int device; + + /* Iterate through the devices */ + for( device = 0;;device++ ) + { + SP_DEVICE_INTERFACE_DATA interfaceData; + SP_DEVINFO_DATA devInfoData; + int noError; + + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + devInfoData.Reserved = 0; + + noError = SetupDiEnumDeviceInterfaces(handle,NULL,&KSCATEGORY_AUDIO,device,&interfaceData); + if( !noError ) + break; /* No more devices */ + + noError = SetupDiGetDeviceInterfaceDetailW(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData); + if( noError ) + { + if (IsDeviceAudio(devInterfaceDetails->DevicePath)) + { + PA_DEBUG(("Hotplug cache populated with: '%S'\n", devInterfaceDetails->DevicePath)); + InsertDeviceIntoCache(pInfo, devInterfaceDetails->DevicePath); + } + } + } + SetupDiDestroyDeviceInfoList(handle); + } + PaUtil_FreeMemory(devInterfaceDetails); + } +} + +static LRESULT CALLBACK PaMsgWinProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PaHotPlugDeviceEventHandlerInfo* pInfo = (PaHotPlugDeviceEventHandlerInfo*)( GetWindowLongPtr(hWnd, GWLP_USERDATA) ); + switch(msg) + { + case WM_DEVICECHANGE: + switch(wParam) + { + case DBT_DEVICEARRIVAL: + { + PDEV_BROADCAST_DEVICEINTERFACE_W ptr = (PDEV_BROADCAST_DEVICEINTERFACE_W)lParam; + if (ptr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) + break; + + if (!IsEqualGUID(&ptr->dbcc_classguid, &KSCATEGORY_AUDIO)) + break; + + if (IsDeviceAudio(ptr->dbcc_name)) + { + PA_DEBUG(("Device inserted : %S\n", ptr->dbcc_name)); + InsertDeviceIntoCache(pInfo, ptr->dbcc_name); + PaUtil_DevicesChanged(1, ptr->dbcc_name); + } + } + break; + case DBT_DEVICEREMOVECOMPLETE: + { + PDEV_BROADCAST_DEVICEINTERFACE_W ptr = (PDEV_BROADCAST_DEVICEINTERFACE_W)lParam; + if (ptr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) + break; + + if (!IsEqualGUID(&ptr->dbcc_classguid, &KSCATEGORY_AUDIO)) + break; + + if (RemoveDeviceFromCache(pInfo, ptr->dbcc_name)) + { + PA_DEBUG(("Device removed : %S\n", ptr->dbcc_name)); + PaUtil_DevicesChanged(2, ptr->dbcc_name); + } + } + break; + default: + break; + } + break; + } + return DefWindowProcW(hWnd, msg, wParam, lParam); +} + +PA_THREAD_FUNC PaRunMessageLoop(void* ptr) +{ + PaHotPlugDeviceEventHandlerInfo* pInfo = (PaHotPlugDeviceEventHandlerInfo*)ptr; + WNDCLASSW wnd = { 0 }; + HMODULE hInstance = GetModuleHandleW(NULL); + + wnd.lpfnWndProc = PaMsgWinProcW; + wnd.hInstance = hInstance; + wnd.lpszClassName = L"{1E0D4F5A-B31F-4dcc-AE3C-4F30A47BD521}"; /* Using a GUID as class name */ + pInfo->hWnd = CreateWindowW((LPCWSTR)MAKEINTATOM(RegisterClassW(&wnd)), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL); + if (pInfo->hWnd) + { + DEV_BROADCAST_DEVICEINTERFACE_W NotificationFilter = { sizeof(DEV_BROADCAST_DEVICEINTERFACE_W) }; + NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + +#ifndef DEVICE_NOTIFY_ALL_INTERFACE_CLASSES +#define DEVICE_NOTIFY_ALL_INTERFACE_CLASSES 0x00000004 +#endif + + pInfo->hNotify = RegisterDeviceNotificationW( + pInfo->hWnd, + &NotificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE|DEVICE_NOTIFY_ALL_INTERFACE_CLASSES + ); + + assert(pInfo->hNotify); + + SetWindowLongPtr(pInfo->hWnd, GWLP_USERDATA, (LONG_PTR)pInfo); + + if (pInfo->hNotify) + { + MSG msg; + BOOL result; + while((result = GetMessageW(&msg, pInfo->hWnd, 0, 0)) != 0) + { + if (result == -1) + { + break; + } + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + UnregisterDeviceNotification(pInfo->hNotify); + pInfo->hNotify = 0; + } + DestroyWindow(pInfo->hWnd); + pInfo->hWnd = 0; + } + return 0; +} + +static PaHotPlugDeviceEventHandlerInfo* s_handler = 0; + +void PaUtil_InitializeHotPlug() +{ + if (s_handler == 0) + { + s_handler = (PaHotPlugDeviceEventHandlerInfo*)PaUtil_AllocateMemory(sizeof(PaHotPlugDeviceEventHandlerInfo)); + if (s_handler) + { + s_handler->cacheAllocGroup = PaUtil_CreateAllocationGroup(); + InitializeCriticalSection(&s_handler->lock); + PopulateCacheWithAvailableAudioDevices(s_handler); + /* Start message thread */ + s_handler->hMsgThread = CREATE_THREAD_FUNCTION(NULL, 0, PaRunMessageLoop, s_handler, 0, NULL); + assert(s_handler->hMsgThread != 0); + } + } +} + +void PaUtil_TerminateHotPlug() +{ + if (s_handler != 0) + { + if (s_handler->hWnd) + { + PostMessage(s_handler->hWnd, WM_QUIT, 0, 0); + if (WaitForSingleObject(s_handler->hMsgThread, 1000) == WAIT_TIMEOUT) + { + TerminateThread(s_handler->hMsgThread, -1); + } + } + DeleteCriticalSection(&s_handler->lock); + PaUtil_FreeAllAllocations( s_handler->cacheAllocGroup ); + PaUtil_DestroyAllocationGroup( s_handler->cacheAllocGroup ); + PaUtil_FreeMemory( s_handler ); + s_handler = 0; + } +} + +void PaUtil_LockHotPlug() +{ + EnterCriticalSection(&s_handler->lock); +} + +void PaUtil_UnlockHotPlug() +{ + LeaveCriticalSection(&s_handler->lock); +} diff --git a/test/patest_update_available_device_list.c b/test/patest_update_available_device_list.c new file mode 100644 index 0000000..2c077f5 --- /dev/null +++ b/test/patest_update_available_device_list.c @@ -0,0 +1,48 @@ +#include +#include + +#include "portaudio.h" + +void printDevices() +{ + int deviceCount = Pa_GetDeviceCount(); + int i; + + for( i=0; i < deviceCount; ++i ){ + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i); + const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo( deviceInfo->hostApi ); + + assert( deviceInfo != 0 ); + + printf( "%d %s (%s)\n", i, deviceInfo->name, hostApiInfo->name ); + } +} + +static void devicesChangedCallback(void* p) +{ + (void)p; + + printf( "Portaudio device list have changed!\n" ); +} + +int main(int argc, char* argv[]) +{ + Pa_Initialize(); + + Pa_SetDevicesChangedCallback(NULL, devicesChangedCallback); + + for(;;){ + printDevices(); + + printf( "press [enter] to update the device list. or q + [enter] to quit.\n" ); + if( getchar() == 'q' ) + break; + + Pa_UpdateAvailableDeviceList(); + } + + Pa_Terminate(); + + return 0; +} +