From 4f7a17ab37e44cd999b06434af497339b7825925 Mon Sep 17 00:00:00 2001 From: Stephen Sinclair Date: Mon, 11 May 2020 13:46:26 +0200 Subject: [PATCH] Add getDeviceHandle() to expose client-specific structs. In cases where the caller really needs access to the API-specific handle (e.g. jack_client_t*), then it can be returned in a derived struct. Since this means that API headers must be included before RtAudio.h, it is not enabled by default; the caller must include them him/herself and define RTAUDIO_API_SPECIFIC, or RTAUDIO_API_SPECIFIC_JACK, etc., before including RtAudio.h. Then, getDeviceHandle() returns a pointer that can be safely dynamic_cast<> so that if there is an API mismatch, nullptr is returned. This commit implements it for Jack and Pulse Audio only! Example: #include #define RTAUDIO_API_SPECIFIC_JACK #include ... RtAudio audio(RtAudio::UNIX_JACK); .. after openStream RtAudioClientHandle *h = audio.getClientHandle(); RtAudioClientHandleJack *h_jack = dynamic_cast(h); if (h_jack) { .. my_function_needing_jack_client_t(h_jack.client); } Note that the above code will not crash if RtAudio::LINUX_PULSE was selected, and only call Jack-specific functions if indeed Jack is the current API. --- RtAudio.cpp | 150 +++++++++++++++++++++++++++++++++------------------- RtAudio.h | 52 +++++++++++++++++- 2 files changed, 147 insertions(+), 55 deletions(-) diff --git a/RtAudio.cpp b/RtAudio.cpp index bd5cf635..c8929fbf 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -286,6 +286,11 @@ void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, userData, options, errorCallback ); } +RtAudioClientHandle *RtAudio :: getClientHandle( void ) +{ + return rtapi_->getClientHandle(); +} + // *************************************************** // // // Public RtApi definitions (see end of file for @@ -497,6 +502,9 @@ unsigned int RtApi :: getStreamSampleRate( void ) return stream_.sampleRate; } +// Re-include RtAudio.h to get API-specific structs +#define RTAUDIO_API_SPECIFIC +#include "RtAudio.h" // *************************************************** // // @@ -2013,11 +2021,19 @@ const char* RtApiCore :: getErrorCode( OSStatus code ) #include #include +// A structure to hold various information related to the Jack API +// implementation that can be exposed to caller. Same struct is +// defined in RtAudio.h if RTAUDIO_API_SPECIFIC is defined. +struct RtAudioClientHandleJack : public RtAudioClientHandle +{ + jack_client_t *client = 0; + jack_port_t **ports[2] = {0,0}; +}; + // A structure to hold various information related to the Jack API // implementation. struct JackHandle { - jack_client_t *client; - jack_port_t **ports[2]; + RtAudioClientHandleJack ch; std::string deviceName[2]; bool xrun[2]; pthread_cond_t condition; @@ -2025,7 +2041,8 @@ struct JackHandle { bool internalDrain; // Indicates if stop is initiated from callback or not. JackHandle() - :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } + : drainCounter(0), internalDrain(false) + { xrun[0] = false; xrun[1] = false; } }; #if !defined(__RTAUDIO_DEBUG__) @@ -2172,6 +2189,13 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) return info; } +RtAudioClientHandle *RtApiJack :: getClientHandle() +{ + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + return &handle->ch; +} + static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -2216,8 +2240,8 @@ static int jackXrun( void *infoPointer ) { JackHandle *handle = *((JackHandle **) infoPointer); - if ( handle->ports[0] ) handle->xrun[0] = true; - if ( handle->ports[1] ) handle->xrun[1] = true; + if ( handle->ch.ports[0] ) handle->xrun[0] = true; + if ( handle->ch.ports[1] ) handle->xrun[1] = true; return 0; } @@ -2246,7 +2270,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } else { // The handle must have been created on an earlier pass. - client = handle->client; + client = handle->ch.client; } const char **ports; @@ -2365,7 +2389,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto error; } stream_.apiHandle = (void *) handle; - handle->client = client; + handle->ch.client = client; } handle->deviceName[mode] = deviceName; @@ -2403,8 +2427,8 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } // Allocate memory for the Jack ports (channels) identifiers. - handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); - if ( handle->ports[mode] == NULL ) { + handle->ch.ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); + if ( handle->ch.ports[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; goto error; } @@ -2419,9 +2443,10 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.mode = DUPLEX; else { stream_.mode = mode; - jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); - jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); - jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); + jack_set_process_callback( handle->ch.client, jackCallbackHandler, + (void *) &stream_.callbackInfo ); + jack_set_xrun_callback( handle->ch.client, jackXrun, (void *) &stream_.apiHandle ); + jack_on_shutdown( handle->ch.client, jackShutdown, (void *) &stream_.callbackInfo ); } // Register our ports. @@ -2429,15 +2454,15 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( mode == OUTPUT ) { for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + handle->ch.ports[0][i] = jack_port_register( handle->ch.client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } } else { for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, - JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + handle->ch.ports[1][i] = jack_port_register( handle->ch.client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); } } @@ -2453,10 +2478,10 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne error: if ( handle ) { pthread_cond_destroy( &handle->condition ); - jack_client_close( handle->client ); + jack_client_close( handle->ch.client ); - if ( handle->ports[0] ) free( handle->ports[0] ); - if ( handle->ports[1] ) free( handle->ports[1] ); + if ( handle->ch.ports[0] ) free( handle->ch.ports[0] ); + if ( handle->ch.ports[1] ) free( handle->ch.ports[1] ); delete handle; stream_.apiHandle = 0; @@ -2489,14 +2514,14 @@ void RtApiJack :: closeStream( void ) if ( handle ) { if ( stream_.state == STREAM_RUNNING ) - jack_deactivate( handle->client ); + jack_deactivate( handle->ch.client ); - jack_client_close( handle->client ); + jack_client_close( handle->ch.client ); } if ( handle ) { - if ( handle->ports[0] ) free( handle->ports[0] ); - if ( handle->ports[1] ) free( handle->ports[1] ); + if ( handle->ch.ports[0] ) free( handle->ch.ports[0] ); + if ( handle->ch.ports[1] ) free( handle->ch.ports[1] ); pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; @@ -2532,7 +2557,7 @@ void RtApiJack :: startStream( void ) #endif JackHandle *handle = (JackHandle *) stream_.apiHandle; - int result = jack_activate( handle->client ); + int result = jack_activate( handle->ch.client ); if ( result ) { errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; goto unlock; @@ -2543,7 +2568,8 @@ void RtApiJack :: startStream( void ) // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); + ports = jack_get_ports( handle->ch.client, handle->deviceName[0].c_str(), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; @@ -2555,7 +2581,8 @@ void RtApiJack :: startStream( void ) for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); + result = jack_connect( handle->ch.client, jack_port_name( handle->ch.ports[0][i] ), + ports[ stream_.channelOffset[0] + i ] ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting output ports!"; @@ -2567,7 +2594,8 @@ void RtApiJack :: startStream( void ) if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); + ports = jack_get_ports( handle->ch.client, handle->deviceName[1].c_str(), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; @@ -2577,7 +2605,8 @@ void RtApiJack :: startStream( void ) for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); + result = jack_connect( handle->ch.client, ports[ stream_.channelOffset[1] + i ], + jack_port_name( handle->ch.ports[1][i] ) ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting input ports!"; @@ -2614,7 +2643,7 @@ void RtApiJack :: stopStream( void ) } } - jack_deactivate( handle->client ); + jack_deactivate( handle->ch.client ); stream_.state = STREAM_STOPPED; } @@ -2711,7 +2740,8 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( + handle->ch.ports[0][i], (jack_nframes_t) nframes ); memset( jackbuffer, 0, bufferBytes ); } @@ -2721,13 +2751,15 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( + handle->ch.ports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); } } else { // no buffer conversion for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( + handle->ch.ports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); } } @@ -2743,14 +2775,16 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) if ( stream_.doConvertBuffer[1] ) { for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( + handle->ch.ports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); } convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { // no buffer conversion for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( + handle->ch.ports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); } } @@ -8493,13 +8527,17 @@ static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; +struct RtAudioClientHandlePulse : public RtAudioClientHandle +{ + pa_simple *s_play = nullptr; + pa_simple *s_rec = nullptr; +}; + struct PulseAudioHandle { - pa_simple *s_play; - pa_simple *s_rec; + RtAudioClientHandlePulse ch; pthread_t thread; pthread_cond_t runnable_cv; - bool runnable; - PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } + bool runnable = false; }; static void rt_pa_mainloop_api_quit(int ret) { @@ -8741,12 +8779,12 @@ void RtApiPulse::closeStream( void ) 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->ch.s_play ) { + pa_simple_flush( pah->ch.s_play, NULL ); + pa_simple_free( pah->ch.s_play ); } - if ( pah->s_rec ) - pa_simple_free( pah->s_rec ); + if ( pah->ch.s_rec ) + pa_simple_free( pah->ch.s_rec ); pthread_cond_destroy( &pah->runnable_cv ); delete pah; @@ -8821,7 +8859,7 @@ void RtApiPulse::callbackEvent( void ) bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { + if ( pa_simple_write( pah->ch.s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); @@ -8837,7 +8875,7 @@ void RtApiPulse::callbackEvent( void ) bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); - if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { + if ( pa_simple_read( pah->ch.s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); @@ -8906,9 +8944,9 @@ void RtApiPulse::stopStream( void ) if ( pah ) { pah->runnable = false; - if ( pah->s_play ) { + if ( pah->ch.s_play ) { int pa_error; - if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + if ( pa_simple_drain( pah->ch.s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::stopStream: error draining output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); @@ -8943,9 +8981,9 @@ void RtApiPulse::abortStream( void ) if ( pah ) { pah->runnable = false; - if ( pah->s_play ) { + if ( pah->ch.s_play ) { int pa_error; - if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + if ( pa_simple_flush( pah->ch.s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); @@ -9125,17 +9163,17 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; - pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, + pah->ch.s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, dev_input, "Record", &ss, NULL, &buffer_attr, &error ); - if ( !pah->s_rec ) { + if ( !pah->ch.s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; case OUTPUT: - pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, + pah->ch.s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, dev_output, "Playback", &ss, NULL, NULL, &error ); - if ( !pah->s_play ) { + if ( !pah->ch.s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } @@ -9233,6 +9271,12 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, return FAILURE; } +RtAudioClientHandle* RtApiPulse :: getClientHandle( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + return &pah->ch; +} + //******************** End of __LINUX_PULSE__ *********************// #endif diff --git a/RtAudio.h b/RtAudio.h index 834302a7..29699438 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -44,7 +44,6 @@ */ #ifndef __RTAUDIO_H -#define __RTAUDIO_H #define RTAUDIO_VERSION "5.1.0" @@ -66,6 +65,7 @@ #include #include #include +#include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. @@ -261,6 +261,21 @@ class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error */ typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); +/************************************************************************/ +/*! \class RtAudioClientHandle + \brief Base class for API-specific handles. + + This is pointed to by RtAudio::StreamOptions.clientHandle if + stream was opened successfully. + + Caller must #include appropriate API headers and #define + RTAUDIO_API_SPECIFIC to get access to derived classes. +*/ +/************************************************************************/ +struct RtAudioClientHandle { + virtual ~RtAudioClientHandle() {} +}; + // **************************************************************** // // // RtAudio class declaration. @@ -606,6 +621,16 @@ class RTAUDIO_DLL_PUBLIC RtAudio */ unsigned int getStreamSampleRate( void ); + //! Returns a struct containing API-specific client data. + /*! + To use the returned struct it must be dynamic_cast<> to an + API-specific derived struct, which is only available by including + the appropriate API headers and defining RTAUDIO_API_SPECIFIC + before including RtAudio.h. The dynamic_cast<> shall return + nullptr if the expected API is not the one in use. + */ + RtAudioClientHandle *getClientHandle(); + //! Specify whether warning messages should be printed to stderr. void showWarnings( bool value = true ); @@ -740,7 +765,7 @@ class RTAUDIO_DLL_PUBLIC RtApi bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void showWarnings( bool value ) { showWarnings_ = value; } - + virtual RtAudioClientHandle *getClientHandle() { return nullptr; } protected: @@ -940,6 +965,7 @@ class RtApiJack: public RtApi void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; + RtAudioClientHandle *getClientHandle( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -957,6 +983,16 @@ class RtApiJack: public RtApi bool shouldAutoconnect_; }; +#if defined(RTAUDIO_API_SPECIFIC) || defined(RTAUDIO_API_SPECIFIC_JACK) +struct RtAudioClientHandleJack : public RtAudioClientHandle +{ +/* If compiler errors encountered here: recall you are responsible for + * including API headers if you define RTAUDIO_API_SPECIFIC_*! */ + jack_client_t *client; + jack_port_t **ports[2]; +}; +#endif + #endif #if defined(__WINDOWS_ASIO__) @@ -1116,6 +1152,7 @@ class RtApiPulse: public RtApi void startStream( void ) override; void stopStream( void ) override; void abortStream( void ) override; + RtAudioClientHandle *getClientHandle( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, @@ -1132,6 +1169,16 @@ class RtApiPulse: public RtApi RtAudio::StreamOptions *options ) override; }; +#if defined(RTAUDIO_API_SPECIFIC) || defined(RTAUDIO_API_SPECIFIC_PULSE) +struct RtAudioClientHandlePulse : public RtAudioClientHandle +{ +/* If compiler errors encountered here: recall you are responsible for + * including API headers if you define RTAUDIO_API_SPECIFIC_*! */ + pa_simple *s_play = nullptr; + pa_simple *s_rec = nullptr; +}; +#endif + #endif #if defined(__LINUX_OSS__) @@ -1191,6 +1238,7 @@ class RtApiDummy: public RtApi #endif +#define __RTAUDIO_H #endif // Indentation settings for Vim and Emacs