Mercurial > audlegacy
changeset 325:ea321d1dae48 trunk
[svn] JACKd output plugin via external contractor james@develia.org.
author | nenolod |
---|---|
date | Mon, 19 Dec 2005 08:58:27 -0800 |
parents | fbafca56b6a8 |
children | 429b73ffcb18 |
files | Plugins/Output/Makefile.am Plugins/Output/jack/Makefile.am Plugins/Output/jack/bio2jack.c Plugins/Output/jack/bio2jack.h Plugins/Output/jack/configure.c Plugins/Output/jack/jack.c Plugins/Output/jack/jack.h Plugins/Output/jack/xconvert.h configure.ac m4/jack.m4 |
diffstat | 10 files changed, 3670 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/Plugins/Output/Makefile.am Mon Dec 19 08:45:45 2005 -0800 +++ b/Plugins/Output/Makefile.am Mon Dec 19 08:58:27 2005 -0800 @@ -1,2 +1,2 @@ -ALL_PLUGINS = crossfade OSS esd alsa disk_writer +ALL_PLUGINS = crossfade OSS jack esd alsa disk_writer SUBDIRS = $(OUTPUT_PLUGINS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/Makefile.am Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,14 @@ + +libjackout_la_SOURCES = jack.c configure.c bio2jack.h bio2jack.c xconvert.h + +if HAVE_JACK + +lib_LTLIBRARIES = libjackout.la + +endif + +libdir = $(plugindir)/$(OUTPUT_PLUGIN_DIR) + +AM_CFLAGS = @GTK_CFLAGS@ @JACK_CFLAGS@ -Wall @SAMPLERATE_CFLAGS@ +LIBS = @GTK_LIBS@ @JACK_LIBS@ @SAMPLERATE_LIBS@ -lpthread +libjackout_la_LDFLAGS = -module -avoid-version
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/bio2jack.c Mon Dec 19 08:58:27 2005 -0800 @@ -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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/bio2jack.h Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,145 @@ +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_JACK_OUT_H +#define _H_JACK_OUT_H + +#include <jack/jack.h> + +#ifdef __cplusplus +extern "C" { +#else +#define bool long +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define ERR_SUCCESS 0 +#define ERR_OPENING_JACK 1 +#define ERR_RATE_MISMATCH 2 +#define ERR_BYTES_PER_OUTPUT_FRAME_INVALID 3 +#define ERR_BYTES_PER_INPUT_FRAME_INVALID 4 +#define ERR_TOO_MANY_OUTPUT_CHANNELS 5 +#define ERR_PORT_NAME_OUTPUT_CHANNEL_MISMATCH 6 +#define ERR_PORT_NOT_FOUND 7 +#define ERR_TOO_MANY_INPUT_CHANNELS 8 +#define ERR_PORT_NAME_INPUT_CHANNEL_MISMATCH 9 + +enum status_enum { PLAYING, PAUSED, STOPPED, CLOSED, RESET }; +enum pos_enum { BYTES, MILLISECONDS }; + +#define PLAYED 1 /* played out of the speakers(estimated value but should be close */ +#define WRITTEN_TO_JACK 2 /* amount written out to jack */ +#define WRITTEN 3 /* amount written to the bio2jack device */ + +/**********************/ +/* External functions */ +void JACK_Init(void); /* call this before any other bio2jack calls */ +void JACK_DoSampleRateConversion(bool value); /* whether the next device that's Open()d should do + sample rate conversion if necessary */ +void JACK_SetSampleRateConversionFunction(int converter); /* which SRC converter function should be used + for the next Open()d device */ +int JACK_Open(int *deviceID, unsigned int bits_per_sample, unsigned long *rate, int channels); /* Note: defaults to 0 input channels + if you need input (record) use OpenEx + instead */ +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); +int JACK_Close(int deviceID); /* return 0 for success */ +void JACK_Reset(int deviceID); /* free all buffered data and reset several values in the device */ +long JACK_Write(int deviceID, unsigned char *data, unsigned long bytes); /* returns the number of bytes written */ +long JACK_Read(int deviceID, unsigned char *data, unsigned long bytes); /* returns the number of bytes read */ + +/* state setting values */ +/* set/get the written/played/buffered value based on a byte or millisecond input value */ +long JACK_GetPosition(int deviceID, enum pos_enum position, int type); +void JACK_SetPosition(int deviceID, enum pos_enum position, long value); + +long JACK_GetJackLatency(int deviceID); /* deprectated, you probably want JACK_GetJackOutputLatency */ +long JACK_GetJackOutputLatency(int deviceID); /* return the output latency in frames */ +long JACK_GetJackInputLatency(int deviceID); /* return the input latency in frames */ + +int JACK_SetState(int deviceID, enum status_enum state); /* playing, paused, stopped */ +enum status_enum JACK_GetState(int deviceID); + +long JACK_GetMaxOutputBufferedBytes(int deviceID); +long JACK_GetMaxInputBufferedBytes(int deviceID); + +/* bytes that jack requests during each callback */ +unsigned long JACK_GetJackBufferedBytes(int deviceID); + +/* Properties of the jack driver */ + +/* linear means 0 volume is silence, 100 is full volume */ +/* dbAttenuation means 0 volume is 0dB attenuation */ +/* Bio2jack defaults to linear */ +/* Note: volume controls only effect output channels for now */ +enum JACK_VOLUME_TYPE { linear, dbAttenuation }; +enum JACK_VOLUME_TYPE JACK_SetVolumeEffectType(int deviceID, + enum JACK_VOLUME_TYPE type); + +int JACK_SetAllVolume(int deviceID, unsigned int volume); /* returns 0 on success */ +int JACK_SetVolumeForChannel(int deviceID, unsigned int channel, unsigned int volume); +void JACK_GetVolumeForChannel(int deviceID, unsigned int channel, unsigned int *volume); + + +unsigned long JACK_GetOutputBytesPerSecond(int deviceID); /* bytes_per_output_frame * sample_rate */ +unsigned long JACK_GetInputBytesPerSecond(int deviceID); /* bytes_per_input_frame * sample_rate */ +unsigned long JACK_GetBytesStored(int deviceID); /* bytes currently buffered in the output buffer */ +unsigned long JACK_GetBytesFreeSpace(int deviceID); /* bytes of free space in the output buffer */ +unsigned long JACK_GetBytesUsedSpace(int deviceID); /* bytes of space used in the input buffer */ +unsigned long JACK_GetBytesPerOutputFrame(int deviceID); +unsigned long JACK_GetBytesPerInputFrame(int deviceID); + +/* Note: these will probably be removed in a future release */ +int JACK_GetNumInputChannels(int deviceID); +int JACK_GetNumOutputChannels(int deviceID); + +long JACK_GetSampleRate(int deviceID); /* samples per second */ + +void JACK_SetClientName(char *name); /* sets the name that bio2jack will use when + creating a new jack client. name_%pid%_%deviceID%%counter% + will be used + NOTE: this defaults to name = bio2jack + NOTE: we limit the size of the client name to + jack_client_name_size() */ + +enum JACK_PORT_CONNECTION_MODE +{ + CONNECT_ALL, /* connect to all avaliable ports */ + CONNECT_OUTPUT, /* connect only to the ports we need for output */ + CONNECT_NONE /* don't connect to any ports */ +}; + +/* set the mode for port connections */ +/* defaults to CONNECT_ALL */ +void JACK_SetPortConnectionMode(enum JACK_PORT_CONNECTION_MODE mode); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef JACK_OUT_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/configure.c Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,163 @@ +/* xmms - jack output plugin + * Copyright (C) 2004 Chris Morgan + * + * + * audacious port (2005) by Giacomo Lozito from develia.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contains code Copyright (C) 1998-2000 Mikael Alm, Olle Hallnas, + * Thomas Nillson, 4Front Technologies and Galex Yen + */ + +#include "jack.h" + +#include "libaudacious/configfile.h" + +#include <gtk/gtk.h> + +extern jackconfig jack_cfg; + +static GtkWidget *configure_win = NULL, *vbox; + +static GtkWidget *GTK_isTraceEnabled; +static GtkWidget *bbox, *ok, *cancel; + +static GtkWidget *option_frame; +static GtkWidget *port_connection_mode_box; +static GtkWidget *port_connection_mode_combo; + +#define GET_CHARS(edit) gtk_editable_get_chars(GTK_EDITABLE(edit), 0, -1) + +static void configure_win_ok_cb(GtkWidget * w, gpointer data) +{ + ConfigFile *cfgfile; + gchar *filename; + + jack_cfg.isTraceEnabled = (gint) GTK_CHECK_BUTTON(GTK_isTraceEnabled)->toggle_button.active; + jack_cfg.port_connection_mode = GET_CHARS(GTK_COMBO(port_connection_mode_combo)->entry); + + jack_set_port_connection_mode(); /* update the connection mode */ + + filename = g_strconcat(g_get_home_dir(), "/.audacious/config", NULL); + + cfgfile = xmms_cfg_open_file(filename); + if (!cfgfile) + cfgfile = xmms_cfg_new(); + + xmms_cfg_write_boolean(cfgfile, "jack", "isTraceEnabled", jack_cfg.isTraceEnabled); + xmms_cfg_write_string(cfgfile, "jack", "port_connection_mode", jack_cfg.port_connection_mode); + xmms_cfg_write_file(cfgfile, filename); + xmms_cfg_free(cfgfile); + + g_free(filename); + + gtk_widget_destroy(configure_win); +} + +static void get_port_connection_modes(GtkCombo *combo) +{ + GtkWidget *item; + char *descr; + + descr = g_strdup("Connect to all available jack ports"); + item = gtk_list_item_new_with_label(descr); + gtk_widget_show(item); + g_free(descr); + gtk_combo_set_item_string(combo, GTK_ITEM(item), "CONNECT_ALL"); + gtk_container_add(GTK_CONTAINER(combo->list), item); + + descr = g_strdup("Connect only the output ports"); + item = gtk_list_item_new_with_label(descr); + gtk_widget_show(item); + g_free(descr); + gtk_combo_set_item_string(combo, GTK_ITEM(item), "CONNECT_OUTPUT"); + gtk_container_add(GTK_CONTAINER(combo->list), item); + + descr = g_strdup("Connect to no ports"); + item = gtk_list_item_new_with_label(descr); + gtk_widget_show(item); + g_free(descr); + gtk_combo_set_item_string(combo, GTK_ITEM(item), "CONNECT_NONE"); + gtk_container_add(GTK_CONTAINER(combo->list), item); +} + +void jack_configure(void) +{ + if (!configure_win) + { + configure_win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint ( GTK_WINDOW(configure_win), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_signal_connect(GTK_OBJECT(configure_win), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &configure_win); + gtk_window_set_title(GTK_WINDOW(configure_win), ("jack Plugin configuration")); + gtk_window_set_policy(GTK_WINDOW(configure_win), FALSE, FALSE, FALSE); + gtk_window_set_position(GTK_WINDOW(configure_win), GTK_WIN_POS_MOUSE); + gtk_container_border_width(GTK_CONTAINER(configure_win), 10); + + vbox = gtk_vbox_new(FALSE, 10); + gtk_container_add(GTK_CONTAINER(configure_win), vbox); + + /* add a frame for other xmms-jack options */ + option_frame = gtk_frame_new("Options:"); + gtk_box_pack_start(GTK_BOX(vbox), option_frame, FALSE, FALSE, 0); + + /* add a hbox that will contain a label for a dropdown and the dropdown itself */ + port_connection_mode_box = gtk_hbox_new(FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(port_connection_mode_box), 5); + gtk_container_add(GTK_CONTAINER(option_frame), port_connection_mode_box); + + /* add the label */ + gtk_box_pack_start(GTK_BOX(port_connection_mode_box), gtk_label_new("Connection mode:"), + FALSE, FALSE, 0); + + /* add the dropdown */ + port_connection_mode_combo = gtk_combo_new(); + get_port_connection_modes(GTK_COMBO(port_connection_mode_combo)); + gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(port_connection_mode_combo)->entry), + jack_cfg.port_connection_mode); + gtk_box_pack_start(GTK_BOX(port_connection_mode_box), port_connection_mode_combo, + TRUE, TRUE, 0); + + /* create a check_button for debug output */ + GTK_isTraceEnabled = gtk_check_button_new_with_label("Enable debug printing"); + gtk_box_pack_start(GTK_BOX(vbox), GTK_isTraceEnabled, FALSE, FALSE, 0); + gtk_widget_show(GTK_isTraceEnabled); + + /* set the state of the check_button based upon the value of */ + /* isTracingEnabled */ + GTK_CHECK_BUTTON(GTK_isTraceEnabled)->toggle_button.active = jack_cfg.isTraceEnabled; + + /* add the box for the ok/canceled buttons at the bottom */ + bbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + + ok = gtk_button_new_with_label(("Ok")); + gtk_signal_connect(GTK_OBJECT(ok), "clicked", GTK_SIGNAL_FUNC(configure_win_ok_cb), NULL); + GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(bbox), ok, TRUE, TRUE, 0); + gtk_widget_show(ok); + gtk_widget_grab_default(ok); + + cancel = gtk_button_new_with_label(("Cancel")); + gtk_signal_connect_object(GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(configure_win)); + GTK_WIDGET_SET_FLAGS(cancel, GTK_CAN_DEFAULT); + gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 0); + gtk_widget_show(cancel); + + gtk_widget_show_all(configure_win); + } + else + gdk_window_raise(configure_win->window); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/jack.c Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,617 @@ +/* xmms - jack output plugin + * Copyright 2002 Chris Morgan<cmorgan@alum.wpi.edu> + * + * audacious port (2005) by Giacomo Lozito from develia.org + * + * This code maps xmms calls into the jack translation library + */ + +#include "libaudacious/configfile.h" +#include <dlfcn.h> +#include <gtk/gtk.h> +#include <stdio.h> +#include "config.h" +#include "bio2jack.h" /* includes for the bio2jack library */ +#include "jack.h" +#include "xconvert.h" /* xmms rate conversion header file */ +#include <string.h> + + + +/* set to 1 for verbose output */ +#define VERBOSE_OUTPUT 0 + +jackconfig jack_cfg; + +#define OUTFILE stderr + +#define TRACE(...) \ + if(jack_cfg.isTraceEnabled) { \ + fprintf(OUTFILE, "%s:", __FUNCTION__), \ + fprintf(OUTFILE, __VA_ARGS__), \ + fflush(OUTFILE); \ + } + +#define ERR(...) \ + if(jack_cfg.isTraceEnabled) { \ + fprintf(OUTFILE, "ERR: %s:", __FUNCTION__), \ + fprintf(OUTFILE, __VA_ARGS__), \ + fflush(OUTFILE); \ + } + + +static int driver = 0; /* handle to the jack output device */ + +typedef struct format_info { + AFormat format; + long frequency; + int channels; + long bps; +} format_info_t; + +static format_info_t input; +static format_info_t effect; +static format_info_t output; + +static convert_freq_func_t freq_convert; /* rate convert function */ +static struct xmms_convert_buffers *convertb; /* convert buffer */ + +#define MAKE_FUNCPTR(f) static typeof(f) * fp_##f = NULL; +MAKE_FUNCPTR(xmms_convert_buffers_new); +MAKE_FUNCPTR(xmms_convert_buffers_destroy); +MAKE_FUNCPTR(xmms_convert_get_frequency_func); +void *xmmslibhandle; /* handle to the dlopen'ed libxmms.so */ + +static int isXmmsFrequencyAvailable = 0; + +static gboolean output_opened; /* true if we have a connection to jack */ + +static GtkWidget *dialog, *button, *label; + + +/* Giacomo's note: removed the destructor from the original xmms-jack, cause + destructors + thread join + NPTL currently leads to problems; solved this + by adding a cleanup function callback for output plugins in Audacious, this + is used to close the JACK connection and to perform a correct shutdown */ +void jack_cleanup(void) +{ + int errval; + TRACE("cleanup\n"); + + if((errval = JACK_Close(driver))) + ERR("error closing device, errval of %d\n", errval); + + /* only clean this up if we have the function to call */ + if(isXmmsFrequencyAvailable) + { + fp_xmms_convert_buffers_destroy(convertb); /* clean up the rate conversion buffers */ + dlclose(xmmslibhandle); + } + + return; +} + + +void jack_sample_rate_error(void) +{ + dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dialog), ("Sample rate mismatch")); + gtk_container_border_width(GTK_CONTAINER(dialog), 5); + label = gtk_label_new(( + "Xmms is asking for a sample rate that differs from\n " + "that of the jack server. Xmms 1.2.8 or later\n" + "contains resampling routines that xmms-jack will\n" + "dynamically load and use to perform resampling.\n" + "Or you can restart the jack server\n" + "with a sample rate that matches the one that\n" + "xmms desires. -r is the option for the jack\n" + "alsa driver so -r 44100 or -r 48000 should do\n\n" + "Chris Morgan <cmorgan@alum.wpi.edu>\n")); + + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 0); + gtk_widget_show(label); + + button = gtk_button_new_with_label((" Close ")); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(dialog)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + gtk_widget_show(dialog); + gtk_widget_grab_focus(button); +} + + +/* Return the number of milliseconds of audio data that has been */ +/* written out to the device */ +gint jack_get_written_time(void) +{ + long return_val; + return_val = JACK_GetPosition(driver, MILLISECONDS, WRITTEN); + + TRACE("returning %ld milliseconds\n", return_val); + return return_val; +} + + +/* Return the current number of milliseconds of audio data that has */ +/* been played out of the audio device, not including the buffer */ +gint jack_get_output_time(void) +{ + gint return_val; + + /* don't try to get any values if the device is still closed */ + if(JACK_GetState(driver) == CLOSED) + return_val = 0; + else + return_val = JACK_GetPosition(driver, MILLISECONDS, PLAYED); /* get played position in terms of milliseconds */ + + TRACE("returning %d milliseconds\n", return_val); + return return_val; +} + + +/* returns TRUE if we are currently playing */ +/* NOTE: this was confusing at first BUT, if the device is open and there */ +/* is no more audio to be played, then the device is NOT PLAYING */ +gint jack_playing(void) +{ + gint return_val; + + /* If we are playing see if we ACTUALLY have something to play */ + if(JACK_GetState(driver) == PLAYING) + { + /* If we have zero bytes stored, we are done playing */ + if(JACK_GetBytesStored(driver) == 0) + return_val = FALSE; + else + return_val = TRUE; + } + else + return_val = FALSE; + + TRACE("returning %d\n", return_val); + return return_val; +} + + +void jack_set_port_connection_mode() +{ + /* setup the port connection mode that determines how bio2jack will connect ports */ + enum JACK_PORT_CONNECTION_MODE mode; + + if(strcmp(jack_cfg.port_connection_mode, "CONNECT_ALL") == 0) + mode = CONNECT_ALL; + else if(strcmp(jack_cfg.port_connection_mode, "CONNECT_OUTPUT") == 0) + mode = CONNECT_OUTPUT; + else if(strcmp(jack_cfg.port_connection_mode, "CONNECT_NONE") == 0) + mode = CONNECT_NONE; + else + { + TRACE("Defaulting to CONNECT_ALL"); + mode = CONNECT_ALL; + } + JACK_SetPortConnectionMode(mode); +} + +/* Initialize necessary things */ +void jack_init(void) +{ + /* read the isTraceEnabled setting from the config file */ + ConfigFile *cfgfile; + gchar *filename; + + filename = g_strconcat(g_get_home_dir(), "/.audacious/config", NULL); + cfgfile = xmms_cfg_open_file(filename); + if (!cfgfile) + { + jack_cfg.isTraceEnabled = FALSE; + jack_cfg.port_connection_mode = "CONNECT_ALL"; /* default to connect all */ + } else + { + xmms_cfg_read_boolean(cfgfile, "jack", "isTraceEnabled", &jack_cfg.isTraceEnabled); + if(!xmms_cfg_read_string(cfgfile, "jack", "port_connection_mode", &jack_cfg.port_connection_mode)) + jack_cfg.port_connection_mode = "CONNECT_ALL"; + } + + xmms_cfg_free(cfgfile); + g_free(filename); + + TRACE("initializing\n"); + JACK_Init(); /* initialize the driver */ + + /* set the bio2jack name so users will see xmms-jack in their */ + /* jack client list */ + JACK_SetClientName("audacious-jack"); + + /* set the port connection mode */ + jack_set_port_connection_mode(); + + xmmslibhandle = dlopen("libaudacious.so", RTLD_NOW); + if(xmmslibhandle) + { + fp_xmms_convert_buffers_new = dlsym(xmmslibhandle, "xmms_convert_buffers_new"); + fp_xmms_convert_buffers_destroy = dlsym(xmmslibhandle, "xmms_convert_buffers_destroy"); + fp_xmms_convert_get_frequency_func = dlsym(xmmslibhandle, "xmms_convert_get_frequency_func"); + + if(!fp_xmms_convert_buffers_new) + { + TRACE("fp_xmms_convert_buffers_new couldn't be dlsym'ed\n"); + TRACE("dlerror: %s\n", dlerror()); + } + + if(!fp_xmms_convert_buffers_destroy) + { + TRACE("fp_xmms_convert_buffers_destroy couldn't be dlsym'ed\n"); + TRACE("dlerror: %s\n", dlerror()); + } + + if(!fp_xmms_convert_get_frequency_func) + { + TRACE("fp_xmms_get_frequency_func couldn't be dlsym'ed\n"); + TRACE("dlerror: %s\n", dlerror()); + } + + if(!fp_xmms_convert_buffers_new || !fp_xmms_convert_buffers_destroy || + !fp_xmms_convert_get_frequency_func) + { + dlclose(xmmslibhandle); /* close the library, no need to keep it open */ + TRACE("One or more frequency convertion functions are missing, upgrade to xmms >=1.2.8\n"); + } else + { + TRACE("Found frequency convertion functions, setting isXmmsFrequencyAvailable to 1\n"); + isXmmsFrequencyAvailable = 1; + } + } else + { + TRACE("unable to dlopen '%s'\n", "libaudacious.so"); + } + + /* only initialize this stuff if we have the functions available */ + if(isXmmsFrequencyAvailable) + { + convertb = fp_xmms_convert_buffers_new (); + freq_convert = fp_xmms_convert_get_frequency_func(FMT_S16_LE, 2); + } + + output_opened = FALSE; +} + + +/* Return the amount of data that can be written to the device */ +gint jack_free(void) +{ + unsigned long return_val = JACK_GetBytesFreeSpace(driver); + unsigned long tmp; + + /* adjust for frequency differences, otherwise xmms could send us */ + /* as much data as we have free, then we go to convert this to */ + /* the output frequency and won't have enough space, so adjust */ + /* by the ratio of the two */ + if(effect.frequency != output.frequency) + { + tmp = return_val; + return_val = (return_val * effect.frequency) / output.frequency; + TRACE("adjusting from %ld to %ld free bytes to compensate for frequency differences\n", tmp, return_val); + } + + if(return_val > G_MAXINT) + { + TRACE("Warning: return_val > G_MAXINT\n"); + return_val = G_MAXINT; + } + + TRACE("free space of %ld bytes\n", return_val); + + return return_val; +} + + +/* Close the device */ +void jack_close(void) +{ + TRACE("\n"); + + JACK_Reset(driver); /* flush buffers, reset position and set state to STOPPED */ + TRACE("resetting driver, not closing now, destructor will close for us\n"); +} + + +/* Open the device up */ +gint jack_open(AFormat fmt, gint sample_rate, gint num_channels) +{ + int bits_per_sample; + int retval; + unsigned long rate; + + TRACE("fmt == %d, sample_rate == %d, num_channels == %d\n", + fmt, sample_rate, num_channels); + + if((fmt == FMT_U8) || (fmt == FMT_S8)) + { + bits_per_sample = 8; + } else + { + bits_per_sample = 16; + } + + /* record some useful information */ + input.format = fmt; + input.frequency = sample_rate; + input.bps = bits_per_sample * sample_rate * num_channels; + input.channels = num_channels; + + /* setup the effect as matching the input format */ + effect.format = input.format; + effect.frequency = input.frequency; + effect.channels = input.channels; + effect.bps = input.bps; + + /* if we are already opened then don't open again */ + if(output_opened) + { + /* if something has changed we should close and re-open the connect to jack */ + if((output.channels != input.channels) || + (output.frequency != input.frequency) || + (output.format != input.format)) + { + TRACE("output.channels is %d, jack_open called with %d channels\n", output.channels, input.channels); + TRACE("output.frequency is %ld, jack_open called with %ld\n", output.frequency, input.frequency); + TRACE("output.format is %d, jack_open called with %d\n", output.format, input.format); + jack_close(); + } else + { + TRACE("output_opened is TRUE and no options changed, not reopening\n"); + return 1; + } + } + + /* try to open the jack device with the requested rate at first */ + output.frequency = input.frequency; + output.bps = input.bps; + output.channels = input.channels; + output.format = input.format; + + rate = output.frequency; + retval = JACK_Open(&driver, bits_per_sample, &rate, output.channels); + output.frequency = rate; /* avoid compile warning as output.frequency differs in type + from what JACK_Open() wants for the type of the rate parameter */ + if((retval == ERR_RATE_MISMATCH) && isXmmsFrequencyAvailable) + { + TRACE("xmms(input) wants rate of '%ld', jacks rate(output) is '%ld', opening at jack rate\n", input.frequency, output.frequency); + + /* open the jack device with true jack's rate, return 0 upon failure */ + retval = JACK_Open(&driver, bits_per_sample, &rate, output.channels); + output.frequency = rate; /* avoid compile warning as output.frequency differs in type + from what JACK_Open() wants for the type of the rate parameter */ + if(retval) + { + TRACE("failed to open jack with JACK_Open(), error %d\n", retval); + return 0; + } + TRACE("success!!\n"); + } else if((retval == ERR_RATE_MISMATCH) && !isXmmsFrequencyAvailable) + { + TRACE("JACK_Open(), sample rate mismatch with no resampling routines available\n"); + + jack_sample_rate_error(); /* notify the user that we can't resample */ + + return 0; + } else if(retval != ERR_SUCCESS) + { + TRACE("failed to open jack with JACK_Open(), error %d\n", retval); + return 0; + } + + output_opened = TRUE; + + return 1; +} + + +/* write some audio out to the device */ +void jack_write(gpointer ptr, gint length) +{ + long written; + EffectPlugin *ep; + AFormat new_format; + int new_frequency, new_channels; + long positionMS; + + TRACE("starting length of %d\n", length); + + /* copy the current values into temporary values */ + new_format = input.format; + new_frequency = input.frequency; + new_channels = input.channels; + + /* query xmms for the current plugin */ + ep = get_current_effect_plugin(); + if(effects_enabled() && ep && ep->query_format) + { + ep->query_format(&new_format, &new_frequency, &new_channels); + } + + /* if the format has changed take this into account by modifying */ + /* the time offset and reopening the device with the new format settings */ + if (new_format != effect.format || + new_frequency != effect.frequency || + new_channels != effect.channels) + { + TRACE("format changed, storing new values and opening/closing jack\n"); + TRACE("effect.format == %d, new_format == %d, effect.frequency == %ld, new_frequency == %d, effect.channels == %d, new_channels = %d\n", + effect.format, new_format, effect.frequency, new_frequency, effect.channels, new_channels); + + positionMS = JACK_GetPosition(driver, MILLISECONDS, PLAYED); + + jack_close(); + jack_open(new_format, new_frequency, new_channels); + + /* restore the position after the open and close */ + JACK_SetState(driver, PAUSED); + JACK_SetPosition(driver, MILLISECONDS, positionMS); + JACK_SetState(driver, PLAYING); + } + + /* if effects are enabled and we have a plugin, run the current */ + /* samples through the plugin */ + if (effects_enabled() && ep && ep->mod_samples) + { + length = ep->mod_samples(&ptr, length, + input.format, + input.frequency, + input.channels); + TRACE("effects_enabled(), length is now %d\n", length); + } + + TRACE("effect.frequency == %ld, input.frequency == %ld, output.frequency == %ld\n", + effect.frequency, input.frequency, output.frequency); + + /* if we need rate conversion, perform it here */ + if((effect.frequency != output.frequency) && isXmmsFrequencyAvailable) + { + TRACE("performing rate conversion from '%ld'(effect) to '%ld'(output)\n", effect.frequency, output.frequency); + length = freq_convert (convertb, &ptr, length, effect.frequency, output.frequency); + } + + TRACE("length = %d\n", length); + /* loop until we have written all the data out to the jack device */ + /* this is due to xmms' audio driver api */ + char *buf = (char*)ptr; + while(length > 0) + { + TRACE("writing %d bytes\n", length); + written = JACK_Write(driver, (unsigned char*)buf, length); + length-=written; + buf+=written; + } + TRACE("finished\n"); +} + + +/* Flush any output currently buffered */ +/* and set the number of bytes written based on ms_offset_time, */ +/* the number of milliseconds of offset passed in */ +/* This is done so the driver itself keeps track of */ +/* current playing position of the mp3 */ +void jack_flush(gint ms_offset_time) +{ + TRACE("setting values for ms_offset_time of %d\n", ms_offset_time); + + JACK_Reset(driver); /* flush buffers and set state to STOPPED */ + + /* update the internal driver values to correspond to the input time given */ + JACK_SetPosition(driver, MILLISECONDS, ms_offset_time); + + JACK_SetState(driver, PLAYING); +} + + +/* Pause the jack device */ +void jack_pause(short p) +{ + TRACE("p == %d\n", p); + + /* pause the device if p is non-zero, unpause the device if p is zero and */ + /* we are currently paused */ + if(p) + JACK_SetState(driver, PAUSED); + else if(JACK_GetState(driver) == PAUSED) + JACK_SetState(driver, PLAYING); +} + + +/* Set the volume */ +void jack_set_volume(int l, int r) +{ + if(output.channels == 1) + { + TRACE("l(%d)\n", l); + } else if(output.channels > 1) + { + TRACE("l(%d), r(%d)\n", l, r); + } + + if(output.channels > 0) + JACK_SetVolumeForChannel(driver, 0, l); + if(output.channels > 1) + JACK_SetVolumeForChannel(driver, 1, r); +} + + +/* Get the current volume setting */ +void jack_get_volume(int *l, int *r) +{ + unsigned int _l, _r; + + if(output.channels > 0) + { + JACK_GetVolumeForChannel(driver, 0, &_l); + (*l) = _l; + } + if(output.channels > 1) + { + JACK_GetVolumeForChannel(driver, 1, &_r); + (*r) = _r; + } + +#if VERBOSE_OUTPUT + if(output.channels == 1) + TRACE("l(%d)\n", *l); + else if(output.channels > 1) + TRACE("l(%d), r(%d)\n", *l, *r); +#endif +} + + +void jack_about(void) +{ + dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dialog), ("About JACK Output Plugin 0.15")); + gtk_container_border_width(GTK_CONTAINER(dialog), 5); + label = gtk_label_new(( + "XMMS jack Driver 0.15\n\n " + "xmms-jack.sf.net\n" + "Chris Morgan<cmorgan@alum.wpi.edu>\n" + "\nAudacious port by\n" + "Giacomo Lozito from develia.org\n")); + + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 0); + gtk_widget_show(label); + + button = gtk_button_new_with_label((" Close ")); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(dialog)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + gtk_widget_show(dialog); + gtk_widget_grab_focus(button); +} + + +OutputPlugin jack_op = +{ + NULL, + NULL, + "JACK Output Plugin 0.15", + jack_init, + jack_cleanup, + jack_about, + jack_configure, + jack_get_volume, + jack_set_volume, + jack_open, + jack_write, + jack_close, + jack_flush, + jack_pause, + jack_free, + jack_playing, + jack_get_output_time, + jack_get_written_time, +}; + + +OutputPlugin *get_oplugin_info(void) +{ + return &jack_op; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/jack.h Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,16 @@ +#ifndef _JACK_H +#define _JACK_H + +#include <glib.h> + +void jack_configure(void); + +typedef struct +{ + gboolean isTraceEnabled; /* if true we will print debug information to the console */ + char *port_connection_mode; /* the port connection mode setting */ +} jackconfig; + +void jack_set_port_connection_mode(); /* called by jack_init() and the 'ok' handler in configure.c */ + +#endif /* #ifndef _JACK_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Output/jack/xconvert.h Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003 Haavard Kvaalen <havardk@xmms.org> + * + * Licensed under GNU LGPL version 2. + */ + + + +#include "audacious/plugin.h" + +void convert_free_buffer(void); + +struct xmms_convert_buffers; + +struct xmms_convert_buffers* xmms_convert_buffers_new(void); +/* + * Free the data assosiated with the buffers, without destroying the + * context. The context can be reused. + */ +void xmms_convert_buffers_free(struct xmms_convert_buffers* buf); +void xmms_convert_buffers_destroy(struct xmms_convert_buffers* buf); + + +typedef int (*convert_func_t)(struct xmms_convert_buffers* buf, void **data, int length); +typedef int (*convert_channel_func_t)(struct xmms_convert_buffers* buf, void **data, int length); +typedef int (*convert_freq_func_t)(struct xmms_convert_buffers* buf, void **data, int length, int ifreq, int ofreq); + + +convert_func_t xmms_convert_get_func(AFormat output, AFormat input); +convert_channel_func_t xmms_convert_get_channel_func(AFormat fmt, int output, int input); +convert_freq_func_t xmms_convert_get_frequency_func(AFormat fmt, int channels);
--- a/configure.ac Mon Dec 19 08:45:45 2005 -0800 +++ b/configure.ac Mon Dec 19 08:58:27 2005 -0800 @@ -442,6 +442,19 @@ AM_CONDITIONAL(ENABLE_CROSSFADE, test "$enable_crossfade" = "yes") AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test "$have_libsamplerate" = "yes") +dnl *** jack output plugin +AC_ARG_ENABLE( jack, +[ --disable-jack disable jack output plugin [default=enabled]],, + enable_jack="yes") + +if test "x$enable_jack" = xyes; then + AM_PATH_JACK(have_jack=yes, have_jack=no) +else + AC_MSG_RESULT([*** jack plugin disabled per user request ***]) + have_jack=no +fi +AM_CONDITIONAL(HAVE_JACK,test "x$have_jack" = xyes) + dnl *** sid AC_ARG_ENABLE( sid, [ --disable-sid disable sid input plugin [default=enabled]],, @@ -764,6 +777,7 @@ Plugins/Output/OSS/Makefile Plugins/Output/esd/Makefile Plugins/Output/alsa/Makefile + Plugins/Output/jack/Makefile Plugins/Output/crossfade/Makefile Plugins/Output/disk_writer/Makefile Plugins/Input/Makefile @@ -834,6 +848,7 @@ echo " Open Sound System (oss): $have_oss" echo " Advanced Linux Sound Arch. (alsa): $have_alsa" echo " Enlightenment Sound Daemon (esd): $have_esd" +echo " Jack Audio Connection Kit (jack): $have_jack" echo " Crossfading (crossfade): $enable_crossfade" echo " + libsamplerate support $have_libsamplerate" echo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/m4/jack.m4 Mon Dec 19 08:58:27 2005 -0800 @@ -0,0 +1,56 @@ +# Configure paths for JACK + +dnl AM_PATH_JACK([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for JACK, and define JACK_CFLAGS and JACK_LIBS +dnl +AC_DEFUN([AM_PATH_JACK], +[dnl +dnl **** Check for Jack sound server **** +dnl +JACK_LIBS= +JACK_CFLAGS= +JACK_EVERYTHINGOK=yes + +AC_CHECK_HEADERS(jack/jack.h) +if test "${ac_cv_header_jack_jack_h}" = "no" +then + AC_MSG_WARN([Could not find jack/jack.h Install jack headers to build bio2jack]) + JACK_EVERYTHINGOK=no +else + JACK_CFLAGS="-lpthread -ljack -ldl" +fi + +AC_CHECK_LIB(jack, jack_activate, JACK_LIBS="-ljack -ldl") +if test "${ac_cv_lib_jack_jack_activate}" = "no" +then + AC_MSG_WARN([Could not find jack_activate in libjack. Ensure that you have libjack installed and that it a current version.]) + JACK_EVERYTHINGOK=no +fi + +AC_SUBST(JACK_CFLAGS) +AC_SUBST(JACK_LIBS) + +dnl **** Check for libsamplerate necessary for bio2jack **** +PKG_CHECK_MODULES(SAMPLERATE, samplerate >= 0.0.15, + ac_cv_samplerate=1, ac_cv_samplerate=0) + +AC_DEFINE_UNQUOTED([HAVE_SAMPLERATE],${ac_cv_samplerate}, + [Set to 1 if you have libsamplerate.]) + +dnl Make sure libsamplerate is found, we can't compile without it +if test "${ac_cv_samplerate}" = 0 +then + AC_MSG_WARN([Could not find libsamplerate, necessary for jack output plugin.]) + JACK_EVERYTHINGOK=no +fi + +AC_SUBST(SAMPLERATE_CFLAGS) +AC_SUBST(SAMPLERATE_LIBS) + +if test "x$JACK_EVERYTHINGOK" = xno; then + ifelse([$2], , :, [$2]) +else + ifelse([$1], , :, [$1]) +fi + +])