diff options
Diffstat (limited to '3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c')
-rw-r--r-- | 3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c new file mode 100644 index 00000000000..18da3228982 --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c @@ -0,0 +1,971 @@ + +/* + * 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 + * + * 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 callback mode and normal write mode support +*/ + + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_unix_util.h" +#include "pa_ringbuffer.h" + +#include "pa_linux_pulseaudio_cb_internal.h" + + +/* PulseAudio headers */ +#include <string.h> +#include <unistd.h> + +int PaPulseAudio_updateTimeInfo( pa_stream * s, + PaStreamCallbackTimeInfo *timeInfo, + int record ) +{ + unsigned int isNegative = 0; + pa_usec_t pulseaudioStreamTime = 0; + pa_usec_t pulseaudioStreamLatency = 0; + + if( pa_stream_get_time( s, + &pulseaudioStreamTime ) == -PA_ERR_NODATA ) + { + return -PA_ERR_NODATA; + } + else + { + timeInfo->currentTime = ((PaTime) pulseaudioStreamTime / (PaTime) 1000000); + } + + if( pa_stream_get_latency( s, + &pulseaudioStreamLatency, + &isNegative ) == -PA_ERR_NODATA ) + { + return -PA_ERR_NODATA; + } + else + { + if( record == 0 ) + { + timeInfo->outputBufferDacTime = timeInfo->currentTime + ((PaTime) pulseaudioStreamLatency / (PaTime) 1000000); + } + else + { + timeInfo->inputBufferAdcTime = timeInfo->currentTime - ((PaTime) pulseaudioStreamLatency / (PaTime) 1000000); + } + } + return 0; +} + + +/* locks the Pulse Main loop when not called from it */ +void PaPulseAudio_Lock( pa_threaded_mainloop *mainloop ) +{ + if( !pa_threaded_mainloop_in_thread( mainloop ) ) { + pa_threaded_mainloop_lock( mainloop ); + } +} + +/* unlocks the Pulse Main loop when not called from it */ +void PaPulseAudio_UnLock( pa_threaded_mainloop *mainloop ) +{ + if( !pa_threaded_mainloop_in_thread( mainloop ) ) { + pa_threaded_mainloop_unlock( mainloop ); + } +} + +void _PaPulseAudio_WriteRingBuffer( PaUtilRingBuffer *ringbuffer, + const void *buffer, + size_t length ) +{ + /* + * If there is not enough room. Read from ringbuffer to make + * sure that if not full and audio will just underrun + * + * If you try to read too much and there is no room then this + * will fail. But I don't know how to get into that? + */ + if( PaUtil_GetRingBufferWriteAvailable( ringbuffer ) < length ) + { + uint8_t tmpBuffer[ PULSEAUDIO_BUFFER_SIZE ]; + PaUtil_ReadRingBuffer( ringbuffer, + tmpBuffer, + length ); + } + + PaUtil_WriteRingBuffer( ringbuffer, + buffer, + length ); + +} + +void _PaPulseAudio_Read( PaPulseAudio_Stream *stream, + size_t length ) +{ + const void *pulseaudioData = NULL; + + /* + * Idea behind this is that we fill ringbuffer with data + * that comes from input device. When it's available + * we'll fill it to callback or blocking reading + */ + if( pa_stream_peek( stream->inputStream, + &pulseaudioData, + &length )) + { + PA_DEBUG( ("Portaudio %s: Can't read audio!\n", + __FUNCTION__) ); + } + else + { + _PaPulseAudio_WriteRingBuffer( &stream->inputRing, pulseaudioData, length ); + } + + pa_stream_drop( stream->inputStream ); + + pulseaudioData = NULL; + +} + +static int _PaPulseAudio_ProcessAudio(PaPulseAudio_Stream *stream, + size_t length) +{ + uint8_t pulseaudioSampleBuffer[PULSEAUDIO_BUFFER_SIZE]; + size_t hostFramesPerBuffer = stream->bufferProcessor.framesPerHostBuffer; + size_t pulseaudioOutputBytes = 0; + size_t pulseaudioInputBytes = 0; + size_t hostFrameCount = 0; + int isOutputCb = 0; + int isInputCb = 0; + PaStreamCallbackTimeInfo timeInfo; + int ret = paContinue; + void *bufferData = NULL; + size_t pulseaudioOutputWritten = 0; + + /* If there is no specified per host buffer then + * just generate one or but correct one in place + */ + if( hostFramesPerBuffer == paFramesPerBufferUnspecified ) + { + if( !stream->framesPerHostCallback ) + { + /* This just good enough and most + * Pulseaudio server and ALSA can handle it + * + * We should never get here but this is ultimate + * backup. + */ + hostFramesPerBuffer = PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC; + + stream->framesPerHostCallback = hostFramesPerBuffer; + } + else + { + hostFramesPerBuffer = stream->framesPerHostCallback; + } + } + + if( stream->outputStream ) + { + /* Calculate how many bytes goes to one frame */ + pulseaudioInputBytes = pulseaudioOutputBytes = (hostFramesPerBuffer * stream->outputFrameSize); + + if( stream->bufferProcessor.streamCallback ) + { + isOutputCb = 1; + } + } + + /* If we just want to have input but not output (Not Duplex) + * Use this calculation + */ + if( stream->inputStream ) + { + pulseaudioInputBytes = pulseaudioOutputBytes = (hostFramesPerBuffer * stream->inputFrameSize); + + if( stream->bufferProcessor.streamCallback ) + { + isInputCb = 1; + } + } + + /* + * When stopped we should stop feeding or recording right away + */ + if( stream->isStopped ) + { + return paStreamIsStopped; + } + + /* + * This can be called before we have reached out + * starting Portaudio stream or Portaudio stream + * is stopped + */ + if( !stream->pulseaudioIsActive ) + { + if(stream->outputStream) + { + bufferData = pulseaudioSampleBuffer; + memset( bufferData, 0x00, length); + + pa_stream_write( stream->outputStream, + bufferData, + length, + NULL, + 0, + PA_SEEK_RELATIVE ); + } + + return paContinue; + } + + /* If we have input which is mono and + * output which is stereo. We have to copy + * mono to monomono which is stereo. + * Then just read half and copy + */ + if( isOutputCb && + stream->outputSampleSpec.channels == 2 && + stream->inputSampleSpec.channels == 1) + { + pulseaudioInputBytes /= 2; + } + + while(1) + { + /* + * If everything fail like stream vanish or mainloop + * is in error state try to handle error + */ + PA_PULSEAUDIO_IS_ERROR( stream, paStreamIsStopped ) + + /* There is only Record stream so + * see if we have enough stuff to feed record stream + * If not then bail out. + */ + if( isInputCb && + PaUtil_GetRingBufferReadAvailable(&stream->inputRing) < pulseaudioInputBytes ) + { + if(isOutputCb && (pulseaudioOutputWritten < length) && !stream->missedBytes) + { + stream->missedBytes = length - pulseaudioOutputWritten; + } + else + { + stream->missedBytes = 0; + } + break; + } + else if( pulseaudioOutputWritten >= length) + { + stream->missedBytes = 0; + break; + } + + if( stream->outputStream ) + { + PaPulseAudio_updateTimeInfo( stream->outputStream, + &timeInfo, + 0 ); + } + + if( stream->inputStream ) + { + PaPulseAudio_updateTimeInfo( stream->inputStream, + &timeInfo, + 1 ); + } + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* When doing Portaudio Duplex one has to write and read same amount of data + * if not done that way Portaudio will go boo boo and nothing works. + * This is why this is done as it's done + * + * Pulseaudio does not care and this is something that needs small adjusting + */ + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, + &timeInfo, + 0 ); + + /* Read of ther is something to read */ + if( isInputCb ) + { + PaUtil_ReadRingBuffer( &stream->inputRing, + pulseaudioSampleBuffer, + pulseaudioInputBytes); + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, + pulseaudioSampleBuffer, + stream->inputSampleSpec.channels ); + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, + hostFramesPerBuffer ); + } + + if( isOutputCb ) + { + + size_t tmpSize = pulseaudioOutputBytes; + + /* Allocate memory to make it faster to output stuff */ + pa_stream_begin_write( stream->outputStream, &bufferData, &tmpSize ); + + /* If bufferData is NULL then output is not ready + * and we have to wait for it + */ + if(!bufferData) + { + return paNotInitialized; + } + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, + bufferData, + stream->outputChannelCount ); + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + hostFramesPerBuffer ); + + if( pa_stream_write( stream->outputStream, + bufferData, + pulseaudioOutputBytes, + NULL, + 0, + PA_SEEK_RELATIVE ) ) + { + PA_DEBUG( ("Portaudio %s: Can't write audio!\n", + __FUNCTION__) ); + } + + pulseaudioOutputWritten += pulseaudioOutputBytes; + } + + hostFrameCount = + PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &ret ); + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, + hostFrameCount ); + } + + return ret; +} + +void PaPulseAudio_StreamRecordCb( pa_stream * s, + size_t length, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + + if( !pulseaudioStream->pulseaudioIsActive ) + { + pulseaudioStream->pulseaudioIsActive = 1; + pulseaudioStream->pulseaudioIsStopped= 0; + } + + _PaPulseAudio_Read( pulseaudioStream, length ); + + /* Let's handle when output happens if Duplex + * + * Also there is no callback there is no meaning to continue + * as we have blocking reading + */ + if( pulseaudioStream->bufferProcessor.streamCallback ) + { + _PaPulseAudio_ProcessAudio( pulseaudioStream, length ); + } + + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +void PaPulseAudio_StreamPlaybackCb( pa_stream * s, + size_t length, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + + if( !pulseaudioStream->inputStream && !pulseaudioStream->pulseaudioIsActive ) + { + pulseaudioStream->pulseaudioIsActive = 1; + pulseaudioStream->pulseaudioIsStopped = 0; + } + + if( pulseaudioStream->bufferProcessor.streamCallback ) + { + _PaPulseAudio_ProcessAudio( pulseaudioStream, length ); + } + + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* This is left for future use! */ +static void PaPulseAudio_StreamSuccessCb( pa_stream * s, + int success, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + PA_DEBUG( ("Portaudio %s: %d\n", __FUNCTION__, + success) ); + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* This is left for future use! */ +static void PaPulseAudio_CorkSuccessCb( + pa_stream * s, + int success, + void *userdata +) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + + +/* This is left for future use! */ +void PaPulseAudio_StreamStartedCb( pa_stream * stream, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +PaError PaPulseAudio_CloseStreamCb( PaStream * s ) +{ + PaError result = paNoError; + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + pa_operation *pulseaudioOperation = NULL; + int waitLoop = 0; + int pulseaudioError = 0; + + /* Wait for stream to be stopped */ + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + if( stream->outputStream != NULL + && pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY ) + { + PaPulseAudio_Lock(stream->mainloop); + /** + * Pause stream so it stops faster + */ + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + waitLoop ++; + + if(waitLoop > 250) + { + break; + } + } + + waitLoop = 0; + + PaPulseAudio_Lock(stream->mainloop); + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + + pa_stream_disconnect( stream->outputStream ); + PaPulseAudio_UnLock( stream->mainloop ); + } + + if( stream->inputStream != NULL + && pa_stream_get_state( stream->inputStream ) == PA_STREAM_READY ) + { + PaPulseAudio_Lock( stream->mainloop ); + + /** + * Pause stream so it stops so it stops faster + */ + pulseaudioOperation = pa_stream_cork( stream->inputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + waitLoop ++; + + if(waitLoop > 250) + { + break; + } + } + + waitLoop = 0; + + PaPulseAudio_Lock( stream->mainloop ); + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + + /* Then we disconnect stream and wait for + * Termination + */ + pa_stream_disconnect( stream->inputStream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + } + + /* Wait for termination for both */ + while(!waitLoop) + { + PaPulseAudio_Lock( stream->mainloop ); + if( stream->inputStream != NULL + && pa_stream_get_state( stream->inputStream ) == PA_STREAM_TERMINATED ) + { + pa_stream_unref( stream->inputStream ); + stream->inputStream = NULL; + } + PaPulseAudio_UnLock( stream->mainloop ); + + PaPulseAudio_Lock( stream->mainloop ); + if( stream->outputStream != NULL + && pa_stream_get_state(stream->outputStream) == PA_STREAM_TERMINATED ) + { + pa_stream_unref( stream->outputStream ); + stream->outputStream = NULL; + } + PaPulseAudio_UnLock( stream->mainloop ); + + if((stream->outputStream == NULL + && stream->inputStream == NULL) + || pulseaudioError >= 5000 ) + { + waitLoop = 1; + } + + pulseaudioError ++; + usleep(10000); + } + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaUtil_FreeMemory( stream->inputStreamName ); + PaUtil_FreeMemory( stream->outputStreamName ); + PaUtil_FreeMemory( stream ); + + return result; +} + +PaError PaPulseAudio_StartStreamCb( PaStream * s ) +{ + PaError ret = paNoError; + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + int pulseaudioPlaybackStarted = 0; + int pulseaudioRecordStarted = 0; + pa_stream_state_t pulseaudioState = PA_STREAM_UNCONNECTED; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + const char *pulseaudioName = NULL; + pa_operation *pulseaudioOperation = NULL; + int waitLoop = 0; + unsigned int pulseaudioReqFrameSize = (1024 * 2); + + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + stream->missedBytes = 0; + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + /* Adjust latencies if that is wanted + * https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/LatencyControl/ + * + * tlength is for Playback + * fragsize if for Record + */ + stream->outputBufferAttr.maxlength = (uint32_t)-1; + stream->inputBufferAttr.maxlength = (uint32_t)-1; + + stream->outputBufferAttr.tlength = (uint32_t)-1; + stream->inputBufferAttr.tlength = (uint32_t)-1; + + stream->outputBufferAttr.fragsize = (uint32_t)-1; + stream->inputBufferAttr.fragsize = (uint32_t)-1; + + stream->outputBufferAttr.prebuf = (uint32_t)-1; + stream->inputBufferAttr.prebuf = (uint32_t)-1; + + stream->outputBufferAttr.minreq = (uint32_t)-1; + stream->inputBufferAttr.minreq = (uint32_t)-1; + + stream->outputUnderflows = 0; + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + pa_stream_flags_t pulseaudioStreamFlags = PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_ADJUST_LATENCY | + PA_STREAM_NO_REMIX_CHANNELS | + PA_STREAM_NO_REMAP_CHANNELS | + PA_STREAM_DONT_MOVE; + + if( stream->inputStream ) + { + /* Default input reads 65,535 bytes setting fragsize + * fragments request to smaller chunks of data so it's + * easier to get nicer looking timestamps with current + * callback system + */ + stream->inputBufferAttr.fragsize = pa_usec_to_bytes( pulseaudioReqFrameSize, + &stream->inputSampleSpec ); + + if( stream->inputDevice != paNoDevice) + { + PA_DEBUG( ("Portaudio %s: %d (%s)\n", __FUNCTION__, stream->inputDevice, + pulseaudioHostApi->pulseaudioDeviceNames[stream-> + inputDevice]) ); + } + + pa_stream_set_read_callback( stream->inputStream, + PaPulseAudio_StreamRecordCb, + stream ); + + PaDeviceIndex defaultInputDevice; + PaError result = PaUtil_DeviceIndexToHostApiDeviceIndex( + &defaultInputDevice, + pulseaudioHostApi->inheritedHostApiRep.info.defaultInputDevice, + &(pulseaudioHostApi->inheritedHostApiRep) ); + + /* NULL means default device */ + pulseaudioName = NULL; + + /* If default device is not requested then change to wanted device */ + if( result == paNoError && stream->inputDevice != defaultInputDevice ) + { + pulseaudioName = pulseaudioHostApi-> + pulseaudioDeviceNames[stream->inputDevice]; + } + + if ( result == paNoError ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + /* Zero means success */ + if( ! pa_stream_connect_record( stream->inputStream, + pulseaudioName, + &stream->inputBufferAttr, + pulseaudioStreamFlags ) ) + { + pa_stream_set_started_callback( stream->inputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't read audio!\n", + __FUNCTION__) ); + + goto startstreamcb_error; + } + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + for( waitLoop = 0; waitLoop < 100; waitLoop ++ ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioState = pa_stream_get_state( stream->inputStream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + if( pulseaudioState == PA_STREAM_READY ) + { + break; + } + else if( pulseaudioState == PA_STREAM_FAILED || + pulseaudioState == PA_STREAM_TERMINATED ) + { + goto startstreamcb_error; + } + + usleep(10000); + } + } + else + { + goto startstreamcb_error; + } + + } + + if( stream->outputStream ) + { + /* tlength does almost the same as fragsize in record. + * See reasoning up there in comments. + * + * In future this should we tuned when things changed + * this just 'good' default + */ + stream->outputBufferAttr.tlength = pa_usec_to_bytes( pulseaudioReqFrameSize, + &stream->outputSampleSpec ); + + pa_stream_set_write_callback( stream->outputStream, + PaPulseAudio_StreamPlaybackCb, + stream ); + + /* Just keep on trucking if we are just corked */ + if( pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY + && pa_stream_is_corked( stream->outputStream ) ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 0, + PaPulseAudio_CorkSuccessCb, + stream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + } + else + { + if( stream->outputDevice != paNoDevice ) + { + PA_DEBUG( ("Portaudio %s: %d (%s)\n", + __FUNCTION__, + stream->outputDevice, + pulseaudioHostApi->pulseaudioDeviceNames[stream-> + outputDevice]) ); + } + + PaDeviceIndex defaultOutputDevice; + PaError result = PaUtil_DeviceIndexToHostApiDeviceIndex( &defaultOutputDevice, + pulseaudioHostApi->inheritedHostApiRep.info.defaultOutputDevice, + &(pulseaudioHostApi->inheritedHostApiRep) ); + + /* NULL means default device */ + pulseaudioName = NULL; + + /* If default device is not requested then change to wanted device */ + if( result == paNoError && stream->outputDevice != defaultOutputDevice ) + { + pulseaudioName = pulseaudioHostApi-> + pulseaudioDeviceNames[stream->outputDevice]; + } + + if(result == paNoError) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + if ( ! pa_stream_connect_playback( stream->outputStream, + pulseaudioName, + &stream->outputBufferAttr, + pulseaudioStreamFlags, + NULL, + NULL ) ) + { + pa_stream_set_underflow_callback( stream->outputStream, + PaPulseAudio_StreamUnderflowCb, + stream); + pa_stream_set_started_callback( stream->outputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't write audio!\n", + __FUNCTION__) ); + goto startstreamcb_error; + } + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + for( waitLoop = 0; waitLoop < 100; waitLoop ++ ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioState = pa_stream_get_state( stream->outputStream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + if( pulseaudioState = PA_STREAM_READY ) + { + break; + } + else if( pulseaudioState == PA_STREAM_FAILED || + pulseaudioState == PA_STREAM_TERMINATED ) + { + goto startstreamcb_error; + } + + usleep(10000); + } + + } + else + { + goto startstreamcb_error; + } + } + } + + if( !stream->outputStream && + !stream->inputStream ) + { + PA_DEBUG( ("Portaudio %s: Streams not initialized!\n", + __FUNCTION__) ); + goto startstreamcb_error; + } + + /* Make sure we pass no error on intialize */ + ret = paNoError; + + /* Stream is now active */ + stream->isActive = 1; + stream->isStopped = 0; + + /* Allways unlock.. so we don't get locked */ + startstreamcb_end: + return ret; + + error: + startstreamcb_error: + PA_DEBUG( ("Portaudio %s: Can't start audio!\n", + __FUNCTION__) ); + + if( pulseaudioPlaybackStarted || pulseaudioRecordStarted ) + { + PaPulseAudio_AbortStreamCb( stream ); + } + + stream->isActive = 0; + stream->isStopped = 1; + ret = paNotInitialized; + + goto startstreamcb_end; +} + +static PaError RequestStop( PaPulseAudio_Stream * stream, + int abort ) +{ + PaError ret = paNoError; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + pa_operation *pulseaudioOperation = NULL; + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + /* Wait for stream to be stopped */ + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + stream->missedBytes = 0; + + /* Test if there is something that we can play */ + if( stream->outputStream + && pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY + && !pa_stream_is_corked( stream->outputStream ) + && !abort ) + { + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + pulseaudioOperation = NULL; + } + + requeststop_error: + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + return ret; +} + +PaError PaPulseAudio_StopStreamCb( PaStream * s ) +{ + return RequestStop( (PaPulseAudio_Stream *) s, + 0 ); +} + +PaError PaPulseAudio_AbortStreamCb( PaStream * s ) +{ + return RequestStop( (PaPulseAudio_Stream *) s, + 1 ); +} |