summaryrefslogtreecommitdiff
path: root/portaudio/src/hostapi/jack/pa_jack.c
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
committersanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
commit37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch)
treee1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/src/hostapi/jack/pa_jack.c
parent5634c7b04da619669f2f29f6798c03982be05180 (diff)
add initial structure
Diffstat (limited to 'portaudio/src/hostapi/jack/pa_jack.c')
-rw-r--r--portaudio/src/hostapi/jack/pa_jack.c1826
1 files changed, 1826 insertions, 0 deletions
diff --git a/portaudio/src/hostapi/jack/pa_jack.c b/portaudio/src/hostapi/jack/pa_jack.c
new file mode 100644
index 0000000..124c0f8
--- /dev/null
+++ b/portaudio/src/hostapi/jack/pa_jack.c
@@ -0,0 +1,1826 @@
+/*
+ * $Id$
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ * JACK Implementation by Joshua Haberman
+ *
+ * Copyright (c) 2004 Stefan Westerfeld <stefan@space.twc.de>
+ * Copyright (c) 2004 Arve Knudsen <aknuds-1@broadpark.no>
+ * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com>
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2002 Ross Bencina, Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortAudio license; however,
+ * the PortAudio community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/**
+ @file
+ @ingroup hostapi_src
+*/
+
+#include <string.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h> /* EBUSY */
+#include <signal.h> /* sig_atomic_t */
+#include <math.h>
+#include <semaphore.h>
+
+#include <jack/types.h>
+#include <jack/jack.h>
+
+#include "pa_util.h"
+#include "pa_hostapi.h"
+#include "pa_stream.h"
+#include "pa_process.h"
+#include "pa_allocation.h"
+#include "pa_cpuload.h"
+#include "pa_ringbuffer.h"
+#include "pa_debugprint.h"
+
+#include "pa_jack.h"
+
+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)
+
+/* Check PaError */
+#define ENSURE_PA(expr) \
+ do { \
+ PaError paErr; \
+ if( (paErr = (expr)) < paNoError ) \
+ { \
+ if( (paErr) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
+ { \
+ const char *err = jackErr_; \
+ if (! err ) err = "unknown error"; \
+ PaUtil_SetLastHostErrorInfo( paJACK, -1, err ); \
+ } \
+ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+ result = paErr; \
+ goto error; \
+ } \
+ } while( 0 )
+
+#define UNLESS(expr, code) \
+ do { \
+ if( (expr) == 0 ) \
+ { \
+ if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
+ { \
+ const char *err = jackErr_; \
+ if (!err) err = "unknown error"; \
+ PaUtil_SetLastHostErrorInfo( paJACK, -1, err ); \
+ } \
+ PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
+ result = (code); \
+ goto error; \
+ } \
+ } while( 0 )
+
+#define ASSERT_CALL(expr, success) \
+ do { \
+ int err = (expr); \
+ assert( err == success ); \
+ } while( 0 )
+
+/*
+ * Functions that directly map to the PortAudio stream interface
+ */
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate );
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+ PaStream** s,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate,
+ unsigned long framesPerBuffer,
+ PaStreamFlags streamFlags,
+ PaStreamCallback *streamCallback,
+ void *userData );
+static PaError CloseStream( PaStream* stream );
+static PaError StartStream( PaStream *stream );
+static PaError StopStream( PaStream *stream );
+static PaError AbortStream( PaStream *stream );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *stream );
+/*static PaTime GetStreamInputLatency( PaStream *stream );*/
+/*static PaTime GetStreamOutputLatency( PaStream *stream );*/
+static PaTime GetStreamTime( PaStream *stream );
+static double GetStreamCpuLoad( PaStream* stream );
+
+
+/*
+ * Data specific to this API
+ */
+
+struct PaJackStream;
+
+typedef struct
+{
+ PaUtilHostApiRepresentation commonHostApiRep;
+ PaUtilStreamInterface callbackStreamInterface;
+ PaUtilStreamInterface blockingStreamInterface;
+
+ PaUtilAllocationGroup *deviceInfoMemory;
+
+ jack_client_t *jack_client;
+ int jack_buffer_size;
+ PaHostApiIndex hostApiIndex;
+
+ pthread_mutex_t mtx;
+ pthread_cond_t cond;
+ unsigned long inputBase, outputBase;
+
+ /* For dealing with the process thread */
+ volatile int xrun; /* Received xrun notification from JACK? */
+ struct PaJackStream * volatile toAdd, * volatile toRemove;
+ struct PaJackStream *processQueue;
+ volatile sig_atomic_t jackIsDown;
+}
+PaJackHostApiRepresentation;
+
+/* PaJackStream - a stream data structure specifically for this implementation */
+
+typedef struct PaJackStream
+{
+ PaUtilStreamRepresentation streamRepresentation;
+ PaUtilBufferProcessor bufferProcessor;
+ PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+ PaJackHostApiRepresentation *hostApi;
+
+ /* our input and output ports */
+ jack_port_t **local_input_ports;
+ jack_port_t **local_output_ports;
+
+ /* the input and output ports of the client we are connecting to */
+ jack_port_t **remote_input_ports;
+ jack_port_t **remote_output_ports;
+
+ int num_incoming_connections;
+ int num_outgoing_connections;
+
+ jack_client_t *jack_client;
+
+ /* The stream is running if it's still producing samples.
+ * The stream is active if samples it produced are still being heard.
+ */
+ volatile sig_atomic_t is_running;
+ volatile sig_atomic_t is_active;
+ /* Used to signal processing thread that stream should start or stop, respectively */
+ volatile sig_atomic_t doStart, doStop, doAbort;
+
+ jack_nframes_t t0;
+
+ PaUtilAllocationGroup *stream_memory;
+
+ /* These are useful in the process callback */
+
+ int callbackResult;
+ int isSilenced;
+ int xrun;
+
+ /* These are useful for the blocking API */
+
+ int isBlockingStream;
+ PaUtilRingBuffer inFIFO;
+ PaUtilRingBuffer outFIFO;
+ volatile sig_atomic_t data_available;
+ sem_t data_semaphore;
+ int bytesPerFrame;
+ int samplesPerFrame;
+
+ struct PaJackStream *next;
+}
+PaJackStream;
+
+/* In calls to jack_get_ports() this filter expression is used instead of ""
+ * to prevent any other types (eg Midi ports etc) being listed */
+#define JACK_PORT_TYPE_FILTER "audio"
+
+#define TRUE 1
+#define FALSE 0
+
+/*
+ * Functions specific to this API
+ */
+
+static int JackCallback( jack_nframes_t frames, void *userData );
+
+
+/*
+ *
+ * Implementation
+ *
+ */
+
+/* ---- blocking emulation layer ---- */
+
+/* Allocate buffer. */
+static PaError BlockingInitFIFO( PaUtilRingBuffer *rbuf, long numFrames, long bytesPerFrame )
+{
+ long numBytes = numFrames * bytesPerFrame;
+ char *buffer = (char *) malloc( numBytes );
+ if( buffer == NULL ) return paInsufficientMemory;
+ memset( buffer, 0, numBytes );
+ return (PaError) PaUtil_InitializeRingBuffer( rbuf, 1, numBytes, buffer );
+}
+
+/* Free buffer. */
+static PaError BlockingTermFIFO( PaUtilRingBuffer *rbuf )
+{
+ if( rbuf->buffer ) free( rbuf->buffer );
+ rbuf->buffer = NULL;
+ return paNoError;
+}
+
+static int
+BlockingCallback( const void *inputBuffer,
+ void *outputBuffer,
+ unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ struct PaJackStream *stream = (PaJackStream *)userData;
+ long numBytes = stream->bytesPerFrame * framesPerBuffer;
+
+ /* This may get called with NULL inputBuffer during initial setup. */
+ if( inputBuffer != NULL )
+ {
+ PaUtil_WriteRingBuffer( &stream->inFIFO, inputBuffer, numBytes );
+ }
+ if( outputBuffer != NULL )
+ {
+ int numRead = PaUtil_ReadRingBuffer( &stream->outFIFO, outputBuffer, numBytes );
+ /* Zero out remainder of buffer if we run out of data. */
+ memset( (char *)outputBuffer + numRead, 0, numBytes - numRead );
+ }
+
+ if( !stream->data_available )
+ {
+ stream->data_available = 1;
+ sem_post( &stream->data_semaphore );
+ }
+ return paContinue;
+}
+
+static PaError
+BlockingBegin( PaJackStream *stream, int minimum_buffer_size )
+{
+ long doRead = 0;
+ long doWrite = 0;
+ PaError result = paNoError;
+ long numFrames;
+
+ doRead = stream->local_input_ports != NULL;
+ doWrite = stream->local_output_ports != NULL;
+ /* <FIXME> */
+ stream->samplesPerFrame = 2;
+ stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame;
+ /* </FIXME> */
+ numFrames = 32;
+ while (numFrames < minimum_buffer_size)
+ numFrames *= 2;
+
+ if( doRead )
+ {
+ ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) );
+ }
+ if( doWrite )
+ {
+ long numBytes;
+
+ ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) );
+
+ /* Make Write FIFO appear full initially. */
+ numBytes = PaUtil_GetRingBufferWriteAvailable( &stream->outFIFO );
+ PaUtil_AdvanceRingBufferWriteIndex( &stream->outFIFO, numBytes );
+ }
+
+ stream->data_available = 0;
+ sem_init( &stream->data_semaphore, 0, 0 );
+
+error:
+ return result;
+}
+
+static void
+BlockingEnd( PaJackStream *stream )
+{
+ BlockingTermFIFO( &stream->inFIFO );
+ BlockingTermFIFO( &stream->outFIFO );
+
+ sem_destroy( &stream->data_semaphore );
+}
+
+static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames )
+{
+ PaError result = paNoError;
+ PaJackStream *stream = (PaJackStream *)s;
+
+ long bytesRead;
+ char *p = (char *) data;
+ long numBytes = stream->bytesPerFrame * numFrames;
+ while( numBytes > 0 )
+ {
+ bytesRead = PaUtil_ReadRingBuffer( &stream->inFIFO, p, numBytes );
+ numBytes -= bytesRead;
+ p += bytesRead;
+ if( numBytes > 0 )
+ {
+ /* see write for an explanation */
+ if( stream->data_available )
+ stream->data_available = 0;
+ else
+ sem_wait( &stream->data_semaphore );
+ }
+ }
+
+ return result;
+}
+
+static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames )
+{
+ PaError result = paNoError;
+ PaJackStream *stream = (PaJackStream *)s;
+ long bytesWritten;
+ char *p = (char *) data;
+ long numBytes = stream->bytesPerFrame * numFrames;
+ while( numBytes > 0 )
+ {
+ bytesWritten = PaUtil_WriteRingBuffer( &stream->outFIFO, p, numBytes );
+ numBytes -= bytesWritten;
+ p += bytesWritten;
+ if( numBytes > 0 )
+ {
+ /* we use the following algorithm:
+ * (1) write data
+ * (2) if some data didn't fit into the ringbuffer, set data_available to 0
+ * to indicate to the audio that if space becomes available, we want to know
+ * (3) retry to write data (because it might be that between (1) and (2)
+ * new space in the buffer became available)
+ * (4) if this failed, we are sure that the buffer is really empty and
+ * we will definitely receive a notification when it becomes available
+ * thus we can safely sleep
+ *
+ * if the algorithm bailed out in step (3) before, it leaks a count of 1
+ * on the semaphore; however, it doesn't matter, because if we block in (4),
+ * we also do it in a loop
+ */
+ if( stream->data_available )
+ stream->data_available = 0;
+ else
+ sem_wait( &stream->data_semaphore );
+ }
+ }
+
+ return result;
+}
+
+static signed long
+BlockingGetStreamReadAvailable( PaStream* s )
+{
+ PaJackStream *stream = (PaJackStream *)s;
+
+ int bytesFull = PaUtil_GetRingBufferReadAvailable( &stream->inFIFO );
+ return bytesFull / stream->bytesPerFrame;
+}
+
+static signed long
+BlockingGetStreamWriteAvailable( PaStream* s )
+{
+ PaJackStream *stream = (PaJackStream *)s;
+
+ int bytesEmpty = PaUtil_GetRingBufferWriteAvailable( &stream->outFIFO );
+ return bytesEmpty / stream->bytesPerFrame;
+}
+
+static PaError
+BlockingWaitEmpty( PaStream *s )
+{
+ PaJackStream *stream = (PaJackStream *)s;
+
+ while( PaUtil_GetRingBufferReadAvailable( &stream->outFIFO ) > 0 )
+ {
+ stream->data_available = 0;
+ sem_wait( &stream->data_semaphore );
+ }
+ return 0;
+}
+
+/* ---- 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
+ * JACK's client/port system is fairly involved, so it is separated
+ * into its own routine.
+ */
+
+static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
+{
+ /* Utility macros for the repetitive process of allocating memory */
+
+ /* JACK has no concept of a device. To JACK, there are clients
+ * which have an arbitrary number of ports. To make this
+ * intelligible to PortAudio clients, we will group each JACK client
+ * into a device, and make each port of that client a channel */
+
+ PaError result = paNoError;
+ PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep;
+
+ const char **jack_ports = NULL;
+ char **client_names = NULL;
+ char *port_regex_string = 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;
+ unsigned long numClients = 0, numPorts = 0;
+ char *tmp_client_name = NULL;
+
+ commonApi->info.defaultInputDevice = paNoDevice;
+ commonApi->info.defaultOutputDevice = paNoDevice;
+ commonApi->info.deviceCount = 0;
+
+ /* Parse the list of ports, using a regex to grab the client names */
+ ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 );
+
+ /* since we are rebuilding the list of devices, free all memory
+ * associated with the previous list */
+ PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory );
+
+ port_regex_string = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, port_regex_size );
+ tmp_client_name = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, jack_client_name_size() );
+
+ /* We can only retrieve the list of clients indirectly, by first
+ * asking for a list of all ports, then parsing the port names
+ * according to the client_name:port_name convention (which is
+ * enforced by jackd)
+ * A: If jack_get_ports returns NULL, there's nothing for us to do */
+ UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", JACK_PORT_TYPE_FILTER, 0 )) && jack_ports[0], paNoError );
+ /* Find number of ports */
+ while( jack_ports[numPorts] )
+ ++numPorts;
+ /* At least there will be one port per client :) */
+ UNLESS( client_names = PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, numPorts *
+ sizeof (char *) ), paInsufficientMemory );
+
+ /* Build a list of clients from the list of ports */
+ for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ )
+ {
+ 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 */
+ UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError );
+ assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size());
+ memcpy( tmp_client_name, port + match_info.rm_so,
+ match_info.rm_eo - match_info.rm_so );
+ tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0';
+
+ /* do we know about this port's client yet? */
+ for( i = 0; i < numClients; i++ )
+ {
+ if( strcmp( tmp_client_name, client_names[i] ) == 0 )
+ client_seen = TRUE;
+ }
+
+ if (client_seen)
+ continue; /* A: Nothing to see here, move along */
+
+ UNLESS( client_names[numClients] = (char*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+ strlen(tmp_client_name) + 1), paInsufficientMemory );
+
+ /* The alsa_pcm client should go in spot 0. If this
+ * is the alsa_pcm client AND we are NOT about to put
+ * it in spot 0 put it in spot 0 and move whatever
+ * was already in spot 0 to the end. */
+ if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 )
+ {
+ /* alsa_pcm goes in spot 0 */
+ strcpy( client_names[ numClients ], client_names[0] );
+ strcpy( client_names[0], tmp_client_name );
+ }
+ else
+ {
+ /* put the new client at the end of the client list */
+ strcpy( client_names[ numClients ], tmp_client_name );
+ }
+ ++numClients;
+ }
+
+ /* Now we have a list of clients, which will become the list of
+ * PortAudio devices. */
+
+ /* there is one global sample rate all clients must conform to */
+
+ globalSampleRate = jack_get_sample_rate( jackApi->jack_client );
+ UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+ sizeof(PaDeviceInfo*) * numClients ), paInsufficientMemory );
+
+ assert( commonApi->info.deviceCount == 0 );
+
+ /* Create a PaDeviceInfo structure for every client */
+ for( client_index = 0; client_index < numClients; client_index++ )
+ {
+ PaDeviceInfo *curDevInfo;
+ const char **clientPorts = NULL;
+
+ UNLESS( curDevInfo = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+ sizeof(PaDeviceInfo) ), paInsufficientMemory );
+ UNLESS( curDevInfo->name = (char*)PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory,
+ strlen(client_names[client_index]) + 1 ), paInsufficientMemory );
+ strcpy( (char *)curDevInfo->name, client_names[client_index] );
+
+ curDevInfo->structVersion = 2;
+ curDevInfo->hostApi = jackApi->hostApiIndex;
+
+ /* JACK is very inflexible: there is one sample rate the whole
+ * system must run at, and all clients must speak IEEE float. */
+ curDevInfo->defaultSampleRate = globalSampleRate;
+
+ /* To determine how many input and output channels are available,
+ * we re-query jackd with more specific parameters. */
+ copy_string_and_escape_regex_chars( port_regex_string,
+ client_names[client_index],
+ device_name_regex_escaped_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, port_regex_string,
+ JACK_PORT_TYPE_FILTER, JackPortIsOutput);
+ curDevInfo->maxInputChannels = 0;
+ curDevInfo->defaultLowInputLatency = 0.;
+ curDevInfo->defaultHighInputLatency = 0.;
+ if( clientPorts )
+ {
+ jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
+ curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency =
+ jack_port_get_latency( p ) / globalSampleRate;
+
+ for( i = 0; clientPorts[i] != NULL; i++)
+ {
+ /* The number of ports returned is the number of output channels.
+ * We don't care what they are, we just care how many */
+ curDevInfo->maxInputChannels++;
+ }
+ free(clientPorts);
+ }
+
+ /* ... what are your input ports (that we could output to)? */
+ clientPorts = jack_get_ports( jackApi->jack_client, port_regex_string,
+ JACK_PORT_TYPE_FILTER, JackPortIsInput);
+ curDevInfo->maxOutputChannels = 0;
+ curDevInfo->defaultLowOutputLatency = 0.;
+ curDevInfo->defaultHighOutputLatency = 0.;
+ if( clientPorts )
+ {
+ jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
+ curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency =
+ jack_port_get_latency( p ) / globalSampleRate;
+
+ for( i = 0; clientPorts[i] != NULL; i++)
+ {
+ /* The number of ports returned is the number of input channels.
+ * We don't care what they are, we just care how many */
+ curDevInfo->maxOutputChannels++;
+ }
+ 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;
+ if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 )
+ commonApi->info.defaultInputDevice = client_index;
+ if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 )
+ commonApi->info.defaultOutputDevice = client_index;
+ }
+
+error:
+ regfree( &port_regex );
+ free( jack_ports );
+ return result;
+}
+
+static void UpdateSampleRate( PaJackStream *stream, double sampleRate )
+{
+ /* XXX: Maybe not the cleanest way of going about this? */
+ stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate;
+ stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+}
+
+static void JackErrorCallback( const char *msg )
+{
+ if( pthread_self() == mainThread_ )
+ {
+ assert( msg );
+ jackErr_ = realloc( jackErr_, strlen( msg ) + 1 );
+ strcpy( jackErr_, msg );
+ }
+}
+
+static void JackOnShutdown( void *arg )
+{
+ PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
+ PaJackStream *stream = jackApi->processQueue;
+
+ PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ ));
+ for( ; stream; stream = stream->next )
+ {
+ stream->is_active = 0;
+ }
+
+ /* Make sure that the main thread doesn't get stuck waiting on the condition */
+ ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 );
+ jackApi->jackIsDown = 1;
+ ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 );
+ ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 );
+
+}
+
+static int JackSrCb( jack_nframes_t nframes, void *arg )
+{
+ PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
+ double sampleRate = (double)nframes;
+ PaJackStream *stream = jackApi->processQueue;
+
+ /* Update all streams in process queue */
+ PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate ));
+ for( ; stream; stream = stream->next )
+ {
+ if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate )
+ {
+ PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ ));
+ UpdateSampleRate( stream, sampleRate );
+ }
+ }
+
+ return 0;
+}
+
+static int JackXRunCb(void *arg) {
+ PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg;
+ assert( hostApi );
+ hostApi->xrun = TRUE;
+ PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ ));
+ return 0;
+}
+
+PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi,
+ PaHostApiIndex hostApiIndex )
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation *jackHostApi;
+ int activated = 0;
+ jack_status_t jackStatus = 0;
+ *hostApi = NULL; /* Initialize to NULL */
+
+ UNLESS( jackHostApi = (PaJackHostApiRepresentation*)
+ PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory );
+ UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+
+ mainThread_ = pthread_self();
+ ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 );
+ ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 );
+
+ /* Try to become a client of the JACK server. If we cannot do
+ * this, then this API cannot be used.
+ *
+ * Without the JackNoStartServer option, the jackd server is started
+ * automatically which we do not want.
+ */
+
+ jackHostApi->jack_client = jack_client_open( clientName_, JackNoStartServer, &jackStatus );
+ if( !jackHostApi->jack_client )
+ {
+ /* the V19 development docs say that if an implementation
+ * detects that it cannot be used, it should return a NULL
+ * interface and paNoError */
+ PA_DEBUG(( "%s: Couldn't connect to JACK, status: %d\n", __FUNCTION__, jackStatus ));
+ result = paNoError;
+ goto error;
+ }
+
+ jackHostApi->hostApiIndex = hostApiIndex;
+
+ *hostApi = &jackHostApi->commonHostApiRep;
+ (*hostApi)->info.structVersion = 1;
+ (*hostApi)->info.type = paJACK;
+ (*hostApi)->info.name = "JACK Audio Connection Kit";
+
+ /* Build a device list by querying the JACK server */
+ ENSURE_PA( BuildDeviceList( jackHostApi ) );
+
+ /* Register functions */
+
+ (*hostApi)->Terminate = Terminate;
+ (*hostApi)->OpenStream = OpenStream;
+ (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+ PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface,
+ CloseStream, StartStream,
+ StopStream, AbortStream,
+ IsStreamStopped, IsStreamActive,
+ GetStreamTime, GetStreamCpuLoad,
+ PaUtil_DummyRead, PaUtil_DummyWrite,
+ PaUtil_DummyGetReadAvailable,
+ PaUtil_DummyGetWriteAvailable );
+
+ PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream,
+ StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+ GetStreamTime, PaUtil_DummyGetCpuLoad,
+ BlockingReadStream, BlockingWriteStream,
+ BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable );
+
+ jackHostApi->inputBase = jackHostApi->outputBase = 0;
+ jackHostApi->xrun = 0;
+ jackHostApi->toAdd = jackHostApi->toRemove = NULL;
+ jackHostApi->processQueue = NULL;
+ jackHostApi->jackIsDown = 0;
+
+ jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi );
+ jack_set_error_function( JackErrorCallback );
+ jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client );
+ /* Don't check for error, may not be supported (deprecated in at least jackdmp) */
+ jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi );
+ UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError );
+ UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError );
+ UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError );
+ activated = 1;
+
+ return result;
+
+error:
+ if( activated )
+ ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
+
+ if( jackHostApi )
+ {
+ if( jackHostApi->jack_client )
+ ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
+
+ if( jackHostApi->deviceInfoMemory )
+ {
+ PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
+ PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
+ }
+
+ PaUtil_FreeMemory( jackHostApi );
+ }
+ return result;
+}
+
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+ PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
+
+ /* note: this automatically disconnects all ports, since a deactivated
+ * client is not allowed to have any ports connected */
+ ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
+
+ ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 );
+ ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 );
+
+ ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
+
+ if( jackHostApi->deviceInfoMemory )
+ {
+ PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
+ PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
+ }
+
+ PaUtil_FreeMemory( jackHostApi );
+
+ free( jackErr_ );
+ jackErr_ = NULL;
+}
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate )
+{
+ int inputChannelCount = 0, outputChannelCount = 0;
+ PaSampleFormat inputSampleFormat, outputSampleFormat;
+
+ if( inputParameters )
+ {
+ 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 */
+ }
+ else
+ {
+ inputChannelCount = 0;
+ }
+
+ if( outputParameters )
+ {
+ 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 */
+ }
+ else
+ {
+ outputChannelCount = 0;
+ }
+
+ /*
+ The following check is not necessary for JACK.
+
+ - if a full duplex stream is requested, check that the combination
+ of input and output parameters is supported
+
+
+ Because the buffer adapter handles conversion between all standard
+ sample formats, the following checks are only required if paCustomFormat
+ is implemented, or under some other unusual conditions.
+
+ - check that input device can support inputSampleFormat, or that
+ we have the capability to convert from outputSampleFormat to
+ a native format
+
+ - check that output device can support outputSampleFormat, or that
+ we have the capability to convert from outputSampleFormat to
+ a native format
+ */
+
+ /* 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;
+#undef ABS
+
+ return paFormatIsSupported;
+}
+
+/* Basic stream initialization */
+static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels,
+ int numOutputChannels )
+{
+ PaError result = paNoError;
+ assert( stream );
+
+ memset( stream, 0, sizeof (PaJackStream) );
+ UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+ stream->jack_client = hostApi->jack_client;
+ stream->hostApi = hostApi;
+
+ if( numInputChannels > 0 )
+ {
+ UNLESS( stream->local_input_ports =
+ (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
+ paInsufficientMemory );
+ memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels );
+ UNLESS( stream->remote_output_ports =
+ (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
+ paInsufficientMemory );
+ memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels );
+ }
+ if( numOutputChannels > 0 )
+ {
+ UNLESS( stream->local_output_ports =
+ (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
+ paInsufficientMemory );
+ memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
+ UNLESS( stream->remote_input_ports =
+ (jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
+ paInsufficientMemory );
+ memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
+ }
+
+ stream->num_incoming_connections = numInputChannels;
+ stream->num_outgoing_connections = numOutputChannels;
+
+error:
+ return result;
+}
+
+/*!
+ * Free resources associated with stream, and eventually stream itself.
+ *
+ * Frees allocated memory, and closes opened pcms.
+ */
+static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor )
+{
+ int i;
+ assert( stream );
+
+ if( stream->isBlockingStream )
+ BlockingEnd( stream );
+
+ for( i = 0; i < stream->num_incoming_connections; ++i )
+ {
+ if( stream->local_input_ports[i] )
+ ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 );
+ }
+ for( i = 0; i < stream->num_outgoing_connections; ++i )
+ {
+ if( stream->local_output_ports[i] )
+ ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 );
+ }
+
+ if( terminateStreamRepresentation )
+ PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+ if( terminateBufferProcessor )
+ PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+
+ if( stream->stream_memory )
+ {
+ PaUtil_FreeAllAllocations( stream->stream_memory );
+ PaUtil_DestroyAllocationGroup( stream->stream_memory );
+ }
+ PaUtil_FreeMemory( stream );
+}
+
+static PaError WaitCondition( PaJackHostApiRepresentation *hostApi )
+{
+ PaError result = paNoError;
+ int err = 0;
+ PaTime pt = PaUtil_GetTime();
+ struct timespec ts;
+
+ ts.tv_sec = (time_t) floor( pt + 10 * 60 /* 10 minutes */ );
+ ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000);
+ /* XXX: Best enclose in loop, in case of spurious wakeups? */
+ err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts );
+
+ /* Make sure we didn't time out */
+ UNLESS( err != ETIMEDOUT, paTimedOut );
+ UNLESS( !err, paInternalError );
+
+error:
+ return result;
+}
+
+static PaError AddStream( PaJackStream *stream )
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation *hostApi = stream->hostApi;
+ /* Add to queue of streams that should be processed */
+ ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
+ if( !hostApi->jackIsDown )
+ {
+ hostApi->toAdd = stream;
+ /* Unlock mutex and await signal from processing thread */
+ result = WaitCondition( stream->hostApi );
+ }
+ ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+ ENSURE_PA( result );
+
+ UNLESS( !hostApi->jackIsDown, paDeviceUnavailable );
+
+error:
+ return result;
+}
+
+/* Remove stream from processing queue */
+static PaError RemoveStream( PaJackStream *stream )
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation *hostApi = stream->hostApi;
+
+ /* Add to queue over streams that should be processed */
+ ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
+ if( !hostApi->jackIsDown )
+ {
+ hostApi->toRemove = stream;
+ /* Unlock mutex and await signal from processing thread */
+ result = WaitCondition( stream->hostApi );
+ }
+ ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+ ENSURE_PA( result );
+
+error:
+ return result;
+}
+
+/* Add stream to JACK callback processing queue */
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+ PaStream** s,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate,
+ unsigned long framesPerBuffer,
+ PaStreamFlags streamFlags,
+ PaStreamCallback *streamCallback,
+ void *userData )
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
+ PaJackStream *stream = NULL;
+ char *port_string = PaUtil_GroupAllocateMemory( jackHostApi->deviceInfoMemory, jack_port_name_size() );
+ // 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_size = jack_client_name_size() * 2 + 1;
+ unsigned long regex_size = regex_escaped_client_name_size + 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;
+ int inputChannelCount, outputChannelCount;
+ 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? */
+ unsigned long ofs;
+
+ /* validate platform specific flags */
+ if( (streamFlags & paPlatformSpecificFlags) != 0 )
+ return paInvalidFlag; /* unexpected platform specific flag */
+ if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 )
+ {
+ streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback;
+ /*return paInvalidFlag;*/ /* This implementation does not support buffer priming */
+ }
+
+ if( framesPerBuffer != paFramesPerBufferUnspecified )
+ {
+ /* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */
+ /*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/ /* TODO: Add descriptive error code? */
+ }
+
+ /* Preliminary checks */
+
+ if( inputParameters )
+ {
+ 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 */
+ }
+ else
+ {
+ inputChannelCount = 0;
+ }
+
+ if( outputParameters )
+ {
+ 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 */
+ }
+ else
+ {
+ outputChannelCount = 0;
+ }
+
+ /* ... check that the sample rate exactly matches the ONE acceptable rate
+ * A: This rate isn't necessarily constant though? */
+
+#define ABS(x) ( (x) > 0 ? (x) : -(x) )
+ if( ABS(sampleRate - jackSr) > 1 )
+ return paInvalidSampleRate;
+#undef ABS
+
+ UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory );
+ ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) );
+
+ /* the blocking emulation, if necessary */
+ stream->isBlockingStream = !streamCallback;
+ if( stream->isBlockingStream )
+ {
+ float latency = 0.001; /* 1ms is the absolute minimum we support */
+ int minimum_buffer_frames = 0;
+
+ if( inputParameters && inputParameters->suggestedLatency > latency )
+ latency = inputParameters->suggestedLatency;
+ else if( outputParameters && outputParameters->suggestedLatency > latency )
+ latency = outputParameters->suggestedLatency;
+
+ /* the latency the user asked for indicates the minimum buffer size in frames */
+ minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client ));
+
+ /* we also need to be able to store at least three full jack buffers to avoid dropouts */
+ if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames )
+ minimum_buffer_frames = jackHostApi->jack_buffer_size * 3;
+
+ /* setup blocking API data structures (FIXME: can fail) */
+ BlockingBegin( stream, minimum_buffer_frames );
+
+ /* install our own callback for the blocking API */
+ streamCallback = BlockingCallback;
+ userData = stream;
+
+ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+ &jackHostApi->blockingStreamInterface, streamCallback, userData );
+ }
+ else
+ {
+ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+ &jackHostApi->callbackStreamInterface, streamCallback, userData );
+ }
+ srInitialized = 1;
+ PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr );
+
+ /* create the JACK ports. We cannot connect them until audio
+ * processing begins */
+
+ /* Register a unique set of ports for this stream
+ * TODO: Robust allocation of new port names */
+
+ ofs = jackHostApi->inputBase;
+ for( i = 0; i < inputChannelCount; i++ )
+ {
+ snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i );
+ UNLESS( stream->local_input_ports[i] = jack_port_register(
+ jackHostApi->jack_client, port_string,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory );
+ }
+ jackHostApi->inputBase += inputChannelCount;
+
+ ofs = jackHostApi->outputBase;
+ for( i = 0; i < outputChannelCount; i++ )
+ {
+ snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i );
+ UNLESS( stream->local_output_ports[i] = jack_port_register(
+ jackHostApi->jack_client, port_string,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory );
+ }
+ jackHostApi->outputBase += outputChannelCount;
+
+ /* look up the jack_port_t's for the remote ports. We could do
+ * this at stream start time, but doing it here ensures the
+ * name lookup only happens once. */
+
+ if( inputChannelCount > 0 )
+ {
+ int err = 0;
+
+ /* Get output ports of our capture device */
+ copy_string_and_escape_regex_chars( regex_pattern,
+ hostApi->deviceInfos[ inputParameters->device ]->name,
+ regex_escaped_client_name_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++ )
+ {
+ if( (stream->remote_output_ports[i] = jack_port_by_name(
+ jackHostApi->jack_client, jack_ports[i] )) == NULL )
+ {
+ err = 1;
+ break;
+ }
+ }
+ free( jack_ports );
+ UNLESS( !err, paInsufficientMemory );
+
+ /* Fewer ports than expected? */
+ UNLESS( i == inputChannelCount, paInternalError );
+ }
+
+ if( outputChannelCount > 0 )
+ {
+ int err = 0;
+
+ /* Get input ports of our playback device */
+ copy_string_and_escape_regex_chars( regex_pattern,
+ hostApi->deviceInfos[ outputParameters->device ]->name,
+ regex_escaped_client_name_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++ )
+ {
+ if( (stream->remote_input_ports[i] = jack_port_by_name(
+ jackHostApi->jack_client, jack_ports[i] )) == 0 )
+ {
+ err = 1;
+ break;
+ }
+ }
+ free( jack_ports );
+ UNLESS( !err , paInsufficientMemory );
+
+ /* Fewer ports than expected? */
+ UNLESS( i == outputChannelCount, paInternalError );
+ }
+
+ ENSURE_PA( PaUtil_InitializeBufferProcessor(
+ &stream->bufferProcessor,
+ inputChannelCount,
+ inputSampleFormat,
+ paFloat32 | paNonInterleaved, /* hostInputSampleFormat */
+ outputChannelCount,
+ outputSampleFormat,
+ paFloat32 | paNonInterleaved, /* hostOutputSampleFormat */
+ jackSr,
+ streamFlags,
+ framesPerBuffer,
+ 0, /* Ignored */
+ paUtilUnknownHostBufferSize, /* Buffer size may vary on JACK's discretion */
+ streamCallback,
+ userData ) );
+ bpInitialized = 1;
+
+ if( stream->num_incoming_connections > 0 )
+ stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] )
+ - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */
+ + PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
+ if( stream->num_outgoing_connections > 0 )
+ stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] )
+ - jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */
+ + PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor )) / sampleRate;
+
+ stream->streamRepresentation.streamInfo.sampleRate = jackSr;
+ stream->t0 = jack_frame_time( jackHostApi->jack_client ); /* A: Time should run from Pa_OpenStream */
+
+ /* Add to queue of opened streams */
+ ENSURE_PA( AddStream( stream ) );
+
+ *s = (PaStream*)stream;
+
+ return result;
+
+error:
+ if( stream )
+ CleanUpStream( stream, srInitialized, bpInitialized );
+
+ return result;
+}
+
+/*
+ When CloseStream() is called, the multi-api layer ensures that
+ the stream has already been stopped or aborted.
+*/
+static PaError CloseStream( PaStream* s )
+{
+ PaError result = paNoError;
+ PaJackStream *stream = (PaJackStream*)s;
+
+ /* Remove this stream from the processing queue */
+ ENSURE_PA( RemoveStream( stream ) );
+
+error:
+ CleanUpStream( stream, 1, 1 );
+ return result;
+}
+
+static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames )
+{
+ PaError result = paNoError;
+ PaStreamCallbackTimeInfo timeInfo = {0,0,0};
+ int chn;
+ int framesProcessed;
+ const double sr = jack_get_sample_rate( stream->jack_client ); /* Shouldn't change during the process callback */
+ PaStreamCallbackFlags cbFlags = 0;
+
+ /* If the user has returned !paContinue from the callback we'll want to flush the internal buffers,
+ * when these are empty we can finally mark the stream as inactive */
+ if( stream->callbackResult != paContinue &&
+ PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+ {
+ stream->is_active = 0;
+ if( stream->streamRepresentation.streamFinishedCallback )
+ stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+ PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ ));
+
+ goto end;
+ }
+
+ timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr;
+ if( stream->num_incoming_connections > 0 )
+ timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] )
+ / sr;
+ if( stream->num_outgoing_connections > 0 )
+ timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] )
+ / sr;
+
+ PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+
+ if( stream->xrun )
+ {
+ /* XXX: Any way to tell which of these occurred? */
+ cbFlags = paOutputUnderflow | paInputOverflow;
+ stream->xrun = FALSE;
+ }
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo,
+ cbFlags );
+
+ if( stream->num_incoming_connections > 0 )
+ PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames );
+ if( stream->num_outgoing_connections > 0 )
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames );
+
+ for( chn = 0; chn < stream->num_incoming_connections; chn++ )
+ {
+ jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
+ jack_port_get_buffer( stream->local_input_ports[chn],
+ frames );
+
+ PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor,
+ chn,
+ channel_buf );
+ }
+
+ for( chn = 0; chn < stream->num_outgoing_connections; chn++ )
+ {
+ jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
+ jack_port_get_buffer( stream->local_output_ports[chn],
+ frames );
+
+ PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor,
+ chn,
+ channel_buf );
+ }
+
+ framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor,
+ &stream->callbackResult );
+ /* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */
+ assert( framesProcessed == frames );
+
+ PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+
+end:
+ return result;
+}
+
+/* Update the JACK callback's stream processing queue. */
+static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi )
+{
+ PaError result = paNoError;
+ int queueModified = 0;
+ const double jackSr = jack_get_sample_rate( hostApi->jack_client );
+ int err;
+
+ if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 )
+ {
+ assert( err == EBUSY );
+ return paNoError;
+ }
+
+ if( hostApi->toAdd )
+ {
+ if( hostApi->processQueue )
+ {
+ PaJackStream *node = hostApi->processQueue;
+ /* Advance to end of queue */
+ while( node->next )
+ node = node->next;
+
+ node->next = hostApi->toAdd;
+ }
+ else
+ {
+ /* The only queue entry. */
+ hostApi->processQueue = (PaJackStream *)hostApi->toAdd;
+ }
+
+ /* If necessary, update stream state */
+ if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr )
+ UpdateSampleRate( hostApi->toAdd, jackSr );
+
+ hostApi->toAdd = NULL;
+ queueModified = 1;
+ }
+ if( hostApi->toRemove )
+ {
+ int removed = 0;
+ PaJackStream *node = hostApi->processQueue, *prev = NULL;
+ assert( hostApi->processQueue );
+
+ while( node )
+ {
+ if( node == hostApi->toRemove )
+ {
+ if( prev )
+ prev->next = node->next;
+ else
+ hostApi->processQueue = (PaJackStream *)node->next;
+
+ removed = 1;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+ UNLESS( removed, paInternalError );
+ hostApi->toRemove = NULL;
+ PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ ));
+ queueModified = 1;
+ }
+
+ if( queueModified )
+ {
+ /* Signal that we've done what was asked of us */
+ ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 );
+ }
+
+error:
+ ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
+
+ return result;
+}
+
+/* Audio processing callback invoked periodically from JACK. */
+static int JackCallback( jack_nframes_t frames, void *userData )
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData;
+ PaJackStream *stream = NULL;
+ int xrun = hostApi->xrun;
+ hostApi->xrun = 0;
+
+ assert( hostApi );
+
+ ENSURE_PA( UpdateQueue( hostApi ) );
+
+ /* Process each stream */
+ stream = hostApi->processQueue;
+ for( ; stream; stream = stream->next )
+ {
+ if( xrun ) /* Don't override if already set */
+ stream->xrun = 1;
+
+ /* See if this stream is to be started */
+ if( stream->doStart )
+ {
+ /* If we can't obtain a lock, we'll try next time */
+ int err = pthread_mutex_trylock( &stream->hostApi->mtx );
+ if( !err )
+ {
+ if( stream->doStart ) /* Could potentially change before obtaining the lock */
+ {
+ stream->is_active = 1;
+ stream->doStart = 0;
+ PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ ));
+ ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
+ stream->callbackResult = paContinue;
+ stream->isSilenced = 0;
+ }
+
+ ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+ }
+ else
+ assert( err == EBUSY );
+ }
+ else if( stream->doStop || stream->doAbort ) /* Should we stop/abort stream? */
+ {
+ if( stream->callbackResult == paContinue ) /* Ok, make it stop */
+ {
+ PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ ));
+ stream->callbackResult = stream->doStop ? paComplete : paAbort;
+ }
+ }
+
+ if( stream->is_active )
+ ENSURE_PA( RealProcess( stream, frames ) );
+ /* If we have just entered inactive state, silence output */
+ if( !stream->is_active && !stream->isSilenced )
+ {
+ int i;
+
+ /* Silence buffer after entering inactive state */
+ PA_DEBUG(( "Silencing the output\n" ));
+ for( i = 0; i < stream->num_outgoing_connections; ++i )
+ {
+ jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames );
+ memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames );
+ }
+
+ stream->isSilenced = 1;
+ }
+
+ if( stream->doStop || stream->doAbort )
+ {
+ /* See if RealProcess has acted on the request */
+ if( !stream->is_active ) /* Ok, signal to the main thread that we've carried out the operation */
+ {
+ /* If we can't obtain a lock, we'll try next time */
+ int err = pthread_mutex_trylock( &stream->hostApi->mtx );
+ if( !err )
+ {
+ stream->doStop = stream->doAbort = 0;
+ ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
+ ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+ }
+ else
+ assert( err == EBUSY );
+ }
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+static PaError StartStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaJackStream *stream = (PaJackStream*)s;
+ int i;
+
+ /* Ready the processor */
+ PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+ /* 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 )
+ {
+ for( i = 0; i < stream->num_incoming_connections; i++ )
+ {
+ int r = jack_connect( stream->jack_client, jack_port_name( stream->remote_output_ports[i] ),
+ jack_port_name( stream->local_input_ports[i] ) );
+ UNLESS( 0 == r || EEXIST == r, paUnanticipatedHostError );
+ }
+ }
+
+ if( stream->num_outgoing_connections > 0 )
+ {
+ for( i = 0; i < stream->num_outgoing_connections; i++ )
+ {
+ int r = jack_connect( stream->jack_client, jack_port_name( stream->local_output_ports[i] ),
+ jack_port_name( stream->remote_input_ports[i] ) );
+ UNLESS( 0 == r || EEXIST == r, paUnanticipatedHostError );
+ }
+ }
+
+ stream->xrun = FALSE;
+
+ /* Enable processing */
+
+ ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
+ stream->doStart = 1;
+
+ /* Wait for stream to be started */
+ result = WaitCondition( stream->hostApi );
+ /*
+ do
+ {
+ err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts );
+ } while( !stream->is_active && !err );
+ */
+ if( result != paNoError ) /* Something went wrong, call off the stream start */
+ {
+ stream->doStart = 0;
+ stream->is_active = 0; /* Cancel any processing */
+ }
+ ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+
+ ENSURE_PA( result );
+
+ stream->is_running = TRUE;
+ PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ ));
+
+error:
+ return result;
+}
+
+static PaError RealStop( PaJackStream *stream, int abort )
+{
+ PaError result = paNoError;
+ int i;
+
+ if( stream->isBlockingStream )
+ BlockingWaitEmpty ( stream );
+
+ ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
+ if( abort )
+ stream->doAbort = 1;
+ else
+ stream->doStop = 1;
+
+ /* Wait for stream to be stopped */
+ result = WaitCondition( stream->hostApi );
+ ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
+ ENSURE_PA( result );
+
+ UNLESS( !stream->is_active, paInternalError );
+
+ PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ ));
+
+error:
+ stream->is_running = FALSE;
+
+ /* Disconnect ports belonging to this stream */
+
+ if( !stream->hostApi->jackIsDown ) /* XXX: Well? */
+ {
+ for( i = 0; i < stream->num_incoming_connections; i++ )
+ {
+ if( jack_port_connected( stream->local_input_ports[i] ) )
+ {
+ UNLESS( !jack_port_disconnect( stream->jack_client, stream->local_input_ports[i] ),
+ paUnanticipatedHostError );
+ }
+ }
+ for( i = 0; i < stream->num_outgoing_connections; i++ )
+ {
+ if( jack_port_connected( stream->local_output_ports[i] ) )
+ {
+ UNLESS( !jack_port_disconnect( stream->jack_client, stream->local_output_ports[i] ),
+ paUnanticipatedHostError );
+ }
+ }
+ }
+
+ return result;
+}
+
+static PaError StopStream( PaStream *s )
+{
+ assert(s);
+ return RealStop( (PaJackStream *)s, 0 );
+}
+
+static PaError AbortStream( PaStream *s )
+{
+ assert(s);
+ return RealStop( (PaJackStream *)s, 1 );
+}
+
+static PaError IsStreamStopped( PaStream *s )
+{
+ PaJackStream *stream = (PaJackStream*)s;
+ return !stream->is_running;
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+ PaJackStream *stream = (PaJackStream*)s;
+ return stream->is_active;
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+ PaJackStream *stream = (PaJackStream*)s;
+
+ /* A: Is this relevant?? --> TODO: what if we're recording-only? */
+ return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client );
+}
+
+
+static double GetStreamCpuLoad( PaStream* s )
+{
+ PaJackStream *stream = (PaJackStream*)s;
+ return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
+}
+
+PaError PaJack_SetClientName( const char* name )
+{
+ if( strlen( name ) > jack_client_name_size() )
+ {
+ /* OK, I don't know any better error code */
+ return paInvalidFlag;
+ }
+ clientName_ = name;
+ return paNoError;
+}
+
+PaError PaJack_GetClientName(const char** clientName)
+{
+ PaError result = paNoError;
+ PaJackHostApiRepresentation* jackHostApi = NULL;
+ PaJackHostApiRepresentation** ref = &jackHostApi;
+ ENSURE_PA( PaUtil_GetHostApiRepresentation( (PaUtilHostApiRepresentation**)ref, paJACK ) );
+ *clientName = jack_get_client_name( jackHostApi->jack_client );
+
+error:
+ return result;
+}
+