+#if defined(__LINUX_PULSE__)\r
+\r
+// Code written by Peter Meerwald, pmeerw@pmeerw.net\r
+// and Tristan Matthews.\r
+\r
+#include <pulse/error.h>\r
+#include <pulse/simple.h>\r
+#include <cstdio>\r
+\r
+static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000,\r
+ 44100, 48000, 96000, 0};\r
+\r
+struct rtaudio_pa_format_mapping_t {\r
+ RtAudioFormat rtaudio_format;\r
+ pa_sample_format_t pa_format;\r
+};\r
+\r
+static const rtaudio_pa_format_mapping_t supported_sampleformats[] = {\r
+ {RTAUDIO_SINT16, PA_SAMPLE_S16LE},\r
+ {RTAUDIO_SINT32, PA_SAMPLE_S32LE},\r
+ {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE},\r
+ {0, PA_SAMPLE_INVALID}};\r
+\r
+struct PulseAudioHandle {\r
+ pa_simple *s_play;\r
+ pa_simple *s_rec;\r
+ pthread_t thread;\r
+ pthread_cond_t runnable_cv;\r
+ bool runnable;\r
+ PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { }\r
+};\r
+\r
+RtApiPulse::~RtApiPulse()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED )\r
+ closeStream();\r
+}\r
+\r
+unsigned int RtApiPulse::getDeviceCount( void )\r
+{\r
+ return 1;\r
+}\r
+\r
+RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device )\r
+{\r
+ RtAudio::DeviceInfo info;\r
+ info.probed = true;\r
+ info.name = "PulseAudio";\r
+ info.outputChannels = 2;\r
+ info.inputChannels = 2;\r
+ info.duplexChannels = 2;\r
+ info.isDefaultOutput = true;\r
+ info.isDefaultInput = true;\r
+\r
+ for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr )\r
+ info.sampleRates.push_back( *sr );\r
+\r
+ info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32;\r
+\r
+ return info;\r
+}\r
+\r
+static void *pulseaudio_callback( void * user )\r
+{\r
+ CallbackInfo *cbi = static_cast<CallbackInfo *>( user );\r
+ RtApiPulse *context = static_cast<RtApiPulse *>( cbi->object );\r
+ volatile bool *isRunning = &cbi->isRunning;\r
+\r
+ while ( *isRunning ) {\r
+ pthread_testcancel();\r
+ context->callbackEvent();\r
+ }\r
+\r
+ pthread_exit( NULL );\r
+}\r
+\r
+void RtApiPulse::closeStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ stream_.callbackInfo.isRunning = false;\r
+ if ( pah ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ pah->runnable = true;\r
+ pthread_cond_signal( &pah->runnable_cv );\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+\r
+ pthread_join( pah->thread, 0 );\r
+ if ( pah->s_play ) {\r
+ pa_simple_flush( pah->s_play, NULL );\r
+ pa_simple_free( pah->s_play );\r
+ }\r
+ if ( pah->s_rec )\r
+ pa_simple_free( pah->s_rec );\r
+\r
+ pthread_cond_destroy( &pah->runnable_cv );\r
+ delete pah;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ if ( stream_.userBuffer[0] ) {\r
+ free( stream_.userBuffer[0] );\r
+ stream_.userBuffer[0] = 0;\r
+ }\r
+ if ( stream_.userBuffer[1] ) {\r
+ free( stream_.userBuffer[1] );\r
+ stream_.userBuffer[1] = 0;\r
+ }\r
+\r
+ stream_.state = STREAM_CLOSED;\r
+ stream_.mode = UNINITIALIZED;\r
+}\r
+\r
+void RtApiPulse::callbackEvent( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ while ( !pah->runnable )\r
+ pthread_cond_wait( &pah->runnable_cv, &stream_.mutex );\r
+\r
+ if ( stream_.state != STREAM_RUNNING ) {\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ return;\r
+ }\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ }\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... "\r
+ "this shouldn't happen!";\r
+ error( RtError::WARNING );\r
+ return;\r
+ }\r
+\r
+ RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback;\r
+ double streamTime = getStreamTime();\r
+ RtAudioStreamStatus status = 0;\r
+ int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT],\r
+ stream_.bufferSize, streamTime, status,\r
+ stream_.callbackInfo.userData );\r
+\r
+ if ( doStopStream == 2 ) {\r
+ abortStream();\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+ void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT];\r
+ void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT];\r
+\r
+ if ( stream_.state != STREAM_RUNNING )\r
+ goto unlock;\r
+\r
+ int pa_error;\r
+ size_t bytes;\r
+ if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {\r
+ if ( stream_.doConvertBuffer[OUTPUT] ) {\r
+ convertBuffer( stream_.deviceBuffer,\r
+ stream_.userBuffer[OUTPUT],\r
+ stream_.convertInfo[OUTPUT] );\r
+ bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.deviceFormat[OUTPUT] );\r
+ } else\r
+ bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.userFormat );\r
+\r
+ if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::callbackEvent: audio write error, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtError::WARNING );\r
+ }\r
+ }\r
+\r
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX) {\r
+ if ( stream_.doConvertBuffer[INPUT] )\r
+ bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.deviceFormat[INPUT] );\r
+ else\r
+ bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *\r
+ formatBytes( stream_.userFormat );\r
+ \r
+ if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::callbackEvent: audio read error, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ error( RtError::WARNING );\r
+ }\r
+ if ( stream_.doConvertBuffer[INPUT] ) {\r
+ convertBuffer( stream_.userBuffer[INPUT],\r
+ stream_.deviceBuffer,\r
+ stream_.convertInfo[INPUT] );\r
+ }\r
+ }\r
+\r
+ unlock:\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ RtApi::tickStreamTime();\r
+\r
+ if ( doStopStream == 1 )\r
+ stopStream();\r
+}\r
+\r
+void RtApiPulse::startStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::startStream(): the stream is not open!";\r
+ error( RtError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiPulse::startStream(): the stream is already running!";\r
+ error( RtError::WARNING );\r
+ return;\r
+ }\r
+\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ stream_.state = STREAM_RUNNING;\r
+\r
+ pah->runnable = true;\r
+ pthread_cond_signal( &pah->runnable_cv );\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+void RtApiPulse::stopStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::stopStream(): the stream is not open!";\r
+ error( RtError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!";\r
+ error( RtError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ if ( pah && pah->s_play ) {\r
+ int pa_error;\r
+ if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::stopStream: error draining output device, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+void RtApiPulse::abortStream( void )\r
+{\r
+ PulseAudioHandle *pah = static_cast<PulseAudioHandle*>( stream_.apiHandle );\r
+\r
+ if ( stream_.state == STREAM_CLOSED ) {\r
+ errorText_ = "RtApiPulse::abortStream(): the stream is not open!";\r
+ error( RtError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!";\r
+ error( RtError::WARNING );\r
+ return;\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_LOCK( &stream_.mutex );\r
+\r
+ if ( pah && pah->s_play ) {\r
+ int pa_error;\r
+ if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) {\r
+ errorStream_ << "RtApiPulse::abortStream: error flushing output device, " <<\r
+ pa_strerror( pa_error ) << ".";\r
+ errorText_ = errorStream_.str();\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+ error( RtError::SYSTEM_ERROR );\r
+ return;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ MUTEX_UNLOCK( &stream_.mutex );\r
+}\r
+\r
+bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,\r
+ unsigned int channels, unsigned int firstChannel,\r
+ unsigned int sampleRate, RtAudioFormat format,\r
+ unsigned int *bufferSize, RtAudio::StreamOptions *options )\r
+{\r
+ PulseAudioHandle *pah = 0;\r
+ unsigned long bufferBytes = 0;\r
+ pa_sample_spec ss;\r
+\r
+ if ( device != 0 ) return false;\r
+ if ( mode != INPUT && mode != OUTPUT ) return false;\r
+ if ( channels != 1 && channels != 2 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels.";\r
+ return false;\r
+ }\r
+ ss.channels = channels;\r
+\r
+ if ( firstChannel != 0 ) return false;\r
+\r
+ bool sr_found = false;\r
+ for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) {\r
+ if ( sampleRate == *sr ) {\r
+ sr_found = true;\r
+ stream_.sampleRate = sampleRate;\r
+ ss.rate = sampleRate;\r
+ break;\r
+ }\r
+ }\r
+ if ( !sr_found ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate.";\r
+ return false;\r
+ }\r
+\r
+ bool sf_found = 0;\r
+ for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats;\r
+ sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) {\r
+ if ( format == sf->rtaudio_format ) {\r
+ sf_found = true;\r
+ stream_.userFormat = sf->rtaudio_format;\r
+ ss.format = sf->pa_format;\r
+ break;\r
+ }\r
+ }\r
+ if ( !sf_found ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample format.";\r
+ return false;\r
+ }\r
+\r
+ // Set interleaving parameters.\r
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false;\r
+ else stream_.userInterleaved = true;\r
+ stream_.deviceInterleaved[mode] = true;\r
+ stream_.nBuffers = 1;\r
+ stream_.doByteSwap[mode] = false;\r
+ stream_.doConvertBuffer[mode] = channels > 1 && !stream_.userInterleaved;\r
+ stream_.deviceFormat[mode] = stream_.userFormat;\r
+ stream_.nUserChannels[mode] = channels;\r
+ stream_.nDeviceChannels[mode] = channels + firstChannel;\r
+ stream_.channelOffset[mode] = 0;\r
+\r
+ // Allocate necessary internal buffers.\r
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );\r
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.userBuffer[mode] == NULL ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory.";\r
+ goto error;\r
+ }\r
+ stream_.bufferSize = *bufferSize;\r
+\r
+ if ( stream_.doConvertBuffer[mode] ) {\r
+\r
+ bool makeBuffer = true;\r
+ bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] );\r
+ if ( mode == INPUT ) {\r
+ if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) {\r
+ unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] );\r
+ if ( bufferBytes <= bytesOut ) makeBuffer = false;\r
+ }\r
+ }\r
+\r
+ if ( makeBuffer ) {\r
+ bufferBytes *= *bufferSize;\r
+ if ( stream_.deviceBuffer ) free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 );\r
+ if ( stream_.deviceBuffer == NULL ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory.";\r
+ goto error;\r
+ }\r
+ }\r
+ }\r
+\r
+ stream_.device[mode] = device;\r
+\r
+ // Setup the buffer conversion information structure.\r
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );\r
+\r
+ if ( !stream_.apiHandle ) {\r
+ PulseAudioHandle *pah = new PulseAudioHandle;\r
+ if ( !pah ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle.";\r
+ goto error;\r
+ }\r
+\r
+ stream_.apiHandle = pah;\r
+ if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable.";\r
+ goto error;\r
+ }\r
+ }\r
+ pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );\r
+\r
+ int error;\r
+ switch ( mode ) {\r
+ case INPUT:\r
+ pah->s_rec = pa_simple_new( NULL, "RtAudio", PA_STREAM_RECORD, NULL, "Record", &ss, NULL, NULL, &error );\r
+ if ( !pah->s_rec ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server.";\r
+ goto error;\r
+ }\r
+ break;\r
+ case OUTPUT:\r
+ pah->s_play = pa_simple_new( NULL, "RtAudio", PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error );\r
+ if ( !pah->s_play ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server.";\r
+ goto error;\r
+ }\r
+ break;\r
+ default:\r
+ goto error;\r
+ }\r
+\r
+ if ( stream_.mode == UNINITIALIZED )\r
+ stream_.mode = mode;\r
+ else if ( stream_.mode == mode )\r
+ goto error;\r
+ else\r
+ stream_.mode = DUPLEX;\r
+\r
+ if ( !stream_.callbackInfo.isRunning ) {\r
+ stream_.callbackInfo.object = this;\r
+ stream_.callbackInfo.isRunning = true;\r
+ if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) {\r
+ errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread.";\r
+ goto error;\r
+ }\r
+ }\r
+\r
+ stream_.state = STREAM_STOPPED;\r
+ return true;\r
+ \r
+ error:\r
+ if ( pah && stream_.callbackInfo.isRunning ) {\r
+ pthread_cond_destroy( &pah->runnable_cv );\r
+ delete pah;\r
+ stream_.apiHandle = 0;\r
+ }\r
+\r
+ for ( int i=0; i<2; i++ ) {\r
+ if ( stream_.userBuffer[i] ) {\r
+ free( stream_.userBuffer[i] );\r
+ stream_.userBuffer[i] = 0;\r
+ }\r
+ }\r
+\r
+ if ( stream_.deviceBuffer ) {\r
+ free( stream_.deviceBuffer );\r
+ stream_.deviceBuffer = 0;\r
+ }\r
+\r
+ return FAILURE;\r
+}\r
+\r
+//******************** End of __LINUX_PULSE__ *********************//\r
+#endif\r