Mercurial > audlegacy-plugins
diff src/Output/jack/bio2jack.c @ 0:13389e613d67 trunk
[svn] - initial import of audacious-plugins tree (lots to do)
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 01:11:49 -0700 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Output/jack/bio2jack.c Mon Sep 18 01:11:49 2006 -0700 @@ -0,0 +1,2612 @@ +/* + * Copyright 2003-2004 Chris Morgan <cmorgan@alum.wpi.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* NOTE: All functions that take a jack_driver_t* do NOT lock the device, in order to get a */ +/* jack_driver_t* you must call getDriver() which will pthread_mutex_lock() */ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#include <unistd.h> +#include <inttypes.h> +#include <jack/jack.h> +#include <jack/ringbuffer.h> +#include <pthread.h> +#include <sys/time.h> +#include <samplerate.h> + +#include "bio2jack.h" + +/* enable/disable TRACING through the JACK_Callback() function */ +/* this can sometimes be too much information */ +#define TRACE_CALLBACK 0 + +/* set to 1 for verbose output */ +#define VERBOSE_OUTPUT 0 + +/* set to 1 to enable debug messages */ +#define DEBUG_OUTPUT 0 + +/* set to 1 to enable tracing */ +#define TRACE_ENABLE 0 + +/* set to 1 to enable the function timers */ +#define TIMER_ENABLE 0 + +/* set to 1 to enable tracing of getDriver() and releaseDriver() */ +#define TRACE_getReleaseDevice 0 + +#define ENABLE_WARNINGS 0 + +#define DEFAULT_RB_SIZE 16384 + +#define OUTFILE stderr + +#if TIMER_ENABLE +/* This seemingly construct makes timing arbitrary functions really easy + all you have to do is place a 'TIMER("start\n")' at the beginning and + a 'TIMER("stop\n")' at the end of any function and this does the rest + (naturally you can place any printf-compliant text you like in the argument + along with the associated values). */ +static struct timeval timer_now; +#define TIMER(format,args...) gettimeofday(&timer_now,0); \ + fprintf(OUTFILE, "%ld.%06ld: %s::%s(%d) "format, timer_now.tv_sec, timer_now.tv_usec, __FILE__, __FUNCTION__, __LINE__, ##args) +#else +#define TIMER(...) +#endif + +#if TRACE_ENABLE +#define TRACE(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \ + fflush(OUTFILE); +#else +#define TRACE(...) +#endif + +#if DEBUG_OUTPUT +#define DEBUG(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \ + fflush(OUTFILE); +#else +#define DEBUG(...) +#endif + +#if TRACE_CALLBACK +#define CALLBACK_TRACE(format,args...) fprintf(OUTFILE, "%s::%s(%d) "format, __FILE__, __FUNCTION__, __LINE__,##args); \ + fflush(OUTFILE); +#else +#define CALLBACK_TRACE(...) +#endif + +#if ENABLE_WARNINGS +#define WARN(format,args...) fprintf(OUTFILE, "WARN: %s::%s(%d) "format, __FILE__,__FUNCTION__,__LINE__,##args); \ + fflush(OUTFILE); +#else +#define WARN(...) +#endif + +#define ERR(format,args...) fprintf(OUTFILE, "ERR: %s::%s(%d) "format, __FILE__,__FUNCTION__,__LINE__,##args); \ + fflush(OUTFILE); + +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define max(a,b) (((a) < (b)) ? (b) : (a)) + +#define MAX_OUTPUT_PORTS 10 +#define MAX_INPUT_PORTS 10 + +typedef struct jack_driver_s +{ + bool allocated; /* whether or not this device has been allocated */ + + int deviceID; /* id of this device */ + int clientCtr; /* to prevent overlapping client ids */ + long jack_sample_rate; /* jack samples(frames) per second */ + + long client_sample_rate; /* client samples(frames) per second */ + double output_sample_rate_ratio; /* ratio between jack's output rate & ours */ + double input_sample_rate_ratio; /* ratio between our input rate & jack's */ + + unsigned long num_input_channels; /* number of input channels(1 is mono, 2 stereo etc..) */ + unsigned long num_output_channels; /* number of output channels(1 is mono, 2 stereo etc..) */ + + unsigned long bits_per_channel; /* number of bits per channel (only 8 & 16 are currently supported) */ + + unsigned long bytes_per_output_frame; /* (num_output_channels * bits_per_channel) / 8 */ + unsigned long bytes_per_input_frame; /* (num_input_channels * bits_per_channel) / 8 */ + + unsigned long bytes_per_jack_output_frame; /* (num_output_channels * bits_per_channel) / 8 */ + unsigned long bytes_per_jack_input_frame; /* (num_input_channels * bits_per_channel) / 8 */ + + unsigned long latencyMS; /* latency in ms between writing and actual audio output of the written data */ + + long clientBytesInJack; /* number of INPUT bytes(from the client of bio2jack) we wrote to jack(not necessary the number of bytes we wrote to jack) */ + long jack_buffer_size; /* size of the buffer jack will pass in to the process callback */ + + unsigned long callback_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */ + char *callback_buffer1; + unsigned long callback_buffer2_size; /* number of bytes in the buffer allocated for processing data in JACK_Callback */ + char *callback_buffer2; + + unsigned long rw_buffer1_size; /* number of bytes in the buffer allocated for processing data in JACK_(Read|Write) */ + char *rw_buffer1; + + struct timeval previousTime; /* time of last JACK_Callback() write to jack, allows for MS accurate bytes played */ + + unsigned long written_client_bytes; /* input bytes we wrote to jack, not necessarily actual bytes we wrote to jack due to channel and other conversion */ + unsigned long played_client_bytes; /* input bytes that jack has played */ + + unsigned long client_bytes; /* total bytes written by the client of bio2jack via JACK_Write() */ + + jack_port_t *output_port[MAX_OUTPUT_PORTS]; /* output ports */ + jack_port_t *input_port[MAX_OUTPUT_PORTS]; /* input ports */ + + jack_client_t *client; /* pointer to jack client */ + + char **jack_port_name; /* user given strings for the port names, can be NULL */ + unsigned int jack_port_name_count; /* the number of port names given */ + + unsigned long jack_output_port_flags; /* flags to be passed to jack when opening the output ports */ + unsigned long jack_input_port_flags; /* flags to be passed to jack when opening the output ports */ + + jack_ringbuffer_t *pPlayPtr; /* the playback ringbuffer */ + jack_ringbuffer_t *pRecPtr; /* the recording ringbuffer */ + + SRC_STATE *output_src; /* SRC object for the output stream */ + SRC_STATE *input_src; /* SRC object for the output stream */ + + enum status_enum state; /* one of PLAYING, PAUSED, STOPPED, CLOSED, RESET etc */ + + unsigned int volume[MAX_OUTPUT_PORTS]; /* percentage of sample value to preserve, 100 would be no attenuation */ + enum JACK_VOLUME_TYPE volumeEffectType; /* linear or dbAttenuation, if dbAttenuation volume is the number of dBs of + attenuation to apply, 0 volume being no attenuation, full volume */ + + long position_byte_offset; /* an offset that we will apply to returned position queries to achieve */ + /* the position that the user of the driver desires set */ + + bool in_use; /* true if this device is currently in use */ + + pthread_mutex_t mutex; /* mutex to lock this specific device */ + + /* variables used for trying to restart the connection to jack */ + bool jackd_died; /* true if jackd has died and we should try to restart it */ + struct timeval last_reconnect_attempt; +} jack_driver_t; + + +static char *client_name; /* the name bio2jack will use when creating a new + jack client. client_name_%deviceID% will be used */ + + +static bool do_sample_rate_conversion; /* whether the client has requested sample rate conversion, + default to on for improved compatibility */ + +/* + Which SRC converter function we should use when doing sample rate conversion. + Default to the fastest of the 'good quality' set. + */ +static int preferred_src_converter = SRC_SINC_FASTEST; + +static bool init_done = 0; /* just to prevent clients from calling JACK_Init twice, that would be very bad */ + +static enum JACK_PORT_CONNECTION_MODE port_connection_mode = CONNECT_ALL; + +/* enable/disable code that allows us to close a device without actually closing the jack device */ +/* this works around the issue where jack doesn't always close devices by the time the close function call returns */ +#define JACK_CLOSE_HACK 1 + +typedef jack_default_audio_sample_t sample_t; +typedef jack_nframes_t nframes_t; + +/* allocate devices for output */ +/* if you increase this past 10, you might want to update 'out_client_name = ... ' in JACK_OpenDevice */ +#define MAX_OUTDEVICES 10 +static jack_driver_t outDev[MAX_OUTDEVICES]; + +static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER; /* this is to lock the entire outDev array + to make managing it in a threaded + environment sane */ + +#if JACK_CLOSE_HACK +static void JACK_CloseDevice(jack_driver_t * drv, bool close_client); +#else +static void JACK_CloseDevice(jack_driver_t * drv); +#endif + + +/* Prototypes */ +static int JACK_OpenDevice(jack_driver_t * drv); +static unsigned long JACK_GetBytesFreeSpaceFromDriver(jack_driver_t * drv); +static void JACK_ResetFromDriver(jack_driver_t * drv); +static long JACK_GetPositionFromDriver(jack_driver_t * drv, + enum pos_enum position, int type); +static void JACK_CleanupDriver(jack_driver_t * drv); + + +/* Return the difference between two timeval structures in terms of milliseconds */ +long +TimeValDifference(struct timeval *start, struct timeval *end) +{ + double long ms; /* milliseconds value */ + + ms = end->tv_sec - start->tv_sec; /* compute seconds difference */ + ms *= (double) 1000; /* convert to milliseconds */ + + ms += (double) (end->tv_usec - start->tv_usec) / (double) 1000; /* add on microseconds difference */ + + return (long) ms; +} + +/* get a device and lock the devices mutex */ +/* */ +/* also attempt to reconnect to jack since this function is called from */ +/* most other bio2jack functions it provides a good point to attempt reconnection */ +/* */ +/* Ok, I know this looks complicated and it kind of is. The point is that when you're + trying to trace mutexes it's more important to know *who* called us than just that + we were called. This uses from pre-processor trickery so that the fprintf is actually + placed in the function making the getDriver call. Thus, the __FUNCTION__ and __LINE__ + macros will actually reference our caller, rather than getDriver. The reason the + fprintf call is passes as a parameter is because this macro has to still return a + jack_driver_t* and we want to log both before *and* after the getDriver call for + easier detection of blocked calls. + */ +#if TRACE_getReleaseDevice +#define getDriver(x) _getDriver(x,fprintf(OUTFILE, "%s::%s(%d) getting driver %d\n", __FILE__, __FUNCTION__, __LINE__,x)); TRACE("got driver %d\n",x); +jack_driver_t * +_getDriver(int deviceID, int ignored) +{ + fflush(OUTFILE); +#else +jack_driver_t * +getDriver(int deviceID) +{ +#endif + jack_driver_t *drv = &outDev[deviceID]; + + if(pthread_mutex_lock(&drv->mutex) != 0) + ERR("lock returned an error\n"); + + /* should we try to restart the jack server? */ + if(drv->jackd_died && drv->client == 0) + { + struct timeval now; + gettimeofday(&now, 0); + + /* wait 250ms before trying again */ + if(TimeValDifference(&drv->last_reconnect_attempt, &now) >= 250) + { + JACK_OpenDevice(drv); + drv->last_reconnect_attempt = now; + } + } + + return drv; +} + +/* release a device's mutex */ +/* */ +/* This macro is similar to the one for getDriver above, only simpler since we only + really need to know when the lock was release for the sake of debugging. +*/ +#if TRACE_getReleaseDevice +#define releaseDriver(x) TRACE("releasing driver %d\n",x->deviceID); _releaseDriver(x); +void +_releaseDriver(jack_driver_t * drv) +#else +void +releaseDriver(jack_driver_t * drv) +#endif +{ + /* + #if TRACE_getReleaseDevice + TRACE("deviceID == %d\n", drv->deviceID); + #endif + */ + if(pthread_mutex_unlock(&drv->mutex) != 0) + ERR("lock returned an error\n"); +} + + +/* Return a string corresponding to the input state */ +char * +DEBUGSTATE(enum status_enum state) +{ + if(state == PLAYING) + return "PLAYING"; + else if(state == PAUSED) + return "PAUSED"; + else if(state == STOPPED) + return "STOPPED"; + else if(state == CLOSED) + return "CLOSED"; + else if(state == RESET) + return "RESET"; + else + return "unknown state"; +} + + +#define SAMPLE_MAX_16BIT 32767.0f +#define SAMPLE_MAX_8BIT 127.0f + +/* floating point volume routine */ +/* volume should be a value between 0.0 and 1.0 */ +static void +float_volume_effect(sample_t * buf, unsigned long nsamples, float volume, + int skip) +{ + if(volume < 0) + volume = 0; + if(volume > 1.0) + volume = 1.0; + + while(nsamples--) + { + *buf = (*buf) * volume; + buf += skip; + } +} + +/* place one channel into a multi-channel stream */ +static inline void +mux(sample_t * dst, sample_t * src, unsigned long nsamples, + unsigned long dst_skip) +{ + /* ALERT: signed sign-extension portability !!! */ + while(nsamples--) + { + *dst = *src; + dst += dst_skip; + src++; + } +} + +/* pull one channel out of a multi-channel stream */ +static void +demux(sample_t * dst, sample_t * src, unsigned long nsamples, + unsigned long src_skip) +{ + /* ALERT: signed sign-extension portability !!! */ + while(nsamples--) + { + *dst = *src; + dst++; + src += src_skip; + } +} + +/* convert from 16 bit to floating point */ +static inline void +sample_move_short_float(sample_t * dst, short *src, unsigned long nsamples) +{ + /* ALERT: signed sign-extension portability !!! */ + unsigned long i; + for(i = 0; i < nsamples; i++) + dst[i] = (sample_t) (src[i]) / SAMPLE_MAX_16BIT; +} + +/* convert from floating point to 16 bit */ +static inline void +sample_move_float_short(short *dst, sample_t * src, unsigned long nsamples) +{ + /* ALERT: signed sign-extension portability !!! */ + unsigned long i; + for(i = 0; i < nsamples; i++) + dst[i] = (short) ((src[i]) * SAMPLE_MAX_16BIT); +} + +/* convert from 8 bit to floating point */ +static inline void +sample_move_char_float(sample_t * dst, char *src, unsigned long nsamples) +{ + /* ALERT: signed sign-extension portability !!! */ + unsigned long i; + for(i = 0; i < nsamples; i++) + dst[i] = (sample_t) (src[i]) / SAMPLE_MAX_8BIT; +} + +/* convert from floating point to 8 bit */ +static inline void +sample_move_float_char(char *dst, sample_t * src, unsigned long nsamples) +{ + /* ALERT: signed sign-extension portability !!! */ + unsigned long i; + for(i = 0; i < nsamples; i++) + dst[i] = (char) ((src[i]) * SAMPLE_MAX_8BIT); +} + +/* fill dst buffer with nsamples worth of silence */ +static void inline +sample_silence_float(sample_t * dst, unsigned long nsamples) +{ + /* ALERT: signed sign-extension portability !!! */ + while(nsamples--) + { + *dst = 0; + dst++; + } +} + +static bool inline +ensure_buffer_size(char **buffer, unsigned long *cur_size, + unsigned long needed_size) +{ + DEBUG("current size = %lu, needed size = %lu\n", *cur_size, needed_size); + if(*cur_size >= needed_size) + return TRUE; + DEBUG("reallocing\n"); + char *tmp = realloc(*buffer, needed_size); + if(tmp) + { + *cur_size = needed_size; + *buffer = tmp; + return TRUE; + } + DEBUG("reallocing failed\n"); + return FALSE; +} + +/****************************************************************** + * JACK_callback + * + * every time the jack server wants something from us it calls this + * function, so we either deliver it some sound to play or deliver it nothing + * to play + */ +static int +JACK_callback(nframes_t nframes, void *arg) +{ + jack_driver_t *drv = (jack_driver_t *) arg; + + unsigned int i; + int src_error = 0; + + TIMER("start\n"); + gettimeofday(&drv->previousTime, 0); /* record the current time */ + + CALLBACK_TRACE("nframes %ld, sizeof(sample_t) == %d\n", (long) nframes, + sizeof(sample_t)); + + if(!drv->client) + ERR("client is closed, this is weird...\n"); + + sample_t *out_buffer[MAX_OUTPUT_PORTS]; + /* retrieve the buffers for the output ports */ + for(i = 0; i < drv->num_output_channels; i++) + out_buffer[i] = (sample_t *) jack_port_get_buffer(drv->output_port[i], nframes); + + sample_t *in_buffer[MAX_INPUT_PORTS]; + /* retrieve the buffers for the input ports */ + for(i = 0; i < drv->num_input_channels; i++) + in_buffer[i] = (sample_t *) jack_port_get_buffer(drv->input_port[i], nframes); + + /* handle playing state */ + if(drv->state == PLAYING) + { + /* handle playback data, if any */ + if(drv->num_output_channels > 0) + { + unsigned long jackFramesAvailable = nframes; /* frames we have left to write to jack */ + unsigned long numFramesToWrite; /* num frames we are writing */ + size_t inputBytesAvailable = jack_ringbuffer_read_space(drv->pPlayPtr); + unsigned long inputFramesAvailable; /* frames we have available */ + + inputFramesAvailable = inputBytesAvailable / drv->bytes_per_jack_output_frame; + size_t jackBytesAvailable = jackFramesAvailable * drv->bytes_per_jack_output_frame; + + long read = 0; + + CALLBACK_TRACE("playing... jackFramesAvailable = %ld inputFramesAvailable = %ld\n", + jackFramesAvailable, inputFramesAvailable); + +#if JACK_CLOSE_HACK + if(drv->in_use == FALSE) + { + /* output silence if nothing is being outputted */ + for(i = 0; i < drv->num_output_channels; i++) + sample_silence_float(out_buffer[i], nframes); + + return -1; + } +#endif + + /* make sure our buffer is large enough for the data we are writing */ + /* ie. callback_buffer1_size < (bytes we already wrote + bytes we are going to write in this loop) */ + if(!ensure_buffer_size + (&drv->callback_buffer1, &drv->callback_buffer1_size, + jackBytesAvailable)) + { + ERR("allocated %lu bytes, need %u bytes\n", + drv->callback_buffer1_size, jackBytesAvailable); + return -1; + } + + /* do sample rate conversion if needed & requested */ + if(drv->output_src && drv->output_sample_rate_ratio != 1.0) + { + long bytes_needed_write = nframes * drv->bytes_per_jack_output_frame; + + /* make a very good guess at how many raw bytes we'll need to satisfy jack's request after conversion */ + long bytes_needed_read = min(inputBytesAvailable, + (double) (bytes_needed_write + + drv-> + output_sample_rate_ratio + * + drv-> + bytes_per_jack_output_frame) + / drv->output_sample_rate_ratio); + DEBUG("guessing that we need %ld bytes in and %ld out for rate conversion ratio = %f\n", + bytes_needed_read, bytes_needed_write, + drv->output_sample_rate_ratio); + + if(!ensure_buffer_size(&drv->callback_buffer1, + &drv->callback_buffer1_size, + bytes_needed_read)) + { + ERR("could not realloc callback_buffer2!\n"); + return 1; + } + if(!ensure_buffer_size(&drv->callback_buffer2, + &drv->callback_buffer2_size, + bytes_needed_write)) + { + ERR("could not realloc callback_buffer2!\n"); + return 1; + } + + if(jackFramesAvailable && inputBytesAvailable > 0) + { + /* read in the data, but don't move the read pointer until we know how much SRC used */ + jack_ringbuffer_peek(drv->pPlayPtr, drv->callback_buffer1, + bytes_needed_read); + + SRC_DATA srcdata; + srcdata.data_in = (sample_t *) drv->callback_buffer1; + srcdata.input_frames = bytes_needed_read / drv->bytes_per_jack_output_frame; + srcdata.src_ratio = drv->output_sample_rate_ratio; + srcdata.data_out = (sample_t *) drv->callback_buffer2; + srcdata.output_frames = nframes; + srcdata.end_of_input = 0; // it's a stream, it never ends + DEBUG("input_frames = %ld, output_frames = %ld\n", + srcdata.input_frames, srcdata.output_frames); + /* convert the sample rate */ + src_error = src_process(drv->output_src, &srcdata); + DEBUG("used = %ld, generated = %ld, error = %d: %s.\n", + srcdata.input_frames_used, srcdata.output_frames_gen, + src_error, src_strerror(src_error)); + + if(src_error == 0) + { + /* now we can move the read pointer */ + jack_ringbuffer_read_advance(drv->pPlayPtr, + srcdata. + input_frames_used * + drv->bytes_per_jack_output_frame); + /* add on what we wrote */ + read = srcdata.input_frames_used * drv->bytes_per_output_frame; + jackFramesAvailable -= srcdata.output_frames_gen; /* take away what was used */ + } + } + + if(src_error == 0) + { + /* demux the stream: we skip over the number of samples we have output channels as the channel data */ + /* is encoded like chan1,chan2,chan3,chan1,chan2,chan3... */ + for(i = 0; i < drv->num_output_channels; i++) + { + demux(out_buffer[i], + (sample_t *) drv->callback_buffer2 + i, + (nframes - jackFramesAvailable), drv->num_output_channels); + } + } + } + else /* no resampling needed or requested */ + { + /* read as much data from the buffer as is available */ + if(jackFramesAvailable && inputBytesAvailable > 0) + { + /* write as many bytes as we have space remaining, or as much as we have data to write */ + numFramesToWrite = min(jackFramesAvailable, inputFramesAvailable); + jack_ringbuffer_read(drv->pPlayPtr, drv->callback_buffer1, + jackBytesAvailable); + /* add on what we wrote */ + read = numFramesToWrite * drv->bytes_per_output_frame; + jackFramesAvailable -= numFramesToWrite; /* take away what was written */ + } + /* demux the stream: we skip over the number of samples we have output channels as the channel data */ + /* is encoded like chan1,chan2,chan3,chan1,chan2,chan3... */ + for(i = 0; i < drv->num_output_channels; i++) + { + demux(out_buffer[i], + (sample_t *) drv->callback_buffer1 + i, + (nframes - jackFramesAvailable), drv->num_output_channels); + } + } + + drv->written_client_bytes += read; + drv->played_client_bytes += drv->clientBytesInJack; /* move forward by the previous bytes we wrote since those must have finished by now */ + drv->clientBytesInJack = read; /* record the input bytes we wrote to jack */ + + /* see if we still have jackBytesLeft here, if we do that means that we + ran out of wave data to play and had a buffer underrun, fill in + the rest of the space with zero bytes so at least there is silence */ + if(jackFramesAvailable) + { + WARN("buffer underrun of %ld frames\n", jackFramesAvailable); + for(i = 0; i < drv->num_output_channels; i++) + sample_silence_float(out_buffer[i] + + (nframes - jackFramesAvailable), + jackFramesAvailable); + } + } + + /* handle record data, if any */ + if(drv->num_input_channels > 0) + { + long jack_bytes = nframes * drv->bytes_per_jack_input_frame; /* how many bytes jack is feeding us */ + + if(!ensure_buffer_size(&drv->callback_buffer1, &drv->callback_buffer1_size, jack_bytes)) + { + ERR("allocated %lu bytes, need %lu bytes\n", + drv->callback_buffer1_size, jack_bytes); + return -1; + } + + /* mux the invividual channels into one stream */ + for(i = 0; i < drv->num_input_channels; i++) + { + mux((sample_t *) drv->callback_buffer1 + i, in_buffer[i], + nframes, drv->num_input_channels); + } + + /* do sample rate conversion if needed & requested */ + if(drv->input_src && drv->input_sample_rate_ratio != 1.0) + { + /* make a very good guess at how many raw bytes we'll need to read all the data jack gave us */ + long bytes_needed_write = (double) (jack_bytes + + drv->input_sample_rate_ratio * + drv->bytes_per_jack_input_frame) * + drv->input_sample_rate_ratio; + DEBUG("guessing that we need %ld bytes in and %ld out for rate conversion ratio = %f\n", + nframes * drv->bytes_per_jack_input_frame, + bytes_needed_write, drv->input_sample_rate_ratio); + + if(!ensure_buffer_size(&drv->callback_buffer2, + &drv->callback_buffer2_size, + bytes_needed_write)) + { + ERR("could not realloc callback_buffer2!\n"); + return 1; + } + + SRC_DATA srcdata; + srcdata.data_in = (sample_t *) drv->callback_buffer1; + srcdata.input_frames = nframes; + srcdata.src_ratio = drv->input_sample_rate_ratio; + srcdata.data_out = (sample_t *) drv->callback_buffer2; + srcdata.output_frames = drv->callback_buffer2_size / drv->bytes_per_jack_input_frame; + srcdata.end_of_input = 0; // it's a stream, it never ends + DEBUG("input_frames = %ld, output_frames = %ld\n", + srcdata.input_frames, srcdata.output_frames); + /* convert the sample rate */ + src_error = src_process(drv->input_src, &srcdata); + DEBUG("used = %ld, generated = %ld, error = %d: %s.\n", + srcdata.input_frames_used, srcdata.output_frames_gen, + src_error, src_strerror(src_error)); + + if(src_error == 0) + { + long write_space = jack_ringbuffer_write_space(drv->pRecPtr); + long bytes_used = srcdata.output_frames_gen * drv->bytes_per_jack_input_frame; + /* if there isn't enough room, make some. sure this discards data, but when dealing with input sources + it seems like it's better to throw away old data than new */ + if(write_space < bytes_used) + { + /* the ringbuffer is designed such that only one thread should ever access each pointer. + since calling read_advance here will be touching the read pointer which is also accessed + by JACK_Read, we need to lock the mutex first for safety */ + getDriver(drv->deviceID); + + /* double check the write space after we've gained the lock, just in case JACK_Read was being called + before we gained it */ + write_space = jack_ringbuffer_write_space(drv->pRecPtr); + if(write_space < bytes_used) + { + /* hey, we warn about underruns, we might as well warn about overruns as well */ + WARN("buffer overrun of %ld bytes\n", jack_bytes - write_space); + jack_ringbuffer_read_advance(drv->pRecPtr, + bytes_used - write_space); + } + releaseDriver(drv); + } + + jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer2, + bytes_used); + } + } + else /* no resampling needed */ + { + long write_space = jack_ringbuffer_write_space(drv->pRecPtr); + /* if there isn't enough room, make some. sure this discards data, but when dealing with input sources + it seems like it's better to throw away old data than new */ + if(write_space < jack_bytes) + { + /* the ringbuffer is designed such that only one thread should ever access each pointer. + since calling read_advance here will be touching the read pointer which is also accessed + by JACK_Read, we need to lock the mutex first for safety */ + getDriver(drv->deviceID); + + /* double check the write space after we've gained the lock, just in case JACK_Read was being called + before we gained it */ + write_space = jack_ringbuffer_write_space(drv->pRecPtr); + if(write_space < jack_bytes) + { + ERR("buffer overrun of %ld bytes\n", jack_bytes - write_space); + jack_ringbuffer_read_advance(drv->pRecPtr, + jack_bytes - write_space); + } + releaseDriver(drv); + } + + jack_ringbuffer_write(drv->pRecPtr, drv->callback_buffer1, + jack_bytes); + } + } + } + else if(drv->state == PAUSED || + drv->state == STOPPED || + drv->state == CLOSED || drv->state == RESET) + { + CALLBACK_TRACE("%s, outputting silence\n", DEBUGSTATE(drv->state)); + + /* output silence if nothing is being outputted */ + for(i = 0; i < drv->num_output_channels; i++) + sample_silence_float(out_buffer[i], nframes); + + /* if we were told to reset then zero out some variables */ + /* and transition to STOPPED */ + if(drv->state == RESET) + { + drv->written_client_bytes = 0; + drv->played_client_bytes = 0; /* number of the clients bytes that jack has played */ + + drv->client_bytes = 0; /* bytes that the client wrote to use */ + + drv->clientBytesInJack = 0; /* number of input bytes in jack(not necessary the number of bytes written to jack) */ + + drv->position_byte_offset = 0; + + if(drv->pPlayPtr) + jack_ringbuffer_reset(drv->pPlayPtr); + + if(drv->pRecPtr) + jack_ringbuffer_reset(drv->pRecPtr); + + drv->state = STOPPED; /* transition to STOPPED */ + } + } + + CALLBACK_TRACE("done\n"); + TIMER("finish\n"); + + return 0; +} + + +/****************************************************************** + * JACK_bufsize + * + * Called whenever the jack server changes the the max number + * of frames passed to JACK_callback + */ +static int +JACK_bufsize(nframes_t nframes, void *arg) +{ + jack_driver_t *drv = (jack_driver_t *) arg; + TRACE("the maximum buffer size is now %lu frames\n", (long) nframes); + + drv->jack_buffer_size = nframes; + + return 0; +} + +/****************************************************************** + * JACK_srate + */ +int +JACK_srate(nframes_t nframes, void *arg) +{ + jack_driver_t *drv = (jack_driver_t *) arg; + + drv->jack_sample_rate = (long) nframes; + + /* make sure to recalculate the ratios needed for proper sample rate conversion */ + drv->output_sample_rate_ratio = (double) drv->jack_sample_rate / (double) drv->client_sample_rate; + if(drv->output_src) src_set_ratio(drv->output_src, drv->output_sample_rate_ratio); + + drv->input_sample_rate_ratio = (double) drv->client_sample_rate / (double) drv->jack_sample_rate; + if(drv->input_src) src_set_ratio(drv->input_src, drv->input_sample_rate_ratio); + + TRACE("the sample rate is now %lu/sec\n", (long) nframes); + return 0; +} + + +/****************************************************************** + * JACK_shutdown + * + * if this is called then jack shut down... handle this appropriately */ +void +JACK_shutdown(void *arg) +{ + jack_driver_t *drv = (jack_driver_t *) arg; + + TRACE("\n"); + + getDriver(drv->deviceID); + + drv->client = 0; /* reset client */ + drv->jackd_died = TRUE; + + TRACE("jack shutdown, setting client to 0 and jackd_died to true, closing device\n"); + +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + + TRACE("trying to reconnect right now\n"); + /* lets see if we can't reestablish the connection */ + if(JACK_OpenDevice(drv) != ERR_SUCCESS) + { + ERR("unable to reconnect with jack\n"); + } + + releaseDriver(drv); +} + + +/****************************************************************** + * JACK_Error + * + * Callback for jack errors + */ +static void +JACK_Error(const char *desc) +{ + ERR("%s\n", desc); +} + + +/****************************************************************** + * JACK_OpenDevice + * + * RETURNS: ERR_SUCCESS upon success + */ +static int +JACK_OpenDevice(jack_driver_t * drv) +{ + const char **ports; + char *our_client_name = 0; + unsigned int i; + int failed = 0; + + TRACE("creating jack client and setting up callbacks\n"); + +#if JACK_CLOSE_HACK + /* see if this device is already open */ + if(drv->client) + { + /* if this device is already in use then it is bad for us to be in here */ + if(drv->in_use) + return ERR_OPENING_JACK; + + TRACE("using existing client\n"); + drv->in_use = TRUE; + return ERR_SUCCESS; + } +#endif + + /* set up an error handler */ + jack_set_error_function(JACK_Error); + + + /* build the client name */ + our_client_name = (char *) malloc(snprintf + (our_client_name, 0, "%s_%d_%d%02d", client_name, getpid(), + drv->deviceID, drv->clientCtr + 1) + 1); + sprintf(our_client_name, "%s_%d_%d%02d", client_name, getpid(), + drv->deviceID, drv->clientCtr++); + + /* try to become a client of the JACK server */ + TRACE("client name '%s'\n", our_client_name); + if((drv->client = jack_client_new(our_client_name)) == 0) + { + /* try once more */ + TRACE("trying once more to jack_client_new"); + if((drv->client = jack_client_new(our_client_name)) == 0) + { + ERR("jack server not running?\n"); + free(our_client_name); + return ERR_OPENING_JACK; + } + } + + free(our_client_name); + + TRACE("setting up jack callbacks\n"); + + /* JACK server to call `JACK_callback()' whenever + there is work to be done. */ + jack_set_process_callback(drv->client, JACK_callback, drv); + + /* setup a buffer size callback */ + jack_set_buffer_size_callback(drv->client, JACK_bufsize, drv); + + /* tell the JACK server to call `srate()' whenever + the sample rate of the system changes. */ + jack_set_sample_rate_callback(drv->client, JACK_srate, drv); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. */ + jack_on_shutdown(drv->client, JACK_shutdown, drv); + + /* display the current sample rate. once the client is activated + (see below), you should rely on your own sample rate + callback (see above) for this value. */ + drv->jack_sample_rate = jack_get_sample_rate(drv->client); + drv->output_sample_rate_ratio = (double) drv->jack_sample_rate / (double) drv->client_sample_rate; + drv->input_sample_rate_ratio = (double) drv->client_sample_rate / (double) drv->jack_sample_rate; + TRACE("client sample rate: %lu, jack sample rate: %lu, output ratio = %f, input ratio = %f\n", + drv->client_sample_rate, drv->jack_sample_rate, + drv->output_sample_rate_ratio, drv->input_sample_rate_ratio); + + drv->jack_buffer_size = jack_get_buffer_size(drv->client); + + /* create the output ports */ + TRACE("creating output ports\n"); + for(i = 0; i < drv->num_output_channels; i++) + { + char portname[32]; + sprintf(portname, "out_%d", i); + TRACE("port %d is named '%s'\n", i, portname); + /* NOTE: Yes, this is supposed to be JackPortIsOutput since this is an output */ + /* port FROM bio2jack */ + drv->output_port[i] = jack_port_register(drv->client, portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + } + + /* create the input ports */ + TRACE("creating input ports\n"); + for(i = 0; i < drv->num_input_channels; i++) + { + char portname[32]; + sprintf(portname, "in_%d", i); + TRACE("port %d is named '%s'\n", i, portname); + /* NOTE: Yes, this is supposed to be JackPortIsInput since this is an input */ + /* port TO bio2jack */ + drv->input_port[i] = jack_port_register(drv->client, portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + } + +#if JACK_CLOSE_HACK + drv->in_use = TRUE; +#endif + + /* tell the JACK server that we are ready to roll */ + TRACE("calling jack_activate()\n"); + if(jack_activate(drv->client)) + { + ERR("cannot activate client\n"); + return ERR_OPENING_JACK; + } + + /* if we have output channels and the port connection mode isn't CONNECT_NONE */ + /* then we should connect up some ports */ + if((drv->num_output_channels > 0) && (port_connection_mode != CONNECT_NONE)) + { + /* determine how we are to acquire output port names */ + if((drv->jack_port_name_count == 0) || (drv->jack_port_name_count == 1)) + { + if(drv->jack_port_name_count == 0) + { + TRACE("jack_get_ports() passing in NULL/NULL\n"); + ports = jack_get_ports(drv->client, NULL, NULL, + drv->jack_output_port_flags); + } + else + { + TRACE("jack_get_ports() passing in port of '%s'\n", + drv->jack_port_name[0]); + ports = jack_get_ports(drv->client, drv->jack_port_name[0], NULL, + drv->jack_output_port_flags); + } + + /* display a trace of the output ports we found */ + int num_ports = 0; + if(ports) + { + for(i = 0; ports[i]; i++) + { + TRACE("ports[%d] = '%s'\n", i, ports[i]); + num_ports++; + } + } + + /* ensure that we found enough ports */ + if(!ports || (i < drv->num_output_channels)) + { + TRACE("ERR: jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", + drv->jack_output_port_flags); +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + return ERR_PORT_NOT_FOUND; + } + + /* connect a port for each output channel. Note: you can't do this before + the client is activated (this may change in the future). */ + for(i = 0; i < drv->num_output_channels; i++) + { + TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[i]); + if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[i])) + { + ERR("cannot connect to output port %d('%s')\n", i, ports[i]); + failed = 1; + } + } + + /* only if we are in CONNECT_ALL mode should we keep connecting ports up beyond */ + /* the minimum number of ports required for each output channel coming into bio2jack */ + if(port_connection_mode == CONNECT_ALL) + { + /* It's much cheaper and easier to let JACK do the processing required to + connect 2 channels to 4 or 4 channels to 2 or any other combinations. + This effectively eliminates the need for sample_move_d16_d16() */ + if(drv->num_output_channels < num_ports) + { + for(i = drv->num_output_channels; ports[i]; i++) + { + int n = i % drv->num_output_channels; + TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[n]); + if(jack_connect(drv->client, jack_port_name(drv->output_port[n]), ports[i])) + { + // non fatal + ERR("cannot connect to output port %d('%s')\n", n, ports[i]); + } + } + } + else if(drv->num_output_channels > num_ports) + { + for(i = num_ports; i < drv->num_output_channels; i++) + { + int n = i % num_ports; + TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[n]); + if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[n])) + { + // non fatal + ERR("cannot connect to output port %d('%s')\n", i, ports[n]); + } + } + } + } + + free(ports); /* free the returned array of ports */ + } + else + { + for(i = 0; i < drv->jack_port_name_count; i++) + { + TRACE("jack_get_ports() portname %d of '%s\n", i, + drv->jack_port_name[i]); + ports = jack_get_ports(drv->client, drv->jack_port_name[i], NULL, + drv->jack_output_port_flags); + + if(!ports) + { + ERR("jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", + drv->jack_output_port_flags); + return ERR_PORT_NOT_FOUND; + } + + TRACE("ports[%d] = '%s'\n", 0, ports[0]); /* display a trace of the output port we found */ + + /* connect the port */ + TRACE("jack_connect() to port %d('%p')\n", i, drv->output_port[i]); + if(jack_connect(drv->client, jack_port_name(drv->output_port[i]), ports[0])) + { + ERR("cannot connect to output port %d('%s')\n", 0, ports[0]); + failed = 1; + } + free(ports); /* free the returned array of ports */ + } + } + } /* if( drv->num_output_channels > 0 ) */ + + + if(drv->num_input_channels > 0) + { + /* determine how we are to acquire input port names */ + if((drv->jack_port_name_count == 0) || (drv->jack_port_name_count == 1)) + { + if(drv->jack_port_name_count == 0) + { + TRACE("jack_get_ports() passing in NULL/NULL\n"); + ports = jack_get_ports(drv->client, NULL, NULL, drv->jack_input_port_flags); + } + else + { + TRACE("jack_get_ports() passing in port of '%s'\n", + drv->jack_port_name[0]); + ports = jack_get_ports(drv->client, drv->jack_port_name[0], NULL, + drv->jack_input_port_flags); + } + + /* display a trace of the input ports we found */ + int num_ports = 0; + if(ports) + { + for(i = 0; ports[i]; i++) + { + TRACE("ports[%d] = '%s'\n", i, ports[i]); + num_ports++; + } + } + + /* ensure that we found enough ports */ + if(!ports || (i < drv->num_input_channels)) + { + TRACE("ERR: jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", + drv->jack_input_port_flags); +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + return ERR_PORT_NOT_FOUND; + } + + /* connect the ports. Note: you can't do this before + the client is activated (this may change in the future). */ + for(i = 0; i < drv->num_input_channels; i++) + { + TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[i]); + if(jack_connect(drv->client, ports[i], jack_port_name(drv->input_port[i]))) + { + ERR("cannot connect to input port %d('%s')\n", i, ports[i]); + failed = 1; + } + } + + /* It's much cheaper and easier to let JACK do the processing required to + connect 2 channels to 4 or 4 channels to 2 or any other combinations. + This effectively eliminates the need for sample_move_d16_d16() */ + if(drv->num_input_channels < num_ports) + { + for(i = drv->num_input_channels; ports[i]; i++) + { + int n = i % drv->num_input_channels; + TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[n]); + if(jack_connect(drv->client, ports[i], jack_port_name(drv->input_port[n]))) + { + // non fatal + ERR("cannot connect to input port %d('%s')\n", n, ports[i]); + } + } + } + else if(drv->num_input_channels > num_ports) + { + for(i = num_ports; i < drv->num_input_channels; i++) + { + int n = i % num_ports; + TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[n]); + if(jack_connect(drv->client, ports[n], jack_port_name(drv->input_port[i]))) + { + // non fatal + ERR("cannot connect to input port %d('%s')\n", i, ports[n]); + } + } + } + + free(ports); /* free the returned array of ports */ + } + else + { + for(i = 0; i < drv->jack_port_name_count; i++) + { + TRACE("jack_get_ports() portname %d of '%s\n", i, + drv->jack_port_name[i]); + ports = jack_get_ports(drv->client, drv->jack_port_name[i], NULL, + drv->jack_input_port_flags); + + if(!ports) + { + ERR("jack_get_ports() failed to find ports with jack port flags of 0x%lX'\n", + drv->jack_input_port_flags); + return ERR_PORT_NOT_FOUND; + } + + TRACE("ports[%d] = '%s'\n", 0, ports[0]); /* display a trace of the input port we found */ + + /* connect the port */ + TRACE("jack_connect() to port %d('%p')\n", i, drv->input_port[i]); + if(jack_connect(drv->client, jack_port_name(drv->input_port[i]), ports[0])) + { + ERR("cannot connect to input port %d('%s')\n", 0, ports[0]); + failed = 1; + } + free(ports); /* free the returned array of ports */ + } + } + } /* if( drv->num_input_channels > 0 ) */ + + /* if something failed we need to shut the client down and return 0 */ + if(failed) + { + TRACE("failed, closing and returning error\n"); +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + return ERR_OPENING_JACK; + } + + TRACE("success\n"); + + drv->jackd_died = FALSE; /* clear out this flag so we don't keep attempting to restart things */ + drv->state = PLAYING; /* clients seem to behave much better with this on from the start, especially when recording */ + + return ERR_SUCCESS; /* return success */ +} + + +/****************************************************************** + * JACK_CloseDevice + * + * Close the connection to the server cleanly. + * If close_client is TRUE we close the client for this device instead of + * just marking the device as in_use(JACK_CLOSE_HACK only) + */ +#if JACK_CLOSE_HACK +static void +JACK_CloseDevice(jack_driver_t * drv, bool close_client) +#else +static void +JACK_CloseDevice(jack_driver_t * drv) +#endif +{ + unsigned int i; + +#if JACK_CLOSE_HACK + if(close_client) + { +#endif + + TRACE("closing the jack client thread\n"); + if(drv->client) + { + TRACE("after jack_deactivate()\n"); + int errorCode = jack_client_close(drv->client); + if(errorCode) + ERR("jack_client_close() failed returning an error code of %d\n", + errorCode); + } + + /* reset client */ + drv->client = 0; + + /* free up the port strings */ + TRACE("freeing up %d port strings\n", drv->jack_port_name_count); + if(drv->jack_port_name_count > 1) + { + for(i = 0; i < drv->jack_port_name_count; i++) + free(drv->jack_port_name[i]); + free(drv->jack_port_name); + } + JACK_CleanupDriver(drv); + + JACK_ResetFromDriver(drv); + +#if JACK_CLOSE_HACK + } else + { + TRACE("setting in_use to FALSE\n"); + drv->in_use = FALSE; + + if(!drv->client) + { + TRACE("critical error, closing a device that has no client\n"); + } + } +#endif +} + + + + +/**************************************/ +/* External interface functions below */ +/**************************************/ + +/* Clear out any buffered data, stop playing, zero out some variables */ +static void +JACK_ResetFromDriver(jack_driver_t * drv) +{ + TRACE("resetting drv->deviceID(%d)\n", drv->deviceID); + + /* NOTE: we use the RESET state so we don't need to worry about clearing out */ + /* variables that the callback modifies while the callback is running */ + /* we set the state to RESET and the callback clears the variables out for us */ + drv->state = RESET; /* tell the callback that we are to reset, the callback will transition this to STOPPED */ +} + +/* Clear out any buffered data, stop playing, zero out some variables */ +void +JACK_Reset(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + TRACE("resetting deviceID(%d)\n", deviceID); + JACK_ResetFromDriver(drv); + releaseDriver(drv); +} + + +/* + * open the audio device for writing to + * + * deviceID is set to the opened device + * if client is non-zero and in_use is FALSE then just set in_use to TRUE + * + * return value is zero upon success, non-zero upon failure + * + * if ERR_RATE_MISMATCH (*rate) will be updated with the jack servers rate + */ +int +JACK_Open(int *deviceID, unsigned int bits_per_channel, unsigned long *rate, + int channels) +{ + /* we call through to JACK_OpenEx(), but default the input channels to 0 for better backwards + compatibility with clients written before recording was available */ + return JACK_OpenEx(deviceID, bits_per_channel, + rate, + 0, channels, + NULL, 0, JackPortIsPhysical); +} + +/* + * see JACK_Open() for comments + * NOTE: jack_port_name has three ways of being used: + * - NULL - finds all ports with the given flags + * - A single regex string used to retrieve all port names + * - A series of port names, one for each output channel + * + * we set *deviceID + */ +int +JACK_OpenEx(int *deviceID, unsigned int bits_per_channel, + unsigned long *rate, + unsigned int input_channels, unsigned int output_channels, + const char **jack_port_name, + unsigned int jack_port_name_count, unsigned long jack_port_flags) +{ + jack_driver_t *drv = 0; + unsigned int i; + int retval; + + if(input_channels < 1 && output_channels < 1) + { + ERR("no input OR output channels, nothing to do\n"); + return ERR_OPENING_JACK; + } + + switch (bits_per_channel) + { + case 8: + case 16: + break; + default: + ERR("invalid bits_per_channel\n"); + return ERR_OPENING_JACK; + } + + /* Lock the device_mutex and find one that's not allocated already. + We'll keep this lock until we've either made use of it, or given up. */ + pthread_mutex_lock(&device_mutex); + + for(i = 0; i < MAX_OUTDEVICES; i++) + { + if(!outDev[i].allocated) + { + drv = &outDev[i]; + break; + } + } + + if(!drv) + { + ERR("no more devices available\n"); + return ERR_OPENING_JACK; + } + + /* We found an unallocated device, now lock it for extra saftey */ + getDriver(drv->deviceID); + + TRACE("bits_per_channel=%d rate=%ld, input_channels=%d, output_channels=%d\n", + bits_per_channel, *rate, input_channels, output_channels); + + if(output_channels > MAX_OUTPUT_PORTS) + { + ERR("output_channels == %d, MAX_OUTPUT_PORTS == %d\n", output_channels, + MAX_OUTPUT_PORTS); + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return ERR_TOO_MANY_OUTPUT_CHANNELS; + } + + if(input_channels > MAX_INPUT_PORTS) + { + ERR("input_channels == %d, MAX_INPUT_PORTS == %d\n", input_channels, + MAX_INPUT_PORTS); + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return ERR_TOO_MANY_INPUT_CHANNELS; + } + + drv->jack_output_port_flags = jack_port_flags | JackPortIsInput; /* port must be input(ie we can put data into it), so mask this in */ + drv->jack_input_port_flags = jack_port_flags | JackPortIsOutput; /* port must be output(ie we can get data from it), so mask this in */ + + /* check that we have the correct number of port names + FIXME?: not sure how we should handle output ports vs input ports.... + */ + if((jack_port_name_count > 1) + && ((jack_port_name_count < output_channels) + || (jack_port_name_count < input_channels))) + { + ERR("specified individual port names but not enough, gave %d names, need %d\n", + jack_port_name_count, output_channels); + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH; + } else + { + /* copy this data into the device information */ + drv->jack_port_name_count = jack_port_name_count; + + if(drv->jack_port_name_count != 0) + { + drv->jack_port_name = + (char **) malloc(sizeof(char *) * drv->jack_port_name_count); + for(i = 0; i < drv->jack_port_name_count; i++) + { + drv->jack_port_name[i] = strdup(jack_port_name[i]); + TRACE("jack_port_name[%d] == '%s'\n", i, jack_port_name[i]); + } + } else + { + drv->jack_port_name = NULL; + TRACE("jack_port_name = NULL\n"); + } + } + + /* initialize some variables */ + drv->in_use = FALSE; + + JACK_ResetFromDriver(drv); /* flushes all queued buffers, sets status to STOPPED and resets some variables */ + + /* drv->jack_sample_rate is set by JACK_OpenDevice() */ + drv->client_sample_rate = *rate; + drv->bits_per_channel = bits_per_channel; + drv->num_input_channels = input_channels; + drv->num_output_channels = output_channels; + drv->bytes_per_input_frame = (drv->bits_per_channel * drv->num_input_channels) / 8; + drv->bytes_per_output_frame = (drv->bits_per_channel * drv->num_output_channels) / 8; + drv->bytes_per_jack_output_frame = sizeof(sample_t) * drv->num_output_channels; + drv->bytes_per_jack_input_frame = sizeof(sample_t) * drv->num_input_channels; + + if(drv->num_output_channels > 0) + { + drv->pPlayPtr = jack_ringbuffer_create(drv->num_output_channels * + drv->bytes_per_jack_output_frame * + DEFAULT_RB_SIZE); + } + + if(drv->num_input_channels > 0) + { + drv->pRecPtr = jack_ringbuffer_create(drv->num_input_channels * + drv->bytes_per_jack_input_frame * + DEFAULT_RB_SIZE); + } + + DEBUG("bytes_per_output_frame == %ld\n", drv->bytes_per_output_frame); + DEBUG("bytes_per_input_frame == %ld\n", drv->bytes_per_input_frame); + DEBUG("bytes_per_jack_output_frame == %ld\n", + drv->bytes_per_jack_output_frame); + DEBUG("bytes_per_jack_input_frame == %ld\n", + drv->bytes_per_jack_input_frame); + + /* go and open up the device */ + retval = JACK_OpenDevice(drv); + if(retval != ERR_SUCCESS) + { + TRACE("error opening jack device\n"); + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return retval; + } + else + { + TRACE("succeeded opening jack device\n"); + } + + /* setup SRC objects just in case they'll be needed but only if requested */ + if(do_sample_rate_conversion) + { + int error; + if(drv->num_output_channels > 0) + { + drv->output_src = src_new(preferred_src_converter, drv->num_output_channels, &error); + if(error != 0) + { + src_delete(drv->output_src); + drv->output_src = 0; + ERR("Could not created SRC object for output stream %d: %s\n", + error, src_strerror(error)); + } + } + if(drv->num_input_channels > 0) + { + drv->input_src = src_new(preferred_src_converter, drv->num_input_channels, &error); + if(error != 0) + { + src_delete(drv->input_src); + drv->input_src = 0; + ERR("Could not created SRC object for input stream %d: %s\n", + error, src_strerror(error)); + } + } + } + else if((long) (*rate) != drv->jack_sample_rate) + { + TRACE("rate of %ld doesn't match jack sample rate of %ld, returning error\n", + *rate, drv->jack_sample_rate); + *rate = drv->jack_sample_rate; +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return ERR_RATE_MISMATCH; + } + + drv->allocated = TRUE; /* record that we opened this device */ + + DEBUG("sizeof(sample_t) == %d\n", sizeof(sample_t)); + + int periodSize = jack_get_buffer_size(drv->client); + int periods = 0; + /* FIXME: maybe we should keep different latency values for input vs output? */ + if(drv->num_output_channels > 0) + { + periods = jack_port_get_total_latency(drv->client, + drv->output_port[0]) / periodSize; + drv->latencyMS = periodSize * periods * 1000 / (drv->jack_sample_rate * + (drv->bits_per_channel / 8 * + drv->num_output_channels)); + } + else if(drv->num_input_channels > 0) + { + periods = jack_port_get_total_latency(drv->client, + drv->input_port[0]) / periodSize; + drv->latencyMS = + periodSize * periods * 1000 / (drv->jack_sample_rate * + (drv->bits_per_channel / 8 * + drv->num_input_channels)); + } + + TRACE("drv->latencyMS == %ldms\n", drv->latencyMS); + + *deviceID = drv->deviceID; /* set the deviceID for the caller */ + releaseDriver(drv); + pthread_mutex_unlock(&device_mutex); + return ERR_SUCCESS; /* success */ +} + +/* Close the jack device */ +//FIXME: add error handling in here at some point... +/* NOTE: return 0 for success, non-zero for failure */ +int +JACK_Close(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + + TRACE("deviceID(%d)\n", deviceID); + +#if JACK_CLOSE_HACK + JACK_CloseDevice(drv, TRUE); +#else + JACK_CloseDevice(drv); +#endif + + JACK_ResetFromDriver(drv); /* reset this device to a normal starting state */ + + pthread_mutex_lock(&device_mutex); + + /* free buffer memory */ + drv->callback_buffer1_size = 0; + if(drv->callback_buffer1) free(drv->callback_buffer1); + drv->callback_buffer1 = 0; + + drv->callback_buffer2_size = 0; + if(drv->callback_buffer2) free(drv->callback_buffer2); + drv->callback_buffer2 = 0; + + drv->rw_buffer1_size = 0; + if(drv->rw_buffer1) free(drv->rw_buffer1); + drv->rw_buffer1 = 0; + + if(drv->pPlayPtr) jack_ringbuffer_free(drv->pPlayPtr); + drv->pPlayPtr = 0; + + if(drv->pRecPtr) jack_ringbuffer_free(drv->pRecPtr); + drv->pRecPtr = 0; + + /* free the SRC objects */ + if(drv->output_src) src_delete(drv->output_src); + drv->output_src = 0; + + if(drv->input_src) src_delete(drv->input_src); + drv->input_src = 0; + + drv->allocated = FALSE; /* release this device */ + + pthread_mutex_unlock(&device_mutex); + + releaseDriver(drv); + + return 0; +} + +/* If we haven't already taken in the max allowed data then create a wave header */ +/* to package the audio data and attach the wave header to the end of the */ +/* linked list of wave headers */ +/* These wave headers will be peeled off as they are played by the callback routine */ +/* Return value is the number of bytes written */ +/* NOTE: this function takes the length of data to be written bytes */ +long +JACK_Write(int deviceID, unsigned char *data, unsigned long bytes) +{ + jack_driver_t *drv = getDriver(deviceID); + + long frames_free, frames; + + TIMER("start\n"); + + TRACE("deviceID(%d), bytes == %ld\n", deviceID, bytes); + + /* check and see that we have enough space for this audio */ + frames_free = + jack_ringbuffer_write_space(drv->pPlayPtr) / + drv->bytes_per_jack_output_frame; + frames = bytes / drv->bytes_per_output_frame; + TRACE("frames free == %ld, bytes = %lu\n", frames_free, bytes); + + TRACE("state = '%s'\n", DEBUGSTATE(drv->state)); + /* if we are currently STOPPED we should start playing now... + do this before the check for bytes == 0 since some clients like + to write 0 bytes the first time out */ + if(drv->state == STOPPED) + { + TRACE("currently STOPPED, transitioning to PLAYING\n"); + drv->state = PLAYING; + } + + /* handle the case where the user calls this routine with 0 bytes */ + if(bytes == 0 || frames_free < 1) + { + TRACE("no room left\n"); + TIMER("finish (nothing to do, buffer is full)\n"); + releaseDriver(drv); + return 0; /* indicate that we couldn't write any bytes */ + } + + frames = min(frames, frames_free); + long jack_bytes = frames * drv->bytes_per_jack_output_frame; + if(!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes)) + { + ERR("couldn't allocate enough space for the buffer\n"); + releaseDriver(drv); + return 0; + } + /* adjust bytes to be how many client bytes we're actually writing */ + bytes = frames * drv->bytes_per_output_frame; + + /* convert from client samples to jack samples + we have to tell it how many samples there are, which is frames * channels */ + switch (drv->bits_per_channel) + { + case 8: + sample_move_char_float((sample_t *) drv->rw_buffer1, (char *) data, + frames * drv->num_output_channels); + case 16: + sample_move_short_float((sample_t *) drv->rw_buffer1, (short *) data, + frames * drv->num_output_channels); + break; + } + + int i; + for(i = 0; i < drv->num_output_channels; i++) + { + /* apply volume to the floating value */ + if(drv->volumeEffectType == dbAttenuation) + { + /* assume the volume setting is dB of attenuation, a volume of 0 */ + /* is 0dB attenuation */ + float volume = powf(10.0, -((float) drv->volume[i]) / 20.0); + float_volume_effect((sample_t *) drv->rw_buffer1 + i, + frames, volume, drv->num_output_channels); + } else + { + float_volume_effect((sample_t *) drv->rw_buffer1 + i, frames, + ((float) drv->volume[i] / 100.0), + drv->num_output_channels); + } + } + + DEBUG("ringbuffer read space = %d, write space = %d\n", + jack_ringbuffer_read_space(drv->pPlayPtr), + jack_ringbuffer_write_space(drv->pPlayPtr)); + + jack_ringbuffer_write(drv->pPlayPtr, drv->rw_buffer1, jack_bytes); + DEBUG("wrote %lu bytes, %lu jack_bytes\n", bytes, jack_bytes); + + DEBUG("ringbuffer read space = %d, write space = %d\n", + jack_ringbuffer_read_space(drv->pPlayPtr), + jack_ringbuffer_write_space(drv->pPlayPtr)); + + drv->client_bytes += bytes; /* update client_bytes */ + + TIMER("finish\n"); + + DEBUG("returning bytes written of %ld\n", bytes); + + releaseDriver(drv); + return bytes; /* return the number of bytes we wrote out */ +} + +long +JACK_Read(int deviceID, unsigned char *data, unsigned long bytes) +{ + jack_driver_t *drv = getDriver(deviceID); + + long frames_available, frames; + + TIMER("start\n"); + + TRACE("deviceID(%d), bytes == %ld\n", deviceID, bytes); + + /* find out if there's any room to write this data */ + frames_available = + jack_ringbuffer_read_space(drv->pRecPtr) / + drv->bytes_per_jack_input_frame; + frames = bytes / drv->bytes_per_input_frame; + DEBUG("frames available = %ld, bytes = %lu\n", frames_available, bytes); + + TRACE("state = '%s'\n", DEBUGSTATE(drv->state)); + /* if we are currently STOPPED we should start recording now... */ + if(drv->state == STOPPED) + { + TRACE("currently STOPPED, transitioning to PLAYING\n"); + drv->state = PLAYING; + } + + /* handle the case where the user calls this routine with 0 bytes */ + if(bytes == 0 || frames_available < 1) + { + TRACE("no bytes in buffer\n"); + + TIMER("finish (nothing to do)\n"); + releaseDriver(drv); + return 0; + } + + frames = min(frames, frames_available); + long jack_bytes = frames * drv->bytes_per_jack_input_frame; + if(!ensure_buffer_size(&drv->rw_buffer1, &drv->rw_buffer1_size, jack_bytes)) + { + ERR("couldn't allocate enough space for the buffer\n"); + releaseDriver(drv); + return 0; + } + + DEBUG("ringbuffer read space = %d, write space = %d\n", + jack_ringbuffer_read_space(drv->pRecPtr), + jack_ringbuffer_write_space(drv->pRecPtr)); + + jack_ringbuffer_read(drv->pRecPtr, drv->rw_buffer1, + frames * drv->bytes_per_jack_input_frame); + + DEBUG("ringbuffer read space = %d, write space = %d\n", + jack_ringbuffer_read_space(drv->pRecPtr), + jack_ringbuffer_write_space(drv->pRecPtr)); + + int i; + for(i = 0; i < drv->num_output_channels; i++) + { + /* apply volume to the floating value */ + if(drv->volumeEffectType == dbAttenuation) + { + /* assume the volume setting is dB of attenuation, a volume of 0 */ + /* is 0dB attenuation */ + float volume = powf(10.0, -((float) drv->volume[i]) / 20.0); + float_volume_effect((sample_t *) drv->rw_buffer1 + i, + frames, volume, drv->num_output_channels); + } else + { + float_volume_effect((sample_t *) drv->rw_buffer1 + i, frames, + ((float) drv->volume[i] / 100.0), + drv->num_output_channels); + } + } + + /* convert from jack samples to client samples + we have to tell it how many samples there are, which is frames * channels */ + switch (drv->bits_per_channel) + { + case 8: + sample_move_float_char((char *) data, (sample_t *) drv->rw_buffer1, + frames * drv->num_input_channels); + break; + case 16: + sample_move_float_short((short *) data, (sample_t *) drv->rw_buffer1, + frames * drv->num_input_channels); + break; + } + + TIMER("finish\n"); + + long read_bytes = frames * drv->bytes_per_input_frame; + + DEBUG("returning bytes read of %ld\n", bytes); + + releaseDriver(drv); + return read_bytes; +} + +/* return ERR_SUCCESS for success */ +static int +JACK_SetVolumeForChannelFromDriver(jack_driver_t * drv, + unsigned int channel, unsigned int volume) +{ + /* TODO?: maybe we should have different volume levels for input & output */ + /* ensure that we have the channel we are setting volume for */ + if(channel > (drv->num_output_channels - 1)) + return 1; + + if(volume > 100) + volume = 100; /* check for values in excess of max */ + + drv->volume[channel] = volume; + return ERR_SUCCESS; +} + +/* return ERR_SUCCESS for success */ +int +JACK_SetVolumeForChannel(int deviceID, unsigned int channel, + unsigned int volume) +{ + jack_driver_t *drv = getDriver(deviceID); + int retval = JACK_SetVolumeForChannelFromDriver(drv, channel, volume); + releaseDriver(drv); + return retval; +} + +/* Set the volume */ +/* return 0 for success */ +/* NOTE: we check for invalid volume values */ +int +JACK_SetAllVolume(int deviceID, unsigned int volume) +{ + jack_driver_t *drv = getDriver(deviceID); + unsigned int i; + + TRACE("deviceID(%d), setting volume of %d\n", deviceID, volume); + + for(i = 0; i < drv->num_output_channels; i++) + { + if(JACK_SetVolumeForChannelFromDriver(drv, i, volume) != ERR_SUCCESS) + { + releaseDriver(drv); + return 1; + } + } + + releaseDriver(drv); + return ERR_SUCCESS; +} + +/* Return the current volume in the inputted pointers */ +/* NOTE: we check for null pointers being passed in just in case */ +void +JACK_GetVolumeForChannel(int deviceID, unsigned int channel, + unsigned int *volume) +{ + jack_driver_t *drv = getDriver(deviceID); + + /* ensure that we have the channel we are getting volume for */ + if(channel > (drv->num_output_channels - 1)) + { + ERR("asking for channel index %d but we only have %ld channels\n", channel, drv->num_output_channels); + releaseDriver(drv); + return; + } + + if(volume) + *volume = drv->volume[channel]; + +#if VERBOSE_OUTPUT + if(volume) + { + TRACE("deviceID(%d), returning volume of %d for channel %d\n", + deviceID, *volume, channel); + } + else + { + TRACE("volume is null, can't dereference it\n"); + } +#endif + + releaseDriver(drv); +} + + +/* linear means 0 volume is silence, 100 is full volume */ +/* dbAttenuation means 0 volume is 0dB attenuation */ +/* Bio2jack defaults to linear */ +enum JACK_VOLUME_TYPE +JACK_SetVolumeEffectType(int deviceID, enum JACK_VOLUME_TYPE type) +{ + enum JACK_VOLUME_TYPE retval; + jack_driver_t *drv = getDriver(deviceID); + + TRACE("setting type of '%s'\n", + (type == dbAttenuation ? "dbAttenuation" : "linear")); + + retval = drv->volumeEffectType; + drv->volumeEffectType = type; + + releaseDriver(drv); + return retval; +} + + +/* Controls the state of the playback(playing, paused, ...) */ +int +JACK_SetState(int deviceID, enum status_enum state) +{ + jack_driver_t *drv = getDriver(deviceID); + + switch (state) + { + case PAUSED: + drv->state = PAUSED; + break; + case PLAYING: + drv->state = PLAYING; + break; + case STOPPED: + drv->state = STOPPED; + break; + default: + TRACE("unknown state of %d\n", state); + } + + TRACE("%s\n", DEBUGSTATE(drv->state)); + + releaseDriver(drv); + return 0; +} + +/* Retrieve the current state of the device */ +enum status_enum +JACK_GetState(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + enum status_enum return_val; + + return_val = drv->state; + releaseDriver(drv); + + TRACE("deviceID(%d), returning current state of %s\n", deviceID, + DEBUGSTATE(return_val)); + return return_val; +} + +/* Retrieve the number of bytes per second we are outputting */ +unsigned long +JACK_GetOutputBytesPerSecondFromDriver(jack_driver_t * drv) +{ + unsigned long return_val; + + return_val = drv->bytes_per_output_frame * drv->client_sample_rate; + +#if VERBOSE_OUTPUT + TRACE("deviceID(%d), return_val = %ld\n", drv->deviceID, return_val); +#endif + + return return_val; +} + +/* Retrieve the number of bytes per second we are outputting */ +unsigned long +JACK_GetOutputBytesPerSecond(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + unsigned long return_val; + + return_val = JACK_GetOutputBytesPerSecondFromDriver(drv); + releaseDriver(drv); + + return return_val; +} + +/* Retrieve the number of input bytes(from jack) per second we are outputting + to the user of bio2jack */ +static long +JACK_GetInputBytesPerSecondFromDriver(jack_driver_t * drv) +{ + long return_val; + + return_val = drv->bytes_per_input_frame * drv->client_sample_rate; +#if VERBOSE_OUTPUT + TRACE("drv->deviceID(%d), return_val = %ld\n", drv->deviceID, return_val); +#endif + + return return_val; +} + +/* Retrieve the number of input bytes(from jack) per second we are outputting + to the user of bio2jack */ +unsigned long +JACK_GetInputBytesPerSecond(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val = JACK_GetInputBytesPerSecondFromDriver(drv); + releaseDriver(drv); + +#if VERBOSE_OUTPUT + TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val); +#endif + + return return_val; +} + +/* Return the number of bytes we have buffered thus far for output */ +/* NOTE: convert from output bytes to input bytes in here */ +static long +JACK_GetBytesStoredFromDriver(jack_driver_t * drv) +{ + if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) + return 0; + + /* leave at least one frame in the buffer at all times to prevent underruns */ + long return_val = + jack_ringbuffer_read_space(drv->pPlayPtr) - drv->jack_buffer_size; + if(return_val <= 0) + { + return_val = 0; + } else + { + /* adjust from jack bytes to client bytes */ + return_val = + return_val / drv->bytes_per_jack_output_frame * + drv->bytes_per_output_frame; + } + + return return_val; +} + +/* An approximation of how many bytes we have to send out to jack */ +/* that is computed as if we were sending jack a continuous stream of */ +/* bytes rather than chunks during discrete callbacks. */ +/* Return the number of bytes we have buffered thus far for output */ +/* NOTE: convert from output bytes to input bytes in here */ +unsigned long +JACK_GetBytesStored(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long retval = JACK_GetBytesStoredFromDriver(drv); + releaseDriver(drv); + TRACE("deviceID(%d), retval = %ld\n", deviceID, retval); + return retval; +} + +static unsigned long +JACK_GetBytesFreeSpaceFromDriver(jack_driver_t * drv) +{ + if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) + return 0; + + /* leave at least one frame in the buffer at all times to prevent underruns */ + long return_val = jack_ringbuffer_write_space(drv->pPlayPtr) - drv->jack_buffer_size; + if(return_val <= 0) + { + return_val = 0; + } else + { + /* adjust from jack bytes to client bytes */ + return_val = + return_val / drv->bytes_per_jack_output_frame * + drv->bytes_per_output_frame; + } + + return return_val; +} + +/* Return the number of bytes we can write to the device */ +unsigned long +JACK_GetBytesFreeSpace(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + unsigned long return_val; + + return_val = JACK_GetBytesFreeSpaceFromDriver(drv); + releaseDriver(drv); + + if(return_val < 0) return_val = 0; + + TRACE("deviceID(%d), retval == %ld\n", deviceID, return_val); + + return return_val; +} + +/* bytes of space used in the input buffer */ +unsigned long +JACK_GetBytesUsedSpace(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val; + + if(drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0) + { + return_val = 0; + } else + { + /* adjust from jack bytes to client bytes */ + return_val = + jack_ringbuffer_read_space(drv->pRecPtr) / + drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame; + } + + releaseDriver(drv); + + if(return_val < 0) + return_val = 0; + TRACE("deviceID(%d), retval == %ld\n", deviceID, return_val); + + return return_val; +} + +/* Get the current position of the driver, either in bytes or */ +/* in milliseconds */ +/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to + input vs. output channel count */ +static long +JACK_GetPositionFromDriver(jack_driver_t * drv, enum pos_enum position, + int type) +{ + long return_val = 0; + struct timeval now; + long elapsedMS; + double sec2msFactor = 1000; + + char *type_str = "UNKNOWN type"; + + /* if we are reset we should return a position of 0 */ + if(drv->state == RESET) + { + TRACE("we are currently RESET, returning 0\n"); + return 0; + } + + if(type == WRITTEN) + { + type_str = "WRITTEN"; + return_val = drv->client_bytes; + } else if(type == WRITTEN_TO_JACK) + { + type_str = "WRITTEN_TO_JACK"; + return_val = drv->written_client_bytes; + } else if(type == PLAYED) /* account for the elapsed time for the played_bytes */ + { + type_str = "PLAYED"; + return_val = drv->played_client_bytes; + gettimeofday(&now, 0); + + elapsedMS = TimeValDifference(&drv->previousTime, &now); /* find the elapsed milliseconds since last JACK_Callback() */ + + TRACE("elapsedMS since last callback is '%ld'\n", elapsedMS); + + /* account for the bytes played since the last JACK_Callback() */ + /* NOTE: [Xms * (Bytes/Sec)] * (1 sec/1,000ms) */ + /* NOTE: don't do any compensation if no data has been sent to jack since the last callback */ + /* as this would result a bogus computed result */ + if(drv->clientBytesInJack != 0) + { + return_val += (long) ((double) elapsedMS * + ((double) JACK_GetOutputBytesPerSecondFromDriver(drv) / + sec2msFactor)); + } else + { + TRACE("clientBytesInJack == 0\n"); + } + } + + /* add on the offset */ + return_val += drv->position_byte_offset; + + /* convert byte position to milliseconds value if necessary */ + if(position == MILLISECONDS) + { + if(JACK_GetOutputBytesPerSecondFromDriver(drv) != 0) + { + return_val = (long) (((double) return_val / + (double) JACK_GetOutputBytesPerSecondFromDriver(drv)) * + (double) sec2msFactor); + } else + { + return_val = 0; + } + } + + TRACE("drv->deviceID(%d), type(%s), return_val = %ld\n", drv->deviceID, + type_str, return_val); + + return return_val; +} + +/* Get the current position of the driver, either in bytes or */ +/* in milliseconds */ +/* NOTE: this is position relative to input bytes, output bytes may differ greatly due to input vs. output channel count */ +long +JACK_GetPosition(int deviceID, enum pos_enum position, int type) +{ + jack_driver_t *drv = getDriver(deviceID); + long retval = JACK_GetPositionFromDriver(drv, position, type); + releaseDriver(drv); + TRACE("retval == %ld\n", retval); + return retval; +} + +// Set position always applies to written bytes +// NOTE: we must apply this instantly because if we pass this as a message +// to the callback we risk the user sending us audio data in the mean time +// and there is no need to send this as a message, we don't modify any +// internal variables +void +JACK_SetPositionFromDriver(jack_driver_t * drv, enum pos_enum position, + long value) +{ + double sec2msFactor = 1000; +#if TRACE_ENABLE + long input_value = value; +#endif + + /* convert the incoming value from milliseconds into bytes */ + if(position == MILLISECONDS) + { + value = (long) (((double) value * + (double) JACK_GetOutputBytesPerSecondFromDriver(drv)) / + sec2msFactor); + } + + /* ensure that if the user asks for the position */ + /* they will at this instant get the correct position */ + drv->position_byte_offset = value - drv->client_bytes; + + TRACE("deviceID(%d) input_value of %ld %s, new value of %ld, setting position_byte_offset to %ld\n", + drv->deviceID, input_value, (position == MILLISECONDS) ? "ms" : "bytes", + value, drv->position_byte_offset); +} + +// Set position always applies to written bytes +// NOTE: we must apply this instantly because if we pass this as a message +// to the callback we risk the user sending us audio data in the mean time +// and there is no need to send this as a message, we don't modify any +// internal variables +void +JACK_SetPosition(int deviceID, enum pos_enum position, long value) +{ + jack_driver_t *drv = getDriver(deviceID); + JACK_SetPositionFromDriver(drv, position, value); + releaseDriver(drv); + + TRACE("deviceID(%d) value of %ld\n", drv->deviceID, value); +} + +/* Return the number of bytes per frame, or (output_channels * bits_per_channel) / 8 */ +unsigned long +JACK_GetBytesPerOutputFrame(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val = drv->bytes_per_output_frame; + releaseDriver(drv); + TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val); + return return_val; +} + +/* Return the number of bytes per frame, or (input_channels * bits_per_channel) / 8 */ +unsigned long +JACK_GetBytesPerInputFrame(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val = drv->bytes_per_input_frame; + releaseDriver(drv); + TRACE("deviceID(%d), return_val = %ld\n", deviceID, return_val); + return return_val; +} + +/* Return the number of output bytes we buffer max */ +long +JACK_GetMaxOutputBufferedBytes(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val; + + if(drv->pPlayPtr == 0 || drv->bytes_per_jack_output_frame == 0) return_val = 0; + + /* adjust from jack bytes to client bytes */ + return_val = + (jack_ringbuffer_read_space(drv->pPlayPtr) + + jack_ringbuffer_write_space(drv->pPlayPtr)) / + drv->bytes_per_jack_output_frame * drv->bytes_per_output_frame; + + releaseDriver(drv); + + TRACE("return_val = %ld\n", return_val); + + return return_val; +} + +/* Return the number of input bytes we buffer max */ +long +JACK_GetMaxInputBufferedBytes(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val; + + if(drv->pRecPtr == 0 || drv->bytes_per_jack_input_frame == 0) return_val = 0; + + /* adjust from jack bytes to client bytes */ + return_val = + (jack_ringbuffer_read_space(drv->pRecPtr) + + jack_ringbuffer_write_space(drv->pRecPtr)) / + drv->bytes_per_jack_input_frame * drv->bytes_per_input_frame; + + releaseDriver(drv); + + TRACE("return_val = %ld\n", return_val); + + return return_val; +} + +/* Get the number of output channels */ +int +JACK_GetNumOutputChannels(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + int return_val = drv->num_output_channels; + releaseDriver(drv); + TRACE("getting num_output_channels of %d\n", return_val); + return return_val; +} + +/* Get the number of input channels */ +int +JACK_GetNumInputChannels(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + int return_val = drv->num_input_channels; + releaseDriver(drv); + TRACE("getting num_input_channels of %d\n", return_val); + return return_val; +} + +/* Get the number of samples per second, the sample rate */ +long +JACK_GetSampleRate(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + int return_val = drv->client_sample_rate; + releaseDriver(drv); + TRACE("getting sample_rate of %d\n", return_val); + return return_val; +} + +void +JACK_CleanupDriver(jack_driver_t * drv) +{ + TRACE("\n"); + /* things that need to be reset both in JACK_Init & JACK_CloseDevice */ + drv->client = 0; + drv->in_use = FALSE; + drv->state = CLOSED; + drv->jack_sample_rate = 0; + drv->output_sample_rate_ratio = 1.0; + drv->input_sample_rate_ratio = 1.0; + drv->jackd_died = FALSE; + gettimeofday(&drv->previousTime, 0); /* record the current time */ + gettimeofday(&drv->last_reconnect_attempt, 0); +} + +/* Initialize the jack porting library to a clean state */ +void +JACK_Init(void) +{ + jack_driver_t *drv; + int x, y; + + if(init_done) + { + TRACE("not initing twice\n"); + return; + } + + init_done = 1; + + TRACE("\n"); + + pthread_mutex_lock(&device_mutex); + + /* initialize the device structures */ + for(x = 0; x < MAX_OUTDEVICES; x++) + { + drv = &outDev[x]; + + pthread_mutex_init(&drv->mutex, NULL); + + getDriver(x); + + memset(drv, 0, sizeof(jack_driver_t)); + drv->volumeEffectType = linear; + drv->deviceID = x; + + for(y = 0; y < MAX_OUTPUT_PORTS; y++) /* make all volume 25% as a default */ + drv->volume[y] = 25; + + JACK_CleanupDriver(drv); + JACK_ResetFromDriver(drv); + releaseDriver(drv); + } + + client_name = 0; /* initialize the name to null */ + do_sample_rate_conversion = TRUE; /* default to on */ + JACK_SetClientName("bio2jack"); + + pthread_mutex_unlock(&device_mutex); + + TRACE("finished\n"); +} + +/* Get the latency, in frames, of jack */ +long +JACK_GetJackOutputLatency(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val = 0; + + if(drv->client && drv->num_output_channels) + return_val = jack_port_get_total_latency(drv->client, drv->output_port[0]); + + TRACE("got latency of %ld frames\n", return_val); + + releaseDriver(drv); + return return_val; +} + +/* Get the latency, in frames, of jack */ +long +JACK_GetJackInputLatency(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val = 0; + + if(drv->client && drv->num_input_channels) + return_val = jack_port_get_total_latency(drv->client, drv->input_port[0]); + + TRACE("got latency of %ld frames\n", return_val); + + releaseDriver(drv); + return return_val; +} + +/* bytes that jack requests during each callback */ +unsigned long +JACK_GetJackBufferedBytes(int deviceID) +{ + jack_driver_t *drv = getDriver(deviceID); + long return_val; + + if(drv->bytes_per_jack_output_frame == 0) + { + return_val = 0; + } else + { + /* adjust from jack bytes to client bytes */ + return_val = + drv->jack_buffer_size / drv->bytes_per_jack_output_frame * + drv->bytes_per_output_frame * drv->num_output_channels; + } + + releaseDriver(drv); + return return_val; +} + +/* value = TRUE, perform sample rate conversion */ +void +JACK_DoSampleRateConversion(bool value) +{ + do_sample_rate_conversion = value; +} + +/* FIXME: put the filename of the resample library header file with the decoders in here */ +/* consider mapping them in the bio2jack.h header file since its useless to the user unless */ +/* they can figure out wtf the settings on */ +void +JACK_SetSampleRateConversionFunction(int converter) +{ + preferred_src_converter = converter; +} + +/* set the client name that will be reported to jack when we open a */ +/* connection via JACK_OpenDevice() */ +void +JACK_SetClientName(char *name) +{ + if(name) + { + if(client_name) free(client_name); + + /* jack_client_name_size() is the max length of a client name, including + the terminating null. */ + int size = strlen(name) + 1; /* take into account the terminating null */ + if(size > jack_client_name_size()) + size = jack_client_name_size(); + + client_name = malloc(size); + if(client_name) + snprintf(client_name, size, "%s", name); + else + ERR("unable to allocate %d bytes for client_name\n", size); + } +} + +void +JACK_SetPortConnectionMode(enum JACK_PORT_CONNECTION_MODE mode) +{ + port_connection_mode = mode; +}