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..a87bc66 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -189,6 +189,9 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); +static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount ); +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount ); +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount ); static PaError CloseStream( PaStream* stream ); static PaError StartStream( PaStream *stream ); static PaError StopStream( PaStream *stream ); @@ -312,6 +315,12 @@ typedef struct PaWinDsStream } PaWinDsStream; +typedef struct PaWinDsScanDeviceInfosResults{ /* used for tranferring device infos during scanning / rescanning */ + PaDeviceInfo **deviceInfos; + PaDeviceIndex defaultInputDevice; + PaDeviceIndex defaultOutputDevice; +} PaWinDsScanDeviceInfosResults; + /* Set minimal latency based on the current OS version. * NT has higher latency. @@ -455,7 +464,7 @@ typedef struct DSDeviceNameAndGUIDVector{ int count; int free; - DSDeviceNameAndGUID *items; // Allocated using LocalAlloc() + DSDeviceNameAndGUID *items; /* Allocated using LocalAlloc() */ } DSDeviceNameAndGUIDVector; typedef struct DSDeviceNamesAndGUIDs{ @@ -667,7 +676,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 +771,15 @@ static double defaultSampleRateSearchOrder_[] = ** The device will not be added to the device list if any errors are encountered. */ static PaError AddOutputDeviceInfoFromDirectSound( - PaWinDsHostApiRepresentation *winDsHostApi, char *name, LPGUID lpGUID, char *pnpInterface ) + PaWinDsDeviceInfo *winDsDeviceInfo, char *name, LPGUID lpGUID, char *pnpInterface ) { - PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; - PaWinDsDeviceInfo *winDsDeviceInfo = (PaWinDsDeviceInfo*) hostApi->deviceInfos[hostApi->info.deviceCount]; PaDeviceInfo *deviceInfo = &winDsDeviceInfo->inheritedDeviceInfo; HRESULT hr; - LPDIRECTSOUND lpDirectSound; + LPDIRECTSOUND lpDirectSound = NULL; DSCAPS caps; - int deviceOK = TRUE; PaError result = paNoError; int i; - /* Copy GUID to the device info structure. Set pointer. */ - if( lpGUID == NULL ) - { - winDsDeviceInfo->lpGUID = NULL; - } - else - { - memcpy( &winDsDeviceInfo->guid, lpGUID, sizeof(GUID) ); - winDsDeviceInfo->lpGUID = &winDsDeviceInfo->guid; - } - - if( lpGUID ) - { - if (IsEqualGUID (&IID_IRolandVSCEmulated1,lpGUID) || - IsEqualGUID (&IID_IRolandVSCEmulated2,lpGUID) ) - { - PA_DEBUG(("BLACKLISTED: %s \n",name)); - return paNoError; - } - } /* Create a DirectSound object for the specified GUID Note that using CoCreateInstance doesn't work on windows CE. @@ -836,7 +822,8 @@ static PaError AddOutputDeviceInfoFromDirectSound( lpGUID->Data4[6], lpGUID->Data4[7])); - deviceOK = FALSE; + result = paUnanticipatedHostError; + goto error; } else { @@ -847,7 +834,9 @@ static PaError AddOutputDeviceInfoFromDirectSound( if( hr != DS_OK ) { DBUG(("Cannot GetCaps() for DirectSound device %s. Result = 0x%x\n", name, hr )); - deviceOK = FALSE; + + result = paUnanticipatedHostError; + goto error; } else { @@ -856,153 +845,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 +1013,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 +1037,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 +1049,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 +1058,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 +1108,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 +1179,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 +1212,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 +1245,6 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde return result; error: - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); - TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); - Terminate( (struct PaUtilHostApiRepresentation *)winDsHostApi ); return result; @@ -1489,6 +1389,291 @@ 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 ); + } + 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..211967d 100644 --- a/src/hostapi/wmme/pa_win_wmme.c +++ b/src/hostapi/wmme/pa_win_wmme.c @@ -123,6 +123,11 @@ #endif #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ +#if !defined(DRVM_MAPPER_PREFERRED_GET) +/* DRVM_MAPPER_PREFERRED_GET is defined in mmddk.h but we avoid a dependency on the DDK by defining it here */ +#define DRVM_MAPPER_PREFERRED_GET (0x2000+21) +#endif + /* use CreateThread for CYGWIN, _beginthreadex for all others */ #if !defined(__CYGWIN__) && !defined(_WIN32_WCE) #define CREATE_THREAD (HANDLE)_beginthreadex( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId ) @@ -282,6 +287,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 ); @@ -429,13 +437,6 @@ typedef struct PaUtilAllocationGroup *allocations; int inputDeviceCount, outputDeviceCount; - - /** winMmeDeviceIds is an array of WinMme device ids. - fields in the range [0, inputDeviceCount) are input device ids, - and [inputDeviceCount, inputDeviceCount + outputDeviceCount) are output - device ids. - */ - UINT *winMmeDeviceIds; } PaWinMmeHostApiRepresentation; @@ -443,12 +444,24 @@ PaWinMmeHostApiRepresentation; typedef struct { PaDeviceInfo inheritedDeviceInfo; + UINT winWmmeDeviceId; /*<< Windows MME device id for this device */ + LPWSTR deviceInterfaceName; /*<< A null-terminated Unicode string containing the device-interface name returned by DRV_QUERYDEVICEINTERFACE. + see: https://msdn.microsoft.com/en-us/library/windows/hardware/ff536363(v=vs.85).aspx + We use this for connectionId impl because it works on Windows XP (DRV_QUERYFUNCTIONINSTANCEID is supported only on Vista and later) */ DWORD dwFormats; /**<< standard formats bitmask from the WAVEINCAPS and WAVEOUTCAPS structures */ char deviceInputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/ char deviceOutputChannelCountIsKnown; /**<< if the system returns 0xFFFF then we don't really know the number of supported channels (1=>known, 0=>unknown)*/ } PaWinMmeDeviceInfo; +typedef struct +{ /* used for transferring device infos during scanning / rescanning */ + PaDeviceInfo **deviceInfos; + PaDeviceIndex defaultInputDevice; + PaDeviceIndex defaultOutputDevice; + int inputDeviceCount, outputDeviceCount; +} +PaWinMmeScanDeviceInfosResults; /************************************************************************* * Returns recommended device ID. @@ -511,9 +524,11 @@ static void InitializeDefaultDeviceIdsFromEnv( PaWinMmeHostApiRepresentation *ho */ static UINT LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device ) { - assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount ); + PaWinMmeDeviceInfo *winWmmeDeviceInfo; - return hostApi->winMmeDeviceIds[ device ]; + assert( device >= 0 && device < hostApi->inputDeviceCount + hostApi->outputDeviceCount ); + winWmmeDeviceInfo = (PaWinMmeDeviceInfo *)hostApi->inheritedHostApiRep.deviceInfos[device]; + return winWmmeDeviceInfo->winWmmeDeviceId; } @@ -666,45 +681,45 @@ static void DetectDefaultSampleRate( PaWinMmeDeviceInfo *winMmeDeviceInfo, int w } -#ifdef PAWIN_USE_WDMKS_DEVICE_INFO -static int QueryWaveInKSFilterMaxChannels( int waveInDeviceId, int *maxChannels ) +/* On error, *resultInterfaceName will be NULL */ +static PaError QueryInputDeviceInterfaceName( PaUtilAllocationGroup *allocations, UINT waveInDeviceId, LPWSTR *resultInterfaceName ) { - void *devicePath; - DWORD devicePathSize; - int result = 0; + LPWSTR resultString = NULL; + DWORD stringSize = 0; - if( waveInMessage((HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACESIZE, - (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR ) - return 0; + *resultInterfaceName = NULL; - devicePath = PaUtil_AllocateMemory( devicePathSize ); - if( !devicePath ) - return 0; + if( waveInMessage( (HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACESIZE, + (DWORD_PTR)&stringSize, 0 ) != MMSYSERR_NOERROR ) + return paUnanticipatedHostError; - /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */ - if( waveInMessage((HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACE, - (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR ) + if( stringSize == 0 ) + return paNoError; + + resultString = (LPWSTR)PaUtil_GroupAllocateMemory( allocations, stringSize ); + if( !resultString ) + return paInsufficientMemory; + + if( waveInMessage( (HWAVEIN)waveInDeviceId, DRV_QUERYDEVICEINTERFACE, + (DWORD_PTR)resultString, stringSize ) == MMSYSERR_NOERROR ) { - int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 1 ); - if( count > 0 ) - { - *maxChannels = count; - result = 1; - } + *resultInterfaceName = resultString; + return paNoError; + } + else + { + PaUtil_GroupFreeMemory( allocations, resultString ); + return paUnanticipatedHostError; } - - PaUtil_FreeMemory( devicePath ); - - return result; } -#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeInputDeviceId, int *success ) { PaError result = paNoError; - char *deviceName; /* non-const ptr */ + char *deviceName = NULL; /* non-const ptr */ + LPWSTR deviceInterfaceName = NULL; MMRESULT mmresult; WAVEINCAPS wic; PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; @@ -760,6 +775,10 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH } deviceInfo->name = deviceName; + QueryInputDeviceInterfaceName( winMmeHostApi->allocations, + winMmeInputDeviceId, &deviceInterfaceName ); + winMmeDeviceInfo->deviceInterfaceName = deviceInterfaceName; + if( wic.wChannels == 0xFFFF || wic.wChannels < 1 || wic.wChannels > 255 ){ /* For Windows versions using WDM (possibly Windows 98 ME and later) * the kernel mixer sits between the application and the driver. As a result, @@ -780,8 +799,17 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH } #ifdef PAWIN_USE_WDMKS_DEVICE_INFO - winMmeDeviceInfo->deviceInputChannelCountIsKnown = - QueryWaveInKSFilterMaxChannels( winMmeInputDeviceId, &deviceInfo->maxInputChannels ); + if( deviceInterfaceName != NULL ) + { + /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path + that we can use with WDM/KS, although this seems to be undocumented */ + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( deviceInterfaceName, /* isInput= */ 1 ); + if( count > 0 ) + { + deviceInfo->maxInputChannels = count; + winMmeDeviceInfo->deviceInputChannelCountIsKnown = 1; + } + } #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ winMmeDeviceInfo->dwFormats = wic.dwFormats; @@ -789,59 +817,61 @@ static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeH DetectDefaultSampleRate( winMmeDeviceInfo, winMmeInputDeviceId, QueryInputWaveFormatEx, deviceInfo->maxInputChannels ); - *success = 1; + winMmeDeviceInfo->winWmmeDeviceId = winMmeInputDeviceId; + *success = 1; + return result; + error: + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceName ); + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceInterfaceName ); return result; } -#ifdef PAWIN_USE_WDMKS_DEVICE_INFO -static int QueryWaveOutKSFilterMaxChannels( int waveOutDeviceId, int *maxChannels ) +/* resultInterfaceName will be NULL if there's an error */ +static PaError QueryOutputDeviceInterfaceName( PaUtilAllocationGroup *allocations, UINT waveOutDeviceId, LPWSTR *resultInterfaceName ) { - void *devicePath; - DWORD devicePathSize; - int result = 0; + LPWSTR resultString = NULL; + DWORD stringSize = 0; - if( waveOutMessage((HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACESIZE, - (DWORD_PTR)&devicePathSize, 0 ) != MMSYSERR_NOERROR ) - return 0; + *resultInterfaceName = NULL; - devicePath = PaUtil_AllocateMemory( devicePathSize ); - if( !devicePath ) - return 0; + if( waveOutMessage( (HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACESIZE, + (DWORD_PTR)&stringSize, 0 ) != MMSYSERR_NOERROR ) + return paInternalError; - /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path, although this is undocumented */ - if( waveOutMessage((HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACE, - (DWORD_PTR)devicePath, devicePathSize ) == MMSYSERR_NOERROR ) + if( stringSize == 0 ) + return paNoError; + + resultString = (LPWSTR)PaUtil_GroupAllocateMemory( allocations, stringSize ); + if( !resultString ) + return paInsufficientMemory; + + if( waveOutMessage( (HWAVEOUT)waveOutDeviceId, DRV_QUERYDEVICEINTERFACE, + (DWORD_PTR)resultString, stringSize ) == MMSYSERR_NOERROR ) { - int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( devicePath, /* isInput= */ 0 ); - if( count > 0 ) - { - *maxChannels = count; - result = 1; - } + *resultInterfaceName = resultString; + return paNoError; + } + else + { + PaUtil_GroupFreeMemory( allocations, resultString ); + return paInternalError; } - - PaUtil_FreeMemory( devicePath ); - - return result; } -#endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeDeviceInfo *winMmeDeviceInfo, UINT winMmeOutputDeviceId, int *success ) { PaError result = paNoError; - char *deviceName; /* non-const ptr */ + char *deviceName = NULL; /* non-const ptr */ + LPWSTR deviceInterfaceName = NULL; MMRESULT mmresult; WAVEOUTCAPS woc; PaDeviceInfo *deviceInfo = &winMmeDeviceInfo->inheritedDeviceInfo; size_t len; -#ifdef PAWIN_USE_WDMKS_DEVICE_INFO - int wdmksDeviceOutputChannelCountIsKnown; -#endif *success = 0; @@ -893,6 +923,10 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme } deviceInfo->name = deviceName; + QueryOutputDeviceInterfaceName( winMmeHostApi->allocations, + winMmeOutputDeviceId, &deviceInterfaceName ); + winMmeDeviceInfo->deviceInterfaceName = deviceInterfaceName; + if( woc.wChannels == 0xFFFF || woc.wChannels < 1 || woc.wChannels > 255 ){ /* For Windows versions using WDM (possibly Windows 98 ME and later) * the kernel mixer sits between the application and the driver. As a result, @@ -913,10 +947,17 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme } #ifdef PAWIN_USE_WDMKS_DEVICE_INFO - wdmksDeviceOutputChannelCountIsKnown = QueryWaveOutKSFilterMaxChannels( - winMmeOutputDeviceId, &deviceInfo->maxOutputChannels ); - if( wdmksDeviceOutputChannelCountIsKnown && !winMmeDeviceInfo->deviceOutputChannelCountIsKnown ) - winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; + if( deviceInterfaceName != NULL ) + { + /* apparently DRV_QUERYDEVICEINTERFACE returns a unicode interface path + that we can use with WDM/KS, although this seems to be undocumented */ + int count = PaWin_WDMKS_QueryFilterMaximumChannelCount( deviceInterfaceName, /* isInput= */ 0 ); + if( count > 0 ) + { + deviceInfo->maxOutputChannels = count; + winMmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; + } + } #endif /* PAWIN_USE_WDMKS_DEVICE_INFO */ winMmeDeviceInfo->dwFormats = woc.dwFormats; @@ -924,9 +965,14 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme DetectDefaultSampleRate( winMmeDeviceInfo, winMmeOutputDeviceId, QueryOutputWaveFormatEx, deviceInfo->maxOutputChannels ); + winMmeDeviceInfo->winWmmeDeviceId = winMmeOutputDeviceId; + *success = 1; - + return result; + error: + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceName ); + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, deviceInterfaceName ); return result; } @@ -970,14 +1016,9 @@ See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprec PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) { PaError result = paNoError; - int i; PaWinMmeHostApiRepresentation *winMmeHostApi; - int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount; - PaWinMmeDeviceInfo *deviceInfoArray; - int deviceInfoInitializationSucceeded; - PaTime defaultLowLatency, defaultHighLatency; - DWORD waveInPreferredDevice, waveOutPreferredDevice; - DWORD preferredDeviceStatusFlags; + void *scanResults = 0; + int deviceCount; winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) ); if( !winMmeHostApi ) @@ -998,10 +1039,11 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd (*hostApi)->info.type = paMME; (*hostApi)->info.name = "MME"; - - /* initialise device counts and default devices under the assumption that - there are no devices. These values are incremented below if and when + /* Initialize device counts and default devices under the assumption that + there are no devices. These values are incremented if and when devices are successfully initialized. + + Note: the following are all updated by CommitDeviceInfos(). */ (*hostApi)->info.deviceCount = 0; (*hostApi)->info.defaultInputDevice = paNoDevice; @@ -1009,152 +1051,21 @@ PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd winMmeHostApi->inputDeviceCount = 0; winMmeHostApi->outputDeviceCount = 0; -#if !defined(DRVM_MAPPER_PREFERRED_GET) -/* DRVM_MAPPER_PREFERRED_GET is defined in mmddk.h but we avoid a dependency on the DDK by defining it here */ -#define DRVM_MAPPER_PREFERRED_GET (0x2000+21) -#endif + result = ScanDeviceInfos( &winMmeHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount ); + if( result != paNoError ) + goto error; - /* the following calls assume that if wave*Message fails the preferred device parameter won't be modified */ - preferredDeviceStatusFlags = 0; - waveInPreferredDevice = -1; - waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveInPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ); + /* Ignore the result of CommitDeviceInfos(), it is a non-failing operation */ + CommitDeviceInfos( &winMmeHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount ); - preferredDeviceStatusFlags = 0; - waveOutPreferredDevice = -1; - waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR)&waveOutPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ); - - maximumPossibleDeviceCount = 0; - - inputDeviceCount = waveInGetNumDevs(); - if( inputDeviceCount > 0 ) - maximumPossibleDeviceCount += inputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ - - outputDeviceCount = waveOutGetNumDevs(); - if( outputDeviceCount > 0 ) - maximumPossibleDeviceCount += outputDeviceCount + 1; /* assume there is a WAVE_MAPPER */ - - - if( maximumPossibleDeviceCount > 0 ){ - - (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( - winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount ); - if( !(*hostApi)->deviceInfos ) - { - result = paInsufficientMemory; - goto error; - } - - /* allocate all device info structs in a contiguous block */ - deviceInfoArray = (PaWinMmeDeviceInfo*)PaUtil_GroupAllocateMemory( - winMmeHostApi->allocations, sizeof(PaWinMmeDeviceInfo) * maximumPossibleDeviceCount ); - if( !deviceInfoArray ) - { - result = paInsufficientMemory; - goto error; - } - - winMmeHostApi->winMmeDeviceIds = (UINT*)PaUtil_GroupAllocateMemory( - winMmeHostApi->allocations, sizeof(int) * maximumPossibleDeviceCount ); - if( !winMmeHostApi->winMmeDeviceIds ) - { - result = paInsufficientMemory; - goto error; - } - - GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency ); - - if( inputDeviceCount > 0 ){ - /* -1 is the WAVE_MAPPER */ - for( i = -1; i < inputDeviceCount; ++i ){ - UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); - PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; - PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - - deviceInfo->maxInputChannels = 0; - wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; - deviceInfo->maxOutputChannels = 0; - wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; - - deviceInfo->defaultLowInputLatency = defaultLowLatency; - deviceInfo->defaultLowOutputLatency = defaultLowLatency; - deviceInfo->defaultHighInputLatency = defaultHighLatency; - deviceInfo->defaultHighOutputLatency = defaultHighLatency; - - result = InitializeInputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, - winMmeDeviceId, &deviceInfoInitializationSucceeded ); - if( result != paNoError ) - goto error; - - if( deviceInfoInitializationSucceeded ){ - if( (*hostApi)->info.defaultInputDevice == paNoDevice ){ - /* if there is currently no default device, use the first one available */ - (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; - - }else if( winMmeDeviceId == waveInPreferredDevice ){ - /* set the default device to the system preferred device */ - (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount; - } - - winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; - (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; - - winMmeHostApi->inputDeviceCount++; - (*hostApi)->info.deviceCount++; - } - } - } - - if( outputDeviceCount > 0 ){ - /* -1 is the WAVE_MAPPER */ - for( i = -1; i < outputDeviceCount; ++i ){ - UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); - PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; - PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - - deviceInfo->maxInputChannels = 0; - wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; - deviceInfo->maxOutputChannels = 0; - wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; - - deviceInfo->defaultLowInputLatency = defaultLowLatency; - deviceInfo->defaultLowOutputLatency = defaultLowLatency; - deviceInfo->defaultHighInputLatency = defaultHighLatency; - deviceInfo->defaultHighOutputLatency = defaultHighLatency; - - result = InitializeOutputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, - winMmeDeviceId, &deviceInfoInitializationSucceeded ); - if( result != paNoError ) - goto error; - - if( deviceInfoInitializationSucceeded ){ - if( (*hostApi)->info.defaultOutputDevice == paNoDevice ){ - /* if there is currently no default device, use the first one available */ - (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; - - }else if( winMmeDeviceId == waveOutPreferredDevice ){ - /* set the default device to the system preferred device */ - (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; - } - - winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = winMmeDeviceId; - (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; - - winMmeHostApi->outputDeviceCount++; - (*hostApi)->info.deviceCount++; - } - } - } - } - - InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); + InitializeDefaultDeviceIdsFromEnv( winMmeHostApi ); /* uses the active device list. must be called after CommitDeviceInfos */ (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; + (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; + (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, @@ -1391,6 +1302,417 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, return paFormatIsSupported; } +/***********************************************************************************/ + +static const PaDeviceInfo* FindDeviceByConnectionId( + const PaDeviceInfo **deviceInfos, int deviceCount, + PaDeviceConnectionId connectionId ) +{ + 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]; + if( deviceInfo->connectionId == connectionId ) + return deviceInfo; + } + + return NULL; +} + +/* used for looking up existing devices to reuse their connectionId */ +static const PaDeviceInfo* FindDeviceInfoByInterfaceName( + const PaDeviceInfo **deviceInfos, int deviceCount, + LPCWSTR deviceInterfaceName, 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 PaWinMmeDeviceInfo *wmmeDeviceInfo = (const PaWinMmeDeviceInfo*)deviceInfo; + if( ((isInput && deviceInfo->maxInputChannels > 0) + || (!isInput && deviceInfo->maxOutputChannels > 0)) + && + /* require both interface names to be non-NULL, because they can be NULL due to errors. */ + (deviceInterfaceName != NULL && wmmeDeviceInfo->deviceInterfaceName != NULL + && (wcscmp(deviceInterfaceName, wmmeDeviceInfo->deviceInterfaceName) == 0)) ) + { + return deviceInfo; + } + } + + return NULL; +} + +/* correlate or allocate device connection id */ +static PaDeviceConnectionId AssignDeviceConnectionId( + const PaDeviceInfo **activeDeviceInfos, int activeDeviceCount, + const PaDeviceInfo **newDeviceInfos, int newDeviceCount, /* this is the new list being built. it contains infos added so far. */ + LPCWSTR deviceInterfaceName, int isInput ) +{ + const PaDeviceInfo *currentDeviceInfo; + + currentDeviceInfo = FindDeviceInfoByInterfaceName( + activeDeviceInfos, activeDeviceCount, /* search active device list for deviceInterfaceName */ + deviceInterfaceName, isInput ); + + if( currentDeviceInfo && FindDeviceByConnectionId( + newDeviceInfos, newDeviceCount, /* search new device list connectionId */ + currentDeviceInfo->connectionId ) == NULL ) + { + /* deviceInterfaceName matches, and its connectionId hasn't already been reused */ + return currentDeviceInfo->connectionId; + } + + return PaUtil_MakeDeviceConnectionId(); +} + + +static void FreeDeviceInfos( PaUtilAllocationGroup *allocations, + PaDeviceInfo **deviceInfos, int deviceCount ) +{ + if( deviceInfos ) + { + if( deviceInfos[0] ) + { + int i; + for( i = 0; i < deviceCount; ++i ) + { + PaWinMmeDeviceInfo *wmmeDeviceInfo = (PaWinMmeDeviceInfo*)deviceInfos[ i ]; + PaUtil_GroupFreeMemory( allocations, wmmeDeviceInfo->deviceInterfaceName ); + } + + /* 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_OUT, int *newDeviceCount_OUT ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaError result = paNoError; + int i; + int waveInDeviceCount, waveOutDeviceCount; + int maximumPossibleDeviceCount; + PaWinMmeScanDeviceInfosResults *winMmeScanResults; + PaWinMmeDeviceInfo *deviceInfoArray; + int initializedInfosCount; + + /* Check preconditions */ + if( scanResults_OUT == NULL || newDeviceCount_OUT == NULL ) + return paInternalError; + + /* initialize the OUT params */ + *scanResults_OUT = NULL; + *newDeviceCount_OUT = 0; + + winMmeScanResults = NULL; + initializedInfosCount = 0; /* incremented after successfully appending to deviceInfoArray */ + + maximumPossibleDeviceCount = 0; + +#ifdef PAWIN_WMME_NO_WAVE_MAPPER + #define PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_ 0 +#else + #define PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_ 1 +#endif + + waveInDeviceCount = waveInGetNumDevs(); + if( waveInDeviceCount > 0 ) + maximumPossibleDeviceCount += waveInDeviceCount + PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_; + + waveOutDeviceCount = waveOutGetNumDevs(); + if( waveOutDeviceCount > 0 ) + maximumPossibleDeviceCount += waveOutDeviceCount + PAWIN_WMME_POSSIBLE_WAVE_MAPPER_COUNT_; + + if( maximumPossibleDeviceCount > 0 ) + { + PaTime defaultLowLatency, defaultHighLatency; + + /* allocate scan results -- it will contain all info needed to update the device list */ + winMmeScanResults = (PaWinMmeScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaWinMmeScanDeviceInfosResults) ); + if( !winMmeScanResults ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate array for pointers to PaDeviceInfo structs */ + winMmeScanResults->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleDeviceCount ); + if( !winMmeScanResults->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinMmeDeviceInfo*)PaUtil_GroupAllocateMemory( + winMmeHostApi->allocations, sizeof(PaWinMmeDeviceInfo) * maximumPossibleDeviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + winMmeScanResults->defaultInputDevice = paNoDevice; + winMmeScanResults->defaultOutputDevice = paNoDevice; + winMmeScanResults->inputDeviceCount = 0; + winMmeScanResults->outputDeviceCount = 0; + + GetDefaultLatencies( &defaultLowLatency, &defaultHighLatency ); + + /* populate device infos set default input and output devices */ + + /* Default device selection logic: + Query for the system preferred input and output devices using + DRVM_MAPPER_PREFERRED_GET; on error default to WAVE_MAPPER. Preferred + devices are stored in wave{In, Out}PreferredDevice. + + When enumerating devices, set PA default devices to the MME devices + with ids matching wave{In, Out}PreferredDevice. If such devices are not + encountered during enumeration, the first device enumerated for each of + {input, output} will be selected as the PA default device. + + Note that if PAWIN_WMME_NO_WAVE_MAPPER is defined, the WAVE_MAPPER + devices will never be enumerated, and will never be made a PA + default device, irrespective of the wave{In, Out}PreferredDevice values. + */ + + if( waveInDeviceCount > 0 ){ + /* get preferred input device -- see comment about default devices above */ + DWORD preferredDeviceStatusFlags = 0; + DWORD waveInPreferredDevice = WAVE_MAPPER; + if( waveInMessage( (HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&waveInPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ) != MMSYSERR_NOERROR ) + waveInPreferredDevice = WAVE_MAPPER; + + /* append input devices to deviceInfoArray */ +#ifdef PAWIN_WMME_NO_WAVE_MAPPER + for( i = 0; i < waveInDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)i; +#else + /* -1 enumerates the WAVE_MAPPER */ + for( i = -1; i < waveInDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); +#endif + int deviceInfoInitializationSucceeded = 0; + int deviceIndex = initializedInfosCount; /* host-api local index. append at end of deviceInfoArray */ + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ deviceIndex ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 3; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; + deviceInfo->maxOutputChannels = 0; + wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeInputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + continue; /* ignore error results here and just skip the device. */ + /* N.B. Earlier WMME versions failed scanning with an error here. */ + + if( deviceInfoInitializationSucceeded ){ + /* NOTE: wmmeDeviceInfo->deviceInterfaceName is now allocated and would need to be freed on error. */ + + if( winMmeScanResults->defaultInputDevice == paNoDevice ) + { + /* if there is currently no default device, use the first one available */ + winMmeScanResults->defaultInputDevice = deviceIndex; + } + else if( winMmeDeviceId == waveInPreferredDevice ) + { + /* set the default device to the system preferred device */ + winMmeScanResults->defaultInputDevice = deviceIndex; + } + + deviceInfo->connectionId = AssignDeviceConnectionId( + hostApi->deviceInfos, hostApi->info.deviceCount, + winMmeScanResults->deviceInfos, initializedInfosCount, + wmmeDeviceInfo->deviceInterfaceName, /* isInput= */ 1 ); + + winMmeScanResults->deviceInfos[ deviceIndex ] = deviceInfo; + + winMmeScanResults->inputDeviceCount++; + initializedInfosCount++; + } + } + } + + if( waveOutDeviceCount > 0 ){ + /* get preferred output device -- see comment about default devices above */ + DWORD preferredDeviceStatusFlags = 0; + DWORD waveOutPreferredDevice = WAVE_MAPPER; + if( waveOutMessage( (HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&waveOutPreferredDevice, (DWORD_PTR)&preferredDeviceStatusFlags ) != MMSYSERR_NOERROR ) + waveOutPreferredDevice = WAVE_MAPPER; + + /* append output devices to deviceInfoArray */ +#ifdef PAWIN_WMME_NO_WAVE_MAPPER + for( i = 0; i < waveOutDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)i; +#else + /* -1 enumerates the WAVE_MAPPER */ + for( i = -1; i < waveOutDeviceCount; ++i ){ + UINT winMmeDeviceId = (UINT)((i==-1) ? WAVE_MAPPER : i); +#endif + int deviceInfoInitializationSucceeded = 0; + int deviceIndex = initializedInfosCount; /* host-api local index. append at end of deviceInfoArray */ + PaWinMmeDeviceInfo *wmmeDeviceInfo = &deviceInfoArray[ deviceIndex ]; + PaDeviceInfo *deviceInfo = &wmmeDeviceInfo->inheritedDeviceInfo; + deviceInfo->structVersion = 3; + deviceInfo->hostApi = hostApiIndex; + + deviceInfo->maxInputChannels = 0; + wmmeDeviceInfo->deviceInputChannelCountIsKnown = 1; + deviceInfo->maxOutputChannels = 0; + wmmeDeviceInfo->deviceOutputChannelCountIsKnown = 1; + + deviceInfo->defaultLowInputLatency = defaultLowLatency; + deviceInfo->defaultLowOutputLatency = defaultLowLatency; + deviceInfo->defaultHighInputLatency = defaultHighLatency; + deviceInfo->defaultHighOutputLatency = defaultHighLatency; + + result = InitializeOutputDeviceInfo( winMmeHostApi, wmmeDeviceInfo, + winMmeDeviceId, &deviceInfoInitializationSucceeded ); + if( result != paNoError ) + continue; /* ignore error results here and just skip the device. */ + /* N.B. Earlier WMME versions failed scanning with an error here. */ + + if( deviceInfoInitializationSucceeded ){ + /* NOTE: wmmeDeviceInfo->deviceInterfaceName is now allocated and would need to be freed on error. */ + + if( winMmeScanResults->defaultOutputDevice == paNoDevice ) + { + /* if there is currently no default device, use the first one available */ + winMmeScanResults->defaultOutputDevice = deviceIndex; + } + else if( winMmeDeviceId == waveOutPreferredDevice ) + { + /* set the default device to the system preferred device */ + winMmeScanResults->defaultOutputDevice = deviceIndex; + } + + deviceInfo->connectionId = AssignDeviceConnectionId( + hostApi->deviceInfos, hostApi->info.deviceCount, + winMmeScanResults->deviceInfos, initializedInfosCount, + wmmeDeviceInfo->deviceInterfaceName, /* isInput= */ 0 ); + + winMmeScanResults->deviceInfos[ deviceIndex ] = deviceInfo; + + winMmeScanResults->outputDeviceCount++; + initializedInfosCount++; + } + } + } + } + + *scanResults_OUT = winMmeScanResults; + *newDeviceCount_OUT = initializedInfosCount; + return result; + +error: + if( winMmeScanResults ) + { + FreeDeviceInfos( winMmeHostApi->allocations, winMmeScanResults->deviceInfos, initializedInfosCount ); + } + + return result; +} + +static PaError CommitDeviceInfos( + struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, + void *scanResults, int deviceCount ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + PaError result = paNoError; + + (void)hostApiIndex; /* unused parameter */ + + /* Free any old memory which might be in the device info */ + if( hostApi->deviceInfos ) + { + FreeDeviceInfos( winMmeHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount ); + hostApi->deviceInfos = NULL; + } + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + winMmeHostApi->inputDeviceCount = 0; + winMmeHostApi->outputDeviceCount = 0; + + if( scanResults != NULL ) + { + PaWinMmeScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinMmeScanDeviceInfosResults * ) scanResults; + + if( deviceCount > 0 ) + { + /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ + hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; + hostApi->info.deviceCount = deviceCount; + hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; + hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; + winMmeHostApi->inputDeviceCount = scanDeviceInfosResults->inputDeviceCount; + winMmeHostApi->outputDeviceCount = scanDeviceInfosResults->outputDeviceCount; + } + + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, scanDeviceInfosResults ); + } + + return result; +} + +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, + void *scanResults, int deviceCount ) +{ + PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi; + + if( scanResults != NULL ) + { + PaWinMmeScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinMmeScanDeviceInfosResults * ) scanResults; + + if( scanDeviceInfosResults->deviceInfos ) + { + FreeDeviceInfos( winMmeHostApi->allocations, hostApi->deviceInfos, deviceCount ); + } + + PaUtil_GroupFreeMemory( winMmeHostApi->allocations, scanDeviceInfosResults ); + } + + return paNoError; +} + +/***********************************************************************************/ static unsigned long ComputeHostBufferCountForFixedBufferSizeFrames( unsigned long suggestedLatencyFrames, @@ -1803,7 +2125,6 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ); static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); @@ -1828,7 +2149,6 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ) { @@ -2352,7 +2672,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, unsigned long hostInputBufferCount; unsigned long framesPerHostOutputBuffer; unsigned long hostOutputBufferCount; - unsigned long framesPerBufferProcessorCall; + unsigned long framesPerBufferProcessorCall = 0; PaWinMmeDeviceAndChannelCount *inputDevices = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ unsigned long winMmeSpecificInputFlags = 0; unsigned long inputDeviceCount = 0; @@ -2409,6 +2729,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, suggestedInputLatency = 0.; inputStreamInfo = 0; hostInputSampleFormat = 0; + inputChannelMask = 0; } @@ -2459,6 +2780,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, outputStreamInfo = 0; hostOutputSampleFormat = 0; suggestedOutputLatency = 0.; + outputChannelMask = 0; } @@ -2580,8 +2902,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( inputParameters ) { result = InitializeWaveHandles( winMmeHostApi, &stream->input, - winMmeSpecificInputFlags, - stream->bufferProcessor.bytesPerHostInputSample, sampleRate, + winMmeSpecificInputFlags, sampleRate, inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ ); if( result != paNoError ) goto error; } @@ -2589,8 +2910,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( outputParameters ) { result = InitializeWaveHandles( winMmeHostApi, &stream->output, - winMmeSpecificOutputFlags, - stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, + winMmeSpecificOutputFlags, sampleRate, outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ ); if( result != paNoError ) goto error; } @@ -2885,7 +3205,7 @@ PA_THREAD_FUNC ProcessingThreadProc( void *pArg ) waitResult = WaitForMultipleObjects( eventCount, events, FALSE /* wait all = FALSE */, timeout ); if( waitResult == WAIT_FAILED ) { - result = paUnanticipatedHostError; + result = (DWORD)paUnanticipatedHostError; /** @todo FIXME/REVIEW: can't return host error info from an asyncronous thread. see http://www.portaudio.com/trac/ticket/143 */ done = 1; } @@ -3467,11 +3787,13 @@ static PaError StopStream( PaStream *s ) { PaError result = paNoError; PaWinMmeStream *stream = (PaWinMmeStream*)s; - int timeout; + DWORD timeoutMs, totalTimeoutMs; + DWORD waitStartTime, elapsedMs; DWORD waitResult; MMRESULT mmresult; signed int hostOutputBufferIndex; - unsigned int channel, waitCount, i; + unsigned int channel, waitCount, i; + unsigned int maxWaitCount; /** @todo REVIEW: the error checking in this function needs review. the basic @@ -3484,23 +3806,26 @@ static PaError StopStream( PaStream *s ) { /* callback stream */ + /* First-chance timeout is for draining the the buffer cleanly. Use a time longer than to total buffers duration. */ + timeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5) + 1; + /* Tell processing thread to stop generating more data and to let current data play out. */ stream->stopProcessing = 1; - /* Calculate timeOut longer than longest time it could take to return all buffers. */ - timeout = (int)(stream->allBuffersDurationMs * 1.5); - if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) - timeout = PA_MME_MIN_TIMEOUT_MSEC_; - PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); - waitResult = WaitForSingleObject( stream->processingThread, timeout ); + waitResult = WaitForSingleObject( stream->processingThread, timeoutMs ); if( waitResult == WAIT_TIMEOUT ) { /* try to abort */ + + /* Last-chance timeout results in no more than PA_MME_MIN_TIMEOUT_MSEC_ of additional waiting. */ + if( timeoutMs > PA_MME_MIN_TIMEOUT_MSEC_ ) + timeoutMs = PA_MME_MIN_TIMEOUT_MSEC_; + stream->abortProcessing = 1; SetEvent( stream->abortEvent ); - waitResult = WaitForSingleObject( stream->processingThread, timeout ); + waitResult = WaitForSingleObject( stream->processingThread, timeoutMs ); if( waitResult == WAIT_TIMEOUT ) { PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n")); @@ -3552,22 +3877,57 @@ static PaError StopStream( PaStream *s ) } - timeout = (stream->allBuffersDurationMs / stream->output.bufferCount) + 1; - if( timeout < PA_MME_MIN_TIMEOUT_MSEC_ ) - timeout = PA_MME_MIN_TIMEOUT_MSEC_; + /* + The purpose of the following wait/poll loop is to wait for + the output to play out (all queued buffers to be returned). + We expect all queued buffers to be returned to us within + allBuffersDurationMs (plus some margin to avoid cutting off + the tail). + When functioning as intended, WaitForSingleObject will wake + after each buffer completes, taking at most bufferCount + loop iterations. We're done when NoBuffersAreQueued() returns true. + + When not functioning as intended, we want to bound the + duration of the wait, but also poll frequently enough to + pick up an early-exit from NoBuffersAreQueued. + + Two mechanisms are used to ensure that this loop does not hang: + + - The total elapsed wait time is limited to totalTimeoutMs. + - The loop is limited to maxWaitCount iterations, and the + timeout for each iteration equals totalTimeoutMs/maxWaitCount. + + This combination should cover cases where the timers or + timeout times are unreliable (e.g. if WFSO takes longer + than timeoutMs to return). + */ + + totalTimeoutMs = (DWORD)(1.5 * stream->allBuffersDurationMs); + + /* poll every 1.5 buffer durations. */ + timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; + /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ + maxWaitCount = stream->output.bufferCount; + + waitStartTime = GetTickCount(); waitCount = 0; - while( !NoBuffersAreQueued( &stream->output ) && waitCount <= stream->output.bufferCount ) + while( !NoBuffersAreQueued( &stream->output ) && waitCount <= maxWaitCount ) { /* wait for MME to signal that a buffer is available */ - waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeoutMs ); if( waitResult == WAIT_FAILED ) { break; } else if( waitResult == WAIT_TIMEOUT ) { - /* keep waiting */ + elapsedMs = GetTickCount() - waitStartTime; + if( elapsedMs >= totalTimeoutMs ) + { + result = paTimedOut; + break; + } } ++waitCount; @@ -3739,9 +4099,19 @@ static PaError ReadStream( PaStream* s, unsigned long framesProcessed; signed int hostInputBufferIndex; DWORD waitResult; - DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); unsigned int channel, i; + /* timeout variables. see StopStream for a discussion. */ + DWORD totalTimeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5); + /* poll every 1.5 buffer durations. */ + DWORD timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; + /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ + unsigned int maxWaitCount = stream->output.bufferCount; + + unsigned int waitCount; + DWORD waitStartTime = 0; + DWORD elapsedMs; + if( PA_IS_INPUT_STREAM_(stream) ) { /* make a local copy of the user buffer pointer(s). this is necessary @@ -3761,9 +4131,13 @@ static PaError ReadStream( PaStream* s, ((void**)userBuffer)[i] = ((void**)buffer)[i]; } + waitCount = 0; + do{ if( CurrentInputBuffersAreDone( stream ) ) { + waitCount = 0; + if( NoBuffersAreQueued( &stream->input ) ) { /** @todo REVIEW: consider what to do if the input overflows. @@ -3808,8 +4182,13 @@ static PaError ReadStream( PaStream* s, framesRead += framesProcessed; }else{ + + if( waitCount == 0 ) + waitStartTime = GetTickCount(); + ++waitCount; + /* wait for MME to signal that a buffer is available */ - waitResult = WaitForSingleObject( stream->input.bufferEvent, timeout ); + waitResult = WaitForSingleObject( stream->input.bufferEvent, timeoutMs ); if( waitResult == WAIT_FAILED ) { result = paUnanticipatedHostError; @@ -3817,10 +4196,25 @@ static PaError ReadStream( PaStream* s, } else if( waitResult == WAIT_TIMEOUT ) { - /* if a timeout is encountered, continue, - perhaps we should give up eventually - */ - } + /* + Repeated timeouts with no forward progress on done buffers + has been reported to happen after disconnecting a device (Jitsi). + + Limit the number and duration of waits, then time out. + */ + if( waitCount >= maxWaitCount ) + { + result = paTimedOut; + break; + } + + elapsedMs = GetTickCount() - waitStartTime; + if( elapsedMs >= totalTimeoutMs ) + { + result = paTimedOut; + break; + } + } } }while( framesRead < frames ); } @@ -3844,10 +4238,19 @@ static PaError WriteStream( PaStream* s, unsigned long framesProcessed; signed int hostOutputBufferIndex; DWORD waitResult; - DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); unsigned int channel, i; - + /* timeout variables. see StopStream for a discussion. */ + DWORD totalTimeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5); + /* poll every 1.5 buffer durations. */ + DWORD timeoutMs = (DWORD)((1.5 * stream->allBuffersDurationMs) / stream->output.bufferCount) + 1; + /* for a total of maxWaitCount iterations, duration: maxWaitCount*timeoutMs = 1.5 * stream->allBuffersDurationMs */ + unsigned int maxWaitCount = stream->output.bufferCount; + + unsigned int waitCount; + DWORD waitStartTime = 0; + DWORD elapsedMs; + if( PA_IS_OUTPUT_STREAM_(stream) ) { /* make a local copy of the user buffer pointer(s). this is necessary @@ -3867,9 +4270,13 @@ static PaError WriteStream( PaStream* s, ((const void**)userBuffer)[i] = ((const void**)buffer)[i]; } + waitCount = 0; + do{ if( CurrentOutputBuffersAreDone( stream ) ) { + waitCount = 0; + if( NoBuffersAreQueued( &stream->output ) ) { /** @todo REVIEW: consider what to do if the output @@ -3916,8 +4323,12 @@ static PaError WriteStream( PaStream* s, } else { + if( waitCount == 0 ) + waitStartTime = GetTickCount(); + ++waitCount; + /* wait for MME to signal that a buffer is available */ - waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); + waitResult = WaitForSingleObject( stream->output.bufferEvent, timeoutMs ); if( waitResult == WAIT_FAILED ) { result = paUnanticipatedHostError; @@ -3925,9 +4336,24 @@ static PaError WriteStream( PaStream* s, } else if( waitResult == WAIT_TIMEOUT ) { - /* if a timeout is encountered, continue, - perhaps we should give up eventually - */ + /* + Repeated timeouts with no forward progress on done buffers + has been reported to happen after disconnecting a device (Jitsi). + + Limit the number and duration of waits, then time out. + */ + if( waitCount >= maxWaitCount ) + { + result = paTimedOut; + break; + } + + elapsedMs = GetTickCount() - waitStartTime; + if( elapsedMs >= totalTimeoutMs ) + { + result = paTimedOut; + break; + } } } }while( framesWritten < frames ); 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; +} + diff --git a/test/patest_unplug.c b/test/patest_unplug.c index ba55b7d..f9b8997 100644 --- a/test/patest_unplug.c +++ b/test/patest_unplug.c @@ -59,9 +59,9 @@ typedef struct { short sine[TABLE_SIZE]; - int32_t phases[MAX_CHANNELS]; - int32_t numChannels; - int32_t sampsToGo; + long phases[MAX_CHANNELS]; + long numChannels; + long sampsToGo; } paTestData; @@ -171,7 +171,7 @@ int main(int argc, char **args) goto error; } - inputParameters.channelCount = 2; + inputParameters.channelCount = 1; inputParameters.sampleFormat = paInt16; deviceInfo = Pa_GetDeviceInfo( inputParameters.device ); if( deviceInfo == NULL ) @@ -227,10 +227,21 @@ int main(int argc, char **args) printf("Pa_IsStreamActive(outputStream) = %d\n", Pa_IsStreamActive(outputStream)); } while( Pa_IsStreamActive(inputStream) && Pa_IsStreamActive(outputStream) ); +#if 1 + err = Pa_StopStream( inputStream ); + if( err != paNoError ) goto error; + printf("Input stream stopped OK.\n"); + err = Pa_StopStream( outputStream ); + if( err != paNoError ) goto error; + printf("Output stream stopped OK.\n"); +#endif + err = Pa_CloseStream( inputStream ); if( err != paNoError ) goto error; + printf("Input stream closed OK.\n"); err = Pa_CloseStream( outputStream ); if( err != paNoError ) goto error; + printf("Output stream closed OK.\n"); Pa_Terminate(); return paNoError; error: diff --git a/test/patest_unplug_readwrite.c b/test/patest_unplug_readwrite.c new file mode 100644 index 0000000..da57fe2 --- /dev/null +++ b/test/patest_unplug_readwrite.c @@ -0,0 +1,273 @@ +/** @file patest_unplug.c + @ingroup test_src + @brief Debug a crash involving unplugging a USB device. + @author Phil Burk http://www.softsynth.com +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and 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 +#include +#include +#include +#include "portaudio.h" + +#define NUM_SECONDS (8) +#define SAMPLE_RATE (44100) +#ifndef M_PI +#define M_PI (3.14159265) +#endif +#define TABLE_SIZE (200) +#define FRAMES_PER_BUFFER (64) +#define MAX_CHANNELS (8) + +#define INPUT_CHANNELS (1) /* 1 so it works with headset mic */ +#define OUTPUT_CHANNELS (2) + +typedef struct +{ + short sine[TABLE_SIZE]; + long phases[MAX_CHANNELS]; + long numChannels; +} +paTestData; + +static void generateSine( short *out, + unsigned long framesPerBuffer, + paTestData *data ) +{ + unsigned int i; + int channelIndex; + + for( i=0; inumChannels; channelIndex++) + { + int phase = data->phases[channelIndex]; + *out++ = data->sine[phase]; + phase += channelIndex + 2; + if( phase >= TABLE_SIZE ) phase -= TABLE_SIZE; + data->phases[channelIndex] = phase; + } + } +} + +/*******************************************************************/ +int main(int argc, char **args); +int main(int argc, char **args) +{ + PaStreamParameters inputParameters; + PaStreamParameters outputParameters; + PaStream *inputStream; + PaStream *outputStream; + const PaDeviceInfo *deviceInfo; + PaError err; + paTestData data; + int i; + int totalSamps; + int inputDevice = -1; + int outputDevice = -1; + int sampsToGo; + short inputBuffer[INPUT_CHANNELS * FRAMES_PER_BUFFER]; + short outputBuffer[OUTPUT_CHANNELS * FRAMES_PER_BUFFER]; + + printf("Test unplugging a USB device.\n"); + + if( argc > 1 ) { + inputDevice = outputDevice = atoi( args[1] ); + printf("Using device number %d.\n\n", inputDevice ); + } else { + printf("Using default device.\n\n" ); + } + + memset(&data, 0, sizeof(data)); + + /* initialise sinusoidal wavetable */ + for( i=0; idefaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &inputStream, + &inputParameters, + NULL, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, + NULL, + &data ); + if( err != paNoError ) goto error; + + outputParameters.channelCount = OUTPUT_CHANNELS; + outputParameters.sampleFormat = paInt16; + deviceInfo = Pa_GetDeviceInfo( outputParameters.device ); + if( deviceInfo == NULL ) + { + fprintf( stderr, "No matching output device.\n" ); + goto error; + } + outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream( + &outputStream, + NULL, + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + (paClipOff | paDitherOff), + NULL, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( inputStream ); + if( err != paNoError ) goto error; + err = Pa_StartStream( outputStream ); + if( err != paNoError ) goto error; + + printf("When you hear sound, unplug the USB device.\n"); + do + { + signed long available; + available = Pa_GetStreamReadAvailable(inputStream); + while( available > 0 ) { + if( available > FRAMES_PER_BUFFER ) + available = FRAMES_PER_BUFFER; + + err = Pa_ReadStream( inputStream, inputBuffer, available ); /* reading <= available means don't block */ + if( err != paNoError ) goto done; /* Move on to stopping stream */ + + sampsToGo -= available; + + available = Pa_GetStreamReadAvailable(inputStream); + } + + available = Pa_GetStreamWriteAvailable(outputStream); + while( available > 0 ) { + if( available > FRAMES_PER_BUFFER ) + available = FRAMES_PER_BUFFER; + + generateSine( outputBuffer, available, &data ); + err = Pa_WriteStream( outputStream, outputBuffer, available ); /* reading <= available means don't block */ + if( err != paNoError ) goto done; /* Move on to stopping stream */ + + available = Pa_GetStreamWriteAvailable(outputStream); + } + + Pa_Sleep(1); + + printf("Frames remaining = %d\n", sampsToGo); + printf("Pa_IsStreamActive(inputStream) = %d\n", Pa_IsStreamActive(inputStream)); + printf("Pa_IsStreamActive(outputStream) = %d\n", Pa_IsStreamActive(outputStream)); + } while( Pa_IsStreamActive(inputStream) && Pa_IsStreamActive(outputStream) && sampsToGo > 0 ); +done: + +#if 1 + printf("Stopping input stream...\n"); + err = Pa_StopStream( inputStream ); + if( err != paNoError ) goto error; + printf("Input stream stopped OK.\n"); + + printf("Stopping output stream...\n"); + err = Pa_StopStream( outputStream ); + if( err != paNoError ) goto error; + printf("Output stream stopped OK.\n"); +#endif + +#if 0 + err = Pa_AbortStream( inputStream ); + if( err != paNoError ) goto error; + printf("Input stream stopped OK.\n"); + err = Pa_AbortStream( outputStream ); + if( err != paNoError ) goto error; + printf("Output stream stopped OK.\n"); +#endif + + err = Pa_CloseStream( inputStream ); + if( err != paNoError ) goto error; + printf("Input stream closed OK.\n"); + err = Pa_CloseStream( outputStream ); + if( err != paNoError ) goto error; + printf("Output stream closed OK.\n"); + Pa_Terminate(); + return paNoError; +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + fprintf( stderr, "Host Error message: %s\n", Pa_GetLastHostErrorInfo()->errorText ); + return err; +}