From b8d4bd665db4c89c159c51c807628295a3ada5f9 Mon Sep 17 00:00:00 2001 From: Ross Bencina Date: Tue, 30 Aug 2016 18:10:38 +1000 Subject: [PATCH] An implementation of runtime host API selection (see ticket #10). See the changes to portaudio.h for documentation for the API. Note that unlike other proposals, this version of the interface allows the client to specify the order of host API initialization. The API is excercised by patest_select_hostapis.c. To reviewers: please verify that the implementation of Pa_SelectHostApis, Pa_GetSelectedHostApis and Pa_GetAvailableHostApis in pa_front.c matches the documentation in portaudio.h --- include/portaudio.h | 58 +++++++++++++ src/common/pa_front.c | 154 +++++++++++++++++++++++++++++++-- src/common/pa_hostapi.h | 18 +++- src/os/unix/pa_unix_hostapis.c | 23 ++--- src/os/win/pa_win_hostapis.c | 17 ++-- test/patest_select_hostapis.c | 148 +++++++++++++++++++++++++++++++ 6 files changed, 390 insertions(+), 28 deletions(-) create mode 100644 test/patest_select_hostapis.c diff --git a/include/portaudio.h b/include/portaudio.h index 3bb3b31..6d90d2f 100644 --- a/include/portaudio.h +++ b/include/portaudio.h @@ -274,6 +274,64 @@ typedef enum PaHostApiTypeId } PaHostApiTypeId; +/** Select host APIs and their initialization order. + + The selected host APIs take effect the next time that Pa_Initialize() is + invoked. + + @param hostApiTypes An array of host API identifiers belonging to the + PaHostApiTypeId enumeration. + + @param count The number of elements in the hostApiTypes array. A count of + zero causes the default host API selection to be restored. + + @return A PaErrorCode indicating whether the call succeeded or failed. + + The paHostApiNotFound error code indicates that a host API specified by the + hostApiTypeIds parameter is not available. + + The paInvalidHostApi error indicates that there was a problem with the + hostApiTypes array. E.g. it contained duplicate entries. + + @note The host API initialization order determines default devices. + + @see PaHostApiTypeId +*/ +PaError Pa_SelectHostApis( const PaHostApiTypeId *hostApiTypes, int count ); + +/** Returns the type ids of the selected host APIs in initialization order. + + @param hostApiTypes (IN/OUT) An array that will be filled with + host API identifiers belonging to the PaHostApiTypeId enumeration. + + @param count (OUT) The number of selected host APIs, returned even on error. + + @param countAvailable The number of available elements in the hostApiTypes array. + + @return A PaErrorCode indicating whether the call succeeded or failed. + + The paInsufficientMemory error indicates that countAvailable was not large enough + to accommodate the list of selected host APIs. In this case, the needed + count is returned in the count parameter. + + @see Pa_SelectHostApis +*/ +PaError Pa_GetSelectedHostApis( PaHostApiTypeId *hostApiTypes, int countAvailable, int *count ); + +/** Returns the type ids of all available host APIs in initialization order. + + @param hostApiTypes (IN/OUT) An array that will be filled with + host API identifiers belonging to the PaHostApiTypeId enumeration. + + @param count (OUT) The number of host APIs, returned even on error. + + @param countAvailable The number of available elements in the hostApiTypes array. + + @see Pa_SelectHostApis +*/ +PaError Pa_GetAvailableHostApis( PaHostApiTypeId *hostApiTypes, int countAvailable, int *count ); + + /** A structure containing information about a particular host API. */ typedef struct PaHostApiInfo diff --git a/src/common/pa_front.c b/src/common/pa_front.c index 0632710..ba831b9 100644 --- a/src/common/pa_front.c +++ b/src/common/pa_front.c @@ -152,7 +152,6 @@ void PaUtil_SetLastHostErrorInfo( PaHostApiTypeId hostApiType, long errorCode, } - static PaUtilHostApiRepresentation **hostApis_ = 0; static int hostApisCount_ = 0; static int defaultHostApiIndex_ = 0; @@ -161,19 +160,161 @@ static int deviceCount_ = 0; PaUtilStreamRepresentation *firstOpenStream_ = NULL; - #define PA_IS_INITIALISED_ (initializationCount_ != 0) +/* + By default, selectedHostApiTypes_ == NULL and PortAudio initializes all + host APIs in the order that they are listed in paHostApiInitializers[]. + + If selectedHostApiTypes_ != NULL, then APIs are initialized in the + order specified in selectedHostApiTypes_. +*/ +static PaHostApiTypeId *selectedHostApiTypes_ = NULL; +static int selectedHostApiCount_ = 0; static int CountHostApiInitializers( void ) { int result = 0; - while( paHostApiInitializers[ result ] != 0 ) + while( paHostApiInitializers[ result ].initFunction != 0 ) ++result; return result; } +static const PaUtilHostApiInitializerEntry* FindHostApiInitializerEntry( PaHostApiTypeId hostApiType ) +{ + int i = 0; + + while( paHostApiInitializers[ i ].initFunction != 0 ) + { + if( paHostApiInitializers[i].hostApiType == hostApiType ) + return &paHostApiInitializers[i]; + ++i; + } + + return NULL; +} + +static int CountSelectedHostApis() +{ + if( selectedHostApiTypes_ == NULL ) + return CountHostApiInitializers(); + else + return selectedHostApiCount_; +} + +static const PaUtilHostApiInitializerEntry* GetSelectedHostApi( int index ) +{ + if( selectedHostApiTypes_ == NULL ) + return &paHostApiInitializers[index]; + else + return FindHostApiInitializerEntry( selectedHostApiTypes_[index] ); +} + +PaError Pa_SelectHostApis( const PaHostApiTypeId *hostApiTypes, int count ) +{ + int i, j; + PaHostApiTypeId *oldSelectedHostApiTypes = selectedHostApiTypes_; + PaHostApiTypeId *newSelectedHostApiTypes = NULL; + + if( count == 0 ) + { + /* revert to default state */ + if( selectedHostApiTypes_ ) + { + PaUtil_FreeMemory( selectedHostApiTypes_ ); + selectedHostApiTypes_ = NULL; + selectedHostApiCount_ = 0; + } + + return paNoError; + } + + if( count < 0 ) + return paInvalidHostApi; + + if( hostApiTypes == NULL ) + return paInvalidHostApi; + + /* validation: + - verify that each hostApiTypes value is available (present in paHostApiInitializers) + - verify that hostApiTypes contains no duplicates + */ + for( i=0; i < count; ++i ) + { + if( FindHostApiInitializerEntry( hostApiTypes[i] ) == NULL ) + return paHostApiNotFound; + + for( j=i+1; j < count; ++j ) + { + if (hostApiTypes[i] == hostApiTypes[j]) + return paInvalidHostApi; + } + } + + /* allocate a newSelectedHostApiTypes, copy ids into it */ + + newSelectedHostApiTypes = (PaHostApiTypeId*)PaUtil_AllocateMemory( + sizeof(PaHostApiTypeId) * count ); + if( newSelectedHostApiTypes == NULL ) + return paInsufficientMemory; + + memcpy( newSelectedHostApiTypes, hostApiTypes, count*sizeof(PaHostApiTypeId) ); + + /* install new selectedHostApis and free old selectedHostApis_ */ + + selectedHostApiTypes_ = newSelectedHostApiTypes; + selectedHostApiCount_ = count; + + if( oldSelectedHostApiTypes != NULL ) + { + PaUtil_FreeMemory( oldSelectedHostApiTypes ); + oldSelectedHostApiTypes = NULL; + } + + return paNoError; +} + +PaError Pa_GetSelectedHostApis( PaHostApiTypeId *hostApiTypes, int countAvailable, int *count ) +{ + if( selectedHostApiTypes_ == NULL ) + { + return Pa_GetAvailableHostApis( hostApiTypes, countAvailable, count ); + } + else + { + *count = selectedHostApiCount_; + if( countAvailable >= selectedHostApiCount_ ) + { + memcpy( hostApiTypes, selectedHostApiTypes_, selectedHostApiCount_*sizeof(PaHostApiTypeId) ); + return paNoError; + } + else + { + return paInsufficientMemory; + } + } +} + +PaError Pa_GetAvailableHostApis( PaHostApiTypeId *hostApiTypes, int countAvailable, int *count ) +{ + int i; + int initializerCount = CountHostApiInitializers(); + + *count = initializerCount; + if( countAvailable >= initializerCount ) + { + for( i=0; i < initializerCount; ++i ) + hostApiTypes[i] = paHostApiInitializers[i].hostApiType; + } + else + { + return paInsufficientMemory; + } + + return paNoError; +} + static void TerminateHostApis( void ) { @@ -201,8 +342,9 @@ static PaError InitializeHostApis( void ) { PaError result = paNoError; int i, initializerCount, baseDeviceIndex; + const PaUtilHostApiInitializerEntry *hostApiInitializer; - initializerCount = CountHostApiInitializers(); + initializerCount = CountSelectedHostApis(); hostApis_ = (PaUtilHostApiRepresentation**)PaUtil_AllocateMemory( sizeof(PaUtilHostApiRepresentation*) * initializerCount ); @@ -223,7 +365,9 @@ static PaError InitializeHostApis( void ) PA_DEBUG(( "before paHostApiInitializers[%d].\n",i)); - result = paHostApiInitializers[i]( &hostApis_[hostApisCount_], hostApisCount_ ); + hostApiInitializer = GetSelectedHostApi(i); + assert( hostApiInitializer != NULL ); + result = hostApiInitializer->initFunction( &hostApis_[hostApisCount_], hostApisCount_ ); if( result != paNoError ) goto error; diff --git a/src/common/pa_hostapi.h b/src/common/pa_hostapi.h index 54b527e..c954231 100644 --- a/src/common/pa_hostapi.h +++ b/src/common/pa_hostapi.h @@ -338,12 +338,22 @@ typedef struct PaUtilHostApiRepresentation { */ typedef PaError PaUtilHostApiInitializer( PaUtilHostApiRepresentation**, PaHostApiIndex ); +/** Associate a PaHostApiTypeId with each host API initialization function. + +*/ +typedef struct { + PaHostApiTypeId hostApiType; + PaUtilHostApiInitializer *initFunction; +} PaUtilHostApiInitializerEntry; + /** paHostApiInitializers is a NULL-terminated array of host API initialization - functions. These functions are called by pa_front.c to initialize the host APIs - when the client calls Pa_Initialize(). + entries, each containing an initialization function. The initialization + functions are called by pa_front.c to initialize the host APIs when the client + calls Pa_Initialize(). - The initialization functions are invoked in order. + By default initialization functions are invoked in order. The initialization + order may be modified by the client using Pa_SelectHostApis(). The first successfully initialized host API that has a default input *or* output device is used as the default PortAudio host API. This is based on the logic that @@ -353,7 +363,7 @@ typedef PaError PaUtilHostApiInitializer( PaUtilHostApiRepresentation**, PaHostA There is a platform specific file that defines paHostApiInitializers for that platform, pa_win/pa_win_hostapis.c contains the Win32 definitions for example. */ -extern PaUtilHostApiInitializer *paHostApiInitializers[]; +extern PaUtilHostApiInitializerEntry paHostApiInitializers[]; #ifdef __cplusplus diff --git a/src/os/unix/pa_unix_hostapis.c b/src/os/unix/pa_unix_hostapis.c index a9b4a05..cc297a5 100644 --- a/src/os/unix/pa_unix_hostapis.c +++ b/src/os/unix/pa_unix_hostapis.c @@ -55,49 +55,50 @@ PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiI /** Note that on Linux, ALSA is placed before OSS so that the former is preferred over the latter. */ -PaUtilHostApiInitializer *paHostApiInitializers[] = +PaUtilHostApiInitializerEntry paHostApiInitializers[] = { #ifdef __linux__ #if PA_USE_ALSA - PaAlsa_Initialize, + { paALSA, PaAlsa_Initialize }, #endif #if PA_USE_OSS - PaOSS_Initialize, + { paOSS, PaOSS_Initialize }, #endif #else /* __linux__ */ #if PA_USE_OSS - PaOSS_Initialize, + { paOSS, PaOSS_Initialize }, #endif #if PA_USE_ALSA - PaAlsa_Initialize, + { paALSA, PaAlsa_Initialize }, #endif #endif /* __linux__ */ #if PA_USE_JACK - PaJack_Initialize, + { paJACK, PaJack_Initialize }, #endif /* Added for IRIX, Pieter, oct 2, 2003: */ #if PA_USE_SGI - PaSGI_Initialize, + { paAL, PaSGI_Initialize }, #endif #if PA_USE_ASIHPI - PaAsiHpi_Initialize, + { paAudioScienceHPI, PaAsiHpi_Initialize }, #endif #if PA_USE_COREAUDIO - PaMacCore_Initialize, + { paCoreAudio, PaMacCore_Initialize }, #endif #if PA_USE_SKELETON - PaSkeleton_Initialize, + /* just for testing. last in list so it isn't marked as default. */ + { paInDevelopment, PaSkeleton_Initialize }, #endif - 0 /* NULL terminated array */ + { -1, 0 }, /* NULL terminated array */ }; diff --git a/src/os/win/pa_win_hostapis.c b/src/os/win/pa_win_hostapis.c index 9c9927a..980c368 100644 --- a/src/os/win/pa_win_hostapis.c +++ b/src/os/win/pa_win_hostapis.c @@ -69,34 +69,35 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd #endif /* __cplusplus */ -PaUtilHostApiInitializer *paHostApiInitializers[] = +PaUtilHostApiInitializerEntry paHostApiInitializers[] = { #if PA_USE_WMME - PaWinMme_Initialize, + { paMME, PaWinMme_Initialize }, #endif #if PA_USE_DS - PaWinDs_Initialize, + { paDirectSound, PaWinDs_Initialize }, #endif #if PA_USE_ASIO - PaAsio_Initialize, + { paASIO, PaAsio_Initialize }, #endif #if PA_USE_WASAPI - PaWasapi_Initialize, + { paWASAPI, PaWasapi_Initialize }, #endif #if PA_USE_WDMKS - PaWinWdm_Initialize, + { paWDMKS, PaWinWdm_Initialize }, #endif #if PA_USE_SKELETON - PaSkeleton_Initialize, /* just for testing. last in list so it isn't marked as default. */ + /* just for testing. last in list so it isn't marked as default. */ + { paInDevelopment, PaSkeleton_Initialize }, #endif - 0 /* NULL terminated array */ + { -1, 0 }, /* NULL terminated array */ }; diff --git a/test/patest_select_hostapis.c b/test/patest_select_hostapis.c new file mode 100644 index 0000000..a50ef60 --- /dev/null +++ b/test/patest_select_hostapis.c @@ -0,0 +1,148 @@ +/** @file patest_select_hostapis.c + @ingroup test_src + @brief Test of Pa_SelectHostApis + @author Ross Bencina +*/ +/* + * $Id$ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com/ + * Copyright (c) 1999-2016 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 "portaudio.h" + +/*******************************************************************/ +int main(void); +int main(void) +{ + int i, j, k; + int availableHostApiCount, scratchHostApiCount; +#define MAX_HOST_API_COUNT 100 + PaHostApiTypeId availableHostApis[MAX_HOST_API_COUNT]; + PaHostApiTypeId scratchHostApis[MAX_HOST_API_COUNT]; + PaError err; + + availableHostApiCount = 0; + err = Pa_GetAvailableHostApis( availableHostApis, MAX_HOST_API_COUNT, &availableHostApiCount ); + assert( err == paNoError ); + assert( availableHostApiCount > 0 ); + + printf("available host api type ids:\n"); + for (i = 0; i < availableHostApiCount; ++i ) + { + printf("%d\n", availableHostApis[i]); + } + + err = Pa_Initialize(); + assert( err == paNoError ); + Pa_Terminate(); + + /* excercise Pa_SelectHostApis and Pa_GetSelectedHostApis */ + + /* each API in turn */ + for(i = 0; i < availableHostApiCount; ++i) + { + PaHostApiTypeId hostApiType = availableHostApis[i]; + + printf("selecting api type %d\n", hostApiType); + + err = Pa_SelectHostApis(&hostApiType, 1); + assert( err == paNoError ); + + /* read back and verify selected apis*/ + err = Pa_GetSelectedHostApis( scratchHostApis, MAX_HOST_API_COUNT, &scratchHostApiCount ); + assert( err == paNoError ); + assert( scratchHostApiCount == 1 ); + assert( scratchHostApis[0] == hostApiType ); + + err = Pa_Initialize(); + assert( err == paNoError ); + + /* verify that all devices match the selected API */ + + for(j = 0; j < Pa_GetDeviceCount(); ++j) + { + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(j); + assert( deviceInfo != NULL ); + assert( Pa_GetHostApiInfo(deviceInfo->hostApi)->type == hostApiType ); + } + + Pa_Terminate(); + } + + /* i counts 1 .. n simultaneously selected APIs */ + for(i = 1; i < availableHostApiCount; ++i) + { + printf("selecting %d apis\n", i); + + err = Pa_SelectHostApis(availableHostApis, i); + assert( err == paNoError ); + + /* read back and verify selected apis*/ + err = Pa_GetSelectedHostApis( scratchHostApis, MAX_HOST_API_COUNT, &scratchHostApiCount ); + assert( err == paNoError ); + assert( scratchHostApiCount == i ); + for( j = 0; j < i; ++j ) + assert( scratchHostApis[j] == availableHostApis[j] ); + + err = Pa_Initialize(); + assert( err == paNoError ); + + /* verify that all devices match one of the selected APIs */ + + for(j = 0; j < Pa_GetDeviceCount(); ++j) + { + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(j); + assert( deviceInfo != NULL ); + + /* search for whether the device's host API is one of the selected host APIs */ + err = paHostApiNotFound; + for(k = 0; k < i; ++k) + { + PaHostApiTypeId hostApiType = availableHostApis[k]; + if( Pa_GetHostApiInfo(deviceInfo->hostApi)->type == hostApiType ) + { + err = paNoError; + break; + } + } + + assert( err != paHostApiNotFound ); + } + + Pa_Terminate(); + } +} -- 2.43.0