Skip to content

Commit

Permalink
JACK: support connecting to no device (only creating ports)
Browse files Browse the repository at this point in the history
Previously, using the JACK backend meant that for each port:
 - We call jack_port_register() to create the port,
 - And call jack_connect() to create a connection between the newly
   created port and a target (the parameters->device argument).

In the JACK world, it is common for processes to spawn a node with ports
and let somebody else do the routing. We therefore allow creating a
stream that targets as input and/or output paNoDevice.

This cannot be done directly using:

     PaStreamParameters outputParams = {
          .device = paNoDevice,
          // ...
     };

As with this, PortAudio cannot use the right API backend.
Instead, we do this:

     PaJackStreamInfo streamInfo;
     PaJack_InitializeNoDeviceStreamInfo(&streamInfo);

     PaStreamParameters outputParams = {
          .device = paUseHostApiSpecificDeviceSpecification,
          .hostApiSpecificStreamInfo = &streamInfo,
          // ...
     };

streamInfo contains the standard API-specific header plus a
PaDeviceIndex device field that _must_ contain paNoDevice.

Some more details of changeset in pa_jack.c:

 - Move parameter validation into an helper called from both
   IsFormatSupported() and OpenStream().

 - If API-specific info is provided and its device field is paNoDevice,
   we create ports without calling jack_connect().

 - We do not allocate stream->remote_*_ports in this case. That allows
   us to detect throughout the driver without storing additional
   state.

Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
  • Loading branch information
tleb committed Jun 24, 2024
1 parent 18a606e commit 9d0614b
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 93 deletions.
13 changes: 13 additions & 0 deletions include/pa_jack.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@
extern "C" {
#endif

typedef struct PaJackStreamInfo
{
unsigned long size;
PaHostApiTypeId hostApiType;
unsigned long version;

PaDeviceIndex device;
}
PaJackStreamInfo;

/** Initialize host API specific structure, to ask for no device connection. */
void PaJack_InitializeNoDeviceStreamInfo( PaJackStreamInfo *info );

/** Set the JACK client name.
*
* During Pa_Initialize, When PA JACK connects as a client of the JACK server, it requests a certain
Expand Down
171 changes: 78 additions & 93 deletions src/hostapi/jack/pa_jack.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ static double GetStreamCpuLoad( PaStream* stream );

struct PaJackStream;

typedef enum
{
StreamDirection_In,
StreamDirection_Out
} StreamDirection;

typedef struct
{
PaUtilHostApiRepresentation commonHostApiRep;
Expand Down Expand Up @@ -895,61 +901,47 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
jackErr_ = NULL;
}

static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
const PaStreamParameters *inputParameters,
const PaStreamParameters *outputParameters,
double sampleRate )
static PaError ValidateParameters( const PaStreamParameters *params, PaUtilHostApiRepresentation *hostApi, StreamDirection mode )
{
int inputChannelCount = 0, outputChannelCount = 0;
PaSampleFormat inputSampleFormat, outputSampleFormat;
PaError result = paNoError;

if( inputParameters )
if ( params && params->device == paUseHostApiSpecificDeviceSpecification )
{
inputChannelCount = inputParameters->channelCount;
inputSampleFormat = inputParameters->sampleFormat;

/* unless alternate device specification is supported, reject the use of
paUseHostApiSpecificDeviceSpecification */
const PaJackStreamInfo *streamInfo = params->hostApiSpecificStreamInfo;

if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
return paInvalidDevice;

/* check that input device can support inputChannelCount */
if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
return paInvalidChannelCount;

/* validate inputStreamInfo */
if( inputParameters->hostApiSpecificStreamInfo )
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
UNLESS( streamInfo, paInvalidDevice );
UNLESS( streamInfo->size == sizeof(*streamInfo), paIncompatibleHostApiSpecificStreamInfo );
UNLESS( streamInfo->version == 1, paIncompatibleHostApiSpecificStreamInfo );
/* Only accept paNoDevice through this alternate method. */
UNLESS( streamInfo->device == paNoDevice, paInvalidDevice );
}
else
else if ( params )
{
inputChannelCount = 0;
}
PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ params->device ];
int deviceMaxChannels = mode == StreamDirection_In ?
deviceInfo->maxInputChannels :
deviceInfo->maxOutputChannels;

if( outputParameters )
{
outputChannelCount = outputParameters->channelCount;
outputSampleFormat = outputParameters->sampleFormat;
UNLESS( params->channelCount <= deviceMaxChannels, paInvalidChannelCount );
UNLESS( !params->hostApiSpecificStreamInfo, paIncompatibleHostApiSpecificStreamInfo );
}

/* unless alternate device specification is supported, reject the use of
paUseHostApiSpecificDeviceSpecification */
error:
return result;
}

if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
return paInvalidDevice;
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
const PaStreamParameters *inputParameters,
const PaStreamParameters *outputParameters,
double sampleRate )
{
PaError result = paFormatIsSupported;
int inputChannelCount = 0, outputChannelCount = 0;
PaSampleFormat inputSampleFormat, outputSampleFormat;

/* check that output device can support inputChannelCount */
if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
return paInvalidChannelCount;
ENSURE_PA( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) );

/* validate outputStreamInfo */
if( outputParameters->hostApiSpecificStreamInfo )
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
}
else
{
outputChannelCount = 0;
}
ENSURE_PA( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ));

/*
The following check is not necessary for JACK.
Expand All @@ -974,16 +966,16 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
/* check that the device supports sampleRate */

#define ABS(x) ( (x) > 0 ? (x) : -(x) )
if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 )
return paInvalidSampleRate;
UNLESS( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) <= 1, paInvalidSampleRate );
#undef ABS

return paFormatIsSupported;
error:
return result;
}

/* Basic stream initialization */
static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels,
int numOutputChannels )
int numOutputChannels, int doConnectInput, int doConnectOutput )
{
PaError result = paNoError;
assert( stream );
Expand All @@ -999,21 +991,29 @@ static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentati
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->local_input_ports being zero-initialized */
UNLESS( stream->remote_output_ports =
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->remote_output_ports being zero-initialized */

if ( doConnectInput )
{
UNLESS( stream->remote_output_ports =
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->remote_output_ports being zero-initialized */
}
}
if( numOutputChannels > 0 )
{
UNLESS( stream->local_output_ports =
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->local_output_ports being zero-initialized */
UNLESS( stream->remote_input_ports =
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->remote_input_ports being zero-initialized */

if ( doConnectOutput )
{
UNLESS( stream->remote_input_ports =
(jack_port_t**) PaUtil_GroupAllocateZeroInitializedMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
paInsufficientMemory );
/* NOTE: we depend on stream->remote_input_ports being zero-initialized */
}
}

stream->num_incoming_connections = numInputChannels;
Expand Down Expand Up @@ -1153,6 +1153,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
/* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */
int i;
int inputChannelCount, outputChannelCount;
int doConnectInput, doConnectOutput;
const double jackSr = jack_get_sample_rate( jackHostApi->jack_client );
PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
int bpInitialized = 0, srInitialized = 0; /* Initialized buffer processor and stream representation? */
Expand All @@ -1177,22 +1178,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,

if( inputParameters )
{
ENSURE_PA( ValidateParameters( inputParameters, hostApi, StreamDirection_In ) );
inputChannelCount = inputParameters->channelCount;
inputSampleFormat = inputParameters->sampleFormat;

/* unless alternate device specification is supported, reject the use of
paUseHostApiSpecificDeviceSpecification */

if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
return paInvalidDevice;

/* check that input device can support inputChannelCount */
if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
return paInvalidChannelCount;

/* validate inputStreamInfo */
if( inputParameters->hostApiSpecificStreamInfo )
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
doConnectInput = inputParameters->device != paUseHostApiSpecificDeviceSpecification;
}
else
{
Expand All @@ -1201,22 +1190,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,

if( outputParameters )
{
ENSURE_PA( ValidateParameters( outputParameters, hostApi, StreamDirection_Out ) );
outputChannelCount = outputParameters->channelCount;
outputSampleFormat = outputParameters->sampleFormat;

/* unless alternate device specification is supported, reject the use of
paUseHostApiSpecificDeviceSpecification */

if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
return paInvalidDevice;

/* check that output device can support inputChannelCount */
if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
return paInvalidChannelCount;

/* validate outputStreamInfo */
if( outputParameters->hostApiSpecificStreamInfo )
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
doConnectOutput = outputParameters->device != paUseHostApiSpecificDeviceSpecification;
}
else
{
Expand All @@ -1232,7 +1209,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
#undef ABS

UNLESS( stream = (PaJackStream*)PaUtil_AllocateZeroInitializedMemory( sizeof(PaJackStream) ), paInsufficientMemory );
ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) );
ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount, doConnectInput, doConnectOutput ) );

/* the blocking emulation, if necessary */
stream->isBlockingStream = !streamCallback;
Expand Down Expand Up @@ -1301,7 +1278,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
* this at stream start time, but doing it here ensures the
* name lookup only happens once. */

if( inputChannelCount > 0 )
if( inputChannelCount > 0 && stream->remote_output_ports )
{
int err = 0;

Expand All @@ -1328,7 +1305,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
UNLESS( i == inputChannelCount, paInternalError );
}

if( outputChannelCount > 0 )
if( outputChannelCount > 0 && stream->remote_input_ports )
{
int err = 0;

Expand Down Expand Up @@ -1372,11 +1349,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
userData ) );
bpInitialized = 1;

if( stream->num_incoming_connections > 0 )
if( stream->num_incoming_connections > 0 && stream->remote_output_ports )
stream->streamRepresentation.streamInfo.inputLatency =
(port_get_min_latency( stream->remote_output_ports[0], JackCaptureLatency )
+ PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
if( stream->num_outgoing_connections > 0 )
if( stream->num_outgoing_connections > 0 && stream->remote_input_ports )
stream->streamRepresentation.streamInfo.outputLatency =
(port_get_min_latency( stream->remote_input_ports[0], JackPlaybackLatency )
+ PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
Expand Down Expand Up @@ -1460,10 +1437,10 @@ static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames )
}

timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr;
if( stream->num_incoming_connections > 0 )
if( stream->num_incoming_connections > 0 && stream->remote_output_ports )
timeInfo.inputBufferAdcTime = timeInfo.currentTime -
port_get_min_latency( stream->remote_output_ports[0], JackCaptureLatency ) / sr;
if( stream->num_outgoing_connections > 0 )
if( stream->num_outgoing_connections > 0 && stream->remote_input_ports )
timeInfo.outputBufferDacTime = timeInfo.currentTime +
port_get_min_latency( stream->remote_input_ports[0], JackPlaybackLatency ) / sr;

Expand Down Expand Up @@ -1699,7 +1676,7 @@ static PaError StartStream( PaStream *s )
/* Connect the ports. Note that the ports may already have been connected by someone else in
* the meantime, in which case JACK returns EEXIST. */

if( stream->num_incoming_connections > 0 )
if( stream->num_incoming_connections > 0 && stream->remote_output_ports )
{
for( i = 0; i < stream->num_incoming_connections; i++ )
{
Expand All @@ -1709,7 +1686,7 @@ static PaError StartStream( PaStream *s )
}
}

if( stream->num_outgoing_connections > 0 )
if( stream->num_outgoing_connections > 0 && stream->remote_input_ports )
{
for( i = 0; i < stream->num_outgoing_connections; i++ )
{
Expand Down Expand Up @@ -1842,6 +1819,14 @@ static double GetStreamCpuLoad( PaStream* s )
return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
}

void PaJack_InitializeNoDeviceStreamInfo( PaJackStreamInfo *info )
{
info->size = sizeof (*info);
info->hostApiType = paJACK;
info->version = 1;
info->device = paNoDevice;
}

PaError PaJack_SetClientName( const char* name )
{
if( strlen( name ) > jack_client_name_size() )
Expand Down

0 comments on commit 9d0614b

Please sign in to comment.