summaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/module-alsa-sink.c292
-rw-r--r--src/modules/module-alsa-source.c278
-rw-r--r--src/modules/module-cli.c88
-rw-r--r--src/modules/module-combine.c394
-rw-r--r--src/modules/module-defs.h.m429
-rw-r--r--src/modules/module-detect.c227
-rw-r--r--src/modules/module-esound-compat-spawnfd.c81
-rw-r--r--src/modules/module-esound-compat-spawnpid.c79
-rw-r--r--src/modules/module-esound-sink.c427
-rw-r--r--src/modules/module-lirc.c241
-rw-r--r--src/modules/module-match.c235
-rw-r--r--src/modules/module-mmkbd-evdev.c250
-rw-r--r--src/modules/module-native-protocol-fd.c85
-rw-r--r--src/modules/module-null-sink.c150
-rw-r--r--src/modules/module-oss-mmap.c426
-rw-r--r--src/modules/module-oss.c468
-rw-r--r--src/modules/module-pipe-sink.c237
-rw-r--r--src/modules/module-pipe-source.c210
-rw-r--r--src/modules/module-protocol-stub.c261
-rw-r--r--src/modules/module-sine.c182
-rw-r--r--src/modules/module-solaris.c488
-rw-r--r--src/modules/module-tunnel.c688
-rw-r--r--src/modules/module-waveout.c583
-rw-r--r--src/modules/module-x11-bell.c173
-rw-r--r--src/modules/module-x11-publish.c192
-rw-r--r--src/modules/module-zeroconf-publish.c508
26 files changed, 7272 insertions, 0 deletions
diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c
new file mode 100644
index 000000000..48e90e9f2
--- /dev/null
+++ b/src/modules/module-alsa-sink.c
@@ -0,0 +1,292 @@
+/* $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 <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include "poll.h"
+#endif
+
+#include <asoundlib.h>
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+#include <polypcore/memchunk.h>
+#include <polypcore/sink.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/alsa-util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-alsa-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ALSA Sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ snd_pcm_t *pcm_handle;
+ pa_sink *sink;
+ pa_io_event **io_events;
+ unsigned n_io_events;
+
+ size_t frame_size, fragment_size;
+ pa_memchunk memchunk, silence;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "sink_name",
+ "format",
+ "channels",
+ "rate",
+ "fragments",
+ "fragment_size",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "alsa_output"
+#define DEFAULT_DEVICE "default"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
+}
+
+static void xrun_recovery(struct userdata *u) {
+ assert(u);
+
+ pa_log(__FILE__": *** ALSA-XRUN (playback) ***\n");
+
+ if (snd_pcm_prepare(u->pcm_handle) < 0)
+ pa_log(__FILE__": snd_pcm_prepare() failed\n");
+}
+
+static void do_write(struct userdata *u) {
+ assert(u);
+
+ update_usage(u);
+
+ for (;;) {
+ pa_memchunk *memchunk = NULL;
+ snd_pcm_sframes_t frames;
+
+ if (u->memchunk.memblock)
+ memchunk = &u->memchunk;
+ else {
+ if (pa_sink_render(u->sink, u->fragment_size, &u->memchunk) < 0)
+ memchunk = &u->silence;
+ else
+ memchunk = &u->memchunk;
+ }
+
+ assert(memchunk->memblock && memchunk->memblock->data && memchunk->length && memchunk->memblock->length && (memchunk->length % u->frame_size) == 0);
+
+ if ((frames = snd_pcm_writei(u->pcm_handle, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length / u->frame_size)) < 0) {
+ if (frames == -EAGAIN)
+ return;
+
+ if (frames == -EPIPE) {
+ xrun_recovery(u);
+ continue;
+ }
+
+ pa_log(__FILE__": snd_pcm_writei() failed\n");
+ return;
+ }
+
+ if (memchunk == &u->memchunk) {
+ size_t l = frames * u->frame_size;
+ memchunk->index += l;
+ memchunk->length -= l;
+
+ if (memchunk->length == 0) {
+ pa_memblock_unref(memchunk->memblock);
+ memchunk->memblock = NULL;
+ memchunk->index = memchunk->length = 0;
+ }
+ }
+
+ break;
+ }
+}
+
+static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && a && e);
+
+ if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
+ xrun_recovery(u);
+
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ snd_pcm_sframes_t frames;
+ assert(s && u && u->sink);
+
+ if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
+ pa_log(__FILE__": failed to get delay\n");
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ if (frames < 0)
+ frames = 0;
+
+ r += pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ struct userdata *u = NULL;
+ const char *dev;
+ pa_sample_spec ss;
+ uint32_t periods, fragsize;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ int err;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+ frame_size = pa_frame_size(&ss);
+
+ periods = 8;
+ fragsize = 1024;
+ if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
+ pa_log(__FILE__": failed to parse buffer metrics\n");
+ goto fail;
+ }
+ period_size = fragsize;
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
+ pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
+ pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
+ goto fail;
+ }
+
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
+
+ if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
+ pa_log(__FILE__": failed to obtain file descriptors\n");
+ goto fail;
+ }
+
+ u->frame_size = frame_size;
+ u->fragment_size = period_size;
+
+ pa_log_info(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
+
+ u->silence.memblock = pa_memblock_new(u->silence.length = u->fragment_size, c->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+
+fail:
+
+ if (u)
+ pa__done(c, m);
+
+ goto finish;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->io_events)
+ pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c
new file mode 100644
index 000000000..f03e51ad6
--- /dev/null
+++ b/src/modules/module-alsa-source.c
@@ -0,0 +1,278 @@
+/* $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 <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include "poll.h"
+#endif
+
+#include <asoundlib.h>
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+#include <polypcore/memchunk.h>
+#include <polypcore/sink.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/alsa-util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-alsa-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ALSA Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("source_name=<name for the source> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ snd_pcm_t *pcm_handle;
+ pa_source *source;
+ pa_io_event **io_events;
+ unsigned n_io_events;
+
+ size_t frame_size, fragment_size;
+ pa_memchunk memchunk;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "source_name",
+ "channels",
+ "rate",
+ "format",
+ "fragments",
+ "fragment_size",
+ NULL
+};
+
+#define DEFAULT_SOURCE_NAME "alsa_input"
+#define DEFAULT_DEVICE "hw:0,0"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void xrun_recovery(struct userdata *u) {
+ assert(u);
+
+ pa_log(__FILE__": *** ALSA-XRUN (capture) ***\n");
+
+ if (snd_pcm_prepare(u->pcm_handle) < 0)
+ pa_log(__FILE__": snd_pcm_prepare() failed\n");
+}
+
+static void do_read(struct userdata *u) {
+ assert(u);
+
+ update_usage(u);
+
+ for (;;) {
+ pa_memchunk post_memchunk;
+ snd_pcm_sframes_t frames;
+ size_t l;
+
+ if (!u->memchunk.memblock) {
+ u->memchunk.memblock = pa_memblock_new(u->memchunk.length = u->fragment_size, u->source->core->memblock_stat);
+ u->memchunk.index = 0;
+ }
+
+ assert(u->memchunk.memblock && u->memchunk.memblock->data && u->memchunk.length && u->memchunk.memblock->length && (u->memchunk.length % u->frame_size) == 0);
+
+ if ((frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) {
+ if (frames == -EAGAIN)
+ return;
+
+ if (frames == -EPIPE) {
+ xrun_recovery(u);
+ continue;
+ }
+
+ pa_log(__FILE__": snd_pcm_readi() failed: %s\n", strerror(-frames));
+ return;
+ }
+
+ l = frames * u->frame_size;
+
+ post_memchunk = u->memchunk;
+ post_memchunk.length = l;
+
+ pa_source_post(u->source, &post_memchunk);
+
+ u->memchunk.index += l;
+ u->memchunk.length -= l;
+
+ if (u->memchunk.length == 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+ }
+
+ break;
+ }
+}
+
+static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && a && e);
+
+ if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
+ xrun_recovery(u);
+
+ do_read(u);
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ snd_pcm_sframes_t frames;
+ assert(s && u && u->source);
+
+ if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
+ pa_log(__FILE__": failed to get delay\n");
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ return pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ struct userdata *u = NULL;
+ const char *dev;
+ pa_sample_spec ss;
+ unsigned periods, fragsize;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ int err;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+ frame_size = pa_frame_size(&ss);
+
+ periods = 12;
+ fragsize = 1024;
+ if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
+ pa_log(__FILE__": failed to parse buffer metrics\n");
+ goto fail;
+ }
+ period_size = fragsize;
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) {
+ pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
+ pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
+ goto fail;
+ }
+
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+
+ u->source->userdata = u;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
+
+ if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
+ pa_log(__FILE__": failed to obtain file descriptors\n");
+ goto fail;
+ }
+
+ u->frame_size = frame_size;
+ u->fragment_size = period_size;
+
+ pa_log(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.index = u->memchunk.length = 0;
+
+ snd_pcm_start(u->pcm_handle);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+
+fail:
+
+ if (u)
+ pa__done(c, m);
+
+ goto finish;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->io_events)
+ pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c
new file mode 100644
index 000000000..c782ff8db
--- /dev/null
+++ b/src/modules/module-cli.c
@@ -0,0 +1,88 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include <polypcore/module.h>
+#include <polypcore/iochannel.h>
+#include <polypcore/cli.h>
+#include <polypcore/sioman.h>
+#include <polypcore/log.h>
+
+#include "module-cli-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Command line interface")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("No arguments")
+
+static void eof_cb(pa_cli*c, void *userdata) {
+ pa_module *m = userdata;
+ assert(c && m);
+
+ pa_module_unload_request(m);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_iochannel *io;
+ assert(c && m);
+
+ if (c->running_as_daemon) {
+ pa_log_info(__FILE__": Running as daemon so won't load this module.\n");
+ return 0;
+ }
+
+ if (m->argument) {
+ pa_log(__FILE__": module doesn't accept arguments.\n");
+ return -1;
+ }
+
+ if (pa_stdio_acquire() < 0) {
+ pa_log(__FILE__": STDIN/STDUSE already in use.\n");
+ return -1;
+ }
+
+ io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO);
+ assert(io);
+ pa_iochannel_set_noclose(io, 1);
+
+ m->userdata = pa_cli_new(c, io, m);
+ assert(m->userdata);
+
+ pa_cli_set_eof_callback(m->userdata, eof_cb, m);
+
+ return 0;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+ if (c->running_as_daemon == 0) {
+ pa_cli_free(m->userdata);
+ pa_stdio_release();
+ }
+}
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
new file mode 100644
index 000000000..aabb8f28b
--- /dev/null
+++ b/src/modules/module-combine.c
@@ -0,0 +1,394 @@
+/* $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 <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <polypcore/module.h>
+#include <polypcore/llist.h>
+#include <polypcore/sink.h>
+#include <polypcore/sink-input.h>
+#include <polypcore/memblockq.h>
+#include <polypcore/log.h>
+#include <polypcore/util.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+#include <polypcore/namereg.h>
+
+#include "module-combine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Combine multiple sinks to one")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> master=<master sink> slaves=<slave sinks> adjust_time=<seconds> resample_method=<method>")
+
+#define DEFAULT_SINK_NAME "combined"
+#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define RENDER_SIZE (1024*10)
+
+#define DEFAULT_ADJUST_TIME 20
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "master",
+ "slaves",
+ "adjust_time",
+ "resample_method",
+ NULL
+};
+
+struct output {
+ struct userdata *userdata;
+ pa_sink_input *sink_input;
+ size_t counter;
+ pa_memblockq *memblockq;
+ pa_usec_t total_latency;
+ PA_LLIST_FIELDS(struct output);
+};
+
+struct userdata {
+ pa_module *module;
+ pa_core *core;
+ pa_sink *sink;
+ unsigned n_outputs;
+ struct output *master;
+ pa_time_event *time_event;
+ uint32_t adjust_time;
+
+ PA_LLIST_HEAD(struct output, outputs);
+};
+
+static void output_free(struct output *o);
+static void clear_up(struct userdata *u);
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
+}
+
+
+static void adjust_rates(struct userdata *u) {
+ struct output *o;
+ pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency;
+ uint32_t base_rate;
+ assert(u && u->sink);
+
+ for (o = u->outputs; o; o = o->next) {
+ uint32_t sink_latency = o->sink_input->sink ? pa_sink_get_latency(o->sink_input->sink) : 0;
+
+ o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input);
+
+ if (sink_latency > max_sink_latency)
+ max_sink_latency = sink_latency;
+
+ if (o->total_latency < min_total_latency)
+ min_total_latency = o->total_latency;
+ }
+
+ assert(min_total_latency != (pa_usec_t) -1);
+
+ target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency;
+
+ pa_log_info(__FILE__": [%s] target latency is %0.0f usec.\n", u->sink->name, (float) target_latency);
+
+ base_rate = u->sink->sample_spec.rate;
+
+ for (o = u->outputs; o; o = o->next) {
+ uint32_t r = base_rate;
+
+ if (o->total_latency < target_latency)
+ r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/ 1000000);
+ else if (o->total_latency > target_latency)
+ r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/ 1000000);
+
+ if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1))
+ pa_log_warn(__FILE__": [%s] sample rates too different, not adjusting (%u vs. %u).\n", o->sink_input->name, base_rate, r);
+ else {
+ pa_log_info(__FILE__": [%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.\n", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency);
+ pa_sink_input_set_rate(o->sink_input, r);
+ }
+ }
+}
+
+static void request_memblock(struct userdata *u) {
+ pa_memchunk chunk;
+ struct output *o;
+ assert(u && u->sink);
+
+ update_usage(u);
+
+ if (pa_sink_render(u->sink, RENDER_SIZE, &chunk) < 0)
+ return;
+
+ for (o = u->outputs; o; o = o->next)
+ pa_memblockq_push_align(o->memblockq, &chunk, 0);
+
+ pa_memblock_unref(chunk.memblock);
+}
+
+static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval n;
+ assert(u && a && u->time_event == e);
+
+ adjust_rates(u);
+
+ pa_gettimeofday(&n);
+ n.tv_sec += u->adjust_time;
+ u->sink->core->mainloop->time_restart(e, &n);
+}
+
+static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input && chunk);
+
+ if (pa_memblockq_peek(o->memblockq, chunk) >= 0)
+ return 0;
+
+ /* Try harder */
+ request_memblock(o->userdata);
+
+ return pa_memblockq_peek(o->memblockq, chunk);
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input && chunk && length);
+
+ pa_memblockq_drop(o->memblockq, chunk, length);
+ o->counter += length;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input);
+ pa_module_unload_request(o->userdata->module);
+ clear_up(o->userdata);
+}
+
+static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) {
+ struct output *o = i->userdata;
+ assert(i && o && o->sink_input);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &i->sample_spec);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink && u->master);
+
+ return pa_sink_input_get_latency(u->master->sink_input);
+}
+
+static struct output *output_new(struct userdata *u, pa_sink *sink, int resample_method) {
+ struct output *o = NULL;
+ char t[256];
+ assert(u && sink && u->sink);
+
+ o = pa_xmalloc(sizeof(struct output));
+ o->userdata = u;
+
+ o->counter = 0;
+ o->memblockq = pa_memblockq_new(MEMBLOCKQ_MAXLENGTH, MEMBLOCKQ_MAXLENGTH, pa_frame_size(&u->sink->sample_spec), 0, 0, sink->core->memblock_stat);
+
+ snprintf(t, sizeof(t), "%s: output #%u", u->sink->name, u->n_outputs+1);
+ if (!(o->sink_input = pa_sink_input_new(sink, __FILE__, t, &u->sink->sample_spec, &u->sink->channel_map, 1, resample_method)))
+ goto fail;
+
+ o->sink_input->get_latency = sink_input_get_latency_cb;
+ o->sink_input->peek = sink_input_peek_cb;
+ o->sink_input->drop = sink_input_drop_cb;
+ o->sink_input->kill = sink_input_kill_cb;
+ o->sink_input->userdata = o;
+ o->sink_input->owner = u->module;
+
+ PA_LLIST_PREPEND(struct output, u->outputs, o);
+ u->n_outputs++;
+ return o;
+
+fail:
+
+ if (o) {
+ if (o->sink_input) {
+ pa_sink_input_disconnect(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ }
+
+ if (o->memblockq)
+ pa_memblockq_free(o->memblockq);
+
+ pa_xfree(o);
+ }
+
+ return NULL;
+}
+
+static void output_free(struct output *o) {
+ assert(o);
+ PA_LLIST_REMOVE(struct output, o->userdata->outputs, o);
+ o->userdata->n_outputs--;
+ pa_memblockq_free(o->memblockq);
+ pa_sink_input_disconnect(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ pa_xfree(o);
+}
+
+static void clear_up(struct userdata *u) {
+ struct output *o;
+ assert(u);
+
+ if (u->time_event) {
+ u->core->mainloop->time_free(u->time_event);
+ u->time_event = NULL;
+ }
+
+ while ((o = u->outputs))
+ output_free(o);
+
+ u->master = NULL;
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ const char *master_name, *slaves, *rm;
+ pa_sink *master_sink;
+ char *n = NULL;
+ const char*split_state;
+ struct timeval tv;
+ int resample_method = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) {
+ if ((resample_method = pa_parse_resample_method(rm)) < 0) {
+ pa_log(__FILE__": invalid resample method '%s'\n", rm);
+ goto fail;
+ }
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ m->userdata = u;
+ u->sink = NULL;
+ u->n_outputs = 0;
+ u->master = NULL;
+ u->module = m;
+ u->core = c;
+ u->time_event = NULL;
+ u->adjust_time = DEFAULT_ADJUST_TIME;
+ PA_LLIST_HEAD_INIT(struct output, u->outputs);
+
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
+ pa_log(__FILE__": failed to parse adjust_time value\n");
+ goto fail;
+ }
+
+ if (!(master_name = pa_modargs_get_value(ma, "master", NULL)) || !(slaves = pa_modargs_get_value(ma, "slaves", NULL))) {
+ pa_log(__FILE__": no master or slave sinks specified\n");
+ goto fail;
+ }
+
+ if (!(master_sink = pa_namereg_get(c, master_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": invalid master sink '%s'\n", master_name);
+ goto fail;
+ }
+
+ if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &master_sink->sample_spec, &master_sink->channel_map))) {
+ pa_log(__FILE__": failed to create sink\n");
+ goto fail;
+ }
+
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Combined sink");
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+
+ if (!(u->master = output_new(u, master_sink, resample_method))) {
+ pa_log(__FILE__": failed to create master sink input on sink '%s'.\n", u->sink->name);
+ goto fail;
+ }
+
+ split_state = NULL;
+ while ((n = pa_split(slaves, ",", &split_state))) {
+ pa_sink *slave_sink;
+
+ if (!(slave_sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": invalid slave sink '%s'\n", n);
+ goto fail;
+ }
+
+ pa_xfree(n);
+
+ if (!output_new(u, slave_sink, resample_method)) {
+ pa_log(__FILE__": failed to create slave sink input on sink '%s'.\n", slave_sink->name);
+ goto fail;
+ }
+ }
+
+ if (u->n_outputs <= 1)
+ pa_log_warn(__FILE__": WARNING: no slave sinks specified.\n");
+
+ if (u->adjust_time > 0) {
+ pa_gettimeofday(&tv);
+ tv.tv_sec += u->adjust_time;
+ u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
+ }
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa_xfree(n);
+
+ 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;
+
+ clear_up(u);
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4
new file mode 100644
index 000000000..8f10d6599
--- /dev/null
+++ b/src/modules/module-defs.h.m4
@@ -0,0 +1,29 @@
+dnl $Id$
+changecom(`/*', `*/')dnl
+define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl
+define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl
+define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl
+define(`incmacro', `foo'c_macro`symdeffoo')dnl
+define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl
+#ifndef incmacro
+#define incmacro
+
+#include <polypcore/core.h>
+#include <polypcore/module.h>
+
+gen_symbol(pa__init)
+gen_symbol(pa__done)
+gen_symbol(pa__get_author)
+gen_symbol(pa__get_description)
+gen_symbol(pa__get_usage)
+gen_symbol(pa__get_version)
+
+int pa__init(struct pa_core *c, struct pa_module*m);
+void pa__done(struct pa_core *c, struct pa_module*m);
+
+const char* pa__get_author(void);
+const char* pa__get_description(void);
+const char* pa__get_usage(void);
+const char* pa__get_version(void);
+
+#endif
diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c
new file mode 100644
index 000000000..e325b22c3
--- /dev/null
+++ b/src/modules/module-detect.c
@@ -0,0 +1,227 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-detect-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("just-one=<boolean>")
+
+static const char *endswith(const char *haystack, const char *needle) {
+ size_t l, m;
+ const char *p;
+
+ if ((l = strlen(haystack)) < (m = strlen(needle)))
+ return NULL;
+
+ if (strcmp(p = haystack + l - m, needle))
+ return NULL;
+
+ return p;
+}
+
+#ifdef HAVE_ALSA
+static int detect_alsa(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, n_sink = 0, n_source = 0;
+
+ if (!(f = fopen("/proc/asound/devices", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error(__FILE__": open(\"/proc/asound/devices\") failed: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device, subdevice;
+ int is_sink;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (endswith(line, "digital audio playback"))
+ is_sink = 1;
+ else if (endswith(line, "digital audio capture"))
+ is_sink = 0;
+ else
+ continue;
+
+ if (just_one && is_sink && n_sink >= 1)
+ continue;
+
+ if (just_one && !is_sink && n_source >= 1)
+ continue;
+
+ if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2)
+ continue;
+
+ /* Only one sink per device */
+ if (subdevice != 0)
+ continue;
+
+ snprintf(args, sizeof(args), "device=hw:%u,0", device);
+ if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
+ continue;
+
+ n++;
+
+ if (is_sink)
+ n_sink++;
+ else
+ n_source++;
+ }
+
+ fclose(f);
+
+ return n;
+}
+#endif
+
+#ifdef HAVE_OSS
+static int detect_oss(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, b = 0;
+
+ if (!(f = fopen("/dev/sndstat", "r")) &&
+ !(f = fopen("/proc/sndstat", "r")) &&
+ !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error(__FILE__": failed to open OSS sndstat device: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (!b) {
+ b = strcmp(line, "Audio devices:") == 0;
+ continue;
+ }
+
+ if (line[0] == 0)
+ break;
+
+ if (sscanf(line, "%u: ", &device) != 1)
+ continue;
+
+ if (device == 0)
+ snprintf(args, sizeof(args), "device=/dev/dsp");
+ else
+ snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
+
+ if (!pa_module_load(c, "module-oss", args))
+ continue;
+
+ n++;
+
+ if (just_one)
+ break;
+ }
+
+ fclose(f);
+ return n;
+}
+#endif
+
+int pa__init(pa_core *c, pa_module*m) {
+ int just_one = 0, n = 0;
+ pa_modargs *ma;
+
+ static const char* const valid_modargs[] = {
+ "just-one",
+ NULL
+ };
+
+ assert(c);
+ assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) {
+ pa_log(__FILE__": just_one= expects a boolean argument.\n");
+ goto fail;
+ }
+
+#if HAVE_ALSA
+ if ((n = detect_alsa(c, just_one)) <= 0)
+#endif
+#if HAVE_OSS
+ if ((n = detect_oss(c, just_one)) <= 0)
+#endif
+ {
+ pa_log_warn(__FILE__": failed to detect any sound hardware.\n");
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": loaded %i modules.\n", n);
+
+ /* We were successful and can unload ourselves now. */
+ pa_module_unload_request(m);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+
+void pa__done(pa_core *c, pa_module*m) {
+ /* NOP */
+}
+
diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c
new file mode 100644
index 000000000..5c656be9c
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnfd.c
@@ -0,0 +1,81 @@
+/* $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 <config.h>
+#endif
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+
+#include "module-esound-compat-spawnfd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation")
+PA_MODULE_USAGE("fd=<file descriptor>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "fd",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1, fd = -1;
+ char x = 1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_s32(ma, "fd", &fd) < 0 ||
+ fd < 0) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (pa_loop_write(fd, &x, sizeof(x)) != sizeof(x))
+ pa_log(__FILE__": WARNING: write(%u, 1, 1) failed: %s\n", fd, strerror(errno));
+
+ close(fd);
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+}
+
+
diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c
new file mode 100644
index 000000000..5daa1297a
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnpid.c
@@ -0,0 +1,79 @@
+
+/***
+ 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 <config.h>
+#endif
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+
+#include "module-esound-compat-spawnpid-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation")
+PA_MODULE_USAGE("pid=<process id>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "pid",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ uint32_t pid = 0;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_u32(ma, "pid", &pid) < 0 ||
+ !pid) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (kill(pid, SIGUSR1) < 0)
+ pa_log(__FILE__": WARNING: kill(%u) failed: %s\n", pid, strerror(errno));
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+}
+
+
diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c
new file mode 100644
index 000000000..4f724811d
--- /dev/null
+++ b/src/modules/module-esound-sink.c
@@ -0,0 +1,427 @@
+/* $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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/esound.h>
+#include <polypcore/authkey.h>
+
+#include "module-esound-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("ESOUND Sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> server=<address> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_SINK_NAME "esound_output"
+
+struct userdata {
+ pa_core *core;
+
+ pa_sink *sink;
+ pa_iochannel *io;
+ pa_socket_client *client;
+
+ pa_defer_event *defer_event;
+
+ pa_memchunk memchunk;
+ pa_module *module;
+
+ void *write_data;
+ size_t write_length, write_index;
+
+ void *read_data;
+ size_t read_length, read_index;
+
+ enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state;
+
+ pa_usec_t latency;
+
+ esd_format_t format;
+ int32_t rate;
+};
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void cancel(struct userdata *u) {
+ assert(u);
+
+ u->state = STATE_DEAD;
+
+ if (u->io) {
+ pa_iochannel_free(u->io);
+ u->io = NULL;
+ }
+
+ if (u->defer_event) {
+ u->core->mainloop->defer_free(u->defer_event);
+ u->defer_event = NULL;
+ }
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+
+ if (u->module) {
+ pa_module_unload_request(u->module);
+ u->module = NULL;
+ }
+}
+
+static int do_write(struct userdata *u) {
+ ssize_t r;
+ assert(u);
+
+ if (!pa_iochannel_is_writable(u->io))
+ return 0;
+
+ if (u->write_data) {
+ assert(u->write_index < u->write_length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ u->write_index += r;
+ assert(u->write_index <= u->write_length);
+
+ if (u->write_index == u->write_length) {
+ free(u->write_data);
+ u->write_data = NULL;
+ u->write_index = u->write_length = 0;
+ }
+ } else if (u->state == STATE_RUNNING) {
+ pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
+
+ if (!u->memchunk.length)
+ if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0)
+ return 0;
+
+ assert(u->memchunk.memblock && u->memchunk.length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int handle_response(struct userdata *u) {
+ assert(u);
+
+ switch (u->state) {
+ case STATE_AUTH:
+ assert(u->read_length == sizeof(int32_t));
+
+ /* Process auth data */
+ if (!*(int32_t*) u->read_data) {
+ pa_log(__FILE__": Authentication failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Request latency data */
+ assert(!u->write_data);
+ *(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY;
+
+ u->write_index = 0;
+ u->state = STATE_LATENCY;
+
+ /* Space for next response */
+ assert(u->read_length >= sizeof(int32_t));
+ u->read_index = 0;
+ u->read_length = sizeof(int32_t);
+
+ break;
+
+ case STATE_LATENCY: {
+ int32_t *p;
+ assert(u->read_length == sizeof(int32_t));
+
+ /* Process latency info */
+ u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100);
+ if (u->latency > 10000000) {
+ pa_log(__FILE__": WARNING! Invalid latency information received from server\n");
+ u->latency = 0;
+ }
+
+ /* Create stream */
+ assert(!u->write_data);
+ p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX);
+ *(p++) = ESD_PROTO_STREAM_PLAY;
+ *(p++) = u->format;
+ *(p++) = u->rate;
+ pa_strlcpy((char*) p, "Polypaudio Tunnel", ESD_NAME_MAX);
+
+ u->write_index = 0;
+ u->state = STATE_RUNNING;
+
+ /* Don't read any further */
+ pa_xfree(u->read_data);
+ u->read_data = NULL;
+ u->read_index = u->read_length = 0;
+
+ break;
+ }
+
+ default:
+ abort();
+ }
+
+ return 0;
+}
+
+static int do_read(struct userdata *u) {
+ assert(u);
+
+ if (!pa_iochannel_is_readable(u->io))
+ return 0;
+
+ if (u->state == STATE_AUTH || u->state == STATE_LATENCY) {
+ ssize_t r;
+
+ if (!u->read_data)
+ return 0;
+
+ assert(u->read_index < u->read_length);
+
+ if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) {
+ pa_log(__FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
+ cancel(u);
+ return -1;
+ }
+
+ u->read_index += r;
+ assert(u->read_index <= u->read_length);
+
+ if (u->read_index == u->read_length)
+ return handle_response(u);
+ }
+
+ return 0;
+}
+
+static void do_work(struct userdata *u) {
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer_event, 0);
+
+ if (do_read(u) < 0 || do_write(u) < 0)
+ cancel(u);
+}
+
+static void notify_cb(pa_sink*s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ if (pa_iochannel_is_writable(u->io))
+ u->core->mainloop->defer_enable(u->defer_event, 1);
+}
+
+static pa_usec_t get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ return
+ u->latency +
+ (u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0);
+}
+
+static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_work(u);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_work(u);
+}
+
+static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log(__FILE__": connection failed: %s\n", strerror(errno));
+ cancel(u);
+ return;
+ }
+
+ u->io = io;
+ pa_iochannel_set_callback(u->io, io_callback, u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ const char *p;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ if ((ss.format != PA_SAMPLE_U8 && ss.format != PA_SAMPLE_S16NE) ||
+ (ss.channels > 2)) {
+ pa_log(__FILE__": esound sample type support is limited to mono/stereo and U8 or S16NE sample data\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+ u->format =
+ (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) |
+ (ss.channels == 2 ? ESD_STEREO : ESD_MONO);
+ u->rate = ss.rate;
+ u->sink = NULL;
+ u->client = NULL;
+ u->io = NULL;
+ u->read_data = u->write_data = NULL;
+ u->read_index = u->write_index = u->read_length = u->write_length = 0;
+ u->state = STATE_AUTH;
+ u->latency = 0;
+
+ 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.\n");
+ goto fail;
+ }
+
+ if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", ESD_UNIX_SOCKET_NAME), ESD_DEFAULT_PORT))) {
+ pa_log(__FILE__": failed to connect to server.\n");
+ goto fail;
+ }
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+ /* Prepare the initial request */
+ u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t));
+ if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) {
+ pa_log(__FILE__": failed to load cookie\n");
+ goto fail;
+ }
+ *(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY;
+
+ /* Reserve space for the response */
+ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t));
+
+ u->sink->notify = notify_cb;
+ u->sink->get_latency = get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Esound sink '%s'", p);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+
+ u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
+ c->mainloop->defer_enable(u->defer_event, 0);
+
+
+ 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;
+
+ u->module = NULL;
+ cancel(u);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->client)
+ pa_socket_client_unref(u->client);
+
+ pa_xfree(u->read_data);
+ pa_xfree(u->write_data);
+
+ pa_xfree(u);
+}
+
+
+
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
new file mode 100644
index 000000000..ea8a2bd2b
--- /dev/null
+++ b/src/modules/module-lirc.c
@@ -0,0 +1,241 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <lirc/lirc_client.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/log.h>
+#include <polypcore/namereg.h>
+#include <polypcore/sink.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+
+#include "module-lirc-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("LIRC volume control")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>")
+
+static const char* const valid_modargs[] = {
+ "config",
+ "sink",
+ "appname",
+ NULL,
+};
+
+struct userdata {
+ int lirc_fd;
+ pa_io_event *io;
+ struct lirc_config *config;
+ char *sink_name;
+ pa_module *module;
+ float mute_toggle_save;
+};
+
+static int lirc_in_use = 0;
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+ char *name = NULL, *code = NULL;
+ assert(io);
+ assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log(__FILE__": lost connection to LIRC daemon.\n");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ char *c;
+
+ if (lirc_nextcode(&code) != 0 || !code) {
+ pa_log(__FILE__": lirc_nextcode() failed.\n");
+ goto fail;
+ }
+
+ c = pa_xstrdup(code);
+ c[strcspn(c, "\n\r")] = 0;
+ pa_log_debug(__FILE__": raw IR code '%s'\n", c);
+ pa_xfree(c);
+
+ while (lirc_code2char(u->config, code, &name) == 0 && name) {
+ enum { INVALID, UP, DOWN, MUTE, RESET, MUTE_TOGGLE } volchange = INVALID;
+
+ pa_log_info(__FILE__": translated IR code '%s'\n", name);
+
+ if (strcasecmp(name, "volume-up") == 0)
+ volchange = UP;
+ else if (strcasecmp(name, "volume-down") == 0)
+ volchange = DOWN;
+ else if (strcasecmp(name, "mute") == 0)
+ volchange = MUTE;
+ else if (strcasecmp(name, "mute-toggle") == 0)
+ volchange = MUTE_TOGGLE;
+ else if (strcasecmp(name, "reset") == 0)
+ volchange = RESET;
+
+ if (volchange == INVALID)
+ pa_log_warn(__FILE__": recieved unknown IR code '%s'\n", name);
+ else {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
+ else {
+ pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
+ pa_cvolume cv;
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ v += PA_VOLUME_NORM/20;
+ break;
+
+ case DOWN:
+ if (v > DELTA)
+ v -= DELTA;
+ else
+ v = PA_VOLUME_MUTED;
+
+ break;
+
+ case MUTE:
+ v = PA_VOLUME_MUTED;
+ break;
+
+ case RESET:
+ v = PA_VOLUME_NORM;
+ break;
+
+ case MUTE_TOGGLE: {
+
+ if (v > 0) {
+ u->mute_toggle_save = v;
+ v = PA_VOLUME_MUTED;
+ } else
+ v = u->mute_toggle_save;
+ }
+ default:
+ ;
+ }
+
+ pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
+ pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
+ }
+ }
+ }
+ }
+
+ free(code);
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+
+ free(code);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ assert(c && m);
+
+ if (lirc_in_use) {
+ pa_log(__FILE__": module-lirc may no be loaded twice.\n");
+ return -1;
+ }
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->module = m;
+ u->io = NULL;
+ u->config = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->lirc_fd = -1;
+ u->mute_toggle_save = 0;
+
+ if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "polypaudio"), 1)) < 0) {
+ pa_log(__FILE__": lirc_init() failed.\n");
+ goto fail;
+ }
+
+ if (lirc_readconfig((char*) pa_modargs_get_value(ma, "config", NULL), &u->config, NULL) < 0) {
+ pa_log(__FILE__": lirc_readconfig() failed.\n");
+ goto fail;
+ }
+
+ u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
+
+ lirc_in_use = 1;
+
+ 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);
+ assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->config)
+ lirc_freeconfig(u->config);
+
+ if (u->lirc_fd >= 0)
+ lirc_deinit();
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+
+ lirc_in_use = 0;
+}
diff --git a/src/modules/module-match.c b/src/modules/module-match.c
new file mode 100644
index 000000000..10ceb75ed
--- /dev/null
+++ b/src/modules/module-match.c
@@ -0,0 +1,235 @@
+/* $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 <config.h>
+#endif
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/sink-input.h>
+
+#include "module-match-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Sink input matching module")
+PA_MODULE_USAGE("table=<filename>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+#define WHITESPACE "\n\r \t"
+
+#ifndef DEFAULT_CONFIG_DIR
+#define DEFAULT_CONFIG_DIR "/etc/polypaudio"
+#endif
+
+#define DEFAULT_MATCH_TABLE_FILE DEFAULT_CONFIG_DIR"/match.table"
+#define DEFAULT_MATCH_TABLE_FILE_USER ".polypaudio/match.table"
+
+static const char* const valid_modargs[] = {
+ "table",
+ NULL,
+};
+
+struct rule {
+ regex_t regex;
+ pa_volume_t volume;
+ struct rule *next;
+};
+
+struct userdata {
+ struct rule *rules;
+ pa_subscription *subscription;
+};
+
+static int load_rules(struct userdata *u, const char *filename) {
+ FILE *f;
+ int n = 0;
+ int ret = -1;
+ struct rule *end = NULL;
+ char *fn = NULL;
+
+ f = filename ?
+ fopen(fn = pa_xstrdup(filename), "r") :
+ pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
+
+ if (!f) {
+ pa_log(__FILE__": failed to open file '%s': %s\n", fn, strerror(errno));
+ goto finish;
+ }
+
+ while (!feof(f)) {
+ char *d, *v;
+ pa_volume_t volume;
+ uint32_t k;
+ regex_t regex;
+ char ln[256];
+ struct rule *rule;
+
+ if (!fgets(ln, sizeof(ln), f))
+ break;
+
+ n++;
+
+ pa_strip_nl(ln);
+
+ if (ln[0] == '#' || !*ln )
+ continue;
+
+ d = ln+strcspn(ln, WHITESPACE);
+ v = d+strspn(d, WHITESPACE);
+
+
+ if (!*v) {
+ pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words\n", filename, n);
+ goto finish;
+ }
+
+ *d = 0;
+ if (pa_atou(v, &k) < 0) {
+ pa_log(__FILE__": [%s:%u] failed to parse volume\n", filename, n);
+ goto finish;
+ }
+
+ volume = (pa_volume_t) k;
+
+
+ if (regcomp(&regex, ln, REG_EXTENDED|REG_NOSUB) != 0) {
+ pa_log(__FILE__": [%s:%u] invalid regular expression\n", filename, n);
+ goto finish;
+ }
+
+ rule = pa_xmalloc(sizeof(struct rule));
+ rule->regex = regex;
+ rule->volume = volume;
+ rule->next = NULL;
+
+ if (end)
+ end->next = rule;
+ else
+ u->rules = rule;
+ end = rule;
+
+ *d = 0;
+ }
+
+ ret = 0;
+
+finish:
+ if (f)
+ fclose(f);
+
+ if (fn)
+ pa_xfree(fn);
+
+ return ret;
+}
+
+static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sink_input *si;
+ struct rule *r;
+ assert(c && u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
+ return;
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ if (!si->name)
+ return;
+
+ for (r = u->rules; r; r = r->next) {
+ if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
+ pa_cvolume cv;
+ pa_log_debug(__FILE__": changing volume of sink input '%s' to 0x%03x\n", si->name, r->volume);
+ pa_cvolume_set(&cv, r->volume, si->sample_spec.channels);
+ pa_sink_input_set_volume(si, &cv);
+ }
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->rules = NULL;
+ u->subscription = NULL;
+ m->userdata = u;
+
+ if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
+ goto fail;
+
+ u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata* u;
+ struct rule *r, *n;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ for (r = u->rules; r; r = n) {
+ n = r->next;
+
+ regfree(&r->regex);
+ pa_xfree(r);
+ }
+
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
new file mode 100644
index 000000000..b60f786d5
--- /dev/null
+++ b/src/modules/module-mmkbd-evdev.c
@@ -0,0 +1,250 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <linux/input.h>
+
+#include <polypcore/module.h>
+#include <polypcore/log.h>
+#include <polypcore/namereg.h>
+#include <polypcore/sink.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/modargs.h>
+#include <polypcore/util.h>
+
+#include "module-mmkbd-evdev-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("device=<evdev device> sink=<sink name>")
+
+#define DEFAULT_DEVICE "/dev/input/event0"
+
+/*
+ * This isn't defined in older kernel headers and there is no way of
+ * detecting it.
+ */
+struct _input_id {
+ __u16 bustype;
+ __u16 vendor;
+ __u16 product;
+ __u16 version;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "sink",
+ NULL,
+};
+
+struct userdata {
+ int fd;
+ pa_io_event *io;
+ char *sink_name;
+ pa_module *module;
+ float mute_toggle_save;
+};
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+ assert(io);
+ assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log(__FILE__": lost connection to evdev device.\n");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ struct input_event ev;
+
+ if (pa_loop_read(u->fd, &ev, sizeof(ev)) <= 0) {
+ pa_log(__FILE__": failed to read from event device: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
+ enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
+
+ pa_log_debug(__FILE__": key code=%u, value=%u\n", ev.code, ev.value);
+
+ switch (ev.code) {
+ case KEY_VOLUMEDOWN: volchange = DOWN; break;
+ case KEY_VOLUMEUP: volchange = UP; break;
+ case KEY_MUTE: volchange = MUTE_TOGGLE; break;
+ }
+
+ if (volchange != INVALID) {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
+ else {
+ pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
+ pa_cvolume cv;
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ v += DELTA;
+ break;
+
+ case DOWN:
+ if (v > DELTA)
+ v -= DELTA;
+ else
+ v = PA_VOLUME_MUTED;
+
+ break;
+
+ case MUTE_TOGGLE: {
+
+ if (v > 0) {
+ u->mute_toggle_save = v;
+ v = PA_VOLUME_MUTED;
+ } else
+ v = u->mute_toggle_save;
+ }
+ default:
+ ;
+ }
+
+ pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
+ pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
+ }
+ }
+ }
+ }
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ int version;
+ struct _input_id input_id;
+ char name[256];
+ uint8_t evtype_bitmask[EV_MAX/8 + 1];
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->module = m;
+ u->io = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->fd = -1;
+ u->mute_toggle_save = 0;
+
+ if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) {
+ pa_log(__FILE__": failed to open evdev device: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ioctl(u->fd, EVIOCGVERSION, &version) < 0) {
+ pa_log(__FILE__": EVIOCGVERSION failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev driver version %i.%i.%i\n", version >> 16, (version >> 8) & 0xff, version & 0xff);
+
+ if(ioctl(u->fd, EVIOCGID, &input_id)) {
+ pa_log(__FILE__": EVIOCGID failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev vendor 0x%04hx product 0x%04hx version 0x%04hx bustype %u\n",
+ input_id.vendor, input_id.product, input_id.version, input_id.bustype);
+
+ if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+ pa_log(__FILE__": EVIOCGNAME failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": evdev device name: %s\n", name);
+
+ memset(evtype_bitmask, 0, sizeof(evtype_bitmask));
+ if (ioctl(u->fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) {
+ pa_log(__FILE__": EVIOCGBIT failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (!test_bit(EV_KEY, evtype_bitmask)) {
+ pa_log(__FILE__": device has no keys.\n");
+ goto fail;
+ }
+
+ u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, 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);
+ assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->fd >= 0)
+ close(u->fd);
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c
new file mode 100644
index 000000000..abc531b31
--- /dev/null
+++ b/src/modules/module-native-protocol-fd.c
@@ -0,0 +1,85 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include <polypcore/module.h>
+#include <polypcore/iochannel.h>
+#include <polypcore/modargs.h>
+#include <polypcore/protocol-native.h>
+#include <polypcore/log.h>
+
+#include "module-native-protocol-fd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Native protocol autospawn helper")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ "fd",
+ "public",
+ "cookie",
+ NULL,
+};
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_iochannel *io;
+ pa_modargs *ma;
+ int fd, r = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto finish;
+ }
+
+ if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) {
+ pa_log(__FILE__": invalid file descriptor.\n");
+ goto finish;
+ }
+
+ io = pa_iochannel_new(c->mainloop, fd, fd);
+
+ if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) {
+ pa_iochannel_free(io);
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return r;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+ pa_protocol_native_free(m->userdata);
+}
diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
new file mode 100644
index 000000000..5731a4032
--- /dev/null
+++ b/src/modules/module-null-sink.c
@@ -0,0 +1,150 @@
+/* $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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-null-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Clocked NULL sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name of sink>")
+
+#define DEFAULT_SINK_NAME "null"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+ pa_time_event *time_event;
+ size_t block_size;
+};
+
+static const char* const valid_modargs[] = {
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ pa_memchunk chunk;
+ struct timeval ntv = *tv;
+ size_t l;
+
+ assert(u);
+
+ if (pa_sink_render(u->sink, u->block_size, &chunk) >= 0) {
+ l = chunk.length;
+ pa_memblock_unref(chunk.memblock);
+ } else
+ l = u->block_size;
+
+ pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec));
+ m->time_restart(e, &ntv);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ struct timeval tv;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification.\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+
+ 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.\n");
+ goto fail;
+ }
+
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("NULL sink");
+
+ pa_gettimeofday(&tv);
+ u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
+
+ u->block_size = pa_bytes_per_second(&ss) / 10;
+
+ 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;
+
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+
+ u->core->mainloop->time_free(u->time_event);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-oss-mmap.c b/src/modules/module-oss-mmap.c
new file mode 100644
index 000000000..6986b03c9
--- /dev/null
+++ b/src/modules/module-oss-mmap.c
@@ -0,0 +1,426 @@
+/* $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 <config.h>
+#endif
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/mman.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/oss-util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-oss-mmap-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("OSS Sink/Source (mmap)")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_core *core;
+ pa_sample_spec sample_spec;
+
+ size_t in_fragment_size, out_fragment_size, in_fragments, out_fragments, out_fill;
+
+ int fd;
+
+ void *in_mmap, *out_mmap;
+ size_t in_mmap_length, out_mmap_length;
+
+ pa_io_event *io_event;
+
+ pa_memblock **in_memblocks, **out_memblocks;
+ unsigned out_current, in_current;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "oss_output"
+#define DEFAULT_SOURCE_NAME "oss_input"
+#define DEFAULT_DEVICE "/dev/dsp"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void out_fill_memblocks(struct userdata *u, unsigned n) {
+ assert(u && u->out_memblocks);
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (u->out_memblocks[u->out_current])
+ pa_memblock_unref_fixed(u->out_memblocks[u->out_current]);
+
+ chunk.memblock = u->out_memblocks[u->out_current] = pa_memblock_new_fixed((uint8_t*)u->out_mmap+u->out_fragment_size*u->out_current, u->out_fragment_size, 1, u->core->memblock_stat);
+ assert(chunk.memblock);
+ chunk.length = chunk.memblock->length;
+ chunk.index = 0;
+
+ pa_sink_render_into_full(u->sink, &chunk);
+
+ u->out_current++;
+ while (u->out_current >= u->out_fragments)
+ u->out_current -= u->out_fragments;
+
+ n--;
+ }
+}
+
+static void do_write(struct userdata *u) {
+ struct count_info info;
+ assert(u && u->sink);
+
+ update_usage(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETOPTR: %s\n", strerror(errno));
+ return;
+ }
+
+ u->out_fill = (u->out_fragment_size * u->out_fragments) - (info.ptr % u->out_fragment_size);
+
+ if (!info.blocks)
+ return;
+
+ out_fill_memblocks(u, info.blocks);
+}
+
+static void in_post_memblocks(struct userdata *u, unsigned n) {
+ assert(u && u->in_memblocks);
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (!u->in_memblocks[u->in_current]) {
+ chunk.memblock = u->in_memblocks[u->in_current] = pa_memblock_new_fixed((uint8_t*) u->in_mmap+u->in_fragment_size*u->in_current, u->in_fragment_size, 1, u->core->memblock_stat);
+ chunk.length = chunk.memblock->length;
+ chunk.index = 0;
+
+ pa_source_post(u->source, &chunk);
+ }
+
+ u->in_current++;
+ while (u->in_current >= u->in_fragments)
+ u->in_current -= u->in_fragments;
+
+ n--;
+ }
+}
+
+static void in_clear_memblocks(struct userdata*u, unsigned n) {
+ unsigned i = u->in_current;
+ assert(u && u->in_memblocks);
+
+ if (n > u->in_fragments)
+ n = u->in_fragments;
+
+ while (n > 0) {
+ if (u->in_memblocks[i]) {
+ pa_memblock_unref_fixed(u->in_memblocks[i]);
+ u->in_memblocks[i] = NULL;
+ }
+
+ i++;
+ while (i >= u->in_fragments)
+ i -= u->in_fragments;
+
+ n--;
+ }
+}
+
+static void do_read(struct userdata *u) {
+ struct count_info info;
+ assert(u && u->source);
+
+ update_usage(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETIPTR: %s\n", strerror(errno));
+ return;
+ }
+
+ if (!info.blocks)
+ return;
+
+ in_post_memblocks(u, info.blocks);
+ in_clear_memblocks(u, u->in_fragments/2);
+}
+
+static void io_callback(pa_mainloop_api *m, pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t f, void *userdata) {
+ struct userdata *u = userdata;
+ assert (u && u->core->mainloop == m && u->io_event == e);
+
+ if (f & PA_IO_EVENT_INPUT)
+ do_read(u);
+ if (f & PA_IO_EVENT_OUTPUT)
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ do_write(u);
+ return pa_bytes_to_usec(u->out_fill, &s->sample_spec);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct audio_buf_info info;
+ struct userdata *u = NULL;
+ const char *p;
+ int nfrags, frag_size;
+ int mode, caps;
+ int enable_bits = 0, zero = 0;
+ int playback = 1, record = 1;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ m->userdata = u = pa_xmalloc0(sizeof(struct userdata));
+ u->module = m;
+ u->fd = -1;
+ u->core = c;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric arguments.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ nfrags = 12;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ u->sample_spec = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &u->sample_spec) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((u->fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0)
+ goto fail;
+
+ if (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_REALTIME) || !(caps & DSP_CAP_TRIGGER)) {
+ pa_log(__FILE__": OSS device not mmap capable.\n");
+ goto fail;
+ }
+
+ pa_log(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (nfrags >= 2 && frag_size >= 1)
+ if (pa_oss_set_fragments(u->fd, nfrags, frag_size) < 0)
+ goto fail;
+
+ if (pa_oss_auto_format(u->fd, &u->sample_spec) < 0)
+ goto fail;
+
+ if (mode != O_WRONLY) {
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETISPACE: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ u->in_mmap_length = (u->in_fragment_size = info.fragsize) * (u->in_fragments = info.fragstotal);
+
+ if ((u->in_mmap = mmap(NULL, u->in_mmap_length, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ if (mode == O_RDWR) {
+ pa_log(__FILE__": mmap failed for input. Changing to O_WRONLY mode.\n");
+ mode = O_WRONLY;
+ } else {
+ pa_log(__FILE__": mmap(): %s\n", strerror(errno));
+ goto fail;
+ }
+ } else {
+
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &u->sample_spec, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
+
+ u->in_memblocks = pa_xmalloc0(sizeof(pa_memblock *)*u->in_fragments);
+
+ enable_bits |= PCM_ENABLE_INPUT;
+ }
+ }
+
+ if (mode != O_RDONLY) {
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETOSPACE: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ u->out_mmap_length = (u->out_fragment_size = info.fragsize) * (u->out_fragments = info.fragstotal);
+
+ if ((u->out_mmap = mmap(NULL, u->out_mmap_length, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ if (mode == O_RDWR) {
+ pa_log(__FILE__": mmap filed for output. Changing to O_RDONLY mode.\n");
+ mode = O_RDONLY;
+ } else {
+ pa_log(__FILE__": mmap(): %s\n", strerror(errno));
+ goto fail;
+ }
+ } else {
+ pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec);
+
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &u->sample_spec, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
+
+ u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments);
+
+ enable_bits |= PCM_ENABLE_OUTPUT;
+ }
+ }
+
+ zero = 0;
+ if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ assert(u->source || u->sink);
+
+ u->io_event = c->mainloop->io_new(c->mainloop, u->fd, (u->source ? PA_IO_EVENT_INPUT : 0) | (u->sink ? PA_IO_EVENT_OUTPUT : 0), io_callback, u);
+ assert(u->io_event);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->out_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->out_fragments; i++)
+ if (u->out_memblocks[i])
+ pa_memblock_unref_fixed(u->out_memblocks[i]);
+ pa_xfree(u->out_memblocks);
+ }
+
+ if (u->in_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->in_fragments; i++)
+ if (u->in_memblocks[i])
+ pa_memblock_unref_fixed(u->in_memblocks[i]);
+ pa_xfree(u->in_memblocks);
+ }
+
+ if (u->in_mmap && u->in_mmap != MAP_FAILED)
+ munmap(u->in_mmap, u->in_mmap_length);
+
+ if (u->out_mmap && u->out_mmap != MAP_FAILED)
+ munmap(u->out_mmap, u->out_mmap_length);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->io_event)
+ u->core->mainloop->io_free(u->io_event);
+
+ if (u->fd >= 0)
+ close(u->fd);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c
new file mode 100644
index 000000000..04458419a
--- /dev/null
+++ b/src/modules/module-oss.c
@@ -0,0 +1,468 @@
+/* $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 <config.h>
+#endif
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/oss-util.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-oss-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("OSS Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_iochannel *io;
+ pa_core *core;
+
+ pa_memchunk memchunk, silence;
+
+ uint32_t in_fragment_size, out_fragment_size, sample_size;
+ int use_getospace, use_getispace;
+
+ int fd;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "oss_output"
+#define DEFAULT_SOURCE_NAME "oss_input"
+#define DEFAULT_DEVICE "/dev/dsp"
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u) {
+ pa_memchunk *memchunk;
+ ssize_t r;
+ size_t l;
+ int loop = 0;
+
+ assert(u);
+
+ if (!u->sink || !pa_iochannel_is_writable(u->io))
+ return;
+
+ update_usage(u);
+
+ l = u->out_fragment_size;
+
+ if (u->use_getospace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
+ u->use_getospace = 0;
+ else {
+ if (info.bytes/l > 0) {
+ l = (info.bytes/l)*l;
+ loop = 1;
+ }
+ }
+ }
+
+ do {
+ memchunk = &u->memchunk;
+
+ if (!memchunk->length)
+ if (pa_sink_render(u->sink, l, memchunk) < 0)
+ memchunk = &u->silence;
+
+ assert(memchunk->memblock);
+ assert(memchunk->memblock->data);
+ assert(memchunk->length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ break;
+ }
+
+ if (memchunk == &u->silence)
+ assert(r % u->sample_size == 0);
+ else {
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ l = l > (size_t) r ? l - r : 0;
+ } while (loop && l > 0);
+}
+
+static void do_read(struct userdata *u) {
+ pa_memchunk memchunk;
+ ssize_t r;
+ size_t l;
+ int loop = 0;
+ assert(u);
+
+ if (!u->source || !pa_iochannel_is_readable(u->io) || !pa_idxset_size(u->source->outputs))
+ return;
+
+ update_usage(u);
+
+ l = u->in_fragment_size;
+
+ if (u->use_getispace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0)
+ u->use_getispace = 0;
+ else {
+ if (info.bytes/l > 0) {
+ l = (info.bytes/l)*l;
+ loop = 1;
+ }
+ }
+ }
+
+ do {
+ memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
+ assert(memchunk.memblock);
+ if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ if (errno != EAGAIN)
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ break;
+ }
+
+ assert(r <= (ssize_t) memchunk.memblock->length);
+ memchunk.length = memchunk.memblock->length = r;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ l = l > (size_t) r ? l - r : 0;
+ } while (loop && l > 0);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+ do_read(u);
+}
+
+static void source_notify_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+ do_read(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ int arg;
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) {
+ pa_log_info(__FILE__": device doesn't support SNDCTL_DSP_GETODELAY: %s\n", strerror(errno));
+ s->get_latency = NULL;
+ return 0;
+ }
+
+ r += pa_bytes_to_usec(arg, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ audio_buf_info info;
+ assert(s && u && u->source);
+
+ if (!u->use_getispace)
+ return 0;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ u->use_getispace = 0;
+ return 0;
+ }
+
+ if (info.bytes <= 0)
+ return 0;
+
+ return pa_bytes_to_usec(info.bytes, &s->sample_spec);
+}
+
+static int sink_get_hw_volume(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+
+ if (ioctl(u->fd, SOUND_MIXER_READ_PCM, &vol) < 0) {
+ pa_log_info(__FILE__": device doesn't support reading mixer settings: %s\n", strerror(errno));
+ s->get_hw_volume = NULL;
+ return -1;
+ }
+
+ s->hw_volume.values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ if ((s->hw_volume.channels = s->sample_spec.channels) >= 2)
+ s->hw_volume.values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ pa_log_info(__FILE__": Read mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
+ return 0;
+}
+
+static int sink_set_hw_volume(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+
+ vol = (s->hw_volume.values[0]*100)/PA_VOLUME_NORM;
+
+ if (s->sample_spec.channels >= 2)
+ vol |= ((s->hw_volume.values[1]*100)/PA_VOLUME_NORM) << 8;
+
+ if (ioctl(u->fd, SOUND_MIXER_WRITE_PCM, &vol) < 0) {
+ pa_log_info(__FILE__": device doesn't support writing mixer settings: %s\n", strerror(errno));
+ s->set_hw_volume = NULL;
+ return -1;
+ }
+
+ pa_log_info(__FILE__": Wrote mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct audio_buf_info info;
+ struct userdata *u = NULL;
+ const char *p;
+ int fd = -1;
+ int nfrags, frag_size, in_frag_size, out_frag_size;
+ int mode;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ nfrags = 12;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, NULL)) < 0)
+ goto fail;
+
+ pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (nfrags >= 2 && frag_size >= 1)
+ if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0)
+ goto fail;
+
+ if (pa_oss_auto_format(fd, &ss) < 0)
+ goto fail;
+
+ if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) {
+ pa_log(__FILE__": SNDCTL_DSP_GETBLKSIZE: %s\n", strerror(errno));
+ goto fail;
+ }
+ assert(frag_size);
+ in_frag_size = out_frag_size = frag_size;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->use_getospace = u->use_getispace = 0;
+
+ if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
+ pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ in_frag_size = info.fragsize;
+ u->use_getispace = 1;
+ }
+
+ if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
+ pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
+ out_frag_size = info.fragsize;
+ u->use_getospace = 1;
+ }
+
+ if (mode != O_WRONLY) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->notify = source_notify_cb;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
+ } else
+ u->source = NULL;
+
+ if (mode != O_RDONLY) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->get_hw_volume = sink_get_hw_volume;
+ u->sink->set_hw_volume = sink_set_hw_volume;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : -1);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+ u->fd = fd;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+ u->sample_size = pa_frame_size(&ss);
+
+ u->out_fragment_size = out_frag_size;
+ u->in_fragment_size = in_frag_size;
+ u->silence.memblock = pa_memblock_new(u->silence.length = u->out_fragment_size, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ /*
+ * Some crappy drivers do not start the recording until we read something.
+ * Without this snippet, poll will never register the fd as ready.
+ */
+ if (u->source) {
+ char buf[u->sample_size];
+ read(u->fd, buf, u->sample_size);
+ }
+
+ /* Read mixer settings */
+ if (u->sink)
+ sink_get_hw_volume(u->sink);
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ pa_iochannel_free(u->io);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
new file mode 100644
index 000000000..6ace377f0
--- /dev/null
+++ b/src/modules/module-pipe-sink.c
@@ -0,0 +1,237 @@
+/* $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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-pipe-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("UNIX pipe sink")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_FIFO_NAME "/tmp/music.output"
+#define DEFAULT_SINK_NAME "fifo_output"
+
+struct userdata {
+ pa_core *core;
+
+ char *filename;
+
+ pa_sink *sink;
+ pa_iochannel *io;
+ pa_defer_event *defer_event;
+
+ pa_memchunk memchunk;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+static void do_write(struct userdata *u) {
+ ssize_t r;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer_event, 0);
+
+ if (!pa_iochannel_is_writable(u->io))
+ return;
+
+ pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
+
+ if (!u->memchunk.length)
+ if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0)
+ return;
+
+ assert(u->memchunk.memblock && u->memchunk.length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+}
+
+static void notify_cb(pa_sink*s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ if (pa_iochannel_is_writable(u->io))
+ u->core->mainloop->defer_enable(u->defer_event, 1);
+}
+
+static pa_usec_t get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(s && u);
+
+ return u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0;
+}
+
+static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ struct stat st;
+ const char *p;
+ int fd = -1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
+
+ if ((fd = open(p, O_RDWR)) < 0) {
+ pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ pa_fd_set_cloexec(fd, 1);
+
+ if (fstat(fd, &st) < 0) {
+ pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log(__FILE__": '%s' is not a FIFO.\n", p);
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+ u->filename = pa_xstrdup(p);
+ u->core = c;
+ u->module = m;
+ m->userdata = u;
+
+ 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.\n");
+ goto fail;
+ }
+ u->sink->notify = notify_cb;
+ u->sink->get_latency = get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Unix FIFO sink '%s'", p);
+ assert(u->sink->description);
+
+ u->io = pa_iochannel_new(c->mainloop, -1, fd);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+
+ u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
+ assert(u->defer_event);
+ c->mainloop->defer_enable(u->defer_event, 0);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ close(fd);
+
+ 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->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ pa_iochannel_free(u->io);
+ u->core->mainloop->defer_free(u->defer_event);
+
+ assert(u->filename);
+ unlink(u->filename);
+ pa_xfree(u->filename);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
new file mode 100644
index 000000000..a7bb0ce7d
--- /dev/null
+++ b/src/modules/module-pipe-source.c
@@ -0,0 +1,210 @@
+/* $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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-pipe-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("UNIX pipe source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("source_name=<name for the source> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
+
+#define DEFAULT_FIFO_NAME "/tmp/music.input"
+#define DEFAULT_SOURCE_NAME "fifo_input"
+
+struct userdata {
+ pa_core *core;
+
+ char *filename;
+
+ pa_source *source;
+ pa_iochannel *io;
+ pa_module *module;
+ pa_memchunk chunk;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "channels",
+ "format",
+ "source_name",
+ NULL
+};
+
+static void do_read(struct userdata *u) {
+ ssize_t r;
+ pa_memchunk chunk;
+ assert(u);
+
+ if (!pa_iochannel_is_readable(u->io))
+ return;
+
+ pa_module_set_used(u->module, pa_idxset_size(u->source->outputs));
+
+ if (!u->chunk.memblock) {
+ u->chunk.memblock = pa_memblock_new(1024, u->core->memblock_stat);
+ u->chunk.index = chunk.length = 0;
+ }
+
+ assert(u->chunk.memblock && u->chunk.memblock->length > u->chunk.index);
+ if ((r = pa_iochannel_read(u->io, (uint8_t*) u->chunk.memblock->data + u->chunk.index, u->chunk.memblock->length - u->chunk.index)) <= 0) {
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ u->chunk.length = r;
+ pa_source_post(u->source, &u->chunk);
+ u->chunk.index += r;
+
+ if (u->chunk.index >= u->chunk.memblock->length) {
+ u->chunk.index = u->chunk.length = 0;
+ pa_memblock_unref(u->chunk.memblock);
+ u->chunk.memblock = NULL;
+ }
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_read(u);
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ struct stat st;
+ const char *p;
+ int fd = -1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
+
+ if ((fd = open(p, O_RDWR)) < 0) {
+ pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ pa_fd_set_cloexec(fd, 1);
+
+ if (fstat(fd, &st) < 0) {
+ pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log(__FILE__": '%s' is not a FIFO.\n", p);
+ goto fail;
+ }
+
+ u = pa_xmalloc0(sizeof(struct userdata));
+
+ u->filename = pa_xstrdup(p);
+ u->core = c;
+
+ if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create source.\n");
+ goto fail;
+ }
+ u->source->userdata = u;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Unix FIFO source '%s'", p);
+ assert(u->source->description);
+
+ u->io = pa_iochannel_new(c->mainloop, fd, -1);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+
+ u->chunk.memblock = NULL;
+ u->chunk.index = u->chunk.length = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ close(fd);
+
+ 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->chunk.memblock)
+ pa_memblock_unref(u->chunk.memblock);
+
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ pa_iochannel_free(u->io);
+
+ assert(u->filename);
+ unlink(u->filename);
+ pa_xfree(u->filename);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c
new file mode 100644
index 000000000..469ddceea
--- /dev/null
+++ b/src/modules/module-protocol-stub.c
@@ -0,0 +1,261 @@
+/* $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 <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <polypcore/winsock.h>
+
+#include <polypcore/module.h>
+#include <polypcore/socket-server.h>
+#include <polypcore/socket-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/native-common.h>
+
+#ifdef USE_TCP_SOCKETS
+#define SOCKET_DESCRIPTION "(TCP sockets)"
+#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
+#elif defined(USE_TCP6_SOCKETS)
+#define SOCKET_DESCRIPTION "(TCP/IPv6 sockets)"
+#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
+#else
+#define SOCKET_DESCRIPTION "(UNIX sockets)"
+#define SOCKET_USAGE "socket=<path to UNIX socket>"
+#endif
+
+#if defined(USE_PROTOCOL_SIMPLE)
+ #include <polypcore/protocol-simple.h>
+ #define protocol_new pa_protocol_simple_new
+ #define protocol_free pa_protocol_simple_free
+ #define TCPWRAP_SERVICE "polypaudio-simple"
+ #define IPV4_PORT 4711
+ #define UNIX_SOCKET "simple"
+ #define MODULE_ARGUMENTS "rate", "format", "channels", "sink", "source", "playback", "record",
+ #if defined(USE_TCP_SOCKETS)
+ #include "module-simple-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-simple-protocol-tcp6-symdef.h"
+ #else
+ #include "module-simple-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("rate=<sample rate> format=<sample format> channels=<number of channels> sink=<sink to connect to> source=<source to connect to> playback=<enable playback?> record=<enable record?> "SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_CLI)
+ #include <polypcore/protocol-cli.h>
+ #define protocol_new pa_protocol_cli_new
+ #define protocol_free pa_protocol_cli_free
+ #define TCPWRAP_SERVICE "polypaudio-cli"
+ #define IPV4_PORT 4712
+ #define UNIX_SOCKET "cli"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-cli-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-cli-protocol-tcp6-symdef.h"
+ #else
+ #include "module-cli-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE(SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_HTTP)
+ #include <polypcore/protocol-http.h>
+ #define protocol_new pa_protocol_http_new
+ #define protocol_free pa_protocol_http_free
+ #define TCPWRAP_SERVICE "polypaudio-http"
+ #define IPV4_PORT 4714
+ #define UNIX_SOCKET "http"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-http-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-http-protocol-tcp6-symdef.h"
+ #else
+ #include "module-http-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE(SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_NATIVE)
+ #include <polypcore/protocol-native.h>
+ #define protocol_new pa_protocol_native_new
+ #define protocol_free pa_protocol_native_free
+ #define TCPWRAP_SERVICE "polypaudio-native"
+ #define IPV4_PORT PA_NATIVE_DEFAULT_PORT
+ #define UNIX_SOCKET PA_NATIVE_DEFAULT_UNIX_SOCKET
+ #define MODULE_ARGUMENTS "public", "cookie",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-native-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-native-protocol-tcp6-symdef.h"
+ #else
+ #include "module-native-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
+#elif defined(USE_PROTOCOL_ESOUND)
+ #include <polypcore/protocol-esound.h>
+ #include <polypcore/esound.h>
+ #define protocol_new pa_protocol_esound_new
+ #define protocol_free pa_protocol_esound_free
+ #define TCPWRAP_SERVICE "esound"
+ #define IPV4_PORT ESD_DEFAULT_PORT
+ #define UNIX_SOCKET ESD_UNIX_SOCKET_NAME
+ #define MODULE_ARGUMENTS "sink", "source", "public", "cookie",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-esound-protocol-tcp-symdef.h"
+ #elif defined(USE_TCP6_SOCKETS)
+ #include "module-esound-protocol-tcp6-symdef.h"
+ #else
+ #include "module-esound-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION)
+ PA_MODULE_USAGE("sink=<sink to connect to> source=<source to connect to> public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
+#else
+ #error "Broken build system"
+#endif
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+static const char* const valid_modargs[] = {
+ MODULE_ARGUMENTS
+#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
+ "port",
+ "loopback",
+#else
+ "socket",
+#endif
+ NULL
+};
+
+static pa_socket_server *create_socket_server(pa_core *c, pa_modargs *ma) {
+ pa_socket_server *s;
+#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
+ int loopback = 1;
+ uint32_t port = IPV4_PORT;
+
+ if (pa_modargs_get_value_boolean(ma, "loopback", &loopback) < 0) {
+ pa_log(__FILE__": loopback= expects a boolean argument.\n");
+ return NULL;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) {
+ pa_log(__FILE__": port= expects a numerical argument between 1 and 65535.\n");
+ return NULL;
+ }
+
+#ifdef USE_TCP6_SOCKETS
+ if (!(s = pa_socket_server_new_ipv6(c->mainloop, loopback ? (const uint8_t*) &in6addr_loopback : (const uint8_t*) &in6addr_any, port)))
+ return NULL;
+#else
+ if (!(s = pa_socket_server_new_ipv4(c->mainloop, loopback ? INADDR_LOOPBACK : INADDR_ANY, port, TCPWRAP_SERVICE)))
+ return NULL;
+#endif
+
+#else
+ int r;
+ const char *v;
+ char tmp[PATH_MAX];
+
+ v = pa_modargs_get_value(ma, "socket", UNIX_SOCKET);
+ assert(v);
+
+ pa_runtime_path(v, tmp, sizeof(tmp));
+
+ if (pa_make_secure_parent_dir(tmp) < 0) {
+ pa_log(__FILE__": Failed to create secure socket directory.\n");
+ return NULL;
+ }
+
+ if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
+ pa_log(__FILE__": Failed to remove stale UNIX socket '%s': %s\n", tmp, strerror(errno));
+ return NULL;
+ }
+
+ if (r)
+ pa_log(__FILE__": Removed stale UNIX socket '%s'.", tmp);
+
+ if (!(s = pa_socket_server_new_unix(c->mainloop, tmp)))
+ return NULL;
+
+#endif
+ return s;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_socket_server *s;
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto finish;
+ }
+
+ if (!(s = create_socket_server(c, ma)))
+ goto finish;
+
+ if (!(m->userdata = protocol_new(c, s, m, ma))) {
+ pa_socket_server_unref(s);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ assert(c && m);
+
+#if defined(USE_PROTOCOL_ESOUND)
+ if (remove (ESD_UNIX_SOCKET_NAME) != 0)
+ pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_NAME, strerror (errno));
+ if (remove (ESD_UNIX_SOCKET_DIR) != 0)
+ pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_DIR, strerror (errno));
+#endif
+
+ protocol_free(m->userdata);
+}
diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c
new file mode 100644
index 000000000..446e39749
--- /dev/null
+++ b/src/modules/module-sine.c
@@ -0,0 +1,182 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+
+#include <polypcore/sink-input.h>
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+
+#include "module-sine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("Sine wave generator")
+PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink_input *sink_input;
+ pa_memblock *memblock;
+ size_t peek_index;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "frequency",
+ NULL,
+};
+
+static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) {
+ struct userdata *u;
+ assert(i && chunk && i->userdata);
+ u = i->userdata;
+
+ chunk->memblock = pa_memblock_ref(u->memblock);
+ chunk->index = u->peek_index;
+ chunk->length = u->memblock->length - u->peek_index;
+ return 0;
+}
+
+static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
+ struct userdata *u;
+ assert(i && chunk && length && i->userdata);
+ u = i->userdata;
+
+ assert(chunk->memblock == u->memblock && length <= u->memblock->length-u->peek_index);
+
+ u->peek_index += length;
+
+ if (u->peek_index >= u->memblock->length)
+ u->peek_index = 0;
+}
+
+static void sink_input_kill(pa_sink_input *i) {
+ struct userdata *u;
+ assert(i && i->userdata);
+ u = i->userdata;
+
+ pa_sink_input_disconnect(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+static void calc_sine(float *f, size_t l, float freq) {
+ size_t i;
+
+ l /= sizeof(float);
+
+ for (i = 0; i < l; i++)
+ f[i] = (float) sin((double) i/l*M_PI*2*freq)/2;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ pa_sink *sink;
+ const char *sink_name;
+ pa_sample_spec ss;
+ uint32_t frequency;
+ char t[256];
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": Failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->module = m;
+ u->sink_input = NULL;
+ u->memblock = NULL;
+
+ sink_name = pa_modargs_get_value(ma, "sink", NULL);
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": No such sink.\n");
+ goto fail;
+ }
+
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = sink->sample_spec.rate;
+ ss.channels = 1;
+
+ frequency = 440;
+ if (pa_modargs_get_value_u32(ma, "frequency", &frequency) < 0 || frequency < 1 || frequency > ss.rate/2) {
+ pa_log(__FILE__": Invalid frequency specification\n");
+ goto fail;
+ }
+
+ u->memblock = pa_memblock_new(pa_bytes_per_second(&ss), c->memblock_stat);
+ calc_sine(u->memblock->data, u->memblock->length, frequency);
+
+ snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
+ if (!(u->sink_input = pa_sink_input_new(sink, __FILE__, t, &ss, NULL, 0, -1)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek;
+ u->sink_input->drop = sink_input_drop;
+ u->sink_input->kill = sink_input_kill;
+ u->sink_input->userdata = u;
+ u->sink_input->owner = m;
+
+ u->peek_index = 0;
+
+ 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 = m->userdata;
+ assert(c && m);
+
+ if (!u)
+ return;
+
+ if (u->sink_input) {
+ pa_sink_input_disconnect(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ }
+
+ if (u->memblock)
+ pa_memblock_unref(u->memblock);
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
new file mode 100644
index 000000000..e0745fc0d
--- /dev/null
+++ b/src/modules/module-solaris.c
@@ -0,0 +1,488 @@
+/* $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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <signal.h>
+#include <stropts.h>
+#include <sys/conf.h>
+#include <sys/audio.h>
+
+#include <polyp/mainloop-signal.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+
+#include "module-solaris-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Solaris Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> buffer_size=<record buffer size>")
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_iochannel *io;
+ pa_core *core;
+ pa_signal_event *sig;
+
+ pa_memchunk memchunk, silence;
+
+ uint32_t sample_size;
+ uint32_t buffer_size;
+ unsigned int written_bytes, read_bytes;
+
+ int fd;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "buffer_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "solaris_output"
+#define DEFAULT_SOURCE_NAME "solaris_input"
+#define DEFAULT_DEVICE "/dev/audio"
+
+#define CHUNK_SIZE 2048
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u) {
+ audio_info_t info;
+ int err;
+ pa_memchunk *memchunk;
+ size_t len;
+ ssize_t r;
+
+ assert(u);
+
+ if (!u->sink || !pa_iochannel_is_writable(u->io))
+ return;
+
+ update_usage(u);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ /*
+ * Since we cannot modify the size of the output buffer we fake it
+ * by not filling it more than u->buffer_size.
+ */
+ len = u->buffer_size;
+ len -= u->written_bytes - (info.play.samples * u->sample_size);
+
+ /*
+ * Do not fill more than half the buffer in one chunk since we only
+ * get notifications upon completion of entire chunks.
+ */
+ if (len > (u->buffer_size / 2))
+ len = u->buffer_size / 2;
+
+ if (len < u->sample_size)
+ return;
+
+ memchunk = &u->memchunk;
+
+ if (!memchunk->length)
+ if (pa_sink_render(u->sink, len, memchunk) < 0)
+ memchunk = &u->silence;
+
+ assert(memchunk->memblock);
+ assert(memchunk->memblock->data);
+ assert(memchunk->length);
+
+ if (memchunk->length < len)
+ len = memchunk->length;
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, len)) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ if (memchunk == &u->silence)
+ assert(r % u->sample_size == 0);
+ else {
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ }
+ }
+
+ u->written_bytes += r;
+
+ /*
+ * Write 0 bytes which will generate a SIGPOLL when "played".
+ */
+ if (write(u->fd, NULL, 0) < 0) {
+ pa_log(__FILE__": write() failed: %s\n", strerror(errno));
+ return;
+ }
+}
+
+static void do_read(struct userdata *u) {
+ pa_memchunk memchunk;
+ int err, l;
+ ssize_t r;
+ assert(u);
+
+ if (!u->source || !pa_iochannel_is_readable(u->io))
+ return;
+
+ update_usage(u);
+
+ err = ioctl(u->fd, I_NREAD, &l);
+ assert(err >= 0);
+
+ memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
+ assert(memchunk.memblock);
+ if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ if (errno != EAGAIN)
+ pa_log(__FILE__": read() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ assert(r <= (ssize_t) memchunk.memblock->length);
+ memchunk.length = memchunk.memblock->length = r;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ u->read_bytes += r;
+}
+
+static void io_callback(pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+ do_read(u);
+}
+
+void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u);
+ do_write(u);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ pa_usec_t r = 0;
+ audio_info_t info;
+ int err;
+ struct userdata *u = s->userdata;
+ assert(s && u && u->sink);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec);
+ r -= pa_bytes_to_usec(info.play.samples * u->sample_size, &s->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
+
+ return r;
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ audio_info_t info;
+ int err;
+ assert(s && u && u->source);
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ r += pa_bytes_to_usec(info.record.samples * u->sample_size, &s->sample_spec);
+ r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
+
+ return r;
+}
+
+static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ if (mode != O_RDONLY) {
+ info.play.sample_rate = ss->rate;
+ info.play.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (mode != O_WRONLY) {
+ info.record.sample_rate = ss->rate;
+ info.record.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.record.precision = 16;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log(__FILE__": AUDIO_SETINFO: Unsupported sample format.\n");
+ else
+ pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_solaris_set_buffer(int fd, int buffer_size) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ info.record.buffer_size = buffer_size;
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log(__FILE__": AUDIO_SETINFO: Unsupported buffer size.\n");
+ else
+ pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ const char *p;
+ int fd = -1;
+ int buffer_size;
+ int mode;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect numeric argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ buffer_size = 16384;
+ if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) {
+ pa_log(__FILE__": failed to parse buffer size argument\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0)
+ goto fail;
+
+ pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (pa_solaris_auto_format(fd, mode, &ss) < 0)
+ goto fail;
+
+ if ((mode != O_WRONLY) && (buffer_size >= 1))
+ if (pa_solaris_set_buffer(fd, buffer_size) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+
+ if (mode != O_WRONLY) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
+ } else
+ u->source = NULL;
+
+ if (mode != O_RDONLY) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0);
+ assert(u->io);
+ pa_iochannel_set_callback(u->io, io_callback, u);
+ u->fd = fd;
+
+ u->memchunk.memblock = NULL;
+ u->memchunk.length = 0;
+ u->sample_size = pa_frame_size(&ss);
+ u->buffer_size = buffer_size;
+
+ u->silence.memblock = pa_memblock_new(u->silence.length = CHUNK_SIZE, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->written_bytes = 0;
+ u->read_bytes = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
+ assert(u->sig);
+ ioctl(u->fd, I_SETSIG, S_MSG);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ ioctl(u->fd, I_SETSIG, 0);
+ pa_signal_free(u->sig);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+ if (u->silence.memblock)
+ pa_memblock_unref(u->silence.memblock);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ pa_iochannel_free(u->io);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
new file mode 100644
index 000000000..5ee10fda9
--- /dev/null
+++ b/src/modules/module-tunnel.c
@@ -0,0 +1,688 @@
+/* $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 <config.h>
+#endif
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <polypcore/module.h>
+#include <polypcore/util.h>
+#include <polypcore/modargs.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/sink-input.h>
+#include <polypcore/pdispatch.h>
+#include <polypcore/pstream.h>
+#include <polypcore/pstream-util.h>
+#include <polypcore/authkey.h>
+#include <polypcore/socket-client.h>
+#include <polypcore/socket-util.h>
+#include <polypcore/authkey-prop.h>
+
+#ifdef TUNNEL_SINK
+#include "module-tunnel-sink-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sinks")
+PA_MODULE_USAGE("server=<address> sink=<remote sink name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name for the local sink>")
+#else
+#include "module-tunnel-source-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sources")
+PA_MODULE_USAGE("server=<address> source=<remote source name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> source_name=<name for the local source>")
+#endif
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+
+#define DEFAULT_SINK_NAME "tunnel"
+#define DEFAULT_SOURCE_NAME "tunnel"
+
+#define DEFAULT_TLENGTH (44100*2*2/10) //(10240*8)
+#define DEFAULT_MAXLENGTH ((DEFAULT_TLENGTH*3)/2)
+#define DEFAULT_MINREQ 512
+#define DEFAULT_PREBUF (DEFAULT_TLENGTH-DEFAULT_MINREQ)
+#define DEFAULT_FRAGSIZE 1024
+
+#define DEFAULT_TIMEOUT 5
+
+#define LATENCY_INTERVAL 10
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "format",
+ "channels",
+ "rate",
+#ifdef TUNNEL_SINK
+ "sink_name",
+ "sink",
+#else
+ "source_name",
+ "source",
+#endif
+ NULL,
+};
+
+static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+#ifdef TUNNEL_SINK
+static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+#endif
+
+static const pa_pdispatch_callback command_table[PA_COMMAND_MAX] = {
+#ifdef TUNNEL_SINK
+ [PA_COMMAND_REQUEST] = command_request,
+#endif
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed,
+ [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed
+};
+
+struct userdata {
+ pa_socket_client *client;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+
+ char *server_name;
+#ifdef TUNNEL_SINK
+ char *sink_name;
+ pa_sink *sink;
+ uint32_t requested_bytes;
+#else
+ char *source_name;
+ pa_source *source;
+#endif
+
+ pa_module *module;
+ pa_core *core;
+
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ uint32_t ctag;
+ uint32_t device_index;
+ uint32_t channel;
+
+ pa_usec_t host_latency;
+
+ pa_time_event *time_event;
+
+ int auth_cookie_in_property;
+};
+
+static void close_stuff(struct userdata *u) {
+ assert(u);
+
+ if (u->pstream) {
+ pa_pstream_close(u->pstream);
+ pa_pstream_unref(u->pstream);
+ u->pstream = NULL;
+ }
+
+ if (u->pdispatch) {
+ pa_pdispatch_unref(u->pdispatch);
+ u->pdispatch = NULL;
+ }
+
+ if (u->client) {
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+ }
+
+#ifdef TUNNEL_SINK
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+ }
+#else
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ u->source = NULL;
+ }
+#endif
+
+ if (u->time_event) {
+ u->core->mainloop->time_free(u->time_event);
+ u->time_event = NULL;
+ }
+}
+
+static void die(struct userdata *u) {
+ assert(u);
+ close_stuff(u);
+ pa_module_unload_request(u->module);
+}
+
+static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ assert(pd && t && u && u->pdispatch == pd);
+
+ pa_log(__FILE__": stream killed\n");
+ die(u);
+}
+
+#ifdef TUNNEL_SINK
+static void send_prebuf_request(struct userdata *u) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_PREBUF_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+ pa_pstream_send_tagstruct(u->pstream, t);
+}
+
+static void send_bytes(struct userdata *u) {
+ assert(u);
+
+ if (!u->pstream)
+ return;
+
+ while (u->requested_bytes > 0) {
+ pa_memchunk chunk;
+ if (pa_sink_render(u->sink, u->requested_bytes, &chunk) < 0) {
+
+
+ if (u->requested_bytes >= DEFAULT_TLENGTH-DEFAULT_PREBUF)
+ send_prebuf_request(u);
+
+ return;
+ }
+
+ pa_pstream_send_memblock(u->pstream, u->channel, 0, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ if (chunk.length > u->requested_bytes)
+ u->requested_bytes = 0;
+ else
+ u->requested_bytes -= chunk.length;
+ }
+}
+
+static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t bytes, channel;
+ assert(pd && command == PA_COMMAND_REQUEST && t && u && u->pdispatch == pd);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &bytes) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid protocol reply\n");
+ die(u);
+ return;
+ }
+
+ if (channel != u->channel) {
+ pa_log(__FILE__": recieved data for invalid channel\n");
+ die(u);
+ return;
+ }
+
+ u->requested_bytes += bytes;
+ send_bytes(u);
+}
+
+#endif
+
+static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_usec_t buffer_usec, sink_usec, source_usec, transport_usec;
+ int playing;
+ uint32_t queue_length;
+ uint64_t counter;
+ struct timeval local, remote, now;
+ assert(pd && u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to get latency.\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+
+ if (pa_tagstruct_get_usec(t, &buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &source_usec) < 0 ||
+ pa_tagstruct_get_boolean(t, &playing) < 0 ||
+ pa_tagstruct_getu32(t, &queue_length) < 0 ||
+ pa_tagstruct_get_timeval(t, &local) < 0 ||
+ pa_tagstruct_get_timeval(t, &remote) < 0 ||
+ pa_tagstruct_getu64(t, &counter) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid reply.\n");
+ die(u);
+ return;
+ }
+
+ pa_gettimeofday(&now);
+
+ if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) {
+ /* local and remote seem to have synchronized clocks */
+#ifdef TUNNEL_SINK
+ transport_usec = pa_timeval_diff(&remote, &local);
+#else
+ transport_usec = pa_timeval_diff(&now, &remote);
+#endif
+ } else
+ transport_usec = pa_timeval_diff(&now, &local)/2;
+
+#ifdef TUNNEL_SINK
+ u->host_latency = sink_usec + transport_usec;
+#else
+ u->host_latency = source_usec + transport_usec;
+ if (u->host_latency > sink_usec)
+ u->host_latency -= sink_usec;
+ else
+ u->host_latency = 0;
+#endif
+
+/* pa_log(__FILE__": estimated host latency: %0.0f usec\n", (double) u->host_latency); */
+}
+
+static void request_latency(struct userdata *u) {
+ pa_tagstruct *t;
+ struct timeval now;
+ uint32_t tag;
+ assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY);
+#endif
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+
+ pa_gettimeofday(&now);
+ pa_tagstruct_put_timeval(t, &now);
+ pa_tagstruct_putu64(t, 0);
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u);
+}
+
+static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ assert(pd && u && u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to create stream.\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+
+ if (pa_tagstruct_getu32(t, &u->channel) < 0 ||
+ pa_tagstruct_getu32(t, &u->device_index) < 0 ||
+#ifdef TUNNEL_SINK
+ pa_tagstruct_getu32(t, &u->requested_bytes) < 0 ||
+#endif
+ !pa_tagstruct_eof(t)) {
+ pa_log(__FILE__": invalid reply.\n");
+ die(u);
+ return;
+ }
+
+ request_latency(u);
+#ifdef TUNNEL_SINK
+ send_bytes(u);
+#endif
+}
+
+static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *reply;
+ char name[256], un[128], hn[128];
+ assert(pd && u && u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY || !pa_tagstruct_eof(t)) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log(__FILE__": failed to authenticate\n");
+ else
+ pa_log(__FILE__": protocol error.\n");
+ die(u);
+ return;
+ }
+#ifdef TUNNEL_SINK
+ snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', sink '%s'",
+ pa_get_host_name(hn, sizeof(hn)),
+ pa_get_user_name(un, sizeof(un)),
+ u->sink->name);
+#else
+ snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', source '%s'",
+ pa_get_host_name(hn, sizeof(hn)),
+ pa_get_user_name(un, sizeof(un)),
+ u->source->name);
+#endif
+
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ /* We ignore the server's reply here */
+
+ reply = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->sink_name);
+ pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
+ pa_tagstruct_put_boolean(reply, 0);
+ pa_tagstruct_putu32(reply, DEFAULT_TLENGTH);
+ pa_tagstruct_putu32(reply, DEFAULT_PREBUF);
+ pa_tagstruct_putu32(reply, DEFAULT_MINREQ);
+ pa_tagstruct_putu32(reply, PA_VOLUME_NORM);
+#else
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->source_name);
+ pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
+ pa_tagstruct_put_boolean(reply, 0);
+ pa_tagstruct_putu32(reply, DEFAULT_FRAGSIZE);
+#endif
+
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u);
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && u);
+
+ pa_log(__FILE__": stream died.\n");
+ die(u);
+}
+
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && packet && u);
+
+ if (pa_pdispatch_run(u->pdispatch, packet, u) < 0) {
+ pa_log(__FILE__": invalid packet\n");
+ die(u);
+ }
+}
+
+#ifndef TUNNEL_SINK
+static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, uint32_t delta, const pa_memchunk *chunk, void *userdata) {
+ struct userdata *u = userdata;
+ assert(p && chunk && u);
+
+ if (channel != u->channel) {
+ pa_log(__FILE__": recieved memory block on bad channel.\n");
+ die(u);
+ return;
+ }
+
+ pa_source_post(u->source, chunk);
+}
+#endif
+
+static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *t;
+ uint32_t tag;
+ assert(sc && u && u->client == sc);
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log(__FILE__": connection failed.\n");
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->memblock_stat);
+ u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX);
+
+ pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u);
+ pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u);
+#ifndef TUNNEL_SINK
+ pa_pstream_set_recieve_memblock_callback(u->pstream, pstream_memblock_callback, u);
+#endif
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_AUTH);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie));
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u);
+
+}
+
+#ifdef TUNNEL_SINK
+static void sink_notify(pa_sink*sink) {
+ struct userdata *u;
+ assert(sink && sink->userdata);
+ u = sink->userdata;
+
+ send_bytes(u);
+}
+
+static pa_usec_t sink_get_latency(pa_sink *sink) {
+ struct userdata *u;
+ uint32_t l;
+ pa_usec_t usec = 0;
+ assert(sink && sink->userdata);
+ u = sink->userdata;
+
+ l = DEFAULT_TLENGTH;
+
+ if (l > u->requested_bytes) {
+ l -= u->requested_bytes;
+ usec += pa_bytes_to_usec(l, &u->sink->sample_spec);
+ }
+
+ usec += u->host_latency;
+
+ return usec;
+}
+#else
+static pa_usec_t source_get_latency(pa_source *source) {
+ struct userdata *u;
+ assert(source && source->userdata);
+ u = source->userdata;
+
+ return u->host_latency;
+}
+#endif
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+ assert(m && e && u);
+
+ request_latency(u);
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ m->time_restart(e, &ntv);
+}
+
+static int load_key(struct userdata *u, const char*fn) {
+ assert(u);
+
+ u->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug(__FILE__": using already loaded auth cookie.\n");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug(__FILE__": loading cookie from disk.\n");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ struct timeval ntv;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ m->userdata = u;
+ u->module = m;
+ u->core = c;
+ u->client = NULL;
+ u->pdispatch = NULL;
+ u->pstream = NULL;
+ u->server_name = NULL;
+#ifdef TUNNEL_SINK
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));;
+ u->sink = NULL;
+ u->requested_bytes = 0;
+#else
+ u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;
+ u->source = NULL;
+#endif
+ u->ctag = 1;
+ u->device_index = u->channel = PA_INVALID_INDEX;
+ u->host_latency = 0;
+ u->auth_cookie_in_property = 0;
+ u->time_event = NULL;
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) {
+ pa_log(__FILE__": no server specified.\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": invalid sample format specification\n");
+ goto fail;
+ }
+
+ if (!(u->client = pa_socket_client_new_string(c->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
+ pa_log(__FILE__": failed to connect to server '%s'\n", u->server_name);
+ goto fail;
+ }
+
+ if (!u->client)
+ goto fail;
+
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+#ifdef TUNNEL_SINK
+ 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.\n");
+ goto fail;
+ }
+
+ u->sink->notify = sink_notify;
+ u->sink->get_latency = sink_get_latency;
+ u->sink->userdata = u;
+ u->sink->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->sink_name ? u->sink_name : "", u->sink_name ? "@" : "", u->server_name);
+
+ pa_sink_set_owner(u->sink, m);
+#else
+ if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
+ pa_log(__FILE__": failed to create source.\n");
+ goto fail;
+ }
+
+ u->source->get_latency = source_get_latency;
+ u->source->userdata = u;
+ u->source->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->source_name ? u->source_name : "", u->source_name ? "@" : "", u->server_name);
+
+ pa_source_set_owner(u->source, m);
+#endif
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ u->time_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata* u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ close_stuff(u);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+#ifdef TUNNEL_SINK
+ pa_xfree(u->sink_name);
+#else
+ pa_xfree(u->source_name);
+#endif
+ pa_xfree(u->server_name);
+
+ pa_xfree(u);
+}
+
+
diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c
new file mode 100644
index 000000000..8809c31d3
--- /dev/null
+++ b/src/modules/module-waveout.c
@@ -0,0 +1,583 @@
+/* $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 <config.h>
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <assert.h>
+
+#include <polyp/mainloop-api.h>
+
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/module.h>
+#include <polypcore/modargs.h>
+#include <polypcore/sample-util.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+#include <polypcore/xmalloc.h>
+
+#include "module-waveout-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
+
+#define DEFAULT_SINK_NAME "wave_output"
+#define DEFAULT_SOURCE_NAME "wave_input"
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_core *core;
+ pa_time_event *event;
+ pa_defer_event *defer;
+ pa_usec_t poll_timeout;
+
+ uint32_t fragments, fragment_size;
+
+ uint32_t free_ofrags, free_ifrags;
+
+ DWORD written_bytes;
+
+ int cur_ohdr, cur_ihdr;
+ unsigned int oremain;
+ WAVEHDR *ohdrs, *ihdrs;
+ pa_memchunk silence;
+
+ HWAVEOUT hwo;
+ HWAVEIN hwi;
+ pa_module *module;
+
+ CRITICAL_SECTION crit;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ NULL
+};
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
+ (u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
+ (u->source ? pa_idxset_size(u->source->outputs) : 0));
+}
+
+static void do_write(struct userdata *u)
+{
+ uint32_t free_frags, remain;
+ pa_memchunk memchunk, *cur_chunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->sink)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ofrags;
+ u->free_ofrags = 0;
+
+ LeaveCriticalSection(&u->crit);
+
+ while (free_frags) {
+ hdr = &u->ohdrs[u->cur_ohdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+
+ remain = u->oremain;
+ while (remain) {
+ cur_chunk = &memchunk;
+
+ if (pa_sink_render(u->sink, remain, cur_chunk) < 0) {
+ /*
+ * Don't fill with silence unless we're getting close to
+ * underflowing.
+ */
+ if (free_frags > u->fragments/2)
+ cur_chunk = &u->silence;
+ else {
+ EnterCriticalSection(&u->crit);
+
+ u->free_ofrags += free_frags;
+
+ LeaveCriticalSection(&u->crit);
+
+ u->oremain = remain;
+ return;
+ }
+ }
+
+ assert(cur_chunk->memblock);
+ assert(cur_chunk->memblock->data);
+ assert(cur_chunk->length);
+
+ memcpy(hdr->lpData + u->fragment_size - remain,
+ (char*)cur_chunk->memblock->data + cur_chunk->index,
+ (cur_chunk->length < remain)?cur_chunk->length:remain);
+
+ remain -= (cur_chunk->length < remain)?cur_chunk->length:remain;
+
+ if (cur_chunk != &u->silence) {
+ pa_memblock_unref(cur_chunk->memblock);
+ cur_chunk->memblock = NULL;
+ }
+ }
+
+ res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d\n",
+ res);
+ }
+ res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d\n",
+ res);
+ }
+
+ u->written_bytes += u->fragment_size;
+
+ free_frags--;
+ u->cur_ohdr++;
+ u->cur_ohdr %= u->fragments;
+ u->oremain = u->fragment_size;
+ }
+}
+
+static void do_read(struct userdata *u)
+{
+ uint32_t free_frags;
+ pa_memchunk memchunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->source)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+ u->free_ifrags = 0;
+
+ LeaveCriticalSection(&u->crit);
+
+ while (free_frags) {
+ hdr = &u->ihdrs[u->cur_ihdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+
+ if (hdr->dwBytesRecorded) {
+ memchunk.memblock = pa_memblock_new(hdr->dwBytesRecorded, u->core->memblock_stat);
+ assert(memchunk.memblock);
+
+ memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded);
+
+ memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+ }
+
+ res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d\n",
+ res);
+ }
+ res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d\n",
+ res);
+ }
+
+ free_frags--;
+ u->cur_ihdr++;
+ u->cur_ihdr %= u->fragments;
+ }
+}
+
+static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+
+ assert(u);
+
+ update_usage(u);
+
+ do_write(u);
+ do_read(u);
+
+ pa_gettimeofday(&ntv);
+ pa_timeval_add(&ntv, u->poll_timeout);
+
+ a->time_restart(e, &ntv);
+}
+
+static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ struct userdata *u = userdata;
+
+ assert(u);
+
+ a->defer_enable(e, 0);
+
+ do_write(u);
+ do_read(u);
+}
+
+static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WOM_DONE)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ofrags++;
+ assert(u->free_ofrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WIM_DATA)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ifrags++;
+ assert(u->free_ifrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ MMTIME mmt;
+ assert(s && u && u->sink);
+
+ memset(&mmt, 0, sizeof(mmt));
+ mmt.wType = TIME_BYTES;
+ if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
+ return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec);
+ else {
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ofrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size,
+ &s->sample_spec);
+ }
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ assert(s && u && u->sink);
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec);
+
+ fprintf(stderr, "Latency: %d us\n", (int)r);
+
+ return r;
+}
+
+static void notify_sink_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static void notify_source_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
+ wf->wFormatTag = WAVE_FORMAT_PCM;
+
+ if (ss->channels > 2) {
+ pa_log_error(__FILE__": ERROR: More than two channels not supported.\n");
+ return -1;
+ }
+
+ wf->nChannels = ss->channels;
+
+ switch (ss->rate) {
+ case 8000:
+ case 11025:
+ case 22005:
+ case 44100:
+ break;
+ default:
+ pa_log_error(__FILE__": ERROR: Unsupported sample rate.\n");
+ return -1;
+ }
+
+ wf->nSamplesPerSec = ss->rate;
+
+ if (ss->format == PA_SAMPLE_U8)
+ wf->wBitsPerSample = 8;
+ else if (ss->format == PA_SAMPLE_S16NE)
+ wf->wBitsPerSample = 16;
+ else {
+ pa_log_error(__FILE__": ERROR: Unsupported sample format.\n");
+ return -1;
+ }
+
+ wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
+ wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
+
+ wf->cbSize = 0;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ HWAVEOUT hwo = INVALID_HANDLE_VALUE;
+ HWAVEIN hwi = INVALID_HANDLE_VALUE;
+ WAVEFORMATEX wf;
+ int nfrags, frag_size;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ unsigned int i;
+ struct timeval tv;
+
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log(__FILE__": record= and playback= expect boolean argument.\n");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log(__FILE__": neither playback nor record enabled for device.\n");
+ goto fail;
+ }
+
+ nfrags = 20;
+ frag_size = 1024;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log(__FILE__": failed to parse fragments arguments\n");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log(__FILE__": failed to parse sample specification\n");
+ goto fail;
+ }
+
+ if (ss_to_waveformat(&ss, &wf) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+
+ if (record) {
+ if (waveInOpen(&hwi, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+ goto fail;
+ if (waveInStart(hwi) != MMSYSERR_NOERROR)
+ goto fail;
+ pa_log_debug(__FILE__": Opened waveIn subsystem.\n");
+ }
+
+ if (playback) {
+ if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+ goto fail;
+ pa_log_debug(__FILE__": Opened waveOut subsystem.\n");
+ }
+
+ InitializeCriticalSection(&u->crit);
+
+ if (hwi != INVALID_HANDLE_VALUE) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->notify = notify_source_cb;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ u->source->description = pa_sprintf_malloc("Windows waveIn PCM");
+ } else
+ u->source = NULL;
+
+ if (hwo != INVALID_HANDLE_VALUE) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+ assert(u->sink);
+ u->sink->notify = notify_sink_cb;
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ u->sink->description = pa_sprintf_malloc("Windows waveOut PCM");
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->core = c;
+ u->hwi = hwi;
+ u->hwo = hwo;
+
+ u->fragments = nfrags;
+ u->free_ifrags = u->fragments;
+ u->free_ofrags = u->fragments;
+ u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
+
+ u->written_bytes = 0;
+
+ u->oremain = u->fragment_size;
+
+ u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 3, &ss);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, u->poll_timeout);
+
+ u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u);
+ assert(u->event);
+
+ u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u);
+ assert(u->defer);
+ c->mainloop->defer_enable(u->defer, 0);
+
+ u->cur_ihdr = 0;
+ u->cur_ohdr = 0;
+ u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ihdrs);
+ u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ohdrs);
+ for (i = 0;i < u->fragments;i++) {
+ u->ihdrs[i].dwBufferLength = u->fragment_size;
+ u->ohdrs[i].dwBufferLength = u->fragment_size;
+ u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ihdrs);
+ u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ohdrs);
+ }
+
+ u->silence.length = u->fragment_size;
+ u->silence.memblock = pa_memblock_new(u->silence.length, u->core->memblock_stat);
+ assert(u->silence.memblock);
+ pa_silence_memblock(u->silence.memblock, &ss);
+ u->silence.index = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (hwi != INVALID_HANDLE_VALUE)
+ waveInClose(hwi);
+
+ if (hwo != INVALID_HANDLE_VALUE)
+ waveOutClose(hwo);
+
+ if (u)
+ pa_xfree(u);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ unsigned int i;
+
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->event)
+ c->mainloop->time_free(u->event);
+
+ if (u->defer)
+ c->mainloop->defer_free(u->defer);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->hwi != INVALID_HANDLE_VALUE) {
+ waveInReset(u->hwi);
+ waveInClose(u->hwi);
+ }
+
+ if (u->hwo != INVALID_HANDLE_VALUE) {
+ waveOutReset(u->hwo);
+ waveOutClose(u->hwo);
+ }
+
+ for (i = 0;i < u->fragments;i++) {
+ pa_xfree(u->ihdrs[i].lpData);
+ pa_xfree(u->ohdrs[i].lpData);
+ }
+
+ pa_xfree(u->ihdrs);
+ pa_xfree(u->ohdrs);
+
+ DeleteCriticalSection(&u->crit);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c
new file mode 100644
index 000000000..d722b732f
--- /dev/null
+++ b/src/modules/module-x11-bell.c
@@ -0,0 +1,173 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include <polypcore/iochannel.h>
+#include <polypcore/sink.h>
+#include <polypcore/scache.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+#include <polypcore/x11wrap.h>
+
+#include "module-x11-bell-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("X11 Bell interceptor")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>")
+
+struct userdata {
+ pa_core *core;
+ int xkb_event_base;
+ char *sink_name;
+ char *scache_item;
+ Display *display;
+
+ pa_x11_wrapper *x11_wrapper;
+ pa_x11_client *x11_client;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "sample",
+ "display",
+ NULL
+};
+
+static int ring_bell(struct userdata *u, int percent) {
+ pa_sink *s;
+ pa_cvolume cv;
+ assert(u);
+
+ if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log(__FILE__": Invalid sink: %s\n", u->sink_name);
+ return -1;
+ }
+
+ pa_scache_play_item(u->core, u->scache_item, s, pa_cvolume_set(&cv, PA_CHANNELS_MAX, percent*PA_VOLUME_NORM/100));
+ return 0;
+}
+
+static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
+ XkbBellNotifyEvent *bne;
+ struct userdata *u = userdata;
+ assert(w && e && u && u->x11_wrapper == w);
+
+ if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
+ return 0;
+
+ bne = (XkbBellNotifyEvent*) e;
+
+ if (ring_bell(u, bne->percent) < 0) {
+ pa_log_info(__FILE__": Ringing bell failed, reverting to X11 device bell.\n");
+ XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
+ }
+
+ return 1;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ pa_modargs *ma = NULL;
+ int major, minor;
+ unsigned int auto_ctrls, auto_values;
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell"));
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->x11_client = NULL;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&major, &minor)) {
+ pa_log(__FILE__": XkbLibraryVersion() failed\n");
+ goto fail;
+ }
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+
+ if (!XkbQueryExtension(u->display, NULL, &u->xkb_event_base, NULL, &major, &minor)) {
+ pa_log(__FILE__": XkbQueryExtension() failed\n");
+ goto fail;
+ }
+
+ XkbSelectEvents(u->display, XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
+ auto_ctrls = auto_values = XkbAudibleBellMask;
+ XkbSetAutoResetControls(u->display, XkbAudibleBellMask, &auto_ctrls, &auto_values);
+ XkbChangeEnabledControls(u->display, XkbUseCoreKbd, XkbAudibleBellMask, 0);
+
+ u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+ if (m->userdata)
+ pa__done(c, m);
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u = m->userdata;
+ assert(c && m && u);
+
+ pa_xfree(u->scache_item);
+ pa_xfree(u->sink_name);
+
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c
new file mode 100644
index 000000000..dca5d049e
--- /dev/null
+++ b/src/modules/module-x11-publish.c
@@ -0,0 +1,192 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <polypcore/module.h>
+#include <polypcore/sink.h>
+#include <polypcore/scache.h>
+#include <polypcore/modargs.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/namereg.h>
+#include <polypcore/log.h>
+#include <polypcore/x11wrap.h>
+#include <polypcore/util.h>
+#include <polypcore/native-common.h>
+#include <polypcore/authkey-prop.h>
+#include <polypcore/authkey.h>
+#include <polypcore/x11prop.h>
+#include <polypcore/strlist.h>
+#include <polypcore/props.h>
+
+#include "module-x11-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("X11 Credential Publisher")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("display=<X11 display>")
+
+static const char* const valid_modargs[] = {
+ "display",
+ "sink",
+ "source",
+ "cookie",
+ NULL
+};
+
+struct userdata {
+ pa_core *core;
+ pa_x11_wrapper *x11_wrapper;
+ Display *display;
+ char *id;
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+ int auth_cookie_in_property;
+};
+
+static int load_key(struct userdata *u, const char*fn) {
+ assert(u);
+
+ u->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug(__FILE__": using already loaded auth cookie.\n");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug(__FILE__": loading cookie from disk.\n");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ char hn[256], un[128];
+ char hx[PA_NATIVE_COOKIE_LENGTH*2+1];
+ const char *t;
+ char *s;
+ pa_strlist *l;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->id = NULL;
+ u->auth_cookie_in_property = 0;
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
+
+ if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME)))
+ goto fail;
+
+ s = pa_strlist_tostring(l);
+ pa_x11_set_prop(u->display, "POLYP_SERVER", s);
+ pa_xfree(s);
+
+ if (!pa_get_fqdn(hn, sizeof(hn)) || !pa_get_user_name(un, sizeof(un)))
+ goto fail;
+
+ u->id = pa_sprintf_malloc("%s@%s/%u", un, hn, (unsigned) getpid());
+ pa_x11_set_prop(u->display, "POLYP_ID", u->id);
+
+ if ((t = pa_modargs_get_value(ma, "source", NULL)))
+ pa_x11_set_prop(u->display, "POLYP_SOURCE", t);
+
+ if ((t = pa_modargs_get_value(ma, "sink", NULL)))
+ pa_x11_set_prop(u->display, "POLYP_SINK", t);
+
+ pa_x11_set_prop(u->display, "POLYP_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx)));
+
+ 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->x11_wrapper) {
+ char t[256];
+
+ /* Yes, here is a race condition */
+ if (!pa_x11_get_prop(u->display, "POLYP_ID", t, sizeof(t)) || strcmp(t, u->id))
+ pa_log("WARNING: Polypaudio information vanished from X11!\n");
+ else {
+ pa_x11_del_prop(u->display, "POLYP_ID");
+ pa_x11_del_prop(u->display, "POLYP_SERVER");
+ pa_x11_del_prop(u->display, "POLYP_SINK");
+ pa_x11_del_prop(u->display, "POLYP_SOURCE");
+ pa_x11_del_prop(u->display, "POLYP_COOKIE");
+ XSync(u->display, False);
+ }
+ }
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+ pa_xfree(u->id);
+ pa_xfree(u);
+}
+
diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c
new file mode 100644
index 000000000..45d566aee
--- /dev/null
+++ b/src/modules/module-zeroconf-publish.c
@@ -0,0 +1,508 @@
+/* $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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <polypcore/howl-wrap.h>
+#include <polypcore/xmalloc.h>
+#include <polypcore/autoload.h>
+#include <polypcore/sink.h>
+#include <polypcore/source.h>
+#include <polypcore/native-common.h>
+#include <polypcore/util.h>
+#include <polypcore/log.h>
+#include <polypcore/subscribe.h>
+#include <polypcore/dynarray.h>
+#include <polypcore/endianmacros.h>
+#include <polypcore/modargs.h>
+
+#include "module-zeroconf-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering")
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE("port=<IP port number>")
+
+#define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
+#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
+#define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
+
+static const char* const valid_modargs[] = {
+ "port",
+ NULL
+};
+
+struct service {
+ sw_discovery_oid oid;
+ char *name;
+ int published; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
+
+ struct {
+ int valid;
+ pa_namereg_type type;
+ uint32_t index;
+ } loaded;
+
+ struct {
+ int valid;
+ pa_namereg_type type;
+ uint32_t index;
+ } autoload;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_howl_wrapper *howl_wrapper;
+ pa_hashmap *services;
+ pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
+ pa_subscription *subscription;
+
+ uint16_t port;
+ sw_discovery_oid server_oid;
+};
+
+static sw_result publish_reply(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) {
+ return SW_OKAY;
+}
+
+static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description, pa_typeid_t *ret_typeid) {
+ assert(u && s && s->loaded.valid && ret_ss && ret_description && ret_typeid);
+
+ if (s->loaded.type == PA_NAMEREG_SINK) {
+ pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
+ assert(sink);
+ *ret_ss = sink->sample_spec;
+ *ret_description = sink->description;
+ *ret_typeid = sink->typeid;
+ } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
+ pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
+ assert(source);
+ *ret_ss = source->sample_spec;
+ *ret_description = source->description;
+ *ret_typeid = source->typeid;
+ } else
+ assert(0);
+}
+
+static void txt_record_server_data(pa_core *c, sw_text_record t) {
+ char s[256];
+ assert(c);
+
+ sw_text_record_add_key_and_string_value(t, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
+ sw_text_record_add_key_and_string_value(t, "user-name", pa_get_user_name(s, sizeof(s)));
+ sw_text_record_add_key_and_string_value(t, "fqdn", pa_get_fqdn(s, sizeof(s)));
+ snprintf(s, sizeof(s), "0x%08x", c->cookie);
+ sw_text_record_add_key_and_string_value(t, "cookie", s);
+}
+
+static int publish_service(struct userdata *u, struct service *s) {
+ char t[256];
+ char hn[256];
+ int r = -1;
+ sw_text_record txt;
+ int free_txt = 0;
+ assert(u && s);
+
+ if ((s->published == 1 && s->loaded.valid) ||
+ (s->published == 2 && s->autoload.valid && !s->loaded.valid))
+ return 0;
+
+ if (s->published) {
+ sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
+ s->published = 0;
+ }
+
+ snprintf(t, sizeof(t), "Networked Audio Device %s on %s", s->name, pa_get_host_name(hn, sizeof(hn)));
+
+ if (sw_text_record_init(&txt) != SW_OKAY) {
+ pa_log(__FILE__": sw_text_record_init() failed\n");
+ goto finish;
+ }
+ free_txt = 1;
+
+ sw_text_record_add_key_and_string_value(txt, "device", s->name);
+
+ txt_record_server_data(u->core, txt);
+
+ if (s->loaded.valid) {
+ char z[64], *description;
+ pa_typeid_t typeid;
+ pa_sample_spec ss;
+
+ get_service_data(u, s, &ss, &description, &typeid);
+
+ snprintf(z, sizeof(z), "%u", ss.rate);
+ sw_text_record_add_key_and_string_value(txt, "rate", z);
+ snprintf(z, sizeof(z), "%u", ss.channels);
+ sw_text_record_add_key_and_string_value(txt, "channels", z);
+ sw_text_record_add_key_and_string_value(txt, "format", pa_sample_format_to_string(ss.format));
+
+ sw_text_record_add_key_and_string_value(txt, "description", description);
+
+ snprintf(z, sizeof(z), "0x%8x", typeid);
+ sw_text_record_add_key_and_string_value(txt, "typeid", z);
+
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ s->loaded.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, s, &s->oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register sink on zeroconf.\n");
+ goto finish;
+ }
+
+ s->published = 1;
+ } else if (s->autoload.valid) {
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ s->autoload.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, s, &s->oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register sink on zeroconf.\n");
+ goto finish;
+ }
+
+ s->published = 2;
+ }
+
+ r = 0;
+
+finish:
+
+ if (!s->published) {
+ /* Remove this service */
+ pa_hashmap_remove(u->services, s->name);
+ pa_xfree(s->name);
+ pa_xfree(s);
+ }
+
+ if (free_txt)
+ sw_text_record_fina(txt);
+
+ return r;
+}
+
+struct service *get_service(struct userdata *u, const char *name) {
+ struct service *s;
+
+ if ((s = pa_hashmap_get(u->services, name)))
+ return s;
+
+ s = pa_xmalloc(sizeof(struct service));
+ s->published = 0;
+ s->name = pa_xstrdup(name);
+ s->loaded.valid = s->autoload.valid = 0;
+
+ pa_hashmap_put(u->services, s->name, s);
+
+ return s;
+}
+
+static int publish_sink(struct userdata *u, pa_sink *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->loaded.valid)
+ return 0;
+
+ svc->loaded.valid = 1;
+ svc->loaded.type = PA_NAMEREG_SINK;
+ svc->loaded.index = s->index;
+
+ pa_dynarray_put(u->sink_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int publish_source(struct userdata *u, pa_source *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->loaded.valid)
+ return 0;
+
+ svc->loaded.valid = 1;
+ svc->loaded.type = PA_NAMEREG_SOURCE;
+ svc->loaded.index = s->index;
+
+ pa_dynarray_put(u->source_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
+ struct service *svc;
+ assert(u && s);
+
+ svc = get_service(u, s->name);
+ if (svc->autoload.valid)
+ return 0;
+
+ svc->autoload.valid = 1;
+ svc->autoload.type = s->type;
+ svc->autoload.index = s->index;
+
+ pa_dynarray_put(u->autoload_dynarray, s->index, svc);
+
+ return publish_service(u, svc);
+}
+
+static int remove_sink(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->sink_dynarray, index)))
+ return 0;
+
+ if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
+ return 0;
+
+ svc->loaded.valid = 0;
+ pa_dynarray_put(u->sink_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static int remove_source(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->source_dynarray, index)))
+ return 0;
+
+ if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
+ return 0;
+
+ svc->loaded.valid = 0;
+ pa_dynarray_put(u->source_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static int remove_autoload(struct userdata *u, uint32_t index) {
+ struct service *svc;
+ assert(u && index != PA_INVALID_INDEX);
+
+ if (!(svc = pa_dynarray_get(u->autoload_dynarray, index)))
+ return 0;
+
+ if (!svc->autoload.valid)
+ return 0;
+
+ svc->autoload.valid = 0;
+ pa_dynarray_put(u->autoload_dynarray, index, NULL);
+
+ return publish_service(u, svc);
+}
+
+static void subscribe_callback(pa_core *c, pa_subscription_event_type t, uint32_t index, void *userdata) {
+ struct userdata *u = userdata;
+ assert(u && c);
+
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
+ case PA_SUBSCRIPTION_EVENT_SINK: {
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_sink *sink;
+
+ if ((sink = pa_idxset_get_by_index(c->sinks, index))) {
+ if (publish_sink(u, sink) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_sink(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_source *source;
+
+ if ((source = pa_idxset_get_by_index(c->sources, index))) {
+ if (publish_source(u, source) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_source(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_autoload_entry *autoload;
+
+ if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, index))) {
+ if (publish_autoload(u, autoload) < 0)
+ goto fail;
+ }
+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ if (remove_autoload(u, index) < 0)
+ goto fail;
+ }
+
+ break;
+ }
+
+ return;
+
+fail:
+ if (u->subscription) {
+ pa_subscription_free(u->subscription);
+ u->subscription = NULL;
+ }
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ uint32_t index, port = PA_NATIVE_DEFAULT_PORT;
+ pa_sink *sink;
+ pa_source *source;
+ pa_autoload_entry *autoload;
+ pa_modargs *ma = NULL;
+ char t[256], hn[256];
+ int free_txt = 0;
+ sw_text_record txt;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log(__FILE__": failed to parse module arguments.\n");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
+ pa_log(__FILE__": invalid port specified.\n");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = c;
+ u->port = (uint16_t) port;
+
+ if (!(u->howl_wrapper = pa_howl_wrapper_get(c)))
+ goto fail;
+
+ u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ u->sink_dynarray = pa_dynarray_new();
+ u->source_dynarray = pa_dynarray_new();
+ u->autoload_dynarray = pa_dynarray_new();
+
+ u->subscription = pa_subscription_new(c,
+ PA_SUBSCRIPTION_MASK_SINK|
+ PA_SUBSCRIPTION_MASK_SOURCE|
+ PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
+
+ for (sink = pa_idxset_first(c->sinks, &index); sink; sink = pa_idxset_next(c->sinks, &index))
+ if (publish_sink(u, sink) < 0)
+ goto fail;
+
+ for (source = pa_idxset_first(c->sources, &index); source; source = pa_idxset_next(c->sources, &index))
+ if (publish_source(u, source) < 0)
+ goto fail;
+
+ if (c->autoload_idxset)
+ for (autoload = pa_idxset_first(c->autoload_idxset, &index); autoload; autoload = pa_idxset_next(c->autoload_idxset, &index))
+ if (publish_autoload(u, autoload) < 0)
+ goto fail;
+
+ snprintf(t, sizeof(t), "Networked Audio Server on %s", pa_get_host_name(hn, sizeof(hn)));
+
+ if (sw_text_record_init(&txt) != SW_OKAY) {
+ pa_log(__FILE__": sw_text_record_init() failed\n");
+ goto fail;
+ }
+ free_txt = 1;
+
+ txt_record_server_data(u->core, txt);
+
+ if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
+ SERVICE_NAME_SERVER,
+ NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
+ publish_reply, u, &u->server_oid) != SW_OKAY) {
+ pa_log(__FILE__": failed to register server on zeroconf.\n");
+ goto fail;
+ }
+
+ sw_text_record_fina(txt);
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(c, m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (free_txt)
+ sw_text_record_fina(txt);
+
+ return -1;
+}
+
+static void service_free(void *p, void *userdata) {
+ struct service *s = p;
+ struct userdata *u = userdata;
+ assert(s && u);
+ sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
+ pa_xfree(s->name);
+ pa_xfree(s);
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata*u;
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->services)
+ pa_hashmap_free(u->services, service_free, u);
+
+ if (u->sink_dynarray)
+ pa_dynarray_free(u->sink_dynarray, NULL, NULL);
+ if (u->source_dynarray)
+ pa_dynarray_free(u->source_dynarray, NULL, NULL);
+ if (u->autoload_dynarray)
+ pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ if (u->howl_wrapper)
+ pa_howl_wrapper_unref(u->howl_wrapper);
+
+
+ pa_xfree(u);
+}
+