+ 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( RtAudioError::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( RtAudioError::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( RtAudioError::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( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_RUNNING ) {\r
+ errorText_ = "RtApiPulse::startStream(): the stream is already running!";\r
+ error( RtAudioError::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( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!";\r
+ error( RtAudioError::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( RtAudioError::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( RtAudioError::INVALID_USE );\r
+ return;\r
+ }\r
+ if ( stream_.state == STREAM_STOPPED ) {\r
+ errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!";\r
+ error( RtAudioError::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( RtAudioError::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
+ std::string streamName = "RtAudio";\r
+ if ( !options->streamName.empty() ) streamName = options->streamName;\r
+ switch ( mode ) {\r
+ case INPUT:\r
+ pa_buffer_attr buffer_attr;\r
+ buffer_attr.fragsize = bufferBytes;\r
+ buffer_attr.maxlength = -1;\r
+\r
+ pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &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
+\r
+#if defined(__LINUX_OSS__)\r
+\r
+#include <unistd.h>\r
+#include <sys/ioctl.h>\r
+#include <unistd.h>\r
+#include <fcntl.h>\r
+#include <sys/soundcard.h>\r
+#include <errno.h>\r
+#include <math.h>\r
+\r
+static void *ossCallbackHandler(void * ptr);\r
+\r
+// A structure to hold various information related to the OSS API\r
+// implementation.\r
+struct OssHandle {\r
+ int id[2]; // device ids\r
+ bool xrun[2];\r
+ bool triggered;\r
+ pthread_cond_t runnable;\r
+\r
+ OssHandle()\r
+ :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; }\r
+};\r
+\r
+RtApiOss :: RtApiOss()\r
+{\r
+ // Nothing to do here.\r
+}\r
+\r
+RtApiOss :: ~RtApiOss()\r
+{\r
+ if ( stream_.state != STREAM_CLOSED ) closeStream();\r
+}\r
+\r
+unsigned int RtApiOss :: getDeviceCount( void )\r
+{\r
+ int mixerfd = open( "/dev/mixer", O_RDWR, 0 );\r
+ if ( mixerfd == -1 ) {\r