summaryrefslogtreecommitdiffstats
path: root/polyp
diff options
context:
space:
mode:
Diffstat (limited to 'polyp')
-rw-r--r--polyp/Makefile.am14
-rw-r--r--polyp/ctl_polyp.c455
-rw-r--r--polyp/pcm_polyp.c626
-rw-r--r--polyp/polyp.c350
-rw-r--r--polyp/polyp.h60
5 files changed, 1505 insertions, 0 deletions
diff --git a/polyp/Makefile.am b/polyp/Makefile.am
new file mode 100644
index 0000000..858de93
--- /dev/null
+++ b/polyp/Makefile.am
@@ -0,0 +1,14 @@
+asound_module_pcm_LTLIBRARIES = libasound_module_pcm_polyp.la
+asound_module_ctl_LTLIBRARIES = libasound_module_ctl_polyp.la
+
+asound_module_pcmdir = $(libdir)/alsa-lib
+asound_module_ctldir = $(libdir)/alsa-lib
+
+AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ $(PTHREAD_CFLAGS) $(polypaudio_CFLAGS) -D_GNU_SOURCE
+AM_LDFLAGS = -module -avoid-version -export-dynamic
+
+libasound_module_pcm_polyp_la_SOURCES = pcm_polyp.c polyp.c polyp.h
+libasound_module_pcm_polyp_la_LIBADD = @ALSA_LIBS@ $(PTHREAD_LIBS) $(polypaudio_LIBS)
+
+libasound_module_ctl_polyp_la_SOURCES = ctl_polyp.c polyp.c polyp.h
+libasound_module_ctl_polyp_la_LIBADD = @ALSA_LIBS@ $(PTHREAD_LIBS) $(polypaudio_LIBS)
diff --git a/polyp/ctl_polyp.c b/polyp/ctl_polyp.c
new file mode 100644
index 0000000..16043bf
--- /dev/null
+++ b/polyp/ctl_polyp.c
@@ -0,0 +1,455 @@
+/*
+ * ALSA <-> Polypaudio mixer control plugin
+ *
+ * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <sys/poll.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/control_external.h>
+
+#include "polyp.h"
+
+typedef struct snd_ctl_polyp {
+ snd_ctl_ext_t ext;
+
+ snd_polyp_t *p;
+
+ char *device;
+
+ pa_cvolume volume;
+
+ int subscribed;
+ int updated;
+} snd_ctl_polyp_t;
+
+#define MIXER_NAME "Master"
+
+static void sink_info_cb(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+ int chan;
+
+ if (is_last)
+ return;
+
+ assert(ctl && i);
+
+ if (ctl->volume.channels == i->volume.channels) {
+ for (chan = 0;chan < ctl->volume.channels;chan++)
+ if (i->volume.values[chan] != ctl->volume.values[chan])
+ break;
+
+ if (chan == ctl->volume.channels)
+ return;
+
+ ctl->updated = 1;
+ }
+
+ memcpy(&ctl->volume, &i->volume, sizeof(pa_cvolume));
+}
+
+static void event_cb(pa_context *c, pa_subscription_event_type_t t,
+ uint32_t index, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device,
+ sink_info_cb, ctl);
+ pa_operation_unref(o);
+}
+
+static int polyp_update_volume(snd_ctl_polyp_t *ctl)
+{
+ int err;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->device,
+ sink_info_cb, ctl);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int polyp_elem_count(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (ctl->device)
+ return 1;
+
+ return 0;
+}
+
+static int polyp_elem_list(snd_ctl_ext_t *ext, unsigned int offset,
+ snd_ctl_elem_id_t *id)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(id, MIXER_NAME);
+
+ return 0;
+}
+
+static snd_ctl_ext_key_t polyp_find_elem(snd_ctl_ext_t *ext,
+ const snd_ctl_elem_id_t *id)
+{
+ const char *name;
+
+ name = snd_ctl_elem_id_get_name(id);
+
+ if (strcmp(name, MIXER_NAME) == 0)
+ return 0;
+
+ return SND_CTL_EXT_KEY_NOT_FOUND;
+}
+
+static int polyp_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ int *type, unsigned int *acc, unsigned int *count)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err;
+
+ assert(ctl && ctl->p);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ *type = SND_CTL_ELEM_TYPE_INTEGER;
+ *acc = SND_CTL_EXT_ACCESS_READWRITE;
+ *count = ctl->volume.channels;
+
+ return 0;
+}
+
+static int polyp_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *imin, long *imax, long *istep)
+{
+ if (key != 0)
+ return -EINVAL;
+
+ *istep = 1;
+ *imin = 0;
+ *imax = PA_VOLUME_NORM;
+
+ return 0;
+}
+
+static int polyp_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err, i;
+
+ assert(ctl && ctl->p);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ for (i = 0;i < ctl->volume.channels;i++)
+ value[i] = ctl->volume.values[i];
+
+ return 0;
+}
+
+static int polyp_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err, i;
+ pa_cvolume vol;
+ pa_operation *o;
+
+ assert(ctl && ctl->p && ctl->p->context);
+
+ if (key != 0)
+ return -EINVAL;
+
+ err = polyp_finish_poll(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(ctl->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_update_volume(ctl);
+ if (err < 0)
+ return err;
+
+ for (i = 0;i < ctl->volume.channels;i++)
+ if (value[i] != ctl->volume.values[i])
+ break;
+
+ if (i == ctl->volume.channels)
+ return 0;
+
+ memset(&vol, 0, sizeof(pa_cvolume));
+
+ vol.channels = ctl->volume.channels;
+ for (i = 0;i < vol.channels;i++)
+ vol.values[i] = value[i];
+
+ o = pa_context_set_sink_volume_by_name(ctl->p->context, ctl->device, &vol,
+ NULL, NULL);
+
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+static void polyp_subscribe_events(snd_ctl_ext_t *ext, int subscribe)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE);
+}
+
+static int polyp_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
+ unsigned int *event_mask)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (!ctl->updated || !ctl->subscribed)
+ return -EAGAIN;
+
+ polyp_elem_list(ext, 0, id);
+ *event_mask = SND_CTL_EVENT_MASK_VALUE;
+ ctl->updated = 0;
+
+ return 1;
+}
+
+static int polyp_ctl_poll_descriptors_count(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl && ctl->p);
+
+ return polyp_poll_descriptors_count(ctl->p);
+}
+
+static int polyp_ctl_poll_descriptors(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int space)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl && ctl->p);
+
+ return polyp_poll_descriptors(ctl->p, pfd, space);
+}
+
+static int polyp_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int nfds, unsigned short *revents)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+ int err;
+
+ assert(ctl && ctl->p);
+
+ err = polyp_poll_revents(ctl->p, pfd, nfds, revents);
+ if (err < 0)
+ return err;
+
+ *revents = 0;
+
+ if (ctl->updated)
+ *revents |= POLLIN;
+
+ return 0;
+}
+
+static void polyp_close(snd_ctl_ext_t *ext)
+{
+ snd_ctl_polyp_t *ctl = ext->private_data;
+
+ assert(ctl);
+
+ if (ctl->p)
+ polyp_free(ctl->p);
+
+ if (ctl->device)
+ free(ctl->device);
+
+ free(ctl);
+}
+
+static snd_ctl_ext_callback_t polyp_ext_callback = {
+ .elem_count = polyp_elem_count,
+ .elem_list = polyp_elem_list,
+ .find_elem = polyp_find_elem,
+ .get_attribute = polyp_get_attribute,
+ .get_integer_info = polyp_get_integer_info,
+ .read_integer = polyp_read_integer,
+ .write_integer = polyp_write_integer,
+ .subscribe_events = polyp_subscribe_events,
+ .read_event = polyp_read_event,
+ .poll_descriptors_count = polyp_ctl_poll_descriptors_count,
+ .poll_descriptors = polyp_ctl_poll_descriptors,
+ .poll_revents = polyp_ctl_poll_revents,
+ .close = polyp_close,
+};
+
+static void server_info_cb(pa_context *c, const pa_server_info*i, void *userdata)
+{
+ snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata;
+
+ assert(ctl && i && i->default_sink_name);
+
+ ctl->device = strdup(i->default_sink_name);
+}
+
+SND_CTL_PLUGIN_DEFINE_FUNC(polyp)
+{
+ snd_config_iterator_t i, next;
+ const char *server = NULL;
+ const char *device = NULL;
+ int err;
+ snd_ctl_polyp_t *ctl;
+ pa_operation *o;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "server") == 0) {
+ if (snd_config_get_string(n, &server) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ if (strcmp(id, "device") == 0) {
+ if (snd_config_get_string(n, &device) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ ctl = calloc(1, sizeof(*ctl));
+
+ ctl->p = polyp_new();
+ assert(ctl->p);
+
+ err = polyp_connect(ctl->p, server);
+ if (err < 0)
+ goto error;
+
+ err = polyp_start_thread(ctl->p);
+ if (err < 0)
+ goto error;
+
+ if (device)
+ ctl->device = strdup(device);
+ else {
+ o = pa_context_get_server_info(ctl->p->context, server_info_cb, ctl);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ goto error;
+ }
+
+ pa_context_set_subscribe_callback(ctl->p->context, event_cb, ctl);
+
+ o = pa_context_subscribe(ctl->p->context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
+ err = polyp_wait_operation(ctl->p, o);
+ pa_operation_unref(o);
+ if (err < 0)
+ goto error;
+
+ ctl->ext.version = SND_CTL_EXT_VERSION;
+ ctl->ext.card_idx = 0;
+ strncpy(ctl->ext.id, "polyp", sizeof(ctl->ext.id) - 1);
+ strncpy(ctl->ext.driver, "Polypaudio plugin", sizeof(ctl->ext.driver) - 1);
+ strncpy(ctl->ext.name, "Polypaudio", sizeof(ctl->ext.name) - 1);
+ strncpy(ctl->ext.longname, "Polypaudio", sizeof(ctl->ext.longname) - 1);
+ strncpy(ctl->ext.mixername, "Polypaudio", sizeof(ctl->ext.mixername) - 1);
+ ctl->ext.poll_fd = -1;
+ ctl->ext.callback = &polyp_ext_callback;
+ ctl->ext.private_data = ctl;
+
+ err = snd_ctl_ext_create(&ctl->ext, name, mode);
+ if (err < 0)
+ goto error;
+
+ *handlep = ctl->ext.handle;
+
+ return 0;
+
+error:
+ if (ctl->device)
+ free(ctl->device);
+
+ if (ctl->p)
+ polyp_free(ctl->p);
+
+ free(ctl);
+
+ return err;
+}
+
+SND_CTL_PLUGIN_SYMBOL(polyp);
diff --git a/polyp/pcm_polyp.c b/polyp/pcm_polyp.c
new file mode 100644
index 0000000..e3ea506
--- /dev/null
+++ b/polyp/pcm_polyp.c
@@ -0,0 +1,626 @@
+/*
+ * ALSA <-> Polypaudio PCM I/O plugin
+ *
+ * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdio.h>
+#include <sys/poll.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+#include "polyp.h"
+
+typedef struct snd_pcm_polyp {
+ snd_pcm_ioplug_t io;
+
+ snd_polyp_t *p;
+
+ char *device;
+
+ /* Since ALSA expects a ring buffer we must do some voodoo. */
+ size_t last_size;
+ size_t ptr;
+
+ size_t offset;
+
+ pa_stream *stream;
+
+ pa_sample_spec ss;
+ unsigned int frame_size;
+ pa_buffer_attr buffer_attr;
+} snd_pcm_polyp_t;
+
+static void update_ptr(snd_pcm_polyp_t *pcm)
+{
+ size_t size;
+
+ if (pcm->io.stream == SND_PCM_STREAM_PLAYBACK)
+ size = pa_stream_writable_size(pcm->stream);
+ else
+ size = pa_stream_readable_size(pcm->stream) - pcm->offset;
+
+ if (size > pcm->last_size) {
+ pcm->ptr += size - pcm->last_size;
+ pcm->ptr %= pcm->buffer_attr.maxlength;
+ }
+
+ pcm->last_size = size;
+}
+
+static int polyp_start(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ pa_operation *o;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ o = pa_stream_cork(pcm->stream, 0, NULL, NULL);
+ assert(o);
+
+ err = polyp_wait_operation(pcm->p, o);
+
+ pa_operation_unref(o);
+
+ if (err < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int polyp_stop(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ pa_operation *o;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ o = pa_stream_flush(pcm->stream, NULL, NULL);
+ assert(o);
+
+ err = polyp_wait_operation(pcm->p, o);
+
+ pa_operation_unref(o);
+
+ if (err < 0)
+ return -EIO;
+
+ o = pa_stream_cork(pcm->stream, 1, NULL, NULL);
+ assert(o);
+
+ err = polyp_wait_operation(pcm->p, o);
+
+ pa_operation_unref(o);
+
+ if (err < 0)
+ return -EIO;
+
+ return 0;
+}
+
+int polyp_drain(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ pa_operation *o;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ o = pa_stream_drain(pcm->stream, NULL, NULL);
+ assert(o);
+
+ err = polyp_wait_operation(pcm->p, o);
+
+ pa_operation_unref(o);
+
+ if (err < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static snd_pcm_sframes_t polyp_pointer(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ update_ptr(pcm);
+
+ err = polyp_start_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ return snd_pcm_bytes_to_frames(io->pcm, pcm->ptr);
+}
+
+static snd_pcm_sframes_t polyp_write(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ const char *buf;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ /* Make sure the buffer pointer is in sync */
+ update_ptr(pcm);
+
+ assert(pcm->last_size >= (size * pcm->frame_size));
+
+ buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
+
+ pa_stream_write(pcm->stream, buf, size * pcm->frame_size, NULL, 0);
+
+ /* Make sure the buffer pointer is in sync */
+ update_ptr(pcm);
+
+ return size;
+}
+
+static snd_pcm_sframes_t polyp_read(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ void *dst_buf, *src_buf;
+ size_t remain_size, frag_length;
+ int err;
+
+ assert(pcm && pcm->p && pcm->stream);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ /* Make sure the buffer pointer is in sync */
+ update_ptr(pcm);
+
+ remain_size = size * pcm->frame_size;
+
+ dst_buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
+ while (remain_size > 0) {
+ pa_stream_peek(pcm->stream, (void**)&src_buf, &frag_length);
+ if (frag_length == 0)
+ break;
+
+ src_buf = (char*)src_buf + pcm->offset;
+ frag_length -= pcm->offset;
+
+ if (frag_length > remain_size) {
+ pcm->offset += remain_size;
+ frag_length = remain_size;
+ } else
+ pcm->offset = 0;
+
+ memcpy(dst_buf, src_buf, frag_length);
+
+ if (pcm->offset == 0)
+ pa_stream_drop(pcm->stream);
+
+ dst_buf = (char*)dst_buf + frag_length;
+ remain_size -= frag_length;
+ }
+
+ /* Make sure the buffer pointer is in sync */
+ update_ptr(pcm);
+
+ return size - (remain_size / pcm->frame_size);
+}
+
+static int polyp_pcm_poll_descriptors_count(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+
+ assert(pcm && pcm->p);
+
+ return polyp_poll_descriptors_count(pcm->p);
+}
+
+static int polyp_pcm_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int space)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+
+ assert(pcm && pcm->p);
+
+ return polyp_poll_descriptors(pcm->p, pfd, space);
+}
+
+static int polyp_pcm_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ int err;
+
+ assert(pcm && pcm->p);
+
+ err = polyp_poll_revents(pcm->p, pfd, nfds, revents);
+ if (err < 0)
+ return err;
+
+ *revents = 0;
+
+ /*
+ * Make sure we have an up-to-date value.
+ */
+ update_ptr(pcm);
+
+ /*
+ * ALSA thinks in periods, not bytes, samples or frames.
+ */
+ if (pcm->last_size >= pcm->buffer_attr.minreq) {
+ if (io->stream == SND_PCM_STREAM_PLAYBACK)
+ *revents |= POLLIN;
+ else
+ *revents |= POLLOUT;
+ }
+
+ return 0;
+}
+
+static int polyp_prepare(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+ int err;
+ pa_stream_state_t state;
+
+ assert(pcm && pcm->p);
+
+ err = polyp_finish_poll(pcm->p);
+ if (err < 0)
+ return err;
+
+ if (pcm->stream) {
+ pa_stream_unref(pcm->stream);
+ pcm->stream = NULL;
+ }
+
+ err = polyp_check_connection(pcm->p);
+ if (err < 0)
+ return err;
+
+ assert(pcm->stream == NULL);
+
+ if (io->stream == SND_PCM_STREAM_PLAYBACK)
+ pcm->stream = pa_stream_new(pcm->p->context, "ALSA Playback", &pcm->ss, NULL);
+ else
+ pcm->stream = pa_stream_new(pcm->p->context, "ALSA Capture", &pcm->ss, NULL);
+ assert(pcm->stream);
+
+ if (io->stream == SND_PCM_STREAM_PLAYBACK)
+ pa_stream_connect_playback(pcm->stream, pcm->device, &pcm->buffer_attr, 0, NULL);
+ else
+ pa_stream_connect_record(pcm->stream, pcm->device, &pcm->buffer_attr, 0);
+
+ while (1) {
+ state = pa_stream_get_state(pcm->stream);
+
+ if (state == PA_STREAM_FAILED)
+ goto error;
+
+ if (state == PA_STREAM_READY)
+ break;
+
+ err = pa_mainloop_iterate(pcm->p->mainloop, 1, NULL);
+ if (err < 0)
+ return -EIO;
+ }
+
+ pcm->last_size = 0;
+ pcm->ptr = 0;
+ pcm->offset = 0;
+
+ return 0;
+
+error:
+ fprintf(stderr, "*** POLYPAUDIO: Unable to create stream.\n");
+ pa_stream_unref(pcm->stream);
+ pcm->stream = NULL;
+ return -EIO;
+}
+
+static int polyp_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+
+ assert(pcm && pcm->p && !pcm->stream);
+
+ pcm->frame_size = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
+
+ switch (io->format) {
+ case SND_PCM_FORMAT_U8:
+ pcm->ss.format = PA_SAMPLE_U8;
+ break;
+ case SND_PCM_FORMAT_A_LAW:
+ pcm->ss.format = PA_SAMPLE_ALAW;
+ break;
+ case SND_PCM_FORMAT_MU_LAW:
+ pcm->ss.format = PA_SAMPLE_ULAW;
+ break;
+ case SND_PCM_FORMAT_S16_LE:
+ pcm->ss.format = PA_SAMPLE_S16LE;
+ break;
+ case SND_PCM_FORMAT_S16_BE:
+ pcm->ss.format = PA_SAMPLE_S16BE;
+ break;
+ case SND_PCM_FORMAT_FLOAT_LE:
+ pcm->ss.format = PA_SAMPLE_FLOAT32LE;
+ break;
+ case SND_PCM_FORMAT_FLOAT_BE:
+ pcm->ss.format = PA_SAMPLE_FLOAT32BE;
+ break;
+ default:
+ fprintf(stderr, "*** POLYPAUDIO: unsupported format %s\n",
+ snd_pcm_format_name(io->format));
+ return -EINVAL;
+ }
+
+ pcm->ss.rate = io->rate;
+ pcm->ss.channels = io->channels;
+
+ pcm->buffer_attr.maxlength = io->buffer_size * pcm->frame_size;
+ pcm->buffer_attr.tlength = io->buffer_size * pcm->frame_size;
+ pcm->buffer_attr.prebuf = io->period_size * pcm->frame_size;
+ pcm->buffer_attr.minreq = io->period_size * pcm->frame_size;
+ pcm->buffer_attr.fragsize = io->period_size * pcm->frame_size;
+
+ return 0;
+}
+
+static int polyp_close(snd_pcm_ioplug_t *io)
+{
+ snd_pcm_polyp_t *pcm = io->private_data;
+
+ assert(pcm);
+
+ if (pcm->stream)
+ pa_stream_unref(pcm->stream);
+
+ if (pcm->p)
+ polyp_free(pcm->p);
+
+ if (pcm->device)
+ free(pcm->device);
+
+ free(pcm);
+
+ return 0;
+}
+
+static snd_pcm_ioplug_callback_t polyp_playback_callback = {
+ .start = polyp_start,
+ .stop = polyp_stop,
+ .drain = polyp_drain,
+ .pointer = polyp_pointer,
+ .transfer = polyp_write,
+ .poll_descriptors_count = polyp_pcm_poll_descriptors_count,
+ .poll_descriptors = polyp_pcm_poll_descriptors,
+ .poll_revents = polyp_pcm_poll_revents,
+ .prepare = polyp_prepare,
+ .hw_params = polyp_hw_params,
+ .close = polyp_close,
+};
+
+
+static snd_pcm_ioplug_callback_t polyp_capture_callback = {
+ .start = polyp_start,
+ .stop = polyp_stop,
+ .pointer = polyp_pointer,
+ .transfer = polyp_read,
+ .poll_descriptors_count = polyp_pcm_poll_descriptors_count,
+ .poll_descriptors = polyp_pcm_poll_descriptors,
+ .poll_revents = polyp_pcm_poll_revents,
+ .prepare = polyp_prepare,
+ .hw_params = polyp_hw_params,
+ .close = polyp_close,
+};
+
+
+static int polyp_hw_constraint(snd_pcm_polyp_t *pcm)
+{
+ snd_pcm_ioplug_t *io = &pcm->io;
+
+ static const snd_pcm_access_t access_list[] = {
+ SND_PCM_ACCESS_RW_INTERLEAVED
+ };
+ static const unsigned int formats[] = {
+ SND_PCM_FORMAT_U8,
+ SND_PCM_FORMAT_A_LAW,
+ SND_PCM_FORMAT_MU_LAW,
+ SND_PCM_FORMAT_S16_LE,
+ SND_PCM_FORMAT_S16_BE,
+ SND_PCM_FORMAT_FLOAT_LE,
+ SND_PCM_FORMAT_FLOAT_BE
+ };
+
+ int err;
+
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
+ ARRAY_SIZE(access_list), access_list);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
+ ARRAY_SIZE(formats), formats);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
+ 1, PA_CHANNELS_MAX);
+ if (err < 0)
+ return err;
+
+ /* FIXME: Investigate actual min and max */
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
+ 8000, 48000);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
+ 8000, 48000);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
+ 1, 4294967295U);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
+ 2, 4294967295U);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
+ 1, 4294967295U);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+SND_PCM_PLUGIN_DEFINE_FUNC(polyp)
+{
+ snd_config_iterator_t i, next;
+ const char *server = NULL;
+ const char *device = NULL;
+ int err;
+ snd_pcm_polyp_t *pcm;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "server") == 0) {
+ if (snd_config_get_string(n, &server) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ if (strcmp(id, "device") == 0) {
+ if (snd_config_get_string(n, &device) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ pcm = calloc(1, sizeof(*pcm));
+
+ if (device)
+ pcm->device = strdup(device);
+
+ pcm->p = polyp_new();
+ assert(pcm->p);
+
+ err = polyp_connect(pcm->p, server);
+ if (err < 0)
+ goto error;
+
+ err = polyp_start_thread(pcm->p);
+ if (err < 0)
+ goto error;
+
+ pcm->io.version = SND_PCM_IOPLUG_VERSION;
+ pcm->io.name = "ALSA <-> Polypaudio PCM I/O Plugin";
+ pcm->io.poll_fd = -1;
+ pcm->io.poll_events = 0;
+ pcm->io.mmap_rw = 0;
+ pcm->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
+ &polyp_playback_callback : &polyp_capture_callback;
+ pcm->io.private_data = pcm;
+
+ err = snd_pcm_ioplug_create(&pcm->io, name, stream, mode);
+ if (err < 0)
+ goto error;
+
+ err = polyp_hw_constraint(pcm);
+ if (err < 0) {
+ snd_pcm_ioplug_delete(&pcm->io);
+ goto error;
+ }
+
+ *pcmp = pcm->io.pcm;
+ return 0;
+
+error:
+ if (pcm->p)
+ polyp_free(pcm->p);
+
+ free(pcm);
+
+ return err;
+}
+
+SND_PCM_PLUGIN_SYMBOL(polyp);
diff --git a/polyp/polyp.c b/polyp/polyp.c
new file mode 100644
index 0000000..139152f
--- /dev/null
+++ b/polyp/polyp.c
@@ -0,0 +1,350 @@
+/*
+ * ALSA <-> Polypaudio plugins
+ *
+ * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <pthread.h>
+
+#include "polyp.h"
+
+enum {
+ COMMAND_POLL = 'p',
+ COMMAND_QUIT = 'q',
+ COMMAND_POLL_DONE = 'P',
+ COMMAND_POLL_FAILED = 'F',
+};
+
+static int write_command(snd_polyp_t *p, char command)
+{
+ if (write(p->main_fd, &command, 1) != 1)
+ return -errno;
+ return 0;
+}
+
+static int write_reply(snd_polyp_t *p, char reply)
+{
+ if (write(p->thread_fd, &reply, 1) != 1)
+ return -errno;
+ return 0;
+}
+
+static int read_command(snd_polyp_t *p)
+{
+ char command;
+
+ if (read(p->thread_fd, &command, 1) != 1)
+ return -errno;
+
+ return command;
+}
+
+static int read_reply(snd_polyp_t *p)
+{
+ char reply;
+
+ if (read(p->main_fd, &reply, 1) != 1)
+ return -errno;
+
+ return reply;
+}
+
+static void* thread_func(void *data)
+{
+ snd_polyp_t *p = (snd_polyp_t*)data;
+ sigset_t mask;
+ char command;
+ int ret;
+
+ sigfillset(&mask);
+ pthread_sigmask(SIG_BLOCK, &mask, NULL);
+
+ do {
+ command = read_command(p);
+ if (command < 0)
+ break;
+
+ switch (command) {
+ case COMMAND_POLL:
+ do {
+ ret = pa_mainloop_poll(p->mainloop);
+ } while ((ret < 0) && (errno == EINTR));
+
+ ret = write_reply(p, (ret < 0) ? COMMAND_POLL_FAILED : COMMAND_POLL_DONE);
+ if (ret < 0)
+ return NULL;
+
+ break;
+ }
+ } while (command != COMMAND_QUIT);
+
+ return NULL;
+}
+
+int polyp_start_poll(snd_polyp_t *p)
+{
+ int err;
+
+ assert(p);
+
+ if (p->state == POLYP_STATE_POLLING)
+ return 0;
+
+ assert(p->state == POLYP_STATE_READY);
+
+ err = pa_mainloop_prepare(p->mainloop, -1);
+ if (err < 0)
+ return err;
+
+ err = write_command(p, COMMAND_POLL);
+ if (err < 0)
+ return err;
+
+ p->state = POLYP_STATE_POLLING;
+
+ return 0;
+}
+
+int polyp_finish_poll(snd_polyp_t *p)
+{
+ char reply;
+ int err;
+
+ assert(p);
+
+ if (p->state == POLYP_STATE_READY)
+ return 0;
+
+ assert(p->state == POLYP_STATE_POLLING);
+
+ p->state = POLYP_STATE_READY;
+
+ pa_mainloop_wakeup(p->mainloop);
+
+ reply = read_reply(p);
+
+ if (reply == COMMAND_POLL_DONE) {
+ err = pa_mainloop_dispatch(p->mainloop);
+ if (err < 0)
+ return err;
+ } else
+ return -EIO;
+
+ return 0;
+}
+
+int polyp_check_connection(snd_polyp_t *p)
+{
+ pa_context_state_t state;
+
+ assert(p && p->context);
+
+ state = pa_context_get_state(p->context);
+
+ if (state != PA_CONTEXT_READY)
+ return -EIO;
+
+ return 0;
+}
+
+int polyp_wait_operation(snd_polyp_t *p, pa_operation *o)
+{
+ int err;
+
+ assert(p && o && (p->state == POLYP_STATE_READY));
+
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
+ err = pa_mainloop_iterate(p->mainloop, 1, NULL);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+snd_polyp_t *polyp_new()
+{
+ snd_polyp_t *p;
+
+ p = calloc(1, sizeof(snd_polyp_t));
+ assert(p);
+
+ p->state = POLYP_STATE_INIT;
+
+ p->main_fd = -1;
+ p->thread_fd = -1;
+ p->thread_running = 0;
+
+ p->mainloop = pa_mainloop_new();
+ assert(p->mainloop);
+
+ p->context = pa_context_new(pa_mainloop_get_api(p->mainloop),
+ "ALSA Plugin");
+ assert(p->context);
+
+ return p;
+}
+
+void polyp_free(snd_polyp_t *p)
+{
+ if (p->thread_running) {
+ assert(p->mainloop && p->thread);
+ write_command(p, COMMAND_QUIT);
+ pa_mainloop_wakeup(p->mainloop);
+ pthread_join(p->thread, NULL);
+ }
+
+ if (p->context)
+ pa_context_unref(p->context);
+ if (p->mainloop)
+ pa_mainloop_free(p->mainloop);
+
+ if (p->thread_fd >= 0)
+ close(p->thread_fd);
+ if (p->main_fd >= 0)
+ close(p->main_fd);
+
+ free(p);
+}
+
+int polyp_connect(snd_polyp_t *p, const char *server)
+{
+ int err;
+ pa_context_state_t state;
+
+ assert(p && p->context && p->mainloop && (p->state == POLYP_STATE_INIT));
+
+ err = pa_context_connect(p->context, server, 1, NULL);
+ if (err < 0)
+ goto error;
+
+ while (1) {
+ state = pa_context_get_state(p->context);
+
+ if (state == PA_CONTEXT_FAILED)
+ goto error;
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ err = pa_mainloop_iterate(p->mainloop, 1, NULL);
+ if (err < 0)
+ return -EIO;
+ }
+
+ p->state = POLYP_STATE_CONNECTED;
+
+ return 0;
+
+error:
+ fprintf(stderr, "*** POLYPAUDIO: Unable to connect: %s\n",
+ pa_strerror(pa_context_errno(p->context)));
+ return -ECONNREFUSED;
+}
+
+int polyp_start_thread(snd_polyp_t *p)
+{
+ int err;
+ int fd[2] = { -1, -1 };
+
+ assert(p && (p->state == POLYP_STATE_CONNECTED));
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) {
+ perror("socketpair()");
+ return -errno;
+ }
+
+ p->thread_fd = fd[0];
+ p->main_fd = fd[1];
+
+ p->thread_running = 0;
+
+ err = pthread_create(&p->thread, NULL, thread_func, p);
+ if (err) {
+ SNDERR("pthread_create(): %s", strerror(err));
+ close(fd[0]);
+ close(fd[1]);
+ p->main_fd = -1;
+ p->thread_fd = -1;
+ return -err;
+ }
+
+ p->thread_running = 1;
+
+ p->state = POLYP_STATE_READY;
+
+ return 0;
+}
+
+int polyp_poll_descriptors_count(snd_polyp_t *p)
+{
+ assert(p);
+
+ if (p->main_fd >= 0)
+ return 1;
+ else
+ return 0;
+}
+
+int polyp_poll_descriptors(snd_polyp_t *p, struct pollfd *pfd, unsigned int space)
+{
+ int err;
+
+ assert(p);
+
+ err = polyp_finish_poll(p);
+ if (err < 0)
+ return err;
+
+ err = polyp_start_poll(p);
+ if (err < 0)
+ return err;
+
+ assert(space >= 1);
+
+ pfd[0].fd = p->main_fd;
+ pfd[0].events = POLL_IN;
+ pfd[0].revents = 0;
+
+ return 1;
+}
+
+int polyp_poll_revents(snd_polyp_t *p, struct pollfd *pfd, unsigned int nfds, unsigned short *revents)
+{
+ int err;
+
+ assert(p);
+
+ err = polyp_finish_poll(p);
+ if (err < 0)
+ return err;
+
+ err = polyp_check_connection(p);
+ if (err < 0)
+ return err;
+
+ /*
+ * The application might redo the poll immediatly.
+ */
+ return polyp_poll_descriptors(p, pfd, nfds);
+}
diff --git a/polyp/polyp.h b/polyp/polyp.h
new file mode 100644
index 0000000..60cf872
--- /dev/null
+++ b/polyp/polyp.h
@@ -0,0 +1,60 @@
+/*
+ * ALSA <-> Polypaudio plugins
+ *
+ * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <alsa/asoundlib.h>
+
+#include <polyp/polypaudio.h>
+#include <polyp/mainloop.h>
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+
+typedef struct snd_polyp {
+ pa_mainloop *mainloop;
+ pa_context *context;
+
+ int thread_fd, main_fd;
+
+ pthread_t thread;
+ int thread_running;
+
+ enum {
+ POLYP_STATE_INIT,
+ POLYP_STATE_CONNECTED,
+ POLYP_STATE_READY,
+ POLYP_STATE_POLLING,
+ } state;
+} snd_polyp_t;
+
+int polyp_start_poll(snd_polyp_t *p);
+int polyp_finish_poll(snd_polyp_t *p);
+
+int polyp_check_connection(snd_polyp_t *p);
+
+int polyp_wait_operation(snd_polyp_t *p, pa_operation *o);
+
+snd_polyp_t *polyp_new();
+void polyp_free(snd_polyp_t *p);
+
+int polyp_connect(snd_polyp_t *p, const char *server);
+int polyp_start_thread(snd_polyp_t *p);
+
+int polyp_poll_descriptors_count(snd_polyp_t *p);
+int polyp_poll_descriptors(snd_polyp_t *p, struct pollfd *pfd, unsigned int space);
+int polyp_poll_revents(snd_polyp_t *p, struct pollfd *pfd, unsigned int nfds, unsigned short *revents);