| /* |
| * $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; |
| } |
| |