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,153 +845,160 @@ 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; + deviceInfo->maxOutputChannels = 2; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 0; + } + else + { + deviceInfo->maxOutputChannels = 1; + winDsDeviceInfo->deviceOutputChannelCountIsKnown = 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; - } - - /* 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 ) + for( i = 0; i < PA_DEFAULTSAMPLERATESEARCHORDER_COUNT_; ++i ) + { + if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate + && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ) { - if( defaultSampleRateSearchOrder_[i] >= caps.dwMinSecondarySampleRate - && defaultSampleRateSearchOrder_[i] <= caps.dwMaxSecondarySampleRate ) - { - deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; - break; - } + deviceInfo->defaultSampleRate = defaultSampleRateSearchOrder_[i]; + break; } } - else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) - { - 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 - { - 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 - - deviceInfo->defaultLowInputLatency = 0.; - deviceInfo->defaultHighInputLatency = 0.; - - deviceInfo->defaultLowOutputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); - deviceInfo->defaultHighOutputLatency = deviceInfo->defaultLowOutputLatency * 2; } + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + 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 + { + 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 + + deviceInfo->defaultLowInputLatency = 0.; + deviceInfo->defaultHighInputLatency = 0.; + + 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 */ - - deviceInfo->defaultLowInputLatency = PaWinDs_GetMinLatencySeconds( deviceInfo->defaultSampleRate ); - deviceInfo->defaultHighInputLatency = deviceInfo->defaultLowInputLatency * 2; - - deviceInfo->defaultLowOutputLatency = 0.; - deviceInfo->defaultHighOutputLatency = 0.; + /* 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->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; - - if( lpGUID == NULL ) - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; - - hostApi->info.deviceCount++; + winDsDeviceInfo->lpGUID = NULL; } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; + memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); + } + + deviceInfo->name = name; + + return result; + +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 ); + result = ScanDeviceInfos( &winDsHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount ); if( result != paNoError ) goto error; - result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations ); - 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 ); - 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 ); - if( result != paNoError ) - goto error; - - result = TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - if( result != paNoError ) - goto error; - - (*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; +} +