Mercurial > audlegacy-plugins
diff src/Output/jack/jack.c @ 0:13389e613d67 trunk
[svn] - initial import of audacious-plugins tree (lots to do)
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 01:11:49 -0700 |
parents | |
children | 088092a52fea |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Output/jack/jack.c Mon Sep 18 01:11:49 2006 -0700 @@ -0,0 +1,636 @@ +/* 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/configdb.h" +#include "libaudacious/util.h" +#include <dlfcn.h> +#include <gtk/gtk.h> +#include <glib/gi18n.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; + +void jack_set_volume(int l, int r); + +/* 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 */ + ConfigDb *cfgfile; + + cfgfile = bmp_cfg_db_open(); + if (!cfgfile) + { + jack_cfg.isTraceEnabled = FALSE; + jack_cfg.port_connection_mode = "CONNECT_ALL"; /* default to connect all */ + jack_cfg.volume_left = 25; /* set default volume to 25 % */ + jack_cfg.volume_right = 25; + } else + { + bmp_cfg_db_get_bool(cfgfile, "jack", "isTraceEnabled", &jack_cfg.isTraceEnabled); + if(!bmp_cfg_db_get_string(cfgfile, "jack", "port_connection_mode", &jack_cfg.port_connection_mode)) + jack_cfg.port_connection_mode = "CONNECT_ALL"; + if(!bmp_cfg_db_get_int(cfgfile, "jack", "volume_left", &jack_cfg.volume_left)) + jack_cfg.volume_left = 25; + if(!bmp_cfg_db_get_int(cfgfile, "jack", "volume_right", &jack_cfg.volume_right)) + jack_cfg.volume_right = 25; + } + + bmp_cfg_db_close(cfgfile); + + 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) +{ + ConfigDb *cfgfile; + + cfgfile = bmp_cfg_db_open(); + bmp_cfg_db_set_int(cfgfile, "jack", "volume_left", jack_cfg.volume_left); /* stores the volume setting */ + bmp_cfg_db_set_int(cfgfile, "jack", "volume_right", jack_cfg.volume_right); + bmp_cfg_db_close(cfgfile); + + 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; + } + + jack_set_volume(jack_cfg.volume_left, jack_cfg.volume_right); /* sets the volume to stored value */ + 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); + jack_cfg.volume_left = l; + } + if(output.channels > 1) { + JACK_SetVolumeForChannel(driver, 1, r); + jack_cfg.volume_right = 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) +{ + static GtkWidget *aboutbox; + + if (!aboutbox) + { + aboutbox = xmms_show_message( + _("About JACK Output Plugin 0.15"), + _("XMMS jack Driver 0.15\n\n" + "xmms-jack.sf.net\nChris Morgan<cmorgan@alum.wpi.edu>\n\n" + "Audacious port by\nGiacomo Lozito from develia.org"), + _("Ok"), FALSE, NULL, NULL); + g_signal_connect(GTK_OBJECT(aboutbox), "destroy", + (GCallback)gtk_widget_destroyed, &aboutbox); + } +} + +static void +jack_tell_audio(AFormat * fmt, gint * srate, gint * nch) +{ + (*fmt) = input.format; + (*srate) = input.frequency; + (*nch) = input.channels; +} + +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, + jack_tell_audio +}; + + +OutputPlugin *get_oplugin_info(void) +{ + return &jack_op; +}