diff options
Diffstat (limited to 'polyp')
-rw-r--r-- | polyp/Makefile.am | 14 | ||||
-rw-r--r-- | polyp/ctl_polyp.c | 455 | ||||
-rw-r--r-- | polyp/pcm_polyp.c | 626 | ||||
-rw-r--r-- | polyp/polyp.c | 350 | ||||
-rw-r--r-- | polyp/polyp.h | 60 |
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); |