diff --git a/build/msvc/portaudio.def b/build/msvc/portaudio.def index bdaa8ee..7dcbd23 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_RefreshDeviceList @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..ebff7ad 100644 --- a/include/portaudio.h +++ b/include/portaudio.h @@ -155,9 +155,15 @@ 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 + when Pa_Initialize() is called. The device list is not automatically updated + when hardware devices are connected or disconnected. To refresh the list of devices, + either call Pa_RefreshDeviceList() or 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. @@ -432,6 +438,69 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void ); PaDeviceIndex Pa_GetDefaultOutputDevice( void ); +/** Refresh the list devices to match currently available native audio devices. + + PortAudio's list of devices is usually frozen when Pa_Initialize() is + called. Call Pa_RefreshDeviceList() to refresh PortAudio's device list + at any time while PortAudio is initialized. + + @return an error code indicating whether the refresh was successful. + + If the function succeeds, future calls to Pa_GetDeviceCount() may return + a different count than before, all device indexes may refer to + different devices, and any previously returned PaDeviceInfo pointers are + invalidated. + + If the function fails, the device list is unchanged. + + @note Open streams will not be affected by calls to this function. + */ +PaError Pa_RefreshDeviceList( void ); + + +/** Functions of type PaDevicesChangedCallback are implemented by PortAudio + clients. They can be registered with PortAudio using the Pa_SetDevicesChangedCallback + function. Once registered they are called when available native audio devices + may have changed. For example, when a USB audio device is connected or disconnected. + + When the callback is invoked, there is no change to the devices listed by + PortAudio. To update PortAudio's view of the device list, the client + must call Pa_RefreshDeviceList(). This must not be done from within + the callback. + + The callback may occur on any thread. The client should not call other PortAudio + functions from within a PaDevicesChangedCallback. In particular, the client + should not directly call Pa_RefreshDeviceList() from within the notification + callback. Instead, the client should set an atomic flag, or queue a message to + notify a controller thread that would then call Pa_RefreshDeviceList(). + + @param userData The userData parameter supplied to Pa_SetDevicesChangedCallback() + + @see Pa_SetDevicesChangedCallback +*/ +typedef void PaDevicesChangedCallback( void *userData ); + + +/** Register a callback function that will be called when native audio devices + become available or unavailable. See the description of PaDevicesChangedCallback + for further details about when the callback will be called. + + @param userData A client supplied pointer that is passed to the devices changed + callback. + + @param devicesChangedCallback a pointer to a function with the same signature + as PaDevicesChangedCallback, which will be called when native devices become + available or unavailable. Passing NULL for this parameter will un-register a + previously registered devices changed callback function. + + @return on success returns paNoError, otherwise an error code indicating the + cause of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetDevicesChangedCallback( void *userData, PaDevicesChangedCallback* devicesChangedCallback ); + + /** The type used to represent monotonic time in seconds. PaTime is used for the fields of the PaStreamCallbackTimeInfo argument to the PaStreamCallback and as the result of Pa_GetStreamTime(). @@ -477,12 +546,30 @@ typedef unsigned long PaSampleFormat; #define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */ + +/** An integer id that uniquely identifies a device, so long as the device is + plugged in and PortAudio is initialized. Its purpose is to identify (correlate) + devices across calls to Pa_RefreshDeviceList(). + + Each device reported by Pa_GetDeviceInfo() has a distinct connectionId. + + PortAudio makes a best-effort to ensure that a persistent underlying device + has the same connectionId before and after a single call to Pa_RefreshDeviceList(). + + If a device is disconnected and later reconnected it may be assigned a new + connectionId. + + @see Pa_RefreshDeviceList() +*/ +typedef unsigned long PaDeviceConnectionId; + + /** A structure providing information and capabilities of PortAudio devices. Devices may support input, output or both input and output. */ typedef struct PaDeviceInfo { - int structVersion; /* this is struct version 2 */ + int structVersion; /* this is struct version 3 */ const char *name; PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/ @@ -497,6 +584,8 @@ typedef struct PaDeviceInfo PaTime defaultHighOutputLatency; double defaultSampleRate; + + PaDeviceConnectionId connectionId; /**< @see PaDeviceConnectionId */ } PaDeviceInfo; @@ -1201,7 +1290,6 @@ PaError Pa_GetSampleSize( PaSampleFormat format ); void Pa_Sleep( long msec ); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/common/pa_front.c b/src/common/pa_front.c index 2cd9c54..5c2eecb 100644 --- a/src/common/pa_front.c +++ b/src/common/pa_front.c @@ -76,11 +76,13 @@ #include "pa_stream.h" #include "pa_trace.h" /* still usefull?*/ #include "pa_debugprint.h" +#include "pa_hotplug.h" #ifndef PA_SVN_REVISION #include "pa_svnrevision.h" #endif + /** * This is incremented if we make incompatible API changes. * This version scheme is based loosely on http://semver.org/ @@ -161,6 +163,8 @@ static int deviceCount_ = 0; PaUtilStreamRepresentation *firstOpenStream_ = NULL; +PaDevicesChangedCallback* devicesChangedCallback_ = NULL; +void* devicesChangedCallbackUserData_ = NULL; #define PA_IS_INITIALISED_ (initializationCount_ != 0) @@ -371,6 +375,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 +403,8 @@ PaError Pa_Terminate( void ) TerminateHostApis(); + PaUtil_TerminateHotPlug(); + PaUtil_DumpTraceMessages(); } result = paNoError; @@ -744,6 +753,152 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void ) } +PaError Pa_RefreshDeviceList( 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; +} + + +PaError Pa_SetDevicesChangedCallback( void *userData, PaStreamFinishedCallback* devicesChangedCallback ) +{ + PaUtil_LockHotPlug(); + devicesChangedCallback_ = devicesChangedCallback; + devicesChangedCallbackUserData_ = userData; + PaUtil_UnlockHotPlug(); + return paNoError; +} + +/* Called by platform hotplug implementation 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(); +} + +static PaDeviceConnectionId nextDeviceConnectionId_ = 1000; + +PaDeviceConnectionId PaUtil_MakeDeviceConnectionId( void ) +{ + /* FIXME: if the counter wraps around we may want to ensure that + we are not issuing an ID that belongs to a current device. + we need to honor the invariant "no two devices have the same connection id" + */ + return nextDeviceConnectionId_++; +} + + const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) { int hostSpecificDeviceIndex; @@ -1806,4 +1961,3 @@ PaError Pa_GetSampleSize( PaSampleFormat format ) return (PaError) result; } - diff --git a/src/common/pa_hostapi.h b/src/common/pa_hostapi.h index 54b527e..ff8d87d 100644 --- a/src/common/pa_hostapi.h +++ b/src/common/pa_hostapi.h @@ -171,6 +171,9 @@ typedef struct PaUtilPrivatePaFrontHostApiInfo { }PaUtilPrivatePaFrontHostApiInfo; +PaDeviceConnectionId PaUtil_MakeDeviceConnectionId( void ); + + /** The common header for all data structures whose pointers are passed through the hostApiSpecificStreamInfo field of the PaStreamParameters structure. Note that in order to keep the public PortAudio interface clean, this structure @@ -322,6 +325,119 @@ typedef struct PaUtilHostApiRepresentation { const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); + + /** ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos are used to refresh the device list. + + You can think of the caller doing something like: + + void *scanResults = 0; + int newDeviceCount = 0; + if( hostApi->ScanDeviceInfos(hostApi, hostApiIndex, &scanResults, &newDeviceCount) == paNoError ) + { + ... do other stuff ... + + if( ok to commit ) + { + hostApi->CommitDeviceInfos(hostApi, hostApiIndex, scanResults, newDeviceCount); + } + else + { + hostApi->DisposeDeviceInfos(hostApi, hostApiIndex, scanResults, newDeviceCount); + } + } + + NB: ScanDeviceInfos may be called on all host APIs first, prior to pa_front + deciding whether to commit or cleanup. + */ + + /** + Scan for current native device information and compute a new device count. + Preparation step of a two-stage transaction that updates the active device list. + + @param hostApi The target host API. + + @param index The host API index of the target host API. + + @param scanResults (OUT) Result parameter. Upon success, set to point to an + opaque structure containing collected device information. Ignored on failure. + + @param newDeviceCount (Out) Result parameter. Upon success, set to the + number of discovered devices. Ignored on failure. + + @return on success returns paNoError, otherwise an error code indicating + the cause of the error. + + This function should not make any visible changes to the host API's internal + state. In particular, it should not update the active device list, device + information or device count. + + If the function returns successfully, 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. + + @note CommitDeviceInfos and DisposeDeviceInfos should not fail, therefore + ScanDeviceInfos should perform all necessary preparations, + such as memory allocation, so that CommitDeviceInfos can always execute + without failure. + + @see CommitDeviceInfos, DisposeDeviceInfos + */ + PaError (*ScanDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, + PaHostApiIndex index, + void **scanResults, + int *newDeviceCount ); + + /** + Update the active device list with new device information that was + returned by ScanDeviceInfos(). This is the final commit step of a two-stage + transaction that updates the active device list. + + As this function is not permitted to fail, it should be very simple: + swap in new device information and free the old information. + + @param hostApi The target host API. + + @param index The host API index of the target host API. + + @param scanResults An opaque structure containing collected device + information previously returned by ScanDeviceInfos. This is the information + that will be installed into the active device list. + + @param newDeviceCount The number of discovered devices previously returned + by ScanDeviceInfos. This is the new number of devices in the active device + list. + + @note This function is not permitted to fail. It should always return paNoError. + + @see ScanDeviceInfos, DisposeDeviceInfos + */ + PaError (*CommitDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, + PaHostApiIndex index, + void *scanResults, + int deviceCount ); + + /** + Free device information that was returned by ScanDeviceInfos. This is + used in the cleanup process for a failed two-stage transaction. + + @param hostApi The target host API. + + @param index The host API index of the target host API. + + @param scanResults An opaque structure containing collected device + information previously returned by ScanDeviceInfos. scanResults will be + freed by this function. + + @param newDeviceCount The number of discovered devices previously returned + by ScanDeviceInfos. + + @note This function is not permitted to fail. It should always return paNoError. + + @see CommitDeviceInfos, ScanDeviceInfos + */ + PaError (*DisposeDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ); + } PaUtilHostApiRepresentation; diff --git a/src/common/pa_hotplug.h b/src/common/pa_hotplug.h new file mode 100644 index 0000000..703849a --- /dev/null +++ b/src/common/pa_hotplug.h @@ -0,0 +1,98 @@ +#ifndef PA_HOTPLUG_H +#define PA_HOTPLUG_H +/* + * $Id$ + * Portable Audio I/O Library + * hotplug interface and utilities + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2016 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + + @brief Utilities for implementing hotplug support. +*/ + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** Init/terminate hotplug notification engine. + + At the moment there is on hotplug implementation per platform. + It is responsible for posting devices changed notifications + by calling PaUtil_DevicesChanged. + + Once we support multiple notification mechanisms we'll probably + have the host APIs init and terminate their own notification + engines (using reference counting?) e.g. wasapi will have its own, + but other windows APIs will use the global windows notifier. + + Implemented in pa_win_hotplug.c +*/ +void PaUtil_InitializeHotPlug(); +void PaUtil_TerminateHotPlug(); + + +/** Invoke the client's registered devices changed notification. + + @param first 0 = unknown, 1 = insertion, 2 = removal + @param second Host specific device change info (in windows it is the (unicode) device path) + + Parameters are currently ignored. TODO REVIEW + + Implemented in pa_front.c +*/ +void PaUtil_DevicesChanged(unsigned, void*); + + +/** Lock/unlock a mutex used to protect the devices changed callback. + Used by pa_front.c to synchronise notification callbacks and client + requests to set/clear the device callback. + + Coded like this because we don't have a cross-platform mutex. + + Implemented in pa_win_hotplug.c +*/ +void PaUtil_LockHotPlug(); +void PaUtil_UnlockHotPlug(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_HOTPLUG_H */ diff --git a/src/hostapi/alsa/pa_linux_alsa.c b/src/hostapi/alsa/pa_linux_alsa.c index f286591..bb16df6 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. @@ -1186,9 +1189,11 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d } } - baseDeviceInfo->structVersion = 2; + baseDeviceInfo->structVersion = 3; baseDeviceInfo->hostApi = alsaApi->hostApiIndex; baseDeviceInfo->name = deviceHwInfo->name; + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); + devInfo->alsaName = deviceHwInfo->alsaName; devInfo->isPlug = deviceHwInfo->isPlug; diff --git a/src/hostapi/asihpi/pa_linux_asihpi.c b/src/hostapi/asihpi/pa_linux_asihpi.c index f5a5290..d1a3f82 100644 --- a/src/hostapi/asihpi/pa_linux_asihpi.c +++ b/src/hostapi/asihpi/pa_linux_asihpi.c @@ -636,7 +636,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA hpiDevice->streamIndex = j; hpiDevice->streamIsOutput = 0; /* Set common PortAudio device stats */ - baseDeviceInfo->structVersion = 2; + baseDeviceInfo->structVersion = 3; /* Make sure name string is owned by API info structure */ sprintf( srcName, "Adapter %d (%4X) - Input Stream %d", i+1, type, j+1 ); @@ -656,6 +656,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA /* HPI interface can actually handle any sampling rate to 1 Hz accuracy, * so this default is as good as any */ baseDeviceInfo->defaultSampleRate = 44100; + baseDeviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); /* Store device in global PortAudio list */ hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice; @@ -678,7 +679,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA hpiDevice->streamIndex = j; hpiDevice->streamIsOutput = 1; /* Set common PortAudio device stats */ - baseDeviceInfo->structVersion = 2; + baseDeviceInfo->structVersion = 3; /* Make sure name string is owned by API info structure */ sprintf( srcName, "Adapter %d (%4X) - Output Stream %d", i+1, type, j+1 ); @@ -698,6 +699,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA /* HPI interface can actually handle any sampling rate to 1 Hz accuracy, * so this default is as good as any */ baseDeviceInfo->defaultSampleRate = 44100; + baseDeviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); /* Store device in global PortAudio list */ hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice; @@ -771,6 +773,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..fe6884c 100644 --- a/src/hostapi/asio/pa_asio.cpp +++ b/src/hostapi/asio/pa_asio.cpp @@ -1322,10 +1322,11 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; deviceInfo->name = names[i]; + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); if( InitPaDeviceInfoFromAsioDriver( asioHostApi, names[i], i, deviceInfo, asioDeviceInfo ) == paNoError ) { @@ -1356,6 +1357,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..8743066 100644 --- a/src/hostapi/coreaudio/pa_mac_core.c +++ b/src/hostapi/coreaudio/pa_mac_core.c @@ -659,7 +659,7 @@ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi, memset(deviceInfo, 0, sizeof(PaDeviceInfo)); - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; /* Get the device name using CFString */ @@ -693,6 +693,7 @@ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi, CFRelease(nameRef); } deviceInfo->name = name; + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); /* Try to get the default sample rate. Don't fail if we can't get this. */ propSize = sizeof(Float64); @@ -825,6 +826,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..44ab4ae 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,13 @@ typedef struct PaWinDsStream } PaWinDsStream; +typedef struct PaWinDsScanDeviceInfosResults +{ + PaDeviceInfo **deviceInfos; + PaDeviceIndex defaultInputDevice; + PaDeviceIndex defaultOutputDevice; +} PaWinDsScanDeviceInfosResults; + /* Set minimal latency based on the current OS version. * NT has higher latency. @@ -455,7 +465,7 @@ typedef struct DSDeviceNameAndGUIDVector{ int count; int free; - DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() + DSDeviceNameAndGUID *items; /* Allocated using LocalAlloc() */ } DSDeviceNameAndGUIDVector; typedef struct DSDeviceNamesAndGUIDs{ @@ -667,7 +677,7 @@ static GUID pawin_IID_IKsPropertySet = property, and the other is using DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE. I tried both methods and only the second worked. I found two postings on the net from people who had the same problem with the first method, so I think the method used here is - more common/likely to work. The probem is that IKsPropertySet_Get returns S_OK + more common/likely to work. The problem is that IKsPropertySet_Get returns S_OK but the fields of the device description are not filled in. The mechanism we use works by registering an enumeration callback which is called for @@ -762,38 +772,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 +823,8 @@ static PaError AddOutputDeviceInfoFromDirectSound( lpGUID->Data4[6], lpGUID->Data4[7])); - deviceOK = FALSE; + result = paUnanticipatedHostError; + goto error; } else { @@ -847,7 +835,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 +846,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; + 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; + deviceInfo->connectionId = -1; /* initialized by caller */ + + return result; + +error: + if( lpDirectSound ) + IDirectSound_Release( lpDirectSound ); return result; } @@ -1016,27 +1014,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 +1038,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 +1050,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 +1059,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 +1109,68 @@ 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; + deviceInfo->connectionId = -1; /* initialized by caller */ + + return result; + +error: + if( lpDirectSoundCapture ) + IDirectSoundCapture_Release( lpDirectSoundCapture ); return result; } @@ -1185,18 +1180,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 +1213,24 @@ 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; + 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 +1246,6 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde return result; error: - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi ); return result; @@ -1489,6 +1390,293 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, return paFormatIsSupported; } +/***********************************************************************************/ + +/* used for looking up existing devices to reuse their connectionId */ +static const PaDeviceInfo* FindDeviceInfo( + const PaDeviceInfo **deviceInfos, int deviceCount, + LPGUID lpGUID, int isInput ) +{ + int i; + +#ifndef NDEBUG + if( deviceCount ) + { + assert( deviceInfos != NULL ); + assert( deviceInfos[0] != NULL ); + } +#endif + + for( i = 0; i < deviceCount; ++i ) + { + const PaDeviceInfo *deviceInfo = deviceInfos[i]; + const PaWinDsDeviceInfo *dsDeviceInfo = (const PaWinDsDeviceInfo*)deviceInfo; + if( ((isInput && deviceInfo->maxInputChannels > 0) + || (!isInput && deviceInfo->maxOutputChannels > 0)) + && ((lpGUID == NULL && dsDeviceInfo->lpGUID == NULL) + || (lpGUID != NULL && dsDeviceInfo->lpGUID != NULL && (memcmp(lpGUID, dsDeviceInfo->lpGUID, sizeof(GUID)) == 0)))) + { + return deviceInfo; + } + } + + return NULL; +} + +static void FreeDeviceInfos( PaUtilAllocationGroup *allocations, PaDeviceInfo **deviceInfos ) +{ + if( deviceInfos ) + { + if( deviceInfos[0] ) + { + /* all device info structs are allocated in a block so we can destroy them like this */ + PaUtil_GroupFreeMemory( allocations, deviceInfos[0] ); + } + + PaUtil_GroupFreeMemory( allocations, deviceInfos ); + } +} + +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 = 3; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + + outArgument->deviceInfos[ i ] = deviceInfo; + } + + for( i = 0 ; i < deviceNamesAndGUIDs.inputNamesAndGUIDs.count ; ++i ) + { + PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*)outArgument->deviceInfos[*newDeviceCount]; + const PaDeviceInfo *currentDeviceInfo = FindDeviceInfo( + hostApi->deviceInfos, hostApi->info.deviceCount, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, /*isInput=*/1 ); + + /* + FIXME REVIEW + - we could also use deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface as parameter to FindDeviceInfo. I'm not sure whether that adds anything + - if currentDeviceInfo is non-NULL we could just copy the device info over instead of calling AddInputDeviceInfoFromDirectSoundCapture + => make an assesment about whether any device info is likely to change + - Channel count could change if speaker configuration changes + - if we're going to leave it the current way, then move call to FindDeviceInfo to only if AddInputDeviceInfoFromDirectSoundCapture succeeds + */ + + result = AddInputDeviceInfoFromDirectSoundCapture( winDsDeviceInfo, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].name, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].lpGUID, + deviceNamesAndGUIDs.inputNamesAndGUIDs.items[i].pnpInterface ); + if( result == paNoError ) + { + if (currentDeviceInfo) + winDsDeviceInfo->inheritedDeviceInfo.connectionId = currentDeviceInfo->connectionId; + else + winDsDeviceInfo->inheritedDeviceInfo.connectionId = PaUtil_MakeDeviceConnectionId(); + + 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]; + const PaDeviceInfo *currentDeviceInfo = FindDeviceInfo( + hostApi->deviceInfos, hostApi->info.deviceCount, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, /*isInput=*/0 ); + + result = AddOutputDeviceInfoFromDirectSound( winDsDeviceInfo, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].name, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].lpGUID, + deviceNamesAndGUIDs.outputNamesAndGUIDs.items[i].pnpInterface ); + if( result == paNoError ) + { + if (currentDeviceInfo) + winDsDeviceInfo->inheritedDeviceInfo.connectionId = currentDeviceInfo->connectionId; + else + winDsDeviceInfo->inheritedDeviceInfo.connectionId = PaUtil_MakeDeviceConnectionId(); + + 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 ) + { + FreeDeviceInfos( winDsHostApi->allocations, outArgument->deviceInfos ); + } + *newDeviceCount = 0; + + return result; +} + +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + 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 ) + { + FreeDeviceInfos( 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 paNoError; +} + +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + if( scanResults != NULL ) + { + PaWinDsScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinDsScanDeviceInfosResults * ) scanResults; + + if( scanDeviceInfosResults->deviceInfos ) + { + FreeDeviceInfos( winDsHostApi->allocations, hostApi->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..792b0b4 100644 --- a/src/hostapi/jack/pa_jack.c +++ b/src/hostapi/jack/pa_jack.c @@ -573,7 +573,7 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) strlen(client_names[client_index]) + 1 ), paInsufficientMemory ); strcpy( (char *)curDevInfo->name, client_names[client_index] ); - curDevInfo->structVersion = 2; + curDevInfo->structVersion = 3; curDevInfo->hostApi = jackApi->hostApiIndex; /* JACK is very inflexible: there is one sample rate the whole @@ -634,6 +634,8 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) commonApi->info.defaultInputDevice = client_index; if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 ) commonApi->info.defaultOutputDevice = client_index; + + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); } error: @@ -756,6 +758,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..1a3d7bb 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 ) ); @@ -295,7 +298,7 @@ PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, { PaError result = paNoError; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; if( allocations ) { size_t len = strlen( name ) + 1; @@ -313,6 +316,7 @@ PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name, deviceInfo->defaultHighInputLatency = defaultHighInputLatency; deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency; deviceInfo->defaultSampleRate = defaultSampleRate; + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); error: return result; 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 e865c7b..81ec25a 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -1563,7 +1563,7 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd for (i = 0; i < paWasapi->deviceCount; ++i) { PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; PA_DEBUG(("WASAPI: device idx: %02d\n", i)); @@ -1796,6 +1796,8 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd break; } + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); + (*hostApi)->deviceInfos[i] = deviceInfo; ++(*hostApi)->info.deviceCount; } @@ -1804,6 +1806,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..44ec174 100644 --- a/src/hostapi/wdmks/pa_win_wdmks.c +++ b/src/hostapi/wdmks/pa_win_wdmks.c @@ -3442,6 +3442,22 @@ static unsigned GetNameIndex(PaNameHashObject* obj, const wchar_t* name, const B return 0; } +static void FreeHostApiDeviceInfos( PaUtilAllocationGroup *allocations, PaDeviceInfo **deviceInfos, int deviceCount ) +{ + int i; + for (i = 0; i < deviceCount; ++i) + { + PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)deviceInfos[i]; + if (pDevice->filter != 0) + { + FilterFree(pDevice->filter); + } + } + + PaUtil_GroupFreeMemory( allocations, deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */ + PaUtil_GroupFreeMemory( allocations, deviceInfos ); +} + static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount ) { PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; @@ -3514,7 +3530,7 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH for( i = 0 ; i < totalDeviceCount; ++i ) { PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; deviceInfo->name = 0; outArgument->deviceInfos[ i ] = deviceInfo; @@ -3557,11 +3573,13 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH wdmDeviceInfo->filter = pFilter; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; deviceInfo->name = wdmDeviceInfo->compositeName; /* deviceInfo->hostApiSpecificDeviceInfo = &pFilter->devInfo; */ + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); + wdmDeviceInfo->pin = pin->pinId; /* Get the name of the "device" */ @@ -3706,22 +3724,17 @@ static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, P { PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; - 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 ) { - PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory( - wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults)); - localScanResults->deviceInfos = hostApi->deviceInfos; - - DisposeDeviceInfos(hostApi, &localScanResults, hostApi->info.deviceCount); - + FreeHostApiDeviceInfos( wdmHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount ); hostApi->deviceInfos = NULL; } + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + if( scanResults != NULL ) { PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults; @@ -3746,7 +3759,7 @@ static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, P static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ) { - PaWinWdmHostApiRepresentation *winDsHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; if( scanResults != NULL ) { @@ -3754,21 +3767,10 @@ static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, if( scanDeviceInfosResults->deviceInfos ) { - int i; - for (i = 0; i < deviceCount; ++i) - { - PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)scanDeviceInfosResults->deviceInfos[i]; - if (pDevice->filter != 0) - { - FilterFree(pDevice->filter); - } - } - - 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 ); + FreeHostApiDeviceInfos( wdmHostApi->allocations, scanDeviceInfosResults->deviceInfos, hostApi->info.deviceCount ); } - PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); + PaUtil_GroupFreeMemory( wdmHostApi->allocations, scanDeviceInfosResults ); } return paNoError; @@ -3853,11 +3855,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, @@ -3900,10 +3901,11 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) if( wdmHostApi) { - PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory( - wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults)); - localScanResults->deviceInfos = hostApi->deviceInfos; - DisposeDeviceInfos(hostApi, localScanResults, hostApi->info.deviceCount); + if( hostApi->deviceInfos ) + { + FreeHostApiDeviceInfos( wdmHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount ); + hostApi->deviceInfos = NULL; + } if( wdmHostApi->allocations ) { diff --git a/src/hostapi/wmme/pa_win_wmme.c b/src/hostapi/wmme/pa_win_wmme.c index 422c867..5afaf8e 100644 --- a/src/hostapi/wmme/pa_win_wmme.c +++ b/src/hostapi/wmme/pa_win_wmme.c @@ -789,6 +789,8 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, QueryInputWaveFormatEx, deviceInfo->maxInputChannels ); + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); + *success = 1; error: @@ -924,6 +926,8 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels ); + deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId(); + *success = 1; error: @@ -1069,7 +1073,7 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; deviceInfo->maxInputChannels = 0; @@ -1112,7 +1116,7 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; - deviceInfo->structVersion = 2; + deviceInfo->structVersion = 3; deviceInfo->hostApi = hostApiIndex; deviceInfo->maxInputChannels = 0; @@ -1155,6 +1159,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..5bc2871 --- /dev/null +++ b/src/os/win/pa_win_hotplug.c @@ -0,0 +1,348 @@ +/* + * $Id$ + * Portable Audio I/O Library + * Hotplug interface and utilities + * Copyright (c) 2011-2016 Robert Bielik + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2016 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include "pa_hotplug.h" +#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 + +/* 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, (DWORD)-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_refresh_device_list.c b/test/patest_refresh_device_list.c new file mode 100644 index 0000000..6c138f0 --- /dev/null +++ b/test/patest_refresh_device_list.c @@ -0,0 +1,49 @@ +#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 ); + assert( deviceInfo->structVersion >= 3 ); /* should be the case if all APIs have implemented connectionId */ + + printf( "%d (conn id: %d) %s (%s)\n", i, deviceInfo->connectionId, 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_RefreshDeviceList(); + } + + Pa_Terminate(); + + return 0; +} +