From: Carl Hetherington Date: Mon, 12 Nov 2018 22:54:01 +0000 (+0000) Subject: Try to support > 2 output channels with PulseAudio. X-Git-Url: https://main.carlh.net/gitweb/?p=rtaudio-cdist.git;a=commitdiff_plain;h=6bded5a6c98849637de3f8303ba07d84d503f6d4 Try to support > 2 output channels with PulseAudio. --- diff --git a/RtAudio.cpp b/RtAudio.cpp index 539cdc2..f2bc5d1 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -418,7 +418,7 @@ double RtApi :: getStreamTime( void ) then = stream_.lastTickTimestamp; return stream_.streamTime + ((now.tv_sec + 0.000001 * now.tv_usec) - - (then.tv_sec + 0.000001 * then.tv_usec)); + (then.tv_sec + 0.000001 * then.tv_usec)); #else return stream_.streamTime; #endif @@ -1841,7 +1841,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, channelsLeft -= streamChannels; } } - + if ( stream_.doConvertBuffer[1] ) { // convert from our internal "device" buffer convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, @@ -2729,7 +2729,7 @@ RtApiAsio :: RtApiAsio() // CoInitialize beforehand, but it must be for appartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; - HRESULT hr = CoInitialize( NULL ); + HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); @@ -3180,7 +3180,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne errorText_ = errorStream_.str(); goto error; } - buffersAllocated = true; + buffersAllocated = true; stream_.state = STREAM_STOPPED; // Set flags for buffer conversion. @@ -3655,13 +3655,13 @@ static long asioMessages( long selector, long value, void* /*message*/, double* static const char* getAsioErrorString( ASIOError result ) { - struct Messages + struct Messages { ASIOError value; const char*message; }; - static const Messages m[] = + static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, @@ -4332,7 +4332,7 @@ void RtApiWasapi::startStream( void ) { verifyStream(); RtApi::startStream(); - + if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiWasapi::startStream: The stream is already running."; error( RtAudioError::WARNING ); @@ -5204,7 +5204,7 @@ Exit: #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 -// - Improvements to DirectX pointer chasing. +// - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 @@ -5244,7 +5244,7 @@ struct DsHandle { void *id[2]; void *buffer[2]; bool xrun[2]; - UINT bufferPointer[2]; + UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; @@ -6082,7 +6082,7 @@ void RtApiDs :: startStream() { verifyStream(); RtApi::startStream(); - + if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); @@ -6094,7 +6094,7 @@ void RtApiDs :: startStream() // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. - timeBeginPeriod( 1 ); + timeBeginPeriod( 1 ); buffersRolling = false; duplexPrerollBytes = 0; @@ -6415,7 +6415,7 @@ void RtApiDs :: callbackEvent() } if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { - + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( handle->drainCounter > 1 ) { // write zeros to the output stream @@ -6482,7 +6482,7 @@ void RtApiDs :: callbackEvent() } if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) - || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { + || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { // We've strayed into the forbidden zone ... resync the read pointer. handle->xrun[0] = true; nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; @@ -6556,14 +6556,14 @@ void RtApiDs :: callbackEvent() if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset DWORD endRead = nextReadPointer + bufferBytes; - // Handling depends on whether we are INPUT or DUPLEX. + // Handling depends on whether we are INPUT or DUPLEX. // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, // then a wait here will drag the write pointers into the forbidden zone. - // - // In DUPLEX mode, rather than wait, we will back off the read pointer until - // it's in a safe position. This causes dropouts, but it seems to be the only - // practical way to sync up the read and write pointers reliably, given the - // the very complex relationship between phase and increment of the read and write + // + // In DUPLEX mode, rather than wait, we will back off the read pointer until + // it's in a safe position. This causes dropouts, but it seems to be the only + // practical way to sync up the read and write pointers reliably, given the + // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in DUPLEX mode, we will @@ -6614,7 +6614,7 @@ void RtApiDs :: callbackEvent() error( RtAudioError::SYSTEM_ERROR ); return; } - + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset } } @@ -7825,7 +7825,7 @@ void RtApiAlsa :: stopStream() AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { - if ( apiInfo->synchronized ) + if ( apiInfo->synchronized ) result = snd_pcm_drop( handle[0] ); else result = snd_pcm_drain( handle[0] ); @@ -8114,6 +8114,7 @@ static void *alsaCallbackHandler( void *ptr ) #include #include +#include #include static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, @@ -8150,8 +8151,33 @@ unsigned int RtApiPulse::getDeviceCount( void ) return 1; } +void RtApiPulse::sinkInfoCallback(pa_context*, const pa_sink_info* info, int, void* arg) +{ + RtApiPulse* api = (RtApiPulse *) arg; + if (info) { + api->channels_ = info->sample_spec.channels; + } + pa_threaded_mainloop_signal(api->mainloop_, 0); +} + +void RtApiPulse::contextStateCallback(pa_context* c, void* arg) +{ + pa_threaded_mainloop* mainloop = (pa_threaded_mainloop*) arg; + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(mainloop, 0); + break; + default: + break; + } +} + RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) { + /* Set up some defaults in case we crash and burn */ RtAudio::DeviceInfo info; info.probed = true; info.name = "PulseAudio"; @@ -8167,6 +8193,72 @@ RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) info.preferredSampleRate = 48000; info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + /* Get the number of output channels from pulseaudio. A simple task, you say? + "What is your mainloop?" */ + mainloop_ = pa_threaded_mainloop_new(); + if (!mainloop_) { + return info; + } + + pa_threaded_mainloop_start(mainloop_); + pa_threaded_mainloop_lock(mainloop_); + + /* "And what is your context?" */ + pa_context* context = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), "RtAudio"); + if (!context) { + pa_threaded_mainloop_unlock(mainloop_); + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + return info; + } + + pa_context_set_state_callback(context, contextStateCallback, mainloop_); + + pa_context_connect(context, 0, (pa_context_flags_t) 0, 0); + + /* "And what is your favourite colour?" */ + int connected = 0; + pa_context_state_t state = pa_context_get_state(context); + for (; !connected; state = pa_context_get_state(context)) { + switch (state) { + case PA_CONTEXT_READY: + connected = 1; + continue; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + /* Blue! No, I mean red! */ + pa_threaded_mainloop_unlock(mainloop_); + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + return info; + default: + pa_threaded_mainloop_wait(mainloop_); + break; + } + } + + pa_operation* op = pa_context_get_sink_info_by_index(context, 0, sinkInfoCallback, this); + + if (op) { + pa_operation_unref(op); + } + + pa_threaded_mainloop_wait(mainloop_); + pa_threaded_mainloop_unlock(mainloop_); + + pa_context_disconnect(context); + pa_context_unref(context); + + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); + mainloop_ = 0; + + info.outputChannels = channels_; + return info; } @@ -8293,7 +8385,7 @@ void RtApiPulse::callbackEvent( void ) else bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); - + if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; @@ -8326,7 +8418,7 @@ void RtApiPulse::callbackEvent( void ) void RtApiPulse::startStream( void ) { RtApi::startStream(); - + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { @@ -8430,10 +8522,6 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, 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; @@ -8581,7 +8669,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, stream_.state = STREAM_STOPPED; return true; - + error: if ( pah && stream_.callbackInfo.isRunning ) { pthread_cond_destroy( &pah->runnable_cv ); @@ -10257,4 +10345,3 @@ void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat // End: // // vim: et sts=2 sw=2 - diff --git a/RtAudio.h b/RtAudio.h index 78fca0c..ba880f3 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -111,7 +111,7 @@ static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/mi open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. - If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to @@ -215,7 +215,7 @@ class RtAudioError : public std::exception //! The constructor. RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} - + //! The destructor. virtual ~RtAudioError( void ) throw() {} @@ -341,7 +341,7 @@ class RtAudio open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. - If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME flag is set. It defines the thread's realtime priority. @@ -410,7 +410,7 @@ class RtAudio /*! This function performs a system query of available devices each time it is called, thus supporting devices connected \e after instantiation. If - a system error occurs during processing, a warning will be issued. + a system error occurs during processing, a warning will be issued. */ unsigned int getDeviceCount( void ) throw(); @@ -477,7 +477,7 @@ class RtAudio from within the callback function. \param options An optional pointer to a structure containing various global stream options, including a list of OR'ed RtAudioStreamFlags - and a suggested number of stream buffers that can be used to + and a suggested number of stream buffers that can be used to control stream latency. More buffers typically result in more robust performance, though at a cost of greater latency. If a value of zero is specified, a system-specific median value is @@ -785,7 +785,7 @@ protected: "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ - virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -877,7 +877,7 @@ public: private: - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -911,7 +911,7 @@ public: private: - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -947,7 +947,7 @@ public: std::vector devices_; void saveDeviceInfo( void ); bool coInitialized_; - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -986,7 +986,7 @@ public: bool buffersRolling; long duplexPrerollBytes; std::vector dsDevices; - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -1057,7 +1057,7 @@ public: std::vector devices_; void saveDeviceInfo( void ); - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -1067,9 +1067,14 @@ public: #if defined(__LINUX_PULSE__) +struct pa_context; +struct pa_sink_info; +struct pa_threaded_mainloop; + class RtApiPulse: public RtApi { public: + RtApiPulse() : mainloop_(0), channels_(2) {} ~RtApiPulse(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } unsigned int getDeviceCount( void ); @@ -1093,6 +1098,11 @@ public: unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); + + static void sinkInfoCallback(pa_context* c, const pa_sink_info* info, int eol, void* arg); + static void contextStateCallback(pa_context* c, void* arg); + pa_threaded_mainloop* mainloop_; + int channels_; }; #endif @@ -1121,7 +1131,7 @@ public: private: - bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); @@ -1146,7 +1156,7 @@ public: private: - bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { return false; }