From eec7bb739771381ab698a6a611d933bdd72cbd8f Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 19 Mar 2021 12:51:31 -0500 Subject: [PATCH] JACK PipeWire fixes (escape regex chars) (#504) 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 --- src/hostapi/jack/pa_jack.c | 83 +++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/src/hostapi/jack/pa_jack.c b/src/hostapi/jack/pa_jack.c index 0dad24fb4..88926f470 100644 --- a/src/hostapi/jack/pa_jack.c +++ b/src/hostapi/jack/pa_jack.c @@ -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) @@ -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 @@ -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; @@ -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 @@ -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 */ @@ -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.; @@ -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.; @@ -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; @@ -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; @@ -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++ ) @@ -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++ ) @@ -1769,3 +1831,4 @@ PaError PaJack_GetClientName(const char** clientName) error: return result; } +