]> Repos - portaudio/commitdiff
Merge of old SVN hotplug branch into current git HEAD. Includes hotplug support for...
authorRoss Bencina <rossb@audiomulch.com>
Sat, 27 Aug 2016 07:51:40 +0000 (17:51 +1000)
committerRoss Bencina <rossb@audiomulch.com>
Sat, 27 Aug 2016 07:51:40 +0000 (17:51 +1000)
18 files changed:
build/msvc/portaudio.def
build/msvc/portaudio.vcproj
include/portaudio.h
src/common/pa_front.c
src/common/pa_hostapi.h
src/hostapi/alsa/pa_linux_alsa.c
src/hostapi/asihpi/pa_linux_asihpi.c
src/hostapi/asio/pa_asio.cpp
src/hostapi/coreaudio/pa_mac_core.c
src/hostapi/dsound/pa_win_ds.c
src/hostapi/jack/pa_jack.c
src/hostapi/oss/pa_unix_oss.c
src/hostapi/skeleton/pa_hostapi_skeleton.c
src/hostapi/wasapi/pa_win_wasapi.c
src/hostapi/wdmks/pa_win_wdmks.c
src/hostapi/wmme/pa_win_wmme.c
src/os/win/pa_win_hotplug.c [new file with mode: 0644]
test/patest_update_available_device_list.c [new file with mode: 0644]

index bdaa8eef1c270648fb577337eabb0167a373e82b..2534daf99457d00d00bd7037467d60a78f3eabb2 100644 (file)
@@ -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
index e5a648bd14a121f43be1cdbabb1f5a09643692ad..e9f30785db379698aad7a09bf2f34a0ee20054db 100644 (file)
                                                RelativePath="..\..\src\os\win\pa_win_wdmks_utils.c"
                                                >
                                        </File>
+                                       <File
+                                               RelativePath="..\..\src\os\win\pa_win_hotplug.c"
+                                               >
+                                       </File>
                                        <File
                                                RelativePath="..\..\src\os\win\pa_x86_plain_converters.c"
                                                >
index 3bb3b3197a77efe9a8b03092f13c31bd4a18ba9c..09d6aa8d5c9ccbce01fb0fb85210ae93ce4d06ed 100644 (file)
@@ -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
 }
index 06327109efc4435e31c8f6b3d220da9115870101..f530bd975ee9e7f68bf20f8af658b49a2add6e18 100644 (file)
 #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();
+}
+
index 54b527ea819f054bb48d10801c778878a67733d2..79e3e88161bcc4f3a5c7d161201cfe3609287dd7 100644 (file)
@@ -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;
 
 
index f286591542a5be54e6d9393554e8ead4a88a8931..dbe2d0445e7f71412430395b37df80992f24ea9a 100644 (file)
@@ -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.
index f5a529028480657c473c7b9f4ae0c5f0ce091738..ab76d3c5aaa2428f39381726b1219db3642a589f 100644 (file)
@@ -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,
index f230d8788d404c395497093d8b9e2503b906a75d..47c5761fea9016bbd1204c81ec31e5ec66e45671 100644 (file)
@@ -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,
index 4b7b43c1c89333d652a758292f1d449e337d4183..4e352661932b0c36dfcd773788de9e170d0769a0 100644 (file)
@@ -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,
index 35fac5f7f03c2579710d983fbd8ad3fdfbd3ecda..0a40797e6bfe889f09742e96148be2c66fd59f27 100644 (file)
@@ -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 <fgouget@ ... .fr>
-    To: Ross Bencina <rbencina@ ... .au>
-    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 <fgouget@ ... .fr>
+To: Ross Bencina <rbencina@ ... .au>
+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,
index a800f8e1dd9155879caf592f09494e91318b20ad..ab6af78dfb8459f8e7b60e332ea707d59c526034 100644 (file)
@@ -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,
index 51e963048fa66d1e7748c4e63928fe1dd6d3db66..b0ce4c32bee1e119ef04c8a4146a96269285d2f3 100644 (file)
@@ -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 ) );
 
index 6edc22cf700ba3062b28afb51bde5b0ffd4378d9..dfb3aa978d72ed0f294b612987bdfafaa2401109 100644 (file)
@@ -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,
index 6e62a4a88bd6a7e360c8315b85ce6089118378df..67618c893038c828d2f29d55659a74ec1fbb5498 100644 (file)
@@ -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,
index 129b9e2e398d5e1856160f90dd30c6cfde51edac..608c1c3e10f31d9ab14261e2cfd016ade0adb9aa 100644 (file)
@@ -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,
index 422c86714a743c7bae163c0e887c8626b47e53a7..4f4c872038107b6c5c499a3810df7555db4c4a9b 100644 (file)
@@ -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 (file)
index 0000000..9257c0c
--- /dev/null
@@ -0,0 +1,315 @@
+
+#include "pa_util.h"
+#include "pa_debugprint.h"
+#include "pa_allocation.h"
+
+#include "pa_win_wdmks_utils.h"
+
+#include <windows.h>
+#include <dbt.h>
+#include <process.h>
+#include <assert.h>
+#include <ks.h>
+#include <ksmedia.h>
+
+#include <setupapi.h>
+
+#include <stdio.h>
+
+#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 (file)
index 0000000..2c077f5
--- /dev/null
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <assert.h>
+
+#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;
+}
+