amarchheda's picture
inital commit
83418c6
raw
history blame
65.7 kB
/*
* $Id$
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
* JACK Implementation by Joshua Haberman
*
* Copyright (c) 2004 Stefan Westerfeld <[email protected]>
* Copyright (c) 2004 Arve Knudsen <[email protected]>
* Copyright (c) 2002 Joshua Haberman <[email protected]>
*
* 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;
}