From 9304087e46a38f932959598720d1e048a6e1042a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 12 Jul 2006 16:47:20 +0200 Subject: Follow Polypaudio/PulseAudio name change Polypaudio recently changed its name to PulseAudio which affects the names of libraries of header files. Update the polyp, now pulse, plug-in to follow this name change. Signed-off-by: Pierre Ossman --- Makefile.am | 6 +- configure.in | 6 +- doc/Makefile.am | 2 +- doc/README-polyp | 41 --- doc/README-pulse | 41 +++ polyp/Makefile.am | 14 - polyp/ctl_polyp.c | 722 ---------------------------------------------------- polyp/pcm_polyp.c | 748 ------------------------------------------------------ polyp/polyp.c | 260 ------------------- polyp/polyp.h | 57 ----- pulse/Makefile.am | 14 + pulse/ctl_pulse.c | 722 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pulse/pcm_pulse.c | 748 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ pulse/pulse.c | 260 +++++++++++++++++++ pulse/pulse.h | 57 +++++ 15 files changed, 1849 insertions(+), 1849 deletions(-) delete mode 100644 doc/README-polyp create mode 100644 doc/README-pulse delete mode 100644 polyp/Makefile.am delete mode 100644 polyp/ctl_polyp.c delete mode 100644 polyp/pcm_polyp.c delete mode 100644 polyp/polyp.c delete mode 100644 polyp/polyp.h create mode 100644 pulse/Makefile.am create mode 100644 pulse/ctl_pulse.c create mode 100644 pulse/pcm_pulse.c create mode 100644 pulse/pulse.c create mode 100644 pulse/pulse.h diff --git a/Makefile.am b/Makefile.am index 8cef8bd..faa3c8e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,8 @@ if HAVE_JACK JACKDIR = jack endif -if HAVE_POLYP -POLYPDIR = polyp +if HAVE_PULSE +PULSEDIR = pulse endif if HAVE_SAMPLERATE SAMPLERATEDIR = rate @@ -11,7 +11,7 @@ if HAVE_AVCODEC A52DIR = a52 endif -SUBDIRS = oss mix $(JACKDIR) $(POLYPDIR) $(SAMPLERATEDIR) $(A52DIR) doc +SUBDIRS = oss mix $(JACKDIR) $(PULSEDIR) $(SAMPLERATEDIR) $(A52DIR) doc EXTRA_DIST = hgcompile version COPYING.GPL AUTOMAKE_OPTIONS = foreign diff --git a/configure.in b/configure.in index 831423d..3f3142e 100644 --- a/configure.in +++ b/configure.in @@ -16,8 +16,8 @@ AC_CHECK_LIB(asound, snd_pcm_ioplug_create,, PKG_CHECK_MODULES(JACK, jack >= 0.98, [HAVE_JACK=yes], [HAVE_JACK=no]) AM_CONDITIONAL(HAVE_JACK, test x$HAVE_JACK = xyes) -PKG_CHECK_MODULES(polypaudio, [polyplib >= 0.9.0], [HAVE_POLYP=yes], [HAVE_POLYP=no]) -AM_CONDITIONAL(HAVE_POLYP, test x$HAVE_POLYP = xyes) +PKG_CHECK_MODULES(PulseAudio, [libpulse >= 0.9.2], [HAVE_PULSE=yes], [HAVE_PULSE=no]) +AM_CONDITIONAL(HAVE_PULSE, test x$HAVE_PULSE = xyes) PKG_CHECK_MODULES(samplerate, [samplerate], [HAVE_SAMPLERATE=yes], [HAVE_SAMPLERATE=no]) AM_CONDITIONAL(HAVE_SAMPLERATE, test x$HAVE_SAMPLERATE = xyes) @@ -49,7 +49,7 @@ AC_OUTPUT([ Makefile oss/Makefile jack/Makefile - polyp/Makefile + pulse/Makefile mix/Makefile rate/Makefile a52/Makefile diff --git a/doc/Makefile.am b/doc/Makefile.am index df66ab8..efcac16 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,2 +1,2 @@ -EXTRA_DIST = README-pcm-oss README-jack README-polyp \ +EXTRA_DIST = README-pcm-oss README-jack README-pulse \ upmix.txt vdownmix.txt samplerate.txt a52.txt diff --git a/doc/README-polyp b/doc/README-polyp deleted file mode 100644 index de46c3d..0000000 --- a/doc/README-polyp +++ /dev/null @@ -1,41 +0,0 @@ -Polypaudio <--> ALSA plugins -============================ - -This plugin allows any program that uses the ALSA API to access a Polypaudio -sound daemon. In other words, native ALSA applications can play and record -sound across a network. - -There are two plugins in the suite, one for PCM and one for mixer control. A -typical configuration will look like: - - pcm.polyp { - type polyp - } - - ctl.polyp { - type polyp - } - -Put the above in ~/.asoundrc, or /etc/asound.conf, and use "polyp" as device -in your ALSA applications. For example: - - % aplay -Dpolyp foo.wav - % amixer -Dpolyp - -Polypaudio will accept more or less any format you throw at it. So a plug -wrapper is unnecessary. Mixing is also handled so dmix will only cause a -performance hit without any gain. - -The plugins will respect your Polypaudio environment variables (like -POLYP_SERVER), but you can override these in ALSA's configuration files. - -Both plugins accept the "server" parameter, specifying which Polypaudio server -to contact. Both also accept the "device" parameter, which indicate which -source and sink to use. - -The mixer control plugin also accepts the parameters "source" and "sink" for -when you need to specify a sink/source combination with different names. If -you need to do this with PCM:s then specify two PCM:s with different "device". - -If you do not specify any source and/or sink, then the server's defaults will -be used. diff --git a/doc/README-pulse b/doc/README-pulse new file mode 100644 index 0000000..d5431b4 --- /dev/null +++ b/doc/README-pulse @@ -0,0 +1,41 @@ +PulseAudio <--> ALSA plugins +============================ + +This plugin allows any program that uses the ALSA API to access a PulseAudio +sound daemon. In other words, native ALSA applications can play and record +sound across a network. + +There are two plugins in the suite, one for PCM and one for mixer control. A +typical configuration will look like: + + pcm.pulse { + type pulse + } + + ctl.pulse { + type pulse + } + +Put the above in ~/.asoundrc, or /etc/asound.conf, and use "pulse" as device +in your ALSA applications. For example: + + % aplay -Dpulse foo.wav + % amixer -Dpulse + +PulseAudio will accept more or less any format you throw at it. So a plug +wrapper is unnecessary. Mixing is also handled so dmix will only cause a +performance hit without any gain. + +The plugins will respect your PulseAudio environment variables (like +PULSE_SERVER), but you can override these in ALSA's configuration files. + +Both plugins accept the "server" parameter, specifying which PulseAudio server +to contact. Both also accept the "device" parameter, which indicate which +source and sink to use. + +The mixer control plugin also accepts the parameters "source" and "sink" for +when you need to specify a sink/source combination with different names. If +you need to do this with PCM:s then specify two PCM:s with different "device". + +If you do not specify any source and/or sink, then the server's defaults will +be used. diff --git a/polyp/Makefile.am b/polyp/Makefile.am deleted file mode 100644 index 858de93..0000000 --- a/polyp/Makefile.am +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index dea3fb8..0000000 --- a/polyp/ctl_polyp.c +++ /dev/null @@ -1,722 +0,0 @@ -/* - * ALSA <-> Polypaudio mixer control plugin - * - * Copyright (c) 2006 by Pierre Ossman - * - * 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 - -#include -#include - -#include "polyp.h" - -typedef struct snd_ctl_polyp { - snd_ctl_ext_t ext; - - snd_polyp_t *p; - - char *source; - char *sink; - - pa_cvolume sink_volume; - pa_cvolume source_volume; - - int sink_muted; - int source_muted; - - int subscribed; - int updated; -} snd_ctl_polyp_t; - -#define SOURCE_VOL_NAME "Capture Volume" -#define SOURCE_MUTE_NAME "Capture Switch" -#define SINK_VOL_NAME "Master Playback Volume" -#define SINK_MUTE_NAME "Master Playback Switch" - -#define UPDATE_SINK_VOL 0x01 -#define UPDATE_SINK_MUTE 0x02 -#define UPDATE_SOURCE_VOL 0x04 -#define UPDATE_SOURCE_MUTE 0x08 - -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; - - assert(ctl); - - if (is_last) { - pa_threaded_mainloop_signal(ctl->p->mainloop, 0); - return; - } - - assert(i); - - if (!!ctl->sink_muted != !!i->mute) { - ctl->sink_muted = i->mute; - ctl->updated |= UPDATE_SINK_MUTE; - polyp_poll_activate(ctl->p); - } - - if (ctl->sink_volume.channels == i->volume.channels) { - for (chan = 0;chan < ctl->sink_volume.channels;chan++) - if (i->volume.values[chan] != ctl->sink_volume.values[chan]) - break; - - if (chan == ctl->sink_volume.channels) - return; - - ctl->updated |= UPDATE_SINK_VOL; - polyp_poll_activate(ctl->p); - } - - memcpy(&ctl->sink_volume, &i->volume, sizeof(pa_cvolume)); -} - -static void source_info_cb(pa_context *c, const pa_source_info *i, int is_last, void *userdata) -{ - snd_ctl_polyp_t *ctl = (snd_ctl_polyp_t*)userdata; - int chan; - - assert(ctl); - - if (is_last) { - pa_threaded_mainloop_signal(ctl->p->mainloop, 0); - return; - } - - assert(i); - - if (!!ctl->source_muted != !!i->mute) { - ctl->source_muted = i->mute; - ctl->updated |= UPDATE_SOURCE_MUTE; - polyp_poll_activate(ctl->p); - } - - if (ctl->source_volume.channels == i->volume.channels) { - for (chan = 0;chan < ctl->source_volume.channels;chan++) - if (i->volume.values[chan] != ctl->source_volume.values[chan]) - break; - - if (chan == ctl->source_volume.channels) - return; - - ctl->updated |= UPDATE_SOURCE_VOL; - polyp_poll_activate(ctl->p); - } - - memcpy(&ctl->source_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->sink, - sink_info_cb, ctl); - pa_operation_unref(o); - - o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, - source_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->sink, - sink_info_cb, ctl); - err = polyp_wait_operation(ctl->p, o); - pa_operation_unref(o); - if (err < 0) - return err; - - o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, - source_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; - int count = 0; - - assert(ctl); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - if (ctl->source) - count += 2; - if (ctl->sink) - count += 2; - - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return count; -} - -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); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - if (ctl->source) { - if (offset == 0) - snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); - else if (offset == 1) - snd_ctl_elem_id_set_name(id, SOURCE_MUTE_NAME); - } else - offset += 2; - - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - if (offset == 2) - snd_ctl_elem_id_set_name(id, SINK_VOL_NAME); - else if (offset == 3) - snd_ctl_elem_id_set_name(id, SINK_MUTE_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, SOURCE_VOL_NAME) == 0) - return 0; - if (strcmp(name, SOURCE_MUTE_NAME) == 0) - return 1; - if (strcmp(name, SINK_VOL_NAME) == 0) - return 2; - if (strcmp(name, SINK_MUTE_NAME) == 0) - return 3; - - 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 = 0; - - if (key > 3) - return -EINVAL; - - assert(ctl); - assert(ctl->p); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - err = polyp_check_connection(ctl->p); - if (err < 0) - goto finish; - - err = polyp_update_volume(ctl); - if (err < 0) - goto finish; - - if (key & 1) - *type = SND_CTL_ELEM_TYPE_BOOLEAN; - else - *type = SND_CTL_ELEM_TYPE_INTEGER; - - *acc = SND_CTL_EXT_ACCESS_READWRITE; - - if (key == 0) - *count = ctl->source_volume.channels; - else if (key == 2) - *count = ctl->sink_volume.channels; - else - *count = 1; - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return err; -} - -static int polyp_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, - long *imin, long *imax, long *istep) -{ - *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 = 0, i; - pa_cvolume *vol = NULL; - - assert(ctl); - assert(ctl->p); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - err = polyp_check_connection(ctl->p); - if (err < 0) - goto finish; - - err = polyp_update_volume(ctl); - if (err < 0) - goto finish; - - switch (key) { - case 0: - vol = &ctl->source_volume; - break; - case 1: - *value = !ctl->source_muted; - break; - case 2: - vol = &ctl->sink_volume; - break; - case 3: - *value = !ctl->sink_muted; - break; - default: - err = -EINVAL; - goto finish; - } - - if (vol) { - for (i = 0;i < vol->channels;i++) - value[i] = vol->values[i]; - } - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return err; -} - -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 = 0, i; - pa_operation *o; - pa_cvolume *vol = NULL; - - assert(ctl); - assert(ctl->p && ctl->p->context); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - err = polyp_check_connection(ctl->p); - if (err < 0) - goto finish; - - err = polyp_update_volume(ctl); - if (err < 0) - goto finish; - - switch (key) { - case 0: - vol = &ctl->source_volume; - break; - case 1: - if (!!ctl->source_muted == !*value) - goto finish; - ctl->source_muted = !*value; - break; - case 2: - vol = &ctl->sink_volume; - break; - case 3: - if (!!ctl->sink_muted == !*value) - goto finish; - ctl->sink_muted = !*value; - break; - default: - err = -EINVAL; - goto finish; - } - - if (vol) { - for (i = 0;i < vol->channels;i++) - if (value[i] != vol->values[i]) - break; - - if (i == vol->channels) - goto finish; - - for (i = 0;i < vol->channels;i++) - vol->values[i] = value[i]; - - if (key == 0) - o = pa_context_set_source_volume_by_name(ctl->p->context, - ctl->source, vol, polyp_context_success_cb, ctl->p); - else - o = pa_context_set_sink_volume_by_name(ctl->p->context, - ctl->sink, vol, polyp_context_success_cb, ctl->p); - } else { - if (key == 1) - o = pa_context_set_source_mute_by_name(ctl->p->context, - ctl->source, ctl->source_muted, polyp_context_success_cb, ctl->p); - else - o = pa_context_set_sink_mute_by_name(ctl->p->context, - ctl->sink, ctl->sink_muted, polyp_context_success_cb, ctl->p); - } - - err = polyp_wait_operation(ctl->p, o); - pa_operation_unref(o); - if (err < 0) - goto finish; - - err = 1; - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return err; -} - -static void polyp_subscribe_events(snd_ctl_ext_t *ext, int subscribe) -{ - snd_ctl_polyp_t *ctl = ext->private_data; - - assert(ctl); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE); - - pa_threaded_mainloop_unlock(ctl->p->mainloop); -} - -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; - int offset; - int err = -EAGAIN; - - assert(ctl); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - if (!ctl->updated || !ctl->subscribed) - goto finish; - - if (ctl->source) - offset = 2; - else - offset = 0; - - if (ctl->updated & UPDATE_SOURCE_VOL) { - polyp_elem_list(ext, 0, id); - ctl->updated &= ~UPDATE_SOURCE_VOL; - } else if (ctl->updated & UPDATE_SOURCE_MUTE) { - polyp_elem_list(ext, 1, id); - ctl->updated &= ~UPDATE_SOURCE_MUTE; - } else if (ctl->updated & UPDATE_SINK_VOL) { - polyp_elem_list(ext, offset + 0, id); - ctl->updated &= ~UPDATE_SINK_VOL; - } else if (ctl->updated & UPDATE_SINK_MUTE) { - polyp_elem_list(ext, offset + 1, id); - ctl->updated &= ~UPDATE_SINK_MUTE; - } - - *event_mask = SND_CTL_EVENT_MASK_VALUE; - - if (!ctl->updated) - polyp_poll_deactivate(ctl->p); - - err = 1; - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return err; -} - -static int polyp_ctl_poll_descriptors_count(snd_ctl_ext_t *ext) -{ - snd_ctl_polyp_t *ctl = ext->private_data; - int count; - - assert(ctl); - assert(ctl->p); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - count = polyp_poll_descriptors_count(ctl->p); - - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return count; -} - -static int polyp_ctl_poll_descriptors(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int space) -{ - int num; - - snd_ctl_polyp_t *ctl = ext->private_data; - - assert(ctl); - assert(ctl->p); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - num = polyp_poll_descriptors(ctl->p, pfd, space); - if (num < 0) - goto finish; - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return num; -} - -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 = 0; - - assert(ctl); - assert(ctl->p); - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - err = polyp_poll_revents(ctl->p, pfd, nfds, revents); - if (err < 0) - goto finish; - - *revents = 0; - - if (ctl->updated) - *revents |= POLLIN; - -finish: - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - return err; -} - -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->source) - free(ctl->source); - if (ctl->sink) - free(ctl->sink); - - 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); - - if (i->default_source_name && !ctl->source) - ctl->source = strdup(i->default_source_name); - if (i->default_sink_name && !ctl->sink) - ctl->sink = strdup(i->default_sink_name); - - pa_threaded_mainloop_signal(ctl->p->mainloop, 0); -} - -SND_CTL_PLUGIN_DEFINE_FUNC(polyp) -{ - snd_config_iterator_t i, next; - const char *server = NULL; - const char *device = NULL; - const char *source = NULL; - const char *sink = 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; - } - if (strcmp(id, "source") == 0) { - if (snd_config_get_string(n, &source) < 0) { - SNDERR("Invalid type for %s", id); - return -EINVAL; - } - continue; - } - if (strcmp(id, "sink") == 0) { - if (snd_config_get_string(n, &sink) < 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(); - if (!ctl->p) { - err = -EIO; - goto error; - } - - err = polyp_connect(ctl->p, server); - if (err < 0) - goto error; - - if (source) - ctl->source = strdup(source); - else if (device) - ctl->source = strdup(device); - - if (sink) - ctl->sink = strdup(sink); - else if (device) - ctl->sink = strdup(device); - - if (!ctl->source || !ctl->sink) { - pa_threaded_mainloop_lock(ctl->p->mainloop); - - o = pa_context_get_server_info(ctl->p->context, server_info_cb, ctl); - err = polyp_wait_operation(ctl->p, o); - - pa_operation_unref(o); - - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - if (err < 0) - goto error; - } - - pa_threaded_mainloop_lock(ctl->p->mainloop); - - pa_context_set_subscribe_callback(ctl->p->context, event_cb, ctl); - - o = pa_context_subscribe(ctl->p->context, - PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, - polyp_context_success_cb, ctl->p); - - err = polyp_wait_operation(ctl->p, o); - - pa_operation_unref(o); - - pa_threaded_mainloop_unlock(ctl->p->mainloop); - - 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->source) - free(ctl->source); - if (ctl->sink) - free(ctl->sink); - - 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 deleted file mode 100644 index 44ae35e..0000000 --- a/polyp/pcm_polyp.c +++ /dev/null @@ -1,748 +0,0 @@ -/* - * ALSA <-> Polypaudio PCM I/O plugin - * - * Copyright (c) 2006 by Pierre Ossman - * - * 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 -#include - -#include -#include - -#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 = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - o = pa_stream_cork(pcm->stream, 0, polyp_stream_success_cb, pcm->p); - assert(o); - - err = polyp_wait_operation(pcm->p, o); - - pa_operation_unref(o); - - if (err < 0) { - err = -EIO; - goto finish; - } - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static int polyp_stop(snd_pcm_ioplug_t *io) -{ - snd_pcm_polyp_t *pcm = io->private_data; - pa_operation *o; - int err = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - o = pa_stream_flush(pcm->stream, polyp_stream_success_cb, pcm->p); - assert(o); - - err = polyp_wait_operation(pcm->p, o); - - pa_operation_unref(o); - - if (err < 0) { - err = -EIO; - goto finish; - } - - o = pa_stream_cork(pcm->stream, 1, polyp_stream_success_cb, pcm->p); - assert(o); - - err = polyp_wait_operation(pcm->p, o); - - pa_operation_unref(o); - - if (err < 0) { - err = -EIO; - goto finish; - } - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -int polyp_drain(snd_pcm_ioplug_t *io) -{ - snd_pcm_polyp_t *pcm = io->private_data; - pa_operation *o; - int err = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - o = pa_stream_drain(pcm->stream, polyp_stream_success_cb, pcm->p); - assert(o); - - err = polyp_wait_operation(pcm->p, o); - - pa_operation_unref(o); - - if (err < 0) { - err = -EIO; - goto finish; - } - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static snd_pcm_sframes_t polyp_pointer(snd_pcm_ioplug_t *io) -{ - snd_pcm_polyp_t *pcm = io->private_data; - int err = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - update_ptr(pcm); - - err = snd_pcm_bytes_to_frames(io->pcm, pcm->ptr); - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static int polyp_delay(snd_pcm_ioplug_t *io, - snd_pcm_sframes_t *delayp) -{ - snd_pcm_polyp_t *pcm = io->private_data; - int err = 0; - pa_usec_t lat; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - if (pa_stream_get_latency(pcm->stream, &lat, NULL)) { - err = -EIO; - goto finish; - } - - *delayp = snd_pcm_bytes_to_frames(io->pcm, pa_usec_to_bytes(lat, &pcm->ss)); - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -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 = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - /* 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, 0); - - /* Make sure the buffer pointer is in sync */ - update_ptr(pcm); - - if (pcm->last_size < pcm->buffer_attr.minreq) - polyp_poll_deactivate(pcm->p); - - err = size; - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -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 = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(pcm->stream); - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - /* 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, (const 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); - - if (pcm->last_size < pcm->buffer_attr.minreq) - polyp_poll_deactivate(pcm->p); - - err = size - (remain_size / pcm->frame_size); - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static void stream_request_cb(pa_stream *p, size_t length, void *userdata) -{ - snd_pcm_polyp_t *pcm = userdata; - - assert(pcm); - assert(pcm->p); - - polyp_poll_activate(pcm->p); -} - -static int polyp_pcm_poll_descriptors_count(snd_pcm_ioplug_t *io) -{ - snd_pcm_polyp_t *pcm = io->private_data; - int count; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - count = polyp_poll_descriptors_count(pcm->p); - - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return count; -} - -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; - int err; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - err = polyp_poll_descriptors(pcm->p, pfd, space); - - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -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 = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - err = polyp_poll_revents(pcm->p, pfd, nfds, revents); - if (err < 0) - goto finish; - - *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 |= POLLOUT; - else - *revents |= POLLIN; - } - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static int polyp_prepare(snd_pcm_ioplug_t *io) -{ - pa_channel_map map; - snd_pcm_polyp_t *pcm = io->private_data; - int err = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - if (pcm->stream) { - pa_stream_disconnect(pcm->stream); - polyp_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_TERMINATED); - pa_stream_unref(pcm->stream); - pcm->stream = NULL; - } - - err = polyp_check_connection(pcm->p); - if (err < 0) - goto finish; - - assert(pcm->stream == NULL); - - if (io->stream == SND_PCM_STREAM_PLAYBACK) - pcm->stream = pa_stream_new(pcm->p->context, "ALSA Playback", &pcm->ss, - pa_channel_map_init_auto(&map, pcm->ss.channels, PA_CHANNEL_MAP_ALSA)); - else - pcm->stream = pa_stream_new(pcm->p->context, "ALSA Capture", &pcm->ss, - pa_channel_map_init_auto(&map, pcm->ss.channels, PA_CHANNEL_MAP_ALSA)); - assert(pcm->stream); - - pa_stream_set_state_callback(pcm->stream, polyp_stream_state_cb, pcm->p); - - if (io->stream == SND_PCM_STREAM_PLAYBACK) { - pa_stream_set_write_callback(pcm->stream, stream_request_cb, pcm); - pa_stream_connect_playback(pcm->stream, pcm->device, &pcm->buffer_attr, - PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING, NULL, NULL); - } else { - pa_stream_set_read_callback(pcm->stream, stream_request_cb, pcm); - pa_stream_connect_record(pcm->stream, pcm->device, &pcm->buffer_attr, - PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING); - } - - err = polyp_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_READY); - if (err < 0) { - fprintf(stderr, "*** POLYPAUDIO: Unable to create stream.\n"); - pa_stream_unref(pcm->stream); - pcm->stream = NULL; - goto finish; - } - - pcm->last_size = 0; - pcm->ptr = 0; - pcm->offset = 0; - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static int polyp_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) -{ - snd_pcm_polyp_t *pcm = io->private_data; - int err = 0; - - assert(pcm); - assert(pcm->p); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - assert(!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)); - err = -EINVAL; - goto finish; - } - - 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; - -finish: - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - return err; -} - -static int polyp_close(snd_pcm_ioplug_t *io) -{ - snd_pcm_polyp_t *pcm = io->private_data; - - assert(pcm); - - pa_threaded_mainloop_lock(pcm->p->mainloop); - - if (pcm->stream) { - pa_stream_disconnect(pcm->stream); - polyp_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_TERMINATED); - pa_stream_unref(pcm->stream); - } - - pa_threaded_mainloop_unlock(pcm->p->mainloop); - - 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, - .delay = polyp_delay, - .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, - .delay = polyp_delay, - .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(); - if (!pcm->p) { - err = -EIO; - goto error; - } - - err = polyp_connect(pcm->p, server); - 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 deleted file mode 100644 index f5e5cac..0000000 --- a/polyp/polyp.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * ALSA <-> Polypaudio plugins - * - * Copyright (c) 2006 by Pierre Ossman - * - * 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 -#include -#include -#include - -#include "polyp.h" - -int polyp_check_connection(snd_polyp_t *p) -{ - pa_context_state_t state; - - assert(p && p->context && p->mainloop); - - state = pa_context_get_state(p->context); - - if (state != PA_CONTEXT_READY) - return -EIO; - - return 0; -} - -void polyp_stream_state_cb(pa_stream *s, void * userdata) -{ - snd_polyp_t *p = userdata; - - assert(s); - assert(p); - - pa_threaded_mainloop_signal(p->mainloop, 0); -} - -void polyp_stream_success_cb(pa_stream *s, int success, void *userdata) -{ - snd_polyp_t *p = userdata; - - assert(s); - assert(p); - - pa_threaded_mainloop_signal(p->mainloop, 0); -} - -void polyp_context_success_cb(pa_context *c, int success, void *userdata) -{ - snd_polyp_t *p = userdata; - - assert(c); - assert(p); - - pa_threaded_mainloop_signal(p->mainloop, 0); -} - -int polyp_wait_operation(snd_polyp_t *p, pa_operation *o) -{ - assert(p && o && (p->state == POLYP_STATE_READY) && p->mainloop); - - while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) - pa_threaded_mainloop_wait(p->mainloop); - - return 0; -} - -int polyp_wait_stream_state(snd_polyp_t *p, pa_stream *stream, pa_stream_state_t target) -{ - pa_stream_state_t state; - - assert(p && stream && (p->state == POLYP_STATE_READY) && p->mainloop); - - while (1) { - state = pa_stream_get_state(stream); - - if (state == PA_STREAM_FAILED) - return -EIO; - - if (state == target) - break; - - pa_threaded_mainloop_wait(p->mainloop); - } - - return 0; -} - -static void context_state_cb(pa_context *c, void *userdata) { - snd_polyp_t *p = userdata; - assert(c); - - switch (pa_context_get_state(c)) { - case PA_CONTEXT_READY: - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - pa_threaded_mainloop_signal(p->mainloop, 0); - break; - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - } -} - -snd_polyp_t *polyp_new() -{ - snd_polyp_t *p; - int fd[2] = { -1, -1 }; - char proc[PATH_MAX], buf[PATH_MAX + 20]; - - p = calloc(1, sizeof(snd_polyp_t)); - assert(p); - - p->state = POLYP_STATE_INIT; - - if (pipe(fd)) { - free(p); - return NULL; - } - - p->main_fd = fd[0]; - p->thread_fd = fd[1]; - - fcntl(fd[0], F_SETFL, O_NONBLOCK); - fcntl(fd[1], F_SETFL, O_NONBLOCK); - - signal(SIGPIPE, SIG_IGN); /* Yes, ugly as hell */ - - p->mainloop = pa_threaded_mainloop_new(); - assert(p->mainloop); - - if (pa_threaded_mainloop_start(p->mainloop) < 0) { - pa_threaded_mainloop_free(p->mainloop); - close(fd[0]); - close(fd[1]); - free(p); - return NULL; - } - - if (pa_get_binary_name(proc, sizeof(proc))) - snprintf(buf, sizeof(buf), "ALSA plug-in [%s]", pa_path_get_filename(proc)); - else - snprintf(buf, sizeof(buf), "ALSA plug-in"); - - p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), buf); - assert(p->context); - - return p; -} - -void polyp_free(snd_polyp_t *p) -{ - pa_threaded_mainloop_stop(p->mainloop); - - pa_context_unref(p->context); - pa_threaded_mainloop_free(p->mainloop); - - close(p->thread_fd); - close(p->main_fd); - - free(p); -} - -int polyp_connect(snd_polyp_t *p, const char *server) -{ - int err; - - assert(p && p->context && p->mainloop && (p->state == POLYP_STATE_INIT)); - - pa_threaded_mainloop_lock(p->mainloop); - - err = pa_context_connect(p->context, server, 0, NULL); - if (err < 0) - goto error; - - pa_context_set_state_callback(p->context, context_state_cb, p); - - pa_threaded_mainloop_wait(p->mainloop); - - if (pa_context_get_state(p->context) != PA_CONTEXT_READY) - goto error; - - pa_threaded_mainloop_unlock(p->mainloop); - - p->state = POLYP_STATE_READY; - - return 0; - -error: - fprintf(stderr, "*** POLYPAUDIO: Unable to connect: %s\n", - pa_strerror(pa_context_errno(p->context))); - - pa_threaded_mainloop_unlock(p->mainloop); - - return -ECONNREFUSED; -} - -void polyp_poll_activate(snd_polyp_t *p) -{ - assert(p); - - write(p->thread_fd, "a", 1); -} - -void polyp_poll_deactivate(snd_polyp_t *p) -{ - char buf[10]; - - assert(p); - - /* Drain the pipe */ - while (read(p->main_fd, buf, sizeof(buf)) > 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) -{ - assert(p); - - assert(space >= 1); - - pfd[0].fd = p->main_fd; - pfd[0].events = POLLIN; - pfd[0].revents = 0; - - return 1; -} - -int polyp_poll_revents(snd_polyp_t *p, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) -{ - assert(p); - - return 1; -} diff --git a/polyp/polyp.h b/polyp/polyp.h deleted file mode 100644 index f210e18..0000000 --- a/polyp/polyp.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * ALSA <-> Polypaudio plugins - * - * Copyright (c) 2006 by Pierre Ossman - * - * 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 - -#include - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) - -typedef struct snd_polyp { - pa_threaded_mainloop *mainloop; - pa_context *context; - - int thread_fd, main_fd; - - enum { - POLYP_STATE_INIT, - POLYP_STATE_READY, - } state; -} snd_polyp_t; - -int polyp_check_connection(snd_polyp_t *p); - -void polyp_stream_state_cb(pa_stream *s, void * userdata); -void polyp_stream_success_cb(pa_stream *s, int success, void *userdata); -void polyp_context_success_cb(pa_context *c, int success, void *userdata); - -int polyp_wait_operation(snd_polyp_t *p, pa_operation *o); -int polyp_wait_stream_state(snd_polyp_t *p, pa_stream *stream, pa_stream_state_t target); - -snd_polyp_t *polyp_new(); -void polyp_free(snd_polyp_t *p); - -int polyp_connect(snd_polyp_t *p, const char *server); - -void polyp_poll_activate(snd_polyp_t *p); -void polyp_poll_deactivate(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); diff --git a/pulse/Makefile.am b/pulse/Makefile.am new file mode 100644 index 0000000..3da25f0 --- /dev/null +++ b/pulse/Makefile.am @@ -0,0 +1,14 @@ +asound_module_pcm_LTLIBRARIES = libasound_module_pcm_pulse.la +asound_module_ctl_LTLIBRARIES = libasound_module_ctl_pulse.la + +asound_module_pcmdir = $(libdir)/alsa-lib +asound_module_ctldir = $(libdir)/alsa-lib + +AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ $(PTHREAD_CFLAGS) $(pulseaudio_CFLAGS) -D_GNU_SOURCE +AM_LDFLAGS = -module -avoid-version -export-dynamic + +libasound_module_pcm_pulse_la_SOURCES = pcm_pulse.c pulse.c pulse.h +libasound_module_pcm_pulse_la_LIBADD = @ALSA_LIBS@ $(PTHREAD_LIBS) $(pulseaudio_LIBS) + +libasound_module_ctl_pulse_la_SOURCES = ctl_pulse.c pulse.c pulse.h +libasound_module_ctl_pulse_la_LIBADD = @ALSA_LIBS@ $(PTHREAD_LIBS) $(pulseaudio_LIBS) diff --git a/pulse/ctl_pulse.c b/pulse/ctl_pulse.c new file mode 100644 index 0000000..06e087f --- /dev/null +++ b/pulse/ctl_pulse.c @@ -0,0 +1,722 @@ +/* + * ALSA <-> PulseAudio mixer control plugin + * + * Copyright (c) 2006 by Pierre Ossman + * + * 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 + +#include +#include + +#include "pulse.h" + +typedef struct snd_ctl_pulse { + snd_ctl_ext_t ext; + + snd_pulse_t *p; + + char *source; + char *sink; + + pa_cvolume sink_volume; + pa_cvolume source_volume; + + int sink_muted; + int source_muted; + + int subscribed; + int updated; +} snd_ctl_pulse_t; + +#define SOURCE_VOL_NAME "Capture Volume" +#define SOURCE_MUTE_NAME "Capture Switch" +#define SINK_VOL_NAME "Master Playback Volume" +#define SINK_MUTE_NAME "Master Playback Switch" + +#define UPDATE_SINK_VOL 0x01 +#define UPDATE_SINK_MUTE 0x02 +#define UPDATE_SOURCE_VOL 0x04 +#define UPDATE_SOURCE_MUTE 0x08 + +static void sink_info_cb(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) +{ + snd_ctl_pulse_t *ctl = (snd_ctl_pulse_t*)userdata; + int chan; + + assert(ctl); + + if (is_last) { + pa_threaded_mainloop_signal(ctl->p->mainloop, 0); + return; + } + + assert(i); + + if (!!ctl->sink_muted != !!i->mute) { + ctl->sink_muted = i->mute; + ctl->updated |= UPDATE_SINK_MUTE; + pulse_poll_activate(ctl->p); + } + + if (ctl->sink_volume.channels == i->volume.channels) { + for (chan = 0;chan < ctl->sink_volume.channels;chan++) + if (i->volume.values[chan] != ctl->sink_volume.values[chan]) + break; + + if (chan == ctl->sink_volume.channels) + return; + + ctl->updated |= UPDATE_SINK_VOL; + pulse_poll_activate(ctl->p); + } + + memcpy(&ctl->sink_volume, &i->volume, sizeof(pa_cvolume)); +} + +static void source_info_cb(pa_context *c, const pa_source_info *i, int is_last, void *userdata) +{ + snd_ctl_pulse_t *ctl = (snd_ctl_pulse_t*)userdata; + int chan; + + assert(ctl); + + if (is_last) { + pa_threaded_mainloop_signal(ctl->p->mainloop, 0); + return; + } + + assert(i); + + if (!!ctl->source_muted != !!i->mute) { + ctl->source_muted = i->mute; + ctl->updated |= UPDATE_SOURCE_MUTE; + pulse_poll_activate(ctl->p); + } + + if (ctl->source_volume.channels == i->volume.channels) { + for (chan = 0;chan < ctl->source_volume.channels;chan++) + if (i->volume.values[chan] != ctl->source_volume.values[chan]) + break; + + if (chan == ctl->source_volume.channels) + return; + + ctl->updated |= UPDATE_SOURCE_VOL; + pulse_poll_activate(ctl->p); + } + + memcpy(&ctl->source_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_pulse_t *ctl = (snd_ctl_pulse_t*)userdata; + pa_operation *o; + + assert(ctl && ctl->p && ctl->p->context); + + o = pa_context_get_sink_info_by_name(ctl->p->context, ctl->sink, + sink_info_cb, ctl); + pa_operation_unref(o); + + o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, + source_info_cb, ctl); + pa_operation_unref(o); +} + +static int pulse_update_volume(snd_ctl_pulse_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->sink, + sink_info_cb, ctl); + err = pulse_wait_operation(ctl->p, o); + pa_operation_unref(o); + if (err < 0) + return err; + + o = pa_context_get_source_info_by_name(ctl->p->context, ctl->source, + source_info_cb, ctl); + err = pulse_wait_operation(ctl->p, o); + pa_operation_unref(o); + if (err < 0) + return err; + + return 0; +} + +static int pulse_elem_count(snd_ctl_ext_t *ext) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int count = 0; + + assert(ctl); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + if (ctl->source) + count += 2; + if (ctl->sink) + count += 2; + + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return count; +} + +static int pulse_elem_list(snd_ctl_ext_t *ext, unsigned int offset, + snd_ctl_elem_id_t *id) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + + assert(ctl); + + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + if (ctl->source) { + if (offset == 0) + snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); + else if (offset == 1) + snd_ctl_elem_id_set_name(id, SOURCE_MUTE_NAME); + } else + offset += 2; + + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + if (offset == 2) + snd_ctl_elem_id_set_name(id, SINK_VOL_NAME); + else if (offset == 3) + snd_ctl_elem_id_set_name(id, SINK_MUTE_NAME); + + return 0; +} + +static snd_ctl_ext_key_t pulse_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, SOURCE_VOL_NAME) == 0) + return 0; + if (strcmp(name, SOURCE_MUTE_NAME) == 0) + return 1; + if (strcmp(name, SINK_VOL_NAME) == 0) + return 2; + if (strcmp(name, SINK_MUTE_NAME) == 0) + return 3; + + return SND_CTL_EXT_KEY_NOT_FOUND; +} + +static int pulse_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + int *type, unsigned int *acc, unsigned int *count) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int err = 0; + + if (key > 3) + return -EINVAL; + + assert(ctl); + assert(ctl->p); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + err = pulse_check_connection(ctl->p); + if (err < 0) + goto finish; + + err = pulse_update_volume(ctl); + if (err < 0) + goto finish; + + if (key & 1) + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + else + *type = SND_CTL_ELEM_TYPE_INTEGER; + + *acc = SND_CTL_EXT_ACCESS_READWRITE; + + if (key == 0) + *count = ctl->source_volume.channels; + else if (key == 2) + *count = ctl->sink_volume.channels; + else + *count = 1; + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return err; +} + +static int pulse_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *imin, long *imax, long *istep) +{ + *istep = 1; + *imin = 0; + *imax = PA_VOLUME_NORM; + + return 0; +} + +static int pulse_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int err = 0, i; + pa_cvolume *vol = NULL; + + assert(ctl); + assert(ctl->p); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + err = pulse_check_connection(ctl->p); + if (err < 0) + goto finish; + + err = pulse_update_volume(ctl); + if (err < 0) + goto finish; + + switch (key) { + case 0: + vol = &ctl->source_volume; + break; + case 1: + *value = !ctl->source_muted; + break; + case 2: + vol = &ctl->sink_volume; + break; + case 3: + *value = !ctl->sink_muted; + break; + default: + err = -EINVAL; + goto finish; + } + + if (vol) { + for (i = 0;i < vol->channels;i++) + value[i] = vol->values[i]; + } + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return err; +} + +static int pulse_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, + long *value) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int err = 0, i; + pa_operation *o; + pa_cvolume *vol = NULL; + + assert(ctl); + assert(ctl->p && ctl->p->context); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + err = pulse_check_connection(ctl->p); + if (err < 0) + goto finish; + + err = pulse_update_volume(ctl); + if (err < 0) + goto finish; + + switch (key) { + case 0: + vol = &ctl->source_volume; + break; + case 1: + if (!!ctl->source_muted == !*value) + goto finish; + ctl->source_muted = !*value; + break; + case 2: + vol = &ctl->sink_volume; + break; + case 3: + if (!!ctl->sink_muted == !*value) + goto finish; + ctl->sink_muted = !*value; + break; + default: + err = -EINVAL; + goto finish; + } + + if (vol) { + for (i = 0;i < vol->channels;i++) + if (value[i] != vol->values[i]) + break; + + if (i == vol->channels) + goto finish; + + for (i = 0;i < vol->channels;i++) + vol->values[i] = value[i]; + + if (key == 0) + o = pa_context_set_source_volume_by_name(ctl->p->context, + ctl->source, vol, pulse_context_success_cb, ctl->p); + else + o = pa_context_set_sink_volume_by_name(ctl->p->context, + ctl->sink, vol, pulse_context_success_cb, ctl->p); + } else { + if (key == 1) + o = pa_context_set_source_mute_by_name(ctl->p->context, + ctl->source, ctl->source_muted, pulse_context_success_cb, ctl->p); + else + o = pa_context_set_sink_mute_by_name(ctl->p->context, + ctl->sink, ctl->sink_muted, pulse_context_success_cb, ctl->p); + } + + err = pulse_wait_operation(ctl->p, o); + pa_operation_unref(o); + if (err < 0) + goto finish; + + err = 1; + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return err; +} + +static void pulse_subscribe_events(snd_ctl_ext_t *ext, int subscribe) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + + assert(ctl); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE); + + pa_threaded_mainloop_unlock(ctl->p->mainloop); +} + +static int pulse_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, + unsigned int *event_mask) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int offset; + int err = -EAGAIN; + + assert(ctl); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + if (!ctl->updated || !ctl->subscribed) + goto finish; + + if (ctl->source) + offset = 2; + else + offset = 0; + + if (ctl->updated & UPDATE_SOURCE_VOL) { + pulse_elem_list(ext, 0, id); + ctl->updated &= ~UPDATE_SOURCE_VOL; + } else if (ctl->updated & UPDATE_SOURCE_MUTE) { + pulse_elem_list(ext, 1, id); + ctl->updated &= ~UPDATE_SOURCE_MUTE; + } else if (ctl->updated & UPDATE_SINK_VOL) { + pulse_elem_list(ext, offset + 0, id); + ctl->updated &= ~UPDATE_SINK_VOL; + } else if (ctl->updated & UPDATE_SINK_MUTE) { + pulse_elem_list(ext, offset + 1, id); + ctl->updated &= ~UPDATE_SINK_MUTE; + } + + *event_mask = SND_CTL_EVENT_MASK_VALUE; + + if (!ctl->updated) + pulse_poll_deactivate(ctl->p); + + err = 1; + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return err; +} + +static int pulse_ctl_poll_descriptors_count(snd_ctl_ext_t *ext) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int count; + + assert(ctl); + assert(ctl->p); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + count = pulse_poll_descriptors_count(ctl->p); + + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return count; +} + +static int pulse_ctl_poll_descriptors(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int space) +{ + int num; + + snd_ctl_pulse_t *ctl = ext->private_data; + + assert(ctl); + assert(ctl->p); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + num = pulse_poll_descriptors(ctl->p, pfd, space); + if (num < 0) + goto finish; + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return num; +} + +static int pulse_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + int err = 0; + + assert(ctl); + assert(ctl->p); + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + err = pulse_poll_revents(ctl->p, pfd, nfds, revents); + if (err < 0) + goto finish; + + *revents = 0; + + if (ctl->updated) + *revents |= POLLIN; + +finish: + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + return err; +} + +static void pulse_close(snd_ctl_ext_t *ext) +{ + snd_ctl_pulse_t *ctl = ext->private_data; + + assert(ctl); + + if (ctl->p) + pulse_free(ctl->p); + + if (ctl->source) + free(ctl->source); + if (ctl->sink) + free(ctl->sink); + + free(ctl); +} + +static snd_ctl_ext_callback_t pulse_ext_callback = { + .elem_count = pulse_elem_count, + .elem_list = pulse_elem_list, + .find_elem = pulse_find_elem, + .get_attribute = pulse_get_attribute, + .get_integer_info = pulse_get_integer_info, + .read_integer = pulse_read_integer, + .write_integer = pulse_write_integer, + .subscribe_events = pulse_subscribe_events, + .read_event = pulse_read_event, + .poll_descriptors_count = pulse_ctl_poll_descriptors_count, + .poll_descriptors = pulse_ctl_poll_descriptors, + .poll_revents = pulse_ctl_poll_revents, + .close = pulse_close, +}; + +static void server_info_cb(pa_context *c, const pa_server_info*i, void *userdata) +{ + snd_ctl_pulse_t *ctl = (snd_ctl_pulse_t*)userdata; + + assert(ctl && i); + + if (i->default_source_name && !ctl->source) + ctl->source = strdup(i->default_source_name); + if (i->default_sink_name && !ctl->sink) + ctl->sink = strdup(i->default_sink_name); + + pa_threaded_mainloop_signal(ctl->p->mainloop, 0); +} + +SND_CTL_PLUGIN_DEFINE_FUNC(pulse) +{ + snd_config_iterator_t i, next; + const char *server = NULL; + const char *device = NULL; + const char *source = NULL; + const char *sink = NULL; + int err; + snd_ctl_pulse_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; + } + if (strcmp(id, "source") == 0) { + if (snd_config_get_string(n, &source) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + if (strcmp(id, "sink") == 0) { + if (snd_config_get_string(n, &sink) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + ctl = calloc(1, sizeof(*ctl)); + + ctl->p = pulse_new(); + if (!ctl->p) { + err = -EIO; + goto error; + } + + err = pulse_connect(ctl->p, server); + if (err < 0) + goto error; + + if (source) + ctl->source = strdup(source); + else if (device) + ctl->source = strdup(device); + + if (sink) + ctl->sink = strdup(sink); + else if (device) + ctl->sink = strdup(device); + + if (!ctl->source || !ctl->sink) { + pa_threaded_mainloop_lock(ctl->p->mainloop); + + o = pa_context_get_server_info(ctl->p->context, server_info_cb, ctl); + err = pulse_wait_operation(ctl->p, o); + + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + if (err < 0) + goto error; + } + + pa_threaded_mainloop_lock(ctl->p->mainloop); + + pa_context_set_subscribe_callback(ctl->p->context, event_cb, ctl); + + o = pa_context_subscribe(ctl->p->context, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, + pulse_context_success_cb, ctl->p); + + err = pulse_wait_operation(ctl->p, o); + + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(ctl->p->mainloop); + + if (err < 0) + goto error; + + ctl->ext.version = SND_CTL_EXT_VERSION; + ctl->ext.card_idx = 0; + strncpy(ctl->ext.id, "pulse", 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 = &pulse_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->source) + free(ctl->source); + if (ctl->sink) + free(ctl->sink); + + if (ctl->p) + pulse_free(ctl->p); + + free(ctl); + + return err; +} + +SND_CTL_PLUGIN_SYMBOL(pulse); diff --git a/pulse/pcm_pulse.c b/pulse/pcm_pulse.c new file mode 100644 index 0000000..3f5247e --- /dev/null +++ b/pulse/pcm_pulse.c @@ -0,0 +1,748 @@ +/* + * ALSA <-> PulseAudio PCM I/O plugin + * + * Copyright (c) 2006 by Pierre Ossman + * + * 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 +#include + +#include +#include + +#include "pulse.h" + +typedef struct snd_pcm_pulse { + snd_pcm_ioplug_t io; + + snd_pulse_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_pulse_t; + +static void update_ptr(snd_pcm_pulse_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 pulse_start(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + pa_operation *o; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + o = pa_stream_cork(pcm->stream, 0, pulse_stream_success_cb, pcm->p); + assert(o); + + err = pulse_wait_operation(pcm->p, o); + + pa_operation_unref(o); + + if (err < 0) { + err = -EIO; + goto finish; + } + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_stop(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + pa_operation *o; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + o = pa_stream_flush(pcm->stream, pulse_stream_success_cb, pcm->p); + assert(o); + + err = pulse_wait_operation(pcm->p, o); + + pa_operation_unref(o); + + if (err < 0) { + err = -EIO; + goto finish; + } + + o = pa_stream_cork(pcm->stream, 1, pulse_stream_success_cb, pcm->p); + assert(o); + + err = pulse_wait_operation(pcm->p, o); + + pa_operation_unref(o); + + if (err < 0) { + err = -EIO; + goto finish; + } + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +int pulse_drain(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + pa_operation *o; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + o = pa_stream_drain(pcm->stream, pulse_stream_success_cb, pcm->p); + assert(o); + + err = pulse_wait_operation(pcm->p, o); + + pa_operation_unref(o); + + if (err < 0) { + err = -EIO; + goto finish; + } + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static snd_pcm_sframes_t pulse_pointer(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + update_ptr(pcm); + + err = snd_pcm_bytes_to_frames(io->pcm, pcm->ptr); + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_delay(snd_pcm_ioplug_t *io, + snd_pcm_sframes_t *delayp) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int err = 0; + pa_usec_t lat; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + if (pa_stream_get_latency(pcm->stream, &lat, NULL)) { + err = -EIO; + goto finish; + } + + *delayp = snd_pcm_bytes_to_frames(io->pcm, pa_usec_to_bytes(lat, &pcm->ss)); + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static snd_pcm_sframes_t pulse_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_pulse_t *pcm = io->private_data; + const char *buf; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + /* 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, 0); + + /* Make sure the buffer pointer is in sync */ + update_ptr(pcm); + + if (pcm->last_size < pcm->buffer_attr.minreq) + pulse_poll_deactivate(pcm->p); + + err = size; + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static snd_pcm_sframes_t pulse_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_pulse_t *pcm = io->private_data; + void *dst_buf, *src_buf; + size_t remain_size, frag_length; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(pcm->stream); + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + /* 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, (const 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); + + if (pcm->last_size < pcm->buffer_attr.minreq) + pulse_poll_deactivate(pcm->p); + + err = size - (remain_size / pcm->frame_size); + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static void stream_request_cb(pa_stream *p, size_t length, void *userdata) +{ + snd_pcm_pulse_t *pcm = userdata; + + assert(pcm); + assert(pcm->p); + + pulse_poll_activate(pcm->p); +} + +static int pulse_pcm_poll_descriptors_count(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int count; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + count = pulse_poll_descriptors_count(pcm->p); + + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return count; +} + +static int pulse_pcm_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int space) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int err; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + err = pulse_poll_descriptors(pcm->p, pfd, space); + + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_pcm_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + err = pulse_poll_revents(pcm->p, pfd, nfds, revents); + if (err < 0) + goto finish; + + *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 |= POLLOUT; + else + *revents |= POLLIN; + } + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_prepare(snd_pcm_ioplug_t *io) +{ + pa_channel_map map; + snd_pcm_pulse_t *pcm = io->private_data; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + if (pcm->stream) { + pa_stream_disconnect(pcm->stream); + pulse_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_TERMINATED); + pa_stream_unref(pcm->stream); + pcm->stream = NULL; + } + + err = pulse_check_connection(pcm->p); + if (err < 0) + goto finish; + + assert(pcm->stream == NULL); + + if (io->stream == SND_PCM_STREAM_PLAYBACK) + pcm->stream = pa_stream_new(pcm->p->context, "ALSA Playback", &pcm->ss, + pa_channel_map_init_auto(&map, pcm->ss.channels, PA_CHANNEL_MAP_ALSA)); + else + pcm->stream = pa_stream_new(pcm->p->context, "ALSA Capture", &pcm->ss, + pa_channel_map_init_auto(&map, pcm->ss.channels, PA_CHANNEL_MAP_ALSA)); + assert(pcm->stream); + + pa_stream_set_state_callback(pcm->stream, pulse_stream_state_cb, pcm->p); + + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + pa_stream_set_write_callback(pcm->stream, stream_request_cb, pcm); + pa_stream_connect_playback(pcm->stream, pcm->device, &pcm->buffer_attr, + PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING, NULL, NULL); + } else { + pa_stream_set_read_callback(pcm->stream, stream_request_cb, pcm); + pa_stream_connect_record(pcm->stream, pcm->device, &pcm->buffer_attr, + PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING); + } + + err = pulse_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_READY); + if (err < 0) { + fprintf(stderr, "*** POLYPAUDIO: Unable to create stream.\n"); + pa_stream_unref(pcm->stream); + pcm->stream = NULL; + goto finish; + } + + pcm->last_size = 0; + pcm->ptr = 0; + pcm->offset = 0; + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) +{ + snd_pcm_pulse_t *pcm = io->private_data; + int err = 0; + + assert(pcm); + assert(pcm->p); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + assert(!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)); + err = -EINVAL; + goto finish; + } + + 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; + +finish: + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + return err; +} + +static int pulse_close(snd_pcm_ioplug_t *io) +{ + snd_pcm_pulse_t *pcm = io->private_data; + + assert(pcm); + + pa_threaded_mainloop_lock(pcm->p->mainloop); + + if (pcm->stream) { + pa_stream_disconnect(pcm->stream); + pulse_wait_stream_state(pcm->p, pcm->stream, PA_STREAM_TERMINATED); + pa_stream_unref(pcm->stream); + } + + pa_threaded_mainloop_unlock(pcm->p->mainloop); + + if (pcm->p) + pulse_free(pcm->p); + + if (pcm->device) + free(pcm->device); + + free(pcm); + + return 0; +} + +static snd_pcm_ioplug_callback_t pulse_playback_callback = { + .start = pulse_start, + .stop = pulse_stop, + .drain = pulse_drain, + .pointer = pulse_pointer, + .transfer = pulse_write, + .delay = pulse_delay, + .poll_descriptors_count = pulse_pcm_poll_descriptors_count, + .poll_descriptors = pulse_pcm_poll_descriptors, + .poll_revents = pulse_pcm_poll_revents, + .prepare = pulse_prepare, + .hw_params = pulse_hw_params, + .close = pulse_close, +}; + + +static snd_pcm_ioplug_callback_t pulse_capture_callback = { + .start = pulse_start, + .stop = pulse_stop, + .pointer = pulse_pointer, + .transfer = pulse_read, + .delay = pulse_delay, + .poll_descriptors_count = pulse_pcm_poll_descriptors_count, + .poll_descriptors = pulse_pcm_poll_descriptors, + .poll_revents = pulse_pcm_poll_revents, + .prepare = pulse_prepare, + .hw_params = pulse_hw_params, + .close = pulse_close, +}; + + +static int pulse_hw_constraint(snd_pcm_pulse_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(pulse) +{ + snd_config_iterator_t i, next; + const char *server = NULL; + const char *device = NULL; + int err; + snd_pcm_pulse_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 = pulse_new(); + if (!pcm->p) { + err = -EIO; + goto error; + } + + err = pulse_connect(pcm->p, server); + 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 ? + &pulse_playback_callback : &pulse_capture_callback; + pcm->io.private_data = pcm; + + err = snd_pcm_ioplug_create(&pcm->io, name, stream, mode); + if (err < 0) + goto error; + + err = pulse_hw_constraint(pcm); + if (err < 0) { + snd_pcm_ioplug_delete(&pcm->io); + goto error; + } + + *pcmp = pcm->io.pcm; + return 0; + +error: + if (pcm->p) + pulse_free(pcm->p); + + free(pcm); + + return err; +} + +SND_PCM_PLUGIN_SYMBOL(pulse); diff --git a/pulse/pulse.c b/pulse/pulse.c new file mode 100644 index 0000000..fd80d9c --- /dev/null +++ b/pulse/pulse.c @@ -0,0 +1,260 @@ +/* + * ALSA <-> PulseAudio plugins + * + * Copyright (c) 2006 by Pierre Ossman + * + * 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 +#include +#include +#include + +#include "pulse.h" + +int pulse_check_connection(snd_pulse_t *p) +{ + pa_context_state_t state; + + assert(p && p->context && p->mainloop); + + state = pa_context_get_state(p->context); + + if (state != PA_CONTEXT_READY) + return -EIO; + + return 0; +} + +void pulse_stream_state_cb(pa_stream *s, void * userdata) +{ + snd_pulse_t *p = userdata; + + assert(s); + assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +void pulse_stream_success_cb(pa_stream *s, int success, void *userdata) +{ + snd_pulse_t *p = userdata; + + assert(s); + assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +void pulse_context_success_cb(pa_context *c, int success, void *userdata) +{ + snd_pulse_t *p = userdata; + + assert(c); + assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +int pulse_wait_operation(snd_pulse_t *p, pa_operation *o) +{ + assert(p && o && (p->state == PULSE_STATE_READY) && p->mainloop); + + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(p->mainloop); + + return 0; +} + +int pulse_wait_stream_state(snd_pulse_t *p, pa_stream *stream, pa_stream_state_t target) +{ + pa_stream_state_t state; + + assert(p && stream && (p->state == PULSE_STATE_READY) && p->mainloop); + + while (1) { + state = pa_stream_get_state(stream); + + if (state == PA_STREAM_FAILED) + return -EIO; + + if (state == target) + break; + + pa_threaded_mainloop_wait(p->mainloop); + } + + return 0; +} + +static void context_state_cb(pa_context *c, void *userdata) { + snd_pulse_t *p = userdata; + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +snd_pulse_t *pulse_new() +{ + snd_pulse_t *p; + int fd[2] = { -1, -1 }; + char proc[PATH_MAX], buf[PATH_MAX + 20]; + + p = calloc(1, sizeof(snd_pulse_t)); + assert(p); + + p->state = PULSE_STATE_INIT; + + if (pipe(fd)) { + free(p); + return NULL; + } + + p->main_fd = fd[0]; + p->thread_fd = fd[1]; + + fcntl(fd[0], F_SETFL, O_NONBLOCK); + fcntl(fd[1], F_SETFL, O_NONBLOCK); + + signal(SIGPIPE, SIG_IGN); /* Yes, ugly as hell */ + + p->mainloop = pa_threaded_mainloop_new(); + assert(p->mainloop); + + if (pa_threaded_mainloop_start(p->mainloop) < 0) { + pa_threaded_mainloop_free(p->mainloop); + close(fd[0]); + close(fd[1]); + free(p); + return NULL; + } + + if (pa_get_binary_name(proc, sizeof(proc))) + snprintf(buf, sizeof(buf), "ALSA plug-in [%s]", pa_path_get_filename(proc)); + else + snprintf(buf, sizeof(buf), "ALSA plug-in"); + + p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), buf); + assert(p->context); + + return p; +} + +void pulse_free(snd_pulse_t *p) +{ + pa_threaded_mainloop_stop(p->mainloop); + + pa_context_unref(p->context); + pa_threaded_mainloop_free(p->mainloop); + + close(p->thread_fd); + close(p->main_fd); + + free(p); +} + +int pulse_connect(snd_pulse_t *p, const char *server) +{ + int err; + + assert(p && p->context && p->mainloop && (p->state == PULSE_STATE_INIT)); + + pa_threaded_mainloop_lock(p->mainloop); + + err = pa_context_connect(p->context, server, 0, NULL); + if (err < 0) + goto error; + + pa_context_set_state_callback(p->context, context_state_cb, p); + + pa_threaded_mainloop_wait(p->mainloop); + + if (pa_context_get_state(p->context) != PA_CONTEXT_READY) + goto error; + + pa_threaded_mainloop_unlock(p->mainloop); + + p->state = PULSE_STATE_READY; + + return 0; + +error: + fprintf(stderr, "*** PULSEAUDIO: Unable to connect: %s\n", + pa_strerror(pa_context_errno(p->context))); + + pa_threaded_mainloop_unlock(p->mainloop); + + return -ECONNREFUSED; +} + +void pulse_poll_activate(snd_pulse_t *p) +{ + assert(p); + + write(p->thread_fd, "a", 1); +} + +void pulse_poll_deactivate(snd_pulse_t *p) +{ + char buf[10]; + + assert(p); + + /* Drain the pipe */ + while (read(p->main_fd, buf, sizeof(buf)) > 0); +} + +int pulse_poll_descriptors_count(snd_pulse_t *p) +{ + assert(p); + + if (p->main_fd >= 0) + return 1; + else + return 0; +} + +int pulse_poll_descriptors(snd_pulse_t *p, struct pollfd *pfd, unsigned int space) +{ + assert(p); + + assert(space >= 1); + + pfd[0].fd = p->main_fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + return 1; +} + +int pulse_poll_revents(snd_pulse_t *p, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) +{ + assert(p); + + return 1; +} diff --git a/pulse/pulse.h b/pulse/pulse.h new file mode 100644 index 0000000..ab53bcb --- /dev/null +++ b/pulse/pulse.h @@ -0,0 +1,57 @@ +/* + * ALSA <-> PulseAudio plugins + * + * Copyright (c) 2006 by Pierre Ossman + * + * 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 + +#include + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + +typedef struct snd_pulse { + pa_threaded_mainloop *mainloop; + pa_context *context; + + int thread_fd, main_fd; + + enum { + PULSE_STATE_INIT, + PULSE_STATE_READY, + } state; +} snd_pulse_t; + +int pulse_check_connection(snd_pulse_t *p); + +void pulse_stream_state_cb(pa_stream *s, void * userdata); +void pulse_stream_success_cb(pa_stream *s, int success, void *userdata); +void pulse_context_success_cb(pa_context *c, int success, void *userdata); + +int pulse_wait_operation(snd_pulse_t *p, pa_operation *o); +int pulse_wait_stream_state(snd_pulse_t *p, pa_stream *stream, pa_stream_state_t target); + +snd_pulse_t *pulse_new(); +void pulse_free(snd_pulse_t *p); + +int pulse_connect(snd_pulse_t *p, const char *server); + +void pulse_poll_activate(snd_pulse_t *p); +void pulse_poll_deactivate(snd_pulse_t *p); +int pulse_poll_descriptors_count(snd_pulse_t *p); +int pulse_poll_descriptors(snd_pulse_t *p, struct pollfd *pfd, unsigned int space); +int pulse_poll_revents(snd_pulse_t *p, struct pollfd *pfd, unsigned int nfds, unsigned short *revents); -- cgit