diff options
Diffstat (limited to '3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c')
-rw-r--r-- | 3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c | 1452 |
1 files changed, 1452 insertions, 0 deletions
diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c new file mode 100644 index 00000000000..e784b65eeb0 --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c @@ -0,0 +1,1452 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen <tuukka.pasanen@ilmi.fi> + * Copyright (c) 2016 Sqweek + * Copyright (c) 2020 Daniel Schurmann + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 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 PulseAudio implementation of support for a host API. + + This host API implements PulseAudio support for portaudio + it has callbackmode and normal write mode support +*/ + + +#include <string.h> /* strlen() */ + +#include "pa_linux_pulseaudio_cb_internal.h" +#include "pa_linux_pulseaudio_block_internal.h" + +/* PulseAudio headers */ +#include <stdio.h> +#include <string.h> +#include <pulse/pulseaudio.h> + +/* This is used to identify process name for PulseAudio. */ +extern char *__progname; + +/* PulseAudio specific functions */ +int PaPulseAudio_CheckConnection( PaPulseAudio_HostApiRepresentation * ptr ) +{ + pa_context_state_t state; + + + /* Sanity check if ptr if NULL don't go anywhere or + * it will SIGSEGV + */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: Host API is NULL! Can't do anything about it" ); + return -1; + } + + if( !ptr->context || !ptr->mainloop ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: PulseAudio context or mainloop are NULL" ); + return -1; + } + + state = pa_context_get_state(ptr->context); + + if( !PA_CONTEXT_IS_GOOD(state) ) + { + switch( state ) + { + /* These can be found from + * https://freedesktop.org/software/pulseaudio/doxygen/def_8h.html + */ + + case PA_CONTEXT_UNCONNECTED: + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: The context hasn't been connected yet (PA_CONTEXT_UNCONNECTED)" ); + break; + + case PA_CONTEXT_FAILED: + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: The connection failed or was disconnected. (PA_CONTEXT_FAILED)" ); + break; + } + + return -1; + } + return 0; +} + +/* Create HostAPI presensentation */ +PaPulseAudio_HostApiRepresentation *PaPulseAudio_New( void ) +{ + PaPulseAudio_HostApiRepresentation *ptr; + int fd[2] = { -1, -1 }; + char pulseaudioDeviceName[PAPULSEAUDIO_MAX_DEVICENAME]; + + ptr = (PaPulseAudio_HostApiRepresentation *) + PaUtil_AllocateZeroInitializedMemory(sizeof(PaPulseAudio_HostApiRepresentation)); + + /* ptr is NULL if runs out of memory or pointer to allocated memory */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't allocate memory required for using PulseAudio" ); + return NULL; + } + + /* Make sure we have NULL all struct first */ + memset(ptr, 0x00, sizeof(PaPulseAudio_HostApiRepresentation)); + + ptr->mainloop = pa_threaded_mainloop_new(); + + if( !ptr->mainloop ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't allocate PulseAudio mainloop" ); + goto fail; + } + + ptr->mainloopApi = pa_threaded_mainloop_get_api(ptr->mainloop); + + /* Use program name as PulseAudio device name */ + snprintf( pulseaudioDeviceName, PAPULSEAUDIO_MAX_DEVICENAME, "%s", __progname ); + + ptr->context = + pa_context_new( pa_threaded_mainloop_get_api(ptr->mainloop), pulseaudioDeviceName ); + + if( !ptr->context ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't instantiate PulseAudio context" ); + goto fail; + } + + pa_context_set_state_callback( ptr->context, PaPulseAudio_CheckContextStateCb, + ptr ); + + + if( pa_threaded_mainloop_start( ptr->mainloop ) < 0 ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: PulseAudio can't start mainloop" ); + goto fail; + } + + ptr->deviceCount = 0; + + return ptr; + + fail: + PaPulseAudio_Free( ptr ); + return NULL; +} + +/* Free HostAPI */ +void PaPulseAudio_Free( PaPulseAudio_HostApiRepresentation * ptr ) +{ + + /* Sanity check if ptr if NULL don't go anywhere or + * it will SIGSEGV + */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_Free: Host API is NULL! Can't do anything about it" ); + return; + } + + if( ptr->mainloop ) + { + pa_threaded_mainloop_stop( ptr->mainloop ); + } + + if( ptr->context ) + { + pa_context_disconnect( ptr->context ); + pa_context_unref( ptr->context ); + ptr->context = NULL; + } + + if( ptr->mainloopApi && ptr->timeEvent ) + { + ptr->mainloopApi->time_free( ptr->timeEvent ); + ptr->mainloopApi = NULL; + ptr->timeEvent = NULL; + } + + + if( ptr->mainloop ) + { + pa_threaded_mainloop_free( ptr->mainloop ); + ptr->mainloop = NULL; + } + + PaUtil_FreeMemory( ptr ); +} + +/* If there is drop connection to server this one is called + * in future it should stop the stream also + */ +void PaPulseAudio_CheckContextStateCb( pa_context * c, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *ptr = + (PaPulseAudio_HostApiRepresentation *) userdata; + /* If this is null we have big problems and we probably are out of memory */ + if( !c ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckContextStateCb: Invalid context " ); + pa_threaded_mainloop_signal( ptr->mainloop, 0 ); + return; + } + + pa_threaded_mainloop_signal( ptr->mainloop, 0 ); +} + +/* Server info callback */ +void PaPulseAudio_ServerInfoCb( pa_context *c, + const pa_server_info *i, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + + if( !c || !i ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_ServerInfoCb: Invalid context or can't get server info" ); + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 ); + return; + } + + pulseaudioHostApi->pulseaudioDefaultSampleSpec = i->sample_spec; + + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 ); +} + +/* Function adds device to list. It can be input or output stream + * or in pulseaudio source or sink. + */ +int _PaPulseAudio_AddAudioDevice( PaPulseAudio_HostApiRepresentation *hostapi, + const char *PaPulseAudio_SinkSourceName, + const char *PaPulseAudio_SinkSourceNameDesc, + int inputChannels, + int outputChannels, + double defaultLowInputLatency, + double defaultHighInputLatency, + double defaultLowOutputLatency, + double defaultHighOutputLatency, + const long defaultSampleRate ) +{ + /* These should be at least 1 + * + * Maximun size of string is 1024 (PAPULSEAUDIO_MAX_DEVICENAME) + * which should be mostly suffient even pulseaudio device + * names can be very long + */ + int pulseaudioRealNameSize = strnlen( PaPulseAudio_SinkSourceNameDesc, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1; + int pulseaudioDeviceNameSize = strnlen( PaPulseAudio_SinkSourceName, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1; + char *pulseaudioLocalDeviceName = NULL; + + hostapi->deviceInfoArray[hostapi->deviceCount].structVersion = 2; + hostapi->deviceInfoArray[hostapi->deviceCount].hostApi = hostapi->hostApiIndex; + hostapi->pulseaudioDeviceNames[hostapi->deviceCount] = + PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations, + pulseaudioRealNameSize ); + pulseaudioLocalDeviceName = PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations, + pulseaudioDeviceNameSize ); + if( !hostapi->pulseaudioDeviceNames[hostapi->deviceCount] && + !pulseaudioLocalDeviceName ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "_PaPulseAudio_AddAudioDevice: Can't alloc memory" ); + return paInsufficientMemory; + } + + /* We can maximum have 1024 (PAPULSEAUDIO_MAX_DEVICECOUNT) + * devices where to choose which should be mostly enough + */ + if( hostapi->deviceCount >= PAPULSEAUDIO_MAX_DEVICECOUNT ) + { + return paDeviceUnavailable; + } + + snprintf( hostapi->pulseaudioDeviceNames[hostapi->deviceCount], + pulseaudioRealNameSize, + "%s", + PaPulseAudio_SinkSourceNameDesc ); + snprintf( pulseaudioLocalDeviceName, + pulseaudioDeviceNameSize, + "%s", + PaPulseAudio_SinkSourceName ); + + + hostapi->deviceInfoArray[hostapi->deviceCount].name = pulseaudioLocalDeviceName; + + hostapi->deviceInfoArray[hostapi->deviceCount].maxInputChannels = inputChannels; + hostapi->deviceInfoArray[hostapi->deviceCount].maxOutputChannels = outputChannels; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowInputLatency = defaultLowInputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowOutputLatency = defaultLowOutputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighInputLatency = defaultHighInputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighOutputLatency = defaultHighOutputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultSampleRate = defaultSampleRate; + hostapi->deviceCount++; + + return paNoError; +} + +/* Called when iterating through sinks */ +void PaPulseAudio_SinkListCb( pa_context * c, + const pa_sink_info * l, + int eol, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + const char *pulseaudioDeviceName = NULL; + + + /* If this is null we have big problems and we probably are out of memory */ + if( !c || !l ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Invalid context or sink info" ); + goto error; + } + + /* If eol is set to a positive number, you're at the end of the list */ + if( eol > 0 ) + { + goto error; + } + + pulseaudioDeviceName = l->name; + + if( l->description != NULL ) + { + pulseaudioDeviceName = l->description; + } + + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + pulseaudioDeviceName, + l->name, + 0, + l->sample_spec.channels, + 0, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + l->sample_spec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } + + error: + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, + 0 ); +} + +/* Called when iterating through sources */ +void PaPulseAudio_SourceListCb( pa_context * c, + const pa_source_info * l, + int eol, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + const char *pulseaudioDeviceName = NULL; + + + /* If this is null we have big problems and we probably are out of memory */ + if( !c ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SourceListCb: Invalid context" ); + goto error; + } + + /* If eol is set to a positive number, you're at the end of the list */ + if( eol > 0 ) + { + goto error; + } + + pulseaudioDeviceName = l->name; + + if( l->description != NULL ) + { + pulseaudioDeviceName = l->description; + } + + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + pulseaudioDeviceName, + l->name, + l->sample_spec.channels, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + 0, + 0, + l->sample_spec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SourceListCb: Can't add device. Maximum amount reached!" ); + } + + error: + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, + 0 ); +} + +/* This routine is called whenever the stream state changes */ +void PaPulseAudio_StreamStateCb( pa_stream * s, + void *userdata ) +{ + const pa_buffer_attr *pulseaudioBufferAttr = NULL; + /* If you need debug pring enable these + * char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + */ + + + /* If this is null we have big problems and we probably are out of memory */ + if( !s ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamStateCb: Invalid stream" ); + return; + } + + switch( pa_stream_get_state(s) ) + { + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_CREATING: + break; + + case PA_STREAM_READY: + if (!(pulseaudioBufferAttr = pa_stream_get_buffer_attr(s))) + { + PA_DEBUG( ("Portaudio %s: Can get buffer attr: '%s'\n", + __FUNCTION__, + pa_strerror(pa_context_errno(pa_stream_get_context(s) ) )) ); + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamStateCb: Can't get Stream pa_buffer_attr" ); + } + else + { + PA_DEBUG( ("%s: %s Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u, fragsize=%u\n", + __FUNCTION__, pa_stream_get_device_name(s), + pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->tlength, pulseaudioBufferAttr->prebuf, + pulseaudioBufferAttr->minreq, pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->fragsize) ); + } + break; + + case PA_STREAM_FAILED: + default: + PA_DEBUG( ("Portaudio %s: FAILED '%s'\n", + __FUNCTION__, + pa_strerror( pa_context_errno( pa_stream_get_context( s ) ) )) ); + + break; + } +} + +/* If stream is underflowed then this callback is called + * one needs to enable debug to make use os this + * + * Otherwise it's used to update error message + */ +void PaPulseAudio_StreamUnderflowCb( pa_stream *s, + void *userdata ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) userdata; + pa_buffer_attr *pulseaudioOutputSampleSpec = NULL; + + /* If this is null we have big problems and we probably are out of memory */ + if( !s ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamUnderflowCb: Invalid stream" ); + return; + } + + stream->outputUnderflows++; + pulseaudioOutputSampleSpec = (pa_buffer_attr *)pa_stream_get_buffer_attr(s); + PA_DEBUG( ("Portaudio %s: PulseAudio '%s' with delay: %ld stream has underflowed\n", __FUNCTION__, pa_stream_get_device_name(s), pulseaudioOutputSampleSpec->tlength) ); + + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamUnderflowCb: Pulseaudio stream underflow"); + + pa_threaded_mainloop_signal( stream->mainloop, + 0 ); +} + +/* Initialize HostAPI */ +PaError PaPulseAudio_Initialize( PaUtilHostApiRepresentation ** hostApi, + PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + int deviceCount; + int ret = 0; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = NULL; + PaDeviceInfo *deviceInfoArray = NULL; + + pa_operation *pulseaudioOperation = NULL; + + pulseaudioHostApi = PaPulseAudio_New(); + + if( !pulseaudioHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + pulseaudioHostApi->allocations = PaUtil_CreateAllocationGroup(); + + if( !pulseaudioHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + pulseaudioHostApi->hostApiIndex = hostApiIndex; + *hostApi = &pulseaudioHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paPulseAudio; + (*hostApi)->info.name = "PulseAudio"; + + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + /* Connect to server */ + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + ret = pa_context_connect( pulseaudioHostApi->context, + NULL, + 0, + NULL ); + + if( ret < 0 ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PulseAudio_Initialize: Can't connect to server"); + result = paNotInitialized; + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + goto error; + } + + ret = 0; + + /* We should wait that PulseAudio server let us in or fails us */ + while( !ret ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + + switch( pa_context_get_state( pulseaudioHostApi->context ) ) + { + case PA_CONTEXT_READY: + ret = 1; + break; + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + goto error; + break; + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + memset( pulseaudioHostApi->deviceInfoArray, + 0x00, + sizeof(PaDeviceInfo) * PAPULSEAUDIO_MAX_DEVICECOUNT ); + for (i = 0; i < PAPULSEAUDIO_MAX_DEVICECOUNT; i++) + { + pulseaudioHostApi->pulseaudioDeviceNames[i] = NULL; + } + + /* Get info about server. This returns Default sink and soure name. */ + pulseaudioOperation = + pa_context_get_server_info( pulseaudioHostApi->context, + PaPulseAudio_ServerInfoCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + /* Add the "Default" sink at index 0 */ + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + "Default Sink", + "The PulseAudio default sink", + 0, + PA_CHANNELS_MAX, + 0, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } else { + pulseaudioHostApi->inheritedHostApiRep.info.defaultOutputDevice = + pulseaudioHostApi->deviceCount - 1; + } + + /* Add the "Default" source at index 1 */ + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + "Default Source", + "The PulseAudio default source", + PA_CHANNELS_MAX, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + 0, + 0, + pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } else { + pulseaudioHostApi->inheritedHostApiRep.info.defaultInputDevice = + pulseaudioHostApi->deviceCount - 1; + } + + /* List PulseAudio sinks. If found callback: PaPulseAudio_SinkListCb */ + pulseaudioOperation = + pa_context_get_sink_info_list( pulseaudioHostApi->context, + PaPulseAudio_SinkListCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + /* List PulseAudio sources. If found callback: PaPulseAudio_SourceListCb */ + pulseaudioOperation = + pa_context_get_source_info_list( pulseaudioHostApi->context, + PaPulseAudio_SourceListCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + (*hostApi)->info.deviceCount = pulseaudioHostApi->deviceCount; + + if( pulseaudioHostApi->deviceCount > 0 ) + { + /* If you have over 1024 Audio devices.. shame on you! */ + + (*hostApi)->deviceInfos = + (PaDeviceInfo **) + PaUtil_GroupAllocateZeroInitializedMemory( pulseaudioHostApi->allocations, + sizeof(PaDeviceInfo *) * + pulseaudioHostApi->deviceCount ); + + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for ( i = 0; i < pulseaudioHostApi->deviceCount; i++ ) + { + (*hostApi)->deviceInfos[i] = + &pulseaudioHostApi->deviceInfoArray[i]; + } + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &pulseaudioHostApi->callbackStreamInterface, + PaPulseAudio_CloseStreamCb, + PaPulseAudio_StartStreamCb, + PaPulseAudio_StopStreamCb, + PaPulseAudio_AbortStreamCb, + IsStreamStopped, + IsStreamActive, + GetStreamTime, + GetStreamCpuLoad, + PaUtil_DummyRead, + PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &pulseaudioHostApi->blockingStreamInterface, + PaPulseAudio_CloseStreamCb, + PaPulseAudio_StartStreamCb, + PaPulseAudio_StopStreamCb, + PaPulseAudio_AbortStreamCb, + IsStreamStopped, + IsStreamActive, + GetStreamTime, + PaUtil_DummyGetCpuLoad, + PaPulseAudio_ReadStreamBlock, + PaPulseAudio_WriteStreamBlock, + PaPulseAudio_GetStreamReadAvailableBlock, + PaUtil_DummyGetWriteAvailable ); + + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return result; + + error: + + if( pulseaudioHostApi ) + { + if( pulseaudioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( pulseaudioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( pulseaudioHostApi->allocations ); + } + + PaPulseAudio_Free( pulseaudioHostApi ); + pulseaudioHostApi = NULL; + } + + return result; +} + +/* Drop stream now */ +void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) hostApi; + + if( pulseaudioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( pulseaudioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( pulseaudioHostApi->allocations ); + } + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pa_context_disconnect( pulseaudioHostApi->context ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + PaPulseAudio_Free( pulseaudioHostApi ); +} + +/* Checks from pulseaudio that is format supported */ +PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate ) +{ + int inputChannelCount, + outputChannelCount; + PaSampleFormat inputSampleFormat, + outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + * this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + { + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > + hostApi->deviceInfos[inputParameters->device]->maxInputChannels ) + { + return paInvalidChannelCount; + } + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + { + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + + } + + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + * this implementation doesn't support any custom sample formats + */ + if( outputSampleFormat & paCustomFormat ) + { + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > + hostApi->deviceInfos[outputParameters->device]->maxOutputChannels ) + { + return paInvalidChannelCount; + } + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + { + /* this implementation doesn't use custom stream info */ + return paIncompatibleHostApiSpecificStreamInfo; + } + + } + + else + { + outputChannelCount = 0; + } + + return paFormatIsSupported; +} + +/* Makes conversion from portaudio to pulseaudio sample defines + * Little endian formats are used (if there is some mystical big endian + * sound device this should be fixed but until then it's safe to believe + * this works + */ +PaError PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( PaSampleFormat portaudiosf, + pa_sample_spec * pulseaudiosf ) +{ + switch( portaudiosf ) + { + case paFloat32: + pulseaudiosf->format = PA_SAMPLE_FLOAT32LE; + break; + + case paInt32: + pulseaudiosf->format = PA_SAMPLE_S32LE; + break; + + case paInt24: + pulseaudiosf->format = PA_SAMPLE_S24LE; + break; + + case paInt16: + pulseaudiosf->format = PA_SAMPLE_S16LE; + break; + + case paInt8: + pulseaudiosf->format = PA_SAMPLE_U8; + break; + + case paUInt8: + pulseaudiosf->format = PA_SAMPLE_U8; + break; + + case paCustomFormat: + case paNonInterleaved: + PA_DEBUG(("PaPulseAudio %s: THIS IS NOT SUPPORTED BY PULSEAUDIO!\n", + __FUNCTION__)); + return paSampleFormatNotSupported; + break; + } + + return paNoError; +} + + +/* Allocate buffer. */ +PaError PaPulseAudio_BlockingInitRingBuffer( PaUtilRingBuffer * rbuf, + int size ) +{ + char *ringbufferBuffer = (char *) malloc( size ); + PaError ret = paNoError; + + if( ringbufferBuffer == NULL ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_BlockingInitRingBuffer: Not enough memory to handle request" ); + return paInsufficientMemory; + } + + memset( ringbufferBuffer, + 0x00, + size ); + + ret = PaUtil_InitializeRingBuffer( rbuf, + 1, + size, + ringbufferBuffer ); + + if( ret < paNoError ) + { + free( ringbufferBuffer ); + PA_DEBUG( ("Portaudio %s: Can't initialize input ringbuffer with size: %ld!\n", + __FUNCTION__, size) ); + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_BlockingInitRingBuffer: Can't initialize input ringbuffer" ); + + return paNotInitialized; + } + + return paNoError; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) hostApi; + PaPulseAudio_Stream *stream = NULL; + unsigned long framesPerHostBuffer = framesPerBuffer; /* these may not be equivalent for all implementations */ + int inputChannelCount, + outputChannelCount; + PaSampleFormat inputSampleFormat, + outputSampleFormat; + PaSampleFormat hostInputSampleFormat, + hostOutputSampleFormat; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + { + return paInvalidFlag; + } + + /* This is something that Pulseaudio can handle + * and it's also bearable small + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + framesPerBuffer = PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC; + } + + PaPulseAudio_Lock(pulseaudioHostApi->mainloop); + stream = + (PaPulseAudio_Stream *) PaUtil_AllocateZeroInitializedMemory( sizeof( PaPulseAudio_Stream ) ); + + if( !stream ) + { + result = paInsufficientMemory; + goto openstream_error; + } + + /* Allocate memory for source and sink names. */ + const char defaultSourceStreamName[] = "Portaudio source"; + const char defaultSinkStreamName[] = "Portaudio sink"; + + stream->framesPerHostCallback = framesPerBuffer; + stream->inputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSourceStreamName ) ); + stream->outputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSinkStreamName ) ); + if ( !stream->inputStreamName || !stream->outputStreamName ) + { + result = paInsufficientMemory; + goto openstream_error; + } + + /* Copy initial stream names to memory. */ + memcpy( stream->inputStreamName, defaultSourceStreamName, sizeof( defaultSourceStreamName ) ); + memcpy( stream->outputStreamName, defaultSinkStreamName, sizeof( defaultSinkStreamName ) ); + + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + stream->inputStream = NULL; + stream->outputStream = NULL; + memset( &stream->inputRing, + 0x00, + sizeof( PaUtilRingBuffer ) ); + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + result = paInvalidDevice; + goto openstream_error; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > + hostApi->deviceInfos[inputParameters->device]->maxInputChannels ) + { + result = paInvalidChannelCount; + goto openstream_error; + } + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + { + /* this implementation doesn't use custom stream info */ + result = paIncompatibleHostApiSpecificStreamInfo; + goto openstream_error; + } + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( inputSampleFormat, + inputSampleFormat ); + + stream->inputFrameSize = Pa_GetSampleSize( inputSampleFormat ) * inputChannelCount; + + result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( + hostInputSampleFormat, + &stream->inputSampleSpec ); + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->inputSampleSpec.rate = sampleRate; + stream->inputSampleSpec.channels = inputChannelCount; + stream->inputChannelCount = inputChannelCount; + + if( !pa_sample_spec_valid( &stream->inputSampleSpec ) ) + { + PA_DEBUG( ("Portaudio %s: Invalid input audio spec!\n", + __FUNCTION__) ); + result = paUnanticipatedHostError; /* should have been caught above */ + goto openstream_error; + } + + stream->inputStream = + pa_stream_new( pulseaudioHostApi->context, + stream->inputStreamName, + &stream->inputSampleSpec, + NULL ); + + if( stream->inputStream != NULL ) + { + pa_stream_set_state_callback( stream->inputStream, + PaPulseAudio_StreamStateCb, + stream); + pa_stream_set_started_callback( stream->inputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't alloc input stream!\n", + __FUNCTION__) ); + } + + stream->inputDevice = inputParameters->device; + + /* + * This is too much as most of the time there is not much + * stuff in buffer but it's enough if we are doing blocked + * and reading is somewhat slower than callback + */ + result = PaPulseAudio_BlockingInitRingBuffer( &stream->inputRing, + (65536 * 4) ); + if( result != paNoError ) + { + goto openstream_error; + } + + } + + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paFloat32; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + result = paInvalidDevice; + goto openstream_error; + } + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > + hostApi->deviceInfos[outputParameters->device]->maxOutputChannels ) + { + result = paInvalidChannelCount; + goto openstream_error; + } + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + { + result = paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + goto openstream_error; + } + + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( outputSampleFormat + /* native formats */ , + outputSampleFormat ); + + stream->outputFrameSize = + Pa_GetSampleSize( outputSampleFormat ) * outputChannelCount; + + result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( + hostOutputSampleFormat, + &stream->outputSampleSpec ); + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->outputSampleSpec.rate = sampleRate; + stream->outputSampleSpec.channels = outputChannelCount; + stream->outputChannelCount = outputChannelCount; + + if( !pa_sample_spec_valid( &stream->outputSampleSpec ) ) + { + PA_DEBUG( ("Portaudio %s: Invalid audio spec for output!\n", + __FUNCTION__) ); + result = paUnanticipatedHostError; /* should have been caught above */ + goto openstream_error; + } + + stream->outputStream = + pa_stream_new( pulseaudioHostApi->context, + stream->outputStreamName, + &stream->outputSampleSpec, + NULL ); + + if( stream->outputStream != NULL ) + { + pa_stream_set_state_callback( stream->outputStream, + PaPulseAudio_StreamStateCb, + stream ); + pa_stream_set_started_callback( stream->outputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + + else + { + PA_DEBUG( ("Portaudio %s: Can't alloc output stream!\n", + __FUNCTION__) ); + } + + if( result != paNoError ) + { + goto openstream_error; + } + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->outputDevice = outputParameters->device; + } + + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; + } + + stream->hostapi = pulseaudioHostApi; + stream->context = pulseaudioHostApi->context; + stream->mainloop = pulseaudioHostApi->mainloop; + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &pulseaudioHostApi->callbackStreamInterface, + streamCallback, + userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &pulseaudioHostApi->blockingStreamInterface, + streamCallback, + userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, + sampleRate + ); + + /* we assume a fixed host buffer size in this example, but the buffer processor + * can also support bounded and unknown host buffer sizes by passing + * paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of + * paUtilFixedHostBufferSize below. */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, + inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, + outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + framesPerBuffer, + framesPerHostBuffer, + paUtilUnknownHostBufferSize, + streamCallback, + userData ); + + if( result != paNoError ) + { + goto openstream_error; + } + + /* inputLatency is specified in _seconds_ */ + stream->streamRepresentation.streamInfo.inputLatency = + (PaTime) PaUtil_GetBufferProcessorInputLatencyFrames( + &stream->bufferProcessor ) / sampleRate; + + /* outputLatency is specified in _seconds_ */ + stream->streamRepresentation.streamInfo.outputLatency = + (PaTime) PaUtil_GetBufferProcessorOutputLatencyFrames( + &stream->bufferProcessor ) / sampleRate; + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->maxFramesHostPerBuffer = framesPerBuffer; + stream->maxFramesPerBuffer = framesPerBuffer; + + *s = (PaStream *) stream; + + openstream_end: + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return result; + + openstream_error: + + if( stream ) + { + PaUtil_FreeMemory( stream->inputStreamName ); + PaUtil_FreeMemory( stream->outputStreamName ); + PaUtil_FreeMemory( stream ); + } + + goto openstream_end; +} + +PaError IsStreamStopped( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + return stream->isStopped; +} + + +PaError IsStreamActive( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + return stream->isActive; +} + + +PaTime GetStreamTime( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 }; + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + if( stream->outputStream ) + { + if( PaPulseAudio_updateTimeInfo( stream->outputStream, + &timeInfo, + 0 ) == -PA_ERR_NODATA ) + { + return 0; + } + } + + if( stream->inputStream ) + { + if( PaPulseAudio_updateTimeInfo( stream->inputStream, + &timeInfo, + 1 ) == -PA_ERR_NODATA ) + { + return 0; + } + } + + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return timeInfo.currentTime; +} + + +double GetStreamCpuLoad( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + +/** Extensions */ +static void RenameStreamCb(pa_stream *s, int success, void *userdata) +{ + /* Currently does nothing but signal the caller. */ + PaPulseAudio_Stream *l_ptrStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( l_ptrStream->mainloop, + 0 ); +} + +PaError PaPulseAudio_RenameSource( PaStream *s, const char *streamName ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaError result = paNoError; + pa_operation *op = NULL; + + if ( stream->inputStream == NULL ) + { + return paInvalidDevice; + } + + /* Reallocate stream name in memory. */ + PaPulseAudio_Lock( stream->mainloop ); + char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 ); + if ( !newStreamName ) + { + PaPulseAudio_UnLock( stream->mainloop ); + return paInsufficientMemory; + } + snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName ); + + PaUtil_FreeMemory( stream->inputStreamName ); + stream->inputStreamName = newStreamName; + + op = pa_stream_set_name( stream->inputStream, streamName, RenameStreamCb, stream ); + PaPulseAudio_UnLock( stream->mainloop ); + + /* Wait for completion. */ + while (pa_operation_get_state( op ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( stream->mainloop ); + } + + return result; +} + +PaError PaPulseAudio_RenameSink( PaStream *s, const char *streamName ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaError result = paNoError; + pa_operation *op = NULL; + + if ( stream->outputStream == NULL ) + { + return paInvalidDevice; + } + + /* Reallocate stream name in memory. */ + PaPulseAudio_Lock( stream->mainloop ); + char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 ); + if ( !newStreamName ) + { + PaPulseAudio_UnLock( stream->mainloop ); + return paInsufficientMemory; + } + snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName ); + + PaUtil_FreeMemory( stream->outputStreamName ); + stream->outputStreamName = newStreamName; + + op = pa_stream_set_name( stream->outputStream, streamName, RenameStreamCb, stream ); + PaPulseAudio_UnLock( stream->mainloop ); + + /* Wait for completion. */ + while (pa_operation_get_state( op ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( stream->mainloop ); + } + + return result; +} |