From 4482e6867df82e889f48a9e7c22f2e0887b1028c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 17 Apr 2006 00:11:04 +0000 Subject: add new JACK sink git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@735 fefdeb5f-60dc-0310-8127-8f9354f1896f --- configure.ac | 9 + src/Makefile.am | 15 +- src/modules/module-jack-sink.c | 382 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 src/modules/module-jack-sink.c diff --git a/configure.ac b/configure.ac index e649e063..3fd32f82 100644 --- a/configure.ac +++ b/configure.ac @@ -345,11 +345,20 @@ AC_SUBST(HOWL_LIBS) AC_SUBST(HAVE_HOWL) AM_CONDITIONAL([HAVE_HOWL], [test "x$HAVE_HOWL" = x1]) +### LIBOIL #### PKG_CHECK_MODULES(LIBOIL, [ liboil-0.3 >= 0.3.0 ]) AC_SUBST(LIBOIL_CFLAGS) AC_SUBST(LIBOIL_LIBS) +### JACK #### + +PKG_CHECK_MODULES(JACK, [ jack >= 0.100 ], HAVE_JACK=1, HAVE_JACK=0) +AC_SUBST(JACK_CFLAGS) +AC_SUBST(JACK_LIBS) +AC_SUBST(HAVE_JACK) +AM_CONDITIONAL([HAVE_JACK], [test "x$HAVE_JACK" = x1]) + #### Async DNS support (optional) #### PKG_CHECK_MODULES(LIBASYNCNS, [ libasyncns >= 0.1 ], HAVE_LIBASYNCNS=1, HAVE_LIBASYNCNS=0) diff --git a/src/Makefile.am b/src/Makefile.am index c30e24c3..c5864373 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -792,6 +792,11 @@ modlib_LTLIBRARIES += \ module-mmkbd-evdev.la endif +if HAVE_JACK +modlib_LTLIBRARIES += \ + module-jack-sink.la +endif + if OS_IS_WIN32 modlib_LTLIBRARIES += \ module-waveout.la @@ -836,7 +841,8 @@ SYMDEF_FILES = \ modules/module-waveout-symdef.h \ modules/module-detect-symdef.h \ modules/rtp/module-rtp-send-symdef.h \ - modules/rtp/module-rtp-recv-symdef.h + modules/rtp/module-rtp-recv-symdef.h \ + modules/module-jack-sink-symdef.h EXTRA_DIST += $(SYMDEF_FILES) BUILT_SOURCES += $(SYMDEF_FILES) @@ -1062,6 +1068,13 @@ module_rtp_recv_la_LDFLAGS = -module -avoid-version module_rtp_recv_la_LIBADD = $(AM_LIBADD) libpolypcore.la module_rtp_recv_la_CFLAGS = $(AM_CFLAGS) +# JACK + +module_jack_sink_la_SOURCES = modules/module-jack-sink.c +module_jack_sink_la_LDFLAGS = -module -avoid-version +module_jack_sink_la_LIBADD = $(AM_LIBADD) libpolypcore.la $(JACK_LIBS) +module_jack_sink_la_CFLAGS = $(AM_LIBADD) libpolypcore.la $(JACK_CFLAGS) + ################################### # Some minor stuff # ################################### diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c new file mode 100644 index 00000000..3d372551 --- /dev/null +++ b/src/modules/module-jack-sink.c @@ -0,0 +1,382 @@ +/* $Id$ */ + +/*** + This file is part of polypaudio. + + polypaudio 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 of the License, + or (at your option) any later version. + + polypaudio 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 Lesser General Public License + along with polypaudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "module-jack-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering") +PA_MODULE_DESCRIPTION("Jack Sink") +PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_USAGE( + "sink_name= " + "server_name= " + "channels= " + "connect=" + "port_prefix=" +) + +#define DEFAULT_SINK_NAME "jack_out" + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink; + + unsigned channels; + + jack_port_t* port[PA_CHANNELS_MAX]; + jack_client_t *client; + + pthread_mutex_t mutex; + pthread_cond_t cond; + + void * buffer[PA_CHANNELS_MAX]; + jack_nframes_t frames_requested; + int quit_requested; + + int pipe_fds[2]; + pa_io_event *io_event; + + jack_nframes_t frames_in_buffer; + struct timeval timestamp; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "server_name", + "channels", + "connect", + "port_prefix", + NULL +}; + +static void stop_sink(struct userdata *u) { + assert (u); + + jack_client_close(u->client); + u->client = NULL; + u->core->mainloop->io_free(u->io_event); + u->io_event = NULL; + pa_sink_disconnect(u->sink); + pa_sink_unref(u->sink); + u->sink = NULL; + pa_module_unload_request(u->module); +} + +static void io_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { + struct userdata *u = userdata; + char x; + + assert(m); + assert(flags == PA_IO_EVENT_INPUT); + assert(u); + assert(u->pipe_fds[0] == fd); + + read(fd, &x, 1); + + if (u->quit_requested) { + stop_sink(u); + u->quit_requested = 0; + return; + } + + pthread_mutex_lock(&u->mutex); + + if (u->frames_requested > 0) { + unsigned fs; + jack_nframes_t frame_idx; + pa_memchunk chunk; + + fs = pa_frame_size(&u->sink->sample_spec); + + pa_sink_render_full(u->sink, u->frames_requested * fs, &chunk); + + for (frame_idx = 0; frame_idx < u->frames_requested; frame_idx ++) { + unsigned c; + + for (c = 0; c < u->channels; c++) { + float *s = ((float*) ((uint8_t*) chunk.memblock->data + chunk.index)) + (frame_idx * u->channels) + c; + float *d = ((float*) u->buffer[c]) + frame_idx; + + *d = *s; + } + } + + pa_memblock_unref(chunk.memblock); + + u->frames_requested = 0; + + pthread_cond_signal(&u->cond); + } + + pthread_mutex_unlock(&u->mutex); +} + +static void request_render(struct userdata *u) { + char c = 'x'; + assert(u); + + assert(u->pipe_fds[1] >= 0); + write(u->pipe_fds[1], &c, 1); +} + +static void jack_shutdown(void *arg) { + struct userdata *u = arg; + assert(u); + + u->quit_requested = 1; + request_render(u); +} + +static int jack_process(jack_nframes_t nframes, void *arg) { + struct userdata *u = arg; + assert(u); + + if (jack_transport_query(u->client, NULL) == JackTransportRolling) { + unsigned c; + + pthread_mutex_lock(&u->mutex); + + u->frames_requested = nframes; + + for (c = 0; c < u->channels; c++) { + u->buffer[c] = jack_port_get_buffer(u->port[c], nframes); + assert(u->buffer[c]); + } + + request_render(u); + + pthread_cond_wait(&u->cond, &u->mutex); + + u->frames_in_buffer = nframes; + pa_gettimeofday(&u->timestamp); + + pthread_mutex_unlock(&u->mutex); + } + + return 0; +} + +static pa_usec_t sink_get_latency_cb(pa_sink *s) { + struct userdata *u; + pa_usec_t t, delta; + assert(s); + u = s->userdata; + + if (jack_transport_query(u->client, NULL) != JackTransportRolling) + return 0; + + delta = pa_timeval_age(&u->timestamp); + t = pa_bytes_to_usec(jack_port_get_total_latency(u->client, u->port[0]) * pa_frame_size(&s->sample_spec), &s->sample_spec); + + if (t > delta) + return t - delta; + else + return 0; +} + +static void jack_error_func(const char*t) { + pa_log_warn(__FILE__": JACK error >%s<", t); +} + +int pa__init(pa_core *c, pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_modargs *ma = NULL; + jack_status_t status; + const char *server_name; + uint32_t channels; + int connect = 1; + const char *pfx; + unsigned i; + + assert(c); + assert(m); + + jack_set_error_function(jack_error_func); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log(__FILE__": failed to parse module arguments."); + goto fail; + } + + channels = c->default_sample_spec.channels; + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) { + pa_log(__FILE__": failed to parse channels= argument."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "connect", &connect) < 0) { + pa_log(__FILE__": failed to parse connect= argument."); + goto fail; + } + + server_name = pa_modargs_get_value(ma, "server_name", NULL); + + u = pa_xnew0(struct userdata, 1); + m->userdata = u; + u->core = c; + u->module = m; + u->pipe_fds[0] = u->pipe_fds[1] = -1; + + pthread_mutex_init(&u->mutex, NULL); + pthread_cond_init(&u->cond, NULL); + + if (pipe(u->pipe_fds) < 0) { + pa_log(__FILE__": pipe() failed: %s", strerror(errno)); + goto fail; + } + + pa_make_nonblock_fd(u->pipe_fds[1]); + + if (!(u->client = jack_client_open("polypaudio", server_name ? JackServerName : JackNullOption, &status, server_name))) { + pa_log(__FILE__": jack_client_open() failed."); + goto fail; + } + + pa_log_info(__FILE__": Successfully connected as '%s'", jack_get_client_name(u->client)); + + ss.channels = u->channels = channels; + ss.rate = jack_get_sample_rate(u->client); + ss.format = PA_SAMPLE_FLOAT32NE; + + assert(pa_sample_spec_valid(&ss)); + + pfx = pa_modargs_get_value(ma, "port_prefix", "channel"); + + for (i = 0; i < ss.channels; i++) { + char tmp[64]; + + snprintf(tmp, sizeof(tmp), "%s_%i", pfx, i+1); + + if (!(u->port[i] = jack_port_register(u->client, tmp, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput/*|JackPortIsTerminal*/, 0))) { + pa_log(__FILE__": jack_port_register() failed."); + goto fail; + } + } + + if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { + pa_log(__FILE__": failed to create sink."); + goto fail; + } + + u->sink->userdata = u; + pa_sink_set_owner(u->sink, m); + u->sink->description = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client)); + u->sink->get_latency = sink_get_latency_cb; + + jack_set_process_callback(u->client, jack_process, u); + jack_on_shutdown(u->client, jack_shutdown, u); + + if (jack_activate(u->client)) { + pa_log(__FILE__": jack_activate() failed"); + goto fail; + } + + if (connect) { + const char **p, **ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); + + for (i = 0, p = ports; i < ss.channels; i++, p++) { + + if (!p) { + pa_log(__FILE__": not enough physical output ports, leaving unconnected."); + break; + } + + pa_log_info(__FILE__": connecting %s to %s", jack_port_name(u->port[i]), *p); + + if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) { + pa_log(__FILE__": failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); + break; + } + } + + free(ports); + } + + u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], PA_IO_EVENT_INPUT, io_event_cb, u); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(c, m); + + return -1; +} + +void pa__done(pa_core *c, pa_module*m) { + struct userdata *u; + assert(c && m); + + if (!(u = m->userdata)) + return; + + if (u->client) + jack_client_close(u->client); + + if (u->io_event) + c->mainloop->io_free(u->io_event); + + if (u->sink) { + pa_sink_disconnect(u->sink); + pa_sink_unref(u->sink); + } + + if (u->pipe_fds[0] >= 0) + close(u->pipe_fds[0]); + if (u->pipe_fds[1] >= 0) + close(u->pipe_fds[1]); + + pthread_mutex_destroy(&u->mutex); + pthread_cond_destroy(&u->cond); + pa_xfree(u); +} -- cgit