Skip to content

Commit eec7bb7

Browse files
authored
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
1 parent 3f7bee7 commit eec7bb7

File tree

1 file changed

+73
-10
lines changed

1 file changed

+73
-10
lines changed

src/hostapi/jack/pa_jack.c

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
static pthread_t mainThread_;
7777
static char *jackErr_ = NULL;
7878
static const char* clientName_ = "PortAudio";
79+
static const char* port_regex_suffix = ":.*";
7980

8081
#define STRINGIZE_HELPER(expr) #expr
8182
#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
@@ -451,6 +452,38 @@ BlockingWaitEmpty( PaStream *s )
451452

452453
/* ---- jack driver ---- */
453454

455+
/* copy null terminated string source to destination, escaping regex characters with '\\' in the process */
456+
static void copy_string_and_escape_regex_chars( char *destination, const char *source, size_t destbuffersize )
457+
{
458+
assert( destination != source );
459+
assert( destbuffersize > 0 );
460+
461+
char *dest = destination;
462+
/* dest_stop is the last location that we can null-terminate the string */
463+
char *dest_stop = destination + (destbuffersize - 1);
464+
465+
const char *src = source;
466+
467+
while ( *src != '\0' && dest != dest_stop )
468+
{
469+
const char c = *src;
470+
if ( strchr( "\\()[]{}*+?|$^.", c ) != NULL )
471+
{
472+
if( (dest + 1) == dest_stop )
473+
break; /* only proceed if we can write both c and the escape */
474+
475+
*dest = '\\';
476+
dest++;
477+
}
478+
*dest = c;
479+
dest++;
480+
481+
src++;
482+
}
483+
484+
*dest = '\0';
485+
}
486+
454487
/* BuildDeviceList():
455488
*
456489
* The process of determining a list of PortAudio "devices" from
@@ -472,7 +505,12 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
472505

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

493-
regex_pattern = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() + 3 );
531+
port_regex_string = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, port_regex_size );
532+
device_name_regex_escaped = PaUtil_GroupAllocateMemory(
533+
jackApi->deviceInfoMemory, device_name_regex_escaped_size );
494534
tmp_client_name = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() );
495535

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

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

585626
/* To determine how many input and output channels are available,
586627
* we re-query jackd with more specific parameters. */
587-
588-
sprintf( regex_pattern, "%s:.*", client_names[client_index] );
628+
copy_string_and_escape_regex_chars( device_name_regex_escaped,
629+
client_names[client_index],
630+
device_name_regex_escaped_size );
631+
strncpy( port_regex_string, device_name_regex_escaped, port_regex_size );
632+
strncat( port_regex_string, port_regex_suffix, port_regex_size );
589633

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

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

676+
PA_DEBUG(( "Adding JACK device %s with %d input channels and %d output channels\n",
677+
client_names[client_index],
678+
curDevInfo->maxInputChannels,
679+
curDevInfo->maxOutputChannels ));
680+
632681
/* Add this client to the list of devices */
633682
commonApi->deviceInfos[client_index] = curDevInfo;
634683
++commonApi->info.deviceCount;
@@ -1080,8 +1129,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
10801129
PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
10811130
PaJackStream *stream = NULL;
10821131
char *port_string = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, jack_port_name_size() );
1083-
unsigned long regexSz = jack_client_name_size() + 3;
1084-
char *regex_pattern = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, regexSz );
1132+
// In the worst case every character would be escaped which would double the string length.
1133+
// Add 1 for null terminator
1134+
size_t regex_escaped_client_name_length = jack_client_name_size() * 2 + 1;
1135+
char *regex_escaped_client_name = PaUtil_GroupAllocateMemory(
1136+
jackHostApi->deviceInfoMemory, regex_escaped_client_name_length );
1137+
unsigned long regex_size = jack_client_name_size() * 2 + 1 + strlen(port_regex_suffix);
1138+
char *regex_pattern = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, regex_size );
10851139
const char **jack_ports = NULL;
10861140
/* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */
10871141
int i;
@@ -1239,7 +1293,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
12391293
int err = 0;
12401294

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

12651323
/* Get input ports of our playback device */
1266-
snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name );
1324+
copy_string_and_escape_regex_chars( regex_escaped_client_name,
1325+
hostApi->deviceInfos[ outputParameters->device ]->name,
1326+
regex_escaped_client_name_length );
1327+
strncpy( regex_pattern, regex_escaped_client_name, regex_size );
1328+
strncat( regex_pattern, port_regex_suffix, regex_size );
12671329
UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
12681330
JACK_PORT_TYPE_FILTER, JackPortIsInput ), paUnanticipatedHostError );
12691331
for( i = 0; i < outputChannelCount && jack_ports[i]; i++ )
@@ -1769,3 +1831,4 @@ PaError PaJack_GetClientName(const char** clientName)
17691831
error:
17701832
return result;
17711833
}
1834+

0 commit comments

Comments
 (0)