Skip to content

Commit

Permalink
Add getDeviceHandle() to expose client-specific structs.
Browse files Browse the repository at this point in the history
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 <jack/jack.h>
    #define RTAUDIO_API_SPECIFIC_JACK
    #include <RtAudio.h>

    ...
    RtAudio audio(RtAudio::UNIX_JACK);
    .. after openStream
    RtAudioClientHandle *h = audio.getClientHandle();
    RtAudioClientHandleJack *h_jack =
      dynamic_cast<RtAudioClientHandleJack*>(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.
  • Loading branch information
radarsat1 committed May 11, 2020
1 parent 4e15551 commit 3035f22
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 55 deletions.
150 changes: 97 additions & 53 deletions RtAudio.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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"

// *************************************************** //
//
Expand Down Expand Up @@ -2013,19 +2021,28 @@ const char* RtApiCore :: getErrorCode( OSStatus code )
#include <unistd.h>
#include <cstdio>

// 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;
int drainCounter; // Tracks callback counts when draining
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__)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand All @@ -2419,25 +2443,26 @@ 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.
char label[64];
if ( mode == OUTPUT ) {
for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {
snprintf( label, 64, "outport %d", i );
handle->ports[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; i<stream_.nUserChannels[1]; i++ ) {
snprintf( label, 64, "inport %d", i );
handle->ports[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 );
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -2555,7 +2581,8 @@ void RtApiJack :: startStream( void )
for ( unsigned int i=0; i<stream_.nUserChannels[0]; i++ ) {
result = 1;
if ( ports[ stream_.channelOffset[0] + i ] )
result = jack_connect( handle->client, 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!";
Expand All @@ -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;
Expand All @@ -2577,7 +2605,8 @@ void RtApiJack :: startStream( void )
for ( unsigned int i=0; i<stream_.nUserChannels[1]; i++ ) {
result = 1;
if ( ports[ stream_.channelOffset[1] + i ] )
result = jack_connect( handle->client, 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!";
Expand Down Expand Up @@ -2614,7 +2643,7 @@ void RtApiJack :: stopStream( void )
}
}

jack_deactivate( handle->client );
jack_deactivate( handle->ch.client );
stream_.state = STREAM_STOPPED;
}

Expand Down Expand Up @@ -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; i<stream_.nDeviceChannels[0]; i++ ) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[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 );
}

Expand All @@ -2721,13 +2751,15 @@ bool RtApiJack :: callbackEvent( unsigned long nframes )
convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] );

for ( unsigned int i=0; i<stream_.nDeviceChannels[0]; i++ ) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[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; i<stream_.nUserChannels[0]; i++ ) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[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 );
}
}
Expand All @@ -2743,14 +2775,16 @@ bool RtApiJack :: callbackEvent( unsigned long nframes )

if ( stream_.doConvertBuffer[1] ) {
for ( unsigned int i=0; i<stream_.nDeviceChannels[1]; i++ ) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[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; i<stream_.nUserChannels[1]; i++ ) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer( handle->ports[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 );
}
}
Expand Down Expand Up @@ -8489,13 +8523,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) {
Expand Down Expand Up @@ -8737,12 +8775,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;
Expand Down Expand Up @@ -8817,7 +8855,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();
Expand All @@ -8833,7 +8871,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();
Expand Down Expand Up @@ -8902,9 +8940,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();
Expand Down Expand Up @@ -8939,9 +8977,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();
Expand Down Expand Up @@ -9121,17 +9159,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;
}
Expand Down Expand Up @@ -9229,6 +9267,12 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode,
return FAILURE;
}

RtAudioClientHandle* RtApiPulse :: getClientHandle( void )
{
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );
return &pah->ch;
}

//******************** End of __LINUX_PULSE__ *********************//
#endif

Expand Down

0 comments on commit 3035f22

Please sign in to comment.