From: Andrew Gundersen Date: Fri, 20 Aug 2021 16:09:19 +0000 (-0500) Subject: Pa_RefreshDevice functionality X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=1f01a3ddc05df7ee3bb3cad5ef960d335aff018b;p=portaudio Pa_RefreshDevice functionality --- diff --git a/examples/paex_record_file.c b/examples/paex_record_file.c index 562a8e9..c437d19 100644 --- a/examples/paex_record_file.c +++ b/examples/paex_record_file.c @@ -258,7 +258,6 @@ static int recordCallback( const void *inputBuffer, void *outputBuffer, data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite); - return paContinue; } /* This routine will be called by the PortAudio engine when audio is needed. @@ -272,8 +271,11 @@ static int playCallback( const void *inputBuffer, void *outputBuffer, void *userData ) { paTestData *data = (paTestData*)userData; + ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer); - ring_buffer_size_t elementsToRead = rbs_min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS)); + + ring_buffer_size_t elementsToRead = rbs_min( elementsToPlay, + (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS) ); SAMPLE* wptr = (SAMPLE*)outputBuffer; (void) inputBuffer; /* Prevent unused variable warnings. */ diff --git a/examples/paex_refresh_devs.c b/examples/paex_refresh_devs.c new file mode 100644 index 0000000..c31b233 --- /dev/null +++ b/examples/paex_refresh_devs.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include "portaudio.h" + +int main() +{ + PaError err = paNoError; + + PaDeviceIndex defaultOutputDevice; + PaDeviceIndex defaultInputDevice; + + err = Pa_Initialize(); + if( err != paNoError ) + { + printf( "ERROR: Pa_Initialize returned 0x%x\n", err ); + goto error; + } + + printf( "PortAudio version: 0x%08X\n", Pa_GetVersion()); + // printf( "Version text: '%s'\n", Pa_GetVersionInfo()->versionText ); + + const int count = 20; + for (int i = 0; i < count; ++i) + { + defaultInputDevice = Pa_GetDefaultInputDevice(); + defaultOutputDevice = Pa_GetDefaultOutputDevice(); + printf("Input: %i, Output: %i\n", defaultInputDevice, defaultOutputDevice); + + err = Pa_RefreshDevices(); + if( err != paNoError ) + { + printf( "ERROR: Pa_RefreshDevices returned 0x%x\n", err ); + goto error; + } + + sleep(2); + } + + Pa_Terminate(); + + printf("----------------------------------------------\n"); + return 0; +error: + return 1; +} \ No newline at end of file diff --git a/examples/paex_sine.c b/examples/paex_sine.c index 50ef205..99ae625 100644 --- a/examples/paex_sine.c +++ b/examples/paex_sine.c @@ -154,6 +154,7 @@ int main(void) if( err != paNoError ) goto error; printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); err = Pa_StopStream( stream ); diff --git a/include/portaudio.h b/include/portaudio.h index c3e1744..8ee9289 100644 --- a/include/portaudio.h +++ b/include/portaudio.h @@ -449,6 +449,17 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void ); PaDeviceIndex Pa_GetDefaultOutputDevice( void ); +/** Normally the list of devices is frozen after initialization, until the + next time port audio is re-initialized. This provides API stability but + has the side effect that hot-pluggable devices such as USB audio headsets + might not be up-to-date while the program is running. This method is used + to refresh the list of devices within port audio without closing open + streams. + @return an error code which indicates whether the device refresh was successful. + */ + PaError Pa_RefreshDevices( void ); + + /** 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(). diff --git a/src/common/pa_front.c b/src/common/pa_front.c index 8da873a..bd19cd9 100644 --- a/src/common/pa_front.c +++ b/src/common/pa_front.c @@ -743,6 +743,55 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void ) } +PaError Pa_RefreshDevices( void ) + { + int i, baseDeviceIndex; + PaError result = paNoError; + + PA_LOGAPI_ENTER( "Pa_RefreshDeviceList" ); + if( !PA_IS_INITIALISED_ ) + return paNotInitialized; + + baseDeviceIndex = 0; + deviceCount_ = 0; + + for( i=0; i < hostApisCount_; ++i ) + { + PaUtilHostApiRepresentation *hostApi = hostApis_[i]; + if( hostApi->RefreshDevices == NULL ) + { + /* RefreshDevices 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(( "refreshing device list for host api %d.\n",i)); + + if(( result = hostApi->RefreshDevices( hostApi, i )) != paNoError ) + return result; + + 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; + } + + return result; + } + + const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) { int hostSpecificDeviceIndex; diff --git a/src/common/pa_hostapi.h b/src/common/pa_hostapi.h index 4ac3ab6..0af8d53 100644 --- a/src/common/pa_hostapi.h +++ b/src/common/pa_hostapi.h @@ -322,6 +322,8 @@ typedef struct PaUtilHostApiRepresentation { const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); + + PaError (*RefreshDevices)( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index ); } PaUtilHostApiRepresentation; diff --git a/src/hostapi/coreaudio/pa_mac_core.c b/src/hostapi/coreaudio/pa_mac_core.c index aaa5bf4..4a9172b 100644 --- a/src/hostapi/coreaudio/pa_mac_core.c +++ b/src/hostapi/coreaudio/pa_mac_core.c @@ -269,6 +269,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaStreamFlags streamFlags, PaStreamCallback *streamCallback, void *userData ); +static PaError RefreshDevices( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index ); static PaError CloseStream( PaStream* stream ); static PaError StartStream( PaStream *stream ); static PaError StopStream( PaStream *stream ); @@ -820,6 +821,7 @@ PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIn (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->RefreshDevices = RefreshDevices; PaUtil_InitializeStreamInterface( &auhalHostApi->callbackStreamInterface, CloseStream, StartStream, @@ -2096,6 +2098,85 @@ error: #define HOST_TIME_TO_PA_TIME( x ) ( AudioConvertHostTimeToNanos( (x) ) * 1.0E-09) /* convert to nanoseconds and then to seconds */ + +PaError RefreshDevices( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex ) +{ + PaMacAUHAL *auhalHostApi = (PaMacAUHAL*)hostApi; + PaError result = paNoError; + PaDeviceInfo *deviceInfoArray; + int i; + + VVDBUG(("RefreshDevices(): hostApiIndex=%d\n", hostApiIndex)); + + auhalHostApi->devIds = NULL; + auhalHostApi->devCount = 0; + + /* get the info we need about the devices */ + result = gatherDeviceInfo( auhalHostApi ); + if( result != paNoError ) + goto error; + + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + hostApi->info.deviceCount = 0; + + /* If the device infos already exist, free the old memory */ + if( hostApi->deviceInfos != NULL ) + { + PaUtil_GroupFreeMemory( auhalHostApi->allocations, hostApi->deviceInfos ); + hostApi->deviceInfos = NULL; + } + + if( auhalHostApi->devCount > 0 ) + { + hostApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo*) * auhalHostApi->devCount); + if( !hostApi->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo) * auhalHostApi->devCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < auhalHostApi->devCount; ++i ) + { + int err; + err = InitializeDeviceInfo( auhalHostApi, &deviceInfoArray[i], + auhalHostApi->devIds[i], + hostApiIndex ); + if (err == paNoError) + { /* copy some info and set the defaults */ + hostApi->deviceInfos[hostApi->info.deviceCount] = &deviceInfoArray[i]; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultIn) + hostApi->info.defaultInputDevice = hostApi->info.deviceCount; + if (auhalHostApi->devIds[i] == auhalHostApi->defaultOut) + hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; + hostApi->info.deviceCount++; + } + else + { /* there was an error. we need to shift the devices down, so we ignore this one */ + int j; + auhalHostApi->devCount--; + for( j=i; jdevCount; ++j ) + auhalHostApi->devIds[j] = auhalHostApi->devIds[j+1]; + i--; + } + } + } + +error: + return result; +} + + PaTime GetStreamTime( PaStream *s ) { return HOST_TIME_TO_PA_TIME( AudioGetCurrentHostTime() ); diff --git a/src/hostapi/dsound/pa_win_ds.c b/src/hostapi/dsound/pa_win_ds.c index 46587f9..a554bb1 100644 --- a/src/hostapi/dsound/pa_win_ds.c +++ b/src/hostapi/dsound/pa_win_ds.c @@ -190,6 +190,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate ); +static PaError RefreshDevices( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index ); static PaError CloseStream( PaStream* stream ); static PaError StartStream( PaStream *stream ); static PaError StopStream( PaStream *stream ); @@ -1325,6 +1326,7 @@ PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInde (*hostApi)->Terminate = Terminate; (*hostApi)->OpenStream = OpenStream; (*hostApi)->IsFormatSupported = IsFormatSupported; + (*hostApi)->RefreshDevices = RefreshDevices; PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream, IsStreamStopped, IsStreamActive, @@ -1488,6 +1490,147 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, } +/***********************************************************************************/ +static PaError RefreshDevices( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + DSDeviceNamesAndGUIDs deviceNamesAndGUIDs; + PaWinDsDeviceInfo *deviceInfoArray; + int i = 0, deviceCount = 0; + char comWasInitialized = winDsHostApi->comWasInitialized; + PaError result = paNoError; + + if( !comWasInitialized ) + return paInternalError; + + /* initialise guid vectors so they can be safely deleted on error */ + deviceNamesAndGUIDs.winDsHostApi = NULL; + deviceNamesAndGUIDs.inputNamesAndGUIDs.items = NULL; + deviceNamesAndGUIDs.outputNamesAndGUIDs.items = NULL; + + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + /* DSound - enumerate devices to count them and to gather their GUIDs */ + result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + result = InitializeDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs, winDsHostApi->allocations ); + if( result != paNoError ) + goto error; + + paWinDsDSoundEntryPoints.DirectSoundCaptureEnumerateA( (LPDSENUMCALLBACK)CollectGUIDsProc, (void *)&deviceNamesAndGUIDs.inputNamesAndGUIDs ); + paWinDsDSoundEntryPoints.DirectSoundEnumerateA( (LPDSENUMCALLBACK)CollectGUIDsProc, (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; + } + + /* Free any old memory which might be in the device info */ + if( hostApi->deviceInfos ) + { + PaUtil_GroupFreeMemory( winDsHostApi->allocations, hostApi->deviceInfos ); + hostApi->deviceInfos = NULL; + } + + 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; + + return result; + +error: + if( winDsHostApi ) + { + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + } + + TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.inputNamesAndGUIDs ); + TerminateDSDeviceNameAndGUIDVector( &deviceNamesAndGUIDs.outputNamesAndGUIDs ); + + + return result; +} + + #ifdef PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, PaWinDsDeviceInfo *inputDevice,