From 5d8514d7eb3918a947ec97b45f4105630c64468d Mon Sep 17 00:00:00 2001 From: Gary Scavone Date: Wed, 13 Jun 2012 20:27:59 +0000 Subject: Mutex removal from several APIs, addition of PulseAudio support, documentation updates for 4.0.11 release. --- RtAudio.cpp | 597 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 456 insertions(+), 141 deletions(-) (limited to 'RtAudio.cpp') diff --git a/RtAudio.cpp b/RtAudio.cpp index 47e5804..823faaf 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -58,7 +58,7 @@ const unsigned int RtApi::SAMPLE_RATES[] = { #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) -#elif defined(__LINUX_ALSA__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) @@ -87,6 +87,9 @@ void RtAudio :: getCompiledApi( std::vector &apis ) throw() #if defined(__LINUX_ALSA__) apis.push_back( LINUX_ALSA ); #endif +#if defined(__LINUX_PULSE__) + apis.push_back( LINUX_PULSE ); +#endif #if defined(__LINUX_OSS__) apis.push_back( LINUX_OSS ); #endif @@ -106,7 +109,7 @@ void RtAudio :: getCompiledApi( std::vector &apis ) throw() void RtAudio :: openRtApi( RtAudio::Api api ) { - if (rtapi_) + if ( rtapi_ ) delete rtapi_; rtapi_ = 0; @@ -118,6 +121,10 @@ void RtAudio :: openRtApi( RtAudio::Api api ) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif +#if defined(__LINUX_PULSE__) + if ( api == LINUX_PULSE ) + rtapi_ = new RtApiPulse(); +#endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); @@ -1357,8 +1364,6 @@ void RtApiCore :: startStream( void ) return; } - //MUTEX_LOCK( &stream_.mutex ); - OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -1387,8 +1392,6 @@ void RtApiCore :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - //MUTEX_UNLOCK( &stream_.mutex ); - if ( result == noErr ) return; error( RtError::SYSTEM_ERROR ); } @@ -1402,15 +1405,6 @@ void RtApiCore :: stopStream( void ) return; } - /* - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - */ - OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -1420,9 +1414,7 @@ void RtApiCore :: stopStream( void ) pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } - //MUTEX_UNLOCK( &stream_.mutex ); result = AudioDeviceStop( handle->id[0], callbackHandler ); - //MUTEX_LOCK( &stream_.mutex ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1432,9 +1424,7 @@ void RtApiCore :: stopStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { - //MUTEX_UNLOCK( &stream_.mutex ); result = AudioDeviceStop( handle->id[1], callbackHandler ); - //MUTEX_LOCK( &stream_.mutex ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1445,8 +1435,6 @@ void RtApiCore :: stopStream( void ) stream_.state = STREAM_STOPPED; unlock: - //MUTEX_UNLOCK( &stream_.mutex ); - if ( result == noErr ) return; error( RtError::SYSTEM_ERROR ); } @@ -1477,7 +1465,6 @@ extern "C" void *coreStopStream( void *ptr ) RtApiCore *object = (RtApiCore *) info->object; object->stopStream(); - pthread_exit( NULL ); } @@ -1498,26 +1485,14 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { - if ( handle->internalDrain == true ) { - stream_.state = STREAM_STOPPING; + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, coreStopStream, info ); - //stopStream(); - } else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } - /* - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return SUCCESS; - } - */ - AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are @@ -1539,7 +1514,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { - //MUTEX_UNLOCK( &stream_.mutex ); + stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return SUCCESS; @@ -2327,8 +2302,6 @@ void RtApiJack :: startStream( void ) return; } - MUTEX_LOCK(&stream_.mutex); - JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { @@ -2390,8 +2363,6 @@ void RtApiJack :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - MUTEX_UNLOCK(&stream_.mutex); - if ( result == 0 ) return; error( RtError::SYSTEM_ERROR ); } @@ -2405,13 +2376,6 @@ void RtApiJack :: stopStream( void ) return; } - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -2423,8 +2387,6 @@ void RtApiJack :: stopStream( void ) jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; - - MUTEX_UNLOCK( &stream_.mutex ); } void RtApiJack :: abortStream( void ) @@ -2453,13 +2415,12 @@ extern "C" void *jackStopStream( void *ptr ) RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); - pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { - if ( stream_.state == STREAM_STOPPED ) return SUCCESS; + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtError::WARNING ); @@ -2476,6 +2437,8 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else @@ -2483,14 +2446,6 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) return SUCCESS; } - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return SUCCESS; - } - // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; @@ -2507,9 +2462,9 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { - MUTEX_UNLOCK( &stream_.mutex ); - ThreadHandle id; + stream_.state = STREAM_STOPPING; handle->drainCounter = 2; + ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } @@ -2571,8 +2526,6 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) } unlock: - MUTEX_UNLOCK(&stream_.mutex); - RtApi::tickStreamTime(); return SUCCESS; } @@ -3189,8 +3142,6 @@ void RtApiAsio :: startStream() return; } - //MUTEX_LOCK( &stream_.mutex ); - AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { @@ -3206,8 +3157,6 @@ void RtApiAsio :: startStream() asioXRun = false; unlock: - //MUTEX_UNLOCK( &stream_.mutex ); - stopThreadCalled = false; if ( result == ASE_OK ) return; @@ -3223,23 +3172,11 @@ void RtApiAsio :: stopStream() return; } - /* - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - */ - AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; - // MUTEX_UNLOCK( &stream_.mutex ); WaitForSingleObject( handle->condition, INFINITE ); // block until signaled - //ResetEvent( handle->condition ); - // MUTEX_LOCK( &stream_.mutex ); } } @@ -3251,8 +3188,6 @@ void RtApiAsio :: stopStream() errorText_ = errorStream_.str(); } - // MUTEX_UNLOCK( &stream_.mutex ); - if ( result == ASE_OK ) return; error( RtError::SYSTEM_ERROR ); } @@ -3286,15 +3221,13 @@ extern "C" unsigned __stdcall asioStopStream( void *ptr ) RtApiAsio *object = (RtApiAsio *) info->object; object->stopStream(); - _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { - if ( stream_.state == STREAM_STOPPED ) return SUCCESS; - if ( stopThreadCalled ) return SUCCESS; + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtError::WARNING ); @@ -3306,22 +3239,18 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; - stopThreadCalled = true; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } - /*MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) goto unlock; */ - // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { @@ -3339,11 +3268,9 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // abortStream(); - unsigned threadId; - stopThreadCalled = true; + stream_.state = STREAM_STOPPING; handle->drainCounter = 2; + unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; @@ -3447,8 +3374,6 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) // drivers apparently do not function correctly without it. ASIOOutputReady(); - // MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); return SUCCESS; } @@ -4447,8 +4372,6 @@ void RtApiDs :: startStream() return; } - //MUTEX_LOCK( &stream_.mutex ); - DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -4493,8 +4416,6 @@ void RtApiDs :: startStream() stream_.state = STREAM_RUNNING; unlock: - // MUTEX_UNLOCK( &stream_.mutex ); - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); } @@ -4507,15 +4428,6 @@ void RtApiDs :: stopStream() return; } - /* - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - */ - HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; @@ -4523,10 +4435,7 @@ void RtApiDs :: stopStream() if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; - // MUTEX_UNLOCK( &stream_.mutex ); WaitForSingleObject( handle->condition, INFINITE ); // block until signaled - //ResetEvent( handle->condition ); - // MUTEX_LOCK( &stream_.mutex ); } stream_.state = STREAM_STOPPED; @@ -4604,8 +4513,6 @@ void RtApiDs :: stopStream() unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. - // MUTEX_UNLOCK( &stream_.mutex ); - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); } @@ -4626,7 +4533,7 @@ void RtApiDs :: abortStream() void RtApiDs :: callbackEvent() { - if ( stream_.state == STREAM_STOPPED ) { + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } @@ -4642,6 +4549,8 @@ void RtApiDs :: callbackEvent() // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else @@ -4649,16 +4558,6 @@ void RtApiDs :: callbackEvent() return; } - /* - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - */ - // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { @@ -4676,7 +4575,7 @@ void RtApiDs :: callbackEvent() int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { - // MUTEX_UNLOCK( &stream_.mutex ); + stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return; @@ -5011,8 +4910,6 @@ void RtApiDs :: callbackEvent() } unlock: - // MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); } @@ -6139,11 +6036,6 @@ void RtApiAlsa :: stopStream() stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - //if ( stream_.state == STREAM_STOPPED ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // return; - //} - int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; @@ -6169,7 +6061,6 @@ void RtApiAlsa :: stopStream() } unlock: - stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; @@ -6188,11 +6079,6 @@ void RtApiAlsa :: abortStream() stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - //if ( stream_.state == STREAM_STOPPED ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // return; - //} - int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; @@ -6215,7 +6101,6 @@ void RtApiAlsa :: abortStream() } unlock: - stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; @@ -6425,6 +6310,436 @@ extern "C" void *alsaCallbackHandler( void *ptr ) //******************** End of __LINUX_ALSA__ *********************// #endif +#if defined(__LINUX_PULSE__) + +// Code written by Peter Meerwald, pmeerw@pmeerw.net +// and Tristan Matthews. + +#include +#include +#include + +namespace { +const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; } + +struct rtaudio_pa_format_mapping_t { + RtAudioFormat rtaudio_format; + pa_sample_format_t pa_format; +}; + +static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { + {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, + {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, + {0, PA_SAMPLE_INVALID}}; + +struct PulseAudioHandle { + pa_simple *s_play; + pa_simple *s_rec; + pthread_t thread; + pthread_cond_t runnable_cv; + bool runnable; + PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } +}; + +RtApiPulse::~RtApiPulse() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); +} + +unsigned int RtApiPulse::getDeviceCount( void ) +{ + return 1; +} + +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = true; + info.name = "PulseAudio"; + info.outputChannels = 2; + info.inputChannels = 2; + info.duplexChannels = 2; + info.isDefaultOutput = true; + info.isDefaultInput = true; + + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + + info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + + return info; +} + +extern "C" void *pulseaudio_callback( void * user ) +{ + CallbackInfo *cbi = static_cast( user ); + RtApiPulse *context = static_cast( cbi->object ); + volatile bool *isRunning = &cbi->isRunning; + + while ( *isRunning ) { + pthread_testcancel(); + context->callbackEvent(); + } + + pthread_exit( NULL ); +} + +void RtApiPulse::closeStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RtError::WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) + goto unlock; + + int pa_error; + size_t bytes; + switch ( stream_.mode ) { + case INPUT: + bytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); + if ( pa_simple_read( pah->s_rec, stream_.userBuffer[1], bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtError::WARNING ); + } + break; + case OUTPUT: + bytes = stream_.nUserChannels[0] * stream_.bufferSize * formatBytes( stream_.userFormat ); + if ( pa_simple_write( pah->s_play, stream_.userBuffer[0], bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtError::WARNING ); + } + break; + case DUPLEX: + bytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); + if ( pa_simple_read( pah->s_rec, stream_.userBuffer[1], bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtError::WARNING ); + } + bytes = stream_.nUserChannels[0] * stream_.bufferSize * formatBytes( stream_.userFormat ); + if ( pa_simple_write( pah->s_play, stream_.userBuffer[0], bytes, &pa_error ) < 0) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtError::WARNING ); + } + break; + default: + // ERROR + break; + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +void RtApiPulse::startStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::startStream(): the stream is not open!"; + error( RtError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + error( RtError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::stopStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; + error( RtError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + error( RtError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtError::SYSTEM_ERROR ); + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::abortStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; + error( RtError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + error( RtError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtError::SYSTEM_ERROR ); + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, + unsigned int channels, unsigned int firstChannel, + unsigned int sampleRate, RtAudioFormat format, + unsigned int *bufferSize, RtAudio::StreamOptions *options ) +{ + PulseAudioHandle *pah = 0; + unsigned long bufferBytes = 0; + pa_sample_spec ss; + + if ( device != 0 ) return false; + if ( mode != INPUT && mode != OUTPUT ) return false; + if ( channels != 1 && channels != 2 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + return false; + } + ss.channels = channels; + + if ( firstChannel != 0 ) return false; + + bool sr_found = false; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { + if ( sampleRate == *sr ) { + sr_found = true; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; + break; + } + } + if ( !sr_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; + return false; + } + + bool sf_found = 0; + for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; + sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { + if ( format == sf->rtaudio_format ) { + sf_found = true; + stream_.userFormat = sf->rtaudio_format; + ss.format = sf->pa_format; + break; + } + } + if ( !sf_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample format."; + return false; + } + + if ( options && ( options->flags & RTAUDIO_NONINTERLEAVED ) ) { + errorText_ = "RtApiPulse::probeDeviceOpen: only interleaved audio data supported."; + return false; + } + + stream_.userInterleaved = true; + stream_.nBuffers = 1; + + stream_.deviceInterleaved[mode] = true; + stream_.doByteSwap[mode] = false; + stream_.doConvertBuffer[mode] = false; + stream_.deviceFormat[mode] = stream_.userFormat; + stream_.nUserChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels; + stream_.channelOffset[mode] = 0; + + // Allocate necessary internal buffers. + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + stream_.bufferSize = *bufferSize; + + if ( !stream_.apiHandle ) { + PulseAudioHandle *pah = new PulseAudioHandle; + if ( !pah ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; + goto error; + } + + stream_.apiHandle = pah; + if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; + goto error; + } + } + pah = static_cast( stream_.apiHandle ); + + int error; + switch ( mode ) { + case INPUT: + pah->s_rec = pa_simple_new( NULL, "RtAudio", PA_STREAM_RECORD, NULL, "Record", &ss, NULL, NULL, &error ); + if ( !pah->s_rec ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; + goto error; + } + break; + case OUTPUT: + pah->s_play = pa_simple_new( NULL, "RtAudio", PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + if ( !pah->s_play ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; + goto error; + } + break; + default: + goto error; + } + + if ( stream_.mode == UNINITIALIZED ) + stream_.mode = mode; + else if ( stream_.mode == mode ) + goto error; + else + stream_.mode = DUPLEX; + + stream_.state = STREAM_STOPPED; + + if ( !stream_.callbackInfo.isRunning ) { + stream_.callbackInfo.object = this; + stream_.callbackInfo.isRunning = true; + if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } + } + return true; + + error: + closeStream(); + return false; +} + +//******************** End of __LINUX_PULSE__ *********************// +#endif #if defined(__LINUX_OSS__) -- cgit v1.2.3