Skip to content

Commit

Permalink
JACK PipeWire fixes (escape regex chars) (#504)
Browse files Browse the repository at this point in the history
This patch fixes a problem caused by special regex characters
appearing in the device names when using the Jack interface to PipeWire.

It is uncommon for JACK ports to have any characters that need to
be escaped in a regex. jackd simply calls the audio interface
"system". However PipeWire uses the device name from ALSA for the
JACK port names. If this contains any special regex characters,
BuildDeviceList would find the device but determine it has 0
input channels and 0 output channels. In my case, I have an RME
Babyface Pro which puts its serial number in parentheses:

$ aplay -l
card 0: Pro70785713 [Babyface Pro (70785713)], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
  • Loading branch information
Be-ing committed Mar 19, 2021
1 parent 3f7bee7 commit eec7bb7
Showing 1 changed file with 73 additions and 10 deletions.
83 changes: 73 additions & 10 deletions src/hostapi/jack/pa_jack.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
static pthread_t mainThread_;
static char *jackErr_ = NULL;
static const char* clientName_ = "PortAudio";
static const char* port_regex_suffix = ":.*";

#define STRINGIZE_HELPER(expr) #expr
#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
Expand Down Expand Up @@ -451,6 +452,38 @@ BlockingWaitEmpty( PaStream *s )

/* ---- jack driver ---- */

/* copy null terminated string source to destination, escaping regex characters with '\\' in the process */
static void copy_string_and_escape_regex_chars( char *destination, const char *source, size_t destbuffersize )
{
assert( destination != source );
assert( destbuffersize > 0 );

char *dest = destination;
/* dest_stop is the last location that we can null-terminate the string */
char *dest_stop = destination + (destbuffersize - 1);

const char *src = source;

while ( *src != '\0' && dest != dest_stop )
{
const char c = *src;
if ( strchr( "\\()[]{}*+?|$^.", c ) != NULL )
{
if( (dest + 1) == dest_stop )
break; /* only proceed if we can write both c and the escape */

*dest = '\\';
dest++;
}
*dest = c;
dest++;

src++;
}

*dest = '\0';
}

/* BuildDeviceList():
*
* The process of determining a list of PortAudio "devices" from
Expand All @@ -472,7 +505,12 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )

const char **jack_ports = NULL;
char **client_names = NULL;
char *regex_pattern = NULL;
char *port_regex_string = NULL;
char *device_name_regex_escaped = NULL;
// In the worst case scenario, every character would be escaped, doubling the string size.
// Add 1 for null terminator.
size_t device_name_regex_escaped_size = jack_client_name_size() * 2 + 1;
size_t port_regex_size = device_name_regex_escaped_size + strlen(port_regex_suffix);
int port_index, client_index, i;
double globalSampleRate;
regex_t port_regex;
Expand All @@ -490,7 +528,9 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
* associated with the previous list */
PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory );

regex_pattern = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() + 3 );
port_regex_string = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, port_regex_size );
device_name_regex_escaped = PaUtil_GroupAllocateMemory(
jackApi->deviceInfoMemory, device_name_regex_escaped_size );
tmp_client_name = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() );

/* We can only retrieve the list of clients indirectly, by first
Expand All @@ -512,6 +552,7 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
int client_seen = FALSE;
regmatch_t match_info;
const char *port = jack_ports[port_index];
PA_DEBUG(( "JACK port found: %s\n", port ));

/* extract the client name from the port name, using a regex
* that parses the clientname:portname syntax */
Expand Down Expand Up @@ -584,11 +625,14 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )

/* To determine how many input and output channels are available,
* we re-query jackd with more specific parameters. */

sprintf( regex_pattern, "%s:.*", client_names[client_index] );
copy_string_and_escape_regex_chars( device_name_regex_escaped,
client_names[client_index],
device_name_regex_escaped_size );
strncpy( port_regex_string, device_name_regex_escaped, port_regex_size );
strncat( port_regex_string, port_regex_suffix, port_regex_size );

/* ... what are your output ports (that we could input from)? */
clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
clientPorts = jack_get_ports( jackApi->jack_client, port_regex_string,
JACK_PORT_TYPE_FILTER, JackPortIsOutput);
curDevInfo->maxInputChannels = 0;
curDevInfo->defaultLowInputLatency = 0.;
Expand All @@ -609,7 +653,7 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
}

/* ... what are your input ports (that we could output to)? */
clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
clientPorts = jack_get_ports( jackApi->jack_client, port_regex_string,
JACK_PORT_TYPE_FILTER, JackPortIsInput);
curDevInfo->maxOutputChannels = 0;
curDevInfo->defaultLowOutputLatency = 0.;
Expand All @@ -629,6 +673,11 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
free(clientPorts);
}

PA_DEBUG(( "Adding JACK device %s with %d input channels and %d output channels\n",
client_names[client_index],
curDevInfo->maxInputChannels,
curDevInfo->maxOutputChannels ));

/* Add this client to the list of devices */
commonApi->deviceInfos[client_index] = curDevInfo;
++commonApi->info.deviceCount;
Expand Down Expand Up @@ -1080,8 +1129,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
PaJackStream *stream = NULL;
char *port_string = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, jack_port_name_size() );
unsigned long regexSz = jack_client_name_size() + 3;
char *regex_pattern = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, regexSz );
// In the worst case every character would be escaped which would double the string length.
// Add 1 for null terminator
size_t regex_escaped_client_name_length = jack_client_name_size() * 2 + 1;
char *regex_escaped_client_name = PaUtil_GroupAllocateMemory(
jackHostApi->deviceInfoMemory, regex_escaped_client_name_length );
unsigned long regex_size = jack_client_name_size() * 2 + 1 + strlen(port_regex_suffix);
char *regex_pattern = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, regex_size );
const char **jack_ports = NULL;
/* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */
int i;
Expand Down Expand Up @@ -1239,7 +1293,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
int err = 0;

/* Get output ports of our capture device */
snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name );
copy_string_and_escape_regex_chars( regex_escaped_client_name,
hostApi->deviceInfos[ inputParameters->device ]->name,
regex_escaped_client_name_length );
strncpy( regex_pattern, regex_escaped_client_name, regex_size );
strncat( regex_pattern, port_regex_suffix, regex_size );
UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
JACK_PORT_TYPE_FILTER, JackPortIsOutput ), paUnanticipatedHostError );
for( i = 0; i < inputChannelCount && jack_ports[i]; i++ )
Expand All @@ -1263,7 +1321,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
int err = 0;

/* Get input ports of our playback device */
snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name );
copy_string_and_escape_regex_chars( regex_escaped_client_name,
hostApi->deviceInfos[ outputParameters->device ]->name,
regex_escaped_client_name_length );
strncpy( regex_pattern, regex_escaped_client_name, regex_size );
strncat( regex_pattern, port_regex_suffix, regex_size );
UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
JACK_PORT_TYPE_FILTER, JackPortIsInput ), paUnanticipatedHostError );
for( i = 0; i < outputChannelCount && jack_ports[i]; i++ )
Expand Down Expand Up @@ -1769,3 +1831,4 @@ PaError PaJack_GetClientName(const char** clientName)
error:
return result;
}

0 comments on commit eec7bb7

Please sign in to comment.