diff options
author | Miguel Freitas <miguelfreitas@users.sourceforge.net> | 2006-08-12 23:57:29 +0000 |
---|---|---|
committer | Miguel Freitas <miguelfreitas@users.sourceforge.net> | 2006-08-12 23:57:29 +0000 |
commit | a001dcbaed1398598a5271ae869f2adacc1ab146 (patch) | |
tree | fe98b3d0807590d03a3cfafe7101062bbde8501b | |
parent | af78d2543c41a5407e688b077c664cc6d386bc97 (diff) | |
download | xine-lib-a001dcbaed1398598a5271ae869f2adacc1ab146.tar.gz xine-lib-a001dcbaed1398598a5271ae869f2adacc1ab146.tar.bz2 |
JACK audio driver by Chris Cannam
CVS patchset: 8178
CVS date: 2006/08/12 23:57:29
-rw-r--r-- | AUTHORS | 5 | ||||
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | configure.ac | 34 | ||||
-rw-r--r-- | src/audio_out/Makefile.am | 10 | ||||
-rw-r--r-- | src/audio_out/audio_jack_out.c | 447 |
5 files changed, 495 insertions, 2 deletions
@@ -561,5 +561,8 @@ Claudio Ciccani <klan@users.sf.net> Maximilian Schwerin <mschwerin@users.sourceforge.net> support for Vorbis-style comments in FLAC files - + +Chris Cannam <chris.cannam@ferventsoftware.com> + JACK audio driver + (let us know if we've forgotten anyone) @@ -16,6 +16,7 @@ xine-lib (1.1.3) * Mark Polypaudio audio plugin as deprecated, as upstream changed APIs at least two times in the mean time. * Allow 0 for DVD title/chapter (navigation or full title). + * New experimental JACK audio driver. xine-lib (1.1.2) * Security fixes: diff --git a/configure.ac b/configure.ac index 0f5f03b22..f452e011a 100644 --- a/configure.ac +++ b/configure.ac @@ -1379,6 +1379,40 @@ AM_CONDITIONAL(HAVE_FUSIONSOUND, test x"$no_fusionsound" != "xyes") dnl --------------------------------------------- +dnl JACK support +dnl --------------------------------------------- + +AC_ARG_ENABLE([jack], + AC_HELP_STRING([--disable-jack], [Disable optional JACK support]), + [ + case "${enableval}" in + yes) jack=yes ;; + no) jack=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-jack) ;; + esac + ], + [jack=auto]) + +if test "x${jack}" != xno ; then + PKG_CHECK_MODULES(JACK, [ jack >= 0.100 ], + HAVE_JACK=1, + [ + HAVE_JACK=0 + if test "x$jack" = xyes ; then + AC_MSG_ERROR([*** JACK support not found]) + fi + ]) +else + HAVE_JACK=0 +fi + +AC_SUBST(JACK_CFLAGS) +AC_SUBST(JACK_LIBS) +AC_SUBST(HAVE_JACK) +AM_CONDITIONAL([HAVE_JACK], [test "x$HAVE_JACK" = x1]) + + +dnl --------------------------------------------- dnl gnome-vfs support dnl --------------------------------------------- diff --git a/src/audio_out/Makefile.am b/src/audio_out/Makefile.am index 996b61925..3eeac31fc 100644 --- a/src/audio_out/Makefile.am +++ b/src/audio_out/Makefile.am @@ -51,6 +51,9 @@ if HAVE_FUSIONSOUND fusionsound_module = xineplug_ao_out_fusionsound.la endif +if HAVE_JACK +jack_module = xineplug_ao_out_jack.la +endif ## # IMPORTANT: @@ -69,7 +72,8 @@ lib_LTLIBRARIES = xineplug_ao_out_none.la xineplug_ao_out_file.la \ $(polypaudio_module) \ $(pulseaudio_module) \ $(directx2_module) \ - $(fusionsound_module) + $(fusionsound_module) \ + $(jack_module) #lib_LTLIBRARIES = \ # $(alsa_module) \ @@ -155,3 +159,7 @@ xineplug_ao_out_fusionsound_la_LIBADD = $(FUSIONSOUND_LIBS) $(XINE_LIB) xineplug_ao_out_fusionsound_la_CFLAGS = $(VISIBILITY_FLAG) $(FUSIONSOUND_CFLAGS) xineplug_ao_out_fusionsound_la_LDFLAGS = -avoid-version -module +xineplug_ao_out_jack_la_SOURCES = audio_jack_out.c +xineplug_ao_out_jack_la_LIBADD = $(JACK_LIBS) $(XINE_LIB) +xineplug_ao_out_jack_la_CFLAGS = $(VISIBILITY_FLAG) $(JACK_CFLAGS) +xineplug_ao_out_jack_la_LDFLAGS = -avoid-version -module diff --git a/src/audio_out/audio_jack_out.c b/src/audio_out/audio_jack_out.c new file mode 100644 index 000000000..1bc070527 --- /dev/null +++ b/src/audio_out/audio_jack_out.c @@ -0,0 +1,447 @@ + +#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 "xine_internal.h" +#include "xineutils.h" +#include "audio_out.h" + +#include <jack/jack.h> + +#define AO_OUT_JACK_IFACE_VERSION 8 + +#define GAP_TOLERANCE AO_MAX_GAP +#define BUFSIZE 81920 + +typedef struct jack_driver_s { + + ao_driver_t ao_driver; + xine_t *xine; + + int capabilities; + + int32_t sample_rate; + uint32_t num_channels; + uint32_t bits_per_sample; + uint32_t bytes_per_frame; + + jack_client_t *client; + jack_port_t *port_1; + jack_port_t *port_2; + float buf_1[BUFSIZE]; + float buf_2[BUFSIZE]; + uint32_t buf_read; + uint32_t buf_write; + + uint32_t volume; + uint32_t mute; + +} jack_driver_t; + +typedef struct { + audio_driver_class_t driver_class; + config_values_t *config; + xine_t *xine; +} jack_class_t; + + +static int jack_process(jack_nframes_t nframes, void *arg) +{ + jack_driver_t *this = (jack_driver_t *)arg; + uint32_t local_buf_read = this->buf_read; + uint32_t local_buf_write = this->buf_write; + uint32_t src_channel, target_channel; + uint32_t frame; + float *buf, *out; + float gain = 0; + + if (!this->client) return 0; + + if (!this->mute) { + gain = (float)this->volume / 100.0; + } + + for (target_channel = 0; target_channel < 2; ++target_channel) { + + if (target_channel < this->num_channels) src_channel = target_channel; + else src_channel = 0; + + jack_port_t *port = (target_channel ? this->port_2 : this->port_1); + if (!port) continue; + + buf = (src_channel ? this->buf_2 : this->buf_1); + out = (float *)jack_port_get_buffer(port, nframes); + + local_buf_read = this->buf_read; + frame = 0; + + while (frame < nframes && local_buf_read != local_buf_write) { + + // local_buf_write doesn't change during this process, + // so we can safely defer updating buf_read until after + + out[frame++] = buf[local_buf_read] * gain; + if (++local_buf_read == BUFSIZE) local_buf_read = 0; + } + + if (frame < nframes) { +// printf("jack_process: underrun: %u required, %u available\n", +// nframes, frame); + while (frame < nframes) { + out[frame++] = 0.0f; + } + } + } + + this->buf_read = local_buf_read; + + +// printf("jack_process: buf_read %u, buf_write %u\n", this->buf_read, this->buf_write); + + return 0; +} + + +static void jack_shutdown(void *arg) +{ + jack_driver_t *this = (jack_driver_t *)arg; + this->client = 0; +} + +/* + * open the audio device for writing to + */ +static int ao_jack_open(ao_driver_t *this_gen, uint32_t bits, uint32_t rate, int mode) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + + if (bits != 16) { + fprintf(stderr, "ao_jack_open: bits=%u expected %u\n", bits, 16); + return 0; + } + + rate = jack_get_sample_rate(this->client); + fprintf(stderr, "ao_jack_open: JACK sample rate is %u\n", rate); + + switch (mode) { + case AO_CAP_MODE_MONO: + this->num_channels = 1; + break; + case AO_CAP_MODE_STEREO: + this->num_channels = 2; + break; + } + + this->buf_read = this->buf_write = 0; + this->sample_rate = rate; + this->bits_per_sample = bits; + this->capabilities = AO_CAP_16BITS | AO_CAP_MODE_MONO | \ + AO_CAP_MODE_STEREO | AO_CAP_MIXER_VOL | AO_CAP_MUTE_VOL; + this->bytes_per_frame = this->num_channels * (bits / 8); + + fprintf(stderr, "ao_jack_open: bits=%d rate=%d, mode=%d OK\n", bits, rate, mode); + + return rate; +} + + +static int ao_jack_num_channels(ao_driver_t *this_gen) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + return this->num_channels; +} + +static int ao_jack_bytes_per_frame(ao_driver_t *this_gen) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + return this->bytes_per_frame; +} + +static int ao_jack_get_gap_tolerance (ao_driver_t *this_gen) +{ + return GAP_TOLERANCE; +} + +static int last_write_space = 0; + +static int ao_jack_write(ao_driver_t *this_gen, int16_t *data, + uint32_t num_frames) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + uint32_t frame, channel; + + uint32_t local_buf_read = this->buf_read; + uint32_t local_buf_write = this->buf_write; + uint32_t space = (local_buf_read + BUFSIZE - local_buf_write - 1) % BUFSIZE; + uint32_t first_frame = 0; + + int c = 0; + while (space < num_frames) { + if (++c == 10) return 0; + usleep(10000); + local_buf_read = this->buf_read; + space = (local_buf_read + BUFSIZE - local_buf_write - 1) % BUFSIZE; + } + +// if (space < num_frames) return 0; + +// printf("ao_jack_write: %u frames on %u channels, space is %u\n", num_frames, this->num_channels, space); + last_write_space = space; + + for (frame = first_frame; frame < num_frames; ++frame) { + for (channel = 0; channel < this->num_channels; ++channel) { + float *buf = (channel ? this->buf_2 : this->buf_1); + int16_t sample = data[frame * this->num_channels + channel]; + buf[local_buf_write] = ((float)sample) / 32767.0f; +// printf("%6f ", buf[local_buf_write]); +// if (++c == 8) { printf("\n"); c = 0; } + } + if (++local_buf_write == BUFSIZE) local_buf_write = 0; + } + + this->buf_write = local_buf_write; + +// printf("ao_jack_write: buf_read %u, buf_write %u\n", this->buf_read, this->buf_write); + + return 1; +} + +static int ao_jack_delay (ao_driver_t *this_gen) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + + uint32_t local_buf_read = this->buf_read; + uint32_t local_buf_write = this->buf_write; + + int delay = 0; + + if (local_buf_write > local_buf_read) { + delay = local_buf_write - local_buf_read; + } else { + delay = ((local_buf_write + BUFSIZE - local_buf_read) % BUFSIZE); + } + + return delay;// - jack_get_buffer_size(this->client); +} + +static void ao_jack_close(ao_driver_t *this_gen) +{ + // nothing +} + +static uint32_t ao_jack_get_capabilities (ao_driver_t *this_gen) { + jack_driver_t *this = (jack_driver_t *) this_gen; + return this->capabilities; +} + +static void ao_jack_exit(ao_driver_t *this_gen) +{ + jack_driver_t *this = (jack_driver_t *) this_gen; + jack_client_t *client = this->client; + ao_jack_close(this_gen); + this->client = 0; + if (client) jack_client_close(client); + free (this); +} + +static int ao_jack_get_property (ao_driver_t *this_gen, int property) { + jack_driver_t *this = (jack_driver_t *) this_gen; + + switch(property) { + case AO_PROP_PCM_VOL: + case AO_PROP_MIXER_VOL: +// printf("ao_jack_get_property(AO_PROP_MIXER_VOL): %u\n", this->volume); + return this->volume; + break; + case AO_PROP_MUTE_VOL: +// printf("ao_jack_get_property(AO_PROP_MUTE_VOL): %u\n", this->mute); + return this->mute; + break; + } + + return 0; +} + +static int ao_jack_set_property (ao_driver_t *this_gen, int property, int value) { + jack_driver_t *this = (jack_driver_t *) this_gen; + + switch(property) { + case AO_PROP_PCM_VOL: + case AO_PROP_MIXER_VOL: +// printf("ao_jack_set_property(AO_PROP_MIXER_VOL): %u\n", value); + this->volume = value; + break; + case AO_PROP_MUTE_VOL: +// printf("ao_jack_get_property(AO_PROP_MUTE_VOL): %u\n", value); + this->mute = value; + break; + } + + return ~value; +} + +static int ao_jack_ctrl(ao_driver_t *this_gen, int cmd, ...) { + jack_driver_t *this = (jack_driver_t *) this_gen; + + switch (cmd) { + + case AO_CTRL_PLAY_PAUSE: + break; + + case AO_CTRL_PLAY_RESUME: + break; + + case AO_CTRL_FLUSH_BUFFERS: +// fprintf(stderr, "ao_jack_ctrl(AO_CTRL_FLUSH_BUFFERS)\n"); + this->buf_write = this->buf_read = 0; + break; + } + + return 0; +} + +static ao_driver_t *open_jack_plugin (audio_driver_class_t *class_gen, + const void *data) +{ + jack_class_t *class = (jack_class_t *) class_gen; + jack_driver_t *this; + jack_client_t *client; + uint32_t rate; + const char **port_names; + int i; + + if ((client = jack_client_new("xine")) == 0) { + + char name[20]; + sprintf(name, "xine (%d)", (int)getpid()); + + if ((client = jack_client_new(name)) == 0) { + fprintf(stderr, "\nopen_jack_plugin: Error: Failed to connect to JACK server\n"); + fprintf(stderr, "open_jack_plugin: (did you start 'jackd' server?)\n"); + return 0; + } + } + + this = (jack_driver_t *) xine_xmalloc (sizeof (jack_driver_t)); + + this->client = client; + + jack_set_process_callback(client, jack_process, this); + jack_on_shutdown(client, jack_shutdown, this); + + rate = jack_get_sample_rate(client); + fprintf(stderr, "open_jack_plugin: JACK sample rate is %u\n", rate); + + // We support up to 2-channel output + + for (i = 0; i < 2; ++i) { + jack_port_t *port = jack_port_register + (client, (i ? "out_r" : "out_l"), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (!port) { + fprintf(stderr, "ao_jack_open: failed to register port %u\n", i); + } + if (i) this->port_2 = port; + else this->port_1 = port; + } + + this->buf_read = this->buf_write = 0; + this->volume = 100; + this->mute = 0; + + if (jack_activate(client)) { + fprintf(stderr, "ao_jack_open: failed to activate JACK client\n"); + return 0; + } + + if ((port_names = jack_get_ports(client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput)) != NULL) { + if (port_names[0]) { + jack_connect(client, jack_port_name(this->port_1), port_names[0]); + } + if (port_names[1] && this->port_2) { + jack_connect(client, jack_port_name(this->port_2), port_names[1]); + } + free(port_names); + } + + this->sample_rate = rate; + + this->xine = class->xine; + this->capabilities = AO_CAP_FLOAT32 | AO_CAP_MODE_MONO | + AO_CAP_MODE_STEREO | AO_CAP_MIXER_VOL | AO_CAP_MUTE_VOL; + + this->ao_driver.get_capabilities = ao_jack_get_capabilities; + this->ao_driver.get_property = ao_jack_get_property; + this->ao_driver.set_property = ao_jack_set_property; + this->ao_driver.open = ao_jack_open; + this->ao_driver.num_channels = ao_jack_num_channels; + this->ao_driver.bytes_per_frame = ao_jack_bytes_per_frame; + this->ao_driver.delay = ao_jack_delay; + this->ao_driver.write = ao_jack_write; + this->ao_driver.close = ao_jack_close; + this->ao_driver.exit = ao_jack_exit; + this->ao_driver.get_gap_tolerance = ao_jack_get_gap_tolerance; + this->ao_driver.control = ao_jack_ctrl; + + fprintf(stderr, "jack open_jack_plugin returning %p\n", (void *)(&this->ao_driver)); + return &this->ao_driver; +} + +/* + * class functions + */ + +static char* get_identifier (audio_driver_class_t *this_gen) { + return "jack"; +} + +static char* get_description (audio_driver_class_t *this_gen) { + return _("xine output plugin for JACK Audio Connection Kit"); +} + +static void dispose_class (audio_driver_class_t *this_gen) { + + jack_class_t *this = (jack_class_t *) this_gen; + free (this); +} + +static void *init_class (xine_t *xine, void *data) { + + jack_class_t *this; + + this = (jack_class_t *) xine_xmalloc (sizeof (jack_class_t)); + + this->driver_class.open_plugin = open_jack_plugin; + this->driver_class.get_identifier = get_identifier; + this->driver_class.get_description = get_description; + this->driver_class.dispose = dispose_class; + + this->config = xine->config; + this->xine = xine; + + fprintf(stderr, "jack init_class returning %p\n", (void *)this); + + return this; +} + +static ao_info_t ao_info_jack = { + 6 +}; + +/* + * exported plugin catalog entry + */ + +const plugin_info_t xine_plugin_info[] EXPORTED = { + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_AUDIO_OUT, AO_OUT_JACK_IFACE_VERSION, "jack", XINE_VERSION_CODE /* XINE_VERSION_CODE */, &ao_info_jack, init_class }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +}; + |