diff options
Diffstat (limited to 'src/modules')
201 files changed, 43301 insertions, 5760 deletions
diff --git a/src/modules/.gitignore b/src/modules/.gitignore deleted file mode 100644 index 2d2d942d..00000000 --- a/src/modules/.gitignore +++ /dev/null @@ -1 +0,0 @@ -module-*-symdef.h diff --git a/src/modules/Makefile b/src/modules/Makefile deleted file mode 120000 index c110232d..00000000 --- a/src/modules/Makefile +++ /dev/null @@ -1 +0,0 @@ -../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/alsa/Makefile b/src/modules/alsa/Makefile deleted file mode 120000 index efe5a336..00000000 --- a/src/modules/alsa/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c new file mode 100644 index 00000000..348f037f --- /dev/null +++ b/src/modules/alsa/alsa-mixer.c @@ -0,0 +1,4194 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2009 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <asoundlib.h> +#include <math.h> + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <pulse/mainloop-api.h> +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/strbuf.h> + +#include "alsa-mixer.h" +#include "alsa-util.h" + +struct description_map { + const char *name; + const char *description; +}; + +static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + if (pa_streq(dm[i].name, name)) + return _(dm[i].description); + + return NULL; +} + +struct pa_alsa_fdlist { + unsigned num_fds; + struct pollfd *fds; + /* This is a temporary buffer used to avoid lots of mallocs */ + struct pollfd *work_fds; + + snd_mixer_t *mixer; + + pa_mainloop_api *m; + pa_defer_event *defer; + pa_io_event **ios; + + pa_bool_t polled; + + void (*cb)(void *userdata); + void *userdata; +}; + +static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + + struct pa_alsa_fdlist *fdl = userdata; + int err; + unsigned i; + unsigned short revents; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + pa_assert(fdl->fds); + pa_assert(fdl->work_fds); + + if (fdl->polled) + return; + + fdl->polled = TRUE; + + memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); + + for (i = 0; i < fdl->num_fds; i++) { + if (e == fdl->ios[i]) { + if (events & PA_IO_EVENT_INPUT) + fdl->work_fds[i].revents |= POLLIN; + if (events & PA_IO_EVENT_OUTPUT) + fdl->work_fds[i].revents |= POLLOUT; + if (events & PA_IO_EVENT_ERROR) + fdl->work_fds[i].revents |= POLLERR; + if (events & PA_IO_EVENT_HANGUP) + fdl->work_fds[i].revents |= POLLHUP; + break; + } + } + + pa_assert(i != fdl->num_fds); + + if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { + pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); + return; + } + + a->defer_enable(fdl->defer, 1); + + if (revents) + snd_mixer_handle_events(fdl->mixer); +} + +static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) { + struct pa_alsa_fdlist *fdl = userdata; + unsigned num_fds, i; + int err, n; + struct pollfd *temp; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + + a->defer_enable(fdl->defer, 0); + + if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) { + pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); + return; + } + num_fds = (unsigned) n; + + if (num_fds != fdl->num_fds) { + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + fdl->fds = pa_xnew0(struct pollfd, num_fds); + fdl->work_fds = pa_xnew(struct pollfd, num_fds); + } + + memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); + + if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { + pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); + return; + } + + fdl->polled = FALSE; + + if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) + return; + + if (fdl->ios) { + for (i = 0; i < fdl->num_fds; i++) + a->io_free(fdl->ios[i]); + + if (num_fds != fdl->num_fds) { + pa_xfree(fdl->ios); + fdl->ios = NULL; + } + } + + if (!fdl->ios) + fdl->ios = pa_xnew(pa_io_event*, num_fds); + + /* Swap pointers */ + temp = fdl->work_fds; + fdl->work_fds = fdl->fds; + fdl->fds = temp; + + fdl->num_fds = num_fds; + + for (i = 0;i < num_fds;i++) + fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, + ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | + ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), + io_cb, fdl); +} + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { + struct pa_alsa_fdlist *fdl; + + fdl = pa_xnew0(struct pa_alsa_fdlist, 1); + + return fdl; +} + +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { + pa_assert(fdl); + + if (fdl->defer) { + pa_assert(fdl->m); + fdl->m->defer_free(fdl->defer); + } + + if (fdl->ios) { + unsigned i; + pa_assert(fdl->m); + for (i = 0; i < fdl->num_fds; i++) + fdl->m->io_free(fdl->ios[i]); + pa_xfree(fdl->ios); + } + + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + + pa_xfree(fdl); +} + +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) { + pa_assert(fdl); + pa_assert(mixer_handle); + pa_assert(m); + pa_assert(!fdl->m); + + fdl->mixer = mixer_handle; + fdl->m = m; + fdl->defer = m->defer_new(m, defer_cb, fdl); + + return 0; +} + +struct pa_alsa_mixer_pdata { + pa_rtpoll *rtpoll; + pa_rtpoll_item *poll_item; + snd_mixer_t *mixer; +}; + + +struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) { + struct pa_alsa_mixer_pdata *pd; + + pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1); + + return pd; +} + +void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) { + pa_assert(pd); + + if (pd->poll_item) { + pa_rtpoll_item_free(pd->poll_item); + } + + pa_xfree(pd); +} + +static int rtpoll_work_cb(pa_rtpoll_item *i) { + struct pa_alsa_mixer_pdata *pd; + struct pollfd *p; + unsigned n_fds; + unsigned short revents = 0; + int err; + + pd = pa_rtpoll_item_get_userdata(i); + pa_assert_fp(pd); + pa_assert_fp(i == pd->poll_item); + + p = pa_rtpoll_item_get_pollfd(i, &n_fds); + + if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) { + pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); + pa_rtpoll_item_free(i); + return -1; + } + + if (revents) { + snd_mixer_handle_events(pd->mixer); + pa_rtpoll_item_free(i); + pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll); + } + + return 0; +} + +int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) { + pa_rtpoll_item *i; + struct pollfd *p; + int err, n; + + pa_assert(pd); + pa_assert(mixer); + pa_assert(rtp); + + if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) { + pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); + return -1; + } + + i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n); + + p = pa_rtpoll_item_get_pollfd(i, NULL); + + memset(p, 0, sizeof(struct pollfd) * n); + + if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) { + pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); + pa_rtpoll_item_free(i); + return -1; + } + + pd->rtpoll = rtp; + pd->poll_item = i; + pd->mixer = mixer; + + pa_rtpoll_item_set_userdata(i, pd); + pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb); + + return 0; +} + +static int prepare_mixer(snd_mixer_t *mixer, const char *dev) { + int err; + + pa_assert(mixer); + pa_assert(dev); + + if ((err = snd_mixer_attach(mixer, dev)) < 0) { + pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err)); + return -1; + } + + if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { + pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err)); + return -1; + } + + if ((err = snd_mixer_load(mixer)) < 0) { + pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err)); + return -1; + } + + pa_log_info("Successfully attached to mixer '%s'", dev); + return 0; +} + +snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + int err; + snd_mixer_t *m; + const char *dev; + snd_pcm_info_t* info; + snd_pcm_info_alloca(&info); + + pa_assert(pcm); + + if ((err = snd_mixer_open(&m, 0)) < 0) { + pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); + return NULL; + } + + /* First, try by name */ + if ((dev = snd_pcm_name(pcm))) + if (prepare_mixer(m, dev) >= 0) { + if (ctl_device) + *ctl_device = pa_xstrdup(dev); + + return m; + } + + /* Then, try by card index */ + if (snd_pcm_info(pcm, info) >= 0) { + char *md; + int card_idx; + + if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card_idx); + + if (!dev || !pa_streq(dev, md)) + if (prepare_mixer(m, md) >= 0) { + + if (ctl_device) + *ctl_device = md; + else + pa_xfree(md); + + return m; + } + + pa_xfree(md); + } + } + + snd_mixer_close(m); + return NULL; +} + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + + [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, + [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + +static void setting_free(pa_alsa_setting *s) { + pa_assert(s); + + if (s->options) + pa_idxset_free(s->options, NULL, NULL); + + pa_xfree(s->name); + pa_xfree(s->description); + pa_xfree(s); +} + +static void option_free(pa_alsa_option *o) { + pa_assert(o); + + pa_xfree(o->alsa_name); + pa_xfree(o->name); + pa_xfree(o->description); + pa_xfree(o); +} + +static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) { + pa_assert(db_fix); + + pa_xfree(db_fix->name); + pa_xfree(db_fix->db_values); + + pa_xfree(db_fix); +} + +static void element_free(pa_alsa_element *e) { + pa_alsa_option *o; + pa_assert(e); + + while ((o = e->options)) { + PA_LLIST_REMOVE(pa_alsa_option, e->options, o); + option_free(o); + } + + if (e->db_fix) + decibel_fix_free(e->db_fix); + + pa_xfree(e->alsa_name); + pa_xfree(e); +} + +void pa_alsa_path_free(pa_alsa_path *p) { + pa_alsa_element *e; + pa_alsa_setting *s; + + pa_assert(p); + + while ((e = p->elements)) { + PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); + element_free(e); + } + + while ((s = p->settings)) { + PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s); + setting_free(s); + } + + pa_xfree(p->name); + pa_xfree(p->description); + pa_xfree(p); +} + +void pa_alsa_path_set_free(pa_alsa_path_set *ps) { + pa_alsa_path *p; + pa_assert(ps); + + while ((p = ps->paths)) { + PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p); + pa_alsa_path_free(p); + } + + pa_xfree(ps); +} + +static long to_alsa_dB(pa_volume_t v) { + return (long) (pa_sw_volume_to_dB(v) * 100.0); +} + +static pa_volume_t from_alsa_dB(long v) { + return pa_sw_volume_from_dB((double) v / 100.0); +} + +static long to_alsa_volume(pa_volume_t v, long min, long max) { + long w; + + w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min; + return PA_CLAMP_UNLIKELY(w, min, max); +} + +static pa_volume_t from_alsa_volume(long v, long min, long max) { + return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min)); +} + +#define SELEM_INIT(sid, name) \ + do { \ + snd_mixer_selem_id_alloca(&(sid)); \ + snd_mixer_selem_id_set_name((sid), (name)); \ + snd_mixer_selem_id_set_index((sid), 0); \ + } while(FALSE) + +static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; + pa_channel_position_mask_t mask = 0; + unsigned k; + + pa_assert(m); + pa_assert(e); + pa_assert(cm); + pa_assert(v); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + pa_cvolume_mute(v, cm->channels); + + /* We take the highest volume of all channels that match */ + + for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { + int r; + pa_volume_t f; + + if (e->has_dB) { + long value = 0; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (snd_mixer_selem_has_playback_channel(me, c)) { + if (e->db_fix) { + if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) { + /* If the channel volume is outside the limits set + * by the dB fix, we clamp the hw volume to be + * within the limits. */ + if (value < e->db_fix->min_step) { + value = e->db_fix->min_step; + snd_mixer_selem_set_playback_volume(me, c, value); + pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. " + "Volume reset to %0.2f dB.", e->alsa_name, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } else if (value > e->db_fix->max_step) { + value = e->db_fix->max_step; + snd_mixer_selem_set_playback_volume(me, c, value); + pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. " + "Volume reset to %0.2f dB.", e->alsa_name, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } + + /* Volume step -> dB value conversion. */ + value = e->db_fix->db_values[value - e->db_fix->min_step]; + } + } else + r = snd_mixer_selem_get_playback_dB(me, c, &value); + } else + r = -1; + } else { + if (snd_mixer_selem_has_capture_channel(me, c)) { + if (e->db_fix) { + if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) { + /* If the channel volume is outside the limits set + * by the dB fix, we clamp the hw volume to be + * within the limits. */ + if (value < e->db_fix->min_step) { + value = e->db_fix->min_step; + snd_mixer_selem_set_capture_volume(me, c, value); + pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. " + "Volume reset to %0.2f dB.", e->alsa_name, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } else if (value > e->db_fix->max_step) { + value = e->db_fix->max_step; + snd_mixer_selem_set_capture_volume(me, c, value); + pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. " + "Volume reset to %0.2f dB.", e->alsa_name, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } + + /* Volume step -> dB value conversion. */ + value = e->db_fix->db_values[value - e->db_fix->min_step]; + } + } else + r = snd_mixer_selem_get_capture_dB(me, c, &value); + } else + r = -1; + } + + if (r < 0) + continue; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value)); +#endif + + f = from_alsa_dB(value); + + } else { + long value = 0; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (snd_mixer_selem_has_playback_channel(me, c)) + r = snd_mixer_selem_get_playback_volume(me, c, &value); + else + r = -1; + } else { + if (snd_mixer_selem_has_capture_channel(me, c)) + r = snd_mixer_selem_get_capture_volume(me, c, &value); + else + r = -1; + } + + if (r < 0) + continue; + + f = from_alsa_volume(value, e->min_volume, e->max_volume); + } + + for (k = 0; k < cm->channels; k++) + if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) + if (v->values[k] < f) + v->values[k] = f; + + mask |= e->masks[c][e->n_channels-1]; + } + + for (k = 0; k < cm->channels; k++) + if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) + v->values[k] = PA_VOLUME_NORM; + + return 0; +} + +int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { + pa_alsa_element *e; + + pa_assert(m); + pa_assert(p); + pa_assert(cm); + pa_assert(v); + + if (!p->has_volume) + return -1; + + pa_cvolume_reset(v, cm->channels); + + PA_LLIST_FOREACH(e, p->elements) { + pa_cvolume ev; + + if (e->volume_use != PA_ALSA_VOLUME_MERGE) + continue; + + pa_assert(!p->has_dB || e->has_dB); + + if (element_get_volume(e, m, cm, &ev) < 0) + return -1; + + /* If we have no dB information all we can do is take the first element and leave */ + if (!p->has_dB) { + *v = ev; + return 0; + } + + pa_sw_cvolume_multiply(v, v, &ev); + } + + return 0; +} + +static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; + + pa_assert(m); + pa_assert(e); + pa_assert(b); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + /* We return muted if at least one channel is muted */ + + for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { + int r; + int value = 0; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (snd_mixer_selem_has_playback_channel(me, c)) + r = snd_mixer_selem_get_playback_switch(me, c, &value); + else + r = -1; + } else { + if (snd_mixer_selem_has_capture_channel(me, c)) + r = snd_mixer_selem_get_capture_switch(me, c, &value); + else + r = -1; + } + + if (r < 0) + continue; + + if (!value) { + *b = FALSE; + return 0; + } + } + + *b = TRUE; + return 0; +} + +int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) { + pa_alsa_element *e; + + pa_assert(m); + pa_assert(p); + pa_assert(muted); + + if (!p->has_mute) + return -1; + + PA_LLIST_FOREACH(e, p->elements) { + pa_bool_t b; + + if (e->switch_use != PA_ALSA_SWITCH_MUTE) + continue; + + if (element_get_switch(e, m, &b) < 0) + return -1; + + if (!b) { + *muted = TRUE; + return 0; + } + } + + *muted = FALSE; + return 0; +} + +/* Finds the closest item in db_fix->db_values and returns the corresponding + * step. *db_value is replaced with the value from the db_values table. + * Rounding is done based on the rounding parameter: -1 means rounding down and + * +1 means rounding up. */ +static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) { + unsigned i = 0; + unsigned max_i = 0; + + pa_assert(db_fix); + pa_assert(db_value); + pa_assert(rounding != 0); + + max_i = db_fix->max_step - db_fix->min_step; + + if (rounding > 0) { + for (i = 0; i < max_i; i++) { + if (db_fix->db_values[i] >= *db_value) + break; + } + } else { + for (i = 0; i < max_i; i++) { + if (db_fix->db_values[i + 1] > *db_value) + break; + } + } + + *db_value = db_fix->db_values[i]; + + return i + db_fix->min_step; +} + +/* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument, + * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above". + * But even with accurate nearest dB volume step is not selected, so that is why we need + * this function. Returns 0 and nearest selectable volume in *value_dB on success or + * negative error code if fails. */ +static int element_get_nearest_alsa_dB(snd_mixer_elem_t *me, snd_mixer_selem_channel_id_t c, pa_alsa_direction_t d, long *value_dB) { + + long alsa_val; + long value_high; + long value_low; + int r = -1; + + pa_assert(me); + pa_assert(value_dB); + + if (d == PA_ALSA_DIRECTION_OUTPUT) { + if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high); + + if (r < 0) + return r; + + if (value_high == *value_dB) + return r; + + if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low); + } else { + if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high); + + if (r < 0) + return r; + + if (value_high == *value_dB) + return r; + + if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low); + } + + if (r < 0) + return r; + + if (labs(value_high - *value_dB) < labs(value_low - *value_dB)) + *value_dB = value_high; + else + *value_dB = value_low; + + return r; +} + +static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) { + + snd_mixer_selem_id_t *sid; + pa_cvolume rv; + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; + pa_channel_position_mask_t mask = 0; + unsigned k; + + pa_assert(m); + pa_assert(e); + pa_assert(cm); + pa_assert(v); + pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + pa_cvolume_mute(&rv, cm->channels); + + for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { + int r; + pa_volume_t f = PA_VOLUME_MUTED; + pa_bool_t found = FALSE; + + for (k = 0; k < cm->channels; k++) + if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) { + found = TRUE; + if (v->values[k] > f) + f = v->values[k]; + } + + if (!found) { + /* Hmm, so this channel does not exist in the volume + * struct, so let's bind it to the overall max of the + * volume. */ + f = pa_cvolume_max(v); + } + + if (e->has_dB) { + long value = to_alsa_dB(f); + int rounding; + + if (e->volume_limit >= 0 && value > (e->max_dB * 100)) + value = e->max_dB * 100; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + /* If we call set_playback_volume() without checking first + * if the channel is available, ALSA behaves very + * strangely and doesn't fail the call */ + if (snd_mixer_selem_has_playback_channel(me, c)) { + rounding = +1; + if (e->db_fix) { + if (write_to_hw) + r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding)); + else { + decibel_fix_get_step(e->db_fix, &value, rounding); + r = 0; + } + + } else { + if (write_to_hw) { + if (sync_volume) { + if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0) + r = snd_mixer_selem_set_playback_dB(me, c, value, 0); + } else { + if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0) + r = snd_mixer_selem_get_playback_dB(me, c, &value); + } + } else { + long alsa_val; + if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value); + } + } + } else + r = -1; + } else { + if (snd_mixer_selem_has_capture_channel(me, c)) { + rounding = -1; + if (e->db_fix) { + if (write_to_hw) + r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding)); + else { + decibel_fix_get_step(e->db_fix, &value, rounding); + r = 0; + } + + } else { + if (write_to_hw) { + if (sync_volume) { + if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0) + r = snd_mixer_selem_set_capture_dB(me, c, value, 0); + } else { + if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0) + r = snd_mixer_selem_get_capture_dB(me, c, &value); + } + } else { + long alsa_val; + if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0) + r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value); + } + } + } else + r = -1; + } + + if (r < 0) + continue; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value)); +#endif + + f = from_alsa_dB(value); + + } else { + long value; + + value = to_alsa_volume(f, e->min_volume, e->max_volume); + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (snd_mixer_selem_has_playback_channel(me, c)) { + if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0) + r = snd_mixer_selem_get_playback_volume(me, c, &value); + } else + r = -1; + } else { + if (snd_mixer_selem_has_capture_channel(me, c)) { + if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0) + r = snd_mixer_selem_get_capture_volume(me, c, &value); + } else + r = -1; + } + + if (r < 0) + continue; + + f = from_alsa_volume(value, e->min_volume, e->max_volume); + } + + for (k = 0; k < cm->channels; k++) + if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) + if (rv.values[k] < f) + rv.values[k] = f; + + mask |= e->masks[c][e->n_channels-1]; + } + + for (k = 0; k < cm->channels; k++) + if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) + rv.values[k] = PA_VOLUME_NORM; + + *v = rv; + return 0; +} + +int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) { + + pa_alsa_element *e; + pa_cvolume rv; + + pa_assert(m); + pa_assert(p); + pa_assert(cm); + pa_assert(v); + pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); + + if (!p->has_volume) + return -1; + + rv = *v; /* Remaining adjustment */ + pa_cvolume_reset(v, cm->channels); /* Adjustment done */ + + PA_LLIST_FOREACH(e, p->elements) { + pa_cvolume ev; + + if (e->volume_use != PA_ALSA_VOLUME_MERGE) + continue; + + pa_assert(!p->has_dB || e->has_dB); + + ev = rv; + if (element_set_volume(e, m, cm, &ev, sync_volume, write_to_hw) < 0) + return -1; + + if (!p->has_dB) { + *v = ev; + return 0; + } + + pa_sw_cvolume_multiply(v, v, &ev); + pa_sw_cvolume_divide(&rv, &rv, &ev); + } + + return 0; +} + +static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) { + snd_mixer_elem_t *me; + snd_mixer_selem_id_t *sid; + int r; + + pa_assert(m); + pa_assert(e); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_set_playback_switch_all(me, b); + else + r = snd_mixer_selem_set_capture_switch_all(me, b); + + if (r < 0) + pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + + return r; +} + +int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) { + pa_alsa_element *e; + + pa_assert(m); + pa_assert(p); + + if (!p->has_mute) + return -1; + + PA_LLIST_FOREACH(e, p->elements) { + + if (e->switch_use != PA_ALSA_SWITCH_MUTE) + continue; + + if (element_set_switch(e, m, !muted) < 0) + return -1; + } + + return 0; +} + +/* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this + * function sets all channels of the volume element to e->min_volume, 0 dB or + * e->constant_volume. */ +static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { + snd_mixer_elem_t *me = NULL; + snd_mixer_selem_id_t *sid = NULL; + int r = 0; + long volume = -1; + pa_bool_t volume_set = FALSE; + + pa_assert(m); + pa_assert(e); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + switch (e->volume_use) { + case PA_ALSA_VOLUME_OFF: + volume = e->min_volume; + volume_set = TRUE; + break; + + case PA_ALSA_VOLUME_ZERO: + if (e->db_fix) { + long dB = 0; + + volume = decibel_fix_get_step(e->db_fix, &dB, +1); + volume_set = TRUE; + } + break; + + case PA_ALSA_VOLUME_CONSTANT: + volume = e->constant_volume; + volume_set = TRUE; + break; + + default: + pa_assert_not_reached(); + } + + if (volume_set) { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_set_playback_volume_all(me, volume); + else + r = snd_mixer_selem_set_capture_volume_all(me, volume); + } else { + pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO); + pa_assert(!e->db_fix); + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_set_playback_dB_all(me, 0, +1); + else + r = snd_mixer_selem_set_capture_dB_all(me, 0, +1); + } + + if (r < 0) + pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + + return r; +} + +int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) { + pa_alsa_element *e; + int r = 0; + + pa_assert(m); + pa_assert(p); + + pa_log_debug("Activating path %s", p->name); + pa_alsa_path_dump(p); + + PA_LLIST_FOREACH(e, p->elements) { + + switch (e->switch_use) { + case PA_ALSA_SWITCH_OFF: + r = element_set_switch(e, m, FALSE); + break; + + case PA_ALSA_SWITCH_ON: + r = element_set_switch(e, m, TRUE); + break; + + case PA_ALSA_SWITCH_MUTE: + case PA_ALSA_SWITCH_IGNORE: + case PA_ALSA_SWITCH_SELECT: + r = 0; + break; + } + + if (r < 0) + return -1; + + switch (e->volume_use) { + case PA_ALSA_VOLUME_OFF: + case PA_ALSA_VOLUME_ZERO: + case PA_ALSA_VOLUME_CONSTANT: + r = element_set_constant_volume(e, m); + break; + + case PA_ALSA_VOLUME_MERGE: + case PA_ALSA_VOLUME_IGNORE: + r = 0; + break; + } + + if (r < 0) + return -1; + } + + return 0; +} + +static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) { + pa_bool_t has_switch; + pa_bool_t has_enumeration; + pa_bool_t has_volume; + + pa_assert(e); + pa_assert(me); + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + has_switch = + snd_mixer_selem_has_playback_switch(me) || + (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)); + } else { + has_switch = + snd_mixer_selem_has_capture_switch(me) || + (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)); + } + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + has_volume = + snd_mixer_selem_has_playback_volume(me) || + (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)); + } else { + has_volume = + snd_mixer_selem_has_capture_volume(me) || + (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)); + } + + has_enumeration = snd_mixer_selem_is_enumerated(me); + + if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) || + (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) || + (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration)) + return -1; + + if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration)) + return -1; + + if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) || + (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) || + (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration)) + return -1; + + if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration)) + return -1; + + if (e->required_any != PA_ALSA_REQUIRED_IGNORE) { + switch (e->required_any) { + case PA_ALSA_REQUIRED_VOLUME: + e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE); + break; + case PA_ALSA_REQUIRED_SWITCH: + e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE); + break; + case PA_ALSA_REQUIRED_ENUMERATION: + e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE); + break; + case PA_ALSA_REQUIRED_ANY: + e->path->req_any_present |= + (e->volume_use != PA_ALSA_VOLUME_IGNORE) || + (e->switch_use != PA_ALSA_SWITCH_IGNORE) || + (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE); + break; + default: + pa_assert_not_reached(); + } + } + + if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { + pa_alsa_option *o; + PA_LLIST_FOREACH(o, e->options) { + e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) && + (o->alsa_idx >= 0); + if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0) + return -1; + if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0) + return -1; + } + } + + return 0; +} + +static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + + pa_assert(m); + pa_assert(e); + pa_assert(e->path); + + SELEM_INIT(sid, e->alsa_name); + + if (!(me = snd_mixer_find_selem(m, sid))) { + + if (e->required != PA_ALSA_REQUIRED_IGNORE) + return -1; + + e->switch_use = PA_ALSA_SWITCH_IGNORE; + e->volume_use = PA_ALSA_VOLUME_IGNORE; + e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; + + return 0; + } + + if (e->switch_use != PA_ALSA_SWITCH_IGNORE) { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + + if (!snd_mixer_selem_has_playback_switch(me)) { + if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)) + e->direction = PA_ALSA_DIRECTION_INPUT; + else + e->switch_use = PA_ALSA_SWITCH_IGNORE; + } + + } else { + + if (!snd_mixer_selem_has_capture_switch(me)) { + if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)) + e->direction = PA_ALSA_DIRECTION_OUTPUT; + else + e->switch_use = PA_ALSA_SWITCH_IGNORE; + } + } + + if (e->switch_use != PA_ALSA_SWITCH_IGNORE) + e->direction_try_other = FALSE; + } + + if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + + if (!snd_mixer_selem_has_playback_volume(me)) { + if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)) + e->direction = PA_ALSA_DIRECTION_INPUT; + else + e->volume_use = PA_ALSA_VOLUME_IGNORE; + } + + } else { + + if (!snd_mixer_selem_has_capture_volume(me)) { + if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)) + e->direction = PA_ALSA_DIRECTION_OUTPUT; + else + e->volume_use = PA_ALSA_VOLUME_IGNORE; + } + } + + if (e->volume_use != PA_ALSA_VOLUME_IGNORE) { + long min_dB = 0, max_dB = 0; + int r; + + e->direction_try_other = FALSE; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume); + else + r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); + + if (r < 0) { + pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + return -1; + } + + if (e->min_volume >= e->max_volume) { + pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", e->min_volume, e->max_volume); + e->volume_use = PA_ALSA_VOLUME_IGNORE; + + } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && + (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { + pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", + e->constant_volume, e->alsa_name, e->min_volume, e->max_volume); + e->volume_use = PA_ALSA_VOLUME_IGNORE; + + } else { + pa_bool_t is_mono; + pa_channel_position_t p; + + if (e->db_fix && + ((e->min_volume > e->db_fix->min_step) || + (e->max_volume < e->db_fix->max_step))) { + pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " + "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name, + e->db_fix->min_step, e->db_fix->max_step, + e->min_volume, e->max_volume); + + decibel_fix_free(e->db_fix); + e->db_fix = NULL; + } + + if (e->db_fix) { + e->has_dB = TRUE; + e->min_volume = e->db_fix->min_step; + e->max_volume = e->db_fix->max_step; + min_dB = e->db_fix->db_values[0]; + max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]; + } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0; + else + e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; + + /* Check that the kernel driver returns consistent limits with + * both _get_*_dB_range() and _ask_*_vol_dB(). */ + if (e->has_dB && !e->db_fix) { + long min_dB_checked = 0; + long max_dB_checked = 0; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked); + else + r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked); + + if (r < 0) { + pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume); + return -1; + } + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked); + else + r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked); + + if (r < 0) { + pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume); + return -1; + } + + if (min_dB != min_dB_checked || max_dB != max_dB_checked) { + pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " + "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " + "%0.2f dB at level %li.", + e->alsa_name, + min_dB / 100.0, max_dB / 100.0, + min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); + return -1; + } + } + + if (e->has_dB) { +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB)); + VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB)); +#endif + + e->min_dB = ((double) min_dB) / 100.0; + e->max_dB = ((double) max_dB) / 100.0; + + if (min_dB >= max_dB) { + pa_assert(!e->db_fix); + pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB); + e->has_dB = FALSE; + } + } + + if (e->volume_limit >= 0) { + if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) + pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " + "%li-%li. The volume limit is ignored.", + e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); + + else { + e->max_volume = e->volume_limit; + + if (e->has_dB) { + if (e->db_fix) { + e->db_fix->max_step = e->max_volume; + e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; + + } else { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB); + else + r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB); + + if (r < 0) { + pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r)); + e->has_dB = FALSE; + } else + e->max_dB = ((double) max_dB) / 100.0; + } + } + } + } + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + is_mono = snd_mixer_selem_is_playback_mono(me) > 0; + else + is_mono = snd_mixer_selem_is_capture_mono(me) > 0; + + if (is_mono) { + e->n_channels = 1; + + if (!e->override_map) { + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; + + e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0; + } + + e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL; + } + + e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1]; + } else { + e->n_channels = 0; + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; + else + e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; + } + + if (e->n_channels <= 0) { + pa_log_warn("Volume element %s with no channels?", e->alsa_name); + return -1; + } + + if (e->n_channels > 2) { + /* FIXME: In some places code like this is used: + * + * e->masks[alsa_channel_ids[p]][e->n_channels-1] + * + * The definition of e->masks is + * + * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2]; + * + * Since the array size is fixed at 2, we obviously + * don't support elements with more than two + * channels... */ + pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels); + return -1; + } + + if (!e->override_map) { + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + pa_bool_t has_channel; + + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; + else + has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; + + e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0; + } + } + + e->merged_mask = 0; + for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { + if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) + continue; + + e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; + } + } + } + } + + } + + if (e->switch_use == PA_ALSA_SWITCH_SELECT) { + pa_alsa_option *o; + + PA_LLIST_FOREACH(o, e->options) + o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0; + } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { + int n; + pa_alsa_option *o; + + if ((n = snd_mixer_selem_get_enum_items(me)) < 0) { + pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n)); + return -1; + } + + PA_LLIST_FOREACH(o, e->options) { + int i; + + for (i = 0; i < n; i++) { + char buf[128]; + + if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0) + continue; + + if (!pa_streq(buf, o->alsa_name)) + continue; + + o->alsa_idx = i; + } + } + } + + if (check_required(e, me) < 0) + return -1; + + return 0; +} + +static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) { + pa_alsa_element *e; + + pa_assert(p); + pa_assert(section); + + if (prefixed) { + if (!pa_startswith(section, "Element ")) + return NULL; + + section += 8; + } + + /* This is not an element section, but an enum section? */ + if (strchr(section, ':')) + return NULL; + + if (p->last_element && pa_streq(p->last_element->alsa_name, section)) + return p->last_element; + + PA_LLIST_FOREACH(e, p->elements) + if (pa_streq(e->alsa_name, section)) + goto finish; + + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; + e->alsa_name = pa_xstrdup(section); + e->direction = p->direction; + e->volume_limit = -1; + + PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); + +finish: + p->last_element = e; + return e; +} + +static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { + char *en; + const char *on; + pa_alsa_option *o; + pa_alsa_element *e; + + if (!pa_startswith(section, "Option ")) + return NULL; + + section += 7; + + /* This is not an enum section, but an element section? */ + if (!(on = strchr(section, ':'))) + return NULL; + + en = pa_xstrndup(section, on - section); + on++; + + if (p->last_option && + pa_streq(p->last_option->element->alsa_name, en) && + pa_streq(p->last_option->alsa_name, on)) { + pa_xfree(en); + return p->last_option; + } + + pa_assert_se(e = element_get(p, en, FALSE)); + pa_xfree(en); + + PA_LLIST_FOREACH(o, e->options) + if (pa_streq(o->alsa_name, on)) + goto finish; + + o = pa_xnew0(pa_alsa_option, 1); + o->element = e; + o->alsa_name = pa_xstrdup(on); + o->alsa_idx = -1; + + if (p->last_option && p->last_option->element == e) + PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o); + else + PA_LLIST_PREPEND(pa_alsa_option, e->options, o); + +finish: + p->last_option = o; + return o; +} + +static int element_parse_switch( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + + pa_assert(p); + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "ignore")) + e->switch_use = PA_ALSA_SWITCH_IGNORE; + else if (pa_streq(rvalue, "mute")) + e->switch_use = PA_ALSA_SWITCH_MUTE; + else if (pa_streq(rvalue, "off")) + e->switch_use = PA_ALSA_SWITCH_OFF; + else if (pa_streq(rvalue, "on")) + e->switch_use = PA_ALSA_SWITCH_ON; + else if (pa_streq(rvalue, "select")) + e->switch_use = PA_ALSA_SWITCH_SELECT; + else { + pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section); + return -1; + } + + return 0; +} + +static int element_parse_volume( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + + pa_assert(p); + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "ignore")) + e->volume_use = PA_ALSA_VOLUME_IGNORE; + else if (pa_streq(rvalue, "merge")) + e->volume_use = PA_ALSA_VOLUME_MERGE; + else if (pa_streq(rvalue, "off")) + e->volume_use = PA_ALSA_VOLUME_OFF; + else if (pa_streq(rvalue, "zero")) + e->volume_use = PA_ALSA_VOLUME_ZERO; + else { + uint32_t constant; + + if (pa_atou(rvalue, &constant) >= 0) { + e->volume_use = PA_ALSA_VOLUME_CONSTANT; + e->constant_volume = constant; + } else { + pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section); + return -1; + } + } + + return 0; +} + +static int element_parse_enumeration( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + + pa_assert(p); + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "ignore")) + e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; + else if (pa_streq(rvalue, "select")) + e->enumeration_use = PA_ALSA_ENUMERATION_SELECT; + else { + pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section); + return -1; + } + + return 0; +} + +static int option_parse_priority( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_option *o; + uint32_t prio; + + pa_assert(p); + + if (!(o = option_get(p, section))) { + pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_atou(rvalue, &prio) < 0) { + pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section); + return -1; + } + + o->priority = prio; + return 0; +} + +static int option_parse_name( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_option *o; + + pa_assert(p); + + if (!(o = option_get(p, section))) { + pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section); + return -1; + } + + pa_xfree(o->name); + o->name = pa_xstrdup(rvalue); + + return 0; +} + +static int element_parse_required( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + pa_alsa_option *o; + pa_alsa_required_t req; + + pa_assert(p); + + e = element_get(p, section, TRUE); + o = option_get(p, section); + if (!e && !o) { + pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "ignore")) + req = PA_ALSA_REQUIRED_IGNORE; + else if (pa_streq(rvalue, "switch") && e) + req = PA_ALSA_REQUIRED_SWITCH; + else if (pa_streq(rvalue, "volume") && e) + req = PA_ALSA_REQUIRED_VOLUME; + else if (pa_streq(rvalue, "enumeration")) + req = PA_ALSA_REQUIRED_ENUMERATION; + else if (pa_streq(rvalue, "any")) + req = PA_ALSA_REQUIRED_ANY; + else { + pa_log("[%s:%u] Required invalid of '%s'", filename, line, section); + return -1; + } + + if (pa_streq(lvalue, "required-absent")) { + if (e) + e->required_absent = req; + if (o) + o->required_absent = req; + } + else if (pa_streq(lvalue, "required-any")) { + if (e) { + e->required_any = req; + e->path->has_req_any = TRUE; + } + if (o) { + o->required_any = req; + o->element->path->has_req_any = TRUE; + } + } + else { + if (e) + e->required = req; + if (o) + o->required = req; + } + + return 0; +} + +static int element_parse_direction( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + + pa_assert(p); + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "playback")) + e->direction = PA_ALSA_DIRECTION_OUTPUT; + else if (pa_streq(rvalue, "capture")) + e->direction = PA_ALSA_DIRECTION_INPUT; + else { + pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section); + return -1; + } + + return 0; +} + +static int element_parse_direction_try_other( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + int yes; + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section); + return -1; + } + + if ((yes = pa_parse_boolean(rvalue)) < 0) { + pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section); + return -1; + } + + e->direction_try_other = !!yes; + return 0; +} + +static int element_parse_volume_limit( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + long volume_limit; + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section); + return -1; + } + + if (pa_atol(rvalue, &volume_limit) < 0 || volume_limit < 0) { + pa_log("[%s:%u] Invalid value for volume-limit", filename, line); + return -1; + } + + e->volume_limit = volume_limit; + return 0; +} + +static pa_channel_position_mask_t parse_mask(const char *m) { + pa_channel_position_mask_t v; + + if (pa_streq(m, "all-left")) + v = PA_CHANNEL_POSITION_MASK_LEFT; + else if (pa_streq(m, "all-right")) + v = PA_CHANNEL_POSITION_MASK_RIGHT; + else if (pa_streq(m, "all-center")) + v = PA_CHANNEL_POSITION_MASK_CENTER; + else if (pa_streq(m, "all-front")) + v = PA_CHANNEL_POSITION_MASK_FRONT; + else if (pa_streq(m, "all-rear")) + v = PA_CHANNEL_POSITION_MASK_REAR; + else if (pa_streq(m, "all-side")) + v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER; + else if (pa_streq(m, "all-top")) + v = PA_CHANNEL_POSITION_MASK_TOP; + else if (pa_streq(m, "all-no-lfe")) + v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE); + else if (pa_streq(m, "all")) + v = PA_CHANNEL_POSITION_MASK_ALL; + else { + pa_channel_position_t p; + + if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) + return 0; + + v = PA_CHANNEL_POSITION_MASK(p); + } + + return v; +} + +static int element_parse_override_map( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_path *p = userdata; + pa_alsa_element *e; + const char *state = NULL; + unsigned i = 0; + char *n; + + if (!(e = element_get(p, section, TRUE))) { + pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section); + return -1; + } + + while ((n = pa_split(rvalue, ",", &state))) { + pa_channel_position_mask_t m; + + if (!*n) + m = 0; + else { + if ((m = parse_mask(n)) == 0) { + pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section); + pa_xfree(n); + return -1; + } + } + + if (pa_streq(lvalue, "override-map.1")) + e->masks[i++][0] = m; + else + e->masks[i++][1] = m; + + /* Later on we might add override-map.3 and so on here ... */ + + pa_xfree(n); + } + + e->override_map = TRUE; + + return 0; +} + +static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + int r; + + pa_assert(e); + pa_assert(m); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return -1; + } + + if (e->switch_use == PA_ALSA_SWITCH_SELECT) { + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx); + else + r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); + + if (r < 0) + pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + + } else { + pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); + + if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) + pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + } + + return r; +} + +int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) { + pa_alsa_option *o; + uint32_t idx; + + pa_assert(s); + pa_assert(m); + + PA_IDXSET_FOREACH(o, s->options, idx) + element_set_option(o->element, m, o->alsa_idx); + + return 0; +} + +static int option_verify(pa_alsa_option *o) { + static const struct description_map well_known_descriptions[] = { + { "input", N_("Input") }, + { "input-docking", N_("Docking Station Input") }, + { "input-docking-microphone", N_("Docking Station Microphone") }, + { "input-docking-linein", N_("Docking Station Line-In") }, + { "input-linein", N_("Line-In") }, + { "input-microphone", N_("Microphone") }, + { "input-microphone-front", N_("Front Microphone") }, + { "input-microphone-rear", N_("Rear Microphone") }, + { "input-microphone-external", N_("External Microphone") }, + { "input-microphone-internal", N_("Internal Microphone") }, + { "input-radio", N_("Radio") }, + { "input-video", N_("Video") }, + { "input-agc-on", N_("Automatic Gain Control") }, + { "input-agc-off", N_("No Automatic Gain Control") }, + { "input-boost-on", N_("Boost") }, + { "input-boost-off", N_("No Boost") }, + { "output-amplifier-on", N_("Amplifier") }, + { "output-amplifier-off", N_("No Amplifier") }, + { "output-bass-boost-on", N_("Bass Boost") }, + { "output-bass-boost-off", N_("No Bass Boost") }, + { "output-speaker", N_("Speaker") }, + { "output-headphones", N_("Headphones") } + }; + + pa_assert(o); + + if (!o->name) { + pa_log("No name set for option %s", o->alsa_name); + return -1; + } + + if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && + o->element->switch_use != PA_ALSA_SWITCH_SELECT) { + pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name); + return -1; + } + + if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && + !pa_streq(o->alsa_name, "on") && + !pa_streq(o->alsa_name, "off")) { + pa_log("Switch %s options need be named off or on ", o->element->alsa_name); + return -1; + } + + if (!o->description) + o->description = pa_xstrdup(lookup_description(o->name, + well_known_descriptions, + PA_ELEMENTSOF(well_known_descriptions))); + if (!o->description) + o->description = pa_xstrdup(o->name); + + return 0; +} + +static int element_verify(pa_alsa_element *e) { + pa_alsa_option *o; + + pa_assert(e); + +// pa_log_debug("Element %s, path %s: r=%d, r-any=%d, r-abs=%d", e->alsa_name, e->path->name, e->required, e->required_any, e->required_absent); + if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) || + (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) || + (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) || + (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { + pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name); + return -1; + } + + if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { + pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name); + return -1; + } + + PA_LLIST_FOREACH(o, e->options) + if (option_verify(o) < 0) + return -1; + + return 0; +} + +static int path_verify(pa_alsa_path *p) { + static const struct description_map well_known_descriptions[] = { + { "analog-input", N_("Analog Input") }, + { "analog-input-microphone", N_("Analog Microphone") }, + { "analog-input-microphone-front", N_("Front Microphone") }, + { "analog-input-microphone-rear", N_("Rear Microphone") }, + { "analog-input-microphone-dock", N_("Docking Station Microphone") }, + { "analog-input-microphone-internal", N_("Internal Microphone") }, + { "analog-input-linein", N_("Analog Line-In") }, + { "analog-input-radio", N_("Analog Radio") }, + { "analog-input-video", N_("Analog Video") }, + { "analog-output", N_("Analog Output") }, + { "analog-output-headphones", N_("Analog Headphones") }, + { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") }, + { "analog-output-mono", N_("Analog Mono Output") }, + { "analog-output-speaker", N_("Analog Speakers") }, + { "iec958-stereo-output", N_("Digital Output (IEC958)") }, + { "iec958-passthrough-output", N_("Digital Passthrough (IEC958)") } + }; + + pa_alsa_element *e; + + pa_assert(p); + + PA_LLIST_FOREACH(e, p->elements) + if (element_verify(e) < 0) + return -1; + + if (!p->description) + p->description = pa_xstrdup(lookup_description(p->name, + well_known_descriptions, + PA_ELEMENTSOF(well_known_descriptions))); + + if (!p->description) + p->description = pa_xstrdup(p->name); + + return 0; +} + +pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction) { + pa_alsa_path *p; + char *fn; + int r; + const char *n; + + pa_config_item items[] = { + /* [General] */ + { "priority", pa_config_parse_unsigned, NULL, "General" }, + { "description", pa_config_parse_string, NULL, "General" }, + { "name", pa_config_parse_string, NULL, "General" }, + + /* [Option ...] */ + { "priority", option_parse_priority, NULL, NULL }, + { "name", option_parse_name, NULL, NULL }, + + /* [Element ...] */ + { "switch", element_parse_switch, NULL, NULL }, + { "volume", element_parse_volume, NULL, NULL }, + { "enumeration", element_parse_enumeration, NULL, NULL }, + { "override-map.1", element_parse_override_map, NULL, NULL }, + { "override-map.2", element_parse_override_map, NULL, NULL }, + /* ... later on we might add override-map.3 and so on here ... */ + { "required", element_parse_required, NULL, NULL }, + { "required-any", element_parse_required, NULL, NULL }, + { "required-absent", element_parse_required, NULL, NULL }, + { "direction", element_parse_direction, NULL, NULL }, + { "direction-try-other", element_parse_direction_try_other, NULL, NULL }, + { "volume-limit", element_parse_volume_limit, NULL, NULL }, + { NULL, NULL, NULL, NULL } + }; + + pa_assert(fname); + + p = pa_xnew0(pa_alsa_path, 1); + n = pa_path_get_filename(fname); + p->name = pa_xstrndup(n, strcspn(n, ".")); + p->direction = direction; + + items[0].data = &p->priority; + items[1].data = &p->description; + items[2].data = &p->name; + + fn = pa_maybe_prefix_path(fname, + pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" : + PA_ALSA_PATHS_DIR); + + r = pa_config_parse(fn, NULL, items, p); + pa_xfree(fn); + + if (r < 0) + goto fail; + + if (path_verify(p) < 0) + goto fail; + + return p; + +fail: + pa_alsa_path_free(p); + return NULL; +} + +pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) { + pa_alsa_path *p; + pa_alsa_element *e; + + pa_assert(element); + + p = pa_xnew0(pa_alsa_path, 1); + p->name = pa_xstrdup(element); + p->direction = direction; + + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; + e->alsa_name = pa_xstrdup(element); + e->direction = direction; + e->volume_limit = -1; + + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; + + PA_LLIST_PREPEND(pa_alsa_element, p->elements, e); + p->last_element = e; + return p; +} + +static pa_bool_t element_drop_unsupported(pa_alsa_element *e) { + pa_alsa_option *o, *n; + + pa_assert(e); + + for (o = e->options; o; o = n) { + n = o->next; + + if (o->alsa_idx < 0) { + PA_LLIST_REMOVE(pa_alsa_option, e->options, o); + option_free(o); + } + } + + return + e->switch_use != PA_ALSA_SWITCH_IGNORE || + e->volume_use != PA_ALSA_VOLUME_IGNORE || + e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE; +} + +static void path_drop_unsupported(pa_alsa_path *p) { + pa_alsa_element *e, *n; + + pa_assert(p); + + for (e = p->elements; e; e = n) { + n = e->next; + + if (!element_drop_unsupported(e)) { + PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); + element_free(e); + } + } +} + +static void path_make_options_unique(pa_alsa_path *p) { + pa_alsa_element *e; + pa_alsa_option *o, *u; + + PA_LLIST_FOREACH(e, p->elements) { + PA_LLIST_FOREACH(o, e->options) { + unsigned i; + char *m; + + for (u = o->next; u; u = u->next) + if (pa_streq(u->name, o->name)) + break; + + if (!u) + continue; + + m = pa_xstrdup(o->name); + + /* OK, this name is not unique, hence let's rename */ + for (i = 1, u = o; u; u = u->next) { + char *nn, *nd; + + if (!pa_streq(u->name, m)) + continue; + + nn = pa_sprintf_malloc("%s-%u", m, i); + pa_xfree(u->name); + u->name = nn; + + nd = pa_sprintf_malloc("%s %u", u->description, i); + pa_xfree(u->description); + u->description = nd; + + i++; + } + + pa_xfree(m); + } + } +} + +static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) { + pa_alsa_option *o; + + for (; e; e = e->next) + if (e->switch_use == PA_ALSA_SWITCH_SELECT || + e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) + break; + + if (!e) + return FALSE; + + for (o = e->options; o; o = o->next) { + pa_alsa_setting *s; + + if (template) { + s = pa_xnewdup(pa_alsa_setting, template, 1); + s->options = pa_idxset_copy(template->options); + s->name = pa_sprintf_malloc(_("%s+%s"), template->name, o->name); + s->description = + (template->description[0] && o->description[0]) + ? pa_sprintf_malloc(_("%s / %s"), template->description, o->description) + : (template->description[0] + ? pa_xstrdup(template->description) + : pa_xstrdup(o->description)); + + s->priority = PA_MAX(template->priority, o->priority); + } else { + s = pa_xnew0(pa_alsa_setting, 1); + s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + s->name = pa_xstrdup(o->name); + s->description = pa_xstrdup(o->description); + s->priority = o->priority; + } + + pa_idxset_put(s->options, o, NULL); + + if (element_create_settings(e->next, s)) + /* This is not a leaf, so let's get rid of it */ + setting_free(s); + else { + /* This is a leaf, so let's add it */ + PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s); + + e->path->last_setting = s; + } + } + + return TRUE; +} + +static void path_create_settings(pa_alsa_path *p) { + pa_assert(p); + + element_create_settings(p->elements, NULL); +} + +int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) { + pa_alsa_element *e; + double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX]; + pa_channel_position_t t; + pa_channel_position_mask_t path_volume_channels = 0; + + pa_assert(p); + pa_assert(m); + + if (p->probed) + return 0; + + pa_zero(min_dB); + pa_zero(max_dB); + + pa_log_debug("Probing path '%s'", p->name); + + PA_LLIST_FOREACH(e, p->elements) { + if (element_probe(e, m) < 0) { + p->supported = FALSE; + pa_log_debug("Probe of element '%s' failed.", e->alsa_name); + return -1; + } + pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use); + + if (ignore_dB) + e->has_dB = FALSE; + + if (e->volume_use == PA_ALSA_VOLUME_MERGE) { + + if (!p->has_volume) { + p->min_volume = e->min_volume; + p->max_volume = e->max_volume; + } + + if (e->has_dB) { + if (!p->has_volume) { + for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) + if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { + min_dB[t] = e->min_dB; + max_dB[t] = e->max_dB; + path_volume_channels |= PA_CHANNEL_POSITION_MASK(t); + } + + p->has_dB = TRUE; + } else { + + if (p->has_dB) { + for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) + if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { + min_dB[t] += e->min_dB; + max_dB[t] += e->max_dB; + path_volume_channels |= PA_CHANNEL_POSITION_MASK(t); + } + } else { + /* Hmm, there's another element before us + * which cannot do dB volumes, so we we need + * to 'neutralize' this slider */ + e->volume_use = PA_ALSA_VOLUME_ZERO; + pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name); + } + } + } else if (p->has_volume) { + /* We can't use this volume, so let's ignore it */ + e->volume_use = PA_ALSA_VOLUME_IGNORE; + pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name); + } + p->has_volume = TRUE; + } + + if (e->switch_use == PA_ALSA_SWITCH_MUTE) + p->has_mute = TRUE; + } + + if (p->has_req_any && !p->req_any_present) { + p->supported = FALSE; + pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name); + return -1; + } + + path_drop_unsupported(p); + path_make_options_unique(p); + path_create_settings(p); + + p->supported = TRUE; + p->probed = TRUE; + + p->min_dB = INFINITY; + p->max_dB = -INFINITY; + + for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) { + if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) { + if (p->min_dB > min_dB[t]) + p->min_dB = min_dB[t]; + + if (p->max_dB < max_dB[t]) + p->max_dB = max_dB[t]; + } + } + + return 0; +} + +void pa_alsa_setting_dump(pa_alsa_setting *s) { + pa_assert(s); + + pa_log_debug("Setting %s (%s) priority=%u", + s->name, + pa_strnull(s->description), + s->priority); +} + +void pa_alsa_option_dump(pa_alsa_option *o) { + pa_assert(o); + + pa_log_debug("Option %s (%s/%s) index=%i, priority=%u", + o->alsa_name, + pa_strnull(o->name), + pa_strnull(o->description), + o->alsa_idx, + o->priority); +} + +void pa_alsa_element_dump(pa_alsa_element *e) { + pa_alsa_option *o; + pa_assert(e); + + pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s", + e->alsa_name, + e->direction, + e->switch_use, + e->volume_use, + e->volume_limit, + e->enumeration_use, + e->required, + e->required_any, + e->required_absent, + (long long unsigned) e->merged_mask, + e->n_channels, + pa_yes_no(e->override_map)); + + PA_LLIST_FOREACH(o, e->options) + pa_alsa_option_dump(o); +} + +void pa_alsa_path_dump(pa_alsa_path *p) { + pa_alsa_element *e; + pa_alsa_setting *s; + pa_assert(p); + + pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, " + "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g", + p->name, + pa_strnull(p->description), + p->direction, + p->priority, + pa_yes_no(p->probed), + pa_yes_no(p->supported), + pa_yes_no(p->has_mute), + pa_yes_no(p->has_volume), + pa_yes_no(p->has_dB), + p->min_volume, p->max_volume, + p->min_dB, p->max_dB); + + PA_LLIST_FOREACH(e, p->elements) + pa_alsa_element_dump(e); + + PA_LLIST_FOREACH(s, p->settings) + pa_alsa_setting_dump(s); +} + +static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + + pa_assert(e); + pa_assert(m); + pa_assert(cb); + + SELEM_INIT(sid, e->alsa_name); + if (!(me = snd_mixer_find_selem(m, sid))) { + pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); + return; + } + + snd_mixer_elem_set_callback(me, cb); + snd_mixer_elem_set_callback_private(me, userdata); +} + +void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { + pa_alsa_element *e; + + pa_assert(p); + pa_assert(m); + pa_assert(cb); + + PA_LLIST_FOREACH(e, p->elements) + element_set_callback(e, m, cb, userdata); +} + +void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { + pa_alsa_path *p; + + pa_assert(ps); + pa_assert(m); + pa_assert(cb); + + PA_LLIST_FOREACH(p, ps->paths) + pa_alsa_path_set_callback(p, m, cb, userdata); +} + +pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction) { + pa_alsa_path_set *ps; + char **pn = NULL, **en = NULL, **ie; + pa_alsa_decibel_fix *db_fix; + void *state; + + pa_assert(m); + pa_assert(m->profile_set); + pa_assert(m->profile_set->decibel_fixes); + pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT); + + if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction) + return NULL; + + ps = pa_xnew0(pa_alsa_path_set, 1); + ps->direction = direction; + + if (direction == PA_ALSA_DIRECTION_OUTPUT) + pn = m->output_path_names; + else if (direction == PA_ALSA_DIRECTION_INPUT) + pn = m->input_path_names; + + if (pn) { + char **in; + + for (in = pn; *in; in++) { + pa_alsa_path *p; + pa_bool_t duplicate = FALSE; + char **kn, *fn; + + for (kn = pn; kn < in; kn++) + if (pa_streq(*kn, *in)) { + duplicate = TRUE; + break; + } + + if (duplicate) + continue; + + fn = pa_sprintf_malloc("%s.conf", *in); + + if ((p = pa_alsa_path_new(fn, direction))) { + p->path_set = ps; + PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p); + ps->last_path = p; + } + + pa_xfree(fn); + } + + goto finish; + } + + if (direction == PA_ALSA_DIRECTION_OUTPUT) + en = m->output_element; + else if (direction == PA_ALSA_DIRECTION_INPUT) + en = m->input_element; + + if (!en) { + pa_alsa_path_set_free(ps); + return NULL; + } + + for (ie = en; *ie; ie++) { + char **je; + pa_alsa_path *p; + + p = pa_alsa_path_synthesize(*ie, direction); + p->path_set = ps; + + /* Mark all other passed elements for require-absent */ + for (je = en; *je; je++) { + pa_alsa_element *e; + + if (je == ie) + continue; + + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; + e->alsa_name = pa_xstrdup(*je); + e->direction = direction; + e->required_absent = PA_ALSA_REQUIRED_ANY; + e->volume_limit = -1; + + PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); + p->last_element = e; + } + + PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p); + ps->last_path = p; + } + +finish: + /* Assign decibel fixes to elements. */ + PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) { + pa_alsa_path *p; + + PA_LLIST_FOREACH(p, ps->paths) { + pa_alsa_element *e; + + PA_LLIST_FOREACH(e, p->elements) { + if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) { + /* The profile set that contains the dB fix may be freed + * before the element, so we have to copy the dB fix + * object. */ + e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1); + e->db_fix->profile_set = NULL; + e->db_fix->name = pa_xstrdup(db_fix->name); + e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long)); + } + } + } + } + + return ps; +} + +void pa_alsa_path_set_dump(pa_alsa_path_set *ps) { + pa_alsa_path *p; + pa_assert(ps); + + pa_log_debug("Path Set %p, direction=%i, probed=%s", + (void*) ps, + ps->direction, + pa_yes_no(ps->probed)); + + PA_LLIST_FOREACH(p, ps->paths) + pa_alsa_path_dump(p); +} + +static void path_set_unify(pa_alsa_path_set *ps) { + pa_alsa_path *p; + pa_bool_t has_dB = TRUE, has_volume = TRUE, has_mute = TRUE; + pa_assert(ps); + + /* We have issues dealing with paths that vary too wildly. That + * means for now we have to have all paths support volume/mute/dB + * or none. */ + + PA_LLIST_FOREACH(p, ps->paths) { + pa_assert(p->probed); + + if (!p->has_volume) + has_volume = FALSE; + else if (!p->has_dB) + has_dB = FALSE; + + if (!p->has_mute) + has_mute = FALSE; + } + + if (!has_volume || !has_dB || !has_mute) { + + if (!has_volume) + pa_log_debug("Some paths of the device lack hardware volume control, disabling hardware control altogether."); + else if (!has_dB) + pa_log_debug("Some paths of the device lack dB information, disabling dB logic altogether."); + + if (!has_mute) + pa_log_debug("Some paths of the device lack hardware mute control, disabling hardware control altogether."); + + PA_LLIST_FOREACH(p, ps->paths) { + if (!has_volume) + p->has_volume = FALSE; + else if (!has_dB) + p->has_dB = FALSE; + + if (!has_mute) + p->has_mute = FALSE; + } + } +} + +static void path_set_make_paths_unique(pa_alsa_path_set *ps) { + pa_alsa_path *p, *q; + + PA_LLIST_FOREACH(p, ps->paths) { + unsigned i; + char *m; + + for (q = p->next; q; q = q->next) + if (pa_streq(q->name, p->name)) + break; + + if (!q) + continue; + + m = pa_xstrdup(p->name); + + /* OK, this name is not unique, hence let's rename */ + for (i = 1, q = p; q; q = q->next) { + char *nn, *nd; + + if (!pa_streq(q->name, m)) + continue; + + nn = pa_sprintf_malloc("%s-%u", m, i); + pa_xfree(q->name); + q->name = nn; + + nd = pa_sprintf_malloc("%s %u", q->description, i); + pa_xfree(q->description); + q->description = nd; + + i++; + } + + pa_xfree(m); + } +} + +void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t ignore_dB) { + pa_alsa_path *p, *n; + + pa_assert(ps); + + if (ps->probed) + return; + + for (p = ps->paths; p; p = n) { + n = p->next; + + if (pa_alsa_path_probe(p, m, ignore_dB) < 0) { + PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p); + pa_alsa_path_free(p); + } + } + + path_set_unify(ps); + path_set_make_paths_unique(ps); + ps->probed = TRUE; +} + +static void mapping_free(pa_alsa_mapping *m) { + pa_assert(m); + + pa_xfree(m->name); + pa_xfree(m->description); + + pa_xstrfreev(m->device_strings); + pa_xstrfreev(m->input_path_names); + pa_xstrfreev(m->output_path_names); + pa_xstrfreev(m->input_element); + pa_xstrfreev(m->output_element); + + pa_assert(!m->input_pcm); + pa_assert(!m->output_pcm); + + pa_xfree(m); +} + +static void profile_free(pa_alsa_profile *p) { + pa_assert(p); + + pa_xfree(p->name); + pa_xfree(p->description); + + pa_xstrfreev(p->input_mapping_names); + pa_xstrfreev(p->output_mapping_names); + + if (p->input_mappings) + pa_idxset_free(p->input_mappings, NULL, NULL); + + if (p->output_mappings) + pa_idxset_free(p->output_mappings, NULL, NULL); + + pa_xfree(p); +} + +void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) { + pa_assert(ps); + + if (ps->profiles) { + pa_alsa_profile *p; + + while ((p = pa_hashmap_steal_first(ps->profiles))) + profile_free(p); + + pa_hashmap_free(ps->profiles, NULL, NULL); + } + + if (ps->mappings) { + pa_alsa_mapping *m; + + while ((m = pa_hashmap_steal_first(ps->mappings))) + mapping_free(m); + + pa_hashmap_free(ps->mappings, NULL, NULL); + } + + if (ps->decibel_fixes) { + pa_alsa_decibel_fix *db_fix; + + while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes))) + decibel_fix_free(db_fix); + + pa_hashmap_free(ps->decibel_fixes, NULL, NULL); + } + + pa_xfree(ps); +} + +static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) { + pa_alsa_mapping *m; + + if (!pa_startswith(name, "Mapping ")) + return NULL; + + name += 8; + + if ((m = pa_hashmap_get(ps->mappings, name))) + return m; + + m = pa_xnew0(pa_alsa_mapping, 1); + m->profile_set = ps; + m->name = pa_xstrdup(name); + pa_channel_map_init(&m->channel_map); + + pa_hashmap_put(ps->mappings, m->name, m); + + return m; +} + +static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) { + pa_alsa_profile *p; + + if (!pa_startswith(name, "Profile ")) + return NULL; + + name += 8; + + if ((p = pa_hashmap_get(ps->profiles, name))) + return p; + + p = pa_xnew0(pa_alsa_profile, 1); + p->profile_set = ps; + p->name = pa_xstrdup(name); + + pa_hashmap_put(ps->profiles, p->name, p); + + return p; +} + +static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) { + pa_alsa_decibel_fix *db_fix; + + if (!pa_startswith(name, "DecibelFix ")) + return NULL; + + name += 11; + + if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name))) + return db_fix; + + db_fix = pa_xnew0(pa_alsa_decibel_fix, 1); + db_fix->profile_set = ps; + db_fix->name = pa_xstrdup(name); + + pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix); + + return db_fix; +} + +static int mapping_parse_device_strings( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_mapping *m; + + pa_assert(ps); + + if (!(m = mapping_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + pa_xstrfreev(m->device_strings); + if (!(m->device_strings = pa_split_spaces_strv(rvalue))) { + pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section); + return -1; + } + + return 0; +} + +static int mapping_parse_channel_map( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_mapping *m; + + pa_assert(ps); + + if (!(m = mapping_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if (!(pa_channel_map_parse(&m->channel_map, rvalue))) { + pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section); + return -1; + } + + return 0; +} + +static int mapping_parse_paths( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_mapping *m; + + pa_assert(ps); + + if (!(m = mapping_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if (pa_streq(lvalue, "paths-input")) { + pa_xstrfreev(m->input_path_names); + m->input_path_names = pa_split_spaces_strv(rvalue); + } else { + pa_xstrfreev(m->output_path_names); + m->output_path_names = pa_split_spaces_strv(rvalue); + } + + return 0; +} + +static int mapping_parse_element( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_mapping *m; + + pa_assert(ps); + + if (!(m = mapping_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if (pa_streq(lvalue, "element-input")) { + pa_xstrfreev(m->input_element); + m->input_element = pa_split_spaces_strv(rvalue); + } else { + pa_xstrfreev(m->output_element); + m->output_element = pa_split_spaces_strv(rvalue); + } + + return 0; +} + +static int mapping_parse_direction( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_mapping *m; + + pa_assert(ps); + + if (!(m = mapping_get(ps, section))) { + pa_log("[%s:%u] Section name %s invalid.", filename, line, section); + return -1; + } + + if (pa_streq(rvalue, "input")) + m->direction = PA_ALSA_DIRECTION_INPUT; + else if (pa_streq(rvalue, "output")) + m->direction = PA_ALSA_DIRECTION_OUTPUT; + else if (pa_streq(rvalue, "any")) + m->direction = PA_ALSA_DIRECTION_ANY; + else { + pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int mapping_parse_description( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_profile *p; + pa_alsa_mapping *m; + + pa_assert(ps); + + if ((m = mapping_get(ps, section))) { + pa_xfree(m->description); + m->description = pa_xstrdup(rvalue); + } else if ((p = profile_get(ps, section))) { + pa_xfree(p->description); + p->description = pa_xstrdup(rvalue); + } else { + pa_log("[%s:%u] Section name %s invalid.", filename, line, section); + return -1; + } + + return 0; +} + +static int mapping_parse_priority( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_profile *p; + pa_alsa_mapping *m; + uint32_t prio; + + pa_assert(ps); + + if (pa_atou(rvalue, &prio) < 0) { + pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section); + return -1; + } + + if ((m = mapping_get(ps, section))) + m->priority = prio; + else if ((p = profile_get(ps, section))) + p->priority = prio; + else { + pa_log("[%s:%u] Section name %s invalid.", filename, line, section); + return -1; + } + + return 0; +} + +static int profile_parse_mappings( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_profile *p; + + pa_assert(ps); + + if (!(p = profile_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if (pa_streq(lvalue, "input-mappings")) { + pa_xstrfreev(p->input_mapping_names); + p->input_mapping_names = pa_split_spaces_strv(rvalue); + } else { + pa_xstrfreev(p->output_mapping_names); + p->output_mapping_names = pa_split_spaces_strv(rvalue); + } + + return 0; +} + +static int profile_parse_skip_probe( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_profile *p; + int b; + + pa_assert(ps); + + if (!(p = profile_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if ((b = pa_parse_boolean(rvalue)) < 0) { + pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section); + return -1; + } + + p->supported = b; + + return 0; +} + +static int decibel_fix_parse_db_values( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + pa_alsa_profile_set *ps = userdata; + pa_alsa_decibel_fix *db_fix; + char **items; + char *item; + long *db_values; + unsigned n = 8; /* Current size of the db_values table. */ + unsigned min_step = 0; + unsigned max_step = 0; + unsigned i = 0; /* Index to the items table. */ + unsigned prev_step = 0; + double prev_db = 0; + + pa_assert(filename); + pa_assert(section); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(ps); + + if (!(db_fix = decibel_fix_get(ps, section))) { + pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section); + return -1; + } + + if (!(items = pa_split_spaces_strv(rvalue))) { + pa_log("[%s:%u] Value missing", pa_strnull(filename), line); + return -1; + } + + db_values = pa_xnew(long, n); + + while ((item = items[i++])) { + char *s = item; /* Step value string. */ + char *d = item; /* dB value string. */ + uint32_t step; + double db; + + /* Move d forward until it points to a colon or to the end of the item. */ + for (; *d && *d != ':'; ++d); + + if (d == s) { + /* item started with colon. */ + pa_log("[%s:%u] No step value found in %s", filename, line, item); + goto fail; + } + + if (!*d || !*(d + 1)) { + /* No colon found, or it was the last character in item. */ + pa_log("[%s:%u] No dB value found in %s", filename, line, item); + goto fail; + } + + /* pa_atou() needs a null-terminating string. Let's replace the colon + * with a zero byte. */ + *d++ = '\0'; + + if (pa_atou(s, &step) < 0) { + pa_log("[%s:%u] Invalid step value: %s", filename, line, s); + goto fail; + } + + if (pa_atod(d, &db) < 0) { + pa_log("[%s:%u] Invalid dB value: %s", filename, line, d); + goto fail; + } + + if (step <= prev_step && i != 1) { + pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step); + goto fail; + } + + if (db < prev_db && i != 1) { + pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db); + goto fail; + } + + if (i == 1) { + min_step = step; + db_values[0] = (long) (db * 100.0); + prev_step = step; + prev_db = db; + } else { + /* Interpolate linearly. */ + double db_increment = (db - prev_db) / (step - prev_step); + + for (; prev_step < step; ++prev_step, prev_db += db_increment) { + + /* Reallocate the db_values table if it's about to overflow. */ + if (prev_step + 1 - min_step == n) { + n *= 2; + db_values = pa_xrenew(long, db_values, n); + } + + db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0); + } + } + + max_step = step; + } + + db_fix->min_step = min_step; + db_fix->max_step = max_step; + pa_xfree(db_fix->db_values); + db_fix->db_values = db_values; + + pa_xstrfreev(items); + + return 0; + +fail: + pa_xstrfreev(items); + pa_xfree(db_values); + + return -1; +} + +static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { + + static const struct description_map well_known_descriptions[] = { + { "analog-mono", N_("Analog Mono") }, + { "analog-stereo", N_("Analog Stereo") }, + { "analog-surround-21", N_("Analog Surround 2.1") }, + { "analog-surround-30", N_("Analog Surround 3.0") }, + { "analog-surround-31", N_("Analog Surround 3.1") }, + { "analog-surround-40", N_("Analog Surround 4.0") }, + { "analog-surround-41", N_("Analog Surround 4.1") }, + { "analog-surround-50", N_("Analog Surround 5.0") }, + { "analog-surround-51", N_("Analog Surround 5.1") }, + { "analog-surround-61", N_("Analog Surround 6.0") }, + { "analog-surround-61", N_("Analog Surround 6.1") }, + { "analog-surround-70", N_("Analog Surround 7.0") }, + { "analog-surround-71", N_("Analog Surround 7.1") }, + { "iec958-stereo", N_("Digital Stereo (IEC958)") }, + { "iec958-passthrough", N_("Digital Passthrough (IEC958)") }, + { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") }, + { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") }, + { "hdmi-stereo", N_("Digital Stereo (HDMI)") } + }; + + pa_assert(m); + + if (!pa_channel_map_valid(&m->channel_map)) { + pa_log("Mapping %s is missing channel map.", m->name); + return -1; + } + + if (!m->device_strings) { + pa_log("Mapping %s is missing device strings.", m->name); + return -1; + } + + if ((m->input_path_names && m->input_element) || + (m->output_path_names && m->output_element)) { + pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name); + return -1; + } + + if (!m->description) + m->description = pa_xstrdup(lookup_description(m->name, + well_known_descriptions, + PA_ELEMENTSOF(well_known_descriptions))); + + if (!m->description) + m->description = pa_xstrdup(m->name); + + if (bonus) { + if (pa_channel_map_equal(&m->channel_map, bonus)) + m->priority += 50; + else if (m->channel_map.channels == bonus->channels) + m->priority += 30; + } + + return 0; +} + +void pa_alsa_mapping_dump(pa_alsa_mapping *m) { + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(m); + + pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i", + m->name, + pa_strnull(m->description), + m->priority, + pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map), + pa_yes_no(m->supported), + m->direction); +} + +static void profile_set_add_auto_pair( + pa_alsa_profile_set *ps, + pa_alsa_mapping *m, /* output */ + pa_alsa_mapping *n /* input */) { + + char *name; + pa_alsa_profile *p; + + pa_assert(ps); + pa_assert(m || n); + + if (m && m->direction == PA_ALSA_DIRECTION_INPUT) + return; + + if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT) + return; + + if (m && n) + name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name); + else if (m) + name = pa_sprintf_malloc("output:%s", m->name); + else + name = pa_sprintf_malloc("input:%s", n->name); + + if (pa_hashmap_get(ps->profiles, name)) { + pa_xfree(name); + return; + } + + p = pa_xnew0(pa_alsa_profile, 1); + p->profile_set = ps; + p->name = name; + + if (m) { + p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_idxset_put(p->output_mappings, m, NULL); + p->priority += m->priority * 100; + } + + if (n) { + p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_idxset_put(p->input_mappings, n, NULL); + p->priority += n->priority; + } + + pa_hashmap_put(ps->profiles, p->name, p); +} + +static void profile_set_add_auto(pa_alsa_profile_set *ps) { + pa_alsa_mapping *m, *n; + void *m_state, *n_state; + + pa_assert(ps); + + PA_HASHMAP_FOREACH(m, ps->mappings, m_state) { + profile_set_add_auto_pair(ps, m, NULL); + + PA_HASHMAP_FOREACH(n, ps->mappings, n_state) + profile_set_add_auto_pair(ps, m, n); + } + + PA_HASHMAP_FOREACH(n, ps->mappings, n_state) + profile_set_add_auto_pair(ps, NULL, n); +} + +static int profile_verify(pa_alsa_profile *p) { + + static const struct description_map well_known_descriptions[] = { + { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") }, + { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") }, + { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") }, + { "off", N_("Off") } + }; + + pa_assert(p); + + /* Replace the output mapping names by the actual mappings */ + if (p->output_mapping_names) { + char **name; + + pa_assert(!p->output_mappings); + p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + for (name = p->output_mapping_names; *name; name++) { + pa_alsa_mapping *m; + char **in; + pa_bool_t duplicate = FALSE; + + for (in = name + 1; *in; in++) + if (pa_streq(*name, *in)) { + duplicate = TRUE; + break; + } + + if (duplicate) + continue; + + if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) { + pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name); + return -1; + } + + pa_idxset_put(p->output_mappings, m, NULL); + + if (p->supported) + m->supported++; + } + + pa_xstrfreev(p->output_mapping_names); + p->output_mapping_names = NULL; + } + + /* Replace the input mapping names by the actual mappings */ + if (p->input_mapping_names) { + char **name; + + pa_assert(!p->input_mappings); + p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + for (name = p->input_mapping_names; *name; name++) { + pa_alsa_mapping *m; + char **in; + pa_bool_t duplicate = FALSE; + + for (in = name + 1; *in; in++) + if (pa_streq(*name, *in)) { + duplicate = TRUE; + break; + } + + if (duplicate) + continue; + + if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) { + pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name); + return -1; + } + + pa_idxset_put(p->input_mappings, m, NULL); + + if (p->supported) + m->supported++; + } + + pa_xstrfreev(p->input_mapping_names); + p->input_mapping_names = NULL; + } + + if (!p->input_mappings && !p->output_mappings) { + pa_log("Profile '%s' lacks mappings.", p->name); + return -1; + } + + if (!p->description) + p->description = pa_xstrdup(lookup_description(p->name, + well_known_descriptions, + PA_ELEMENTSOF(well_known_descriptions))); + + if (!p->description) { + pa_strbuf *sb; + uint32_t idx; + pa_alsa_mapping *m; + + sb = pa_strbuf_new(); + + if (p->output_mappings) + PA_IDXSET_FOREACH(m, p->output_mappings, idx) { + if (!pa_strbuf_isempty(sb)) + pa_strbuf_puts(sb, " + "); + + pa_strbuf_printf(sb, _("%s Output"), m->description); + } + + if (p->input_mappings) + PA_IDXSET_FOREACH(m, p->input_mappings, idx) { + if (!pa_strbuf_isempty(sb)) + pa_strbuf_puts(sb, " + "); + + pa_strbuf_printf(sb, _("%s Input"), m->description); + } + + p->description = pa_strbuf_tostring_free(sb); + } + + return 0; +} + +void pa_alsa_profile_dump(pa_alsa_profile *p) { + uint32_t idx; + pa_alsa_mapping *m; + pa_assert(p); + + pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u", + p->name, + pa_strnull(p->description), + p->priority, + pa_yes_no(p->supported), + p->input_mappings ? pa_idxset_size(p->input_mappings) : 0, + p->output_mappings ? pa_idxset_size(p->output_mappings) : 0); + + if (p->input_mappings) + PA_IDXSET_FOREACH(m, p->input_mappings, idx) + pa_log_debug("Input %s", m->name); + + if (p->output_mappings) + PA_IDXSET_FOREACH(m, p->output_mappings, idx) + pa_log_debug("Output %s", m->name); +} + +static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) { + pa_assert(db_fix); + + /* Check that the dB mapping has been configured. Since "db-values" is + * currently the only option in the DecibelFix section, and decibel fix + * objects don't get created if a DecibelFix section is empty, this is + * actually a redundant check. Having this may prevent future bugs, + * however. */ + if (!db_fix->db_values) { + pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name); + return -1; + } + + return 0; +} + +void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) { + char *db_values = NULL; + + pa_assert(db_fix); + + if (db_fix->db_values) { + pa_strbuf *buf; + unsigned long i, nsteps; + + pa_assert(db_fix->min_step <= db_fix->max_step); + nsteps = db_fix->max_step - db_fix->min_step + 1; + + buf = pa_strbuf_new(); + for (i = 0; i < nsteps; ++i) + pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0); + + db_values = pa_strbuf_tostring_free(buf); + } + + pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s", + db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values)); + + pa_xfree(db_values); +} + +pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) { + pa_alsa_profile_set *ps; + pa_alsa_profile *p; + pa_alsa_mapping *m; + pa_alsa_decibel_fix *db_fix; + char *fn; + int r; + void *state; + + static pa_config_item items[] = { + /* [General] */ + { "auto-profiles", pa_config_parse_bool, NULL, "General" }, + + /* [Mapping ...] */ + { "device-strings", mapping_parse_device_strings, NULL, NULL }, + { "channel-map", mapping_parse_channel_map, NULL, NULL }, + { "paths-input", mapping_parse_paths, NULL, NULL }, + { "paths-output", mapping_parse_paths, NULL, NULL }, + { "element-input", mapping_parse_element, NULL, NULL }, + { "element-output", mapping_parse_element, NULL, NULL }, + { "direction", mapping_parse_direction, NULL, NULL }, + + /* Shared by [Mapping ...] and [Profile ...] */ + { "description", mapping_parse_description, NULL, NULL }, + { "priority", mapping_parse_priority, NULL, NULL }, + + /* [Profile ...] */ + { "input-mappings", profile_parse_mappings, NULL, NULL }, + { "output-mappings", profile_parse_mappings, NULL, NULL }, + { "skip-probe", profile_parse_skip_probe, NULL, NULL }, + + /* [DecibelFix ...] */ + { "db-values", decibel_fix_parse_db_values, NULL, NULL }, + { NULL, NULL, NULL, NULL } + }; + + ps = pa_xnew0(pa_alsa_profile_set, 1); + ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + items[0].data = &ps->auto_profiles; + + if (!fname) + fname = "default.conf"; + + fn = pa_maybe_prefix_path(fname, + pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" : + PA_ALSA_PROFILE_SETS_DIR); + + r = pa_config_parse(fn, NULL, items, ps); + pa_xfree(fn); + + if (r < 0) + goto fail; + + PA_HASHMAP_FOREACH(m, ps->mappings, state) + if (mapping_verify(m, bonus) < 0) + goto fail; + + if (ps->auto_profiles) + profile_set_add_auto(ps); + + PA_HASHMAP_FOREACH(p, ps->profiles, state) + if (profile_verify(p) < 0) + goto fail; + + PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state) + if (decibel_fix_verify(db_fix) < 0) + goto fail; + + return ps; + +fail: + pa_alsa_profile_set_free(ps); + return NULL; +} + +void pa_alsa_profile_set_probe( + pa_alsa_profile_set *ps, + const char *dev_id, + const pa_sample_spec *ss, + unsigned default_n_fragments, + unsigned default_fragment_size_msec) { + + void *state; + pa_alsa_profile *p, *last = NULL; + pa_alsa_mapping *m; + + pa_assert(ps); + pa_assert(dev_id); + pa_assert(ss); + + if (ps->probed) + return; + + PA_HASHMAP_FOREACH(p, ps->profiles, state) { + pa_sample_spec try_ss; + pa_channel_map try_map; + snd_pcm_uframes_t try_period_size, try_buffer_size; + uint32_t idx; + + /* Is this already marked that it is supported? (i.e. from the config file) */ + if (p->supported) + continue; + + pa_log_debug("Looking at profile %s", p->name); + + /* Close PCMs from the last iteration we don't need anymore */ + if (last && last->output_mappings) + PA_IDXSET_FOREACH(m, last->output_mappings, idx) { + + if (!m->output_pcm) + break; + + if (last->supported) + m->supported++; + + if (!p->output_mappings || !pa_idxset_get_by_data(p->output_mappings, m, NULL)) { + snd_pcm_close(m->output_pcm); + m->output_pcm = NULL; + } + } + + if (last && last->input_mappings) + PA_IDXSET_FOREACH(m, last->input_mappings, idx) { + + if (!m->input_pcm) + break; + + if (last->supported) + m->supported++; + + if (!p->input_mappings || !pa_idxset_get_by_data(p->input_mappings, m, NULL)) { + snd_pcm_close(m->input_pcm); + m->input_pcm = NULL; + } + } + + p->supported = TRUE; + + /* Check if we can open all new ones */ + if (p->output_mappings) + PA_IDXSET_FOREACH(m, p->output_mappings, idx) { + + if (m->output_pcm) + continue; + + pa_log_debug("Checking for playback on %s (%s)", m->description, m->name); + try_map = m->channel_map; + try_ss = *ss; + try_ss.channels = try_map.channels; + + try_period_size = + pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / + pa_frame_size(&try_ss); + try_buffer_size = default_n_fragments * try_period_size; + + if (!(m ->output_pcm = pa_alsa_open_by_template( + m->device_strings, + dev_id, + NULL, + &try_ss, &try_map, + SND_PCM_STREAM_PLAYBACK, + &try_period_size, &try_buffer_size, 0, NULL, NULL, + TRUE))) { + p->supported = FALSE; + break; + } + } + + if (p->input_mappings && p->supported) + PA_IDXSET_FOREACH(m, p->input_mappings, idx) { + + if (m->input_pcm) + continue; + + pa_log_debug("Checking for recording on %s (%s)", m->description, m->name); + try_map = m->channel_map; + try_ss = *ss; + try_ss.channels = try_map.channels; + + try_period_size = + pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) / + pa_frame_size(&try_ss); + try_buffer_size = default_n_fragments * try_period_size; + + if (!(m ->input_pcm = pa_alsa_open_by_template( + m->device_strings, + dev_id, + NULL, + &try_ss, &try_map, + SND_PCM_STREAM_CAPTURE, + &try_period_size, &try_buffer_size, 0, NULL, NULL, + TRUE))) { + p->supported = FALSE; + break; + } + } + + last = p; + + if (p->supported) + pa_log_debug("Profile %s supported.", p->name); + } + + /* Clean up */ + if (last) { + uint32_t idx; + + if (last->output_mappings) + PA_IDXSET_FOREACH(m, last->output_mappings, idx) + if (m->output_pcm) { + + if (last->supported) + m->supported++; + + snd_pcm_close(m->output_pcm); + m->output_pcm = NULL; + } + + if (last->input_mappings) + PA_IDXSET_FOREACH(m, last->input_mappings, idx) + if (m->input_pcm) { + + if (last->supported) + m->supported++; + + snd_pcm_close(m->input_pcm); + m->input_pcm = NULL; + } + } + + PA_HASHMAP_FOREACH(p, ps->profiles, state) + if (!p->supported) { + pa_hashmap_remove(ps->profiles, p->name); + profile_free(p); + } + + PA_HASHMAP_FOREACH(m, ps->mappings, state) + if (m->supported <= 0) { + pa_hashmap_remove(ps->mappings, m->name); + mapping_free(m); + } + + ps->probed = TRUE; +} + +void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { + pa_alsa_profile *p; + pa_alsa_mapping *m; + pa_alsa_decibel_fix *db_fix; + void *state; + + pa_assert(ps); + + pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u", + (void*) + ps, + pa_yes_no(ps->auto_profiles), + pa_yes_no(ps->probed), + pa_hashmap_size(ps->mappings), + pa_hashmap_size(ps->profiles), + pa_hashmap_size(ps->decibel_fixes)); + + PA_HASHMAP_FOREACH(m, ps->mappings, state) + pa_alsa_mapping_dump(m); + + PA_HASHMAP_FOREACH(p, ps->profiles, state) + pa_alsa_profile_dump(p); + + PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state) + pa_alsa_decibel_fix_dump(db_fix); +} + +void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) { + pa_alsa_path *path; + + pa_assert(p); + pa_assert(!*p); + pa_assert(ps); + + /* if there is no path, we don't want a port list */ + if (!ps->paths) + return; + + if (!ps->paths->next){ + pa_alsa_setting *s; + + /* If there is only one path, but no or only one setting, then + * we want a port list either */ + if (!ps->paths->settings || !ps->paths->settings->next) + return; + + /* Ok, there is only one path, however with multiple settings, + * so let's create a port for each setting */ + *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + PA_LLIST_FOREACH(s, ps->paths->settings) { + pa_device_port *port; + pa_alsa_port_data *data; + + port = pa_device_port_new(s->name, s->description, sizeof(pa_alsa_port_data)); + port->priority = s->priority; + + data = PA_DEVICE_PORT_DATA(port); + data->path = ps->paths; + data->setting = s; + + pa_hashmap_put(*p, port->name, port); + } + + } else { + + /* We have multiple paths, so let's create a port for each + * one, and each of each settings */ + *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + PA_LLIST_FOREACH(path, ps->paths) { + + if (!path->settings || !path->settings->next) { + pa_device_port *port; + pa_alsa_port_data *data; + + /* If there is no or just one setting we only need a + * single entry */ + + port = pa_device_port_new(path->name, path->description, sizeof(pa_alsa_port_data)); + port->priority = path->priority * 100; + + + data = PA_DEVICE_PORT_DATA(port); + data->path = path; + data->setting = path->settings; + + pa_hashmap_put(*p, port->name, port); + } else { + pa_alsa_setting *s; + + PA_LLIST_FOREACH(s, path->settings) { + pa_device_port *port; + pa_alsa_port_data *data; + char *n, *d; + + n = pa_sprintf_malloc("%s;%s", path->name, s->name); + + if (s->description[0]) + d = pa_sprintf_malloc(_("%s / %s"), path->description, s->description); + else + d = pa_xstrdup(path->description); + + port = pa_device_port_new(n, d, sizeof(pa_alsa_port_data)); + port->priority = path->priority * 100 + s->priority; + + pa_xfree(n); + pa_xfree(d); + + data = PA_DEVICE_PORT_DATA(port); + data->path = path; + data->setting = s; + + pa_hashmap_put(*p, port->name, port); + } + } + } + } + + pa_log_debug("Added %u ports", pa_hashmap_size(*p)); +} diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h new file mode 100644 index 00000000..d92d3e98 --- /dev/null +++ b/src/modules/alsa/alsa-mixer.h @@ -0,0 +1,328 @@ +#ifndef fooalsamixerhfoo +#define fooalsamixerhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <asoundlib.h> + +#include <pulse/sample.h> +#include <pulse/mainloop-api.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +#include <pulsecore/llist.h> +#include <pulsecore/rtpoll.h> + +typedef struct pa_alsa_fdlist pa_alsa_fdlist; +typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata; +typedef struct pa_alsa_setting pa_alsa_setting; +typedef struct pa_alsa_option pa_alsa_option; +typedef struct pa_alsa_element pa_alsa_element; +typedef struct pa_alsa_path pa_alsa_path; +typedef struct pa_alsa_path_set pa_alsa_path_set; +typedef struct pa_alsa_mapping pa_alsa_mapping; +typedef struct pa_alsa_profile pa_alsa_profile; +typedef struct pa_alsa_decibel_fix pa_alsa_decibel_fix; +typedef struct pa_alsa_profile_set pa_alsa_profile_set; +typedef struct pa_alsa_port_data pa_alsa_port_data; + +#include "alsa-util.h" + +typedef enum pa_alsa_switch_use { + PA_ALSA_SWITCH_IGNORE, + PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */ + PA_ALSA_SWITCH_OFF, /* set this switch to 'off' unconditionally */ + PA_ALSA_SWITCH_ON, /* set this switch to 'on' unconditionally */ + PA_ALSA_SWITCH_SELECT /* allow the user to select switch status through a setting */ +} pa_alsa_switch_use_t; + +typedef enum pa_alsa_volume_use { + PA_ALSA_VOLUME_IGNORE, + PA_ALSA_VOLUME_MERGE, /* merge this volume slider into the global volume slider */ + PA_ALSA_VOLUME_OFF, /* set this volume to minimal unconditionally */ + PA_ALSA_VOLUME_ZERO, /* set this volume to 0dB unconditionally */ + PA_ALSA_VOLUME_CONSTANT /* set this volume to a constant value unconditionally */ +} pa_alsa_volume_use_t; + +typedef enum pa_alsa_enumeration_use { + PA_ALSA_ENUMERATION_IGNORE, + PA_ALSA_ENUMERATION_SELECT +} pa_alsa_enumeration_use_t; + +typedef enum pa_alsa_required { + PA_ALSA_REQUIRED_IGNORE, + PA_ALSA_REQUIRED_SWITCH, + PA_ALSA_REQUIRED_VOLUME, + PA_ALSA_REQUIRED_ENUMERATION, + PA_ALSA_REQUIRED_ANY +} pa_alsa_required_t; + +typedef enum pa_alsa_direction { + PA_ALSA_DIRECTION_ANY, + PA_ALSA_DIRECTION_OUTPUT, + PA_ALSA_DIRECTION_INPUT +} pa_alsa_direction_t; + +/* A setting combines a couple of options into a single entity that + * may be selected. Only one setting can be active at the same + * time. */ +struct pa_alsa_setting { + pa_alsa_path *path; + PA_LLIST_FIELDS(pa_alsa_setting); + + pa_idxset *options; + + char *name; + char *description; + unsigned priority; +}; + +/* An option belongs to an element and refers to one enumeration item + * of the element is an enumeration item, or a switch status if the + * element is a switch item. */ +struct pa_alsa_option { + pa_alsa_element *element; + PA_LLIST_FIELDS(pa_alsa_option); + + char *alsa_name; + int alsa_idx; + + char *name; + char *description; + unsigned priority; + + pa_alsa_required_t required; + pa_alsa_required_t required_any; + pa_alsa_required_t required_absent; +}; + +/* An element wraps one specific ALSA element. A series of elements + * make up a path (see below). If the element is an enumeration or switch + * element it may include a list of options. */ +struct pa_alsa_element { + pa_alsa_path *path; + PA_LLIST_FIELDS(pa_alsa_element); + + char *alsa_name; + pa_alsa_direction_t direction; + + pa_alsa_switch_use_t switch_use; + pa_alsa_volume_use_t volume_use; + pa_alsa_enumeration_use_t enumeration_use; + + pa_alsa_required_t required; + pa_alsa_required_t required_any; + pa_alsa_required_t required_absent; + + long constant_volume; + + pa_bool_t override_map:1; + pa_bool_t direction_try_other:1; + + pa_bool_t has_dB:1; + long min_volume, max_volume; + long volume_limit; /* -1 for no configured limit */ + double min_dB, max_dB; + + pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2]; + unsigned n_channels; + + pa_channel_position_mask_t merged_mask; + + PA_LLIST_HEAD(pa_alsa_option, options); + + pa_alsa_decibel_fix *db_fix; +}; + +/* A path wraps a series of elements into a single entity which can be + * used to control it as if it had a single volume slider, a single + * mute switch and a single list of selectable options. */ +struct pa_alsa_path { + pa_alsa_path_set *path_set; + PA_LLIST_FIELDS(pa_alsa_path); + + pa_alsa_direction_t direction; + + char *name; + char *description; + unsigned priority; + + pa_bool_t probed:1; + pa_bool_t supported:1; + pa_bool_t has_mute:1; + pa_bool_t has_volume:1; + pa_bool_t has_dB:1; + /* These two are used during probing only */ + pa_bool_t has_req_any:1; + pa_bool_t req_any_present:1; + + long min_volume, max_volume; + double min_dB, max_dB; + + /* This is used during parsing only, as a shortcut so that we + * don't have to iterate the list all the time */ + pa_alsa_element *last_element; + pa_alsa_option *last_option; + pa_alsa_setting *last_setting; + + PA_LLIST_HEAD(pa_alsa_element, elements); + PA_LLIST_HEAD(pa_alsa_setting, settings); +}; + +/* A path set is simply a set of paths that are applicable to a + * device */ +struct pa_alsa_path_set { + PA_LLIST_HEAD(pa_alsa_path, paths); + pa_alsa_direction_t direction; + pa_bool_t probed:1; + + /* This is used during parsing only, as a shortcut so that we + * don't have to iterate the list all the time */ + pa_alsa_path *last_path; +}; + +int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m); +void pa_alsa_setting_dump(pa_alsa_setting *s); + +void pa_alsa_option_dump(pa_alsa_option *o); + +void pa_alsa_element_dump(pa_alsa_element *e); + +pa_alsa_path *pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction); +pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); +int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB); +void pa_alsa_path_dump(pa_alsa_path *p); +int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); +int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted); +int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw); +int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t muted); +int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m); +void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); +void pa_alsa_path_free(pa_alsa_path *p); + +pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction); +void pa_alsa_path_set_probe(pa_alsa_path_set *s, snd_mixer_t *m, pa_bool_t ignore_dB); +void pa_alsa_path_set_dump(pa_alsa_path_set *s); +void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); +void pa_alsa_path_set_free(pa_alsa_path_set *s); + +struct pa_alsa_mapping { + pa_alsa_profile_set *profile_set; + + char *name; + char *description; + unsigned priority; + pa_alsa_direction_t direction; + + pa_channel_map channel_map; + + char **device_strings; + + char **input_path_names; + char **output_path_names; + char **input_element; /* list of fallbacks */ + char **output_element; + + unsigned supported; + + /* Temporarily used during probing */ + snd_pcm_t *input_pcm; + snd_pcm_t *output_pcm; + + pa_sink *sink; + pa_source *source; +}; + +struct pa_alsa_profile { + pa_alsa_profile_set *profile_set; + + char *name; + char *description; + unsigned priority; + + pa_bool_t supported:1; + + char **input_mapping_names; + char **output_mapping_names; + + pa_idxset *input_mappings; + pa_idxset *output_mappings; +}; + +struct pa_alsa_decibel_fix { + pa_alsa_profile_set *profile_set; + + char *name; /* Alsa volume element name. */ + long min_step; + long max_step; + + /* An array that maps alsa volume element steps to decibels. The steps can + * be used as indices to this array, after substracting min_step from the + * real value. + * + * The values are actually stored as integers representing millibels, + * because that's the format the alsa API uses. */ + long *db_values; +}; + +struct pa_alsa_profile_set { + pa_hashmap *mappings; + pa_hashmap *profiles; + pa_hashmap *decibel_fixes; + + pa_bool_t auto_profiles; + pa_bool_t probed:1; +}; + +void pa_alsa_mapping_dump(pa_alsa_mapping *m); +void pa_alsa_profile_dump(pa_alsa_profile *p); +void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix); + +pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); +void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); +void pa_alsa_profile_set_free(pa_alsa_profile_set *s); +void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); + +snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); + +pa_alsa_fdlist *pa_alsa_fdlist_new(void); +void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl); +int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); + +/* Alternative for handling alsa mixer events in io-thread. */ + +pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void); +void pa_alsa_mixer_pdata_free(pa_alsa_mixer_pdata *pd); +int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp); + +/* Data structure for inclusion in pa_device_port for alsa + * sinks/sources. This contains nothing that needs to be freed + * individually */ +struct pa_alsa_port_data { + pa_alsa_path *path; + pa_alsa_setting *setting; +}; + +void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps); + +#endif diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 59f53110..0164040d 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -32,25 +32,25 @@ #include <valgrind/memcheck.h> #endif -#include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulse/timeval.h> #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> #include <pulsecore/core.h> #include <pulsecore/module.h> #include <pulsecore/memchunk.h> #include <pulsecore/sink.h> #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h> #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> #include <pulsecore/thread.h> -#include <pulsecore/core-error.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h> #include <pulsecore/time-smoother.h> #include <modules/reserve-wrap.h> @@ -61,11 +61,32 @@ /* #define DEBUG_TIMING */ #define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */ -#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */ -#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */ -#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/ + +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */ + +#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */ +#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */ +#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */ +#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms -- If the buffer level ever below this theshold, increase the watermark */ +#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */ + +/* Note that TSCHED_WATERMARK_INC_THRESHOLD_USEC == 0 means tht we + * will increase the watermark only if we hit a real underrun. */ + +#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */ +#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/ + +#define SMOOTHER_WINDOW_USEC (10*PA_USEC_PER_SEC) /* 10s -- smoother windows size */ +#define SMOOTHER_ADJUST_USEC (1*PA_USEC_PER_SEC) /* 1s -- smoother adjust time */ + +#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */ +#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update interval */ + +#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ + +#define DEFAULT_REWIND_SAFEGUARD_BYTES (256U) /* 1.33ms @48kHz, we'll never rewind less than this */ +#define DEFAULT_REWIND_SAFEGUARD_USEC (1330) /* 1.33ms, depending on channels/rate/sample we may rewind more than 256 above */ struct userdata { pa_core *core; @@ -79,14 +100,15 @@ struct userdata { snd_pcm_t *pcm_handle; pa_alsa_fdlist *mixer_fdl; + pa_alsa_mixer_pdata *mixer_pd; snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - long hw_volume_max, hw_volume_min; - long hw_dB_max, hw_dB_min; - pa_bool_t hw_dB_supported:1; - pa_bool_t mixer_seperate_channels:1; + pa_alsa_path_set *mixer_path_set; + pa_alsa_path *mixer_path; + pa_cvolume hardware_volume; + uint32_t old_rate; + size_t frame_size, fragment_size, @@ -95,12 +117,18 @@ struct userdata { hwbuf_unused, min_sleep, min_wakeup, - watermark_step; + watermark_inc_step, + watermark_dec_step, + watermark_inc_threshold, + watermark_dec_threshold, + rewind_safeguard; + + pa_usec_t watermark_dec_not_before; - unsigned nfragments; pa_memchunk memchunk; - char *device_name; + char *device_name; /* name of the PCM device */ + char *control_device; /* name of the control device */ pa_bool_t use_mmap:1, use_tsched:1; @@ -113,9 +141,13 @@ struct userdata { pa_smoother *smoother; uint64_t write_count; uint64_t since_start; + pa_usec_t smoother_interval; + pa_usec_t last_smoother_update; pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -124,7 +156,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_sink_suspend(u->sink, TRUE) < 0) + if (pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -167,10 +199,10 @@ static int reserve_init(struct userdata *u, const char *dname) { if (pa_in_system_mode()) return 0; - /* We are resuming, try to lock the device */ if (!(rname = pa_alsa_get_reserve_name(dname))) return 0; + /* We are resuming, try to lock the device */ u->reserve = pa_reserve_wrapper_get(u->core, rname); pa_xfree(rname); @@ -185,10 +217,62 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_sink_suspend(u->sink, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + /* We are resuming, try to lock the device */ + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec); @@ -203,6 +287,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) { static void fix_tsched_watermark(struct userdata *u) { size_t max_use; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; @@ -213,7 +298,7 @@ static void fix_tsched_watermark(struct userdata *u) { u->tsched_watermark = u->min_wakeup; } -static void adjust_after_underrun(struct userdata *u) { +static void increase_watermark(struct userdata *u) { size_t old_watermark; pa_usec_t old_min_latency, new_min_latency; @@ -222,31 +307,64 @@ static void adjust_after_underrun(struct userdata *u) { /* First, just try to increase the watermark */ old_watermark = u->tsched_watermark; - u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step); + u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step); fix_tsched_watermark(u); if (old_watermark != u->tsched_watermark) { - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + pa_log_info("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); return; } /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */ old_min_latency = u->sink->thread_info.min_latency; - new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC); + new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC); new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency); if (old_min_latency != new_min_latency) { - pa_log_notice("Increasing minimal latency to %0.2f ms", - (double) new_min_latency / PA_USEC_PER_MSEC); + pa_log_info("Increasing minimal latency to %0.2f ms", + (double) new_min_latency / PA_USEC_PER_MSEC); pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency); - return; } /* When we reach this we're officialy fucked! */ } +static void decrease_watermark(struct userdata *u) { + size_t old_watermark; + pa_usec_t now; + + pa_assert(u); + pa_assert(u->use_tsched); + + now = pa_rtclock_now(); + + if (u->watermark_dec_not_before <= 0) + goto restart; + + if (u->watermark_dec_not_before > now) + return; + + old_watermark = u->tsched_watermark; + + if (u->tsched_watermark < u->watermark_dec_step) + u->tsched_watermark = u->tsched_watermark / 2; + else + u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step); + + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_info("Decreasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + + /* We don't change the latency range*/ + +restart: + u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC; +} + static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { pa_usec_t usec, wm; @@ -254,6 +372,7 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p pa_assert(process_usec); pa_assert(u); + pa_assert(u->use_tsched); usec = pa_sink_get_requested_latency_within_thread(u->sink); @@ -288,6 +407,9 @@ static int try_recover(struct userdata *u, const char *call, int err) { if (err == -EPIPE) pa_log_debug("%s: Buffer underrun!", call); + if (err == -ESTRPIPE) + pa_log_debug("%s: System suspended!", call); + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) { pa_log("%s: %s", call, pa_alsa_strerror(err)); return -1; @@ -298,43 +420,66 @@ static int try_recover(struct userdata *u, const char *call, int err) { return 0; } -static size_t check_left_to_play(struct userdata *u, size_t n_bytes) { +static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) { size_t left_to_play; + pa_bool_t underrun = FALSE; /* We use <= instead of < for this check here because an underrun * only happens after the last sample was processed, not already when * it is removed from the buffer. This is particularly important * when block transfer is used. */ - if (n_bytes <= u->hwbuf_size) { + if (n_bytes <= u->hwbuf_size) left_to_play = u->hwbuf_size - n_bytes; + else { + + /* We got a dropout. What a mess! */ + left_to_play = 0; + underrun = TRUE; #ifdef DEBUG_TIMING - pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + PA_DEBUG_TRAP; #endif - } else { - left_to_play = 0; + if (!u->first && !u->after_rewind) + if (pa_log_ratelimit(PA_LOG_INFO)) + pa_log_info("Underrun!"); + } #ifdef DEBUG_TIMING - PA_DEBUG_TRAP; + pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms", + (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC); #endif + if (u->use_tsched) { + pa_bool_t reset_not_before = TRUE; + if (!u->first && !u->after_rewind) { + if (underrun || left_to_play < u->watermark_inc_threshold) + increase_watermark(u); + else if (left_to_play > u->watermark_dec_threshold) { + reset_not_before = FALSE; - if (pa_log_ratelimit()) - pa_log_info("Underrun!"); + /* We decrease the watermark only if have actually + * been woken up by a timeout. If something else woke + * us up it's too easy to fulfill the deadlines... */ - if (u->use_tsched) - adjust_after_underrun(u); + if (on_timeout) + decrease_watermark(u); + } } + + if (reset_not_before) + u->watermark_dec_not_before = 0; } return left_to_play; } -static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { - pa_bool_t work_done = TRUE; +static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { + pa_bool_t work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; unsigned j = 0; @@ -349,6 +494,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle snd_pcm_sframes_t n; size_t n_bytes; int r; + pa_bool_t after_avail = TRUE; /* First we determine how many samples are missing to fill the * buffer up to 100% */ @@ -367,7 +513,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle pa_log_debug("avail: %lu", (unsigned long) n_bytes); #endif - left_to_play = check_left_to_play(u, n_bytes); + left_to_play = check_left_to_play(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) @@ -432,6 +579,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + if (!after_avail && err == -EAGAIN) + break; + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) continue; @@ -442,6 +592,12 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size) frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size; + if (!after_avail && frames == 0) + break; + + pa_assert(frames > 0); + after_avail = FALSE; + /* Check these are multiples of 8 bit */ pa_assert((areas[0].first & 7) == 0); pa_assert((areas[0].step & 7)== 0); @@ -461,6 +617,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + if (!after_avail && (int) sframes == -EAGAIN) + break; + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) continue; @@ -483,17 +642,20 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle } } - *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); + if (u->use_tsched) { + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); - if (*sleep_usec > process_usec) - *sleep_usec -= process_usec; - else + if (*sleep_usec > process_usec) + *sleep_usec -= process_usec; + else + *sleep_usec = 0; + } else *sleep_usec = 0; return work_done ? 1 : 0; } -static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { pa_bool_t work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; @@ -509,6 +671,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle snd_pcm_sframes_t n; size_t n_bytes; int r; + pa_bool_t after_avail = TRUE; if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { @@ -519,7 +682,8 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle } n_bytes = (size_t) n * u->frame_size; - left_to_play = check_left_to_play(u, n_bytes); + left_to_play = check_left_to_play(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) @@ -579,16 +743,23 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames); pa_memblock_release(u->memchunk.memblock); - pa_assert(frames != 0); - if (PA_UNLIKELY(frames < 0)) { + if (!after_avail && (int) frames == -EAGAIN) + break; + if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) continue; return r; } + if (!after_avail && frames == 0) + break; + + pa_assert(frames > 0); + after_avail = FALSE; + u->memchunk.index += (size_t) frames * u->frame_size; u->memchunk.length -= (size_t) frames * u->frame_size; @@ -611,11 +782,14 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle } } - *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); + if (u->use_tsched) { + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec); - if (*sleep_usec > process_usec) - *sleep_usec -= process_usec; - else + if (*sleep_usec > process_usec) + *sleep_usec -= process_usec; + else + *sleep_usec = 0; + } else *sleep_usec = 0; return work_done ? 1 : 0; @@ -635,7 +809,7 @@ static void update_smoother(struct userdata *u) { /* Let's update the time smoother */ - if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->sink->sample_spec, FALSE)) < 0)) { pa_log_warn("Failed to query DSP status data: %s", pa_alsa_strerror(err)); return; } @@ -648,18 +822,27 @@ static void update_smoother(struct userdata *u) { now1 = pa_timespec_load(&htstamp); } + /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ + if (now1 <= 0) + now1 = pa_rtclock_now(); + + /* check if the time since the last update is bigger than the interval */ + if (u->last_smoother_update > 0) + if (u->last_smoother_update + u->smoother_interval > now1) + return; + position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size); if (PA_UNLIKELY(position < 0)) position = 0; - /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ - if (now1 <= 0) - now1 = pa_rtclock_usec(); - now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec); pa_smoother_put(u->smoother, now1, now2); + + u->last_smoother_update = now1; + /* exponentially increase the update interval up to the MAX limit */ + u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL); } static pa_usec_t sink_get_latency(struct userdata *u) { @@ -669,7 +852,7 @@ static pa_usec_t sink_get_latency(struct userdata *u) { pa_assert(u); - now1 = pa_rtclock_usec(); + now1 = pa_rtclock_now(); now2 = pa_smoother_get(u->smoother, now1); delay = (int64_t) pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now2; @@ -700,7 +883,7 @@ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->pcm_handle); - pa_smoother_pause(u->smoother, pa_rtclock_usec()); + pa_smoother_pause(u->smoother, pa_rtclock_now()); /* Let's suspend -- we don't call snd_pcm_drain() here since that might * take awfully long with our long buffer sizes today. */ @@ -712,6 +895,14 @@ static int suspend(struct userdata *u) { u->alsa_rtpoll_item = NULL; } + /* We reset max_rewind/max_request here to make sure that while we + * are suspended the old max_request/max_rewind values set before + * the suspend can influence the per-stream buffer of newly + * created streams, without their requirements having any + * influence on them. */ + pa_sink_set_max_rewind_within_thread(u->sink, 0); + pa_sink_set_max_request_within_thread(u->sink, 0); + pa_log_info("Device suspended..."); return 0; @@ -763,12 +954,18 @@ static int update_sw_params(struct userdata *u) { pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) { pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err)); return err; } pa_sink_set_max_request_within_thread(u->sink, u->hwbuf_size - u->hwbuf_unused); + if (pa_alsa_pcm_is_hw(u->pcm_handle)) + pa_sink_set_max_rewind_within_thread(u->sink, u->hwbuf_size); + else { + pa_log_info("Disabling rewind_within_thread for device %s", u->device_name); + pa_sink_set_max_rewind_within_thread(u->sink, 0); + } return 0; } @@ -778,17 +975,15 @@ static int unsuspend(struct userdata *u) { pa_sample_spec ss; int err; pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; + snd_pcm_uframes_t period_size, buffer_size; pa_assert(u); pa_assert(!u->pcm_handle); pa_log_info("Trying resume..."); - snd_config_update_free_global(); if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, - /*SND_PCM_NONBLOCK|*/ + SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| SND_PCM_NO_AUTO_FORMAT)) < 0) { @@ -797,12 +992,12 @@ static int unsuspend(struct userdata *u) { } ss = u->sink->sample_spec; - nfrags = u->nfragments; period_size = u->fragment_size / u->frame_size; + buffer_size = u->hwbuf_size / u->frame_size; b = u->use_mmap; d = u->use_tsched; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) { pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err)); goto fail; } @@ -817,10 +1012,11 @@ static int unsuspend(struct userdata *u) { goto fail; } - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); + if (period_size*u->frame_size != u->fragment_size || + buffer_size*u->frame_size != u->hwbuf_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", + (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, + (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size)); goto fail; } @@ -830,6 +1026,11 @@ static int unsuspend(struct userdata *u) { if (build_pollfd(u) < 0) goto fail; + u->write_count = 0; + pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->last_smoother_update = 0; + u->first = TRUE; u->since_start = 0; @@ -843,7 +1044,7 @@ fail: u->pcm_handle = NULL; } - return -1; + return -PA_ERR_IO; } /* Called from IO context */ @@ -852,6 +1053,56 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch (code) { + case PA_SINK_MESSAGE_FINISH_MOVE: + case PA_SINK_MESSAGE_ADD_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(data); + int r = 0; + + if (PA_LIKELY(!pa_sink_input_is_passthrough(i))) + break; + + u->old_rate = u->sink->sample_spec.rate; + + /* Passthrough format, see if we need to reset sink sample rate */ + if (u->sink->sample_spec.rate == i->thread_info.sample_spec.rate) + break; + + /* .. we do */ + if ((r = suspend(u)) < 0) + return r; + + u->sink->sample_spec.rate = i->thread_info.sample_spec.rate; + + if ((r = unsuspend(u)) < 0) + return r; + + break; + } + + case PA_SINK_MESSAGE_START_MOVE: + case PA_SINK_MESSAGE_REMOVE_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(data); + int r = 0; + + if (PA_LIKELY(!pa_sink_input_is_passthrough(i))) + break; + + /* Passthrough format, see if we need to reset sink sample rate */ + if (u->sink->sample_spec.rate == u->old_rate) + break; + + /* .. we do */ + if ((r = suspend(u)) < 0) + return r; + + u->sink->sample_spec.rate = u->old_rate; + + if ((r = unsuspend(u)) < 0) + return r; + + break; + } + case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t r = 0; @@ -867,28 +1118,33 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - case PA_SINK_SUSPENDED: + case PA_SINK_SUSPENDED: { + int r; + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - if (suspend(u) < 0) - return -1; + if ((r = suspend(u)) < 0) + return r; break; + } case PA_SINK_IDLE: - case PA_SINK_RUNNING: + case PA_SINK_RUNNING: { + int r; if (u->sink->thread_info.state == PA_SINK_INIT) { if (build_pollfd(u) < 0) - return -1; + return -PA_ERR_IO; } if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; + if ((r = unsuspend(u)) < 0) + return r; } break; + } case PA_SINK_UNLINKED: case PA_SINK_INIT: @@ -916,12 +1172,12 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) { reserve_done(u); else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state)) if (reserve_init(u, u->device_name) < 0) - return -1; + return -PA_ERR_BUSY; return 0; } -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { +static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { struct userdata *u = snd_mixer_elem_get_callback_private(elem); pa_assert(u); @@ -930,252 +1186,233 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; + if (u->sink->suspend_cause & PA_SUSPEND_SESSION) + return 0; + if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_sink_get_volume(u->sink, TRUE, FALSE); + pa_sink_get_volume(u->sink, TRUE); pa_sink_get_mute(u->sink, TRUE); } return 0; } -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { +static int io_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); - return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / - (double) (u->hw_volume_max - u->hw_volume_min)); -} + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { - long alsa_vol; + if (u->sink->suspend_cause & PA_SUSPEND_SESSION) + return 0; - alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) - / PA_VOLUME_NORM) + u->hw_volume_min; + if (mask & SND_CTL_EVENT_MASK_VALUE) + pa_sink_update_volume_and_mute(u->sink); - return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); + return 0; } static void sink_get_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err; - unsigned i; pa_cvolume r; - char t[PA_CVOLUME_SNPRINT_MAX]; + char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX]; pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - } else { - - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif + if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) + return; - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume); - } else { + pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r)); - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + if (u->mixer_path->has_dB) { + char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX]; - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &r)); } - pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - if (!pa_cvolume_equal(&u->hardware_volume, &r)) { - - s->virtual_volume = u->hardware_volume = r; - - if (u->hw_dB_supported) { - pa_cvolume reset; - - /* Hmm, so the hardware volume changed, let's reset our software volume */ - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_sink_set_soft_volume(s, &reset); - } - } + if (pa_cvolume_equal(&u->hardware_volume, &r)) + return; - return; + s->real_volume = u->hardware_volume = r; -fail: - pa_log_error("Unable to read volume: %s", pa_alsa_strerror(err)); + /* Hmm, so the hardware volume changed, let's reset our software volume */ + if (u->mixer_path->has_dB) + pa_sink_set_soft_volume(s, NULL); } static void sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err; - unsigned i; pa_cvolume r; + char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX]; + pa_bool_t sync_volume = !!(s->flags & PA_SINK_SYNC_VOLUME); pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - pa_volume_t vol; - - vol = s->virtual_volume.values[i]; - - if (u->hw_dB_supported) { - - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) - goto fail; + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, sync_volume, !sync_volume) < 0) + return; - } else { - alsa_vol = to_alsa_volume(u, vol); + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume); - if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) - goto fail; + u->hardware_volume = r; - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; + if (u->mixer_path->has_dB) { + pa_cvolume new_soft_volume; + pa_bool_t accurate_enough; + char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX]; - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } + /* Match exactly what the user requested by software */ + pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume); + + /* If the adjustment to do in software is only minimal we + * can skip it. That saves us CPU at the expense of a bit of + * accuracy */ + accurate_enough = + (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->real_volume)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->real_volume)); + pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &u->hardware_volume)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &u->hardware_volume)); + pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", + pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &new_soft_volume), + pa_yes_no(accurate_enough)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &new_soft_volume)); + + if (!accurate_enough) + s->soft_volume = new_soft_volume; } else { - pa_volume_t vol; - long alsa_vol; - - vol = pa_cvolume_max(&s->virtual_volume); - - if (u->hw_dB_supported) { - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_playback_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r)); -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + s->real_volume = r; + } +} - } else { - alsa_vol = to_alsa_volume(u, vol); +static void sink_write_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + pa_cvolume hw_vol = s->thread_info.current_hw_volume; - if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) - goto fail; + pa_assert(u); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); + pa_assert(s->flags & PA_SINK_SYNC_VOLUME); - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume); - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &hw_vol, TRUE, TRUE) < 0) + pa_log_error("Writing HW volume failed"); + else { + pa_cvolume tmp_vol; + pa_bool_t accurate_enough; + + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&hw_vol, &hw_vol, s->base_volume); + + pa_sw_cvolume_divide(&tmp_vol, &hw_vol, &s->thread_info.current_hw_volume); + accurate_enough = + (pa_cvolume_min(&tmp_vol) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&tmp_vol) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + if (!accurate_enough) { + union { + char db[2][PA_SW_CVOLUME_SNPRINT_DB_MAX]; + char pcnt[2][PA_CVOLUME_SNPRINT_MAX]; + } vol; + + pa_log_debug("Written HW volume did not match with the request: %s (request) != %s", + pa_cvolume_snprint(vol.pcnt[0], sizeof(vol.pcnt[0]), &s->thread_info.current_hw_volume), + pa_cvolume_snprint(vol.pcnt[1], sizeof(vol.pcnt[1]), &hw_vol)); + pa_log_debug(" in dB: %s (request) != %s", + pa_sw_cvolume_snprint_dB(vol.db[0], sizeof(vol.db[0]), &s->thread_info.current_hw_volume), + pa_sw_cvolume_snprint_dB(vol.db[1], sizeof(vol.db[1]), &hw_vol)); } } - - u->hardware_volume = r; - - if (u->hw_dB_supported) { - char t[PA_CVOLUME_SNPRINT_MAX]; - - /* Match exactly what the user requested by software */ - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); - - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); - pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); - - } else - - /* We can't match exactly what the user requested, hence let's - * at least tell the user about it */ - - s->virtual_volume = r; - - return; - -fail: - pa_log_error("Unable to set volume: %s", pa_alsa_strerror(err)); } static void sink_get_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err, sw; + pa_bool_t b; pa_assert(u); - pa_assert(u->mixer_elem); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); - if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { - pa_log_error("Unable to get switch: %s", pa_alsa_strerror(err)); + if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0) return; - } - s->muted = !sw; + s->muted = b; } static void sink_set_mute_cb(pa_sink *s) { struct userdata *u = s->userdata; - int err; pa_assert(u); - pa_assert(u->mixer_elem); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); - if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { - pa_log_error("Unable to set switch: %s", pa_alsa_strerror(err)); - return; + pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); +} + +static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { + struct userdata *u = s->userdata; + pa_alsa_port_data *data; + + pa_assert(u); + pa_assert(p); + pa_assert(u->mixer_handle); + + data = PA_DEVICE_PORT_DATA(p); + + pa_assert_se(u->mixer_path = data->path); + pa_alsa_path_select(u->mixer_path, u->mixer_handle); + + if (u->mixer_path->has_volume && u->mixer_path->has_dB) { + s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); + s->n_volume_steps = PA_VOLUME_NORM+1; + + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); + } else { + s->base_volume = PA_VOLUME_NORM; + s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; } + + if (data->setting) + pa_alsa_setting_select(data->setting, u->mixer_handle); + + if (s->set_mute) + s->set_mute(s); + if (s->set_volume) + s->set_volume(s); + + return 0; } static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u = s->userdata; size_t before; pa_assert(u); + pa_assert(u->use_tsched); /* only when timer scheduling is used + * we can dynamically adjust the + * latency */ if (!u->pcm_handle) return; @@ -1210,7 +1447,10 @@ static int process_rewind(struct userdata *u) { return -1; } - unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size; + unused_nbytes = (size_t) unused * u->frame_size; + + /* make sure rewind doesn't go too far, can cause issues with DMAs */ + unused_nbytes += u->rewind_safeguard; if (u->hwbuf_size > unused_nbytes) limit_nbytes = u->hwbuf_size - unused_nbytes; @@ -1229,8 +1469,11 @@ static int process_rewind(struct userdata *u) { pa_log_debug("before: %lu", (unsigned long) in_frames); if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) { pa_log("snd_pcm_rewind() failed: %s", pa_alsa_strerror((int) out_frames)); - return -1; + if (try_recover(u, "process_rewind", out_frames) < 0) + return -1; + out_frames = 0; } + pa_log_debug("after: %lu", (unsigned long) out_frames); rewind_nbytes = (size_t) out_frames * u->frame_size; @@ -1238,7 +1481,7 @@ static int process_rewind(struct userdata *u) { if (rewind_nbytes <= 0) pa_log_info("Tried rewind, but was apparently not possible."); else { - u->write_count -= out_frames * u->frame_size; + u->write_count -= rewind_nbytes; pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); pa_sink_process_rewind(u->sink, rewind_nbytes); @@ -1264,10 +1507,10 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; + pa_usec_t rtpoll_sleep = 0; #ifdef DEBUG_TIMING pa_log_debug("Loop"); @@ -1277,15 +1520,16 @@ static void thread_func(void *userdata) { if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { int work_done; pa_usec_t sleep_usec = 0; + pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll); if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) if (process_rewind(u) < 0) goto fail; if (u->use_mmap) - work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); + work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout); else - work_done = unix_write(u, &sleep_usec, revents & POLLOUT); + work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout); if (work_done < 0) goto fail; @@ -1298,7 +1542,9 @@ static void thread_func(void *userdata) { pa_log_info("Starting playback."); snd_pcm_start(u->pcm_handle); - pa_smoother_resume(u->smoother, pa_rtclock_usec(), TRUE); + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); + + u->first = FALSE; } update_smoother(u); @@ -1317,7 +1563,8 @@ static void thread_func(void *userdata) { * we have filled the buffer at least once * completely.*/ - pa_log_debug("Cutting sleep time for the initial iterations by half."); + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("Cutting sleep time for the initial iterations by half."); sleep_usec /= 2; } @@ -1327,26 +1574,37 @@ static void thread_func(void *userdata) { /* Convert from the sound card time domain to the * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); /* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ /* We don't trust the conversion, so we wake up whatever comes first */ - pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + rtpoll_sleep = PA_MIN(sleep_usec, cusec); } - u->first = FALSE; u->after_rewind = FALSE; - } else if (u->use_tsched) + } + + if (u->sink->flags & PA_SINK_SYNC_VOLUME) { + pa_usec_t volume_sleep; + pa_sink_volume_change_apply(u->sink, &volume_sleep); + if (volume_sleep > 0) + rtpoll_sleep = PA_MIN(volume_sleep, rtpoll_sleep); + } - /* OK, we're in an invalid state, let's disable our timers */ + if (rtpoll_sleep > 0) + pa_rtpoll_set_timer_relative(u->rtpoll, rtpoll_sleep); + else pa_rtpoll_set_timer_disabled(u->rtpoll); /* Hmm, nothing to do. Let's sleep */ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; + if (u->sink->flags & PA_SINK_SYNC_VOLUME) + pa_sink_volume_change_apply(u->sink, NULL); + if (ret == 0) goto finish; @@ -1369,7 +1627,7 @@ static void thread_func(void *userdata) { u->first = TRUE; u->since_start = 0; - } else if (revents && u->use_tsched && pa_log_ratelimit()) + } else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG)) pa_log_debug("Wakeup from ALSA!"); } else @@ -1386,7 +1644,7 @@ finish: pa_log_debug("Thread shutting down"); } -static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name) { +static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) { const char *n; char *t; @@ -1407,108 +1665,184 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de data->namereg_fail = FALSE; } - t = pa_sprintf_malloc("alsa_output.%s", n); + if (mapping) + t = pa_sprintf_malloc("alsa_output.%s.%s", n, mapping->name); + else + t = pa_sprintf_malloc("alsa_output.%s", n); + pa_sink_new_data_set_name(data, t); pa_xfree(t); } -static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { +static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) { + + if (!mapping && !element) + return; + + if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { + pa_log_info("Failed to find a working mixer device."); + return; + } + + if (element) { + + if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT))) + goto fail; + + if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0) + goto fail; + + pa_log_debug("Probed mixer path %s:", u->mixer_path->name); + pa_alsa_path_dump(u->mixer_path); + } else { + + if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_OUTPUT))) + goto fail; + + pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB); + + pa_log_debug("Probed mixer paths:"); + pa_alsa_path_set_dump(u->mixer_path_set); + } + + return; + +fail: + + if (u->mixer_path_set) { + pa_alsa_path_set_free(u->mixer_path_set); + u->mixer_path_set = NULL; + } else if (u->mixer_path) { + pa_alsa_path_free(u->mixer_path); + u->mixer_path = NULL; + } + + if (u->mixer_handle) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } +} + +static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) { pa_assert(u); if (!u->mixer_handle) return 0; - pa_assert(u->mixer_elem); + if (u->sink->active_port) { + pa_alsa_port_data *data; - if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { - pa_bool_t suitable = FALSE; + /* We have a list of supported paths, so let's activate the + * one that has been chosen as active */ - if (snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) - pa_log_info("Failed to get volume range. Falling back to software volume control."); - else if (u->hw_volume_min >= u->hw_volume_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); - else { - pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); - suitable = TRUE; - } + data = PA_DEVICE_PORT_DATA(u->sink->active_port); + u->mixer_path = data->path; - if (suitable) { - if (ignore_dB || snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) - pa_log_info("Mixer doesn't support dB information or data is ignored."); - else { -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif + pa_alsa_path_select(data->path, u->mixer_handle); - if (u->hw_dB_min >= u->hw_dB_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - else { - pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - u->hw_dB_supported = TRUE; - - if (u->hw_dB_max > 0) { - u->sink->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); - pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); - } else - pa_log_info("No particular base volume set, fixing to 0 dB"); - } - } + if (data->setting) + pa_alsa_setting_select(data->setting, u->mixer_handle); - if (!u->hw_dB_supported && - u->hw_volume_max - u->hw_volume_min < 3) { + } else { - pa_log_info("Device doesn't do dB volume and has less than 4 volume levels. Falling back to software volume control."); - suitable = FALSE; - } - } + if (!u->mixer_path && u->mixer_path_set) + u->mixer_path = u->mixer_path_set->paths; - if (suitable) { - u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &u->sink->channel_map, u->mixer_map, TRUE) >= 0; + if (u->mixer_path) { + /* Hmm, we have only a single path, then let's activate it */ - u->sink->get_volume = sink_get_volume_cb; - u->sink->set_volume = sink_set_volume_cb; - u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); - pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + pa_alsa_path_select(u->mixer_path, u->mixer_handle); - if (!u->hw_dB_supported) - u->sink->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1; + if (u->mixer_path->settings) + pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle); } else - pa_log_info("Using software volume control."); + return 0; } - if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { + if (!u->mixer_path->has_volume) + pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); + else { + + if (u->mixer_path->has_dB) { + pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB); + + u->sink->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); + u->sink->n_volume_steps = PA_VOLUME_NORM+1; + + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); + + } else { + pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume); + u->sink->base_volume = PA_VOLUME_NORM; + u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; + } + + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->write_volume = sink_write_volume_cb; + + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + if (u->mixer_path->has_dB) { + u->sink->flags |= PA_SINK_DECIBEL_VOLUME; + if (sync_volume) { + u->sink->flags |= PA_SINK_SYNC_VOLUME; + pa_log_info("Successfully enabled synchronous volume."); + } + } + + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); + } + + if (!u->mixer_path->has_mute) { + pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); + } else { u->sink->get_mute = sink_get_mute_cb; u->sink->set_mute = sink_set_mute_cb; u->sink->flags |= PA_SINK_HW_MUTE_CTRL; - } else - pa_log_info("Using software mute control."); + pa_log_info("Using hardware mute control."); + } - u->mixer_fdl = pa_alsa_fdlist_new(); + if (u->sink->flags & (PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL)) { + int (*mixer_callback)(snd_mixer_elem_t *, unsigned int); + if (u->sink->flags & PA_SINK_SYNC_VOLUME) { + u->mixer_pd = pa_alsa_mixer_pdata_new(); + mixer_callback = io_mixer_callback; - if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) { - pa_log("Failed to initialize file descriptor monitoring"); - return -1; - } + if (pa_alsa_set_mixer_rtpoll(u->mixer_pd, u->mixer_handle, u->rtpoll) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + return -1; + } + } else { + u->mixer_fdl = pa_alsa_fdlist_new(); + mixer_callback = ctl_mixer_callback; + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + return -1; + } + } - snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); - snd_mixer_elem_set_callback_private(u->mixer_elem, u); + if (u->mixer_path_set) + pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); + else + pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u); + } return 0; } -pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile) { +pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) { struct userdata *u = NULL; const char *dev_id = NULL; pa_sample_spec ss, requested_ss; pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; + uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark, rewind_safeguard; + snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames; size_t frame_size; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE; pa_sink_new_data data; - char *control_device = NULL; + pa_alsa_profile_set *profile_set = NULL; pa_assert(m); pa_assert(ma); @@ -1538,8 +1872,10 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - hwbuf_size = frag_size * nfrags; + buffer_size = nfrags * frag_size; + period_frames = frag_size/frame_size; + buffer_frames = buffer_size/frame_size; tsched_frames = tsched_size/frame_size; if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { @@ -1557,64 +1893,81 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; + rewind_safeguard = PA_MAX(DEFAULT_REWIND_SAFEGUARD_BYTES, pa_usec_to_bytes(DEFAULT_REWIND_SAFEGUARD_USEC, &ss)); + if (pa_modargs_get_value_u32(ma, "rewind_safeguard", &rewind_safeguard) < 0) { + pa_log("Failed to parse rewind_safeguard argument"); + goto fail; } + sync_volume = m->core->sync_volume; + if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) { + pa_log("Failed to parse sync_volume argument."); + goto fail; + } + + use_tsched = pa_alsa_may_tsched(use_tsched); + u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->use_mmap = use_mmap; u->use_tsched = use_tsched; u->first = TRUE; + u->rewind_safeguard = rewind_safeguard; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->smoother = pa_smoother_new( - DEFAULT_TSCHED_BUFFER_USEC*2, - DEFAULT_TSCHED_BUFFER_USEC*2, + SMOOTHER_ADJUST_USEC, + SMOOTHER_WINDOW_USEC, TRUE, TRUE, 5, - pa_rtclock_usec(), + pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; + + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; d = use_tsched; - if (profile) { + if (mapping) { if (!(dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { pa_log("device_id= not set"); goto fail; } - if (!(u->pcm_handle = pa_alsa_open_by_device_id_profile( + if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, - &b, &d, profile))) - + &period_frames, &buffer_frames, tsched_frames, + &b, &d, mapping))) goto fail; } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + if (!(profile_set = pa_alsa_profile_set_new(NULL, &map))) + goto fail; + if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto( dev_id, &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, - &b, &d, &profile))) - + &period_frames, &buffer_frames, tsched_frames, + &b, &d, profile_set, &mapping))) goto fail; } else { @@ -1624,10 +1977,9 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, FALSE))) goto fail; - } pa_assert(u->device_name); @@ -1638,8 +1990,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } - if (profile) - pa_log_info("Selected configuration '%s' (%s).", profile->description, profile->name); + if (mapping) + pa_log_info("Selected mapping '%s' (%s).", mapping->description, mapping->name); if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); @@ -1651,11 +2003,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca u->use_tsched = use_tsched = FALSE; } - if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) { - pa_log_info("Device is not a hardware device, disabling timer-based scheduling."); - u->use_tsched = use_tsched = FALSE; - } - if (u->use_mmap) pa_log_info("Successfully enabled mmap() mode."); @@ -1665,33 +2012,44 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_sink_new_data_init(&data); data.driver = driver; data.module = m; data.card = card; - set_sink_name(&data, ma, dev_id, u->device_name); + set_sink_name(&data, ma, dev_id, u->device_name, mapping); + + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse boolean argument namereg_fail."); + pa_sink_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); - pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle, u->mixer_elem); + pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size)); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); - if (profile) { - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, profile->name); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, profile->description); + if (mapping) { + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description); } pa_alsa_init_description(data.proplist); - if (control_device) { - pa_alsa_init_proplist_ctl(data.proplist, control_device); - pa_xfree(control_device); - } + if (u->control_device) + pa_alsa_init_proplist_ctl(data.proplist, u->control_device); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log("Invalid properties"); @@ -1699,6 +2057,9 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } + if (u->mixer_path_set) + pa_alsa_add_ports(&data.ports, u->mixer_path_set); + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY|(u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0)); pa_sink_new_data_done(&data); @@ -1707,30 +2068,56 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } + if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin", + &u->sink->thread_info.volume_change_safety_margin) < 0) { + pa_log("Failed to parse sync_volume_safety_margin parameter"); + goto fail; + } + + if (pa_modargs_get_value_s32(ma, "sync_volume_extra_delay", + &u->sink->thread_info.volume_change_extra_delay) < 0) { + pa_log("Failed to parse sync_volume_extra_delay parameter"); + goto fail; + } + u->sink->parent.process_msg = sink_process_msg; - u->sink->update_requested_latency = sink_update_requested_latency_cb; + if (u->use_tsched) + u->sink->update_requested_latency = sink_update_requested_latency_cb; u->sink->set_state = sink_set_state_cb; + u->sink->set_port = sink_set_port_cb; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec); + u->fragment_size = frag_size = (size_t) (period_frames * frame_size); + u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size); pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, + pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)", + (double) u->hwbuf_size / (double) u->fragment_size, + (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC, + (long unsigned) u->hwbuf_size, (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); pa_sink_set_max_request(u->sink, u->hwbuf_size); - pa_sink_set_max_rewind(u->sink, u->hwbuf_size); + if (pa_alsa_pcm_is_hw(u->pcm_handle)) + pa_sink_set_max_rewind(u->sink, u->hwbuf_size); + else { + pa_log_info("Disabling rewind for device %s", u->device_name); + pa_sink_set_max_rewind(u->sink, 0); + } if (u->use_tsched) { - u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->sink->sample_spec); + u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec); + + u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec); + u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec); + + u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec); + u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec); fix_min_sleep_wakeup(u); fix_tsched_watermark(u); @@ -1749,12 +2136,12 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca if (update_sw_params(u) < 0) goto fail; - if (setup_mixer(u, ignore_dB) < 0) + if (setup_mixer(u, ignore_dB, sync_volume) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("alsa-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -1778,6 +2165,9 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca pa_sink_put(u->sink); + if (profile_set) + pa_alsa_profile_set_free(profile_set); + return u->sink; fail: @@ -1785,6 +2175,9 @@ fail: if (u) userdata_free(u); + if (profile_set) + pa_alsa_profile_set_free(profile_set); + return NULL; } @@ -1807,29 +2200,39 @@ static void userdata_free(struct userdata *u) { if (u->memchunk.memblock) pa_memblock_unref(u->memchunk.memblock); + if (u->mixer_pd) + pa_alsa_mixer_pdata_free(u->mixer_pd); + if (u->alsa_rtpoll_item) pa_rtpoll_item_free(u->alsa_rtpoll_item); if (u->rtpoll) pa_rtpoll_free(u->rtpoll); + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + if (u->mixer_fdl) pa_alsa_fdlist_free(u->mixer_fdl); + if (u->mixer_path_set) + pa_alsa_path_set_free(u->mixer_path_set); + else if (u->mixer_path) + pa_alsa_path_free(u->mixer_path); + if (u->mixer_handle) snd_mixer_close(u->mixer_handle); - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - } - if (u->smoother) pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); + pa_xfree(u->control_device); pa_xfree(u); } diff --git a/src/modules/alsa/alsa-sink.h b/src/modules/alsa/alsa-sink.h index bbf64234..e640b624 100644 --- a/src/modules/alsa/alsa-sink.h +++ b/src/modules/alsa/alsa-sink.h @@ -29,7 +29,7 @@ #include "alsa-util.h" -pa_sink* pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile); +pa_sink* pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping); void pa_alsa_sink_free(pa_sink *s); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index f1c18196..f847b1ee 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -28,31 +28,26 @@ #include <asoundlib.h> -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include <valgrind/memcheck.h> -#endif - -#include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulse/timeval.h> #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> -#include <pulsecore/core-error.h> #include <pulsecore/core.h> #include <pulsecore/module.h> #include <pulsecore/memchunk.h> #include <pulsecore/sink.h> #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h> #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> #include <pulsecore/thread.h> -#include <pulsecore/core-error.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h> #include <modules/reserve-wrap.h> @@ -62,11 +57,27 @@ /* #define DEBUG_TIMING */ #define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ -#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ -#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ -#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */ + +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ + +#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ +#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */ +#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */ +#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms */ +#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */ +#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ + +#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */ +#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */ + +#define SMOOTHER_WINDOW_USEC (10*PA_USEC_PER_SEC) /* 10s */ +#define SMOOTHER_ADJUST_USEC (1*PA_USEC_PER_SEC) /* 1s */ + +#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */ +#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */ + +#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) struct userdata { pa_core *core; @@ -80,12 +91,10 @@ struct userdata { snd_pcm_t *pcm_handle; pa_alsa_fdlist *mixer_fdl; + pa_alsa_mixer_pdata *mixer_pd; snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - long hw_volume_max, hw_volume_min; - long hw_dB_max, hw_dB_min; - pa_bool_t hw_dB_supported:1; - pa_bool_t mixer_seperate_channels:1; + pa_alsa_path_set *mixer_path_set; + pa_alsa_path *mixer_path; pa_cvolume hardware_volume; @@ -97,23 +106,33 @@ struct userdata { hwbuf_unused, min_sleep, min_wakeup, - watermark_step; + watermark_inc_step, + watermark_dec_step, + watermark_inc_threshold, + watermark_dec_threshold; - unsigned nfragments; + pa_usec_t watermark_dec_not_before; - char *device_name; + char *device_name; /* name of the PCM device */ + char *control_device; /* name of the control device */ pa_bool_t use_mmap:1, use_tsched:1; + pa_bool_t first; + pa_rtpoll_item *alsa_rtpoll_item; snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; pa_smoother *smoother; uint64_t read_count; + pa_usec_t smoother_interval; + pa_usec_t last_smoother_update; pa_reserve_wrapper *reserve; pa_hook_slot *reserve_slot; + pa_reserve_monitor_wrapper *monitor; + pa_hook_slot *monitor_slot; }; static void userdata_free(struct userdata *u); @@ -122,7 +141,7 @@ static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct u pa_assert(r); pa_assert(u); - if (pa_source_suspend(u->source, TRUE) < 0) + if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0) return PA_HOOK_CANCEL; return PA_HOOK_OK; @@ -165,10 +184,10 @@ static int reserve_init(struct userdata *u, const char *dname) { if (pa_in_system_mode()) return 0; - /* We are resuming, try to lock the device */ if (!(rname = pa_alsa_get_reserve_name(dname))) return 0; + /* We are resuming, try to lock the device */ u->reserve = pa_reserve_wrapper_get(u->core, rname); pa_xfree(rname); @@ -183,9 +202,62 @@ static int reserve_init(struct userdata *u, const char *dname) { return 0; } +static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) { + pa_bool_t b; + + pa_assert(w); + pa_assert(u); + + b = PA_PTR_TO_UINT(busy) && !u->reserve; + + pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION); + return PA_HOOK_OK; +} + +static void monitor_done(struct userdata *u) { + pa_assert(u); + + if (u->monitor_slot) { + pa_hook_slot_free(u->monitor_slot); + u->monitor_slot = NULL; + } + + if (u->monitor) { + pa_reserve_monitor_wrapper_unref(u->monitor); + u->monitor = NULL; + } +} + +static int reserve_monitor_init(struct userdata *u, const char *dname) { + char *rname; + + pa_assert(u); + pa_assert(dname); + + if (pa_in_system_mode()) + return 0; + + if (!(rname = pa_alsa_get_reserve_name(dname))) + return 0; + + /* We are resuming, try to lock the device */ + u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); + pa_xfree(rname); + + if (!(u->monitor)) + return -1; + + pa_assert(!u->monitor_slot); + u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u); + + return 0; +} + static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; + pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec); @@ -200,6 +272,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) { static void fix_tsched_watermark(struct userdata *u) { size_t max_use; pa_assert(u); + pa_assert(u->use_tsched); max_use = u->hwbuf_size - u->hwbuf_unused; @@ -210,7 +283,7 @@ static void fix_tsched_watermark(struct userdata *u) { u->tsched_watermark = u->min_wakeup; } -static void adjust_after_overrun(struct userdata *u) { +static void increase_watermark(struct userdata *u) { size_t old_watermark; pa_usec_t old_min_latency, new_min_latency; @@ -219,36 +292,72 @@ static void adjust_after_overrun(struct userdata *u) { /* First, just try to increase the watermark */ old_watermark = u->tsched_watermark; - u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step); - + u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step); fix_tsched_watermark(u); if (old_watermark != u->tsched_watermark) { - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + pa_log_info("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); return; } /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */ old_min_latency = u->source->thread_info.min_latency; - new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC); + new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC); new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency); if (old_min_latency != new_min_latency) { - pa_log_notice("Increasing minimal latency to %0.2f ms", - (double) new_min_latency / PA_USEC_PER_MSEC); + pa_log_info("Increasing minimal latency to %0.2f ms", + (double) new_min_latency / PA_USEC_PER_MSEC); pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency); - return; } /* When we reach this we're officialy fucked! */ } -static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { +static void decrease_watermark(struct userdata *u) { + size_t old_watermark; + pa_usec_t now; + + pa_assert(u); + pa_assert(u->use_tsched); + + now = pa_rtclock_now(); + + if (u->watermark_dec_not_before <= 0) + goto restart; + + if (u->watermark_dec_not_before > now) + return; + + old_watermark = u->tsched_watermark; + + if (u->tsched_watermark < u->watermark_dec_step) + u->tsched_watermark = u->tsched_watermark / 2; + else + u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step); + + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_info("Decreasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + + /* We don't change the latency range*/ + +restart: + u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC; +} + +static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { pa_usec_t wm, usec; + pa_assert(sleep_usec); + pa_assert(process_usec); + pa_assert(u); + pa_assert(u->use_tsched); usec = pa_source_get_requested_latency_within_thread(u->source); @@ -269,8 +378,6 @@ static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_use (unsigned long) (*sleep_usec / PA_USEC_PER_MSEC), (unsigned long) (*process_usec / PA_USEC_PER_MSEC)); #endif - - return usec; } static int try_recover(struct userdata *u, const char *call, int err) { @@ -285,49 +392,72 @@ static int try_recover(struct userdata *u, const char *call, int err) { if (err == -EPIPE) pa_log_debug("%s: Buffer overrun!", call); + if (err == -ESTRPIPE) + pa_log_debug("%s: System suspended!", call); + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) { pa_log("%s: %s", call, pa_alsa_strerror(err)); return -1; } - snd_pcm_start(u->pcm_handle); + u->first = TRUE; return 0; } -static size_t check_left_to_record(struct userdata *u, size_t n_bytes) { +static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) { size_t left_to_record; size_t rec_space = u->hwbuf_size - u->hwbuf_unused; + pa_bool_t overrun = FALSE; /* We use <= instead of < for this check here because an overrun * only happens after the last sample was processed, not already when * it is removed from the buffer. This is particularly important * when block transfer is used. */ - if (n_bytes <= rec_space) { + if (n_bytes <= rec_space) left_to_record = rec_space - n_bytes; + else { -#ifdef DEBUG_TIMING - pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); -#endif - - } else { + /* We got a dropout. What a mess! */ left_to_record = 0; + overrun = TRUE; #ifdef DEBUG_TIMING PA_DEBUG_TRAP; #endif - if (pa_log_ratelimit()) + if (pa_log_ratelimit(PA_LOG_INFO)) pa_log_info("Overrun!"); + } - if (u->use_tsched) - adjust_after_overrun(u); +#ifdef DEBUG_TIMING + pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); +#endif + + if (u->use_tsched) { + pa_bool_t reset_not_before = TRUE; + + if (overrun || left_to_record < u->watermark_inc_threshold) + increase_watermark(u); + else if (left_to_record > u->watermark_dec_threshold) { + reset_not_before = FALSE; + + /* We decrease the watermark only if have actually + * been woken up by a timeout. If something else woke + * us up it's too easy to fulfill the deadlines... */ + + if (on_timeout) + decrease_watermark(u); + } + + if (reset_not_before) + u->watermark_dec_not_before = 0; } return left_to_record; } -static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { pa_bool_t work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; @@ -343,6 +473,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled snd_pcm_sframes_t n; size_t n_bytes; int r; + pa_bool_t after_avail = TRUE; if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { @@ -358,7 +489,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled pa_log_debug("avail: %lu", (unsigned long) n_bytes); #endif - left_to_record = check_left_to_record(u, n_bytes); + left_to_record = check_left_to_record(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) if (!polled && @@ -387,6 +519,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled break; } + if (++j > 10) { #ifdef DEBUG_TIMING pa_log_debug("Not filling up, because already too many iterations."); @@ -402,19 +535,21 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled #endif for (;;) { + pa_memchunk chunk; + void *p; int err; const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset, frames; - pa_memchunk chunk; - void *p; snd_pcm_sframes_t sframes; frames = (snd_pcm_uframes_t) (n_bytes / u->frame_size); - /* pa_log_debug("%lu frames to read", (unsigned long) frames); */ if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) { + if (!after_avail && err == -EAGAIN) + break; + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) continue; @@ -425,6 +560,12 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size) frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size; + if (!after_avail && frames == 0) + break; + + pa_assert(frames > 0); + after_avail = FALSE; + /* Check these are multiples of 8 bit */ pa_assert((areas[0].first & 7) == 0); pa_assert((areas[0].step & 7)== 0); @@ -465,17 +606,19 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled } } - *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); + if (u->use_tsched) { + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); - if (*sleep_usec > process_usec) - *sleep_usec -= process_usec; - else - *sleep_usec = 0; + if (*sleep_usec > process_usec) + *sleep_usec -= process_usec; + else + *sleep_usec = 0; + } return work_done ? 1 : 0; } -static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) { int work_done = FALSE; pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; @@ -491,6 +634,7 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled snd_pcm_sframes_t n; size_t n_bytes; int r; + pa_bool_t after_avail = TRUE; if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { @@ -501,7 +645,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled } n_bytes = (size_t) n * u->frame_size; - left_to_record = check_left_to_record(u, n_bytes); + left_to_record = check_left_to_record(u, n_bytes, on_timeout); + on_timeout = FALSE; if (u->use_tsched) if (!polled && @@ -551,17 +696,26 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames); pa_memblock_release(chunk.memblock); - pa_assert(frames != 0); - if (PA_UNLIKELY(frames < 0)) { pa_memblock_unref(chunk.memblock); - if ((r = try_recover(u, "snd_pcm_readi", (int) (frames))) == 0) + if (!after_avail && (int) frames == -EAGAIN) + break; + + if ((r = try_recover(u, "snd_pcm_readi", (int) frames)) == 0) continue; return r; } + if (!after_avail && frames == 0) { + pa_memblock_unref(chunk.memblock); + break; + } + + pa_assert(frames > 0); + after_avail = FALSE; + chunk.index = 0; chunk.length = (size_t) frames * u->frame_size; @@ -581,12 +735,14 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled } } - *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); + if (u->use_tsched) { + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec); - if (*sleep_usec > process_usec) - *sleep_usec -= process_usec; - else - *sleep_usec = 0; + if (*sleep_usec > process_usec) + *sleep_usec -= process_usec; + else + *sleep_usec = 0; + } return work_done ? 1 : 0; } @@ -605,7 +761,7 @@ static void update_smoother(struct userdata *u) { /* Let's update the time smoother */ - if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->source->sample_spec)) < 0)) { + if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->source->sample_spec, TRUE)) < 0)) { pa_log_warn("Failed to get delay: %s", pa_alsa_strerror(err)); return; } @@ -618,24 +774,32 @@ static void update_smoother(struct userdata *u) { now1 = pa_timespec_load(&htstamp); } - position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size); - /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */ if (now1 <= 0) - now1 = pa_rtclock_usec(); + now1 = pa_rtclock_now(); + /* check if the time since the last update is bigger than the interval */ + if (u->last_smoother_update > 0) + if (u->last_smoother_update + u->smoother_interval > now1) + return; + + position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size); now2 = pa_bytes_to_usec(position, &u->source->sample_spec); pa_smoother_put(u->smoother, now1, now2); + + u->last_smoother_update = now1; + /* exponentially increase the update interval up to the MAX limit */ + u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL); } static pa_usec_t source_get_latency(struct userdata *u) { - int64_t delay; + int64_t delay; pa_usec_t now1, now2; pa_assert(u); - now1 = pa_rtclock_usec(); + now1 = pa_rtclock_now(); now2 = pa_smoother_get(u->smoother, now1); delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec); @@ -656,11 +820,12 @@ static int build_pollfd(struct userdata *u) { return 0; } +/* Called from IO context */ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->pcm_handle); - pa_smoother_pause(u->smoother, pa_rtclock_usec()); + pa_smoother_pause(u->smoother, pa_rtclock_now()); /* Let's suspend */ snd_pcm_close(u->pcm_handle); @@ -676,6 +841,7 @@ static int suspend(struct userdata *u) { return 0; } +/* Called from IO context */ static int update_sw_params(struct userdata *u) { snd_pcm_uframes_t avail_min; int err; @@ -720,7 +886,7 @@ static int update_sw_params(struct userdata *u) { pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) { pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err)); return err; } @@ -728,22 +894,20 @@ static int update_sw_params(struct userdata *u) { return 0; } +/* Called from IO context */ static int unsuspend(struct userdata *u) { pa_sample_spec ss; int err; pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; + snd_pcm_uframes_t period_size, buffer_size; pa_assert(u); pa_assert(!u->pcm_handle); pa_log_info("Trying resume..."); - snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, - /*SND_PCM_NONBLOCK|*/ + SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| SND_PCM_NO_AUTO_FORMAT)) < 0) { @@ -752,12 +916,12 @@ static int unsuspend(struct userdata *u) { } ss = u->source->sample_spec; - nfrags = u->nfragments; period_size = u->fragment_size / u->frame_size; + buffer_size = u->hwbuf_size / u->frame_size; b = u->use_mmap; d = u->use_tsched; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) { pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err)); goto fail; } @@ -772,10 +936,11 @@ static int unsuspend(struct userdata *u) { goto fail; } - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); + if (period_size*u->frame_size != u->fragment_size || + buffer_size*u->frame_size != u->hwbuf_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)", + (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size, + (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size)); goto fail; } @@ -787,8 +952,12 @@ static int unsuspend(struct userdata *u) { /* FIXME: We need to reload the volume somehow */ - snd_pcm_start(u->pcm_handle); - pa_smoother_resume(u->smoother, pa_rtclock_usec(), TRUE); + u->read_count = 0; + pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; + u->last_smoother_update = 0; + + u->first = TRUE; pa_log_info("Resumed successfully..."); @@ -800,9 +969,10 @@ fail: u->pcm_handle = NULL; } - return -1; + return -PA_ERR_IO; } +/* Called from IO context */ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SOURCE(o)->userdata; @@ -823,30 +993,33 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { - case PA_SOURCE_SUSPENDED: + case PA_SOURCE_SUSPENDED: { + int r; + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); - if (suspend(u) < 0) - return -1; + if ((r = suspend(u)) < 0) + return r; break; + } case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: + case PA_SOURCE_RUNNING: { + int r; if (u->source->thread_info.state == PA_SOURCE_INIT) { if (build_pollfd(u) < 0) - return -1; - - snd_pcm_start(u->pcm_handle); + return -PA_ERR_IO; } if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; + if ((r = unsuspend(u)) < 0) + return r; } break; + } case PA_SOURCE_UNLINKED: case PA_SOURCE_INIT: @@ -870,16 +1043,16 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) { old_state = pa_source_get_state(u->source); - if (PA_SINK_IS_OPENED(old_state) && new_state == PA_SINK_SUSPENDED) + if (PA_SOURCE_IS_OPENED(old_state) && new_state == PA_SOURCE_SUSPENDED) reserve_done(u); - else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state)) + else if (old_state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(new_state)) if (reserve_init(u, u->device_name) < 0) - return -1; + return -PA_ERR_BUSY; return 0; } -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { +static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { struct userdata *u = snd_mixer_elem_get_callback_private(elem); pa_assert(u); @@ -888,6 +1061,9 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; + if (u->source->suspend_cause & PA_SUSPEND_SESSION) + return 0; + if (mask & SND_CTL_EVENT_MASK_VALUE) { pa_source_get_volume(u->source, TRUE); pa_source_get_mute(u->source, TRUE); @@ -896,244 +1072,221 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { return 0; } -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { +static int io_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); - return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / - (double) (u->hw_volume_max - u->hw_volume_min)); -} + pa_assert(u); + pa_assert(u->mixer_handle); -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { - long alsa_vol; + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; - alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) - / PA_VOLUME_NORM) + u->hw_volume_min; + if (u->source->suspend_cause & PA_SUSPEND_SESSION) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) + pa_source_update_volume_and_mute(u->source); - return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); + return 0; } static void source_get_volume_cb(pa_source *s) { struct userdata *u = s->userdata; - int err; - unsigned i; pa_cvolume r; - char t[PA_CVOLUME_SNPRINT_MAX]; + char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX]; pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - } else { - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif + if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) + return; - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume); - } else { + pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r)); - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + if (u->mixer_path->has_dB) { + char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX]; - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &r)); } - pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - if (!pa_cvolume_equal(&u->hardware_volume, &r)) { - - s->virtual_volume = u->hardware_volume = r; - - if (u->hw_dB_supported) { - pa_cvolume reset; - - /* Hmm, so the hardware volume changed, let's reset our software volume */ - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_source_set_soft_volume(s, &reset); - } - } + if (pa_cvolume_equal(&u->hardware_volume, &r)) + return; - return; + s->real_volume = u->hardware_volume = r; -fail: - pa_log_error("Unable to read volume: %s", pa_alsa_strerror(err)); + /* Hmm, so the hardware volume changed, let's reset our software volume */ + if (u->mixer_path->has_dB) + pa_source_set_soft_volume(s, NULL); } static void source_set_volume_cb(pa_source *s) { struct userdata *u = s->userdata; - int err; - unsigned i; pa_cvolume r; + char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX]; + pa_bool_t sync_volume = !!(s->flags & PA_SOURCE_SYNC_VOLUME); pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - pa_volume_t vol; - - vol = s->virtual_volume.values[i]; - - if (u->hw_dB_supported) { - - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, sync_volume, !sync_volume) < 0) + return; - } else { - alsa_vol = to_alsa_volume(u, vol); + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume); - if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) - goto fail; + u->hardware_volume = r; - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; + if (u->mixer_path->has_dB) { + pa_cvolume new_soft_volume; + pa_bool_t accurate_enough; + char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX]; - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } + /* Match exactly what the user requested by software */ + pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume); + + /* If the adjustment to do in software is only minimal we + * can skip it. That saves us CPU at the expense of a bit of + * accuracy */ + accurate_enough = + (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->real_volume)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->real_volume)); + pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &u->hardware_volume)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &u->hardware_volume)); + pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", + pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &new_soft_volume), + pa_yes_no(accurate_enough)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &new_soft_volume)); + + if (!accurate_enough) + s->soft_volume = new_soft_volume; } else { - pa_volume_t vol; - long alsa_vol; - - vol = pa_cvolume_max(&s->virtual_volume); - - if (u->hw_dB_supported) { - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r)); - if ((err = snd_mixer_selem_set_capture_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + s->real_volume = r; + } +} - } else { - alsa_vol = to_alsa_volume(u, vol); +static void source_write_volume_cb(pa_source *s) { + struct userdata *u = s->userdata; + pa_cvolume hw_vol = s->thread_info.current_hw_volume; - if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) - goto fail; + pa_assert(u); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); + pa_assert(s->flags & PA_SOURCE_SYNC_VOLUME); - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume); - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &hw_vol, TRUE, TRUE) < 0) + pa_log_error("Writing HW volume failed"); + else { + pa_cvolume tmp_vol; + pa_bool_t accurate_enough; + + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&hw_vol, &hw_vol, s->base_volume); + + pa_sw_cvolume_divide(&tmp_vol, &hw_vol, &s->thread_info.current_hw_volume); + accurate_enough = + (pa_cvolume_min(&tmp_vol) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && + (pa_cvolume_max(&tmp_vol) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); + + if (!accurate_enough) { + union { + char db[2][PA_SW_CVOLUME_SNPRINT_DB_MAX]; + char pcnt[2][PA_CVOLUME_SNPRINT_MAX]; + } vol; + + pa_log_debug("Written HW volume did not match with the request: %s (request) != %s", + pa_cvolume_snprint(vol.pcnt[0], sizeof(vol.pcnt[0]), &s->thread_info.current_hw_volume), + pa_cvolume_snprint(vol.pcnt[1], sizeof(vol.pcnt[1]), &hw_vol)); + pa_log_debug(" in dB: %s (request) != %s", + pa_sw_cvolume_snprint_dB(vol.db[0], sizeof(vol.db[0]), &s->thread_info.current_hw_volume), + pa_sw_cvolume_snprint_dB(vol.db[1], sizeof(vol.db[1]), &hw_vol)); } } - - u->hardware_volume = r; - - if (u->hw_dB_supported) { - char t[PA_CVOLUME_SNPRINT_MAX]; - - /* Match exactly what the user requested by software */ - - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); - - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); - pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); - - } else - - /* We can't match exactly what the user requested, hence let's - * at least tell the user about it */ - - s->virtual_volume = r; - - return; - -fail: - pa_log_error("Unable to set volume: %s", pa_alsa_strerror(err)); } static void source_get_mute_cb(pa_source *s) { struct userdata *u = s->userdata; - int err, sw; + pa_bool_t b; pa_assert(u); - pa_assert(u->mixer_elem); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); - if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { - pa_log_error("Unable to get switch: %s", pa_alsa_strerror(err)); + if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0) return; - } - s->muted = !sw; + s->muted = b; } static void source_set_mute_cb(pa_source *s) { struct userdata *u = s->userdata; - int err; pa_assert(u); - pa_assert(u->mixer_elem); + pa_assert(u->mixer_path); + pa_assert(u->mixer_handle); - if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { - pa_log_error("Unable to set switch: %s", pa_alsa_strerror(err)); - return; + pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); +} + +static int source_set_port_cb(pa_source *s, pa_device_port *p) { + struct userdata *u = s->userdata; + pa_alsa_port_data *data; + + pa_assert(u); + pa_assert(p); + pa_assert(u->mixer_handle); + + data = PA_DEVICE_PORT_DATA(p); + + pa_assert_se(u->mixer_path = data->path); + pa_alsa_path_select(u->mixer_path, u->mixer_handle); + + if (u->mixer_path->has_volume && u->mixer_path->has_dB) { + s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); + s->n_volume_steps = PA_VOLUME_NORM+1; + + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); + } else { + s->base_volume = PA_VOLUME_NORM; + s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; } + + if (data->setting) + pa_alsa_setting_select(data->setting, u->mixer_handle); + + if (s->set_mute) + s->set_mute(s); + if (s->set_volume) + s->set_volume(s); + + return 0; } static void source_update_requested_latency_cb(pa_source *s) { struct userdata *u = s->userdata; pa_assert(u); + pa_assert(u->use_tsched); /* only when timer scheduling is used + * we can dynamically adjust the + * latency */ if (!u->pcm_handle) return; @@ -1153,10 +1306,10 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; + pa_usec_t rtpoll_sleep = 0; #ifdef DEBUG_TIMING pa_log_debug("Loop"); @@ -1166,11 +1319,21 @@ static void thread_func(void *userdata) { if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { int work_done; pa_usec_t sleep_usec = 0; + pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll); + + if (u->first) { + pa_log_info("Starting capture."); + snd_pcm_start(u->pcm_handle); + + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); + + u->first = FALSE; + } if (u->use_mmap) - work_done = mmap_read(u, &sleep_usec, revents & POLLIN); + work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout); else - work_done = unix_read(u, &sleep_usec, revents & POLLIN); + work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout); if (work_done < 0) goto fail; @@ -1190,22 +1353,34 @@ static void thread_func(void *userdata) { /* Convert from the sound card time domain to the * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec); /* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ /* We don't trust the conversion, so we wake up whatever comes first */ - pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + rtpoll_sleep = PA_MIN(sleep_usec, cusec); } - } else if (u->use_tsched) + } - /* OK, we're in an invalid state, let's disable our timers */ + if (u->source->flags & PA_SOURCE_SYNC_VOLUME) { + pa_usec_t volume_sleep; + pa_source_volume_change_apply(u->source, &volume_sleep); + if (volume_sleep > 0) + rtpoll_sleep = PA_MIN(volume_sleep, rtpoll_sleep); + } + + if (rtpoll_sleep > 0) + pa_rtpoll_set_timer_relative(u->rtpoll, rtpoll_sleep); + else pa_rtpoll_set_timer_disabled(u->rtpoll); /* Hmm, nothing to do. Let's sleep */ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; + if (u->source->flags & PA_SOURCE_SYNC_VOLUME) + pa_source_volume_change_apply(u->source, NULL); + if (ret == 0) goto finish; @@ -1226,8 +1401,8 @@ static void thread_func(void *userdata) { if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) goto fail; - snd_pcm_start(u->pcm_handle); - } else if (revents && u->use_tsched && pa_log_ratelimit()) + u->first = TRUE; + } else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG)) pa_log_debug("Wakeup from ALSA!"); } else @@ -1244,7 +1419,7 @@ finish: pa_log_debug("Thread shutting down"); } -static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name) { +static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) { const char *n; char *t; @@ -1265,108 +1440,184 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char data->namereg_fail = FALSE; } - t = pa_sprintf_malloc("alsa_input.%s", n); + if (mapping) + t = pa_sprintf_malloc("alsa_input.%s.%s", n, mapping->name); + else + t = pa_sprintf_malloc("alsa_input.%s", n); + pa_source_new_data_set_name(data, t); pa_xfree(t); } -static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { +static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) { + + if (!mapping && !element) + return; + + if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { + pa_log_info("Failed to find a working mixer device."); + return; + } + + if (element) { + + if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT))) + goto fail; + + if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0) + goto fail; + + pa_log_debug("Probed mixer path %s:", u->mixer_path->name); + pa_alsa_path_dump(u->mixer_path); + } else { + + if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_INPUT))) + goto fail; + + pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB); + + pa_log_debug("Probed mixer paths:"); + pa_alsa_path_set_dump(u->mixer_path_set); + } + + return; + +fail: + + if (u->mixer_path_set) { + pa_alsa_path_set_free(u->mixer_path_set); + u->mixer_path_set = NULL; + } else if (u->mixer_path) { + pa_alsa_path_free(u->mixer_path); + u->mixer_path = NULL; + } + + if (u->mixer_handle) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } +} + +static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) { pa_assert(u); if (!u->mixer_handle) return 0; - pa_assert(u->mixer_elem); + if (u->source->active_port) { + pa_alsa_port_data *data; - if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { - pa_bool_t suitable = FALSE; + /* We have a list of supported paths, so let's activate the + * one that has been chosen as active */ - if (snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) - pa_log_info("Failed to get volume range. Falling back to software volume control."); - else if (u->hw_volume_min >= u->hw_volume_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); - else { - pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); - suitable = TRUE; - } + data = PA_DEVICE_PORT_DATA(u->source->active_port); + u->mixer_path = data->path; - if (suitable) { - if (ignore_dB || snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) - pa_log_info("Mixer doesn't support dB information or data is ignored."); - else { -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif + pa_alsa_path_select(data->path, u->mixer_handle); - if (u->hw_dB_min >= u->hw_dB_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - else { - pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - u->hw_dB_supported = TRUE; - - if (u->hw_dB_max > 0) { - u->source->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); - pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); - } else - pa_log_info("No particular base volume set, fixing to 0 dB"); - } - } + if (data->setting) + pa_alsa_setting_select(data->setting, u->mixer_handle); - if (!u->hw_dB_supported && - u->hw_volume_max - u->hw_volume_min < 3) { + } else { - pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); - suitable = FALSE; - } - } + if (!u->mixer_path && u->mixer_path_set) + u->mixer_path = u->mixer_path_set->paths; - if (suitable) { - u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &u->source->channel_map, u->mixer_map, FALSE) >= 0; + if (u->mixer_path) { + /* Hmm, we have only a single path, then let's activate it */ - u->source->get_volume = source_get_volume_cb; - u->source->set_volume = source_set_volume_cb; - u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0); - pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + pa_alsa_path_select(u->mixer_path, u->mixer_handle); - if (!u->hw_dB_supported) - u->source->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1; + if (u->mixer_path->settings) + pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle); } else - pa_log_info("Using software volume control."); + return 0; } - if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { + if (!u->mixer_path->has_volume) + pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); + else { + + if (u->mixer_path->has_dB) { + pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB); + + u->source->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB); + u->source->n_volume_steps = PA_VOLUME_NORM+1; + + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); + + } else { + pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume); + u->source->base_volume = PA_VOLUME_NORM; + u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1; + } + + u->source->get_volume = source_get_volume_cb; + u->source->set_volume = source_set_volume_cb; + u->source->write_volume = source_write_volume_cb; + + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + if (u->mixer_path->has_dB) { + u->source->flags |= PA_SOURCE_DECIBEL_VOLUME; + if (sync_volume) { + u->source->flags |= PA_SOURCE_SYNC_VOLUME; + pa_log_info("Successfully enabled synchronous volume."); + } + } + + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); + } + + if (!u->mixer_path->has_mute) { + pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); + } else { u->source->get_mute = source_get_mute_cb; u->source->set_mute = source_set_mute_cb; u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; - } else - pa_log_info("Using software mute control."); + pa_log_info("Using hardware mute control."); + } - u->mixer_fdl = pa_alsa_fdlist_new(); + if (u->source->flags & (PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_HW_MUTE_CTRL)) { + int (*mixer_callback)(snd_mixer_elem_t *, unsigned int); + if (u->source->flags & PA_SOURCE_SYNC_VOLUME) { + u->mixer_pd = pa_alsa_mixer_pdata_new(); + mixer_callback = io_mixer_callback; - if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) { - pa_log("Failed to initialize file descriptor monitoring"); - return -1; - } + if (pa_alsa_set_mixer_rtpoll(u->mixer_pd, u->mixer_handle, u->rtpoll) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + return -1; + } + } else { + u->mixer_fdl = pa_alsa_fdlist_new(); + mixer_callback = ctl_mixer_callback; - snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); - snd_mixer_elem_set_callback_private(u->mixer_elem, u); + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + return -1; + } + } + + if (u->mixer_path_set) + pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); + else + pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u); + } return 0; } -pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile) { +pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) { struct userdata *u = NULL; const char *dev_id = NULL; pa_sample_spec ss, requested_ss; pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; + uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames; size_t frame_size; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE; pa_source_new_data data; - char *control_device = NULL; + pa_alsa_profile_set *profile_set = NULL; pa_assert(m); pa_assert(ma); @@ -1374,7 +1625,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p ss = m->core->default_sample_spec; map = m->core->default_channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("Failed to parse sample specification"); + pa_log("Failed to parse sample specification and channel map"); goto fail; } @@ -1396,8 +1647,10 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } - hwbuf_size = frag_size * nfrags; + buffer_size = nfrags * frag_size; + period_frames = frag_size/frame_size; + buffer_frames = buffer_size/frame_size; tsched_frames = tsched_size/frame_size; if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { @@ -1406,7 +1659,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p } if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { - pa_log("Failed to parse timer_scheduling argument."); + pa_log("Failed to parse tsched argument."); goto fail; } @@ -1415,62 +1668,74 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; + sync_volume = m->core->sync_volume; + if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) { + pa_log("Failed to parse sync_volume argument."); + goto fail; } + use_tsched = pa_alsa_may_tsched(use_tsched); + u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->use_mmap = use_mmap; u->use_tsched = use_tsched; + u->first = TRUE; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->alsa_rtpoll_item = NULL; u->smoother = pa_smoother_new( - DEFAULT_TSCHED_WATERMARK_USEC*2, - DEFAULT_TSCHED_WATERMARK_USEC*2, + SMOOTHER_ADJUST_USEC, + SMOOTHER_WINDOW_USEC, TRUE, TRUE, 5, - pa_rtclock_usec(), - FALSE); + pa_rtclock_now(), + TRUE); + u->smoother_interval = SMOOTHER_MIN_INTERVAL; + + dev_id = pa_modargs_get_value( + ma, "device_id", + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + + if (reserve_init(u, dev_id) < 0) + goto fail; - if (reserve_init(u, pa_modargs_get_value( - ma, "device_id", - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE))) < 0) + if (reserve_monitor_init(u, dev_id) < 0) goto fail; b = use_mmap; d = use_tsched; - if (profile) { + if (mapping) { if (!(dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { pa_log("device_id= not set"); goto fail; } - if (!(u->pcm_handle = pa_alsa_open_by_device_id_profile( + if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, - &b, &d, profile))) + &period_frames, &buffer_frames, tsched_frames, + &b, &d, mapping))) goto fail; } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + if (!(profile_set = pa_alsa_profile_set_new(NULL, &map))) + goto fail; + if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto( dev_id, &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, - &b, &d, &profile))) + &period_frames, &buffer_frames, tsched_frames, + &b, &d, profile_set, &mapping))) goto fail; } else { @@ -1480,7 +1745,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, + &period_frames, &buffer_frames, tsched_frames, &b, &d, FALSE))) goto fail; } @@ -1493,8 +1758,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } - if (profile) - pa_log_info("Selected configuration '%s' (%s).", profile->description, profile->name); + if (mapping) + pa_log_info("Selected mapping '%s' (%s).", mapping->description, mapping->name); if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); @@ -1506,11 +1771,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p u->use_tsched = use_tsched = FALSE; } - if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) { - pa_log_info("Device is not a hardware device, disabling timer-based scheduling."); - u->use_tsched = use_tsched = FALSE; - } - if (u->use_mmap) pa_log_info("Successfully enabled mmap() mode."); @@ -1520,33 +1780,44 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); - pa_alsa_find_mixer_and_elem(u->pcm_handle, &control_device, &u->mixer_handle, &u->mixer_elem, pa_modargs_get_value(ma, "control", NULL), profile); + find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); pa_source_new_data_init(&data); data.driver = driver; data.module = m; data.card = card; - set_source_name(&data, ma, dev_id, u->device_name); + set_source_name(&data, ma, dev_id, u->device_name, mapping); + + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse boolean argument namereg_fail."); + pa_source_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + pa_source_new_data_set_sample_spec(&data, &ss); pa_source_new_data_set_channel_map(&data, &map); - pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle, u->mixer_elem); + pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (buffer_frames * frame_size)); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); - if (profile) { - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, profile->name); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, profile->description); + if (mapping) { + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description); } pa_alsa_init_description(data.proplist); - if (control_device) { - pa_alsa_init_proplist_ctl(data.proplist, control_device); - pa_xfree(control_device); - } + if (u->control_device) + pa_alsa_init_proplist_ctl(data.proplist, u->control_device); if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log("Invalid properties"); @@ -1554,6 +1825,9 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } + if (u->mixer_path_set) + pa_alsa_add_ports(&data.ports, u->mixer_path_set); + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0)); pa_source_new_data_done(&data); @@ -1562,27 +1836,48 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } + if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin", + &u->source->thread_info.volume_change_safety_margin) < 0) { + pa_log("Failed to parse sync_volume_safety_margin parameter"); + goto fail; + } + + if (pa_modargs_get_value_s32(ma, "sync_volume_extra_delay", + &u->source->thread_info.volume_change_extra_delay) < 0) { + pa_log("Failed to parse sync_volume_extra_delay parameter"); + goto fail; + } + u->source->parent.process_msg = source_process_msg; - u->source->update_requested_latency = source_update_requested_latency_cb; + if (u->use_tsched) + u->source->update_requested_latency = source_update_requested_latency_cb; u->source->set_state = source_set_state_cb; + u->source->set_port = source_set_port_cb; u->source->userdata = u; pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec); + u->fragment_size = frag_size = (size_t) (period_frames * frame_size); + u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size); pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels); - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, + pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)", + (double) u->hwbuf_size / (double) u->fragment_size, + (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC, + (long unsigned) u->hwbuf_size, (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); if (u->use_tsched) { - u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec); + u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec); + + u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec); + u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec); + + u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec); + u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec); fix_min_sleep_wakeup(u); fix_tsched_watermark(u); @@ -1601,15 +1896,16 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p if (update_sw_params(u) < 0) goto fail; - if (setup_mixer(u, ignore_dB) < 0) + if (setup_mixer(u, ignore_dB, sync_volume) < 0) goto fail; pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("alsa-source", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } + /* Get initial mixer settings */ if (data.volume_is_set) { if (u->source->set_volume) @@ -1629,6 +1925,9 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_source_put(u->source); + if (profile_set) + pa_alsa_profile_set_free(profile_set); + return u->source; fail: @@ -1636,6 +1935,9 @@ fail: if (u) userdata_free(u); + if (profile_set) + pa_alsa_profile_set_free(profile_set); + return NULL; } @@ -1655,29 +1957,39 @@ static void userdata_free(struct userdata *u) { if (u->source) pa_source_unref(u->source); + if (u->mixer_pd) + pa_alsa_mixer_pdata_free(u->mixer_pd); + if (u->alsa_rtpoll_item) pa_rtpoll_item_free(u->alsa_rtpoll_item); if (u->rtpoll) pa_rtpoll_free(u->rtpoll); + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + if (u->mixer_fdl) pa_alsa_fdlist_free(u->mixer_fdl); + if (u->mixer_path_set) + pa_alsa_path_set_free(u->mixer_path_set); + else if (u->mixer_path) + pa_alsa_path_free(u->mixer_path); + if (u->mixer_handle) snd_mixer_close(u->mixer_handle); - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - } - if (u->smoother) pa_smoother_free(u->smoother); reserve_done(u); + monitor_done(u); pa_xfree(u->device_name); + pa_xfree(u->control_device); pa_xfree(u); } diff --git a/src/modules/alsa/alsa-source.h b/src/modules/alsa/alsa-source.h index 9cbb0e17..5d9409e2 100644 --- a/src/modules/alsa/alsa-source.h +++ b/src/modules/alsa/alsa-source.h @@ -29,7 +29,7 @@ #include "alsa-util.h" -pa_source* pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile); +pa_source* pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping); void pa_alsa_source_free(pa_source *s); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index c03866cc..883c26f9 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -25,7 +25,6 @@ #endif #include <sys/types.h> -#include <limits.h> #include <asoundlib.h> #include <pulse/sample.h> @@ -40,10 +39,12 @@ #include <pulsecore/core-util.h> #include <pulsecore/atomic.h> #include <pulsecore/core-error.h> -#include <pulsecore/once.h> #include <pulsecore/thread.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/core-rtclock.h> #include "alsa-util.h" +#include "alsa-mixer.h" #ifdef HAVE_HAL #include "hal-util.h" @@ -53,182 +54,6 @@ #include "udev-util.h" #endif -struct pa_alsa_fdlist { - unsigned num_fds; - struct pollfd *fds; - /* This is a temporary buffer used to avoid lots of mallocs */ - struct pollfd *work_fds; - - snd_mixer_t *mixer; - - pa_mainloop_api *m; - pa_defer_event *defer; - pa_io_event **ios; - - pa_bool_t polled; - - void (*cb)(void *userdata); - void *userdata; -}; - -static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { - - struct pa_alsa_fdlist *fdl = userdata; - int err; - unsigned i; - unsigned short revents; - - pa_assert(a); - pa_assert(fdl); - pa_assert(fdl->mixer); - pa_assert(fdl->fds); - pa_assert(fdl->work_fds); - - if (fdl->polled) - return; - - fdl->polled = TRUE; - - memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - - for (i = 0; i < fdl->num_fds; i++) { - if (e == fdl->ios[i]) { - if (events & PA_IO_EVENT_INPUT) - fdl->work_fds[i].revents |= POLLIN; - if (events & PA_IO_EVENT_OUTPUT) - fdl->work_fds[i].revents |= POLLOUT; - if (events & PA_IO_EVENT_ERROR) - fdl->work_fds[i].revents |= POLLERR; - if (events & PA_IO_EVENT_HANGUP) - fdl->work_fds[i].revents |= POLLHUP; - break; - } - } - - pa_assert(i != fdl->num_fds); - - if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { - pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); - return; - } - - a->defer_enable(fdl->defer, 1); - - if (revents) - snd_mixer_handle_events(fdl->mixer); -} - -static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { - struct pa_alsa_fdlist *fdl = userdata; - unsigned num_fds, i; - int err, n; - struct pollfd *temp; - - pa_assert(a); - pa_assert(fdl); - pa_assert(fdl->mixer); - - a->defer_enable(fdl->defer, 0); - - if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) { - pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); - return; - } - num_fds = (unsigned) n; - - if (num_fds != fdl->num_fds) { - if (fdl->fds) - pa_xfree(fdl->fds); - if (fdl->work_fds) - pa_xfree(fdl->work_fds); - fdl->fds = pa_xnew0(struct pollfd, num_fds); - fdl->work_fds = pa_xnew(struct pollfd, num_fds); - } - - memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); - - if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { - pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); - return; - } - - fdl->polled = FALSE; - - if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) - return; - - if (fdl->ios) { - for (i = 0; i < fdl->num_fds; i++) - a->io_free(fdl->ios[i]); - - if (num_fds != fdl->num_fds) { - pa_xfree(fdl->ios); - fdl->ios = NULL; - } - } - - if (!fdl->ios) - fdl->ios = pa_xnew(pa_io_event*, num_fds); - - /* Swap pointers */ - temp = fdl->work_fds; - fdl->work_fds = fdl->fds; - fdl->fds = temp; - - fdl->num_fds = num_fds; - - for (i = 0;i < num_fds;i++) - fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, - ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | - ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), - io_cb, fdl); -} - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { - struct pa_alsa_fdlist *fdl; - - fdl = pa_xnew0(struct pa_alsa_fdlist, 1); - - return fdl; -} - -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { - pa_assert(fdl); - - if (fdl->defer) { - pa_assert(fdl->m); - fdl->m->defer_free(fdl->defer); - } - - if (fdl->ios) { - unsigned i; - pa_assert(fdl->m); - for (i = 0; i < fdl->num_fds; i++) - fdl->m->io_free(fdl->ios[i]); - pa_xfree(fdl->ios); - } - - if (fdl->fds) - pa_xfree(fdl->fds); - if (fdl->work_fds) - pa_xfree(fdl->work_fds); - - pa_xfree(fdl); -} - -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { - pa_assert(fdl); - pa_assert(mixer_handle); - pa_assert(m); - pa_assert(!fdl->m); - - fdl->mixer = mixer_handle; - fdl->m = m; - fdl->defer = m->defer_new(m, defer_cb, fdl); - - return 0; -} - static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) { static const snd_pcm_format_t format_trans[] = { @@ -260,13 +85,14 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s PA_SAMPLE_S16RE, PA_SAMPLE_ALAW, PA_SAMPLE_ULAW, - PA_SAMPLE_U8, - PA_SAMPLE_INVALID + PA_SAMPLE_U8 }; - int i, ret; + unsigned i; + int ret; pa_assert(pcm_handle); + pa_assert(hwparams); pa_assert(f); if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) @@ -276,7 +102,6 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s snd_pcm_format_description(format_trans[*f]), pa_alsa_strerror(ret)); - if (*f == PA_SAMPLE_FLOAT32BE) *f = PA_SAMPLE_FLOAT32LE; else if (*f == PA_SAMPLE_FLOAT32LE) @@ -309,7 +134,7 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s try_auto: - for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { + for (i = 0; i < PA_ELEMENTSOF(try_order); i++) { *f = try_order[i]; if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) @@ -323,33 +148,71 @@ try_auto: return -1; } +static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { + snd_pcm_uframes_t s; + int d, ret; + + pa_assert(pcm_handle); + pa_assert(hwparams); + + s = size; + d = 0; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { + s = size; + d = -1; + if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { + s = size; + d = 1; + if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) { + pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret)); + return ret; + } + } + } + + return 0; +} + +static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { + int ret; + + pa_assert(pcm_handle); + pa_assert(hwparams); + + if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) { + pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + return ret; + } + + return 0; +} + /* Set the hardware parameters of the given ALSA device. Returns the - * selected fragment settings in *period and *period_size */ + * selected fragment settings in *buffer_size and *period_size. If tsched mode can be enabled */ int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, pa_sample_spec *ss, - uint32_t *periods, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, pa_bool_t require_exact_channel_number) { int ret = -1; + snd_pcm_hw_params_t *hwparams, *hwparams_copy; + int dir; snd_pcm_uframes_t _period_size = period_size ? *period_size : 0; - unsigned int _periods = periods ? *periods : 0; - unsigned int r = ss->rate; - unsigned int c = ss->channels; - pa_sample_format_t f = ss->format; - snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0; pa_bool_t _use_mmap = use_mmap && *use_mmap; pa_bool_t _use_tsched = use_tsched && *use_tsched; - int dir; + pa_sample_spec _ss = *ss; pa_assert(pcm_handle); pa_assert(ss); snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_hw_params_alloca(&hwparams_copy); if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); @@ -383,105 +246,173 @@ int pa_alsa_set_hw_params( if (!_use_mmap) _use_tsched = FALSE; - if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) + if (!pa_alsa_pcm_is_hw(pcm_handle)) + _use_tsched = FALSE; + +#if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */ + if (_use_tsched) { + + /* try to disable period wakeups if hardware can do so */ + if (snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) { + + if (snd_pcm_hw_params_set_period_wakeup(pcm_handle, hwparams, FALSE) < 0) + /* don't bail, keep going with default mode with period wakeups */ + pa_log_debug("snd_pcm_hw_params_set_period_wakeup() failed: %s", pa_alsa_strerror(ret)); + else + pa_log_info("Trying to disable ALSA period wakeups, using timers only"); + } else + pa_log_info("cannot disable ALSA period wakeups"); + } +#endif + + if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) { + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &_ss.rate, NULL)) < 0) { pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); goto finish; } + /* We ignore very small sampling rate deviations */ + if (_ss.rate >= ss->rate*.95 && _ss.rate <= ss->rate*1.05) + _ss.rate = ss->rate; + if (require_exact_channel_number) { - if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_channels() failed: %s", pa_alsa_strerror(ret)); + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) { + pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } } else { + unsigned int c = _ss.channels; + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_channels_near() failed: %s", pa_alsa_strerror(ret)); + pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } + + _ss.channels = c; } - if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) { - pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret)); - goto finish; + if (_use_tsched && tsched_size > 0) { + _buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate); + _period_size = _buffer_size; + } else { + _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate); + _buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate); } - if (_period_size && tsched_size && _periods) { + if (_buffer_size > 0 || _period_size > 0) { + snd_pcm_uframes_t max_frames = 0; - /* Adjust the buffer sizes, if we didn't get the rate we were asking for */ - _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate); - tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate); + if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) + pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret)); + else + pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) (max_frames * PA_MSEC_PER_SEC / _ss.rate)); - if (_use_tsched) { - snd_pcm_uframes_t buffer_size; + /* Some ALSA drivers really don't like if we set the buffer + * size first and the number of periods second. (which would + * make a lot more sense to me) So, try a few combinations + * before we give up. */ - pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); - pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); + if (_buffer_size > 0 && _period_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); - _period_size = tsched_size; - _periods = 1; - } + /* First try: set buffer size first, followed by period size */ + if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set buffer size first (to %lu samples), period size second (to %lu samples).", (unsigned long) _buffer_size, (unsigned long) _period_size); + goto success; + } - if (_period_size > 0 && _periods > 0) { - snd_pcm_uframes_t buffer_size; + /* Second try: set period size first, followed by buffer size */ + if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set period size first (to %lu samples), buffer size second (to %lu samples).", (unsigned long) _period_size, (unsigned long) _buffer_size); + goto success; + } + } - buffer_size = _periods * _period_size; + if (_buffer_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); - if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) - pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); + /* Third try: set only buffer size */ + if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set only buffer size (to %lu samples).", (unsigned long) _buffer_size); + goto success; + } } - if (_periods > 0) { - - /* First we pass 0 as direction to get exactly what we - * asked for. That this is necessary is presumably a bug - * in ALSA. All in all this is mostly a hint to ALSA, so - * we don't care if this fails. */ - - dir = 0; - if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) { - dir = 1; - if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir) < 0) { - dir = -1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) - pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret)); - } + if (_period_size > 0) { + snd_pcm_hw_params_copy(hwparams_copy, hwparams); + + /* Fourth try: set only period size */ + if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && + snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { + pa_log_debug("Set only period size (to %lu samples).", (unsigned long) _period_size); + goto success; } } } - if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) + pa_log_debug("Set neither period nor buffer size."); + + /* Last chance, set nothing */ + if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { + pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret)); goto finish; + } + +success: - if (ss->rate != r) - pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r); + if (ss->rate != _ss.rate) + pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate); - if (ss->channels != c) - pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); + if (ss->channels != _ss.channels) + pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels); - if (ss->format != f) - pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); + if (ss->format != _ss.format) + pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format)); - if ((ret = snd_pcm_prepare(pcm_handle)) < 0) + if ((ret = snd_pcm_prepare(pcm_handle)) < 0) { + pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret)); goto finish; + } + + if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) { + pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret)); + goto finish; + } if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 || - (ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) + (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) { + pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret)); goto finish; + } - /* If the sample rate deviates too much, we need to resample */ - if (r < ss->rate*.95 || r > ss->rate*1.05) - ss->rate = r; - ss->channels = (uint8_t) c; - ss->format = f; +#if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */ + if (_use_tsched) { + unsigned int no_wakeup; + /* see if period wakeups were disabled */ + snd_pcm_hw_params_get_period_wakeup(pcm_handle, hwparams, &no_wakeup); + if (no_wakeup == 0) + pa_log_info("ALSA period wakeups disabled"); + else + pa_log_info("ALSA period wakeups were not disabled"); + } +#endif + + ss->rate = _ss.rate; + ss->channels = _ss.channels; + ss->format = _ss.format; - pa_assert(_periods > 0); pa_assert(_period_size > 0); + pa_assert(_buffer_size > 0); - if (periods) - *periods = _periods; + if (buffer_size) + *buffer_size = _buffer_size; if (period_size) *period_size = _period_size; @@ -494,14 +425,12 @@ int pa_alsa_set_hw_params( ret = 0; - snd_pcm_nonblock(pcm_handle, 1); - finish: return ret; } -int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, pa_bool_t period_event) { snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t boundary; int err; @@ -515,7 +444,7 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { return err; } - if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, 0)) < 0) { + if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) { pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err)); return err; } @@ -553,335 +482,152 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { return 0; } -static const struct pa_alsa_profile_info device_table[] = { - {{ 1, { PA_CHANNEL_POSITION_MONO }}, - "hw", NULL, - N_("Analog Mono"), - "analog-mono", - 1, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "front", "hw", - N_("Analog Stereo"), - "analog-stereo", - 10, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "iec958", NULL, - N_("Digital Stereo (IEC958)"), - "iec958-stereo", - 5, - "IEC958", NULL, - "IEC958 In", NULL }, - - {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT }}, - "hdmi", NULL, - N_("Digital Stereo (HDMI)"), - "hdmi-stereo", - 4, - "IEC958", NULL, - "IEC958 In", NULL }, - - {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "surround40", NULL, - N_("Analog Surround 4.0"), - "analog-surround-40", - 7, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, - "a52", NULL, - N_("Digital Surround 4.0 (IEC958/AC3)"), - "iec958-ac3-surround-40", - 2, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_LFE }}, - "surround41", NULL, - N_("Analog Surround 4.1"), - "analog-surround-41", - 7, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER }}, - "surround50", NULL, - N_("Analog Surround 5.0"), - "analog-surround-50", - 7, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, - "surround51", NULL, - N_("Analog Surround 5.1"), - "analog-surround-51", - 8, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE}}, - "a52", NULL, - N_("Digital Surround 5.1 (IEC958/AC3)"), - "iec958-ac3-surround-51", - 3, - "IEC958", NULL, - "IEC958 In", NULL }, - - {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }}, - "surround71", NULL, - N_("Analog Surround 7.1"), - "analog-surround-71", - 7, - "Master", "PCM", - "Capture", "Mic" }, - - {{ 0, { 0 }}, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } -}; - -static snd_pcm_t *open_by_device_string_with_fallback( - const char *prefix, - const char *prefix_fallback, +snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, - pa_bool_t require_exact_channel_number) { + pa_alsa_profile_set *ps, + pa_alsa_mapping **mapping) { - snd_pcm_t *pcm_handle; char *d; + snd_pcm_t *pcm_handle; + void *state; + pa_alsa_mapping *m; - d = pa_sprintf_malloc("%s:%s", prefix, dev_id); + pa_assert(dev_id); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(ps); - pcm_handle = pa_alsa_open_by_device_string( - d, - dev, - ss, - map, - mode, - nfrags, - period_size, - tsched_size, - use_mmap, - use_tsched, - require_exact_channel_number); - pa_xfree(d); + /* First we try to find a device string with a superset of the + * requested channel map. We iterate through our device table from + * top to bottom and take the first that matches. If we didn't + * find a working device that way, we iterate backwards, and check + * all devices that do not provide a superset of the requested + * channel map.*/ - if (!pcm_handle && prefix_fallback) { + PA_HASHMAP_FOREACH(m, ps->mappings, state) { + if (!pa_channel_map_superset(&m->channel_map, map)) + continue; - d = pa_sprintf_malloc("%s:%s", prefix_fallback, dev_id); + pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]); - pcm_handle = pa_alsa_open_by_device_string( - d, + pcm_handle = pa_alsa_open_by_device_id_mapping( + dev_id, dev, ss, map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, - require_exact_channel_number); - pa_xfree(d); - } - - return pcm_handle; -} - -snd_pcm_t *pa_alsa_open_by_device_id_auto( - const char *dev_id, - char **dev, - pa_sample_spec *ss, - pa_channel_map* map, - int mode, - uint32_t *nfrags, - snd_pcm_uframes_t *period_size, - snd_pcm_uframes_t tsched_size, - pa_bool_t *use_mmap, - pa_bool_t *use_tsched, - const pa_alsa_profile_info **profile) { + m); - int i; - int direction = 1; - char *d; - snd_pcm_t *pcm_handle; + if (pcm_handle) { + if (mapping) + *mapping = m; - pa_assert(dev_id); - pa_assert(dev); - pa_assert(ss); - pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); - - /* First we try to find a device string with a superset of the - * requested channel map and open it without the plug: prefix. We - * iterate through our device table from top to bottom and take - * the first that matches. If we didn't find a working device that - * way, we iterate backwards, and check all devices that do not - * provide a superset of the requested channel map.*/ - - i = 0; - for (;;) { - - if ((direction > 0) == pa_channel_map_superset(&device_table[i].map, map)) { - pa_sample_spec try_ss; - - pa_log_debug("Checking for %s (%s)", device_table[i].name, device_table[i].alsa_name); - - try_ss.channels = device_table[i].map.channels; - try_ss.rate = ss->rate; - try_ss.format = ss->format; - - pcm_handle = open_by_device_string_with_fallback( - device_table[i].alsa_name, - device_table[i].alsa_name_fallback, - dev_id, - dev, - &try_ss, - map, - mode, - nfrags, - period_size, - tsched_size, - use_mmap, - use_tsched, - TRUE); - - if (pcm_handle) { - - *ss = try_ss; - *map = device_table[i].map; - pa_assert(map->channels == ss->channels); - - if (profile) - *profile = &device_table[i]; - - return pcm_handle; - } - - } - - if (direction > 0) { - if (!device_table[i+1].alsa_name) { - /* OK, so we are at the end of our list. Let's turn - * back. */ - direction = -1; - } else { - /* We are not at the end of the list, so let's simply - * try the next entry */ - i++; - } + return pcm_handle; } + } - if (direction < 0) { - - if (device_table[i+1].alsa_name && - device_table[i].map.channels == device_table[i+1].map.channels) { - - /* OK, the next entry has the same number of channels, - * let's try it */ - i++; + PA_HASHMAP_FOREACH_BACKWARDS(m, ps->mappings, state) { + if (pa_channel_map_superset(&m->channel_map, map)) + continue; - } else { - /* Hmm, so the next entry does not have the same - * number of channels, so let's go backwards until we - * find the next entry with a different number of - * channels */ + pa_log_debug("Checking for subset %s (%s)", m->name, m->device_strings[0]); - for (i--; i >= 0; i--) - if (device_table[i].map.channels != device_table[i+1].map.channels) - break; + pcm_handle = pa_alsa_open_by_device_id_mapping( + dev_id, + dev, + ss, + map, + mode, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + m); - /* Hmm, there is no entry with a different number of - * entries, then we're done */ - if (i < 0) - break; + if (pcm_handle) { + if (mapping) + *mapping = m; - /* OK, now lets find go back as long as we have the same number of channels */ - for (; i > 0; i--) - if (device_table[i].map.channels != device_table[i-1].map.channels) - break; - } + return pcm_handle; } } - /* OK, we didn't find any good device, so let's try the raw plughw: stuff */ - + /* OK, we didn't find any good device, so let's try the raw hw: stuff */ d = pa_sprintf_malloc("hw:%s", dev_id); pa_log_debug("Trying %s as last resort...", d); - pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE); + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + FALSE); pa_xfree(d); - if (pcm_handle && profile) - *profile = NULL; + if (pcm_handle && mapping) + *mapping = NULL; return pcm_handle; } -snd_pcm_t *pa_alsa_open_by_device_id_profile( +snd_pcm_t *pa_alsa_open_by_device_id_mapping( const char *dev_id, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, - const pa_alsa_profile_info *profile) { + pa_alsa_mapping *m) { snd_pcm_t *pcm_handle; pa_sample_spec try_ss; + pa_channel_map try_map; pa_assert(dev_id); pa_assert(dev); pa_assert(ss); pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); - pa_assert(profile); + pa_assert(m); - try_ss.channels = profile->map.channels; + try_ss.channels = m->channel_map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; + try_map = m->channel_map; - pcm_handle = open_by_device_string_with_fallback( - profile->alsa_name, - profile->alsa_name_fallback, + pcm_handle = pa_alsa_open_by_template( + m->device_strings, dev_id, dev, &try_ss, - map, + &try_map, mode, - nfrags, period_size, + buffer_size, tsched_size, use_mmap, use_tsched, @@ -891,7 +637,7 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile( return NULL; *ss = try_ss; - *map = profile->map; + *map = try_map; pa_assert(map->channels == ss->channels); return pcm_handle; @@ -903,8 +649,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_sample_spec *ss, pa_channel_map* map, int mode, - uint32_t *nfrags, snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, pa_bool_t *use_mmap, pa_bool_t *use_tsched, @@ -924,14 +670,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( for (;;) { pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); - /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= - * 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead - * we enable nonblock mode afterwards via - * snd_pcm_nonblock(). Also see - * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ - if ((err = snd_pcm_open(&pcm_handle, d, mode, - /*SND_PCM_NONBLOCK|*/ + SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) { @@ -941,7 +681,15 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_log_debug("Managed to open %s", d); - if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) { + if ((err = pa_alsa_set_hw_params( + pcm_handle, + ss, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number)) < 0) { if (!reformat) { reformat = TRUE; @@ -951,7 +699,6 @@ snd_pcm_t *pa_alsa_open_by_device_string( } /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ - if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) { char *t; @@ -988,440 +735,48 @@ fail: return NULL; } -int pa_alsa_probe_profiles( +snd_pcm_t *pa_alsa_open_by_template( + char **template, const char *dev_id, - const pa_sample_spec *ss, - void (*cb)(const pa_alsa_profile_info *sink, const pa_alsa_profile_info *source, void *userdata), - void *userdata) { - - const pa_alsa_profile_info *i; - - pa_assert(dev_id); - pa_assert(ss); - pa_assert(cb); - - /* We try each combination of playback/capture. We also try to - * open only for capture resp. only for sink. Don't get confused - * by the trailing entry in device_table we use for this! */ - - for (i = device_table; i < device_table + PA_ELEMENTSOF(device_table); i++) { - const pa_alsa_profile_info *j; - snd_pcm_t *pcm_i = NULL; - - if (i->alsa_name) { - pa_sample_spec try_ss; - pa_channel_map try_map; - - pa_log_debug("Checking for playback on %s (%s)", i->name, i->alsa_name); - - try_ss = *ss; - try_ss.channels = i->map.channels; - try_map = i->map; - - pcm_i = open_by_device_string_with_fallback( - i->alsa_name, - i->alsa_name_fallback, - dev_id, - NULL, - &try_ss, &try_map, - SND_PCM_STREAM_PLAYBACK, - NULL, NULL, 0, NULL, NULL, - TRUE); - - if (!pcm_i) - continue; - } - - for (j = device_table; j < device_table + PA_ELEMENTSOF(device_table); j++) { - snd_pcm_t *pcm_j = NULL; - - if (j->alsa_name) { - pa_sample_spec try_ss; - pa_channel_map try_map; - - pa_log_debug("Checking for capture on %s (%s)", j->name, j->alsa_name); - - try_ss = *ss; - try_ss.channels = j->map.channels; - try_map = j->map; - - pcm_j = open_by_device_string_with_fallback( - j->alsa_name, - j->alsa_name_fallback, - dev_id, - NULL, - &try_ss, &try_map, - SND_PCM_STREAM_CAPTURE, - NULL, NULL, 0, NULL, NULL, - TRUE); - - if (!pcm_j) - continue; - } - - if (pcm_j) - snd_pcm_close(pcm_j); - - if (i->alsa_name || j->alsa_name) - cb(i->alsa_name ? i : NULL, - j->alsa_name ? j : NULL, userdata); - } - - if (pcm_i) - snd_pcm_close(pcm_i); - } - - return TRUE; -} - -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { - int err; - - pa_assert(mixer); - pa_assert(dev); - - if ((err = snd_mixer_attach(mixer, dev)) < 0) { - pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err)); - return -1; - } - - if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { - pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err)); - return -1; - } - - if ((err = snd_mixer_load(mixer)) < 0) { - pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err)); - return -1; - } - - pa_log_info("Successfully attached to mixer '%s'", dev); - - return 0; -} - -static pa_bool_t elem_has_volume(snd_mixer_elem_t *elem, pa_bool_t playback) { - pa_assert(elem); - - if (playback && snd_mixer_selem_has_playback_volume(elem)) - return TRUE; - - if (!playback && snd_mixer_selem_has_capture_volume(elem)) - return TRUE; - - return FALSE; -} - -static pa_bool_t elem_has_switch(snd_mixer_elem_t *elem, pa_bool_t playback) { - pa_assert(elem); - - if (playback && snd_mixer_selem_has_playback_switch(elem)) - return TRUE; - - if (!playback && snd_mixer_selem_has_capture_switch(elem)) - return TRUE; - - return FALSE; -} - -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback) { - snd_mixer_elem_t *elem = NULL, *fallback_elem = NULL; - snd_mixer_selem_id_t *sid = NULL; - - snd_mixer_selem_id_alloca(&sid); - - pa_assert(mixer); - pa_assert(name); - - snd_mixer_selem_id_set_name(sid, name); - snd_mixer_selem_id_set_index(sid, 0); - - if ((elem = snd_mixer_find_selem(mixer, sid))) { - - if (elem_has_volume(elem, playback) && - elem_has_switch(elem, playback)) - goto success; - - if (!elem_has_volume(elem, playback) && - !elem_has_switch(elem, playback)) - elem = NULL; - } - - pa_log_info("Cannot find mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); - - if (fallback) { - snd_mixer_selem_id_set_name(sid, fallback); - snd_mixer_selem_id_set_index(sid, 0); - - if ((fallback_elem = snd_mixer_find_selem(mixer, sid))) { - - if (elem_has_volume(fallback_elem, playback) && - elem_has_switch(fallback_elem, playback)) { - elem = fallback_elem; - goto success; - } - - if (!elem_has_volume(fallback_elem, playback) && - !elem_has_switch(fallback_elem, playback)) - fallback_elem = NULL; - } - - pa_log_info("Cannot find fallback mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); - } - - if (elem && fallback_elem) { - - /* Hmm, so we have both elements, but neither has both mute - * and volume. Let's prefer the one with the volume */ - - if (elem_has_volume(elem, playback)) - goto success; - - if (elem_has_volume(fallback_elem, playback)) { - elem = fallback_elem; - goto success; - } - } - - if (!elem && fallback_elem) - elem = fallback_elem; - -success: - - if (elem) - pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); - - return elem; -} - -int pa_alsa_find_mixer_and_elem( - snd_pcm_t *pcm, - char **ctl_device, - snd_mixer_t **_m, - snd_mixer_elem_t **_e, - const char *control_name, - const pa_alsa_profile_info *profile) { - - int err; - snd_mixer_t *m; - snd_mixer_elem_t *e; - pa_bool_t found = FALSE; - const char *dev; - - pa_assert(pcm); - pa_assert(_m); - pa_assert(_e); - - if (control_name && *control_name == 0) { - pa_log_debug("Hardware mixer usage disabled because empty control name passed"); - return -1; - } - - if ((err = snd_mixer_open(&m, 0)) < 0) { - pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); - return -1; - } - - /* First, try by name */ - if ((dev = snd_pcm_name(pcm))) - if (pa_alsa_prepare_mixer(m, dev) >= 0) { - found = TRUE; - - if (ctl_device) - *ctl_device = pa_xstrdup(dev); - } - - /* Then, try by card index */ - if (!found) { - snd_pcm_info_t* info; - snd_pcm_info_alloca(&info); - - if (snd_pcm_info(pcm, info) >= 0) { - char *md; - int card_idx; - - if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { - - md = pa_sprintf_malloc("hw:%i", card_idx); - - if (!dev || !pa_streq(dev, md)) - if (pa_alsa_prepare_mixer(m, md) >= 0) { - found = TRUE; - - if (ctl_device) { - *ctl_device = md; - md = NULL; - } - } - - pa_xfree(md); - } - } - } - - if (!found) { - snd_mixer_close(m); - return -1; - } - - switch (snd_pcm_stream(pcm)) { - - case SND_PCM_STREAM_PLAYBACK: - if (control_name) - e = pa_alsa_find_elem(m, control_name, NULL, TRUE); - else if (profile) - e = pa_alsa_find_elem(m, profile->playback_control_name, profile->playback_control_fallback, TRUE); - else - e = pa_alsa_find_elem(m, "Master", "PCM", TRUE); - break; - - case SND_PCM_STREAM_CAPTURE: - if (control_name) - e = pa_alsa_find_elem(m, control_name, NULL, FALSE); - else if (profile) - e = pa_alsa_find_elem(m, profile->record_control_name, profile->record_control_fallback, FALSE); - else - e = pa_alsa_find_elem(m, "Capture", "Mic", FALSE); - break; - - default: - pa_assert_not_reached(); - } - - if (!e) { - if (ctl_device) - pa_xfree(*ctl_device); - - snd_mixer_close(m); - return -1; - } - - pa_assert(e && m); - - *_m = m; - *_e = e; - - return 0; -} - -static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { - [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ - - [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, - [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, - [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, - - [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, - [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, - [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, - - [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, - - [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, - [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, - - [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN -}; - - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { - unsigned i; - pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; - pa_bool_t mono_used = FALSE; - - pa_assert(elem); - pa_assert(channel_map); - pa_assert(mixer_map); - - memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); - - if (channel_map->channels > 1 && - ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || - (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { - pa_log_info("ALSA device lacks independant volume controls for each channel."); - return -1; - } - - for (i = 0; i < channel_map->channels; i++) { - snd_mixer_selem_channel_id_t id; - pa_bool_t is_mono; + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *buffer_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number) { - is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; - id = alsa_channel_ids[channel_map->map[i]]; + snd_pcm_t *pcm_handle; + char **i; - if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { - pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer.", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } + for (i = template; *i; i++) { + char *d; - if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { - pa_log_info("Channel map has duplicate channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } + d = pa_replace(*i, "%f", dev_id); - if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || - (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { + pcm_handle = pa_alsa_open_by_device_string( + d, + dev, + ss, + map, + mode, + period_size, + buffer_size, + tsched_size, + use_mmap, + use_tsched, + require_exact_channel_number); - pa_log_info("ALSA device lacks separate volumes control for channel '%s'", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } + pa_xfree(d); - if (is_mono) { - mixer_map[i] = SND_MIXER_SCHN_MONO; - mono_used = TRUE; - } else { - mixer_map[i] = id; - alsa_channel_used[id] = TRUE; - } + if (pcm_handle) + return pcm_handle; } - pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); - - return 0; + return NULL; } void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) { @@ -1447,24 +802,33 @@ void pa_alsa_dump_status(snd_pcm_t *pcm) { int err; snd_output_t *out; snd_pcm_status_t *status; + char *s = NULL; pa_assert(pcm); snd_pcm_status_alloca(&status); - pa_assert_se(snd_output_buffer_open(&out) == 0); + if ((err = snd_output_buffer_open(&out)) < 0) { + pa_log_debug("snd_output_buffer_open() failed: %s", pa_cstrerror(err)); + return; + } - pa_assert_se(snd_pcm_status(pcm, status) == 0); + if ((err = snd_pcm_status(pcm, status)) < 0) { + pa_log_debug("snd_pcm_status() failed: %s", pa_cstrerror(err)); + goto finish; + } - if ((err = snd_pcm_status_dump(status, out)) < 0) + if ((err = snd_pcm_status_dump(status, out)) < 0) { pa_log_debug("snd_pcm_dump(): %s", pa_alsa_strerror(err)); - else { - char *s = NULL; - snd_output_buffer_string(out, &s); - pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + goto finish; } - pa_assert_se(snd_output_close(out) == 0); + snd_output_buffer_string(out, &s); + pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + +finish: + + snd_output_close(out); } static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { @@ -1484,38 +848,43 @@ static void alsa_error_handler(const char *file, int line, const char *function, static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0); -void pa_alsa_redirect_errors_inc(void) { +void pa_alsa_refcnt_inc(void) { /* This is not really thread safe, but we do our best */ if (pa_atomic_inc(&n_error_handler_installed) == 0) snd_lib_error_set_handler(alsa_error_handler); } -void pa_alsa_redirect_errors_dec(void) { +void pa_alsa_refcnt_dec(void) { int r; pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1); - if (r == 1) + if (r == 1) { snd_lib_error_set_handler(NULL); + snd_config_update_free_global(); + } } pa_bool_t pa_alsa_init_description(pa_proplist *p) { - const char *s; + const char *d, *k; pa_assert(p); if (pa_device_init_description(p)) return TRUE; - if ((s = pa_proplist_gets(p, "alsa.card_name"))) { - pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s); - return TRUE; - } + if (!(d = pa_proplist_gets(p, "alsa.card_name"))) + d = pa_proplist_gets(p, "alsa.name"); - if ((s = pa_proplist_gets(p, "alsa.name"))) { - pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s); - return TRUE; - } + if (!d) + return FALSE; + + k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); + + if (d && k) + pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k); + else if (d) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d); return FALSE; } @@ -1529,12 +898,12 @@ void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) { pa_proplist_setf(p, "alsa.card", "%i", card); if (snd_card_get_name(card, &cn) >= 0) { - pa_proplist_sets(p, "alsa.card_name", cn); + pa_proplist_sets(p, "alsa.card_name", pa_strip(cn)); free(cn); } if (snd_card_get_longname(card, &lcn) >= 0) { - pa_proplist_sets(p, "alsa.long_card_name", lcn); + pa_proplist_sets(p, "alsa.long_card_name", pa_strip(lcn)); free(lcn); } @@ -1544,7 +913,7 @@ void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) { } #ifdef HAVE_UDEV - pa_udev_get_info(c, p, card); + pa_udev_get_info(card, p); #endif #ifdef HAVE_HAL @@ -1581,21 +950,22 @@ void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t * pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); - class = snd_pcm_info_get_class(pcm_info); - if (class <= SND_PCM_CLASS_LAST) { + if ((class = snd_pcm_info_get_class(pcm_info)) <= SND_PCM_CLASS_LAST) { if (class_table[class]) pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, class_table[class]); if (alsa_class_table[class]) pa_proplist_sets(p, "alsa.class", alsa_class_table[class]); } - subclass = snd_pcm_info_get_subclass(pcm_info); - if (subclass <= SND_PCM_SUBCLASS_LAST) + if ((subclass = snd_pcm_info_get_subclass(pcm_info)) <= SND_PCM_SUBCLASS_LAST) if (alsa_subclass_table[subclass]) pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]); - if ((n = snd_pcm_info_get_name(pcm_info))) - pa_proplist_sets(p, "alsa.name", n); + if ((n = snd_pcm_info_get_name(pcm_info))) { + char *t = pa_xstrdup(n); + pa_proplist_sets(p, "alsa.name", pa_strip(t)); + pa_xfree(t); + } if ((id = snd_pcm_info_get_id(pcm_info))) pa_proplist_sets(p, "alsa.id", id); @@ -1610,7 +980,7 @@ void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t * pa_alsa_init_proplist_card(c, p, card); } -void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm, snd_mixer_elem_t *elem) { +void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm) { snd_pcm_hw_params_t *hwparams; snd_pcm_info_t *info; int bits, err; @@ -1626,9 +996,6 @@ void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm, snd_m pa_proplist_setf(p, "alsa.resolution_bits", "%i", bits); } - if (elem) - pa_proplist_sets(p, "alsa.mixer_element", snd_mixer_selem_get_name(elem)); - if ((err = snd_pcm_info(pcm, info)) < 0) pa_log_warn("Error fetching PCM info: %s", pa_alsa_strerror(err)); else @@ -1646,7 +1013,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { snd_ctl_card_info_alloca(&info); if ((err = snd_ctl_open(&ctl, name, 0)) < 0) { - pa_log_warn("Error opening low-level control device '%s'", name); + pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err)); return; } @@ -1656,10 +1023,10 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { return; } - if ((t = snd_ctl_card_info_get_mixername(info))) + if ((t = snd_ctl_card_info_get_mixername(info)) && *t) pa_proplist_sets(p, "alsa.mixer_name", t); - if ((t = snd_ctl_card_info_get_components(info))) + if ((t = snd_ctl_card_info_get_components(info)) && *t) pa_proplist_sets(p, "alsa.components", t); snd_ctl_close(ctl); @@ -1761,8 +1128,8 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa k = (size_t) n * pa_frame_size(ss); - if (k >= hwbuf_size * 5 || - k >= pa_bytes_per_second(ss)*10) { + if (PA_UNLIKELY(k >= hwbuf_size * 5 || + k >= pa_bytes_per_second(ss)*10)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); @@ -1782,10 +1149,11 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa return n; } -int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss) { +int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, pa_bool_t capture) { ssize_t k; size_t abs_k; int r; + snd_pcm_sframes_t avail = 0; pa_assert(pcm); pa_assert(delay); @@ -1793,17 +1161,18 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si pa_assert(ss); /* Some ALSA driver expose weird bugs, let's inform the user about - * what is going on */ + * what is going on. We're going to get both the avail and delay values so + * that we can compare and check them for capture */ - if ((r = snd_pcm_delay(pcm, delay)) < 0) + if ((r = snd_pcm_avail_delay(pcm, &avail, delay)) < 0) return r; k = (ssize_t) *delay * (ssize_t) pa_frame_size(ss); abs_k = k >= 0 ? (size_t) k : (size_t) -k; - if (abs_k >= hwbuf_size * 5 || - abs_k >= pa_bytes_per_second(ss)*10) { + if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 || + abs_k >= pa_bytes_per_second(ss)*10)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); @@ -1824,6 +1193,44 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si *delay = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); } + if (capture) { + abs_k = (size_t) avail * pa_frame_size(ss); + + if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 || + abs_k >= pa_bytes_per_second(ss)*10)) { + + PA_ONCE_BEGIN { + char *dn = pa_alsa_get_driver_name_by_pcm(pcm); + pa_log(_("snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" + "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."), + (unsigned long) k, + (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), + pa_strnull(dn)); + pa_xfree(dn); + pa_alsa_dump(PA_LOG_ERROR, pcm); + } PA_ONCE_END; + + /* Mhmm, let's try not to fail completely */ + avail = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); + } + + if (PA_UNLIKELY(*delay < avail)) { + PA_ONCE_BEGIN { + char *dn = pa_alsa_get_driver_name_by_pcm(pcm); + pa_log(_("snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" + "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."), + (unsigned long) *delay, + (unsigned long) avail, + pa_strnull(dn)); + pa_xfree(dn); + pa_alsa_dump(PA_LOG_ERROR, pcm); + } PA_ONCE_END; + + /* try to fixup */ + *delay = avail; + } + } + return 0; } @@ -1848,10 +1255,9 @@ int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas k = (size_t) *frames * pa_frame_size(ss); - if (*frames > before || - k >= hwbuf_size * 3 || - k >= pa_bytes_per_second(ss)*10) - + if (PA_UNLIKELY(*frames > before || + k >= hwbuf_size * 3 || + k >= pa_bytes_per_second(ss)*10)) PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log(_("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" @@ -1971,3 +1377,25 @@ const char* pa_alsa_strerror(int errnum) { return translated; } + +pa_bool_t pa_alsa_may_tsched(pa_bool_t want) { + + if (!want) + return FALSE; + + if (!pa_rtclock_hrtimer()) { + /* We cannot depend on being woken up in time when the timers + are inaccurate, so let's fallback to classic IO based playback + then. */ + pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + return FALSE; } + + if (pa_running_in_vm()) { + /* We cannot depend on being woken up when we ask for in a VM, + * so let's fallback to classic IO based playback then. */ + pa_log_notice("Disabling timer-based scheduling because running inside a VM."); + return FALSE; + } + + return TRUE; +} diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 27f43712..ee5e781e 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -26,8 +26,6 @@ #include <asoundlib.h> #include <pulse/sample.h> -#include <pulse/volume.h> -#include <pulse/mainloop-api.h> #include <pulse/channelmap.h> #include <pulse/proplist.h> @@ -35,100 +33,91 @@ #include <pulsecore/core.h> #include <pulsecore/log.h> -typedef struct pa_alsa_fdlist pa_alsa_fdlist; - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); +#include "alsa-mixer.h" int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, - pa_sample_spec *ss, - uint32_t *periods, - snd_pcm_uframes_t *period_size, + pa_sample_spec *ss, /* modified at return */ + snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, - pa_bool_t *use_mmap, - pa_bool_t *use_tsched, + pa_bool_t *use_mmap, /* modified at return */ + pa_bool_t *use_tsched, /* modified at return */ pa_bool_t require_exact_channel_number); -int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); - -typedef struct pa_alsa_profile_info { - pa_channel_map map; - const char *alsa_name; - const char *alsa_name_fallback; - const char *description; /* internationalized */ - const char *name; - unsigned priority; - const char *playback_control_name, *playback_control_fallback; - const char *record_control_name, *record_control_fallback; -} pa_alsa_profile_info; +int pa_alsa_set_sw_params( + snd_pcm_t *pcm, + snd_pcm_uframes_t avail_min, + pa_bool_t period_event); -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback); -int pa_alsa_find_mixer_and_elem(snd_pcm_t *pcm, char **ctl_device, snd_mixer_t **_m, snd_mixer_elem_t **_e, const char *control_name, const pa_alsa_profile_info*profile); - -void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name); - -/* Picks a working profile based on the specified ss/map */ +/* Picks a working mapping from the profile set based on the specified ss/map */ snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, - char **dev, - pa_sample_spec *ss, - pa_channel_map* map, + char **dev, /* modified at return */ + pa_sample_spec *ss, /* modified at return */ + pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, - snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, - pa_bool_t *use_mmap, - pa_bool_t *use_tsched, - const pa_alsa_profile_info **profile); + pa_bool_t *use_mmap, /* modified at return */ + pa_bool_t *use_tsched, /* modified at return */ + pa_alsa_profile_set *ps, + pa_alsa_mapping **mapping); /* modified at return */ -/* Uses the specified profile */ -snd_pcm_t *pa_alsa_open_by_device_id_profile( +/* Uses the specified mapping */ +snd_pcm_t *pa_alsa_open_by_device_id_mapping( const char *dev_id, - char **dev, - pa_sample_spec *ss, - pa_channel_map* map, + char **dev, /* modified at return */ + pa_sample_spec *ss, /* modified at return */ + pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, - snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, - pa_bool_t *use_mmap, - pa_bool_t *use_tsched, - const pa_alsa_profile_info *profile); + pa_bool_t *use_mmap, /* modified at return */ + pa_bool_t *use_tsched, /* modified at return */ + pa_alsa_mapping *mapping); /* Opens the explicit ALSA device */ snd_pcm_t *pa_alsa_open_by_device_string( - const char *device, - char **dev, - pa_sample_spec *ss, - pa_channel_map* map, + const char *dir, + char **dev, /* modified at return */ + pa_sample_spec *ss, /* modified at return */ + pa_channel_map* map, /* modified at return */ int mode, - uint32_t *nfrags, - snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, - pa_bool_t *use_mmap, - pa_bool_t *use_tsched, + pa_bool_t *use_mmap, /* modified at return */ + pa_bool_t *use_tsched, /* modified at return */ pa_bool_t require_exact_channel_number); -int pa_alsa_probe_profiles( +/* Opens the explicit ALSA device with a fallback list */ +snd_pcm_t *pa_alsa_open_by_template( + char **template, const char *dev_id, - const pa_sample_spec *ss, - void (*cb)(const pa_alsa_profile_info *sink, const pa_alsa_profile_info *source, void *userdata), - void *userdata); - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); + char **dev, /* modified at return */ + pa_sample_spec *ss, /* modified at return */ + pa_channel_map* map, /* modified at return */ + int mode, + snd_pcm_uframes_t *period_size, /* modified at return */ + snd_pcm_uframes_t *buffer_size, /* modified at return */ + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, /* modified at return */ + pa_bool_t *use_tsched, /* modified at return */ + pa_bool_t require_exact_channel_number); void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm); void pa_alsa_dump_status(snd_pcm_t *pcm); -void pa_alsa_redirect_errors_inc(void); -void pa_alsa_redirect_errors_dec(void); +void pa_alsa_refcnt_inc(void); +void pa_alsa_refcnt_dec(void); void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info); void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card); -void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm, snd_mixer_elem_t *elem); +void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm); +void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name); pa_bool_t pa_alsa_init_description(pa_proplist *p); int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); @@ -136,19 +125,19 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss); -int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss); +int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, pa_bool_t capture); int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss); char *pa_alsa_get_driver_name(int card); - char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm); char *pa_alsa_get_reserve_name(const char *device); pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm); - pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm); const char* pa_alsa_strerror(int errnum); +pa_bool_t pa_alsa_may_tsched(pa_bool_t want); + #endif diff --git a/src/modules/alsa/mixer/paths/analog-input-aux.conf b/src/modules/alsa/mixer/paths/analog-input-aux.conf new file mode 100644 index 00000000..3a7cb7b2 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-aux.conf @@ -0,0 +1,66 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where an 'Aux' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-input + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf new file mode 100644 index 00000000..74826a96 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf @@ -0,0 +1,81 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Dock Mic' or 'Dock Mic Boost' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 80 +name = analog-input-microphone-dock + +[Element Dock Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Dock Mic Boost:on] +name = input-boost-on + +[Option Dock Mic Boost:off] +name = input-boost-off + +[Element Dock Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Dock Mic] +name = analog-input-microphone-dock +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Dock Mic] +name = analog-input-microphone-dock +required-any = any + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Front Mic] +switch = off +volume = off + +[Element Rear Mic] +switch = off +volume = off + +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-fm.conf b/src/modules/alsa/mixer/paths/analog-input-fm.conf new file mode 100644 index 00000000..7f150e36 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-fm.conf @@ -0,0 +1,66 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where an 'FM' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 +name = analog-input-radio + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf new file mode 100644 index 00000000..6c58ece1 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf @@ -0,0 +1,81 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Front Mic' or 'Front Mic Boost' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-input-microphone-front + +[Element Front Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Front Mic Boost:on] +name = input-boost-on + +[Option Front Mic Boost:off] +name = input-boost-off + +[Element Front Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Front Mic] +name = analog-input-microphone-front +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Front Mic] +name = analog-input-microphone-front +required-any = any + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Rear Mic] +switch = off +volume = off + +[Element Dock Mic] +switch = off +volume = off + +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf new file mode 100644 index 00000000..70a1cd12 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf @@ -0,0 +1,111 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Internal Mic' or 'Internal Mic Boost' element exists +; 'Int Mic' and 'Int Mic Boost' are for compatibility with kernels < 2.6.38 +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 89 +name = analog-input-microphone-internal + +[Element Internal Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Internal Mic Boost:on] +name = input-boost-on + +[Option Internal Mic Boost:off] +name = input-boost-off + +[Element Int Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Int Mic Boost:on] +name = input-boost-on + +[Option Int Mic Boost:off] +name = input-boost-off + + +[Element Internal Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Int Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Internal Mic] +name = analog-input-microphone-internal +required-any = any + +[Option Input Source:Int Mic] +name = analog-input-microphone-internal +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Internal Mic] +name = analog-input-microphone-internal +required-any = any + +[Option Capture Source:Int Mic] +name = analog-input-microphone-internal +required-any = any + +[Element Mic] +switch = off +volume = off + +[Element Dock Mic] +switch = off +volume = off + +[Element Front Mic] +switch = off +volume = off + +[Element Rear Mic] +switch = off +volume = off + +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf new file mode 100644 index 00000000..461cebdb --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf @@ -0,0 +1,93 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Line' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line Boost] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Line] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Line] +name = analog-input-linein +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Line] +name = analog-input-linein +required-any = any + + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +[Element Mic Jack Mode] +enumeration = select + +[Option Mic Jack Mode:Line In] +priority = 19 +required-any = any +name = input-linein diff --git a/src/modules/alsa/mixer/paths/analog-input-mic-line.conf b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf new file mode 100644 index 00000000..fa680aab --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf @@ -0,0 +1,67 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Mic/Line' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-input + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf new file mode 100644 index 00000000..d88028bf --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf @@ -0,0 +1,104 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Mic' or 'Mic Boost' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 89 +name = analog-input-microphone + +[Element Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Mic Boost:on] +name = input-boost-on + +[Option Mic Boost:off] +name = input-boost-off + +[Element Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Mic] +name = analog-input-microphone +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Mic] +name = analog-input-microphone +required-any = any + +;;; Some AC'97s have "Mic Select" and "Mic Boost (+20dB)" + +[Element Mic Select] +enumeration = select + +[Option Mic Select:Mic1] +name = input-microphone +priority = 20 + +[Option Mic Select:Mic2] +name = input-microphone +priority = 19 + +[Element Mic Boost (+20dB)] +switch = select +volume = merge + +[Option Mic Boost (+20dB):on] +name = input-boost-on + +[Option Mic Boost (+20dB):off] +name = input-boost-off + +[Element Front Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Rear Mic] +switch = off +volume = off + +[Element Dock Mic] +switch = off +volume = off + +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf.common b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common new file mode 100644 index 00000000..2e4f0d81 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common @@ -0,0 +1,54 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Common element for all microphone inputs +; +; See analog-output.conf.common for an explanation on the directives + +[Element Line] +switch = off +volume = off + +[Element Line Boost] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +[Element Mic Jack Mode] +enumeration = select + +[Option Mic Jack Mode:Mic In] +priority = 19 +name = input-microphone diff --git a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf new file mode 100644 index 00000000..75ed61b0 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf @@ -0,0 +1,81 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Rear Mic' or 'Rear Mic Boost' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 89 +name = analog-input-microphone-rear + +[Element Rear Mic Boost] +required-any = any +switch = select +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Option Rear Mic Boost:on] +name = input-boost-on + +[Option Rear Mic Boost:off] +name = input-boost-off + +[Element Rear Mic] +required-any = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Input Source] +enumeration = select + +[Option Input Source:Rear Mic] +name = analog-input-microphone-rear +required-any = any + +[Element Capture Source] +enumeration = select + +[Option Capture Source:Rear Mic] +name = analog-input-microphone-rear +required-any = any + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Front Mic] +switch = off +volume = off + +[Element Dock Mic] +switch = off +volume = off + +.include analog-input-mic.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf new file mode 100644 index 00000000..fae3ce83 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf @@ -0,0 +1,66 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'TV Tuner' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 +name = analog-input-video + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +switch = off +volume = off + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input-video.conf b/src/modules/alsa/mixer/paths/analog-input-video.conf new file mode 100644 index 00000000..19f18099 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-video.conf @@ -0,0 +1,65 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; For devices where a 'Video' element exists +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 70 + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +switch = off +volume = off + +[Element Internal Mic] +switch = off +volume = off + +[Element Line] +switch = off +volume = off + +[Element Aux] +switch = off +volume = off + +[Element Video] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic/Line] +switch = off +volume = off + +[Element TV Tuner] +switch = off +volume = off + +[Element FM] +switch = off +volume = off + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input.conf b/src/modules/alsa/mixer/paths/analog-input.conf new file mode 100644 index 00000000..b86c3564 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf @@ -0,0 +1,83 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; A fallback for devices that lack seperate Mic/Line/Aux/Video/TV +; Tuner/FM elements +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 100 + +[Element Capture] +required = volume +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +required-absent = any + +[Element Dock Mic] +required-absent = any + +[Element Dock Mic Boost] +required-absent = any + +[Element Front Mic] +required-absent = any + +[Element Front Mic Boost] +required-absent = any + +[Element Int Mic] +required-absent = any + +[Element Int Mic Boost] +required-absent = any + +[Element Internal Mic] +required-absent = any + +[Element Internal Mic Boost] +required-absent = any + +[Element Rear Mic] +required-absent = any + +[Element Rear Mic Boost] +required-absent = any + +[Element Line] +required-absent = any + +[Element Aux] +required-absent = any + +[Element Video] +required-absent = any + +[Element Mic/Line] +required-absent = any + +[Element TV Tuner] +required-absent = any + +[Element FM] +required-absent = any + +.include analog-input.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-input.conf.common b/src/modules/alsa/mixer/paths/analog-input.conf.common new file mode 100644 index 00000000..94165776 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf.common @@ -0,0 +1,290 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Mixer path for PulseAudio's ALSA backend, common elements for all +; input paths. If multiple options by the same id are discovered they +; will be suffixed with a number to distuingish them, in the same +; order they appear here. +; +; Source selection should use the following names: +; +; input -- If we don't know the exact kind of input +; input-microphone +; input-microphone-internal +; input-microphone-external +; input-linein +; input-video +; input-radio +; input-docking-microphone +; input-docking-linein +; input-docking +; +; We explicitly don't want to wrap the following sources: +; +; CD +; Synth/MIDI +; Phone +; Mix +; Digital/SPDIF +; Master +; PC Speaker +; +; See analog-output.conf.common for an explanation on the directives + +;;; 'Input Source Select' + +[Element Input Source Select] +enumeration = select + +[Option Input Source Select:Input1] +name = input +priority = 10 + +[Option Input Source Select:Input2] +name = input +priority = 5 + +;;; 'Input Source' + +[Element Input Source] +enumeration = select + +[Option Input Source:Digital Mic] +name = input-microphone +priority = 20 + +[Option Input Source:Microphone] +name = input-microphone +priority = 20 + +[Option Input Source:Front Microphone] +name = input-microphone +priority = 19 + +[Option Input Source:Internal Mic 1] +name = input-microphone +priority = 19 + +[Option Input Source:Line-In] +name = input-linein +priority = 18 + +[Option Input Source:Line In] +name = input-linein +priority = 18 + +[Option Input Source:Docking-Station] +name = input-docking +priority = 17 + +[Option Input Source:AUX IN] +name = input +priority = 10 + +;;; 'Capture Source' + +[Element Capture Source] +enumeration = select + +[Option Capture Source:TV Tuner] +name = input-video + +[Option Capture Source:FM] +name = input-radio + +[Option Capture Source:Mic/Line] +name = input + +[Option Capture Source:Line/Mic] +name = input + +[Option Capture Source:Microphone] +name = input-microphone + +[Option Capture Source:Int DMic] +name = input-microphone-internal + +[Option Capture Source:iMic] +name = input-microphone-internal + +[Option Capture Source:i-Mic] +name = input-microphone-internal + +[Option Capture Source:Internal Microphone] +name = input-microphone-internal + +[Option Capture Source:Front Microphone] +name = input-microphone + +[Option Capture Source:Mic1] +name = input-microphone + +[Option Capture Source:Mic2] +name = input-microphone + +[Option Capture Source:D-Mic] +name = input-microphone + +[Option Capture Source:IntMic] +name = input-microphone-internal + +[Option Capture Source:ExtMic] +name = input-microphone-external + +[Option Capture Source:Ext Mic] +name = input-microphone-external + +[Option Capture Source:E-Mic] +name = input-microphone-external + +[Option Capture Source:e-Mic] +name = input-microphone-external + +[Option Capture Source:LineIn] +name = input-linein + +[Option Capture Source:Analog] +name = input + +[Option Capture Source:Line-In] +name = input-linein + +[Option Capture Source:Line In] +name = input-linein + +[Option Capture Source:Video] +name = input-video + +[Option Capture Source:Aux] +name = input + +[Option Capture Source:Aux0] +name = input + +[Option Capture Source:Aux1] +name = input + +[Option Capture Source:Aux2] +name = input + +[Option Capture Source:Aux3] +name = input + +[Option Capture Source:AUX IN] +name = input + +[Option Capture Source:Aux In] +name = input + +[Option Capture Source:AOUT] +name = input + +[Option Capture Source:AUX] +name = input + +[Option Capture Source:Cam Mic] +name = input-microphone + +[Option Capture Source:Digital Mic] +name = input-microphone + +[Option Capture Source:Digital Mic 1] +name = input-microphone + +[Option Capture Source:Digital Mic 2] +name = input-microphone + +[Option Capture Source:Analog Inputs] +name = input + +[Option Capture Source:Unknown1] +name = input + +[Option Capture Source:Unknown2] +name = input + +[Option Capture Source:Docking-Station] +name = input-docking + +;;; 'Mic Jack Mode' + +[Element Mic Jack Mode] +enumeration = select + +[Option Mic Jack Mode:Mic In] +name = input-microphone + +[Option Mic Jack Mode:Line In] +name = input-linein + +;;; 'Digital Input Source' + +[Element Digital Input Source] +enumeration = select + +[Option Digital Input Source:Analog Inputs] +name = input + +[Option Digital Input Source:Digital Mic 1] +name = input-microphone + +[Option Digital Input Source:Digital Mic 2] +name = input-microphone + +;;; 'Analog Source' + +[Element Analog Source] +enumeration = select + +[Option Analog Source:Mic] +name = input-microphone + +[Option Analog Source:Line in] +name = input-linein + +[Option Analog Source:Aux] +name = input + +;;; 'Shared Mic/Line in' + +[Element Shared Mic/Line in] +enumeration = select + +[Option Shared Mic/Line in:Mic in] +name = input-microphone + +[Option Shared Mic/Line in:Line in] +name = input-linein + +;;; Various Boosts + +[Element Capture Boost] +switch = select + +[Option Capture Boost:on] +name = input-boost-on + +[Option Capture Boost:off] +name = input-boost-off + +[Element Auto Gain Control] +switch = select + +[Option Auto Gain Control:on] +name = input-agc-on + +[Option Auto Gain Control:off] +name = input-agc-off diff --git a/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf new file mode 100644 index 00000000..dfdecf41 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf @@ -0,0 +1,99 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Desktop Speaker' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 101 +name = analog-output-speaker + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the desktop speaker, not +; the headphones. But it should not hurt if we leave the headphone +; jack enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +switch = off +volume = off + +[Element Desktop Speaker] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Front] +switch = mute +volume = merge +override-map.1 = all-front +override-map.2 = front-left,front-right + +[Element Rear] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Surround] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Side] +switch = mute +volume = merge +override-map.1 = all-side +override-map.2 = side-left,side-right + +[Element Center] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,all-center + +[Element LFE] +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf new file mode 100644 index 00000000..e47543f5 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf @@ -0,0 +1,87 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Headphone2' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 89 +name = analog-output-headphones + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the second headphones, not +; the first headphones. But it should not hurt if we leave the +; headphone jack enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Speaker] +switch = off +volume = off + +[Element Desktop Speaker] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Surround] +switch = off +volume = off + +[Element Side] +switch = off +volume = off + +[Element Center] +switch = off +volume = off + +[Element LFE] +switch = off +volume = off + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf new file mode 100644 index 00000000..1d7bb0ba --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -0,0 +1,87 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Headphone' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 90 +name = analog-output-headphones + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +[Element Headphone] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +; This profile path is intended to control the first headphones, not +; the second headphones. But it should not hurt if we leave the second +; headphone jack enabled nonetheless. +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +switch = off +volume = off + +[Element Desktop Speaker] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Surround] +switch = off +volume = off + +[Element Side] +switch = off +volume = off + +[Element Center] +switch = off +volume = off + +[Element LFE] +switch = off +volume = off + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf new file mode 100644 index 00000000..67ee32f7 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf @@ -0,0 +1,89 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Intended for usage in laptops that have a seperate LFE speaker +; connected to the Master mono connector +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 40 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all-no-lfe +override-map.2 = all-left,all-right + +[Element Master Mono] +required = any +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Desktop Speaker] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Surround] +switch = off +volume = off + +[Element Side] +switch = off +volume = off + +[Element Center] +switch = off +volume = off + +[Element LFE] +switch = off +volume = off + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf new file mode 100644 index 00000000..13a2d6aa --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf @@ -0,0 +1,86 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Intended for usage on boards that have a seperate Mono output plug. +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 50 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = off +volume = off + +[Element Master Mono] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Desktop Speaker] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Surround] +switch = off +volume = off + +[Element Side] +switch = off +volume = off + +[Element Center] +switch = off +volume = off + +[Element LFE] +switch = off +volume = off + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf new file mode 100644 index 00000000..c6916d6b --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf @@ -0,0 +1,99 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Path for mixers that have a 'Speaker' control +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 100 +name = analog-output-speaker + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the speaker, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Desktop Speaker] +switch = off +volume = off + +[Element Front] +switch = mute +volume = merge +override-map.1 = all-front +override-map.2 = front-left,front-right + +[Element Rear] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Surround] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Side] +switch = mute +volume = merge +override-map.1 = all-side +override-map.2 = side-left,side-right + +[Element Center] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,all-center + +[Element LFE] +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf new file mode 100644 index 00000000..50fc88ea --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf @@ -0,0 +1,96 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Intended for the 'default' output. Note that a-o-speaker.conf has a +; higher priority than this +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 99 + +[Element Hardware Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Master Mono] +switch = off +volume = off + +; This profile path is intended to control the default output, not the +; headphones. But it should not hurt if we leave the headphone jack +; enabled nonetheless. +[Element Headphone] +switch = mute +volume = zero + +[Element Headphone2] +switch = mute +volume = zero + +[Element Speaker] +switch = mute +volume = off + +[Element Desktop Speaker] +switch = mute +volume = off + +[Element Front] +switch = mute +volume = merge +override-map.1 = all-front +override-map.2 = front-left,front-right + +[Element Rear] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Surround] +switch = mute +volume = merge +override-map.1 = all-rear +override-map.2 = rear-left,rear-right + +[Element Side] +switch = mute +volume = merge +override-map.1 = all-side +override-map.2 = side-left,side-right + +[Element Center] +switch = mute +volume = merge +override-map.1 = all-center +override-map.2 = all-center,all-center + +[Element LFE] +switch = mute +volume = merge +override-map.1 = lfe +override-map.2 = lfe,lfe + +.include analog-output.conf.common diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common new file mode 100644 index 00000000..ccaa494b --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf.common @@ -0,0 +1,147 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Common part of all paths + +; So here's generally how mixer paths are used by PA: PA goes through +; a mixer path file from top to bottom and checks if a mixer element +; described therein exists. If so it is added to the list of mixer +; elements PA will control, keeping the order it read them in. If a +; mixer element described here has set the required= or +; required-absent= directives a path might not be accepted as valid +; and is ignored in its entirety (see below). However usually if a +; element listed here is missing this one element is ignored but not +; the entire path. +; +; When a device shall be muted/unmuted *all* elements listed in a path +; file with "switch = mute" will be toggled. +; +; When a device shall change its volume, PA will got through the list +; of all elements with "volume = merge" and set the volume on the +; first element. If that element does not support dB volumes, this is +; where the story ends. If it does support dB volumes, PA divides the +; requested volume by the volume that was set on this element, and +; then go on to the next element with "volume = merge" and then set +; that there, and so on. That way the first volume element in the +; path will be the one that does the 'biggest' part of the overall +; volume adjustment, with the remaining elements usually being set to +; some value next to 0dB. This logic makes sure we get the full range +; over all volume sliders and a very high granularity of volumes +; already in hardware. +; +; All switches and enumerations set to "select" are exposed via the +; "port" functionality of sinks/sources. Basically every possible +; switch setting and every possible enumeration setting will be +; combined and made into a "port". So make sure you don't list too +; many switches/enums for exposing, because the number of ports might +; rise exponentially. +; +; Only one path can be selected at a time. All paths that are valid +; for an audio device will be exposed as "port" for the sink/source. + + +; [General] +; priority = ... # Priority for this path +; description = ... +; +; [Option ...:...] # For each option of an enumeration or switch element +; # that shall be exposed as a sink/source port. Needs to +; # be named after the Element, followed by a colon, followed +; # by the option name, resp. on/off if the element is a switch. +; name = ... # Logical name to use in the path identifier +; priority = ... # Priority if this is made into a device port +; required = ignore | enumeration | any # In this element, this option must exist or the path will be invalid. ("any" is an alias for "enumeration".) +; required-any = ignore | enumeration | any # In this element, either this or another option must exist (or an element) +; required-absent = ignore | enumeration | any # In this element, this option must not exist or the path will be invalid +; +; [Element ...] # For each element that we shall control +; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available, +; # otherwise don't consider this path valid for the card +; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements with required-any in this +; # path must be present, otherwise this path is invalid for the card +; required-absent = ignore | switch | volume # If set, require this element to not be of this kind and not +; # available, otherwise don't consider this path valid for the card +; +; switch = ignore | mute | off | on | select # What to do with this switch: ignore it, make it follow mute status, +; # always set it to off, always to on, or make it selectable as port. +; # If set to 'select' you need to define an Option section for on +; # and off +; volume = ignore | merge | off | zero | <volume step> # What to do with this volume: ignore it, merge it into the device +; # volume slider, always set it to the lowest value possible, or always +; # set it to 0 dB (for whatever that means), or always set it to +; # <volume step> (this only makes sense in path configurations where +; # the exact hardware and driver are known beforehand). +; volume-limit = <volume step> # Limit the maximum volume by disabling the volume steps above <volume step>. +; enumeration = ignore | select # What to do with this enumeration, ignore it or make it selectable +; # via device ports. If set to 'select' you need to define an Option section +; # for each of the items you want to expose +; direction = playback | capture # Is this relevant only for playback or capture? If not set this will implicitly be +; # set the direction of the PCM device is opened as. Generally this doesn't need to be set +; # unless you have a broken driver that has playback controls marked for capture or vice +; # versa +; direction-try-other = no | yes # If the element does not supported what is requested, try the other direction, too? +; +; override-map.1 = ... # Override the channel mask of the mixer control if the control only exposes a single channel +; override-map.2 = ... # Override the channel masks of the mixer control if the control only exposes two channels +; # Override maps should list for each element channel which high-level channels it controls via a +; # channel mask. A channel mask may either be the name of a single channel, or the words "all-left", +; # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of +; # channels in a mask + +[Element PCM] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element External Amplifier] +switch = select + +[Option External Amplifier:on] +name = output-amplifier-on +priority = 10 + +[Option External Amplifier:off] +name = output-amplifier-off +priority = 0 + +[Element Bass Boost] +switch = select + +[Option Bass Boost:on] +name = output-bass-boost-on +priority = 0 + +[Option Bass Boost:off] +name = output-bass-boost-off +priority = 10 + +;;; 'Analog Output' + +[Element Analog Output] +enumeration = select + +[Option Analog Output:Speakers] +name = output-speaker +priority = 10 + +[Option Analog Output:Headphones] +name = output-headphones +priority = 9 + +[Option Analog Output:FP Headphones] +name = output-headphones +priority = 8 diff --git a/src/modules/alsa/mixer/paths/iec958-stereo-output.conf b/src/modules/alsa/mixer/paths/iec958-stereo-output.conf new file mode 100644 index 00000000..8506a580 --- /dev/null +++ b/src/modules/alsa/mixer/paths/iec958-stereo-output.conf @@ -0,0 +1,19 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + + +[Element IEC958] +switch = mute diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules new file mode 100644 index 00000000..e1da3314 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules @@ -0,0 +1,40 @@ +# do not edit this file, it will be overwritten on update + +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +SUBSYSTEM!="sound", GOTO="pulseaudio_end" +ACTION!="change", GOTO="pulseaudio_end" +KERNEL!="card*", GOTO="pulseaudio_end" + +# Some specific work arounds until we can handle heasets/handsets properly (i.e. "Speaker" only, no "master") +SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="01ab", ENV{PULSE_PROFILE_SET}="usb-headset.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a0c", ENV{PULSE_PROFILE_SET}="usb-headset.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0002", ENV{PULSE_PROFILE_SET}="usb-headset.conf" +# UAC1.0 Sennheiser Dongle +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="3554", ENV{PULSE_PROFILE_SET}="usb-headset.conf" +# BT Agile Handset +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1885", ATTRS{idProduct}=="0501", ENV{PULSE_PROFILE_SET}="usb-headset.conf" + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{PULSE_PROFILE_SET}="native-instruments-korecontroller.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio10.conf" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf" + +LABEL="pulseaudio_end" diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf new file mode 100644 index 00000000..283edfb3 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -0,0 +1,180 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Default profile definitions for the ALSA backend of PulseAudio. This +; is used as fallback for all cards that have no special mapping +; assigned (and should be good enough for the vast majority of +; cards). If you want to assign a different profile set than this one +; to a device, either set the udev property PULSE_PROFILE_SET for the +; card, or use the "profile_set" module argument when loading +; module-alsa-card. +; +; So what is this about? Simply, what we do here is map ALSA devices +; to how they are exposed in PA. We say which ALSA device string to +; use to open a device, which channel mapping to use then, and which +; mixer path to use. This is encoded in a 'mapping'. Multiple of these +; mappings can be bound together in a 'profile' which is then directly +; exposed in the UI as a card profile. Each mapping assigned to a +; profile will result in one sink/source to be created if the profile +; is selected for the card. +; +; Additionally, the path set configuration files can describe the +; decibel values assigned to the steps of the volume elements. This +; can be used to work around situations when the alsa driver doesn't +; provide any decibel information, or when the information is +; incorrect. + + +; [General] +; auto-profiles = no | yes # Instead of defining all profiles manually, autogenerate +; # them by combining every input mapping with every output mapping. +; +; [Mapping id] +; device-strings = ... # ALSA device string. %f will be replaced by the card identifier. +; channel-map = ... # Channel mapping to use for this device +; description = ... +; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed. +; # If multiple are found to be working they will be available as device ports +; paths-output = ... +; element-input = ... # Instead of configuring a full mixer path simply configure a single +; # mixer element for volume/mute handling +; element-output = ... +; priority = ... +; direction = any | input | output # Only useful for? +; +; [Profile id] +; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be +; # defined in this file too +; output-mappings = ... # Lists mappings for sinks on this profile, those mappings must be +; # defined in this file too +; description = ... +; priority = ... # Numeric value to deduce priority for this profile +; skip-probe = no | yes # Skip probing for availability? If this is yes then this profile +; # will be assumed as working without probing. Makes initialization +; # a bit faster but only works if the card is really known well. +; +; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB +; # information from alsa. A decibel fix is a table that maps volume steps +; # to decibel values for one volume element. The "element" part in the +; # section title is the name of the volume element. +; # +; # NOTE: This feature is meant just as a help for figuring out the correct +; # decibel values. Pulseaudio is not the correct place to maintain the +; # decibel mappings! +; # +; # If you need this feature, then you should make sure that when you have +; # the correct values figured out, the alsa driver developers get informed +; # too, so that they can fix the driver. +; +; db-values = ... # The option value consists of pairs of step numbers and decibel values. +; # The pairs are separated with whitespace, and steps are separated from +; # the corresponding decibel values with a colon. The values must be in an +; # increasing order. Here's an example of a valid string: +; # +; # "0:-40.50 1:-38.70 3:-33.00 11:0" +; # +; # The lowest step imposes a lower limit for hardware volume and the +; # highest step correspondingly imposes a higher limit. That means that +; # that the mixer will never be set outside those values - the rest of the +; # volume scale is done using software volume. +; # +; # As can be seen in the example, you don't need to specify a dB value for +; # each step. The dB values for skipped steps will be linearly interpolated +; # using the nearest steps that are given. + +[General] +auto-profiles = yes + +[Mapping analog-mono] +device-strings = hw:%f +channel-map = mono +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono +paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line +priority = 1 + +[Mapping analog-stereo] +device-strings = front:%f hw:%f +channel-map = left,right +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono +paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line +priority = 10 + +[Mapping analog-surround-40] +device-strings = surround40:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono +priority = 7 +direction = output + +[Mapping analog-surround-41] +device-strings = surround41:%f +channel-map = front-left,front-right,rear-left,rear-right,lfe +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono +priority = 8 +direction = output + +[Mapping analog-surround-50] +device-strings = surround50:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono +priority = 7 +direction = output + +[Mapping analog-surround-51] +device-strings = surround51:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono +priority = 8 +direction = output + +[Mapping analog-surround-71] +device-strings = surround71:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right +description = Analog Surround 7.1 +paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono +priority = 7 +direction = output + +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +paths-input = iec958-stereo-input +paths-output = iec958-stereo-output +priority = 5 + +[Mapping iec958-ac3-surround-40] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right +priority = 2 +direction = output + +[Mapping iec958-ac3-surround-51] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 3 +direction = output + +[Mapping hdmi-stereo] +device-strings = hdmi:%f +channel-map = left,right +priority = 4 +direction = output + +; An example for defining multiple-sink profiles +#[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] +#description = Foobar +#output-mappings = analog-stereo iec958-stereo +#input-mappings = analog-stereo diff --git a/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf b/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf new file mode 100644 index 00000000..75f51121 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf @@ -0,0 +1,85 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; M-Audio FastTrack Pro +; +; This card has one duplex stereo channel called A and an additional +; stereo output channel called B. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-a-output] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right +direction = output + +[Mapping analog-stereo-a-input] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right +direction = input + +[Mapping analog-stereo-b-output] +description = Analog Stereo Channel B +device-strings = hw:%f,1,0 +channel-map = left,right +direction = output + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channel A, Analog Stereo output Channel B +output-mappings = analog-stereo-a-output analog-stereo-b-output +input-mappings = analog-stereo-a-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-a-output+input:analog-stereo-a-input] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-a-output +input-mappings = analog-stereo-a-input +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Output Channel B +output-mappings = analog-stereo-b-output +input-mappings = +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-a] +description = Analog Stereo Output Channel A +output-mappings = analog-stereo-a-output +priority = 5 +skip-probe = yes + +[Profile output:analog-stereo-b] +description = Analog Stereo Output Channel B +output-mappings = analog-stereo-b-output +priority = 6 +skip-probe = yes + +[Profile input:analog-stereo-a] +description = Analog Stereo Input Channel A +input-mappings = analog-stereo-a-input +priority = 2 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf new file mode 100644 index 00000000..2b835308 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf @@ -0,0 +1,91 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Audio 4 DJ +; +; This card has two stereo pairs of input and two stereo pairs of +; output, named channels A and B. Channel B has an additional +; Headphone connector. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-b-output] +description = Analog Stereo Channel B (Headphones) +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-b-input] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels A, B (Headphones) +output-mappings = analog-stereo-a analog-stereo-b-output +input-mappings = analog-stereo-a analog-stereo-b-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-a +input-mappings = analog-stereo-a +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B (Headphones) +output-mappings = analog-stereo-b-output +input-mappings = analog-stereo-b-input +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-a] +description = Analog Stereo Output Channel A +output-mappings = analog-stereo-a +priority = 5 +skip-probe = yes + +[Profile output:analog-stereo-b] +description = Analog Stereo Output Channel B (Headphones) +output-mappings = analog-stereo-b-output +priority = 6 +skip-probe = yes + +[Profile input:analog-stereo-a] +description = Analog Stereo Input Channel A +input-mappings = analog-stereo-a +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-b] +description = Analog Stereo Input Channel B +input-mappings = analog-stereo-b-input +priority = 1 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf new file mode 100644 index 00000000..3fe3cc56 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf @@ -0,0 +1,162 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Audio 8 DJ +; +; This card has four stereo pairs of input and four stereo pairs of +; output, named channels A to D. Channel C has an additional Mic/Line +; connector, channel D an additional Headphone connector. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right + +# Since we want to set a different description for channel C's/D's input +# and output we define two seperate mappings for them +[Mapping analog-stereo-c-output] +description = Analog Stereo Channel C +device-strings = hw:%f,0,2 +channel-map = left,right +direction = output + +[Mapping analog-stereo-c-input] +description = Analog Stereo Channel C (Line/Mic) +device-strings = hw:%f,0,2 +channel-map = left,right +direction = input + +[Mapping analog-stereo-d-output] +description = Analog Stereo Channel D (Headphones) +device-strings = hw:%f,0,3 +channel-map = left,right +direction = output + +[Mapping analog-stereo-d-input] +description = Analog Stereo Channel D +device-strings = hw:%f,0,3 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels A, B, C (Line/Mic), D (Headphones) +output-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-output analog-stereo-d-output +input-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-input analog-stereo-d-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-d+input:analog-stereo-c] +description = Analog Stereo Channel D (Headphones) Output, Channel C (Line/Mic) Input +output-mappings = analog-stereo-d-output +input-mappings = analog-stereo-c-input +priority = 90 +skip-probe = yes + +[Profile output:analog-stereo-c-d+input:analog-stereo-c-d] +description = Analog Stereo Duplex Channels C (Line/Mic), D (Line/Mic) +output-mappings = analog-stereo-c-output analog-stereo-d-output +input-mappings = analog-stereo-c-input analog-stereo-d-input +priority = 80 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-a +input-mappings = analog-stereo-a +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B +output-mappings = analog-stereo-b +input-mappings = analog-stereo-b +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-c+input:analog-stereo-c] +description = Analog Stereo Duplex Channel C (Line/Mic) +output-mappings = analog-stereo-c-output +input-mappings = analog-stereo-c-input +priority = 60 +skip-probe = yes + +[Profile output:analog-stereo-d+input:analog-stereo-d] +description = Analog Stereo Duplex Channel D (Headphones) +output-mappings = analog-stereo-d-output +input-mappings = analog-stereo-d-input +priority = 70 +skip-probe = yes + +[Profile output:analog-stereo-a] +description = Analog Stereo Output Channel A +output-mappings = analog-stereo-a +priority = 6 +skip-probe = yes + +[Profile output:analog-stereo-b] +description = Analog Stereo Output Channel B +output-mappings = analog-stereo-b +priority = 5 +skip-probe = yes + +[Profile output:analog-stereo-c] +description = Analog Stereo Output Channel C +output-mappings = analog-stereo-c-output +priority = 7 +skip-probe = yes + +[Profile output:analog-stereo-d] +description = Analog Stereo Output Channel D (Headphones) +output-mappings = analog-stereo-d-output +priority = 8 +skip-probe = yes + +[Profile input:analog-stereo-a] +description = Analog Stereo Input Channel A +input-mappings = analog-stereo-a +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-b] +description = Analog Stereo Input Channel B +input-mappings = analog-stereo-b +priority = 1 +skip-probe = yes + +[Profile input:analog-stereo-c] +description = Analog Stereo Input Channel C (Line/Mic) +input-mappings = analog-stereo-c-input +priority = 4 +skip-probe = yes + +[Profile input:analog-stereo-d] +description = Analog Stereo Input Channel D +input-mappings = analog-stereo-d-input +priority = 3 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf new file mode 100644 index 00000000..904357d0 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf @@ -0,0 +1,85 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Kore Controller +; +; This card has one stereo pairs of input and two stereo pairs of +; output, named "Master" and "Headphone". The master channel has +; an additional Coax S/PDIF connector which is always on. +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-master-out] +description = Analog Stereo Master Channel +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-headphone-out] +description = Analog Stereo Headphone Channel +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-input] +description = Analog Stereo +device-strings = hw:%f,0,0 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Master Output, Headphones Output +output-mappings = analog-stereo-master-out analog-stereo-headphone-out +input-mappings = analog-stereo-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-master+input:analog-stereo-input] +description = Analog Stereo Duplex Master Output +output-mappings = analog-stereo-master-out +input-mappings = analog-stereo-input +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-headphone-out+input:analog-stereo-input] +description = Analog Stereo Headphones Output +output-mappings = analog-stereo-headphone-out +input-mappings = analog-stereo-input +priority = 30 +skip-probe = yes + +[Profile output:analog-stereo-master] +description = Analog Stereo Master Output +output-mappings = analog-stereo-master-out +priority = 3 +skip-probe = yes + +[Profile output:analog-stereo-headphone] +description = Analog Stereo Headphones Output +output-mappings = analog-stereo-headphone-out +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-input] +description = Analog Stereo Input +input-mappings = analog-stereo-input +priority = 1 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf new file mode 100644 index 00000000..4deb65da --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf @@ -0,0 +1,131 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Audio 10 DJ +; +; This card has five stereo pairs of input and five stereo pairs of +; output +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-out-main] +description = Analog Stereo Main +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-out-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-out-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-out-c] +description = Analog Stereo Channel C +device-strings = hw:%f,0,2 +channel-map = left,right +direction = output + +[Mapping analog-stereo-out-d] +description = Analog Stereo Channel D +device-strings = hw:%f,0,3 +channel-map = left,right +direction = output + +[Mapping analog-stereo-in-main] +description = Analog Stereo Main +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-in-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Mapping analog-stereo-in-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Mapping analog-stereo-in-c] +description = Analog Stereo Channel C +device-strings = hw:%f,0,2 +channel-map = left,right +direction = input + +[Mapping analog-stereo-in-d] +description = Analog Stereo Channel D +device-strings = hw:%f,0,3 +channel-map = left,right +direction = input + + + + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels Main, A, B, C, D +output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b analog-stereo-out-c analog-stereo-out-d +input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b analog-stereo-in-c analog-stereo-in-d +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-main+input:analog-stereo-main] +description = Analog Stereo Duplex Main +output-mappings = analog-stereo-out-main +input-mappings = analog-stereo-in-main +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-out-a +input-mappings = analog-stereo-in-a +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B +output-mappings = analog-stereo-out-b +input-mappings = analog-stereo-in-b +priority = 30 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-c] +description = Analog Stereo Duplex Channel C +output-mappings = analog-stereo-out-c +input-mappings = analog-stereo-in-c +priority = 20 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-d] +description = Analog Stereo Duplex Channel D +output-mappings = analog-stereo-out-d +input-mappings = analog-stereo-in-d +priority = 10 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf new file mode 100644 index 00000000..48d9058b --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf @@ -0,0 +1,92 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Audio 6 DJ +; +; This card has three stereo pairs of input and three stereo pairs of +; output +; +; We knowingly only define a subset of the theoretically possible +; mapping combinations as profiles here. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-out-main] +description = Analog Stereo Main +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-out-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-out-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-in-main] +description = Analog Stereo Main +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-in-a] +description = Analog Stereo Channel A +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Mapping analog-stereo-in-b] +description = Analog Stereo Channel B +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + + + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex Channels A, B (Headphones) +output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b +input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-main+input:analog-stereo-main] +description = Analog Stereo Duplex Channel Main +output-mappings = analog-stereo-out-main +input-mappings = analog-stereo-in-main +priority = 50 +skip-probe = yes + +[Profile output:analog-stereo-a+input:analog-stereo-a] +description = Analog Stereo Duplex Channel A +output-mappings = analog-stereo-out-a +input-mappings = analog-stereo-in-a +priority = 40 +skip-probe = yes + +[Profile output:analog-stereo-b+input:analog-stereo-b] +description = Analog Stereo Duplex Channel B +output-mappings = analog-stereo-out-b +input-mappings = analog-stereo-in-b +priority = 30 +skip-probe = yes diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf new file mode 100644 index 00000000..1da843a1 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf @@ -0,0 +1,81 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; Native Instruments Traktor Kontrol S4 +; +; This controller has two stereo pairs of input (named "Channel C" and +; "Channel D") and two stereo pairs of output, one "Main Out" and +; "Headphone Out". +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = no + +[Mapping analog-stereo-output-main] +description = Analog Stereo Main Out +device-strings = hw:%f,0,0 +channel-map = left,right + +[Mapping analog-stereo-output-headphone] +description = Analog Stereo Headphones Out +device-strings = hw:%f,0,1 +channel-map = left,right +direction = output + +[Mapping analog-stereo-c-input] +description = Analog Stereo Channel C +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Mapping analog-stereo-d-input] +description = Analog Stereo Channel D +device-strings = hw:%f,0,1 +channel-map = left,right +direction = input + +[Profile output:analog-stereo-all+input:analog-stereo-all] +description = Analog Stereo Duplex +output-mappings = analog-stereo-output-main analog-stereo-output-headphone +input-mappings = analog-stereo-c-input analog-stereo-d-input +priority = 100 +skip-probe = yes + +[Profile output:analog-stereo-main] +description = Analog Stereo Main Output +output-mappings = analog-stereo-output-main +priority = 4 +skip-probe = yes + +[Profile output:analog-stereo-headphone] +description = Analog Stereo Output Headphones Out +output-mappings = analog-stereo-output-headphone +priority = 3 +skip-probe = yes + +[Profile input:analog-stereo-c] +description = Analog Stereo Input Channel C +input-mappings = analog-stereo-c-input +priority = 2 +skip-probe = yes + +[Profile input:analog-stereo-d] +description = Analog Stereo Input Channel D +input-mappings = analog-stereo-d-input +priority = 1 +skip-probe = yes + diff --git a/src/modules/alsa/mixer/profile-sets/usb-headset.conf b/src/modules/alsa/mixer/profile-sets/usb-headset.conf new file mode 100644 index 00000000..adf78d17 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/usb-headset.conf @@ -0,0 +1,35 @@ +# This file is part of PulseAudio. +# +# PulseAudio 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. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +; This is a workaround - these usb headsets have one output volume control only, labeled "Speaker". +; This causes the default profile set to not control the volume at all, which is a bug. + +[General] +auto-profiles = yes + +[Mapping analog-mono] +device-strings = hw:%f +channel-map = mono +paths-output = analog-output-speaker +paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line +priority = 1 + +[Mapping analog-stereo] +device-strings = front:%f hw:%f +channel-map = left,right +paths-output = analog-output-speaker +paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line +priority = 10 diff --git a/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 new file mode 100644 index 00000000..082c9a1b --- /dev/null +++ b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 @@ -0,0 +1,150 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 29 [94%] [-3.00dB] [on] + Front Right: Playback 29 [94%] [-3.00dB] [on] +Simple mixer control 'Master Mono',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 23 [74%] [0.00dB] [on] + Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Surround',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-46.50dB] [off] + Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Surround Jack Mode',0 + Capabilities: enum + Items: 'Shared' 'Independent' + Item0: 'Shared' +Simple mixer control 'Center',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'LFE',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [on] + Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Mic Select',0 + Capabilities: enum + Items: 'Mic1' 'Mic2' + Item0: 'Mic1' +Simple mixer control 'Video',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Phone',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 31 [100%] [12.00dB] [off] + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined cswitch cswitch-joined + Playback channels: Mono + Capture channels: Mono + Mono: Playback [off] Capture [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 3 + Mono: 0 [0%] +Simple mixer control 'IEC958 Playback Source',0 + Capabilities: enum + Items: 'PCM' 'Analog In' 'IEC958 In' + Item0: 'PCM' +Simple mixer control 'PC Speaker',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 15 + Mono: Playback 0 [0%] [-45.00dB] [on] +Simple mixer control 'Aux',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [on] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [on] Capture [off] +Simple mixer control 'Mono Output Select',0 + Capabilities: enum + Items: 'Mix' 'Mic' + Item0: 'Mix' +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch cswitch-joined + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 12 [80%] [18.00dB] [on] + Front Right: Capture 12 [80%] [18.00dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 + Capabilities: enum + Items: '2ch' '4ch' '6ch' + Item0: '2ch' +Simple mixer control 'Duplicate Front',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'External Amplifier',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x new file mode 100644 index 00000000..b8f61fab --- /dev/null +++ b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x @@ -0,0 +1,24 @@ +Simple mixer control 'FM',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [off] +Simple mixer control 'Mic/Line',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [off] +Simple mixer control 'Capture',0 + Capabilities: cvolume cvolume-joined + Capture channels: Mono + Limits: Capture 0 - 15 + Mono: Capture 13 [87%] +Simple mixer control 'Capture Boost',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] +Simple mixer control 'TV Tuner',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [on] diff --git a/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 new file mode 100644 index 00000000..a500a817 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 @@ -0,0 +1,135 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 63 + Mono: + Front Left: Playback 63 [100%] [0.00dB] [on] + Front Right: Playback 63 [100%] [0.00dB] [on] +Simple mixer control 'Master Mono',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Headphone',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-46.50dB] [off] + Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control '3D Control - Center',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 15 + Mono: 0 [0%] +Simple mixer control '3D Control - Depth',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 15 + Mono: 0 [0%] +Simple mixer control '3D Control - Switch',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 23 [74%] [0.00dB] [on] + Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 23 [74%] [0.00dB] [on] + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mic Boost (+20dB)',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Mic Select',0 + Capabilities: enum + Items: 'Mic1' 'Mic2' + Item0: 'Mic1' +Simple mixer control 'Video',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Phone',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'PC Speaker',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 15 + Mono: Playback 0 [0%] [-45.00dB] [off] +Simple mixer control 'Aux',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 + Capabilities: enum + Items: 'Mix' 'Mic' + Item0: 'Mic' +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch cswitch-joined + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 15 [100%] [22.50dB] [on] + Front Right: Capture 15 [100%] [22.50dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'External Amplifier',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] diff --git a/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI new file mode 100644 index 00000000..244f24a8 --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI @@ -0,0 +1,4 @@ +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 new file mode 100644 index 00000000..165522fa --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 @@ -0,0 +1,62 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 63 + Mono: + Front Left: Playback 63 [100%] [3.00dB] [on] + Front Right: Playback 63 [100%] [3.00dB] [on] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 23 [74%] [0.00dB] [on] + Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Mono + Limits: Playback 0 - 31 + Mono: Capture [off] + Front Left: Playback 0 [0%] [-34.50dB] [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Mono + Limits: Playback 0 - 31 + Mono: Capture [on] + Front Left: Playback 0 [0%] [-34.50dB] [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic Boost',0 + Capabilities: volume + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: 0 - 3 + Front Left: 0 [0%] + Front Right: 0 [0%] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Default PCM',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Playback Source',0 + Capabilities: enum + Items: 'PCM' 'ADC' + Item0: 'PCM' +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 0 [0%] [0.00dB] [on] + Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [off] diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A new file mode 100644 index 00000000..28a2e73c --- /dev/null +++ b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A @@ -0,0 +1,113 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 64 + Mono: Playback 64 [100%] [0.00dB] [on] +Simple mixer control 'Headphone',0 + Capabilities: pswitch + Playback channels: Front Left - Front Right + Mono: + Front Left: Playback [on] + Front Right: Playback [on] +Simple mixer control 'PCM',0 + Capabilities: pvolume + Playback channels: Front Left - Front Right + Limits: Playback 0 - 255 + Mono: + Front Left: Playback 255 [100%] [0.00dB] + Front Right: Playback 255 [100%] [0.00dB] +Simple mixer control 'Front',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 64 + Mono: + Front Left: Playback 44 [69%] [-20.00dB] [on] + Front Right: Playback 44 [69%] [-20.00dB] [on] +Simple mixer control 'Front Mic',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-34.50dB] [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Front Mic Boost',0 + Capabilities: volume + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: 0 - 3 + Front Left: 0 [0%] + Front Right: 0 [0%] +Simple mixer control 'Surround',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 64 + Mono: + Front Left: Playback 0 [0%] [-64.00dB] [on] + Front Right: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Center',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 64 + Mono: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'LFE',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 64 + Mono: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Side',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 64 + Mono: + Front Left: Playback 0 [0%] [-64.00dB] [on] + Front Right: Playback 0 [0%] [-64.00dB] [on] +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-34.50dB] [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-34.50dB] [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] +Simple mixer control 'Mic Boost',0 + Capabilities: volume + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: 0 - 3 + Front Left: 0 [0%] + Front Right: 0 [0%] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined cswitch cswitch-joined + Playback channels: Mono + Capture channels: Mono + Mono: Playback [on] Capture [on] +Simple mixer control 'IEC958 Default PCM',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch + Capture channels: Front Left - Front Right + Limits: Capture 0 - 46 + Front Left: Capture 23 [50%] [7.00dB] [on] + Front Right: Capture 23 [50%] [7.00dB] [on] +Simple mixer control 'Capture',1 + Capabilities: cvolume cswitch + Capture channels: Front Left - Front Right + Limits: Capture 0 - 46 + Front Left: Capture 0 [0%] [-16.00dB] [off] + Front Right: Capture 0 [0%] [-16.00dB] [off] +Simple mixer control 'Input Source',0 + Capabilities: cenum + Items: 'Mic' 'Front Mic' 'Line' + Item0: 'Mic' +Simple mixer control 'Input Source',1 + Capabilities: cenum + Items: 'Mic' 'Front Mic' 'Line' + Item0: 'Mic' diff --git a/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A new file mode 100644 index 00000000..3ddd8af6 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A @@ -0,0 +1,128 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 63 + Mono: + Front Left: Playback 44 [70%] [-28.50dB] [on] + Front Right: Playback 60 [95%] [-4.50dB] [on] +Simple mixer control 'Master Mono',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 17 [55%] [-21.00dB] [on] +Simple mixer control '3D Control - Center',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 15 + Mono: 0 [0%] +Simple mixer control '3D Control - Depth',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 15 + Mono: 0 [0%] +Simple mixer control '3D Control - Switch',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 9 [29%] [-21.00dB] [on] + Front Right: Playback 9 [29%] [-21.00dB] [on] +Simple mixer control 'PCM Out Path & Mute',0 + Capabilities: enum + Items: 'pre 3D' 'post 3D' + Item0: 'pre 3D' +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 9 [29%] [-21.00dB] [on] Capture [off] + Front Right: Playback 9 [29%] [-21.00dB] [on] Capture [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [on] + Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Mic Select',0 + Capabilities: enum + Items: 'Mic1' 'Mic2' + Item0: 'Mic1' +Simple mixer control 'Video',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Phone',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'PC Speaker',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 15 + Mono: Playback 8 [53%] [-21.00dB] [on] +Simple mixer control 'Aux',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 + Capabilities: enum + Items: 'Mix' 'Mic' + Item0: 'Mix' +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch cswitch-joined + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 13 [87%] [19.50dB] [on] + Front Right: Capture 13 [87%] [19.50dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'External Amplifier',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer new file mode 100644 index 00000000..38cf6778 --- /dev/null +++ b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer @@ -0,0 +1,27 @@ +Simple mixer control 'Bass',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 48 + Mono: 22 [46%] +Simple mixer control 'Bass Boost',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Treble',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 48 + Mono: 25 [52%] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 44 + Mono: + Front Left: Playback 10 [23%] [-31.00dB] [on] + Front Right: Playback 10 [23%] [-31.00dB] [on] +Simple mixer control 'Auto Gain Control',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] diff --git a/src/modules/alsa/mixer/samples/USB Audio--USB Mixer b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer new file mode 100644 index 00000000..9cb4fa7f --- /dev/null +++ b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer @@ -0,0 +1,37 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 255 + Mono: Playback 105 [41%] [-28.97dB] [on] +Simple mixer control 'Line',0 + Capabilities: pvolume cvolume pswitch pswitch-joined cswitch cswitch-joined + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 255 Capture 0 - 128 + Front Left: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] + Front Right: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pvolume-joined cvolume cvolume-joined pswitch pswitch-joined cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Mono + Limits: Playback 0 - 255 Capture 0 - 128 + Mono: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [on] +Simple mixer control 'Mic Capture',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 In',0 + Capabilities: cswitch cswitch-joined + Capture channels: Mono + Mono: Capture [off] +Simple mixer control 'Input 1',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [off] +Simple mixer control 'Input 2',0 + Capabilities: cswitch cswitch-joined cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Mono + Mono: Capture [off] diff --git a/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer new file mode 100644 index 00000000..783f826f --- /dev/null +++ b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer @@ -0,0 +1,5 @@ +Simple mixer control 'Mic',0 + Capabilities: cvolume cvolume-joined cswitch cswitch-joined + Capture channels: Mono + Limits: Capture 0 - 3072 + Mono: Capture 1536 [50%] [23.00dB] [on] diff --git a/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 new file mode 100644 index 00000000..15e7b5a6 --- /dev/null +++ b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 @@ -0,0 +1,211 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [0.00dB] [on] + Front Right: Playback 31 [100%] [0.00dB] [on] +Simple mixer control 'Master Mono',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Master Surround',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-46.50dB] [off] + Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Headphone Jack Sense',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 23 [74%] [0.00dB] [on] + Front Right: Playback 23 [74%] [0.00dB] [on] +Simple mixer control 'Surround',0 + Capabilities: pvolume pswitch + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-46.50dB] [off] + Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Surround Jack Mode',0 + Capabilities: enum + Items: 'Shared' 'Independent' + Item0: 'Shared' +Simple mixer control 'Center',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 31 [100%] [0.00dB] [off] +Simple mixer control 'LFE',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Line Jack Sense',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [on] + Front Right: Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Mic Select',0 + Capabilities: enum + Items: 'Mic1' 'Mic2' + Item0: 'Mic1' +Simple mixer control 'Video',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Phone',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Mono + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-34.50dB] [off] + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Output',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 3 + Mono: 3 [100%] +Simple mixer control 'IEC958 Playback Source',0 + Capabilities: enum + Items: 'AC-Link' 'A/D Converter' + Item0: 'AC-Link' +Simple mixer control 'Aux',0 + Capabilities: pvolume pswitch cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 0 [0%] [0.00dB] [on] + Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 + Capabilities: enum + Items: '2ch' '4ch' '6ch' + Item0: '2ch' +Simple mixer control 'Downmix',0 + Capabilities: enum + Items: 'Off' '6 -> 4' '6 -> 2' + Item0: 'Off' +Simple mixer control 'Exchange Front/Surround',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'External Amplifier',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] +Simple mixer control 'High Pass Filter Enable',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Input Source Select',0 + Capabilities: enum + Items: 'Input1' 'Input2' + Item0: 'Input1' +Simple mixer control 'Input Source Select',1 + Capabilities: enum + Items: 'Input1' 'Input2' + Item0: 'Input1' +Simple mixer control 'Spread Front to Surround and Center/LFE',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'VIA DXS',0 + Capabilities: pvolume + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [-48.00dB] + Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',1 + Capabilities: pvolume + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [-48.00dB] + Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',2 + Capabilities: pvolume + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [-48.00dB] + Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'VIA DXS',3 + Capabilities: pvolume + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [-48.00dB] + Front Right: Playback 31 [100%] [-48.00dB] +Simple mixer control 'V_REFOUT Enable',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] diff --git a/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ new file mode 100644 index 00000000..d4f3db62 --- /dev/null +++ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ @@ -0,0 +1,160 @@ +Simple mixer control 'Master',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 0 [0%] [-46.50dB] [off] + Front Right: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'PCM',0 + Capabilities: pvolume pswitch pswitch-joined + Playback channels: Front Left - Front Right + Limits: Playback 0 - 31 + Mono: + Front Left: Playback 31 [100%] [-48.00dB] [off] + Front Right: Playback 31 [100%] [-48.00dB] [off] +Simple mixer control 'Surround',0 + Capabilities: pswitch + Playback channels: Front Left - Front Right + Mono: + Front Left: Playback [off] + Front Right: Playback [off] +Simple mixer control 'Surround Jack Mode',0 + Capabilities: enum + Items: 'Shared' 'Independent' + Item0: 'Shared' +Simple mixer control 'Center',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 31 [100%] [0.00dB] [off] +Simple mixer control 'LFE',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 31 + Mono: Playback 0 [0%] [-46.50dB] [off] +Simple mixer control 'Line',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'CD',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mic',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] +Simple mixer control 'Mic Boost (+20dB)',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'Mic Select',0 + Capabilities: enum + Items: 'Mic1' 'Mic2' + Item0: 'Mic1' +Simple mixer control 'Video',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Phone',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'IEC958',0 + Capabilities: pswitch pswitch-joined cswitch cswitch-joined + Playback channels: Mono + Capture channels: Mono + Mono: Playback [off] Capture [off] +Simple mixer control 'IEC958 Capture Monitor',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Capture Valid',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Output',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [off] +Simple mixer control 'IEC958 Playback AC97-SPSA',0 + Capabilities: volume volume-joined + Playback channels: Mono + Capture channels: Mono + Limits: 0 - 3 + Mono: 3 [100%] +Simple mixer control 'IEC958 Playback Source',0 + Capabilities: enum + Items: 'AC-Link' 'ADC' 'SPDIF-In' + Item0: 'AC-Link' +Simple mixer control 'PC Speaker',0 + Capabilities: pvolume pvolume-joined pswitch pswitch-joined + Playback channels: Mono + Limits: Playback 0 - 15 + Mono: Playback 0 [0%] [-45.00dB] [off] +Simple mixer control 'Aux',0 + Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive + Capture exclusive group: 0 + Playback channels: Front Left - Front Right + Capture channels: Front Left - Front Right + Limits: Playback 0 - 31 + Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] + Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] +Simple mixer control 'Mono Output Select',0 + Capabilities: enum + Items: 'Mix' 'Mic' + Item0: 'Mix' +Simple mixer control 'Capture',0 + Capabilities: cvolume cswitch cswitch-joined + Capture channels: Front Left - Front Right + Limits: Capture 0 - 15 + Front Left: Capture 0 [0%] [0.00dB] [on] + Front Right: Capture 0 [0%] [0.00dB] [on] +Simple mixer control 'Mix',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Mix Mono',0 + Capabilities: cswitch cswitch-exclusive + Capture exclusive group: 0 + Capture channels: Front Left - Front Right + Front Left: Capture [off] + Front Right: Capture [off] +Simple mixer control 'Channel Mode',0 + Capabilities: enum + Items: '2ch' '4ch' '6ch' + Item0: '2ch' +Simple mixer control 'DAC Clock Source',0 + Capabilities: enum + Items: 'AC-Link' 'SPDIF-In' 'Both' + Item0: 'AC-Link' +Simple mixer control 'External Amplifier',0 + Capabilities: pswitch pswitch-joined + Playback channels: Mono + Mono: Playback [on] +Simple mixer control 'Input Source Select',0 + Capabilities: enum + Items: 'Input1' 'Input2' + Item0: 'Input1' +Simple mixer control 'Input Source Select',1 + Capabilities: enum + Items: 'Input1' 'Input2' + Item0: 'Input1' diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index ad52f5e3..e60aa5ef 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -32,6 +32,10 @@ #include <modules/reserve-wrap.h> +#ifdef HAVE_UDEV +#include <modules/udev-util.h> +#endif + #include "alsa-util.h" #include "alsa-sink.h" #include "alsa-source.h" @@ -49,6 +53,7 @@ PA_MODULE_USAGE( "sink_properties=<properties for the sink> " "source_name=<name for the source> " "source_properties=<properties for the source> " + "namereg_fail=<pa_namereg_register() fail parameter value> " "device_id=<ALSA card index> " "format=<sample format> " "rate=<sample rate> " @@ -59,7 +64,9 @@ PA_MODULE_USAGE( "tsched_buffer_size=<buffer size when using timer based scheduling> " "tsched_buffer_watermark=<lower fill watermark> " "profile=<profile name> " - "ignore_dB=<ignore dB information from the device?>"); + "ignore_dB=<ignore dB information from the device?> " + "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> " + "profile_set=<profile set configuration file> "); static const char* const valid_modargs[] = { "name", @@ -69,6 +76,7 @@ static const char* const valid_modargs[] = { "sink_properties", "source_name", "source_properties", + "namereg_fail", "device_id", "format", "rate", @@ -80,6 +88,8 @@ static const char* const valid_modargs[] = { "tsched_buffer_watermark", "profile", "ignore_dB", + "sync_volume", + "profile_set", NULL }; @@ -92,81 +102,53 @@ struct userdata { char *device_id; pa_card *card; - pa_sink *sink; - pa_source *source; pa_modargs *modargs; - pa_hashmap *profiles; + pa_alsa_profile_set *profile_set; }; struct profile_data { - const pa_alsa_profile_info *sink_profile, *source_profile; + pa_alsa_profile *profile; }; -static void enumerate_cb( - const pa_alsa_profile_info *sink, - const pa_alsa_profile_info *source, - void *userdata) { - - struct userdata *u = userdata; - char *t, *n; - pa_card_profile *p; - struct profile_data *d; - unsigned bonus = 0; - - if (sink && source) { - n = pa_sprintf_malloc("output-%s+input-%s", sink->name, source->name); - t = pa_sprintf_malloc(_("Output %s + Input %s"), sink->description, _(source->description)); - } else if (sink) { - n = pa_sprintf_malloc("output-%s", sink->name); - t = pa_sprintf_malloc(_("Output %s"), _(sink->description)); - } else { - pa_assert(source); - n = pa_sprintf_malloc("input-%s", source->name); - t = pa_sprintf_malloc(_("Input %s"), _(source->description)); - } - - if (sink) { - if (pa_channel_map_equal(&sink->map, &u->core->default_channel_map)) - bonus += 50000; - else if (sink->map.channels == u->core->default_channel_map.channels) - bonus += 40000; - } +static void add_profiles(struct userdata *u, pa_hashmap *h) { + pa_alsa_profile *ap; + void *state; - if (source) { - if (pa_channel_map_equal(&source->map, &u->core->default_channel_map)) - bonus += 30000; - else if (source->map.channels == u->core->default_channel_map.channels) - bonus += 20000; - } - - pa_log_info("Found profile '%s'", t); + pa_assert(u); + pa_assert(h); - p = pa_card_profile_new(n, t, sizeof(struct profile_data)); + PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) { + struct profile_data *d; + pa_card_profile *cp; + pa_alsa_mapping *m; + uint32_t idx; - pa_xfree(t); - pa_xfree(n); + cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); + cp->priority = ap->priority; - p->priority = - (sink ? sink->priority : 0) * 100 + - (source ? source->priority : 0) + - bonus; + if (ap->output_mappings) { + cp->n_sinks = pa_idxset_size(ap->output_mappings); - p->n_sinks = !!sink; - p->n_sources = !!source; + PA_IDXSET_FOREACH(m, ap->output_mappings, idx) + if (m->channel_map.channels > cp->max_sink_channels) + cp->max_sink_channels = m->channel_map.channels; + } - if (sink) - p->max_sink_channels = sink->map.channels; - if (source) - p->max_source_channels = source->map.channels; + if (ap->input_mappings) { + cp->n_sources = pa_idxset_size(ap->input_mappings); - d = PA_CARD_PROFILE_DATA(p); + PA_IDXSET_FOREACH(m, ap->input_mappings, idx) + if (m->channel_map.channels > cp->max_source_channels) + cp->max_source_channels = m->channel_map.channels; + } - d->sink_profile = sink; - d->source_profile = source; + d = PA_CARD_PROFILE_DATA(cp); + d->profile = ap; - pa_hashmap_put(u->profiles, p->name, p); + pa_hashmap_put(h, cp->name, cp); + } } static void add_disabled_profile(pa_hashmap *profiles) { @@ -176,7 +158,7 @@ static void add_disabled_profile(pa_hashmap *profiles) { p = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data)); d = PA_CARD_PROFILE_DATA(p); - d->sink_profile = d->source_profile = NULL; + d->profile = NULL; pa_hashmap_put(profiles, p->name, p); } @@ -184,6 +166,9 @@ static void add_disabled_profile(pa_hashmap *profiles) { static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { struct userdata *u; struct profile_data *nd, *od; + uint32_t idx; + pa_alsa_mapping *am; + pa_queue *sink_inputs = NULL, *source_outputs = NULL; pa_assert(c); pa_assert(new_profile); @@ -192,67 +177,85 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { nd = PA_CARD_PROFILE_DATA(new_profile); od = PA_CARD_PROFILE_DATA(c->active_profile); - if (od->sink_profile != nd->sink_profile) { - pa_queue *inputs = NULL; + if (od->profile && od->profile->output_mappings) + PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) { + if (!am->sink) + continue; - if (u->sink) { - if (nd->sink_profile) - inputs = pa_sink_move_all_start(u->sink); + if (nd->profile && + nd->profile->output_mappings && + pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL)) + continue; - pa_alsa_sink_free(u->sink); - u->sink = NULL; + sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs); + pa_alsa_sink_free(am->sink); + am->sink = NULL; } - if (nd->sink_profile) { - u->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, nd->sink_profile); + if (od->profile && od->profile->input_mappings) + PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) { + if (!am->source) + continue; - if (inputs) { - if (u->sink) - pa_sink_move_all_finish(u->sink, inputs, FALSE); - else - pa_sink_move_all_fail(inputs); - } + if (nd->profile && + nd->profile->input_mappings && + pa_idxset_get_by_data(nd->profile->input_mappings, am, NULL)) + continue; + + source_outputs = pa_source_move_all_start(am->source, source_outputs); + pa_alsa_source_free(am->source); + am->source = NULL; } - } - if (od->source_profile != nd->source_profile) { - pa_queue *outputs = NULL; + if (nd->profile && nd->profile->output_mappings) + PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) { - if (u->source) { - if (nd->source_profile) - outputs = pa_source_move_all_start(u->source); + if (!am->sink) + am->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, am); - pa_alsa_source_free(u->source); - u->source = NULL; + if (sink_inputs && am->sink) { + pa_sink_move_all_finish(am->sink, sink_inputs, FALSE); + sink_inputs = NULL; + } } - if (nd->source_profile) { - u->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, nd->source_profile); + if (nd->profile && nd->profile->input_mappings) + PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) { + + if (!am->source) + am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am); - if (outputs) { - if (u->source) - pa_source_move_all_finish(u->source, outputs, FALSE); - else - pa_source_move_all_fail(outputs); + if (source_outputs && am->source) { + pa_source_move_all_finish(am->source, source_outputs, FALSE); + source_outputs = NULL; } } - } + + if (sink_inputs) + pa_sink_move_all_fail(sink_inputs); + + if (source_outputs) + pa_source_move_all_fail(source_outputs); return 0; } static void init_profile(struct userdata *u) { + uint32_t idx; + pa_alsa_mapping *am; struct profile_data *d; pa_assert(u); d = PA_CARD_PROFILE_DATA(u->card->active_profile); - if (d->sink_profile) - u->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, d->sink_profile); + if (d->profile && d->profile->output_mappings) + PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx) + am->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, am); - if (d->source_profile) - u->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, d->source_profile); + if (d->profile && d->profile->input_mappings) + PA_IDXSET_FOREACH(am, d->profile->input_mappings, idx) + am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am); } static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { @@ -286,12 +289,12 @@ int pa__init(pa_module *m) { pa_modargs *ma; int alsa_card_index; struct userdata *u; - char rname[32]; pa_reserve_wrapper *reserve = NULL; const char *description; + char *fn = NULL; + pa_bool_t namereg_fail = FALSE; - pa_alsa_redirect_errors_inc(); - snd_config_update_free_global(); + pa_alsa_refcnt_inc(); pa_assert(m); @@ -300,13 +303,10 @@ int pa__init(pa_module *m) { goto fail; } - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID)); - u->card = NULL; - u->sink = NULL; - u->source = NULL; u->modargs = ma; if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) { @@ -314,29 +314,64 @@ int pa__init(pa_module *m) { goto fail; } - pa_snprintf(rname, sizeof(rname), "Audio%i", alsa_card_index); + if (!pa_in_system_mode()) { + char *rname; + + if ((rname = pa_alsa_get_reserve_name(u->device_id))) { + reserve = pa_reserve_wrapper_get(m->core, rname); + pa_xfree(rname); + + if (!reserve) + goto fail; + } + } + +#ifdef HAVE_UDEV + fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET"); +#endif + + if (pa_modargs_get_value(ma, "profile_set", NULL)) { + pa_xfree(fn); + fn = pa_xstrdup(pa_modargs_get_value(ma, "profile_set", NULL)); + } + + u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); + pa_xfree(fn); - if (!pa_in_system_mode()) - if (!(reserve = pa_reserve_wrapper_get(m->core, rname))) - goto fail; + if (!u->profile_set) + goto fail; + + pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); + pa_alsa_profile_set_dump(u->profile_set); pa_card_new_data_init(&data); data.driver = __FILE__; data.module = m; + pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id); pa_alsa_init_description(data.proplist); set_card_name(&data, ma, u->device_id); + /* We need to give pa_modargs_get_value_boolean() a pointer to a local + * variable instead of using &data.namereg_fail directly, because + * data.namereg_fail is a bitfield and taking the address of a bitfield + * variable is impossible. */ + namereg_fail = data.namereg_fail; + if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) { + pa_log("Failed to parse boolean argument namereg_fail."); + pa_card_new_data_done(&data); + goto fail; + } + data.namereg_fail = namereg_fail; + if (reserve) if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION))) pa_reserve_wrapper_set_application_device_name(reserve, description); - u->profiles = data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - if (pa_alsa_probe_profiles(u->device_id, &m->core->default_sample_spec, enumerate_cb, u) < 0) { - pa_card_new_data_done(&data); - goto fail; - } + data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + add_profiles(u, data.profiles); if (pa_hashmap_isempty(data.profiles)) { pa_log("Failed to find a working profile."); @@ -366,6 +401,14 @@ int pa__init(pa_module *m) { if (reserve) pa_reserve_wrapper_unref(reserve); + if (!pa_hashmap_isempty(u->profile_set->decibel_fixes)) + pa_log_warn("Card %s uses decibel fixes (i.e. overrides the decibel information for some alsa volume elements). " + "Please note that this feature is meant just as a help for figuring out the correct decibel values. " + "Pulseaudio is not the correct place to maintain the decibel mappings! The fixed decibel values " + "should be sent to ALSA developers so that they can fix the driver. If it turns out that this feature " + "is abused (i.e. fixes are not pushed to ALSA), the decibel fix feature may be removed in some future " + "Pulseaudio version.", u->card->name); + return 0; fail: @@ -379,13 +422,22 @@ fail: int pa__get_n_used(pa_module *m) { struct userdata *u; + int n = 0; + uint32_t idx; + pa_sink *sink; + pa_source *source; pa_assert(m); pa_assert_se(u = m->userdata); + pa_assert(u->card); - return - (u->sink ? pa_sink_linked_by(u->sink) : 0) + - (u->source ? pa_source_linked_by(u->source) : 0); + PA_IDXSET_FOREACH(sink, u->card->sinks, idx) + n += pa_sink_linked_by(sink); + + PA_IDXSET_FOREACH(source, u->card->sources, idx) + n += pa_source_linked_by(source); + + return n; } void pa__done(pa_module*m) { @@ -396,11 +448,19 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) goto finish; - if (u->sink) - pa_alsa_sink_free(u->sink); + if (u->card && u->card->sinks) { + pa_sink *s; + + while ((s = pa_idxset_steal_first(u->card->sinks, NULL))) + pa_alsa_sink_free(s); + } - if (u->source) - pa_alsa_source_free(u->source); + if (u->card && u->card->sources) { + pa_source *s; + + while ((s = pa_idxset_steal_first(u->card->sources, NULL))) + pa_alsa_source_free(s); + } if (u->card) pa_card_free(u->card); @@ -408,10 +468,12 @@ void pa__done(pa_module*m) { if (u->modargs) pa_modargs_free(u->modargs); + if (u->profile_set) + pa_alsa_profile_set_free(u->profile_set); + pa_xfree(u->device_id); pa_xfree(u); finish: - snd_config_update_free_global(); - pa_alsa_redirect_errors_dec(); + pa_alsa_refcnt_dec(); } diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c index 058ea205..6e64ab31 100644 --- a/src/modules/alsa/module-alsa-sink.c +++ b/src/modules/alsa/module-alsa-sink.c @@ -24,7 +24,6 @@ #include <config.h> #endif -#include <pulsecore/core.h> #include <pulsecore/module.h> #include <pulsecore/sink.h> #include <pulsecore/modargs.h> @@ -40,7 +39,8 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "name=<name of the sink, to be prefixed> " "sink_name=<name for the sink> " - "sink_properities=<properties for the sink> " + "sink_properties=<properties for the sink> " + "namereg_fail=<pa_namereg_register() fail parameter value> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -54,12 +54,17 @@ PA_MODULE_USAGE( "tsched_buffer_size=<buffer size when using timer based scheduling> " "tsched_buffer_watermark=<lower fill watermark> " "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control>"); + "control=<name of mixer control> " + "rewind_safeguard=<number of bytes that cannot be rewound> " + "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> " + "sync_volume_safety_margin=<usec adjustment depending on volume direction> " + "sync_volume_extra_delay=<usec adjustment to HW volume changes>"); static const char* const valid_modargs[] = { "name", "sink_name", "sink_properties", + "namereg_fail", "device", "device_id", "format", @@ -74,6 +79,10 @@ static const char* const valid_modargs[] = { "tsched_buffer_watermark", "ignore_dB", "control", + "rewind_safeguard", + "sync_volume", + "sync_volume_safety_margin", + "sync_volume_extra_delay", NULL }; @@ -82,8 +91,7 @@ int pa__init(pa_module*m) { pa_assert(m); - pa_alsa_redirect_errors_inc(); - snd_config_update_free_global(); + pa_alsa_refcnt_inc(); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -124,6 +132,5 @@ void pa__done(pa_module*m) { if ((sink = m->userdata)) pa_alsa_sink_free(sink); - snd_config_update_free_global(); - pa_alsa_redirect_errors_dec(); + pa_alsa_refcnt_dec(); } diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index 3bd1b451..5ecd1e34 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -32,26 +32,10 @@ #include <valgrind/memcheck.h> #endif -#include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulse/timeval.h> - -#include <pulsecore/core-error.h> -#include <pulsecore/core.h> #include <pulsecore/module.h> -#include <pulsecore/memchunk.h> -#include <pulsecore/sink.h> #include <pulsecore/modargs.h> -#include <pulsecore/core-util.h> -#include <pulsecore/sample-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> -#include <pulsecore/thread.h> -#include <pulsecore/core-error.h> -#include <pulsecore/thread-mq.h> -#include <pulsecore/rtpoll.h> -#include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h> #include "alsa-util.h" #include "alsa-source.h" @@ -65,6 +49,7 @@ PA_MODULE_USAGE( "name=<name for the source, to be prefixed> " "source_name=<name for the source> " "source_properties=<properties for the source> " + "namereg_fail=<pa_namereg_register() fail parameter value> " "device=<ALSA device> " "device_id=<ALSA card index> " "format=<sample format> " @@ -78,12 +63,16 @@ PA_MODULE_USAGE( "tsched_buffer_size=<buffer size when using timer based scheduling> " "tsched_buffer_watermark=<upper fill watermark> " "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control>"); + "control=<name of mixer control>" + "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> " + "sync_volume_safety_margin=<usec adjustment depending on volume direction> " + "sync_volume_extra_delay=<usec adjustment to HW volume changes>"); static const char* const valid_modargs[] = { "name", "source_name", "source_properties", + "namereg_fail", "device", "device_id", "format", @@ -98,6 +87,9 @@ static const char* const valid_modargs[] = { "tsched_buffer_watermark", "ignore_dB", "control", + "sync_volume", + "sync_volume_safety_margin", + "sync_volume_extra_delay", NULL }; @@ -106,8 +98,7 @@ int pa__init(pa_module*m) { pa_assert(m); - pa_alsa_redirect_errors_inc(); - snd_config_update_free_global(); + pa_alsa_refcnt_inc(); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); @@ -148,6 +139,5 @@ void pa__done(pa_module*m) { if ((source = m->userdata)) pa_alsa_source_free(source); - snd_config_update_free_global(); - pa_alsa_redirect_errors_dec(); + pa_alsa_refcnt_dec(); } diff --git a/src/modules/bluetooth/Makefile b/src/modules/bluetooth/Makefile deleted file mode 120000 index efe5a336..00000000 --- a/src/modules/bluetooth/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/bluetooth/a2dp-codecs.h b/src/modules/bluetooth/a2dp-codecs.h new file mode 100644 index 00000000..e44634ea --- /dev/null +++ b/src/modules/bluetooth/a2dp-codecs.h @@ -0,0 +1,116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +#else +#error "Unknown byte order" +#endif diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index 5c7681d4..b24fe7a3 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -23,11 +23,43 @@ #include <config.h> #endif +#include <pulse/xmalloc.h> + #include <pulsecore/core-util.h> #include <pulsecore/shared.h> #include <pulsecore/dbus-shared.h> #include "bluetooth-util.h" +#include "ipc.h" +#include "a2dp-codecs.h" + +#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG" +#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" +#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" + +#define ENDPOINT_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name=\"org.bluez.MediaEndpoint\">" \ + " <method name=\"SetConfiguration\">" \ + " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \ + " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"SelectConfiguration\">" \ + " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \ + " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"ClearConfiguration\">" \ + " </method>" \ + " <method name=\"Release\">" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" \ + "</node>" struct pa_bluetooth_discovery { PA_REFCNT_DECLARE; @@ -37,10 +69,11 @@ struct pa_bluetooth_discovery { PA_LLIST_HEAD(pa_dbus_pending, pending); pa_hashmap *devices; pa_hook hook; + pa_bool_t filter_added; }; static void get_properties_reply(DBusPendingCall *pending, void *userdata); -static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func); +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data); static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) { pa_assert(value); @@ -85,6 +118,7 @@ static pa_bluetooth_device* device_new(const char *path) { d->name = NULL; d->path = pa_xstrdup(path); + d->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); d->paired = -1; d->alias = NULL; d->device_connected = -1; @@ -95,16 +129,32 @@ static pa_bluetooth_device* device_new(const char *path) { d->audio_state = PA_BT_AUDIO_STATE_INVALID; d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID; + d->audio_source_state = PA_BT_AUDIO_STATE_INVALID; d->headset_state = PA_BT_AUDIO_STATE_INVALID; + d->hfgw_state = PA_BT_AUDIO_STATE_INVALID; return d; } +static void transport_free(pa_bluetooth_transport *t) { + pa_assert(t); + + pa_xfree(t->path); + pa_xfree(t->config); + pa_xfree(t); +} + static void device_free(pa_bluetooth_device *d) { pa_bluetooth_uuid *u; + pa_bluetooth_transport *t; pa_assert(d); + while ((t = pa_hashmap_steal_first(d->transports))) + transport_free(t); + + pa_hashmap_free(d->transports, NULL, NULL); + while ((u = d->uuids)) { PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u); uuid_free(u); @@ -121,10 +171,11 @@ static pa_bool_t device_is_audio(pa_bluetooth_device *d) { pa_assert(d); return - d->device_info_valid && - (d->audio_state != PA_BT_AUDIO_STATE_INVALID || - d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || - d->headset_state != PA_BT_AUDIO_STATE_INVALID); + d->device_info_valid && (d->hfgw_state != PA_BT_AUDIO_STATE_INVALID || + (d->audio_state != PA_BT_AUDIO_STATE_INVALID && + (d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID || + d->audio_source_state != PA_BT_AUDIO_STATE_INVALID || + d->headset_state != PA_BT_AUDIO_STATE_INVALID))); } static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) { @@ -142,7 +193,7 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device dbus_message_iter_get_basic(i, &key); - if (!dbus_message_iter_next(i)) { + if (!dbus_message_iter_next(i)) { pa_log("Property value missing"); return -1; } @@ -226,19 +277,25 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device node = uuid_new(value); PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node); - /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */ - pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); - /* Vudentz said the interfaces are here when the UUIDs are announced */ - if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) { + if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, d); + } else if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); + send_and_add_to_pending(y, m, get_properties_reply, d); } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) { pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); + send_and_add_to_pending(y, m, get_properties_reply, d); + } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, d); } + /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */ + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, d); + if (!dbus_message_iter_next(&ai)) break; } @@ -266,7 +323,7 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa dbus_message_iter_get_basic(i, &key); - if (!dbus_message_iter_next(i)) { + if (!dbus_message_iter_next(i)) { pa_log("Property value missing"); return -1; } @@ -278,7 +335,7 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa dbus_message_iter_recurse(i, &variant_i); -/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */ +/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */ switch (dbus_message_iter_get_arg_type(&variant_i)) { @@ -287,9 +344,10 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessa const char *value; dbus_message_iter_get_basic(&variant_i, &value); - if (pa_streq(key, "State")) + if (pa_streq(key, "State")) { *state = pa_bt_audio_state_from_string(value); -/* pa_log_debug("Value %s", value); */ + pa_log_debug("dbus: property 'State' changed to value '%s'", value); + } break; } @@ -309,6 +367,40 @@ static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_b pa_hook_fire(&y->hook, d); } +static void remove_all_devices(pa_bluetooth_discovery *y) { + pa_bluetooth_device *d; + + pa_assert(y); + + while ((d = pa_hashmap_steal_first(y->devices))) { + run_callback(y, d, TRUE); + device_free(d); + } +} + +static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) { + DBusMessage *m; + pa_bluetooth_device *d; + + pa_assert(y); + pa_assert(path); + + d = pa_hashmap_get(y->devices, path); + if (d) + return d; + + d = device_new(path); + + pa_hashmap_put(y->devices, d->path, d); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, d); + + /* Before we read the other properties (Audio, AudioSink, AudioSource, + * Headset) we wait that the UUID is read */ + return d; +} + static void get_properties_reply(DBusPendingCall *pending, void *userdata) { DBusMessage *r; DBusMessageIter arg_i, element_i; @@ -325,13 +417,25 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { /* dbus_message_get_interface(p->message), */ /* dbus_message_get_path(p->message)); */ - d = p->call_data; + /* We don't use p->call_data here right-away since the device + * might already be invalidated at this point */ + + if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) + return; + + pa_assert(p->call_data == d); valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1; if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties")) d->device_info_valid = valid; + if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { + pa_log_debug("Bluetooth daemon is apparently not available."); + remove_all_devices(y); + goto finish2; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { if (!dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) @@ -373,6 +477,15 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) { if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0) goto finish; + + } else if (dbus_message_has_interface(p->message, "org.bluez.AudioSource")) { + if (parse_audio_property(y, &d->audio_source_state, &dict_i) < 0) + goto finish; + + } else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) { + if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0) + goto finish; + } } @@ -383,13 +496,14 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { finish: run_callback(y, d, FALSE); +finish2: dbus_message_unref(r); PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); pa_dbus_pending_free(p); } -static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func) { +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, void *call_data) { pa_dbus_pending *p; DBusPendingCall *call; @@ -398,30 +512,55 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bl pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1)); - p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d); + p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data); PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p); dbus_pending_call_set_notify(call, func, p, NULL); return p; } -static void found_device(pa_bluetooth_discovery *y, const char* path) { - DBusMessage *m; - pa_bluetooth_device *d; +#ifdef DBUS_TYPE_UNIX_FD +static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { + DBusError e; + DBusMessage *r; + pa_dbus_pending *p; + pa_bluetooth_discovery *y; + char *endpoint; - pa_assert(y); - pa_assert(path); + pa_assert(pending); - d = device_new(path); + dbus_error_init(&e); - pa_hashmap_put(y->devices, d->path, d); + pa_assert_se(p = userdata); + pa_assert_se(y = p->context_data); + pa_assert_se(endpoint = p->call_data); + pa_assert_se(r = dbus_pending_call_steal_reply(pending)); - pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); + if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { + pa_log_debug("Bluetooth daemon is apparently not available."); + remove_all_devices(y); + goto finish; + } + + if (dbus_message_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) { + pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint); + goto finish; + } + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + pa_log("Error from RegisterEndpoint reply: %s", dbus_message_get_error_name(r)); + goto finish; + } - /* Before we read the other properties (Audio, AudioSink, Headset) we wait - * that the UUID is read */ +finish: + dbus_message_unref(r); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(p); + + pa_xfree(endpoint); } +#endif static void list_devices_reply(DBusPendingCall *pending, void *userdata) { DBusError e; @@ -439,9 +578,15 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) { pa_assert_se(y = p->context_data); pa_assert_se(r = dbus_pending_call_steal_reply(pending)); + if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { + pa_log_debug("Bluetooth daemon is apparently not available."); + remove_all_devices(y); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r)); - goto end; + goto finish; } if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) { @@ -454,9 +599,9 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) { found_device(y, paths[i]); } -end: +finish: if (paths) - dbus_free_string_array (paths); + dbus_free_string_array(paths); dbus_message_unref(r); @@ -464,11 +609,65 @@ end: pa_dbus_pending_free(p); } +#ifdef DBUS_TYPE_UNIX_FD +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) { + DBusMessage *m; + DBusMessageIter i, d; + uint8_t codec = 0; + + pa_log_debug("Registering %s on adapter %s.", endpoint, path); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint")); + + dbus_message_iter_init_append(m, &i); + + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &d); + + pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); + + pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); + + if (pa_streq(uuid, HFP_AG_UUID)) { + uint8_t capability = 0; + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1); + } else { + a2dp_sbc_t capabilities; + + capabilities.channel_mode = BT_A2DP_CHANNEL_MODE_MONO | BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL | + BT_A2DP_CHANNEL_MODE_STEREO | BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + capabilities.frequency = BT_SBC_SAMPLING_FREQ_16000 | BT_SBC_SAMPLING_FREQ_32000 | + BT_SBC_SAMPLING_FREQ_44100 | BT_SBC_SAMPLING_FREQ_48000; + capabilities.allocation_method = BT_A2DP_ALLOCATION_SNR | BT_A2DP_ALLOCATION_LOUDNESS; + capabilities.subbands = BT_A2DP_SUBBANDS_4 | BT_A2DP_SUBBANDS_8; + capabilities.block_length = BT_A2DP_BLOCK_LENGTH_4 | BT_A2DP_BLOCK_LENGTH_8 | + BT_A2DP_BLOCK_LENGTH_12 | BT_A2DP_BLOCK_LENGTH_16; + capabilities.min_bitpool = MIN_BITPOOL; + capabilities.max_bitpool = MAX_BITPOOL; + + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities)); + } + + dbus_message_iter_close_container(&i, &d); + + send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint)); +} +#endif + static void found_adapter(pa_bluetooth_discovery *y, const char *path) { DBusMessage *m; pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices")); - send_and_add_to_pending(y, NULL, m, list_devices_reply); + send_and_add_to_pending(y, m, list_devices_reply, NULL); + +#ifdef DBUS_TYPE_UNIX_FD + register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID); + register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID); + register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID); +#endif } static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { @@ -487,9 +686,15 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { pa_assert_se(y = p->context_data); pa_assert_se(r = dbus_pending_call_steal_reply(pending)); + if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { + pa_log_debug("Bluetooth daemon is apparently not available."); + remove_all_devices(y); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r)); - goto end; + goto finish; } if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) { @@ -502,9 +707,9 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { found_adapter(y, paths[i]); } -end: +finish: if (paths) - dbus_free_string_array (paths); + dbus_free_string_array(paths); dbus_message_unref(r); @@ -517,7 +722,48 @@ static void list_adapters(pa_bluetooth_discovery *y) { pa_assert(y); pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters")); - send_and_add_to_pending(y, NULL, m, list_adapters_reply); + send_and_add_to_pending(y, m, list_adapters_reply, NULL); +} + +int pa_bluetooth_transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) +{ + const char *key; + DBusMessageIter variant_i; + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + return -1; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + pa_log("Property value missing"); + return -1; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + return -1; + } + + dbus_message_iter_recurse(i, &variant_i); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_BOOLEAN: { + + pa_bool_t *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "NREC")) + t->nrec = value; + + break; + } + } + + return 0; } static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) { @@ -583,6 +829,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") || dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) { pa_bluetooth_device *d; @@ -610,12 +858,84 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us } else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) { if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0) goto fail; + + } else if (dbus_message_has_interface(m, "org.bluez.AudioSource")) { + if (parse_audio_property(y, &d->audio_source_state, &arg_i) < 0) + goto fail; + + } else if (dbus_message_has_interface(m, "org.bluez.HandsfreeGateway")) { + if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0) + goto fail; } run_callback(y, d, FALSE); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Device", "DisconnectRequested")) { + pa_bluetooth_device *d; + + if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { + /* Device will disconnect in 2 sec */ + d->audio_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->audio_sink_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->audio_source_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->headset_state = PA_BT_AUDIO_STATE_DISCONNECTED; + d->hfgw_state = PA_BT_AUDIO_STATE_DISCONNECTED; + + run_callback(y, d, FALSE); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + + if (!dbus_message_get_args(m, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); + goto fail; + } + + if (pa_streq(name, "org.bluez")) { + if (old_owner && *old_owner) { + pa_log_debug("Bluetooth daemon disappeared."); + remove_all_devices(y); + } + + if (new_owner && *new_owner) { + pa_log_debug("Bluetooth daemon appeared."); + list_adapters(y); + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) { + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + void *state = NULL; + DBusMessageIter arg_i; + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) + if ((t = pa_hashmap_get(d->transports, dbus_message_get_path(m)))) + break; + + if (!t) + goto fail; + + if (!dbus_message_iter_init(m, &arg_i)) { + pa_log("Failed to parse PropertyChanged: %s", err.message); + goto fail; + } + + if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0) + goto fail; + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } fail: @@ -637,12 +957,14 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_di while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) if (pa_streq(d->address, address)) - return d; + return device_is_audio(d) ? d : NULL; return NULL; } const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) { + pa_bluetooth_device *d; + pa_assert(y); pa_assert(PA_REFCNT_VALUE(y) > 0); pa_assert(path); @@ -650,7 +972,103 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_disco if (!pa_hook_is_firing(&y->hook)) pa_bluetooth_discovery_sync(y); - return pa_hashmap_get(y->devices, path); + if ((d = pa_hashmap_get(y->devices, path))) + if (device_is_audio(d)) + return d; + + return NULL; +} + +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path) { + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + void *state = NULL; + + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + pa_assert(path); + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) + if ((t = pa_hashmap_get(d->transports, path))) + return t; + + return NULL; +} + +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile) { + pa_bluetooth_transport *t; + void *state = NULL; + + pa_assert(d); + + while ((t = pa_hashmap_iterate(d->transports, &state, NULL))) + if (t->profile == profile) + return t; + + return NULL; +} + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype, size_t *imtu, size_t *omtu) { + DBusMessage *m, *r; + DBusError err; + int ret; + uint16_t i, o; + + pa_assert(t); + pa_assert(t->y); + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Acquire")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + + if (dbus_error_is_set(&err) || !r) { + pa_log("Failed to acquire transport fd: %s", err.message); + dbus_error_free(&err); + return -1; + } + +#ifdef DBUS_TYPE_UNIX_FD + if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message); + ret = -1; + dbus_error_free(&err); + goto fail; + } +#endif + + if (imtu) + *imtu = i; + + if (omtu) + *omtu = o; + +#ifdef DBUS_TYPE_UNIX_FD +fail: +#endif + dbus_message_unref(r); + return ret; +} + +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype) { + DBusMessage *m; + DBusError err; + + pa_assert(t); + pa_assert(t->y); + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Release")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + + if (dbus_error_is_set(&err)) { + pa_log("Failed to release transport %s: %s", t->path, err.message); + dbus_error_free(&err); + } else + pa_log_info("Transport %s released", t->path); } static int setup_dbus(pa_bluetooth_discovery *y) { @@ -669,9 +1087,369 @@ static int setup_dbus(pa_bluetooth_discovery *y) { return 0; } +#ifdef DBUS_TYPE_UNIX_FD +static pa_bluetooth_transport *transport_new(pa_bluetooth_discovery *y, const char *path, enum profile p, const uint8_t *config, int size) { + pa_bluetooth_transport *t; + + t = pa_xnew0(pa_bluetooth_transport, 1); + t->y = y; + t->path = pa_xstrdup(path); + t->profile = p; + t->config_size = size; + + if (size > 0) { + t->config = pa_xnew(uint8_t, size); + memcpy(t->config, config, size); + } + + return t; +} + +static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + const char *path, *dev_path = NULL, *uuid = NULL; + uint8_t *config = NULL; + int size = 0; + pa_bool_t nrec; + enum profile p; + DBusMessageIter args, props; + DBusMessage *r; + + dbus_message_iter_init(m, &args); + + dbus_message_iter_get_basic(&args, &path); + if (!dbus_message_iter_next(&args)) + goto fail; + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + goto fail; + + /* Read transport properties */ + while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + goto fail; + dbus_message_iter_get_basic(&value, &uuid); + } else if (strcasecmp(key, "Device") == 0) { + if (var != DBUS_TYPE_OBJECT_PATH) + goto fail; + dbus_message_iter_get_basic(&value, &dev_path); + } else if (strcasecmp(key, "NREC") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + goto fail; + dbus_message_iter_get_basic(&value, &nrec); + } else if (strcasecmp(key, "Configuration") == 0) { + DBusMessageIter array; + if (var != DBUS_TYPE_ARRAY) + goto fail; + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, &config, &size); + } + + dbus_message_iter_next(&props); + } + + d = found_device(y, dev_path); + if (!d) + goto fail; + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) + p = PROFILE_HSP; + else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT)) + p = PROFILE_A2DP; + else + p = PROFILE_A2DP_SOURCE; + + t = transport_new(y, path, p, config, size); + if (nrec) + t->nrec = nrec; + pa_hashmap_put(d->transports, t->path, t); + + pa_log_debug("Transport %s profile %d available", t->path, t->profile); + + pa_assert_se(r = dbus_message_new_method_return(m)); + + return r; + +fail: + pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments"); + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to set configuration"))); + return r; +} + +static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + void *state = NULL; + DBusMessage *r; + DBusError e; + const char *path; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) { + if ((t = pa_hashmap_get(d->transports, path))) { + pa_log_debug("Clearing transport %s profile %d", t->path, t->profile); + pa_hashmap_remove(d->transports, t->path); + transport_free(t); + break; + } + } + + pa_assert_se(r = dbus_message_new_method_return(m)); + + return r; + +fail: + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to clear configuration"))); + return r; +} + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { + + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + + case BT_SBC_SAMPLING_FREQ_44100: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 53; + } + + case BT_SBC_SAMPLING_FREQ_48000: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 51; + } + + default: + pa_log_warn("Invalid sampling freq %u", freq); + return 53; + } +} + +static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + a2dp_sbc_t *cap, config; + uint8_t *pconf = (uint8_t *) &config; + int i, size; + DBusMessage *r; + DBusError e; + + static const struct { + uint32_t rate; + uint8_t cap; + } freq_table[] = { + { 16000U, BT_SBC_SAMPLING_FREQ_16000 }, + { 32000U, BT_SBC_SAMPLING_FREQ_32000 }, + { 44100U, BT_SBC_SAMPLING_FREQ_44100 }, + { 48000U, BT_SBC_SAMPLING_FREQ_48000 } + }; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) + goto done; + + pa_assert(size == sizeof(config)); + + memset(&config, 0, sizeof(config)); + + /* Find the lowest freq that is at least as high as the requested + * sampling rate */ + for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) + if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) { + config.frequency = freq_table[i].cap; + break; + } + + if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { + for (--i; i >= 0; i--) { + if (cap->frequency & freq_table[i].cap) { + config.frequency = freq_table[i].cap; + break; + } + } + + if (i < 0) { + pa_log("Not suitable sample rate"); + goto fail; + } + } + + pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); + + if (y->core->default_sample_spec.channels <= 1) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (y->core->default_sample_spec.channels >= 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + config.channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } else { + pa_log("No supported channel modes"); + goto fail; + } + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + config.block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + config.block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + config.block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + config.block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + pa_log_error("No supported block lengths"); + goto fail; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + config.subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + config.subbands = BT_A2DP_SUBBANDS_4; + else { + pa_log_error("No supported subbands"); + goto fail; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + config.allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + config.allocation_method = BT_A2DP_ALLOCATION_SNR; + + config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); + config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); + +done: + pa_assert_se(r = dbus_message_new_method_return(m)); + + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, + DBUS_TYPE_INVALID)); + + return r; + +fail: + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to select configuration"))); + return r; +} + +static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct pa_bluetooth_discovery *y = userdata; + DBusMessage *r = NULL; + DBusError e; + const char *path; + + pa_assert(y); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(m), + dbus_message_get_path(m), + dbus_message_get_member(m)); + + path = dbus_message_get_path(m); + dbus_error_init(&e); + + if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = ENDPOINT_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) { + r = endpoint_set_configuration(c, m, userdata); + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) { + r = endpoint_select_configuration(c, m, userdata); + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration")) + r = endpoint_clear_configuration(c, m, userdata); + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} +#endif /* DBUS_TYPE_UNIX_FD */ + pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { DBusError err; pa_bluetooth_discovery *y; +#ifdef DBUS_TYPE_UNIX_FD + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = endpoint_handler, + }; +#endif pa_assert(c); @@ -696,20 +1474,33 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { pa_log_error("Failed to add filter function"); goto fail; } + y->filter_added = TRUE; if (pa_dbus_add_matches( pa_dbus_connection_get(y->connection), &err, + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'", "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) { + "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", + NULL) < 0) { pa_log("Failed to add D-Bus matches: %s", err.message); goto fail; } +#ifdef DBUS_TYPE_UNIX_FD + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y)); +#endif + list_adapters(y); return y; @@ -734,8 +1525,6 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) { } void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { - pa_bluetooth_device *d; - pa_assert(y); pa_assert(PA_REFCNT_VALUE(y) > 0); @@ -745,26 +1534,34 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { pa_dbus_free_pending_list(&y->pending); if (y->devices) { - while ((d = pa_hashmap_steal_first(y->devices))) { - run_callback(y, d, TRUE); - device_free(d); - } - + remove_all_devices(y); pa_hashmap_free(y->devices, NULL, NULL); } if (y->connection) { +#ifdef DBUS_TYPE_UNIX_FD + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); +#endif pa_dbus_remove_matches(pa_dbus_connection_get(y->connection), + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'", "type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.Device',member='DisconnectRequested'", "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'", "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL); - - dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); + "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", + NULL); + + if (y->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); pa_dbus_connection_unref(y->connection); } diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 265caf40..248ca47d 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -4,7 +4,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -26,25 +26,27 @@ #include <pulsecore/llist.h> #include <pulsecore/macro.h> -#include <pulsecore/core-util.h> + +#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" /* UUID copied from bluez/audio/device.h */ -#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805F9B34FB" +#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb" -#define HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB" -#define HSP_AG_UUID "00001112-0000-1000-8000-00805F9B34FB" +#define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb" +#define HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb" -#define HFP_HS_UUID "0000111E-0000-1000-8000-00805F9B34FB" -#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB" +#define HFP_HS_UUID "0000111e-0000-1000-8000-00805f9b34fb" +#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb" -#define ADVANCED_AUDIO_UUID "0000110D-0000-1000-8000-00805F9B34FB" +#define ADVANCED_AUDIO_UUID "0000110d-0000-1000-8000-00805f9b34fb" -#define A2DP_SOURCE_UUID "0000110A-0000-1000-8000-00805F9B34FB" -#define A2DP_SINK_UUID "0000110B-0000-1000-8000-00805F9B34FB" +#define A2DP_SOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb" +#define A2DP_SINK_UUID "0000110b-0000-1000-8000-00805f9b34fb" typedef struct pa_bluetooth_uuid pa_bluetooth_uuid; typedef struct pa_bluetooth_device pa_bluetooth_device; typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; +typedef struct pa_bluetooth_transport pa_bluetooth_transport; struct userdata; @@ -53,14 +55,31 @@ struct pa_bluetooth_uuid { PA_LLIST_FIELDS(pa_bluetooth_uuid); }; -/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */ +enum profile { + PROFILE_A2DP, + PROFILE_A2DP_SOURCE, + PROFILE_HSP, + PROFILE_HFGW, + PROFILE_OFF +}; + +struct pa_bluetooth_transport { + pa_bluetooth_discovery *y; + char *path; + enum profile profile; + uint8_t codec; + uint8_t *config; + int config_size; + pa_bool_t nrec; +}; + +/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */ typedef enum pa_bt_audio_state { PA_BT_AUDIO_STATE_INVALID = -1, PA_BT_AUDIO_STATE_DISCONNECTED, PA_BT_AUDIO_STATE_CONNECTING, PA_BT_AUDIO_STATE_CONNECTED, - PA_BT_AUDIO_STATE_PLAYING, - PA_BT_AUDIO_STATE_LAST + PA_BT_AUDIO_STATE_PLAYING } pa_bt_audio_state_t; struct pa_bluetooth_device { @@ -71,6 +90,7 @@ struct pa_bluetooth_device { /* Device information */ char *name; char *path; + pa_hashmap *transports; int paired; char *alias; int device_connected; @@ -85,8 +105,14 @@ struct pa_bluetooth_device { /* AudioSink state */ pa_bt_audio_state_t audio_sink_state; + /* AudioSource state */ + pa_bt_audio_state_t audio_source_state; + /* Headset state */ pa_bt_audio_state_t headset_state; + + /* HandsfreeGateway state */ + pa_bt_audio_state_t hfgw_state; }; pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core); @@ -98,6 +124,13 @@ void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *d); const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path); const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path); +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile); + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype, size_t *imtu, size_t *omtu); +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype); +int pa_bluetooth_transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i); + pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *d); const char* pa_bluetooth_get_form_factor(uint32_t class); diff --git a/src/modules/bluetooth/ipc.c b/src/modules/bluetooth/ipc.c index dcecad8a..1bdad784 100644 --- a/src/modules/bluetooth/ipc.c +++ b/src/modules/bluetooth/ipc.c @@ -2,7 +2,7 @@ * * BlueZ - Bluetooth protocol stack for Linux * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -106,8 +106,10 @@ int bt_audio_service_get_data_fd(int sk) for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET - && cmsg->cmsg_type == SCM_RIGHTS) - return (*(int *) CMSG_DATA(cmsg)); + && cmsg->cmsg_type == SCM_RIGHTS) { + memcpy(&ret, CMSG_DATA(cmsg), sizeof(int)); + return ret; + } } errno = EINVAL; diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h index 2e170f50..d69b97e4 100644 --- a/src/modules/bluetooth/ipc.h +++ b/src/modules/bluetooth/ipc.h @@ -2,7 +2,7 @@ * * BlueZ - Bluetooth protocol stack for Linux * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -101,6 +101,7 @@ typedef struct { #define BT_STOP_STREAM 5 #define BT_CLOSE 6 #define BT_CONTROL 7 +#define BT_DELAY_REPORT 8 #define BT_CAPABILITIES_TRANSPORT_A2DP 0 #define BT_CAPABILITIES_TRANSPORT_SCO 1 @@ -324,6 +325,16 @@ struct bt_control_ind { uint8_t key; /* Control Key */ } __attribute__ ((packed)); +struct bt_delay_report_req { + bt_audio_msg_header_t h; + uint16_t delay; +} __attribute__ ((packed)); + +struct bt_delay_report_ind { + bt_audio_msg_header_t h; + uint16_t delay; +} __attribute__ ((packed)); + /* Function declaration */ /* Opens a connection to the audio service: return a socket descriptor */ diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index dbec00d4..288ad2fd 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -25,37 +25,40 @@ #include <string.h> #include <errno.h> -#include <poll.h> -#include <sys/ioctl.h> #include <linux/sockios.h> #include <arpa/inet.h> -#include <pulse/xmalloc.h> -#include <pulse/timeval.h> -#include <pulse/sample.h> #include <pulse/i18n.h> +#include <pulse/rtclock.h> +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> #include <pulsecore/module.h> #include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h> #include <pulsecore/core-util.h> #include <pulsecore/core-error.h> +#include <pulsecore/shared.h> #include <pulsecore/socket-util.h> #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> +#include <pulsecore/poll.h> #include <pulsecore/rtpoll.h> #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h> #include <pulsecore/namereg.h> #include <pulsecore/dbus-shared.h> #include "module-bluetooth-device-symdef.h" #include "ipc.h" #include "sbc.h" +#include "a2dp-codecs.h" #include "rtp.h" #include "bluetooth-util.h" -#define MAX_BITPOOL 64 -#define MIN_BITPOOL 2U +#define BITPOOL_DEC_LIMIT 32 +#define BITPOOL_DEC_STEP 5 +#define HSP_MAX_GAIN 15 PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); PA_MODULE_DESCRIPTION("Bluetooth audio sink and source"); @@ -70,17 +73,13 @@ PA_MODULE_USAGE( "source_name=<name for the source> " "source_properties=<properties for the source> " "address=<address of the device> " - "profile=<a2dp|hsp> " + "profile=<a2dp|hsp|hfgw> " "rate=<sample rate> " "channels=<number of channels> " - "path=<device object path>"); - -/* -#ifdef NOKIA + "path=<device object path> " + "auto_connect=<automatically connect?> " "sco_sink=<SCO over PCM sink name> " - "sco_source=<SCO over PCM source name>" -#endif -*/ + "sco_source=<SCO over PCM source name>"); /* TODO: not close fd when entering suspend mode in a2dp */ @@ -97,10 +96,9 @@ static const char* const valid_modargs[] = { "rate", "channels", "path", -#ifdef NOKIA + "auto_connect", "sco_sink", "sco_source", -#endif NULL }; @@ -114,31 +112,31 @@ struct a2dp_info { size_t buffer_size; /* Size of the buffer */ uint16_t seq_num; /* Cumulative packet sequence */ + uint8_t min_bitpool; + uint8_t max_bitpool; }; struct hsp_info { pcm_capabilities_t pcm_capabilities; -#ifdef NOKIA pa_sink *sco_sink; + void (*sco_sink_set_volume)(pa_sink *s); pa_source *sco_source; -#endif + void (*sco_source_set_volume)(pa_source *s); pa_hook_slot *sink_state_changed_slot; pa_hook_slot *source_state_changed_slot; }; -enum profile { - PROFILE_A2DP, - PROFILE_HSP, - PROFILE_OFF -}; - struct userdata { pa_core *core; pa_module *module; char *address; char *path; + char *transport; + char *accesstype; + pa_bluetooth_discovery *discovery; + pa_bool_t auto_connect; pa_dbus_connection *connection; @@ -174,17 +172,18 @@ struct userdata { int stream_write_type; int service_write_type, service_read_type; + + pa_bool_t filter_added; }; #define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC) #define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) #define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) -#ifdef NOKIA #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) -#endif static int init_bt(struct userdata *u); static int init_profile(struct userdata *u); @@ -193,10 +192,14 @@ static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg) { ssize_t r; pa_assert(u); - pa_assert(u->service_fd >= 0); pa_assert(msg); pa_assert(msg->length > 0); + if (u->service_fd < 0) { + pa_log_warn("Service not connected"); + return -1; + } + pa_log_debug("Sending %s -> %s", pa_strnull(bt_audio_strtype(msg->type)), pa_strnull(bt_audio_strname(msg->name))); @@ -218,9 +221,7 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r pa_assert(u); pa_assert(u->service_fd >= 0); pa_assert(msg); - - if (room <= 0) - room = BT_SUGGESTED_BUFFER_SIZE; + pa_assert(room >= sizeof(*msg)); pa_log_debug("Trying to receive message from audio service..."); @@ -233,6 +234,11 @@ static int service_recv(struct userdata *u, bt_audio_msg_header_t *msg, size_t r return -1; } + if (msg->length > room) { + pa_log_error("Not enough room."); + return -1; + } + /* Secondly, read the payload */ if (msg->length > sizeof(*msg)) { @@ -306,13 +312,13 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa pa_log_debug("Payload size is %lu %lu", (unsigned long) bytes_left, (unsigned long) sizeof(*codec)); - if ((u->profile == PROFILE_A2DP && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) || - (u->profile == PROFILE_HSP && codec->transport != BT_CAPABILITIES_TRANSPORT_SCO)) { + if (((u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) || + ((u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) && codec->transport != BT_CAPABILITIES_TRANSPORT_SCO)) { pa_log_error("Got capabilities for wrong codec."); return -1; } - if (u->profile == PROFILE_HSP) { + if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { if (bytes_left <= 0 || codec->length != sizeof(u->hsp.pcm_capabilities)) return -1; @@ -343,6 +349,26 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa return codec->seid; memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); + + } else if (u->profile == PROFILE_A2DP_SOURCE) { + + while (bytes_left > 0) { + if ((codec->type == BT_A2DP_SBC_SOURCE) && !codec->lock) + break; + + bytes_left -= codec->length; + codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length); + } + + if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities)) + return -1; + + pa_assert(codec->type == BT_A2DP_SBC_SOURCE); + + if (codec->configured && seid == 0) + return codec->seid; + + memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); } return 0; @@ -367,13 +393,13 @@ static int get_caps(struct userdata *u, uint8_t seid) { msg.getcaps_req.seid = seid; pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object)); - if (u->profile == PROFILE_A2DP) + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP; else { - pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO; } - msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT; + msg.getcaps_req.flags = u->auto_connect ? BT_FLAG_AUTOCONNECT : 0; if (service_send(u, &msg.getcaps_req.h) < 0) return -1; @@ -450,7 +476,7 @@ static int setup_a2dp(struct userdata *u) { }; pa_assert(u); - pa_assert(u->profile == PROFILE_A2DP); + pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE); cap = &u->a2dp.sbc_capabilities; @@ -543,7 +569,7 @@ static int setup_a2dp(struct userdata *u) { } /* Run from main thread */ -static void setup_sbc(struct a2dp_info *a2dp) { +static void setup_sbc(struct a2dp_info *a2dp, enum profile p) { sbc_capabilities_t *active_capabilities; pa_assert(a2dp); @@ -629,7 +655,11 @@ static void setup_sbc(struct a2dp_info *a2dp) { pa_assert_not_reached(); } - a2dp->sbc.bitpool = active_capabilities->max_bitpool; + a2dp->min_bitpool = active_capabilities->min_bitpool; + a2dp->max_bitpool = active_capabilities->max_bitpool; + + /* Set minimum bitpool for source to get the maximum possible block_size */ + a2dp->sbc.bitpool = p == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool; a2dp->codesize = sbc_get_codesize(&a2dp->sbc); a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); } @@ -651,8 +681,8 @@ static int set_conf(struct userdata *u) { msg.open_req.h.length = sizeof(msg.open_req); pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object)); - msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1; - msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; + msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1; + msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK; if (service_send(u, &msg.open_req.h) < 0) return -1; @@ -660,13 +690,13 @@ static int set_conf(struct userdata *u) { if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0) return -1; - if (u->profile == PROFILE_A2DP ) { + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { u->sample_spec.format = PA_SAMPLE_S16LE; if (setup_a2dp(u) < 0) return -1; } else { - pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); u->sample_spec.format = PA_SAMPLE_S16LE; u->sample_spec.channels = 1; @@ -678,7 +708,7 @@ static int set_conf(struct userdata *u) { msg.setconf_req.h.name = BT_SET_CONFIGURATION; msg.setconf_req.h.length = sizeof(msg.setconf_req); - if (u->profile == PROFILE_A2DP) { + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities)); } else { msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO; @@ -696,8 +726,8 @@ static int set_conf(struct userdata *u) { u->link_mtu = msg.setconf_rsp.link_mtu; /* setup SBC encoder now we agree on parameters */ - if (u->profile == PROFILE_A2DP) { - setup_sbc(&u->a2dp); + if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) { + setup_sbc(&u->a2dp, u->profile); u->block_size = ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) @@ -712,42 +742,44 @@ static int set_conf(struct userdata *u) { return 0; } -/* from IO thread, except in SCO over PCM */ -static int start_stream_fd(struct userdata *u) { - union { - bt_audio_msg_header_t rsp; - struct bt_start_stream_req start_req; - struct bt_start_stream_rsp start_rsp; - struct bt_new_stream_ind streamfd_ind; - bt_audio_error_t error; - uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; - } msg; - struct pollfd *pollfd; - int one; +/* from IO thread */ +static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) +{ + struct a2dp_info *a2dp; pa_assert(u); - pa_assert(u->rtpoll); - pa_assert(!u->rtpoll_item); - pa_assert(u->stream_fd < 0); - memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); - msg.start_req.h.type = BT_REQUEST; - msg.start_req.h.name = BT_START_STREAM; - msg.start_req.h.length = sizeof(msg.start_req); + a2dp = &u->a2dp; - if (service_send(u, &msg.start_req.h) < 0) - return -1; + if (a2dp->sbc.bitpool == bitpool) + return; - if (service_expect(u, &msg.rsp, sizeof(msg), BT_START_STREAM, sizeof(msg.start_rsp)) < 0) - return -1; + if (bitpool > a2dp->max_bitpool) + bitpool = a2dp->max_bitpool; + else if (bitpool < a2dp->min_bitpool) + bitpool = a2dp->min_bitpool; - if (service_expect(u, &msg.rsp, sizeof(msg), BT_NEW_STREAM, sizeof(msg.streamfd_ind)) < 0) - return -1; + a2dp->sbc.bitpool = bitpool; - if ((u->stream_fd = bt_audio_service_get_data_fd(u->service_fd)) < 0) { - pa_log("Failed to get stream fd from audio service."); - return -1; - } + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool); + + u->block_size = + (u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / a2dp->frame_length * a2dp->codesize; + + pa_sink_set_max_request_within_thread(u->sink, u->block_size); + pa_sink_set_fixed_latency_within_thread(u->sink, + FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->block_size, &u->sample_spec)); +} + +/* from IO thread, except in SCO over PCM */ + +static int setup_stream(struct userdata *u) { + struct pollfd *pollfd; + int one; pa_make_fd_nonblock(u->stream_fd); pa_make_socket_low_delay(u->stream_fd); @@ -758,6 +790,9 @@ static int start_stream_fd(struct userdata *u) { pa_log_debug("Stream properly set up, we're ready to roll!"); + if (u->profile == PROFILE_A2DP) + a2dp_set_bitpool(u, u->a2dp.max_bitpool); + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->fd = u->stream_fd; @@ -773,12 +808,49 @@ static int start_stream_fd(struct userdata *u) { TRUE, TRUE, 10, - pa_rtclock_usec(), + pa_rtclock_now(), TRUE); return 0; } +static int start_stream_fd(struct userdata *u) { + union { + bt_audio_msg_header_t rsp; + struct bt_start_stream_req start_req; + struct bt_start_stream_rsp start_rsp; + struct bt_new_stream_ind streamfd_ind; + bt_audio_error_t error; + uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; + } msg; + + pa_assert(u); + pa_assert(u->rtpoll); + pa_assert(!u->rtpoll_item); + pa_assert(u->stream_fd < 0); + + memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); + msg.start_req.h.type = BT_REQUEST; + msg.start_req.h.name = BT_START_STREAM; + msg.start_req.h.length = sizeof(msg.start_req); + + if (service_send(u, &msg.start_req.h) < 0) + return -1; + + if (service_expect(u, &msg.rsp, sizeof(msg), BT_START_STREAM, sizeof(msg.start_rsp)) < 0) + return -1; + + if (service_expect(u, &msg.rsp, sizeof(msg), BT_NEW_STREAM, sizeof(msg.streamfd_ind)) < 0) + return -1; + + if ((u->stream_fd = bt_audio_service_get_data_fd(u->service_fd)) < 0) { + pa_log("Failed to get stream fd from audio service."); + return -1; + } + + return setup_stream(u); +} + /* from IO thread */ static int stop_stream_fd(struct userdata *u) { union { @@ -792,23 +864,25 @@ static int stop_stream_fd(struct userdata *u) { pa_assert(u); pa_assert(u->rtpoll); - pa_assert(u->rtpoll_item); - pa_assert(u->stream_fd >= 0); - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } - memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); - msg.start_req.h.type = BT_REQUEST; - msg.start_req.h.name = BT_STOP_STREAM; - msg.start_req.h.length = sizeof(msg.start_req); + if (u->stream_fd >= 0) { + memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE); + msg.start_req.h.type = BT_REQUEST; + msg.start_req.h.name = BT_STOP_STREAM; + msg.start_req.h.length = sizeof(msg.start_req); - if (service_send(u, &msg.start_req.h) < 0 || - service_expect(u, &msg.rsp, sizeof(msg), BT_STOP_STREAM, sizeof(msg.start_rsp)) < 0) - r = -1; + if (service_send(u, &msg.start_req.h) < 0 || + service_expect(u, &msg.rsp, sizeof(msg), BT_STOP_STREAM, sizeof(msg.start_rsp)) < 0) + r = -1; - pa_close(u->stream_fd); - u->stream_fd = -1; + pa_close(u->stream_fd); + u->stream_fd = -1; + } if (u->read_smoother) { pa_smoother_free(u->read_smoother); @@ -818,6 +892,75 @@ static int stop_stream_fd(struct userdata *u) { return r; } +static void bt_transport_release(struct userdata *u) { + const char *accesstype = "rw"; + const pa_bluetooth_transport *t; + + /* Ignore if already released */ + if (!u->accesstype) + return; + + pa_log_debug("Releasing transport %s", u->transport); + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + if (t) + pa_bluetooth_transport_release(t, accesstype); + + pa_xfree(u->accesstype); + u->accesstype = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + if (u->stream_fd >= 0) { + pa_close(u->stream_fd); + u->stream_fd = -1; + } + + if (u->read_smoother) { + pa_smoother_free(u->read_smoother); + u->read_smoother = NULL; + } +} + +static int bt_transport_acquire(struct userdata *u, pa_bool_t start) { + const char *accesstype = "rw"; + const pa_bluetooth_transport *t; + + if (u->accesstype) { + if (start) + goto done; + return 0; + } + + pa_log_debug("Acquiring transport %s", u->transport); + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + if (!t) { + pa_log("Transport %s no longer available", u->transport); + pa_xfree(u->transport); + u->transport = NULL; + return -1; + } + + /* FIXME: Handle in/out MTU properly when unix socket is not longer supported */ + u->stream_fd = pa_bluetooth_transport_acquire(t, accesstype, NULL, &u->link_mtu); + if (u->stream_fd < 0) + return -1; + + u->accesstype = pa_xstrdup(accesstype); + pa_log_info("Transport %s acquired: fd %d", u->transport, u->stream_fd); + + if (!start) + return 0; + +done: + pa_log_info("Transport %s resuming", u->transport); + return setup_stream(u); +} + /* Run from IO thread */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; @@ -836,11 +979,15 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); /* Stop the device if the source is suspended as well */ - if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) + if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) { /* We deliberately ignore whether stopping * actually worked. Since the stream_fd is * closed it doesn't really matter */ - stop_stream_fd(u); + if (u->transport) + bt_transport_release(u); + else + stop_stream_fd(u); + } break; @@ -850,9 +997,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse break; /* Resume the device if the source was suspended as well */ - if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) - if (start_stream_fd(u) < 0) + if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) { + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) + failed = TRUE; + } else if (start_stream_fd(u) < 0) failed = TRUE; + } break; case PA_SINK_UNLINKED: @@ -867,20 +1018,20 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse if (u->read_smoother) { pa_usec_t wi, ri; - ri = pa_smoother_get(u->read_smoother, pa_rtclock_usec()); + ri = pa_smoother_get(u->read_smoother, pa_rtclock_now()); wi = pa_bytes_to_usec(u->write_index + u->block_size, &u->sample_spec); *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; } else { pa_usec_t ri, wi; - ri = pa_rtclock_usec() - u->started_at; + ri = pa_rtclock_now() - u->started_at; wi = pa_bytes_to_usec(u->write_index, &u->sample_spec); *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; } - *((pa_usec_t*) data) += u->sink->fixed_latency; + *((pa_usec_t*) data) += u->sink->thread_info.fixed_latency; return 0; } } @@ -908,11 +1059,15 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); /* Stop the device if the sink is suspended as well */ - if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) - stop_stream_fd(u); + if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) { + if (u->transport) + bt_transport_release(u); + else + stop_stream_fd(u); + } if (u->read_smoother) - pa_smoother_pause(u->read_smoother, pa_rtclock_usec()); + pa_smoother_pause(u->read_smoother, pa_rtclock_now()); break; case PA_SOURCE_IDLE: @@ -921,10 +1076,13 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off break; /* Resume the device if the sink was suspended as well */ - if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) - if (start_stream_fd(u) < 0) + if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) failed = TRUE; - + } else if (start_stream_fd(u) < 0) + failed = TRUE; + } /* We don't resume the smoother here. Instead we * wait until the first packet arrives */ break; @@ -939,10 +1097,14 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off case PA_SOURCE_MESSAGE_GET_LATENCY: { pa_usec_t wi, ri; - wi = pa_smoother_get(u->read_smoother, pa_rtclock_usec()); - ri = pa_bytes_to_usec(u->read_index, &u->sample_spec); + if (u->read_smoother) { + wi = pa_smoother_get(u->read_smoother, pa_rtclock_now()); + ri = pa_bytes_to_usec(u->read_index, &u->sample_spec); + + *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency; + } else + *((pa_usec_t*) data) = 0; - *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency; return 0; } @@ -958,7 +1120,7 @@ static int hsp_process_render(struct userdata *u) { int ret = 0; pa_assert(u); - pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); pa_assert(u->sink); /* First, render some data */ @@ -1023,7 +1185,7 @@ static int hsp_process_push(struct userdata *u) { pa_memchunk memchunk; pa_assert(u); - pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); pa_assert(u->source); pa_assert(u->read_smoother); @@ -1086,7 +1248,7 @@ static int hsp_process_push(struct userdata *u) { if (!found_tstamp) { pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); - tstamp = pa_rtclock_usec(); + tstamp = pa_rtclock_now(); } pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec)); @@ -1154,7 +1316,7 @@ static int a2dp_process_render(struct userdata *u) { to_write = a2dp->buffer_size - sizeof(*header) - sizeof(*payload); while (PA_LIKELY(to_encode > 0 && to_write > 0)) { - size_t written; + ssize_t written; ssize_t encoded; encoded = sbc_encode(&a2dp->sbc, @@ -1223,7 +1385,7 @@ static int a2dp_process_render(struct userdata *u) { break; pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno)); - ret = -1; + ret = -1; break; } @@ -1249,6 +1411,144 @@ static int a2dp_process_render(struct userdata *u) { return ret; } +static int a2dp_process_push(struct userdata *u) { + int ret = 0; + pa_memchunk memchunk; + + pa_assert(u); + pa_assert(u->profile == PROFILE_A2DP_SOURCE); + pa_assert(u->source); + pa_assert(u->read_smoother); + + memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size); + memchunk.index = memchunk.length = 0; + + for (;;) { + pa_bool_t found_tstamp = FALSE; + pa_usec_t tstamp; + struct a2dp_info *a2dp; + struct rtp_header *header; + struct rtp_payload *payload; + const void *p; + void *d; + ssize_t l; + size_t to_write, to_decode; + unsigned frame_count; + + a2dp_prepare_buffer(u); + + a2dp = &u->a2dp; + header = a2dp->buffer; + payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); + + l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type); + + if (l <= 0) { + + if (l < 0 && errno == EINTR) + /* Retry right away if we got interrupted */ + continue; + + else if (l < 0 && errno == EAGAIN) + /* Hmm, apparently the socket was not readable, give up for now. */ + break; + + pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); + ret = -1; + break; + } + + pa_assert((size_t) l <= a2dp->buffer_size); + + u->read_index += (uint64_t) l; + + /* TODO: get timestamp from rtp */ + if (!found_tstamp) { + /* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */ + tstamp = pa_rtclock_now(); + } + + pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec)); + pa_smoother_resume(u->read_smoother, tstamp, TRUE); + + p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); + to_decode = l - sizeof(*header) - sizeof(*payload); + + d = pa_memblock_acquire(memchunk.memblock); + to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock); + + while (PA_LIKELY(to_decode > 0)) { + size_t written; + ssize_t decoded; + + decoded = sbc_decode(&a2dp->sbc, + p, to_decode, + d, to_write, + &written); + + if (PA_UNLIKELY(decoded <= 0)) { + pa_log_error("SBC decoding error (%li)", (long) decoded); + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + return -1; + } + +/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */ +/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */ + + /* Reset frame length, it can be changed due to bitpool change */ + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + pa_assert_fp((size_t) decoded <= to_decode); + pa_assert_fp((size_t) decoded == a2dp->frame_length); + + pa_assert_fp((size_t) written == a2dp->codesize); + + p = (const uint8_t*) p + decoded; + to_decode -= decoded; + + d = (uint8_t*) d + written; + to_write -= written; + + frame_count++; + } + + memchunk.length -= to_write; + + pa_memblock_release(memchunk.memblock); + + pa_source_post(u->source, &memchunk); + + ret = 1; + break; + } + + pa_memblock_unref(memchunk.memblock); + + return ret; +} + +static void a2dp_reduce_bitpool(struct userdata *u) +{ + struct a2dp_info *a2dp; + uint8_t bitpool; + + pa_assert(u); + + a2dp = &u->a2dp; + + /* Check if bitpool is already at its limit */ + if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT) + return; + + bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP; + + if (bitpool < BITPOOL_DEC_LIMIT) + bitpool = BITPOOL_DEC_LIMIT; + + a2dp_set_bitpool(u, bitpool); +} + static void thread_func(void *userdata) { struct userdata *u = userdata; unsigned do_write = 0; @@ -1261,11 +1561,13 @@ static void thread_func(void *userdata) { if (u->core->realtime_scheduling) pa_make_realtime(u->core->realtime_priority); - if (start_stream_fd(u) < 0) - goto fail; - pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); + + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) + goto fail; + } else if (start_stream_fd(u) < 0) + goto fail; for (;;) { struct pollfd *pollfd; @@ -1285,7 +1587,12 @@ static void thread_func(void *userdata) { if (pollfd && (pollfd->revents & POLLIN)) { int n_read; - if ((n_read = hsp_process_push(u)) < 0) + if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) + n_read = hsp_process_push(u); + else + n_read = a2dp_process_push(u); + + if (n_read < 0) goto fail; /* We just read something, so we are supposed to write something, too */ @@ -1309,7 +1616,7 @@ static void thread_func(void *userdata) { /* Hmm, there is no input stream we could synchronize * to. So let's do things by time */ - time_passed = pa_rtclock_usec() - u->started_at; + time_passed = pa_rtclock_now() - u->started_at; audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec); if (audio_sent <= time_passed) { @@ -1319,18 +1626,24 @@ static void thread_func(void *userdata) { if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) { pa_usec_t skip_usec; uint64_t skip_bytes; - pa_memchunk tmp; skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); - pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", - (unsigned long long) skip_usec, - (unsigned long long) skip_bytes); + if (skip_bytes > 0) { + pa_memchunk tmp; + + pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream", + (unsigned long long) skip_usec, + (unsigned long long) skip_bytes); - pa_sink_render_full(u->sink, skip_bytes, &tmp); - pa_memblock_unref(tmp.memblock); - u->write_index += skip_bytes; + pa_sink_render_full(u->sink, skip_bytes, &tmp); + pa_memblock_unref(tmp.memblock); + u->write_index += skip_bytes; + + if (u->profile == PROFILE_A2DP) + a2dp_reduce_bitpool(u); + } } do_write = 1; @@ -1341,7 +1654,7 @@ static void thread_func(void *userdata) { int n_written; if (u->write_index <= 0) - u->started_at = pa_rtclock_usec(); + u->started_at = pa_rtclock_now(); if (u->profile == PROFILE_A2DP) { if ((n_written = a2dp_process_render(u)) < 0) @@ -1351,21 +1664,27 @@ static void thread_func(void *userdata) { goto fail; } + if (n_written == 0) + pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!"); + do_write -= n_written; writable = FALSE; } if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) { - pa_usec_t time_passed, next_write_at, sleep_for; - - /* Hmm, there is no input stream we could synchronize - * to. So let's estimate when we need to wake up the latest */ - - time_passed = pa_rtclock_usec() - u->started_at; - next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec); - sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0; - -/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */ + pa_usec_t sleep_for; + pa_usec_t time_passed, next_write_at; + + if (writable) { + /* Hmm, there is no input stream we could synchronize + * to. So let's estimate when we need to wake up the latest */ + time_passed = pa_rtclock_now() - u->started_at; + next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec); + sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0; + /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */ + } else + /* drop stream every 500 ms */ + sleep_for = PA_USEC_PER_MSEC * 500; pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for); disable_timer = FALSE; @@ -1425,8 +1744,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us dbus_message_get_path(m), dbus_message_get_member(m)); - if (!dbus_message_has_path(m, u->path)) - goto fail; + if (!dbus_message_has_path(m, u->path) && !dbus_message_has_path(m, u->transport)) + goto fail; if (dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged") || dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { @@ -1434,23 +1753,55 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us dbus_uint16_t gain; pa_cvolume v; - if (!dbus_message_get_args(m, &err, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID) || gain > 15) { + if (!dbus_message_get_args(m, &err, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID) || gain > HSP_MAX_GAIN) { pa_log("Failed to parse org.bluez.Headset.{Speaker|Microphone}GainChanged: %s", err.message); goto fail; } if (u->profile == PROFILE_HSP) { if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) { + pa_volume_t volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN); + + /* increment volume by one to correct rounding errors */ + if (volume < PA_VOLUME_NORM) + volume++; - pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&v, u->sample_spec.channels, volume); pa_sink_volume_changed(u->sink, &v); } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { + pa_volume_t volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN); - pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + /* increment volume by one to correct rounding errors */ + if (volume < PA_VOLUME_NORM) + volume++; + + pa_cvolume_set(&v, u->sample_spec.channels, volume); pa_source_volume_changed(u->source, &v); } } + } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) { + DBusMessageIter arg_i; + pa_bluetooth_transport *t; + pa_bool_t nrec; + + t = (pa_bluetooth_transport *) pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + pa_assert(t); + + if (!dbus_message_iter_init(m, &arg_i)) { + pa_log("Failed to parse PropertyChanged: %s", err.message); + goto fail; + } + + nrec = t->nrec; + + if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0) + goto fail; + + if (nrec != t->nrec) { + pa_log_debug("dbus: property 'NREC' changed to value '%s'", t->nrec ? "True" : "False"); + pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0"); + } } fail: @@ -1461,21 +1812,35 @@ fail: /* Run from main thread */ static void sink_set_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; DBusMessage *m; dbus_uint16_t gain; + pa_volume_t volume; + struct userdata *u; + char *k; + + pa_assert(s); + pa_assert(s->core); + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); + u = pa_shared_get(s->core, k); + pa_xfree(k); pa_assert(u); + pa_assert(u->sink == s); + pa_assert(u->profile == PROFILE_HSP); - if (u->profile != PROFILE_HSP) - return; + gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; - gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; + if (gain > HSP_MAX_GAIN) + gain = HSP_MAX_GAIN; - if (gain > 15) - gain = 15; + volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN); - pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + /* increment volume by one to correct rounding errors */ + if (volume < PA_VOLUME_NORM) + volume++; + + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1485,21 +1850,35 @@ static void sink_set_volume_cb(pa_sink *s) { /* Run from main thread */ static void source_set_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; DBusMessage *m; dbus_uint16_t gain; + pa_volume_t volume; + struct userdata *u; + char *k; + + pa_assert(s); + pa_assert(s->core); + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); + u = pa_shared_get(s->core, k); + pa_xfree(k); pa_assert(u); + pa_assert(u->source == s); + pa_assert(u->profile == PROFILE_HSP); - if (u->profile != PROFILE_HSP) - return; + gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; - gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM; + if (gain > HSP_MAX_GAIN) + gain = HSP_MAX_GAIN; - if (gain > 15) - gain = 15; + volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN); - pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + /* increment volume by one to correct rounding errors */ + if (volume < PA_VOLUME_NORM) + volume++; + + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1536,34 +1915,48 @@ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, p return pa_sprintf_malloc("bluez_%s.%s", type, n); } -#ifdef NOKIA - -static void sco_over_pcm_state_update(struct userdata *u) { +static int sco_over_pcm_state_update(struct userdata *u, pa_bool_t changed) { pa_assert(u); pa_assert(USE_SCO_OVER_PCM(u)); if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) || PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) { - if (u->service_fd >= 0) - return; + if (u->service_fd >= 0 && u->stream_fd >= 0) + return 0; + + init_bt(u); pa_log_debug("Resuming SCO over PCM"); - if ((init_bt(u) < 0) || (init_profile(u) < 0)) + if (init_profile(u) < 0) { pa_log("Can't resume SCO over PCM"); + return -1; + } - start_stream_fd(u); - } else { + if (u->transport) + return bt_transport_acquire(u, TRUE); - if (u->service_fd < 0) - return; + return start_stream_fd(u); + } - stop_stream_fd(u); + if (changed) { + if (u->service_fd < 0 && u->stream_fd < 0) + return 0; pa_log_debug("Closing SCO over PCM"); - pa_close(u->service_fd); - u->service_fd = -1; + + if (u->transport) + bt_transport_release(u); + else if (u->stream_fd >= 0) + stop_stream_fd(u); + + if (u->service_fd >= 0) { + pa_close(u->service_fd); + u->service_fd = -1; + } } + + return 0; } static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) { @@ -1574,7 +1967,7 @@ static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct use if (s != u->hsp.sco_sink) return PA_HOOK_OK; - sco_over_pcm_state_update(u); + sco_over_pcm_state_update(u, TRUE); return PA_HOOK_OK; } @@ -1587,17 +1980,15 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct if (s != u->hsp.sco_source) return PA_HOOK_OK; - sco_over_pcm_state_update(u); + sco_over_pcm_state_update(u, TRUE); return PA_HOOK_OK; } -#endif - /* Run from main thread */ static int add_sink(struct userdata *u) { + char *k; -#ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { pa_proplist *p; @@ -1610,10 +2001,7 @@ static int add_sink(struct userdata *u) { if (!u->hsp.sink_state_changed_slot) u->hsp.sink_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u); - } else -#endif - - { + } else { pa_sink_new_data data; pa_bool_t b; @@ -1622,6 +2010,8 @@ static int add_sink(struct userdata *u) { data.module = u->module; pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); + if (u->profile == PROFILE_HSP) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); data.card = u->card; data.name = get_name("sink", u->modargs, u->address, &b); data.namereg_fail = b; @@ -1652,6 +2042,10 @@ static int add_sink(struct userdata *u) { if (u->profile == PROFILE_HSP) { u->sink->set_volume = sink_set_volume_cb; u->sink->n_volume_steps = 16; + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_set(u->core, k, u); + pa_xfree(k); } return 0; @@ -1659,8 +2053,8 @@ static int add_sink(struct userdata *u) { /* Run from main thread */ static int add_source(struct userdata *u) { + char *k; -#ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { u->source = u->hsp.sco_source; pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "hsp"); @@ -1668,10 +2062,7 @@ static int add_source(struct userdata *u) { if (!u->hsp.source_state_changed_slot) u->hsp.source_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u); - } else -#endif - - { + } else { pa_source_new_data data; pa_bool_t b; @@ -1679,7 +2070,10 @@ static int add_source(struct userdata *u) { data.driver = __FILE__; data.module = u->module; pa_source_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp"); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP_SOURCE ? "a2dp_source" : "hsp"); + if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); + data.card = u->card; data.name = get_name("source", u->modargs, u->address, &b); data.namereg_fail = b; @@ -1702,14 +2096,27 @@ static int add_source(struct userdata *u) { u->source->parent.process_msg = source_process_msg; pa_source_set_fixed_latency(u->source, - (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + + (u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) + pa_bytes_to_usec(u->block_size, &u->sample_spec)); } + if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) { + if (u->transport) { + const pa_bluetooth_transport *t; + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + pa_assert(t); + pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0"); + } else + pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0"); + } + if (u->profile == PROFILE_HSP) { - pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0"); u->source->set_volume = source_set_volume_cb; u->source->n_volume_steps = 16; + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_set(u->core, k, u); + pa_xfree(k); } return 0; @@ -1729,7 +2136,8 @@ static void shutdown_bt(struct userdata *u) { if (u->service_fd >= 0) { pa_close(u->service_fd); u->service_fd = -1; - u->service_write_type = u->service_write_type = 0; + u->service_write_type = 0; + u->service_read_type = 0; } if (u->write_memchunk.memblock) { @@ -1738,6 +2146,144 @@ static void shutdown_bt(struct userdata *u) { } } +static int bt_transport_config_a2dp(struct userdata *u) { + const pa_bluetooth_transport *t; + struct a2dp_info *a2dp = &u->a2dp; + a2dp_sbc_t *config; + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + pa_assert(t); + + config = (a2dp_sbc_t *) t->config; + + u->sample_spec.format = PA_SAMPLE_S16LE; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = TRUE; + + switch (config->frequency) { + case BT_SBC_SAMPLING_FREQ_16000: + a2dp->sbc.frequency = SBC_FREQ_16000; + u->sample_spec.rate = 16000U; + break; + case BT_SBC_SAMPLING_FREQ_32000: + a2dp->sbc.frequency = SBC_FREQ_32000; + u->sample_spec.rate = 32000U; + break; + case BT_SBC_SAMPLING_FREQ_44100: + a2dp->sbc.frequency = SBC_FREQ_44100; + u->sample_spec.rate = 44100U; + break; + case BT_SBC_SAMPLING_FREQ_48000: + a2dp->sbc.frequency = SBC_FREQ_48000; + u->sample_spec.rate = 48000U; + break; + default: + pa_assert_not_reached(); + } + + switch (config->channel_mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + a2dp->sbc.mode = SBC_MODE_MONO; + u->sample_spec.channels = 1; + break; + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + u->sample_spec.channels = 2; + break; + case BT_A2DP_CHANNEL_MODE_STEREO: + a2dp->sbc.mode = SBC_MODE_STEREO; + u->sample_spec.channels = 2; + break; + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + u->sample_spec.channels = 2; + break; + default: + pa_assert_not_reached(); + } + + switch (config->allocation_method) { + case BT_A2DP_ALLOCATION_SNR: + a2dp->sbc.allocation = SBC_AM_SNR; + break; + case BT_A2DP_ALLOCATION_LOUDNESS: + a2dp->sbc.allocation = SBC_AM_LOUDNESS; + break; + default: + pa_assert_not_reached(); + } + + switch (config->subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + default: + pa_assert_not_reached(); + } + + switch (config->block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + default: + pa_assert_not_reached(); + } + + a2dp->min_bitpool = config->min_bitpool; + a2dp->max_bitpool = config->max_bitpool; + + /* Set minimum bitpool for source to get the maximum possible block_size */ + a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool; + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + u->block_size = + ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / a2dp->frame_length + * a2dp->codesize); + + pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool); + + return 0; +} + +static int bt_transport_config(struct userdata *u) { + if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { + u->block_size = u->link_mtu; + u->sample_spec.format = PA_SAMPLE_S16LE; + u->sample_spec.channels = 1; + u->sample_spec.rate = 8000; + return 0; + } + + return bt_transport_config_a2dp(u); +} + +/* Run from main thread */ +static int bt_transport_open(struct userdata *u) { + if (bt_transport_acquire(u, FALSE) < 0) + return -1; + + return bt_transport_config(u); +} + /* Run from main thread */ static int init_bt(struct userdata *u) { pa_assert(u); @@ -1745,10 +2291,11 @@ static int init_bt(struct userdata *u) { shutdown_bt(u); u->stream_write_type = 0; - u->service_write_type = u->service_write_type = 0; + u->service_write_type = 0; + u->service_read_type = 0; if ((u->service_fd = bt_audio_service_open()) < 0) { - pa_log_error("Couldn't connect to bluetooth audio service"); + pa_log_warn("Bluetooth audio service not available"); return -1; } @@ -1759,8 +2306,30 @@ static int init_bt(struct userdata *u) { /* Run from main thread */ static int setup_bt(struct userdata *u) { + const pa_bluetooth_device *d; + const pa_bluetooth_transport *t; + pa_assert(u); + if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) { + pa_log_error("Failed to get device object."); + return -1; + } + + /* release transport if exist */ + if (u->transport) { + bt_transport_release(u); + pa_xfree(u->transport); + u->transport = NULL; + } + + /* check if profile has a transport */ + t = pa_bluetooth_device_get_transport(d, u->profile); + if (t) { + u->transport = pa_xstrdup(t->path); + return bt_transport_open(u); + } + if (get_caps(u, 0) < 0) return -1; @@ -1771,12 +2340,10 @@ static int setup_bt(struct userdata *u) { pa_log_debug("Connection to the device configured"); -#ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { pa_log_debug("Configured to use SCO over PCM"); return 0; } -#endif pa_log_debug("Got the stream socket"); @@ -1793,11 +2360,14 @@ static int init_profile(struct userdata *u) { return -1; if (u->profile == PROFILE_A2DP || - u->profile == PROFILE_HSP) + u->profile == PROFILE_HSP || + u->profile == PROFILE_HFGW) if (add_sink(u) < 0) r = -1; - if (u->profile == PROFILE_HSP) + if (u->profile == PROFILE_HSP || + u->profile == PROFILE_A2DP_SOURCE || + u->profile == PROFILE_HFGW) if (add_source(u) < 0) r = -1; @@ -1806,6 +2376,8 @@ static int init_profile(struct userdata *u) { /* Run from main thread */ static void stop_thread(struct userdata *u) { + char *k; + pa_assert(u); if (u->thread) { @@ -1830,11 +2402,23 @@ static void stop_thread(struct userdata *u) { } if (u->sink) { + if (u->profile == PROFILE_HSP) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_remove(u->core, k); + pa_xfree(k); + } + pa_sink_unref(u->sink); u->sink = NULL; } if (u->source) { + if (u->profile == PROFILE_HSP) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_remove(u->core, k); + pa_xfree(k); + } + pa_source_unref(u->source); u->source = NULL; } @@ -1862,19 +2446,32 @@ static int start_thread(struct userdata *u) { u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); -#ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { - if (start_stream_fd(u) < 0) + if (sco_over_pcm_state_update(u, FALSE) < 0) { + char *k; + + if (u->sink) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_remove(u->core, k); + pa_xfree(k); + u->sink = NULL; + } + if (u->source) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_remove(u->core, k); + pa_xfree(k); + u->source = NULL; + } return -1; + } pa_sink_ref(u->sink); pa_source_ref(u->source); /* FIXME: monitor stream_fd error */ return 0; } -#endif - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) { pa_log_error("Failed to create IO thread"); stop_thread(u); return -1; @@ -1901,6 +2498,22 @@ static int start_thread(struct userdata *u) { return 0; } +static void save_sco_volume_callbacks(struct userdata *u) { + pa_assert(u); + pa_assert(USE_SCO_OVER_PCM(u)); + + u->hsp.sco_sink_set_volume = u->hsp.sco_sink->set_volume; + u->hsp.sco_source_set_volume = u->hsp.sco_source->set_volume; +} + +static void restore_sco_volume_callbacks(struct userdata *u) { + pa_assert(u); + pa_assert(USE_SCO_OVER_PCM(u)); + + u->hsp.sco_sink->set_volume = u->hsp.sco_sink_set_volume; + u->hsp.sco_source->set_volume = u->hsp.sco_source_set_volume; +} + /* Run from main thread */ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { struct userdata *u; @@ -1916,7 +2529,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { if (!(device = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) { pa_log_error("Failed to get device object."); - return -1; + return -PA_ERR_IO; } /* The state signal is sent by bluez, so it is racy to check @@ -1926,35 +2539,43 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { module will be unloaded. */ if (device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) { pa_log_warn("HSP is not connected, refused to switch profile"); - return -1; + return -PA_ERR_IO; } else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) { pa_log_warn("A2DP is not connected, refused to switch profile"); - return -1; + return -PA_ERR_IO; + } + else if (device->hfgw_state <= PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW) { + pa_log_warn("HandsfreeGateway is not connected, refused to switch profile"); + return -PA_ERR_IO; } if (u->sink) { - inputs = pa_sink_move_all_start(u->sink); -#ifdef NOKIA + inputs = pa_sink_move_all_start(u->sink, NULL); + if (!USE_SCO_OVER_PCM(u)) -#endif pa_sink_unlink(u->sink); } if (u->source) { - outputs = pa_source_move_all_start(u->source); -#ifdef NOKIA + outputs = pa_source_move_all_start(u->source, NULL); + if (!USE_SCO_OVER_PCM(u)) -#endif pa_source_unlink(u->source); } stop_thread(u); shutdown_bt(u); + if (USE_SCO_OVER_PCM(u)) + restore_sco_volume_callbacks(u); + u->profile = *d; u->sample_spec = u->requested_sample_spec; + if (USE_SCO_OVER_PCM(u)) + save_sco_volume_callbacks(u); + init_bt(u); if (u->profile != PROFILE_OFF) @@ -2038,6 +2659,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { pa_hashmap_put(data.profiles, p->name, p); } + if (pa_bluetooth_uuid_has(device->uuids, A2DP_SOURCE_UUID)) { + p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile)); + p->priority = 10; + p->n_sinks = 0; + p->n_sources = 1; + p->max_sink_channels = 0; + p->max_source_channels = 2; + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_A2DP_SOURCE; + + pa_hashmap_put(data.profiles, p->name, p); + } + if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) || pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); @@ -2053,6 +2688,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { pa_hashmap_put(data.profiles, p->name, p); } + if (pa_bluetooth_uuid_has(device->uuids, HFP_AG_UUID)) { + p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile)); + p->priority = 20; + p->n_sinks = 1; + p->n_sources = 1; + p->max_sink_channels = 1; + p->max_source_channels = 1; + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_HFGW; + + pa_hashmap_put(data.profiles, p->name, p); + } + pa_assert(!pa_hashmap_isempty(data.profiles)); p = pa_card_profile_new("off", _("Off"), sizeof(enum profile)); @@ -2079,8 +2728,21 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) { u->card->set_profile = card_set_profile; d = PA_CARD_PROFILE_DATA(u->card->active_profile); + + if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) || + (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) || + (device->hfgw_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW)) { + pa_log_warn("Default profile not connected, selecting off profile"); + u->card->active_profile = pa_hashmap_get(u->card->profiles, "off"); + u->card->save_profile = FALSE; + } + + d = PA_CARD_PROFILE_DATA(u->card->active_profile); u->profile = *d; + if (USE_SCO_OVER_PCM(u)) + save_sco_volume_callbacks(u); + return 0; } @@ -2102,7 +2764,7 @@ static const pa_bluetooth_device* find_device(struct userdata *u, const char *ad } if (address && !(pa_streq(d->address, address))) { - pa_log_error("Passed path %s and address %s don't match.", path, address); + pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address); return NULL; } @@ -2144,7 +2806,7 @@ int pa__init(pa_module* m) { struct userdata *u; const char *address, *path; DBusError err; - char *mike, *speaker; + char *mike, *speaker, *transport; const pa_bluetooth_device *device; pa_assert(m); @@ -2164,7 +2826,6 @@ int pa__init(pa_module* m) { u->sample_spec = m->core->default_sample_spec; u->modargs = ma; -#ifdef NOKIA if (pa_modargs_get_value(ma, "sco_sink", NULL) && !(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) { pa_log("SCO sink not found"); @@ -2176,7 +2837,6 @@ int pa__init(pa_module* m) { pa_log("SCO source not found"); goto fail; } -#endif if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 || u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) { @@ -2184,6 +2844,12 @@ int pa__init(pa_module* m) { goto fail; } + u->auto_connect = TRUE; + if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) { + pa_log("Failed to parse auto_connect= argument"); + goto fail; + } + channels = u->sample_spec.channels; if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels > PA_CHANNELS_MAX) { @@ -2209,26 +2875,26 @@ int pa__init(pa_module* m) { if (add_card(u, device) < 0) goto fail; - /* Connect to the BT service and query capabilities */ - if (init_bt(u) < 0) - goto fail; - if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) { pa_log_error("Failed to add filter function"); goto fail; } + u->filter_added = TRUE; speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path); mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path); + transport = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'"); if (pa_dbus_add_matches( pa_dbus_connection_get(u->connection), &err, speaker, mike, + transport, NULL) < 0) { pa_xfree(speaker); pa_xfree(mike); + pa_xfree(transport); pa_log("Failed to add D-Bus matches: %s", err.message); goto fail; @@ -2236,6 +2902,10 @@ int pa__init(pa_module* m) { pa_xfree(speaker); pa_xfree(mike); + pa_xfree(transport); + + /* Connect to the BT service */ + init_bt(u); if (u->profile != PROFILE_OFF) if (init_profile(u) < 0) @@ -2269,27 +2939,23 @@ int pa__get_n_used(pa_module *m) { void pa__done(pa_module *m) { struct userdata *u; + pa_assert(m); if (!(u = m->userdata)) return; - if (u->sink -#ifdef NOKIA - && !USE_SCO_OVER_PCM(u) -#endif - ) + if (u->sink && !USE_SCO_OVER_PCM(u)) pa_sink_unlink(u->sink); - if (u->source -#ifdef NOKIA - && !USE_SCO_OVER_PCM(u) -#endif - ) + if (u->source && !USE_SCO_OVER_PCM(u)) pa_source_unlink(u->source); stop_thread(u); + if (USE_SCO_OVER_PCM(u)) + restore_sco_volume_callbacks(u); + if (u->connection) { if (u->path) { @@ -2297,16 +2963,15 @@ void pa__done(pa_module *m) { speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path); mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path); - pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), - speaker, - mike, - NULL); + pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike, NULL); pa_xfree(speaker); pa_xfree(mike); } - dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); + if (u->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); + pa_dbus_connection_unref(u->connection); } @@ -2329,6 +2994,11 @@ void pa__done(pa_module *m) { pa_xfree(u->address); pa_xfree(u->path); + if (u->transport) { + bt_transport_release(u); + pa_xfree(u->transport); + } + if (u->discovery) pa_bluetooth_discovery_unref(u->discovery); diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index 788fee00..7b27f6bb 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2008 Joao Paulo Rechi Vita + Copyright 2008-2009 Joao Paulo Rechi Vita PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -25,14 +25,12 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <pulse/xmalloc.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/macro.h> -#include <pulsecore/llist.h> #include <pulsecore/core-util.h> #include <pulsecore/dbus-shared.h> @@ -42,21 +40,14 @@ PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers"); PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_USAGE("async=<Asynchronous initialization?>"); +PA_MODULE_USAGE("async=<Asynchronous initialization?> " + "sco_sink=<name of sink> " + "sco_source=<name of source> "); PA_MODULE_LOAD_ONCE(TRUE); -/* -#ifdef NOKIA - "sco_sink=<name of sink> " - "sco_source=<name of source>" -#endif -*/ - static const char* const valid_modargs[] = { -#ifdef NOKIA "sco_sink", "sco_source", -#endif "async", NULL }; @@ -83,8 +74,10 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const mi = pa_hashmap_get(u->hashmap, d->path); - if (!d->dead && - d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) { + if (!d->dead && d->device_connected > 0 && + (d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED || + d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED || + d->hfgw_state > PA_BT_AUDIO_STATE_CONNECTED)) { if (!mi) { pa_module *m = NULL; @@ -103,7 +96,6 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const } #endif -#ifdef NOKIA if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) && pa_modargs_get_value(u->modargs, "sco_source", NULL)) { char *tmp; @@ -114,7 +106,12 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_xfree(args); args = tmp; } -#endif + + if (d->audio_source_state >= PA_BT_AUDIO_STATE_CONNECTED) + args = pa_sprintf_malloc("%s profile=\"a2dp_source\" auto_connect=no", args); + + if (d->hfgw_state > PA_BT_AUDIO_STATE_CONNECTED) + args = pa_sprintf_malloc("%s profile=\"hfgw\"", args); pa_log_debug("Loading module-bluetooth-device %s", args); m = pa_module_load(u->module->core, "module-bluetooth-device", args); diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c index 9993c8dc..8c3a5b9f 100644 --- a/src/modules/bluetooth/module-bluetooth-proximity.c +++ b/src/modules/bluetooth/module-bluetooth-proximity.c @@ -59,7 +59,6 @@ PA_MODULE_USAGE( static const char* const valid_modargs[] = { "sink", - "rssi", "hci", NULL, }; @@ -92,7 +91,8 @@ struct userdata { unsigned n_found; unsigned n_unknown; - pa_bool_t muted; + pa_bool_t muted:1; + pa_bool_t filter_added:1; }; static void update_volume(struct userdata *u) { @@ -109,7 +109,7 @@ static void update_volume(struct userdata *u) { } pa_log_info("Found %u BT devices, unmuting.", u->n_found); - pa_sink_set_mute(s, FALSE); + pa_sink_set_mute(s, FALSE, FALSE); } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) { pa_sink *s; @@ -122,7 +122,7 @@ static void update_volume(struct userdata *u) { } pa_log_info("No BT devices found, muting."); - pa_sink_set_mute(s, TRUE); + pa_sink_set_mute(s, TRUE, FALSE); } else pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); @@ -358,9 +358,10 @@ static int add_matches(struct userdata *u, pa_bool_t add) { } else dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); - if (add) + if (add) { pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL)); - else + u->filter_added = TRUE; + } else if (u->filter_added) dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u); r = 0; @@ -393,9 +394,6 @@ int pa__init(pa_module*m) { u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI)); u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci); - u->n_found = u->n_unknown = 0; - u->muted = FALSE; - u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) { diff --git a/src/modules/bluetooth/rtp.h b/src/modules/bluetooth/rtp.h index 14573628..45fddcf1 100644 --- a/src/modules/bluetooth/rtp.h +++ b/src/modules/bluetooth/rtp.h @@ -2,7 +2,7 @@ * * BlueZ - Bluetooth protocol stack for Linux * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * * * This library is free software; you can redistribute it and/or diff --git a/src/modules/bluetooth/sbc.c b/src/modules/bluetooth/sbc/sbc.c index 779be4bd..77fcc5d1 100644 --- a/src/modules/bluetooth/sbc.c +++ b/src/modules/bluetooth/sbc/sbc.c @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com> * @@ -77,7 +78,7 @@ struct sbc_frame { uint8_t joint; /* only the lower 4 bits of every element are to be used */ - uint32_t scale_factor[2][8]; + uint32_t SBC_ALIGNED scale_factor[2][8]; /* raw integer subband samples in the frame */ int32_t SBC_ALIGNED sb_sample_f[16][2][8]; @@ -159,7 +160,8 @@ static uint8_t sbc_crc8(const uint8_t *data, size_t len) * Takes a pointer to the frame in question, a pointer to the bits array and * the sampling frequency (as 2 bit integer) */ -static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) +static SBC_ALWAYS_INLINE void sbc_calculate_bits_internal( + const struct sbc_frame *frame, int (*bits)[8], int subbands) { uint8_t sf = frame->frequency; @@ -170,17 +172,17 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) for (ch = 0; ch < frame->channels; ch++) { max_bitneed = 0; if (frame->allocation == SNR) { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { bitneed[ch][sb] = frame->scale_factor[ch][sb]; if (bitneed[ch][sb] > max_bitneed) max_bitneed = bitneed[ch][sb]; } } else { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if (frame->scale_factor[ch][sb] == 0) bitneed[ch][sb] = -5; else { - if (frame->subbands == 4) + if (subbands == 4) loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb]; else loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb]; @@ -201,7 +203,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) bitslice--; bitcount += slicecount; slicecount = 0; - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16)) slicecount++; else if (bitneed[ch][sb] == bitslice + 1) @@ -214,7 +216,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) bitslice--; } - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if (bitneed[ch][sb] < bitslice + 2) bits[ch][sb] = 0; else { @@ -224,7 +226,8 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) } } - for (sb = 0; bitcount < frame->bitpool && sb < frame->subbands; sb++) { + for (sb = 0; bitcount < frame->bitpool && + sb < subbands; sb++) { if ((bits[ch][sb] >= 2) && (bits[ch][sb] < 16)) { bits[ch][sb]++; bitcount++; @@ -234,7 +237,8 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) } } - for (sb = 0; bitcount < frame->bitpool && sb < frame->subbands; sb++) { + for (sb = 0; bitcount < frame->bitpool && + sb < subbands; sb++) { if (bits[ch][sb] < 16) { bits[ch][sb]++; bitcount++; @@ -250,7 +254,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) max_bitneed = 0; if (frame->allocation == SNR) { for (ch = 0; ch < 2; ch++) { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { bitneed[ch][sb] = frame->scale_factor[ch][sb]; if (bitneed[ch][sb] > max_bitneed) max_bitneed = bitneed[ch][sb]; @@ -258,11 +262,11 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) } } else { for (ch = 0; ch < 2; ch++) { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if (frame->scale_factor[ch][sb] == 0) bitneed[ch][sb] = -5; else { - if (frame->subbands == 4) + if (subbands == 4) loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb]; else loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb]; @@ -285,7 +289,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) bitcount += slicecount; slicecount = 0; for (ch = 0; ch < 2; ch++) { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16)) slicecount++; else if (bitneed[ch][sb] == bitslice + 1) @@ -300,7 +304,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) } for (ch = 0; ch < 2; ch++) { - for (sb = 0; sb < frame->subbands; sb++) { + for (sb = 0; sb < subbands; sb++) { if (bitneed[ch][sb] < bitslice + 2) { bits[ch][sb] = 0; } else { @@ -324,7 +328,8 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) if (ch == 1) { ch = 0; sb++; - if (sb >= frame->subbands) break; + if (sb >= subbands) + break; } else ch = 1; } @@ -339,7 +344,8 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) if (ch == 1) { ch = 0; sb++; - if (sb >= frame->subbands) break; + if (sb >= subbands) + break; } else ch = 1; } @@ -348,6 +354,14 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) } +static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) +{ + if (frame->subbands == 4) + sbc_calculate_bits_internal(frame, bits, 4); + else + sbc_calculate_bits_internal(frame, bits, 8); +} + /* * Unpacks a SBC frame at the beginning of the stream in data, * which has at most len bytes into frame. @@ -360,7 +374,7 @@ static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) * -4 Bitpool value out of bounds */ static int sbc_unpack_frame(const uint8_t *data, struct sbc_frame *frame, - size_t len) + size_t len) { unsigned int consumed; /* Will copy the parts of the header that are relevant to crc @@ -522,7 +536,7 @@ static int sbc_unpack_frame(const uint8_t *data, struct sbc_frame *frame, } static void sbc_decoder_init(struct sbc_decoder_state *state, - const struct sbc_frame *frame) + const struct sbc_frame *frame) { int i, ch; @@ -534,6 +548,16 @@ static void sbc_decoder_init(struct sbc_decoder_state *state, state->offset[ch][i] = (10 * i + 10); } +static SBC_ALWAYS_INLINE int16_t sbc_clip16(int32_t s) +{ + if (s > 0x7FFF) + return 0x7FFF; + else if (s < -0x8000) + return -0x8000; + else + return s; +} + static inline void sbc_synthesize_four(struct sbc_decoder_state *state, struct sbc_frame *frame, int ch, int blk) { @@ -562,7 +586,7 @@ static inline void sbc_synthesize_four(struct sbc_decoder_state *state, k = (i + 4) & 0xf; /* Store in output, Q0 */ - frame->pcm_sample[ch][blk * 4 + i] = SCALE4_STAGED1( + frame->pcm_sample[ch][blk * 4 + i] = sbc_clip16(SCALE4_STAGED1( MULA(v[offset[i] + 0], sbc_proto_4_40m0[idx + 0], MULA(v[offset[k] + 1], sbc_proto_4_40m1[idx + 0], MULA(v[offset[i] + 2], sbc_proto_4_40m0[idx + 1], @@ -572,7 +596,7 @@ static inline void sbc_synthesize_four(struct sbc_decoder_state *state, MULA(v[offset[i] + 6], sbc_proto_4_40m0[idx + 3], MULA(v[offset[k] + 7], sbc_proto_4_40m1[idx + 3], MULA(v[offset[i] + 8], sbc_proto_4_40m0[idx + 4], - MUL( v[offset[k] + 9], sbc_proto_4_40m1[idx + 4]))))))))))); + MUL( v[offset[k] + 9], sbc_proto_4_40m1[idx + 4])))))))))))); } } @@ -607,8 +631,8 @@ static inline void sbc_synthesize_eight(struct sbc_decoder_state *state, for (idx = 0, i = 0; i < 8; i++, idx += 5) { k = (i + 8) & 0xf; - /* Store in output */ - frame->pcm_sample[ch][blk * 8 + i] = SCALE8_STAGED1( // Q0 + /* Store in output, Q0 */ + frame->pcm_sample[ch][blk * 8 + i] = sbc_clip16(SCALE8_STAGED1( MULA(state->V[ch][offset[i] + 0], sbc_proto_8_80m0[idx + 0], MULA(state->V[ch][offset[k] + 1], sbc_proto_8_80m1[idx + 0], MULA(state->V[ch][offset[i] + 2], sbc_proto_8_80m0[idx + 1], @@ -618,12 +642,12 @@ static inline void sbc_synthesize_eight(struct sbc_decoder_state *state, MULA(state->V[ch][offset[i] + 6], sbc_proto_8_80m0[idx + 3], MULA(state->V[ch][offset[k] + 7], sbc_proto_8_80m1[idx + 3], MULA(state->V[ch][offset[i] + 8], sbc_proto_8_80m0[idx + 4], - MUL( state->V[ch][offset[k] + 9], sbc_proto_8_80m1[idx + 4]))))))))))); + MUL( state->V[ch][offset[k] + 9], sbc_proto_8_80m1[idx + 4])))))))))))); } } static int sbc_synthesize_audio(struct sbc_decoder_state *state, - struct sbc_frame *frame) + struct sbc_frame *frame) { int ch, blk; @@ -648,7 +672,7 @@ static int sbc_synthesize_audio(struct sbc_decoder_state *state, } static int sbc_analyze_audio(struct sbc_encoder_state *state, - struct sbc_frame *frame) + struct sbc_frame *frame) { int ch, blk; int16_t *x; @@ -732,9 +756,10 @@ static int sbc_analyze_audio(struct sbc_encoder_state *state, * -99 not implemented */ -static SBC_ALWAYS_INLINE int sbc_pack_frame_internal( - uint8_t *data, struct sbc_frame *frame, size_t len, - int frame_subbands, int frame_channels) +static SBC_ALWAYS_INLINE ssize_t sbc_pack_frame_internal(uint8_t *data, + struct sbc_frame *frame, size_t len, + int frame_subbands, int frame_channels, + int joint) { /* Bitstream writer starts from the fourth byte */ uint8_t *data_ptr = data + 4; @@ -791,63 +816,6 @@ static SBC_ALWAYS_INLINE int sbc_pack_frame_internal( crc_pos = 16; if (frame->mode == JOINT_STEREO) { - /* like frame->sb_sample but joint stereo */ - int32_t sb_sample_j[16][2]; - /* scalefactor and scale_factor in joint case */ - uint32_t scalefactor_j[2]; - uint8_t scale_factor_j[2]; - - uint8_t joint = 0; - frame->joint = 0; - - for (sb = 0; sb < frame_subbands - 1; sb++) { - scale_factor_j[0] = 0; - scalefactor_j[0] = 2 << SCALE_OUT_BITS; - scale_factor_j[1] = 0; - scalefactor_j[1] = 2 << SCALE_OUT_BITS; - - for (blk = 0; blk < frame->blocks; blk++) { - uint32_t tmp; - /* Calculate joint stereo signal */ - sb_sample_j[blk][0] = - ASR(frame->sb_sample_f[blk][0][sb], 1) + - ASR(frame->sb_sample_f[blk][1][sb], 1); - sb_sample_j[blk][1] = - ASR(frame->sb_sample_f[blk][0][sb], 1) - - ASR(frame->sb_sample_f[blk][1][sb], 1); - - /* calculate scale_factor_j and scalefactor_j for joint case */ - tmp = fabs(sb_sample_j[blk][0]); - while (scalefactor_j[0] < tmp) { - scale_factor_j[0]++; - scalefactor_j[0] *= 2; - } - tmp = fabs(sb_sample_j[blk][1]); - while (scalefactor_j[1] < tmp) { - scale_factor_j[1]++; - scalefactor_j[1] *= 2; - } - } - - /* decide whether to join this subband */ - if ((frame->scale_factor[0][sb] + - frame->scale_factor[1][sb]) > - (scale_factor_j[0] + - scale_factor_j[1])) { - /* use joint stereo for this subband */ - joint |= 1 << (frame_subbands - 1 - sb); - frame->joint |= 1 << sb; - frame->scale_factor[0][sb] = scale_factor_j[0]; - frame->scale_factor[1][sb] = scale_factor_j[1]; - for (blk = 0; blk < frame->blocks; blk++) { - frame->sb_sample_f[blk][0][sb] = - sb_sample_j[blk][0]; - frame->sb_sample_f[blk][1][sb] = - sb_sample_j[blk][1]; - } - } - } - PUT_BITS(data_ptr, bits_cache, bits_count, joint, frame_subbands); crc_header[crc_pos >> 3] = joint; @@ -905,26 +873,31 @@ static SBC_ALWAYS_INLINE int sbc_pack_frame_internal( return data_ptr - data; } -static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len) +static ssize_t sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len, + int joint) { if (frame->subbands == 4) { if (frame->channels == 1) - return sbc_pack_frame_internal(data, frame, len, 4, 1); + return sbc_pack_frame_internal( + data, frame, len, 4, 1, joint); else - return sbc_pack_frame_internal(data, frame, len, 4, 2); + return sbc_pack_frame_internal( + data, frame, len, 4, 2, joint); } else { if (frame->channels == 1) - return sbc_pack_frame_internal(data, frame, len, 8, 1); + return sbc_pack_frame_internal( + data, frame, len, 8, 1, joint); else - return sbc_pack_frame_internal(data, frame, len, 8, 2); + return sbc_pack_frame_internal( + data, frame, len, 8, 2, joint); } } static void sbc_encoder_init(struct sbc_encoder_state *state, - const struct sbc_frame *frame) + const struct sbc_frame *frame) { memset(&state->X, 0, sizeof(state->X)); - state->position = SBC_X_BUFFER_SIZE - frame->subbands * 9; + state->position = (SBC_X_BUFFER_SIZE - frame->subbands * 9) & ~7; sbc_init_primitives(state); } @@ -1005,6 +978,9 @@ ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len, priv->frame.codesize = sbc_get_codesize(sbc); priv->frame.length = framelen; + } else if (priv->frame.bitpool != sbc->bitpool) { + priv->frame.length = framelen; + sbc->bitpool = priv->frame.bitpool; } if (!output) @@ -1045,10 +1021,11 @@ ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len, } ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, - void *output, size_t output_len, size_t *written) + void *output, size_t output_len, ssize_t *written) { struct sbc_priv *priv; - int framelen, samples; + int samples; + ssize_t framelen; int (*sbc_enc_process_input)(int position, const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE], int nsamples, int nchannels); @@ -1076,6 +1053,9 @@ ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, sbc_encoder_init(&priv->enc_state, &priv->frame); priv->init = 1; + } else if (priv->frame.bitpool != sbc->bitpool) { + priv->frame.length = sbc_get_frame_length(sbc); + priv->frame.bitpool = sbc->bitpool; } /* input must be large enough to encode a complete frame */ @@ -1110,11 +1090,18 @@ ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, samples = sbc_analyze_audio(&priv->enc_state, &priv->frame); - priv->enc_state.sbc_calc_scalefactors( - priv->frame.sb_sample_f, priv->frame.scale_factor, - priv->frame.blocks, priv->frame.channels, priv->frame.subbands); - - framelen = sbc_pack_frame(output, &priv->frame, output_len); + if (priv->frame.mode == JOINT_STEREO) { + int j = priv->enc_state.sbc_calc_scalefactors_j( + priv->frame.sb_sample_f, priv->frame.scale_factor, + priv->frame.blocks, priv->frame.subbands); + framelen = sbc_pack_frame(output, &priv->frame, output_len, j); + } else { + priv->enc_state.sbc_calc_scalefactors( + priv->frame.sb_sample_f, priv->frame.scale_factor, + priv->frame.blocks, priv->frame.channels, + priv->frame.subbands); + framelen = sbc_pack_frame(output, &priv->frame, output_len, 0); + } if (written) *written = framelen; @@ -1127,20 +1114,19 @@ void sbc_finish(sbc_t *sbc) if (!sbc) return; - if (sbc->priv_alloc_base) - free(sbc->priv_alloc_base); + free(sbc->priv_alloc_base); memset(sbc, 0, sizeof(sbc_t)); } size_t sbc_get_frame_length(sbc_t *sbc) { - size_t ret; + int ret; uint8_t subbands, channels, blocks, joint, bitpool; struct sbc_priv *priv; priv = sbc->priv; - if (priv->init) + if (priv->init && priv->frame.bitpool == sbc->bitpool) return priv->frame.length; subbands = sbc->subbands ? 8 : 4; diff --git a/src/modules/bluetooth/sbc.h b/src/modules/bluetooth/sbc/sbc.h index 65435884..2f830ad5 100644 --- a/src/modules/bluetooth/sbc.h +++ b/src/modules/bluetooth/sbc/sbc.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * @@ -85,12 +86,13 @@ int sbc_reinit(sbc_t *sbc, unsigned long flags); ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len); +/* Decodes ONE input block into ONE output block */ ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len, void *output, size_t output_len, size_t *written); /* Encodes ONE input block into ONE output block */ ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len, - void *output, size_t output_len, size_t *written); + void *output, size_t output_len, ssize_t *written); /* Returns the output block size in bytes */ size_t sbc_get_frame_length(sbc_t *sbc); diff --git a/src/modules/bluetooth/sbc_math.h b/src/modules/bluetooth/sbc/sbc_math.h index b87bc81c..5476860d 100644 --- a/src/modules/bluetooth/sbc_math.h +++ b/src/modules/bluetooth/sbc/sbc_math.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com> * @@ -47,7 +48,7 @@ typedef int32_t sbc_fixed_t; #define SBC_FIXED_0(val) { val = 0; } #define MUL(a, b) ((a) * (b)) -#ifdef __arm__ +#if defined(__arm__) && (!defined(__thumb__) || defined(__thumb2__)) #define MULA(a, b, res) ({ \ int tmp = res; \ __asm__( \ diff --git a/src/modules/bluetooth/sbc_primitives.c b/src/modules/bluetooth/sbc/sbc_primitives.c index 6b0be3f5..ad780d08 100644 --- a/src/modules/bluetooth/sbc_primitives.c +++ b/src/modules/bluetooth/sbc/sbc_primitives.c @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * @@ -32,7 +33,9 @@ #include "sbc_primitives.h" #include "sbc_primitives_mmx.h" +#include "sbc_primitives_iwmmxt.h" #include "sbc_primitives_neon.h" +#include "sbc_primitives_armv6.h" /* * A reference C code of analysis filter with SIMD-friendly tables @@ -231,12 +234,12 @@ static SBC_ALWAYS_INLINE int sbc_encoder_process_input_s4_internal( /* handle X buffer wraparound */ if (position < nsamples) { if (nchannels > 0) - memcpy(&X[0][SBC_X_BUFFER_SIZE - 36], &X[0][position], + memcpy(&X[0][SBC_X_BUFFER_SIZE - 40], &X[0][position], 36 * sizeof(int16_t)); if (nchannels > 1) - memcpy(&X[1][SBC_X_BUFFER_SIZE - 36], &X[1][position], + memcpy(&X[1][SBC_X_BUFFER_SIZE - 40], &X[1][position], 36 * sizeof(int16_t)); - position = SBC_X_BUFFER_SIZE - 36; + position = SBC_X_BUFFER_SIZE - 40; } #define PCM(i) (big_endian ? \ @@ -439,6 +442,80 @@ static void sbc_calc_scalefactors( } } +static int sbc_calc_scalefactors_j( + int32_t sb_sample_f[16][2][8], + uint32_t scale_factor[2][8], + int blocks, int subbands) +{ + int blk, joint = 0; + int32_t tmp0, tmp1; + uint32_t x, y; + + /* last subband does not use joint stereo */ + int sb = subbands - 1; + x = 1 << SCALE_OUT_BITS; + y = 1 << SCALE_OUT_BITS; + for (blk = 0; blk < blocks; blk++) { + tmp0 = fabs(sb_sample_f[blk][0][sb]); + tmp1 = fabs(sb_sample_f[blk][1][sb]); + if (tmp0 != 0) + x |= tmp0 - 1; + if (tmp1 != 0) + y |= tmp1 - 1; + } + scale_factor[0][sb] = (31 - SCALE_OUT_BITS) - sbc_clz(x); + scale_factor[1][sb] = (31 - SCALE_OUT_BITS) - sbc_clz(y); + + /* the rest of subbands can use joint stereo */ + while (--sb >= 0) { + int32_t sb_sample_j[16][2]; + x = 1 << SCALE_OUT_BITS; + y = 1 << SCALE_OUT_BITS; + for (blk = 0; blk < blocks; blk++) { + tmp0 = sb_sample_f[blk][0][sb]; + tmp1 = sb_sample_f[blk][1][sb]; + sb_sample_j[blk][0] = ASR(tmp0, 1) + ASR(tmp1, 1); + sb_sample_j[blk][1] = ASR(tmp0, 1) - ASR(tmp1, 1); + tmp0 = fabs(tmp0); + tmp1 = fabs(tmp1); + if (tmp0 != 0) + x |= tmp0 - 1; + if (tmp1 != 0) + y |= tmp1 - 1; + } + scale_factor[0][sb] = (31 - SCALE_OUT_BITS) - + sbc_clz(x); + scale_factor[1][sb] = (31 - SCALE_OUT_BITS) - + sbc_clz(y); + x = 1 << SCALE_OUT_BITS; + y = 1 << SCALE_OUT_BITS; + for (blk = 0; blk < blocks; blk++) { + tmp0 = fabs(sb_sample_j[blk][0]); + tmp1 = fabs(sb_sample_j[blk][1]); + if (tmp0 != 0) + x |= tmp0 - 1; + if (tmp1 != 0) + y |= tmp1 - 1; + } + x = (31 - SCALE_OUT_BITS) - sbc_clz(x); + y = (31 - SCALE_OUT_BITS) - sbc_clz(y); + + /* decide whether to use joint stereo for this subband */ + if ((scale_factor[0][sb] + scale_factor[1][sb]) > x + y) { + joint |= 1 << (subbands - 1 - sb); + scale_factor[0][sb] = x; + scale_factor[1][sb] = y; + for (blk = 0; blk < blocks; blk++) { + sb_sample_f[blk][0][sb] = sb_sample_j[blk][0]; + sb_sample_f[blk][1][sb] = sb_sample_j[blk][1]; + } + } + } + + /* bitmask with the information about subbands using joint stereo */ + return joint; +} + /* * Detect CPU features and setup function pointers */ @@ -456,6 +533,7 @@ void sbc_init_primitives(struct sbc_encoder_state *state) /* Default implementation for scale factors calculation */ state->sbc_calc_scalefactors = sbc_calc_scalefactors; + state->sbc_calc_scalefactors_j = sbc_calc_scalefactors_j; state->implementation_info = "Generic C"; /* X86/AMD64 optimizations */ @@ -464,6 +542,12 @@ void sbc_init_primitives(struct sbc_encoder_state *state) #endif /* ARM optimizations */ +#ifdef SBC_BUILD_WITH_ARMV6_SUPPORT + sbc_init_primitives_armv6(state); +#endif +#ifdef SBC_BUILD_WITH_IWMMXT_SUPPORT + sbc_init_primitives_iwmmxt(state); +#endif #ifdef SBC_BUILD_WITH_NEON_SUPPORT sbc_init_primitives_neon(state); #endif diff --git a/src/modules/bluetooth/sbc_primitives.h b/src/modules/bluetooth/sbc/sbc_primitives.h index 3d01c115..3fec8d5b 100644 --- a/src/modules/bluetooth/sbc_primitives.h +++ b/src/modules/bluetooth/sbc/sbc_primitives.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * @@ -62,6 +63,10 @@ struct sbc_encoder_state { void (*sbc_calc_scalefactors)(int32_t sb_sample_f[16][2][8], uint32_t scale_factor[2][8], int blocks, int channels, int subbands); + /* Scale factors calculation with joint stereo support */ + int (*sbc_calc_scalefactors_j)(int32_t sb_sample_f[16][2][8], + uint32_t scale_factor[2][8], + int blocks, int subbands); const char *implementation_info; }; diff --git a/src/modules/bluetooth/sbc/sbc_primitives_armv6.c b/src/modules/bluetooth/sbc/sbc_primitives_armv6.c new file mode 100644 index 00000000..95860980 --- /dev/null +++ b/src/modules/bluetooth/sbc/sbc_primitives_armv6.c @@ -0,0 +1,299 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <stdint.h> +#include <limits.h> +#include "sbc.h" +#include "sbc_math.h" +#include "sbc_tables.h" + +#include "sbc_primitives_armv6.h" + +/* + * ARMv6 optimizations. The instructions are scheduled for ARM11 pipeline. + */ + +#ifdef SBC_BUILD_WITH_ARMV6_SUPPORT + +static void __attribute__((naked)) sbc_analyze_four_armv6() +{ + /* r0 = in, r1 = out, r2 = consts */ + asm volatile ( + "push {r1, r4-r7, lr}\n" + "push {r8-r11}\n" + "ldrd r4, r5, [r0, #0]\n" + "ldrd r6, r7, [r2, #0]\n" + "ldrd r8, r9, [r0, #16]\n" + "ldrd r10, r11, [r2, #16]\n" + "mov r14, #0x8000\n" + "smlad r3, r4, r6, r14\n" + "smlad r12, r5, r7, r14\n" + "ldrd r4, r5, [r0, #32]\n" + "ldrd r6, r7, [r2, #32]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #48]\n" + "ldrd r10, r11, [r2, #48]\n" + "smlad r3, r4, r6, r3\n" + "smlad r12, r5, r7, r12\n" + "ldrd r4, r5, [r0, #64]\n" + "ldrd r6, r7, [r2, #64]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #8]\n" + "ldrd r10, r11, [r2, #8]\n" + "smlad r3, r4, r6, r3\n" /* t1[0] is done */ + "smlad r12, r5, r7, r12\n" /* t1[1] is done */ + "ldrd r4, r5, [r0, #24]\n" + "ldrd r6, r7, [r2, #24]\n" + "pkhtb r3, r12, r3, asr #16\n" /* combine t1[0] and t1[1] */ + "smlad r12, r8, r10, r14\n" + "smlad r14, r9, r11, r14\n" + "ldrd r8, r9, [r0, #40]\n" + "ldrd r10, r11, [r2, #40]\n" + "smlad r12, r4, r6, r12\n" + "smlad r14, r5, r7, r14\n" + "ldrd r4, r5, [r0, #56]\n" + "ldrd r6, r7, [r2, #56]\n" + "smlad r12, r8, r10, r12\n" + "smlad r14, r9, r11, r14\n" + "ldrd r8, r9, [r0, #72]\n" + "ldrd r10, r11, [r2, #72]\n" + "smlad r12, r4, r6, r12\n" + "smlad r14, r5, r7, r14\n" + "ldrd r4, r5, [r2, #80]\n" /* start loading cos table */ + "smlad r12, r8, r10, r12\n" /* t1[2] is done */ + "smlad r14, r9, r11, r14\n" /* t1[3] is done */ + "ldrd r6, r7, [r2, #88]\n" + "ldrd r8, r9, [r2, #96]\n" + "ldrd r10, r11, [r2, #104]\n" /* cos table fully loaded */ + "pkhtb r12, r14, r12, asr #16\n" /* combine t1[2] and t1[3] */ + "smuad r4, r3, r4\n" + "smuad r5, r3, r5\n" + "smlad r4, r12, r8, r4\n" + "smlad r5, r12, r9, r5\n" + "smuad r6, r3, r6\n" + "smuad r7, r3, r7\n" + "smlad r6, r12, r10, r6\n" + "smlad r7, r12, r11, r7\n" + "pop {r8-r11}\n" + "stmia r1, {r4, r5, r6, r7}\n" + "pop {r1, r4-r7, pc}\n" + ); +} + +#define sbc_analyze_four(in, out, consts) \ + ((void (*)(int16_t *, int32_t *, const FIXED_T*)) \ + sbc_analyze_four_armv6)((in), (out), (consts)) + +static void __attribute__((naked)) sbc_analyze_eight_armv6() +{ + /* r0 = in, r1 = out, r2 = consts */ + asm volatile ( + "push {r1, r4-r7, lr}\n" + "push {r8-r11}\n" + "ldrd r4, r5, [r0, #24]\n" + "ldrd r6, r7, [r2, #24]\n" + "ldrd r8, r9, [r0, #56]\n" + "ldrd r10, r11, [r2, #56]\n" + "mov r14, #0x8000\n" + "smlad r3, r4, r6, r14\n" + "smlad r12, r5, r7, r14\n" + "ldrd r4, r5, [r0, #88]\n" + "ldrd r6, r7, [r2, #88]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #120]\n" + "ldrd r10, r11, [r2, #120]\n" + "smlad r3, r4, r6, r3\n" + "smlad r12, r5, r7, r12\n" + "ldrd r4, r5, [r0, #152]\n" + "ldrd r6, r7, [r2, #152]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #16]\n" + "ldrd r10, r11, [r2, #16]\n" + "smlad r3, r4, r6, r3\n" /* t1[6] is done */ + "smlad r12, r5, r7, r12\n" /* t1[7] is done */ + "ldrd r4, r5, [r0, #48]\n" + "ldrd r6, r7, [r2, #48]\n" + "pkhtb r3, r12, r3, asr #16\n" /* combine t1[6] and t1[7] */ + "str r3, [sp, #-4]!\n" /* save to stack */ + "smlad r3, r8, r10, r14\n" + "smlad r12, r9, r11, r14\n" + "ldrd r8, r9, [r0, #80]\n" + "ldrd r10, r11, [r2, #80]\n" + "smlad r3, r4, r6, r3\n" + "smlad r12, r5, r7, r12\n" + "ldrd r4, r5, [r0, #112]\n" + "ldrd r6, r7, [r2, #112]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #144]\n" + "ldrd r10, r11, [r2, #144]\n" + "smlad r3, r4, r6, r3\n" + "smlad r12, r5, r7, r12\n" + "ldrd r4, r5, [r0, #0]\n" + "ldrd r6, r7, [r2, #0]\n" + "smlad r3, r8, r10, r3\n" /* t1[4] is done */ + "smlad r12, r9, r11, r12\n" /* t1[5] is done */ + "ldrd r8, r9, [r0, #32]\n" + "ldrd r10, r11, [r2, #32]\n" + "pkhtb r3, r12, r3, asr #16\n" /* combine t1[4] and t1[5] */ + "str r3, [sp, #-4]!\n" /* save to stack */ + "smlad r3, r4, r6, r14\n" + "smlad r12, r5, r7, r14\n" + "ldrd r4, r5, [r0, #64]\n" + "ldrd r6, r7, [r2, #64]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #96]\n" + "ldrd r10, r11, [r2, #96]\n" + "smlad r3, r4, r6, r3\n" + "smlad r12, r5, r7, r12\n" + "ldrd r4, r5, [r0, #128]\n" + "ldrd r6, r7, [r2, #128]\n" + "smlad r3, r8, r10, r3\n" + "smlad r12, r9, r11, r12\n" + "ldrd r8, r9, [r0, #8]\n" + "ldrd r10, r11, [r2, #8]\n" + "smlad r3, r4, r6, r3\n" /* t1[0] is done */ + "smlad r12, r5, r7, r12\n" /* t1[1] is done */ + "ldrd r4, r5, [r0, #40]\n" + "ldrd r6, r7, [r2, #40]\n" + "pkhtb r3, r12, r3, asr #16\n" /* combine t1[0] and t1[1] */ + "smlad r12, r8, r10, r14\n" + "smlad r14, r9, r11, r14\n" + "ldrd r8, r9, [r0, #72]\n" + "ldrd r10, r11, [r2, #72]\n" + "smlad r12, r4, r6, r12\n" + "smlad r14, r5, r7, r14\n" + "ldrd r4, r5, [r0, #104]\n" + "ldrd r6, r7, [r2, #104]\n" + "smlad r12, r8, r10, r12\n" + "smlad r14, r9, r11, r14\n" + "ldrd r8, r9, [r0, #136]\n" + "ldrd r10, r11, [r2, #136]!\n" + "smlad r12, r4, r6, r12\n" + "smlad r14, r5, r7, r14\n" + "ldrd r4, r5, [r2, #(160 - 136 + 0)]\n" + "smlad r12, r8, r10, r12\n" /* t1[2] is done */ + "smlad r14, r9, r11, r14\n" /* t1[3] is done */ + "ldrd r6, r7, [r2, #(160 - 136 + 8)]\n" + "smuad r4, r3, r4\n" + "smuad r5, r3, r5\n" + "pkhtb r12, r14, r12, asr #16\n" /* combine t1[2] and t1[3] */ + /* r3 = t2[0:1] */ + /* r12 = t2[2:3] */ + "pop {r0, r14}\n" /* t2[4:5], t2[6:7] */ + "ldrd r8, r9, [r2, #(160 - 136 + 32)]\n" + "smuad r6, r3, r6\n" + "smuad r7, r3, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 40)]\n" + "smlad r4, r12, r8, r4\n" + "smlad r5, r12, r9, r5\n" + "ldrd r8, r9, [r2, #(160 - 136 + 64)]\n" + "smlad r6, r12, r10, r6\n" + "smlad r7, r12, r11, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 72)]\n" + "smlad r4, r0, r8, r4\n" + "smlad r5, r0, r9, r5\n" + "ldrd r8, r9, [r2, #(160 - 136 + 96)]\n" + "smlad r6, r0, r10, r6\n" + "smlad r7, r0, r11, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 104)]\n" + "smlad r4, r14, r8, r4\n" + "smlad r5, r14, r9, r5\n" + "ldrd r8, r9, [r2, #(160 - 136 + 16 + 0)]\n" + "smlad r6, r14, r10, r6\n" + "smlad r7, r14, r11, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 16 + 8)]\n" + "stmia r1!, {r4, r5}\n" + "smuad r4, r3, r8\n" + "smuad r5, r3, r9\n" + "ldrd r8, r9, [r2, #(160 - 136 + 16 + 32)]\n" + "stmia r1!, {r6, r7}\n" + "smuad r6, r3, r10\n" + "smuad r7, r3, r11\n" + "ldrd r10, r11, [r2, #(160 - 136 + 16 + 40)]\n" + "smlad r4, r12, r8, r4\n" + "smlad r5, r12, r9, r5\n" + "ldrd r8, r9, [r2, #(160 - 136 + 16 + 64)]\n" + "smlad r6, r12, r10, r6\n" + "smlad r7, r12, r11, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 16 + 72)]\n" + "smlad r4, r0, r8, r4\n" + "smlad r5, r0, r9, r5\n" + "ldrd r8, r9, [r2, #(160 - 136 + 16 + 96)]\n" + "smlad r6, r0, r10, r6\n" + "smlad r7, r0, r11, r7\n" + "ldrd r10, r11, [r2, #(160 - 136 + 16 + 104)]\n" + "smlad r4, r14, r8, r4\n" + "smlad r5, r14, r9, r5\n" + "smlad r6, r14, r10, r6\n" + "smlad r7, r14, r11, r7\n" + "pop {r8-r11}\n" + "stmia r1!, {r4, r5, r6, r7}\n" + "pop {r1, r4-r7, pc}\n" + ); +} + +#define sbc_analyze_eight(in, out, consts) \ + ((void (*)(int16_t *, int32_t *, const FIXED_T*)) \ + sbc_analyze_eight_armv6)((in), (out), (consts)) + +static void sbc_analyze_4b_4s_armv6(int16_t *x, int32_t *out, int out_stride) +{ + /* Analyze blocks */ + sbc_analyze_four(x + 12, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + sbc_analyze_four(x + 8, out, analysis_consts_fixed4_simd_even); + out += out_stride; + sbc_analyze_four(x + 4, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + sbc_analyze_four(x + 0, out, analysis_consts_fixed4_simd_even); +} + +static void sbc_analyze_4b_8s_armv6(int16_t *x, int32_t *out, int out_stride) +{ + /* Analyze blocks */ + sbc_analyze_eight(x + 24, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + sbc_analyze_eight(x + 16, out, analysis_consts_fixed8_simd_even); + out += out_stride; + sbc_analyze_eight(x + 8, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + sbc_analyze_eight(x + 0, out, analysis_consts_fixed8_simd_even); +} + +void sbc_init_primitives_armv6(struct sbc_encoder_state *state) +{ + state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_armv6; + state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_armv6; + state->implementation_info = "ARMv6 SIMD"; +} + +#endif diff --git a/src/modules/bluetooth/sbc/sbc_primitives_armv6.h b/src/modules/bluetooth/sbc/sbc_primitives_armv6.h new file mode 100644 index 00000000..6a9efe50 --- /dev/null +++ b/src/modules/bluetooth/sbc/sbc_primitives_armv6.h @@ -0,0 +1,52 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SBC_PRIMITIVES_ARMV6_H +#define __SBC_PRIMITIVES_ARMV6_H + +#include "sbc_primitives.h" + +#if defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \ + defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) || \ + defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_7__) || \ + defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || \ + defined(__ARM_ARCH_7M__) +#define SBC_HAVE_ARMV6 1 +#endif + +#if !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15) && \ + defined(__GNUC__) && defined(SBC_HAVE_ARMV6) && \ + defined(__ARM_EABI__) && !defined(__ARM_NEON__) && \ + (!defined(__thumb__) || defined(__thumb2__)) + +#define SBC_BUILD_WITH_ARMV6_SUPPORT + +void sbc_init_primitives_armv6(struct sbc_encoder_state *encoder_state); + +#endif + +#endif diff --git a/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.c b/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.c new file mode 100644 index 00000000..213967ef --- /dev/null +++ b/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.c @@ -0,0 +1,304 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2010 Keith Mok <ek9852@gmail.com> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <stdint.h> +#include <limits.h> +#include "sbc.h" +#include "sbc_math.h" +#include "sbc_tables.h" + +#include "sbc_primitives_iwmmxt.h" + +/* + * IWMMXT optimizations + */ + +#ifdef SBC_BUILD_WITH_IWMMXT_SUPPORT + +static inline void sbc_analyze_four_iwmmxt(const int16_t *in, int32_t *out, + const FIXED_T *consts) +{ + asm volatile ( + "wldrd wr0, [%0]\n" + "tbcstw wr4, %2\n" + "wldrd wr2, [%1]\n" + "wldrd wr1, [%0, #8]\n" + "wldrd wr3, [%1, #8]\n" + "wmadds wr0, wr2, wr0\n" + " wldrd wr6, [%0, #16]\n" + "wmadds wr1, wr3, wr1\n" + " wldrd wr7, [%0, #24]\n" + "waddwss wr0, wr0, wr4\n" + " wldrd wr8, [%1, #16]\n" + "waddwss wr1, wr1, wr4\n" + " wldrd wr9, [%1, #24]\n" + " wmadds wr6, wr8, wr6\n" + " wldrd wr2, [%0, #32]\n" + " wmadds wr7, wr9, wr7\n" + " wldrd wr3, [%0, #40]\n" + " waddwss wr0, wr6, wr0\n" + " wldrd wr4, [%1, #32]\n" + " waddwss wr1, wr7, wr1\n" + " wldrd wr5, [%1, #40]\n" + " wmadds wr2, wr4, wr2\n" + "wldrd wr6, [%0, #48]\n" + " wmadds wr3, wr5, wr3\n" + "wldrd wr7, [%0, #56]\n" + " waddwss wr0, wr2, wr0\n" + "wldrd wr8, [%1, #48]\n" + " waddwss wr1, wr3, wr1\n" + "wldrd wr9, [%1, #56]\n" + "wmadds wr6, wr8, wr6\n" + " wldrd wr2, [%0, #64]\n" + "wmadds wr7, wr9, wr7\n" + " wldrd wr3, [%0, #72]\n" + "waddwss wr0, wr6, wr0\n" + " wldrd wr4, [%1, #64]\n" + "waddwss wr1, wr7, wr1\n" + " wldrd wr5, [%1, #72]\n" + " wmadds wr2, wr4, wr2\n" + "tmcr wcgr0, %4\n" + " wmadds wr3, wr5, wr3\n" + " waddwss wr0, wr2, wr0\n" + " waddwss wr1, wr3, wr1\n" + "\n" + "wsrawg wr0, wr0, wcgr0\n" + " wldrd wr4, [%1, #80]\n" + "wsrawg wr1, wr1, wcgr0\n" + " wldrd wr5, [%1, #88]\n" + "wpackwss wr0, wr0, wr0\n" + " wldrd wr6, [%1, #96]\n" + "wpackwss wr1, wr1, wr1\n" + "wmadds wr2, wr5, wr0\n" + " wldrd wr7, [%1, #104]\n" + "wmadds wr0, wr4, wr0\n" + "\n" + " wmadds wr3, wr7, wr1\n" + " wmadds wr1, wr6, wr1\n" + " waddwss wr2, wr3, wr2\n" + " waddwss wr0, wr1, wr0\n" + "\n" + "wstrd wr0, [%3]\n" + "wstrd wr2, [%3, #8]\n" + : + : "r" (in), "r" (consts), + "r" (1 << (SBC_PROTO_FIXED4_SCALE - 1)), "r" (out), + "r" (SBC_PROTO_FIXED4_SCALE) + : "wr0", "wr1", "wr2", "wr3", "wr4", "wr5", "wr6", "wr7", + "wr8", "wr9", "wcgr0", "memory"); +} + +static inline void sbc_analyze_eight_iwmmxt(const int16_t *in, int32_t *out, + const FIXED_T *consts) +{ + asm volatile ( + "wldrd wr0, [%0]\n" + "tbcstw wr15, %2\n" + "wldrd wr1, [%0, #8]\n" + "wldrd wr2, [%0, #16]\n" + "wldrd wr3, [%0, #24]\n" + "wldrd wr4, [%1]\n" + "wldrd wr5, [%1, #8]\n" + "wldrd wr6, [%1, #16]\n" + "wldrd wr7, [%1, #24]\n" + "wmadds wr0, wr0, wr4\n" + " wldrd wr8, [%1, #32]\n" + "wmadds wr1, wr1, wr5\n" + " wldrd wr9, [%1, #40]\n" + "wmadds wr2, wr2, wr6\n" + " wldrd wr10, [%1, #48]\n" + "wmadds wr3, wr3, wr7\n" + " wldrd wr11, [%1, #56]\n" + "waddwss wr0, wr0, wr15\n" + " wldrd wr4, [%0, #32]\n" + "waddwss wr1, wr1, wr15\n" + " wldrd wr5, [%0, #40]\n" + "waddwss wr2, wr2, wr15\n" + " wldrd wr6, [%0, #48]\n" + "waddwss wr3, wr3, wr15\n" + " wldrd wr7, [%0, #56]\n" + " wmadds wr4, wr4, wr8\n" + " wldrd wr12, [%0, #64]\n" + " wmadds wr5, wr5, wr9\n" + " wldrd wr13, [%0, #72]\n" + " wmadds wr6, wr6, wr10\n" + " wldrd wr14, [%0, #80]\n" + " wmadds wr7, wr7, wr11\n" + " wldrd wr15, [%0, #88]\n" + " waddwss wr0, wr4, wr0\n" + " wldrd wr8, [%1, #64]\n" + " waddwss wr1, wr5, wr1\n" + " wldrd wr9, [%1, #72]\n" + " waddwss wr2, wr6, wr2\n" + " wldrd wr10, [%1, #80]\n" + " waddwss wr3, wr7, wr3\n" + " wldrd wr11, [%1, #88]\n" + " wmadds wr12, wr12, wr8\n" + "wldrd wr4, [%0, #96]\n" + " wmadds wr13, wr13, wr9\n" + "wldrd wr5, [%0, #104]\n" + " wmadds wr14, wr14, wr10\n" + "wldrd wr6, [%0, #112]\n" + " wmadds wr15, wr15, wr11\n" + "wldrd wr7, [%0, #120]\n" + " waddwss wr0, wr12, wr0\n" + "wldrd wr8, [%1, #96]\n" + " waddwss wr1, wr13, wr1\n" + "wldrd wr9, [%1, #104]\n" + " waddwss wr2, wr14, wr2\n" + "wldrd wr10, [%1, #112]\n" + " waddwss wr3, wr15, wr3\n" + "wldrd wr11, [%1, #120]\n" + "wmadds wr4, wr4, wr8\n" + " wldrd wr12, [%0, #128]\n" + "wmadds wr5, wr5, wr9\n" + " wldrd wr13, [%0, #136]\n" + "wmadds wr6, wr6, wr10\n" + " wldrd wr14, [%0, #144]\n" + "wmadds wr7, wr7, wr11\n" + " wldrd wr15, [%0, #152]\n" + "waddwss wr0, wr4, wr0\n" + " wldrd wr8, [%1, #128]\n" + "waddwss wr1, wr5, wr1\n" + " wldrd wr9, [%1, #136]\n" + "waddwss wr2, wr6, wr2\n" + " wldrd wr10, [%1, #144]\n" + " waddwss wr3, wr7, wr3\n" + " wldrd wr11, [%1, #152]\n" + " wmadds wr12, wr12, wr8\n" + "tmcr wcgr0, %4\n" + " wmadds wr13, wr13, wr9\n" + " wmadds wr14, wr14, wr10\n" + " wmadds wr15, wr15, wr11\n" + " waddwss wr0, wr12, wr0\n" + " waddwss wr1, wr13, wr1\n" + " waddwss wr2, wr14, wr2\n" + " waddwss wr3, wr15, wr3\n" + "\n" + "wsrawg wr0, wr0, wcgr0\n" + "wsrawg wr1, wr1, wcgr0\n" + "wsrawg wr2, wr2, wcgr0\n" + "wsrawg wr3, wr3, wcgr0\n" + "\n" + "wpackwss wr0, wr0, wr0\n" + "wpackwss wr1, wr1, wr1\n" + " wldrd wr4, [%1, #160]\n" + "wpackwss wr2, wr2, wr2\n" + " wldrd wr5, [%1, #168]\n" + "wpackwss wr3, wr3, wr3\n" + " wldrd wr6, [%1, #192]\n" + " wmadds wr4, wr4, wr0\n" + " wldrd wr7, [%1, #200]\n" + " wmadds wr5, wr5, wr0\n" + " wldrd wr8, [%1, #224]\n" + " wmadds wr6, wr6, wr1\n" + " wldrd wr9, [%1, #232]\n" + " wmadds wr7, wr7, wr1\n" + " waddwss wr4, wr6, wr4\n" + " waddwss wr5, wr7, wr5\n" + " wmadds wr8, wr8, wr2\n" + "wldrd wr6, [%1, #256]\n" + " wmadds wr9, wr9, wr2\n" + "wldrd wr7, [%1, #264]\n" + "waddwss wr4, wr8, wr4\n" + " waddwss wr5, wr9, wr5\n" + "wmadds wr6, wr6, wr3\n" + "wmadds wr7, wr7, wr3\n" + "waddwss wr4, wr6, wr4\n" + "waddwss wr5, wr7, wr5\n" + "\n" + "wstrd wr4, [%3]\n" + "wstrd wr5, [%3, #8]\n" + "\n" + "wldrd wr6, [%1, #176]\n" + "wldrd wr5, [%1, #184]\n" + "wmadds wr5, wr5, wr0\n" + "wldrd wr8, [%1, #208]\n" + "wmadds wr0, wr6, wr0\n" + "wldrd wr9, [%1, #216]\n" + "wmadds wr9, wr9, wr1\n" + "wldrd wr6, [%1, #240]\n" + "wmadds wr1, wr8, wr1\n" + "wldrd wr7, [%1, #248]\n" + "waddwss wr0, wr1, wr0\n" + "waddwss wr5, wr9, wr5\n" + "wmadds wr7, wr7, wr2\n" + "wldrd wr8, [%1, #272]\n" + "wmadds wr2, wr6, wr2\n" + "wldrd wr9, [%1, #280]\n" + "waddwss wr0, wr2, wr0\n" + "waddwss wr5, wr7, wr5\n" + "wmadds wr9, wr9, wr3\n" + "wmadds wr3, wr8, wr3\n" + "waddwss wr0, wr3, wr0\n" + "waddwss wr5, wr9, wr5\n" + "\n" + "wstrd wr0, [%3, #16]\n" + "wstrd wr5, [%3, #24]\n" + : + : "r" (in), "r" (consts), + "r" (1 << (SBC_PROTO_FIXED8_SCALE - 1)), "r" (out), + "r" (SBC_PROTO_FIXED8_SCALE) + : "wr0", "wr1", "wr2", "wr3", "wr4", "wr5", "wr6", "wr7", + "wr8", "wr9", "wr10", "wr11", "wr12", "wr13", "wr14", "wr15", + "wcgr0", "memory"); +} + +static inline void sbc_analyze_4b_4s_iwmmxt(int16_t *x, int32_t *out, + int out_stride) +{ + /* Analyze blocks */ + sbc_analyze_four_iwmmxt(x + 12, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + sbc_analyze_four_iwmmxt(x + 8, out, analysis_consts_fixed4_simd_even); + out += out_stride; + sbc_analyze_four_iwmmxt(x + 4, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + sbc_analyze_four_iwmmxt(x + 0, out, analysis_consts_fixed4_simd_even); +} + +static inline void sbc_analyze_4b_8s_iwmmxt(int16_t *x, int32_t *out, + int out_stride) +{ + /* Analyze blocks */ + sbc_analyze_eight_iwmmxt(x + 24, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + sbc_analyze_eight_iwmmxt(x + 16, out, analysis_consts_fixed8_simd_even); + out += out_stride; + sbc_analyze_eight_iwmmxt(x + 8, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + sbc_analyze_eight_iwmmxt(x + 0, out, analysis_consts_fixed8_simd_even); +} + +void sbc_init_primitives_iwmmxt(struct sbc_encoder_state *state) +{ + state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_iwmmxt; + state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_iwmmxt; + state->implementation_info = "IWMMXT"; +} + +#endif diff --git a/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.h b/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.h new file mode 100644 index 00000000..b535e686 --- /dev/null +++ b/src/modules/bluetooth/sbc/sbc_primitives_iwmmxt.h @@ -0,0 +1,42 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2010 Keith Mok <ek9852@gmail.com> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SBC_PRIMITIVES_IWMMXT_H +#define __SBC_PRIMITIVES_IWMMXT_H + +#include "sbc_primitives.h" + +#if defined(__GNUC__) && defined(__IWMMXT__) && \ + !defined(SBC_HIGH_PRECISION) && (SCALE_OUT_BITS == 15) + +#define SBC_BUILD_WITH_IWMMXT_SUPPORT + +void sbc_init_primitives_iwmmxt(struct sbc_encoder_state *encoder_state); + +#endif + +#endif diff --git a/src/modules/bluetooth/sbc_primitives_mmx.c b/src/modules/bluetooth/sbc/sbc_primitives_mmx.c index 08e9ca28..7f2fbc37 100644 --- a/src/modules/bluetooth/sbc_primitives_mmx.c +++ b/src/modules/bluetooth/sbc/sbc_primitives_mmx.c @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * @@ -100,7 +101,7 @@ static inline void sbc_analyze_four_mmx(const int16_t *in, int32_t *out, : : "r" (in), "r" (consts), "r" (&round_c), "r" (out), "i" (SBC_PROTO_FIXED4_SCALE) - : "memory"); + : "cc", "memory"); } static inline void sbc_analyze_eight_mmx(const int16_t *in, int32_t *out, @@ -242,7 +243,7 @@ static inline void sbc_analyze_eight_mmx(const int16_t *in, int32_t *out, : : "r" (in), "r" (consts), "r" (&round_c), "r" (out), "i" (SBC_PROTO_FIXED8_SCALE) - : "memory"); + : "cc", "memory"); } static inline void sbc_analyze_4b_4s_mmx(int16_t *x, int32_t *out, @@ -275,6 +276,59 @@ static inline void sbc_analyze_4b_8s_mmx(int16_t *x, int32_t *out, asm volatile ("emms\n"); } +static void sbc_calc_scalefactors_mmx( + int32_t sb_sample_f[16][2][8], + uint32_t scale_factor[2][8], + int blocks, int channels, int subbands) +{ + static const SBC_ALIGNED int32_t consts[2] = { + 1 << SCALE_OUT_BITS, + 1 << SCALE_OUT_BITS, + }; + int ch, sb; + intptr_t blk; + for (ch = 0; ch < channels; ch++) { + for (sb = 0; sb < subbands; sb += 2) { + blk = (blocks - 1) * (((char *) &sb_sample_f[1][0][0] - + (char *) &sb_sample_f[0][0][0])); + asm volatile ( + "movq (%4), %%mm0\n" + "1:\n" + "movq (%1, %0), %%mm1\n" + "pxor %%mm2, %%mm2\n" + "pcmpgtd %%mm2, %%mm1\n" + "paddd (%1, %0), %%mm1\n" + "pcmpgtd %%mm1, %%mm2\n" + "pxor %%mm2, %%mm1\n" + + "por %%mm1, %%mm0\n" + + "sub %2, %0\n" + "jns 1b\n" + + "movd %%mm0, %k0\n" + "psrlq $32, %%mm0\n" + "bsrl %k0, %k0\n" + "subl %5, %k0\n" + "movl %k0, (%3)\n" + + "movd %%mm0, %k0\n" + "bsrl %k0, %k0\n" + "subl %5, %k0\n" + "movl %k0, 4(%3)\n" + : "+r" (blk) + : "r" (&sb_sample_f[0][ch][sb]), + "i" ((char *) &sb_sample_f[1][0][0] - + (char *) &sb_sample_f[0][0][0]), + "r" (&scale_factor[ch][sb]), + "r" (&consts), + "i" (SCALE_OUT_BITS) + : "cc", "memory"); + } + } + asm volatile ("emms\n"); +} + static int check_mmx_support(void) { #ifdef __amd64__ @@ -313,6 +367,7 @@ void sbc_init_primitives_mmx(struct sbc_encoder_state *state) if (check_mmx_support()) { state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_mmx; state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_mmx; + state->sbc_calc_scalefactors = sbc_calc_scalefactors_mmx; state->implementation_info = "MMX"; } } diff --git a/src/modules/bluetooth/sbc_primitives_mmx.h b/src/modules/bluetooth/sbc/sbc_primitives_mmx.h index c1e44a5d..e0e728bc 100644 --- a/src/modules/bluetooth/sbc_primitives_mmx.h +++ b/src/modules/bluetooth/sbc/sbc_primitives_mmx.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * diff --git a/src/modules/bluetooth/sbc/sbc_primitives_neon.c b/src/modules/bluetooth/sbc/sbc_primitives_neon.c new file mode 100644 index 00000000..0572158d --- /dev/null +++ b/src/modules/bluetooth/sbc/sbc_primitives_neon.c @@ -0,0 +1,893 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <stdint.h> +#include <limits.h> +#include "sbc.h" +#include "sbc_math.h" +#include "sbc_tables.h" + +#include "sbc_primitives_neon.h" + +/* + * ARM NEON optimizations + */ + +#ifdef SBC_BUILD_WITH_NEON_SUPPORT + +static inline void _sbc_analyze_four_neon(const int16_t *in, int32_t *out, + const FIXED_T *consts) +{ + /* TODO: merge even and odd cases (or even merge all four calls to this + * function) in order to have only aligned reads from 'in' array + * and reduce number of load instructions */ + asm volatile ( + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmull.s16 q0, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmull.s16 q1, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + + "vmlal.s16 q0, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmlal.s16 q1, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q0, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmlal.s16 q1, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + + "vmlal.s16 q0, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmlal.s16 q1, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q0, d4, d8\n" + "vmlal.s16 q1, d5, d9\n" + + "vpadd.s32 d0, d0, d1\n" + "vpadd.s32 d1, d2, d3\n" + + "vrshrn.s32 d0, q0, %3\n" + + "vld1.16 {d2, d3, d4, d5}, [%1, :128]!\n" + + "vdup.i32 d1, d0[1]\n" /* TODO: can be eliminated */ + "vdup.i32 d0, d0[0]\n" /* TODO: can be eliminated */ + + "vmull.s16 q3, d2, d0\n" + "vmull.s16 q4, d3, d0\n" + "vmlal.s16 q3, d4, d1\n" + "vmlal.s16 q4, d5, d1\n" + + "vpadd.s32 d0, d6, d7\n" /* TODO: can be eliminated */ + "vpadd.s32 d1, d8, d9\n" /* TODO: can be eliminated */ + + "vst1.32 {d0, d1}, [%2, :128]\n" + : "+r" (in), "+r" (consts) + : "r" (out), + "i" (SBC_PROTO_FIXED4_SCALE) + : "memory", + "d0", "d1", "d2", "d3", "d4", "d5", + "d6", "d7", "d8", "d9", "d10", "d11"); +} + +static inline void _sbc_analyze_eight_neon(const int16_t *in, int32_t *out, + const FIXED_T *consts) +{ + /* TODO: merge even and odd cases (or even merge all four calls to this + * function) in order to have only aligned reads from 'in' array + * and reduce number of load instructions */ + asm volatile ( + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmull.s16 q6, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmull.s16 q7, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + "vmull.s16 q8, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmull.s16 q9, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q6, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmlal.s16 q7, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + "vmlal.s16 q8, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmlal.s16 q9, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q6, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmlal.s16 q7, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + "vmlal.s16 q8, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmlal.s16 q9, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q6, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmlal.s16 q7, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + "vmlal.s16 q8, d6, d10\n" + "vld1.16 {d4, d5}, [%0, :64]!\n" + "vmlal.s16 q9, d7, d11\n" + "vld1.16 {d8, d9}, [%1, :128]!\n" + + "vmlal.s16 q6, d4, d8\n" + "vld1.16 {d6, d7}, [%0, :64]!\n" + "vmlal.s16 q7, d5, d9\n" + "vld1.16 {d10, d11}, [%1, :128]!\n" + + "vmlal.s16 q8, d6, d10\n" + "vmlal.s16 q9, d7, d11\n" + + "vpadd.s32 d0, d12, d13\n" + "vpadd.s32 d1, d14, d15\n" + "vpadd.s32 d2, d16, d17\n" + "vpadd.s32 d3, d18, d19\n" + + "vrshr.s32 q0, q0, %3\n" + "vrshr.s32 q1, q1, %3\n" + "vmovn.s32 d0, q0\n" + "vmovn.s32 d1, q1\n" + + "vdup.i32 d3, d1[1]\n" /* TODO: can be eliminated */ + "vdup.i32 d2, d1[0]\n" /* TODO: can be eliminated */ + "vdup.i32 d1, d0[1]\n" /* TODO: can be eliminated */ + "vdup.i32 d0, d0[0]\n" /* TODO: can be eliminated */ + + "vld1.16 {d4, d5}, [%1, :128]!\n" + "vmull.s16 q6, d4, d0\n" + "vld1.16 {d6, d7}, [%1, :128]!\n" + "vmull.s16 q7, d5, d0\n" + "vmull.s16 q8, d6, d0\n" + "vmull.s16 q9, d7, d0\n" + + "vld1.16 {d4, d5}, [%1, :128]!\n" + "vmlal.s16 q6, d4, d1\n" + "vld1.16 {d6, d7}, [%1, :128]!\n" + "vmlal.s16 q7, d5, d1\n" + "vmlal.s16 q8, d6, d1\n" + "vmlal.s16 q9, d7, d1\n" + + "vld1.16 {d4, d5}, [%1, :128]!\n" + "vmlal.s16 q6, d4, d2\n" + "vld1.16 {d6, d7}, [%1, :128]!\n" + "vmlal.s16 q7, d5, d2\n" + "vmlal.s16 q8, d6, d2\n" + "vmlal.s16 q9, d7, d2\n" + + "vld1.16 {d4, d5}, [%1, :128]!\n" + "vmlal.s16 q6, d4, d3\n" + "vld1.16 {d6, d7}, [%1, :128]!\n" + "vmlal.s16 q7, d5, d3\n" + "vmlal.s16 q8, d6, d3\n" + "vmlal.s16 q9, d7, d3\n" + + "vpadd.s32 d0, d12, d13\n" /* TODO: can be eliminated */ + "vpadd.s32 d1, d14, d15\n" /* TODO: can be eliminated */ + "vpadd.s32 d2, d16, d17\n" /* TODO: can be eliminated */ + "vpadd.s32 d3, d18, d19\n" /* TODO: can be eliminated */ + + "vst1.32 {d0, d1, d2, d3}, [%2, :128]\n" + : "+r" (in), "+r" (consts) + : "r" (out), + "i" (SBC_PROTO_FIXED8_SCALE) + : "memory", + "d0", "d1", "d2", "d3", "d4", "d5", + "d6", "d7", "d8", "d9", "d10", "d11", + "d12", "d13", "d14", "d15", "d16", "d17", + "d18", "d19"); +} + +static inline void sbc_analyze_4b_4s_neon(int16_t *x, + int32_t *out, int out_stride) +{ + /* Analyze blocks */ + _sbc_analyze_four_neon(x + 12, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + _sbc_analyze_four_neon(x + 8, out, analysis_consts_fixed4_simd_even); + out += out_stride; + _sbc_analyze_four_neon(x + 4, out, analysis_consts_fixed4_simd_odd); + out += out_stride; + _sbc_analyze_four_neon(x + 0, out, analysis_consts_fixed4_simd_even); +} + +static inline void sbc_analyze_4b_8s_neon(int16_t *x, + int32_t *out, int out_stride) +{ + /* Analyze blocks */ + _sbc_analyze_eight_neon(x + 24, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + _sbc_analyze_eight_neon(x + 16, out, analysis_consts_fixed8_simd_even); + out += out_stride; + _sbc_analyze_eight_neon(x + 8, out, analysis_consts_fixed8_simd_odd); + out += out_stride; + _sbc_analyze_eight_neon(x + 0, out, analysis_consts_fixed8_simd_even); +} + +static void sbc_calc_scalefactors_neon( + int32_t sb_sample_f[16][2][8], + uint32_t scale_factor[2][8], + int blocks, int channels, int subbands) +{ + int ch, sb; + for (ch = 0; ch < channels; ch++) { + for (sb = 0; sb < subbands; sb += 4) { + int blk = blocks; + int32_t *in = &sb_sample_f[0][ch][sb]; + asm volatile ( + "vmov.s32 q0, #0\n" + "vmov.s32 q1, %[c1]\n" + "vmov.s32 q14, #1\n" + "vmov.s32 q15, %[c2]\n" + "vadd.s32 q1, q1, q14\n" + "1:\n" + "vld1.32 {d16, d17}, [%[in], :128], %[inc]\n" + "vabs.s32 q8, q8\n" + "vld1.32 {d18, d19}, [%[in], :128], %[inc]\n" + "vabs.s32 q9, q9\n" + "vld1.32 {d20, d21}, [%[in], :128], %[inc]\n" + "vabs.s32 q10, q10\n" + "vld1.32 {d22, d23}, [%[in], :128], %[inc]\n" + "vabs.s32 q11, q11\n" + "vmax.s32 q0, q0, q8\n" + "vmax.s32 q1, q1, q9\n" + "vmax.s32 q0, q0, q10\n" + "vmax.s32 q1, q1, q11\n" + "subs %[blk], %[blk], #4\n" + "bgt 1b\n" + "vmax.s32 q0, q0, q1\n" + "vsub.s32 q0, q0, q14\n" + "vclz.s32 q0, q0\n" + "vsub.s32 q0, q15, q0\n" + "vst1.32 {d0, d1}, [%[out], :128]\n" + : + [blk] "+r" (blk), + [in] "+r" (in) + : + [inc] "r" ((char *) &sb_sample_f[1][0][0] - + (char *) &sb_sample_f[0][0][0]), + [out] "r" (&scale_factor[ch][sb]), + [c1] "i" (1 << SCALE_OUT_BITS), + [c2] "i" (31 - SCALE_OUT_BITS) + : "d0", "d1", "d2", "d3", "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23", "d24", "d25", "d26", + "d27", "d28", "d29", "d30", "d31", "cc", "memory"); + } + } +} + +int sbc_calc_scalefactors_j_neon( + int32_t sb_sample_f[16][2][8], + uint32_t scale_factor[2][8], + int blocks, int subbands) +{ + static SBC_ALIGNED int32_t joint_bits_mask[8] = { + 8, 4, 2, 1, 128, 64, 32, 16 + }; + int joint, i; + int32_t *in0, *in1; + int32_t *in = &sb_sample_f[0][0][0]; + uint32_t *out0, *out1; + uint32_t *out = &scale_factor[0][0]; + int32_t *consts = joint_bits_mask; + + i = subbands; + + asm volatile ( + /* + * constants: q13 = (31 - SCALE_OUT_BITS), q14 = 1 + * input: q0 = ((1 << SCALE_OUT_BITS) + 1) + * %[in0] - samples for channel 0 + * %[in1] - samples for shannel 1 + * output: q0, q1 - scale factors without joint stereo + * q2, q3 - scale factors with joint stereo + * q15 - joint stereo selection mask + */ + ".macro calc_scalefactors\n" + "vmov.s32 q1, q0\n" + "vmov.s32 q2, q0\n" + "vmov.s32 q3, q0\n" + "mov %[i], %[blocks]\n" + "1:\n" + "vld1.32 {d18, d19}, [%[in1], :128], %[inc]\n" + "vbic.s32 q11, q9, q14\n" + "vld1.32 {d16, d17}, [%[in0], :128], %[inc]\n" + "vhadd.s32 q10, q8, q11\n" + "vhsub.s32 q11, q8, q11\n" + "vabs.s32 q8, q8\n" + "vabs.s32 q9, q9\n" + "vabs.s32 q10, q10\n" + "vabs.s32 q11, q11\n" + "vmax.s32 q0, q0, q8\n" + "vmax.s32 q1, q1, q9\n" + "vmax.s32 q2, q2, q10\n" + "vmax.s32 q3, q3, q11\n" + "subs %[i], %[i], #1\n" + "bgt 1b\n" + "vsub.s32 q0, q0, q14\n" + "vsub.s32 q1, q1, q14\n" + "vsub.s32 q2, q2, q14\n" + "vsub.s32 q3, q3, q14\n" + "vclz.s32 q0, q0\n" + "vclz.s32 q1, q1\n" + "vclz.s32 q2, q2\n" + "vclz.s32 q3, q3\n" + "vsub.s32 q0, q13, q0\n" + "vsub.s32 q1, q13, q1\n" + "vsub.s32 q2, q13, q2\n" + "vsub.s32 q3, q13, q3\n" + ".endm\n" + /* + * constants: q14 = 1 + * input: q15 - joint stereo selection mask + * %[in0] - value set by calc_scalefactors macro + * %[in1] - value set by calc_scalefactors macro + */ + ".macro update_joint_stereo_samples\n" + "sub %[out1], %[in1], %[inc]\n" + "sub %[out0], %[in0], %[inc]\n" + "sub %[in1], %[in1], %[inc], asl #1\n" + "sub %[in0], %[in0], %[inc], asl #1\n" + "vld1.32 {d18, d19}, [%[in1], :128]\n" + "vbic.s32 q11, q9, q14\n" + "vld1.32 {d16, d17}, [%[in0], :128]\n" + "vld1.32 {d2, d3}, [%[out1], :128]\n" + "vbic.s32 q3, q1, q14\n" + "vld1.32 {d0, d1}, [%[out0], :128]\n" + "vhsub.s32 q10, q8, q11\n" + "vhadd.s32 q11, q8, q11\n" + "vhsub.s32 q2, q0, q3\n" + "vhadd.s32 q3, q0, q3\n" + "vbif.s32 q10, q9, q15\n" + "vbif.s32 d22, d16, d30\n" + "sub %[inc], %[zero], %[inc], asl #1\n" + "sub %[i], %[blocks], #2\n" + "2:\n" + "vbif.s32 d23, d17, d31\n" + "vst1.32 {d20, d21}, [%[in1], :128], %[inc]\n" + "vbif.s32 d4, d2, d30\n" + "vld1.32 {d18, d19}, [%[in1], :128]\n" + "vbif.s32 d5, d3, d31\n" + "vst1.32 {d22, d23}, [%[in0], :128], %[inc]\n" + "vbif.s32 d6, d0, d30\n" + "vld1.32 {d16, d17}, [%[in0], :128]\n" + "vbif.s32 d7, d1, d31\n" + "vst1.32 {d4, d5}, [%[out1], :128], %[inc]\n" + "vbic.s32 q11, q9, q14\n" + "vld1.32 {d2, d3}, [%[out1], :128]\n" + "vst1.32 {d6, d7}, [%[out0], :128], %[inc]\n" + "vbic.s32 q3, q1, q14\n" + "vld1.32 {d0, d1}, [%[out0], :128]\n" + "vhsub.s32 q10, q8, q11\n" + "vhadd.s32 q11, q8, q11\n" + "vhsub.s32 q2, q0, q3\n" + "vhadd.s32 q3, q0, q3\n" + "vbif.s32 q10, q9, q15\n" + "vbif.s32 d22, d16, d30\n" + "subs %[i], %[i], #2\n" + "bgt 2b\n" + "sub %[inc], %[zero], %[inc], asr #1\n" + "vbif.s32 d23, d17, d31\n" + "vst1.32 {d20, d21}, [%[in1], :128]\n" + "vbif.s32 q2, q1, q15\n" + "vst1.32 {d22, d23}, [%[in0], :128]\n" + "vbif.s32 q3, q0, q15\n" + "vst1.32 {d4, d5}, [%[out1], :128]\n" + "vst1.32 {d6, d7}, [%[out0], :128]\n" + ".endm\n" + + "vmov.s32 q14, #1\n" + "vmov.s32 q13, %[c2]\n" + + "cmp %[i], #4\n" + "bne 8f\n" + + "4:\n" /* 4 subbands */ + "add %[in0], %[in], #0\n" + "add %[in1], %[in], #32\n" + "add %[out0], %[out], #0\n" + "add %[out1], %[out], #32\n" + "vmov.s32 q0, %[c1]\n" + "vadd.s32 q0, q0, q14\n" + + "calc_scalefactors\n" + + /* check whether to use joint stereo for subbands 0, 1, 2 */ + "vadd.s32 q15, q0, q1\n" + "vadd.s32 q9, q2, q3\n" + "vmov.s32 d31[1], %[zero]\n" /* last subband -> no joint */ + "vld1.32 {d16, d17}, [%[consts], :128]!\n" + "vcgt.s32 q15, q15, q9\n" + + /* calculate and save to memory 'joint' variable */ + /* update and save scale factors to memory */ + " vand.s32 q8, q8, q15\n" + "vbit.s32 q0, q2, q15\n" + " vpadd.s32 d16, d16, d17\n" + "vbit.s32 q1, q3, q15\n" + " vpadd.s32 d16, d16, d16\n" + "vst1.32 {d0, d1}, [%[out0], :128]\n" + "vst1.32 {d2, d3}, [%[out1], :128]\n" + " vst1.32 {d16[0]}, [%[joint]]\n" + + "update_joint_stereo_samples\n" + "b 9f\n" + + "8:\n" /* 8 subbands */ + "add %[in0], %[in], #16\n\n" + "add %[in1], %[in], #48\n" + "add %[out0], %[out], #16\n\n" + "add %[out1], %[out], #48\n" + "vmov.s32 q0, %[c1]\n" + "vadd.s32 q0, q0, q14\n" + + "calc_scalefactors\n" + + /* check whether to use joint stereo for subbands 4, 5, 6 */ + "vadd.s32 q15, q0, q1\n" + "vadd.s32 q9, q2, q3\n" + "vmov.s32 d31[1], %[zero]\n" /* last subband -> no joint */ + "vld1.32 {d16, d17}, [%[consts], :128]!\n" + "vcgt.s32 q15, q15, q9\n" + + /* calculate part of 'joint' variable and save it to d24 */ + /* update and save scale factors to memory */ + " vand.s32 q8, q8, q15\n" + "vbit.s32 q0, q2, q15\n" + " vpadd.s32 d16, d16, d17\n" + "vbit.s32 q1, q3, q15\n" + "vst1.32 {d0, d1}, [%[out0], :128]\n" + "vst1.32 {d2, d3}, [%[out1], :128]\n" + " vpadd.s32 d24, d16, d16\n" + + "update_joint_stereo_samples\n" + + "add %[in0], %[in], #0\n" + "add %[in1], %[in], #32\n" + "add %[out0], %[out], #0\n\n" + "add %[out1], %[out], #32\n" + "vmov.s32 q0, %[c1]\n" + "vadd.s32 q0, q0, q14\n" + + "calc_scalefactors\n" + + /* check whether to use joint stereo for subbands 0, 1, 2, 3 */ + "vadd.s32 q15, q0, q1\n" + "vadd.s32 q9, q2, q3\n" + "vld1.32 {d16, d17}, [%[consts], :128]!\n" + "vcgt.s32 q15, q15, q9\n" + + /* combine last part of 'joint' with d24 and save to memory */ + /* update and save scale factors to memory */ + " vand.s32 q8, q8, q15\n" + "vbit.s32 q0, q2, q15\n" + " vpadd.s32 d16, d16, d17\n" + "vbit.s32 q1, q3, q15\n" + " vpadd.s32 d16, d16, d16\n" + "vst1.32 {d0, d1}, [%[out0], :128]\n" + " vadd.s32 d16, d16, d24\n" + "vst1.32 {d2, d3}, [%[out1], :128]\n" + " vst1.32 {d16[0]}, [%[joint]]\n" + + "update_joint_stereo_samples\n" + "9:\n" + ".purgem calc_scalefactors\n" + ".purgem update_joint_stereo_samples\n" + : + [i] "+&r" (i), + [in] "+&r" (in), + [in0] "=&r" (in0), + [in1] "=&r" (in1), + [out] "+&r" (out), + [out0] "=&r" (out0), + [out1] "=&r" (out1), + [consts] "+&r" (consts) + : + [inc] "r" ((char *) &sb_sample_f[1][0][0] - + (char *) &sb_sample_f[0][0][0]), + [blocks] "r" (blocks), + [joint] "r" (&joint), + [c1] "i" (1 << SCALE_OUT_BITS), + [c2] "i" (31 - SCALE_OUT_BITS), + [zero] "r" (0) + : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + "d16", "d17", "d18", "d19", "d20", "d21", "d22", + "d23", "d24", "d25", "d26", "d27", "d28", "d29", + "d30", "d31", "cc", "memory"); + + return joint; +} + +#define PERM_BE(a, b, c, d) { \ + (a * 2) + 1, (a * 2) + 0, \ + (b * 2) + 1, (b * 2) + 0, \ + (c * 2) + 1, (c * 2) + 0, \ + (d * 2) + 1, (d * 2) + 0 \ + } +#define PERM_LE(a, b, c, d) { \ + (a * 2) + 0, (a * 2) + 1, \ + (b * 2) + 0, (b * 2) + 1, \ + (c * 2) + 0, (c * 2) + 1, \ + (d * 2) + 0, (d * 2) + 1 \ + } + +static SBC_ALWAYS_INLINE int sbc_enc_process_input_4s_neon_internal( + int position, + const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels, int big_endian) +{ + static SBC_ALIGNED uint8_t perm_be[2][8] = { + PERM_BE(7, 3, 6, 4), + PERM_BE(0, 2, 1, 5) + }; + static SBC_ALIGNED uint8_t perm_le[2][8] = { + PERM_LE(7, 3, 6, 4), + PERM_LE(0, 2, 1, 5) + }; + /* handle X buffer wraparound */ + if (position < nsamples) { + int16_t *dst = &X[0][SBC_X_BUFFER_SIZE - 40]; + int16_t *src = &X[0][position]; + asm volatile ( + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0}, [%[src], :64]!\n" + "vst1.16 {d0}, [%[dst], :64]!\n" + : + [dst] "+r" (dst), + [src] "+r" (src) + : : "memory", "d0", "d1", "d2", "d3"); + if (nchannels > 1) { + dst = &X[1][SBC_X_BUFFER_SIZE - 40]; + src = &X[1][position]; + asm volatile ( + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0}, [%[src], :64]!\n" + "vst1.16 {d0}, [%[dst], :64]!\n" + : + [dst] "+r" (dst), + [src] "+r" (src) + : : "memory", "d0", "d1", "d2", "d3"); + } + position = SBC_X_BUFFER_SIZE - 40; + } + + if ((nchannels > 1) && ((uintptr_t)pcm & 1)) { + /* poor 'pcm' alignment */ + int16_t *x = &X[0][position]; + int16_t *y = &X[1][position]; + asm volatile ( + "vld1.8 {d0, d1}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #16\n" + "sub %[y], %[y], #16\n" + "sub %[position], %[position], #8\n" + "vld1.8 {d4, d5}, [%[pcm]]!\n" + "vuzp.16 d4, d5\n" + "vld1.8 {d20, d21}, [%[pcm]]!\n" + "vuzp.16 d20, d21\n" + "vswp d5, d20\n" + "vtbl.8 d16, {d4, d5}, d0\n" + "vtbl.8 d17, {d4, d5}, d1\n" + "vtbl.8 d18, {d20, d21}, d0\n" + "vtbl.8 d19, {d20, d21}, d1\n" + "vst1.16 {d16, d17}, [%[x], :128]\n" + "vst1.16 {d18, d19}, [%[y], :128]\n" + "subs %[nsamples], %[nsamples], #8\n" + "bgt 1b\n" + : + [x] "+r" (x), + [y] "+r" (y), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23"); + } else if (nchannels > 1) { + /* proper 'pcm' alignment */ + int16_t *x = &X[0][position]; + int16_t *y = &X[1][position]; + asm volatile ( + "vld1.8 {d0, d1}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #16\n" + "sub %[y], %[y], #16\n" + "sub %[position], %[position], #8\n" + "vld2.16 {d4, d5}, [%[pcm]]!\n" + "vld2.16 {d20, d21}, [%[pcm]]!\n" + "vswp d5, d20\n" + "vtbl.8 d16, {d4, d5}, d0\n" + "vtbl.8 d17, {d4, d5}, d1\n" + "vtbl.8 d18, {d20, d21}, d0\n" + "vtbl.8 d19, {d20, d21}, d1\n" + "vst1.16 {d16, d17}, [%[x], :128]\n" + "vst1.16 {d18, d19}, [%[y], :128]\n" + "subs %[nsamples], %[nsamples], #8\n" + "bgt 1b\n" + : + [x] "+r" (x), + [y] "+r" (y), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23"); + } else { + int16_t *x = &X[0][position]; + asm volatile ( + "vld1.8 {d0, d1}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #16\n" + "sub %[position], %[position], #8\n" + "vld1.8 {d4, d5}, [%[pcm]]!\n" + "vtbl.8 d16, {d4, d5}, d0\n" + "vtbl.8 d17, {d4, d5}, d1\n" + "vst1.16 {d16, d17}, [%[x], :128]\n" + "subs %[nsamples], %[nsamples], #8\n" + "bgt 1b\n" + : + [x] "+r" (x), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19"); + } + return position; +} + +static SBC_ALWAYS_INLINE int sbc_enc_process_input_8s_neon_internal( + int position, + const uint8_t *pcm, int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels, int big_endian) +{ + static SBC_ALIGNED uint8_t perm_be[4][8] = { + PERM_BE(15, 7, 14, 8), + PERM_BE(13, 9, 12, 10), + PERM_BE(11, 3, 6, 0), + PERM_BE(5, 1, 4, 2) + }; + static SBC_ALIGNED uint8_t perm_le[4][8] = { + PERM_LE(15, 7, 14, 8), + PERM_LE(13, 9, 12, 10), + PERM_LE(11, 3, 6, 0), + PERM_LE(5, 1, 4, 2) + }; + /* handle X buffer wraparound */ + if (position < nsamples) { + int16_t *dst = &X[0][SBC_X_BUFFER_SIZE - 72]; + int16_t *src = &X[0][position]; + asm volatile ( + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1}, [%[src], :128]!\n" + "vst1.16 {d0, d1}, [%[dst], :128]!\n" + : + [dst] "+r" (dst), + [src] "+r" (src) + : : "memory", "d0", "d1", "d2", "d3"); + if (nchannels > 1) { + dst = &X[1][SBC_X_BUFFER_SIZE - 72]; + src = &X[1][position]; + asm volatile ( + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1, d2, d3}, [%[src], :128]!\n" + "vst1.16 {d0, d1, d2, d3}, [%[dst], :128]!\n" + "vld1.16 {d0, d1}, [%[src], :128]!\n" + "vst1.16 {d0, d1}, [%[dst], :128]!\n" + : + [dst] "+r" (dst), + [src] "+r" (src) + : : "memory", "d0", "d1", "d2", "d3"); + } + position = SBC_X_BUFFER_SIZE - 72; + } + + if ((nchannels > 1) && ((uintptr_t)pcm & 1)) { + /* poor 'pcm' alignment */ + int16_t *x = &X[0][position]; + int16_t *y = &X[1][position]; + asm volatile ( + "vld1.8 {d0, d1, d2, d3}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #32\n" + "sub %[y], %[y], #32\n" + "sub %[position], %[position], #16\n" + "vld1.8 {d4, d5, d6, d7}, [%[pcm]]!\n" + "vuzp.16 q2, q3\n" + "vld1.8 {d20, d21, d22, d23}, [%[pcm]]!\n" + "vuzp.16 q10, q11\n" + "vswp q3, q10\n" + "vtbl.8 d16, {d4, d5, d6, d7}, d0\n" + "vtbl.8 d17, {d4, d5, d6, d7}, d1\n" + "vtbl.8 d18, {d4, d5, d6, d7}, d2\n" + "vtbl.8 d19, {d4, d5, d6, d7}, d3\n" + "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n" + "vtbl.8 d16, {d20, d21, d22, d23}, d0\n" + "vtbl.8 d17, {d20, d21, d22, d23}, d1\n" + "vtbl.8 d18, {d20, d21, d22, d23}, d2\n" + "vtbl.8 d19, {d20, d21, d22, d23}, d3\n" + "vst1.16 {d16, d17, d18, d19}, [%[y], :128]\n" + "subs %[nsamples], %[nsamples], #16\n" + "bgt 1b\n" + : + [x] "+r" (x), + [y] "+r" (y), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23"); + } else if (nchannels > 1) { + /* proper 'pcm' alignment */ + int16_t *x = &X[0][position]; + int16_t *y = &X[1][position]; + asm volatile ( + "vld1.8 {d0, d1, d2, d3}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #32\n" + "sub %[y], %[y], #32\n" + "sub %[position], %[position], #16\n" + "vld2.16 {d4, d5, d6, d7}, [%[pcm]]!\n" + "vld2.16 {d20, d21, d22, d23}, [%[pcm]]!\n" + "vswp q3, q10\n" + "vtbl.8 d16, {d4, d5, d6, d7}, d0\n" + "vtbl.8 d17, {d4, d5, d6, d7}, d1\n" + "vtbl.8 d18, {d4, d5, d6, d7}, d2\n" + "vtbl.8 d19, {d4, d5, d6, d7}, d3\n" + "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n" + "vtbl.8 d16, {d20, d21, d22, d23}, d0\n" + "vtbl.8 d17, {d20, d21, d22, d23}, d1\n" + "vtbl.8 d18, {d20, d21, d22, d23}, d2\n" + "vtbl.8 d19, {d20, d21, d22, d23}, d3\n" + "vst1.16 {d16, d17, d18, d19}, [%[y], :128]\n" + "subs %[nsamples], %[nsamples], #16\n" + "bgt 1b\n" + : + [x] "+r" (x), + [y] "+r" (y), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19", + "d20", "d21", "d22", "d23"); + } else { + int16_t *x = &X[0][position]; + asm volatile ( + "vld1.8 {d0, d1, d2, d3}, [%[perm], :128]\n" + "1:\n" + "sub %[x], %[x], #32\n" + "sub %[position], %[position], #16\n" + "vld1.8 {d4, d5, d6, d7}, [%[pcm]]!\n" + "vtbl.8 d16, {d4, d5, d6, d7}, d0\n" + "vtbl.8 d17, {d4, d5, d6, d7}, d1\n" + "vtbl.8 d18, {d4, d5, d6, d7}, d2\n" + "vtbl.8 d19, {d4, d5, d6, d7}, d3\n" + "vst1.16 {d16, d17, d18, d19}, [%[x], :128]\n" + "subs %[nsamples], %[nsamples], #16\n" + "bgt 1b\n" + : + [x] "+r" (x), + [pcm] "+r" (pcm), + [nsamples] "+r" (nsamples), + [position] "+r" (position) + : + [perm] "r" (big_endian ? perm_be : perm_le) + : "cc", "memory", "d0", "d1", "d2", "d3", "d4", + "d5", "d6", "d7", "d16", "d17", "d18", "d19"); + } + return position; +} + +#undef PERM_BE +#undef PERM_LE + +static int sbc_enc_process_input_4s_be_neon(int position, const uint8_t *pcm, + int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels) +{ + return sbc_enc_process_input_4s_neon_internal( + position, pcm, X, nsamples, nchannels, 1); +} + +static int sbc_enc_process_input_4s_le_neon(int position, const uint8_t *pcm, + int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels) +{ + return sbc_enc_process_input_4s_neon_internal( + position, pcm, X, nsamples, nchannels, 0); +} + +static int sbc_enc_process_input_8s_be_neon(int position, const uint8_t *pcm, + int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels) +{ + return sbc_enc_process_input_8s_neon_internal( + position, pcm, X, nsamples, nchannels, 1); +} + +static int sbc_enc_process_input_8s_le_neon(int position, const uint8_t *pcm, + int16_t X[2][SBC_X_BUFFER_SIZE], + int nsamples, int nchannels) +{ + return sbc_enc_process_input_8s_neon_internal( + position, pcm, X, nsamples, nchannels, 0); +} + +void sbc_init_primitives_neon(struct sbc_encoder_state *state) +{ + state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_neon; + state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_neon; + state->sbc_calc_scalefactors = sbc_calc_scalefactors_neon; + state->sbc_calc_scalefactors_j = sbc_calc_scalefactors_j_neon; + state->sbc_enc_process_input_4s_le = sbc_enc_process_input_4s_le_neon; + state->sbc_enc_process_input_4s_be = sbc_enc_process_input_4s_be_neon; + state->sbc_enc_process_input_8s_le = sbc_enc_process_input_8s_le_neon; + state->sbc_enc_process_input_8s_be = sbc_enc_process_input_8s_be_neon; + state->implementation_info = "NEON"; +} + +#endif diff --git a/src/modules/bluetooth/sbc_primitives_neon.h b/src/modules/bluetooth/sbc/sbc_primitives_neon.h index 30766ed8..ea3da06a 100644 --- a/src/modules/bluetooth/sbc_primitives_neon.h +++ b/src/modules/bluetooth/sbc/sbc_primitives_neon.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * diff --git a/src/modules/bluetooth/sbc_tables.h b/src/modules/bluetooth/sbc/sbc_tables.h index 0057c73f..28c0d54b 100644 --- a/src/modules/bluetooth/sbc_tables.h +++ b/src/modules/bluetooth/sbc/sbc_tables.h @@ -2,7 +2,8 @@ * * Bluetooth low-complexity, subband codec (SBC) library * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2008-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> * diff --git a/src/modules/bluetooth/sbc_primitives_neon.c b/src/modules/bluetooth/sbc_primitives_neon.c deleted file mode 100644 index f1bc7b48..00000000 --- a/src/modules/bluetooth/sbc_primitives_neon.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * - * Bluetooth low-complexity, subband codec (SBC) library - * - * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> - * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> - * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> - * - * - * 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 library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include <stdint.h> -#include <limits.h> -#include "sbc.h" -#include "sbc_math.h" -#include "sbc_tables.h" - -#include "sbc_primitives_neon.h" - -/* - * ARM NEON optimizations - */ - -#ifdef SBC_BUILD_WITH_NEON_SUPPORT - -static inline void _sbc_analyze_four_neon(const int16_t *in, int32_t *out, - const FIXED_T *consts) -{ - /* TODO: merge even and odd cases (or even merge all four calls to this - * function) in order to have only aligned reads from 'in' array - * and reduce number of load instructions */ - asm volatile ( - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmull.s16 q0, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmull.s16 q1, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - - "vmlal.s16 q0, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmlal.s16 q1, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q0, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmlal.s16 q1, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - - "vmlal.s16 q0, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmlal.s16 q1, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q0, d4, d8\n" - "vmlal.s16 q1, d5, d9\n" - - "vpadd.s32 d0, d0, d1\n" - "vpadd.s32 d1, d2, d3\n" - - "vrshrn.s32 d0, q0, %3\n" - - "vld1.16 {d2, d3, d4, d5}, [%1, :128]!\n" - - "vdup.i32 d1, d0[1]\n" /* TODO: can be eliminated */ - "vdup.i32 d0, d0[0]\n" /* TODO: can be eliminated */ - - "vmull.s16 q3, d2, d0\n" - "vmull.s16 q4, d3, d0\n" - "vmlal.s16 q3, d4, d1\n" - "vmlal.s16 q4, d5, d1\n" - - "vpadd.s32 d0, d6, d7\n" /* TODO: can be eliminated */ - "vpadd.s32 d1, d8, d9\n" /* TODO: can be eliminated */ - - "vst1.32 {d0, d1}, [%2, :128]\n" - : "+r" (in), "+r" (consts) - : "r" (out), - "i" (SBC_PROTO_FIXED4_SCALE) - : "memory", - "d0", "d1", "d2", "d3", "d4", "d5", - "d6", "d7", "d8", "d9", "d10", "d11"); -} - -static inline void _sbc_analyze_eight_neon(const int16_t *in, int32_t *out, - const FIXED_T *consts) -{ - /* TODO: merge even and odd cases (or even merge all four calls to this - * function) in order to have only aligned reads from 'in' array - * and reduce number of load instructions */ - asm volatile ( - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmull.s16 q6, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmull.s16 q7, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - "vmull.s16 q8, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmull.s16 q9, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q6, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmlal.s16 q7, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - "vmlal.s16 q8, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmlal.s16 q9, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q6, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmlal.s16 q7, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - "vmlal.s16 q8, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmlal.s16 q9, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q6, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmlal.s16 q7, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - "vmlal.s16 q8, d6, d10\n" - "vld1.16 {d4, d5}, [%0, :64]!\n" - "vmlal.s16 q9, d7, d11\n" - "vld1.16 {d8, d9}, [%1, :128]!\n" - - "vmlal.s16 q6, d4, d8\n" - "vld1.16 {d6, d7}, [%0, :64]!\n" - "vmlal.s16 q7, d5, d9\n" - "vld1.16 {d10, d11}, [%1, :128]!\n" - - "vmlal.s16 q8, d6, d10\n" - "vmlal.s16 q9, d7, d11\n" - - "vpadd.s32 d0, d12, d13\n" - "vpadd.s32 d1, d14, d15\n" - "vpadd.s32 d2, d16, d17\n" - "vpadd.s32 d3, d18, d19\n" - - "vrshr.s32 q0, q0, %3\n" - "vrshr.s32 q1, q1, %3\n" - "vmovn.s32 d0, q0\n" - "vmovn.s32 d1, q1\n" - - "vdup.i32 d3, d1[1]\n" /* TODO: can be eliminated */ - "vdup.i32 d2, d1[0]\n" /* TODO: can be eliminated */ - "vdup.i32 d1, d0[1]\n" /* TODO: can be eliminated */ - "vdup.i32 d0, d0[0]\n" /* TODO: can be eliminated */ - - "vld1.16 {d4, d5}, [%1, :128]!\n" - "vmull.s16 q6, d4, d0\n" - "vld1.16 {d6, d7}, [%1, :128]!\n" - "vmull.s16 q7, d5, d0\n" - "vmull.s16 q8, d6, d0\n" - "vmull.s16 q9, d7, d0\n" - - "vld1.16 {d4, d5}, [%1, :128]!\n" - "vmlal.s16 q6, d4, d1\n" - "vld1.16 {d6, d7}, [%1, :128]!\n" - "vmlal.s16 q7, d5, d1\n" - "vmlal.s16 q8, d6, d1\n" - "vmlal.s16 q9, d7, d1\n" - - "vld1.16 {d4, d5}, [%1, :128]!\n" - "vmlal.s16 q6, d4, d2\n" - "vld1.16 {d6, d7}, [%1, :128]!\n" - "vmlal.s16 q7, d5, d2\n" - "vmlal.s16 q8, d6, d2\n" - "vmlal.s16 q9, d7, d2\n" - - "vld1.16 {d4, d5}, [%1, :128]!\n" - "vmlal.s16 q6, d4, d3\n" - "vld1.16 {d6, d7}, [%1, :128]!\n" - "vmlal.s16 q7, d5, d3\n" - "vmlal.s16 q8, d6, d3\n" - "vmlal.s16 q9, d7, d3\n" - - "vpadd.s32 d0, d12, d13\n" /* TODO: can be eliminated */ - "vpadd.s32 d1, d14, d15\n" /* TODO: can be eliminated */ - "vpadd.s32 d2, d16, d17\n" /* TODO: can be eliminated */ - "vpadd.s32 d3, d18, d19\n" /* TODO: can be eliminated */ - - "vst1.32 {d0, d1, d2, d3}, [%2, :128]\n" - : "+r" (in), "+r" (consts) - : "r" (out), - "i" (SBC_PROTO_FIXED8_SCALE) - : "memory", - "d0", "d1", "d2", "d3", "d4", "d5", - "d6", "d7", "d8", "d9", "d10", "d11", - "d12", "d13", "d14", "d15", "d16", "d17", - "d18", "d19"); -} - -static inline void sbc_analyze_4b_4s_neon(int16_t *x, - int32_t *out, int out_stride) -{ - /* Analyze blocks */ - _sbc_analyze_four_neon(x + 12, out, analysis_consts_fixed4_simd_odd); - out += out_stride; - _sbc_analyze_four_neon(x + 8, out, analysis_consts_fixed4_simd_even); - out += out_stride; - _sbc_analyze_four_neon(x + 4, out, analysis_consts_fixed4_simd_odd); - out += out_stride; - _sbc_analyze_four_neon(x + 0, out, analysis_consts_fixed4_simd_even); -} - -static inline void sbc_analyze_4b_8s_neon(int16_t *x, - int32_t *out, int out_stride) -{ - /* Analyze blocks */ - _sbc_analyze_eight_neon(x + 24, out, analysis_consts_fixed8_simd_odd); - out += out_stride; - _sbc_analyze_eight_neon(x + 16, out, analysis_consts_fixed8_simd_even); - out += out_stride; - _sbc_analyze_eight_neon(x + 8, out, analysis_consts_fixed8_simd_odd); - out += out_stride; - _sbc_analyze_eight_neon(x + 0, out, analysis_consts_fixed8_simd_even); -} - -void sbc_init_primitives_neon(struct sbc_encoder_state *state) -{ - state->sbc_analyze_4b_4s = sbc_analyze_4b_4s_neon; - state->sbc_analyze_4b_8s = sbc_analyze_4b_8s_neon; - state->implementation_info = "NEON"; -} - -#endif diff --git a/src/modules/dbus/iface-card-profile.c b/src/modules/dbus/iface-card-profile.c new file mode 100644 index 00000000..004e2e88 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.c @@ -0,0 +1,228 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-card-profile.h" + +#define OBJECT_NAME "profile" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card_profile { + uint32_t index; + pa_card_profile *profile; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PRIORITY, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "u", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "u", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info profile_interface_info = { + .name = PA_DBUSIFACE_CARD_PROFILE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->description); +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sinks = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sinks); +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sources = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sources = p->profile->n_sources; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sources); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->profile->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t sinks = 0; + dbus_uint32_t sources = 0; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + sources = p->profile->n_sources; + priority = p->profile->priority; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->profile->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->profile->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_UINT32, &sinks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_UINT32, &sources); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx) { + pa_dbusiface_card_profile *p = NULL; + + pa_assert(card); + pa_assert(core); + pa_assert(profile); + + p = pa_xnew(pa_dbusiface_card_profile, 1); + p->index = idx; + p->profile = profile; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_card_get_path(card), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &profile_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, profile_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->profile->name; +} diff --git a/src/modules/dbus/iface-card-profile.h b/src/modules/dbus/iface-card-profile.h new file mode 100644 index 00000000..8ffb4b9c --- /dev/null +++ b/src/modules/dbus/iface-card-profile.h @@ -0,0 +1,49 @@ +#ifndef foodbusifacecardprofilehfoo +#define foodbusifacecardprofilehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.CardProfile. + * + * See http://pulseaudio.org/wiki/DBusInterface for the CardProfile interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> + +#include "iface-card.h" + +#define PA_DBUSIFACE_CARD_PROFILE_INTERFACE PA_DBUS_CORE_INTERFACE ".CardProfile" + +typedef struct pa_dbusiface_card_profile pa_dbusiface_card_profile; + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx); +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p); + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p); +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p); + +#endif diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c new file mode 100644 index 00000000..d99c8b95 --- /dev/null +++ b/src/modules/dbus/iface-card.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-card-profile.h" + +#include "iface-card.h" + +#define OBJECT_NAME "card" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card { + pa_dbusiface_core *core; + + pa_card *card; + char *path; + pa_hashmap *profiles; + uint32_t next_profile_index; + pa_card_profile *active_profile; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PROFILES, + PROPERTY_HANDLER_ACTIVE_PROFILE, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PROFILES] = { .property_name = "Profiles", .type = "ao", .get_cb = handle_get_profiles, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o", .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_GET_PROFILE_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_PROFILE_BY_NAME] = { + .method_name = "GetProfileByName", + .arguments = get_profile_by_name_args, + .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_profile_by_name } +}; + +enum signal_index { + SIGNAL_ACTIVE_PROFILE_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info active_profile_updated_args[] = { { "profile", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info card_interface_info = { + .name = PA_DBUSIFACE_CARD_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + dbus_uint32_t idx; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *owner_module; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->card->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) { + const char **sinks = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink *sink = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sinks); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink, c->card->sinks, idx) { + sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink); + ++i; + } + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sinks; + unsigned n_sinks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n_sinks); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + pa_xfree(sinks); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_card *c, unsigned *n) { + const char **sources = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source *source = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sources); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source, c->card->sinks, idx) { + sources[i] = pa_dbusiface_core_get_source_path(c->core, source); + ++i; + } + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sources; + unsigned n_sources; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n_sources); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + pa_xfree(sources); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) { + const char **profiles; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card_profile *profile; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->profiles); + + if (*n == 0) + return NULL; + + profiles = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(profile, c->profiles, state) + profiles[i++] = pa_dbusiface_card_profile_get_path(profile); + + return profiles; +} + +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **profiles; + unsigned n_profiles; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + profiles = get_profiles(c, &n_profiles); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + pa_xfree(profiles); +} + +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *active_profile; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", c->card->name); + return; + } + + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile); +} + +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *new_active_path; + pa_dbusiface_card_profile *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", + c->card->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path); + return; + } + + if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx; + const char *owner_module = NULL; + const char **sinks = NULL; + unsigned n_sinks = 0; + const char **sources = NULL; + unsigned n_sources = 0; + const char **profiles = NULL; + unsigned n_profiles = 0; + const char *active_profile = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + if (c->card->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + sinks = get_sinks(c, &n_sinks); + sources = get_sources(c, &n_sources); + profiles = get_profiles(c, &n_profiles); + if (c->active_profile) + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + if (active_profile) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(profiles); +} + +static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *profile_name = NULL; + pa_dbusiface_card_profile *profile = NULL; + const char *profile_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID)); + + if (!(profile = pa_hashmap_get(c->profiles, profile_name))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name); + return; + } + + profile_path = pa_dbusiface_card_profile_get_path(profile); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *signal_msg = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD); + pa_assert(c); + + /* We can't use idx != c->card->index, because the c->card pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->cards, idx) != c->card) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (c->active_profile != c->card->active_profile) { + const char *object_path; + + c->active_profile = c->card->active_profile; + object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_assert_se(signal_msg = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + if (!pa_proplist_equal(c->proplist, c->card->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) { + pa_dbusiface_card *c = NULL; + + pa_assert(core); + pa_assert(card); + + c = pa_xnew0(pa_dbusiface_card, 1); + c->core = core; + c->card = card; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index); + c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->next_profile_index = 0; + c->active_profile = NULL; + c->proplist = pa_proplist_copy(card->proplist); + c->dbus_protocol = pa_dbus_protocol_get(card->core); + c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c); + + if (card->profiles) { + pa_card_profile *profile; + void *state = NULL; + + PA_HASHMAP_FOREACH(profile, card->profiles, state) { + pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++); + pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p); + } + pa_assert_se(c->active_profile = card->active_profile); + } + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0); + + return c; +} + +static void profile_free_cb(void *p, void *userdata) { + pa_dbusiface_card_profile *profile = p; + + pa_assert(profile); + + pa_dbusiface_card_profile_free(profile); +} + +void pa_dbusiface_card_free(pa_dbusiface_card *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0); + + pa_hashmap_free(c->profiles, profile_free_cb, NULL); + pa_proplist_free(c->proplist); + pa_dbus_protocol_unref(c->dbus_protocol); + pa_subscription_free(c->subscription); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-card.h b/src/modules/dbus/iface-card.h new file mode 100644 index 00000000..e2c08a3b --- /dev/null +++ b/src/modules/dbus/iface-card.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacecardhfoo +#define foodbusifacecardhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Card. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Card interface + * documentation. + */ + +#include <pulsecore/card.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CARD_INTERFACE PA_DBUS_CORE_INTERFACE ".Card" + +typedef struct pa_dbusiface_card pa_dbusiface_card; + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card); +void pa_dbusiface_card_free(pa_dbusiface_card *c); + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c); + +#endif diff --git a/src/modules/dbus/iface-client.c b/src/modules/dbus/iface-client.c new file mode 100644 index 00000000..e6675449 --- /dev/null +++ b/src/modules/dbus/iface-client.c @@ -0,0 +1,461 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" + +#define OBJECT_NAME "client" + +struct pa_dbusiface_client { + pa_dbusiface_core *core; + + pa_client *client; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_KILL, + METHOD_HANDLER_UPDATE_PROPERTIES, + METHOD_HANDLER_REMOVE_PROPERTIES, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } }; +static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_KILL] = { + .method_name = "Kill", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_kill }, + [METHOD_HANDLER_UPDATE_PROPERTIES] = { + .method_name = "UpdateProperties", + .arguments = update_properties_args, + .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_update_properties }, + [METHOD_HANDLER_REMOVE_PROPERTIES] = { + .method_name = "RemoveProperties", + .arguments = remove_properties_args, + .n_arguments = sizeof(remove_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_remove_properties } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_CLIENT_EVENT, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; +static pa_dbus_arg_info client_event_args[] = { { "name", "s", NULL }, + { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }, + /* ClientEvent is sent from module-dbus-protocol.c. */ + [SIGNAL_CLIENT_EVENT] = { .name = "ClientEvent", .arguments = client_event_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info client_interface_info = { + .name = PA_DBUSIFACE_CLIENT_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char *owner_module = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->client->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) { + const char **playback_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink_input *sink_input = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->sink_inputs); + + if (*n == 0) + return NULL; + + playback_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx) + playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input); + + return playback_streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n_playback_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) { + const char **record_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source_output *source_output = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->source_outputs); + + if (*n == 0) + return NULL; + + record_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx) + record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output); + + return record_streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n_record_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + + pa_xfree(record_streams); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *owner_module = NULL; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + if (c->client->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(playback_streams); + pa_xfree(record_streams); +} + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + dbus_connection_ref(conn); + + pa_client_kill(c->client); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_connection_unref(conn); +} + +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessageIter msg_iter; + pa_proplist *property_list = NULL; + dbus_uint32_t update_mode = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_get_basic(&msg_iter, &update_mode); + + if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode); + goto finish; + } + + pa_client_update_proplist(c->client, update_mode, property_list); + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + char **keys = NULL; + int n_keys = 0; + pa_bool_t changed = FALSE; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID)); + + for (i = 0; i < n_keys; ++i) + changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0; + + pa_dbus_send_empty_reply(conn, msg); + + if (changed) + pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + + dbus_free_string_array(keys); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *signal_msg = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT); + pa_assert(c); + + /* We can't use idx != c->client->index, because the c->client pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->clients, idx) != c->client) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(c->proplist, c->client->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CLIENT_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) { + pa_dbusiface_client *c = NULL; + + pa_assert(core); + pa_assert(client); + + c = pa_xnew(pa_dbusiface_client, 1); + c->core = core; + c->client = client; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index); + c->proplist = pa_proplist_copy(client->proplist); + c->dbus_protocol = pa_dbus_protocol_get(client->core); + c->subscription = pa_subscription_new(client->core, PA_SUBSCRIPTION_MASK_CLIENT, subscription_cb, c); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0); + + return c; +} + +void pa_dbusiface_client_free(pa_dbusiface_client *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0); + + pa_proplist_free(c->proplist); + pa_dbus_protocol_unref(c->dbus_protocol); + pa_subscription_free(c->subscription); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-client.h b/src/modules/dbus/iface-client.h new file mode 100644 index 00000000..e8f151cd --- /dev/null +++ b/src/modules/dbus/iface-client.h @@ -0,0 +1,45 @@ +#ifndef foodbusifaceclienthfoo +#define foodbusifaceclienthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Client. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Client interface + * documentation. + */ + +#include <pulsecore/client.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CLIENT_INTERFACE PA_DBUS_CORE_INTERFACE ".Client" + +typedef struct pa_dbusiface_client pa_dbusiface_client; + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client); +void pa_dbusiface_client_free(pa_dbusiface_client *c); + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c); + +#endif diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c new file mode 100644 index 00000000..bb43df9b --- /dev/null +++ b/src/modules/dbus/iface-core.c @@ -0,0 +1,2206 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> + +#include <dbus/dbus.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/strbuf.h> + +#include "iface-card.h" +#include "iface-client.h" +#include "iface-device.h" +#include "iface-memstats.h" +#include "iface-module.h" +#include "iface-sample.h" +#include "iface-stream.h" + +#include "iface-core.h" + +#define INTERFACE_REVISION 0 + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_core { + pa_core *core; + pa_subscription *subscription; + + pa_dbus_protocol *dbus_protocol; + + pa_hashmap *cards; + pa_hashmap *sinks_by_index; + pa_hashmap *sinks_by_path; + pa_hashmap *sources_by_index; + pa_hashmap *sources_by_path; + pa_hashmap *playback_streams; + pa_hashmap *record_streams; + pa_hashmap *samples; + pa_hashmap *modules; + pa_hashmap *clients; + + pa_sink *fallback_sink; + pa_source *fallback_source; + + pa_hook_slot *sink_put_slot; + pa_hook_slot *sink_unlink_slot; + pa_hook_slot *source_put_slot; + pa_hook_slot *source_unlink_slot; + pa_hook_slot *extension_registered_slot; + pa_hook_slot *extension_unregistered_slot; + + pa_dbusiface_memstats *memstats; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INTERFACE_REVISION, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_VERSION, + PROPERTY_HANDLER_IS_LOCAL, + PROPERTY_HANDLER_USERNAME, + PROPERTY_HANDLER_HOSTNAME, + PROPERTY_HANDLER_DEFAULT_CHANNELS, + PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT, + PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE, + PROPERTY_HANDLER_CARDS, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_FALLBACK_SINK, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_FALLBACK_SOURCE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + PROPERTY_HANDLER_SAMPLES, + PROPERTY_HANDLER_MODULES, + PROPERTY_HANDLER_CLIENTS, + PROPERTY_HANDLER_MY_CLIENT, + PROPERTY_HANDLER_EXTENSIONS, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_VERSION] = { .property_name = "Version", .type = "s", .get_cb = handle_get_version, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_LOCAL] = { .property_name = "IsLocal", .type = "b", .get_cb = handle_get_is_local, .set_cb = NULL }, + [PROPERTY_HANDLER_USERNAME] = { .property_name = "Username", .type = "s", .get_cb = handle_get_username, .set_cb = NULL }, + [PROPERTY_HANDLER_HOSTNAME] = { .property_name = "Hostname", .type = "s", .get_cb = handle_get_hostname, .set_cb = NULL }, + [PROPERTY_HANDLER_DEFAULT_CHANNELS] = { .property_name = "DefaultChannels", .type = "au", .get_cb = handle_get_default_channels, .set_cb = handle_set_default_channels }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT] = { .property_name = "DefaultSampleFormat", .type = "u", .get_cb = handle_get_default_sample_format, .set_cb = handle_set_default_sample_format }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE] = { .property_name = "DefaultSampleRate", .type = "u", .get_cb = handle_get_default_sample_rate, .set_cb = handle_set_default_sample_rate }, + [PROPERTY_HANDLER_CARDS] = { .property_name = "Cards", .type = "ao", .get_cb = handle_get_cards, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SINK] = { .property_name = "FallbackSink", .type = "o", .get_cb = handle_get_fallback_sink, .set_cb = handle_set_fallback_sink }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SOURCE] = { .property_name = "FallbackSource", .type = "o", .get_cb = handle_get_fallback_source, .set_cb = handle_set_fallback_source }, + [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLES] = { .property_name = "Samples", .type = "ao", .get_cb = handle_get_samples, .set_cb = NULL }, + [PROPERTY_HANDLER_MODULES] = { .property_name = "Modules", .type = "ao", .get_cb = handle_get_modules, .set_cb = NULL }, + [PROPERTY_HANDLER_CLIENTS] = { .property_name = "Clients", .type = "ao", .get_cb = handle_get_clients, .set_cb = NULL }, + [PROPERTY_HANDLER_MY_CLIENT] = { .property_name = "MyClient", .type = "o", .get_cb = handle_get_my_client, .set_cb = NULL }, + [PROPERTY_HANDLER_EXTENSIONS] = { .property_name = "Extensions", .type = "as", .get_cb = handle_get_extensions, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_GET_CARD_BY_NAME, + METHOD_HANDLER_GET_SINK_BY_NAME, + METHOD_HANDLER_GET_SOURCE_BY_NAME, + METHOD_HANDLER_GET_SAMPLE_BY_NAME, + METHOD_HANDLER_UPLOAD_SAMPLE, + METHOD_HANDLER_LOAD_MODULE, + METHOD_HANDLER_EXIT, + METHOD_HANDLER_LISTEN_FOR_SIGNAL, + METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_card_by_name_args[] = { { "name", "s", "in" }, { "card", "o", "out" } }; +static pa_dbus_arg_info get_sink_by_name_args[] = { { "name", "s", "in" }, { "sink", "o", "out" } }; +static pa_dbus_arg_info get_source_by_name_args[] = { { "name", "s", "in" }, { "source", "o", "out" } }; +static pa_dbus_arg_info get_sample_by_name_args[] = { { "name", "s", "in" }, { "sample", "o", "out" } }; +static pa_dbus_arg_info upload_sample_args[] = { { "name", "s", "in" }, + { "sample_format", "u", "in" }, + { "sample_rate", "u", "in" }, + { "channels", "au", "in" }, + { "default_volume", "au", "in" }, + { "property_list", "a{say}", "in" }, + { "data", "ay", "in" }, + { "sample", "o", "out" } }; +static pa_dbus_arg_info load_module_args[] = { { "name", "s", "in" }, { "arguments", "a{ss}", "in" }, { "module", "o", "out" } }; +static pa_dbus_arg_info listen_for_signal_args[] = { { "signal", "s", "in" }, { "objects", "ao", "in" } }; +static pa_dbus_arg_info stop_listening_for_signal_args[] = { { "signal", "s", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_CARD_BY_NAME] = { + .method_name = "GetCardByName", + .arguments = get_card_by_name_args, + .n_arguments = sizeof(get_card_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_card_by_name }, + [METHOD_HANDLER_GET_SINK_BY_NAME] = { + .method_name = "GetSinkByName", + .arguments = get_sink_by_name_args, + .n_arguments = sizeof(get_sink_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sink_by_name }, + [METHOD_HANDLER_GET_SOURCE_BY_NAME] = { + .method_name = "GetSourceByName", + .arguments = get_source_by_name_args, + .n_arguments = sizeof(get_source_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_source_by_name }, + [METHOD_HANDLER_GET_SAMPLE_BY_NAME] = { + .method_name = "GetSampleByName", + .arguments = get_sample_by_name_args, + .n_arguments = sizeof(get_sample_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sample_by_name }, + [METHOD_HANDLER_UPLOAD_SAMPLE] = { + .method_name = "UploadSample", + .arguments = upload_sample_args, + .n_arguments = sizeof(upload_sample_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_upload_sample }, + [METHOD_HANDLER_LOAD_MODULE] = { + .method_name = "LoadModule", + .arguments = load_module_args, + .n_arguments = sizeof(load_module_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_load_module }, + [METHOD_HANDLER_EXIT] = { + .method_name = "Exit", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_exit }, + [METHOD_HANDLER_LISTEN_FOR_SIGNAL] = { + .method_name = "ListenForSignal", + .arguments = listen_for_signal_args, + .n_arguments = sizeof(listen_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_listen_for_signal }, + [METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL] = { + .method_name = "StopListeningForSignal", + .arguments = stop_listening_for_signal_args, + .n_arguments = sizeof(stop_listening_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_stop_listening_for_signal } +}; + +enum signal_index { + SIGNAL_NEW_CARD, + SIGNAL_CARD_REMOVED, + SIGNAL_NEW_SINK, + SIGNAL_SINK_REMOVED, + SIGNAL_FALLBACK_SINK_UPDATED, + SIGNAL_FALLBACK_SINK_UNSET, + SIGNAL_NEW_SOURCE, + SIGNAL_SOURCE_REMOVED, + SIGNAL_FALLBACK_SOURCE_UPDATED, + SIGNAL_FALLBACK_SOURCE_UNSET, + SIGNAL_NEW_PLAYBACK_STREAM, + SIGNAL_PLAYBACK_STREAM_REMOVED, + SIGNAL_NEW_RECORD_STREAM, + SIGNAL_RECORD_STREAM_REMOVED, + SIGNAL_NEW_SAMPLE, + SIGNAL_SAMPLE_REMOVED, + SIGNAL_NEW_MODULE, + SIGNAL_MODULE_REMOVED, + SIGNAL_NEW_CLIENT, + SIGNAL_CLIENT_REMOVED, + SIGNAL_NEW_EXTENSION, + SIGNAL_EXTENSION_REMOVED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info new_card_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info card_removed_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info new_sink_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info sink_removed_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info fallback_sink_updated_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info new_source_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info source_removed_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info fallback_source_updated_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info new_playback_stream_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info playback_stream_removed_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info new_record_stream_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info record_stream_removed_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info new_sample_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info sample_removed_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info new_module_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info module_removed_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info new_client_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info client_removed_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info new_extension_args[] = { { "extension", "s", NULL } }; +static pa_dbus_arg_info extension_removed_args[] = { { "extension", "s", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_NEW_CARD] = { .name = "NewCard", .arguments = new_card_args, .n_arguments = 1 }, + [SIGNAL_CARD_REMOVED] = { .name = "CardRemoved", .arguments = card_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SINK] = { .name = "NewSink", .arguments = new_sink_args, .n_arguments = 1 }, + [SIGNAL_SINK_REMOVED] = { .name = "SinkRemoved", .arguments = sink_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UPDATED] = { .name = "FallbackSinkUpdated", .arguments = fallback_sink_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UNSET] = { .name = "FallbackSinkUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_SOURCE] = { .name = "NewSource", .arguments = new_source_args, .n_arguments = 1 }, + [SIGNAL_SOURCE_REMOVED] = { .name = "SourceRemoved", .arguments = source_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UPDATED] = { .name = "FallbackSourceUpdated", .arguments = fallback_source_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UNSET] = { .name = "FallbackSourceUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_PLAYBACK_STREAM] = { .name = "NewPlaybackStream", .arguments = new_playback_stream_args, .n_arguments = 1 }, + [SIGNAL_PLAYBACK_STREAM_REMOVED] = { .name = "PlaybackStreamRemoved", .arguments = playback_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_RECORD_STREAM] = { .name = "NewRecordStream", .arguments = new_record_stream_args, .n_arguments = 1 }, + [SIGNAL_RECORD_STREAM_REMOVED] = { .name = "RecordStreamRemoved", .arguments = record_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SAMPLE] = { .name = "NewSample", .arguments = new_sample_args, .n_arguments = 1 }, + [SIGNAL_SAMPLE_REMOVED] = { .name = "SampleRemoved", .arguments = sample_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_MODULE] = { .name = "NewModule", .arguments = new_module_args, .n_arguments = 1 }, + [SIGNAL_MODULE_REMOVED] = { .name = "ModuleRemoved", .arguments = module_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_CLIENT] = { .name = "NewClient", .arguments = new_client_args, .n_arguments = 1 }, + [SIGNAL_CLIENT_REMOVED] = { .name = "ClientRemoved", .arguments = client_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_EXTENSION] = { .name = "NewExtension", .arguments = new_extension_args, .n_arguments = 1 }, + [SIGNAL_EXTENSION_REMOVED] = { .name = "ExtensionRemoved", .arguments = extension_removed_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info core_interface_info = { + .name = PA_DBUS_CORE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_uint32_t interface_revision = INTERFACE_REVISION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *server_name = PACKAGE_NAME; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &server_name); +} + +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *version = PACKAGE_VERSION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &version); +} + +static dbus_bool_t get_is_local(DBusConnection *conn) { + int conn_fd; + + pa_assert(conn); + + if (!dbus_connection_get_socket(conn, &conn_fd)) + return FALSE; + + return pa_socket_is_local(conn_fd); +} + +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_bool_t is_local; + + pa_assert(conn); + pa_assert(msg); + + is_local = get_is_local(conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_local); +} + +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *username = NULL; + + pa_assert(conn); + pa_assert(msg); + + username = pa_get_user_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &username); + + pa_xfree(username); +} + +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *hostname = NULL; + + pa_assert(conn); + pa_assert(msg); + + hostname = pa_get_host_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &hostname); + + pa_xfree(hostname); +} + +/* Caller frees the returned array. */ +static dbus_uint32_t *get_default_channels(pa_dbusiface_core *c, unsigned *n) { + dbus_uint32_t *default_channels = NULL; + unsigned i; + + pa_assert(c); + pa_assert(n); + + *n = c->core->default_channel_map.channels; + default_channels = pa_xnew(dbus_uint32_t, *n); + + for (i = 0; i < *n; ++i) + default_channels[i] = c->core->default_channel_map.map[i]; + + return default_channels; +} + +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t *default_channels = NULL; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_channels = get_default_channels(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_channels, n); + + pa_xfree(default_channels); +} + +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter array_iter; + pa_channel_map new_channel_map; + const dbus_uint32_t *default_channels; + int n_channels; + unsigned i; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + pa_channel_map_init(&new_channel_map); + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_channels, &n_channels); + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel array."); + return; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum number of channels is %u.", n_channels, PA_CHANNELS_MAX); + return; + } + + new_channel_map.channels = n_channels; + + for (i = 0; i < new_channel_map.channels; ++i) { + if (default_channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u.", default_channels[i]); + return; + } + + new_channel_map.map[i] = default_channels[i]; + } + + c->core->default_channel_map = new_channel_map; + c->core->default_sample_spec.channels = n_channels; + + pa_dbus_send_empty_reply(conn, msg); +}; + +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_format = c->core->default_sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_format); +} + +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_format); + + if (default_sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + return; + } + + c->core->default_sample_spec.format = default_sample_format; + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_rate = c->core->default_sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_rate); +} + +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_rate); + + if (default_sample_rate <= 0 || default_sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + return; + } + + c->core->default_sample_spec.rate = default_sample_rate; + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_cards(pa_dbusiface_core *c, unsigned *n) { + const char **cards; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card *card; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->cards); + + if (*n == 0) + return NULL; + + cards = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(card, c->cards, state) + cards[i++] = pa_dbusiface_card_get_path(card); + + return cards; +} + +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **cards; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + cards = get_cards(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, cards, n); + + pa_xfree(cards); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_core *c, unsigned *n) { + const char **sinks; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *sink; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sinks_by_index); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sink, c->sinks_by_index, state) + sinks[i++] = pa_dbusiface_device_get_path(sink); + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sinks; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n); + + pa_xfree(sinks); +} + +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + pa_assert_se((fallback_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))); + object_path = pa_dbusiface_device_get_path(fallback_sink); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_sink = pa_hashmap_get(c->sinks_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", object_path); + return; + } + + pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_core *c, unsigned *n) { + const char **sources; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *source; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sources_by_index); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(source, c->sources_by_index, state) + sources[i++] = pa_dbusiface_device_get_path(source); + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sources; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n); + + pa_xfree(sources); +} + +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + pa_assert_se((fallback_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index)))); + object_path = pa_dbusiface_device_get_path(fallback_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_source = pa_hashmap_get(c->sources_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", object_path); + return; + } + + pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->playback_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->playback_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **playback_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->record_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->record_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **record_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n); + + pa_xfree(record_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_samples(pa_dbusiface_core *c, unsigned *n) { + const char **samples; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_sample *sample; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->samples); + + if (*n == 0) + return NULL; + + samples = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sample, c->samples, state) + samples[i++] = pa_dbusiface_sample_get_path(sample); + + return samples; +} + +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **samples; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + samples = get_samples(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, samples, n); + + pa_xfree(samples); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_modules(pa_dbusiface_core *c, unsigned *n) { + const char **modules; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_module *module; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->modules); + + if (*n == 0) + return NULL; + + modules = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(module, c->modules, state) + modules[i++] = pa_dbusiface_module_get_path(module); + + return modules; +} + +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **modules; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + modules = get_modules(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, modules, n); + + pa_xfree(modules); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_clients(pa_dbusiface_core *c, unsigned *n) { + const char **clients; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_client *client; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->clients); + + if (*n == 0) + return NULL; + + clients = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(client, c->clients, state) + clients[i++] = pa_dbusiface_client_get_path(client); + + return clients; +} + +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **clients; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + clients = get_clients(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, clients, n); + + pa_xfree(clients); +} + +static const char *get_my_client(pa_dbusiface_core *c, DBusConnection *conn) { + pa_client *my_client; + + pa_assert(c); + pa_assert(conn); + + pa_assert_se((my_client = pa_dbus_protocol_get_client(c->dbus_protocol, conn))); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(my_client->index))); +} + +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *my_client; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + my_client = get_my_client(c, conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &my_client); +} + +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **extensions; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, extensions, n); + + pa_xfree(extensions); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t interface_revision; + const char *server_name; + const char *version; + dbus_bool_t is_local; + char *username; + char *hostname; + dbus_uint32_t *default_channels; + unsigned n_default_channels; + dbus_uint32_t default_sample_format; + dbus_uint32_t default_sample_rate; + const char **cards; + unsigned n_cards; + const char **sinks; + unsigned n_sinks; + const char *fallback_sink; + const char **sources; + unsigned n_sources; + const char *fallback_source; + const char **playback_streams; + unsigned n_playback_streams; + const char **record_streams; + unsigned n_record_streams; + const char **samples; + unsigned n_samples; + const char **modules; + unsigned n_modules; + const char **clients; + unsigned n_clients; + const char *my_client; + const char **extensions; + unsigned n_extensions; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + interface_revision = INTERFACE_REVISION; + server_name = PACKAGE_NAME; + version = PACKAGE_VERSION; + is_local = get_is_local(conn); + username = pa_get_user_name_malloc(); + hostname = pa_get_host_name_malloc(); + default_channels = get_default_channels(c, &n_default_channels); + default_sample_format = c->core->default_sample_spec.format; + default_sample_rate = c->core->default_sample_spec.rate; + cards = get_cards(c, &n_cards); + sinks = get_sinks(c, &n_sinks); + fallback_sink = c->fallback_sink + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))) + : NULL; + sources = get_sources(c, &n_sources); + fallback_source = c->fallback_source + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, + PA_UINT32_TO_PTR(c->fallback_source->index))) + : NULL; + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + samples = get_samples(c, &n_samples); + modules = get_modules(c, &n_modules); + clients = get_clients(c, &n_clients); + my_client = get_my_client(c, conn); + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n_extensions); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &server_name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VERSION].property_name, DBUS_TYPE_STRING, &version); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_LOCAL].property_name, DBUS_TYPE_BOOLEAN, &is_local); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_USERNAME].property_name, DBUS_TYPE_STRING, &username); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HOSTNAME].property_name, DBUS_TYPE_STRING, &hostname); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_CHANNELS].property_name, DBUS_TYPE_UINT32, default_channels, n_default_channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &default_sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &default_sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARDS].property_name, DBUS_TYPE_OBJECT_PATH, cards, n_cards); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + if (fallback_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_sink); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + if (fallback_source) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_source); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLES].property_name, DBUS_TYPE_OBJECT_PATH, samples, n_samples); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MODULES].property_name, DBUS_TYPE_OBJECT_PATH, modules, n_modules); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENTS].property_name, DBUS_TYPE_OBJECT_PATH, clients, n_clients); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MY_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &my_client); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_EXTENSIONS].property_name, DBUS_TYPE_STRING, extensions, n_extensions); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(username); + pa_xfree(hostname); + pa_xfree(default_channels); + pa_xfree(cards); + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(playback_streams); + pa_xfree(record_streams); + pa_xfree(samples); + pa_xfree(modules); + pa_xfree(clients); + pa_xfree(extensions); +} + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *card_name; + pa_card *card; + pa_dbusiface_card *dbus_card; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &card_name, DBUS_TYPE_INVALID)); + + if (!(card = pa_namereg_get(c->core, card_name, PA_NAMEREG_CARD))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such card."); + return; + } + + pa_assert_se((dbus_card = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index)))); + + object_path = pa_dbusiface_card_get_path(dbus_card); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sink_name; + pa_sink *sink; + pa_dbusiface_device *dbus_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sink_name, DBUS_TYPE_INVALID)); + + if (!(sink = pa_namereg_get(c->core, sink_name, PA_NAMEREG_SINK))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_name); + return; + } + + pa_assert_se((dbus_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_sink); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *source_name; + pa_source *source; + pa_dbusiface_device *dbus_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &source_name, DBUS_TYPE_INVALID)); + + if (!(source = pa_namereg_get(c->core, source_name, PA_NAMEREG_SOURCE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", source_name); + return; + } + + pa_assert_se((dbus_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_source); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sample_name; + pa_scache_entry *sample; + pa_dbusiface_sample *dbus_sample; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sample_name, DBUS_TYPE_INVALID)); + + if (!(sample = pa_namereg_get(c->core, sample_name, PA_NAMEREG_SAMPLE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such sample."); + return; + } + + pa_assert_se((dbus_sample = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(sample->index)))); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter array_iter; + const char *name; + dbus_uint32_t sample_format; + dbus_uint32_t sample_rate; + const dbus_uint32_t *channels; + int n_channels; + const dbus_uint32_t *default_volume; + int n_volume_entries; + pa_proplist *property_list; + const uint8_t *data; + int data_length; + int i; + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk chunk; + uint32_t idx; + pa_dbusiface_sample *dbus_sample = NULL; + pa_scache_entry *sample = NULL; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + chunk.memblock = NULL; + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_format); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_rate); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &channels, &n_channels); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_volume, &n_volume_entries); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &data, &data_length); + + if (sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + goto finish; + } + + if (sample_rate <= 0 || sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + goto finish; + } + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel map."); + goto finish; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum is %u.", n_channels, PA_CHANNELS_MAX); + goto finish; + } + + for (i = 0; i < n_channels; ++i) { + if (channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position."); + goto finish; + } + } + + if (n_volume_entries != 0 && n_volume_entries != n_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The channels and default_volume arguments have different number of elements (%i and %i, resp).", + n_channels, n_volume_entries); + goto finish; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (!PA_VOLUME_IS_VALID(default_volume[i])) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u.", default_volume[i]); + goto finish; + } + } + + if (data_length == 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty data."); + goto finish; + } + + if (data_length > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too big sample: %i bytes. The maximum sample length is %u bytes.", + data_length, PA_SCACHE_ENTRY_SIZE_MAX); + goto finish; + } + + ss.format = sample_format; + ss.rate = sample_rate; + ss.channels = n_channels; + + pa_assert(pa_sample_spec_valid(&ss)); + + if (!pa_frame_aligned(data_length, &ss)) { + char buf[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The sample length (%i bytes) doesn't align with the sample format and channels (%s).", + data_length, pa_sample_spec_snprint(buf, sizeof(buf), &ss)); + goto finish; + } + + map.channels = n_channels; + for (i = 0; i < n_channels; ++i) + map.map[i] = channels[i]; + + chunk.memblock = pa_memblock_new(c->core->mempool, data_length); + chunk.index = 0; + chunk.length = data_length; + + memcpy(pa_memblock_acquire(chunk.memblock), data, data_length); + pa_memblock_release(chunk.memblock); + + if (pa_scache_add_item(c->core, name, &ss, &map, &chunk, property_list, &idx) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Adding the sample failed."); + goto finish; + } + + sample = pa_idxset_get_by_index(c->core->scache, idx); + + if (n_volume_entries > 0) { + sample->volume.channels = n_channels; + for (i = 0; i < n_volume_entries; ++i) + sample->volume.values[i] = default_volume[i]; + sample->volume_is_set = TRUE; + } else { + sample->volume_is_set = FALSE; + } + + dbus_sample = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), dbus_sample); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (property_list) + pa_proplist_free(property_list); + + if (chunk.memblock) + pa_memblock_unref(chunk.memblock); +} + +static pa_bool_t contains_space(const char *string) { + const char *p; + + pa_assert(string); + + for (p = string; *p; ++p) { + if (isspace(*p)) + return TRUE; + } + + return FALSE; +} + +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + char *name = NULL; + const char *key = NULL; + const char *value = NULL; + char *escaped_value = NULL; + pa_strbuf *arg_buffer = NULL; + char *arg_string = NULL; + pa_module *module = NULL; + pa_dbusiface_module *dbus_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module loading."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + arg_buffer = pa_strbuf_new(); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + if (!pa_strbuf_isempty(arg_buffer)) + pa_strbuf_putc(arg_buffer, ' '); + + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + + if (strlen(key) <= 0 || !pa_ascii_valid(key) || contains_space(key)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid module argument name: %s", key); + goto finish; + } + + pa_assert_se(dbus_message_iter_next(&dict_entry_iter)); + dbus_message_iter_get_basic(&dict_entry_iter, &value); + + escaped_value = pa_escape(value, "\""); + pa_strbuf_printf(arg_buffer, "%s=\"%s\"", key, escaped_value); + pa_xfree(escaped_value); + + dbus_message_iter_next(&dict_iter); + } + + arg_string = pa_strbuf_tostring(arg_buffer); + + if (!(module = pa_module_load(c->core, name, arg_string))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module."); + goto finish; + } + + dbus_module = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(module->index), dbus_module); + + object_path = pa_dbusiface_module_get_path(dbus_module); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (arg_buffer) + pa_strbuf_free(arg_buffer); + + pa_xfree(arg_string); +} + +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_exit) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow exiting."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_core_exit(c->core, FALSE, 0); +} + +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal_str; + char **objects = NULL; + int n_objects; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &signal_str, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, &n_objects, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_add_signal_listener(c->dbus_protocol, conn, *signal_str ? signal_str : NULL, objects, n_objects); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_free_string_array(objects); +} + +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal_str; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &signal_str, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_remove_signal_listener(c->dbus_protocol, conn, *signal_str ? signal_str : NULL); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_card *card_iface = NULL; + pa_dbusiface_device *device_iface = NULL; + pa_dbusiface_stream *stream_iface = NULL; + pa_dbusiface_sample *sample_iface = NULL; + pa_dbusiface_module *module_iface = NULL; + pa_dbusiface_client *client_iface = NULL; + DBusMessage *signal_msg = NULL; + const char *object_path = NULL; + pa_sink *new_fallback_sink = NULL; + pa_source *new_fallback_source = NULL; + + pa_assert(c); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + new_fallback_sink = pa_namereg_get_default_sink(core); + new_fallback_source = pa_namereg_get_default_source(core); + + if (c->fallback_sink != new_fallback_sink) { + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL; + + if (c->fallback_sink) { + pa_assert_se(device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))); + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + + } else { + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } + + if (c->fallback_source != new_fallback_source) { + if (c->fallback_source) + pa_source_unref(c->fallback_source); + c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL; + + if (c->fallback_source) { + pa_assert_se(device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index))); + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + + } else { + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + if (!(card_iface = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(idx)))) { + pa_card *card = NULL; + + if (!(card = pa_idxset_get_by_index(core->cards, idx))) + return; /* The card was removed immediately after creation. */ + + card_iface = pa_dbusiface_card_new(c, card); + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), card_iface); + } + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CARD].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(card_iface = pa_hashmap_remove(c->cards, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CARD_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_card_free(card_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_sink_input *sink_input = NULL; + + if (!(sink_input = pa_idxset_get_by_index(core->sink_inputs, idx))) + return; /* The sink input was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_playback(c, sink_input); + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_PLAYBACK_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->playback_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_PLAYBACK_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_source_output *source_output = NULL; + + if (!(source_output = pa_idxset_get_by_index(core->source_outputs, idx))) + return; /* The source output was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_record(c, source_output); + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_RECORD_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->record_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_RECORD_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_scache_entry *sample = NULL; + + if (!(sample = pa_idxset_get_by_index(core->scache, idx))) + return; /* The sample was removed immediately after creation. */ + + if (!(sample_iface = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(idx)))) { + sample_iface = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), sample_iface); + } + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SAMPLE].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(sample_iface = pa_hashmap_remove(c->samples, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SAMPLE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_sample_free(sample_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_MODULE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_module *module = NULL; + + if (!(module = pa_idxset_get_by_index(core->modules, idx))) + return; /* The module was removed immediately after creation. */ + + if (!(module_iface = pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(idx)))) { + module_iface = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), module_iface); + } + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_MODULE].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(module_iface = pa_hashmap_remove(c->modules, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_MODULE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_module_free(module_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_client *client = NULL; + + if (!(client = pa_idxset_get_by_index(core->clients, idx))) + return; /* The client was removed immediately after creation. */ + + if (!(client_iface = pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(idx)))) { + client_iface = pa_dbusiface_client_new(c, client); + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), client_iface); + } + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CLIENT].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(client_iface = pa_hashmap_remove(c->clients, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CLIENT_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_client_free(client_iface); + } + break; + } + + if (signal_msg) { + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + } +} + +static pa_hook_result_t sink_put_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_sink *s = call_data; + pa_dbusiface_device *d = NULL; + const char *object_path = NULL; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + + d = pa_dbusiface_device_new_sink(c, s); + object_path = pa_dbusiface_device_get_path(d); + + pa_assert_se(pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); + pa_assert_se(pa_hashmap_put(c->sinks_by_path, object_path, d) >= 0); + + pa_assert_se(signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SINK].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_sink *s = call_data; + pa_dbusiface_device *d = NULL; + const char *object_path = NULL; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + + pa_assert_se(d = pa_hashmap_remove(c->sinks_by_index, PA_UINT32_TO_PTR(s->index))); + object_path = pa_dbusiface_device_get_path(d); + pa_assert_se(pa_hashmap_remove(c->sinks_by_path, object_path)); + + pa_assert_se(signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SINK_REMOVED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + pa_dbusiface_device_free(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_source *s = call_data; + pa_dbusiface_device *d = NULL; + const char *object_path = NULL; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + + d = pa_dbusiface_device_new_source(c, s); + object_path = pa_dbusiface_device_get_path(d); + + pa_assert_se(pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(s->index), d) >= 0); + pa_assert_se(pa_hashmap_put(c->sources_by_path, object_path, d) >= 0); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SOURCE].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + pa_source *s = call_data; + pa_dbusiface_device *d = NULL; + const char *object_path = NULL; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + + pa_assert_se(d = pa_hashmap_remove(c->sources_by_index, PA_UINT32_TO_PTR(s->index))); + object_path = pa_dbusiface_device_get_path(d); + pa_assert_se(pa_hashmap_remove(c->sources_by_path, object_path)); + + pa_assert_se(signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SOURCE_REMOVED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + pa_dbusiface_device_free(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t extension_registered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_EXTENSION].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + return PA_HOOK_OK; +} + +static pa_hook_result_t extension_unregistered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal_msg = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_EXTENSION_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + return PA_HOOK_OK; +} + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) { + pa_dbusiface_core *c; + pa_card *card; + pa_sink *sink; + pa_source *source; + pa_dbusiface_device *device; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_scache_entry *sample; + pa_module *module; + pa_client *client; + uint32_t idx; + + pa_assert(core); + + c = pa_xnew(pa_dbusiface_core, 1); + c->core = core; + c->subscription = pa_subscription_new(core, PA_SUBSCRIPTION_MASK_ALL, subscription_cb, c); + c->dbus_protocol = pa_dbus_protocol_get(core); + c->cards = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->sources_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sources_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->samples = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->modules = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->fallback_sink = pa_namereg_get_default_sink(core); + c->fallback_source = pa_namereg_get_default_source(core); + c->sink_put_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, sink_put_cb, c); + c->sink_unlink_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_NORMAL, sink_unlink_cb, c); + c->source_put_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, source_put_cb, c); + c->source_unlink_slot = pa_hook_connect(&core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_NORMAL, source_unlink_cb, c); + c->extension_registered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, + PA_HOOK_NORMAL, + extension_registered_cb, + c); + c->extension_unregistered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, + PA_HOOK_NORMAL, + extension_unregistered_cb, + c); + c->memstats = pa_dbusiface_memstats_new(c, core); + + if (c->fallback_sink) + pa_sink_ref(c->fallback_sink); + if (c->fallback_source) + pa_source_ref(c->fallback_source); + + PA_IDXSET_FOREACH(card, core->cards, idx) + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), pa_dbusiface_card_new(c, card)); + + PA_IDXSET_FOREACH(sink, core->sinks, idx) { + device = pa_dbusiface_device_new_sink(c, sink); + pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(source, core->sources, idx) { + device = pa_dbusiface_device_new_source(c, source); + pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(sink_input, core->sink_inputs, idx) + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_playback(c, sink_input)); + + PA_IDXSET_FOREACH(source_output, core->source_outputs, idx) + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_record(c, source_output)); + + PA_IDXSET_FOREACH(sample, core->scache, idx) + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), pa_dbusiface_sample_new(c, sample)); + + PA_IDXSET_FOREACH(module, core->modules, idx) + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), pa_dbusiface_module_new(module)); + + PA_IDXSET_FOREACH(client, core->clients, idx) + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), pa_dbusiface_client_new(c, client)); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, &core_interface_info, c) >= 0); + + return c; +} + +static void free_card_cb(void *p, void *userdata) { + pa_dbusiface_card *c = p; + + pa_assert(c); + + pa_dbusiface_card_free(c); +} + +static void free_device_cb(void *p, void *userdata) { + pa_dbusiface_device *d = p; + + pa_assert(d); + + pa_dbusiface_device_free(d); +} + +static void free_stream_cb(void *p, void *userdata) { + pa_dbusiface_stream *s = p; + + pa_assert(s); + + pa_dbusiface_stream_free(s); +} + +static void free_sample_cb(void *p, void *userdata) { + pa_dbusiface_sample *s = p; + + pa_assert(s); + + pa_dbusiface_sample_free(s); +} + +static void free_module_cb(void *p, void *userdata) { + pa_dbusiface_module *m = p; + + pa_assert(m); + + pa_dbusiface_module_free(m); +} + +static void free_client_cb(void *p, void *userdata) { + pa_dbusiface_client *c = p; + + pa_assert(c); + + pa_dbusiface_client_free(c); +} + +void pa_dbusiface_core_free(pa_dbusiface_core *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, core_interface_info.name) >= 0); + + pa_subscription_free(c->subscription); + pa_hashmap_free(c->cards, free_card_cb, NULL); + pa_hashmap_free(c->sinks_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sinks_by_path, NULL, NULL); + pa_hashmap_free(c->sources_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sources_by_path, NULL, NULL); + pa_hashmap_free(c->playback_streams, free_stream_cb, NULL); + pa_hashmap_free(c->record_streams, free_stream_cb, NULL); + pa_hashmap_free(c->samples, free_sample_cb, NULL); + pa_hashmap_free(c->modules, free_module_cb, NULL); + pa_hashmap_free(c->clients, free_client_cb, NULL); + pa_hook_slot_free(c->sink_put_slot); + pa_hook_slot_free(c->sink_unlink_slot); + pa_hook_slot_free(c->source_put_slot); + pa_hook_slot_free(c->source_unlink_slot); + pa_hook_slot_free(c->extension_registered_slot); + pa_hook_slot_free(c->extension_unregistered_slot); + pa_dbusiface_memstats_free(c->memstats); + + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + if (c->fallback_source) + pa_source_unref(c->fallback_source); + + pa_dbus_protocol_unref(c->dbus_protocol); + + pa_xfree(c); +} + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card) { + pa_assert(c); + pa_assert(card); + + return pa_dbusiface_card_get_path(pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index))); +} + +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink) { + pa_assert(c); + pa_assert(sink); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index))); +} + +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source) { + pa_assert(c); + pa_assert(source); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index))); +} + +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input) { + pa_assert(c); + pa_assert(sink_input); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(sink_input->index))); +} + +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output) { + pa_assert(c); + pa_assert(source_output); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(source_output->index))); +} + +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module) { + pa_assert(c); + pa_assert(module); + + return pa_dbusiface_module_get_path(pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(module->index))); +} + +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client) { + pa_assert(c); + pa_assert(client); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(client->index))); +} + +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sinks_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_sink(device); + else + return NULL; +} + +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sources_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_source(device); + else + return NULL; +} diff --git a/src/modules/dbus/iface-core.h b/src/modules/dbus/iface-core.h new file mode 100644 index 00000000..900b6d1c --- /dev/null +++ b/src/modules/dbus/iface-core.h @@ -0,0 +1,52 @@ +#ifndef foodbusifacecorehfoo +#define foodbusifacecorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Core interface + * documentation. + */ + +#include <pulsecore/core.h> + +typedef struct pa_dbusiface_core pa_dbusiface_core; + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core); +void pa_dbusiface_core_free(pa_dbusiface_core *c); + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card); +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink); +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source); +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input); +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output); +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module); +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client); + +/* Returns NULL if there's no sink with the given path. */ +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path); + +/* Returns NULL if there's no source with the given path. */ +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path); + +#endif diff --git a/src/modules/dbus/iface-device-port.c b/src/modules/dbus/iface-device-port.c new file mode 100644 index 00000000..d403b6a2 --- /dev/null +++ b/src/modules/dbus/iface-device-port.c @@ -0,0 +1,190 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-device-port.h" + +#define OBJECT_NAME "port" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_device_port { + uint32_t index; + pa_device_port *port; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_PRIORITY, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info port_interface_info = { + .name = PA_DBUSIFACE_DEVICE_PORT_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->description); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->port->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->port->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx) { + pa_dbusiface_device_port *p = NULL; + + pa_assert(device); + pa_assert(core); + pa_assert(port); + + p = pa_xnew(pa_dbusiface_device_port, 1); + p->index = idx; + p->port = port; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_device_get_path(device), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &port_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, port_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->port->name; +} diff --git a/src/modules/dbus/iface-device-port.h b/src/modules/dbus/iface-device-port.h new file mode 100644 index 00000000..0461e2ff --- /dev/null +++ b/src/modules/dbus/iface-device-port.h @@ -0,0 +1,50 @@ +#ifndef foodbusifacedeviceporthfoo +#define foodbusifacedeviceporthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.DevicePort. + * + * See http://pulseaudio.org/wiki/DBusInterface for the DevicePort interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> + +#include "iface-device.h" + +#define PA_DBUSIFACE_DEVICE_PORT_INTERFACE PA_DBUS_CORE_INTERFACE ".DevicePort" + +typedef struct pa_dbusiface_device_port pa_dbusiface_device_port; + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx); +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p); + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p); +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p); + +#endif diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c new file mode 100644 index 00000000..652790f0 --- /dev/null +++ b/src/modules/dbus/iface-device.c @@ -0,0 +1,1315 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-device-port.h" + +#include "iface-device.h" + +#define SINK_OBJECT_NAME "sink" +#define SOURCE_OBJECT_NAME "source" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum device_type { + DEVICE_TYPE_SINK, + DEVICE_TYPE_SOURCE +}; + +struct pa_dbusiface_device { + pa_dbusiface_core *core; + + union { + pa_sink *sink; + pa_source *source; + }; + enum device_type type; + char *path; + pa_cvolume volume; + dbus_bool_t mute; + union { + pa_sink_state_t sink_state; + pa_source_state_t source_state; + }; + pa_hashmap *ports; + uint32_t next_port_index; + pa_device_port *active_port; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_CARD, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_VOLUME, + PROPERTY_HANDLER_HAS_FLAT_VOLUME, + PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME, + PROPERTY_HANDLER_BASE_VOLUME, + PROPERTY_HANDLER_VOLUME_STEPS, + PROPERTY_HANDLER_MUTE, + PROPERTY_HANDLER_HAS_HARDWARE_VOLUME, + PROPERTY_HANDLER_HAS_HARDWARE_MUTE, + PROPERTY_HANDLER_CONFIGURED_LATENCY, + PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY, + PROPERTY_HANDLER_LATENCY, + PROPERTY_HANDLER_IS_HARDWARE_DEVICE, + PROPERTY_HANDLER_IS_NETWORK_DEVICE, + PROPERTY_HANDLER_STATE, + PROPERTY_HANDLER_PORTS, + PROPERTY_HANDLER_ACTIVE_PORT, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +enum sink_property_handler_index { + SINK_PROPERTY_HANDLER_MONITOR_SOURCE, + SINK_PROPERTY_HANDLER_MAX +}; + +enum source_property_handler_index { + SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK, + SOURCE_PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_CARD] = { .property_name = "Card", .type = "o", .get_cb = handle_get_card, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume }, + [PROPERTY_HANDLER_HAS_FLAT_VOLUME] = { .property_name = "HasFlatVolume", .type = "b", .get_cb = handle_get_has_flat_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME] = { .property_name = "HasConvertibleToDecibelVolume", .type = "b", .get_cb = handle_get_has_convertible_to_decibel_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_BASE_VOLUME] = { .property_name = "BaseVolume", .type = "u", .get_cb = handle_get_base_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME_STEPS] = { .property_name = "VolumeSteps", .type = "u", .get_cb = handle_get_volume_steps, .set_cb = NULL }, + [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute }, + [PROPERTY_HANDLER_HAS_HARDWARE_VOLUME] = { .property_name = "HasHardwareVolume", .type = "b", .get_cb = handle_get_has_hardware_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_HARDWARE_MUTE] = { .property_name = "HasHardwareMute", .type = "b", .get_cb = handle_get_has_hardware_mute, .set_cb = NULL }, + [PROPERTY_HANDLER_CONFIGURED_LATENCY] = { .property_name = "ConfiguredLatency", .type = "t", .get_cb = handle_get_configured_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY] = { .property_name = "HasDynamicLatency", .type = "b", .get_cb = handle_get_has_dynamic_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_LATENCY] = { .property_name = "Latency", .type = "t", .get_cb = handle_get_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_HARDWARE_DEVICE] = { .property_name = "IsHardwareDevice", .type = "b", .get_cb = handle_get_is_hardware_device, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_NETWORK_DEVICE] = { .property_name = "IsNetworkDevice", .type = "b", .get_cb = handle_get_is_network_device, .set_cb = NULL }, + [PROPERTY_HANDLER_STATE] = { .property_name = "State", .type = "u", .get_cb = handle_get_state, .set_cb = NULL }, + [PROPERTY_HANDLER_PORTS] = { .property_name = "Ports", .type = "ao", .get_cb = handle_get_ports, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PORT] = { .property_name = "ActivePort", .type = "o", .get_cb = handle_get_active_port, .set_cb = handle_set_active_port }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +static pa_dbus_property_handler sink_property_handlers[SINK_PROPERTY_HANDLER_MAX] = { + [SINK_PROPERTY_HANDLER_MONITOR_SOURCE] = { .property_name = "MonitorSource", .type = "o", .get_cb = handle_sink_get_monitor_source, .set_cb = NULL } +}; + +static pa_dbus_property_handler source_property_handlers[SOURCE_PROPERTY_HANDLER_MAX] = { + [SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK] = { .property_name = "MonitorOfSink", .type = "o", .get_cb = handle_source_get_monitor_of_sink, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_SUSPEND, + METHOD_HANDLER_GET_PORT_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info suspend_args[] = { { "suspend", "b", "in" } }; +static pa_dbus_arg_info get_port_by_name_args[] = { { "name", "s", "in" }, { "port", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_SUSPEND] = { + .method_name = "Suspend", + .arguments = suspend_args, + .n_arguments = sizeof(suspend_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_suspend }, + [METHOD_HANDLER_GET_PORT_BY_NAME] = { + .method_name = "GetPortByName", + .arguments = get_port_by_name_args, + .n_arguments = sizeof(get_port_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_port_by_name } +}; + +enum signal_index { + SIGNAL_VOLUME_UPDATED, + SIGNAL_MUTE_UPDATED, + SIGNAL_STATE_UPDATED, + SIGNAL_ACTIVE_PORT_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } }; +static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } }; +static pa_dbus_arg_info state_updated_args[] = { { "state", "u", NULL } }; +static pa_dbus_arg_info active_port_updated_args[] = { { "port", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 }, + [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 }, + [SIGNAL_STATE_UPDATED] = { .name = "StateUpdated", .arguments = state_updated_args, .n_arguments = 1 }, + [SIGNAL_ACTIVE_PORT_UPDATED] = { .name = "ActivePortUpdated", .arguments = active_port_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info device_interface_info = { + .name = PA_DBUSIFACE_DEVICE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static pa_dbus_interface_info sink_interface_info = { + .name = PA_DBUSIFACE_SINK_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = sink_property_handlers, + .n_property_handlers = SINK_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_sink_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static pa_dbus_interface_info source_interface_info = { + .name = PA_DBUSIFACE_SOURCE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = source_property_handlers, + .n_property_handlers = SOURCE_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_source_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + idx = (d->type == DEVICE_TYPE_SINK) ? d->sink->index : d->source->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *name = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + name = (d->type == DEVICE_TYPE_SINK) ? d->sink->name : d->source->name; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *driver = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + driver = (d->type == DEVICE_TYPE_SINK) ? d->sink->driver : d->source->driver; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_module *owner_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + owner_module = (d->type == DEVICE_TYPE_SINK) ? d->sink->module : d->source->module; + + if (!owner_module) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't have an owner module.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't have an owner module.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_card *card = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + card = (d->type == DEVICE_TYPE_SINK) ? d->sink->card : d->source->card; + + if (!card) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't belong to any card.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't belong to any card.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_card_path(d->core, card); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_format = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.format : d->source->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_rate = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.rate : d->source->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + channel_map = (d->type == DEVICE_TYPE_SINK) ? &d->sink->channel_map : &d->source->channel_map; + + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels); +} + +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, d->volume.channels); +} + +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessageIter array_iter; + int device_channels = 0; + dbus_uint32_t *volume = NULL; + int n_volume_entries = 0; + pa_cvolume new_vol; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + device_channels = (d->type == DEVICE_TYPE_SINK) ? d->sink->channel_map.channels : d->source->channel_map.channels; + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries); + + if (n_volume_entries != device_channels && n_volume_entries != 1) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %i.", device_channels, n_volume_entries); + return; + } + + pa_cvolume_init(&new_vol); + new_vol.channels = n_volume_entries; + + for (i = 0; i < n_volume_entries; ++i) { + if (!PA_VOLUME_IS_VALID(volume[i])) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]); + return; + } + new_vol.values[i] = volume[i]; + } + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE); + else + pa_source_set_volume(d->source, &new_vol, TRUE, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_flat_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_flat_volume = (d->type == DEVICE_TYPE_SINK) ? (d->sink->flags & PA_SINK_FLAT_VOLUME) : FALSE; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_flat_volume); +} + +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_convertible_to_decibel_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DECIBEL_VOLUME) + : (d->source->flags & PA_SOURCE_DECIBEL_VOLUME); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); +} + +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t base_volume; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + base_volume = (d->type == DEVICE_TYPE_SINK) ? d->sink->base_volume : d->source->base_volume; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &base_volume); +} + +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t volume_steps; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + volume_steps = (d->type == DEVICE_TYPE_SINK) ? d->sink->n_volume_steps : d->source->n_volume_steps; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &volume_steps); +} + +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &d->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + dbus_message_iter_get_basic(iter, &mute); + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_mute(d->sink, mute, TRUE); + else + pa_source_set_mute(d->source, mute, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_VOLUME_CTRL) + : (d->source->flags & PA_SOURCE_HW_VOLUME_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_volume); +} + +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_mute = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_MUTE_CTRL) + : (d->source->flags & PA_SOURCE_HW_MUTE_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_mute); +} + +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t configured_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + configured_latency = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_requested_latency(d->sink) + : pa_source_get_requested_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &configured_latency); +} + +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_dynamic_latency = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_dynamic_latency = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DYNAMIC_LATENCY) + : (d->source->flags & PA_SOURCE_DYNAMIC_LATENCY); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); +} + +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK && !(d->sink->flags & PA_SINK_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't support latency querying.", d->sink->name); + else if (d->type == DEVICE_TYPE_SOURCE && !(d->source->flags & PA_SOURCE_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't support latency querying.", d->source->name); + return; + + latency = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_latency(d->sink) : pa_source_get_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &latency); +} + +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_hardware_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_hardware_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HARDWARE) + : (d->source->flags & PA_SOURCE_HARDWARE); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_hardware_device); +} + +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_network_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_network_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_NETWORK) + : (d->source->flags & PA_SOURCE_NETWORK); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_network_device); +} + +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t state; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &state); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_ports(pa_dbusiface_device *d, unsigned *n) { + const char **ports; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device_port *port = NULL; + + pa_assert(d); + pa_assert(n); + + *n = pa_hashmap_size(d->ports); + + if (*n == 0) + return NULL; + + ports = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(port, d->ports, state) + ports[i++] = pa_dbusiface_device_port_get_path(port); + + return ports; +} + +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char **ports = NULL; + unsigned n_ports = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + ports = get_ports(d, &n_ports); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + pa_xfree(ports); +} + +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *active_port; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_port); +} + +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *new_active_path; + pa_dbusiface_device_port *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(d->ports, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such port: %s", new_active_path); + return; + } + + if (d->type == DEVICE_TYPE_SINK) { + if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r); + return; + } + } else { + if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r); + return; + } + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_proplist_variant_reply(conn, msg, d->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *name = NULL; + const char *driver = NULL; + pa_module *owner_module = NULL; + const char *owner_module_path = NULL; + pa_card *card = NULL; + const char *card_path = NULL; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_bool_t has_flat_volume = FALSE; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + dbus_uint32_t base_volume = 0; + dbus_uint32_t volume_steps = 0; + dbus_bool_t has_hardware_volume = FALSE; + dbus_bool_t has_hardware_mute = FALSE; + dbus_uint64_t configured_latency = 0; + dbus_bool_t has_dynamic_latency = FALSE; + dbus_uint64_t latency = 0; + dbus_bool_t is_hardware_device = FALSE; + dbus_bool_t is_network_device = FALSE; + dbus_uint32_t state = 0; + const char **ports = NULL; + unsigned n_ports = 0; + const char *active_port = NULL; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK) { + idx = d->sink->index; + name = d->sink->name; + driver = d->sink->driver; + owner_module = d->sink->module; + card = d->sink->card; + sample_format = d->sink->sample_spec.format; + sample_rate = d->sink->sample_spec.rate; + channel_map = &d->sink->channel_map; + has_flat_volume = d->sink->flags & PA_SINK_FLAT_VOLUME; + has_convertible_to_decibel_volume = d->sink->flags & PA_SINK_DECIBEL_VOLUME; + base_volume = d->sink->base_volume; + volume_steps = d->sink->n_volume_steps; + has_hardware_volume = d->sink->flags & PA_SINK_HW_VOLUME_CTRL; + has_hardware_mute = d->sink->flags & PA_SINK_HW_MUTE_CTRL; + configured_latency = pa_sink_get_requested_latency(d->sink); + has_dynamic_latency = d->sink->flags & PA_SINK_DYNAMIC_LATENCY; + latency = pa_sink_get_latency(d->sink); + is_hardware_device = d->sink->flags & PA_SINK_HARDWARE; + is_network_device = d->sink->flags & PA_SINK_NETWORK; + state = pa_sink_get_state(d->sink); + } else { + idx = d->source->index; + name = d->source->name; + driver = d->source->driver; + owner_module = d->source->module; + card = d->source->card; + sample_format = d->source->sample_spec.format; + sample_rate = d->source->sample_spec.rate; + channel_map = &d->source->channel_map; + has_flat_volume = FALSE; + has_convertible_to_decibel_volume = d->source->flags & PA_SOURCE_DECIBEL_VOLUME; + base_volume = d->source->base_volume; + volume_steps = d->source->n_volume_steps; + has_hardware_volume = d->source->flags & PA_SOURCE_HW_VOLUME_CTRL; + has_hardware_mute = d->source->flags & PA_SOURCE_HW_MUTE_CTRL; + configured_latency = pa_source_get_requested_latency(d->source); + has_dynamic_latency = d->source->flags & PA_SOURCE_DYNAMIC_LATENCY; + latency = pa_source_get_latency(d->source); + is_hardware_device = d->source->flags & PA_SOURCE_HARDWARE; + is_network_device = d->source->flags & PA_SOURCE_NETWORK; + state = pa_source_get_state(d->source); + } + if (owner_module) + owner_module_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + if (card) + card_path = pa_dbusiface_core_get_card_path(d->core, card); + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + ports = get_ports(d, &n_ports); + if (d->active_port) + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path); + + if (card) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARD].property_name, DBUS_TYPE_OBJECT_PATH, &card_path); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, d->volume.channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_FLAT_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_flat_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BASE_VOLUME].property_name, DBUS_TYPE_UINT32, &base_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME_STEPS].property_name, DBUS_TYPE_UINT32, &volume_steps); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &d->mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_MUTE].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CONFIGURED_LATENCY].property_name, DBUS_TYPE_UINT64, &configured_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY].property_name, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_LATENCY].property_name, DBUS_TYPE_UINT64, &latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_HARDWARE_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_hardware_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_NETWORK_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_network_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_STATE].property_name, DBUS_TYPE_UINT32, &state); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PORTS].property_name, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + if (active_port) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PORT].property_name, DBUS_TYPE_OBJECT_PATH, &active_port); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, d->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(ports); +} + +static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t suspend = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &suspend, DBUS_TYPE_INVALID)); + + if ((d->type == DEVICE_TYPE_SINK) && (pa_sink_suspend(d->sink, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_sink_suspend() failed."); + return; + } else if ((d->type == DEVICE_TYPE_SOURCE) && (pa_source_suspend(d->source, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_source_suspend() failed."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *port_name = NULL; + pa_dbusiface_device_port *port = NULL; + const char *port_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &port_name, DBUS_TYPE_INVALID)); + + if (!(port = pa_hashmap_get(d->ports, port_name))) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on sink %s.", port_name, d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on source %s.", port_name, d->source->name); + return; + } + + port_path = pa_dbusiface_device_port_get_path(port); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &port_path); +} + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_source); +} + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SINK_PROPERTY_HANDLER_MONITOR_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_source); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (!d->source->monitor_of) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Source %s is not a monitor source.", d->source->name); + return; + } + + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); +} + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (d->source->monitor_of) + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + if (monitor_of_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *signal_msg = NULL; + const pa_cvolume *new_volume = NULL; + pa_bool_t new_mute = FALSE; + pa_sink_state_t new_sink_state = 0; + pa_source_state_t new_source_state = 0; + pa_device_port *new_active_port = NULL; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(d); + + if ((d->type == DEVICE_TYPE_SINK && idx != d->sink->index) || (d->type == DEVICE_TYPE_SOURCE && idx != d->source->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((d->type == DEVICE_TYPE_SINK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)) + || ((d->type == DEVICE_TYPE_SOURCE) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE))); + + new_volume = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_volume(d->sink, FALSE) + : pa_source_get_volume(d->source, FALSE); + + if (!pa_cvolume_equal(&d->volume, new_volume)) { + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint32_t *volume_ptr = volume; + + d->volume = *new_volume; + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_assert_se(signal_msg = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, d->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + new_mute = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE); + + if (d->mute != new_mute) { + d->mute = new_mute; + + pa_assert_se(signal_msg = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_BOOLEAN, &d->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + if (d->type == DEVICE_TYPE_SINK) + new_sink_state = pa_sink_get_state(d->sink); + else + new_source_state = pa_source_get_state(d->source); + + if ((d->type == DEVICE_TYPE_SINK && d->sink_state != new_sink_state) + || (d->type == DEVICE_TYPE_SOURCE && d->source_state != new_source_state)) { + dbus_uint32_t state = 0; + + if (d->type == DEVICE_TYPE_SINK) + d->sink_state = new_sink_state; + else + d->source_state = new_source_state; + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_assert_se(signal_msg = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_STATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + new_active_port = (d->type == DEVICE_TYPE_SINK) ? d->sink->active_port : d->source->active_port; + + if (d->active_port != new_active_port) { + const char *object_path = NULL; + + d->active_port = new_active_port; + object_path = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_assert_se(signal_msg = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_ACTIVE_PORT_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + new_proplist = (d->type == DEVICE_TYPE_SINK) ? d->sink->proplist : d->source->proplist; + + if (!pa_proplist_equal(d->proplist, new_proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(d->proplist, PA_UPDATE_SET, new_proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, d->proplist); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(sink); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->sink = pa_sink_ref(sink); + d->type = DEVICE_TYPE_SINK; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SINK_OBJECT_NAME, sink->index); + d->volume = *pa_sink_get_volume(sink, FALSE); + d->mute = pa_sink_get_mute(sink, FALSE); + d->sink_state = pa_sink_get_state(sink); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(sink->proplist); + d->dbus_protocol = pa_dbus_protocol_get(sink->core); + d->subscription = pa_subscription_new(sink->core, PA_SUBSCRIPTION_MASK_SINK, subscription_cb, d); + + if (sink->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, sink->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, sink->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = sink->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &sink_interface_info, d) >= 0); + + return d; +} + +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(source); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->source = pa_source_ref(source); + d->type = DEVICE_TYPE_SOURCE; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SOURCE_OBJECT_NAME, source->index); + d->volume = *pa_source_get_volume(source, FALSE); + d->mute = pa_source_get_mute(source, FALSE); + d->source_state = pa_source_get_state(source); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(source->proplist); + d->dbus_protocol = pa_dbus_protocol_get(source->core); + d->subscription = pa_subscription_new(source->core, PA_SUBSCRIPTION_MASK_SOURCE, subscription_cb, d); + + if (source->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, source->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, source->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = source->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &source_interface_info, d) >= 0); + + return d; +} + +static void port_free_cb(void *p, void *userdata) { + pa_dbusiface_device_port *port = p; + + pa_assert(port); + + pa_dbusiface_device_port_free(port); +} + +void pa_dbusiface_device_free(pa_dbusiface_device *d) { + pa_assert(d); + + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, device_interface_info.name) >= 0); + + if (d->type == DEVICE_TYPE_SINK) { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, sink_interface_info.name) >= 0); + pa_sink_unref(d->sink); + + } else { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, source_interface_info.name) >= 0); + pa_source_unref(d->source); + } + pa_hashmap_free(d->ports, port_free_cb, NULL); + pa_proplist_free(d->proplist); + pa_dbus_protocol_unref(d->dbus_protocol); + pa_subscription_free(d->subscription); + + pa_xfree(d->path); + pa_xfree(d); +} + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d) { + pa_assert(d); + + return d->path; +} + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + return d->sink; +} + +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + return d->source; +} diff --git a/src/modules/dbus/iface-device.h b/src/modules/dbus/iface-device.h new file mode 100644 index 00000000..62e05e9a --- /dev/null +++ b/src/modules/dbus/iface-device.h @@ -0,0 +1,53 @@ +#ifndef foodbusifacedevicehfoo +#define foodbusifacedevicehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interfaces org.PulseAudio.Core1.Device, + * org.PulseAudio.Core1.Sink and org.PulseAudio.Core1.Source. + * + * See http://pulseaudio.org/wiki/DBusInterface for the interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_DEVICE_INTERFACE PA_DBUS_CORE_INTERFACE ".Device" +#define PA_DBUSIFACE_SINK_INTERFACE PA_DBUS_CORE_INTERFACE ".Sink" +#define PA_DBUSIFACE_SOURCE_INTERFACE PA_DBUS_CORE_INTERFACE ".Source" + +typedef struct pa_dbusiface_device pa_dbusiface_device; + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink); +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source); +void pa_dbusiface_device_free(pa_dbusiface_device *d); + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d); + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d); +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d); + +#endif diff --git a/src/modules/dbus/iface-memstats.c b/src/modules/dbus/iface-memstats.c new file mode 100644 index 00000000..4cd692db --- /dev/null +++ b/src/modules/dbus/iface-memstats.c @@ -0,0 +1,230 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-scache.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-memstats.h" + +#define OBJECT_NAME "memstats" + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_memstats { + pa_core *core; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_CURRENT_MEMBLOCKS, + PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_SAMPLE_CACHE_SIZE, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS] = { .property_name = "CurrentMemblocks", .type = "u", .get_cb = handle_get_current_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE] = { .property_name = "CurrentMemblocksSize", .type = "u", .get_cb = handle_get_current_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS] = { .property_name = "AccumulatedMemblocks", .type = "u", .get_cb = handle_get_accumulated_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE] = { .property_name = "AccumulatedMemblocksSize", .type = "u", .get_cb = handle_get_accumulated_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_CACHE_SIZE] = { .property_name = "SampleCacheSize", .type = "u", .get_cb = handle_get_sample_cache_size, .set_cb = NULL } +}; + +static pa_dbus_interface_info memstats_interface_info = { + .name = PA_DBUSIFACE_MEMSTATS_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks); +} + +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks_size); +} + +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks); +} + +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks_size); +} + +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + dbus_uint32_t sample_cache_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + sample_cache_size = pa_scache_total_size(m->core); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_cache_size); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + dbus_uint32_t current_memblocks_size; + dbus_uint32_t accumulated_memblocks; + dbus_uint32_t accumulated_memblocks_size; + dbus_uint32_t sample_cache_size; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + sample_cache_size = pa_scache_total_size(m->core); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, ¤t_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, ¤t_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_CACHE_SIZE].property_name, DBUS_TYPE_UINT32, &sample_cache_size); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core) { + pa_dbusiface_memstats *m; + + pa_assert(dbus_core); + pa_assert(core); + + m = pa_xnew(pa_dbusiface_memstats, 1); + m->core = core; + m->path = pa_sprintf_malloc("%s/%s", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME); + m->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &memstats_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, memstats_interface_info.name) >= 0); + + pa_xfree(m->path); + + pa_dbus_protocol_unref(m->dbus_protocol); + + pa_xfree(m); +} + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/dbus/iface-memstats.h b/src/modules/dbus/iface-memstats.h new file mode 100644 index 00000000..0820e8fe --- /dev/null +++ b/src/modules/dbus/iface-memstats.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacememstatshfoo +#define foodbusifacememstatshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Memstats. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Memstats interface + * documentation. + */ + +#include <pulsecore/core.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_MEMSTATS_INTERFACE PA_DBUS_CORE_INTERFACE ".Memstats" + +typedef struct pa_dbusiface_memstats pa_dbusiface_memstats; + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core); +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m); + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m); + +#endif diff --git a/src/modules/dbus/iface-module.c b/src/modules/dbus/iface-module.c new file mode 100644 index 00000000..9973166c --- /dev/null +++ b/src/modules/dbus/iface-module.c @@ -0,0 +1,336 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-module.h" + +#define OBJECT_NAME "module" + +struct pa_dbusiface_module { + pa_module *module; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_ARGUMENTS, + PROPERTY_HANDLER_USAGE_COUNTER, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_ARGUMENTS] = { .property_name = "Arguments", .type = "a{ss}", .get_cb = handle_get_arguments, .set_cb = NULL }, + [PROPERTY_HANDLER_USAGE_COUNTER] = { .property_name = "UsageCounter", .type = "u", .get_cb = handle_get_usage_counter, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_UNLOAD, + METHOD_HANDLER_MAX +}; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_UNLOAD] = { + .method_name = "Unload", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_unload } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info module_interface_info = { + .name = PA_DBUSIFACE_MODULE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &m->module->name); +} + +static void append_modargs_variant(DBusMessageIter *iter, pa_dbusiface_module *m) { + pa_modargs *ma = NULL; + DBusMessageIter variant_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + void *state = NULL; + const char *key = NULL; + const char *value = NULL; + + pa_assert(iter); + pa_assert(m); + + pa_assert_se(ma = pa_modargs_new(m->module->argument, NULL)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{ss}", &variant_iter)); + pa_assert_se(dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "{ss}", &dict_iter)); + + for (state = NULL, key = pa_modargs_iterate(ma, &state); key; key = pa_modargs_iterate(ma, &state)) { + pa_assert_se(value = pa_modargs_get_value(ma, key, NULL)); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &value)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(&variant_iter, &dict_iter)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_modargs_free(ma); +} + +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(reply = dbus_message_new_method_return(msg)); + dbus_message_iter_init_append(reply, &msg_iter); + append_modargs_variant(&msg_iter, m); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (!m->module->get_n_used || (real_counter_value = m->module->get_n_used(m->module)) < 0) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Module %u (%s) doesn't have a usage counter.", m->module->index, m->module->name); + return; + } + + usage_counter = real_counter_value; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &usage_counter); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_proplist_variant_reply(conn, msg, m->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + dbus_uint32_t idx = 0; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + if (m->module->get_n_used && (real_counter_value = m->module->get_n_used(m->module)) >= 0) + usage_counter = real_counter_value; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &m->module->name); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name)); + append_modargs_variant(&dict_entry_iter, m); + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + + if (real_counter_value >= 0) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name, DBUS_TYPE_UINT32, &usage_counter); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, m->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); +} + +static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (m->module->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module unloading."); + return; + } + + pa_module_unload_request(m->module, FALSE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *signal_msg = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_MODULE); + pa_assert(m); + + /* We can't use idx != m->module->index, because the m->module pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->modules, idx) != m->module) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(m->proplist, m->module->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(m->proplist, PA_UPDATE_SET, m->module->proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(m->path, + PA_DBUSIFACE_MODULE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, m->proplist); + + pa_dbus_protocol_send_signal(m->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module) { + pa_dbusiface_module *m; + + pa_assert(module); + + m = pa_xnew0(pa_dbusiface_module, 1); + m->module = module; + m->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, module->index); + m->proplist = pa_proplist_copy(module->proplist); + m->dbus_protocol = pa_dbus_protocol_get(module->core); + m->subscription = pa_subscription_new(module->core, PA_SUBSCRIPTION_MASK_MODULE, subscription_cb, m); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &module_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_module_free(pa_dbusiface_module *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, module_interface_info.name) >= 0); + + pa_proplist_free(m->proplist); + pa_dbus_protocol_unref(m->dbus_protocol); + pa_subscription_free(m->subscription); + + pa_xfree(m->path); + pa_xfree(m); +} + +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/module-dbus-protocol.c b/src/modules/dbus/iface-module.h index 077f4178..68ca1de5 100644 --- a/src/modules/module-dbus-protocol.c +++ b/src/modules/dbus/iface-module.h @@ -1,3 +1,6 @@ +#ifndef foodbusifacemodulehfoo +#define foodbusifacemodulehfoo + /*** This file is part of PulseAudio. @@ -19,48 +22,24 @@ USA. ***/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <pulse/xmalloc.h> +/* This object implements the D-Bus interface org.PulseAudio.Core1.Module. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Module interface + * documentation. + */ -#include <pulsecore/macro.h> #include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> -#include "module-dbus-protocol-symdef.h" - -PA_MODULE_DESCRIPTION("D-Bus interface"); -PA_MODULE_USAGE("<no module arguments>"); -PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_AUTHOR("Tanu Kaskinen"); -PA_MODULE_VERSION(PACKAGE_VERSION); - -struct userdata { - pa_module *module; -}; +#include "iface-core.h" -int pa__init(pa_module *m) { - struct userdata *u = NULL; +#define PA_DBUSIFACE_MODULE_INTERFACE PA_DBUS_CORE_INTERFACE ".Module" - pa_assert(m); +typedef struct pa_dbusiface_module pa_dbusiface_module; - m->userdata = u = pa_xnew0(struct userdata, 1); - u->module = m; +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module); +void pa_dbusiface_module_free(pa_dbusiface_module *m); - pa_log_notice("Hello, world!"); +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m); - return 0; -} - -void pa__done(pa_module*m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) - return; - - pa_xfree(u); - m->userdata = NULL; -} +#endif diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c new file mode 100644 index 00000000..93d4fc8c --- /dev/null +++ b/src/modules/dbus/iface-sample.c @@ -0,0 +1,519 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-sample.h" + +#define OBJECT_NAME "sample" + +struct pa_dbusiface_sample { + pa_dbusiface_core *core; + + pa_scache_entry *sample; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_DEFAULT_VOLUME, + PROPERTY_HANDLER_DURATION, + PROPERTY_HANDLER_BYTES, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_DEFAULT_VOLUME] = { .property_name = "DefaultVolume", .type = "au", .get_cb = handle_get_default_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_DURATION] = { .property_name = "Duration", .type = "t", .get_cb = handle_get_duration, .set_cb = NULL }, + [PROPERTY_HANDLER_BYTES] = { .property_name = "Bytes", .type = "u", .get_cb = handle_get_bytes, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_PLAY, + METHOD_HANDLER_PLAY_TO_SINK, + METHOD_HANDLER_REMOVE, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info play_args[] = { { "volume", "u", "in" }, { "property_list", "a{say}", "in" } }; +static pa_dbus_arg_info play_to_sink_args[] = { { "sink", "o", "in" }, + { "volume", "u", "in" }, + { "property_list", "a{say}", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_PLAY] = { + .method_name = "Play", + .arguments = play_args, + .n_arguments = sizeof(play_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play }, + [METHOD_HANDLER_PLAY_TO_SINK] = { + .method_name = "PlayToSink", + .arguments = play_to_sink_args, + .n_arguments = sizeof(play_to_sink_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play_to_sink }, + [METHOD_HANDLER_REMOVE] = { + .method_name = "Remove", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_remove } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info sample_interface_info = { + .name = PA_DBUSIFACE_SAMPLE_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &s->sample->name); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample format is unknown.", s->sample->name); + return; + } + + sample_format = s->sample->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample rate is unknown.", s->sample->name); + return; + } + + sample_rate = s->sample->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its channel map is unknown.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); +} + +static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->volume_is_set) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s doesn't have default volume stored.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); +} + +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint64_t duration = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its duration is unknown.", s->sample->name); + return; + } + + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &duration); +} + +static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t bytes = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its size is unknown.", s->sample->name); + return; + } + + bytes = s->sample->memchunk.length; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &bytes); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + dbus_uint64_t duration = 0; + dbus_uint32_t bytes = 0; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + if (s->sample->memchunk.memblock) { + sample_format = s->sample->sample_spec.format; + sample_rate = s->sample->sample_spec.rate; + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + bytes = s->sample->memchunk.length; + } + if (s->sample->volume_is_set) { + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + } + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &s->sample->name); + + if (s->sample->memchunk.memblock) { + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); + } + + if (s->sample->volume_is_set) + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_VOLUME].property_name, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); + + if (s->sample->memchunk.memblock) { + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DURATION].property_name, DBUS_TYPE_UINT64, &duration); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BYTES].property_name, DBUS_TYPE_UINT32, &bytes); + } + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (!(sink = pa_namereg_get_default_sink(s->sample->core))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Can't play sample %s, because there are no sinks available.", s->sample->name); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + const char *sink_path = NULL; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sink_path); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (!(sink = pa_dbusiface_core_get_sink(s->core, sink_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_path); + goto finish; + } + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (pa_scache_remove_item(s->sample->core, s->sample->name) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Removing sample %s failed.", s->sample->name); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessage *signal_msg = NULL; + + pa_assert(c); + pa_assert(s); + + /* We can't use idx != s->sample->index, because the s->sample pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(c->scache, idx) != s->sample) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(s->proplist, s->sample->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(s->proplist, PA_UPDATE_SET, s->sample->proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_SAMPLE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample) { + pa_dbusiface_sample *s = NULL; + + pa_assert(core); + pa_assert(sample); + + s = pa_xnew0(pa_dbusiface_sample, 1); + s->core = core; + s->sample = sample; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, sample->index); + s->proplist = pa_proplist_copy(sample->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sample->core); + s->subscription = pa_subscription_new(sample->core, PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, subscription_cb, s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &sample_interface_info, s) >= 0); + + return s; +} + +void pa_dbusiface_sample_free(pa_dbusiface_sample *s) { + pa_assert(s); + + pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, sample_interface_info.name) >= 0); + + pa_proplist_free(s->proplist); + pa_dbus_protocol_unref(s->dbus_protocol); + pa_subscription_free(s->subscription); + + pa_xfree(s->path); + pa_xfree(s); +} + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *s) { + pa_assert(s); + + return s->path; +} diff --git a/src/modules/dbus/iface-sample.h b/src/modules/dbus/iface-sample.h new file mode 100644 index 00000000..f1947ce8 --- /dev/null +++ b/src/modules/dbus/iface-sample.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacesamplehfoo +#define foodbusifacesamplehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Sample. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Sample interface + * documentation. + */ + +#include <pulsecore/core-scache.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_SAMPLE_INTERFACE PA_DBUS_CORE_INTERFACE ".Sample" + +typedef struct pa_dbusiface_sample pa_dbusiface_sample; + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample); +void pa_dbusiface_sample_free(pa_dbusiface_sample *c); + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *c); + +#endif diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c new file mode 100644 index 00000000..d9f12374 --- /dev/null +++ b/src/modules/dbus/iface-stream.c @@ -0,0 +1,933 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-stream.h" + +#define PLAYBACK_OBJECT_NAME "playback_stream" +#define RECORD_OBJECT_NAME "record_stream" + +enum stream_type { + STREAM_TYPE_PLAYBACK, + STREAM_TYPE_RECORD +}; + +struct pa_dbusiface_stream { + pa_dbusiface_core *core; + + union { + pa_sink_input *sink_input; + pa_source_output *source_output; + }; + enum stream_type type; + char *path; + union { + pa_sink *sink; + pa_source *source; + }; + uint32_t sample_rate; + pa_cvolume volume; + dbus_bool_t mute; + pa_proplist *proplist; + + pa_bool_t has_volume; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; + pa_hook_slot *send_event_slot; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_CLIENT, + PROPERTY_HANDLER_DEVICE, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_VOLUME, + PROPERTY_HANDLER_MUTE, + PROPERTY_HANDLER_BUFFER_LATENCY, + PROPERTY_HANDLER_DEVICE_LATENCY, + PROPERTY_HANDLER_RESAMPLE_METHOD, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL }, + [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL }, + [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL }, + [PROPERTY_HANDLER_CLIENT] = { .property_name = "Client", .type = "o", .get_cb = handle_get_client, .set_cb = NULL }, + [PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "o", .get_cb = handle_get_device, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL }, + [PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume }, + [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute }, + [PROPERTY_HANDLER_BUFFER_LATENCY] = { .property_name = "BufferLatency", .type = "t", .get_cb = handle_get_buffer_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_DEVICE_LATENCY] = { .property_name = "DeviceLatency", .type = "t", .get_cb = handle_get_device_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_RESAMPLE_METHOD] = { .property_name = "ResampleMethod", .type = "s", .get_cb = handle_get_resample_method, .set_cb = NULL }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_MOVE, + METHOD_HANDLER_KILL, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info move_args[] = { { "device", "o", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_MOVE] = { + .method_name = "Move", + .arguments = move_args, + .n_arguments = sizeof(move_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_move }, + [METHOD_HANDLER_KILL] = { + .method_name = "Kill", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_kill } +}; + +enum signal_index { + SIGNAL_DEVICE_UPDATED, + SIGNAL_SAMPLE_RATE_UPDATED, + SIGNAL_VOLUME_UPDATED, + SIGNAL_MUTE_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_STREAM_EVENT, + SIGNAL_MAX +}; + +static pa_dbus_arg_info device_updated_args[] = { { "device", "o", NULL } }; +static pa_dbus_arg_info sample_rate_updated_args[] = { { "sample_rate", "u", NULL } }; +static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } }; +static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; +static pa_dbus_arg_info stream_event_args[] = { { "name", "s", NULL }, { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = device_updated_args, .n_arguments = 1 }, + [SIGNAL_SAMPLE_RATE_UPDATED] = { .name = "SampleRateUpdated", .arguments = sample_rate_updated_args, .n_arguments = 1 }, + [SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 }, + [SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }, + [SIGNAL_STREAM_EVENT] = { .name = "StreamEvent", .arguments = stream_event_args, .n_arguments = sizeof(stream_event_args) / sizeof(pa_dbus_arg_info) } +}; + +static pa_dbus_interface_info stream_interface_info = { + .name = PA_DBUSIFACE_STREAM_INTERFACE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t idx; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->index : s->source_output->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +/* The returned string has to be freed with pa_xfree() by the caller. */ +static char *stream_to_string(pa_dbusiface_stream *s) { + if (s->type == STREAM_TYPE_PLAYBACK) + return pa_sprintf_malloc("Playback stream %u", (unsigned) s->sink_input->index); + else + return pa_sprintf_malloc("Record stream %u", (unsigned) s->source_output->index); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *driver = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + driver = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->driver : s->source_output->driver; + + if (!driver) { + char *str = stream_to_string(s); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have a driver.", str); + pa_xfree(str); + + return; + } + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_module *owner_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + owner_module = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->module : s->source_output->module; + + if (!owner_module) { + char *str = stream_to_string(s); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have an owner module.", str); + pa_xfree(str); + + return; + } + + object_path = pa_dbusiface_core_get_module_path(s->core, owner_module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_client *client = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + client = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->client : s->source_output->client; + + if (!client) { + char *str = stream_to_string(s); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s isn't associated to any client.", str); + pa_xfree(str); + + return; + } + + object_path = pa_dbusiface_core_get_client_path(s->core, client); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *device = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + device = pa_dbusiface_core_get_sink_path(s->core, s->sink); + else + device = pa_dbusiface_core_get_source_path(s->core, s->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &device); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + sample_format = (s->type == STREAM_TYPE_PLAYBACK) + ? s->sink_input->sample_spec.format + : s->source_output->sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format); +} + +static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &s->sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + channel_map = (s->type == STREAM_TYPE_PLAYBACK) ? &s->sink_input->channel_map : &s->source_output->channel_map; + + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels); +} + +static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->has_volume) { + char *str = stream_to_string(s); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have volume.", str); + pa_xfree(str); + + return; + } + + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, s->volume.channels); +} + +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_stream *s = userdata; + pa_bool_t volume_writable = TRUE; + DBusMessageIter array_iter; + int stream_channels = 0; + dbus_uint32_t *volume = NULL; + int n_volume_entries = 0; + pa_cvolume new_vol; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(s); + + volume_writable = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->volume_writable : FALSE; + + if (!s->has_volume || !volume_writable) { + char *str = stream_to_string(s); + + if (!s->has_volume) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have volume.", str); + else if (!volume_writable) + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "%s has read-only volume.", str); + pa_xfree(str); + + return; + } + + stream_channels = s->sink_input->channel_map.channels; + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries); + + if (n_volume_entries != stream_channels && n_volume_entries != 1) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %u.", stream_channels, n_volume_entries); + return; + } + + pa_cvolume_init(&new_vol); + new_vol.channels = n_volume_entries; + + for (i = 0; i < n_volume_entries; ++i) { + if (!PA_VOLUME_IS_VALID(volume[i])) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", volume[i]); + return; + } + new_vol.values[i] = volume[i]; + } + + pa_sink_input_set_volume(s->sink_input, &new_vol, TRUE, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute."); + return; + } + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &s->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_bool_t mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(s); + + dbus_message_iter_get_basic(iter, &mute); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute."); + return; + } + + pa_sink_input_set_mute(s->sink_input, mute, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +}; + +static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint64_t buffer_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + buffer_latency = pa_sink_input_get_latency(s->sink_input, NULL); + else + buffer_latency = pa_source_output_get_latency(s->source_output, NULL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &buffer_latency); +} + +static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + dbus_uint64_t device_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + pa_sink_input_get_latency(s->sink_input, &device_latency); + else + pa_source_output_get_latency(s->source_output, &device_latency); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &device_latency); +} + +static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *resample_method = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method); + else + resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method); + + if (!resample_method) + resample_method = ""; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &resample_method); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *driver = NULL; + pa_module *owner_module = NULL; + const char *owner_module_path = NULL; + pa_client *client = NULL; + const char *client_path = NULL; + const char *device = NULL; + dbus_uint32_t sample_format = 0; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint64_t buffer_latency = 0; + dbus_uint64_t device_latency = 0; + const char *resample_method = NULL; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->has_volume) { + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + } + + if (s->type == STREAM_TYPE_PLAYBACK) { + idx = s->sink_input->index; + driver = s->sink_input->driver; + owner_module = s->sink_input->module; + client = s->sink_input->client; + device = pa_dbusiface_core_get_sink_path(s->core, s->sink); + sample_format = s->sink_input->sample_spec.format; + channel_map = &s->sink_input->channel_map; + buffer_latency = pa_sink_input_get_latency(s->sink_input, &device_latency); + resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method); + } else { + idx = s->source_output->index; + driver = s->source_output->driver; + owner_module = s->source_output->module; + client = s->source_output->client; + device = pa_dbusiface_core_get_source_path(s->core, s->source); + sample_format = s->source_output->sample_spec.format; + channel_map = &s->source_output->channel_map; + buffer_latency = pa_source_output_get_latency(s->source_output, &device_latency); + resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method); + } + if (owner_module) + owner_module_path = pa_dbusiface_core_get_module_path(s->core, owner_module); + if (client) + client_path = pa_dbusiface_core_get_client_path(s->core, client); + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + if (!resample_method) + resample_method = ""; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx); + + if (driver) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver); + + if (owner_module) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path); + + if (client) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &client_path); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_OBJECT_PATH, &device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &s->sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels); + + if (s->has_volume) { + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, s->volume.channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &s->mute); + } + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BUFFER_LATENCY].property_name, DBUS_TYPE_UINT64, &buffer_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE_LATENCY].property_name, DBUS_TYPE_UINT64, &device_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RESAMPLE_METHOD].property_name, DBUS_TYPE_STRING, &resample_method); + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + const char *device = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID)); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink *sink = pa_dbusiface_core_get_sink(s->core, device); + + if (!sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", device); + return; + } + + if (pa_sink_input_move_to(s->sink_input, sink, TRUE) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Moving playback stream %u to sink %s failed.", s->sink_input->index, sink->name); + return; + } + } else { + pa_source *source = pa_dbusiface_core_get_source(s->core, device); + + if (!source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", device); + return; + } + + if (pa_source_output_move_to(s->source_output, source, TRUE) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Moving record stream %u to source %s failed.", s->source_output->index, source->name); + return; + } + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_stream *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) + pa_sink_input_kill(s->sink_input); + else + pa_source_output_kill(s->source_output); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessage *signal_msg = NULL; + const char *new_device_path = NULL; + uint32_t new_sample_rate = 0; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(s); + + if ((s->type == STREAM_TYPE_PLAYBACK && idx != s->sink_input->index) + || (s->type == STREAM_TYPE_RECORD && idx != s->source_output->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((s->type == STREAM_TYPE_PLAYBACK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT)) + || ((s->type == STREAM_TYPE_RECORD) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT))); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink *new_sink = s->sink_input->sink; + + if (s->sink != new_sink) { + pa_sink_unref(s->sink); + s->sink = pa_sink_ref(new_sink); + + new_device_path = pa_dbusiface_core_get_sink_path(s->core, new_sink); + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } else { + pa_source *new_source = s->source_output->source; + + if (s->source != new_source) { + pa_source_unref(s->source); + s->source = pa_source_ref(new_source); + + new_device_path = pa_dbusiface_core_get_source_path(s->core, new_source); + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } + + new_sample_rate = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->sample_spec.rate : s->source_output->sample_spec.rate; + + if (s->sample_rate != new_sample_rate) { + s->sample_rate = new_sample_rate; + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_SAMPLE_RATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_bool_t new_mute = FALSE; + + if (s->has_volume) { + pa_cvolume new_volume; + + pa_sink_input_get_volume(s->sink_input, &new_volume, TRUE); + + if (!pa_cvolume_equal(&s->volume, &new_volume)) { + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint32_t *volume_ptr = volume; + + s->volume = new_volume; + + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } + + new_mute = pa_sink_input_get_mute(s->sink_input); + + if (s->mute != new_mute) { + s->mute = new_mute; + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_BOOLEAN, &s->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } + } + + new_proplist = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->proplist : s->source_output->proplist; + + if (!pa_proplist_equal(s->proplist, new_proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(s->proplist, PA_UPDATE_SET, new_proplist); + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + signal_msg = NULL; + } +} + +static pa_hook_result_t send_event_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_stream *s = slot_data; + DBusMessage *signal_msg = NULL; + DBusMessageIter msg_iter; + const char *name = NULL; + pa_proplist *property_list = NULL; + + pa_assert(call_data); + pa_assert(s); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink_input_send_event_hook_data *data = call_data; + + if (data->sink_input != s->sink_input) + return PA_HOOK_OK; + + name = data->event; + property_list = data->data; + } else { + pa_source_output_send_event_hook_data *data = call_data; + + if (data->source_output != s->source_output) + return PA_HOOK_OK; + + name = data->event; + property_list = data->data; + } + + pa_assert_se(signal_msg = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_STREAM_EVENT].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name)); + pa_dbus_append_proplist(&msg_iter, property_list); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); + + return PA_HOOK_OK; +} + +pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input) { + pa_dbusiface_stream *s; + + pa_assert(core); + pa_assert(sink_input); + + s = pa_xnew(pa_dbusiface_stream, 1); + s->core = core; + s->sink_input = pa_sink_input_ref(sink_input); + s->type = STREAM_TYPE_PLAYBACK; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, PLAYBACK_OBJECT_NAME, sink_input->index); + s->sink = pa_sink_ref(sink_input->sink); + s->sample_rate = sink_input->sample_spec.rate; + s->has_volume = pa_sink_input_is_volume_readable(sink_input); + + if (s->has_volume) + pa_sink_input_get_volume(sink_input, &s->volume, TRUE); + else + pa_cvolume_init(&s->volume); + + s->mute = pa_sink_input_get_mute(sink_input); + s->proplist = pa_proplist_copy(sink_input->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sink_input->core); + s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s); + s->send_event_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], + PA_HOOK_NORMAL, + send_event_cb, + s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0); + + return s; +} + +pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output) { + pa_dbusiface_stream *s; + + pa_assert(core); + pa_assert(source_output); + + s = pa_xnew(pa_dbusiface_stream, 1); + s->core = core; + s->source_output = pa_source_output_ref(source_output); + s->type = STREAM_TYPE_RECORD; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, RECORD_OBJECT_NAME, source_output->index); + s->source = pa_source_ref(source_output->source); + s->sample_rate = source_output->sample_spec.rate; + pa_cvolume_init(&s->volume); + s->mute = FALSE; + s->proplist = pa_proplist_copy(source_output->proplist); + s->has_volume = FALSE; + s->dbus_protocol = pa_dbus_protocol_get(source_output->core); + s->subscription = pa_subscription_new(source_output->core, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscription_cb, s); + s->send_event_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT], + PA_HOOK_NORMAL, + send_event_cb, + s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0); + + return s; +} + +void pa_dbusiface_stream_free(pa_dbusiface_stream *s) { + pa_assert(s); + + pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, stream_interface_info.name) >= 0); + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_sink_input_unref(s->sink_input); + pa_sink_unref(s->sink); + } else { + pa_source_output_unref(s->source_output); + pa_source_unref(s->source); + } + + pa_proplist_free(s->proplist); + pa_dbus_protocol_unref(s->dbus_protocol); + pa_subscription_free(s->subscription); + pa_hook_slot_free(s->send_event_slot); + + pa_xfree(s->path); + pa_xfree(s); +} + +const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s) { + pa_assert(s); + + return s->path; +} diff --git a/src/modules/dbus/iface-stream.h b/src/modules/dbus/iface-stream.h new file mode 100644 index 00000000..036b4e7e --- /dev/null +++ b/src/modules/dbus/iface-stream.h @@ -0,0 +1,47 @@ +#ifndef foodbusifacestreamhfoo +#define foodbusifacestreamhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Stream. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Stream interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_STREAM_INTERFACE PA_DBUS_CORE_INTERFACE ".Stream" + +typedef struct pa_dbusiface_stream pa_dbusiface_stream; + +pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input); +pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output); +void pa_dbusiface_stream_free(pa_dbusiface_stream *s); + +const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s); + +#endif diff --git a/src/modules/dbus/module-dbus-protocol.c b/src/modules/dbus/module-dbus-protocol.c new file mode 100644 index 00000000..4969585f --- /dev/null +++ b/src/modules/dbus/module-dbus-protocol.c @@ -0,0 +1,621 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/mainloop-api.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/client.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/idxset.h> +#include <pulsecore/macro.h> +#include <pulsecore/modargs.h> +#include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" +#include "iface-core.h" + +#include "module-dbus-protocol-symdef.h" + +PA_MODULE_DESCRIPTION("D-Bus interface"); +PA_MODULE_USAGE( + "access=local|remote|local,remote " + "tcp_port=<port number> " + "tcp_listen=<hostname>"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_AUTHOR("Tanu Kaskinen"); +PA_MODULE_VERSION(PACKAGE_VERSION); + +enum server_type { + SERVER_TYPE_LOCAL, + SERVER_TYPE_TCP +}; + +struct server; +struct connection; + +struct userdata { + pa_module *module; + pa_bool_t local_access; + pa_bool_t remote_access; + uint32_t tcp_port; + char *tcp_listen; + + struct server *local_server; + struct server *tcp_server; + + pa_idxset *connections; + + pa_defer_event *cleanup_event; + + pa_dbus_protocol *dbus_protocol; + pa_dbusiface_core *core_iface; +}; + +struct server { + struct userdata *userdata; + enum server_type type; + DBusServer *dbus_server; +}; + +struct connection { + struct server *server; + pa_dbus_wrap_connection *wrap_conn; + pa_client *client; +}; + +static const char* const valid_modargs[] = { + "access", + "tcp_port", + "tcp_listen", + NULL +}; + +static void connection_free(struct connection *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_unregister_connection(c->server->userdata->dbus_protocol, pa_dbus_wrap_connection_get(c->wrap_conn)) >= 0); + + pa_client_free(c->client); + pa_dbus_wrap_connection_free(c->wrap_conn); + pa_xfree(c); +} + +/* Called from pa_client_kill(). */ +static void client_kill_cb(pa_client *c) { + struct connection *conn; + + pa_assert(c); + pa_assert(c->userdata); + + conn = c->userdata; + connection_free(conn); + c->userdata = NULL; + + pa_log_info("Connection killed."); +} + +/* Called from pa_client_send_event(). */ +static void client_send_event_cb(pa_client *c, const char *name, pa_proplist *data) { + struct connection *conn = NULL; + DBusMessage *signal_msg = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->userdata); + + conn = c->userdata; + + pa_assert_se(signal_msg = dbus_message_new_signal(pa_dbusiface_core_get_client_path(conn->server->userdata->core_iface, c), + PA_DBUSIFACE_CLIENT_INTERFACE, + "ClientEvent")); + dbus_message_iter_init_append(signal_msg, &msg_iter); + pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name)); + pa_dbus_append_proplist(&msg_iter, data); + + pa_assert_se(dbus_connection_send(pa_dbus_wrap_connection_get(conn->wrap_conn), signal_msg, NULL)); + dbus_message_unref(signal_msg); +} + +/* Called by D-Bus at the authentication phase. */ +static dbus_bool_t user_check_cb(DBusConnection *connection, unsigned long uid, void *data) { + pa_log_debug("Allowing connection by user %lu.", uid); + + return TRUE; +} + +static DBusHandlerResult disconnection_filter_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + struct connection *c = user_data; + + pa_assert(connection); + pa_assert(message); + pa_assert(c); + + if (dbus_message_is_signal(message, "org.freedesktop.DBus.Local", "Disconnected")) { + /* The connection died. Now we want to free the connection object, but + * let's wait until this message is fully processed, in case someone + * else is interested in this signal too. */ + c->server->userdata->module->core->mainloop->defer_enable(c->server->userdata->cleanup_event, 1); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +/* Called by D-Bus when a new client connection is received. */ +static void connection_new_cb(DBusServer *dbus_server, DBusConnection *new_connection, void *data) { + struct server *s = data; + struct connection *c; + pa_client_new_data new_data; + pa_client *client; + + pa_assert(new_connection); + pa_assert(s); + + pa_client_new_data_init(&new_data); + new_data.module = s->userdata->module; + new_data.driver = __FILE__; + pa_proplist_sets(new_data.proplist, PA_PROP_APPLICATION_NAME, "D-Bus client"); + client = pa_client_new(s->userdata->module->core, &new_data); + pa_client_new_data_done(&new_data); + + if (!client) { + dbus_connection_close(new_connection); + return; + } + + if (s->type == SERVER_TYPE_TCP || s->userdata->module->core->server_type == PA_SERVER_TYPE_SYSTEM) { + /* FIXME: Here we allow anyone from anywhere to access the server, + * anonymously. Access control should be configurable. */ + dbus_connection_set_unix_user_function(new_connection, user_check_cb, NULL, NULL); + dbus_connection_set_allow_anonymous(new_connection, TRUE); + } + + c = pa_xnew(struct connection, 1); + c->server = s; + c->wrap_conn = pa_dbus_wrap_connection_new_from_existing(s->userdata->module->core->mainloop, TRUE, new_connection); + c->client = client; + + c->client->kill = client_kill_cb; + c->client->send_event = client_send_event_cb; + c->client->userdata = c; + + pa_assert_se(dbus_connection_add_filter(new_connection, disconnection_filter_cb, c, NULL)); + + pa_idxset_put(s->userdata->connections, c, NULL); + + pa_assert_se(pa_dbus_protocol_register_connection(s->userdata->dbus_protocol, new_connection, c->client) >= 0); +} + +/* Called by PA mainloop when a D-Bus fd watch event needs handling. */ +static void io_event_cb(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* Called by PA mainloop when a D-Bus timer event needs handling. */ +static void time_event_cb(pa_mainloop_api *mainloop, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval next = *tv; + dbus_timeout_handle(timeout); + + /* restart it for the next scheduled time */ + pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + mainloop->time_restart(e, &next); + } +} + +/* Translates D-Bus fd watch event flags to PA IO event flags. */ +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* Called by D-Bus when a D-Bus fd watch event is added. */ +static dbus_bool_t watch_add_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + ev = mainloop->io_new( + mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), io_event_cb, watch); + + dbus_watch_set_data(watch, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus fd watch event is removed. */ +static void watch_remove_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + if ((ev = dbus_watch_get_data(watch))) + s->userdata->module->core->mainloop->io_free(ev); +} + +/* Called by D-Bus when a D-Bus fd watch event is toggled. */ +static void watch_toggled_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + pa_assert_se(ev = dbus_watch_get_data(watch)); + + /* get_watch_flags() checks if the watch is enabled */ + s->userdata->module->core->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +/* Called by D-Bus when a D-Bus timer event is added. */ +static dbus_bool_t timeout_add_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + struct timeval tv; + + pa_assert(timeout); + pa_assert(s); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + mainloop = s->userdata->module->core->mainloop; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + ev = mainloop->time_new(mainloop, &tv, time_event_cb, timeout); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus timer event is removed. */ +static void timeout_remove_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + if ((ev = dbus_timeout_get_data(timeout))) + s->userdata->module->core->mainloop->time_free(ev); +} + +/* Called by D-Bus when a D-Bus timer event is toggled. */ +static void timeout_toggled_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + mainloop->time_restart(ev, &tv); + } else + mainloop->time_restart(ev, NULL); +} + +static void server_free(struct server *s) { + pa_assert(s); + + if (s->dbus_server) { + dbus_server_disconnect(s->dbus_server); + dbus_server_unref(s->dbus_server); + } + + pa_xfree(s); +} + +static struct server *start_server(struct userdata *u, const char *address, enum server_type type) { + /* XXX: We assume that when we unref the DBusServer instance at module + * shutdown, nobody else holds any references to it. If we stop assuming + * that someday, dbus_server_set_new_connection_function, + * dbus_server_set_watch_functions and dbus_server_set_timeout_functions + * calls should probably register free callbacks, instead of providing NULL + * as they do now. */ + + struct server *s = NULL; + DBusError error; + + pa_assert(u); + pa_assert(address); + + dbus_error_init(&error); + + s = pa_xnew0(struct server, 1); + s->userdata = u; + s->type = type; + s->dbus_server = dbus_server_listen(address, &error); + + if (dbus_error_is_set(&error)) { + pa_log("dbus_server_listen() failed: %s: %s", error.name, error.message); + goto fail; + } + + dbus_server_set_new_connection_function(s->dbus_server, connection_new_cb, s, NULL); + + if (!dbus_server_set_watch_functions(s->dbus_server, watch_add_cb, watch_remove_cb, watch_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_watch_functions() ran out of memory."); + goto fail; + } + + if (!dbus_server_set_timeout_functions(s->dbus_server, timeout_add_cb, timeout_remove_cb, timeout_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_timeout_functions() ran out of memory."); + goto fail; + } + + return s; + +fail: + if (s) + server_free(s); + + dbus_error_free(&error); + + return NULL; +} + +static struct server *start_local_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_get_dbus_address_from_server_type(u->module->core->server_type); + + s = start_server(u, address, SERVER_TYPE_LOCAL); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static struct server *start_tcp_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_sprintf_malloc("tcp:host=%s,port=%u", u->tcp_listen, u->tcp_port); + + s = start_server(u, address, SERVER_TYPE_TCP); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static int get_access_arg(pa_modargs *ma, pa_bool_t *local_access, pa_bool_t *remote_access) { + const char *value = NULL; + + pa_assert(ma); + pa_assert(local_access); + pa_assert(remote_access); + + if (!(value = pa_modargs_get_value(ma, "access", NULL))) + return 0; + + if (!strcmp(value, "local")) { + *local_access = TRUE; + *remote_access = FALSE; + } else if (!strcmp(value, "remote")) { + *local_access = FALSE; + *remote_access = TRUE; + } else if (!strcmp(value, "local,remote")) { + *local_access = TRUE; + *remote_access = TRUE; + } else + return -1; + + return 0; +} + +/* Frees dead client connections. */ +static void cleanup_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) { + struct userdata *u = userdata; + struct connection *conn = NULL; + uint32_t idx; + + PA_IDXSET_FOREACH(conn, u->connections, idx) { + if (!dbus_connection_get_is_connected(pa_dbus_wrap_connection_get(conn->wrap_conn))) { + pa_idxset_remove_by_data(u->connections, conn, NULL); + connection_free(conn); + } + } + + u->module->core->mainloop->defer_enable(e, 0); +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->local_access = TRUE; + u->remote_access = FALSE; + u->tcp_port = PA_DBUS_DEFAULT_PORT; + + if (get_access_arg(ma, &u->local_access, &u->remote_access) < 0) { + pa_log("Invalid access argument: '%s'", pa_modargs_get_value(ma, "access", NULL)); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "tcp_port", &u->tcp_port) < 0 || u->tcp_port < 1 || u->tcp_port > 49150) { + pa_log("Invalid tcp_port argument: '%s'", pa_modargs_get_value(ma, "tcp_port", NULL)); + goto fail; + } + + u->tcp_listen = pa_xstrdup(pa_modargs_get_value(ma, "tcp_listen", "0.0.0.0")); + + if (u->local_access && !(u->local_server = start_local_server(u))) { + pa_log("Starting the local D-Bus server failed."); + goto fail; + } + + if (u->remote_access && !(u->tcp_server = start_tcp_server(u))) { + pa_log("Starting the D-Bus server for remote connections failed."); + goto fail; + } + + u->connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->cleanup_event = m->core->mainloop->defer_new(m->core->mainloop, cleanup_cb, u); + m->core->mainloop->defer_enable(u->cleanup_event, 0); + + u->dbus_protocol = pa_dbus_protocol_get(m->core); + u->core_iface = pa_dbusiface_core_new(m->core); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + struct connection *c; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->core_iface) + pa_dbusiface_core_free(u->core_iface); + + while ((c = pa_idxset_steal_first(u->connections, NULL))) + connection_free(c); + + pa_idxset_free(u->connections, NULL, NULL); + + /* This must not be called before the connections are freed, because if + * there are any connections left, they will emit the + * org.freedesktop.DBus.Local.Disconnected signal, and + * disconnection_filter_cb() will be called. disconnection_filter_cb() then + * tries to enable the defer event, and if it's already freed, an assertion + * will be hit in mainloop.c. */ + if (u->cleanup_event) + m->core->mainloop->defer_free(u->cleanup_event); + + if (u->tcp_server) + server_free(u->tcp_server); + + if (u->local_server) + server_free(u->local_server); + + if (u->dbus_protocol) + pa_dbus_protocol_unref(u->dbus_protocol); + + pa_xfree(u->tcp_listen); + pa_xfree(u); + m->userdata = NULL; +} diff --git a/src/modules/echo-cancel/adrian-aec.c b/src/modules/echo-cancel/adrian-aec.c new file mode 100644 index 00000000..e969e8c5 --- /dev/null +++ b/src/modules/echo-cancel/adrian-aec.c @@ -0,0 +1,275 @@ +/* aec.cpp + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * + * Acoustic Echo Cancellation NLMS-pw algorithm + * + * Version 0.3 filter created with www.dsptutor.freeuk.com + * Version 0.3.1 Allow change of stability parameter delta + * Version 0.4 Leaky Normalized LMS - pre whitening algorithm + */ + +#include <math.h> +#include <string.h> +#include <stdint.h> + +#include <pulse/xmalloc.h> + +#include "adrian-aec.h" + +#ifndef DISABLE_ORC +#include "adrian-aec-orc-gen.h" +#endif + +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +/* Vector Dot Product */ +static REAL dotp(REAL a[], REAL b[]) +{ + REAL sum0 = 0.0, sum1 = 0.0; + int j; + + for (j = 0; j < NLMS_LEN; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * b[j]; + sum1 += a[j + 1] * b[j + 1]; + } + return sum0 + sum1; +} + +static REAL dotp_sse(REAL a[], REAL b[]) +{ +#ifdef __SSE__ + /* This is taken from speex's inner product implementation */ + int j; + REAL sum; + __m128 acc = _mm_setzero_ps(); + + for (j=0;j<NLMS_LEN;j+=8) + { + acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j), _mm_loadu_ps(b+j))); + acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j+4), _mm_loadu_ps(b+j+4))); + } + acc = _mm_add_ps(acc, _mm_movehl_ps(acc, acc)); + acc = _mm_add_ss(acc, _mm_shuffle_ps(acc, acc, 0x55)); + _mm_store_ss(&sum, acc); + + return sum; +#else + return dotp(a, b); +#endif +} + + +AEC* AEC_init(int RATE, int have_vector) +{ + AEC *a = pa_xnew(AEC, 1); + a->hangover = 0; + memset(a->x, 0, sizeof(a->x)); + memset(a->xf, 0, sizeof(a->xf)); + memset(a->w_arr, 0, sizeof(a->w_arr)); + a->j = NLMS_EXT; + a->delta = 0.0f; + AEC_setambient(a, NoiseFloor); + a->dfast = a->dslow = M75dB_PCM; + a->xfast = a->xslow = M80dB_PCM; + a->gain = 1.0f; + a->Fx = IIR1_init(2000.0f/RATE); + a->Fe = IIR1_init(2000.0f/RATE); + a->cutoff = FIR_HP_300Hz_init(); + a->acMic = IIR_HP_init(); + a->acSpk = IIR_HP_init(); + + a->aes_y2 = M0dB; + + a->fdwdisplay = -1; + a->dumpcnt = 0; + memset(a->ws, 0, sizeof(a->ws)); + + if (have_vector) { + /* Get a 16-byte aligned location */ + a->w = (REAL *) (((uintptr_t) a->w_arr) + (((uintptr_t) a->w_arr) % 16)); + a->dotp = dotp_sse; + } else { + /* We don't care about alignment, just use the array as-is */ + a->w = a->w_arr; + a->dotp = dotp; + } + + return a; +} + +// Adrian soft decision DTD +// (Dual Average Near-End to Far-End signal Ratio DTD) +// This algorithm uses exponential smoothing with differnt +// ageing parameters to get fast and slow near-end and far-end +// signal averages. The ratio of NFRs term +// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize +// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is +// mapped to 1.0 with a limited linear function. +static float AEC_dtd(AEC *a, REAL d, REAL x) +{ + float ratio, stepsize; + + // fast near-end and far-end average + a->dfast += ALPHAFAST * (fabsf(d) - a->dfast); + a->xfast += ALPHAFAST * (fabsf(x) - a->xfast); + + // slow near-end and far-end average + a->dslow += ALPHASLOW * (fabsf(d) - a->dslow); + a->xslow += ALPHASLOW * (fabsf(x) - a->xslow); + + if (a->xfast < M70dB_PCM) { + return 0.0; // no Spk signal + } + + if (a->dfast < M70dB_PCM) { + return 0.0; // no Mic signal + } + + // ratio of NFRs + ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast); + + // Linear interpolation with clamping at the limits + if (ratio < STEPX1) + stepsize = STEPY1; + else if (ratio > STEPX2) + stepsize = STEPY2; + else + stepsize = STEPY1 + (STEPY2 - STEPY1) * (ratio - STEPX1) / (STEPX2 - STEPX1); + + return stepsize; +} + + +static void AEC_leaky(AEC *a) +// The xfast signal is used to charge the hangover timer to Thold. +// When hangover expires (no Spk signal for some time) the vector w +// is erased. This is my implementation of Leaky NLMS. +{ + if (a->xfast >= M70dB_PCM) { + // vector w is valid for hangover Thold time + a->hangover = Thold; + } else { + if (a->hangover > 1) { + --(a->hangover); + } else if (1 == a->hangover) { + --(a->hangover); + // My Leaky NLMS is to erase vector w when hangover expires + memset(a->w, 0, sizeof(a->w)); + } + } +} + + +#if 0 +void AEC::openwdisplay() { + // open TCP connection to program wdisplay.tcl + fdwdisplay = socket_async("127.0.0.1", 50999); +}; +#endif + + +static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize) +{ + REAL e; + REAL ef; + a->x[a->j] = x_; + a->xf[a->j] = IIR1_highpass(a->Fx, x_); // pre-whitening of x + + // calculate error value + // (mic signal - estimated mic signal from spk signal) + e = d; + if (a->hangover > 0) { + e -= a->dotp(a->w, a->x + a->j); + } + ef = IIR1_highpass(a->Fe, e); // pre-whitening of e + + // optimize: iterative dotp(xf, xf) + a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]); + + if (stepsize > 0.0) { + // calculate variable step size + REAL mikro_ef = stepsize * ef / a->dotp_xf_xf; + +#ifdef DISABLE_ORC + // update tap weights (filter learning) + int i; + for (i = 0; i < NLMS_LEN; i += 2) { + // optimize: partial loop unrolling + a->w[i] += mikro_ef * a->xf[i + a->j]; + a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1]; + } +#else + update_tap_weights(a->w, &a->xf[a->j], mikro_ef, NLMS_LEN); +#endif + } + + if (--(a->j) < 0) { + // optimize: decrease number of memory copies + a->j = NLMS_EXT; + memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL)); + memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL)); + } + + // Saturation + if (e > MAXPCM) { + return MAXPCM; + } else if (e < -MAXPCM) { + return -MAXPCM; + } else { + return e; + } +} + + +int AEC_doAEC(AEC *a, int d_, int x_) +{ + REAL d = (REAL) d_; + REAL x = (REAL) x_; + + // Mic Highpass Filter - to remove DC + d = IIR_HP_highpass(a->acMic, d); + + // Mic Highpass Filter - cut-off below 300Hz + d = FIR_HP_300Hz_highpass(a->cutoff, d); + + // Amplify, for e.g. Soundcards with -6dB max. volume + d *= a->gain; + + // Spk Highpass Filter - to remove DC + x = IIR_HP_highpass(a->acSpk, x); + + // Double Talk Detector + a->stepsize = AEC_dtd(a, d, x); + + // Leaky (ageing of vector w) + AEC_leaky(a); + + // Acoustic Echo Cancellation + d = AEC_nlms_pw(a, d, x, a->stepsize); + +#if 0 + if (fdwdisplay >= 0) { + if (++dumpcnt >= (WIDEB*RATE/10)) { + // wdisplay creates 10 dumps per seconds = large CPU load! + dumpcnt = 0; + write(fdwdisplay, ws, DUMP_LEN*sizeof(float)); + // we don't check return value. This is not production quality!!! + memset(ws, 0, sizeof(ws)); + } else { + int i; + for (i = 0; i < DUMP_LEN; i += 2) { + // optimize: partial loop unrolling + ws[i] += w[i]; + ws[i + 1] += w[i + 1]; + } + } + } +#endif + + return (int) d; +} diff --git a/src/modules/echo-cancel/adrian-aec.h b/src/modules/echo-cancel/adrian-aec.h new file mode 100644 index 00000000..d024b3c5 --- /dev/null +++ b/src/modules/echo-cancel/adrian-aec.h @@ -0,0 +1,382 @@ +/* aec.h + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * Author: Andre Adrian + * + * Acoustic Echo Cancellation Leaky NLMS-pw algorithm + * + * Version 0.3 filter created with www.dsptutor.freeuk.com + * Version 0.3.1 Allow change of stability parameter delta + * Version 0.4 Leaky Normalized LMS - pre whitening algorithm + */ + +#ifndef _AEC_H /* include only once */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +#define WIDEB 2 + +// use double if your CPU does software-emulation of float +#define REAL float + +/* dB Values */ +#define M0dB 1.0f +#define M3dB 0.71f +#define M6dB 0.50f +#define M9dB 0.35f +#define M12dB 0.25f +#define M18dB 0.125f +#define M24dB 0.063f + +/* dB values for 16bit PCM */ +/* MxdB_PCM = 32767 * 10 ^(x / 20) */ +#define M10dB_PCM 10362.0f +#define M20dB_PCM 3277.0f +#define M25dB_PCM 1843.0f +#define M30dB_PCM 1026.0f +#define M35dB_PCM 583.0f +#define M40dB_PCM 328.0f +#define M45dB_PCM 184.0f +#define M50dB_PCM 104.0f +#define M55dB_PCM 58.0f +#define M60dB_PCM 33.0f +#define M65dB_PCM 18.0f +#define M70dB_PCM 10.0f +#define M75dB_PCM 6.0f +#define M80dB_PCM 3.0f +#define M85dB_PCM 2.0f +#define M90dB_PCM 1.0f + +#define MAXPCM 32767.0f + +/* Design constants (Change to fine tune the algorithms */ + +/* The following values are for hardware AEC and studio quality + * microphone */ + +/* NLMS filter length in taps (samples). A longer filter length gives + * better Echo Cancellation, but maybe slower convergence speed and + * needs more CPU power (Order of NLMS is linear) */ +#define NLMS_LEN (100*WIDEB*8) + +/* Vector w visualization length in taps (samples). + * Must match argv value for wdisplay.tcl */ +#define DUMP_LEN (40*WIDEB*8) + +/* minimum energy in xf. Range: M70dB_PCM to M50dB_PCM. Should be equal + * to microphone ambient Noise level */ +#define NoiseFloor M55dB_PCM + +/* Leaky hangover in taps. + */ +#define Thold (60 * WIDEB * 8) + +// Adrian soft decision DTD +// left point. X is ratio, Y is stepsize +#define STEPX1 1.0 +#define STEPY1 1.0 +// right point. STEPX2=2.0 is good double talk, 3.0 is good single talk. +#define STEPX2 2.5 +#define STEPY2 0 +#define ALPHAFAST (1.0f / 100.0f) +#define ALPHASLOW (1.0f / 20000.0f) + + + +/* Ageing multiplier for LMS memory vector w */ +#define Leaky 0.9999f + +/* Double Talk Detector Speaker/Microphone Threshold. Range <=1 + * Large value (M0dB) is good for Single-Talk Echo cancellation, + * small value (M12dB) is good for Doulbe-Talk AEC */ +#define GeigelThreshold M6dB + +/* for Non Linear Processor. Range >0 to 1. Large value (M0dB) is good + * for Double-Talk, small value (M12dB) is good for Single-Talk */ +#define NLPAttenuation M12dB + +/* Below this line there are no more design constants */ + +typedef struct IIR_HP IIR_HP; + +/* Exponential Smoothing or IIR Infinite Impulse Response Filter */ +struct IIR_HP { + REAL x; +}; + +static IIR_HP* IIR_HP_init(void) { + IIR_HP *i = pa_xnew(IIR_HP, 1); + i->x = 0.0f; + return i; + } + +static REAL IIR_HP_highpass(IIR_HP *i, REAL in) { + const REAL a0 = 0.01f; /* controls Transfer Frequency */ + /* Highpass = Signal - Lowpass. Lowpass = Exponential Smoothing */ + i->x += a0 * (in - i->x); + return in - i->x; + }; + +typedef struct FIR_HP_300Hz FIR_HP_300Hz; + +#if WIDEB==1 +/* 17 taps FIR Finite Impulse Response filter + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +class FIR_HP_300Hz { + REAL z[18]; + +public: + FIR_HP_300Hz() { + memset(this, 0, sizeof(FIR_HP_300Hz)); + } + + REAL highpass(REAL in) { + const REAL a[18] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 300.0 - 4000.0 Hz, Order: 16 + // Transition band: 75.0 Hz, Stopband attenuation: 10.0 dB + -0.034870606, -0.039650206, -0.044063766, -0.04800318, + -0.051370874, -0.054082647, -0.056070227, -0.057283327, + 0.8214126, -0.057283327, -0.056070227, -0.054082647, + -0.051370874, -0.04800318, -0.044063766, -0.039650206, + -0.034870606, 0.0 + }; + memmove(z + 1, z, 17 * sizeof(REAL)); + z[0] = in; + REAL sum0 = 0.0, sum1 = 0.0; + int j; + + for (j = 0; j < 18; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * z[j]; + sum1 += a[j + 1] * z[j + 1]; + } + return sum0 + sum1; + } +}; + +#else + +/* 35 taps FIR Finite Impulse Response filter + * Passband 150Hz to 4kHz for 8kHz sample rate, 300Hz to 8kHz for 16kHz + * sample rate. + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +struct FIR_HP_300Hz { + REAL z[36]; +}; + +static FIR_HP_300Hz* FIR_HP_300Hz_init(void) { + FIR_HP_300Hz *ret = pa_xnew(FIR_HP_300Hz, 1); + memset(ret, 0, sizeof(FIR_HP_300Hz)); + return ret; + } + +static REAL FIR_HP_300Hz_highpass(FIR_HP_300Hz *f, REAL in) { + REAL sum0 = 0.0, sum1 = 0.0; + int j; + const REAL a[36] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 150.0 - 4000.0 Hz, Order: 34 + // Transition band: 34.0 Hz, Stopband attenuation: 10.0 dB + -0.016165324, -0.017454365, -0.01871232, -0.019931411, + -0.021104068, -0.022222936, -0.02328091, -0.024271343, + -0.025187887, -0.02602462, -0.026776174, -0.027437767, + -0.028004972, -0.028474221, -0.028842418, -0.029107114, + -0.02926664, 0.8524841, -0.02926664, -0.029107114, + -0.028842418, -0.028474221, -0.028004972, -0.027437767, + -0.026776174, -0.02602462, -0.025187887, -0.024271343, + -0.02328091, -0.022222936, -0.021104068, -0.019931411, + -0.01871232, -0.017454365, -0.016165324, 0.0 + }; + memmove(f->z + 1, f->z, 35 * sizeof(REAL)); + f->z[0] = in; + + for (j = 0; j < 36; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * f->z[j]; + sum1 += a[j + 1] * f->z[j + 1]; + } + return sum0 + sum1; + } +#endif + +typedef struct IIR1 IIR1; + +/* Recursive single pole IIR Infinite Impulse response High-pass filter + * + * Reference: The Scientist and Engineer's Guide to Digital Processing + * + * output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1] + * + * X = exp(-2.0 * pi * Fc) + * A0 = (1 + X) / 2 + * A1 = -(1 + X) / 2 + * B1 = X + * Fc = cutoff freq / sample rate + */ +struct IIR1 { + REAL in0, out0; + REAL a0, a1, b1; +}; + +#if 0 + IIR1() { + memset(this, 0, sizeof(IIR1)); + } +#endif + +static IIR1* IIR1_init(REAL Fc) { + IIR1 *i = pa_xnew(IIR1, 1); + i->b1 = expf(-2.0f * M_PI * Fc); + i->a0 = (1.0f + i->b1) / 2.0f; + i->a1 = -(i->a0); + i->in0 = 0.0f; + i->out0 = 0.0f; + return i; + } + +static REAL IIR1_highpass(IIR1 *i, REAL in) { + REAL out = i->a0 * in + i->a1 * i->in0 + i->b1 * i->out0; + i->in0 = in; + i->out0 = out; + return out; + } + + +#if 0 +/* Recursive two pole IIR Infinite Impulse Response filter + * Coefficients calculated with + * http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html + */ +class IIR2 { + REAL x[2], y[2]; + +public: + IIR2() { + memset(this, 0, sizeof(IIR2)); + } + + REAL highpass(REAL in) { + // Butterworth IIR filter, Filter type: HP + // Passband: 2000 - 4000.0 Hz, Order: 2 + const REAL a[] = { 0.29289323f, -0.58578646f, 0.29289323f }; + const REAL b[] = { 1.3007072E-16f, 0.17157288f }; + REAL out = + a[0] * in + a[1] * x[0] + a[2] * x[1] - b[0] * y[0] - b[1] * y[1]; + + x[1] = x[0]; + x[0] = in; + y[1] = y[0]; + y[0] = out; + return out; + } +}; +#endif + + +// Extention in taps to reduce mem copies +#define NLMS_EXT (10*8) + +// block size in taps to optimize DTD calculation +#define DTD_LEN 16 + +typedef struct AEC AEC; + +struct AEC { + // Time domain Filters + IIR_HP *acMic, *acSpk; // DC-level remove Highpass) + FIR_HP_300Hz *cutoff; // 150Hz cut-off Highpass + REAL gain; // Mic signal amplify + IIR1 *Fx, *Fe; // pre-whitening Highpass for x, e + + // Adrian soft decision DTD (Double Talk Detector) + REAL dfast, xfast; + REAL dslow, xslow; + + // NLMS-pw + REAL x[NLMS_LEN + NLMS_EXT]; // tap delayed loudspeaker signal + REAL xf[NLMS_LEN + NLMS_EXT]; // pre-whitening tap delayed signal + REAL w_arr[NLMS_LEN + (16 / sizeof(REAL))]; // tap weights + REAL *w; // this will be a 16-byte aligned pointer into w_arr + int j; // optimize: less memory copies + double dotp_xf_xf; // double to avoid loss of precision + float delta; // noise floor to stabilize NLMS + + // AES + float aes_y2; // not in use! + + // w vector visualization + REAL ws[DUMP_LEN]; // tap weights sums + int fdwdisplay; // TCP file descriptor + int dumpcnt; // wdisplay output counter + + // variables are public for visualization + int hangover; + float stepsize; + + // vfuncs that are picked based on processor features available + REAL (*dotp) (REAL[], REAL[]); +}; + +/* Double-Talk Detector + * + * in d: microphone sample (PCM as REALing point value) + * in x: loudspeaker sample (PCM as REALing point value) + * return: from 0 for doubletalk to 1.0 for single talk + */ +static float AEC_dtd(AEC *a, REAL d, REAL x); + +static void AEC_leaky(AEC *a); + +/* Normalized Least Mean Square Algorithm pre-whitening (NLMS-pw) + * The LMS algorithm was developed by Bernard Widrow + * book: Haykin, Adaptive Filter Theory, 4. edition, Prentice Hall, 2002 + * + * in d: microphone sample (16bit PCM value) + * in x_: loudspeaker sample (16bit PCM value) + * in stepsize: NLMS adaptation variable + * return: echo cancelled microphone sample + */ +static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize); + + AEC* AEC_init(int RATE, int have_vector); + +/* Acoustic Echo Cancellation and Suppression of one sample + * in d: microphone signal with echo + * in x: loudspeaker signal + * return: echo cancelled microphone signal + */ + int AEC_doAEC(AEC *a, int d_, int x_); + +PA_GCC_UNUSED static float AEC_getambient(AEC *a) { + return a->dfast; + }; +static void AEC_setambient(AEC *a, float Min_xf) { + a->dotp_xf_xf -= a->delta; // subtract old delta + a->delta = (NLMS_LEN-1) * Min_xf * Min_xf; + a->dotp_xf_xf += a->delta; // add new delta + }; +PA_GCC_UNUSED static void AEC_setgain(AEC *a, float gain_) { + a->gain = gain_; + }; +#if 0 + void AEC_openwdisplay(AEC *a); +#endif +PA_GCC_UNUSED static void AEC_setaes(AEC *a, float aes_y2_) { + a->aes_y2 = aes_y2_; + }; + +#define _AEC_H +#endif diff --git a/src/modules/echo-cancel/adrian-aec.orc b/src/modules/echo-cancel/adrian-aec.orc new file mode 100644 index 00000000..80547723 --- /dev/null +++ b/src/modules/echo-cancel/adrian-aec.orc @@ -0,0 +1,8 @@ +.function update_tap_weights +.dest 4 w float +.source 4 xf float +.floatparam 4 mikro_ef +.temp 4 tmp float + +mulf tmp, mikro_ef, xf +addf w, w, tmp diff --git a/src/modules/echo-cancel/adrian-license.txt b/src/modules/echo-cancel/adrian-license.txt new file mode 100644 index 00000000..7c06efd0 --- /dev/null +++ b/src/modules/echo-cancel/adrian-license.txt @@ -0,0 +1,17 @@ + Copyright (C) DFS Deutsche Flugsicherung (2004). All Rights Reserved. + + You are allowed to use this source code in any open source or closed + source software you want. You are allowed to use the algorithms for a + hardware solution. You are allowed to modify the source code. + You are not allowed to remove the name of the author from this memo or + from the source code files. You are not allowed to monopolize the + source code or the algorithms behind the source code as your + intellectual property. This source code is free of royalty and comes + with no warranty. + +--- The following does not apply to the PulseAudio module --- + + Please see g711/gen-lic.txt for the ITU-T G.711 codec copyright. + Please see gsm/gen-lic.txt for the ITU-T GSM codec copyright. + Please see ilbc/COPYRIGHT and ilbc/NOTICE for the IETF iLBC codec + copyright. diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c new file mode 100644 index 00000000..ab3858a4 --- /dev/null +++ b/src/modules/echo-cancel/adrian.c @@ -0,0 +1,117 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + Contributor: Wim Taymans <wim.taymans@gmail.com> + + The actual implementation is taken from the sources at + http://andreadrian.de/intercom/ - for the license, look for + adrian-license.txt in the same directory as this file. + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/modargs.h> + +#include "echo-cancel.h" + +/* should be between 10-20 ms */ +#define DEFAULT_FRAME_SIZE_MS 20 + +static const char* const valid_modargs[] = { + "frame_size_ms", + NULL +}; + +static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map) +{ + source_ss->format = PA_SAMPLE_S16NE; + source_ss->channels = 1; + pa_channel_map_init_mono(source_map); + + *sink_ss = *source_ss; + *sink_map = *source_map; +} + +pa_bool_t pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + uint32_t *blocksize, const char *args) +{ + int framelen, rate, have_vector = 0; + uint32_t frame_size_ms; + pa_modargs *ma; + + if (!(ma = pa_modargs_new(args, valid_modargs))) { + pa_log("Failed to parse submodule arguments."); + goto fail; + } + + frame_size_ms = DEFAULT_FRAME_SIZE_MS; + if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) { + pa_log("Invalid frame_size_ms specification"); + goto fail; + } + + pa_adrian_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map); + + rate = source_ss->rate; + framelen = (rate * frame_size_ms) / 1000; + + *blocksize = ec->params.priv.adrian.blocksize = framelen * pa_frame_size (source_ss); + + pa_log_debug ("Using framelen %d, blocksize %u, channels %d, rate %d", framelen, ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate); + + /* For now we only support SSE */ + if (c->cpu_info.cpu_type == PA_CPU_X86 && (c->cpu_info.flags.x86 & PA_CPU_X86_SSE)) + have_vector = 1; + + ec->params.priv.adrian.aec = AEC_init(rate, have_vector); + if (!ec->params.priv.adrian.aec) + goto fail; + + pa_modargs_free(ma); + return TRUE; + +fail: + if (ma) + pa_modargs_free(ma); + return FALSE; +} + +void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { + unsigned int i; + + for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) { + /* We know it's S16NE mono data */ + int r = *(int16_t *)(rec + i); + int p = *(int16_t *)(play + i); + *(int16_t *)(out + i) = (int16_t) AEC_doAEC(ec->params.priv.adrian.aec, r, p); + } +} + +void pa_adrian_ec_done(pa_echo_canceller *ec) { + pa_xfree(ec->params.priv.adrian.aec); + ec->params.priv.adrian.aec = NULL; +} diff --git a/src/modules/echo-cancel/adrian.h b/src/modules/echo-cancel/adrian.h new file mode 100644 index 00000000..639fa9ec --- /dev/null +++ b/src/modules/echo-cancel/adrian.h @@ -0,0 +1,31 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + The actual implementation is taken from the sources at + http://andreadrian.de/intercom/ - for the license, look for + adrian-license.txt in the same directory as this file. + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* Forward declarations */ + +typedef struct AEC AEC; + +AEC* AEC_init(int RATE, int have_vector); +int AEC_doAEC(AEC *a, int d_, int x_); diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h new file mode 100644 index 00000000..aa40adce --- /dev/null +++ b/src/modules/echo-cancel/echo-cancel.h @@ -0,0 +1,90 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +#include <speex/speex_echo.h> +#include <speex/speex_preprocess.h> +#include "adrian.h" + +/* Common data structures */ + +typedef struct pa_echo_canceller_params pa_echo_canceller_params; + +struct pa_echo_canceller_params { + union { + struct { + SpeexEchoState *state; + } speex; + struct { + uint32_t blocksize; + AEC *aec; + } adrian; + /* each canceller-specific structure goes here */ + } priv; +}; + +typedef struct pa_echo_canceller pa_echo_canceller; + +struct pa_echo_canceller { + pa_bool_t (*init) (pa_core *c, + pa_echo_canceller *ec, + pa_sample_spec *source_ss, + pa_channel_map *source_map, + pa_sample_spec *sink_ss, + pa_channel_map *sink_map, + uint32_t *blocksize, + const char *args); + void (*run) (pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out); + void (*done) (pa_echo_canceller *ec); + + pa_echo_canceller_params params; + + pa_bool_t agc; + pa_bool_t denoise; + pa_bool_t echo_suppress; + int32_t echo_suppress_attenuation; + int32_t echo_suppress_attenuation_active; + SpeexPreprocessState *pp_state; +}; + +/* Speex canceller functions */ +pa_bool_t pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + uint32_t *blocksize, const char *args); +void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out); +void pa_speex_ec_done(pa_echo_canceller *ec); + +/* Adrian Andre's echo canceller */ +pa_bool_t pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + uint32_t *blocksize, const char *args); +void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out); +void pa_adrian_ec_done(pa_echo_canceller *ec); diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c new file mode 100644 index 00000000..b84bf1db --- /dev/null +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -0,0 +1,1778 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Wim Taymans <wim.taymans@gmail.com> + + Based on module-virtual-sink.c + module-virtual-source.c + module-loopback.c + + Copyright 2010 Intel Corporation + Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include "echo-cancel.h" + +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> +#include <pulse/timeval.h> +#include <pulse/rtclock.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/macro.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/ltdl-helper.h> + +#include "module-echo-cancel-symdef.h" + +PA_MODULE_AUTHOR("Wim Taymans"); +PA_MODULE_DESCRIPTION("Echo Cancelation"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + _("source_name=<name for the source> " + "source_properties=<properties for the source> " + "source_master=<name of source to filter> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "sink_master=<name of sink to filter> " + "adjust_time=<how often to readjust rates in s> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "aec_method=<implementation to use> " + "aec_args=<parameters for the AEC engine> " + "agc=<perform automagic gain control?> " + "denoise=<apply denoising?> " + "echo_suppress=<perform residual echo suppression? (only with the speex canceller)> " + "echo_suppress_attenuation=<dB value of residual echo attenuation> " + "echo_suppress_attenuation_active=<dB value of residual echo attenuation when near end is active> " + "save_aec=<save AEC data in /tmp> " + "autoloaded=<set if this module is being loaded automatically> " + )); + +/* NOTE: Make sure the enum and ec_table are maintained in the correct order */ +typedef enum { + PA_ECHO_CANCELLER_INVALID = -1, + PA_ECHO_CANCELLER_SPEEX = 0, + PA_ECHO_CANCELLER_ADRIAN, +} pa_echo_canceller_method_t; + +#define DEFAULT_ECHO_CANCELLER "speex" + +static const pa_echo_canceller ec_table[] = { + { + /* Speex */ + .init = pa_speex_ec_init, + .run = pa_speex_ec_run, + .done = pa_speex_ec_done, + }, + { + /* Adrian Andre's NLMS implementation */ + .init = pa_adrian_ec_init, + .run = pa_adrian_ec_run, + .done = pa_adrian_ec_done, + }, +}; + +#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC) +#define DEFAULT_AGC_ENABLED FALSE +#define DEFAULT_DENOISE_ENABLED FALSE +#define DEFAULT_ECHO_SUPPRESS_ENABLED FALSE +#define DEFAULT_ECHO_SUPPRESS_ATTENUATION 0 +#define DEFAULT_SAVE_AEC 0 +#define DEFAULT_AUTOLOADED FALSE + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +/* This module creates a new (virtual) source and sink. + * + * The data sent to the new sink is kept in a memblockq before being + * forwarded to the real sink_master. + * + * Data read from source_master is matched against the saved sink data and + * echo canceled data is then pushed onto the new source. + * + * Both source and sink masters have their own threads to push/pull data + * respectively. We however perform all our actions in the source IO thread. + * To do this we send all played samples to the source IO thread where they + * are then pushed into the memblockq. + * + * Alignment is performed in two steps: + * + * 1) when something happens that requires quick adjustement of the alignment of + * capture and playback samples, we perform a resync. This adjusts the + * position in the playback memblock to the requested sample. Quick + * adjustements include moving the playback samples before the capture + * samples (because else the echo canceler does not work) or when the + * playback pointer drifts too far away. + * + * 2) periodically check the difference between capture and playback. we use a + * low and high watermark for adjusting the alignment. playback should always + * be before capture and the difference should not be bigger than one frame + * size. We would ideally like to resample the sink_input but most driver + * don't give enough accuracy to be able to do that right now. + */ + +struct snapshot { + pa_usec_t sink_now; + pa_usec_t sink_latency; + size_t sink_delay; + int64_t send_counter; + + pa_usec_t source_now; + pa_usec_t source_latency; + size_t source_delay; + int64_t recv_counter; + size_t rlen; + size_t plen; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_bool_t autoloaded; + uint32_t save_aec; + + pa_echo_canceller *ec; + uint32_t blocksize; + + pa_bool_t need_realign; + + /* to wakeup the source I/O thread */ + pa_bool_t in_push; + pa_asyncmsgq *asyncmsgq; + pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; + + pa_source *source; + pa_bool_t source_auto_desc; + pa_source_output *source_output; + pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */ + size_t source_skip; + + pa_sink *sink; + pa_bool_t sink_auto_desc; + pa_sink_input *sink_input; + pa_memblockq *sink_memblockq; + int64_t send_counter; /* updated in sink IO thread */ + int64_t recv_counter; + size_t sink_skip; + + pa_atomic_t request_resync; + + int active_mask; + pa_time_event *time_event; + pa_usec_t adjust_time; + + FILE *captured_file; + FILE *played_file; + FILE *canceled_file; +}; + +static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot); + +static const char* const valid_modargs[] = { + "source_name", + "source_properties", + "source_master", + "sink_name", + "sink_properties", + "sink_master", + "adjust_time", + "format", + "rate", + "channels", + "channel_map", + "aec_method", + "aec_args", + "agc", + "denoise", + "echo_suppress", + "echo_suppress_attenuation", + "echo_suppress_attenuation_active", + "save_aec", + "autoloaded", + NULL +}; + +enum { + SOURCE_OUTPUT_MESSAGE_POST = PA_SOURCE_OUTPUT_MESSAGE_MAX, + SOURCE_OUTPUT_MESSAGE_REWIND, + SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, + SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME +}; + +enum { + SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT +}; + +static int64_t calc_diff(struct userdata *u, struct snapshot *snapshot) { + int64_t buffer, diff_time, buffer_latency; + + /* get the number of samples between capture and playback */ + if (snapshot->plen > snapshot->rlen) + buffer = snapshot->plen - snapshot->rlen; + else + buffer = 0; + + buffer += snapshot->source_delay + snapshot->sink_delay; + + /* add the amount of samples not yet transfered to the source context */ + if (snapshot->recv_counter <= snapshot->send_counter) + buffer += (int64_t) (snapshot->send_counter - snapshot->recv_counter); + else + buffer += PA_CLIP_SUB(buffer, (int64_t) (snapshot->recv_counter - snapshot->send_counter)); + + /* convert to time */ + buffer_latency = pa_bytes_to_usec(buffer, &u->source_output->sample_spec); + + /* capture and playback samples are perfectly aligned when diff_time is 0 */ + diff_time = (snapshot->sink_now + snapshot->sink_latency - buffer_latency) - + (snapshot->source_now - snapshot->source_latency); + + pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld %lld", (long long) diff_time, + (long long) snapshot->sink_latency, + (long long) buffer_latency, (long long) snapshot->source_latency, + (long long) snapshot->source_delay, (long long) snapshot->sink_delay, + (long long) (snapshot->send_counter - snapshot->recv_counter), + (long long) (snapshot->sink_now - snapshot->source_now)); + + return diff_time; +} + +/* Called from main context */ +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + uint32_t old_rate, base_rate, new_rate; + int64_t diff_time; + /*size_t fs*/ + struct snapshot latency_snapshot; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + pa_assert_ctl_context(); + + if (u->active_mask != 3) + return; + + /* update our snapshots */ + pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL); + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL); + + /* calculate drift between capture and playback */ + diff_time = calc_diff(u, &latency_snapshot); + + /*fs = pa_frame_size(&u->source_output->sample_spec);*/ + old_rate = u->sink_input->sample_spec.rate; + base_rate = u->source_output->sample_spec.rate; + + if (diff_time < 0) { + /* recording before playback, we need to adjust quickly. The echo + * canceler does not work in this case. */ + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME, + NULL, diff_time, NULL, NULL); + /*new_rate = base_rate - ((pa_usec_to_bytes(-diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;*/ + new_rate = base_rate; + } + else { + if (diff_time > 1000) { + /* diff too big, quickly adjust */ + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME, + NULL, diff_time, NULL, NULL); + } + + /* recording behind playback, we need to slowly adjust the rate to match */ + /*new_rate = base_rate + ((pa_usec_to_bytes(diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;*/ + + /* assume equal samplerates for now */ + new_rate = base_rate; + } + + /* make sure we don't make too big adjustements because that sounds horrible */ + if (new_rate > base_rate * 1.1 || new_rate < base_rate * 0.9) + new_rate = base_rate; + + if (new_rate != old_rate) { + pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate); + + pa_sink_input_set_rate(u->sink_input, new_rate); + } + + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); +} + +/* Called from source I/O thread context */ +static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + /* The source is _put() before the source output is, so let's + * make sure we don't access it in that time. Also, the + * source output is first shut down, the source second. */ + if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master source */ + pa_source_get_latency_within_thread(u->source_output->source) + + /* Add the latency internal to our source output on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) + + /* and the buffering we do on the source */ + pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec); + + return 0; + + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +/* Called from sink I/O thread context */ +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + + +/* Called from main context */ +static int source_set_state_cb(pa_source *s, pa_source_state_t state) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(state) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return 0; + + pa_log_debug("Source state %d %d", state, u->active_mask); + + if (state == PA_SOURCE_RUNNING) { + /* restart timer when both sink and source are active */ + u->active_mask |= 1; + if (u->active_mask == 3) + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); + + pa_atomic_store(&u->request_resync, 1); + pa_source_output_cork(u->source_output, FALSE); + } else if (state == PA_SOURCE_SUSPENDED) { + u->active_mask &= ~1; + pa_source_output_cork(u->source_output, TRUE); + } + return 0; +} + +/* Called from main context */ +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + + pa_log_debug("Sink state %d %d", state, u->active_mask); + + if (state == PA_SINK_RUNNING) { + /* restart timer when both sink and source are active */ + u->active_mask |= 2; + if (u->active_mask == 3) + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); + + pa_atomic_store(&u->request_resync, 1); + pa_sink_input_cork(u->sink_input, FALSE); + } else if (state == PA_SINK_SUSPENDED) { + u->active_mask &= ~2; + pa_sink_input_cork(u->sink_input, TRUE); + } + return 0; +} + +/* Called from I/O thread context */ +static void source_update_requested_latency_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) + return; + + pa_log_debug("Source update requested latency"); + + /* Just hand this one over to the master source */ + pa_source_output_set_requested_latency_within_thread( + u->source_output, + pa_source_get_requested_latency_within_thread(s)); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + pa_log_debug("Sink update requested latency"); + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from I/O thread context */ +static void sink_request_rewind_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + pa_log_debug("Sink request rewind %lld", (long long) s->thread_info.rewind_nbytes); + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, + s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE); +} + +/* Called from main context */ +static void source_set_volume_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +static void source_get_volume_cb(pa_source *s) { + struct userdata *u; + pa_cvolume v; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_get_volume(u->source_output, &v, TRUE); + + if (pa_cvolume_equal(&s->real_volume, &v)) + /* no change */ + return; + + s->real_volume = v; + pa_source_set_soft_volume(s, NULL); +} + +/* Called from main context */ +static void source_set_mute_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); +} + +/* Called from main context */ +static void source_get_mute_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_get_mute(u->source_output); +} + +/* must be called from the input thread context */ +static void apply_diff_time(struct userdata *u, int64_t diff_time) { + int64_t diff; + + if (diff_time < 0) { + diff = pa_usec_to_bytes(-diff_time, &u->source_output->sample_spec); + + if (diff > 0) { + /* add some extra safety samples to compensate for jitter in the + * timings */ + diff += 10 * pa_frame_size (&u->source_output->sample_spec); + + pa_log("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff); + + u->sink_skip = diff; + u->source_skip = 0; + } + } else if (diff_time > 0) { + diff = pa_usec_to_bytes(diff_time, &u->source_output->sample_spec); + + if (diff > 0) { + pa_log("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff); + + u->source_skip = diff; + u->sink_skip = 0; + } + } +} + +/* must be called from the input thread */ +static void do_resync(struct userdata *u) { + int64_t diff_time; + struct snapshot latency_snapshot; + + pa_log("Doing resync"); + + /* update our snapshot */ + source_output_snapshot_within_thread(u, &latency_snapshot); + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL); + + /* calculate drift between capture and playback */ + diff_time = calc_diff(u, &latency_snapshot); + + /* and adjust for the drift */ + apply_diff_time(u, diff_time); +} + +/* Called from input thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + size_t rlen, plen; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) { + pa_log("push when no link?"); + return; + } + + /* handle queued messages */ + u->in_push = TRUE; + while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0) + ; + u->in_push = FALSE; + + if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) { + do_resync(u); + } + + pa_memblockq_push_align(u->source_memblockq, chunk); + + rlen = pa_memblockq_get_length(u->source_memblockq); + plen = pa_memblockq_get_length(u->sink_memblockq); + + while (rlen >= u->blocksize) { + pa_memchunk rchunk, pchunk; + + /* take fixed block from recorded samples */ + pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk); + + if (plen > u->blocksize && u->source_skip == 0) { + uint8_t *rdata, *pdata, *cdata; + pa_memchunk cchunk; + + if (u->sink_skip) { + size_t to_skip; + + if (u->sink_skip > plen) + to_skip = plen; + else + to_skip = u->sink_skip; + + pa_memblockq_drop(u->sink_memblockq, to_skip); + plen -= to_skip; + + u->sink_skip -= to_skip; + } + + if (plen > u->blocksize && u->sink_skip == 0) { + /* take fixed block from played samples */ + pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk); + + rdata = pa_memblock_acquire(rchunk.memblock); + rdata += rchunk.index; + pdata = pa_memblock_acquire(pchunk.memblock); + pdata += pchunk.index; + + cchunk.index = 0; + cchunk.length = u->blocksize; + cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length); + cdata = pa_memblock_acquire(cchunk.memblock); + + if (u->save_aec) { + if (u->captured_file) + fwrite(rdata, 1, u->blocksize, u->captured_file); + if (u->played_file) + fwrite(pdata, 1, u->blocksize, u->played_file); + } + + /* perform echo cancelation */ + u->ec->run(u->ec, rdata, pdata, cdata); + + /* preprecessor is run after AEC. This is not a mistake! */ + if (u->ec->pp_state) + speex_preprocess_run(u->ec->pp_state, (spx_int16_t *) cdata); + + if (u->save_aec) { + if (u->canceled_file) + fwrite(cdata, 1, u->blocksize, u->canceled_file); + } + + pa_memblock_release(cchunk.memblock); + pa_memblock_release(pchunk.memblock); + pa_memblock_release(rchunk.memblock); + + /* drop consumed sink samples */ + pa_memblockq_drop(u->sink_memblockq, u->blocksize); + pa_memblock_unref(pchunk.memblock); + + pa_memblock_unref(rchunk.memblock); + /* the filtered samples now become the samples from our + * source */ + rchunk = cchunk; + + plen -= u->blocksize; + } + } + + /* forward the (echo-canceled) data to the virtual source */ + pa_source_post(u->source, &rchunk); + pa_memblock_unref(rchunk.memblock); + + pa_memblockq_drop(u->source_memblockq, u->blocksize); + rlen -= u->blocksize; + + if (u->source_skip) { + if (u->source_skip > u->blocksize) { + u->source_skip -= u->blocksize; + } + else { + u->sink_skip += (u->blocksize - u->source_skip); + u->source_skip = 0; + } + } + } +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + pa_assert_se(u = i->userdata); + + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + pa_sink_render_full(u->sink, nbytes, chunk); + + if (i->thread_info.underrun_for > 0) { + pa_log_debug("Handling end of underrun."); + pa_atomic_store(&u->request_resync, 1); + } + + /* let source thread handle the chunk. pass the sample count as well so that + * the source IO thread can update the right variables. */ + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_POST, + NULL, 0, chunk, NULL); + u->send_counter += chunk->length; + + return 0; +} + +/* Called from input thread context */ +static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_source_process_rewind(u->source, nbytes); + + /* go back on read side, we need to use older sink data for this */ + pa_memblockq_rewind(u->sink_memblockq, nbytes); + + /* manipulate write index */ + pa_memblockq_seek(u->source_memblockq, -nbytes, PA_SEEK_RELATIVE, TRUE); + + pa_log_debug("Source rewind (%lld) %lld", (long long) nbytes, + (long long) pa_memblockq_get_length (u->source_memblockq)); +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink process rewind %lld", (long long) nbytes); + + pa_sink_process_rewind(u->sink, nbytes); + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); + u->send_counter -= nbytes; +} + +static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot) { + size_t delay, rlen, plen; + pa_usec_t now, latency; + + now = pa_rtclock_now(); + latency = pa_source_get_latency_within_thread(u->source_output->source); + delay = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq); + + delay = (u->source_output->thread_info.resampler ? pa_resampler_request(u->source_output->thread_info.resampler, delay) : delay); + rlen = pa_memblockq_get_length(u->source_memblockq); + plen = pa_memblockq_get_length(u->sink_memblockq); + + snapshot->source_now = now; + snapshot->source_latency = latency; + snapshot->source_delay = delay; + snapshot->recv_counter = u->recv_counter; + snapshot->rlen = rlen + u->sink_skip; + snapshot->plen = plen + u->source_skip; +} + + +/* Called from output thread context */ +static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_POST: + + pa_source_output_assert_io_context(u->source_output); + + if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state)) + pa_memblockq_push_align(u->sink_memblockq, chunk); + else + pa_memblockq_flush_write(u->sink_memblockq, TRUE); + + u->recv_counter += (int64_t) chunk->length; + + return 0; + + case SOURCE_OUTPUT_MESSAGE_REWIND: + pa_source_output_assert_io_context(u->source_output); + + /* manipulate write index, never go past what we have */ + if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state)) + pa_memblockq_seek(u->sink_memblockq, -offset, PA_SEEK_RELATIVE, TRUE); + else + pa_memblockq_flush_write(u->sink_memblockq, TRUE); + + pa_log_debug("Sink rewind (%lld)", (long long) offset); + + u->recv_counter -= offset; + + return 0; + + case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: { + struct snapshot *snapshot = (struct snapshot *) data; + + source_output_snapshot_within_thread(u, snapshot); + return 0; + } + + case SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME: + apply_diff_time(u, offset); + return 0; + + } + + return pa_source_output_process_msg(obj, code, data, offset, chunk); +} + +static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t delay; + pa_usec_t now, latency; + struct snapshot *snapshot = (struct snapshot *) data; + + pa_sink_input_assert_io_context(u->sink_input); + + now = pa_rtclock_now(); + latency = pa_sink_get_latency_within_thread(u->sink_input->sink); + delay = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq); + + delay = (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, delay) : delay); + + snapshot->sink_now = now; + snapshot->sink_latency = latency; + snapshot->sink_delay = delay; + snapshot->send_counter = u->send_counter; + return 0; + } + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink input update max rewind %lld", (long long) nbytes); + + pa_memblockq_set_maxrewind(u->sink_memblockq, nbytes); + pa_sink_set_max_rewind_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + pa_log_debug("Source output update max rewind %lld", (long long) nbytes); + + pa_source_set_max_rewind_within_thread(u->source, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink input update max request %lld", (long long) nbytes); + + pa_sink_set_max_request_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { + struct userdata *u; + pa_usec_t latency; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + latency = pa_sink_get_requested_latency_within_thread(i->sink); + + pa_log_debug("Sink input update requested latency %lld", (long long) latency); +} + +/* Called from I/O thread context */ +static void source_output_update_source_requested_latency_cb(pa_source_output *o) { + struct userdata *u; + pa_usec_t latency; + + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + latency = pa_source_get_requested_latency_within_thread(o->source); + + pa_log_debug("source output update requested latency %lld", (long long) latency); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink input update latency range %lld %lld", + (long long) i->sink->thread_info.min_latency, + (long long) i->sink->thread_info.max_latency); + + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void source_output_update_source_latency_range_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + pa_log_debug("Source output update latency range %lld %lld", + (long long) o->source->thread_info.min_latency, + (long long) o->source->thread_info.max_latency); + + pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink input update fixed latency %lld", + (long long) i->sink->thread_info.fixed_latency); + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void source_output_update_source_fixed_latency_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + pa_log_debug("Source output update fixed latency %lld", + (long long) o->source->thread_info.fixed_latency); + + pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); +} + +/* Called from output thread context */ +static void source_output_attach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll); + pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); + pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); + pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o)); + + pa_log_debug("Source output %p attach", o); + + pa_source_attach_within_thread(u->source); + + u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + o->source->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + + /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE + * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */ + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + + /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND + * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT + * HERE. SEE (6) */ + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); + + pa_log_debug("Sink input %p attach", i); + + u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + i->sink->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); + + pa_sink_attach_within_thread(u->sink); +} + + +/* Called from output thread context */ +static void source_output_detach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_source_detach_within_thread(u->source); + pa_source_set_rtpoll(u->source, NULL); + + pa_log_debug("Source output %p detach", o); + + if (u->rtpoll_item_read) { + pa_rtpoll_item_free(u->rtpoll_item_read); + u->rtpoll_item_read = NULL; + } +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); + + pa_sink_set_rtpoll(u->sink, NULL); + + pa_log_debug("Sink input %p detach", i); + + if (u->rtpoll_item_write) { + pa_rtpoll_item_free(u->rtpoll_item_write); + u->rtpoll_item_write = NULL; + } +} + +/* Called from output thread context */ +static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_log_debug("Source output %p state %d", o, state); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_log_debug("Sink input %p state %d", i, state); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + } +} + +/* Called from main thread */ +static void source_output_kill_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + /* The order here matters! We first kill the source output, followed + * by the source. That means the source callbacks must be protected + * against an unconnected source output! */ + pa_source_output_unlink(u->source_output); + pa_source_unlink(u->source); + + pa_source_output_unref(u->source_output); + u->source_output = NULL; + + pa_source_unref(u->source); + u->source = NULL; + + pa_log_debug("Source output kill %p", o); + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ + pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_log_debug("Sink input kill %p", i); + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + return (u->source != dest) && (u->sink != dest->monitor_of); +} + +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + +/* Called from main thread */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + if (dest) { + pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); + pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); + } else + pa_source_set_asyncmsgq(u->source, NULL); + + if (u->source_auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s", + pa_proplist_gets(u->source->proplist, "device.echo-cancel.name"), z ? z : dest->name); + + pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->sink_auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s", + pa_proplist_gets(u->sink->proplist, "device.echo-cancel.name"), z ? z : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + +static pa_echo_canceller_method_t get_ec_method_from_string(const char *method) { + if (strcmp(method, "speex") == 0) + return PA_ECHO_CANCELLER_SPEEX; + else if (strcmp(method, "adrian") == 0) + return PA_ECHO_CANCELLER_ADRIAN; + else + return PA_ECHO_CANCELLER_INVALID; +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec source_ss, sink_ss; + pa_channel_map source_map, sink_map; + pa_modargs *ma; + pa_source *source_master=NULL; + pa_sink *sink_master=NULL; + pa_source_output_new_data source_output_data; + pa_sink_input_new_data sink_input_data; + pa_source_new_data source_data; + pa_sink_new_data sink_data; + pa_memchunk silence; + pa_echo_canceller_method_t ec_method; + uint32_t adjust_time_sec; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(source_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source_master", NULL), PA_NAMEREG_SOURCE))) { + pa_log("Master source not found"); + goto fail; + } + pa_assert(source_master); + + if (!(sink_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink_master", NULL), PA_NAMEREG_SINK))) { + pa_log("Master sink not found"); + goto fail; + } + pa_assert(sink_master); + + source_ss = source_master->sample_spec; + source_map = source_master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &source_ss, &source_map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + sink_ss = sink_master->sample_spec; + sink_map = sink_master->channel_map; + + u = pa_xnew0(struct userdata, 1); + if (!u) { + pa_log("Failed to alloc userdata"); + goto fail; + } + u->core = m->core; + u->module = m; + m->userdata = u; + + u->ec = pa_xnew0(pa_echo_canceller, 1); + if (!u->ec) { + pa_log("Failed to alloc echo canceller"); + goto fail; + } + + if ((ec_method = get_ec_method_from_string(pa_modargs_get_value(ma, "aec_method", DEFAULT_ECHO_CANCELLER))) < 0) { + pa_log("Invalid echo canceller implementation"); + goto fail; + } + + u->ec->init = ec_table[ec_method].init; + u->ec->run = ec_table[ec_method].run; + u->ec->done = ec_table[ec_method].done; + + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { + pa_log("Failed to parse adjust_time value"); + goto fail; + } + + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + + u->ec->agc = DEFAULT_AGC_ENABLED; + if (pa_modargs_get_value_boolean(ma, "agc", &u->ec->agc) < 0) { + pa_log("Failed to parse agc value"); + goto fail; + } + + u->ec->denoise = DEFAULT_DENOISE_ENABLED; + if (pa_modargs_get_value_boolean(ma, "denoise", &u->ec->denoise) < 0) { + pa_log("Failed to parse denoise value"); + goto fail; + } + + u->ec->echo_suppress = DEFAULT_ECHO_SUPPRESS_ENABLED; + if (pa_modargs_get_value_boolean(ma, "echo_suppress", &u->ec->echo_suppress) < 0) { + pa_log("Failed to parse echo_suppress value"); + goto fail; + } + if (u->ec->echo_suppress && ec_method != PA_ECHO_CANCELLER_SPEEX) { + pa_log("Echo suppression is only useful with the speex canceller"); + goto fail; + } + + u->ec->echo_suppress_attenuation = DEFAULT_ECHO_SUPPRESS_ATTENUATION; + if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation", &u->ec->echo_suppress_attenuation) < 0) { + pa_log("Failed to parse echo_suppress_attenuation value"); + goto fail; + } + if (u->ec->echo_suppress_attenuation > 0) { + pa_log("echo_suppress_attenuation should be a negative dB value"); + goto fail; + } + + u->ec->echo_suppress_attenuation_active = DEFAULT_ECHO_SUPPRESS_ATTENUATION; + if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation_active", &u->ec->echo_suppress_attenuation_active) < 0) { + pa_log("Failed to parse echo_supress_attenuation_active value"); + goto fail; + } + if (u->ec->echo_suppress_attenuation_active > 0) { + pa_log("echo_suppress_attenuation_active should be a negative dB value"); + goto fail; + } + + u->save_aec = DEFAULT_SAVE_AEC; + if (pa_modargs_get_value_u32(ma, "save_aec", &u->save_aec) < 0) { + pa_log("Failed to parse save_aec value"); + goto fail; + } + + u->autoloaded = DEFAULT_AUTOLOADED; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + goto fail; + } + + u->asyncmsgq = pa_asyncmsgq_new(0); + u->need_realign = TRUE; + if (u->ec->init) { + if (!u->ec->init(u->core, u->ec, &source_ss, &source_map, &sink_ss, &sink_map, &u->blocksize, pa_modargs_get_value(ma, "aec_args", NULL))) { + pa_log("Failed to init AEC engine"); + goto fail; + } + } + + if (u->ec->agc || u->ec->denoise || u->ec->echo_suppress) { + spx_int32_t tmp; + + if (source_ss.channels != 1) { + pa_log("AGC, denoising and echo suppression only work with channels=1"); + goto fail; + } + + u->ec->pp_state = speex_preprocess_state_init(u->blocksize / pa_frame_size(&source_ss), source_ss.rate); + + tmp = u->ec->agc; + speex_preprocess_ctl(u->ec->pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp); + tmp = u->ec->denoise; + speex_preprocess_ctl(u->ec->pp_state, SPEEX_PREPROCESS_SET_DENOISE, &tmp); + if (u->ec->echo_suppress) { + if (u->ec->echo_suppress_attenuation) + speex_preprocess_ctl(u->ec->pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &u->ec->echo_suppress_attenuation); + if (u->ec->echo_suppress_attenuation_active) { + speex_preprocess_ctl(u->ec->pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, + &u->ec->echo_suppress_attenuation_active); + } + speex_preprocess_ctl(u->ec->pp_state, SPEEX_PREPROCESS_SET_ECHO_STATE, u->ec->params.priv.speex.state); + } + } + + /* Create source */ + pa_source_new_data_init(&source_data); + source_data.driver = __FILE__; + source_data.module = m; + if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) + source_data.name = pa_sprintf_malloc("%s.echo-cancel", source_master->name); + pa_source_new_data_set_sample_spec(&source_data, &source_ss); + pa_source_new_data_set_channel_map(&source_data, &source_map); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + if (!u->autoloaded) + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); + pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name); + + if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_data); + goto fail; + } + + if ((u->source_auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(source_master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s", source_data.name, z ? z : source_master->name); + } + + u->source = pa_source_new(m->core, &source_data, + PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME| + (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY))); + pa_source_new_data_done(&source_data); + + if (!u->source) { + pa_log("Failed to create source."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg_cb; + u->source->set_state = source_set_state_cb; + u->source->update_requested_latency = source_update_requested_latency_cb; + u->source->set_volume = source_set_volume_cb; + u->source->set_mute = source_set_mute_cb; + u->source->get_volume = source_get_volume_cb; + u->source->get_mute = source_get_mute_cb; + u->source->userdata = u; + + pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq); + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.echo-cancel", sink_master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &sink_ss); + pa_sink_new_data_set_channel_map(&sink_data, &sink_map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + if (!u->autoloaded) + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); + pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + + if ((u->sink_auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(sink_master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s", sink_data.name, z ? z : sink_master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, + PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| + (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq); + + /* Create source output */ + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + pa_source_output_new_data_set_source(&source_output_data, source_master, FALSE); + source_output_data.destination_source = u->source; + /* FIXME + source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */ + + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream"); + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_source_output_new_data_set_sample_spec(&source_output_data, &source_ss); + pa_source_output_new_data_set_channel_map(&source_output_data, &source_map); + + pa_source_output_new(&u->source_output, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!u->source_output) + goto fail; + + u->source_output->parent.process_msg = source_output_process_msg_cb; + u->source_output->push = source_output_push_cb; + u->source_output->process_rewind = source_output_process_rewind_cb; + u->source_output->update_max_rewind = source_output_update_max_rewind_cb; + u->source_output->update_source_requested_latency = source_output_update_source_requested_latency_cb; + u->source_output->update_source_latency_range = source_output_update_source_latency_range_cb; + u->source_output->update_source_fixed_latency = source_output_update_source_fixed_latency_cb; + u->source_output->kill = source_output_kill_cb; + u->source_output->attach = source_output_attach_cb; + u->source_output->detach = source_output_detach_cb; + u->source_output->state_change = source_output_state_change_cb; + u->source_output->may_move_to = source_output_may_move_to_cb; + u->source_output->moving = source_output_moving_cb; + u->source_output->userdata = u; + + u->source->output_from_master = u->source_output; + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, sink_master, FALSE); + sink_input_data.origin_sink = u->sink; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &sink_ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_map); + sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE; + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; + u->sink_input->userdata = u; + + u->sink->input_to_master = u->sink_input; + + pa_sink_input_get_silence(u->sink_input, &silence); + + u->source_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, + pa_frame_size(&source_ss), 1, 1, 0, &silence); + u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, + pa_frame_size(&sink_ss), 1, 1, 0, &silence); + + pa_memblock_unref(silence.memblock); + + if (!u->source_memblockq || !u->sink_memblockq) { + pa_log("Failed to create memblockq."); + goto fail; + } + + /* our source and sink are not suspended when we create them */ + u->active_mask = 3; + + if (u->adjust_time > 0) + u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u); + + if (u->save_aec) { + pa_log("Creating AEC files in /tmp"); + u->captured_file = fopen("/tmp/aec_rec.sw", "wb"); + if (u->captured_file == NULL) + perror ("fopen failed"); + u->played_file = fopen("/tmp/aec_play.sw", "wb"); + if (u->played_file == NULL) + perror ("fopen failed"); + u->canceled_file = fopen("/tmp/aec_out.sw", "wb"); + if (u->canceled_file == NULL) + perror ("fopen failed"); + } + + pa_sink_put(u->sink); + pa_source_put(u->source); + + pa_sink_input_put(u->sink_input); + pa_source_output_put(u->source_output); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink) + pa_source_linked_by(u->source); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + /* See comments in source_output_kill_cb() above regarding + * destruction order! */ + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + if (u->source_output) + pa_source_output_unlink(u->source_output); + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->source) + pa_source_unlink(u->source); + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->source_output) + pa_source_output_unref(u->source_output); + if (u->sink_input) + pa_sink_input_unref(u->sink_input); + + if (u->source) + pa_source_unref(u->source); + if (u->sink) + pa_sink_unref(u->sink); + + if (u->source_memblockq) + pa_memblockq_free(u->source_memblockq); + if (u->sink_memblockq) + pa_memblockq_free(u->sink_memblockq); + + if (u->ec->pp_state) + speex_preprocess_state_destroy(u->ec->pp_state); + + if (u->ec) { + if (u->ec->done) + u->ec->done(u->ec); + + pa_xfree(u->ec); + } + + if (u->asyncmsgq) + pa_asyncmsgq_unref(u->asyncmsgq); + + pa_xfree(u); +} diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c new file mode 100644 index 00000000..72c52680 --- /dev/null +++ b/src/modules/echo-cancel/speex.c @@ -0,0 +1,115 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Wim Taymans <wim.taymans@gmail.com> + + Contributor: Arun Raghavan <arun.raghavan@collabora.co.uk> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/modargs.h> +#include "echo-cancel.h" + +/* should be between 10-20 ms */ +#define DEFAULT_FRAME_SIZE_MS 20 +/* should be between 100-500 ms */ +#define DEFAULT_FILTER_SIZE_MS 200 + +static const char* const valid_modargs[] = { + "frame_size_ms", + "filter_size_ms", + NULL +}; + +static void pa_speex_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map) +{ + source_ss->format = PA_SAMPLE_S16NE; + + *sink_ss = *source_ss; + *sink_map = *source_map; +} + +pa_bool_t pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec, + pa_sample_spec *source_ss, pa_channel_map *source_map, + pa_sample_spec *sink_ss, pa_channel_map *sink_map, + uint32_t *blocksize, const char *args) +{ + int framelen, y, rate; + uint32_t frame_size_ms, filter_size_ms; + pa_modargs *ma; + + if (!(ma = pa_modargs_new(args, valid_modargs))) { + pa_log("Failed to parse submodule arguments."); + goto fail; + } + + filter_size_ms = DEFAULT_FILTER_SIZE_MS; + if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) { + pa_log("Invalid filter_size_ms specification"); + goto fail; + } + + frame_size_ms = DEFAULT_FRAME_SIZE_MS; + if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) { + pa_log("Invalid frame_size_ms specification"); + goto fail; + } + + pa_speex_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map); + + rate = source_ss->rate; + framelen = (rate * frame_size_ms) / 1000; + /* framelen should be a power of 2, round down to nearest power of two */ + y = 1 << ((8 * sizeof (int)) - 2); + while (y > framelen) + y >>= 1; + framelen = y; + + *blocksize = framelen * pa_frame_size (source_ss); + + pa_log_debug ("Using framelen %d, blocksize %u, channels %d, rate %d", framelen, *blocksize, source_ss->channels, source_ss->rate); + + ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, source_ss->channels, source_ss->channels); + + if (!ec->params.priv.speex.state) + goto fail; + + speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate); + + pa_modargs_free(ma); + return TRUE; + +fail: + if (ma) + pa_modargs_free(ma); + return FALSE; +} + +void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { + speex_echo_cancellation(ec->params.priv.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, (spx_int16_t *) out); +} + +void pa_speex_ec_done(pa_echo_canceller *ec) { + if (ec->params.priv.speex.state) + speex_echo_state_destroy(ec->params.priv.speex.state); + ec->params.priv.speex.state = NULL; +} diff --git a/src/modules/gconf/Makefile b/src/modules/gconf/Makefile deleted file mode 120000 index efe5a336..00000000 --- a/src/modules/gconf/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index c01ebbf6..3bad9113 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -30,12 +30,10 @@ #include <signal.h> #include <sys/types.h> #include <sys/wait.h> -#include <fcntl.h> #include <pulse/xmalloc.h> #include <pulsecore/module.h> #include <pulsecore/core.h> -#include <pulsecore/llist.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulse/mainloop-api.h> @@ -52,9 +50,6 @@ PA_MODULE_LOAD_ONCE(TRUE); #define MAX_MODULES 10 #define BUF_MAX 2048 -/* #undef PA_GCONF_HELPER */ -/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */ - struct module_item { char *name; char *args; @@ -343,7 +338,11 @@ int pa__init(pa_module*m) { u->io_event = NULL; u->buf_fill = 0; - if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0) + if ((u->fd = pa_start_child_for_read( +#if defined(__linux__) && !defined(__OPTIMIZE__) + pa_run_from_build_tree() ? PA_BUILDDIR "/gconf-helper" : +#endif + PA_GCONF_HELPER, NULL, &u->pid)) < 0) goto fail; u->io_event = m->core->mainloop->io_new( diff --git a/src/modules/hal-util.c b/src/modules/hal-util.c index e2a2d8d7..2d59f51d 100644 --- a/src/modules/hal-util.c +++ b/src/modules/hal-util.c @@ -65,7 +65,7 @@ int pa_hal_get_info(pa_core *core, pa_proplist *p, int card) { goto finish; } - if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error)) < 0) { + if (!(udis = libhal_find_device_by_capability(hal, "sound", &n, &error))) { pa_log_error("Couldn't find devices: %s: %s", error.name, error.message); goto finish; } diff --git a/src/modules/jack/module-jack-sink.c b/src/modules/jack/module-jack-sink.c index 290038e7..35b0385d 100644 --- a/src/modules/jack/module-jack-sink.c +++ b/src/modules/jack/module-jack-sink.c @@ -24,19 +24,15 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> #include <jack/jack.h> #include <pulse/xmalloc.h> -#include <pulsecore/core-error.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> @@ -68,7 +64,7 @@ PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( "sink_name=<name for the sink> " - "sink_properties=<properties for the card> " + "sink_properties=<properties for the card> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " @@ -147,6 +143,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_sample_spec ss; /* Humm, we're not RUNNING, hence let's write some silence */ + /* This can happen if we're paused, or during shutdown (when we're unlinked but jack is still running). */ ss = u->sink->sample_spec; ss.channels = 1; @@ -197,6 +194,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse return pa_sink_process_msg(o, code, data, offset, memchunk); } +/* JACK Callback: This is called when JACK needs some data */ static int jack_process(jack_nframes_t nframes, void *arg) { struct userdata *u = arg; unsigned c; @@ -225,7 +223,6 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; @@ -251,6 +248,7 @@ finish: pa_log_debug("Thread shutting down"); } +/* JACK Callback: This is called when JACK triggers an error */ static void jack_error_func(const char*t) { char *s; @@ -259,6 +257,7 @@ static void jack_error_func(const char*t) { pa_xfree(s); } +/* JACK Callback: This is called when JACK is set up */ static void jack_init(void *arg) { struct userdata *u = arg; @@ -268,6 +267,7 @@ static void jack_init(void *arg) { pa_make_realtime(u->core->realtime_priority+4); } +/* JACK Callback: This is called when JACK kicks us */ static void jack_shutdown(void* arg) { struct userdata *u = arg; @@ -275,6 +275,7 @@ static void jack_shutdown(void* arg) { pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); } +/* JACK Callback: This is called when JACK changes the buffer size */ static int jack_buffer_size(jack_nframes_t nframes, void *arg) { struct userdata *u = arg; @@ -335,11 +336,12 @@ int pa__init(pa_module*m) { goto fail; } - ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); + ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput); channels = 0; - for (p = ports; *p; p++) - channels++; + if (ports) + for (p = ports; *p; p++) + channels++; if (!channels) channels = m->core->default_sample_spec.channels; @@ -414,7 +416,7 @@ int pa__init(pa_module*m) { jack_set_thread_init_callback(u->client, jack_init, u); jack_set_buffer_size_callback(u->client, jack_buffer_size, u); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("jack-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -427,7 +429,7 @@ int pa__init(pa_module*m) { if (do_connect) { for (i = 0, p = ports; i < ss.channels; i++, p++) { - if (!*p) { + if (!p || !*p) { pa_log("Not enough physical output ports, leaving unconnected."); break; } @@ -443,7 +445,8 @@ int pa__init(pa_module*m) { pa_sink_put(u->sink); - free(ports); + if (ports) + jack_free(ports); pa_modargs_free(ma); return 0; @@ -452,7 +455,8 @@ fail: if (ma) pa_modargs_free(ma); - free(ports); + if (ports) + jack_free(ports); pa__done(m); @@ -476,12 +480,12 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->client) - jack_client_close(u->client); - if (u->sink) pa_sink_unlink(u->sink); + if (u->client) + jack_client_close(u->client); + if (u->thread) { pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); pa_thread_free(u->thread); diff --git a/src/modules/jack/module-jack-source.c b/src/modules/jack/module-jack-source.c index ef89a98e..13109f3e 100644 --- a/src/modules/jack/module-jack-source.c +++ b/src/modules/jack/module-jack-source.c @@ -24,19 +24,15 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> #include <jack/jack.h> #include <pulse/xmalloc.h> -#include <pulsecore/core-error.h> #include <pulsecore/source.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> @@ -196,7 +192,6 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; @@ -287,11 +282,12 @@ int pa__init(pa_module*m) { goto fail; } - ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsOutput); channels = 0; - for (p = ports; *p; p++) - channels++; + if (ports) + for (p = ports; *p; p++) + channels++; if (!channels) channels = m->core->default_sample_spec.channels; @@ -364,7 +360,7 @@ int pa__init(pa_module*m) { jack_on_shutdown(u->client, jack_shutdown, u); jack_set_thread_init_callback(u->client, jack_init, u); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("jack-source", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -377,7 +373,7 @@ int pa__init(pa_module*m) { if (do_connect) { for (i = 0, p = ports; i < ss.channels; i++, p++) { - if (!*p) { + if (!p || !*p) { pa_log("Not enough physical output ports, leaving unconnected."); break; } @@ -394,7 +390,8 @@ int pa__init(pa_module*m) { pa_source_put(u->source); - free(ports); + if (ports) + jack_free(ports); pa_modargs_free(ma); return 0; @@ -403,7 +400,8 @@ fail: if (ma) pa_modargs_free(ma); - free(ports); + if (ports) + jack_free(ports); pa__done(m); @@ -426,12 +424,12 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->client) - jack_client_close(u->client); - if (u->source) pa_source_unlink(u->source); + if (u->client) + jack_client_close(u->client); + if (u->thread) { pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); pa_thread_free(u->thread); diff --git a/src/modules/jack/module-jackdbus-detect.c b/src/modules/jack/module-jackdbus-detect.c new file mode 100644 index 00000000..864f96b1 --- /dev/null +++ b/src/modules/jack/module-jackdbus-detect.c @@ -0,0 +1,298 @@ +/*** + This file is part of PulseAudio. + + Written by David Henningsson <david.henningsson@canonical.com> + Copyright 2010 Canonical Ltd. + + Some code taken from other parts of PulseAudio, these are + Copyright 2006-2009 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> + +#include "module-jackdbus-detect-symdef.h" + +PA_MODULE_AUTHOR("David Henningsson"); +PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE("connect=<connect ports?>"); + +#define JACK_SERVICE_NAME "org.jackaudio.service" +#define JACK_INTERFACE_NAME "org.jackaudio.JackControl" +#define JACK_INTERFACE_PATH "/org/jackaudio/Controller" + +#define SERVICE_FILTER \ + "type='signal'," \ + "sender='" DBUS_SERVICE_DBUS "'," \ + "interface='" DBUS_INTERFACE_DBUS "'," \ + "member='NameOwnerChanged'," \ + "arg0='" JACK_SERVICE_NAME "'" + +#define RUNNING_FILTER(_a) \ + "type='signal'," \ + "sender='" JACK_SERVICE_NAME "'," \ + "interface='" JACK_INTERFACE_NAME "'," \ + "member='" _a "'" + +static const char* const valid_modargs[] = { + "connect", + NULL +}; + +#define JACK_SS_SINK 0 +#define JACK_SS_SOURCE 1 +#define JACK_SS_COUNT 2 + +static const char* const modnames[JACK_SS_COUNT] = { + "module-jack-sink", + "module-jack-source" +}; + + +struct userdata { + pa_module *module; + pa_core *core; + pa_dbus_connection *connection; + pa_bool_t filter_added, match_added; + pa_bool_t is_service_started; + pa_bool_t autoconnect_ports; + /* Using index here protects us from module unloading without us knowing */ + int jack_module_index[JACK_SS_COUNT]; +}; + + +static void ensure_ports_stopped(struct userdata* u) { + int i; + pa_assert(u); + + for (i = 0; i < JACK_SS_COUNT; i++) + if (u->jack_module_index[i]) { + pa_module_unload_request_by_index(u->core, u->jack_module_index[i], TRUE); + u->jack_module_index[i] = 0; + pa_log_info("Stopped %s.", modnames[i]); + } +} + +static void ensure_ports_started(struct userdata* u) { + int i; + pa_assert(u); + + for (i = 0; i < JACK_SS_COUNT; i++) + if (!u->jack_module_index[i]) { + char* args; + pa_module* m; + args = pa_sprintf_malloc("connect=%s", pa_yes_no(u->autoconnect_ports)); + m = pa_module_load(u->core, modnames[i], args); + pa_xfree(args); + + if (m) { + pa_log_info("Successfully started %s.", modnames[i]); + u->jack_module_index[i] = m->index; + } + else + pa_log_info("Failed to start %s.", modnames[i]); + } +} + + +static pa_bool_t check_service_started(struct userdata* u) { + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + pa_bool_t new_status = FALSE; + dbus_bool_t call_result; + pa_assert(u); + + dbus_error_init(&error); + + /* Just a safety check; it isn't such a big deal if the name disappears just after the call. */ + if (!dbus_bus_name_has_owner(pa_dbus_connection_get(u->connection), + JACK_SERVICE_NAME, &error)) { + pa_log_debug("jackdbus isn't running."); + goto finish; + } + + if (!(m = dbus_message_new_method_call(JACK_SERVICE_NAME, JACK_INTERFACE_PATH, JACK_INTERFACE_NAME, "IsStarted"))) { + pa_log("Failed to allocate IsStarted() method call."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { + pa_log("IsStarted() call failed: %s: %s", error.name, error.message); + goto finish; + } + + if (!dbus_message_get_args(reply, &error, DBUS_TYPE_BOOLEAN, &call_result, DBUS_TYPE_INVALID)) { + pa_log("IsStarted() call return failed: %s: %s", error.name, error.message); + goto finish; + } + + new_status = call_result; + +finish: + if (m) + dbus_message_unref(m); + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + if (new_status) + ensure_ports_started(u); + else + ensure_ports_stopped(u); + u->is_service_started = new_status; + return new_status; +} + +static DBusHandlerResult dbus_filter_handler(DBusConnection *c, DBusMessage *s, void *userdata) { + struct userdata *u = NULL; + DBusError error; + + pa_assert(userdata); + u = ((pa_module*) userdata)->userdata; + pa_assert(u); + + dbus_error_init(&error); + + if (dbus_message_is_signal(s, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + const char *name, *old, *new; + if (!dbus_message_get_args(s, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) + goto finish; + if (strcmp(name, JACK_SERVICE_NAME)) + goto finish; + + ensure_ports_stopped(u); + check_service_started(u); + } + + else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStarted")) { + ensure_ports_stopped(u); + check_service_started(u); + } + + else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStopped")) { + ensure_ports_stopped(u); + } + +finish: + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int pa__init(pa_module *m) { + DBusError error; + pa_dbus_connection *connection = NULL; + struct userdata *u = NULL; + pa_modargs *ma; + + pa_assert(m); + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->autoconnect_ports = TRUE; + + if (pa_modargs_get_value_boolean(ma, "connect", &u->autoconnect_ports) < 0) { + pa_log("Failed to parse connect= argument."); + goto fail; + } + + if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + + if (connection) + pa_dbus_connection_unref(connection); + + pa_log_error("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + goto fail; + } + u->connection = connection; + + if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), dbus_filter_handler, m, NULL)) { + pa_log_error("Unable to add D-Bus filter"); + goto fail; + } + u->filter_added = 1; + + if (pa_dbus_add_matches( + pa_dbus_connection_get(connection), &error, SERVICE_FILTER, + RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL) < 0) { + pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message); + goto fail; + } + u->match_added = 1; + + check_service_started(u); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + ensure_ports_stopped(u); + + if (u->match_added) { + pa_dbus_remove_matches( + pa_dbus_connection_get(u->connection), SERVICE_FILTER, + RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL); + } + + if (u->filter_added) { + dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), dbus_filter_handler, m); + } + + if (u->connection) { + pa_dbus_connection_unref(u->connection); + } + + pa_xfree(u); +} diff --git a/src/modules/macosx/module-bonjour-publish.c b/src/modules/macosx/module-bonjour-publish.c new file mode 100644 index 00000000..667b6b73 --- /dev/null +++ b/src/modules/macosx/module-bonjour-publish.c @@ -0,0 +1,513 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Daniel Mack + based on module-zeroconf-publish.c + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <dns_sd.h> + +#include <CoreFoundation/CoreFoundation.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/parseaddr.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/native-common.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/dynarray.h> +#include <pulsecore/modargs.h> +#include <pulsecore/protocol-native.h> + +#include "module-bonjour-publish-symdef.h" + +PA_MODULE_AUTHOR("Daniel Mack"); +PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_pulse-source._tcp" +#define SERVICE_TYPE_SERVER "_pulse-server._tcp" + +static const char* const valid_modargs[] = { + NULL +}; + +enum service_subtype { + SUBTYPE_HARDWARE, + SUBTYPE_VIRTUAL, + SUBTYPE_MONITOR +}; + +struct service { + struct userdata *userdata; + DNSServiceRef service; + DNSRecordRef rec, rec2; + char *service_name; + pa_object *device; + enum service_subtype subtype; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hashmap *services; + char *service_name; + + pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; + + pa_native_protocol *native; + DNSServiceRef main_service; +}; + +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) { + pa_assert(s); + pa_assert(ret_ss); + pa_assert(ret_proplist); + pa_assert(ret_subtype); + + if (pa_sink_isinstance(s->device)) { + pa_sink *sink = PA_SINK(s->device); + + *ret_ss = sink->sample_spec; + *ret_map = sink->channel_map; + *ret_name = sink->name; + *ret_proplist = sink->proplist; + *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; + + } else if (pa_source_isinstance(s->device)) { + pa_source *source = PA_SOURCE(s->device); + + *ret_ss = source->sample_spec; + *ret_map = source->channel_map; + *ret_name = source->name; + *ret_proplist = source->proplist; + *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); + + } else + pa_assert_not_reached(); +} + +static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) { + char s[128]; + char *t; + + pa_assert(c); + + TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION); + + t = pa_get_user_name_malloc(); + TXTRecordSetValue(txt, "user-name", strlen(t), t); + pa_xfree(t); + + t = pa_machine_id(); + TXTRecordSetValue(txt, "machine-id", strlen(t), t); + pa_xfree(t); + + t = pa_uname_string(); + TXTRecordSetValue(txt, "uname", strlen(t), t); + pa_xfree(t); + + t = pa_get_fqdn(s, sizeof(s)); + TXTRecordSetValue(txt, "fqdn", strlen(t), t); + + snprintf(s, sizeof(s), "0x%08x", c->cookie); + TXTRecordSetValue(txt, "cookie", strlen(s), s); +} + +static void service_free(struct service *s); + +static void dns_service_register_reply(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context) { + struct service *s = context; + + pa_assert(s); + + switch (errorCode) { + case kDNSServiceErr_NameConflict: + pa_log("DNS service reported kDNSServiceErr_NameConflict\n"); + service_free(s); + break; + + case kDNSServiceErr_NoError: + default: + break; + } +} + +static uint16_t compute_port(struct userdata *u) { + pa_strlist *i; + + pa_assert(u); + + for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) { + pa_parsed_address a; + + if (pa_parse_address(pa_strlist_data(i), &a) >= 0 && + (a.type == PA_PARSED_ADDRESS_TCP4 || + a.type == PA_PARSED_ADDRESS_TCP6 || + a.type == PA_PARSED_ADDRESS_TCP_AUTO) && + a.port > 0) { + + pa_xfree(a.path_or_host); + return a.port; + } + + pa_xfree(a.path_or_host); + } + + return PA_NATIVE_DEFAULT_PORT; +} + +static int publish_service(struct service *s) { + int r = -1; + TXTRecordRef txt; + DNSServiceErrorType err; + const char *name = NULL, *t; + pa_proplist *proplist = NULL; + pa_sample_spec ss; + pa_channel_map map; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64]; + enum service_subtype subtype; + + const char * const subtype_text[] = { + [SUBTYPE_HARDWARE] = "hardware", + [SUBTYPE_VIRTUAL] = "virtual", + [SUBTYPE_MONITOR] = "monitor" + }; + + pa_assert(s); + + if (s->service) { + DNSServiceRefDeallocate(s->service); + s->service = NULL; + } + + TXTRecordCreate(&txt, 0, NULL); + + txt_record_server_data(s->userdata->core, &txt); + + get_service_data(s, &ss, &map, &name, &proplist, &subtype); + TXTRecordSetValue(&txt, "device", strlen(name), name); + + snprintf(tmp, sizeof(tmp), "%u", ss.rate); + TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp); + + snprintf(tmp, sizeof(tmp), "%u", ss.channels); + TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp); + + t = pa_sample_format_to_string(ss.format); + TXTRecordSetValue(&txt, "format", strlen(t), t); + + t = pa_channel_map_snprint(cm, sizeof(cm), &map); + TXTRecordSetValue(&txt, "channel_map", strlen(t), t); + + t = subtype_text[subtype]; + TXTRecordSetValue(&txt, "subtype", strlen(t), t); + + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION))) + TXTRecordSetValue(&txt, "description", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME))) + TXTRecordSetValue(&txt, "icon-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME))) + TXTRecordSetValue(&txt, "vendor-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME))) + TXTRecordSetValue(&txt, "product-name", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS))) + TXTRecordSetValue(&txt, "class", strlen(t), t); + if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR))) + TXTRecordSetValue(&txt, "form-factor", strlen(t), t); + + err = DNSServiceRegister(&s->service, + 0, /* flags */ + kDNSServiceInterfaceIndexAny, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, /* domain */ + NULL, /* host */ + compute_port(s->userdata), + TXTRecordGetLength(&txt), + TXTRecordGetBytesPtr(&txt), + dns_service_register_reply, s); + + if (err != kDNSServiceErr_NoError) { + pa_log("DNSServiceRegister() returned err %d", err); + goto finish; + } + + pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name); + return 0; + +finish: + + /* Remove this service */ + if (r < 0) + service_free(s); + + TXTRecordDeallocate(&txt); + + return r; +} + +static struct service *get_service(struct userdata *u, pa_object *device) { + struct service *s; + char *hn, *un; + const char *n; + + pa_assert(u); + pa_object_assert_ref(device); + + if ((s = pa_hashmap_get(u->services, device))) + return s; + + s = pa_xnew0(struct service, 1); + s->userdata = u; + s->device = device; + + if (pa_sink_isinstance(device)) { + if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SINK(device)->name; + } else { + if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) + n = PA_SOURCE(device)->name; + } + + hn = pa_get_host_name_malloc(); + un = pa_get_user_name_malloc(); + + s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1); + + pa_xfree(un); + pa_xfree(hn); + + pa_hashmap_put(u->services, s->device, s); + + return s; +} + +static void service_free(struct service *s) { + pa_assert(s); + + pa_hashmap_remove(s->userdata->services, s->device); + + if (s->service) + DNSServiceRefDeallocate(s->service); + + pa_xfree(s->service_name); + pa_xfree(s); +} + +static pa_bool_t shall_ignore(pa_object *o) { + pa_object_assert_ref(o); + + if (pa_sink_isinstance(o)) + return !!(PA_SINK(o)->flags & PA_SINK_NETWORK); + + if (pa_source_isinstance(o)) + return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK); + + pa_assert_not_reached(); +} + +static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) { + pa_assert(c); + pa_object_assert_ref(o); + + if (!shall_ignore(o)) + publish_service(get_service(u, o)); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct service *s; + + pa_assert(c); + pa_object_assert_ref(o); + + if ((s = pa_hashmap_get(u->services, o))) + service_free(s); + + return PA_HOOK_OK; +} + +static int publish_main_service(struct userdata *u) { + DNSServiceErrorType err; + TXTRecordRef txt; + + pa_assert(u); + + if (u->main_service) { + DNSServiceRefDeallocate(u->main_service); + u->main_service = NULL; + } + + TXTRecordCreate(&txt, 0, NULL); + txt_record_server_data(u->core, &txt); + + err = DNSServiceRegister(&u->main_service, + 0, /* flags */ + kDNSServiceInterfaceIndexAny, + u->service_name, + SERVICE_TYPE_SERVER, + NULL, /* domain */ + NULL, /* host */ + compute_port(u), + TXTRecordGetLength(&txt), + TXTRecordGetBytesPtr(&txt), + NULL, NULL); + + if (err != kDNSServiceErr_NoError) { + pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err); + return err; + } + + TXTRecordDeallocate(&txt); + + return 0; +} + +static int publish_all_services(struct userdata *u) { + pa_sink *sink; + pa_source *source; + uint32_t idx; + + pa_assert(u); + + pa_log_debug("Publishing services in Bonjour"); + + for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx))) + if (!shall_ignore(PA_OBJECT(sink))) + publish_service(get_service(u, PA_OBJECT(sink))); + + for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx))) + if (!shall_ignore(PA_OBJECT(source))) + publish_service(get_service(u, PA_OBJECT(source))); + + return publish_main_service(u); +} + +static void unpublish_all_services(struct userdata *u) { + void *state = NULL; + struct service *s; + + pa_assert(u); + + pa_log_debug("Unpublishing services in Bonjour"); + + while ((s = pa_hashmap_iterate(u->services, &state, NULL))) + service_free(s); + + if (u->main_service) + DNSServiceRefDeallocate(u->main_service); +} + +int pa__init(pa_module*m) { + + struct userdata *u; + pa_modargs *ma = NULL; + char *hn, *un; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->native = pa_native_protocol_get(u->core); + + u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); + + un = pa_get_user_name_malloc(); + hn = pa_get_host_name_malloc(); + u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1); + pa_xfree(un); + pa_xfree(hn); + + publish_all_services(u); + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata*u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + unpublish_all_services(u); + + if (u->services) + pa_hashmap_free(u->services, NULL, NULL); + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->sink_changed_slot) + pa_hook_slot_free(u->sink_changed_slot); + if (u->source_changed_slot) + pa_hook_slot_free(u->source_changed_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->native) + pa_native_protocol_unref(u->native); + + pa_xfree(u->service_name); + pa_xfree(u); +} diff --git a/src/modules/macosx/module-coreaudio-detect.c b/src/modules/macosx/module-coreaudio-detect.c new file mode 100644 index 00000000..f4f2ee28 --- /dev/null +++ b/src/modules/macosx/module-coreaudio-detect.c @@ -0,0 +1,285 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009,2010 Daniel Mack <daniel@caiaq.de> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/llist.h> + +#include <CoreAudio/CoreAudio.h> + +#include "module-coreaudio-detect-symdef.h" + +#define DEVICE_MODULE_NAME "module-coreaudio-device" + +PA_MODULE_AUTHOR("Daniel Mack"); +PA_MODULE_DESCRIPTION("CoreAudio device detection"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("ioproc_frames=<passed on to module-coreaudio-device> "); + +static const char* const valid_modargs[] = { + "ioproc_frames", + NULL +}; + +typedef struct ca_device ca_device; + +struct ca_device { + AudioObjectID id; + unsigned int module_index; + PA_LLIST_FIELDS(ca_device); +}; + +struct userdata { + int detect_fds[2]; + pa_io_event *detect_io; + unsigned int ioproc_frames; + PA_LLIST_HEAD(ca_device, devices); +}; + +static int ca_device_added(struct pa_module *m, AudioObjectID id) { + AudioObjectPropertyAddress property_address; + OSStatus err; + pa_module *mod; + struct userdata *u; + struct ca_device *dev; + char *args, tmp[64]; + UInt32 size; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + /* To prevent generating a black hole that will suck us in, + don't create sources/sinks for PulseAudio virtual devices */ + + property_address.mSelector = kAudioDevicePropertyDeviceManufacturer; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + size = sizeof(tmp); + err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &size, tmp); + + if (!err && strcmp(tmp, "pulseaudio.org") == 0) + return 0; + + if (u->ioproc_frames) + args = pa_sprintf_malloc("object_id=%d ioproc_frames=%d", (int) id, u->ioproc_frames); + else + args = pa_sprintf_malloc("object_id=%d", (int) id); + + pa_log_debug("Loading %s with arguments '%s'", DEVICE_MODULE_NAME, args); + mod = pa_module_load(m->core, DEVICE_MODULE_NAME, args); + pa_xfree(args); + + if (!mod) { + pa_log_info("Failed to load module %s with arguments '%s'", DEVICE_MODULE_NAME, args); + return -1; + } + + dev = pa_xnew0(ca_device, 1); + dev->module_index = mod->index; + dev->id = id; + + PA_LLIST_INIT(ca_device, dev); + PA_LLIST_PREPEND(ca_device, u->devices, dev); + + return 0; +} + +static int ca_update_device_list(struct pa_module *m) { + AudioObjectPropertyAddress property_address; + OSStatus err; + UInt32 i, size, num_devices; + AudioDeviceID *device_id; + struct ca_device *dev; + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + property_address.mSelector = kAudioHardwarePropertyDevices; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + /* get the number of currently available audio devices */ + err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &property_address, 0, NULL, &size); + if (err) { + pa_log("Unable to get data size for kAudioHardwarePropertyDevices."); + return -1; + } + + num_devices = size / sizeof(AudioDeviceID); + device_id = pa_xnew(AudioDeviceID, num_devices); + + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_address, 0, NULL, &size, device_id); + if (err) { + pa_log("Unable to get kAudioHardwarePropertyDevices."); + pa_xfree(device_id); + return -1; + } + + /* scan for devices which are reported but not in our cached list */ + for (i = 0; i < num_devices; i++) { + bool found = FALSE; + + PA_LLIST_FOREACH(dev, u->devices) + if (dev->id == device_id[i]) { + found = TRUE; + break; + } + + if (!found) + ca_device_added(m, device_id[i]); + } + + /* scan for devices which are in our cached list but are not reported */ +scan_removed: + + PA_LLIST_FOREACH(dev, u->devices) { + bool found = FALSE; + + for (i = 0; i < num_devices; i++) + if (dev->id == device_id[i]) { + found = TRUE; + break; + } + + if (!found) { + pa_log_debug("object id %d has been removed (module index %d) %p", (unsigned int) dev->id, dev->module_index, dev); + pa_module_unload_request_by_index(m->core, dev->module_index, TRUE); + PA_LLIST_REMOVE(ca_device, u->devices, dev); + pa_xfree(dev); + /* the current list item pointer is not valid anymore, so start over. */ + goto scan_removed; + } + } + + pa_xfree(device_id); + return 0; +} + +static OSStatus property_listener_proc(AudioObjectID objectID, UInt32 numberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *clientData) +{ + struct userdata *u = clientData; + char dummy = 1; + + pa_assert(u); + + /* dispatch module load/unload operations in main thread */ + write(u->detect_fds[1], &dummy, 1); + + return 0; +} + +static void detect_handle(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_module *m = userdata; + char dummy; + + pa_assert(m); + + read(fd, &dummy, 1); + ca_update_device_list(m); +} + +int pa__init(pa_module *m) { + struct userdata *u = pa_xnew0(struct userdata, 1); + AudioObjectPropertyAddress property_address; + pa_modargs *ma; + + pa_assert(m); + + m->userdata = u; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + pa_modargs_get_value_u32(ma, "ioproc_frames", &u->ioproc_frames); + + property_address.mSelector = kAudioHardwarePropertyDevices; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + if (AudioObjectAddPropertyListener(kAudioObjectSystemObject, &property_address, property_listener_proc, u)) { + pa_log("AudioObjectAddPropertyListener() failed."); + goto fail; + } + + if (ca_update_device_list(m)) + goto fail; + + pa_assert_se(pipe(u->detect_fds) == 0); + pa_assert_se(u->detect_io = m->core->mainloop->io_new(m->core->mainloop, u->detect_fds[0], PA_IO_EVENT_INPUT, detect_handle, m)); + + return 0; + +fail: + pa_xfree(u); + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + struct ca_device *dev; + AudioObjectPropertyAddress property_address; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + dev = u->devices; + + property_address.mSelector = kAudioHardwarePropertyDevices; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &property_address, property_listener_proc, u); + + while (dev) { + struct ca_device *next = dev->next; + + pa_module_unload_request_by_index(m->core, dev->module_index, TRUE); + pa_xfree(dev); + + dev = next; + } + + if (u->detect_fds[0] >= 0) + close(u->detect_fds[0]); + + if (u->detect_fds[1] >= 0) + close(u->detect_fds[1]); + + if (u->detect_io) + m->core->mainloop->io_free(u->detect_io); + + pa_xfree(u); +} diff --git a/src/modules/macosx/module-coreaudio-device.c b/src/modules/macosx/module-coreaudio-device.c new file mode 100644 index 00000000..d2762819 --- /dev/null +++ b/src/modules/macosx/module-coreaudio-device.c @@ -0,0 +1,876 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009,2010 Daniel Mack <daniel@caiaq.de> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* TODO: + - implement hardware volume controls + - handle audio device stream format changes (will require changes to the core) +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/llist.h> +#include <pulsecore/card.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> + +#include <CoreAudio/CoreAudio.h> +#include <CoreAudio/CoreAudioTypes.h> +#include <CoreAudio/AudioHardware.h> + +#include "module-coreaudio-device-symdef.h" + +#define DEFAULT_FRAMES_PER_IOPROC 512 + +PA_MODULE_AUTHOR("Daniel Mack"); +PA_MODULE_DESCRIPTION("CoreAudio device"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("object_id=<the CoreAudio device id> " + "ioproc_frames=<audio frames per IOProc call> "); + +static const char* const valid_modargs[] = { + "object_id", + "ioproc_frames", + NULL +}; + +enum { + CA_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX, +}; + +typedef struct coreaudio_sink coreaudio_sink; +typedef struct coreaudio_source coreaudio_source; + +struct userdata { + AudioObjectID object_id; + AudioDeviceIOProcID proc_id; + + pa_thread_mq thread_mq; + pa_asyncmsgq *async_msgq; + + pa_rtpoll *rtpoll; + pa_thread *thread; + + pa_module *module; + pa_card *card; + pa_bool_t running; + + char *device_name, *vendor_name; + + const AudioBufferList *render_input_data; + AudioBufferList *render_output_data; + + AudioStreamBasicDescription stream_description; + + PA_LLIST_HEAD(coreaudio_sink, sinks); + PA_LLIST_HEAD(coreaudio_source, sources); +}; + +struct coreaudio_sink { + pa_sink *pa_sink; + struct userdata *userdata; + + char *name; + unsigned int channel_idx; + pa_bool_t active; + + pa_channel_map map; + pa_sample_spec ss; + + PA_LLIST_FIELDS(coreaudio_sink); +}; + +struct coreaudio_source { + pa_source *pa_source; + struct userdata *userdata; + + char *name; + unsigned int channel_idx; + pa_bool_t active; + + pa_channel_map map; + pa_sample_spec ss; + + PA_LLIST_FIELDS(coreaudio_source); +}; + +static OSStatus io_render_proc (AudioDeviceID device, + const AudioTimeStamp *now, + const AudioBufferList *inputData, + const AudioTimeStamp *inputTime, + AudioBufferList *outputData, + const AudioTimeStamp *outputTime, + void *clientData) +{ + struct userdata *u = clientData; + + pa_assert(u); + pa_assert(device == u->object_id); + + u->render_input_data = inputData; + u->render_output_data = outputData; + + if (u->sinks) + pa_assert_se(pa_asyncmsgq_send(u->async_msgq, PA_MSGOBJECT(u->sinks->pa_sink), + CA_MESSAGE_RENDER, NULL, 0, NULL) == 0); + + if (u->sources) + pa_assert_se(pa_asyncmsgq_send(u->async_msgq, PA_MSGOBJECT(u->sources->pa_source), + CA_MESSAGE_RENDER, NULL, 0, NULL) == 0); + + return 0; +} + +static OSStatus ca_stream_format_changed(AudioObjectID objectID, + UInt32 numberAddresses, + const AudioObjectPropertyAddress addresses[], + void *clientData) +{ + struct userdata *u = clientData; + UInt32 i; + + pa_assert(u); + + /* REVISIT: PA can't currently handle external format change requests. + * Hence, we set the original format back in this callback to avoid horrible audio artefacts. + * The device settings will appear to be 'locked' for any application as long as the PA daemon is running. + * Once we're able to propagate such events up in the core, this needs to be changed. */ + + for (i = 0; i < numberAddresses; i++) + AudioObjectSetPropertyData(objectID, addresses + i, 0, NULL, sizeof(u->stream_description), &u->stream_description); + + return 0; +} + +static pa_usec_t get_latency_us(pa_object *o) { + struct userdata *u; + pa_sample_spec *ss; + bool is_source; + UInt32 v, total = 0; + UInt32 err, size = sizeof(v); + AudioObjectPropertyAddress property_address; + AudioObjectID stream_id; + + if (pa_sink_isinstance(o)) { + coreaudio_sink *sink = PA_SINK(o)->userdata; + + u = sink->userdata; + ss = &sink->ss; + is_source = FALSE; + } else if (pa_source_isinstance(o)) { + coreaudio_source *source = PA_SOURCE(o)->userdata; + + u = source->userdata; + ss = &source->ss; + is_source = TRUE; + } else + pa_assert_not_reached(); + + pa_assert(u); + + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + /* get the device latency */ + property_address.mSelector = kAudioDevicePropertyLatency; + size = sizeof(total); + AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &total); + total += v; + + /* the the IOProc buffer size */ + property_address.mSelector = kAudioDevicePropertyBufferFrameSize; + size = sizeof(v); + AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &v); + total += v; + + /* IOProc safety offset - this value is the same for both directions, hence we divide it by 2 */ + property_address.mSelector = kAudioDevicePropertySafetyOffset; + size = sizeof(v); + AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &v); + total += v / 2; + + /* get the stream latency. + * FIXME: this assumes the stream latency is the same for all streams */ + property_address.mSelector = kAudioDevicePropertyStreams; + size = sizeof(stream_id); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &stream_id); + if (!err) { + property_address.mSelector = kAudioStreamPropertyLatency; + size = sizeof(v); + err = AudioObjectGetPropertyData(stream_id, &property_address, 0, NULL, &size, &v); + if (!err) + total += v; + } + + return pa_bytes_to_usec(total * pa_frame_size(ss), ss); +} + +static void ca_device_check_device_state(struct userdata *u) { + coreaudio_sink *ca_sink; + coreaudio_source *ca_source; + pa_bool_t active = FALSE; + + pa_assert(u); + + for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next) + if (ca_sink->active) + active = TRUE; + + for (ca_source = u->sources; ca_source; ca_source = ca_source->next) + if (ca_source->active) + active = TRUE; + + if (active && !u->running) + AudioDeviceStart(u->object_id, u->proc_id); + else if (!active && u->running) + AudioDeviceStop(u->object_id, u->proc_id); + + u->running = active; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + coreaudio_sink *sink = PA_SINK(o)->userdata; + struct userdata *u = sink->userdata; + unsigned int i; + pa_memchunk audio_chunk; + + switch (code) { + case CA_MESSAGE_RENDER: { + /* audio out */ + for (i = 0; i < u->render_output_data->mNumberBuffers; i++) { + AudioBuffer *buf = u->render_output_data->mBuffers + i; + + pa_assert(sink); + + if (PA_SINK_IS_OPENED(sink->pa_sink->thread_info.state)) { + if (sink->pa_sink->thread_info.rewind_requested) + pa_sink_process_rewind(sink->pa_sink, 0); + + audio_chunk.memblock = pa_memblock_new_fixed(u->module->core->mempool, buf->mData, buf->mDataByteSize, FALSE); + audio_chunk.length = buf->mDataByteSize; + audio_chunk.index = 0; + + pa_sink_render_into_full(sink->pa_sink, &audio_chunk); + pa_memblock_unref_fixed(audio_chunk.memblock); + } + + sink = sink->next; + } + + return 0; + } + + case PA_SINK_MESSAGE_GET_LATENCY: { + *((pa_usec_t *) data) = get_latency_us(PA_OBJECT(o)); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + coreaudio_source *source = PA_SOURCE(o)->userdata; + struct userdata *u = source->userdata; + unsigned int i; + pa_memchunk audio_chunk; + + switch (code) { + case CA_MESSAGE_RENDER: { + /* audio in */ + for (i = 0; i < u->render_input_data->mNumberBuffers; i++) { + const AudioBuffer *buf = u->render_input_data->mBuffers + i; + + pa_assert(source); + + if (PA_SOURCE_IS_OPENED(source->pa_source->thread_info.state)) { + audio_chunk.memblock = pa_memblock_new_fixed(u->module->core->mempool, buf->mData, buf->mDataByteSize, TRUE); + audio_chunk.length = buf->mDataByteSize; + audio_chunk.index = 0; + + pa_source_post(source->pa_source, &audio_chunk); + pa_memblock_unref_fixed(audio_chunk.memblock); + } + + source = source->next; + } + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + *((pa_usec_t *) data) = get_latency_us(PA_OBJECT(o)); + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk);; +} + +static int ca_sink_set_state(pa_sink *s, pa_sink_state_t state) +{ + coreaudio_sink *sink = s->userdata; + + switch (state) { + case PA_SINK_SUSPENDED: + case PA_SINK_IDLE: + sink->active = FALSE; + break; + + case PA_SINK_RUNNING: + sink->active = TRUE; + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + ; + } + + ca_device_check_device_state(sink->userdata); + + return 0; +} + +static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx) { + OSStatus err; + UInt32 size; + struct userdata *u = m->userdata; + pa_sink_new_data new_data; + pa_sink_flags_t flags = PA_SINK_LATENCY | PA_SINK_HARDWARE; + coreaudio_sink *ca_sink; + pa_sink *sink; + unsigned int i; + char tmp[255]; + pa_strbuf *strbuf; + AudioObjectPropertyAddress property_address; + + ca_sink = pa_xnew0(coreaudio_sink, 1); + ca_sink->map.channels = buf->mNumberChannels; + ca_sink->ss.channels = buf->mNumberChannels; + ca_sink->channel_idx = channel_idx; + + /* build a name for this stream */ + strbuf = pa_strbuf_new(); + + for (i = 0; i < buf->mNumberChannels; i++) { + property_address.mSelector = kAudioObjectPropertyElementName; + property_address.mScope = kAudioDevicePropertyScopeOutput; + property_address.mElement = channel_idx + i + 1; + size = sizeof(tmp); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp); + if (err || !strlen(tmp)) + snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement); + + if (i > 0) + pa_strbuf_puts(strbuf, ", "); + + pa_strbuf_puts(strbuf, tmp); + } + + ca_sink->name = pa_strbuf_tostring_free(strbuf); + + pa_log_debug("Stream name is >%s<", ca_sink->name); + + /* default to mono streams */ + for (i = 0; i < ca_sink->map.channels; i++) + ca_sink->map.map[i] = PA_CHANNEL_POSITION_MONO; + + if (buf->mNumberChannels == 2) { + ca_sink->map.map[0] = PA_CHANNEL_POSITION_LEFT; + ca_sink->map.map[1] = PA_CHANNEL_POSITION_RIGHT; + } + + ca_sink->ss.rate = u->stream_description.mSampleRate; + ca_sink->ss.format = PA_SAMPLE_FLOAT32LE; + + pa_sink_new_data_init(&new_data); + new_data.card = u->card; + new_data.driver = __FILE__; + new_data.module = u->module; + new_data.namereg_fail = FALSE; + pa_sink_new_data_set_name(&new_data, ca_sink->name); + pa_sink_new_data_set_channel_map(&new_data, &ca_sink->map); + pa_sink_new_data_set_sample_spec(&new_data, &ca_sink->ss); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_PRODUCT_NAME, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "mmap"); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_CLASS, "sound"); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_API, "CoreAudio"); + pa_proplist_setf(new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) buf->mDataByteSize); + + if (u->vendor_name) + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_VENDOR_NAME, u->vendor_name); + + sink = pa_sink_new(m->core, &new_data, flags); + pa_sink_new_data_done(&new_data); + + if (!sink) { + pa_log("unable to create sink."); + return -1; + } + + sink->parent.process_msg = sink_process_msg; + sink->userdata = ca_sink; + sink->set_state = ca_sink_set_state; + + pa_sink_set_asyncmsgq(sink, u->thread_mq.inq); + pa_sink_set_rtpoll(sink, u->rtpoll); + + ca_sink->pa_sink = sink; + ca_sink->userdata = u; + + PA_LLIST_PREPEND(coreaudio_sink, u->sinks, ca_sink); + + return 0; +} + +static int ca_source_set_state(pa_source *s, pa_source_state_t state) +{ + coreaudio_source *source = s->userdata; + + switch (state) { + case PA_SOURCE_SUSPENDED: + case PA_SOURCE_IDLE: + source->active = FALSE; + break; + + case PA_SOURCE_RUNNING: + source->active = TRUE; + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + case PA_SOURCE_INVALID_STATE: + ; + } + + ca_device_check_device_state(source->userdata); + + return 0; +} + +static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_idx) { + OSStatus err; + UInt32 size; + struct userdata *u = m->userdata; + pa_source_new_data new_data; + pa_source_flags_t flags = PA_SOURCE_LATENCY | PA_SOURCE_HARDWARE; + coreaudio_source *ca_source; + pa_source *source; + unsigned int i; + char tmp[255]; + pa_strbuf *strbuf; + AudioObjectPropertyAddress property_address; + + ca_source = pa_xnew0(coreaudio_source, 1); + ca_source->map.channels = buf->mNumberChannels; + ca_source->ss.channels = buf->mNumberChannels; + ca_source->channel_idx = channel_idx; + + /* build a name for this stream */ + strbuf = pa_strbuf_new(); + + for (i = 0; i < buf->mNumberChannels; i++) { + property_address.mSelector = kAudioObjectPropertyElementName; + property_address.mScope = kAudioDevicePropertyScopeInput; + property_address.mElement = channel_idx + i + 1; + size = sizeof(tmp); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp); + if (err || !strlen(tmp)) + snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement); + + if (i > 0) + pa_strbuf_puts(strbuf, ", "); + + pa_strbuf_puts(strbuf, tmp); + } + + ca_source->name = pa_strbuf_tostring_free(strbuf); + + pa_log_debug("Stream name is >%s<", ca_source->name); + + /* default to mono streams */ + for (i = 0; i < ca_source->map.channels; i++) + ca_source->map.map[i] = PA_CHANNEL_POSITION_MONO; + + if (buf->mNumberChannels == 2) { + ca_source->map.map[0] = PA_CHANNEL_POSITION_LEFT; + ca_source->map.map[1] = PA_CHANNEL_POSITION_RIGHT; + } + + ca_source->ss.rate = u->stream_description.mSampleRate; + ca_source->ss.format = PA_SAMPLE_FLOAT32LE; + + pa_source_new_data_init(&new_data); + new_data.card = u->card; + new_data.driver = __FILE__; + new_data.module = u->module; + new_data.namereg_fail = FALSE; + pa_source_new_data_set_name(&new_data, ca_source->name); + pa_source_new_data_set_channel_map(&new_data, &ca_source->map); + pa_source_new_data_set_sample_spec(&new_data, &ca_source->ss); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_PRODUCT_NAME, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, u->device_name); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "mmap"); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_CLASS, "sound"); + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_API, "CoreAudio"); + pa_proplist_setf(new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) buf->mDataByteSize); + + if (u->vendor_name) + pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_VENDOR_NAME, u->vendor_name); + + source = pa_source_new(m->core, &new_data, flags); + pa_source_new_data_done(&new_data); + + if (!source) { + pa_log("unable to create source."); + return -1; + } + + source->parent.process_msg = source_process_msg; + source->userdata = ca_source; + source->set_state = ca_source_set_state; + + pa_source_set_asyncmsgq(source, u->thread_mq.inq); + pa_source_set_rtpoll(source, u->rtpoll); + + ca_source->pa_source = source; + ca_source->userdata = u; + + PA_LLIST_PREPEND(coreaudio_source, u->sources, ca_source); + + return 0; +} + +static int ca_device_create_streams(pa_module *m, bool direction_in) { + OSStatus err; + UInt32 size, i, channel_idx; + struct userdata *u = m->userdata; + AudioBufferList *buffer_list; + AudioObjectPropertyAddress property_address; + + property_address.mScope = direction_in ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + property_address.mElement = kAudioObjectPropertyElementMaster; + + /* get current stream format */ + size = sizeof(AudioStreamBasicDescription); + property_address.mSelector = kAudioDevicePropertyStreamFormat; + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, &u->stream_description); + if (err) { + /* no appropriate streams found - silently bail. */ + return -1; + } + + if (u->stream_description.mFormatID != kAudioFormatLinearPCM) { + pa_log("Unsupported audio format '%c%c%c%c'", + (char) (u->stream_description.mFormatID >> 24), + (char) (u->stream_description.mFormatID >> 16) & 0xff, + (char) (u->stream_description.mFormatID >> 8) & 0xff, + (char) (u->stream_description.mFormatID & 0xff)); + return -1; + } + + /* get stream configuration */ + size = 0; + property_address.mSelector = kAudioDevicePropertyStreamConfiguration; + err = AudioObjectGetPropertyDataSize(u->object_id, &property_address, 0, NULL, &size); + if (err) { + pa_log("Failed to get kAudioDevicePropertyStreamConfiguration (%s).", direction_in ? "input" : "output"); + return -1; + } + + if (!size) + return 0; + + buffer_list = (AudioBufferList *) pa_xmalloc(size); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, buffer_list); + + if (!err) { + pa_log_debug("Sample rate: %f", u->stream_description.mSampleRate); + pa_log_debug("%d bytes per packet", (unsigned int) u->stream_description.mBytesPerPacket); + pa_log_debug("%d frames per packet", (unsigned int) u->stream_description.mFramesPerPacket); + pa_log_debug("%d bytes per frame", (unsigned int) u->stream_description.mBytesPerFrame); + pa_log_debug("%d channels per frame", (unsigned int) u->stream_description.mChannelsPerFrame); + pa_log_debug("%d bits per channel", (unsigned int) u->stream_description.mBitsPerChannel); + + for (channel_idx = 0, i = 0; i < buffer_list->mNumberBuffers; i++) { + AudioBuffer *buf = buffer_list->mBuffers + i; + + if (direction_in) + ca_device_create_source(m, buf, channel_idx); + else + ca_device_create_sink(m, buf, channel_idx); + + channel_idx += buf->mNumberChannels; + } + } + + pa_xfree(buffer_list); + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_assert(u->module); + pa_assert(u->module->core); + + pa_log_debug("Thread starting up"); + + if (u->module->core->realtime_scheduling) + pa_make_realtime(u->module->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + for (;;) { + int ret; + + ret = pa_rtpoll_run(u->rtpoll, TRUE); + + if (ret < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->module->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module *m) { + OSStatus err; + UInt32 size, frames; + struct userdata *u = NULL; + pa_modargs *ma = NULL; + char tmp[64]; + pa_card_new_data card_new_data; + coreaudio_sink *ca_sink; + coreaudio_source *ca_source; + AudioObjectPropertyAddress property_address; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->module = m; + m->userdata = u; + + if (pa_modargs_get_value_u32(ma, "object_id", (unsigned int *) &u->object_id) != 0) { + pa_log("Failed to parse object_id argument."); + goto fail; + } + + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + /* get device product name */ + property_address.mSelector = kAudioDevicePropertyDeviceName; + size = sizeof(tmp); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp); + if (err) { + pa_log("Failed to get kAudioDevicePropertyDeviceName (err = %08x).", (int) err); + goto fail; + } + + u->device_name = pa_xstrdup(tmp); + + pa_card_new_data_init(&card_new_data); + pa_proplist_sets(card_new_data.proplist, PA_PROP_DEVICE_STRING, tmp); + card_new_data.driver = __FILE__; + pa_card_new_data_set_name(&card_new_data, tmp); + pa_log_info("Initializing module for CoreAudio device '%s' (id %d)", tmp, (unsigned int) u->object_id); + + /* get device vendor name (may fail) */ + property_address.mSelector = kAudioDevicePropertyDeviceManufacturer; + size = sizeof(tmp); + err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp); + if (!err) + u->vendor_name = pa_xstrdup(tmp); + + /* create the card object */ + u->card = pa_card_new(m->core, &card_new_data); + if (!u->card) { + pa_log("Unable to create card.\n"); + goto fail; + } + + pa_card_new_data_done(&card_new_data); + u->card->userdata = u; + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->async_msgq = pa_asyncmsgq_new(0); + pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->async_msgq); + + PA_LLIST_HEAD_INIT(coreaudio_sink, u->sinks); + + /* create sinks */ + ca_device_create_streams(m, FALSE); + + /* create sources */ + ca_device_create_streams(m, TRUE); + + /* create the message thread */ + if (!(u->thread = pa_thread_new(u->device_name, thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* register notification callback for stream format changes */ + property_address.mSelector = kAudioDevicePropertyStreamFormat; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + AudioObjectAddPropertyListener(u->object_id, &property_address, ca_stream_format_changed, u); + + /* set number of frames in IOProc */ + frames = DEFAULT_FRAMES_PER_IOPROC; + pa_modargs_get_value_u32(ma, "ioproc_frames", (unsigned int *) &frames); + + property_address.mSelector = kAudioDevicePropertyBufferFrameSize; + AudioObjectSetPropertyData(u->object_id, &property_address, 0, NULL, sizeof(frames), &frames); + pa_log_debug("%u frames per IOProc\n", (unsigned int) frames); + + /* create one ioproc for both directions */ + err = AudioDeviceCreateIOProcID(u->object_id, io_render_proc, u, &u->proc_id); + if (err) { + pa_log("AudioDeviceCreateIOProcID() failed (err = %08x\n).", (int) err); + goto fail; + } + + for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next) + pa_sink_put(ca_sink->pa_sink); + + for (ca_source = u->sources; ca_source; ca_source = ca_source->next) + pa_source_put(ca_source->pa_source); + + pa_modargs_free(ma); + + return 0; + +fail: + if (u) + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + coreaudio_sink *ca_sink; + coreaudio_source *ca_source; + AudioObjectPropertyAddress property_address; + + pa_assert(m); + + u = m->userdata; + pa_assert(u); + + /* unlink sinks */ + for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next) + if (ca_sink->pa_sink) + pa_sink_unlink(ca_sink->pa_sink); + + /* unlink sources */ + for (ca_source = u->sources; ca_source; ca_source = ca_source->next) + if (ca_source->pa_source) + pa_source_unlink(ca_source->pa_source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + pa_thread_mq_done(&u->thread_mq); + pa_asyncmsgq_unref(u->async_msgq); + } + + /* free sinks */ + for (ca_sink = u->sinks; ca_sink;) { + coreaudio_sink *next = ca_sink->next; + + if (ca_sink->pa_sink) + pa_sink_unref(ca_sink->pa_sink); + + pa_xfree(ca_sink->name); + pa_xfree(ca_sink); + ca_sink = next; + } + + /* free sources */ + for (ca_source = u->sources; ca_source;) { + coreaudio_source *next = ca_source->next; + + if (ca_source->pa_source) + pa_source_unref(ca_source->pa_source); + + pa_xfree(ca_source->name); + pa_xfree(ca_source); + ca_source = next; + } + + if (u->proc_id) { + AudioDeviceStop(u->object_id, u->proc_id); + AudioDeviceDestroyIOProcID(u->object_id, u->proc_id); + } + + property_address.mSelector = kAudioDevicePropertyStreamFormat; + property_address.mScope = kAudioObjectPropertyScopeGlobal; + property_address.mElement = kAudioObjectPropertyElementMaster; + + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &property_address, ca_stream_format_changed, u); + + pa_xfree(u->device_name); + pa_xfree(u->vendor_name); + pa_rtpoll_free(u->rtpoll); + pa_card_free(u->card); + + pa_xfree(u); +} diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c index aee1c650..4c871da4 100644 --- a/src/modules/module-always-sink.c +++ b/src/modules/module-always-sink.c @@ -24,18 +24,18 @@ #endif #include <pulse/xmalloc.h> +#include <pulse/i18n.h> #include <pulsecore/core.h> -#include <pulsecore/sink-input.h> +#include <pulsecore/sink.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulsecore/namereg.h> #include <pulsecore/core-util.h> #include "module-always-sink-symdef.h" PA_MODULE_AUTHOR("Colin Guthrie"); -PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one"); +PA_MODULE_DESCRIPTION(_("Always keeps at least one sink loaded even if it's a null one")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( @@ -78,7 +78,8 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata* u->ignore = TRUE; - t = pa_sprintf_malloc("sink_name=%s", u->sink_name); + t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name, + _("Dummy Output")); m = pa_module_load(c, "module-null-sink", t); u->null_module = m ? m->index : PA_INVALID_INDEX; pa_xfree(t); diff --git a/src/modules/module-augment-properties.c b/src/modules/module-augment-properties.c index c3e5997a..05f6f0a4 100644 --- a/src/modules/module-augment-properties.c +++ b/src/modules/module-augment-properties.c @@ -24,12 +24,11 @@ #endif #include <sys/stat.h> +#include <dirent.h> +#include <time.h> #include <pulse/xmalloc.h> -#include <pulse/volume.h> -#include <pulse/channelmap.h> -#include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> @@ -58,6 +57,7 @@ struct rule { char *process_name; char *application_name; char *icon_name; + char *role; pa_proplist *proplist; }; @@ -72,12 +72,21 @@ static void rule_free(struct rule *r) { pa_xfree(r->process_name); pa_xfree(r->application_name); pa_xfree(r->icon_name); + pa_xfree(r->role); if (r->proplist) pa_proplist_free(r->proplist); pa_xfree(r); } -static int parse_properties(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_properties( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + struct rule *r = userdata; pa_proplist *n; @@ -93,11 +102,56 @@ static int parse_properties(const char *filename, unsigned line, const char *sec return 0; } -static int check_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int parse_categories( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + + struct rule *r = userdata; + const char *state = NULL; + char *c; + + while ((c = pa_split(rvalue, ";", &state))) { + + if (pa_streq(c, "Game")) { + pa_xfree(r->role); + r->role = pa_xstrdup("game"); + } else if (pa_streq(c, "Telephony")) { + pa_xfree(r->role); + r->role = pa_xstrdup("phone"); + } + + pa_xfree(c); + } + + return 0; +} + +static int check_type( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return pa_streq(rvalue, "Application") ? 0 : -1; } -static int catch_all(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) { +static int catch_all( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + const char *rvalue, + void *data, + void *userdata) { + return 0; } @@ -109,29 +163,65 @@ static void update_rule(struct rule *r) { { "Icon", pa_config_parse_string, NULL, "Desktop Entry" }, { "Type", check_type, NULL, "Desktop Entry" }, { "X-PulseAudio-Properties", parse_properties, NULL, "Desktop Entry" }, + { "Categories", parse_categories, NULL, "Desktop Entry" }, { NULL, catch_all, NULL, NULL }, { NULL, NULL, NULL, NULL }, }; + pa_bool_t found = FALSE; pa_assert(r); fn = pa_sprintf_malloc(DESKTOPFILEDIR PA_PATH_SEP "%s.desktop", r->process_name); - if (stat(fn, &st) < 0) { + if (stat(fn, &st) == 0) + found = TRUE; + else { +#ifdef DT_DIR + DIR *desktopfiles_dir; + struct dirent *dir; + + /* Let's try a more aggressive search, but only one level */ + if ((desktopfiles_dir = opendir(DESKTOPFILEDIR))) { + while ((dir = readdir(desktopfiles_dir))) { + if (dir->d_type != DT_DIR + || strcmp(dir->d_name, ".") == 0 + || strcmp(dir->d_name, "..") == 0) + continue; + + pa_xfree(fn); + fn = pa_sprintf_malloc(DESKTOPFILEDIR PA_PATH_SEP "%s" PA_PATH_SEP "%s.desktop", dir->d_name, r->process_name); + + if (stat(fn, &st) == 0) { + found = TRUE; + break; + } + } + closedir(desktopfiles_dir); + } +#endif + } + if (!found) { r->good = FALSE; pa_xfree(fn); return; } - if (r->good && st.st_mtime == r->mtime) { - pa_xfree(fn); - return; - } + if (r->good) { + if (st.st_mtime == r->mtime) { + /* Theoretically the filename could have changed, but if so + having the same mtime is very unlikely so not worth tracking it in r */ + pa_xfree(fn); + return; + } + pa_log_debug("Found %s (which has been updated since we last checked).", fn); + } else + pa_log_debug("Found %s.", fn); r->good = TRUE; r->mtime = st.st_mtime; pa_xfree(r->application_name); pa_xfree(r->icon_name); - r->application_name = r->icon_name = NULL; + pa_xfree(r->role); + r->application_name = r->icon_name = r->role = NULL; if (r->proplist) pa_proplist_clear(r->proplist); @@ -151,6 +241,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { if (!r->good) return; + if (r->proplist) + pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->icon_name) if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME)) pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, r->icon_name); @@ -164,8 +257,9 @@ static void apply_rule(struct rule *r, pa_proplist *p) { pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, r->application_name); } - if (r->proplist) - pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist); + if (r->role) + if (!pa_proplist_contains(p, PA_PROP_MEDIA_ROLE)) + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, r->role); } static void make_room(pa_hashmap *cache) { @@ -259,7 +353,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module *m) { diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 85478d12..fc5df5f6 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -29,12 +29,11 @@ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> -#include <ctype.h> +#include <pulse/gccmacro.h> #include <pulse/xmalloc.h> -#include <pulse/volume.h> #include <pulse/timeval.h> -#include <pulse/util.h> +#include <pulse/rtclock.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -45,6 +44,7 @@ #include <pulsecore/card.h> #include <pulsecore/namereg.h> #include <pulsecore/database.h> +#include <pulsecore/tagstruct.h> #include "module-card-restore-symdef.h" @@ -53,7 +53,7 @@ PA_MODULE_DESCRIPTION("Automatically restore profile of cards"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) static const char* const valid_modargs[] = { NULL @@ -72,15 +72,14 @@ struct userdata { struct entry { uint8_t version; - char profile[PA_NAME_MAX]; -} PA_GCC_PACKED ; + char *profile; +}; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; pa_assert(a); pa_assert(e); - pa_assert(tv); pa_assert(u); pa_assert(e == u->save_time_event); @@ -91,11 +90,95 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct pa_log_info("Synced."); } -static struct entry* read_entry(struct userdata *u, const char *name) { +static void trigger_save(struct userdata *u) { + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static struct entry* entry_new(void) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + return r; +} + +static void entry_free(struct entry* e) { + pa_assert(e); + + pa_xfree(e->profile); + pa_xfree(e); +} + +static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e) { + pa_tagstruct *t; pa_datum key, data; + pa_bool_t r; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu8(t, e->version); + pa_tagstruct_puts(t, e->profile); + + key.data = (char *) name; + key.size = strlen(name); + + data.data = (void*)pa_tagstruct_data(t, &data.size); + + r = (pa_database_set(u->database, &key, &data, TRUE) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 1 +static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) { + struct legacy_entry { + uint8_t version; + char profile[PA_NAME_MAX]; + } PA_GCC_PACKED ; + struct legacy_entry *le; struct entry *e; pa_assert(u); + pa_assert(data); + + if (data->size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + return NULL; + } + + le = (struct legacy_entry*)data->data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + return NULL; + } + + if (!memchr(le->profile, 0, sizeof(le->profile))) { + pa_log_warn("Profile has missing NUL byte."); + return NULL; + } + + e = entry_new(); + e->profile = pa_xstrdup(le->profile); + return e; +} +#endif + +static struct entry* entry_read(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e = NULL; + pa_tagstruct *t = NULL; + const char* profile; + + pa_assert(u); pa_assert(name); key.data = (char*) name; @@ -106,46 +189,54 @@ static struct entry* read_entry(struct userdata *u, const char *name) { if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.size != sizeof(struct entry)) { - pa_log_debug("Database contains entry for card %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); - goto fail; - } + t = pa_tagstruct_new(data.data, data.size); + e = entry_new(); - e = (struct entry*) data.data; + if (pa_tagstruct_getu8(t, &e->version) < 0 || + e->version > ENTRY_VERSION || + pa_tagstruct_gets(t, &profile) < 0) { - if (e->version != ENTRY_VERSION) { - pa_log_debug("Version of database entry for card %s doesn't match our version. Probably due to upgrade, ignoring.", name); goto fail; } - if (!memchr(e->profile, 0, sizeof(e->profile))) { - pa_log_warn("Database contains entry for card %s with missing NUL byte in profile name", name); + e->profile = pa_xstrdup(profile); + + if (!pa_tagstruct_eof(t)) goto fail; - } + + pa_tagstruct_free(t); + pa_datum_free(&data); return e; fail: + pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name); + + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name); + if ((e = legacy_entry_read(u, &data))) { + pa_log_debug("Success. Saving new format for key: %s", name); + if (entry_write(u, name, e)) + trigger_save(u); + pa_datum_free(&data); + return e; + } else + pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name); +#endif + pa_datum_free(&data); return NULL; } -static void trigger_save(struct userdata *u) { - struct timeval tv; - - if (u->save_time_event) - return; - - pa_gettimeofday(&tv); - tv.tv_sec += SAVE_INTERVAL; - u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); -} - static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; - struct entry entry, *old; - pa_datum key, data; + struct entry *entry, *old; pa_card *card; pa_assert(c); @@ -155,8 +246,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - pa_zero(entry); - entry.version = ENTRY_VERSION; + entry = entry_new(); if (!(card = pa_idxset_get_by_index(c->cards, idx))) return; @@ -164,29 +254,25 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!card->save_profile) return; - pa_strlcpy(entry.profile, card->active_profile ? card->active_profile->name : "", sizeof(entry.profile)); + entry->profile = pa_xstrdup(card->active_profile ? card->active_profile->name : ""); - if ((old = read_entry(u, card->name))) { + if ((old = entry_read(u, card->name))) { - if (strncmp(old->profile, entry.profile, sizeof(entry.profile)) == 0) { - pa_xfree(old); + if (pa_streq(old->profile, entry->profile)) { + entry_free(old); + entry_free(entry); return; } - pa_xfree(old); + entry_free(old); } - key.data = card->name; - key.size = strlen(card->name); - - data.data = &entry; - data.size = sizeof(entry); - pa_log_info("Storing profile for card %s.", card->name); - pa_database_set(u->database, &key, &data, TRUE); + if (entry_write(u, card->name, entry)) + trigger_save(u); - trigger_save(u); + entry_free(entry); } static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new_data, struct userdata *u) { @@ -194,15 +280,16 @@ static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *new pa_assert(new_data); - if ((e = read_entry(u, new_data->name)) && e->profile[0]) { + if ((e = entry_read(u, new_data->name)) && e->profile[0]) { if (!new_data->active_profile) { - pa_card_new_data_set_profile(new_data, e->profile); pa_log_info("Restoring profile for card %s.", new_data->name); + pa_card_new_data_set_profile(new_data, e->profile); + new_data->save_profile = TRUE; } else pa_log_debug("Not restoring profile for card %s, because already set.", new_data->name); - pa_xfree(e); + entry_free(e); } return PA_HOOK_OK; @@ -222,11 +309,9 @@ int pa__init(pa_module*m) { goto fail; } - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; - u->save_time_event = NULL; - u->database = NULL; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_CARD, subscribe_callback, u); @@ -241,7 +326,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_log_info("Successfully opened database file '%s'.", fname); pa_xfree(fname); for (card = pa_idxset_first(m->core->cards, &idx); card; card = pa_idxset_next(m->core->cards, &idx)) @@ -256,7 +341,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c index fd9452b4..2a1d1751 100644 --- a/src/modules/module-cli.c +++ b/src/modules/module-cli.c @@ -25,6 +25,8 @@ #include <stdio.h> #include <unistd.h> +#include <fcntl.h> +#include <errno.h> #include <pulsecore/module.h> #include <pulsecore/iochannel.h> @@ -33,6 +35,7 @@ #include <pulsecore/log.h> #include <pulsecore/modargs.h> #include <pulsecore/macro.h> +#include <pulsecore/core-util.h> #include "module-cli-symdef.h" @@ -69,6 +72,9 @@ int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; pa_bool_t exit_on_eof = FALSE; +#ifndef OS_IS_WIN32 + int fd; +#endif pa_assert(m); @@ -88,15 +94,32 @@ int pa__init(pa_module*m) { } if (pa_stdio_acquire() < 0) { - pa_log("STDIN/STDUSE already in use."); + pa_log("STDIN/STDOUT already in use."); goto fail; } - io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); - pa_iochannel_set_noclose(io, 1); + /* We try to open the controlling tty anew here. This has the + * benefit of giving us a new fd that doesn't share the O_NDELAY + * flag with fds 0, 1, or 2. Since pa_iochannel_xxx needs O_NDELAY + * on its fd using those fds directly could set O_NDELAY which + * fprintf() doesn't really like, resulting in truncated output + * of log messages, particularly because if stdout and stderr are + * dup'ed they share the same O_NDELAY, too. */ + +#ifndef OS_IS_WIN32 + if ((fd = pa_open_cloexec("/dev/tty", O_RDWR|O_NONBLOCK, 0)) >= 0) { + io = pa_iochannel_new(m->core->mainloop, fd, fd); + pa_log_debug("Managed to open /dev/tty."); + } + else +#endif + { + io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); + pa_iochannel_set_noclose(io, TRUE); + pa_log_debug("Failed to open /dev/tty, using stdin/stdout fds instead."); + } m->userdata = pa_cli_new(m->core, io, m); - pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m); pa_modargs_free(ma); @@ -114,7 +137,7 @@ fail: void pa__done(pa_module*m) { pa_assert(m); - if (m->core->running_as_daemon == 0) { + if (m->userdata) { pa_cli_free(m->userdata); pa_stdio_release(); } diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c new file mode 100644 index 00000000..5d29af4b --- /dev/null +++ b/src/modules/module-combine-sink.c @@ -0,0 +1,1414 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/module.h> +#include <pulsecore/llist.h> +#include <pulsecore/sink.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/strlist.h> + +#include "module-combine-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Combine multiple sinks to one"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "slaves=<slave sinks> " + "adjust_time=<how often to readjust rates in s> " + "resample_method=<method> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map>"); + +#define DEFAULT_SINK_NAME "combined" + +#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) + +#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC) + +#define BLOCK_USEC (PA_USEC_PER_MSEC * 200) + +static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", + "slaves", + "adjust_time", + "resample_method", + "format", + "rate", + "channels", + "channel_map", + NULL +}; + +struct output { + struct userdata *userdata; + + pa_sink *sink; + pa_sink_input *sink_input; + pa_bool_t ignore_state_change; + + pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ + *outq; /* Message queue from this sink input to the sink thread */ + pa_rtpoll_item *inq_rtpoll_item_read, *inq_rtpoll_item_write; + pa_rtpoll_item *outq_rtpoll_item_read, *outq_rtpoll_item_write; + + pa_memblockq *memblockq; + + /* For communication of the stream latencies to the main thread */ + pa_usec_t total_latency; + + /* For coomunication of the stream parameters to the sink thread */ + pa_atomic_t max_request; + pa_atomic_t requested_latency; + + PA_LLIST_FIELDS(struct output); +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_time_event *time_event; + pa_usec_t adjust_time; + + pa_bool_t automatic; + pa_bool_t auto_desc; + + pa_strlist *unlinked_slaves; + + pa_hook_slot *sink_put_slot, *sink_unlink_slot, *sink_state_changed_slot; + + pa_resample_method_t resample_method; + + pa_usec_t block_usec; + + pa_idxset* outputs; /* managed in main context */ + + struct { + PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */ + pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */ + pa_usec_t timestamp; + pa_bool_t in_null_mode; + pa_smoother *smoother; + uint64_t counter; + } thread_info; +}; + +enum { + SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOVE_OUTPUT, + SINK_MESSAGE_NEED, + SINK_MESSAGE_UPDATE_LATENCY, + SINK_MESSAGE_UPDATE_MAX_REQUEST, + SINK_MESSAGE_UPDATE_REQUESTED_LATENCY +}; + +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, +}; + +static void output_disable(struct output *o); +static void output_enable(struct output *o); +static void output_free(struct output *o); +static int output_create_sink_input(struct output *o); + +static void adjust_rates(struct userdata *u) { + struct output *o; + pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency, avg_total_latency = 0; + uint32_t base_rate; + uint32_t idx; + unsigned n = 0; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (pa_idxset_size(u->outputs) <= 0) + return; + + if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))) + return; + + PA_IDXSET_FOREACH(o, u->outputs, idx) { + pa_usec_t sink_latency; + + if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) + continue; + + o->total_latency = pa_sink_input_get_latency(o->sink_input, &sink_latency); + o->total_latency += sink_latency; + + if (sink_latency > max_sink_latency) + max_sink_latency = sink_latency; + + if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) + min_total_latency = o->total_latency; + + avg_total_latency += o->total_latency; + n++; + + pa_log_debug("[%s] total=%0.2fms sink=%0.2fms ", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC, (double) sink_latency / PA_USEC_PER_MSEC); + + if (o->total_latency > 10*PA_USEC_PER_SEC) + pa_log_warn("[%s] Total latency of output is very high (%0.2fms), most likely the audio timing in one of your drivers is broken.", o->sink->name, (double) o->total_latency / PA_USEC_PER_MSEC); + } + + if (min_total_latency == (pa_usec_t) -1) + return; + + avg_total_latency /= n; + + target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency; + + pa_log_info("[%s] avg total latency is %0.2f msec.", u->sink->name, (double) avg_total_latency / PA_USEC_PER_MSEC); + pa_log_info("[%s] target latency is %0.2f msec.", u->sink->name, (double) target_latency / PA_USEC_PER_MSEC); + + base_rate = u->sink->sample_spec.rate; + + PA_IDXSET_FOREACH(o, u->outputs, idx) { + uint32_t new_rate = base_rate; + uint32_t current_rate = o->sink_input->sample_spec.rate; + + if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) + continue; + + if (o->total_latency != target_latency) + new_rate += (uint32_t) (((double) o->total_latency - (double) target_latency) / (double) u->adjust_time * (double) new_rate); + + if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) { + pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->sink->name, base_rate, new_rate); + new_rate = base_rate; + } else { + if (base_rate < new_rate + 20 && new_rate < base_rate + 20) + new_rate = base_rate; + /* Do the adjustment in small steps; 2‰ can be considered inaudible */ + if (new_rate < (uint32_t) (current_rate*0.998) || new_rate > (uint32_t) (current_rate*1.002)) { + pa_log_info("[%s] new rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", o->sink_input->sink->name, new_rate, current_rate); + new_rate = PA_CLAMP(new_rate, (uint32_t) (current_rate*0.998), (uint32_t) (current_rate*1.002)); + } + pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.2f msec.", o->sink_input->sink->name, new_rate, (double) new_rate / base_rate, (double) o->total_latency / PA_USEC_PER_MSEC); + } + pa_sink_input_set_rate(o->sink_input, new_rate); + } + + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, NULL, (int64_t) avg_total_latency, NULL); +} + +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + + adjust_rates(u); + + pa_core_rttime_restart(u->core, e, pa_rtclock_now() + u->adjust_time); +} + +static void process_render_null(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + pa_assert(u); + + /* If we are not running, we cannot produce any data */ + if (!pa_atomic_load(&u->thread_info.running)) + return; + + if (u->thread_info.in_null_mode) + u->thread_info.timestamp = now; + + while (u->thread_info.timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + + u->thread_info.counter += chunk.length; + +/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ + u->thread_info.timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) + break; + } + +/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ + + pa_smoother_put(u->thread_info.smoother, now, + pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec) - (u->thread_info.timestamp - now)); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+1); + + pa_thread_mq_install(&u->thread_mq); + + u->thread_info.timestamp = pa_rtclock_now(); + u->thread_info.in_null_mode = FALSE; + + for (;;) { + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + /* If no outputs are connected, render some data and drop it immediately. */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && !u->thread_info.active_outputs) { + pa_usec_t now; + + now = pa_rtclock_now(); + + if (!u->thread_info.in_null_mode || u->thread_info.timestamp <= now) + process_render_null(u, now); + + pa_rtpoll_set_timer_absolute(u->rtpoll, u->thread_info.timestamp); + u->thread_info.in_null_mode = TRUE; + } else { + pa_rtpoll_set_timer_disabled(u->rtpoll); + u->thread_info.in_null_mode = FALSE; + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) { + pa_log_info("pa_rtpoll_run() = %i", ret); + goto fail; + } + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +/* Called from I/O thread context */ +static void render_memblock(struct userdata *u, struct output *o, size_t length) { + pa_assert(u); + pa_assert(o); + + /* We are run by the sink thread, on behalf of an output (o). The + * output is waiting for us, hence it is safe to access its + * mainblockq and asyncmsgq directly. */ + + /* If we are not running, we cannot produce any data */ + if (!pa_atomic_load(&u->thread_info.running)) + return; + + /* Maybe there's some data in the requesting output's queue + * now? */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; + + /* Ok, now let's prepare some data if we really have to */ + while (!pa_memblockq_is_readable(o->memblockq)) { + struct output *j; + pa_memchunk chunk; + + /* Render data! */ + pa_sink_render(u->sink, length, &chunk); + + u->thread_info.counter += chunk.length; + + /* OK, let's send this data to the other threads */ + PA_LLIST_FOREACH(j, u->thread_info.active_outputs) { + if (j == o) + continue; + + pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); + } + + /* And place it directly into the requesting output's queue */ + pa_memblockq_push_align(o->memblockq, &chunk); + pa_memblock_unref(chunk.memblock); + } +} + +/* Called from I/O thread context */ +static void request_memblock(struct output *o, size_t length) { + pa_assert(o); + pa_sink_input_assert_ref(o->sink_input); + pa_sink_assert_ref(o->userdata->sink); + + /* If another thread already prepared some data we received + * the data over the asyncmsgq, hence let's first process + * it. */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; + + /* Check whether we're now readable */ + if (pa_memblockq_is_readable(o->memblockq)) + return; + + /* OK, we need to prepare new data, but only if the sink is actually running */ + if (pa_atomic_load(&o->userdata->thread_info.running)) + pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, (int64_t) length, NULL); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* If necessary, get some new data */ + request_memblock(o, nbytes); + + /* pa_log("%s q size is %u + %u (%u/%u)", */ + /* i->sink->name, */ + /* pa_memblockq_get_nblocks(o->memblockq), */ + /* pa_memblockq_get_nblocks(i->thread_info.render_memblockq), */ + /* pa_memblockq_get_maxrewind(o->memblockq), */ + /* pa_memblockq_get_maxrewind(i->thread_info.render_memblockq)); */ + + if (pa_memblockq_peek(o->memblockq, chunk) < 0) + return -1; + + pa_memblockq_drop(o->memblockq, chunk->length); + + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + pa_memblockq_rewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + pa_memblockq_set_maxrewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + if (pa_atomic_load(&o->max_request) == (int) nbytes) + return; + + pa_atomic_store(&o->max_request, (int) nbytes); + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); +} + +/* Called from thread context */ +static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) { + struct output *o; + pa_usec_t c; + + pa_assert(i); + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + c = pa_sink_get_requested_latency_within_thread(i->sink); + + if (c == (pa_usec_t) -1) + c = i->sink->thread_info.max_latency; + + if (pa_atomic_load(&o->requested_latency) == (int) c) + return; + + pa_atomic_store(&o->requested_latency, (int) c); + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct output *o; + pa_usec_t c; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* Set up the queue from the sink thread to us */ + pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write); + + o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + i->sink->thread_info.rtpoll, + PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ + o->inq); + + o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + i->sink->thread_info.rtpoll, + PA_RTPOLL_EARLY, + o->outq); + + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + + pa_atomic_store(&o->max_request, (int) pa_sink_input_get_max_request(i)); + + c = pa_sink_get_requested_latency_within_thread(i->sink); + pa_atomic_store(&o->requested_latency, (int) (c == (pa_usec_t) -1 ? 0 : c)); + + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_REQUESTED_LATENCY, NULL, 0, NULL, NULL); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + if (o->inq_rtpoll_item_read) { + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + o->inq_rtpoll_item_read = NULL; + } + + if (o->outq_rtpoll_item_write) { + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + o->outq_rtpoll_item_write = NULL; + } +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + pa_module_unload_request(o->userdata->module, TRUE); + output_free(o); +} + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct output *o = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &o->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + if (PA_SINK_IS_OPENED(o->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(o->memblockq, chunk); + else + pa_memblockq_flush_write(o->memblockq, TRUE); + + return 0; + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} + +/* Called from main context */ +static void suspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's suspend by unlinking all streams */ + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_disable(o); + + pa_log_info("Device suspended..."); +} + +/* Called from main context */ +static void unsuspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's resume */ + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_enable(o); + + pa_log_info("Resumed successfully..."); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(sink); + pa_assert_se(u = sink->userdata); + + /* Please note that in contrast to the ALSA modules we call + * suspend/unsuspend from main context here! */ + + switch (state) { + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))); + + suspend(u); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED) + unsuspend(u); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + ; + } + + return 0; +} + +/* Called from IO context */ +static void update_max_request(struct userdata *u) { + size_t max_request = 0; + struct output *o; + + pa_assert(u); + pa_sink_assert_io_context(u->sink); + + /* Collects the max_request values of all streams and sets the + * largest one locally */ + + PA_LLIST_FOREACH(o, u->thread_info.active_outputs) { + size_t mr = (size_t) pa_atomic_load(&o->max_request); + + if (mr > max_request) + max_request = mr; + } + + if (max_request <= 0) + max_request = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + pa_sink_set_max_request_within_thread(u->sink, max_request); +} + +/* Called from IO context */ +static void update_fixed_latency(struct userdata *u) { + pa_usec_t fixed_latency = 0; + struct output *o; + + pa_assert(u); + pa_sink_assert_io_context(u->sink); + + /* Collects the requested_latency values of all streams and sets + * the largest one as fixed_latency locally */ + + PA_LLIST_FOREACH(o, u->thread_info.active_outputs) { + pa_usec_t rl = (size_t) pa_atomic_load(&o->requested_latency); + + if (rl > fixed_latency) + fixed_latency = rl; + } + + if (fixed_latency <= 0) + fixed_latency = u->block_usec; + + pa_sink_set_fixed_latency_within_thread(u->sink, fixed_latency); +} + +/* Called from thread context of the io thread */ +static void output_add_within_thread(struct output *o) { + pa_assert(o); + pa_sink_assert_io_context(o->sink); + + PA_LLIST_PREPEND(struct output, o->userdata->thread_info.active_outputs, o); + + pa_assert(!o->outq_rtpoll_item_read && !o->inq_rtpoll_item_write); + + o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + o->userdata->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + o->outq); + o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + o->userdata->rtpoll, + PA_RTPOLL_EARLY, + o->inq); +} + +/* Called from thread context of the io thread */ +static void output_remove_within_thread(struct output *o) { + pa_assert(o); + pa_sink_assert_io_context(o->sink); + + PA_LLIST_REMOVE(struct output, o->userdata->thread_info.active_outputs, o); + + if (o->outq_rtpoll_item_read) { + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + o->outq_rtpoll_item_read = NULL; + } + + if (o->inq_rtpoll_item_write) { + pa_rtpoll_item_free(o->inq_rtpoll_item_write); + o->inq_rtpoll_item_write = NULL; + } +} + +/* Called from thread context of the io thread */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: { + pa_bool_t running = (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING); + + pa_atomic_store(&u->thread_info.running, running); + + if (running) + pa_smoother_resume(u->thread_info.smoother, pa_rtclock_now(), TRUE); + else + pa_smoother_pause(u->thread_info.smoother, pa_rtclock_now()); + + break; + } + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t x, y, c, *delay = data; + + x = pa_rtclock_now(); + y = pa_smoother_get(u->thread_info.smoother, x); + + c = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); + + if (y < c) + *delay = c - y; + else + *delay = 0; + + return 0; + } + + case SINK_MESSAGE_ADD_OUTPUT: + output_add_within_thread(data); + update_max_request(u); + update_fixed_latency(u); + return 0; + + case SINK_MESSAGE_REMOVE_OUTPUT: + output_remove_within_thread(data); + update_max_request(u); + update_fixed_latency(u); + return 0; + + case SINK_MESSAGE_NEED: + render_memblock(u, (struct output*) data, (size_t) offset); + return 0; + + case SINK_MESSAGE_UPDATE_LATENCY: { + pa_usec_t x, y, latency = (pa_usec_t) offset; + + x = pa_rtclock_now(); + y = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); + + if (y > latency) + y -= latency; + else + y = 0; + + pa_smoother_put(u->thread_info.smoother, x, y); + return 0; + } + + case SINK_MESSAGE_UPDATE_MAX_REQUEST: + update_max_request(u); + break; + + case SINK_MESSAGE_UPDATE_REQUESTED_LATENCY: + update_fixed_latency(u); + break; +} + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void update_description(struct userdata *u) { + pa_bool_t first = TRUE; + char *t; + struct output *o; + uint32_t idx; + + pa_assert(u); + + if (!u->auto_desc) + return; + + if (pa_idxset_isempty(u->outputs)) { + pa_sink_set_description(u->sink, "Simultaneous output"); + return; + } + + t = pa_xstrdup("Simultaneous output to"); + + PA_IDXSET_FOREACH(o, u->outputs, idx) { + char *e; + + if (first) { + e = pa_sprintf_malloc("%s %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + first = FALSE; + } else + e = pa_sprintf_malloc("%s, %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + pa_xfree(t); + t = e; + } + + pa_sink_set_description(u->sink, t); + pa_xfree(t); +} + +static int output_create_sink_input(struct output *o) { + pa_sink_input_new_data data; + + pa_assert(o); + + if (o->sink_input) + return 0; + + pa_sink_input_new_data_init(&data); + pa_sink_input_new_data_set_sink(&data, o->sink, FALSE); + data.driver = __FILE__; + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec); + pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); + data.module = o->userdata->module; + data.resample_method = o->userdata->resample_method; + data.flags = PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE|PA_SINK_INPUT_NO_CREATE_ON_SUSPEND; + + pa_sink_input_new(&o->sink_input, o->userdata->core, &data); + + pa_sink_input_new_data_done(&data); + + if (!o->sink_input) + return -1; + + o->sink_input->parent.process_msg = sink_input_process_msg; + o->sink_input->pop = sink_input_pop_cb; + o->sink_input->process_rewind = sink_input_process_rewind_cb; + o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + o->sink_input->update_max_request = sink_input_update_max_request_cb; + o->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb; + o->sink_input->attach = sink_input_attach_cb; + o->sink_input->detach = sink_input_detach_cb; + o->sink_input->kill = sink_input_kill_cb; + o->sink_input->userdata = o; + + pa_sink_input_set_requested_latency(o->sink_input, BLOCK_USEC); + + return 0; +} + +/* Called from main context */ +static struct output *output_new(struct userdata *u, pa_sink *sink) { + struct output *o; + + pa_assert(u); + pa_assert(sink); + pa_assert(u->sink); + + o = pa_xnew0(struct output, 1); + o->userdata = u; + o->inq = pa_asyncmsgq_new(0); + o->outq = pa_asyncmsgq_new(0); + o->sink = sink; + o->memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + MEMBLOCKQ_MAXLENGTH, + pa_frame_size(&u->sink->sample_spec), + 1, + 0, + 0, + &u->sink->silence); + + pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); + update_description(u); + + return o; +} + +/* Called from main context */ +static void output_free(struct output *o) { + pa_assert(o); + + output_disable(o); + + pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); + update_description(o->userdata); + + if (o->inq_rtpoll_item_read) + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + if (o->inq_rtpoll_item_write) + pa_rtpoll_item_free(o->inq_rtpoll_item_write); + + if (o->outq_rtpoll_item_read) + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + if (o->outq_rtpoll_item_write) + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + + if (o->inq) + pa_asyncmsgq_unref(o->inq); + + if (o->outq) + pa_asyncmsgq_unref(o->outq); + + if (o->memblockq) + pa_memblockq_free(o->memblockq); + + pa_xfree(o); +} + +/* Called from main context */ +static void output_enable(struct output *o) { + pa_assert(o); + + if (o->sink_input) + return; + + /* This might cause the sink to be resumed. The state change hook + * of the sink might hence be called from here, which might then + * cause us to be called in a loop. Make sure that state changes + * for this output don't cause this loop by setting a flag here */ + o->ignore_state_change = TRUE; + + if (output_create_sink_input(o) >= 0) { + + if (pa_sink_get_state(o->sink) != PA_SINK_INIT) { + + /* First we register the output. That means that the sink + * will start to pass data to this output. */ + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); + + /* Then we enable the sink input. That means that the sink + * is now asked for new data. */ + pa_sink_input_put(o->sink_input); + + } else + /* Hmm the sink is not yet started, do things right here */ + output_add_within_thread(o); + } + + o->ignore_state_change = FALSE; +} + +/* Called from main context */ +static void output_disable(struct output *o) { + pa_assert(o); + + if (!o->sink_input) + return; + + /* First we disable the sink input. That means that the sink is + * not asked for new data anymore */ + pa_sink_input_unlink(o->sink_input); + + /* Then we unregister the output. That means that the sink doesn't + * pass any further data to this output */ + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + + /* Now dellocate the stream */ + pa_sink_input_unref(o->sink_input); + o->sink_input = NULL; + + /* Finally, drop all queued data */ + pa_memblockq_flush_write(o->memblockq, TRUE); + pa_asyncmsgq_flush(o->inq, FALSE); + pa_asyncmsgq_flush(o->outq, FALSE); +} + +/* Called from main context */ +static void output_verify(struct output *o) { + pa_assert(o); + + if (PA_SINK_IS_OPENED(pa_sink_get_state(o->userdata->sink))) + output_enable(o); + else + output_disable(o); +} + +/* Called from main context */ +static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) { + const char *t; + + pa_sink_assert_ref(s); + + if (s == u->sink) + return FALSE; + + if (!(s->flags & PA_SINK_HARDWARE)) + return FALSE; + + if (!(s->flags & PA_SINK_LATENCY)) + return FALSE; + + if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS))) + if (!pa_streq(t, "sound")) + return FALSE; + + return TRUE; +} + +/* Called from main context */ +static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + pa_core_assert_ref(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (u->automatic) { + if (!is_suitable_sink(u, s)) + return PA_HOOK_OK; + } else { + /* Check if the sink is a previously unlinked slave (non-automatic mode) */ + pa_strlist *l = u->unlinked_slaves; + + while (l && !pa_streq(pa_strlist_data(l), s->name)) + l = pa_strlist_next(l); + + if (!l) + return PA_HOOK_OK; + + u->unlinked_slaves = pa_strlist_remove(u->unlinked_slaves, s->name); + } + + pa_log_info("Configuring new sink: %s", s->name); + if (!(o = output_new(u, s))) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + return PA_HOOK_OK; + } + + output_verify(o); + + return PA_HOOK_OK; +} + +/* Called from main context */ +static struct output* find_output(struct userdata *u, pa_sink *s) { + struct output *o; + uint32_t idx; + + pa_assert(u); + pa_assert(s); + + if (u->sink == s) + return NULL; + + PA_IDXSET_FOREACH(o, u->outputs, idx) + if (o->sink == s) + return o; + + return NULL; +} + +/* Called from main context */ +static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (!(o = find_output(u, s))) + return PA_HOOK_OK; + + pa_log_info("Unconfiguring sink: %s", s->name); + + if (!u->automatic) + u->unlinked_slaves = pa_strlist_prepend(u->unlinked_slaves, s->name); + + output_free(o); + + return PA_HOOK_OK; +} + +/* Called from main context */ +static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + if (!(o = find_output(u, s))) + return PA_HOOK_OK; + + /* This state change might be triggered because we are creating a + * stream here, in that case we don't want to create it a second + * time here and enter a loop */ + if (o->ignore_state_change) + return PA_HOOK_OK; + + output_verify(o); + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_modargs *ma = NULL; + const char *slaves, *rm; + int resample_method = PA_RESAMPLER_TRIVIAL; + pa_sample_spec ss; + pa_channel_map map; + struct output *o; + uint32_t idx; + pa_sink_new_data data; + uint32_t adjust_time_sec; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) { + if ((resample_method = pa_parse_resample_method(rm)) < 0) { + pa_log("invalid resample method '%s'", rm); + goto fail; + } + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->resample_method = resample_method; + u->outputs = pa_idxset_new(NULL, NULL); + u->thread_info.smoother = pa_smoother_new( + PA_USEC_PER_SEC, + PA_USEC_PER_SEC*2, + TRUE, + TRUE, + 10, + pa_rtclock_now(), + TRUE); + + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { + pa_log("Failed to parse adjust_time value"); + goto fail; + } + + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + + slaves = pa_modargs_get_value(ma, "slaves", NULL); + u->automatic = !slaves; + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + + /* Check the specified slave sinks for sample_spec and channel_map to use for the combined sink */ + if (!u->automatic) { + const char*split_state = NULL; + char *n = NULL; + pa_sample_spec slaves_spec; + pa_channel_map slaves_map; + pa_bool_t is_first_slave = TRUE; + + pa_sample_spec_init(&slaves_spec); + + while ((n = pa_split(slaves, ",", &split_state))) { + pa_sink *slave_sink; + + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK))) { + pa_log("Invalid slave sink '%s'", n); + pa_xfree(n); + goto fail; + } + + pa_xfree(n); + + if (is_first_slave) { + slaves_spec = slave_sink->sample_spec; + slaves_map = slave_sink->channel_map; + is_first_slave = FALSE; + } else { + if (slaves_spec.format != slave_sink->sample_spec.format) + slaves_spec.format = PA_SAMPLE_INVALID; + + if (slaves_spec.rate < slave_sink->sample_spec.rate) + slaves_spec.rate = slave_sink->sample_spec.rate; + + if (!pa_channel_map_equal(&slaves_map, &slave_sink->channel_map)) + slaves_spec.channels = 0; + } + } + + if (!is_first_slave) { + if (slaves_spec.format != PA_SAMPLE_INVALID) + ss.format = slaves_spec.format; + + ss.rate = slaves_spec.rate; + + if (slaves_spec.channels > 0) { + map = slaves_map; + ss.channels = slaves_map.channels; + } + } + } + + if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) { + pa_log("Invalid sample specification."); + goto fail; + } + + pa_sink_new_data_init(&data); + data.namereg_fail = FALSE; + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (slaves) + pa_proplist_sets(data.proplist, "combine.slaves", slaves); + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + + /* Check proplist for a description & fill in a default value if not */ + u->auto_desc = FALSE; + if (NULL == pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)) { + u->auto_desc = TRUE; + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output"); + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + + u->block_usec = BLOCK_USEC; + pa_sink_set_max_request(u->sink, pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec)); + + if (!u->automatic) { + const char*split_state; + char *n = NULL; + pa_assert(slaves); + + /* The slaves have been specified manually */ + + split_state = NULL; + while ((n = pa_split(slaves, ",", &split_state))) { + pa_sink *slave_sink; + + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK)) || slave_sink == u->sink) { + pa_log("Invalid slave sink '%s'", n); + pa_xfree(n); + goto fail; + } + + pa_xfree(n); + + if (!output_new(u, slave_sink)) { + pa_log("Failed to create slave sink input on sink '%s'.", slave_sink->name); + goto fail; + } + } + + if (pa_idxset_size(u->outputs) <= 1) + pa_log_warn("No slave sinks specified."); + + u->sink_put_slot = NULL; + + } else { + pa_sink *s; + + /* We're in automatic mode, we add every sink that matches our needs */ + + PA_IDXSET_FOREACH(s, m->core->sinks, idx) { + + if (!is_suitable_sink(u, s)) + continue; + + if (!output_new(u, s)) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + goto fail; + } + } + } + + u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_put_hook_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_hook_cb, u); + + if (!(u->thread = pa_thread_new("combine", thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Activate the sink and the sink inputs */ + pa_sink_put(u->sink); + + PA_IDXSET_FOREACH(o, u->outputs, idx) + output_verify(o); + + if (u->adjust_time > 0) + u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + struct output *o; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + pa_strlist_free(u->unlinked_slaves); + + if (u->sink_put_slot) + pa_hook_slot_free(u->sink_put_slot); + + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->outputs) { + while ((o = pa_idxset_first(u->outputs, NULL))) + output_free(o); + + pa_idxset_free(u->outputs, NULL, NULL); + } + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + if (u->thread_info.smoother) + pa_smoother_free(u->thread_info.smoother); + + pa_xfree(u); +} diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 02a7e1ff..251df494 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -1,7 +1,8 @@ /*** This file is part of PulseAudio. - Copyright 2004-2008 Lennart Poettering + Copyright 2009 Lennart Poettering + Copyright 2011 Colin Guthrie PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -23,1251 +24,49 @@ #include <config.h> #endif -#include <stdio.h> -#include <errno.h> - -#include <pulse/timeval.h> #include <pulse/xmalloc.h> -#include <pulsecore/macro.h> #include <pulsecore/module.h> -#include <pulsecore/llist.h> -#include <pulsecore/sink.h> -#include <pulsecore/sink-input.h> -#include <pulsecore/memblockq.h> #include <pulsecore/log.h> -#include <pulsecore/core-util.h> -#include <pulsecore/modargs.h> -#include <pulsecore/namereg.h> -#include <pulsecore/mutex.h> -#include <pulsecore/thread.h> -#include <pulsecore/thread-mq.h> -#include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h> -#include <pulsecore/core-error.h> -#include <pulsecore/time-smoother.h> #include "module-combine-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Combine multiple sinks to one"); +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Compatibility module (module-combine rename)"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE( - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "slaves=<slave sinks> " - "adjust_time=<seconds> " - "resample_method=<method> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map>"); - -#define DEFAULT_SINK_NAME "combined" - -#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) - -#define DEFAULT_ADJUST_TIME 10 - -#define BLOCK_USEC (PA_USEC_PER_MSEC * 200) - -static const char* const valid_modargs[] = { - "sink_name", - "sink_properties", - "slaves", - "adjust_time", - "resample_method", - "format", - "rate", - "channels", - "channel_map", - NULL -}; - -struct output { - struct userdata *userdata; - - pa_sink *sink; - pa_sink_input *sink_input; - - pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ - *outq; /* Message queue from this sink input to the sink thread */ - pa_rtpoll_item *inq_rtpoll_item_read, *inq_rtpoll_item_write; - pa_rtpoll_item *outq_rtpoll_item_read, *outq_rtpoll_item_write; - - pa_memblockq *memblockq; - - pa_usec_t total_latency; - - pa_atomic_t max_request; - - PA_LLIST_FIELDS(struct output); -}; +PA_MODULE_DEPRECATED("Please use module-combine-sink instead of module-combine!"); struct userdata { - pa_core *core; - pa_module *module; - pa_sink *sink; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - pa_time_event *time_event; - uint32_t adjust_time; - - pa_bool_t automatic; - - pa_hook_slot *sink_put_slot, *sink_unlink_slot, *sink_state_changed_slot; - - pa_resample_method_t resample_method; - - struct timeval adjust_timestamp; - - pa_usec_t block_usec; - - pa_idxset* outputs; /* managed in main context */ - - struct { - PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */ - pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */ - pa_usec_t timestamp; - pa_bool_t in_null_mode; - pa_smoother *smoother; - uint64_t counter; - } thread_info; -}; - -enum { - SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, - SINK_MESSAGE_REMOVE_OUTPUT, - SINK_MESSAGE_NEED, - SINK_MESSAGE_UPDATE_LATENCY, - SINK_MESSAGE_UPDATE_MAX_REQUEST -}; - -enum { - SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, + uint32_t module_index; }; -static void output_free(struct output *o); -static int output_create_sink_input(struct output *o); - -static void adjust_rates(struct userdata *u) { - struct output *o; - pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency, avg_total_latency = 0; - uint32_t base_rate; - uint32_t idx; - unsigned n = 0; - - pa_assert(u); - pa_sink_assert_ref(u->sink); - - if (pa_idxset_size(u->outputs) <= 0) - return; - - if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))) - return; - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - pa_usec_t sink_latency; - - if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) - continue; - - o->total_latency = pa_sink_input_get_latency(o->sink_input, &sink_latency); - o->total_latency += sink_latency; - - if (sink_latency > max_sink_latency) - max_sink_latency = sink_latency; - - if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) - min_total_latency = o->total_latency; - - avg_total_latency += o->total_latency; - n++; - } - - if (min_total_latency == (pa_usec_t) -1) - return; - - avg_total_latency /= n; - - target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency; - - pa_log_info("[%s] avg total latency is %0.2f msec.", u->sink->name, (double) avg_total_latency / PA_USEC_PER_MSEC); - pa_log_info("[%s] target latency is %0.2f msec.", u->sink->name, (double) target_latency / PA_USEC_PER_MSEC); - - base_rate = u->sink->sample_spec.rate; - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - uint32_t r = base_rate; - - if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) - continue; - - if (o->total_latency < target_latency) - r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); - else if (o->total_latency > target_latency) - r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); - - if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { - pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r); - pa_sink_input_set_rate(o->sink_input, base_rate); - } else { - pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency); - pa_sink_input_set_rate(o->sink_input, r); - } - } - - pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, NULL, (int64_t) avg_total_latency, NULL); -} - -static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { - struct userdata *u = userdata; - struct timeval n; - - pa_assert(u); - pa_assert(a); - pa_assert(u->time_event == e); - - adjust_rates(u); - - pa_gettimeofday(&n); - n.tv_sec += (time_t) u->adjust_time; - u->sink->core->mainloop->time_restart(e, &n); -} - -static void process_render_null(struct userdata *u, pa_usec_t now) { - size_t ate = 0; - pa_assert(u); - - if (u->thread_info.in_null_mode) - u->thread_info.timestamp = now; - - while (u->thread_info.timestamp < now + u->block_usec) { - pa_memchunk chunk; - - pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); - pa_memblock_unref(chunk.memblock); - - u->thread_info.counter += chunk.length; - -/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ - u->thread_info.timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); - - ate += chunk.length; - - if (ate >= u->sink->thread_info.max_request) - break; - } - -/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ - - pa_smoother_put(u->thread_info.smoother, now, - pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec) - (u->thread_info.timestamp - now)); -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_make_realtime(u->core->realtime_priority+1); - - pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - - u->thread_info.timestamp = pa_rtclock_usec(); - u->thread_info.in_null_mode = FALSE; - - for (;;) { - int ret; - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) - if (u->sink->thread_info.rewind_requested) - pa_sink_process_rewind(u->sink, 0); - - /* If no outputs are connected, render some data and drop it immediately. */ - if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && !u->thread_info.active_outputs) { - pa_usec_t now; - - now = pa_rtclock_usec(); - - if (!u->thread_info.in_null_mode || u->thread_info.timestamp <= now) - process_render_null(u, now); - - pa_rtpoll_set_timer_absolute(u->rtpoll, u->thread_info.timestamp); - u->thread_info.in_null_mode = TRUE; - } else { - pa_rtpoll_set_timer_disabled(u->rtpoll); - u->thread_info.in_null_mode = FALSE; - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) { - pa_log_info("pa_rtpoll_run() = %i", ret); - goto fail; - } - - if (ret == 0) - goto finish; - } - -fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -/* Called from I/O thread context */ -static void render_memblock(struct userdata *u, struct output *o, size_t length) { - pa_assert(u); - pa_assert(o); - - /* We are run by the sink thread, on behalf of an output (o). The - * output is waiting for us, hence it is safe to access its - * mainblockq and asyncmsgq directly. */ - - /* If we are not running, we cannot produce any data */ - if (!pa_atomic_load(&u->thread_info.running)) - return; - - /* Maybe there's some data in the requesting output's queue - * now? */ - while (pa_asyncmsgq_process_one(o->inq) > 0) - ; - - /* Ok, now let's prepare some data if we really have to */ - while (!pa_memblockq_is_readable(o->memblockq)) { - struct output *j; - pa_memchunk chunk; - - /* Render data! */ - pa_sink_render(u->sink, length, &chunk); - - u->thread_info.counter += chunk.length; - - /* OK, let's send this data to the other threads */ - for (j = u->thread_info.active_outputs; j; j = j->next) - - /* Send to other outputs, which are not the requesting - * one */ - - if (j != o) - pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); - - /* And place it directly into the requesting output's queue */ - if (o) - pa_memblockq_push_align(o->memblockq, &chunk); - - pa_memblock_unref(chunk.memblock); - } -} - -/* Called from I/O thread context */ -static void request_memblock(struct output *o, size_t length) { - pa_assert(o); - pa_sink_input_assert_ref(o->sink_input); - pa_sink_assert_ref(o->userdata->sink); - - /* If another thread already prepared some data we received - * the data over the asyncmsgq, hence let's first process - * it. */ - while (pa_asyncmsgq_process_one(o->inq) > 0) - ; - - /* Check whether we're now readable */ - if (pa_memblockq_is_readable(o->memblockq)) - return; - - /* OK, we need to prepare new data, but only if the sink is actually running */ - if (pa_atomic_load(&o->userdata->thread_info.running)) - pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, (int64_t) length, NULL); -} - -/* Called from I/O thread context */ -static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - /* If necessary, get some new data */ - request_memblock(o, nbytes); - - if (pa_memblockq_peek(o->memblockq, chunk) < 0) - return -1; - - pa_memblockq_drop(o->memblockq, chunk->length); - return 0; -} - -/* Called from I/O thread context */ -static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - pa_memblockq_rewind(o->memblockq, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - pa_memblockq_set_maxrewind(o->memblockq, nbytes); -} - -/* Called from I/O thread context */ -static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - if (pa_atomic_load(&o->max_request) == (int) nbytes) - return; - - pa_atomic_store(&o->max_request, (int) nbytes); - - pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); -} - -/* Called from I/O thread context */ -static void sink_input_attach_cb(pa_sink_input *i) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - /* Set up the queue from the sink thread to us */ - pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write); - - o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - i->sink->rtpoll, - PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ - o->inq); - - o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - i->sink->rtpoll, - PA_RTPOLL_EARLY, - o->outq); -} - -/* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - /* Shut down the queue from the sink thread to us */ - pa_assert(o->inq_rtpoll_item_read && o->outq_rtpoll_item_write); - - pa_rtpoll_item_free(o->inq_rtpoll_item_read); - o->inq_rtpoll_item_read = NULL; - - pa_rtpoll_item_free(o->outq_rtpoll_item_write); - o->outq_rtpoll_item_write = NULL; -} - -/* Called from main context */ -static void sink_input_kill_cb(pa_sink_input *i) { - struct output *o; - - pa_sink_input_assert_ref(i); - pa_assert_se(o = i->userdata); - - pa_module_unload_request(o->userdata->module, TRUE); - output_free(o); -} - -/* Called from IO thread context */ -static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { - struct userdata *u; - - pa_sink_input_assert_ref(i); - pa_assert_se(u = i->userdata); - - /* If we are added for the first time, ask for a rewinding so that - * we are heard right-away. */ - if (PA_SINK_INPUT_IS_LINKED(state) && - i->thread_info.state == PA_SINK_INPUT_INIT) - pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); -} - -/* Called from thread context */ -static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct output *o = PA_SINK_INPUT(obj)->userdata; - - switch (code) { - - case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { - pa_usec_t *r = data; - - *r = pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &o->sink_input->sample_spec); - - /* Fall through, the default handler will add in the extra - * latency added by the resampler */ - break; - } - - case SINK_INPUT_MESSAGE_POST: - - if (PA_SINK_IS_OPENED(o->sink_input->sink->thread_info.state)) - pa_memblockq_push_align(o->memblockq, chunk); - else - pa_memblockq_flush_write(o->memblockq); - - return 0; - } - - return pa_sink_input_process_msg(obj, code, data, offset, chunk); -} - -/* Called from main context */ -static void disable_output(struct output *o) { - pa_assert(o); - - if (!o->sink_input) - return; - - pa_sink_input_unlink(o->sink_input); - pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); - pa_sink_input_unref(o->sink_input); - o->sink_input = NULL; -} - -/* Called from main context */ -static void enable_output(struct output *o) { - pa_assert(o); - - if (o->sink_input) - return; - - if (output_create_sink_input(o) >= 0) { - - pa_memblockq_flush_write(o->memblockq); - - pa_sink_input_put(o->sink_input); - - if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink))) - pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - } -} - -/* Called from main context */ -static void suspend(struct userdata *u) { - struct output *o; - uint32_t idx; - - pa_assert(u); - - /* Let's suspend by unlinking all streams */ - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - disable_output(o); - - pa_log_info("Device suspended..."); -} - -/* Called from main context */ -static void unsuspend(struct userdata *u) { - struct output *o; - uint32_t idx; - - pa_assert(u); - - /* Let's resume */ - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - - pa_sink_suspend(o->sink, FALSE); - - if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) - enable_output(o); - } - - pa_log_info("Resumed successfully..."); -} - -/* Called from main context */ -static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { - struct userdata *u; - - pa_sink_assert_ref(sink); - pa_assert_se(u = sink->userdata); - - /* Please note that in contrast to the ALSA modules we call - * suspend/unsuspend from main context here! */ - - switch (state) { - case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))); - - suspend(u); - break; - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: - - if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED) - unsuspend(u); - - break; - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - case PA_SINK_INVALID_STATE: - ; - } - - return 0; -} - -/* Called from IO context */ -static void update_max_request(struct userdata *u) { - size_t max_request = 0; - struct output *o; - - for (o = u->thread_info.active_outputs; o; o = o->next) { - size_t mr = (size_t) pa_atomic_load(&o->max_request); - - if (mr > max_request) - max_request = mr; - } - - if (max_request <= 0) - max_request = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); - - pa_sink_set_max_request_within_thread(u->sink, max_request); -} - -/* Called from thread context of the io thread */ -static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - - case PA_SINK_MESSAGE_SET_STATE: - pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING); - - if (PA_PTR_TO_UINT(data) == PA_SINK_SUSPENDED) - pa_smoother_pause(u->thread_info.smoother, pa_rtclock_usec()); - else - pa_smoother_resume(u->thread_info.smoother, pa_rtclock_usec(), TRUE); - - break; - - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t x, y, c, *delay = data; - - x = pa_rtclock_usec(); - y = pa_smoother_get(u->thread_info.smoother, x); - - c = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); - - if (y < c) - *delay = c - y; - else - *delay = 0; - - return 0; - } - - case SINK_MESSAGE_ADD_OUTPUT: { - struct output *op = data; - - PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op); - - pa_assert(!op->outq_rtpoll_item_read && !op->inq_rtpoll_item_write); - - op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - u->rtpoll, - PA_RTPOLL_EARLY-1, /* This item is very important */ - op->outq); - op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - u->rtpoll, - PA_RTPOLL_EARLY, - op->inq); - - update_max_request(u); - return 0; - } - - case SINK_MESSAGE_REMOVE_OUTPUT: { - struct output *op = data; - - PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); - - pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write); - - pa_rtpoll_item_free(op->outq_rtpoll_item_read); - op->outq_rtpoll_item_read = NULL; - - pa_rtpoll_item_free(op->inq_rtpoll_item_write); - op->inq_rtpoll_item_write = NULL; - - update_max_request(u); - return 0; - } - - case SINK_MESSAGE_NEED: - render_memblock(u, (struct output*) data, (size_t) offset); - return 0; - - case SINK_MESSAGE_UPDATE_LATENCY: { - pa_usec_t x, y, latency = (pa_usec_t) offset; - - x = pa_rtclock_usec(); - y = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); - - if (y > latency) - y -= latency; - else - y = 0; - - pa_smoother_put(u->thread_info.smoother, x, y); - return 0; - } - - case SINK_MESSAGE_UPDATE_MAX_REQUEST: - - update_max_request(u); - break; - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -static void update_description(struct userdata *u) { - pa_bool_t first = TRUE; - char *t; - struct output *o; - uint32_t idx; - - pa_assert(u); - - if (pa_idxset_isempty(u->outputs)) { - pa_sink_set_description(u->sink, "Simultaneous output"); - return; - } - - t = pa_xstrdup("Simultaneous output to"); - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { - char *e; - - if (first) { - e = pa_sprintf_malloc("%s %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); - first = FALSE; - } else - e = pa_sprintf_malloc("%s, %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); - - pa_xfree(t); - t = e; - } - - pa_sink_set_description(u->sink, t); - pa_xfree(t); -} - -static int output_create_sink_input(struct output *o) { - pa_sink_input_new_data data; - - pa_assert(o); - - if (o->sink_input) - return 0; - - pa_sink_input_new_data_init(&data); - data.sink = o->sink; - data.driver = __FILE__; - pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); - pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "filter"); - pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec); - pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); - data.module = o->userdata->module; - data.resample_method = o->userdata->resample_method; - - pa_sink_input_new(&o->sink_input, o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE); - - pa_sink_input_new_data_done(&data); - - if (!o->sink_input) - return -1; - - o->sink_input->parent.process_msg = sink_input_process_msg; - o->sink_input->pop = sink_input_pop_cb; - o->sink_input->process_rewind = sink_input_process_rewind_cb; - o->sink_input->state_change = sink_input_state_change_cb; - o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; - o->sink_input->update_max_request = sink_input_update_max_request_cb; - o->sink_input->attach = sink_input_attach_cb; - o->sink_input->detach = sink_input_detach_cb; - o->sink_input->kill = sink_input_kill_cb; - o->sink_input->userdata = o; - - pa_sink_input_set_requested_latency(o->sink_input, BLOCK_USEC); - - return 0; -} - -static struct output *output_new(struct userdata *u, pa_sink *sink) { - struct output *o; - pa_sink_state_t state; - - pa_assert(u); - pa_assert(sink); - pa_assert(u->sink); - - o = pa_xnew(struct output, 1); - o->userdata = u; - o->inq = pa_asyncmsgq_new(0); - o->outq = pa_asyncmsgq_new(0); - o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL; - o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL; - o->sink = sink; - o->sink_input = NULL; - o->memblockq = pa_memblockq_new( - 0, - MEMBLOCKQ_MAXLENGTH, - MEMBLOCKQ_MAXLENGTH, - pa_frame_size(&u->sink->sample_spec), - 1, - 0, - 0, - NULL); - pa_atomic_store(&o->max_request, 0); - PA_LLIST_INIT(struct output, o); - - pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); - - state = pa_sink_get_state(u->sink); - - if (state != PA_SINK_INIT) - pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); - else { - /* If the sink is not yet started, we need to do the activation ourselves */ - PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o); - - o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( - u->rtpoll, - PA_RTPOLL_EARLY-1, /* This item is very important */ - o->outq); - o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( - u->rtpoll, - PA_RTPOLL_EARLY, - o->inq); - } - - if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { - pa_sink_suspend(sink, FALSE); - - if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) - if (output_create_sink_input(o) < 0) - goto fail; - } - - update_description(u); - - return o; - -fail: - - if (o) { - pa_idxset_remove_by_data(u->outputs, o, NULL); - - if (o->sink_input) { - pa_sink_input_unlink(o->sink_input); - pa_sink_input_unref(o->sink_input); - } - - if (o->memblockq) - pa_memblockq_free(o->memblockq); - - if (o->inq) - pa_asyncmsgq_unref(o->inq); - - if (o->outq) - pa_asyncmsgq_unref(o->outq); - - pa_xfree(o); - } - - return NULL; -} - -static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) { - const char *t; - - pa_sink_assert_ref(s); - - if (!(s->flags & PA_SINK_HARDWARE)) - return FALSE; - - if (s == u->sink) - return FALSE; - - if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS))) - if (strcmp(t, "sound")) - return FALSE; - - return TRUE; -} - -static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { - struct output *o; - - pa_core_assert_ref(c); - pa_sink_assert_ref(s); - pa_assert(u); - pa_assert(u->automatic); - - if (!is_suitable_sink(u, s)) - return PA_HOOK_OK; - - pa_log_info("Configuring new sink: %s", s->name); - - if (!(o = output_new(u, s))) { - pa_log("Failed to create sink input on sink '%s'.", s->name); - return PA_HOOK_OK; - } - - if (o->sink_input) - pa_sink_input_put(o->sink_input); - - return PA_HOOK_OK; -} - -static struct output* find_output(struct userdata *u, pa_sink *s) { - struct output *o; - uint32_t idx; - - pa_assert(u); - pa_assert(s); - - if (u->sink == s) - return NULL; - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - if (o->sink == s) - return o; - - return NULL; -} - -static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { - struct output *o; - - pa_assert(c); - pa_sink_assert_ref(s); - pa_assert(u); - - if (!(o = find_output(u, s))) - return PA_HOOK_OK; - - pa_log_info("Unconfiguring sink: %s", s->name); - - output_free(o); - - return PA_HOOK_OK; -} - -static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { - struct output *o; - pa_sink_state_t state; - - if (!(o = find_output(u, s))) - return PA_HOOK_OK; - - state = pa_sink_get_state(s); - - if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) - enable_output(o); - - if (state == PA_SINK_SUSPENDED && o->sink_input) - disable_output(o); - - return PA_HOOK_OK; -} - int pa__init(pa_module*m) { struct userdata *u; - pa_modargs *ma = NULL; - const char *slaves, *rm; - int resample_method = PA_RESAMPLER_TRIVIAL; - pa_sample_spec ss; - pa_channel_map map; - struct output *o; - uint32_t idx; - pa_sink_new_data data; + pa_module *module; pa_assert(m); + pa_assert_se(m->userdata = u = pa_xnew0(struct userdata, 1)); - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); - goto fail; - } - - if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) { - if ((resample_method = pa_parse_resample_method(rm)) < 0) { - pa_log("invalid resample method '%s'", rm); - goto fail; - } - } - - m->userdata = u = pa_xnew(struct userdata, 1); - u->core = m->core; - u->module = m; - u->sink = NULL; - u->time_event = NULL; - u->adjust_time = DEFAULT_ADJUST_TIME; - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->thread = NULL; - u->resample_method = resample_method; - u->outputs = pa_idxset_new(NULL, NULL); - memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp)); - u->sink_put_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; - PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs); - pa_atomic_store(&u->thread_info.running, FALSE); - u->thread_info.in_null_mode = FALSE; - u->thread_info.counter = 0; - u->thread_info.smoother = pa_smoother_new( - PA_USEC_PER_SEC, - PA_USEC_PER_SEC*2, - TRUE, - TRUE, - 10, - 0, - FALSE); - - if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { - pa_log("Failed to parse adjust_time value"); - goto fail; - } - - slaves = pa_modargs_get_value(ma, "slaves", NULL); - u->automatic = !slaves; - - ss = m->core->default_sample_spec; - map = m->core->default_channel_map; - if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) { - pa_log("Invalid sample specification."); - goto fail; - } - - pa_sink_new_data_init(&data); - data.namereg_fail = FALSE; - data.driver = __FILE__; - data.module = m; - pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); - pa_sink_new_data_set_sample_spec(&data, &ss); - pa_sink_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - - if (slaves) - pa_proplist_sets(data.proplist, "combine.slaves", slaves); - - if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { - pa_log("Invalid properties"); - pa_sink_new_data_done(&data); - goto fail; - } - - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); - pa_sink_new_data_done(&data); - - if (!u->sink) { - pa_log("Failed to create sink"); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_state = sink_set_state; - u->sink->userdata = u; - - pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - - u->block_usec = BLOCK_USEC; - pa_sink_set_max_request(u->sink, pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec)); + pa_log_warn("We will now load module-combine-sink. Please make sure to remove module-combine from your configuration."); - if (!u->automatic) { - const char*split_state; - char *n = NULL; - pa_assert(slaves); + module = pa_module_load(m->core, "module-combine-sink", m->argument); + u->module_index = module ? module->index : PA_INVALID_INDEX; - /* The slaves have been specified manually */ - - split_state = NULL; - while ((n = pa_split(slaves, ",", &split_state))) { - pa_sink *slave_sink; - - if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK)) || slave_sink == u->sink) { - pa_log("Invalid slave sink '%s'", n); - pa_xfree(n); - goto fail; - } - - pa_xfree(n); - - if (!output_new(u, slave_sink)) { - pa_log("Failed to create slave sink input on sink '%s'.", slave_sink->name); - goto fail; - } - } - - if (pa_idxset_size(u->outputs) <= 1) - pa_log_warn("No slave sinks specified."); - - u->sink_put_slot = NULL; - - } else { - pa_sink *s; - - /* We're in automatic mode, we add every sink that matches our needs */ - - for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { - - if (!is_suitable_sink(u, s)) - continue; - - if (!output_new(u, s)) { - pa_log("Failed to create sink input on sink '%s'.", s->name); - goto fail; - } - } - - u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_put_hook_cb, u); - } - - u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_cb, u); - u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_hook_cb, u); - - if (!(u->thread = pa_thread_new(thread_func, u))) { - pa_log("Failed to create thread."); - goto fail; - } - - /* Activate the sink and the sink inputs */ - pa_sink_put(u->sink); - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - if (o->sink_input) - pa_sink_input_put(o->sink_input); - - if (u->adjust_time > 0) { - struct timeval tv; - pa_gettimeofday(&tv); - tv.tv_sec += (time_t) u->adjust_time; - u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); - } - - pa_modargs_free(ma); - - return 0; - -fail: - - if (ma) - pa_modargs_free(ma); - - pa__done(m); - - return -1; + return module ? 0 : -1; } -static void output_free(struct output *o) { - pa_assert(o); - - disable_output(o); - - pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); - - update_description(o->userdata); - - if (o->inq_rtpoll_item_read) - pa_rtpoll_item_free(o->inq_rtpoll_item_read); - if (o->inq_rtpoll_item_write) - pa_rtpoll_item_free(o->inq_rtpoll_item_write); - - if (o->outq_rtpoll_item_read) - pa_rtpoll_item_free(o->outq_rtpoll_item_read); - if (o->outq_rtpoll_item_write) - pa_rtpoll_item_free(o->outq_rtpoll_item_write); - - if (o->inq) - pa_asyncmsgq_unref(o->inq); - - if (o->outq) - pa_asyncmsgq_unref(o->outq); - - if (o->memblockq) - pa_memblockq_free(o->memblockq); - - pa_xfree(o); -} void pa__done(pa_module*m) { struct userdata *u; - struct output *o; pa_assert(m); + pa_assert(m->userdata); - if (!(u = m->userdata)) - return; - - if (u->sink_put_slot) - pa_hook_slot_free(u->sink_put_slot); - - if (u->sink_unlink_slot) - pa_hook_slot_free(u->sink_unlink_slot); - - if (u->sink_state_changed_slot) - pa_hook_slot_free(u->sink_state_changed_slot); - - if (u->outputs) { - while ((o = pa_idxset_first(u->outputs, NULL))) - output_free(o); - - pa_idxset_free(u->outputs, NULL, NULL); - } - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->time_event) - u->core->mainloop->time_free(u->time_event); + u = m->userdata; - if (u->thread_info.smoother) - pa_smoother_free(u->thread_info.smoother); + if (u && PA_INVALID_INDEX != u->module_index) + pa_module_unload_by_index(m->core, u->module_index, TRUE); pa_xfree(u); } diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c index a666073c..4c5857cf 100644 --- a/src/modules/module-console-kit.c +++ b/src/modules/module-console-kit.c @@ -25,24 +25,17 @@ #include <stdio.h> #include <unistd.h> -#include <string.h> #include <stdlib.h> #include <errno.h> #include <stdlib.h> #include <sys/types.h> -#include <sys/stat.h> #include <pulse/xmalloc.h> -#include <pulse/timeval.h> -#include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/log.h> #include <pulsecore/hashmap.h> #include <pulsecore/idxset.h> -#include <pulsecore/core-util.h> -#include <pulsecore/namereg.h> -#include <pulsecore/core-scache.h> #include <pulsecore/modargs.h> #include <pulsecore/dbus-shared.h> @@ -67,6 +60,7 @@ struct userdata { pa_core *core; pa_dbus_connection *connection; pa_hashmap *sessions; + pa_bool_t filter_added; }; static void add_session(struct userdata *u, const char *id) { @@ -76,7 +70,7 @@ static void add_session(struct userdata *u, const char *id) { struct session *session; pa_client_new_data data; - dbus_error_init (&error); + dbus_error_init(&error); if (pa_hashmap_get(u->sessions, id)) { pa_log_warn("Duplicate session %s, ignoring.", id); @@ -187,7 +181,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } add_session(u, path); - return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) { @@ -202,7 +195,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } remove_session(u, path); - return DBUS_HANDLER_RESULT_HANDLED; } finish: @@ -302,7 +294,7 @@ int pa__init(pa_module*m) { goto fail; } - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->connection = connection; @@ -313,6 +305,8 @@ int pa__init(pa_module*m) { goto fail; } + u->filter_added = TRUE; + if (pa_dbus_add_matches( pa_dbus_connection_get(connection), &error, "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionAdded'", @@ -361,7 +355,9 @@ void pa__done(pa_module *m) { "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionAdded'", "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionRemoved'", NULL); - dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); + if (u->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); + pa_dbus_connection_unref(u->connection); } diff --git a/src/modules/module-cork-music-on-phone.c b/src/modules/module-cork-music-on-phone.c index d3a2b00e..4c9c1788 100644 --- a/src/modules/module-cork-music-on-phone.c +++ b/src/modules/module-cork-music-on-phone.c @@ -23,6 +23,8 @@ #include <config.h> #endif +#include <pulse/xmalloc.h> + #include <pulsecore/macro.h> #include <pulsecore/hashmap.h> #include <pulsecore/hook-list.h> @@ -66,8 +68,10 @@ static pa_bool_t shall_cork(pa_sink *s, pa_sink_input *ignore) { if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE))) continue; - if (pa_streq(role, "phone")) + if (pa_streq(role, "phone")) { + pa_log_debug("Found a phone stream that will trigger the auto-cork."); return TRUE; + } } return FALSE; @@ -81,7 +85,7 @@ static void apply_cork(struct userdata *u, pa_sink *s, pa_sink_input *ignore, pa pa_sink_assert_ref(s); for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - pa_bool_t corked; + pa_bool_t corked, muted, corked_here; const char *role; if (j == ignore) @@ -94,18 +98,25 @@ static void apply_cork(struct userdata *u, pa_sink *s, pa_sink_input *ignore, pa !pa_streq(role, "music")) continue; - corked = !!pa_hashmap_get(u->cork_state, j); + corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED); + muted = pa_sink_input_get_mute(j); + corked_here = !!pa_hashmap_get(u->cork_state, j); - if (cork && !corked) { - pa_hashmap_put(u->cork_state, j, PA_INT_TO_PTR(1)); + if (cork && !corked && !muted) { + pa_log_debug("Found a music/video stream that should be corked/muted."); + if (!corked_here) + pa_hashmap_put(u->cork_state, j, PA_INT_TO_PTR(1)); pa_sink_input_set_mute(j, TRUE, FALSE); pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_CORK, NULL); } else if (!cork) { pa_hashmap_remove(u->cork_state, j); - if (corked) { - pa_sink_input_set_mute(j, FALSE, FALSE); - pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_UNCORK, NULL); + if (corked_here && (corked || muted)) { + pa_log_debug("Found a music/video stream that should be uncorked/unmuted."); + if (muted) + pa_sink_input_set_mute(j, FALSE, FALSE); + if (corked) + pa_sink_input_send_event(j, PA_STREAM_EVENT_REQUEST_UNCORK, NULL); } } } @@ -129,6 +140,9 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, pa_bool_t !pa_streq(role, "video")) return PA_HOOK_OK; + if (!i->sink) + return PA_HOOK_OK; + cork = shall_cork(i->sink, create ? NULL : i); apply_cork(u, i->sink, create ? NULL : i, cork); @@ -193,7 +207,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c index 0092d1c7..f28bddb7 100644 --- a/src/modules/module-default-device-restore.c +++ b/src/modules/module-default-device-restore.c @@ -26,8 +26,9 @@ #include <errno.h> #include <stdio.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> -#include <pulse/util.h> +#include <pulse/xmalloc.h> #include <pulsecore/core-util.h> #include <pulsecore/module.h> @@ -42,7 +43,7 @@ PA_MODULE_DESCRIPTION("Automatically restore the default sink and source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -#define DEFAULT_SAVE_INTERVAL 5 +#define SAVE_INTERVAL (5 * PA_USEC_PER_SEC) struct userdata { pa_core *core; @@ -59,12 +60,12 @@ static void load(struct userdata *u) { if (u->core->default_sink) pa_log_info("Manually configured default sink, not overwriting."); - else if ((f = fopen(u->sink_filename, "r"))) { + else if ((f = pa_fopen_cloexec(u->sink_filename, "r"))) { char ln[256] = ""; pa_sink *s; - (void) fgets(ln, sizeof(ln)-1, f); - pa_strip_nl(ln); + if (fgets(ln, sizeof(ln)-1, f)) + pa_strip_nl(ln); fclose(f); if (!ln[0]) @@ -80,12 +81,12 @@ static void load(struct userdata *u) { if (u->core->default_source) pa_log_info("Manually configured default source, not overwriting."); - else if ((f = fopen(u->source_filename, "r"))) { + else if ((f = pa_fopen_cloexec(u->source_filename, "r"))) { char ln[256] = ""; pa_source *s; - (void) fgets(ln, sizeof(ln)-1, f); - pa_strip_nl(ln); + if (fgets(ln, sizeof(ln)-1, f)) + pa_strip_nl(ln); fclose(f); if (!ln[0]) @@ -107,7 +108,7 @@ static void save(struct userdata *u) { return; if (u->sink_filename) { - if ((f = fopen(u->sink_filename, "w"))) { + if ((f = pa_fopen_cloexec(u->sink_filename, "w"))) { pa_sink *s = pa_namereg_get_default_sink(u->core); fprintf(f, "%s\n", s ? s->name : ""); fclose(f); @@ -116,7 +117,7 @@ static void save(struct userdata *u) { } if (u->source_filename) { - if ((f = fopen(u->source_filename, "w"))) { + if ((f = pa_fopen_cloexec(u->source_filename, "w"))) { pa_source *s = pa_namereg_get_default_source(u->core); fprintf(f, "%s\n", s ? s->name : ""); fclose(f); @@ -127,7 +128,7 @@ static void save(struct userdata *u) { u->modified = FALSE; } -static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; pa_assert(u); @@ -146,12 +147,8 @@ static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t id u->modified = TRUE; - if (!u->time_event) { - struct timeval tv; - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEFAULT_SAVE_INTERVAL*PA_USEC_PER_SEC); - u->time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, time_cb, u); - } + if (!u->time_event) + u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, time_cb, u); } int pa__init(pa_module *m) { diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 18479df3..bb4994c6 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -34,8 +34,6 @@ #include <sys/types.h> #include <sys/stat.h> -#include <pulse/xmalloc.h> - #include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/modargs.h> @@ -50,7 +48,7 @@ PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE("just-one=<boolean>"); -PA_MODULE_DEPRECATED("Please use module-hal-detect instead of module-detect!"); +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!"); static const char* const valid_modargs[] = { "just-one", @@ -63,7 +61,7 @@ static int detect_alsa(pa_core *c, int just_one) { FILE *f; int n = 0, n_sink = 0, n_source = 0; - if (!(f = fopen("/proc/asound/devices", "r"))) { + if (!(f = pa_fopen_cloexec("/proc/asound/devices", "r"))) { if (errno != ENOENT) pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno)); @@ -119,14 +117,14 @@ static int detect_alsa(pa_core *c, int just_one) { } #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT static int detect_oss(pa_core *c, int just_one) { FILE *f; int n = 0, b = 0; - if (!(f = fopen("/dev/sndstat", "r")) && - !(f = fopen("/proc/sndstat", "r")) && - !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { if (errno != ENOENT) pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); @@ -144,7 +142,7 @@ static int detect_oss(pa_core *c, int just_one) { line[strcspn(line, "\r\n")] = 0; if (!b) { - b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0; + b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0; continue; } @@ -240,7 +238,7 @@ int pa__init(pa_module*m) { #ifdef HAVE_ALSA if ((n = detect_alsa(m->core, just_one)) <= 0) #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if ((n = detect_oss(m->core, just_one)) <= 0) #endif #ifdef HAVE_SOLARIS diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c new file mode 100644 index 00000000..67baef31 --- /dev/null +++ b/src/modules/module-device-manager.c @@ -0,0 +1,1700 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/rtclock.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-native.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/database.h> +#include <pulsecore/tagstruct.h> + +#include "module-device-manager-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "do_routing=<Automatically route streams based on a priority list (unique per-role)?> " + "on_hotplug=<When new device becomes available, recheck streams?> " + "on_rescue=<When device becomes unavailable, recheck streams?>"); + +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) +#define DUMP_DATABASE + +static const char* const valid_modargs[] = { + "do_routing", + "on_hotplug", + "on_rescue", + NULL +}; + +#define NUM_ROLES 9 +enum { + ROLE_NONE, + ROLE_VIDEO, + ROLE_MUSIC, + ROLE_GAME, + ROLE_EVENT, + ROLE_PHONE, + ROLE_ANIMATION, + ROLE_PRODUCTION, + ROLE_A11Y, + ROLE_MAX +}; + +typedef uint32_t role_indexes_t[NUM_ROLES]; + +static const char* role_names[NUM_ROLES] = { + "none", + "video", + "music", + "game", + "event", + "phone", + "animation", + "production", + "a11y", +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_subscription *subscription; + pa_hook_slot + *sink_new_hook_slot, + *source_new_hook_slot, + *sink_input_new_hook_slot, + *source_output_new_hook_slot, + *sink_put_hook_slot, + *source_put_hook_slot, + *sink_unlink_hook_slot, + *source_unlink_hook_slot, + *connection_unlink_hook_slot; + pa_time_event *save_time_event; + pa_database *database; + + pa_native_protocol *protocol; + pa_idxset *subscribed; + + pa_bool_t on_hotplug; + pa_bool_t on_rescue; + pa_bool_t do_routing; + + role_indexes_t preferred_sinks; + role_indexes_t preferred_sources; +}; + +#define ENTRY_VERSION 1 + +struct entry { + uint8_t version; + char *description; + pa_bool_t user_set_description; + char *icon; + role_indexes_t priority; +}; + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + + +/* Forward declarations */ +#ifdef DUMP_DATABASE +static void dump_database(struct userdata *); +#endif +static void notify_subscribers(struct userdata *); + + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(u); + + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + pa_database_sync(u->database); + pa_log_info("Synced."); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif +} + +static void trigger_save(struct userdata *u) { + + pa_assert(u); + + notify_subscribers(u); + + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static struct entry* entry_new(void) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + return r; +} + +static void entry_free(struct entry* e) { + pa_assert(e); + + pa_xfree(e->description); + pa_xfree(e->icon); +} + +static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e) { + pa_tagstruct *t; + pa_datum key, data; + pa_bool_t r; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu8(t, e->version); + pa_tagstruct_puts(t, e->description); + pa_tagstruct_put_boolean(t, e->user_set_description); + pa_tagstruct_puts(t, e->icon); + for (uint8_t i=0; i<ROLE_MAX; ++i) + pa_tagstruct_putu32(t, e->priority[i]); + + key.data = (char *) name; + key.size = strlen(name); + + data.data = (void*)pa_tagstruct_data(t, &data.size); + + r = (pa_database_set(u->database, &key, &data, TRUE) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 1 +static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) { + struct legacy_entry { + uint8_t version; + char description[PA_NAME_MAX]; + pa_bool_t user_set_description; + char icon[PA_NAME_MAX]; + role_indexes_t priority; + } PA_GCC_PACKED; + struct legacy_entry *le; + struct entry *e; + + pa_assert(u); + pa_assert(data); + + if (data->size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + return NULL; + } + + le = (struct legacy_entry*)data->data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + return NULL; + } + + if (!memchr(le->description, 0, sizeof(le->description))) { + pa_log_warn("Description has missing NUL byte."); + return NULL; + } + + if (!memchr(le->icon, 0, sizeof(le->icon))) { + pa_log_warn("Icon has missing NUL byte."); + return NULL; + } + + e = entry_new(); + e->description = pa_xstrdup(le->description); + e->icon = pa_xstrdup(le->icon); + return e; +} +#endif + +static struct entry* entry_read(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e = NULL; + pa_tagstruct *t = NULL; + const char *description, *icon; + + pa_assert(u); + pa_assert(name); + + key.data = (char*) name; + key.size = strlen(name); + + pa_zero(data); + + if (!pa_database_get(u->database, &key, &data)) + goto fail; + + t = pa_tagstruct_new(data.data, data.size); + e = entry_new(); + + if (pa_tagstruct_getu8(t, &e->version) < 0 || + e->version > ENTRY_VERSION || + pa_tagstruct_gets(t, &description) < 0 || + pa_tagstruct_get_boolean(t, &e->user_set_description) < 0 || + pa_tagstruct_gets(t, &icon) < 0) { + + goto fail; + } + + e->description = pa_xstrdup(description); + e->icon = pa_xstrdup(icon); + + for (uint8_t i=0; i<ROLE_MAX; ++i) { + if (pa_tagstruct_getu32(t, &e->priority[i]) < 0) + goto fail; + } + + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_free(t); + pa_datum_free(&data); + + return e; + +fail: + pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name); + + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name); + if ((e = legacy_entry_read(u, &data))) { + pa_log_debug("Success. Saving new format for key: %s", name); + if (entry_write(u, name, e)) + trigger_save(u); + pa_datum_free(&data); + return e; + } else + pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name); +#endif + + pa_datum_free(&data); + return NULL; +} + +#ifdef DUMP_DATABASE +static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) { + pa_assert(u); + pa_assert(human); + + if (sink_mode) { + pa_sink *s; + if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No sink specified", human); + } else { + pa_source *s; + if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No source specified", human); + } +} + +static void dump_database(struct userdata *u) { + pa_datum key; + pa_bool_t done; + + pa_assert(u); + + done = !pa_database_first(u->database, &key, NULL); + + pa_log_debug("Dumping database"); + while (!done) { + char *name; + struct entry *e; + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + + if ((e = entry_read(u, name))) { + pa_log_debug(" Got entry: %s", name); + pa_log_debug(" Description: %s", e->description); + pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u", + e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]); + pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u", + e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]); + entry_free(e); + } + + pa_xfree(name); + + pa_datum_free(&key); + key = next_key; + } + + if (u->do_routing) { + pa_log_debug(" Highest priority devices per-role:"); + + pa_log_debug(" Sinks:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, TRUE); + } + + pa_log_debug(" Sources:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, FALSE); + } + } + + pa_log_debug("Completed database dump"); +} +#endif + +static void notify_subscribers(struct userdata *u) { + + pa_native_connection *c; + uint32_t idx; + + pa_assert(u); + + for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) { + pa_tagstruct *t; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION); + pa_tagstruct_putu32(t, 0); + pa_tagstruct_putu32(t, u->module->index); + pa_tagstruct_puts(t, u->module->name); + pa_tagstruct_putu32(t, SUBCOMMAND_EVENT); + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t); + } +} + +static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { + + pa_assert(a); + pa_assert(b); + + if (!pa_streq(a->description, b->description) + || a->user_set_description != b->user_set_description + || !pa_streq(a->icon, b->icon)) + return FALSE; + + for (int i=0; i < NUM_ROLES; ++i) + if (a->priority[i] != b->priority[i]) + return FALSE; + + return TRUE; +} + +static char *get_name(const char *key, const char *prefix) { + char *t; + + if (strncmp(key, prefix, strlen(prefix))) + return NULL; + + t = pa_xstrdup(key + strlen(prefix)); + return t; +} + +static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) { + struct entry *old; + + pa_assert(u); + pa_assert(entry); + pa_assert(name); + pa_assert(prefix); + + if ((old = entry_read(u, name))) { + *entry = *old; + entry->description = pa_xstrdup(old->description); + entry->icon = pa_xstrdup(old->icon); + } else { + /* This is a new device, so make sure we write it's priority list correctly */ + role_indexes_t max_priority; + pa_datum key; + pa_bool_t done; + + pa_zero(max_priority); + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we calculate the current max priority for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name2; + struct entry *e; + + name2 = pa_xstrndup(key.data, key.size); + + if ((e = entry_read(u, name2))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + max_priority[i] = PA_MAX(max_priority[i], e->priority[i]); + } + + entry_free(e); + } + + pa_xfree(name2); + } + pa_datum_free(&key); + key = next_key; + } + + /* Actually initialise our entry now we've calculated it */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + entry->priority[i] = max_priority[i] + 1; + } + entry->user_set_description = FALSE; + } + + return old; +} + +static uint32_t get_role_index(const char* role) { + pa_assert(role); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) + if (strcmp(role, role_names[i]) == 0) + return i; + + return PA_INVALID_INDEX; +} + +static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) { + role_indexes_t *indexes, highest_priority_available; + pa_datum key; + pa_bool_t done, sink_mode; + + pa_assert(u); + pa_assert(prefix); + + sink_mode = (strcmp(prefix, "sink:") == 0); + + if (sink_mode) + indexes = &u->preferred_sinks; + else + indexes = &u->preferred_sources; + + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + (*indexes)[i] = PA_INVALID_INDEX; + } + pa_zero(highest_priority_available); + + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we find the highest priority device for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name, *device_name; + struct entry *e; + + name = pa_xstrndup(key.data, key.size); + device_name = get_name(name, prefix); + + if ((e = entry_read(u, name))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) { + /* We've found a device with a higher priority than that we've currently got, + so see if it is currently available or not and update our list */ + uint32_t idx; + pa_bool_t found = FALSE; + + if (sink_mode) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if ((pa_sink*) ignore_device == sink) + continue; + if (strcmp(sink->name, device_name) == 0) { + found = TRUE; + idx = sink->index; /* Is this needed? */ + break; + } + } + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + if ((pa_source*) ignore_device == source) + continue; + if (strcmp(source->name, device_name) == 0) { + found = TRUE; + idx = source->index; /* Is this needed? */ + break; + } + } + } + if (found) { + highest_priority_available[i] = e->priority[i]; + (*indexes)[i] = idx; + } + + } + } + + entry_free(e); + } + + pa_xfree(name); + pa_xfree(device_name); + } + + pa_datum_free(&key); + key = next_key; + } +} + + +static void route_sink_input(struct userdata *u, pa_sink_input *si) { + const char *role; + uint32_t role_index, device_index; + pa_sink *sink; + + pa_assert(u); + pa_assert(u->do_routing); + + if (si->save_sink) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!si->sink) + return; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + return; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) + return; + + if (si->sink != sink) + pa_sink_input_move_to(si, sink, FALSE); +} + +static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "sink:", ignore_sink); + + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { + route_sink_input(u, si); + } + + return PA_HOOK_OK; +} + +static void route_source_output(struct userdata *u, pa_source_output *so) { + const char *role; + uint32_t role_index, device_index; + pa_source *source; + + pa_assert(u); + pa_assert(u->do_routing); + + if (so->save_source) + return; + + if (so->direct_on_input) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + return; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + return; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) + return; + + if (so->source != source) + pa_source_output_move_to(so, source, FALSE); +} + +static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) { + pa_source_output *so; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "source:", ignore_source); + + PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { + route_source_output(u, so); + } + + return PA_HOOK_OK; +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + struct entry *entry, *old = NULL; + char *name = NULL; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) && + + /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + entry = entry_new(); + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + pa_sink_input *si; + + if (!u->do_routing) + return; + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_sink_input(u, si); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { + pa_source_output *so; + + if (!u->do_routing) + return; + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_source_output(u, so); + + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + pa_sink *sink; + + if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) + return; + + name = pa_sprintf_malloc("sink:%s", sink->name); + + old = load_or_initialize_entry(u, entry, name, "sink:"); + + if (!entry->user_set_description) { + pa_xfree(entry->description); + entry->description = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } else if (!pa_streq(entry->description, pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_sink_set_description(sink, entry->description); + } + + pa_xfree(entry->icon); + entry->icon = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + pa_source *source; + + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (!(source = pa_idxset_get_by_index(c->sources, idx))) + return; + + if (source->monitor_of) + return; + + name = pa_sprintf_malloc("source:%s", source->name); + + old = load_or_initialize_entry(u, entry, name, "source:"); + + if (!entry->user_set_description) { + pa_xfree(entry->description); + entry->description = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } else if (!pa_streq(entry->description, pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_source_set_description(source, entry->description); + } + + pa_xfree(entry->icon); + entry->icon = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)); + } + + pa_assert(name); + + if (old) { + + if (entries_equal(old, entry)) { + entry_free(old); + entry_free(entry); + pa_xfree(name); + + return; + } + + entry_free(old); + } + + pa_log_info("Storing device %s.", name); + + if (entry_write(u, name, entry)) + trigger_save(u); + else + pa_log_warn("Could not save device");; + + entry_free(entry); + pa_xfree(name); +} + +static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("sink:%s", new_data->name); + + if ((e = entry_read(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + pa_log_info("Restoring description for sink %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + entry_free(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = entry_read(u, name))) { + if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */ + pa_log_info("Restoring description for source %s.", new_data->name); + pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); + } + + entry_free(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->sink) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_sink *sink; + + if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) { + if (!pa_sink_input_new_data_set_sink(new_data, sink, FALSE)) + pa_log_debug("Not restoring device for stream because no supported format was found"); + } + } + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + if (new_data->direct_on_input) + return PA_HOOK_OK; + + if (new_data->source) + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX != device_index) { + pa_source *source; + + if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) + if (!pa_source_output_new_data_set_source(new_data, source, FALSE)) + pa_log_debug("Not restoring device for stream because no supported format was found"); + } + } + } + + return PA_HOOK_OK; +} + + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_sink_inputs(u, NULL); +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_hotplug); + + notify_subscribers(u); + + return route_source_outputs(u, NULL); +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_sink_inputs(u, sink); +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->core == c); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + notify_subscribers(u); + + return route_source_outputs(u, source); +} + + +static void apply_entry(struct userdata *u, const char *name, struct entry *e) { + uint32_t idx; + char *n; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + if (!e->user_set_description) + return; + + if ((n = get_name(name, "sink:"))) { + pa_sink *s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + pa_log_info("Setting description for sink %s to '%s'", s->name, e->description); + pa_sink_set_description(s, e->description); + } + pa_xfree(n); + } + else if ((n = get_name(name, "source:"))) { + pa_source *s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (!pa_streq(s->name, n)) { + continue; + } + + if (s->monitor_of) { + pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name); + continue; + } + + pa_log_info("Setting description for source %s to '%s'", s->name, e->description); + pa_source_set_description(s, e->description); + } + pa_xfree(n); + } +} + + +#define EXT_VERSION 1 + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + + case SUBCOMMAND_READ: { + pa_datum key; + pa_bool_t done; + + if (!pa_tagstruct_eof(t)) + goto fail; + + done = !pa_database_first(u->database, &key, NULL); + + while (!done) { + pa_datum next_key; + struct entry *e; + char *name; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); + + if ((e = entry_read(u, name))) { + uint32_t idx; + char *device_name; + uint32_t found_index = PA_INVALID_INDEX; + + if ((device_name = get_name(name, "sink:"))) { + pa_sink* s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (strcmp(s->name, device_name) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(device_name); + } else if ((device_name = get_name(name, "source:"))) { + pa_source* s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (strcmp(s->name, device_name) == 0) { + found_index = s->index; + break; + } + } + pa_xfree(device_name); + } + + pa_tagstruct_puts(reply, name); + pa_tagstruct_puts(reply, e->description); + pa_tagstruct_puts(reply, e->icon); + pa_tagstruct_putu32(reply, found_index); + pa_tagstruct_putu32(reply, NUM_ROLES); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) { + pa_tagstruct_puts(reply, role_names[i]); + pa_tagstruct_putu32(reply, e->priority[i]); + } + + entry_free(e); + } + + pa_xfree(name); + + key = next_key; + } + + break; + } + + case SUBCOMMAND_RENAME: { + + struct entry *e; + const char *device, *description; + + if (pa_tagstruct_gets(t, &device) < 0 || + pa_tagstruct_gets(t, &description) < 0) + goto fail; + + if (!device || !*device || !description || !*description) + goto fail; + + if ((e = entry_read(u, device))) { + pa_xfree(e->description); + e->description = pa_xstrdup(description); + e->user_set_description = TRUE; + + if (entry_write(u, (char *)device, e)) { + apply_entry(u, device, e); + + trigger_save(u); + } + else + pa_log_warn("Could not save device"); + + entry_free(e); + } + else + pa_log_warn("Could not rename device %s, no entry in database", device); + + break; + } + + case SUBCOMMAND_DELETE: + + while (!pa_tagstruct_eof(t)) { + const char *name; + pa_datum key; + + if (pa_tagstruct_gets(t, &name) < 0) + goto fail; + + key.data = (char*) name; + key.size = strlen(name); + + /** @todo: Reindex the priorities */ + pa_database_unset(u->database, &key); + } + + trigger_save(u); + + break; + + case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: { + + pa_bool_t enable; + + if (pa_tagstruct_get_boolean(t, &enable) < 0) + goto fail; + + if ((u->do_routing = enable)) { + /* Update our caches */ + update_highest_priority_device_indexes(u, "sink:", NULL); + update_highest_priority_device_indexes(u, "source:", NULL); + } + + break; + } + + case SUBCOMMAND_REORDER: { + + const char *role; + struct entry *e; + uint32_t role_index, n_devices; + pa_datum key; + pa_bool_t done, sink_mode = TRUE; + struct device_t { uint32_t prio; char *device; }; + struct device_t *device; + struct device_t **devices; + uint32_t i, idx, offset; + pa_hashmap *h; + /*void *state;*/ + pa_bool_t first; + + if (pa_tagstruct_gets(t, &role) < 0 || + pa_tagstruct_getu32(t, &n_devices) < 0 || + n_devices < 1) + goto fail; + + if (PA_INVALID_INDEX == (role_index = get_role_index(role))) + goto fail; + + /* Cycle through the devices given and make sure they exist */ + h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + first = TRUE; + idx = 0; + for (i = 0; i < n_devices; ++i) { + const char *s; + if (pa_tagstruct_gets(t, &s) < 0) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Protocol error on reorder"); + goto fail; + } + + /* Ensure this is a valid entry */ + if (!(e = entry_read(u, s))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Client specified an unknown device in it's reorder list."); + goto fail; + } + entry_free(e); + + if (first) { + first = FALSE; + sink_mode = (0 == strncmp("sink:", s, 5)); + } else if ((sink_mode && 0 != strncmp("sink:", s, 5)) || (!sink_mode && 0 != strncmp("source:", s, 7))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_hashmap_free(h, NULL, NULL); + pa_log_error("Attempted to reorder mixed devices (sinks and sources)"); + goto fail; + } + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrdup(s); + if (pa_hashmap_put(h, device->device, device) == 0) { + device->prio = idx; + idx++; + } else { + pa_xfree(device->device); + pa_xfree(device); + } + } + + /*pa_log_debug("Hashmap contents (received from client)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now cycle through our list and add all the devices. + This has the effect of addign in any in our DB, + not specified in the device list (and thus will be + tacked on at the end) */ + offset = idx; + done = !pa_database_first(u->database, &key, NULL); + + while (!done && idx < 256) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrndup(key.data, key.size); + if ((sink_mode && 0 == strncmp("sink:", device->device, 5)) + || (!sink_mode && 0 == strncmp("source:", device->device, 7))) { + + /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + if (pa_hashmap_put(h, device->device, device) == 0 + && (e = entry_read(u, device->device))) { + /* We add offset on to the existing priorirty so that when we order, the + existing entries are always lower priority than the new ones. */ + device->prio = (offset + e->priority[role_index]); + pa_xfree(e); + } + else { + pa_xfree(device->device); + pa_xfree(device); + } + } else { + pa_xfree(device->device); + pa_xfree(device); + } + + pa_datum_free(&key); + + key = next_key; + } + + /*pa_log_debug("Hashmap contents (combined with database)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now we put all the entries in a simple list for sorting it. */ + n_devices = pa_hashmap_size(h); + devices = pa_xnew(struct device_t *, n_devices); + idx = 0; + while ((device = pa_hashmap_steal_first(h))) { + devices[idx++] = device; + } + pa_hashmap_free(h, NULL, NULL); + + /* Simple bubble sort */ + for (i = 0; i < n_devices; ++i) { + for (uint32_t j = i; j < n_devices; ++j) { + if (devices[i]->prio > devices[j]->prio) { + struct device_t *tmp; + tmp = devices[i]; + devices[i] = devices[j]; + devices[j] = tmp; + } + } + } + + /*pa_log_debug("Sorted device list"); + for (i = 0; i < n_devices; ++i) { + pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio); + }*/ + + /* Go through in order and write the new entry and cleanup our own list */ + idx = 1; + first = TRUE; + for (i = 0; i < n_devices; ++i) { + if ((e = entry_read(u, devices[i]->device))) { + if (e->priority[role_index] == idx) + idx++; + else { + e->priority[role_index] = idx; + + if (entry_write(u, (char *) devices[i]->device, e)) { + first = FALSE; + idx++; + } + } + + pa_xfree(e); + } + pa_xfree(devices[i]->device); + pa_xfree(devices[i]); + } + + if (!first) { + trigger_save(u); + + if (sink_mode) + route_sink_inputs(u, NULL); + else + route_source_outputs(u, NULL); + } + + break; + } + + case SUBCOMMAND_SUBSCRIBE: { + + pa_bool_t enabled; + + if (pa_tagstruct_get_boolean(t, &enabled) < 0 || + !pa_tagstruct_eof(t)) + goto fail; + + if (enabled) + pa_idxset_put(u->subscribed, c, NULL); + else + pa_idxset_remove_by_data(u->subscribed, c, NULL); + + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + + fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) { + pa_assert(p); + pa_assert(c); + pa_assert(u); + + pa_idxset_remove_by_data(u->subscribed, c, NULL); + return PA_HOOK_OK; +} + +struct prioritised_indexes { + uint32_t index; + int32_t priority; +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + char *fname; + pa_sink *sink; + pa_source *source; + uint32_t idx; + pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE; + uint32_t total_devices; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "do_routing", &do_routing) < 0 || + pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("on_hotplug= and on_rescue= expect boolean arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->do_routing = do_routing; + u->on_hotplug = on_hotplug; + u->on_rescue = on_rescue; + u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + u->connection_unlink_hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u); + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); + + /* Used to handle device description management */ + u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u); + u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u); + + /* The following slots are used to deal with routing */ + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) source_output_new_hook_callback, u); + + if (on_hotplug) { + /* A little bit later than module-stream-restore, but before module-intended-roles */ + u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) source_put_hook_callback, u); + } + + if (on_rescue) { + /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */ + u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u); + } + + if (!(fname = pa_state_path("device-manager", TRUE))) + goto fail; + + if (!(u->database = pa_database_open(fname, TRUE))) { + pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); + pa_xfree(fname); + goto fail; + } + + pa_log_info("Successfully opened database file '%s'.", fname); + pa_xfree(fname); + + /* Attempt to inject the devices into the list in priority order */ + total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources)); + if (total_devices > 0 && total_devices < 128) { + uint32_t i; + struct prioritised_indexes p_i[128]; + + /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */ + i = 0; + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) { + pa_log_debug("Found sink index %u", sink->index); + p_i[i ].index = sink->index; + p_i[i++].priority = sink->priority; + } + /* Bubble sort it (only really useful for first time creation) */ + if (i > 1) + for (uint32_t j = 0; j < i; ++j) + for (uint32_t k = 0; k < i; ++k) + if (p_i[j].priority > p_i[k].priority) { + struct prioritised_indexes tmp_pi = p_i[k]; + p_i[k] = p_i[j]; + p_i[j] = tmp_pi; + } + /* Register it */ + for (uint32_t j = 0; j < i; ++j) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u); + + + /* We cycle over all the available sources so that they are added to our database if they are not in it yet */ + i = 0; + PA_IDXSET_FOREACH(source, m->core->sources, idx) { + p_i[i ].index = source->index; + p_i[i++].priority = source->priority; + } + /* Bubble sort it (only really useful for first time creation) */ + if (i > 1) + for (uint32_t j = 0; j < i; ++j) + for (uint32_t k = 0; k < i; ++k) + if (p_i[j].priority > p_i[k].priority) { + struct prioritised_indexes tmp_pi = p_i[k]; + p_i[k] = p_i[j]; + p_i[j] = tmp_pi; + } + /* Register it */ + for (uint32_t j = 0; j < i; ++j) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u); + } + else if (total_devices > 0) { + /* This user has a *lot* of devices... */ + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + PA_IDXSET_FOREACH(source, m->core->sources, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + } + + /* Perform the routing (if it's enabled) which will update our priority list cache too */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX; + } + + route_sink_inputs(u, NULL); + route_source_outputs(u, NULL); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_new_hook_slot) + pa_hook_slot_free(u->sink_new_hook_slot); + if (u->source_new_hook_slot) + pa_hook_slot_free(u->source_new_hook_slot); + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); + + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->source_put_hook_slot) + pa_hook_slot_free(u->source_put_hook_slot); + + if (u->sink_unlink_hook_slot) + pa_hook_slot_free(u->sink_unlink_hook_slot); + if (u->source_unlink_hook_slot) + pa_hook_slot_free(u->source_unlink_hook_slot); + + if (u->connection_unlink_hook_slot) + pa_hook_slot_free(u->connection_unlink_hook_slot); + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + if (u->database) + pa_database_close(u->database); + + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + if (u->subscribed) + pa_idxset_free(u->subscribed, NULL, NULL); + + pa_xfree(u); +} diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index ae21acd5..7d94ffa4 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -2,6 +2,7 @@ This file is part of PulseAudio. Copyright 2006-2008 Lennart Poettering + Copyright 2011 Colin Guthrie PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -29,12 +30,14 @@ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> -#include <ctype.h> +#include <pulse/gccmacro.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> #include <pulse/timeval.h> -#include <pulse/util.h> +#include <pulse/rtclock.h> +#include <pulse/format.h> +#include <pulse/internal.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -42,10 +45,14 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/core-subscribe.h> -#include <pulsecore/sink-input.h> -#include <pulsecore/source-output.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> #include <pulsecore/namereg.h> +#include <pulsecore/protocol-native.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pstream-util.h> #include <pulsecore/database.h> +#include <pulsecore/tagstruct.h> #include "module-device-restore-symdef.h" @@ -54,14 +61,16 @@ PA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( + "restore_port=<Save/restore port?> " "restore_volume=<Save/restore volumes?> " "restore_muted=<Save/restore muted states?>"); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) static const char* const valid_modargs[] = { "restore_volume", "restore_muted", + "restore_port", NULL }; @@ -70,30 +79,50 @@ struct userdata { pa_module *module; pa_subscription *subscription; pa_hook_slot + *sink_new_hook_slot, *sink_fixate_hook_slot, - *source_fixate_hook_slot; + *source_new_hook_slot, + *source_fixate_hook_slot, + *connection_unlink_hook_slot; pa_time_event *save_time_event; pa_database *database; + pa_native_protocol *protocol; + pa_idxset *subscribed; + pa_bool_t restore_volume:1; pa_bool_t restore_muted:1; + pa_bool_t restore_port:1; +}; + +/* Protocol extention commands */ +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT, + SUBCOMMAND_READ_SINK_FORMATS_ALL, + SUBCOMMAND_READ_SINK_FORMATS, + SUBCOMMAND_SAVE_SINK_FORMATS }; + #define ENTRY_VERSION 1 struct entry { uint8_t version; - pa_bool_t muted:1; + pa_bool_t muted_valid, volume_valid, port_valid; + pa_bool_t muted; pa_channel_map channel_map; pa_cvolume volume; -} PA_GCC_PACKED; + char *port; + pa_idxset *formats; +}; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; pa_assert(a); pa_assert(e); - pa_assert(tv); pa_assert(u); pa_assert(e == u->save_time_event); @@ -104,11 +133,140 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct pa_log_info("Synced."); } -static struct entry* read_entry(struct userdata *u, const char *name) { +static void trigger_save(struct userdata *u) { + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static struct entry* entry_new(pa_bool_t add_pcm_format) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + r->formats = pa_idxset_new(NULL, NULL); + if (add_pcm_format) { + pa_format_info *f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + pa_idxset_put(r->formats, f, NULL); + } + return r; +} + +static void entry_free(struct entry* e) { + pa_assert(e); + + pa_idxset_free(e->formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + pa_xfree(e->port); + pa_xfree(e); +} + +static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e) { + pa_tagstruct *t; pa_datum key, data; + pa_bool_t r; + uint32_t i; + pa_format_info *f; + uint8_t n_formats; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + n_formats = pa_idxset_size(e->formats); + pa_assert(n_formats > 0); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu8(t, e->version); + pa_tagstruct_put_boolean(t, e->volume_valid); + pa_tagstruct_put_channel_map(t, &e->channel_map); + pa_tagstruct_put_cvolume(t, &e->volume); + pa_tagstruct_put_boolean(t, e->muted_valid); + pa_tagstruct_put_boolean(t, e->muted); + pa_tagstruct_put_boolean(t, e->port_valid); + pa_tagstruct_puts(t, e->port); + pa_tagstruct_putu8(t, n_formats); + + PA_IDXSET_FOREACH(f, e->formats, i) { + pa_tagstruct_put_format_info(t, f); + } + + key.data = (char *) name; + key.size = strlen(name); + + data.data = (void*)pa_tagstruct_data(t, &data.size); + + r = (pa_database_set(u->database, &key, &data, TRUE) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 2 +static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) { + struct legacy_entry { + uint8_t version; + pa_bool_t muted_valid:1, volume_valid:1, port_valid:1; + pa_bool_t muted:1; + pa_channel_map channel_map; + pa_cvolume volume; + char port[PA_NAME_MAX]; + } PA_GCC_PACKED; + struct legacy_entry *le; struct entry *e; pa_assert(u); + pa_assert(data); + + if (data->size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + return NULL; + } + + le = (struct legacy_entry*)data->data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + return NULL; + } + + if (!memchr(le->port, 0, sizeof(le->port))) { + pa_log_warn("Port has missing NUL byte."); + return NULL; + } + + if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { + pa_log_warn("Invalid channel map."); + return NULL; + } + + if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { + pa_log_warn("Volume and channel map don't match."); + return NULL; + } + + e = entry_new(TRUE); + e->muted_valid = le->muted_valid; + e->volume_valid = le->volume_valid; + e->port_valid = le->port_valid; + e->muted = le->muted; + e->channel_map = le->channel_map; + e->volume = le->volume; + e->port = pa_xstrdup(le->port); + return e; +} +#endif + +static struct entry* entry_read(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e = NULL; + pa_tagstruct *t = NULL; + const char* port; + uint8_t i, n_formats; + + pa_assert(u); pa_assert(name); key.data = (char*) name; @@ -119,57 +277,127 @@ static struct entry* read_entry(struct userdata *u, const char *name) { if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.size != sizeof(struct entry)) { - pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); + t = pa_tagstruct_new(data.data, data.size); + e = entry_new(FALSE); + + if (pa_tagstruct_getu8(t, &e->version) < 0 || + e->version > ENTRY_VERSION || + pa_tagstruct_get_boolean(t, &e->volume_valid) < 0 || + pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &e->volume) < 0 || + pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 || + pa_tagstruct_get_boolean(t, &e->muted) < 0 || + pa_tagstruct_get_boolean(t, &e->port_valid) < 0 || + pa_tagstruct_gets(t, &port) < 0 || + pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) { + goto fail; } - e = (struct entry*) data.data; + e->port = pa_xstrdup(port); - if (e->version != ENTRY_VERSION) { - pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); - goto fail; + for (i = 0; i < n_formats; ++i) { + pa_format_info *f = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, f) < 0) { + pa_format_info_free(f); + goto fail; + } + pa_idxset_put(e->formats, f, NULL); } - if (!(pa_cvolume_valid(&e->volume))) { - pa_log_warn("Invalid volume stored in database for device %s", name); + if (!pa_tagstruct_eof(t)) goto fail; - } - if (!(pa_channel_map_valid(&e->channel_map))) { + if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) { pa_log_warn("Invalid channel map stored in database for device %s", name); goto fail; } - if (e->volume.channels != e->channel_map.channels) { + if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) { pa_log_warn("Volume and channel map don't match in database entry for device %s", name); goto fail; } + pa_tagstruct_free(t); + pa_datum_free(&data); + return e; fail: + pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name); + + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name); + if ((e = legacy_entry_read(u, &data))) { + pa_log_debug("Success. Saving new format for key: %s", name); + if (entry_write(u, name, e)) + trigger_save(u); + pa_datum_free(&data); + return e; + } else + pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name); +#endif + pa_datum_free(&data); return NULL; } -static void trigger_save(struct userdata *u) { - struct timeval tv; +static struct entry* entry_copy(const struct entry *e) { + struct entry* r; + uint32_t idx; + pa_format_info *f; - if (u->save_time_event) - return; + pa_assert(e); + r = entry_new(FALSE); + r->version = e->version; + r->muted_valid = e->muted_valid; + r->volume_valid = e->volume_valid; + r->port_valid = e->port_valid; + r->muted = e->muted; + r->channel_map = e->channel_map; + r->volume = e->volume; + r->port = pa_xstrdup(e->port); + + PA_IDXSET_FOREACH(f, e->formats, idx) { + pa_idxset_put(r->formats, pa_format_info_copy(f), NULL); + } + return r; +} + +static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { + pa_cvolume t; - pa_gettimeofday(&tv); - tv.tv_sec += SAVE_INTERVAL; - u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); + if (a->port_valid != b->port_valid || + (a->port_valid && !pa_streq(a->port, b->port))) + return FALSE; + + if (a->muted_valid != b->muted_valid || + (a->muted_valid && (a->muted != b->muted))) + return FALSE; + + t = b->volume; + if (a->volume_valid != b->volume_valid || + (a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume))) + return FALSE; + + if (pa_idxset_size(a->formats) != pa_idxset_size(b->formats)) + return FALSE; + + /** TODO: Compare a bit better */ + + return TRUE; } static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; - struct entry entry, *old; + struct entry *entry, *old; char *name; - pa_datum key, data; pa_assert(c); pa_assert(u); @@ -180,9 +408,6 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - pa_zero(entry); - entry.version = ENTRY_VERSION; - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { pa_sink *sink; @@ -190,9 +415,28 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 return; name = pa_sprintf_malloc("sink:%s", sink->name); - entry.channel_map = sink->channel_map; - entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE); - entry.muted = pa_sink_get_mute(sink, FALSE); + + if ((old = entry_read(u, name))) + entry = entry_copy(old); + else + entry = entry_new(TRUE); + + if (sink->save_volume) { + entry->channel_map = sink->channel_map; + entry->volume = *pa_sink_get_volume(sink, FALSE); + entry->volume_valid = TRUE; + } + + if (sink->save_muted) { + entry->muted = pa_sink_get_mute(sink, FALSE); + entry->muted_valid = TRUE; + } + + if (sink->save_port) { + pa_xfree(entry->port); + entry->port = pa_xstrdup(sink->active_port ? sink->active_port->name : ""); + entry->port_valid = TRUE; + } } else { pa_source *source; @@ -203,69 +447,153 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 return; name = pa_sprintf_malloc("source:%s", source->name); - entry.channel_map = source->channel_map; - entry.volume = *pa_source_get_volume(source, FALSE); - entry.muted = pa_source_get_mute(source, FALSE); + + if ((old = entry_read(u, name))) + entry = entry_copy(old); + else + entry = entry_new(TRUE); + + if (source->save_volume) { + entry->channel_map = source->channel_map; + entry->volume = *pa_source_get_volume(source, FALSE); + entry->volume_valid = TRUE; + } + + if (source->save_muted) { + entry->muted = pa_source_get_mute(source, FALSE); + entry->muted_valid = TRUE; + } + + if (source->save_port) { + pa_xfree(entry->port); + entry->port = pa_xstrdup(source->active_port ? source->active_port->name : ""); + entry->port_valid = TRUE; + } } - if ((old = read_entry(u, name))) { + pa_assert(entry); - if (pa_cvolume_equal(pa_cvolume_remap(&old->volume, &old->channel_map, &entry.channel_map), &entry.volume) && - !old->muted == !entry.muted) { + if (old) { - pa_xfree(old); + if (entries_equal(old, entry)) { + entry_free(old); + entry_free(entry); pa_xfree(name); return; } - pa_xfree(old); + entry_free(old); } - key.data = name; - key.size = strlen(name); + pa_log_info("Storing volume/mute/port for device %s.", name); + + if (entry_write(u, name, entry)) + trigger_save(u); + + entry_free(entry); + pa_xfree(name); +} - data.data = &entry; - data.size = sizeof(entry); +static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; - pa_log_info("Storing volume/mute for device %s.", name); + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + pa_assert(u->restore_port); + + name = pa_sprintf_malloc("sink:%s", new_data->name); - pa_database_set(u->database, &key, &data, TRUE); + if ((e = entry_read(u, name))) { + + if (e->port_valid) { + if (!new_data->active_port) { + pa_log_info("Restoring port for sink %s.", name); + pa_sink_new_data_set_port(new_data, e->port); + new_data->save_port = TRUE; + } else + pa_log_debug("Not restoring port for sink %s, because already set.", name); + } + + entry_free(e); + } pa_xfree(name); - trigger_save(u); + return PA_HOOK_OK; } static pa_hook_result_t sink_fixate_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { char *name; struct entry *e; + pa_assert(c); pa_assert(new_data); + pa_assert(u); + pa_assert(u->restore_volume || u->restore_muted); name = pa_sprintf_malloc("sink:%s", new_data->name); - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { - if (u->restore_volume) { + if (u->restore_volume && e->volume_valid) { if (!new_data->volume_is_set) { + pa_cvolume v; + pa_log_info("Restoring volume for sink %s.", new_data->name); - pa_sink_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + + v = e->volume; + pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); + pa_sink_new_data_set_volume(new_data, &v); + + new_data->save_volume = TRUE; } else pa_log_debug("Not restoring volume for sink %s, because already set.", new_data->name); - } - if (u->restore_muted) { + if (u->restore_muted && e->muted_valid) { if (!new_data->muted_is_set) { pa_log_info("Restoring mute state for sink %s.", new_data->name); pa_sink_new_data_set_muted(new_data, e->muted); + new_data->save_muted = TRUE; } else pa_log_debug("Not restoring mute state for sink %s, because already set.", new_data->name); } - pa_xfree(e); + entry_free(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + pa_assert(u->restore_port); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = entry_read(u, name))) { + + if (e->port_valid) { + if (!new_data->active_port) { + pa_log_info("Restoring port for source %s.", name); + pa_source_new_data_set_port(new_data, e->port); + new_data->save_port = TRUE; + } else + pa_log_debug("Not restoring port for source %s, because already set.", name); + } + + entry_free(e); } pa_xfree(name); @@ -277,31 +605,42 @@ static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_da char *name; struct entry *e; + pa_assert(c); pa_assert(new_data); + pa_assert(u); + pa_assert(u->restore_volume || u->restore_muted); name = pa_sprintf_malloc("source:%s", new_data->name); - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { - if (u->restore_volume) { + if (u->restore_volume && e->volume_valid) { if (!new_data->volume_is_set) { + pa_cvolume v; + pa_log_info("Restoring volume for source %s.", new_data->name); - pa_source_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + + v = e->volume; + pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); + pa_source_new_data_set_volume(new_data, &v); + + new_data->save_volume = TRUE; } else pa_log_debug("Not restoring volume for source %s, because already set.", new_data->name); } - if (u->restore_muted) { + if (u->restore_muted && e->muted_valid) { if (!new_data->muted_is_set) { pa_log_info("Restoring mute state for source %s.", new_data->name); pa_source_new_data_set_muted(new_data, e->muted); + new_data->save_muted = TRUE; } else pa_log_debug("Not restoring mute state for source %s, because already set.", new_data->name); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -309,6 +648,197 @@ static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_da return PA_HOOK_OK; } +#define EXT_VERSION 1 + +static void read_sink_format_reply(struct userdata *u, pa_tagstruct *reply, pa_sink *sink) { + struct entry *e; + char *name; + + pa_assert(u); + pa_assert(reply); + pa_assert(sink); + + pa_tagstruct_putu32(reply, sink->index); + + /* Read or create an entry */ + name = pa_sprintf_malloc("sink:%s", sink->name); + if (!(e = entry_read(u, name))) { + /* Fake a reply with PCM encoding supported */ + pa_format_info *f = pa_format_info_new(); + + pa_tagstruct_putu8(reply, 1); + f->encoding = PA_ENCODING_PCM; + pa_tagstruct_put_format_info(reply, f); + + pa_format_info_free(f); + } else { + uint32_t idx; + pa_format_info *f; + + /* Write all the formats from the entry to the reply */ + pa_tagstruct_putu8(reply, pa_idxset_size(e->formats)); + PA_IDXSET_FOREACH(f, e->formats, idx) { + pa_tagstruct_put_format_info(reply, f); + } + } + pa_xfree(name); +} + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + + case SUBCOMMAND_SUBSCRIBE: { + + pa_bool_t enabled; + + if (pa_tagstruct_get_boolean(t, &enabled) < 0 || + !pa_tagstruct_eof(t)) + goto fail; + + if (enabled) + pa_idxset_put(u->subscribed, c, NULL); + else + pa_idxset_remove_by_data(u->subscribed, c, NULL); + + break; + } + + case SUBCOMMAND_READ_SINK_FORMATS_ALL: { + pa_sink *sink; + uint32_t idx; + + if (!pa_tagstruct_eof(t)) + goto fail; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + read_sink_format_reply(u, reply, sink); + } + + break; + } + case SUBCOMMAND_READ_SINK_FORMATS: { + uint32_t sink_index; + pa_sink *sink; + + pa_assert(reply); + + /* Get the sink index and the number of formats from the tagstruct */ + if (pa_tagstruct_getu32(t, &sink_index) < 0) + goto fail; + + if (!pa_tagstruct_eof(t)) + goto fail; + + /* Now find our sink */ + if (!(sink = pa_idxset_get_by_index(u->core->sinks, sink_index))) + goto fail; + + read_sink_format_reply(u, reply, sink); + + break; + } + + case SUBCOMMAND_SAVE_SINK_FORMATS: { + + struct entry *e; + uint32_t sink_index; + char *name; + pa_sink *sink; + uint8_t i, n_formats; + + /* Get the sink index and the number of formats from the tagstruct */ + if (pa_tagstruct_getu32(t, &sink_index) < 0 || + pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) { + + goto fail; + } + + /* Now find our sink */ + if (!(sink = pa_idxset_get_by_index(u->core->sinks, sink_index))) + goto fail; + + /* Read or create an entry */ + name = pa_sprintf_malloc("sink:%s", sink->name); + if (!(e = entry_read(u, name))) + e = entry_new(FALSE); + + /* Read all the formats from our tagstruct */ + for (i = 0; i < n_formats; ++i) { + pa_format_info *f = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, f) < 0) { + pa_format_info_free(f); + pa_xfree(name); + goto fail; + } + pa_idxset_put(e->formats, f, NULL); + } + + if (!pa_tagstruct_eof(t)) { + entry_free(e); + pa_xfree(name); + goto fail; + } + + if (entry_write(u, name, e)) + trigger_save(u); + else + pa_log_warn("Could not save format info for sink %s", sink->name); + + pa_xfree(name); + entry_free(e); + + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + +fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) { + pa_assert(p); + pa_assert(c); + pa_assert(u); + + pa_idxset_remove_by_data(u->subscribed, c, NULL); + return PA_HOOK_OK; +} + int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; @@ -316,7 +846,7 @@ int pa__init(pa_module*m) { pa_sink *sink; pa_source *source; uint32_t idx; - pa_bool_t restore_volume = TRUE, restore_muted = TRUE; + pa_bool_t restore_volume = TRUE, restore_muted = TRUE, restore_port = TRUE; pa_assert(m); @@ -326,24 +856,36 @@ int pa__init(pa_module*m) { } if (pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 || - pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0) { - pa_log("restore_volume= and restore_muted= expect boolean arguments"); + pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0 || + pa_modargs_get_value_boolean(ma, "restore_port", &restore_port) < 0) { + pa_log("restore_port=, restore_volume= and restore_muted= expect boolean arguments"); goto fail; } - if (!restore_muted && !restore_volume) - pa_log_warn("Neither restoring volume nor restoring muted enabled!"); + if (!restore_muted && !restore_volume && !restore_port) + pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring port enabled!"); - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; - u->save_time_event = NULL; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->database = NULL; + u->restore_port = restore_port; + + u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + u->connection_unlink_hook_slot = pa_hook_connect(&pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u); u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); + if (restore_port) { + u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u); + u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u); + } + if (restore_muted || restore_volume) { u->sink_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_fixate_hook_callback, u); u->source_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u); @@ -358,7 +900,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_log_info("Successfully opened database file '%s'.", fname); pa_xfree(fname); for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) @@ -376,7 +918,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { @@ -394,6 +936,13 @@ void pa__done(pa_module*m) { pa_hook_slot_free(u->sink_fixate_hook_slot); if (u->source_fixate_hook_slot) pa_hook_slot_free(u->source_fixate_hook_slot); + if (u->sink_new_hook_slot) + pa_hook_slot_free(u->sink_new_hook_slot); + if (u->source_new_hook_slot) + pa_hook_slot_free(u->source_new_hook_slot); + + if (u->connection_unlink_hook_slot) + pa_hook_slot_free(u->connection_unlink_hook_slot); if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); @@ -401,5 +950,13 @@ void pa__done(pa_module*m) { if (u->database) pa_database_close(u->database); + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + if (u->subscribed) + pa_idxset_free(u->subscribed, NULL, NULL); + pa_xfree(u); } diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c new file mode 100644 index 00000000..e7d8790a --- /dev/null +++ b/src/modules/module-equalizer-sink.c @@ -0,0 +1,2221 @@ +/*** + This file is part of PulseAudio. + + This module is based off Lennart Poettering's LADSPA sink and swaps out + LADSPA functionality for a dbus-aware STFT OLA based digital equalizer. + All new work is published under Pulseaudio's original license. + + Copyright 2009 Jason Newton <nevion@gmail.com> + + Original Author: + Copyright 2004-2008 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <float.h> +#include <math.h> +#include <string.h> +#include <stdint.h> + +//#undef __SSE2__ +#ifdef __SSE2__ +#include <xmmintrin.h> +#include <emmintrin.h> +#endif + +#include <fftw3.h> + +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-rtclock.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/shared.h> +#include <pulsecore/idxset.h> +#include <pulsecore/strlist.h> +#include <pulsecore/database.h> +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/dbus-util.h> + +#include "module-equalizer-sink-symdef.h" + +PA_MODULE_AUTHOR("Jason Newton"); +PA_MODULE_DESCRIPTION(_("General Purpose Equalizer")); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + _("sink_name=<name of the sink> " + "sink_properties=<properties for the sink> " + "sink_master=<sink to connect to> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "autoloaded=<set if this module is being loaded automatically> " + )); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) +#define DEFAULT_AUTOLOADED FALSE + +struct userdata { + pa_module *module; + pa_sink *sink; + pa_sink_input *sink_input; + pa_bool_t autoloaded; + + size_t channels; + size_t fft_size;//length (res) of fft + size_t window_size;/* + *sliding window size + *effectively chooses R + */ + size_t R;/* the hop size between overlapping windows + * the latency of the filter, calculated from window_size + * based on constraints of COLA and window function + */ + //for twiddling with pulseaudio + size_t overlap_size;//window_size-R + size_t samples_gathered; + size_t input_buffer_max; + //message + float *W;//windowing function (time domain) + float *work_buffer, **input, **overlap_accum; + fftwf_complex *output_window; + fftwf_plan forward_plan, inverse_plan; + //size_t samplings; + + float **Xs; + float ***Hs;//thread updatable copies of the freq response filters (magintude based) + pa_aupdate **a_H; + pa_memblockq *input_q; + char *output_buffer; + size_t output_buffer_length; + size_t output_buffer_max_length; + pa_memblockq *output_q; + pa_bool_t first_iteration; + + pa_dbus_protocol *dbus_protocol; + char *dbus_path; + + pa_database *database; + char **base_profiles; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", + "sink_master", + "format", + "rate", + "channels", + "channel_map", + "autoloaded", + NULL +}; + +#define v_size 4 +#define SINKLIST "equalized_sinklist" +#define EQDB "equalizer_db" +#define EQ_STATE_DB "equalizer-state" +#define FILTER_SIZE(u) ((u)->fft_size / 2 + 1) +#define CHANNEL_PROFILE_SIZE(u) (FILTER_SIZE(u) + 1) +#define FILTER_STATE_SIZE(u) (CHANNEL_PROFILE_SIZE(u) * (u)->channels) + +static void dbus_init(struct userdata *u); +static void dbus_done(struct userdata *u); + +static void hanning_window(float *W, size_t window_size){ + /* h=.5*(1-cos(2*pi*j/(window_size+1)), COLA for R=(M+1)/2 */ + for (size_t i = 0; i < window_size; ++i) + W[i] = (float).5 * (1 - cos(2*M_PI*i / (window_size+1))); +} + +static void fix_filter(float *H, size_t fft_size){ + /* divide out the fft gain */ + for (size_t i = 0; i < fft_size / 2 + 1; ++i) + H[i] /= fft_size; +} + +static void interpolate(float *signal, size_t length, uint32_t *xs, float *ys, size_t n_points){ + /* Note that xs must be monotonically increasing! */ + float x_range_lower, x_range_upper, c0; + + pa_assert(n_points >= 2); + pa_assert(xs[0] == 0); + pa_assert(xs[n_points - 1] == length - 1); + + for (size_t x = 0, x_range_lower_i = 0; x < length-1; ++x) { + pa_assert(x_range_lower_i < n_points-1); + + x_range_lower = (float) xs[x_range_lower_i]; + x_range_upper = (float) xs[x_range_lower_i+1]; + + pa_assert_se(x_range_lower < x_range_upper); + pa_assert_se(x >= x_range_lower); + pa_assert_se(x <= x_range_upper); + + /* bilinear-interpolation of coefficients specified */ + c0 = (x-x_range_lower) / (x_range_upper-x_range_lower); + pa_assert(c0 >= 0 && c0 <= 1.0); + + signal[x] = ((1.0f - c0) * ys[x_range_lower_i] + c0 * ys[x_range_lower_i + 1]); + while(x >= xs[x_range_lower_i + 1]) + x_range_lower_i++; + } + + signal[length-1] = ys[n_points-1]; +} + +static pa_bool_t is_monotonic(const uint32_t *xs, size_t length) { + pa_assert(xs); + + if (length < 2) + return TRUE; + + for(size_t i = 1; i < length; ++i) + if (xs[i] <= xs[i-1]) + return FALSE; + + return TRUE; +} + +/* ensures memory allocated is a multiple of v_size and aligned */ +static void * alloc(size_t x, size_t s){ + size_t f; + float *t; + + f = PA_ROUND_UP(x*s, sizeof(float)*v_size); + pa_assert_se(t = fftwf_malloc(f)); + pa_memzero(t, f); + + return t; +} + +static void alloc_input_buffers(struct userdata *u, size_t min_buffer_length){ + if (min_buffer_length <= u->input_buffer_max) + return; + + pa_assert(min_buffer_length >= u->window_size); + for (size_t c = 0; c < u->channels; ++c) { + float *tmp = alloc(min_buffer_length, sizeof(float)); + if (u->input[c]) { + if (!u->first_iteration) + memcpy(tmp, u->input[c], u->overlap_size * sizeof(float)); + free(u->input[c]); + } + u->input[c] = tmp; + } + u->input_buffer_max = min_buffer_length; +} + +/* Called from I/O thread context */ +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + //size_t fs=pa_frame_size(&u->sink->sample_spec); + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->output_q) + + pa_memblockq_get_length(u->input_q), &u->sink_input->sink->sample_spec) + + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); + // pa_bytes_to_usec(u->samples_gathered * fs, &u->sink->sample_spec); + //+ pa_bytes_to_usec(u->latency * fs, ss) + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + + +/* Called from main context */ +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from I/O thread context */ +static void sink_request_rewind_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes+pa_memblockq_get_length(u->input_q), TRUE, FALSE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); +} + +#if 1 +//reference implementation +static void dsp_logic( + float * restrict dst,//used as a temp array too, needs to be fft_length! + float * restrict src,/*input data w/ overlap at start, + *automatically cycled in routine + */ + float * restrict overlap, + const float X,//multipliar + const float * restrict H,//The freq. magnitude scalers filter + const float * restrict W,//The windowing function + fftwf_complex * restrict output_window,//The transformed window'd src + struct userdata *u){ + + //use a linear-phase sliding STFT and overlap-add method (for each channel) + //window the data + for(size_t j = 0; j < u->window_size; ++j){ + dst[j] = X * W[j] * src[j]; + } + //zero padd the the remaining fft window + memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float)); + //Processing is done here! + //do fft + fftwf_execute_dft_r2c(u->forward_plan, dst, output_window); + //perform filtering + for(size_t j = 0; j < FILTER_SIZE(u); ++j){ + u->output_window[j][0] *= H[j]; + u->output_window[j][1] *= H[j]; + } + //inverse fft + fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst); + ////debug: tests overlaping add + ////and negates ALL PREVIOUS processing + ////yields a perfect reconstruction if COLA is held + //for(size_t j = 0; j < u->window_size; ++j){ + // u->work_buffer[j] = u->W[j] * u->input[c][j]; + //} + + //overlap add and preserve overlap component from this window (linear phase) + for(size_t j = 0; j < u->overlap_size; ++j){ + u->work_buffer[j] += overlap[j]; + overlap[j] = dst[u->R + j]; + } + ////debug: tests if basic buffering works + ////shouldn't modify the signal AT ALL (beyond roundoff) + //for(size_t j = 0; j < u->window_size;++j){ + // u->work_buffer[j] = u->input[c][j]; + //} + + //preseve the needed input for the next window's overlap + memmove(src, src + u->R, + (u->samples_gathered - u->R) * sizeof(float) + ); +} +#else +typedef float v4sf __attribute__ ((__aligned__(v_size * sizeof(float)))); +typedef union float_vector { + float f[v_size]; + v4sf v; + __m128 m; +} float_vector_t; + +//regardless of sse enabled, the loops in here assume +//16 byte aligned addresses and memory allocations divisible by v_size +static void dsp_logic( + float * restrict dst,//used as a temp array too, needs to be fft_length! + float * restrict src,/*input data w/ overlap at start, + *automatically cycled in routine + */ + float * restrict overlap,//The size of the overlap + const float X,//multipliar + const float * restrict H,//The freq. magnitude scalers filter + const float * restrict W,//The windowing function + fftwf_complex * restrict output_window,//The transformed window'd src + struct userdata *u){//Collection of constants + const size_t overlap_size = PA_ROUND_UP(u->overlap_size, v_size); + float_vector_t x; + x.f[0] = x.f[1] = x.f[2] = x.f[3] = X; + + //assert(u->samples_gathered >= u->R); + //use a linear-phase sliding STFT and overlap-add method + for(size_t j = 0; j < u->window_size; j += v_size){ + //dst[j] = W[j] * src[j]; + float_vector_t *d = (float_vector_t*) (dst + j); + float_vector_t *w = (float_vector_t*) (W + j); + float_vector_t *s = (float_vector_t*) (src + j); +//#if __SSE2__ + d->m = _mm_mul_ps(x.m, _mm_mul_ps(w->m, s->m)); +// d->v = x->v * w->v * s->v; +//#endif + } + //zero padd the the remaining fft window + memset(dst + u->window_size, 0, (u->fft_size - u->window_size) * sizeof(float)); + + //Processing is done here! + //do fft + fftwf_execute_dft_r2c(u->forward_plan, dst, output_window); + //perform filtering - purely magnitude based + for(size_t j = 0; j < FILTER_SIZE; j += v_size / 2){ + //output_window[j][0]*=H[j]; + //output_window[j][1]*=H[j]; + float_vector_t *d = (float_vector_t*)( ((float *) output_window) + 2 * j); + float_vector_t h; + h.f[0] = h.f[1] = H[j]; + h.f[2] = h.f[3] = H[j + 1]; +//#if __SSE2__ + d->m = _mm_mul_ps(d->m, h.m); +//#else +// d->v = d->v * h.v; +//#endif + } + + //inverse fft + fftwf_execute_dft_c2r(u->inverse_plan, output_window, dst); + + ////debug: tests overlaping add + ////and negates ALL PREVIOUS processing + ////yields a perfect reconstruction if COLA is held + //for(size_t j = 0; j < u->window_size; ++j){ + // dst[j] = W[j] * src[j]; + //} + + //overlap add and preserve overlap component from this window (linear phase) + for(size_t j = 0; j < overlap_size; j += v_size){ + //dst[j]+=overlap[j]; + //overlap[j]+=dst[j+R]; + float_vector_t *d = (float_vector_t*)(dst + j); + float_vector_t *o = (float_vector_t*)(overlap + j); +//#if __SSE2__ + d->m = _mm_add_ps(d->m, o->m); + o->m = ((float_vector_t*)(dst + u->R + j))->m; +//#else +// d->v = d->v + o->v; +// o->v = ((float_vector_t*)(dst + u->R + j))->v; +//#endif + } + //memcpy(overlap, dst+u->R, u->overlap_size * sizeof(float)); //overlap preserve (debug) + //zero out the bit beyond the real overlap so we don't add garbage next iteration + memset(overlap + u->overlap_size, 0, overlap_size - u->overlap_size); + + ////debug: tests if basic buffering works + ////shouldn't modify the signal AT ALL (beyond roundoff) + //for(size_t j = 0; j < u->window_size; ++j){ + // dst[j] = src[j]; + //} + + //preseve the needed input for the next window's overlap + memmove(src, src + u->R, + (u->samples_gathered - u->R) * sizeof(float) + ); +} +#endif + +static void flatten_to_memblockq(struct userdata *u){ + size_t mbs = pa_mempool_block_size_max(u->sink->core->mempool); + pa_memchunk tchunk; + char *dst; + size_t i = 0; + while(i < u->output_buffer_length){ + tchunk.index = 0; + tchunk.length = PA_MIN((u->output_buffer_length - i), mbs); + tchunk.memblock = pa_memblock_new(u->sink->core->mempool, tchunk.length); + //pa_log_debug("pushing %ld into the q", tchunk.length); + dst = pa_memblock_acquire(tchunk.memblock); + memcpy(dst, u->output_buffer + i, tchunk.length); + pa_memblock_release(tchunk.memblock); + pa_memblockq_push(u->output_q, &tchunk); + pa_memblock_unref(tchunk.memblock); + i += tchunk.length; + } +} + +static void process_samples(struct userdata *u){ + size_t fs = pa_frame_size(&(u->sink->sample_spec)); + unsigned a_i; + float *H, X; + size_t iterations, offset; + pa_assert(u->samples_gathered >= u->window_size); + iterations = (u->samples_gathered - u->overlap_size) / u->R; + //make sure there is enough buffer memory allocated + if(iterations * u->R * fs > u->output_buffer_max_length){ + u->output_buffer_max_length = iterations * u->R * fs; + pa_xfree(u->output_buffer); + u->output_buffer = pa_xmalloc(u->output_buffer_max_length); + } + u->output_buffer_length = iterations * u->R * fs; + + for(size_t iter = 0; iter < iterations; ++iter){ + offset = iter * u->R * fs; + for(size_t c = 0;c < u->channels; c++) { + a_i = pa_aupdate_read_begin(u->a_H[c]); + X = u->Xs[c][a_i]; + H = u->Hs[c][a_i]; + dsp_logic( + u->work_buffer, + u->input[c], + u->overlap_accum[c], + X, + H, + u->W, + u->output_window, + u + ); + pa_aupdate_read_end(u->a_H[c]); + if(u->first_iteration){ + /* The windowing function will make the audio ramped in, as a cheap fix we can + * undo the windowing (for non-zero window values) + */ + for(size_t i = 0; i < u->overlap_size; ++i){ + u->work_buffer[i] = u->W[i] <= FLT_EPSILON ? u->work_buffer[i] : u->work_buffer[i] / u->W[i]; + } + } + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, (uint8_t *) (((float *)u->output_buffer) + c) + offset, fs, u->work_buffer, sizeof(float), u->R); + } + if(u->first_iteration){ + u->first_iteration = FALSE; + } + u->samples_gathered -= u->R; + } + flatten_to_memblockq(u); +} + +static void input_buffer(struct userdata *u, pa_memchunk *in){ + size_t fs = pa_frame_size(&(u->sink->sample_spec)); + size_t samples = in->length/fs; + float *src = (float*) ((uint8_t*) pa_memblock_acquire(in->memblock) + in->index); + pa_assert(u->samples_gathered + samples <= u->input_buffer_max); + for(size_t c = 0; c < u->channels; c++) { + //buffer with an offset after the overlap from previous + //iterations + pa_assert_se( + u->input[c] + u->samples_gathered + samples <= u->input[c] + u->input_buffer_max + ); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c] + u->samples_gathered, sizeof(float), src + c, fs, samples); + } + u->samples_gathered += samples; + pa_memblock_release(in->memblock); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + size_t fs, target_samples; + size_t mbs; + //struct timeval start, end; + pa_memchunk tchunk; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(chunk); + pa_assert(u->sink); + + /* FIXME: Please clean this up. I see more commented code lines + * than uncommented code lines. I am sorry, but I am too dumb to + * understand this. */ + + fs = pa_frame_size(&(u->sink->sample_spec)); + mbs = pa_mempool_block_size_max(u->sink->core->mempool); + if(pa_memblockq_get_length(u->output_q) > 0){ + //pa_log_debug("qsize is %ld", pa_memblockq_get_length(u->output_q)); + goto END; + } + //nbytes = PA_MIN(nbytes, pa_mempool_block_size_max(u->sink->core->mempool)); + target_samples = PA_ROUND_UP(nbytes / fs, u->R); + ////pa_log_debug("vanilla mbs = %ld",mbs); + //mbs = PA_ROUND_DOWN(mbs / fs, u->R); + //mbs = PA_MAX(mbs, u->R); + //target_samples = PA_MAX(target_samples, mbs); + //pa_log_debug("target samples: %ld", target_samples); + if(u->first_iteration){ + //allocate request_size + target_samples = PA_MAX(target_samples, u->window_size); + }else{ + //allocate request_size + overlap + target_samples += u->overlap_size; + } + alloc_input_buffers(u, target_samples); + //pa_log_debug("post target samples: %ld", target_samples); + chunk->memblock = NULL; + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + //pa_log_debug("start output-buffered %ld, input-buffered %ld, requested %ld",buffered_samples,u->samples_gathered,samples_requested); + //pa_rtclock_get(&start); + do{ + size_t input_remaining = target_samples - u->samples_gathered; + // pa_log_debug("input remaining %ld samples", input_remaining); + pa_assert(input_remaining > 0); + while (pa_memblockq_peek(u->input_q, &tchunk) < 0) { + //pa_sink_render(u->sink, input_remaining * fs, &tchunk); + pa_sink_render_full(u->sink, PA_MIN(input_remaining * fs, mbs), &tchunk); + pa_memblockq_push(u->input_q, &tchunk); + pa_memblock_unref(tchunk.memblock); + } + pa_assert(tchunk.memblock); + + tchunk.length = PA_MIN(input_remaining * fs, tchunk.length); + + pa_memblockq_drop(u->input_q, tchunk.length); + //pa_log_debug("asked for %ld input samples, got %ld samples",input_remaining,buffer->length/fs); + /* copy new input */ + //pa_rtclock_get(start); + // pa_log_debug("buffering %ld bytes", tchunk.length); + input_buffer(u, &tchunk); + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.5f seconds to setup", pa_timeval_diff(end, start) / (double) PA_USEC_PER_SEC); + pa_memblock_unref(tchunk.memblock); + } while(u->samples_gathered < target_samples); + + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.6f seconds to get data", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC); + + pa_assert(u->fft_size >= u->window_size); + pa_assert(u->R < u->window_size); + //pa_rtclock_get(&start); + /* process a block */ + process_samples(u); + //pa_rtclock_get(&end); + //pa_log_debug("Took %0.6f seconds to process", (double) pa_timeval_diff(&end, &start) / PA_USEC_PER_SEC); +END: + pa_assert_se(pa_memblockq_peek(u->output_q, chunk) >= 0); + pa_assert(chunk->memblock); + pa_memblockq_drop(u->output_q, chunk->length); + + /** FIXME: Uh? you need to unref the chunk here! */ + + //pa_log_debug("gave %ld", chunk->length/fs); + //pa_log_debug("end pop"); + return 0; +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + +static void reset_filter(struct userdata *u){ + size_t fs = pa_frame_size(&u->sink->sample_spec); + size_t max_request; + + u->samples_gathered = 0; + + for(size_t i = 0; i < u->channels; ++i) + pa_memzero(u->overlap_accum[i], u->overlap_size * sizeof(float)); + + u->first_iteration = TRUE; + //set buffer size to max request, no overlap copy + max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs , u->R); + max_request = PA_MAX(max_request, u->window_size); + pa_sink_set_max_request_within_thread(u->sink, max_request * fs); +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + size_t amount = 0; + + pa_log_debug("Rewind callback!"); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t max_rewrite; + + //max_rewrite = nbytes; + max_rewrite = nbytes + pa_memblockq_get_length(u->input_q); + //PA_MIN(pa_memblockq_get_length(u->input_q), nbytes); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); + u->sink->thread_info.rewind_nbytes = 0; + + if (amount > 0) { + //invalidate the output q + pa_memblockq_seek(u->input_q, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE); + pa_log("Resetting filter"); + //reset_filter(u); //this is the "proper" thing to do... + } + } + + pa_sink_process_rewind(u->sink, amount); + pa_memblockq_rewind(u->input_q, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->input_q, nbytes); + pa_sink_set_max_rewind_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + size_t fs; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + fs = pa_frame_size(&u->sink_input->sample_spec); + pa_sink_set_max_request_within_thread(u->sink, PA_ROUND_UP(nbytes / fs, u->R) * fs); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); + + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + size_t fs, max_request; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + + fs = pa_frame_size(&u->sink_input->sample_spec); + /* set buffer size to max request, no overlap copy */ + max_request = PA_ROUND_UP(pa_sink_input_get_max_request(u->sink_input) / fs, u->R); + max_request = PA_MAX(max_request, u->window_size); + + pa_sink_set_max_request_within_thread(u->sink, max_request * fs); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ + pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + } +} + +static void pack(char **strs, size_t len, char **packed, size_t *length){ + size_t t_len = 0; + size_t headers = (1+len) * sizeof(uint16_t); + char *p; + for(size_t i = 0; i < len; ++i){ + t_len += strlen(strs[i]); + } + *length = headers + t_len; + p = *packed = pa_xmalloc0(*length); + *((uint16_t *) p) = (uint16_t) len; + p += sizeof(uint16_t); + for(size_t i = 0; i < len; ++i){ + uint16_t l = strlen(strs[i]); + *((uint16_t *) p) = (uint16_t) l; + p += sizeof(uint16_t); + memcpy(p, strs[i], l); + p += l; + } +} +static void unpack(char *str, size_t length, char ***strs, size_t *len){ + char *p = str; + *len = *((uint16_t *) p); + p += sizeof(uint16_t); + *strs = pa_xnew(char *, *len); + + for(size_t i = 0; i < *len; ++i){ + size_t l = *((uint16_t *) p); + p += sizeof(uint16_t); + (*strs)[i] = pa_xnew(char, l + 1); + memcpy((*strs)[i], p, l); + (*strs)[i][l] = '\0'; + p += l; + } +} +static void save_profile(struct userdata *u, size_t channel, char *name){ + unsigned a_i; + const size_t profile_size = CHANNEL_PROFILE_SIZE(u) * sizeof(float); + float *H_n, *profile; + const float *H; + pa_datum key, data; + profile = pa_xnew0(float, profile_size); + a_i = pa_aupdate_read_begin(u->a_H[channel]); + profile[0] = u->Xs[a_i][channel]; + H = u->Hs[channel][a_i]; + H_n = profile + 1; + for(size_t i = 0 ; i <= FILTER_SIZE(u); ++i){ + H_n[i] = H[i] * u->fft_size; + //H_n[i] = H[i]; + } + pa_aupdate_read_end(u->a_H[channel]); + key.data=name; + key.size = strlen(key.data); + data.data = profile; + data.size = profile_size; + pa_database_set(u->database, &key, &data, TRUE); + pa_database_sync(u->database); + if(u->base_profiles[channel]){ + pa_xfree(u->base_profiles[channel]); + } + u->base_profiles[channel] = pa_xstrdup(name); +} + +static void save_state(struct userdata *u){ + unsigned a_i; + const size_t filter_state_size = FILTER_STATE_SIZE(u) * sizeof(float); + float *H_n, *state; + float *H; + pa_datum key, data; + pa_database *database; + char *dbname; + char *packed; + size_t packed_length; + + pack(u->base_profiles, u->channels, &packed, &packed_length); + state = (float *) pa_xmalloc0(filter_state_size + packed_length); + memcpy(state + FILTER_STATE_SIZE(u), packed, packed_length); + pa_xfree(packed); + + for(size_t c = 0; c < u->channels; ++c){ + a_i = pa_aupdate_read_begin(u->a_H[c]); + state[c * CHANNEL_PROFILE_SIZE(u)] = u->Xs[c][a_i]; + H = u->Hs[c][a_i]; + H_n = &state[c * CHANNEL_PROFILE_SIZE(u) + 1]; + memcpy(H_n, H, FILTER_SIZE(u) * sizeof(float)); + pa_aupdate_read_end(u->a_H[c]); + } + + key.data = u->sink->name; + key.size = strlen(key.data); + data.data = state; + data.size = filter_state_size + packed_length; + //thread safety for 0.9.17? + pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE)); + pa_assert_se(database = pa_database_open(dbname, TRUE)); + pa_xfree(dbname); + + pa_database_set(database, &key, &data, TRUE); + pa_database_sync(database); + pa_database_close(database); + pa_xfree(state); +} + +static void remove_profile(pa_core *c, char *name){ + pa_datum key; + pa_database *database; + key.data = name; + key.size = strlen(key.data); + pa_assert_se(database = pa_shared_get(c, EQDB)); + pa_database_unset(database, &key); + pa_database_sync(database); +} + +static const char* load_profile(struct userdata *u, size_t channel, char *name){ + unsigned a_i; + pa_datum key, value; + const size_t profile_size = CHANNEL_PROFILE_SIZE(u) * sizeof(float); + key.data = name; + key.size = strlen(key.data); + if(pa_database_get(u->database, &key, &value) != NULL){ + if(value.size == profile_size){ + float *profile = (float *) value.data; + a_i = pa_aupdate_write_begin(u->a_H[channel]); + u->Xs[channel][a_i] = profile[0]; + memcpy(u->Hs[channel][a_i], profile + 1, FILTER_SIZE(u) * sizeof(float)); + fix_filter(u->Hs[channel][a_i], u->fft_size); + pa_aupdate_write_end(u->a_H[channel]); + pa_xfree(u->base_profiles[channel]); + u->base_profiles[channel] = pa_xstrdup(name); + }else{ + return "incompatible size"; + } + pa_datum_free(&value); + }else{ + return "profile doesn't exist"; + } + return NULL; +} + +static void load_state(struct userdata *u){ + unsigned a_i; + float *H; + pa_datum key, value; + pa_database *database; + char *dbname; + pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, FALSE)); + database = pa_database_open(dbname, FALSE); + pa_xfree(dbname); + if(!database){ + pa_log("No resume state"); + return; + } + + key.data = u->sink->name; + key.size = strlen(key.data); + + if(pa_database_get(database, &key, &value) != NULL){ + if(value.size > FILTER_STATE_SIZE(u) * sizeof(float) + sizeof(uint16_t)){ + float *state = (float *) value.data; + size_t n_profs; + char **names; + for(size_t c = 0; c < u->channels; ++c){ + a_i = pa_aupdate_write_begin(u->a_H[c]); + H = state + c * CHANNEL_PROFILE_SIZE(u) + 1; + u->Xs[c][a_i] = state[c * CHANNEL_PROFILE_SIZE(u)]; + memcpy(u->Hs[c][a_i], H, FILTER_SIZE(u) * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + unpack(((char *)value.data) + FILTER_STATE_SIZE(u) * sizeof(float), value.size - FILTER_STATE_SIZE(u) * sizeof(float), &names, &n_profs); + n_profs = PA_MIN(n_profs, u->channels); + for(size_t c = 0; c < n_profs; ++c){ + pa_xfree(u->base_profiles[c]); + u->base_profiles[c] = names[c]; + } + pa_xfree(names); + } + pa_datum_free(&value); + }else{ + pa_log("resume state exists but is wrong size!"); + } + pa_database_close(database); +} + +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + const char *z; + pa_sink *master; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + size_t fs, i; + unsigned c; + float *H; + unsigned a_i; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink_master", NULL), PA_NAMEREG_SINK))) { + pa_log("Master sink not found"); + goto fail; + } + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + fs = pa_frame_size(&ss); + + u = pa_xnew0(struct userdata, 1); + u->module = m; + m->userdata = u; + + u->channels = ss.channels; + u->fft_size = pow(2, ceil(log(ss.rate) / log(2)));//probably unstable near corner cases of powers of 2 + pa_log_debug("fft size: %ld", u->fft_size); + u->window_size = 15999; + if (u->window_size % 2 == 0) + u->window_size--; + u->R = (u->window_size + 1) / 2; + u->overlap_size = u->window_size - u->R; + u->samples_gathered = 0; + u->input_buffer_max = 0; + + u->a_H = pa_xnew0(pa_aupdate *, u->channels); + u->Xs = pa_xnew0(float *, u->channels); + u->Hs = pa_xnew0(float **, u->channels); + + for (c = 0; c < u->channels; ++c) { + u->Xs[c] = pa_xnew0(float, 2); + u->Hs[c] = pa_xnew0(float *, 2); + for (i = 0; i < 2; ++i) + u->Hs[c][i] = alloc(FILTER_SIZE(u), sizeof(float)); + } + + u->W = alloc(u->window_size, sizeof(float)); + u->work_buffer = alloc(u->fft_size, sizeof(float)); + u->input = pa_xnew0(float *, u->channels); + u->overlap_accum = pa_xnew0(float *, u->channels); + for (c = 0; c < u->channels; ++c) { + u->a_H[c] = pa_aupdate_new(); + u->input[c] = NULL; + u->overlap_accum[c] = alloc(u->overlap_size, sizeof(float)); + } + u->output_window = alloc(FILTER_SIZE(u), sizeof(fftwf_complex)); + u->forward_plan = fftwf_plan_dft_r2c_1d(u->fft_size, u->work_buffer, u->output_window, FFTW_ESTIMATE); + u->inverse_plan = fftwf_plan_dft_c2r_1d(u->fft_size, u->output_window, u->work_buffer, FFTW_ESTIMATE); + + hanning_window(u->W, u->window_size); + u->first_iteration = TRUE; + + u->base_profiles = pa_xnew0(char *, u->channels); + for (c = 0; c < u->channels; ++c) + u->base_profiles[c] = pa_xstrdup("default"); + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.equalizer", master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "FFT based equalizer on %s", z ? z : master->name); + + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + + u->autoloaded = DEFAULT_AUTOLOADED; + if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) { + pa_log("Failed to parse autoloaded value"); + goto fail; + } + + u->sink = pa_sink_new(m->core, &sink_data, + PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| + (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->userdata = u; + + u->input_q = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, fs, 1, 1, 0, &u->sink->silence); + u->output_q = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, fs, 1, 1, 0, NULL); + u->output_buffer = NULL; + u->output_buffer_length = 0; + u->output_buffer_max_length = 0; + + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + //pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->R*fs, &ss)); + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE); + sink_input_data.origin_sink = u->sink; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Equalized Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; + u->sink_input->userdata = u; + + u->sink->input_to_master = u->sink_input; + + dbus_init(u); + + /* default filter to these */ + for (c = 0; c< u->channels; ++c) { + a_i = pa_aupdate_write_begin(u->a_H[c]); + H = u->Hs[c][a_i]; + u->Xs[c][a_i] = 1.0f; + + for(i = 0; i < FILTER_SIZE(u); ++i) + H[i] = 1.0 / sqrtf(2.0f); + + fix_filter(H, u->fft_size); + pa_aupdate_write_end(u->a_H[c]); + } + + /* load old parameters */ + load_state(u); + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + unsigned c; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + save_state(u); + + dbus_done(u); + + for(c = 0; c < u->channels; ++c) + pa_xfree(u->base_profiles[c]); + pa_xfree(u->base_profiles); + + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ + + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) + pa_sink_input_unref(u->sink_input); + + if (u->sink) + pa_sink_unref(u->sink); + + pa_xfree(u->output_buffer); + pa_memblockq_free(u->output_q); + pa_memblockq_free(u->input_q); + + fftwf_destroy_plan(u->inverse_plan); + fftwf_destroy_plan(u->forward_plan); + pa_xfree(u->output_window); + for (c = 0; c < u->channels; ++c) { + pa_aupdate_free(u->a_H[c]); + pa_xfree(u->overlap_accum[c]); + pa_xfree(u->input[c]); + } + pa_xfree(u->a_H); + pa_xfree(u->overlap_accum); + pa_xfree(u->input); + pa_xfree(u->work_buffer); + pa_xfree(u->W); + for (c = 0; c < u->channels; ++c) { + pa_xfree(u->Xs[c]); + for (size_t i = 0; i < 2; ++i) + pa_xfree(u->Hs[c][i]); + pa_xfree(u->Hs[c]); + } + pa_xfree(u->Xs); + pa_xfree(u->Hs); + + pa_xfree(u); +} + +/* + * DBus Routines and Callbacks + */ +#define EXTNAME "org.PulseAudio.Ext.Equalizing1" +#define MANAGER_PATH "/org/pulseaudio/equalizing1" +#define MANAGER_IFACE EXTNAME ".Manager" +#define EQUALIZER_IFACE EXTNAME ".Equalizer" +static void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u); +static void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u); +static void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u); +enum manager_method_index { + MANAGER_METHOD_REMOVE_PROFILE, + MANAGER_METHOD_MAX +}; + +pa_dbus_arg_info remove_profile_args[]={ + {"name", "s","in"}, +}; + +static pa_dbus_method_handler manager_methods[MANAGER_METHOD_MAX]={ + [MANAGER_METHOD_REMOVE_PROFILE]{ + .method_name="RemoveProfile", + .arguments=remove_profile_args, + .n_arguments=sizeof(remove_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=manager_handle_remove_profile} +}; + +enum manager_handler_index { + MANAGER_HANDLER_REVISION, + MANAGER_HANDLER_EQUALIZED_SINKS, + MANAGER_HANDLER_PROFILES, + MANAGER_HANDLER_MAX +}; + +static pa_dbus_property_handler manager_handlers[MANAGER_HANDLER_MAX]={ + [MANAGER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=manager_get_revision,.set_cb=NULL}, + [MANAGER_HANDLER_EQUALIZED_SINKS]={.property_name="EqualizedSinks",.type="ao",.get_cb=manager_get_sinks,.set_cb=NULL}, + [MANAGER_HANDLER_PROFILES]={.property_name="Profiles",.type="as",.get_cb=manager_get_profiles,.set_cb=NULL} +}; + +pa_dbus_arg_info sink_args[]={ + {"sink", "o", NULL} +}; + +enum manager_signal_index{ + MANAGER_SIGNAL_SINK_ADDED, + MANAGER_SIGNAL_SINK_REMOVED, + MANAGER_SIGNAL_PROFILES_CHANGED, + MANAGER_SIGNAL_MAX +}; + +static pa_dbus_signal_info manager_signals[MANAGER_SIGNAL_MAX]={ + [MANAGER_SIGNAL_SINK_ADDED]={.name="SinkAdded", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)}, + [MANAGER_SIGNAL_SINK_REMOVED]={.name="SinkRemoved", .arguments=sink_args, .n_arguments=sizeof(sink_args)/sizeof(pa_dbus_arg_info)}, + [MANAGER_SIGNAL_PROFILES_CHANGED]={.name="ProfilesChanged", .arguments=NULL, .n_arguments=0} +}; + +static pa_dbus_interface_info manager_info={ + .name=MANAGER_IFACE, + .method_handlers=manager_methods, + .n_method_handlers=MANAGER_METHOD_MAX, + .property_handlers=manager_handlers, + .n_property_handlers=MANAGER_HANDLER_MAX, + .get_all_properties_cb=manager_get_all, + .signals=manager_signals, + .n_signals=MANAGER_SIGNAL_MAX +}; + +enum equalizer_method_index { + EQUALIZER_METHOD_FILTER_POINTS, + EQUALIZER_METHOD_SEED_FILTER, + EQUALIZER_METHOD_SAVE_PROFILE, + EQUALIZER_METHOD_LOAD_PROFILE, + EQUALIZER_METHOD_SET_FILTER, + EQUALIZER_METHOD_GET_FILTER, + EQUALIZER_METHOD_SAVE_STATE, + EQUALIZER_METHOD_GET_PROFILE_NAME, + EQUALIZER_METHOD_MAX +}; + +enum equalizer_handler_index { + EQUALIZER_HANDLER_REVISION, + EQUALIZER_HANDLER_SAMPLERATE, + EQUALIZER_HANDLER_FILTERSAMPLERATE, + EQUALIZER_HANDLER_N_COEFS, + EQUALIZER_HANDLER_N_CHANNELS, + EQUALIZER_HANDLER_MAX +}; + +pa_dbus_arg_info filter_points_args[]={ + {"channel", "u","in"}, + {"xs", "au","in"}, + {"ys", "ad","out"}, + {"preamp", "d","out"} +}; +pa_dbus_arg_info seed_filter_args[]={ + {"channel", "u","in"}, + {"xs", "au","in"}, + {"ys", "ad","in"}, + {"preamp", "d","in"} +}; + +pa_dbus_arg_info set_filter_args[]={ + {"channel", "u","in"}, + {"ys", "ad","in"}, + {"preamp", "d","in"} +}; +pa_dbus_arg_info get_filter_args[]={ + {"channel", "u","in"}, + {"ys", "ad","out"}, + {"preamp", "d","out"} +}; + +pa_dbus_arg_info save_profile_args[]={ + {"channel", "u","in"}, + {"name", "s","in"} +}; +pa_dbus_arg_info load_profile_args[]={ + {"channel", "u","in"}, + {"name", "s","in"} +}; +pa_dbus_arg_info base_profile_name_args[]={ + {"channel", "u","in"}, + {"name", "s","out"} +}; + +static pa_dbus_method_handler equalizer_methods[EQUALIZER_METHOD_MAX]={ + [EQUALIZER_METHOD_SEED_FILTER]{ + .method_name="SeedFilter", + .arguments=seed_filter_args, + .n_arguments=sizeof(seed_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_seed_filter}, + [EQUALIZER_METHOD_FILTER_POINTS]{ + .method_name="FilterAtPoints", + .arguments=filter_points_args, + .n_arguments=sizeof(filter_points_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_filter_points}, + [EQUALIZER_METHOD_SET_FILTER]{ + .method_name="SetFilter", + .arguments=set_filter_args, + .n_arguments=sizeof(set_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_set_filter}, + [EQUALIZER_METHOD_GET_FILTER]{ + .method_name="GetFilter", + .arguments=get_filter_args, + .n_arguments=sizeof(get_filter_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_filter}, + [EQUALIZER_METHOD_SAVE_PROFILE]{ + .method_name="SaveProfile", + .arguments=save_profile_args, + .n_arguments=sizeof(save_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_save_profile}, + [EQUALIZER_METHOD_LOAD_PROFILE]{ + .method_name="LoadProfile", + .arguments=load_profile_args, + .n_arguments=sizeof(load_profile_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_load_profile}, + [EQUALIZER_METHOD_SAVE_STATE]{ + .method_name="SaveState", + .arguments=NULL, + .n_arguments=0, + .receive_cb=equalizer_handle_save_state}, + [EQUALIZER_METHOD_GET_PROFILE_NAME]{ + .method_name="BaseProfile", + .arguments=base_profile_name_args, + .n_arguments=sizeof(base_profile_name_args)/sizeof(pa_dbus_arg_info), + .receive_cb=equalizer_handle_get_profile_name} +}; + +static pa_dbus_property_handler equalizer_handlers[EQUALIZER_HANDLER_MAX]={ + [EQUALIZER_HANDLER_REVISION]={.property_name="InterfaceRevision",.type="u",.get_cb=equalizer_get_revision,.set_cb=NULL}, + [EQUALIZER_HANDLER_SAMPLERATE]{.property_name="SampleRate",.type="u",.get_cb=equalizer_get_sample_rate,.set_cb=NULL}, + [EQUALIZER_HANDLER_FILTERSAMPLERATE]{.property_name="FilterSampleRate",.type="u",.get_cb=equalizer_get_filter_rate,.set_cb=NULL}, + [EQUALIZER_HANDLER_N_COEFS]{.property_name="NFilterCoefficients",.type="u",.get_cb=equalizer_get_n_coefs,.set_cb=NULL}, + [EQUALIZER_HANDLER_N_CHANNELS]{.property_name="NChannels",.type="u",.get_cb=equalizer_get_n_channels,.set_cb=NULL}, +}; + +enum equalizer_signal_index{ + EQUALIZER_SIGNAL_FILTER_CHANGED, + EQUALIZER_SIGNAL_SINK_RECONFIGURED, + EQUALIZER_SIGNAL_MAX +}; + +static pa_dbus_signal_info equalizer_signals[EQUALIZER_SIGNAL_MAX]={ + [EQUALIZER_SIGNAL_FILTER_CHANGED]={.name="FilterChanged", .arguments=NULL, .n_arguments=0}, + [EQUALIZER_SIGNAL_SINK_RECONFIGURED]={.name="SinkReconfigured", .arguments=NULL, .n_arguments=0}, +}; + +static pa_dbus_interface_info equalizer_info={ + .name=EQUALIZER_IFACE, + .method_handlers=equalizer_methods, + .n_method_handlers=EQUALIZER_METHOD_MAX, + .property_handlers=equalizer_handlers, + .n_property_handlers=EQUALIZER_HANDLER_MAX, + .get_all_properties_cb=equalizer_get_all, + .signals=equalizer_signals, + .n_signals=EQUALIZER_SIGNAL_MAX +}; + +void dbus_init(struct userdata *u){ + uint32_t dummy; + DBusMessage *signal = NULL; + pa_idxset *sink_list = NULL; + u->dbus_protocol=pa_dbus_protocol_get(u->sink->core); + u->dbus_path=pa_sprintf_malloc("/org/pulseaudio/core1/sink%d", u->sink->index); + + pa_dbus_protocol_add_interface(u->dbus_protocol, u->dbus_path, &equalizer_info, u); + sink_list = pa_shared_get(u->sink->core, SINKLIST); + u->database = pa_shared_get(u->sink->core, EQDB); + if(sink_list == NULL){ + char *dbname; + sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func); + pa_shared_set(u->sink->core, SINKLIST, sink_list); + pa_assert_se(dbname = pa_state_path("equalizer-presets", FALSE)); + pa_assert_se(u->database = pa_database_open(dbname, TRUE)); + pa_xfree(dbname); + pa_shared_set(u->sink->core, EQDB, u->database); + pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core); + pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME); + } + pa_idxset_put(sink_list, u, &dummy); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_ADDED].name))); + dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void dbus_done(struct userdata *u){ + pa_idxset *sink_list; + uint32_t dummy; + + DBusMessage *signal = NULL; + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_SINK_REMOVED].name))); + dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &u->dbus_path, DBUS_TYPE_INVALID); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); + + pa_assert_se(sink_list=pa_shared_get(u->sink->core,SINKLIST)); + pa_idxset_remove_by_data(sink_list,u,&dummy); + if(pa_idxset_size(sink_list)==0){ + pa_dbus_protocol_unregister_extension(u->dbus_protocol, EXTNAME); + pa_dbus_protocol_remove_interface(u->dbus_protocol, MANAGER_PATH, manager_info.name); + pa_shared_remove(u->sink->core, EQDB); + pa_database_close(u->database); + pa_shared_remove(u->sink->core, SINKLIST); + pa_xfree(sink_list); + } + pa_dbus_protocol_remove_interface(u->dbus_protocol, u->dbus_path, equalizer_info.name); + pa_xfree(u->dbus_path); + pa_dbus_protocol_unref(u->dbus_protocol); +} + +void manager_handle_remove_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + DBusError error; + pa_core *c = (pa_core *)_u; + DBusMessage *signal = NULL; + pa_dbus_protocol *dbus_protocol; + char *name; + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + remove_profile(c,name); + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name))); + dbus_protocol = pa_dbus_protocol_get(c); + pa_dbus_protocol_send_signal(dbus_protocol, signal); + pa_dbus_protocol_unref(dbus_protocol); + dbus_message_unref(signal); +} + +void manager_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){ + uint32_t rev=1; + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev); +} + +static void get_sinks(pa_core *u, char ***names, unsigned *n_sinks){ + void *iter = NULL; + struct userdata *sink_u = NULL; + uint32_t dummy; + pa_idxset *sink_list; + pa_assert(u); + pa_assert(names); + pa_assert(n_sinks); + + pa_assert_se(sink_list = pa_shared_get(u, SINKLIST)); + *n_sinks = (unsigned) pa_idxset_size(sink_list); + *names = *n_sinks > 0 ? pa_xnew0(char *,*n_sinks) : NULL; + for(uint32_t i = 0; i < *n_sinks; ++i){ + sink_u = (struct userdata *) pa_idxset_iterate(sink_list, &iter, &dummy); + (*names)[i] = pa_xstrdup(sink_u->dbus_path); + } +} + +void manager_get_sinks(DBusConnection *conn, DBusMessage *msg, void *_u){ + unsigned n; + char **names = NULL; + pa_assert(conn); + pa_assert(msg); + pa_assert(_u); + + get_sinks((pa_core *) _u, &names, &n); + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); +} + +static void get_profiles(pa_core *c, char ***names, unsigned *n){ + char *name; + pa_database *database; + pa_datum key, next_key; + pa_strlist *head=NULL, *iter; + pa_bool_t done; + pa_assert_se(database = pa_shared_get(c, EQDB)); + + pa_assert(c); + pa_assert(names); + pa_assert(n); + done = !pa_database_first(database, &key, NULL); + *n = 0; + while(!done){ + done = !pa_database_next(database, &key, &next_key, NULL); + name=pa_xmalloc(key.size + 1); + memcpy(name, key.data, key.size); + name[key.size] = '\0'; + pa_datum_free(&key); + head = pa_strlist_prepend(head, name); + pa_xfree(name); + key = next_key; + (*n)++; + } + (*names) = *n > 0 ? pa_xnew0(char *, *n) : NULL; + iter=head; + for(unsigned i = 0; i < *n; ++i){ + (*names)[*n - 1 - i] = pa_xstrdup(pa_strlist_data(iter)); + iter = pa_strlist_next(iter); + } + pa_strlist_free(head); +} + +void manager_get_profiles(DBusConnection *conn, DBusMessage *msg, void *_u){ + char **names; + unsigned n; + pa_assert(conn); + pa_assert(msg); + pa_assert(_u); + + get_profiles((pa_core *)_u, &names, &n); + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); +} + +void manager_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){ + pa_core *c; + char **names = NULL; + unsigned n; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, dict_iter; + uint32_t rev; + pa_assert(conn); + pa_assert(msg); + pa_assert_se(c = _u); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + rev = 1; + pa_dbus_append_basic_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev); + + get_sinks(c, &names, &n); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter,manager_handlers[MANAGER_HANDLER_EQUALIZED_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); + + get_profiles(c, &names, &n); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, manager_handlers[MANAGER_HANDLER_PROFILES].property_name, DBUS_TYPE_STRING, names, n); + for(unsigned i = 0; i < n; ++i){ + pa_xfree(names[i]); + } + pa_xfree(names); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +void equalizer_handle_seed_filter(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = _u; + DBusError error; + DBusMessage *signal = NULL; + float *ys; + uint32_t *xs, channel, r_channel; + double *_ys, preamp; + unsigned x_npoints, y_npoints, a_i; + float *H; + pa_bool_t points_good = TRUE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints, + DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &_ys, &y_npoints, + DBUS_TYPE_DOUBLE, &preamp, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + for(size_t i = 0; i < x_npoints; ++i){ + if(xs[i] >= FILTER_SIZE(u)){ + points_good = FALSE; + break; + } + } + if(!is_monotonic(xs, x_npoints) || !points_good){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs must be monotonic and 0<=x<=%ld", u->fft_size / 2); + dbus_error_free(&error); + return; + }else if(x_npoints != y_npoints || x_npoints < 2 || x_npoints > FILTER_SIZE(u)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs and ys must be the same length and 2<=l<=%ld!", FILTER_SIZE(u)); + dbus_error_free(&error); + return; + }else if(xs[0] != 0 || xs[x_npoints - 1] != u->fft_size / 2){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs[0] must be 0 and xs[-1]=fft_size/2"); + dbus_error_free(&error); + return; + } + + ys = pa_xmalloc(x_npoints * sizeof(float)); + for(uint32_t i = 0; i < x_npoints; ++i){ + ys[i] = (float) _ys[i]; + } + r_channel = channel == u->channels ? 0 : channel; + a_i = pa_aupdate_write_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + u->Xs[r_channel][a_i] = preamp; + interpolate(H, FILTER_SIZE(u), xs, ys, x_npoints); + fix_filter(H, u->fft_size); + if(channel == u->channels){ + for(size_t c = 1; c < u->channels; ++c){ + unsigned b_i = pa_aupdate_write_begin(u->a_H[c]); + float *H_p = u->Hs[c][b_i]; + u->Xs[c][b_i] = preamp; + memcpy(H_p, H, FILTER_SIZE(u) * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + } + pa_aupdate_write_end(u->a_H[r_channel]); + pa_xfree(ys); + + + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_get_filter_points(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + uint32_t *xs, channel, r_channel; + double *ys, preamp; + unsigned x_npoints, a_i; + float *H; + pa_bool_t points_good=TRUE; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusError error; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &xs, &x_npoints, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + + for(size_t i = 0; i < x_npoints; ++i){ + if(xs[i] >= FILTER_SIZE(u)){ + points_good=FALSE; + break; + } + } + + if(x_npoints > FILTER_SIZE(u) || !points_good){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "xs indices/length must be <= %ld!", FILTER_SIZE(u)); + dbus_error_free(&error); + return; + } + + r_channel = channel == u->channels ? 0 : channel; + ys = pa_xmalloc(x_npoints * sizeof(double)); + a_i = pa_aupdate_read_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + preamp = u->Xs[r_channel][a_i]; + for(uint32_t i = 0; i < x_npoints; ++i){ + ys[i] = H[xs[i]] * u->fft_size; + } + pa_aupdate_read_end(u->a_H[r_channel]); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + + pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, ys, x_npoints); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + pa_xfree(ys); +} + +static void get_filter(struct userdata *u, size_t channel, double **H_, double *preamp){ + float *H; + unsigned a_i; + size_t r_channel = channel == u->channels ? 0 : channel; + *H_ = pa_xnew0(double, FILTER_SIZE(u)); + a_i = pa_aupdate_read_begin(u->a_H[r_channel]); + H = u->Hs[r_channel][a_i]; + for(size_t i = 0;i < FILTER_SIZE(u); ++i){ + (*H_)[i] = H[i] * u->fft_size; + } + *preamp = u->Xs[r_channel][a_i]; + + pa_aupdate_read_end(u->a_H[r_channel]); +} + +void equalizer_handle_get_filter(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + unsigned n_coefs; + uint32_t channel; + double *H_, preamp; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusError error; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + + n_coefs = CHANNEL_PROFILE_SIZE(u); + pa_assert(conn); + pa_assert(msg); + get_filter(u, channel, &H_, &preamp); + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + + pa_dbus_append_basic_array(&msg_iter, DBUS_TYPE_DOUBLE, H_, n_coefs); + pa_dbus_append_basic_variant(&msg_iter, DBUS_TYPE_DOUBLE, &preamp); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); + pa_xfree(H_); +} + +static void set_filter(struct userdata *u, size_t channel, double *H_, double preamp){ + unsigned a_i; + size_t r_channel = channel == u->channels ? 0 : channel; + float *H; + //all channels + a_i = pa_aupdate_write_begin(u->a_H[r_channel]); + u->Xs[r_channel][a_i] = (float) preamp; + H = u->Hs[r_channel][a_i]; + for(size_t i = 0; i < FILTER_SIZE(u); ++i){ + H[i] = (float) H_[i]; + } + fix_filter(H, u->fft_size); + if(channel == u->channels){ + for(size_t c = 1; c < u->channels; ++c){ + unsigned b_i = pa_aupdate_write_begin(u->a_H[c]); + u->Xs[c][b_i] = u->Xs[r_channel][a_i]; + memcpy(u->Hs[c][b_i], u->Hs[r_channel][a_i], FILTER_SIZE(u) * sizeof(float)); + pa_aupdate_write_end(u->a_H[c]); + } + } + pa_aupdate_write_end(u->a_H[r_channel]); +} + +void equalizer_handle_set_filter(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + double *H, preamp; + uint32_t channel; + unsigned _n_coefs; + DBusMessage *signal = NULL; + DBusError error; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + dbus_error_init(&error); + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &H, &_n_coefs, + DBUS_TYPE_DOUBLE, &preamp, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + if(_n_coefs != FILTER_SIZE(u)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "This filter takes exactly %ld coefficients, you gave %d", FILTER_SIZE(u), _n_coefs); + return; + } + set_filter(u, channel, H, preamp); + + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_save_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + char *name; + uint32_t channel, r_channel; + DBusMessage *signal = NULL; + DBusError error; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + save_profile(u, r_channel, name); + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(MANAGER_PATH, MANAGER_IFACE, manager_signals[MANAGER_SIGNAL_PROFILES_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_load_profile(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + char *name; + DBusError error; + uint32_t channel, r_channel; + const char *err_msg = NULL; + DBusMessage *signal = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + + err_msg = load_profile(u, r_channel, name); + if(err_msg != NULL){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "error loading profile %s: %s", name, err_msg); + dbus_error_free(&error); + return; + } + if(channel == u->channels){ + for(uint32_t c = 1; c < u->channels; ++c){ + load_profile(u, c, name); + } + } + pa_dbus_send_empty_reply(conn, msg); + + pa_assert_se((signal = dbus_message_new_signal(u->dbus_path, EQUALIZER_IFACE, equalizer_signals[EQUALIZER_SIGNAL_FILTER_CHANGED].name))); + pa_dbus_protocol_send_signal(u->dbus_protocol, signal); + dbus_message_unref(signal); +} + +void equalizer_handle_save_state(DBusConnection *conn, DBusMessage *msg, void *_u) { + struct userdata *u = (struct userdata *) _u; + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + save_state(u); + pa_dbus_send_empty_reply(conn, msg); +} + +void equalizer_handle_get_profile_name(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u = (struct userdata *) _u; + DBusError error; + uint32_t channel, r_channel; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + dbus_error_init(&error); + + if(!dbus_message_get_args(msg, &error, + DBUS_TYPE_UINT32, &channel, + DBUS_TYPE_INVALID)){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "%s", error.message); + dbus_error_free(&error); + return; + } + if(channel > u->channels){ + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "invalid channel: %d", channel); + dbus_error_free(&error); + return; + } + r_channel = channel == u->channels ? 0 : channel; + pa_assert(u->base_profiles[r_channel]); + pa_dbus_send_basic_value_reply(conn,msg, DBUS_TYPE_STRING, &u->base_profiles[r_channel]); +} + +void equalizer_get_revision(DBusConnection *conn, DBusMessage *msg, void *_u){ + uint32_t rev=1; + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_UINT32, &rev); +} + +void equalizer_get_n_channels(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t channels; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + channels = (uint32_t) u->channels; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &channels); +} + +void equalizer_get_n_coefs(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t n_coefs; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE(u); + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &n_coefs); +} + +void equalizer_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t rate; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + rate = (uint32_t) u->sink->sample_spec.rate; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &rate); +} + +void equalizer_get_filter_rate(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + uint32_t fft_size; + pa_assert_se(u = (struct userdata *) _u); + pa_assert(conn); + pa_assert(msg); + + fft_size = (uint32_t) u->fft_size; + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &fft_size); +} + +void equalizer_get_all(DBusConnection *conn, DBusMessage *msg, void *_u){ + struct userdata *u; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter, dict_iter; + uint32_t rev, n_coefs, rate, fft_size, channels; + + pa_assert_se(u = _u); + pa_assert(msg); + + rev = 1; + n_coefs = (uint32_t) CHANNEL_PROFILE_SIZE(u); + rate = (uint32_t) u->sink->sample_spec.rate; + fft_size = (uint32_t) u->fft_size; + channels = (uint32_t) u->channels; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_REVISION].property_name, DBUS_TYPE_UINT32, &rev); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_SAMPLERATE].property_name, DBUS_TYPE_UINT32, &rate); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_FILTERSAMPLERATE].property_name, DBUS_TYPE_UINT32, &fft_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_COEFS].property_name, DBUS_TYPE_UINT32, &n_coefs); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, equalizer_handlers[EQUALIZER_HANDLER_N_CHANNELS].property_name, DBUS_TYPE_UINT32, &channels); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c index 56cda4df..617d5a14 100644 --- a/src/modules/module-esound-compat-spawnfd.c +++ b/src/modules/module-esound-compat-spawnfd.c @@ -24,7 +24,6 @@ #endif #include <unistd.h> -#include <string.h> #include <errno.h> #include <pulsecore/core-error.h> diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c index 5925f591..94ebdaad 100644 --- a/src/modules/module-esound-compat-spawnpid.c +++ b/src/modules/module-esound-compat-spawnpid.c @@ -25,13 +25,11 @@ #endif #include <unistd.h> -#include <string.h> #include <errno.h> #include <signal.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> -#include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index 8cb25c51..d79054f8 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -24,26 +24,32 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> -#include <poll.h> -#include <sys/socket.h> + +#ifdef HAVE_NETINET_IN_H #include <netinet/in.h> +#endif + +#ifdef HAVE_NETINET_TCP_H #include <netinet/tcp.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> +#endif #ifdef HAVE_LINUX_SOCKIOS_H #include <linux/sockios.h> #endif -#include <pulse/xmalloc.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> +#include <pulse/xmalloc.h> +#include <pulsecore/socket.h> #include <pulsecore/core-error.h> #include <pulsecore/iochannel.h> #include <pulsecore/sink.h> @@ -57,8 +63,9 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/thread.h> #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h> #include <pulsecore/socket-util.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/poll.h> #include "module-esound-sink-symdef.h" @@ -145,14 +152,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_SUSPENDED: pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - pa_smoother_pause(u->smoother, pa_rtclock_usec()); + pa_smoother_pause(u->smoother, pa_rtclock_now()); break; case PA_SINK_IDLE: case PA_SINK_RUNNING: if (u->sink->thread_info.state == PA_SINK_SUSPENDED) - pa_smoother_resume(u->smoother, pa_rtclock_usec(), TRUE); + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); break; @@ -167,7 +174,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t w, r; - r = pa_smoother_get(u->smoother, pa_rtclock_usec()); + r = pa_smoother_get(u->smoother, pa_rtclock_now()); w = pa_bytes_to_usec((uint64_t) u->offset + u->memchunk.length, &u->sink->sample_spec); *((pa_usec_t*) data) = w > r ? w - r : 0; @@ -200,9 +207,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); for (;;) { int ret; @@ -295,7 +301,7 @@ static void thread_func(void *userdata) { else usec = 0; - pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); + pa_smoother_put(u->smoother, pa_rtclock_now(), usec); } /* Hmm, nothing to do. Let's sleep */ @@ -608,7 +614,7 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - if (!(u->client = pa_socket_client_new_string(u->core->mainloop, espeaker, ESD_DEFAULT_PORT))) { + if (!(u->client = pa_socket_client_new_string(u->core->mainloop, TRUE, espeaker, ESD_DEFAULT_PORT))) { pa_log("Failed to connect to server."); goto fail; } @@ -628,7 +634,7 @@ int pa__init(pa_module*m) { /* Reserve space for the response */ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t)); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("esound-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c new file mode 100644 index 00000000..c742373a --- /dev/null +++ b/src/modules/module-filter-apply.c @@ -0,0 +1,600 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Colin Guthrie + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/rtclock.h> +#include <pulse/i18n.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/modargs.h> + +#include "module-filter-apply-symdef.h" + +#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE(_("autoclean=<automatically unload unused filters?>")); + +static const char* const valid_modargs[] = { + "autoclean", + NULL +}; + +#define DEFAULT_AUTOCLEAN TRUE +#define HOUSEKEEPING_INTERVAL (10 * PA_USEC_PER_SEC) + +struct filter { + char *name; + uint32_t module_index; + pa_bool_t is_sink; + pa_object *parent_obj; /* source or sink that the filter is connected to */ + pa_object *obj; /* source or sink of the filter */ +}; + +struct userdata { + pa_core *core; + pa_hashmap *filters; + pa_hook_slot + *sink_input_put_slot, + *sink_input_move_finish_slot, + *sink_input_proplist_slot, + *sink_input_unlink_slot, + *sink_unlink_slot, + *source_output_put_slot, + *source_output_move_finish_slot, + *source_output_proplist_slot, + *source_output_unlink_slot, + *source_unlink_slot; + pa_bool_t autoclean; + pa_time_event *housekeeping_time_event; +}; + +static unsigned filter_hash(const void *p) { + const struct filter *f = p; + + if (f->is_sink) + return (unsigned) (PA_SINK(f->parent_obj)->index + pa_idxset_string_hash_func(f->name)); + else + return (unsigned) ((PA_SOURCE(f->parent_obj)->index << 16) + pa_idxset_string_hash_func(f->name)); +} + +static int filter_compare(const void *a, const void *b) { + const struct filter *fa = a, *fb = b; + int r; + + if (fa->parent_obj != fb->parent_obj) + return 1; + if ((r = strcmp(fa->name, fb->name))) + return r; + + return 0; +} + +static struct filter *filter_new(const char *name, pa_object* parent_obj, pa_bool_t is_sink) { + struct filter *f; + + f = pa_xnew(struct filter, 1); + f->name = pa_xstrdup(name); + pa_assert_se(f->parent_obj = parent_obj); + f->is_sink = is_sink; + f->module_index = PA_INVALID_INDEX; + f->obj = NULL; + return f; +} + +static void filter_free(struct filter *f) { + pa_assert(f); + + pa_xfree(f->name); + pa_xfree(f); +} + +static const char* should_filter(pa_object *o, pa_bool_t is_sink_input) { + const char *apply; + pa_proplist *pl; + + if (is_sink_input) + pl = PA_SINK_INPUT(o)->proplist; + else + pl = PA_SOURCE_OUTPUT(o)->proplist; + + /* If the stream doesn't what any filter, then let it be. */ + if ((apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) { + const char* suppress = pa_proplist_gets(pl, PA_PROP_FILTER_SUPPRESS); + + if (!suppress || !pa_streq(suppress, apply)) + return apply; + } + + return NULL; +} + +static pa_bool_t nothing_attached(pa_object *obj, pa_bool_t is_sink) +{ + if (is_sink) + return pa_idxset_isempty(PA_SINK(obj)->inputs); + else + return pa_idxset_isempty(PA_SOURCE(obj)->outputs); +} + +static void housekeeping_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + struct filter *filter; + void *state; + + pa_assert(a); + pa_assert(e); + pa_assert(u); + + pa_assert(e == u->housekeeping_time_event); + u->core->mainloop->time_free(u->housekeeping_time_event); + u->housekeeping_time_event = NULL; + + PA_HASHMAP_FOREACH(filter, u->filters, state) { + if (filter->obj && nothing_attached(filter->obj, filter->is_sink)) { + uint32_t idx; + + pa_log_debug("Detected filter %s as no longer used. Unloading.", filter->name); + idx = filter->module_index; + pa_hashmap_remove(u->filters, filter); + filter_free(filter); + pa_module_unload_request_by_index(u->core, idx, TRUE); + } + } + + pa_log_info("Housekeeping Done."); +} + +static void trigger_housekeeping(struct userdata *u) { + pa_assert(u); + + if (!u->autoclean) + return; + + if (u->housekeeping_time_event) + return; + + u->housekeeping_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + HOUSEKEEPING_INTERVAL, housekeeping_time_callback, u); +} + +static int do_move(pa_object *obj, pa_object *parent, pa_bool_t restore, pa_bool_t is_input) { + if (is_input) + return pa_sink_input_move_to(PA_SINK_INPUT(obj), PA_SINK(parent), restore); + else + return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), restore); +} + +static void move_object_for_filter(pa_object *o, struct filter* filter, pa_bool_t restore, pa_bool_t is_sink_input) { + pa_object *parent; + pa_proplist *pl; + const char *name; + + pa_assert(o); + pa_assert(filter); + + pa_assert_se(parent = (restore ? filter->parent_obj : filter->obj)); + + if (is_sink_input) { + pl = PA_SINK_INPUT(o)->proplist; + name = PA_SINK(parent)->name; + } else { + pl = PA_SOURCE_OUTPUT(o)->proplist; + name = PA_SOURCE(parent)->name; + } + + pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_MOVING, "1"); + + if (do_move(o, parent, FALSE, is_sink_input) < 0) + pa_log_info("Failed to move %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output", + pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name); + else + pa_log_info("Sucessfully moved %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output", + pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name); + + pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_MOVING); +} + +static void find_filters_for_module(struct userdata *u, pa_module *m, const char *name) { + uint32_t idx; + pa_sink *sink; + pa_source *source; + struct filter *fltr; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if (sink->module == m) { + pa_assert(sink->input_to_master != NULL); + + fltr = filter_new(name, PA_OBJECT(sink->input_to_master->sink), TRUE); + fltr->module_index = m->index; + fltr->obj = PA_OBJECT(sink); + + pa_hashmap_put(u->filters, fltr, fltr); + } + } + + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + if (source->module == m && !source->monitor_of) { + pa_assert(source->output_from_master != NULL); + + fltr = filter_new(name, PA_OBJECT(source->output_from_master->source), FALSE); + fltr->module_index = m->index; + fltr->obj = PA_OBJECT(source); + + pa_hashmap_put(u->filters, fltr, fltr); + } + } +} + +static pa_bool_t can_unload_module(struct userdata *u, uint32_t idx) { + void *state; + struct filter *filter; + + /* Check if any other struct filters point to the same module */ + PA_HASHMAP_FOREACH(filter, u->filters, state) { + if (filter->module_index == idx && !nothing_attached(filter->obj, pa_sink_isinstance(filter->obj))) + return FALSE; + } + + return TRUE; +} + +static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_sink_input) { + const char *want; + pa_bool_t done_something = FALSE; + + pa_object *parent; /* source/sink of the given source-output/sink-input */ + const char *parent_name; + pa_module *module; + + if (is_sink_input) { + parent = PA_OBJECT(PA_SINK_INPUT(o)->sink); + parent_name = PA_SINK_INPUT(o)->sink->name; + module = PA_SINK_INPUT(o)->sink->module; + } else { + parent = PA_OBJECT(PA_SOURCE_OUTPUT(o)->source); + parent_name = PA_SOURCE_OUTPUT(o)->source->name; + module = PA_SOURCE_OUTPUT(o)->source->module; + } + + /* If there is no sink yet, we can't do much */ + if (!parent) + return PA_HOOK_OK; + + /* If the stream doesn't what any filter, then let it be. */ + if ((want = should_filter(o, is_sink_input))) { + char *module_name; + struct filter *fltr, *filter; + + /* We need to ensure the SI is playing on a sink of this type + * attached to the sink it's "officially" playing on */ + + if (!module) + return PA_HOOK_OK; + + module_name = pa_sprintf_malloc("module-%s", want); + if (pa_streq(module->name, module_name)) { + pa_log_debug("Stream appears to be playing on an appropriate sink already. Ignoring."); + pa_xfree(module_name); + return PA_HOOK_OK; + } + + fltr = filter_new(want, parent, is_sink_input); + + if (!(filter = pa_hashmap_get(u->filters, fltr))) { + char *args; + pa_module *m; + + args = pa_sprintf_malloc("autoloaded=1 %s_master=%s", is_sink_input ? "sink" : "source", parent_name); + pa_log_debug("Loading %s with arguments '%s'", module_name, args); + + if ((m = pa_module_load(u->core, module_name, args))) { + find_filters_for_module(u, m, want); + filter = pa_hashmap_get(u->filters, fltr); + done_something = TRUE; + } + pa_xfree(args); + } + + pa_xfree(fltr); + + if (!filter) { + pa_log("Unable to load %s for <%s>", module_name, parent_name); + pa_xfree(module_name); + return PA_HOOK_OK; + } + pa_xfree(module_name); + + if (filter->obj) { + /* We can move the sink_input now as the know the destination. + * If this isn't true, we will do it later when the sink appears. */ + move_object_for_filter(o, filter, FALSE, is_sink_input); + done_something = TRUE; + } + } else { + void *state; + struct filter *filter = NULL; + + /* We do not want to filter... but are we already filtered? + * This can happen if an input's proplist changes */ + PA_HASHMAP_FOREACH(filter, u->filters, state) { + if (parent == filter->obj) { + move_object_for_filter(o, filter, TRUE, is_sink_input); + done_something = TRUE; + break; + } + } + } + + if (done_something) + trigger_housekeeping(u); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + return process(u, PA_OBJECT(i), TRUE); +} + +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; + + return process(u, PA_OBJECT(i), TRUE); +} + +static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + return process(u, PA_OBJECT(i), TRUE); +} + +static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + pa_assert(u); + + if (pa_hashmap_size(u->filters) > 0) + trigger_housekeeping(u); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_cb(pa_core *core, pa_sink *sink, struct userdata *u) { + void *state; + struct filter *filter = NULL; + + pa_core_assert_ref(core); + pa_sink_assert_ref(sink); + pa_assert(u); + + /* If either the parent or the sink we've loaded disappears, + * we should remove it from our hashmap */ + PA_HASHMAP_FOREACH(filter, u->filters, state) { + if (filter->parent_obj == PA_OBJECT(sink) || filter->obj == PA_OBJECT(sink)) { + uint32_t idx; + + /* Attempt to rescue any streams to the parent sink as this is likely + * the best course of action (as opposed to a generic rescue via + * module-rescue-streams */ + if (filter->obj == PA_OBJECT(sink)) { + pa_sink_input *i; + + PA_IDXSET_FOREACH(i, sink->inputs, idx) + move_object_for_filter(PA_OBJECT(i), filter, TRUE, TRUE); + } + + idx = filter->module_index; + pa_hashmap_remove(u->filters, filter); + filter_free(filter); + + if (can_unload_module(u, idx)) + pa_module_unload_request_by_index(u->core, idx, TRUE); + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + return process(u, PA_OBJECT(o), FALSE); +} + +static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; + + return process(u, PA_OBJECT(o), FALSE); +} + +static pa_hook_result_t source_output_proplist_cb(pa_core *core, pa_source_output *o, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + return process(u, PA_OBJECT(o), FALSE); +} + +static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(o); + + pa_assert(u); + + if (pa_hashmap_size(u->filters) > 0) + trigger_housekeeping(u); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, struct userdata *u) { + void *state; + struct filter *filter = NULL; + + pa_core_assert_ref(core); + pa_source_assert_ref(source); + pa_assert(u); + + /* If either the parent or the source we've loaded disappears, + * we should remove it from our hashmap */ + PA_HASHMAP_FOREACH(filter, u->filters, state) { + if (filter->parent_obj == PA_OBJECT(source) || filter->obj == PA_OBJECT(source)) { + uint32_t idx; + + /* Attempt to rescue any streams to the parent source as this is likely + * the best course of action (as opposed to a generic rescue via + * module-rescue-streams */ + if (filter->obj == PA_OBJECT(source)) { + pa_source_output *o; + + PA_IDXSET_FOREACH(o, source->outputs, idx) + move_object_for_filter(PA_OBJECT(o), filter, TRUE, FALSE); + } + + idx = filter->module_index; + pa_hashmap_remove(u->filters, filter); + filter_free(filter); + + if (can_unload_module(u, idx)) + pa_module_unload_request_by_index(u->core, idx, TRUE); + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + + u->core = m->core; + + u->autoclean = DEFAULT_AUTOCLEAN; + if (pa_modargs_get_value_boolean(ma, "autoclean", &u->autoclean) < 0) { + pa_log("Failed to parse autoclean value"); + goto fail; + } + + u->filters = pa_hashmap_new(filter_hash, filter_compare); + + u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); + u->sink_input_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_unlink_cb, u); + u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_output_put_cb, u); + u->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) source_output_move_finish_cb, u); + u->source_output_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_proplist_cb, u); + u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_output_unlink_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_unlink_cb, u); + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_put_slot) + pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_move_finish_slot) + pa_hook_slot_free(u->sink_input_move_finish_slot); + if (u->sink_input_proplist_slot) + pa_hook_slot_free(u->sink_input_proplist_slot); + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_output_put_slot) + pa_hook_slot_free(u->source_output_put_slot); + if (u->source_output_move_finish_slot) + pa_hook_slot_free(u->source_output_move_finish_slot); + if (u->source_output_proplist_slot) + pa_hook_slot_free(u->source_output_proplist_slot); + if (u->source_output_unlink_slot) + pa_hook_slot_free(u->source_output_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->housekeeping_time_event) + u->core->mainloop->time_free(u->housekeeping_time_event); + + if (u->filters) { + struct filter *f; + + while ((f = pa_hashmap_steal_first(u->filters))) { + pa_module_unload_request_by_index(u->core, f->module_index, TRUE); + filter_free(f); + } + + pa_hashmap_free(u->filters, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c new file mode 100644 index 00000000..222787fc --- /dev/null +++ b/src/modules/module-filter-heuristics.c @@ -0,0 +1,217 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Colin Guthrie + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/modargs.h> + +#include "module-filter-heuristics-symdef.h" + +#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving" +#define PA_PROP_FILTER_HEURISTICS "filter.heuristics" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Detect when various filters are desirable"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL +}; + +struct userdata { + pa_core *core; + pa_hook_slot + *sink_input_put_slot, + *sink_input_move_finish_slot, + *source_output_put_slot, + *source_output_move_finish_slot; +}; + +static pa_bool_t role_match(pa_proplist *proplist, const char *role) { + const char *ir; + char *r; + const char *state = NULL; + + if (!(ir = pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES))) + return FALSE; + + while ((r = pa_split_spaces(ir, &state))) { + + if (pa_streq(role, r)) { + pa_xfree(r); + return TRUE; + } + + pa_xfree(r); + } + + return FALSE; +} + +static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_sink_input) { + const char *want, *stream_role; + pa_proplist *pl, *parent_pl; + + if (is_sink_input) { + pl = PA_SINK_INPUT(o)->proplist; + parent_pl = PA_SINK_INPUT(o)->sink->proplist; + } else { + pl = PA_SOURCE_OUTPUT(o)->proplist; + parent_pl = PA_SOURCE_OUTPUT(o)->source->proplist; + } + + /* If the stream already specifies what it must have, then let it be. */ + if (!pa_proplist_gets(pl, PA_PROP_FILTER_HEURISTICS) && pa_proplist_gets(pl, PA_PROP_FILTER_APPLY)) + return PA_HOOK_OK; + + want = pa_proplist_gets(pl, PA_PROP_FILTER_WANT); + if (!want) { + /* This is a phone stream, maybe we want echo cancellation */ + if ((stream_role = pa_proplist_gets(pl, PA_PROP_MEDIA_ROLE)) && pa_streq(stream_role, "phone")) + want = "echo-cancel"; + } + + /* On phone sinks, make sure we're not applying echo cancellation */ + if (role_match(parent_pl, "phone")) { + const char *apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY); + + if (apply && pa_streq(apply, "echo-cancel")) { + pa_proplist_unset(pl, PA_PROP_FILTER_APPLY); + pa_proplist_unset(pl, PA_PROP_FILTER_HEURISTICS); + } + + return PA_HOOK_OK; + } + + if (want) { + /* There's a filter that we want, ask module-filter-apply to apply it, and remember that we're managing filter.apply */ + pa_proplist_sets(pl, PA_PROP_FILTER_APPLY, want); + pa_proplist_sets(pl, PA_PROP_FILTER_HEURISTICS, "1"); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + pa_assert(u); + + return process(u, PA_OBJECT(i), TRUE); +} + +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + pa_assert(u); + + /* module-filter-apply triggered this move, ignore */ + if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; + + return process(u, PA_OBJECT(i), TRUE); +} + +static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(i); + pa_assert(u); + + return process(u, PA_OBJECT(i), FALSE); +} + +static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_source_output_assert_ref(i); + pa_assert(u); + + /* module-filter-apply triggered this move, ignore */ + if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; + + return process(u, PA_OBJECT(i), FALSE); +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + + u->core = m->core; + + u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_move_finish_cb, u); + u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) source_output_put_cb, u); + u->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) source_output_move_finish_cb, u); + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; + + +} + +void pa__done(pa_module *m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_put_slot) + pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_move_finish_slot) + pa_hook_slot_free(u->sink_input_move_finish_slot); + if (u->source_output_put_slot) + pa_hook_slot_free(u->source_output_put_slot); + if (u->source_output_move_finish_slot) + pa_hook_slot_free(u->source_output_move_finish_slot); + + pa_xfree(u); + +} diff --git a/src/modules/module-hal-detect-compat.c b/src/modules/module-hal-detect-compat.c new file mode 100644 index 00000000..14cf8143 --- /dev/null +++ b/src/modules/module-hal-detect-compat.c @@ -0,0 +1,84 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include "module-hal-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Compatibility module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); + +static const char* const valid_modargs[] = { + "api", + "tsched", + "subdevices", + NULL, +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + pa_bool_t tsched = TRUE; + pa_module *n; + char *t; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "tsched", &tsched) < 0) { + pa_log("tsched= expects boolean arguments"); + goto fail; + } + + pa_log_warn("We will now load module-udev-detect. Please make sure to remove module-hal-detect from your configuration."); + + t = pa_sprintf_malloc("tsched=%s", pa_yes_no(tsched)); + n = pa_module_load(m->core, "module-udev-detect", t); + pa_xfree(t); + + if (n) + pa_module_unload_request(m, TRUE); + + pa_modargs_free(ma); + + return n ? 0 : -1; + +fail: + if (ma) + pa_modargs_free(ma); + + return -1; +} diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index b6139e43..62f0f203 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -31,19 +31,15 @@ #include <errno.h> #include <stdlib.h> #include <sys/types.h> -#include <sys/stat.h> #include <pulse/xmalloc.h> -#include <pulse/timeval.h> -#include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/log.h> #include <pulsecore/hashmap.h> #include <pulsecore/idxset.h> #include <pulsecore/core-util.h> #include <pulsecore/namereg.h> -#include <pulsecore/core-scache.h> #include <pulsecore/modargs.h> #include <pulsecore/dbus-shared.h> @@ -55,15 +51,18 @@ PA_MODULE_AUTHOR("Shahms King"); PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -#if defined(HAVE_ALSA) && defined(HAVE_OSS) +#if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT) PA_MODULE_USAGE("api=<alsa or oss> " - "tsched=<enable system timer based scheduling mode?>"); + "tsched=<enable system timer based scheduling mode?> " + "subdevices=<init all subdevices>"); #elif defined(HAVE_ALSA) PA_MODULE_USAGE("api=<alsa> " "tsched=<enable system timer based scheduling mode?>"); -#elif defined(HAVE_OSS) -PA_MODULE_USAGE("api=<oss>"); +#elif defined(HAVE_OSS_OUTPUT) +PA_MODULE_USAGE("api=<oss> " + "subdevices=<init all subdevices>"); #endif +PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!"); struct device { char *udi, *originating_udi; @@ -81,6 +80,10 @@ struct userdata { #ifdef HAVE_ALSA pa_bool_t use_tsched; #endif +#ifdef HAVE_OSS_OUTPUT + pa_bool_t init_subdevs; +#endif + pa_bool_t filter_added:1; }; #define CAPABILITY_ALSA "alsa" @@ -91,6 +94,9 @@ static const char* const valid_modargs[] = { #ifdef HAVE_ALSA "tsched", #endif +#ifdef HAVE_OSS_OUTPUT + "subdevices", +#endif NULL }; @@ -232,7 +238,7 @@ static int hal_device_load_alsa(struct userdata *u, const char *udi, struct devi goto fail; card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi)); - args = pa_sprintf_malloc("device_id=%u name=%s card_name=%s tsched=%i", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); + args = pa_sprintf_malloc("device_id=%u name=\"%s\" card_name=\"%s\" tsched=%i card_properties=\"module-hal-detect.discovered=1\"", card, strip_udi(originating_udi), card_name, (int) u->use_tsched); pa_log_debug("Loading module-alsa-card with arguments '%s'", args); m = pa_module_load(u->core, "module-alsa-card", args); @@ -261,9 +267,9 @@ fail: #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT -static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi) { +static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) { char *class = NULL, *dev = NULL, *e; int device; pa_bool_t r = FALSE; @@ -293,7 +299,7 @@ static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi) /* We only care for the main device */ device = libhal_device_get_property_int(context, udi, "oss.device", &error); - if (dbus_error_is_set(&error) || device != 0) + if (dbus_error_is_set(&error) || (device != 0 && init_subdevices == FALSE)) goto finish; r = TRUE; @@ -323,7 +329,7 @@ static int hal_device_load_oss(struct userdata *u, const char *udi, struct devic pa_assert(d); /* We only care for OSS PCM devices */ - if (!hal_oss_device_is_pcm(u->context, udi)) + if (!hal_oss_device_is_pcm(u->context, udi, u->init_subdevs)) goto fail; /* We store only one entry per card, hence we look for the originating device */ @@ -393,7 +399,7 @@ static struct device* hal_device_add(struct userdata *u, const char *udi) { if (pa_streq(u->capability, CAPABILITY_ALSA)) r = hal_device_load_alsa(u, udi, d); #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if (pa_streq(u->capability, CAPABILITY_OSS)) r = hal_device_load_oss(u, udi, d); #endif @@ -426,9 +432,7 @@ static int hal_device_add_all(struct userdata *u) { int i; for (i = 0; i < n; i++) { - struct device *d; - - if ((d = hal_device_add(u, udis[i]))) { + if (hal_device_add(u, udis[i])) { count++; pa_log_debug("Loaded device %s", udis[i]); } else @@ -567,7 +571,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) { - pa_bool_t success = pa_sink_suspend(sink, suspend) >= 0; + pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -580,7 +584,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) { - pa_bool_t success = pa_source_suspend(source, suspend) >= 0; + pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -593,7 +597,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_card *card; if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) { - pa_bool_t success = pa_card_suspend(card, suspend) >= 0; + pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0; if (!success && !suspend) d->acl_race_fix = TRUE; /* resume failed, let's try again */ @@ -614,8 +618,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo } - return DBUS_HANDLER_RESULT_HANDLED; - } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) { /* We use this message to avoid a dirty race condition when we get an ACLAdded message before the previously owning PA @@ -637,21 +639,21 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_sink *sink; if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) - pa_sink_suspend(sink, FALSE); + pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION); } if (d->source_name) { pa_source *source; if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) - pa_source_suspend(source, FALSE); + pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION); } if (d->card_name) { pa_card *card; if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD))) - pa_card_suspend(card, FALSE); + pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION); } } @@ -659,7 +661,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo /* Yes, we don't check the UDI for validity, but hopefully HAL will */ device_added_cb(u->context, udi); - return DBUS_HANDLER_RESULT_HANDLED; } finish: @@ -729,12 +730,9 @@ int pa__init(pa_module*m) { goto fail; } - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; - u->context = NULL; - u->connection = NULL; u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - u->capability = NULL; #ifdef HAVE_ALSA u->use_tsched = TRUE; @@ -752,7 +750,7 @@ int pa__init(pa_module*m) { api = pa_modargs_get_value(ma, "api", "oss"); #endif -#ifdef HAVE_OSS +#ifdef HAVE_OSS_OUTPUT if (pa_streq(api, "oss")) u->capability = CAPABILITY_OSS; #endif @@ -762,6 +760,13 @@ int pa__init(pa_module*m) { goto fail; } +#ifdef HAVE_OSS_OUTPUT + if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) { + pa_log("Failed to parse subdevices= argument."); + goto fail; + } +#endif + if (!(u->connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message); goto fail; @@ -789,6 +794,7 @@ int pa__init(pa_module*m) { pa_log_error("Failed to add filter function"); goto fail; } + u->filter_added = TRUE; if (pa_dbus_add_matches( pa_dbus_connection_get(u->connection), &error, @@ -845,7 +851,8 @@ void pa__done(pa_module *m) { "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'", "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL); - dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); + if (u->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); pa_dbus_connection_unref(u->connection); } diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c new file mode 100644 index 00000000..9ba893b2 --- /dev/null +++ b/src/modules/module-intended-roles.c @@ -0,0 +1,464 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> + +#include "module-intended-roles-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically set device of streams based of intended roles of devices"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "on_hotplug=<When new device becomes available, recheck streams?> " + "on_rescue=<When device becomes unavailable, recheck streams?>"); + +static const char* const valid_modargs[] = { + "on_hotplug", + "on_rescue", + NULL +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hook_slot + *sink_input_new_hook_slot, + *source_output_new_hook_slot, + *sink_put_hook_slot, + *source_put_hook_slot, + *sink_unlink_hook_slot, + *source_unlink_hook_slot; + + pa_bool_t on_hotplug:1; + pa_bool_t on_rescue:1; +}; + +static pa_bool_t role_match(pa_proplist *proplist, const char *role) { + const char *ir; + char *r; + const char *state = NULL; + + if (!(ir = pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES))) + return FALSE; + + while ((r = pa_split_spaces(ir, &state))) { + + if (pa_streq(role, r)) { + pa_xfree(r); + return TRUE; + } + + pa_xfree(r); + } + + return FALSE; +} + +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; + pa_sink *s, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!new_data->proplist) { + pa_log_debug("New stream lacks property data."); + return PA_HOOK_OK; + } + + if (new_data->sink) { + pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); + return PA_HOOK_OK; + } + + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { + pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); + return PA_HOOK_OK; + } + + /* Prefer the default sink over any other sink, just in case... */ + if ((def = pa_namereg_get_default_sink(c))) + if (role_match(def->proplist, role) && pa_sink_input_new_data_set_sink(new_data, def, FALSE)) + return PA_HOOK_OK; + + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(s, c->sinks, idx) { + if (s == def) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s))) + continue; + + if (role_match(s->proplist, role) && pa_sink_input_new_data_set_sink(new_data, s, FALSE)) + return PA_HOOK_OK; + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; + pa_source *s, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!new_data->proplist) { + pa_log_debug("New stream lacks property data."); + return PA_HOOK_OK; + } + + if (new_data->source) { + pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); + return PA_HOOK_OK; + } + + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { + pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); + return PA_HOOK_OK; + } + + /* Prefer the default source over any other source, just in case... */ + if ((def = pa_namereg_get_default_source(c))) + if (role_match(def->proplist, role)) { + pa_source_output_new_data_set_source(new_data, def, FALSE); + return PA_HOOK_OK; + } + + PA_IDXSET_FOREACH(s, c->sources, idx) { + if (s->monitor_of) + continue; + + if (s == def) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s))) + continue; + + /* @todo: favour the highest priority device, not the first one we find? */ + if (role_match(s->proplist, role)) { + pa_source_output_new_data_set_source(new_data, s, FALSE); + return PA_HOOK_OK; + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->on_hotplug); + + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + const char *role; + + if (si->sink == sink) + continue; + + if (si->save_sink) + continue; + + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + continue; + + if (role_match(si->sink->proplist, role)) + continue; + + if (!role_match(sink->proplist, role)) + continue; + + pa_sink_input_move_to(si, sink, FALSE); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_source_output *so; + uint32_t idx; + + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->on_hotplug); + + if (source->monitor_of) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(so, c->source_outputs, idx) { + const char *role; + + if (so->source == source) + continue; + + if (so->save_source) + continue; + + if (so->direct_on_input) + continue; + + /* Skip this if it is already in the process of being moved + * anyway */ + if (!so->source) + continue; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + continue; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + continue; + + if (role_match(so->source->proplist, role)) + continue; + + if (!role_match(source->proplist, role)) + continue; + + pa_source_output_move_to(so, source, FALSE); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_sink_input *si; + uint32_t idx; + pa_sink *def; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + /* If there not default sink, then there is no sink at all */ + if (!(def = pa_namereg_get_default_sink(c))) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(si, sink->inputs, idx) { + const char *role; + uint32_t jdx; + pa_sink *d; + + if (!si->sink) + continue; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + continue; + + /* Would the default sink fit? If so, let's use it */ + if (def != sink && role_match(def->proplist, role)) + if (pa_sink_input_move_to(si, def, FALSE) >= 0) + continue; + + /* Try to find some other fitting sink */ + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(d, c->sinks, jdx) { + if (d == def || d == sink) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(d))) + continue; + + if (role_match(d->proplist, role)) + if (pa_sink_input_move_to(si, d, FALSE) >= 0) + break; + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_source_output *so; + uint32_t idx; + pa_source *def; + + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->on_rescue); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + /* If there not default source, then there is no source at all */ + if (!(def = pa_namereg_get_default_source(c))) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(so, source->outputs, idx) { + const char *role; + uint32_t jdx; + pa_source *d; + + if (so->direct_on_input) + continue; + + if (!so->source) + continue; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + continue; + + /* Would the default source fit? If so, let's use it */ + if (def != source && role_match(def->proplist, role) && !source->monitor_of == !def->monitor_of) { + pa_source_output_move_to(so, def, FALSE); + continue; + } + + /* Try to find some other fitting source */ + /* @todo: favour the highest priority device, not the first one we find? */ + PA_IDXSET_FOREACH(d, c->sources, jdx) { + if (d == def || d == source) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(d))) + continue; + + /* If moving from a monitor, move to another monitor */ + if (!source->monitor_of == !d->monitor_of && role_match(d->proplist, role)) { + pa_source_output_move_to(so, d, FALSE); + break; + } + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_bool_t on_hotplug = TRUE, on_rescue = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("on_hotplug= and on_rescue= expect boolean arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->on_hotplug = on_hotplug; + u->on_rescue = on_rescue; + + /* A little bit later than module-stream-restore */ + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) source_output_new_hook_callback, u); + + if (on_hotplug) { + /* A little bit later than module-stream-restore */ + u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) source_put_hook_callback, u); + } + + if (on_rescue) { + /* A little bit later than module-stream-restore, a little bit earlier than module-rescue-streams, ... */ + u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) source_unlink_hook_callback, u); + } + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); + + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->source_put_hook_slot) + pa_hook_slot_free(u->source_put_hook_slot); + + if (u->sink_unlink_hook_slot) + pa_hook_slot_free(u->sink_unlink_hook_slot); + if (u->source_unlink_hook_slot) + pa_hook_slot_free(u->source_unlink_hook_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 15af74a6..9cce269d 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -26,17 +26,17 @@ #include <config.h> #endif +#include <math.h> + #include <pulse/xmalloc.h> +#include <pulse/i18n.h> -#include <pulsecore/core-error.h> #include <pulsecore/namereg.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulsecore/thread.h> -#include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include <pulsecore/sample-util.h> #include <pulsecore/ltdl-helper.h> @@ -45,43 +45,48 @@ #include "ladspa.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Virtual LADSPA sink"); +PA_MODULE_DESCRIPTION(_("Virtual LADSPA sink")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "master=<name of sink to remap> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "plugin=<ladspa plugin name> " - "label=<ladspa plugin label> " - "control=<comma seperated list of input control values>"); + _("sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "master=<name of sink to filter> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<input channel map> " + "plugin=<ladspa plugin name> " + "label=<ladspa plugin label> " + "control=<comma seperated list of input control values> " + "input_ladspaport_map=<comma separated list of input LADSPA port names> " + "output_ladspaport_map=<comma separated list of output LADSPA port names> ")); #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) +/* PLEASE NOTICE: The PortAudio ports and the LADSPA ports are two different concepts. +They are not related and where possible the names of the LADSPA port variables contains "ladspa" to avoid confusion */ + struct userdata { - pa_core *core; pa_module *module; - pa_sink *sink, *master; + pa_sink *sink; pa_sink_input *sink_input; const LADSPA_Descriptor *descriptor; - unsigned channels; LADSPA_Handle handle[PA_CHANNELS_MAX]; - LADSPA_Data *input, *output; + unsigned long max_ladspaport_count, input_count, output_count, channels; + LADSPA_Data **input, **output; size_t block_size; - unsigned long input_port, output_port; LADSPA_Data *control; /* This is a dummy buffer. Every port must be connected, but we don't care - about control out ports. We connect them all to this single buffer. */ + about control out ports. We connect them all to this single buffer. */ LADSPA_Data control_out; pa_memblockq *memblockq; + + pa_bool_t auto_desc; }; static const char* const valid_modargs[] = { @@ -95,71 +100,117 @@ static const char* const valid_modargs[] = { "plugin", "label", "control", + "input_ladspaport_map", + "output_ladspaport_map", NULL }; /* Called from I/O thread context */ -static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; switch (code) { - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t usec = 0; + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = /* Get the latency of the master sink */ - if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; + pa_sink_get_latency_within_thread(u->sink_input->sink) + /* Add the latency internal to our sink input on top */ - usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - *((pa_usec_t*) data) = usec; - return 0; - } + return 0; } return pa_sink_process_msg(o, code, data, offset, chunk); } /* Called from main context */ -static int sink_set_state(pa_sink *s, pa_sink_state_t state) { +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_IS_LINKED(state) && - u->sink_input && - PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } /* Called from I/O thread context */ -static void sink_request_rewind(pa_sink *s) { +static void sink_request_rewind_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ - pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE); + pa_sink_input_request_rewind(u->sink_input, + s->thread_info.rewind_nbytes + + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE); } /* Called from I/O thread context */ -static void sink_update_requested_latency(pa_sink *s) { +static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ pa_sink_input_set_requested_latency_within_thread( - u->sink_input, - pa_sink_get_requested_latency_within_thread(s)); + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); } /* Called from I/O thread context */ @@ -167,15 +218,15 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk struct userdata *u; float *src, *dst; size_t fs; - unsigned n, c; + unsigned n, h, c; pa_memchunk tchunk; pa_sink_input_assert_ref(i); pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return -1; + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { pa_memchunk nchunk; @@ -202,10 +253,12 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); dst = (float*) pa_memblock_acquire(chunk->memblock); - for (c = 0; c < u->channels; c++) { - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input, sizeof(float), src+c, u->channels*sizeof(float), n); - u->descriptor->run(u->handle[c], n); - pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst+c, u->channels*sizeof(float), u->output, sizeof(float), n); + for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) { + for (c = 0; c < u->input_count; c++) + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input[c], sizeof(float), src+ h*u->max_ladspaport_count + c, u->channels*sizeof(float), n); + u->descriptor->run(u->handle[h], n); + for (c = 0; c < u->output_count; c++) + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst + h*u->max_ladspaport_count + c, u->channels*sizeof(float), u->output[c], sizeof(float), n); } pa_memblock_release(tchunk.memblock); @@ -224,9 +277,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return; - if (u->sink->thread_info.rewind_nbytes > 0) { size_t max_rewrite; @@ -243,10 +293,10 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { /* Reset the plugin */ if (u->descriptor->deactivate) - for (c = 0; c < u->channels; c++) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) u->descriptor->deactivate(u->handle[c]); if (u->descriptor->activate) - for (c = 0; c < u->channels; c++) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) u->descriptor->activate(u->handle[c]); } } @@ -262,9 +312,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_memblockq_set_maxrewind(u->memblockq, nbytes); pa_sink_set_max_rewind_within_thread(u->sink, nbytes); } @@ -276,9 +323,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_request_within_thread(u->sink, nbytes); } @@ -289,24 +333,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_sink_detach_within_thread(u->sink); - pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } @@ -317,14 +365,13 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); - pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); pa_sink_attach_within_thread(u->sink); - - pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -334,14 +381,18 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_sink_unlink(u->sink); + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; pa_sink_input_unref(u->sink_input); u->sink_input = NULL; + pa_sink_unref(u->sink); + u->sink = NULL; + pa_module_unload_request(u->module, TRUE); } @@ -355,7 +406,7 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s /* If we are added for the first time, ask for a rewinding so that * we are heard right-away. */ if (PA_SINK_INPUT_IS_LINKED(state) && - i->thread_info.state == PA_SINK_INPUT_INIT) { + i->thread_info.state == PA_SINK_INPUT_INIT) { pa_log_debug("Requesting rewind due to state change."); pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); } @@ -371,27 +422,73 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { return u->sink != dest; } +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", + pa_proplist_gets(u->sink->proplist, "device.ladspa.name"), z ? z : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; char *t; - const char *z; pa_sink *master; pa_sink_input_new_data sink_input_data; pa_sink_new_data sink_data; - const char *plugin, *label; + const char *plugin, *label, *input_ladspaport_map, *output_ladspaport_map; LADSPA_Descriptor_Function descriptor_func; + unsigned long input_ladspaport[PA_CHANNELS_MAX], output_ladspaport[PA_CHANNELS_MAX]; const char *e, *cdata; const LADSPA_Descriptor *d; - unsigned long input_port, output_port, p, j, n_control; - unsigned c; + unsigned long p, h, j, n_control, c; pa_bool_t *use_default = NULL; pa_assert(m); - pa_assert(sizeof(LADSPA_Data) == sizeof(float)); + pa_assert_cc(sizeof(LADSPA_Data) == sizeof(float)); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); @@ -421,16 +518,22 @@ int pa__init(pa_module*m) { goto fail; } + if (!(input_ladspaport_map = pa_modargs_get_value(ma, "input_ladspaport_map", NULL))) + pa_log_debug("Using default input ladspa port mapping"); + + if (!(output_ladspaport_map = pa_modargs_get_value(ma, "output_ladspaport_map", NULL))) + pa_log_debug("Using default output ladspa port mapping"); + cdata = pa_modargs_get_value(ma, "control", NULL); u = pa_xnew0(struct userdata, 1); - u->core = m->core; u->module = m; m->userdata = u; - u->master = master; - u->sink = NULL; - u->sink_input = NULL; u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); + u->max_ladspaport_count = 1; /*to avoid division by zero etc. in pa__done when failing before this value has been set*/ + u->channels = 0; + u->input = NULL; + u->output = NULL; if (!(e = getenv("LADSPA_PATH"))) e = LADSPA_PATH; @@ -472,64 +575,127 @@ int pa__init(pa_module*m) { pa_log_debug("Maker: %s", d->Maker); pa_log_debug("Copyright: %s", d->Copyright); - input_port = output_port = (unsigned long) -1; n_control = 0; + u->channels = ss.channels; + /* + * Enumerate ladspa ports + * Default mapping is in order given by the plugin + */ for (p = 0; p < d->PortCount; p++) { + if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) { + pa_log_debug("Port %lu is input: %s", p, d->PortNames[p]); + input_ladspaport[u->input_count] = p; + u->input_count++; + } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) { + pa_log_debug("Port %lu is output: %s", p, d->PortNames[p]); + output_ladspaport[u->output_count] = p; + u->output_count++; + } + } else if (LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]) && LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) { + pa_log_debug("Port %lu is control: %s", p, d->PortNames[p]); + n_control++; + } else + pa_log_debug("Ignored port %s", d->PortNames[p]); + /* XXX: Has anyone ever seen an in-place plugin with non-equal number of input and output ports? */ + /* Could be if the plugin is for up-mixing stereo to 5.1 channels */ + /* Or if the plugin is down-mixing 5.1 to two channel stereo or binaural encoded signal */ + if (u->input_count > u->max_ladspaport_count) + u->max_ladspaport_count = u->input_count; + else + u->max_ladspaport_count = u->output_count; + } + + if (u->channels % u->max_ladspaport_count) { + pa_log("Cannot handle non-integral number of plugins required for given number of channels"); + goto fail; + } - if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + pa_log_debug("Will run %lu plugin instances", u->channels / u->max_ladspaport_count); - if (strcmp(d->PortNames[p], "Input") == 0) { - pa_assert(input_port == (unsigned long) -1); - input_port = p; - } else { - pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]); + /* Parse data for input ladspa port map */ + if (input_ladspaport_map) { + const char *state = NULL; + char *pname; + c = 0; + while ((pname = pa_split(input_ladspaport_map, ",", &state))) { + if (c == u->input_count) { + pa_log("Too many ports in input ladspa port map"); goto fail; } - } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { - if (strcmp(d->PortNames[p], "Output") == 0) { - pa_assert(output_port == (unsigned long) -1); - output_port = p; - } else { - pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]); - goto fail; + for (p = 0; p < d->PortCount; p++) { + if (strcmp(d->PortNames[p], pname) == 0) { + if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p]) && LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) { + input_ladspaport[c] = p; + } else { + pa_log("Port %s is not an audio input ladspa port", pname); + pa_xfree(pname); + goto fail; + } + } } - - } else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) - n_control++; - else { - pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])); - pa_log_debug("Ignored control output port \"%s\".", d->PortNames[p]); + c++; + pa_xfree(pname); } } - if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) { - pa_log("Failed to identify input and output ports. " - "Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. " - "Patches welcome!"); - goto fail; + /* Parse data for output port map */ + if (output_ladspaport_map) { + const char *state = NULL; + char *pname; + c = 0; + while ((pname = pa_split(output_ladspaport_map, ",", &state))) { + if (c == u->output_count) { + pa_log("Too many ports in output ladspa port map"); + goto fail; + } + for (p = 0; p < d->PortCount; p++) { + if (strcmp(d->PortNames[p], pname) == 0) { + if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p]) && LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) { + output_ladspaport[c] = p; + } else { + pa_log("Port %s is not an output ladspa port", pname); + pa_xfree(pname); + goto fail; + } + } + } + c++; + pa_xfree(pname); + } } + u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss); - u->input = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); - if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) - u->output = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); - else + /* Create buffers */ + if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) { + u->input = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->input_count); + for (c = 0; c < u->input_count; c++) + u->input[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); + u->output = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->output_count); + for (c = 0; c < u->output_count; c++) + u->output[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); + } else { + u->input = (LADSPA_Data**) pa_xnew(LADSPA_Data*, (unsigned) u->max_ladspaport_count); + for (c = 0; c < u->max_ladspaport_count; c++) + u->input[c] = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); u->output = u->input; - - u->channels = ss.channels; - - for (c = 0; c < ss.channels; c++) { - if (!(u->handle[c] = d->instantiate(d, ss.rate))) { - pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c); + } + /* Initialize plugin instances */ + for (h = 0; h < (u->channels / u->max_ladspaport_count); h++) { + if (!(u->handle[h] = d->instantiate(d, ss.rate))) { + pa_log("Failed to instantiate plugin %s with label %s", plugin, d->Label); goto fail; } - d->connect_port(u->handle[c], input_port, u->input); - d->connect_port(u->handle[c], output_port, u->output); + for (c = 0; c < u->input_count; c++) + d->connect_port(u->handle[h], input_ladspaport[c], u->input[c]); + for (c = 0; c < u->output_count; c++) + d->connect_port(u->handle[h], output_ladspaport[c], u->output[c]); } if (!cdata && n_control > 0) { @@ -540,7 +706,6 @@ int pa__init(pa_module*m) { if (n_control > 0) { const char *state = NULL; char *k; - unsigned long h; u->control = pa_xnew(LADSPA_Data, (unsigned) n_control); use_default = pa_xnew(pa_bool_t, (unsigned) n_control); @@ -568,7 +733,7 @@ int pa__init(pa_module*m) { } /* The previous loop doesn't take the last control value into account - if it is left empty, so we do it here. */ + if it is left empty, so we do it here. */ if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') { if (p < n_control) use_default[p] = TRUE; @@ -594,7 +759,7 @@ int pa__init(pa_module*m) { continue; if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) { - for (c = 0; c < ss.channels; c++) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) d->connect_port(u->handle[c], p, &u->control_out); continue; } @@ -619,53 +784,53 @@ int pa__init(pa_module*m) { switch (hint & LADSPA_HINT_DEFAULT_MASK) { - case LADSPA_HINT_DEFAULT_MINIMUM: - u->control[h] = lower; - break; - - case LADSPA_HINT_DEFAULT_MAXIMUM: - u->control[h] = upper; - break; - - case LADSPA_HINT_DEFAULT_LOW: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = (LADSPA_Data) exp(log(lower) * 0.75 + log(upper) * 0.25); - else - u->control[h] = (LADSPA_Data) (lower * 0.75 + upper * 0.25); - break; - - case LADSPA_HINT_DEFAULT_MIDDLE: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = (LADSPA_Data) exp(log(lower) * 0.5 + log(upper) * 0.5); - else - u->control[h] = (LADSPA_Data) (lower * 0.5 + upper * 0.5); - break; - - case LADSPA_HINT_DEFAULT_HIGH: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = (LADSPA_Data) exp(log(lower) * 0.25 + log(upper) * 0.75); - else - u->control[h] = (LADSPA_Data) (lower * 0.25 + upper * 0.75); - break; - - case LADSPA_HINT_DEFAULT_0: - u->control[h] = 0; - break; - - case LADSPA_HINT_DEFAULT_1: - u->control[h] = 1; - break; - - case LADSPA_HINT_DEFAULT_100: - u->control[h] = 100; - break; - - case LADSPA_HINT_DEFAULT_440: - u->control[h] = 440; - break; - - default: - pa_assert_not_reached(); + case LADSPA_HINT_DEFAULT_MINIMUM: + u->control[h] = lower; + break; + + case LADSPA_HINT_DEFAULT_MAXIMUM: + u->control[h] = upper; + break; + + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.75 + log(upper) * 0.25); + else + u->control[h] = (LADSPA_Data) (lower * 0.75 + upper * 0.25); + break; + + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.5 + log(upper) * 0.5); + else + u->control[h] = (LADSPA_Data) (lower * 0.5 + upper * 0.5); + break; + + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.25 + log(upper) * 0.75); + else + u->control[h] = (LADSPA_Data) (lower * 0.25 + upper * 0.75); + break; + + case LADSPA_HINT_DEFAULT_0: + u->control[h] = 0; + break; + + case LADSPA_HINT_DEFAULT_1: + u->control[h] = 1; + break; + + case LADSPA_HINT_DEFAULT_100: + u->control[h] = 100; + break; + + case LADSPA_HINT_DEFAULT_440: + u->control[h] = 440; + break; + + default: + pa_assert_not_reached(); } } @@ -674,7 +839,7 @@ int pa__init(pa_module*m) { pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]); - for (c = 0; c < ss.channels; c++) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) d->connect_port(u->handle[c], p, &u->control[h]); h++; @@ -684,7 +849,7 @@ int pa__init(pa_module*m) { } if (d->activate) - for (c = 0; c < u->channels; c++) + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) d->activate(u->handle[c]); /* Create sink */ @@ -693,11 +858,8 @@ int pa__init(pa_module*m) { sink_data.module = m; if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name); - sink_data.namereg_fail = FALSE; pa_sink_new_data_set_sample_spec(&sink_data, &ss); pa_sink_new_data_set_channel_map(&sink_data, &map); - z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin); @@ -713,7 +875,16 @@ int pa__init(pa_module*m) { goto fail; } - u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", d->Name, z ? z : master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, + PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME| + (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))); pa_sink_new_data_done(&sink_data); if (!u->sink) { @@ -721,26 +892,28 @@ int pa__init(pa_module*m) { goto fail; } - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_state = sink_set_state; - u->sink->update_requested_latency = sink_update_requested_latency; - u->sink->request_rewind = sink_request_rewind; + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ pa_sink_input_new_data_init(&sink_input_data); sink_input_data.driver = __FILE__; sink_input_data.module = m; - sink_input_data.sink = u->master; + pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE); + sink_input_data.origin_sink = u->sink; pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream"); pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE); + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); pa_sink_input_new_data_done(&sink_input_data); if (!u->sink_input) @@ -751,18 +924,23 @@ int pa__init(pa_module*m) { u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; u->sink_input->update_max_request = sink_input_update_max_request_cb; u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; u->sink_input->userdata = u; + u->sink->input_to_master = u->sink_input; + pa_sink_put(u->sink); pa_sink_input_put(u->sink_input); pa_modargs_free(ma); - pa_xfree(use_default); return 0; @@ -796,32 +974,51 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ - if (u->sink_input) { + if (u->sink_input) pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) pa_sink_input_unref(u->sink_input); - } - for (c = 0; c < u->channels; c++) + if (u->sink) + pa_sink_unref(u->sink); + + for (c = 0; c < (u->channels / u->max_ladspaport_count); c++) { if (u->handle[c]) { if (u->descriptor->deactivate) u->descriptor->deactivate(u->handle[c]); u->descriptor->cleanup(u->handle[c]); } + } - if (u->output != u->input) - pa_xfree(u->output); + if (u->output == u->input) { + if (u->input != NULL) { + for (c = 0; c < u->max_ladspaport_count; c++) + pa_xfree(u->input[c]); + pa_xfree(u->input); + } + } else { + if (u->input != NULL) { + for (c = 0; c < u->input_count; c++) + pa_xfree(u->input[c]); + pa_xfree(u->input); + } + if (u->output != NULL) { + for (c = 0; c < u->output_count; c++) + pa_xfree(u->output[c]); + pa_xfree(u->output); + } + } if (u->memblockq) pa_memblockq_free(u->memblockq); - pa_xfree(u->input); - pa_xfree(u->control); - pa_xfree(u); } diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index a1a8726f..15f3442d 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -45,12 +45,14 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("LIRC volume control"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>"); +PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name> volume_limit=<volume limit> volume_step=<volume change step>"); static const char* const valid_modargs[] = { "config", "sink", "appname", + "volume_limit", + "volume_step", NULL, }; @@ -61,6 +63,8 @@ struct userdata { char *sink_name; pa_module *module; float mute_toggle_save; + pa_volume_t volume_limit; + pa_volume_t volume_step; }; static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { @@ -112,52 +116,36 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event volchange = RESET; if (volchange == INVALID) - pa_log_warn("Recieved unknown IR code '%s'", name); + pa_log_warn("Received unknown IR code '%s'", name); else { pa_sink *s; if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) pa_log("Failed to get sink '%s'", u->sink_name); else { - int i; - pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE); - -#define DELTA (PA_VOLUME_NORM/20) + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); switch (volchange) { case UP: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] < PA_VOLUME_MAX - DELTA) - cv.values[i] += DELTA; - else - cv.values[i] = PA_VOLUME_MAX; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); + pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case DOWN: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] > DELTA) - cv.values[i] -= DELTA; - else - cv.values[i] = PA_VOLUME_MUTED; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); + pa_cvolume_dec(&cv, u->volume_step); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case MUTE: - pa_sink_set_mute(s, TRUE); + pa_sink_set_mute(s, TRUE, TRUE); break; case RESET: - pa_sink_set_mute(s, FALSE); + pa_sink_set_mute(s, FALSE, TRUE); break; case MUTE_TOGGLE: - - pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE); break; case INVALID: @@ -184,6 +172,8 @@ fail: int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; + pa_volume_t volume_limit = PA_CLAMP_VOLUME(PA_VOLUME_NORM*3/2); + pa_volume_t volume_step = PA_VOLUME_NORM/20; pa_assert(m); @@ -192,6 +182,16 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) { + pa_log("Failed to parse volume limit"); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) { + pa_log("Failed to parse volume step"); + goto fail; + } + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; @@ -199,6 +199,8 @@ int pa__init(pa_module*m) { u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->lirc_fd = -1; u->mute_toggle_save = 0; + u->volume_limit = PA_CLAMP_VOLUME(volume_limit); + u->volume_step = PA_CLAMP_VOLUME(volume_step); if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "pulseaudio"), 1)) < 0) { pa_log("lirc_init() failed."); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c new file mode 100644 index 00000000..cf88267d --- /dev/null +++ b/src/modules/module-loopback.c @@ -0,0 +1,857 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Intel Corporation + Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> + +#include "module-loopback-symdef.h" + +PA_MODULE_AUTHOR("Pierre-Louis Bossart"); +PA_MODULE_DESCRIPTION("Loopback from source to sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source=<source to connect to> " + "sink=<sink to connect to> " + "adjust_time=<how often to readjust rates in s> " + "latency_msec=<latency in ms> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "sink_input_name=<custom name for the sink input> " + "source_output_name=<custom name for the source output> " + "sink_input_role=<media.role for the sink input> " + "source_output_role=<media.role for the source output> " + "source_dont_move=<boolean> " + "sink_dont_move=<boolean> " + "remix=<remix channels?> "); + +#define DEFAULT_LATENCY_MSEC 200 + +#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) + +#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC) + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink_input *sink_input; + pa_source_output *source_output; + + pa_asyncmsgq *asyncmsgq; + pa_memblockq *memblockq; + + pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write; + + pa_time_event *time_event; + pa_usec_t adjust_time; + + int64_t recv_counter; + int64_t send_counter; + + size_t skip; + pa_usec_t latency; + + pa_bool_t in_pop; + size_t min_memblockq_length; + + struct { + int64_t send_counter; + size_t source_output_buffer; + pa_usec_t source_latency; + + int64_t recv_counter; + size_t sink_input_buffer; + pa_usec_t sink_latency; + + size_t min_memblockq_length; + size_t max_request; + } latency_snapshot; +}; + +static const char* const valid_modargs[] = { + "source", + "sink", + "adjust_time", + "latency_msec", + "format", + "rate", + "channels", + "channel_map", + "sink_input_name", + "source_output_name", + "sink_input_role", + "source_output_role", + "source_dont_move", + "sink_dont_move", + "remix", + NULL, +}; + +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, + SINK_INPUT_MESSAGE_REWIND, + SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, + SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED +}; + +enum { + SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT +}; + +/* Called from main context */ +static void teardown(struct userdata *u) { + pa_assert(u); + pa_assert_ctl_context(); + + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->source_output) + pa_source_output_unlink(u->source_output); + + if (u->sink_input) { + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + } + + if (u->source_output) { + pa_source_output_unref(u->source_output); + u->source_output = NULL; + } +} + +/* Called from main context */ +static void adjust_rates(struct userdata *u) { + size_t buffer, fs; + uint32_t old_rate, base_rate, new_rate; + pa_usec_t buffer_latency; + + pa_assert(u); + pa_assert_ctl_context(); + + pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, NULL, 0, NULL); + + buffer = + u->latency_snapshot.sink_input_buffer + + u->latency_snapshot.source_output_buffer; + + if (u->latency_snapshot.recv_counter <= u->latency_snapshot.send_counter) + buffer += (size_t) (u->latency_snapshot.send_counter - u->latency_snapshot.recv_counter); + else + buffer += PA_CLIP_SUB(buffer, (size_t) (u->latency_snapshot.recv_counter - u->latency_snapshot.send_counter)); + + buffer_latency = pa_bytes_to_usec(buffer, &u->sink_input->sample_spec); + + pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms", + (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC, + (double) buffer_latency / PA_USEC_PER_MSEC, + (double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC, + ((double) u->latency_snapshot.sink_latency + buffer_latency + u->latency_snapshot.source_latency) / PA_USEC_PER_MSEC); + + pa_log_debug("Should buffer %zu bytes, buffered at minimum %zu bytes", + u->latency_snapshot.max_request*2, + u->latency_snapshot.min_memblockq_length); + + fs = pa_frame_size(&u->sink_input->sample_spec); + old_rate = u->sink_input->sample_spec.rate; + base_rate = u->source_output->sample_spec.rate; + + if (u->latency_snapshot.min_memblockq_length < u->latency_snapshot.max_request*2) + new_rate = base_rate - (((u->latency_snapshot.max_request*2 - u->latency_snapshot.min_memblockq_length) / fs) *PA_USEC_PER_SEC)/u->adjust_time; + else + new_rate = base_rate + (((u->latency_snapshot.min_memblockq_length - u->latency_snapshot.max_request*2) / fs) *PA_USEC_PER_SEC)/u->adjust_time; + + if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) { + pa_log_warn("Sample rates too different, not adjusting (%u vs. %u).", base_rate, new_rate); + new_rate = base_rate; + } else { + if (base_rate < new_rate + 20 && new_rate < base_rate + 20) + new_rate = base_rate; + /* Do the adjustment in small steps; 2‰ can be considered inaudible */ + if (new_rate < (uint32_t) (old_rate*0.998) || new_rate > (uint32_t) (old_rate*1.002)) { + pa_log_info("New rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", new_rate, old_rate); + new_rate = PA_CLAMP(new_rate, (uint32_t) (old_rate*0.998), (uint32_t) (old_rate*1.002)); + } + } + + pa_sink_input_set_rate(u->sink_input, new_rate); + pa_log_debug("[%s] Updated sampling rate to %lu Hz.", u->sink_input->sink->name, (unsigned long) new_rate); + + pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time); +} + +/* Called from main context */ +static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + + adjust_rates(u); +} + +/* Called from input thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + pa_memchunk copy; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (u->skip > chunk->length) { + u->skip -= chunk->length; + return; + } + + if (u->skip > 0) { + copy = *chunk; + copy.index += u->skip; + copy.length -= u->skip; + u->skip = 0; + + chunk = © + } + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, chunk, NULL); + u->send_counter += (int64_t) chunk->length; +} + +/* Called from input thread context */ +static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); + u->send_counter -= (int64_t) nbytes; +} + +/* Called from output thread context */ +static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + length = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq); + + u->latency_snapshot.send_counter = u->send_counter; + u->latency_snapshot.source_output_buffer = u->source_output->thread_info.resampler ? pa_resampler_result(u->source_output->thread_info.resampler, length) : length; + u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source); + + return 0; + } + } + + return pa_source_output_process_msg(obj, code, data, offset, chunk); +} + +/* Called from output thread context */ +static void source_output_attach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + o->source->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); +} + +/* Called from output thread context */ +static void source_output_detach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (u->rtpoll_item_write) { + pa_rtpoll_item_free(u->rtpoll_item_write); + u->rtpoll_item_write = NULL; + } +} + +/* Called from output thread context */ +static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT) { + + u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source), + u->latency), + &o->sample_spec); + + pa_log_info("Skipping %lu bytes", (unsigned long) u->skip); + } +} + +/* Called from main thread */ +static void source_output_kill_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + teardown(u); + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + return dest != u->sink_input->sink->monitor_source; +} + +/* Called from main thread */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + pa_proplist *p; + const char *n; + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + p = pa_proplist_new(); + pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback of %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n); + + pa_sink_input_update_proplist(u->sink_input, PA_UPDATE_REPLACE, p); + pa_proplist_free(p); +} + +/* Called from output thread context */ +static void update_min_memblockq_length(struct userdata *u) { + size_t length; + + pa_assert(u); + pa_sink_input_assert_io_context(u->sink_input); + + length = pa_memblockq_get_length(u->memblockq); + + if (u->min_memblockq_length == (size_t) -1 || + length < u->min_memblockq_length) + u->min_memblockq_length = length; +} + +/* Called from output thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + pa_assert(chunk); + + u->in_pop = TRUE; + while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0) + ; + u->in_pop = FALSE; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + pa_log_info("Could not peek into queue"); + return -1; + } + + chunk->length = PA_MIN(chunk->length, nbytes); + pa_memblockq_drop(u->memblockq, chunk->length); + + update_min_memblockq_length(u); + + return 0; +} + +/* Called from output thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + pa_sink_input_assert_io_context(u->sink_input); + + *r = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + pa_sink_input_assert_io_context(u->sink_input); + + if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(u->memblockq, chunk); + else + pa_memblockq_flush_write(u->memblockq, TRUE); + + update_min_memblockq_length(u); + + /* Is this the end of an underrun? Then let's start things + * right-away */ + if (!u->in_pop && + u->sink_input->thread_info.underrun_for > 0 && + pa_memblockq_is_readable(u->memblockq)) { + + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(u->sink_input, + (size_t) (u->sink_input->thread_info.underrun_for == (size_t) -1 ? 0 : u->sink_input->thread_info.underrun_for), + FALSE, TRUE, FALSE); + } + + u->recv_counter += (int64_t) chunk->length; + + return 0; + + case SINK_INPUT_MESSAGE_REWIND: + + pa_sink_input_assert_io_context(u->sink_input); + + if (PA_SINK_IS_OPENED(u->sink_input->sink->thread_info.state)) + pa_memblockq_seek(u->memblockq, -offset, PA_SEEK_RELATIVE, TRUE); + else + pa_memblockq_flush_write(u->memblockq, TRUE); + + u->recv_counter -= offset; + + update_min_memblockq_length(u); + + return 0; + + case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: { + size_t length; + + update_min_memblockq_length(u); + + length = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq); + + u->latency_snapshot.recv_counter = u->recv_counter; + u->latency_snapshot.sink_input_buffer = + pa_memblockq_get_length(u->memblockq) + + (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, length) : length); + u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink); + + u->latency_snapshot.max_request = pa_sink_input_get_max_request(u->sink_input); + + u->latency_snapshot.min_memblockq_length = u->min_memblockq_length; + u->min_memblockq_length = (size_t) -1; + + return 0; + } + + case SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED: { + /* This message is sent from the IO thread to the main + * thread! So don't be confused. All the user cases above + * are executed in thread context, but this one is not! */ + + pa_assert_ctl_context(); + + if (u->adjust_time > 0) + adjust_rates(u); + return 0; + } + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} + +/* Called from output thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( + i->sink->thread_info.rtpoll, + PA_RTPOLL_LATE, + u->asyncmsgq); + + pa_memblockq_set_prebuf(u->memblockq, pa_sink_input_get_max_request(i)*2); + pa_memblockq_set_maxrewind(u->memblockq, pa_sink_input_get_max_rewind(i)); + + u->min_memblockq_length = (size_t) -1; +} + +/* Called from output thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + if (u->rtpoll_item_read) { + pa_rtpoll_item_free(u->rtpoll_item_read); + u->rtpoll_item_read = NULL; + } +} + +/* Called from output thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +/* Called from output thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_prebuf(u->memblockq, nbytes*2); + pa_log_info("Max request changed"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_MAX_REQUEST_CHANGED, NULL, 0, NULL, NULL); +} + +/* Called from main thread */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + teardown(u); + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + pa_proplist *p; + const char *n; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + p = pa_proplist_new(); + pa_proplist_setf(p, PA_PROP_MEDIA_NAME, "Loopback to %s", pa_strnull(pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if ((n = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(p, PA_PROP_MEDIA_ICON_NAME, n); + + pa_source_output_update_proplist(u->source_output, PA_UPDATE_REPLACE, p); + pa_proplist_free(p); +} + +/* Called from main thread */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert_se(u = i->userdata); + + if (!u->source_output->source->monitor_of) + return TRUE; + + return dest != u->source_output->source->monitor_of; +} + +int pa__init(pa_module *m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_sink *sink; + pa_sink_input_new_data sink_input_data; + pa_bool_t sink_dont_move; + pa_source *source; + pa_source_output_new_data source_output_data; + pa_bool_t source_dont_move; + uint32_t latency_msec; + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk silence; + uint32_t adjust_time_sec; + const char *n; + pa_bool_t remix = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!(source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE))) { + pa_log("No such source."); + goto fail; + } + + if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK))) { + pa_log("No such sink."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + goto fail; + } + + ss = sink->sample_spec; + map = sink->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + latency_msec = DEFAULT_LATENCY_MSEC; + if (pa_modargs_get_value_u32(ma, "latency_msec", &latency_msec) < 0 || latency_msec < 1 || latency_msec > 2000) { + pa_log("Invalid latency specification"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->latency = (pa_usec_t) latency_msec * PA_USEC_PER_MSEC; + + adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; + if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { + pa_log("Failed to parse adjust_time value"); + goto fail; + } + + if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC) + u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC; + else + u->adjust_time = DEFAULT_ADJUST_TIME_USEC; + + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, sink, FALSE); + + if ((n = pa_modargs_get_value(ma, "sink_input_name", NULL))) + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, n); + else + pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Loopback from %s", + pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if ((n = pa_modargs_get_value(ma, "sink_input_role", NULL))) + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, n); + else + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + + if ((n = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ICON_NAME, n); + + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX); + + sink_dont_move = FALSE; + if (pa_modargs_get_value_boolean(ma, "sink_dont_move", &sink_dont_move) < 0) { + pa_log("sink_dont_move= expects a boolean argument."); + goto fail; + } + + if (sink_dont_move) + sink_input_data.flags |= PA_SINK_INPUT_DONT_MOVE; + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->userdata = u; + + pa_sink_input_set_requested_latency(u->sink_input, u->latency/3); + + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + pa_source_output_new_data_set_source(&source_output_data, source, FALSE); + + if ((n = pa_modargs_get_value(ma, "source_output_name", NULL))) + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, n); + else + pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Loopback to %s", + pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + + if ((n = pa_modargs_get_value(ma, "source_output_role", NULL))) + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, n); + else + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + + if ((n = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME))) + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ICON_NAME, n); + + pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); + pa_source_output_new_data_set_channel_map(&source_output_data, &map); + source_output_data.flags = (remix ? 0 : PA_SOURCE_OUTPUT_NO_REMIX); + + source_dont_move = FALSE; + if (pa_modargs_get_value_boolean(ma, "source_dont_move", &source_dont_move) < 0) { + pa_log("source_dont_move= expects a boolean argument."); + goto fail; + } + + if (source_dont_move) + source_output_data.flags |= PA_SOURCE_OUTPUT_DONT_MOVE; + + pa_source_output_new(&u->source_output, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!u->source_output) + goto fail; + + u->source_output->parent.process_msg = source_output_process_msg_cb; + u->source_output->push = source_output_push_cb; + u->source_output->process_rewind = source_output_process_rewind_cb; + u->source_output->kill = source_output_kill_cb; + u->source_output->attach = source_output_attach_cb; + u->source_output->detach = source_output_detach_cb; + u->source_output->state_change = source_output_state_change_cb; + u->source_output->may_move_to = source_output_may_move_to_cb; + u->source_output->moving = source_output_moving_cb; + u->source_output->userdata = u; + + pa_source_output_set_requested_latency(u->source_output, u->latency/3); + + pa_sink_input_get_silence(u->sink_input, &silence); + u->memblockq = pa_memblockq_new( + 0, /* idx */ + MEMBLOCKQ_MAXLENGTH, /* maxlength */ + MEMBLOCKQ_MAXLENGTH, /* tlength */ + pa_frame_size(&ss), /* base */ + 0, /* prebuf */ + 0, /* minreq */ + 0, /* maxrewind */ + &silence); /* silence frame */ + pa_memblock_unref(silence.memblock); + + u->asyncmsgq = pa_asyncmsgq_new(0); + + pa_sink_input_put(u->sink_input); + pa_source_output_put(u->source_output); + + if (u->adjust_time > 0) + u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u); + + pa_modargs_free(ma); + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + teardown(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->asyncmsgq) + pa_asyncmsgq_unref(u->asyncmsgq); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + pa_xfree(u); +} diff --git a/src/modules/module-match.c b/src/modules/module-match.c index 625f2a8b..c94ef790 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -27,10 +27,15 @@ #include <string.h> #include <errno.h> #include <sys/types.h> -#include <regex.h> #include <stdio.h> #include <stdlib.h> +#if defined(HAVE_REGEX_H) +#include <regex.h> +#elif defined(HAVE_PCREPOSIX_H) +#include <pcreposix.h> +#endif + #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> @@ -38,7 +43,6 @@ #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulsecore/core-subscribe.h> #include <pulsecore/sink-input.h> #include <pulsecore/core-util.h> @@ -56,6 +60,9 @@ PA_MODULE_USAGE("table=<filename> " #define DEFAULT_MATCH_TABLE_FILE PA_DEFAULT_CONFIG_DIR"/match.table" #define DEFAULT_MATCH_TABLE_FILE_USER "match.table" +#define UPDATE_REPLACE "replace" +#define UPDATE_MERGE "merge" + static const char* const valid_modargs[] = { "table", "key", @@ -66,13 +73,14 @@ struct rule { regex_t regex; pa_volume_t volume; pa_proplist *proplist; + pa_update_mode_t mode; struct rule *next; }; struct userdata { struct rule *rules; char *property_key; - pa_subscription *subscription; + pa_hook_slot *sink_input_new_hook_slot; }; static int load_rules(struct userdata *u, const char *filename) { @@ -85,12 +93,11 @@ static int load_rules(struct userdata *u, const char *filename) { pa_assert(u); if (filename) - f = fopen(fn = pa_xstrdup(filename), "r"); + f = pa_fopen_cloexec(fn = pa_xstrdup(filename), "r"); else f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); if (!f) { - pa_xfree(fn); pa_log("Failed to open file config file: %s", pa_cstrerror(errno)); goto finish; } @@ -98,13 +105,14 @@ static int load_rules(struct userdata *u, const char *filename) { pa_lock_fd(fileno(f), 1); while (!feof(f)) { - char *d, *v; + char *token_end, *value_str; pa_volume_t volume = PA_VOLUME_NORM; uint32_t k; regex_t regex; char ln[256]; struct rule *rule; pa_proplist *proplist = NULL; + pa_update_mode_t mode = (pa_update_mode_t) -1; if (!fgets(ln, sizeof(ln), f)) break; @@ -116,51 +124,72 @@ static int load_rules(struct userdata *u, const char *filename) { if (ln[0] == '#' || !*ln ) continue; - d = ln+strcspn(ln, WHITESPACE); - v = d+strspn(d, WHITESPACE); + token_end = ln + strcspn(ln, WHITESPACE); + value_str = token_end + strspn(token_end, WHITESPACE); + *token_end = 0; + if (!*ln) { + pa_log("[%s:%u] failed to parse line - missing regexp", fn, n); + goto finish; + } - if (!*v) { - pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n); + if (!*value_str) { + pa_log("[%s:%u] failed to parse line - too few words", fn, n); goto finish; } - *d = 0; - if (pa_atou(v, &k) >= 0) { - volume = (pa_volume_t) k; - } else if (*v == '"') { - char *e; - - e = strchr(v+1, '"'); - if (!e) { - pa_log(__FILE__ ": [%s:%u] failed to parse line - missing role closing quote", filename, n); - goto finish; - } - - *e = '\0'; - e = pa_sprintf_malloc("media.role=\"%s\"", v+1); - proplist = pa_proplist_from_string(e); - pa_xfree(e); - } else { - char *e; - - e = v+strspn(v, WHITESPACE); - if (!*e) { - pa_log(__FILE__ ": [%s:%u] failed to parse line - missing end of property list", filename, n); - goto finish; - } - *e = '\0'; - proplist = pa_proplist_from_string(v); + if (pa_atou(value_str, &k) >= 0) + volume = (pa_volume_t) PA_CLAMP_VOLUME(k); + else { + size_t len; + + token_end = value_str + strcspn(value_str, WHITESPACE); + + len = token_end - value_str; + if (len == (sizeof(UPDATE_REPLACE) - 1) && !strncmp(value_str, UPDATE_REPLACE, len)) + mode = PA_UPDATE_REPLACE; + else if (len == (sizeof(UPDATE_MERGE) - 1) && !strncmp(value_str, UPDATE_MERGE, len)) + mode = PA_UPDATE_MERGE; + + if (mode != (pa_update_mode_t) -1) { + value_str = token_end + strspn(token_end, WHITESPACE); + + if (!*value_str) { + pa_log("[%s:%u] failed to parse line - too few words", fn, n); + goto finish; + } + } else + mode = PA_UPDATE_MERGE; + + if (*value_str == '"') { + value_str++; + + token_end = strchr(value_str, '"'); + if (!token_end) { + pa_log("[%s:%u] failed to parse line - missing role closing quote", fn, n); + goto finish; + } + } else + token_end = value_str + strcspn(value_str, WHITESPACE); + + *token_end = 0; + + value_str = pa_sprintf_malloc("media.role=\"%s\"", value_str); + proplist = pa_proplist_from_string(value_str); + pa_xfree(value_str); } if (regcomp(®ex, ln, REG_EXTENDED|REG_NOSUB) != 0) { - pa_log("[%s:%u] invalid regular expression", filename, n); + pa_log("[%s:%u] invalid regular expression", fn, n); + if (proplist) + pa_proplist_free(proplist); goto finish; } rule = pa_xnew(struct rule, 1); rule->regex = regex; rule->proplist = proplist; + rule->mode = mode; rule->volume = volume; rule->next = NULL; @@ -169,8 +198,6 @@ static int load_rules(struct userdata *u, const char *filename) { else u->rules = rule; end = rule; - - *d = 0; } ret = 0; @@ -181,29 +208,20 @@ finish: fclose(f); } - if (fn) - pa_xfree(fn); + pa_xfree(fn); return ret; } -static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { - struct userdata *u = userdata; - pa_sink_input *si; +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *si, struct userdata *u) { struct rule *r; const char *n; pa_assert(c); pa_assert(u); - if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW)) - return; - - if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) - return; - if (!(n = pa_proplist_gets(si->proplist, u->property_key))) - return; + return PA_HOOK_OK; pa_log_debug("Matching with %s", n); @@ -211,15 +229,18 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v if (!regexec(&r->regex, n, 0, NULL, 0)) { if (r->proplist) { pa_log_debug("updating proplist of sink input '%s'", n); - pa_proplist_update(si->proplist, PA_UPDATE_MERGE, r->proplist); - } else { + pa_proplist_update(si->proplist, r->mode, r->proplist); + } else if (si->volume_writable) { pa_cvolume cv; pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); - pa_sink_input_set_volume(si, &cv, TRUE, TRUE); - } + pa_sink_input_new_data_set_volume(si, &cv); + } else + pa_log_debug("the volume of sink input '%s' is not writable, can't change it", n); } } + + return PA_HOOK_OK; } int pa__init(pa_module*m) { @@ -233,9 +254,8 @@ int pa__init(pa_module*m) { goto fail; } - u = pa_xnew(struct userdata, 1); + u = pa_xnew0(struct userdata, 1); u->rules = NULL; - u->subscription = NULL; m->userdata = u; u->property_key = pa_xstrdup(pa_modargs_get_value(ma, "key", PA_PROP_MEDIA_NAME)); @@ -243,7 +263,8 @@ int pa__init(pa_module*m) { if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) goto fail; - u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); + /* hook EARLY - 1, to match before stream-restore */ + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY - 1, (pa_hook_cb_t) sink_input_new_hook_callback, u); pa_modargs_free(ma); return 0; @@ -253,7 +274,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { @@ -265,8 +286,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->subscription) - pa_subscription_free(u->subscription); + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); if (u->property_key) pa_xfree(u->property_key); diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index d8b9c79e..4e89aed9 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -48,13 +48,15 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE("device=<evdev device> sink=<sink name>"); +PA_MODULE_USAGE("device=<evdev device> sink=<sink name> volume_limit=<volume limit> volume_step=<volume change step>"); #define DEFAULT_DEVICE "/dev/input/event0" static const char* const valid_modargs[] = { "device", "sink", + "volume_limit", + "volume_step", NULL, }; @@ -63,6 +65,8 @@ struct userdata { pa_io_event *io; char *sink_name; pa_module *module; + pa_volume_t volume_limit; + pa_volume_t volume_step; }; static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { @@ -85,14 +89,27 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event } if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) { - enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID; + enum { + INVALID, + UP, + DOWN, + MUTE_TOGGLE + } volchange = INVALID; pa_log_debug("Key code=%u, value=%u", ev.code, ev.value); switch (ev.code) { - case KEY_VOLUMEDOWN: volchange = DOWN; break; - case KEY_VOLUMEUP: volchange = UP; break; - case KEY_MUTE: volchange = MUTE_TOGGLE; break; + case KEY_VOLUMEDOWN: + volchange = DOWN; + break; + + case KEY_VOLUMEUP: + volchange = UP; + break; + + case KEY_MUTE: + volchange = MUTE_TOGGLE; + break; } if (volchange != INVALID) { @@ -101,37 +118,21 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) pa_log("Failed to get sink '%s'", u->sink_name); else { - int i; - pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE); - -#define DELTA (PA_VOLUME_NORM/20) + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); switch (volchange) { case UP: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] < PA_VOLUME_MAX - DELTA) - cv.values[i] += DELTA; - else - cv.values[i] = PA_VOLUME_MAX; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); + pa_cvolume_inc_clamp(&cv, u->volume_step, u->volume_limit); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case DOWN: - for (i = 0; i < cv.channels; i++) { - if (cv.values[i] > DELTA) - cv.values[i] -= DELTA; - else - cv.values[i] = PA_VOLUME_MUTED; - } - - pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); + pa_cvolume_dec(&cv, u->volume_step); + pa_sink_set_volume(s, &cv, TRUE, TRUE); break; case MUTE_TOGGLE: - - pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); + pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE); break; case INVALID: @@ -161,6 +162,8 @@ int pa__init(pa_module*m) { struct input_id input_id; char name[256]; uint8_t evtype_bitmask[EV_MAX/8 + 1]; + pa_volume_t volume_limit = PA_VOLUME_NORM*3/2; + pa_volume_t volume_step = PA_VOLUME_NORM/20; pa_assert(m); @@ -169,14 +172,26 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_u32(ma, "volume_limit", &volume_limit) < 0) { + pa_log("Failed to parse volume limit"); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "volume_step", &volume_step) < 0) { + pa_log("Failed to parse volume step"); + goto fail; + } + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->io = NULL; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->fd = -1; u->fd_type = 0; + u->volume_limit = PA_CLAMP_VOLUME(volume_limit); + u->volume_step = PA_CLAMP_VOLUME(volume_step); - if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY, 0)) < 0) { pa_log("Failed to open evdev device: %s", pa_cstrerror(errno)); goto fail; } diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 30a99ca7..bedd6efc 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -24,33 +24,29 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> -#include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> +#include <pulse/i18n.h> #include <pulsecore/macro.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> -#include <pulsecore/core-error.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h> #include "module-null-sink-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Clocked NULL sink"); +PA_MODULE_DESCRIPTION(_("Clocked NULL sink")); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( @@ -84,7 +80,6 @@ static const char* const valid_modargs[] = { "rate", "channels", "channel_map", - "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -101,14 +96,14 @@ static int sink_process_msg( case PA_SINK_MESSAGE_SET_STATE: if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) - u->timestamp = pa_rtclock_usec(); + u->timestamp = pa_rtclock_now(); break; case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t now; - now = pa_rtclock_usec(); + now = pa_rtclock_now(); *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0ULL; return 0; @@ -208,9 +203,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - u->timestamp = pa_rtclock_usec(); + u->timestamp = pa_rtclock_now(); for (;;) { int ret; @@ -219,7 +213,7 @@ static void thread_func(void *userdata) { if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { pa_usec_t now; - now = pa_rtclock_usec(); + now = pa_rtclock_now(); if (u->sink->thread_info.rewind_requested) { if (u->sink->thread_info.rewind_nbytes > 0) @@ -287,7 +281,7 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, _("Null Output")); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { @@ -316,7 +310,7 @@ int pa__init(pa_module*m) { pa_sink_set_max_rewind(u->sink, nbytes); pa_sink_set_max_request(u->sink, nbytes); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("null-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/module-null-source.c b/src/modules/module-null-source.c new file mode 100644 index 00000000..b2981c34 --- /dev/null +++ b/src/modules/module-null-source.c @@ -0,0 +1,288 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/modargs.h> +#include <pulsecore/module.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/source.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/thread.h> + +#include "module-null-source-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering & Marc-Andre Lureau"); +PA_MODULE_DESCRIPTION("Clocked NULL source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "source_name=<name of source> " + "channel_map=<channel map> " + "description=<description for the source> " + "latency_time=<latency time in ms>"); + +#define DEFAULT_SOURCE_NAME "source.null" +#define DEFAULT_LATENCY_TIME 20 +#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2) + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + size_t block_size; + + pa_usec_t block_usec; + pa_usec_t timestamp; + pa_usec_t latency_time; +}; + +static const char* const valid_modargs[] = { + "rate", + "format", + "channels", + "source_name", + "channel_map", + "description", + "latency_time", + NULL +}; + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + case PA_SOURCE_MESSAGE_SET_STATE: + + if (PA_PTR_TO_UINT(data) == PA_SOURCE_RUNNING) + u->timestamp = pa_rtclock_now(); + + break; + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t now; + + now = pa_rtclock_now(); + *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0; + + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static void source_update_requested_latency_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + u = s->userdata; + pa_assert(u); + + u->block_usec = pa_source_get_requested_latency_within_thread(s); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + + u->timestamp = pa_rtclock_now(); + + for (;;) { + int ret; + + /* Generate some null data */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + pa_usec_t now; + pa_memchunk chunk; + + now = pa_rtclock_now(); + + if ((chunk.length = pa_usec_to_bytes(now - u->timestamp, &u->source->sample_spec)) > 0) { + + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); /* or chunk.length? */ + chunk.index = 0; + pa_source_post(u->source, &chunk); + pa_memblock_unref(chunk.memblock); + + u->timestamp = now; + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp + u->latency_time * PA_USEC_PER_MSEC); + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + pa_source_new_data data; + uint32_t latency_time = DEFAULT_LATENCY_TIME; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Input")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY | PA_SOURCE_DYNAMIC_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source object."); + goto fail; + } + + u->latency_time = DEFAULT_LATENCY_TIME; + if (pa_modargs_get_value_u32(ma, "latency_time", &latency_time) < 0) { + pa_log("Failed to parse latency_time value."); + goto fail; + } + u->latency_time = latency_time; + + u->source->parent.process_msg = source_process_msg; + u->source->update_requested_latency = source_update_requested_latency_cb; + u->source->userdata = u; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + pa_source_set_latency_range(u->source, 0, MAX_LATENCY_USEC); + u->block_usec = u->source->thread_info.max_latency; + + u->source->thread_info.max_rewind = + pa_usec_to_bytes(u->block_usec, &u->source->sample_spec); + + if (!(u->thread = pa_thread_new("null-source", thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_source_put(u->source); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); +} diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index 5b0f6414..91e01f99 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -27,12 +27,13 @@ #include <sys/stat.h> #include <stdio.h> #include <errno.h> -#include <string.h> #include <fcntl.h> #include <unistd.h> -#include <limits.h> #include <sys/ioctl.h> -#include <poll.h> + +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif #include <pulse/xmalloc.h> @@ -45,6 +46,7 @@ #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> +#include <pulsecore/poll.h> #include "module-pipe-sink-symdef.h" @@ -57,7 +59,7 @@ PA_MODULE_USAGE( "sink_properties=<properties for the sink> " "file=<path of the FIFO> " "format=<sample format> " - "rate=<sample rate>" + "rate=<sample rate> " "channels=<number of channels> " "channel_map=<channel map>"); @@ -101,9 +103,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { size_t n = 0; - int l; #ifdef FIONREAD + int l; + if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0) n = (size_t) l; #endif @@ -122,7 +125,7 @@ static int process_render(struct userdata *u) { pa_assert(u); if (u->memchunk.length <= 0) - pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + pa_sink_render(u->sink, pa_pipe_buf(u->fd), &u->memchunk); pa_assert(u->memchunk.length > 0); @@ -170,7 +173,6 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { struct pollfd *pollfd; @@ -254,12 +256,11 @@ int pa__init(pa_module*m) { u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); - if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) { pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(u->fd); pa_make_fd_nonblock(u->fd); if (fstat(u->fd, &st) < 0) { @@ -300,15 +301,15 @@ int pa__init(pa_module*m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_max_request(u->sink, PIPE_BUF); - pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(PIPE_BUF, &u->sink->sample_spec)); + pa_sink_set_max_request(u->sink, pa_pipe_buf(u->fd)); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(pa_pipe_buf(u->fd), &u->sink->sample_spec)); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->fd = u->fd; pollfd->events = pollfd->revents = 0; - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("pipe-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -359,7 +360,7 @@ void pa__done(pa_module*m) { pa_sink_unref(u->sink); if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + pa_memblock_unref(u->memchunk.memblock); if (u->rtpoll_item) pa_rtpoll_item_free(u->rtpoll_item); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 61c9fc0e..a941f088 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -27,12 +27,13 @@ #include <sys/stat.h> #include <stdio.h> #include <errno.h> -#include <string.h> #include <fcntl.h> #include <unistd.h> -#include <limits.h> #include <sys/ioctl.h> -#include <sys/poll.h> + +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif #include <pulse/xmalloc.h> @@ -45,6 +46,7 @@ #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> +#include <pulsecore/poll.h> #include "module-pipe-source-symdef.h" @@ -105,9 +107,10 @@ static int source_process_msg( case PA_SOURCE_MESSAGE_GET_LATENCY: { size_t n = 0; - int l; #ifdef FIONREAD + int l; + if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0) n = (size_t) l; #endif @@ -129,7 +132,6 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; @@ -143,7 +145,7 @@ static void thread_func(void *userdata) { void *p; if (!u->memchunk.memblock) { - u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF); + u->memchunk.memblock = pa_memblock_new(u->core->mempool, pa_pipe_buf(u->fd)); u->memchunk.index = u->memchunk.length = 0; } @@ -160,7 +162,7 @@ static void thread_func(void *userdata) { if (errno == EINTR) continue; else if (errno != EAGAIN) { - pa_log("Faile to read data from FIFO: %s", pa_cstrerror(errno)); + pa_log("Failed to read data from FIFO: %s", pa_cstrerror(errno)); goto fail; } @@ -239,12 +241,11 @@ int pa__init(pa_module*m) { u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); - if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + if ((u->fd = pa_open_cloexec(u->filename, O_RDWR, 0)) < 0) { pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); goto fail; } - pa_make_fd_cloexec(u->fd); pa_make_fd_nonblock(u->fd); if (fstat(u->fd, &st) < 0) { @@ -292,7 +293,7 @@ int pa__init(pa_module*m) { pollfd->fd = u->fd; pollfd->events = pollfd->revents = 0; - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("pipe-source", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/module-position-event-sounds.c b/src/modules/module-position-event-sounds.c index e191ec33..091453a4 100644 --- a/src/modules/module-position-event-sounds.c +++ b/src/modules/module-position-event-sounds.c @@ -24,18 +24,15 @@ #endif #include <unistd.h> -#include <string.h> #include <errno.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> -#include <ctype.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> #include <pulse/channelmap.h> -#include <pulsecore/core-error.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> @@ -57,35 +54,85 @@ struct userdata { pa_hook_slot *sink_input_fixate_hook_slot; }; +static int parse_pos(const char *pos, double *f) { + + if (pa_atod(pos, f) < 0) { + pa_log_warn("Failed to parse hpos/vpos property '%s'.", pos); + return -1; + } + + if (*f < 0.0 || *f > 1.0) { + pa_log_debug("Property hpos/vpos out of range %0.2f", *f); + + *f = PA_CLAMP(*f, 0.0, 1.0); + } + + return 0; +} + static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) { - const char *hpos; + const char *hpos, *vpos, *role, *id; double f; char t[PA_CVOLUME_SNPRINT_MAX]; pa_cvolume v; pa_assert(data); - if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + if (!(role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE))) return PA_HOOK_OK; - if (pa_atod(hpos, &f) < 0) { - pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos); + if (!pa_streq(role, "event")) return PA_HOOK_OK; + + if ((id = pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID))) { + + /* The test sounds should never be positioned in space, since + * they might be trigered themselves to configure the speakers + * in space, which we don't want to mess up. */ + + if (pa_startswith(id, "audio-channel-")) + return PA_HOOK_OK; + + if (pa_streq(id, "audio-volume-change")) + return PA_HOOK_OK; + + if (pa_streq(id, "audio-test-signal")) + return PA_HOOK_OK; } - if (f < 0.0 || f > 1.0) { - pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f); + if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + hpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_HPOS); + + if (!(vpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_VPOS))) + vpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_VPOS); + + if (!hpos && !vpos) return PA_HOOK_OK; + + pa_cvolume_reset(&v, data->sink->sample_spec.channels); + + if (hpos) { + if (parse_pos(hpos, &f) < 0) + return PA_HOOK_OK; + + if (pa_channel_map_can_balance(&data->sink->channel_map)) { + pa_log_debug("Positioning event sound '%s' horizontally at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + pa_cvolume_set_balance(&v, &data->sink->channel_map, f*2.0-1.0); + } } - pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + if (vpos) { + if (parse_pos(vpos, &f) < 0) + return PA_HOOK_OK; - pa_cvolume_reset(&v, data->sample_spec.channels); - pa_cvolume_set_balance(&v, &data->channel_map, f*2.0-1.0); + if (pa_channel_map_can_fade(&data->sink->channel_map)) { + pa_log_debug("Positioning event sound '%s' vertically at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + pa_cvolume_set_fade(&v, &data->sink->channel_map, f*2.0-1.0); + } + } pa_log_debug("Final volume factor %s.", pa_cvolume_snprint(t, sizeof(t), &v)); - - pa_sink_input_new_data_apply_volume_factor(data, &v); + pa_sink_input_new_data_apply_volume_factor_sink(data, &v); return PA_HOOK_OK; } @@ -114,7 +161,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index 5b351d19..e1bf3977 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -24,27 +24,19 @@ #include <config.h> #endif -#include <string.h> #include <errno.h> #include <stdio.h> #include <unistd.h> -#include <limits.h> -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> -#endif -#ifdef HAVE_ARPA_INET_H -#include <arpa/inet.h> -#endif #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif #include <pulse/xmalloc.h> -#include <pulsecore/winsock.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> +#include <pulsecore/socket.h> #include <pulsecore/socket-server.h> #include <pulsecore/socket-util.h> #include <pulsecore/core-util.h> @@ -52,6 +44,7 @@ #include <pulsecore/log.h> #include <pulsecore/native-common.h> #include <pulsecore/creds.h> +#include <pulsecore/arpa-inet.h> #ifdef USE_TCP_SOCKETS #define SOCKET_DESCRIPTION "(TCP sockets)" @@ -140,7 +133,7 @@ PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("auth-anonymous=<don't check for cookies?> " "auth-cookie=<path to cookie file> " - "auth-cookie-enabled=<enable cookie authentification? " + "auth-cookie-enabled=<enable cookie authentification?> " AUTH_USAGE SOCKET_USAGE); #elif defined(USE_PROTOCOL_ESOUND) @@ -169,7 +162,7 @@ "source=<source to connect to> " "auth-anonymous=<don't verify cookies?> " "auth-cookie=<path to cookie file> " - "auth-cookie-enabled=<enable cookie authentification? " + "auth-cookie-enabled=<enable cookie authentification?> " AUTH_USAGE SOCKET_USAGE); #else @@ -246,6 +239,7 @@ int pa__init(pa_module*m) { #if defined(USE_TCP_SOCKETS) uint32_t port = IPV4_PORT; + pa_bool_t port_fallback = TRUE; const char *listen_on; #else int r; @@ -293,6 +287,10 @@ int pa__init(pa_module*m) { #endif #if defined(USE_TCP_SOCKETS) + + if (pa_in_system_mode() || pa_modargs_get_value(ma, "port", NULL)) + port_fallback = FALSE; + if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) { pa_log("port= expects a numerical argument between 1 and 65535."); goto fail; @@ -302,14 +300,14 @@ int pa__init(pa_module*m) { if (listen_on) { # ifdef HAVE_IPV6 - u->socket_server_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, (uint16_t) port, port_fallback, TCPWRAP_SERVICE); # endif - u->socket_server_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, (uint16_t) port, port_fallback, TCPWRAP_SERVICE); } else { # ifdef HAVE_IPV6 - u->socket_server_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, (uint16_t) port, port_fallback, TCPWRAP_SERVICE); # endif - u->socket_server_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, (uint16_t) port, port_fallback, TCPWRAP_SERVICE); } # ifdef HAVE_IPV6 diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 119f5b9f..2822a7fc 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2004-2008 Lennart Poettering + Copyright 2004-2009 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -25,15 +25,12 @@ #include <pulse/xmalloc.h> -#include <pulsecore/core-error.h> #include <pulsecore/namereg.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulsecore/thread.h> -#include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include "module-remap-sink-symdef.h" @@ -48,17 +45,18 @@ PA_MODULE_USAGE( "master=<name of sink to remap> " "master_channel_map=<channel map> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " "channel_map=<channel map> " "remix=<remix channels?>"); struct userdata { - pa_core *core; pa_module *module; - pa_sink *sink, *master; + pa_sink *sink; pa_sink_input *sink_input; + + pa_bool_t auto_desc; }; static const char* const valid_modargs[] = { @@ -80,19 +78,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch (code) { - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t usec = 0; + case PA_SINK_MESSAGE_GET_LATENCY: - /* Get the latency of the master sink */ - if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - usec = 0; + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it yet */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } - /* Add the latency internal to our sink input on top */ - usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + *((pa_usec_t*) data) = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); - *((pa_usec_t*) data) = usec; return 0; - } } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -105,12 +108,11 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_IS_LINKED(state) && - u->sink_input && - PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) - - pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } @@ -121,6 +123,10 @@ static void sink_request_rewind(pa_sink *s) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE); } @@ -131,6 +137,10 @@ static void sink_update_requested_latency(pa_sink *s) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + /* Just hand this one over to the master sink */ pa_sink_input_set_requested_latency_within_thread( u->sink_input, @@ -145,8 +155,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return -1; + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); pa_sink_render(u->sink, nbytes, chunk); return 0; @@ -160,9 +170,6 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) - return; - if (u->sink->thread_info.rewind_nbytes > 0) { amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); u->sink->thread_info.rewind_nbytes = 0; @@ -178,9 +185,6 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_rewind_within_thread(u->sink, nbytes); } @@ -191,9 +195,6 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_max_request_within_thread(u->sink, nbytes); } @@ -204,24 +205,28 @@ static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; - pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ -static void sink_input_detach_cb(pa_sink_input *i) { +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_sink_detach_within_thread(u->sink); - pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } @@ -232,14 +237,13 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) - return; + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); - pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); - pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); pa_sink_attach_within_thread(u->sink); - - pa_sink_set_latency_range_within_thread(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -249,14 +253,18 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_sink_unlink(u->sink); + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; pa_sink_input_unref(u->sink_input); u->sink_input = NULL; + pa_sink_unref(u->sink); + u->sink = NULL; + pa_module_unload_request(u->module, TRUE); } @@ -286,12 +294,37 @@ static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { return u->sink != dest; } +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->auto_desc && dest) { + const char *k; + pa_proplist *pl; + + pl = pa_proplist_new(); + k = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map sink_map, stream_map; pa_modargs *ma; - const char *k; pa_sink *master; pa_sink_input_new_data sink_input_data; pa_sink_new_data sink_data; @@ -336,12 +369,8 @@ int pa__init(pa_module*m) { } u = pa_xnew0(struct userdata, 1); - u->core = m->core; u->module = m; m->userdata = u; - u->master = master; - u->sink = NULL; - u->sink_input = NULL; /* Create sink */ pa_sink_new_data_init(&sink_data); @@ -351,8 +380,6 @@ int pa__init(pa_module*m) { sink_data.name = pa_sprintf_malloc("%s.remapped", master->name); pa_sink_new_data_set_sample_spec(&sink_data, &ss); pa_sink_new_data_set_channel_map(&sink_data, &sink_map); - k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); - pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); @@ -362,7 +389,14 @@ int pa__init(pa_module*m) { goto fail; } - u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *k; + + k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)); pa_sink_new_data_done(&sink_data); if (!u->sink) { @@ -377,19 +411,20 @@ int pa__init(pa_module*m) { u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); - pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ pa_sink_input_new_data_init(&sink_input_data); sink_input_data.driver = __FILE__; sink_input_data.module = m; - sink_input_data.sink = u->master; + pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE); + sink_input_data.origin_sink = u->sink; pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map); + sink_input_data.flags = (remix ? 0 : PA_SINK_INPUT_NO_REMIX); - pa_sink_input_new(&u->sink_input, m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX)); + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); pa_sink_input_new_data_done(&sink_input_data); if (!u->sink_input) @@ -400,13 +435,17 @@ int pa__init(pa_module*m) { u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; u->sink_input->update_max_request = sink_input_update_max_request_cb; u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; u->sink_input->userdata = u; + u->sink->input_to_master = u->sink_input; + pa_sink_put(u->sink); pa_sink_input_put(u->sink_input); @@ -440,15 +479,20 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink) { - pa_sink_unlink(u->sink); - pa_sink_unref(u->sink); - } + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ - if (u->sink_input) { + if (u->sink_input) pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) pa_sink_input_unref(u->sink_input); - } + + if (u->sink) + pa_sink_unref(u->sink); pa_xfree(u); } diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index c22711ae..8b35809a 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -45,13 +45,46 @@ static const char* const valid_modargs[] = { }; struct userdata { - pa_hook_slot *sink_slot, *source_slot; + pa_hook_slot + *sink_unlink_slot, + *source_unlink_slot, + *sink_input_move_fail_slot, + *source_output_move_fail_slot; }; -static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { +static pa_sink* find_evacuation_sink(pa_core *c, pa_sink_input *i, pa_sink *skip) { + pa_sink *target, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(i); + + def = pa_namereg_get_default_sink(c); + + if (def && def != skip && pa_sink_input_may_move_to(i, def)) + return def; + + PA_IDXSET_FOREACH(target, c->sinks, idx) { + if (target == def) + continue; + + if (target == skip) + continue; + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(target))) + continue; + + if (pa_sink_input_may_move_to(i, target)) + return target; + } + + pa_log_debug("No evacuation sink found."); + return NULL; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { pa_sink_input *i; uint32_t idx; - pa_sink *target; pa_assert(c); pa_assert(sink); @@ -65,35 +98,83 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user return PA_HOOK_OK; } - if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK)) || target == sink) { - - PA_IDXSET_FOREACH(target, c->sinks, idx) - if (target != sink) - break; - - if (!target) { - pa_log_info("No evacuation sink found."); - return PA_HOOK_OK; - } - } + PA_IDXSET_FOREACH(i, sink->inputs, idx) { + pa_sink *target; - pa_assert(target != sink); + if (!(target = find_evacuation_sink(c, i, sink))) + continue; - PA_IDXSET_FOREACH(i, sink->inputs, idx) { if (pa_sink_input_move_to(i, target, FALSE) < 0) pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, + pa_log_info("Successfully moved sink input %u \"%s\" to %s.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); } return PA_HOOK_OK; } -static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) { +static pa_hook_result_t sink_input_move_fail_hook_callback(pa_core *c, pa_sink_input *i, void *userdata) { + pa_sink *target; + + pa_assert(c); + pa_assert(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + if (!(target = find_evacuation_sink(c, i, NULL))) + return PA_HOOK_OK; + + if (pa_sink_input_finish_move(i, target, FALSE) < 0) { + pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_OK; + + } else { + pa_log_info("Successfully moved sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_STOP; + } +} + +static pa_source* find_evacuation_source(pa_core *c, pa_source_output *o, pa_source *skip) { + pa_source *target, *def; + uint32_t idx; + + pa_assert(c); + pa_assert(o); + + def = pa_namereg_get_default_source(c); + + if (def && def != skip && pa_source_output_may_move_to(o, def)) + return def; + + PA_IDXSET_FOREACH(target, c->sources, idx) { + if (target == def) + continue; + + if (target == skip) + continue; + + if (skip && !target->monitor_of != !skip->monitor_of) + continue; + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(target))) + continue; + + if (pa_source_output_may_move_to(o, target)) + return target; + } + + pa_log_debug("No evacuation source found."); + return NULL; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) { pa_source_output *o; - pa_source *target; uint32_t idx; pa_assert(c); @@ -108,32 +189,48 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void return PA_HOOK_OK; } - if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE)) || target == source) { - - PA_IDXSET_FOREACH(target, c->sources, idx) - if (target != source && !target->monitor_of == !source->monitor_of) - break; - - if (!target) { - pa_log_info("No evacuation source found."); - return PA_HOOK_OK; - } - } + PA_IDXSET_FOREACH(o, source->outputs, idx) { + pa_source *target; - pa_assert(target != source); + if (!(target = find_evacuation_source(c, o, source))) + continue; - PA_IDXSET_FOREACH(o, source->outputs, idx) { if (pa_source_output_move_to(o, target, FALSE) < 0) pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); else - pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, + pa_log_info("Successfully moved source output %u \"%s\" to %s.", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), target->name); } return PA_HOOK_OK; } +static pa_hook_result_t source_output_move_fail_hook_callback(pa_core *c, pa_source_output *i, void *userdata) { + pa_source *target; + + pa_assert(c); + pa_assert(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + if (!(target = find_evacuation_source(c, i, NULL))) + return PA_HOOK_OK; + + if (pa_source_output_finish_move(i, target, FALSE) < 0) { + pa_log_info("Failed to move source input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_OK; + + } else { + pa_log_info("Successfully moved source input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), target->name); + return PA_HOOK_STOP; + } +} + int pa__init(pa_module*m) { pa_modargs *ma; struct userdata *u; @@ -146,8 +243,13 @@ int pa__init(pa_module*m) { } m->userdata = u = pa_xnew(struct userdata, 1); - u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_hook_callback, NULL); - u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_hook_callback, NULL); + + /* A little bit later than module-stream-restore, module-intended-roles... */ + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+20, (pa_hook_cb_t) source_unlink_hook_callback, u); + + u->sink_input_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) sink_input_move_fail_hook_callback, u); + u->source_output_move_fail_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], PA_HOOK_LATE+20, (pa_hook_cb_t) source_output_move_fail_hook_callback, u); pa_modargs_free(ma); return 0; @@ -161,10 +263,15 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink_slot) - pa_hook_slot_free(u->sink_slot); - if (u->source_slot) - pa_hook_slot_free(u->source_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->sink_input_move_fail_slot) + pa_hook_slot_free(u->sink_input_move_fail_slot); + if (u->source_output_move_fail_slot) + pa_hook_slot_free(u->source_output_move_fail_slot); pa_xfree(u); } diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c index 4c02e958..22930749 100644 --- a/src/modules/module-rygel-media-server.c +++ b/src/modules/module-rygel-media-server.c @@ -28,8 +28,8 @@ #include <string.h> #include <unistd.h> +#include <pulse/gccmacro.h> #include <pulse/xmalloc.h> -#include <pulse/util.h> #include <pulse/i18n.h> #include <pulse/utf8.h> @@ -39,7 +39,6 @@ #include <pulsecore/log.h> #include <pulsecore/modargs.h> #include <pulsecore/dbus-shared.h> -#include <pulsecore/endianmacros.h> #include <pulsecore/namereg.h> #include <pulsecore/mime-type.h> #include <pulsecore/strbuf.h> @@ -52,33 +51,52 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE( - "display_name=<UPnP Media Server name>"); +PA_MODULE_USAGE("display_name=<UPnP Media Server name>"); -/* This implements http://live.gnome.org/Rygel/MediaServerSpec */ +/* This implements http://live.gnome.org/Rygel/MediaServer2Spec */ -#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" +#define SERVICE_NAME "org.gnome.UPnP.MediaServer2.PulseAudio" -#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" -#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" -#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer2/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer2/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer2/PulseAudio/Sources" #define CONTAINER_INTROSPECT_XML_PREFIX \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "<node>" \ " <!-- If you are looking for documentation make sure to check out" \ - " http://live.gnome.org/Rygel/MediaServerSpec -->" \ - " <interface name=\"org.gnome.UPnP.MediaContainer1\">" \ + " http://live.gnome.org/Rygel/MediaServer2Spec -->" \ + " <interface name=\"org.gnome.UPnP.MediaContainer2\">" \ + " <method name='ListChildren'>" \ + " <arg direction='in' name='offset' type='u' />" \ + " <arg direction='in' name='max' type='u' />" \ + " <arg direction='in' name='filter' type='as' />" \ + " <arg direction='out' type='aa{sv}' />" \ + " </method>" \ + " <method name='ListContainers'>" \ + " <arg direction='in' name='offset' type='u' />" \ + " <arg direction='in' name='max' type='u' />" \ + " <arg direction='in' name='filter' type='as' />" \ + " <arg direction='out' type='aa{sv}' />" \ + " </method>" \ + " <method name='ListItems'>" \ + " <arg direction='in' name='offset' type='u' />" \ + " <arg direction='in' name='max' type='u' />" \ + " <arg direction='in' name='filter' type='as' />" \ + " <arg direction='out' type='aa{sv}' />" \ + " </method>" \ " <signal name=\"Updated\">" \ " <arg name=\"path\" type=\"o\"/>" \ " </signal>" \ - " <property name=\"Items\" type=\"ao\" access=\"read\"/>" \ + " <property name=\"ChildCount\" type=\"u\" access=\"read\"/>" \ " <property name=\"ItemCount\" type=\"u\" access=\"read\"/>" \ - " <property name=\"Containers\" type=\"ao\" access=\"read\"/>" \ " <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>" \ + " <property name=\"Searchable\" type=\"b\" access=\"read\"/>" \ " </interface>" \ - " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <interface name=\"org.gnome.UPnP.MediaObject2\">" \ " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Path\" type=\"s\" access=\"read\"/>" \ " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ " </interface>" \ " <interface name=\"org.freedesktop.DBus.Properties\">" \ @@ -111,14 +129,14 @@ PA_MODULE_USAGE( DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "<node>" \ " <!-- If you are looking for documentation make sure to check out" \ - " http://live.gnome.org/Rygel/MediaProviderSpec -->" \ - " <interface name=\"org.gnome.UPnP.MediaItem1\">" \ + " http://live.gnome.org/Rygel/MediaProvider2Spec -->" \ + " <interface name=\"org.gnome.UPnP.MediaItem2\">" \ " <property name=\"URLs\" type=\"as\" access=\"read\"/>" \ " <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \ - " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ - " </interface>" \ - " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ + " <interface name=\"org.gnome.UPnP.MediaObject2\">" \ " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ + " <property name=\"Path\" type=\"s\" access=\"read\"/>" \ " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ " </interface>" \ " <interface name=\"org.freedesktop.DBus.Properties\">" \ @@ -159,6 +177,8 @@ struct userdata { pa_http_protocol *http; }; +static char *compute_url(const struct userdata *u, const char *name); + static void send_signal(struct userdata *u, pa_source *s) { DBusMessage *m; const char *parent; @@ -174,7 +194,7 @@ static void send_signal(struct userdata *u, pa_source *s) { else parent = OBJECT_SOURCES; - pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); + pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer2", "Updated")); pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL)); dbus_message_unref(m); @@ -296,6 +316,70 @@ static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsig pa_assert_se(dbus_message_iter_close_container(iter, &sub)); } +static void append_variant_boolean(DBusMessage *m, DBusMessageIter *iter, dbus_bool_t b) { + DBusMessageIter _iter, sub; + + pa_assert(m); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) { + DBusMessageIter _iter, sub, array; + char *url; + + pa_assert(m); + pa_assert(u); + pa_assert(sink || source); + + if (!iter) { + dbus_message_iter_init_append(m, &_iter); + iter = &_iter; + } + + url = compute_url(u, sink ? sink->monitor_source->name : source->name); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", &sub)); + pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); + pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); + pa_assert_se(dbus_message_iter_close_container(&sub, &array)); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); + + pa_xfree(url); +} + +static void append_variant_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { + char *mime_type; + + pa_assert(sink || source); + + if (sink) + mime_type = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + else + mime_type = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + append_variant_string(m, iter, mime_type); + + pa_xfree(mime_type); +} + +static void append_variant_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { + const char *display_name; + + pa_assert(sink || source); + + display_name = pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + append_variant_string(m, iter, display_name); +} + +PA_GCC_UNUSED static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) { DBusMessageIter sub; @@ -340,6 +424,195 @@ static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter pa_assert_se(dbus_message_iter_close_container(iter, &sub)); } +static void append_property_dict_entry_boolean(DBusMessage *m, DBusMessageIter *iter, const char *name, dbus_bool_t b) { + DBusMessageIter sub; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); + append_variant_boolean(m, &sub, b); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) { + DBusMessageIter sub; + const char *property_name = "URLs"; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); + append_variant_urls(m, &sub, u, sink, source); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { + DBusMessageIter sub; + const char *property_name = "MIMEType"; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); + append_variant_mime_type(m, &sub, sink, source); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { + DBusMessageIter sub; + const char *property_name = "DisplayName"; + + pa_assert(iter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); + pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); + append_variant_item_display_name(m, &sub, sink, source); + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static pa_bool_t get_mediacontainer2_list_args(DBusMessage *m, unsigned *offset, unsigned *max_entries, char ***filter, int *filter_len) { + DBusError error; + + dbus_error_init(&error); + + pa_assert(m); + pa_assert(offset); + pa_assert(max_entries); + pa_assert(filter); + + if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT32, offset, DBUS_TYPE_UINT32, max_entries, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, filter, filter_len, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + return TRUE; +} + +static unsigned get_sinks_or_sources_count(const char *path, const struct userdata *u) { + unsigned n, k; + + n = pa_idxset_size(u->core->sinks); + k = pa_idxset_size(u->core->sources); + pa_assert(k >= n); + + return pa_streq(path, OBJECT_SINKS) ? n : k - n; +} + +static void append_sink_or_source_container_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path) { + append_property_dict_entry_object(r, sub, "Parent", OBJECT_ROOT); + append_property_dict_entry_string(r, sub, "Type", "container"); + append_property_dict_entry_object(r, sub, "Path", path); + append_property_dict_entry_string(r, sub, "DisplayName", + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); +} + +static void append_sink_or_source_container_properties( + DBusMessage *r, DBusMessageIter *iter, + const char *path, const struct userdata *user_data, + char * const * filter, int filter_len) { + + DBusMessageIter sub; + + pa_assert(r); + pa_assert(iter); + pa_assert(path); + pa_assert(filter); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + + if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') { + append_sink_or_source_container_mediaobject2_properties(r, &sub, path); + append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data)); + append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); + } + else { + for (int i = 0; i < filter_len; ++i) { + const char *property_name = filter[i]; + if (pa_streq(property_name, "Parent")) { + append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT); + } + else if (pa_streq(property_name, "Type")) { + append_property_dict_entry_string(r, &sub, "Type", "container"); + } + else if (pa_streq(property_name, "Path")) { + append_property_dict_entry_object(r, &sub, "Path", path); + } + else if (pa_streq(property_name, "DisplayName")) { + append_property_dict_entry_string(r, &sub, "DisplayName", + pa_streq(path, OBJECT_SINKS) ? + _("Output Devices") : + _("Input Devices")); + } + else if (pa_streq(property_name, "ChildCount")) { + append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data)); + } + else if (pa_streq(property_name, "Searchable")) { + append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); + } + } + } + + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_sink_or_source_item_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path, pa_sink *sink, pa_source *source) { + append_property_dict_entry_object(r, sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); + append_property_dict_entry_string(r, sub, "Type", "audio"); + append_property_dict_entry_object(r, sub, "Path", path); + append_property_dict_entry_item_display_name(r, sub, sink, source); +} + +static void append_sink_or_source_item_properties( + DBusMessage *r, DBusMessageIter *iter, + const char *path, const struct userdata *user_data, + pa_sink *sink, pa_source *source, + char * const * filter, int filter_len) { + + DBusMessageIter sub; + + pa_assert(r); + pa_assert(iter); + pa_assert(path); + pa_assert(filter); + pa_assert(sink || source); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + + if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') { + append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source); + append_property_dict_entry_urls(r, &sub, user_data, sink, source); + append_property_dict_entry_mime_type(r, &sub, sink, source); + } + else { + for (int i = 0; i < filter_len; ++i) { + const char *property_name = filter[i]; + if (pa_streq(property_name, "Parent")) { + append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); + } + else if (pa_streq(property_name, "Type")) { + append_property_dict_entry_string(r, &sub, "Type", "audio"); + } + else if (pa_streq(property_name, "Path")) { + append_property_dict_entry_object(r, &sub, "Path", path); + } + else if (pa_streq(property_name, "DisplayName")) { + append_property_dict_entry_item_display_name(r, &sub, sink, source); + } + else if (pa_streq(property_name, "URLs")) { + append_property_dict_entry_urls(r, &sub, user_data, sink, source); + } + else if (pa_streq(property_name, "MIMEType")) { + append_property_dict_entry_mime_type(r, &sub, sink, source); + } + } + } + + pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES }; static const char *array_no_children[] = { }; @@ -349,50 +622,100 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u pa_assert(u); - if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + append_variant_boolean(r, NULL, FALSE); - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers)); - append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); - append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ChildCount", PA_ELEMENTSOF(array_root_containers)); append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); + append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren") + || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) { + DBusMessageIter iter, sub; + unsigned offset, max; + char ** filter; + int filter_len; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); + + if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) { + unsigned end = (max != 0 && offset + max < PA_ELEMENTSOF(array_root_containers)) + ? max + offset + : PA_ELEMENTSOF(array_root_containers); + + for (unsigned i = offset; i < end; ++i) { + const char *container_path = array_root_containers[i]; + append_sink_or_source_container_properties(r, &sub, container_path, u, filter, filter_len); + } + + dbus_free_string_array(filter); + } + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + + } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + + dbus_message_iter_init_append(r, &iter); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, OBJECT_ROOT); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, "container"); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { + const char *path = dbus_message_get_path(m); + + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, path); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, u->display_name); - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) { DBusMessageIter iter, sub; + const char *path = dbus_message_get_path(m); pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT); + append_property_dict_entry_string(r, &sub, "Type", "container"); + append_property_dict_entry_object(r, &sub, "Path", path); append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); @@ -400,10 +723,7 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u const char *xml = ROOT_INTROSPECT_XML; pa_assert_se(r = dbus_message_new_method_return(m)); - pa_assert_se(dbus_message_append_args( - r, - DBUS_TYPE_STRING, &xml, - DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); } else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -416,7 +736,7 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u return DBUS_HANDLER_RESULT_HANDLED; } -static char *compute_url(struct userdata *u, const char *name) { +static char *compute_url(const struct userdata *u, const char *name) { pa_strlist *i; pa_assert(u); @@ -453,49 +773,6 @@ static char *compute_url(struct userdata *u, const char *name) { return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name); } -static char **child_array(struct userdata *u, const char *path, unsigned *n) { - unsigned m; - uint32_t idx; - char **array; - - pa_assert(u); - pa_assert(path); - pa_assert(n); - - if (pa_streq(path, OBJECT_SINKS)) - m = pa_idxset_size(u->core->sinks); - else - m = pa_idxset_size(u->core->sources); - - array = pa_xnew(char*, m); - *n = 0; - - if (pa_streq(path, OBJECT_SINKS)) { - pa_sink *sink; - - PA_IDXSET_FOREACH(sink, u->core->sinks, idx) - array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); - } else { - pa_source *source; - - PA_IDXSET_FOREACH(source, u->core->sources, idx) - if (!source->monitor_of) - array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); - } - - pa_assert((*n) <= m); - - return array; -} - -static void free_child_array(char **array, unsigned n) { - - for (; n >= 1; n--) - pa_xfree(array[n-1]); - - pa_xfree(array); -} - static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct userdata *u = userdata; DBusMessage *r = NULL; @@ -509,59 +786,114 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag /* Container nodes */ - if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount") + || message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); + append_variant_unsigned(r, NULL, get_sinks_or_sources_count(path, u)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); + append_variant_unsigned(r, NULL, 0); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { - char ** array; - unsigned n; + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_boolean(r, NULL, FALSE); - array = child_array(u, path, &n); + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) { + DBusMessageIter iter, sub; + unsigned item_count; pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_object_array(r, NULL, (const char**) array, n); + dbus_message_iter_init_append(r, &iter); - free_child_array(array, n); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { - pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_unsigned(r, NULL, - pa_streq(path, OBJECT_SINKS) ? - pa_idxset_size(u->core->sinks) : - pa_idxset_size(u->core->sources)); + item_count = get_sinks_or_sources_count(path, u); + + append_property_dict_entry_unsigned(r, &sub, "ChildCount", item_count); + append_property_dict_entry_unsigned(r, &sub, "ItemCount", item_count); + append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); + append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); + + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { + } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren") + || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) { DBusMessageIter iter, sub; - char **array; - unsigned n; + unsigned offset, max; + char **filter; + int filter_len; pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); + + if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) { + unsigned end = (max != 0) ? max + offset : UINT_MAX; + + if (pa_streq(path, OBJECT_SINKS)) { + pa_sink *sink; + char sink_path[sizeof(OBJECT_SINKS) + 32]; + char *path_end = sink_path + sizeof(OBJECT_SINKS); + unsigned item_index = 0; + uint32_t idx; + + strcpy(sink_path, OBJECT_SINKS "/"); + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if (item_index >= offset && item_index < end) { + sprintf(path_end, "%u", sink->index); + append_sink_or_source_item_properties(r, &sub, sink_path, u, sink, NULL, filter, filter_len); + } + ++item_index; + } + } else { + pa_source *source; + char source_path[sizeof(OBJECT_SOURCES) + 32]; + char *path_end = source_path + sizeof(OBJECT_SOURCES); + unsigned item_index = 0; + uint32_t idx; + + strcpy(source_path, OBJECT_SOURCES "/"); + + PA_IDXSET_FOREACH(source, u->core->sources, idx) + if (!source->monitor_of) { + if (item_index >= offset && item_index < end) { + sprintf(path_end, "%u", source->index); + append_sink_or_source_item_properties(r, &sub, source_path, u, NULL, source, filter, filter_len); + } + ++item_index; + } + } + + dbus_free_string_array(filter); + } - pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children)); - append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); + pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - array = child_array(u, path, &n); - append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); - free_child_array(array, n); - append_property_dict_entry_unsigned(r, &sub, "ItemCount", - pa_streq(path, OBJECT_SINKS) ? - pa_idxset_size(u->core->sinks) : - pa_idxset_size(u->core->sources)); + } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) { + DBusMessageIter iter, sub; + + pa_assert_se(r = dbus_message_new_method_return(m)); + dbus_message_iter_init_append(r, &iter); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, OBJECT_ROOT); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_string(r, NULL, "container"); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, path); + + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, @@ -569,20 +901,14 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag _("Output Devices") : _("Input Devices")); - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); - pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT); - append_property_dict_entry_string(m, &sub, "DisplayName", - pa_streq(path, OBJECT_SINKS) ? - _("Output Devices") : - _("Input Devices")); - pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + append_sink_or_source_container_mediaobject2_properties(r, &sub, path); } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { pa_strbuf *sb; @@ -609,10 +935,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag xml = pa_strbuf_tostring_free(sb); pa_assert_se(r = dbus_message_new_method_return(m)); - pa_assert_se(dbus_message_append_args( - r, - DBUS_TYPE_STRING, &xml, - DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); pa_xfree(xml); } else @@ -632,91 +955,49 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag if (!sink && !source) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { - pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); - - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { - DBusMessageIter iter, sub; - - pa_assert_se(r = dbus_message_new_method_return(m)); - dbus_message_iter_init_append(r, &iter); - - pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); - append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); - pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, "audio"); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { - char *t; - - if (sink) - t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); - else - t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_object(r, NULL, path); + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); - append_variant_string(r, NULL, t); - pa_xfree(t); + append_variant_item_display_name(r, NULL, sink, source); - } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { - DBusMessageIter iter, sub, array; - char *url; + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) { + DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); - dbus_message_iter_init_append(r, &iter); - url = compute_url(u, sink ? sink->monitor_source->name : source->name); + pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source); - pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub)); - pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); - pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); - pa_assert_se(dbus_message_iter_close_container(&sub, &array)); - pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "MIMEType")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_mime_type(r, NULL, sink, source); - pa_xfree(url); + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "URLs")) { + pa_assert_se(r = dbus_message_new_method_return(m)); + append_variant_urls(r, NULL, u, sink, source); - } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { - DBusMessageIter iter, sub, dict, variant, array; - char *url, *t; - const char *un = "URLs"; + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem2")) { + DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); - append_property_dict_entry_string(r, &sub, "Type", "audio"); - - if (sink) - t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); - else - t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); - - append_property_dict_entry_string(r, &sub, "MIMEType", t); - pa_xfree(t); - - pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict)); - pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un)); - - url = compute_url(u, sink ? sink->monitor_source->name : source->name); - - pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant)); - pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array)); - pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); - pa_assert_se(dbus_message_iter_close_container(&variant, &array)); - pa_assert_se(dbus_message_iter_close_container(&dict, &variant)); - pa_assert_se(dbus_message_iter_close_container(&sub, &dict)); - pa_xfree(url); + append_property_dict_entry_mime_type(r, &sub, sink, source); + append_property_dict_entry_urls(r, &sub, u, sink, source); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); @@ -725,10 +1006,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag ITEM_INTROSPECT_XML; pa_assert_se(r = dbus_message_new_method_return(m)); - pa_assert_se(dbus_message_append_args( - r, - DBUS_TYPE_STRING, &xml, - DBUS_TYPE_INVALID)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); } else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; diff --git a/src/modules/module-sine-source.c b/src/modules/module-sine-source.c index 14a04e47..20a68680 100644 --- a/src/modules/module-sine-source.c +++ b/src/modules/module-sine-source.c @@ -24,20 +24,14 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> -#include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> -#include <sys/ioctl.h> -#include <sys/poll.h> -#include <pulse/xmalloc.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> +#include <pulse/xmalloc.h> -#include <pulsecore/core-error.h> #include <pulsecore/source.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> @@ -46,7 +40,6 @@ #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> -#include <pulsecore/rtclock.h> #include "module-sine-source-symdef.h" @@ -101,14 +94,14 @@ static int source_process_msg( case PA_SOURCE_MESSAGE_SET_STATE: if (PA_PTR_TO_UINT(data) == PA_SOURCE_RUNNING) - u->timestamp = pa_rtclock_usec(); + u->timestamp = pa_rtclock_now(); break; case PA_SOURCE_MESSAGE_GET_LATENCY: { pa_usec_t now, left_to_fill; - now = pa_rtclock_usec(); + now = pa_rtclock_now(); left_to_fill = u->timestamp > now ? u->timestamp - now : 0ULL; *((pa_usec_t*) data) = u->block_usec > left_to_fill ? u->block_usec - left_to_fill : 0ULL; @@ -166,9 +159,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - u->timestamp = pa_rtclock_usec(); + u->timestamp = pa_rtclock_now(); for (;;) { int ret; @@ -176,7 +168,7 @@ static void thread_func(void *userdata) { if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { pa_usec_t now; - now = pa_rtclock_usec(); + now = pa_rtclock_now(); if (u->timestamp <= now) process_render(u, now); @@ -274,7 +266,7 @@ int pa__init(pa_module*m) { pa_source_set_rtpoll(u->source, u->rtpoll); pa_source_set_fixed_latency(u->source, u->block_usec); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("sine-source", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 0be1d722..c6d73039 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -24,7 +24,6 @@ #endif #include <stdio.h> -#include <math.h> #include <pulse/xmalloc.h> @@ -33,7 +32,6 @@ #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> #include <pulsecore/log.h> -#include <pulsecore/core-util.h> #include "module-sine-symdef.h" @@ -157,13 +155,13 @@ int pa__init(pa_module*m) { pa_sink_input_new_data_init(&data); data.driver = __FILE__; data.module = m; - data.sink = sink; + pa_sink_input_new_data_set_sink(&data, sink, FALSE); pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "%u Hz Sine", frequency); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_sink_input_new_data_set_sample_spec(&data, &ss); - pa_sink_input_new(&u->sink_input, m->core, &data, 0); + pa_sink_input_new(&u->sink_input, m->core, &data); pa_sink_input_new_data_done(&data); if (!u->sink_input) diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 5cfa97a7..0e4e4017 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -28,12 +28,9 @@ #include <stdlib.h> #include <stdio.h> #include <errno.h> -#include <string.h> #include <fcntl.h> #include <unistd.h> -#include <limits.h> #include <sys/ioctl.h> -#include <sys/stat.h> #include <sys/types.h> #include <signal.h> @@ -41,13 +38,12 @@ #include <sys/conf.h> #include <sys/audio.h> -#include <pulse/error.h> #include <pulse/mainloop-signal.h> #include <pulse/xmalloc.h> #include <pulse/timeval.h> #include <pulse/util.h> +#include <pulse/rtclock.h> -#include <pulsecore/iochannel.h> #include <pulsecore/sink.h> #include <pulsecore/source.h> #include <pulsecore/module.h> @@ -59,7 +55,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include <pulsecore/thread.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/time-smoother.h> #include "module-solaris-symdef.h" @@ -110,6 +106,8 @@ struct userdata { uint32_t prev_playback_samples, prev_record_samples; int32_t minimum_request; + + pa_smoother *smoother; }; static const char* const valid_modargs[] = { @@ -133,6 +131,9 @@ static const char* const valid_modargs[] = { #define MAX_RENDER_HZ (300) /* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */ +#define MAX_BUFFER_SIZE (128 * 1024) +/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */ + static uint64_t get_playback_buffered_bytes(struct userdata *u) { audio_info_t info; uint64_t played_bytes; @@ -145,7 +146,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { /* Handle wrap-around of the device's sample counter, which is a uint_32. */ if (u->prev_playback_samples > info.play.samples) { - /* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */ + /* + * Unfortunately info.play.samples can sometimes go backwards, even before it wraps! + * The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine. + * The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver. + * An earlier revision of this file mentions the same bug independently (unknown configuration). + */ if (u->prev_playback_samples + info.play.samples < 240000) { ++u->play_samples_msw; } else { @@ -155,6 +161,8 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) { u->prev_playback_samples = info.play.samples; played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size; + pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec)); + return u->written_bytes - played_bytes; } @@ -294,8 +302,8 @@ static int auto_format(int fd, int mode, pa_sample_spec *ss) { info.record.encoding = AUDIO_ENCODING_LINEAR; break; default: - pa_log("AUDIO_SETINFO: Unsupported sample format."); - return -1; + pa_log("AUDIO_SETINFO: Unsupported sample format."); + return -1; } } @@ -314,7 +322,7 @@ static int open_audio_device(struct userdata *u, pa_sample_spec *ss) { pa_assert(u); pa_assert(ss); - if ((u->fd = open(u->device_name, u->mode | O_NONBLOCK)) < 0) { + if ((u->fd = pa_open_cloexec(u->device_name, u->mode | O_NONBLOCK, 0)) < 0) { pa_log_warn("open %s failed (%s)", u->device_name, pa_cstrerror(errno)); return -1; } @@ -387,6 +395,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + pa_smoother_pause(u->smoother, pa_rtclock_now()); + if (!u->source || u->source_suspended) { if (suspend(u) < 0) return -1; @@ -398,6 +408,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_RUNNING: if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); + if (!u->source || u->source_suspended) { if (unsuspend(u) < 0) return -1; @@ -479,7 +491,7 @@ static void sink_set_volume(pa_sink *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -501,8 +513,7 @@ static void sink_get_volume(pa_sink *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -515,7 +526,7 @@ static void source_set_volume(pa_source *s) { if (u->fd >= 0) { AUDIO_INITINFO(&info); - info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + info.play.gain = pa_cvolume_max(&s->volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; assert(info.play.gain <= AUDIO_MAX_GAIN); if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { @@ -537,8 +548,7 @@ static void source_get_volume(pa_source *s) { if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); else - pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels, - info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + pa_cvolume_set(&s->volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); } } @@ -585,6 +595,10 @@ static void process_rewind(struct userdata *u) { pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); rewind_nbytes = PA_MIN(u->memchunk.length, rewind_nbytes); u->memchunk.length -= rewind_nbytes; + if (u->memchunk.length <= 0 && u->memchunk.memblock) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); } @@ -605,13 +619,14 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); for (;;) { /* Render some data and write it to the dsp */ if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - pa_usec_t xtime0; + pa_usec_t xtime0, ysleep_interval, xsleep_interval; uint64_t buffered_bytes; if (u->sink->thread_info.rewind_requested) @@ -630,18 +645,21 @@ static void thread_func(void *userdata) { info.play.error = 0; if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + + pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE); } for (;;) { void *p; ssize_t w; size_t len; + int write_type = 1; /* * Since we cannot modify the size of the output buffer we fake it * by not filling it more than u->buffer_size. */ - xtime0 = pa_rtclock_usec(); + xtime0 = pa_rtclock_now(); buffered_bytes = get_playback_buffered_bytes(u); if (buffered_bytes >= (uint64_t)u->buffer_size) break; @@ -652,39 +670,32 @@ static void thread_func(void *userdata) { if (len < (size_t) u->minimum_request) break; - if (u->memchunk.length < len) + if (!u->memchunk.length) pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk); + len = PA_MIN(u->memchunk.length, len); + p = pa_memblock_acquire(u->memchunk.memblock); - w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL); + w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type); pa_memblock_release(u->memchunk.memblock); if (w <= 0) { - switch (errno) { - case EINTR: - continue; - case EAGAIN: - /* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error - * is not ideal, but I don't know how to get the system to tell me what the limit is. - */ - u->buffer_size = u->buffer_size * 18 / 25; - u->buffer_size -= u->buffer_size % u->frame_size; - u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request); - pa_sink_set_max_request_within_thread(u->sink, u->buffer_size); - pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size); - pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes); - break; - default: - pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); - goto fail; + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */ + pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes); + break; + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; } } else { pa_assert(w % u->frame_size == 0); u->written_bytes += w; - u->memchunk.length -= w; - u->memchunk.index += w; + u->memchunk.length -= w; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); pa_memchunk_reset(&u->memchunk); @@ -692,7 +703,9 @@ static void thread_func(void *userdata) { } } - pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec)); + ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec); + xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval); + pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval)); } else pa_rtpoll_set_timer_disabled(u->rtpoll); @@ -798,7 +811,7 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void pa_log_debug("caught signal"); if (u->sink) { - pa_sink_get_volume(u->sink, TRUE, FALSE); + pa_sink_get_volume(u->sink, TRUE); pa_sink_get_mute(u->sink, TRUE); } @@ -813,7 +826,7 @@ int pa__init(pa_module *m) { pa_channel_map map; pa_modargs *ma = NULL; uint32_t buffer_length_msec; - int fd; + int fd = -1; pa_sink_new_data sink_new_data; pa_source_new_data source_new_data; char const *name; @@ -839,6 +852,9 @@ int pa__init(pa_module *m) { u = pa_xnew0(struct userdata, 1); + if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, TRUE, TRUE, 10, pa_rtclock_now(), TRUE))) + goto fail; + /* * For a process (or several processes) to use the same audio device for both * record and playback at the same time, the device's mixer must be enabled. @@ -862,7 +878,13 @@ int pa__init(pa_module *m) { } u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss); if (u->buffer_size < 2 * u->minimum_request) { - pa_log("supplied buffer size argument is too small"); + pa_log("buffer_length argument cannot be smaller than %u", + (unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000)); + goto fail; + } + if (u->buffer_size > MAX_BUFFER_SIZE) { + pa_log("buffer_length argument cannot be greater than %u", + (unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000)); goto fail; } @@ -925,6 +947,7 @@ int pa__init(pa_module *m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec)); u->source->get_volume = source_get_volume; u->source->set_volume = source_set_volume; @@ -967,15 +990,15 @@ int pa__init(pa_module *m) { pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec)); + pa_sink_set_max_request(u->sink, u->buffer_size); + pa_sink_set_max_rewind(u->sink, u->buffer_size); u->sink->get_volume = sink_get_volume; u->sink->set_volume = sink_set_volume; u->sink->get_mute = sink_get_mute; u->sink->set_mute = sink_set_mute; u->sink->refresh_volume = u->sink->refresh_muted = TRUE; - - pa_sink_set_max_request(u->sink, u->buffer_size); - pa_sink_set_max_rewind(u->sink, u->buffer_size); } else u->sink = NULL; @@ -987,7 +1010,7 @@ int pa__init(pa_module *m) { else pa_log_warn("Could not register SIGPOLL handler"); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("solaris", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -1076,6 +1099,9 @@ void pa__done(pa_module *m) { if (u->fd >= 0) close(u->fd); + if (u->smoother) + pa_smoother_free(u->smoother); + pa_xfree(u->device_name); pa_xfree(u); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 2de98f4e..19c09bb6 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -2,6 +2,7 @@ This file is part of PulseAudio. Copyright 2008 Lennart Poettering + Copyright 2009 Tanu Kaskinen PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -29,12 +30,12 @@ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> -#include <ctype.h> +#include <pulse/gccmacro.h> #include <pulse/xmalloc.h> #include <pulse/volume.h> #include <pulse/timeval.h> -#include <pulse/util.h> +#include <pulse/rtclock.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -49,6 +50,12 @@ #include <pulsecore/pstream.h> #include <pulsecore/pstream-util.h> #include <pulsecore/database.h> +#include <pulsecore/tagstruct.h> + +#ifdef HAVE_DBUS +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> +#endif #include "module-stream-restore-symdef.h" @@ -59,15 +66,19 @@ PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( "restore_device=<Save/restore sinks/sources?> " "restore_volume=<Save/restore volumes?> " - "restore_muted=<Save/restore muted states?>"); + "restore_muted=<Save/restore muted states?> " + "on_hotplug=<When new device becomes available, recheck streams?> " + "on_rescue=<When device becomes unavailable, recheck streams?>"); -#define SAVE_INTERVAL 10 +#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) #define IDENTIFICATION_PROPERTY "module-stream-restore.id" static const char* const valid_modargs[] = { "restore_device", "restore_volume", "restore_muted", + "on_hotplug", + "on_rescue", NULL }; @@ -79,6 +90,10 @@ struct userdata { *sink_input_new_hook_slot, *sink_input_fixate_hook_slot, *source_output_new_hook_slot, + *sink_put_hook_slot, + *source_put_hook_slot, + *sink_unlink_hook_slot, + *source_unlink_hook_slot, *connection_unlink_hook_slot; pa_time_event *save_time_event; pa_database* database; @@ -86,21 +101,30 @@ struct userdata { pa_bool_t restore_device:1; pa_bool_t restore_volume:1; pa_bool_t restore_muted:1; + pa_bool_t on_hotplug:1; + pa_bool_t on_rescue:1; pa_native_protocol *protocol; pa_idxset *subscribed; + +#ifdef HAVE_DBUS + pa_dbus_protocol *dbus_protocol; + pa_hashmap *dbus_entries; + uint32_t next_index; /* For generating object paths for entries. */ +#endif }; -#define ENTRY_VERSION 2 +#define ENTRY_VERSION 1 struct entry { uint8_t version; - pa_bool_t muted_valid:1, volume_valid:1, device_valid:1; - pa_bool_t muted:1; + pa_bool_t muted_valid, volume_valid, device_valid, card_valid; + pa_bool_t muted; pa_channel_map channel_map; pa_cvolume volume; - char device[PA_NAME_MAX]; -} PA_GCC_PACKED; + char* device; + char* card; +}; enum { SUBCOMMAND_TEST, @@ -111,12 +135,823 @@ enum { SUBCOMMAND_EVENT }; -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + +static struct entry* entry_new(void); +static void entry_free(struct entry *e); +static struct entry *entry_read(struct userdata *u, const char *name); +static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e, pa_bool_t replace); +static struct entry* entry_copy(const struct entry *e); +static void entry_apply(struct userdata *u, const char *name, struct entry *e); +static void trigger_save(struct userdata *u); + +#ifdef HAVE_DBUS + +#define OBJECT_PATH "/org/pulseaudio/stream_restore1" +#define ENTRY_OBJECT_NAME "entry" +#define INTERFACE_STREAM_RESTORE "org.PulseAudio.Ext.StreamRestore1" +#define INTERFACE_ENTRY INTERFACE_STREAM_RESTORE ".RestoreEntry" + +#define DBUS_INTERFACE_REVISION 0 + +struct dbus_entry { + struct userdata *userdata; + + char *entry_name; + uint32_t index; + char *object_path; +}; + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); + +static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INTERFACE_REVISION, + PROPERTY_HANDLER_ENTRIES, + PROPERTY_HANDLER_MAX +}; + +enum entry_property_handler_index { + ENTRY_PROPERTY_HANDLER_INDEX, + ENTRY_PROPERTY_HANDLER_NAME, + ENTRY_PROPERTY_HANDLER_DEVICE, + ENTRY_PROPERTY_HANDLER_VOLUME, + ENTRY_PROPERTY_HANDLER_MUTE, + ENTRY_PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL }, + [PROPERTY_HANDLER_ENTRIES] = { .property_name = "Entries", .type = "ao", .get_cb = handle_get_entries, .set_cb = NULL } +}; + +static pa_dbus_property_handler entry_property_handlers[ENTRY_PROPERTY_HANDLER_MAX] = { + [ENTRY_PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_entry_get_index, .set_cb = NULL }, + [ENTRY_PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_entry_get_name, .set_cb = NULL }, + [ENTRY_PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "s", .get_cb = handle_entry_get_device, .set_cb = handle_entry_set_device }, + [ENTRY_PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "a(uu)", .get_cb = handle_entry_get_volume, .set_cb = handle_entry_set_volume }, + [ENTRY_PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_entry_get_mute, .set_cb = handle_entry_set_mute } +}; + +enum method_handler_index { + METHOD_HANDLER_ADD_ENTRY, + METHOD_HANDLER_GET_ENTRY_BY_NAME, + METHOD_HANDLER_MAX +}; + +enum entry_method_handler_index { + ENTRY_METHOD_HANDLER_REMOVE, + ENTRY_METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" }, + { "device", "s", "in" }, + { "volume", "a(uu)", "in" }, + { "mute", "b", "in" }, + { "entry", "o", "out" } }; +static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_ADD_ENTRY] = { + .method_name = "AddEntry", + .arguments = add_entry_args, + .n_arguments = sizeof(add_entry_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_add_entry }, + [METHOD_HANDLER_GET_ENTRY_BY_NAME] = { + .method_name = "GetEntryByName", + .arguments = get_entry_by_name_args, + .n_arguments = sizeof(get_entry_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_entry_by_name } +}; + +static pa_dbus_method_handler entry_method_handlers[ENTRY_METHOD_HANDLER_MAX] = { + [ENTRY_METHOD_HANDLER_REMOVE] = { + .method_name = "Remove", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_entry_remove } +}; + +enum signal_index { + SIGNAL_NEW_ENTRY, + SIGNAL_ENTRY_REMOVED, + SIGNAL_MAX +}; + +enum entry_signal_index { + ENTRY_SIGNAL_DEVICE_UPDATED, + ENTRY_SIGNAL_VOLUME_UPDATED, + ENTRY_SIGNAL_MUTE_UPDATED, + ENTRY_SIGNAL_MAX +}; + +static pa_dbus_arg_info new_entry_args[] = { { "entry", "o", NULL } }; +static pa_dbus_arg_info entry_removed_args[] = { { "entry", "o", NULL } }; + +static pa_dbus_arg_info entry_device_updated_args[] = { { "device", "s", NULL } }; +static pa_dbus_arg_info entry_volume_updated_args[] = { { "volume", "a(uu)", NULL } }; +static pa_dbus_arg_info entry_mute_updated_args[] = { { "muted", "b", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_NEW_ENTRY] = { .name = "NewEntry", .arguments = new_entry_args, .n_arguments = 1 }, + [SIGNAL_ENTRY_REMOVED] = { .name = "EntryRemoved", .arguments = entry_removed_args, .n_arguments = 1 } +}; + +static pa_dbus_signal_info entry_signals[ENTRY_SIGNAL_MAX] = { + [ENTRY_SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = entry_device_updated_args, .n_arguments = 1 }, + [ENTRY_SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = entry_volume_updated_args, .n_arguments = 1 }, + [ENTRY_SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = entry_mute_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info stream_restore_interface_info = { + .name = INTERFACE_STREAM_RESTORE, + .method_handlers = method_handlers, + .n_method_handlers = METHOD_HANDLER_MAX, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = signals, + .n_signals = SIGNAL_MAX +}; + +static pa_dbus_interface_info entry_interface_info = { + .name = INTERFACE_ENTRY, + .method_handlers = entry_method_handlers, + .n_method_handlers = ENTRY_METHOD_HANDLER_MAX, + .property_handlers = entry_property_handlers, + .n_property_handlers = ENTRY_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_entry_get_all, + .signals = entry_signals, + .n_signals = ENTRY_SIGNAL_MAX +}; + +static struct dbus_entry *dbus_entry_new(struct userdata *u, const char *entry_name) { + struct dbus_entry *de; + + pa_assert(u); + pa_assert(entry_name); + pa_assert(*entry_name); + + de = pa_xnew(struct dbus_entry, 1); + de->userdata = u; + de->entry_name = pa_xstrdup(entry_name); + de->index = u->next_index++; + de->object_path = pa_sprintf_malloc("%s/%s%u", OBJECT_PATH, ENTRY_OBJECT_NAME, de->index); + + pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, de->object_path, &entry_interface_info, de) >= 0); + + return de; +} + +static void dbus_entry_free(struct dbus_entry *de) { + pa_assert(de); + + pa_assert_se(pa_dbus_protocol_remove_interface(de->userdata->dbus_protocol, de->object_path, entry_interface_info.name) >= 0); + + pa_xfree(de->entry_name); + pa_xfree(de->object_path); +} + +/* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are + * are a channel position and a volume value, respectively. The result is + * stored in the map and vol arguments. The iterator must point to a "a(uu)" + * element. If the data is invalid, an error reply is sent and a negative + * number is returned. In case of a failure we make no guarantees about the + * state of map and vol. In case of an empty array the channels field of both + * map and vol are set to 0. This function calls dbus_message_iter_next(iter) + * before returning. */ +static int get_volume_arg(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, pa_channel_map *map, pa_cvolume *vol) { + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a(uu)")); + pa_assert(map); + pa_assert(vol); + + pa_channel_map_init(map); + pa_cvolume_init(vol); + + map->channels = 0; + vol->channels = 0; + + dbus_message_iter_recurse(iter, &array_iter); + + while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) { + dbus_uint32_t chan_pos; + dbus_uint32_t chan_vol; + + dbus_message_iter_recurse(&array_iter, &struct_iter); + + dbus_message_iter_get_basic(&struct_iter, &chan_pos); + + if (chan_pos >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u", chan_pos); + return -1; + } + + pa_assert_se(dbus_message_iter_next(&struct_iter)); + dbus_message_iter_get_basic(&struct_iter, &chan_vol); + + if (!PA_VOLUME_IS_VALID(chan_vol)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", chan_vol); + return -1; + } + + if (map->channels < PA_CHANNELS_MAX) { + map->map[map->channels] = chan_pos; + vol->values[map->channels] = chan_vol; + } + ++map->channels; + ++vol->channels; + + dbus_message_iter_next(&array_iter); + } + + if (map->channels > PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too many channels: %u. The maximum is %u.", map->channels, PA_CHANNELS_MAX); + return -1; + } + + dbus_message_iter_next(iter); + + return 0; +} + +static void append_volume(DBusMessageIter *iter, struct entry *e) { + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + unsigned i; + + pa_assert(iter); + pa_assert(e); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(uu)", &array_iter)); + + if (!e->volume_valid) { + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); + return; + } + + for (i = 0; i < e->channel_map.channels; ++i) { + pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->channel_map.map[i])); + pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->volume.values[i])); + + pa_assert_se(dbus_message_iter_close_container(&array_iter, &struct_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); +} + +static void append_volume_variant(DBusMessageIter *iter, struct entry *e) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(e); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(uu)", &variant_iter)); + + append_volume(&variant_iter, e); + + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +static void send_new_entry_signal(struct dbus_entry *entry) { + DBusMessage *signal_msg; + + pa_assert(entry); + + pa_assert_se(signal_msg = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_NEW_ENTRY].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); +} + +static void send_entry_removed_signal(struct dbus_entry *entry) { + DBusMessage *signal_msg; + + pa_assert(entry); + + pa_assert_se(signal_msg = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_ENTRY_REMOVED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); +} + +static void send_device_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal_msg; + const char *device; + + pa_assert(de); + pa_assert(e); + + device = e->device_valid ? e->device : ""; + + pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); +} + +static void send_volume_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal_msg; + DBusMessageIter msg_iter; + + pa_assert(de); + pa_assert(e); + + pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_VOLUME_UPDATED].name)); + dbus_message_iter_init_append(signal_msg, &msg_iter); + append_volume(&msg_iter, e); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); +} + +static void send_mute_updated_signal(struct dbus_entry *de, struct entry *e) { + DBusMessage *signal_msg; + dbus_bool_t muted; + + pa_assert(de); + pa_assert(e); + + pa_assert(e->muted_valid); + + muted = e->muted; + + pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg); + dbus_message_unref(signal_msg); +} + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_uint32_t interface_revision = DBUS_INTERFACE_REVISION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_entries(struct userdata *u, unsigned *n) { + const char **entries; + unsigned i = 0; + void *state = NULL; + struct dbus_entry *de; + + pa_assert(u); + pa_assert(n); + + *n = pa_hashmap_size(u->dbus_entries); + + if (*n == 0) + return NULL; + + entries = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(de, u->dbus_entries, state) + entries[i++] = de->object_path; + + return entries; +} + +static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + const char **entries; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + entries = get_entries(u, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, entries, n); + + pa_xfree(entries); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t interface_revision; + const char **entries; + unsigned n_entries; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + interface_revision = DBUS_INTERFACE_REVISION; + entries = get_entries(u, &n_entries); + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ENTRIES].property_name, DBUS_TYPE_OBJECT_PATH, entries, n_entries); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(entries); +} + +static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + DBusMessageIter msg_iter; + const char *name = NULL; + const char *device = NULL; + pa_channel_map map; + pa_cvolume vol; + dbus_bool_t muted = FALSE; + dbus_bool_t apply_immediately = FALSE; + struct dbus_entry *dbus_entry = NULL; + struct entry *e = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &device); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (get_volume_arg(conn, msg, &msg_iter, &map, &vol) < 0) + return; + + dbus_message_iter_get_basic(&msg_iter, &muted); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &apply_immediately); + + if (!*name) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "An empty string was given as the entry name."); + return; + } + + if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) { + pa_bool_t mute_updated = FALSE; + pa_bool_t volume_updated = FALSE; + pa_bool_t device_updated = FALSE; + + pa_assert_se(e = entry_read(u, name)); + mute_updated = e->muted != muted; + e->muted = muted; + e->muted_valid = TRUE; + + volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); + e->volume = vol; + e->channel_map = map; + e->volume_valid = !!map.channels; + + device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + pa_xfree(e->device); + e->device = pa_xstrdup(device); + e->device_valid = !!device[0]; + + if (mute_updated) + send_mute_updated_signal(dbus_entry, e); + if (volume_updated) + send_volume_updated_signal(dbus_entry, e); + if (device_updated) + send_device_updated_signal(dbus_entry, e); + + } else { + dbus_entry = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) == 0); + + e = entry_new(); + e->muted_valid = TRUE; + e->volume_valid = !!map.channels; + e->device_valid = !!device[0]; + e->muted = muted; + e->volume = vol; + e->channel_map = map; + e->device = pa_xstrdup(device); + + send_new_entry_signal(dbus_entry); + } + + pa_assert_se(entry_write(u, name, e, TRUE)); + + if (apply_immediately) + entry_apply(u, name, e); + + trigger_save(u); + + pa_dbus_send_empty_reply(conn, msg); + + entry_free(e); +} + +static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct userdata *u = userdata; + const char *name; + struct dbus_entry *de; + + pa_assert(conn); + pa_assert(msg); + pa_assert(u); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)); + + if (!(de = pa_hashmap_get(u->dbus_entries, name))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such stream restore entry."); + return; + } + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &de->object_path); +} + +static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &de->index); +} + +static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &de->entry_name); +} + +static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + const char *device; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + device = e->device_valid ? e->device : ""; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device); + + entry_free(e); +} + +static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + const char *device; + struct entry *e; + pa_bool_t updated; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + dbus_message_iter_get_basic(iter, &device); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); + + if (updated) { + pa_xfree(e->device); + e->device = pa_xstrdup(device); + e->device_valid = !!device[0]; + + pa_assert_se(entry_write(de->userdata, de->entry_name, e, TRUE)); + + entry_apply(de->userdata, de->entry_name, e); + send_device_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + entry_free(e); +} + +static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + DBusMessage *reply; + DBusMessageIter msg_iter; + struct entry *e; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + pa_assert_se(reply = dbus_message_new_method_return(msg)); + + dbus_message_iter_init_append(reply, &msg_iter); + append_volume_variant(&msg_iter, e); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + entry_free(e); +} + +static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + pa_channel_map map; + pa_cvolume vol; + struct entry *e = NULL; + pa_bool_t updated = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + if (get_volume_arg(conn, msg, iter, &map, &vol) < 0) + return; + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); + + if (updated) { + e->volume = vol; + e->channel_map = map; + e->volume_valid = !!map.channels; + + pa_assert_se(entry_write(de->userdata, de->entry_name, e, TRUE)); + + entry_apply(de->userdata, de->entry_name, e); + send_volume_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + entry_free(e); +} + +static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + dbus_bool_t mute; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + mute = e->muted_valid ? e->muted : FALSE; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute); + + entry_free(e); +} + +static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + struct dbus_entry *de = userdata; + dbus_bool_t mute; + struct entry *e; + pa_bool_t updated; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(de); + + dbus_message_iter_get_basic(iter, &mute); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + updated = !e->muted_valid || e->muted != mute; + + if (updated) { + e->muted = mute; + e->muted_valid = TRUE; + + pa_assert_se(entry_write(de->userdata, de->entry_name, e, TRUE)); + + entry_apply(de->userdata, de->entry_name, e); + send_mute_updated_signal(de, e); + trigger_save(de->userdata); + } + + pa_dbus_send_empty_reply(conn, msg); + + entry_free(e); +} + +static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + struct entry *e; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + const char *device; + dbus_bool_t mute; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); + + device = e->device_valid ? e->device : ""; + mute = e->muted_valid ? e->muted : FALSE; + + pa_assert_se((reply = dbus_message_new_method_return(msg))); + + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name)); + append_volume_variant(&dict_entry_iter, e); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + + pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute); + + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + + dbus_message_unref(reply); + + entry_free(e); +} + +static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { + struct dbus_entry *de = userdata; + pa_datum key; + + pa_assert(conn); + pa_assert(msg); + pa_assert(de); + + key.data = de->entry_name; + key.size = strlen(de->entry_name); + + pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0); + + send_entry_removed_signal(de); + trigger_save(de->userdata); + + pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name)); + dbus_entry_free(de); + + pa_dbus_send_empty_reply(conn, msg); +} + +#endif /* HAVE_DBUS */ + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; pa_assert(a); pa_assert(e); - pa_assert(tv); pa_assert(u); pa_assert(e == u->save_time_event); @@ -152,11 +987,107 @@ static char *get_name(pa_proplist *p, const char *prefix) { return t; } -static struct entry* read_entry(struct userdata *u, const char *name) { +static struct entry* entry_new(void) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + return r; +} + +static void entry_free(struct entry* e) { + pa_assert(e); + + pa_xfree(e->device); + pa_xfree(e->card); + pa_xfree(e); +} + +static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e, pa_bool_t replace) { + pa_tagstruct *t; pa_datum key, data; + pa_bool_t r; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu8(t, e->version); + pa_tagstruct_put_boolean(t, e->volume_valid); + pa_tagstruct_put_channel_map(t, &e->channel_map); + pa_tagstruct_put_cvolume(t, &e->volume); + pa_tagstruct_put_boolean(t, e->muted_valid); + pa_tagstruct_put_boolean(t, e->muted); + pa_tagstruct_put_boolean(t, e->device_valid); + pa_tagstruct_puts(t, e->device); + pa_tagstruct_put_boolean(t, e->card_valid); + pa_tagstruct_puts(t, e->card); + + key.data = (char *) name; + key.size = strlen(name); + + data.data = (void*)pa_tagstruct_data(t, &data.size); + + r = (pa_database_set(u->database, &key, &data, replace) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 3 +static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) { + struct legacy_entry { + uint8_t version; + pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1; + pa_bool_t muted:1; + pa_channel_map channel_map; + pa_cvolume volume; + char device[PA_NAME_MAX]; + char card[PA_NAME_MAX]; + } PA_GCC_PACKED; + struct legacy_entry *le; struct entry *e; pa_assert(u); + pa_assert(data); + + if (data->size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + return NULL; + } + + le = (struct legacy_entry*)data->data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + return NULL; + } + + if (!memchr(le->device, 0, sizeof(le->device))) { + pa_log_warn("Device has missing NUL byte."); + return NULL; + } + + if (!memchr(le->card, 0, sizeof(le->card))) { + pa_log_warn("Card has missing NUL byte."); + return NULL; + } + + e = entry_new(); + e->card = pa_xstrdup(le->card); + return e; +} +#endif + +static struct entry *entry_read(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e = NULL; + pa_tagstruct *t = NULL; + const char *device, *card; + + pa_assert(u); pa_assert(name); key.data = (char*) name; @@ -167,27 +1098,37 @@ static struct entry* read_entry(struct userdata *u, const char *name) { if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.size != sizeof(struct entry)) { - /* This is probably just a database upgrade, hence let's not - * consider this more than a debug message */ - pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); + t = pa_tagstruct_new(data.data, data.size); + e = entry_new(); + + if (pa_tagstruct_getu8(t, &e->version) < 0 || + e->version > ENTRY_VERSION || + pa_tagstruct_get_boolean(t, &e->volume_valid) < 0 || + pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &e->volume) < 0 || + pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 || + pa_tagstruct_get_boolean(t, &e->muted) < 0 || + pa_tagstruct_get_boolean(t, &e->device_valid) < 0 || + pa_tagstruct_gets(t, &device) < 0 || + pa_tagstruct_get_boolean(t, &e->card_valid) < 0 || + pa_tagstruct_gets(t, &card) < 0) { + goto fail; } - e = (struct entry*) data.data; + e->device = pa_xstrdup(device); + e->card = pa_xstrdup(card); - if (e->version != ENTRY_VERSION) { - pa_log_debug("Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring.", name); + if (!pa_tagstruct_eof(t)) goto fail; - } - if (!memchr(e->device, 0, sizeof(e->device))) { - pa_log_warn("Database contains entry for stream %s with missing NUL byte in device name", name); + if (e->device_valid && !pa_namereg_is_valid_name(e->device)) { + pa_log_warn("Invalid device name stored in database for stream %s", name); goto fail; } - if (e->device_valid && !pa_namereg_is_valid_name(e->device)) { - pa_log_warn("Invalid device name stored in database for stream %s", name); + if (e->card_valid && !pa_namereg_is_valid_name(e->card)) { + pa_log_warn("Invalid card name stored in database for stream %s", name); goto fail; } @@ -201,16 +1142,48 @@ static struct entry* read_entry(struct userdata *u, const char *name) { goto fail; } + pa_tagstruct_free(t); + pa_datum_free(&data); + return e; fail: + pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name); + + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name); + if ((e = legacy_entry_read(u, &data))) { + pa_log_debug("Success. Saving new format for key: %s", name); + if (entry_write(u, name, e, TRUE)) + trigger_save(u); + pa_datum_free(&data); + return e; + } else + pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name); +#endif + pa_datum_free(&data); return NULL; } +static struct entry* entry_copy(const struct entry *e) { + struct entry* r; + + pa_assert(e); + r = entry_new(); + *r = *e; + r->device = pa_xstrdup(e->device); + r->card = pa_xstrdup(e->card); + return r; +} + static void trigger_save(struct userdata *u) { - struct timeval tv; pa_native_connection *c; uint32_t idx; @@ -230,9 +1203,7 @@ static void trigger_save(struct userdata *u) { if (u->save_time_event) return; - pa_gettimeofday(&tv); - tv.tv_sec += SAVE_INTERVAL; - u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); } static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { @@ -242,7 +1213,11 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { pa_assert(b); if (a->device_valid != b->device_valid || - (a->device_valid && strncmp(a->device, b->device, sizeof(a->device)))) + (a->device_valid && !pa_streq(a->device, b->device))) + return FALSE; + + if (a->card_valid != b->card_valid || + (a->card_valid && !pa_streq(a->card, b->card))) return FALSE; if (a->muted_valid != b->muted_valid || @@ -259,9 +1234,19 @@ static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; - struct entry entry, *old; - char *name; - pa_datum key, data; + struct entry *entry, *old = NULL; + char *name = NULL; + + /* These are only used when D-Bus is enabled, but in order to reduce ifdef + * clutter these are defined here unconditionally. */ + pa_bool_t created_new_entry = TRUE; + pa_bool_t device_updated = FALSE; + pa_bool_t volume_updated = FALSE; + pa_bool_t mute_updated = FALSE; + +#ifdef HAVE_DBUS + struct dbus_entry *de = NULL; +#endif pa_assert(c); pa_assert(u); @@ -272,9 +1257,6 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - pa_zero(entry); - entry.version = ENTRY_VERSION; - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { pa_sink_input *sink_input; @@ -284,23 +1266,43 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!(name = get_name(sink_input->proplist, "sink-input"))) return; - if ((old = read_entry(u, name))) - entry = *old; + if ((old = entry_read(u, name))) { + entry = entry_copy(old); + created_new_entry = FALSE; + } else + entry = entry_new(); + + if (sink_input->save_volume && pa_sink_input_is_volume_readable(sink_input)) { + pa_assert(sink_input->volume_writable); - if (sink_input->save_volume) { - entry.channel_map = sink_input->channel_map; - pa_sink_input_get_volume(sink_input, &entry.volume, FALSE); - entry.volume_valid = TRUE; + entry->channel_map = sink_input->channel_map; + pa_sink_input_get_volume(sink_input, &entry->volume, FALSE); + entry->volume_valid = TRUE; + + volume_updated = !created_new_entry + && (!old->volume_valid + || !pa_channel_map_equal(&entry->channel_map, &old->channel_map) + || !pa_cvolume_equal(&entry->volume, &old->volume)); } if (sink_input->save_muted) { - entry.muted = pa_sink_input_get_mute(sink_input); - entry.muted_valid = TRUE; + entry->muted = pa_sink_input_get_mute(sink_input); + entry->muted_valid = TRUE; + + mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted); } if (sink_input->save_sink) { - pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device)); - entry.device_valid = TRUE; + pa_xfree(entry->device); + entry->device = pa_xstrdup(sink_input->sink->name); + entry->device_valid = TRUE; + + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry->device, old->device)); + if (sink_input->sink->card) { + pa_xfree(entry->card); + entry->card = pa_xstrdup(sink_input->sink->card->name); + entry->card_valid = TRUE; + } } } else { @@ -314,69 +1316,102 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!(name = get_name(source_output->proplist, "source-output"))) return; - if ((old = read_entry(u, name))) - entry = *old; + if ((old = entry_read(u, name))) { + entry = entry_copy(old); + created_new_entry = FALSE; + } else + entry = entry_new(); if (source_output->save_source) { - pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); - entry.device_valid = source_output->save_source; + pa_xfree(entry->device); + entry->device = pa_xstrdup(source_output->source->name); + entry->device_valid = TRUE; + + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry->device, old->device)); + + if (source_output->source->card) { + pa_xfree(entry->card); + entry->card = pa_xstrdup(source_output->source->card->name); + entry->card_valid = TRUE; + } } } + pa_assert(entry); + if (old) { - if (entries_equal(old, &entry)) { - pa_xfree(old); + if (entries_equal(old, entry)) { + entry_free(old); + entry_free(entry); pa_xfree(name); return; } - pa_xfree(old); + entry_free(old); } - key.data = name; - key.size = strlen(name); - - data.data = &entry; - data.size = sizeof(entry); - pa_log_info("Storing volume/mute/device for stream %s.", name); - pa_database_set(u->database, &key, &data, TRUE); + if (entry_write(u, name, entry, TRUE)) + trigger_save(u); - pa_xfree(name); +#ifdef HAVE_DBUS + if (created_new_entry) { + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0); + send_new_entry_signal(de); + } else { + pa_assert_se(de = pa_hashmap_get(u->dbus_entries, name)); + + if (device_updated) + send_device_updated_signal(de, entry); + if (volume_updated) + send_volume_updated_signal(de, entry); + if (mute_updated) + send_mute_updated_signal(de, entry); + } +#endif - trigger_save(u); + entry_free(entry); + pa_xfree(name); } static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { char *name; struct entry *e; + pa_assert(c); pa_assert(new_data); - - if (!u->restore_device) - return PA_HOOK_OK; + pa_assert(u); + pa_assert(u->restore_device); if (!(name = get_name(new_data->proplist, "sink-input"))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { - pa_sink *s; + if (new_data->sink) + pa_log_debug("Not restoring device for stream %s, because already set to '%s'.", name, new_data->sink->name); + else if ((e = entry_read(u, name))) { + pa_sink *s = NULL; - if (e->device_valid) { + if (e->device_valid) + s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK); - if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK))) { - if (!new_data->sink) { - pa_log_info("Restoring device for stream %s.", name); - new_data->sink = s; - new_data->save_sink = TRUE; - } else - pa_log_info("Not restore device for stream %s, because already set.", name); - } + if (!s && e->card_valid) { + pa_card *card; + + if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD))) + s = pa_idxset_first(card->sinks, NULL); } - pa_xfree(e); + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) + if (pa_sink_input_new_data_set_sink(new_data, s, TRUE)) + pa_log_info("Restoring device for stream %s.", name); + + entry_free(e); } pa_xfree(name); @@ -388,30 +1423,33 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu char *name; struct entry *e; + pa_assert(c); pa_assert(new_data); - - if (!u->restore_volume && !u->restore_muted) - return PA_HOOK_OK; + pa_assert(u); + pa_assert(u->restore_volume || u->restore_muted); if (!(name = get_name(new_data->proplist, "sink-input"))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (u->restore_volume && e->volume_valid) { - - if (!new_data->volume_is_set) { + if (!new_data->volume_writable) + pa_log_debug("Not restoring volume for sink input %s, because its volume can't be changed.", name); + else if (new_data->volume_is_set) + pa_log_debug("Not restoring volume for sink input %s, because already set.", name); + else { pa_cvolume v; pa_log_info("Restoring volume for sink input %s.", name); + v = e->volume; pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); pa_sink_input_new_data_set_volume(new_data, &v); new_data->volume_is_absolute = FALSE; - new_data->save_volume = FALSE; - } else - pa_log_debug("Not restoring volume for sink input %s, because already set.", name); + new_data->save_volume = TRUE; + } } if (u->restore_muted && e->muted_valid) { @@ -424,7 +1462,7 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu pa_log_debug("Not restoring mute state for sink input %s, because already set.", name); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -436,10 +1474,10 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou char *name; struct entry *e; + pa_assert(c); pa_assert(new_data); - - if (!u->restore_device) - return PA_HOOK_OK; + pa_assert(u); + pa_assert(u->restore_device); if (new_data->direct_on_input) return PA_HOOK_OK; @@ -447,21 +1485,30 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou if (!(name = get_name(new_data->proplist, "source-output"))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { - pa_source *s; + if (new_data->source) + pa_log_debug("Not restoring device for stream %s, because already set", name); + else if ((e = entry_read(u, name))) { + pa_source *s = NULL; - if (e->device_valid) { - if ((s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE))) { - if (!new_data->source) { - pa_log_info("Restoring device for stream %s.", name); - new_data->source = s; - new_data->save_source = TRUE; - } else - pa_log_info("Not restoring device for stream %s, because already set", name); - } + if (e->device_valid) + s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE); + + if (!s && e->card_valid) { + pa_card *card; + + if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD))) + s = pa_idxset_first(card->sources, NULL); + } + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) { + pa_log_info("Restoring device for stream %s.", name); + pa_source_output_new_data_set_source(new_data, s, TRUE); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -469,9 +1516,192 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou return PA_HOOK_OK; } +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->on_hotplug && u->restore_device); + + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + char *name; + struct entry *e; + + if (si->sink == sink) + continue; + + if (si->save_sink) + continue; + + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + + if (!(name = get_name(si->proplist, "sink-input"))) + continue; + + if ((e = entry_read(u, name))) { + if (e->device_valid && pa_streq(e->device, sink->name)) + pa_sink_input_move_to(si, sink, TRUE); + + entry_free(e); + } + + pa_xfree(name); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_source_output *so; + uint32_t idx; + + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->on_hotplug && u->restore_device); + + PA_IDXSET_FOREACH(so, c->source_outputs, idx) { + char *name; + struct entry *e; + + if (so->source == source) + continue; + + if (so->save_source) + continue; + + if (so->direct_on_input) + continue; + + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + continue; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + continue; + + if (!(name = get_name(so->proplist, "source-output"))) + continue; + + if ((e = entry_read(u, name))) { + if (e->device_valid && pa_streq(e->device, source->name)) + pa_source_output_move_to(so, source, TRUE); + + entry_free(e); + } + + pa_xfree(name); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->on_rescue && u->restore_device); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(si, sink->inputs, idx) { + char *name; + struct entry *e; + + if (!si->sink) + continue; + + if (!(name = get_name(si->proplist, "sink-input"))) + continue; + + if ((e = entry_read(u, name))) { + + if (e->device_valid) { + pa_sink *d; + + if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && + d != sink && + PA_SINK_IS_LINKED(pa_sink_get_state(d))) + pa_sink_input_move_to(si, d, TRUE); + } + + entry_free(e); + } + + pa_xfree(name); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { + pa_source_output *so; + uint32_t idx; + + pa_assert(c); + pa_assert(source); + pa_assert(u); + pa_assert(u->on_rescue && u->restore_device); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + PA_IDXSET_FOREACH(so, source->outputs, idx) { + char *name; + struct entry *e; + + if (so->direct_on_input) + continue; + + if (!so->source) + continue; + + if (!(name = get_name(so->proplist, "source-output"))) + continue; + + if ((e = entry_read(u, name))) { + + if (e->device_valid) { + pa_source *d; + + if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && + d != source && + PA_SOURCE_IS_LINKED(pa_source_get_state(d))) + pa_source_output_move_to(so, d, TRUE); + } + + entry_free(e); + } + + pa_xfree(name); + } + + return PA_HOOK_OK; +} + #define EXT_VERSION 1 -static void apply_entry(struct userdata *u, const char *name, struct entry *e) { +static void entry_apply(struct userdata *u, const char *name, struct entry *e) { pa_sink_input *si; pa_source_output *so; uint32_t idx; @@ -480,7 +1710,7 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { pa_assert(name); pa_assert(e); - for (si = pa_idxset_first(u->core->sink_inputs, &idx); si; si = pa_idxset_next(u->core->sink_inputs, &idx)) { + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { char *n; pa_sink *s; @@ -493,12 +1723,13 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { } pa_xfree(n); - if (u->restore_volume && e->volume_valid) { + if (u->restore_volume && e->volume_valid && si->volume_writable) { pa_cvolume v; v = e->volume; pa_log_info("Restoring volume for sink input %s.", name); - pa_sink_input_set_volume(si, pa_cvolume_remap(&v, &e->channel_map, &si->channel_map), FALSE, FALSE); + pa_cvolume_remap(&v, &e->channel_map, &si->channel_map); + pa_sink_input_set_volume(si, &v, TRUE, FALSE); } if (u->restore_muted && e->muted_valid) { @@ -506,16 +1737,28 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { pa_sink_input_set_mute(si, e->muted, TRUE); } - if (u->restore_device && - e->device_valid && - (s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SINK))) { - - pa_log_info("Restoring device for stream %s.", name); - pa_sink_input_move_to(si, s, TRUE); + if (u->restore_device) { + if (!e->device_valid) { + if (si->save_sink) { + pa_log_info("Ensuring device is not saved for stream %s.", name); + /* If the device is not valid we should make sure the + save flag is cleared as the user may have specifically + removed the sink element from the rule. */ + si->save_sink = FALSE; + /* This is cheating a bit. The sink input itself has not changed + but the rules governing it's routing have, so we fire this event + such that other routing modules (e.g. module-device-manager) + will pick up the change and reapply their routing */ + pa_subscription_post(si->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, si->index); + } + } else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SINK))) { + pa_log_info("Restoring device for stream %s.", name); + pa_sink_input_move_to(si, s, TRUE); + } } } - for (so = pa_idxset_first(u->core->source_outputs, &idx); so; so = pa_idxset_next(u->core->source_outputs, &idx)) { + PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { char *n; pa_source *s; @@ -528,18 +1771,30 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { } pa_xfree(n); - if (u->restore_device && - e->device_valid && - (s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SOURCE))) { - - pa_log_info("Restoring device for stream %s.", name); - pa_source_output_move_to(so, s, TRUE); + if (u->restore_device) { + if (!e->device_valid) { + if (so->save_source) { + pa_log_info("Ensuring device is not saved for stream %s.", name); + /* If the device is not valid we should make sure the + save flag is cleared as the user may have specifically + removed the source element from the rule. */ + so->save_source = FALSE; + /* This is cheating a bit. The source output itself has not changed + but the rules governing it's routing have, so we fire this event + such that other routing modules (e.g. module-device-manager) + will pick up the change and reapply their routing */ + pa_subscription_post(so->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, so->index); + } + } else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SOURCE))) { + pa_log_info("Restoring device for stream %s.", name); + pa_source_output_move_to(so, s, TRUE); + } } } } -#if 0 -static void dump_database(struct userdata *u) { +#ifdef DEBUG_VOLUME +PA_GCC_UNUSED static void stream_restore_dump_database(struct userdata *u) { pa_datum key; pa_bool_t done; @@ -555,14 +1810,14 @@ static void dump_database(struct userdata *u) { name = pa_xstrndup(key.data, key.size); pa_datum_free(&key); - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { char t[256]; pa_log("name=%s", name); pa_log("device=%s %s", e->device, pa_yes_no(e->device_valid)); pa_log("channel_map=%s", pa_channel_map_snprint(t, sizeof(t), &e->channel_map)); pa_log("volume=%s %s", pa_cvolume_snprint(t, sizeof(t), &e->volume), pa_yes_no(e->volume_valid)); pa_log("mute=%s %s", pa_yes_no(e->muted), pa_yes_no(e->volume_valid)); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -619,7 +1874,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio name = pa_xstrndup(key.data, key.size); pa_datum_free(&key); - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { pa_cvolume r; pa_channel_map cm; @@ -629,7 +1884,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_tagstruct_puts(reply, e->device_valid ? e->device : NULL); pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : FALSE); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -653,54 +1908,103 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio mode != PA_UPDATE_SET) goto fail; - if (mode == PA_UPDATE_SET) + if (mode == PA_UPDATE_SET) { +#ifdef HAVE_DBUS + struct dbus_entry *de; + void *state = NULL; + + PA_HASHMAP_FOREACH(de, u->dbus_entries, state) { + send_entry_removed_signal(de); + dbus_entry_free(pa_hashmap_remove(u->dbus_entries, de->entry_name)); + } +#endif pa_database_clear(u->database); + } while (!pa_tagstruct_eof(t)) { const char *name, *device; pa_bool_t muted; - struct entry entry; - pa_datum key, data; + struct entry *entry; +#ifdef HAVE_DBUS + struct entry *old; +#endif - pa_zero(entry); - entry.version = ENTRY_VERSION; + entry = entry_new(); if (pa_tagstruct_gets(t, &name) < 0 || - pa_tagstruct_get_channel_map(t, &entry.channel_map) || - pa_tagstruct_get_cvolume(t, &entry.volume) < 0 || + pa_tagstruct_get_channel_map(t, &entry->channel_map) || + pa_tagstruct_get_cvolume(t, &entry->volume) < 0 || pa_tagstruct_gets(t, &device) < 0 || pa_tagstruct_get_boolean(t, &muted) < 0) goto fail; - if (!name || !*name) + if (!name || !*name) { + entry_free(entry); goto fail; + } - entry.volume_valid = entry.volume.channels > 0; + entry->volume_valid = entry->volume.channels > 0; - if (entry.volume_valid) - if (!pa_cvolume_compatible_with_channel_map(&entry.volume, &entry.channel_map)) + if (entry->volume_valid) + if (!pa_cvolume_compatible_with_channel_map(&entry->volume, &entry->channel_map)) { + entry_free(entry); goto fail; + } - entry.muted = muted; - entry.muted_valid = TRUE; + entry->muted = muted; + entry->muted_valid = TRUE; - if (device) - pa_strlcpy(entry.device, device, sizeof(entry.device)); - entry.device_valid = !!entry.device[0]; + entry->device = pa_xstrdup(device); + entry->device_valid = device && !!entry->device[0]; - if (entry.device_valid && - !pa_namereg_is_valid_name(entry.device)) + if (entry->device_valid && !pa_namereg_is_valid_name(entry->device)) { + entry_free(entry); goto fail; + } - key.data = (char*) name; - key.size = strlen(name); +#ifdef HAVE_DBUS + old = entry_read(u, name); +#endif + + pa_log_debug("Client %s changes entry %s.", + pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)), + name); + + if (entry_write(u, name, entry, mode == PA_UPDATE_REPLACE)) { +#ifdef HAVE_DBUS + struct dbus_entry *de; + + if (old) { + pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name))); + + if ((old->device_valid != entry->device_valid) + || (entry->device_valid && !pa_streq(entry->device, old->device))) + send_device_updated_signal(de, entry); - data.data = &entry; - data.size = sizeof(entry); + if ((old->volume_valid != entry->volume_valid) + || (entry->volume_valid && (!pa_cvolume_equal(&entry->volume, &old->volume) + || !pa_channel_map_equal(&entry->channel_map, &old->channel_map)))) + send_volume_updated_signal(de, entry); + + if (!old->muted_valid || (entry->muted != old->muted)) + send_mute_updated_signal(de, entry); + + } else { + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0); + send_new_entry_signal(de); + } +#endif - if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) if (apply_immediately) - apply_entry(u, name, &entry); + entry_apply(u, name, entry); + } + +#ifdef HAVE_DBUS + if (old) + entry_free(old); +#endif + entry_free(entry); } trigger_save(u); @@ -713,10 +2017,20 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while (!pa_tagstruct_eof(t)) { const char *name; pa_datum key; +#ifdef HAVE_DBUS + struct dbus_entry *de; +#endif if (pa_tagstruct_gets(t, &name) < 0) goto fail; +#ifdef HAVE_DBUS + if ((de = pa_hashmap_get(u->dbus_entries, name))) { + send_entry_removed_signal(de); + dbus_entry_free(pa_hashmap_remove(u->dbus_entries, name)); + } +#endif + key.data = (char*) name; key.size = strlen(name); @@ -774,7 +2088,11 @@ int pa__init(pa_module*m) { pa_sink_input *si; pa_source_output *so; uint32_t idx; - pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE; + pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE; +#ifdef HAVE_DBUS + pa_datum key; + pa_bool_t done; +#endif pa_assert(m); @@ -785,22 +2103,24 @@ int pa__init(pa_module*m) { if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 || pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 || - pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0) { - pa_log("restore_device=, restore_volume= and restore_muted= expect boolean arguments"); + pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0 || + pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("restore_device=, restore_volume=, restore_muted=, on_hotplug= and on_rescue= expect boolean arguments"); goto fail; } if (!restore_muted && !restore_volume && !restore_device) pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring device enabled!"); - m->userdata = u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; - u->save_time_event = NULL; u->restore_device = restore_device; u->restore_volume = restore_volume; u->restore_muted = restore_muted; - u->database = NULL; + u->on_hotplug = on_hotplug; + u->on_rescue = on_rescue; u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); u->protocol = pa_native_protocol_get(m->core); @@ -811,17 +2131,27 @@ int pa__init(pa_module*m) { u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); if (restore_device) { + /* A little bit earlier than module-intended-roles ... */ u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u); u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_hook_callback, u); } - if (restore_volume || restore_muted) - u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + if (restore_device && on_hotplug) { + /* A little bit earlier than module-intended-roles ... */ + u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_put_hook_callback, u); + } + if (restore_device && on_rescue) { + /* A little bit earlier than module-intended-roles, module-rescue-streams, ... */ + u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_unlink_hook_callback, u); + } - fname = pa_state_path("stream-volumes", TRUE); + if (restore_volume || restore_muted) + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); - if (!fname) + if (!(fname = pa_state_path("stream-volumes", TRUE))) goto fail; if (!(u->database = pa_database_open(fname, TRUE))) { @@ -830,13 +2160,46 @@ int pa__init(pa_module*m) { goto fail; } - pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_log_info("Successfully opened database file '%s'.", fname); pa_xfree(fname); - for (si = pa_idxset_first(m->core->sink_inputs, &idx); si; si = pa_idxset_next(m->core->sink_inputs, &idx)) +#ifdef HAVE_DBUS + u->dbus_protocol = pa_dbus_protocol_get(u->core); + u->dbus_entries = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0); + pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); + + /* Create the initial dbus entries. */ + done = !pa_database_first(u->database, &key, NULL); + while (!done) { + pa_datum next_key; + char *name; + struct dbus_entry *de; + struct entry *e; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + pa_datum_free(&key); + + /* Use entry_read() for checking that the entry is valid. */ + if ((e = entry_read(u, name))) { + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0); + entry_free(e); + } + + pa_xfree(name); + + key = next_key; + } +#endif + + PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx) subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u); - for (so = pa_idxset_first(m->core->source_outputs, &idx); so; so = pa_idxset_next(m->core->source_outputs, &idx)) + PA_IDXSET_FOREACH(so, m->core->source_outputs, idx) subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, so->index, u); pa_modargs_free(ma); @@ -848,8 +2211,18 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; +} + +#ifdef HAVE_DBUS +static void free_dbus_entry_cb(void *p, void *userdata) { + struct dbus_entry *de = p; + + pa_assert(de); + + dbus_entry_free(de); } +#endif void pa__done(pa_module*m) { struct userdata* u; @@ -859,6 +2232,19 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; +#ifdef HAVE_DBUS + if (u->dbus_protocol) { + pa_assert(u->dbus_entries); + + pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); + pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0); + + pa_hashmap_free(u->dbus_entries, free_dbus_entry_cb, NULL); + + pa_dbus_protocol_unref(u->dbus_protocol); + } +#endif + if (u->subscription) pa_subscription_free(u->subscription); @@ -869,6 +2255,16 @@ void pa__done(pa_module*m) { if (u->source_output_new_hook_slot) pa_hook_slot_free(u->source_output_new_hook_slot); + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->source_put_hook_slot) + pa_hook_slot_free(u->source_put_hook_slot); + + if (u->sink_unlink_hook_slot) + pa_hook_slot_free(u->sink_unlink_hook_slot); + if (u->source_unlink_hook_slot) + pa_hook_slot_free(u->source_unlink_hook_slot); + if (u->connection_unlink_hook_slot) pa_hook_slot_free(u->connection_unlink_hook_slot); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index cc69d74c..e7242628 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -25,6 +25,7 @@ #include <pulse/xmalloc.h> #include <pulse/timeval.h> +#include <pulse/rtclock.h> #include <pulsecore/core.h> #include <pulsecore/core-util.h> @@ -32,7 +33,6 @@ #include <pulsecore/source-output.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> -#include <pulsecore/namereg.h> #include "module-suspend-on-idle-symdef.h" @@ -40,9 +40,13 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "timeout=<timeout> " + "mempool_vacuum=<vacuum memory if all sinks and sources are suspended?>"); static const char* const valid_modargs[] = { "timeout", + "mempool_vacuum", NULL, }; @@ -69,51 +73,78 @@ struct userdata { *source_output_move_finish_slot, *sink_input_state_changed_slot, *source_output_state_changed_slot; + + pa_bool_t mempool_vacuum:1; }; struct device_info { struct userdata *userdata; pa_sink *sink; pa_source *source; - struct timeval last_use; + pa_usec_t last_use; pa_time_event *time_event; }; -static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { +static void check_meempool_vacuum(struct device_info *d) { + pa_sink *si; + pa_source *so; + uint32_t idx; + + pa_assert(d); + pa_assert(d->userdata); + pa_assert(d->userdata->core); + + idx = 0; + PA_IDXSET_FOREACH(si, d->userdata->core->sinks, idx) + if (pa_sink_get_state(si) != PA_SINK_SUSPENDED) + return; + + idx = 0; + PA_IDXSET_FOREACH(so, d->userdata->core->sources, idx) + if (pa_source_get_state(so) != PA_SOURCE_SUSPENDED) + return; + + pa_log_info("All sinks and sources are suspended, vacuuming memory"); + pa_mempool_vacuum(d->userdata->core->mempool); +} + +static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct device_info *d = userdata; pa_assert(d); d->userdata->core->mainloop->time_restart(d->time_event, NULL); - if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && !(d->sink->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); - pa_sink_suspend(d->sink, TRUE); + pa_sink_suspend(d->sink, TRUE, PA_SUSPEND_IDLE); + if (d->userdata->mempool_vacuum) + check_meempool_vacuum(d); } - if (d->source && pa_source_check_suspend(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); - pa_source_suspend(d->source, TRUE); + pa_source_suspend(d->source, TRUE, PA_SUSPEND_IDLE); + if (d->userdata->mempool_vacuum) + check_meempool_vacuum(d); } } static void restart(struct device_info *d) { - struct timeval tv; + pa_usec_t now; const char *s; uint32_t timeout; + pa_assert(d); pa_assert(d->sink || d->source); - pa_gettimeofday(&tv); - d->last_use = tv; + d->last_use = now = pa_rtclock_now(); s = pa_proplist_gets(d->sink ? d->sink->proplist : d->source->proplist, "module-suspend-on-idle.timeout"); if (!s || pa_atou(s, &timeout) < 0) - timeout = d->userdata->timeout; - - pa_timeval_add(&tv, timeout * PA_USEC_PER_SEC); + timeout = d->userdata->timeout; - d->userdata->core->mainloop->time_restart(d->time_event, &tv); + pa_core_rttime_restart(d->userdata->core, d->time_event, now + timeout * PA_USEC_PER_SEC); if (d->sink) pa_log_debug("Sink %s becomes idle, timeout in %u seconds.", d->sink->name, timeout); @@ -127,13 +158,13 @@ static void resume(struct device_info *d) { d->userdata->core->mainloop->time_restart(d->time_event, NULL); if (d->sink) { - pa_sink_suspend(d->sink, FALSE); + pa_sink_suspend(d->sink, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Sink %s becomes busy.", d->sink->name); } if (d->source) { - pa_source_suspend(d->source, FALSE); + pa_source_suspend(d->source, FALSE, PA_SUSPEND_IDLE); pa_log_debug("Source %s becomes busy.", d->source->name); } @@ -146,6 +177,10 @@ static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_ pa_assert(data); pa_assert(u); + /* We need to resume the audio device here even for + * PA_SINK_INPUT_START_CORKED, since we need the device parameters + * to be fully available while the stream is set up. */ + if ((d = pa_hashmap_get(u->device_infos, data->sink))) resume(d); @@ -227,11 +262,16 @@ static pa_hook_result_t sink_input_move_start_hook_cb(pa_core *c, pa_sink_input static pa_hook_result_t sink_input_move_finish_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { struct device_info *d; + pa_sink_input_state_t state; pa_assert(c); pa_sink_input_assert_ref(s); pa_assert(u); + state = pa_sink_input_get_state(s); + if (state != PA_SINK_INPUT_RUNNING && state != PA_SINK_INPUT_DRAINED) + return PA_HOOK_OK; + if ((d = pa_hashmap_get(u->device_infos, s->sink))) resume(d); @@ -266,6 +306,9 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_ pa_source_output_assert_ref(s); pa_assert(u); + if (pa_source_output_get_state(s) != PA_SOURCE_OUTPUT_RUNNING) + return PA_HOOK_OK; + if (s->source->monitor_of) d = pa_hashmap_get(u->device_infos, s->source->monitor_of); else @@ -280,6 +323,7 @@ static pa_hook_result_t source_output_move_finish_hook_cb(pa_core *c, pa_source_ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { struct device_info *d; pa_sink_input_state_t state; + pa_assert(c); pa_sink_input_assert_ref(s); pa_assert(u); @@ -293,15 +337,11 @@ static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_inp } static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { - pa_source_output_state_t state; - pa_assert(c); pa_source_output_assert_ref(s); pa_assert(u); - state = pa_source_output_get_state(s); - - if (state == PA_SOURCE_OUTPUT_RUNNING) { + if (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_RUNNING) { struct device_info *d; if (s->source->monitor_of) @@ -338,7 +378,7 @@ static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct user d->userdata = u; d->source = source ? pa_source_ref(source) : NULL; d->sink = sink ? pa_sink_ref(sink) : NULL; - d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d); + d->time_event = pa_core_rttime_new(c, PA_USEC_INVALID, timeout_cb, d); pa_hashmap_put(u->device_infos, o, d); if ((d->sink && pa_sink_check_suspend(d->sink) <= 0) || @@ -388,22 +428,17 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s pa_sink *s = PA_SINK(o); pa_sink_state_t state = pa_sink_get_state(s); - if (pa_sink_check_suspend(s) <= 0) { - + if (pa_sink_check_suspend(s) <= 0) if (PA_SINK_IS_OPENED(state)) restart(d); - } - } else if (pa_source_isinstance(o)) { pa_source *s = PA_SOURCE(o); pa_source_state_t state = pa_source_get_state(s); - if (pa_source_check_suspend(s) <= 0) { - + if (pa_source_check_suspend(s) <= 0) if (PA_SOURCE_IS_OPENED(state)) restart(d); - } } return PA_HOOK_OK; @@ -413,6 +448,7 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; uint32_t timeout = 5; + pa_bool_t mempool_vacuum = FALSE; uint32_t idx; pa_sink *sink; pa_source *source; @@ -429,10 +465,16 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_boolean(ma, "mempool_vacuum", &mempool_vacuum) < 0) { + pa_log("Failed to parse mempool_vacuum boolean parameter."); + goto fail; + } + m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->timeout = timeout; u->device_infos = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + u->mempool_vacuum = mempool_vacuum; for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) device_new_hook_cb(m->core, PA_OBJECT(sink), u); diff --git a/src/modules/module-switch-on-connect.c b/src/modules/module-switch-on-connect.c new file mode 100644 index 00000000..b121fd9a --- /dev/null +++ b/src/modules/module-switch-on-connect.c @@ -0,0 +1,187 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2009 Canonical Ltd + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> + +#include "module-switch-on-connect-symdef.h" + +PA_MODULE_AUTHOR("Michael Terry"); +PA_MODULE_DESCRIPTION("When a sink/source is added, switch to it"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL, +}; + +struct userdata { + pa_hook_slot + *sink_put_slot, + *source_put_slot; +}; + +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + pa_sink_input *i; + uint32_t idx; + pa_sink *def; + const char *s; + + pa_assert(c); + pa_assert(sink); + + /* Don't want to run during startup or shutdown */ + if (c->state != PA_CORE_RUNNING) + return PA_HOOK_OK; + + /* Don't switch to any internal devices */ + if ((s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS))) { + if (pa_streq(s, "pci")) + return PA_HOOK_OK; + else if (pa_streq(s, "isa")) + return PA_HOOK_OK; + } + + def = pa_namereg_get_default_sink(c); + if (def == sink) + return PA_HOOK_OK; + + /* Actually do the switch to the new sink */ + pa_namereg_set_default_sink(c, sink); + + /* Now move all old inputs over */ + if (pa_idxset_size(def->inputs) <= 0) { + pa_log_debug("No sink inputs to move away."); + return PA_HOOK_OK; + } + + PA_IDXSET_FOREACH(i, def->inputs, idx) { + if (i->save_sink) + continue; + + if (pa_sink_input_move_to(i, sink, FALSE) < 0) + pa_log_info("Failed to move sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name); + else + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void* userdata) { + pa_source_output *o; + uint32_t idx; + pa_source *def; + const char *s; + + pa_assert(c); + pa_assert(source); + + /* Don't want to run during startup or shutdown */ + if (c->state != PA_CORE_RUNNING) + return PA_HOOK_OK; + + /* Don't switch to any internal devices */ + if ((s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS))) { + if (pa_streq(s, "pci")) + return PA_HOOK_OK; + else if (pa_streq(s, "isa")) + return PA_HOOK_OK; + } + + def = pa_namereg_get_default_source(c); + if (def == source) + return PA_HOOK_OK; + + /* Actually do the switch to the new source */ + pa_namereg_set_default_source(c, source); + + /* Now move all old outputs over */ + if (pa_idxset_size(def->outputs) <= 0) { + pa_log_debug("No source outputs to move away."); + return PA_HOOK_OK; + } + + PA_IDXSET_FOREACH(o, def->outputs, idx) { + if (o->save_source) + continue; + + if (pa_source_output_move_to(o, source, FALSE) < 0) + pa_log_info("Failed to move source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), source->name); + else + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), source->name); + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + return -1; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + + /* A little bit later than module-rescue-streams... */ + u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+30, (pa_hook_cb_t) sink_put_hook_callback, u); + u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+20, (pa_hook_cb_t) source_put_hook_callback, u); + + pa_modargs_free(ma); + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_put_slot) + pa_hook_slot_free(u->sink_put_slot); + if (u->source_put_slot) + pa_hook_slot_free(u->source_put_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 6f525da3..4b1ae7df 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -31,6 +31,7 @@ #include <stdio.h> #include <stdlib.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/util.h> #include <pulse/version.h> @@ -41,19 +42,18 @@ #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/core-subscribe.h> -#include <pulsecore/sink-input.h> #include <pulsecore/pdispatch.h> #include <pulsecore/pstream.h> #include <pulsecore/pstream-util.h> #include <pulsecore/socket-client.h> -#include <pulsecore/socket-util.h> #include <pulsecore/time-smoother.h> #include <pulsecore/thread.h> #include <pulsecore/thread-mq.h> -#include <pulsecore/rtclock.h> +#include <pulsecore/core-rtclock.h> #include <pulsecore/core-error.h> #include <pulsecore/proplist-util.h> #include <pulsecore/auth-cookie.h> +#include <pulsecore/mcalign.h> #ifdef TUNNEL_SINK #include "module-tunnel-sink-symdef.h" @@ -112,7 +112,7 @@ static const char* const valid_modargs[] = { #define DEFAULT_TIMEOUT 5 -#define LATENCY_INTERVAL 10 +#define LATENCY_INTERVAL (10*PA_USEC_PER_SEC) #define MIN_NETWORK_LATENCY_USEC (8*PA_USEC_PER_MSEC) @@ -193,6 +193,7 @@ struct userdata { #else char *source_name; pa_source *source; + pa_mcalign *mcalign; #endif pa_auth_cookie *auth_cookie; @@ -329,7 +330,7 @@ static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - uint32_t channel, maxlength, tlength, fragsize, prebuf, minreq; + uint32_t channel, maxlength, tlength = 0, fragsize, prebuf, minreq; pa_usec_t usec; pa_assert(pd); @@ -376,7 +377,7 @@ static void command_stream_buffer_attr_changed(pa_pdispatch *pd, uint32_t comman /* Called from main context */ static void command_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { - struct userdata *u = userdata; + struct userdata *u = userdata; pa_assert(pd); pa_assert(t); @@ -390,12 +391,12 @@ static void command_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa #endif /* Called from IO thread context */ -static void check_smoother_status(struct userdata *u, pa_bool_t past) { +static void check_smoother_status(struct userdata *u, pa_bool_t past) { pa_usec_t x; pa_assert(u); - x = pa_rtclock_usec(); + x = pa_rtclock_now(); /* Correct by the time the requested issued needs to travel to the * other side. This is a valid thread-safe access, because the @@ -500,7 +501,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_usec_t yl, yr, *usec = data; yl = pa_bytes_to_usec((uint64_t) u->counter, &u->sink->sample_spec); - yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); + yr = pa_smoother_get(u->smoother, pa_rtclock_now()); *usec = yl > yr ? yl - yr : 0; return 0; @@ -533,7 +534,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse else y = 0; - pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + pa_smoother_put(u->smoother, pa_rtclock_now(), y); /* We can access this freely here, since the main thread is waiting for us */ u->thread_transport_usec = u->transport_usec; @@ -607,20 +608,29 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off pa_usec_t yr, yl, *usec = data; yl = pa_bytes_to_usec((uint64_t) u->counter, &PA_SOURCE(o)->sample_spec); - yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); + yr = pa_smoother_get(u->smoother, pa_rtclock_now()); *usec = yr > yl ? yr - yl : 0; return 0; } - case SOURCE_MESSAGE_POST: + case SOURCE_MESSAGE_POST: { + pa_memchunk c; + + pa_mcalign_push(u->mcalign, chunk); + + while (pa_mcalign_pop(u->mcalign, &c) >= 0) { - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) - pa_source_post(u->source, chunk); + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) + pa_source_post(u->source, &c); - u->counter += (int64_t) chunk->length; + pa_memblock_unref(c.memblock); + + u->counter += (int64_t) c.length; + } return 0; + } case SOURCE_MESSAGE_REMOTE_SUSPEND: @@ -633,7 +643,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off y = pa_bytes_to_usec((uint64_t) u->counter, &u->source->sample_spec); y += (pa_usec_t) offset; - pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + pa_smoother_put(u->smoother, pa_rtclock_now(), y); /* We can access this freely here, since the main thread is waiting for us */ u->thread_transport_usec = u->transport_usec; @@ -683,7 +693,6 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; @@ -730,7 +739,7 @@ static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, p } if (channel != u->channel) { - pa_log("Recieved data for invalid channel"); + pa_log("Received data for invalid channel"); goto fail; } @@ -878,9 +887,8 @@ static void request_latency(struct userdata *u) { } /* Called from main context */ -static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { +static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; - struct timeval ntv; pa_assert(m); pa_assert(e); @@ -888,9 +896,7 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct request_latency(u); - pa_gettimeofday(&ntv); - ntv.tv_sec += LATENCY_INTERVAL; - m->time_restart(e, &ntv); + pa_core_rttime_restart(u->core, e, pa_rtclock_now() + LATENCY_INTERVAL); } /* Called from main context */ @@ -965,8 +971,7 @@ static void server_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_tagstruct_gets(t, &default_sink_name) < 0 || pa_tagstruct_gets(t, &default_source_name) < 0 || pa_tagstruct_getu32(t, &cookie) < 0 || - (u->version >= 15 && - pa_tagstruct_get_channel_map(t, &cm) < 0)) { + (u->version >= 15 && pa_tagstruct_get_channel_map(t, &cm) < 0)) { pa_log("Parse failure"); goto fail; @@ -1061,6 +1066,50 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_t } } + if (u->version >= 16) { + uint32_t n_ports; + const char *s; + + if (pa_tagstruct_getu32(t, &n_ports)) { + pa_log("Parse failure"); + goto fail; + } + + for (uint32_t j = 0; j < n_ports; j++) { + uint32_t priority; + + if (pa_tagstruct_gets(t, &s) < 0 || /* name */ + pa_tagstruct_gets(t, &s) < 0 || /* description */ + pa_tagstruct_getu32(t, &priority) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (pa_tagstruct_gets(t, &s) < 0) { /* active port */ + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 21) { + uint8_t n_formats; + pa_format_info format; + + if (pa_tagstruct_getu8(t, &n_formats) < 0) { /* no. of formats */ + pa_log("Parse failure"); + goto fail; + } + + for (uint8_t j = 0; j < n_formats; j++) { + if (pa_tagstruct_get_format_info(t, &format)) { /* format info */ + pa_log("Parse failure"); + goto fail; + } + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1089,11 +1138,12 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag uint32_t idx, owner_module, client, sink; pa_usec_t buffer_usec, sink_usec; const char *name, *driver, *resample_method; - pa_bool_t mute; + pa_bool_t mute = FALSE; pa_sample_spec sample_spec; pa_channel_map channel_map; pa_cvolume volume; pa_proplist *pl; + pa_bool_t b; pa_assert(pd); pa_assert(u); @@ -1141,6 +1191,33 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag } } + if (u->version >= 19) { + if (pa_tagstruct_get_boolean(t, &b) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 20) { + if (pa_tagstruct_get_boolean(t, &b) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 21) { + pa_format_info format; + + if (pa_tagstruct_get_format_info(t, &format) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1154,7 +1231,7 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag pa_assert(u->sink); if ((u->version < 11 || !!mute == !!u->sink->muted) && - pa_cvolume_equal(&volume, &u->sink->virtual_volume)) + pa_cvolume_equal(&volume, &u->sink->real_volume)) return; pa_sink_volume_changed(u->sink, &volume); @@ -1237,6 +1314,33 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa } } + if (u->version >= 16) { + uint32_t n_ports; + const char *s; + + if (pa_tagstruct_getu32(t, &n_ports)) { + pa_log("Parse failure"); + goto fail; + } + + for (uint32_t j = 0; j < n_ports; j++) { + uint32_t priority; + + if (pa_tagstruct_gets(t, &s) < 0 || /* name */ + pa_tagstruct_gets(t, &s) < 0 || /* description */ + pa_tagstruct_getu32(t, &priority) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (pa_tagstruct_gets(t, &s) < 0) { /* active port */ + pa_log("Parse failure"); + goto fail; + } + } + if (!pa_tagstruct_eof(t)) { pa_log("Packet too long"); goto fail; @@ -1337,12 +1441,11 @@ static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32 /* Called from main context */ static void start_subscribe(struct userdata *u) { pa_tagstruct *t; - uint32_t tag; pa_assert(u); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER| #ifdef TUNNEL_SINK PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK @@ -1357,7 +1460,6 @@ static void start_subscribe(struct userdata *u) { /* Called from main context */ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - struct timeval ntv; #ifdef TUNNEL_SINK uint32_t bytes; #endif @@ -1432,6 +1534,13 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t /* #endif */ } + if (u->version >= 21) { + pa_format_info format; + + if (pa_tagstruct_get_format_info(t, &format) < 0) + goto parse_error; + } + if (!pa_tagstruct_eof(t)) goto parse_error; @@ -1439,9 +1548,7 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t request_info(u); pa_assert(!u->time_event); - pa_gettimeofday(&ntv); - ntv.tv_sec += LATENCY_INTERVAL; - u->time_event = u->core->mainloop->time_new(u->core->mainloop, &ntv, timeout_callback, u); + u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + LATENCY_INTERVAL, timeout_callback, u); request_latency(u); @@ -1521,7 +1628,7 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t reply = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME); - pa_tagstruct_putu32(reply, tag = u->ctag++); + pa_tagstruct_putu32(reply, u->ctag++); if (u->version >= 13) { pa_proplist *pl; @@ -1628,6 +1735,21 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t pa_tagstruct_put_boolean(reply, FALSE); /* fail on suspend */ } +#ifdef TUNNEL_SINK + if (u->version >= 17) + pa_tagstruct_put_boolean(reply, FALSE); /* relative volume */ + + if (u->version >= 18) + pa_tagstruct_put_boolean(reply, FALSE); /* passthrough stream */ +#endif + +#ifdef TUNNEL_SINK + if (u->version >= 21) { + /* We're not using the extended API, so n_formats = 0 and that's that */ + pa_tagstruct_putu8(t, 0); + } +#endif + pa_pstream_send_tagstruct(u->pstream, reply); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); @@ -1675,7 +1797,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_assert(u); if (channel != u->channel) { - pa_log("Recieved memory block on bad channel."); + pa_log("Received memory block on bad channel."); pa_module_unload_request(u->module, TRUE); return; } @@ -1706,7 +1828,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata } u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->mempool); - u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX); + u->pdispatch = pa_pdispatch_new(u->core->mainloop, TRUE, command_table, PA_COMMAND_MAX); pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u); pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u); @@ -1748,7 +1870,6 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata static void sink_set_volume(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; - uint32_t tag; pa_assert(sink); u = sink->userdata; @@ -1756,9 +1877,9 @@ static void sink_set_volume(pa_sink *sink) { t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, u->device_index); - pa_tagstruct_put_cvolume(t, &sink->virtual_volume); + pa_tagstruct_put_cvolume(t, &sink->real_volume); pa_pstream_send_tagstruct(u->pstream, t); } @@ -1766,7 +1887,6 @@ static void sink_set_volume(pa_sink *sink) { static void sink_set_mute(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; - uint32_t tag; pa_assert(sink); u = sink->userdata; @@ -1777,7 +1897,7 @@ static void sink_set_mute(pa_sink *sink) { t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE); - pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, u->device_index); pa_tagstruct_put_boolean(t, !!sink->muted); pa_pstream_send_tagstruct(u->pstream, t); @@ -1825,7 +1945,7 @@ int pa__init(pa_module*m) { TRUE, TRUE, 10, - pa_rtclock_usec(), + pa_rtclock_now(), FALSE); u->ctag = 1; u->device_index = u->channel = PA_INVALID_INDEX; @@ -1853,7 +1973,7 @@ int pa__init(pa_module*m) { goto fail; } - if (!(u->client = pa_socket_client_new_string(m->core->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { + if (!(u->client = pa_socket_client_new_string(m->core->mainloop, TRUE, u->server_name, PA_NATIVE_DEFAULT_PORT))) { pa_log("Failed to connect to server '%s'", u->server_name); goto fail; } @@ -1863,7 +1983,7 @@ int pa__init(pa_module*m) { #ifdef TUNNEL_SINK if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) - dn = pa_sprintf_malloc("tunnel.%s", u->server_name); + dn = pa_sprintf_malloc("tunnel-sink.%s", u->server_name); pa_sink_new_data_init(&data); data.driver = __FILE__; @@ -1907,7 +2027,7 @@ int pa__init(pa_module*m) { #else if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) - dn = pa_sprintf_malloc("tunnel.%s", u->server_name); + dn = pa_sprintf_malloc("tunnel-source.%s", u->server_name); pa_source_new_data_init(&data); data.driver = __FILE__; @@ -1943,6 +2063,8 @@ int pa__init(pa_module*m) { pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); + + u->mcalign = pa_mcalign_new(pa_frame_size(&u->source->sample_spec)); #endif pa_xfree(dn); @@ -1956,7 +2078,7 @@ int pa__init(pa_module*m) { u->fragsize = (uint32_t) -1; #endif - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("module-tunnel", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } @@ -1979,7 +2101,7 @@ fail: pa_xfree(dn); - return -1; + return -1; } void pa__done(pa_module*m) { @@ -2036,6 +2158,11 @@ void pa__done(pa_module*m) { if (u->time_event) u->core->mainloop->time_free(u->time_event); +#ifndef TUNNEL_SINK + if (u->mcalign) + pa_mcalign_free(u->mcalign); +#endif + #ifdef TUNNEL_SINK pa_xfree(u->sink_name); #else diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c new file mode 100644 index 00000000..63ad1952 --- /dev/null +++ b/src/modules/module-udev-detect.c @@ -0,0 +1,809 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <dirent.h> +#include <sys/inotify.h> +#include <libudev.h> + +#include <pulse/timeval.h> + +#include <pulsecore/modargs.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/ratelimit.h> + +#include "module-udev-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "tsched=<enable system timer based scheduling mode?> " + "ignore_dB=<ignore dB information from the device?> " + "sync_volume=<syncronize sw and hw voluchanges in IO-thread?>"); + +struct device { + char *path; + pa_bool_t need_verify; + char *card_name; + char *args; + uint32_t module; + pa_ratelimit ratelimit; +}; + +struct userdata { + pa_core *core; + pa_hashmap *devices; + + pa_bool_t use_tsched:1; + pa_bool_t ignore_dB:1; + pa_bool_t sync_volume:1; + + struct udev* udev; + struct udev_monitor *monitor; + pa_io_event *udev_io; + + int inotify_fd; + pa_io_event *inotify_io; +}; + +static const char* const valid_modargs[] = { + "tsched", + "ignore_dB", + "sync_volume", + NULL +}; + +static int setup_inotify(struct userdata *u); + +static void device_free(struct device *d) { + pa_assert(d); + + pa_xfree(d->path); + pa_xfree(d->card_name); + pa_xfree(d->args); + pa_xfree(d); +} + +static const char *path_get_card_id(const char *path) { + const char *e; + + if (!path) + return NULL; + + if (!(e = strrchr(path, '/'))) + return NULL; + + if (!pa_startswith(e, "/card")) + return NULL; + + return e + 5; +} + +static char *card_get_sysattr(const char *card_idx, const char *name) { + struct udev *udev; + struct udev_device *card = NULL; + char *t, *r = NULL; + const char *v; + + pa_assert(card_idx); + pa_assert(name); + + if (!(udev = udev_new())) { + pa_log_error("Failed to allocate udev context."); + goto finish; + } + + t = pa_sprintf_malloc("%s/class/sound/card%s", udev_get_sys_path(udev), card_idx); + card = udev_device_new_from_syspath(udev, t); + pa_xfree(t); + + if (!card) { + pa_log_error("Failed to get card object."); + goto finish; + } + + if ((v = udev_device_get_sysattr_value(card, name)) && *v) + r = pa_xstrdup(v); + +finish: + + if (card) + udev_device_unref(card); + + if (udev) + udev_unref(udev); + + return r; +} + +static pa_bool_t pcm_is_modem(const char *card_idx, const char *pcm) { + char *sysfs_path, *pcm_class; + pa_bool_t is_modem; + + pa_assert(card_idx); + pa_assert(pcm); + + /* Check /sys/class/sound/card.../pcmC...../pcm_class. An HDA + * modem can be used simultaneously with generic + * playback/record. */ + + sysfs_path = pa_sprintf_malloc("pcmC%sD%s/pcm_class", card_idx, pcm); + pcm_class = card_get_sysattr(card_idx, sysfs_path); + is_modem = pcm_class && pa_streq(pcm_class, "modem"); + pa_xfree(pcm_class); + pa_xfree(sysfs_path); + + return is_modem; +} + +static pa_bool_t is_card_busy(const char *id) { + char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL; + DIR *card_dir = NULL, *pcm_dir = NULL; + FILE *status_file = NULL; + size_t len; + struct dirent *space = NULL, *de; + pa_bool_t busy = FALSE; + int r; + + pa_assert(id); + + /* This simply uses /proc/asound/card.../pcm.../sub.../status to + * check whether there is still a process using this audio device. */ + + card_path = pa_sprintf_malloc("/proc/asound/card%s", id); + + if (!(card_dir = opendir(card_path))) { + pa_log_warn("Failed to open %s: %s", card_path, pa_cstrerror(errno)); + goto fail; + } + + len = offsetof(struct dirent, d_name) + fpathconf(dirfd(card_dir), _PC_NAME_MAX) + 1; + space = pa_xmalloc(len); + + for (;;) { + de = NULL; + + if ((r = readdir_r(card_dir, space, &de)) != 0) { + pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r)); + goto fail; + } + + if (!de) + break; + + if (!pa_startswith(de->d_name, "pcm")) + continue; + + if (pcm_is_modem(id, de->d_name + 3)) + continue; + + pa_xfree(pcm_path); + pcm_path = pa_sprintf_malloc("%s/%s", card_path, de->d_name); + + if (pcm_dir) + closedir(pcm_dir); + + if (!(pcm_dir = opendir(pcm_path))) { + pa_log_warn("Failed to open %s: %s", pcm_path, pa_cstrerror(errno)); + continue; + } + + for (;;) { + char line[32]; + + if ((r = readdir_r(pcm_dir, space, &de)) != 0) { + pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r)); + goto fail; + } + + if (!de) + break; + + if (!pa_startswith(de->d_name, "sub")) + continue; + + pa_xfree(sub_status); + sub_status = pa_sprintf_malloc("%s/%s/status", pcm_path, de->d_name); + + if (status_file) + fclose(status_file); + + if (!(status_file = pa_fopen_cloexec(sub_status, "r"))) { + pa_log_warn("Failed to open %s: %s", sub_status, pa_cstrerror(errno)); + continue; + } + + if (!(fgets(line, sizeof(line)-1, status_file))) { + pa_log_warn("Failed to read from %s: %s", sub_status, pa_cstrerror(errno)); + continue; + } + + if (!pa_streq(line, "closed\n")) { + busy = TRUE; + break; + } + } + } + +fail: + + pa_xfree(card_path); + pa_xfree(pcm_path); + pa_xfree(sub_status); + pa_xfree(space); + + if (card_dir) + closedir(card_dir); + + if (pcm_dir) + closedir(pcm_dir); + + if (status_file) + fclose(status_file); + + return busy; +} + +static void verify_access(struct userdata *u, struct device *d) { + char *cd; + pa_card *card; + pa_bool_t accessible; + + pa_assert(u); + pa_assert(d); + + cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path)); + accessible = access(cd, R_OK|W_OK) >= 0; + pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible)); + + pa_xfree(cd); + + if (d->module == PA_INVALID_INDEX) { + + /* If we are not loaded, try to load */ + + if (accessible) { + pa_module *m; + pa_bool_t busy; + + /* Check if any of the PCM devices that belong to this + * card are currently busy. If they are, don't try to load + * right now, to make sure the probing phase can + * successfully complete. When the current user of the + * device closes it we will get another notification via + * inotify and can then recheck. */ + + busy = is_card_busy(path_get_card_id(d->path)); + pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy)); + + if (!busy) { + + /* So, why do we rate limit here? It's certainly ugly, + * but there seems to be no other way. Problem is + * this: if we are unable to configure/probe an audio + * device after opening it we will close it again and + * the module initialization will fail. This will then + * cause an inotify event on the device node which + * will be forwarded to us. We then try to reopen the + * audio device again, practically entering a busy + * loop. + * + * A clean fix would be if we would be able to ignore + * our own inotify close events. However, inotify + * lacks such functionality. Also, during probing of + * the device we cannot really distuingish between + * other processes causing EBUSY or ourselves, which + * means we have no way to figure out if the probing + * during opening was canceled by a "try again" + * failure or a "fatal" failure. */ + + if (pa_ratelimit_test(&d->ratelimit, PA_LOG_DEBUG)) { + pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args); + m = pa_module_load(u->core, "module-alsa-card", d->args); + + if (m) { + d->module = m->index; + pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name); + } else + pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name); + } else + pa_log_warn("Tried to configure %s (%s) more often than %u times in %llus", + d->path, + d->card_name, + d->ratelimit.burst, + (long long unsigned) (d->ratelimit.interval / PA_USEC_PER_SEC)); + } + } + + } else { + + /* If we are already loaded update suspend status with + * accessible boolean */ + + if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) + pa_card_suspend(card, !accessible, PA_SUSPEND_SESSION); + } +} + +static void card_changed(struct userdata *u, struct udev_device *dev) { + struct device *d; + const char *path; + const char *t; + char *n; + + pa_assert(u); + pa_assert(dev); + + /* Maybe /dev/snd is now available? */ + setup_inotify(u); + + path = udev_device_get_devpath(dev); + + if ((d = pa_hashmap_get(u->devices, path))) { + verify_access(u, d); + return; + } + + d = pa_xnew0(struct device, 1); + d->path = pa_xstrdup(path); + d->module = PA_INVALID_INDEX; + PA_INIT_RATELIMIT(d->ratelimit, 10*PA_USEC_PER_SEC, 5); + + if (!(t = udev_device_get_property_value(dev, "PULSE_NAME"))) + if (!(t = udev_device_get_property_value(dev, "ID_ID"))) + if (!(t = udev_device_get_property_value(dev, "ID_PATH"))) + t = path_get_card_id(path); + + n = pa_namereg_make_valid_name(t); + d->card_name = pa_sprintf_malloc("alsa_card.%s", n); + d->args = pa_sprintf_malloc("device_id=\"%s\" " + "name=\"%s\" " + "card_name=\"%s\" " + "namereg_fail=false " + "tsched=%s " + "ignore_dB=%s " + "sync_volume=%s " + "card_properties=\"module-udev-detect.discovered=1\"", + path_get_card_id(path), + n, + d->card_name, + pa_yes_no(u->use_tsched), + pa_yes_no(u->ignore_dB), + pa_yes_no(u->sync_volume)); + pa_xfree(n); + + pa_hashmap_put(u->devices, d->path, d); + + verify_access(u, d); +} + +static void remove_card(struct userdata *u, struct udev_device *dev) { + struct device *d; + + pa_assert(u); + pa_assert(dev); + + if (!(d = pa_hashmap_remove(u->devices, udev_device_get_devpath(dev)))) + return; + + pa_log_info("Card %s removed.", d->path); + + if (d->module != PA_INVALID_INDEX) + pa_module_unload_request_by_index(u->core, d->module, TRUE); + + device_free(d); +} + +static void process_device(struct userdata *u, struct udev_device *dev) { + const char *action, *ff; + + pa_assert(u); + pa_assert(dev); + + if (udev_device_get_property_value(dev, "PULSE_IGNORE")) { + pa_log_debug("Ignoring %s, because marked so.", udev_device_get_devpath(dev)); + return; + } + + if ((ff = udev_device_get_property_value(dev, "SOUND_CLASS")) && + pa_streq(ff, "modem")) { + pa_log_debug("Ignoring %s, because it is a modem.", udev_device_get_devpath(dev)); + return; + } + + action = udev_device_get_action(dev); + + if (action && pa_streq(action, "remove")) + remove_card(u, dev); + else if ((!action || pa_streq(action, "change")) && udev_device_get_property_value(dev, "SOUND_INITIALIZED")) + card_changed(u, dev); + + /* For an explanation why we don't look for 'add' events here + * have a look into /lib/udev/rules.d/78-sound-card.rules! */ +} + +static void process_path(struct userdata *u, const char *path) { + struct udev_device *dev; + + if (!path_get_card_id(path)) + return; + + if (!(dev = udev_device_new_from_syspath(u->udev, path))) { + pa_log("Failed to get udev device object from udev."); + return; + } + + process_device(u, dev); + udev_device_unref(dev); +} + +static void monitor_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct userdata *u = userdata; + struct udev_device *dev; + + pa_assert(a); + + if (!(dev = udev_monitor_receive_device(u->monitor))) { + pa_log("Failed to get udev device object from monitor."); + goto fail; + } + + if (!path_get_card_id(udev_device_get_devpath(dev))) { + udev_device_unref(dev); + return; + } + + process_device(u, dev); + udev_device_unref(dev); + return; + +fail: + a->io_free(u->udev_io); + u->udev_io = NULL; +} + +static pa_bool_t pcm_node_belongs_to_device( + struct device *d, + const char *node) { + + char *cd; + pa_bool_t b; + + cd = pa_sprintf_malloc("pcmC%sD", path_get_card_id(d->path)); + b = pa_startswith(node, cd); + pa_xfree(cd); + + return b; +} + +static pa_bool_t control_node_belongs_to_device( + struct device *d, + const char *node) { + + char *cd; + pa_bool_t b; + + cd = pa_sprintf_malloc("controlC%s", path_get_card_id(d->path)); + b = pa_streq(node, cd); + pa_xfree(cd); + + return b; +} + +static void inotify_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct { + struct inotify_event e; + char name[NAME_MAX]; + } buf; + struct userdata *u = userdata; + static int type = 0; + pa_bool_t deleted = FALSE; + struct device *d; + void *state; + + for (;;) { + ssize_t r; + struct inotify_event *event; + + pa_zero(buf); + if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) { + + if (r < 0 && errno == EAGAIN) + break; + + pa_log("read() from inotify failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + goto fail; + } + + event = &buf.e; + while (r > 0) { + size_t len; + + if ((size_t) r < sizeof(struct inotify_event)) { + pa_log("read() too short."); + goto fail; + } + + len = sizeof(struct inotify_event) + event->len; + + if ((size_t) r < len) { + pa_log("Payload missing."); + goto fail; + } + + /* From udev we get the guarantee that the control + * device's ACL is changed last. To avoid races when ACLs + * are changed we hence watch only the control device */ + if (((event->mask & IN_ATTRIB) && pa_startswith(event->name, "controlC"))) + PA_HASHMAP_FOREACH(d, u->devices, state) + if (control_node_belongs_to_device(d, event->name)) + d->need_verify = TRUE; + + /* ALSA doesn't really give us any guarantee on the closing + * order, so let's simply hope */ + if (((event->mask & IN_CLOSE_WRITE) && pa_startswith(event->name, "pcmC"))) + PA_HASHMAP_FOREACH(d, u->devices, state) + if (pcm_node_belongs_to_device(d, event->name)) + d->need_verify = TRUE; + + /* /dev/snd/ might have been removed */ + if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF))) + deleted = TRUE; + + event = (struct inotify_event*) ((uint8_t*) event + len); + r -= len; + } + } + + PA_HASHMAP_FOREACH(d, u->devices, state) + if (d->need_verify) { + d->need_verify = FALSE; + verify_access(u, d); + } + + if (!deleted) + return; + +fail: + if (u->inotify_io) { + a->io_free(u->inotify_io); + u->inotify_io = NULL; + } + + if (u->inotify_fd >= 0) { + pa_close(u->inotify_fd); + u->inotify_fd = -1; + } +} + +static int setup_inotify(struct userdata *u) { + char *dev_snd; + int r; + + if (u->inotify_fd >= 0) + return 0; + + if ((u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + pa_log("inotify_init1() failed: %s", pa_cstrerror(errno)); + return -1; + } + + dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev)); + r = inotify_add_watch(u->inotify_fd, dev_snd, IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF); + pa_xfree(dev_snd); + + if (r < 0) { + int saved_errno = errno; + + pa_close(u->inotify_fd); + u->inotify_fd = -1; + + if (saved_errno == ENOENT) { + pa_log_debug("/dev/snd/ is apparently not existing yet, retrying to create inotify watch later."); + return 0; + } + + if (saved_errno == ENOSPC) { + pa_log("You apparently ran out of inotify watches, probably because Tracker/Beagle took them all away. " + "I wished people would do their homework first and fix inotify before using it for watching whole " + "directory trees which is something the current inotify is certainly not useful for. " + "Please make sure to drop the Tracker/Beagle guys a line complaining about their broken use of inotify."); + return 0; + } + + pa_log("inotify_add_watch() failed: %s", pa_cstrerror(saved_errno)); + return -1; + } + + pa_assert_se(u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, inotify_cb, u)); + + return 0; +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma; + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + int fd; + pa_bool_t use_tsched = TRUE, ignore_dB = FALSE, sync_volume = m->core->sync_volume; + + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->inotify_fd = -1; + + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse tsched= argument."); + goto fail; + } + u->use_tsched = use_tsched; + + if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) { + pa_log("Failed to parse ignore_dB= argument."); + goto fail; + } + u->ignore_dB = ignore_dB; + + if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) { + pa_log("Failed to parse sync_volume= argument."); + goto fail; + } + u->sync_volume = sync_volume; + + if (!(u->udev = udev_new())) { + pa_log("Failed to initialize udev library."); + goto fail; + } + + if (setup_inotify(u) < 0) + goto fail; + + if (!(u->monitor = udev_monitor_new_from_netlink(u->udev, "udev"))) { + pa_log("Failed to initialize monitor."); + goto fail; + } + + if (udev_monitor_filter_add_match_subsystem_devtype(u->monitor, "sound", NULL) < 0) { + pa_log("Failed to subscribe to sound devices."); + goto fail; + } + + errno = 0; + if (udev_monitor_enable_receiving(u->monitor) < 0) { + pa_log("Failed to enable monitor: %s", pa_cstrerror(errno)); + if (errno == EPERM) + pa_log_info("Most likely your kernel is simply too old and " + "allows only priviliged processes to listen to device events. " + "Please upgrade your kernel to at least 2.6.30."); + goto fail; + } + + if ((fd = udev_monitor_get_fd(u->monitor)) < 0) { + pa_log("Failed to get udev monitor fd."); + goto fail; + } + + pa_assert_se(u->udev_io = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, monitor_cb, u)); + + if (!(enumerate = udev_enumerate_new(u->udev))) { + pa_log("Failed to initialize udev enumerator."); + goto fail; + } + + if (udev_enumerate_add_match_subsystem(enumerate, "sound") < 0) { + pa_log("Failed to match to subsystem."); + goto fail; + } + + if (udev_enumerate_scan_devices(enumerate) < 0) { + pa_log("Failed to scan for devices."); + goto fail; + } + + first = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(item, first) + process_path(u, udev_list_entry_get_name(item)); + + udev_enumerate_unref(enumerate); + + pa_log_info("Found %u cards.", pa_hashmap_size(u->devices)); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (enumerate) + udev_enumerate_unref(enumerate); + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->udev_io) + m->core->mainloop->io_free(u->udev_io); + + if (u->monitor) + udev_monitor_unref(u->monitor); + + if (u->udev) + udev_unref(u->udev); + + if (u->inotify_io) + m->core->mainloop->io_free(u->inotify_io); + + if (u->inotify_fd >= 0) + pa_close(u->inotify_fd); + + if (u->devices) { + struct device *d; + + while ((d = pa_hashmap_steal_first(u->devices))) + device_free(d); + + pa_hashmap_free(u->devices, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c new file mode 100644 index 00000000..a6be2446 --- /dev/null +++ b/src/modules/module-virtual-sink.c @@ -0,0 +1,669 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Intel Corporation + Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* TODO: Some plugins cause latency, and some even report it by using a control + out port. We don't currently use the latency information. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> + +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/ltdl-helper.h> + +#include "module-virtual-sink-symdef.h" + +PA_MODULE_AUTHOR("Pierre-Louis Bossart"); +PA_MODULE_DESCRIPTION(_("Virtual sink")); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + _("sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "master=<name of sink to filter> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> " + )); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +struct userdata { + pa_module *module; + + /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ + /* pa_bool_t autoloaded; */ + + pa_sink *sink; + pa_sink_input *sink_input; + + pa_memblockq *memblockq; + + pa_bool_t auto_desc; + unsigned channels; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", + "master", + "format", + "rate", + "channels", + "channel_map", + "use_volume_sharing", + "force_flat_volume", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* The sink is _put() before the sink input is, so let's + * make sure we don't access it in that time. Also, the + * sink input is first shut down, the sink second. */ + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(u->sink_input->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec); + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return 0; + + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + return 0; +} + +/* Called from I/O thread context */ +static void sink_request_rewind_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, + s->thread_info.rewind_nbytes + + pa_memblockq_get_length(u->memblockq), TRUE, FALSE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) || + !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) + return; + + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); +} + +/* Called from main context */ +static void sink_set_volume_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void sink_set_mute_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) || + !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + return; + + pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted); +} + +/* Called from I/O thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + struct userdata *u; + float *src, *dst; + size_t fs; + unsigned n, c; + pa_memchunk tchunk; + pa_usec_t current_latency PA_GCC_UNUSED; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + pa_assert_se(u = i->userdata); + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + /* (1) IF YOU NEED A FIXED BLOCK SIZE USE + * pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS + * WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY + * PREFERRED. */ + while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + pa_sink_render(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + + /* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT + * NECESSARY */ + tchunk.length = PA_MIN(nbytes, tchunk.length); + pa_assert(tchunk.length > 0); + + fs = pa_frame_size(&i->sample_spec); + n = (unsigned) (tchunk.length / fs); + + pa_assert(n > 0); + + chunk->index = 0; + chunk->length = n*fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); + + pa_memblockq_drop(u->memblockq, chunk->length); + + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(chunk->memblock); + + /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */ + + /* As an example, copy input to output */ + for (c = 0; c < u->channels; c++) { + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, + dst+c, u->channels * sizeof(float), + src+c, u->channels * sizeof(float), + n); + } + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(chunk->memblock); + + pa_memblock_unref(tchunk.memblock); + + /* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */ + current_latency = + /* Get the latency of the master sink */ + pa_sink_get_latency_within_thread(i->sink) + + + /* Add the latency internal to our sink input on top */ + pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); + + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + size_t amount = 0; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t max_rewrite; + + max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); + u->sink->thread_info.rewind_nbytes = 0; + + if (amount > 0) { + pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, TRUE); + + /* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER */ + } + } + + pa_sink_process_rewind(u->sink, amount); + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); + pa_sink_set_max_rewind_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES + * OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */ + + pa_sink_set_max_request_within_thread(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE + * BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS + * USEFUL FOR THAT. */ + + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); + + pa_sink_set_rtpoll(u->sink, NULL); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll); + pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + + /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE + * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */ + pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency); + + /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND + * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT + * HERE. SEE (6) */ + pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i)); + pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i)); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* The order here matters! We first kill the sink input, followed + * by the sink. That means the sink callbacks must be protected + * against an unconnected sink input! */ + pa_sink_input_unlink(u->sink_input); + pa_sink_unlink(u->sink); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE); + } +} + +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (dest) { + pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq); + pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags); + } else + pa_sink_set_asyncmsgq(u->sink, NULL); + + if (u->auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", + pa_proplist_gets(u->sink->proplist, "device.vsink.name"), z ? z : dest->name); + + pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_volume_changed(u->sink, &i->volume); +} + +/* Called from main context */ +static void sink_input_mute_changed_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_mute_changed(u->sink, i->muted); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + pa_sink *master=NULL; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + pa_bool_t use_volume_sharing = FALSE; + pa_bool_t force_flat_volume = FALSE; + pa_memchunk silence; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK))) { + pa_log("Master sink not found"); + goto fail; + } + + pa_assert(master); + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) { + pa_log("use_volume_sharing= expects a boolean argument"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + goto fail; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->module = m; + m->userdata = u; + u->channels = ss.channels; + + /* Create sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.vsink", master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + pa_proplist_sets(sink_data.proplist, "device.vsink.name", sink_data.name); + + if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&sink_data); + goto fail; + } + + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Sink %s on %s", sink_data.name, z ? z : master->name); + } + + u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0) + | (force_flat_volume ? PA_SINK_FLAT_VOLUME : 0)); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_volume = use_volume_sharing ? NULL : sink_set_volume_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + + /* Create sink input */ + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + pa_sink_input_new_data_set_sink(&sink_input_data, master, FALSE); + sink_input_data.origin_sink = u->sink; + pa_proplist_setf(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Sink Stream from %s", pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + pa_sink_input_new(&u->sink_input, m->core, &sink_input_data); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; + u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; + u->sink_input->moving = sink_input_moving_cb; + u->sink_input->volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb; + u->sink_input->mute_changed = sink_input_mute_changed_cb; + u->sink_input->userdata = u; + + u->sink->input_to_master = u->sink_input; + + pa_sink_input_get_silence(u->sink_input, &silence); + u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + + /* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */ + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + /* See comments in sink_input_kill_cb() above regarding + * destruction order! */ + + if (u->sink_input) + pa_sink_input_unlink(u->sink_input); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->sink_input) + pa_sink_input_unref(u->sink_input); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + pa_xfree(u); +} diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c new file mode 100644 index 00000000..e15f4b94 --- /dev/null +++ b/src/modules/module-virtual-source.c @@ -0,0 +1,746 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Intel Corporation + Contributor: Pierre-Louis Bossart <pierre-louis.bossart@intel.com> + + PulseAudio 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. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulse/i18n.h> + +#include <pulsecore/macro.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/ltdl-helper.h> + +#include "module-virtual-source-symdef.h" + +PA_MODULE_AUTHOR("Pierre-Louis Bossart"); +PA_MODULE_DESCRIPTION("Virtual source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + _("source_name=<name for the source> " + "source_properties=<properties for the source> " + "master=<name of source to filter> " + "uplink_sink=<name> (optional)" + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> " + )); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) +#define BLOCK_USEC 1000 /* FIXME */ + +struct userdata { + pa_module *module; + + /* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */ + /* pa_bool_t autoloaded; */ + + pa_source *source; + pa_source_output *source_output; + + pa_memblockq *memblockq; + + pa_bool_t auto_desc; + unsigned channels; + + /* optional fields for uplink sink */ + pa_sink *sink; + pa_usec_t block_usec; + pa_memblockq *sink_memblockq; + +}; + +static const char* const valid_modargs[] = { + "source_name", + "source_properties", + "master", + "uplink_sink", + "format", + "rate", + "channels", + "channel_map", + "use_volume_sharing", + "force_flat_volume", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* there's no real latency here */ + *((pa_usec_t*) data) = 0; + + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SINK_IS_LINKED(state)) { + return 0; + } + + if (state == PA_SINK_RUNNING) { + /* need to wake-up source if it was suspended */ + pa_source_suspend(u->source, FALSE, PA_SUSPEND_ALL); + + /* FIXME: if there's no client connected, the source will suspend + and playback will be stuck. You'd want to prevent the source from + sleeping when the uplink sink is active; even if the audio is + discarded at least the app isn't stuck */ + + } else { + /* nothing to do, if the sink becomes idle or suspended let + module-suspend-idle handle the sources later */ + } + + return 0; +} + +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + /* FIXME: there's no latency support */ + +} + + +/* Called from I/O thread context */ +static void sink_request_rewind_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + /* Do nothing */ + pa_sink_process_rewind(u->sink, 0); + +} + +/* Called from I/O thread context */ +static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + /* The source is _put() before the source output is, so let's + * make sure we don't access it in that time. Also, the + * source output is first shut down, the source second. */ + if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) { + *((pa_usec_t*) data) = 0; + return 0; + } + + *((pa_usec_t*) data) = + + /* Get the latency of the master source */ + pa_source_get_latency_within_thread(u->source_output->source) + + + /* Add the latency internal to our source output on top */ + /* FIXME, no idea what I am doing here */ + pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec); + + return 0; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int source_set_state_cb(pa_source *s, pa_source_state_t state) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(state) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return 0; + + pa_source_output_cork(u->source_output, state == PA_SOURCE_SUSPENDED); + return 0; +} + +/* Called from I/O thread context */ +static void source_update_requested_latency_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) || + !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) + return; + + /* Just hand this one over to the master source */ + pa_source_output_set_requested_latency_within_thread( + u->source_output, + pa_source_get_requested_latency_within_thread(s)); +} + +/* Called from main context */ +static void source_set_volume_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, TRUE); +} + +/* Called from main context */ +static void source_set_mute_cb(pa_source *s) { + struct userdata *u; + + pa_source_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || + !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) + return; + + pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); +} + +/* Called from input thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) { + pa_log("push when no link?"); + return; + } + + /* PUT YOUR CODE HERE TO DO SOMETHING WITH THE SOURCE DATA */ + + /* if uplink sink exists, pull data from there; simplify by using + same length as chunk provided by source */ + if(u->sink && (pa_sink_get_state(u->sink) == PA_SINK_RUNNING)) { + pa_memchunk tchunk; + size_t nbytes = chunk->length; + pa_mix_info streams[2]; + pa_memchunk target_chunk; + void *target; + int ch; + + /* Hmm, process any rewind request that might be queued up */ + pa_sink_process_rewind(u->sink, 0); + + /* get data from the sink */ + while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) { + pa_memchunk nchunk; + + /* make sure we get nbytes from the sink with render_full, + otherwise we cannot mix with the uplink */ + pa_sink_render_full(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->sink_memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } + pa_assert(tchunk.length == chunk->length); + + /* move the read pointer for sink memblockq */ + pa_memblockq_drop(u->sink_memblockq, tchunk.length); + + /* allocate target chunk */ + /* this could probably be done in-place, but having chunk as both + the input and output creates issues with reference counts */ + target_chunk.index = 0; + target_chunk.length = chunk->length; + pa_assert(target_chunk.length == chunk->length); + + target_chunk.memblock = pa_memblock_new(o->source->core->mempool, + target_chunk.length); + pa_assert( target_chunk.memblock ); + + /* get target pointer */ + target = (void*)((uint8_t*)pa_memblock_acquire(target_chunk.memblock) + + target_chunk.index); + + /* set-up mixing structure + volume was taken care of in sink and source already */ + streams[0].chunk = *chunk; + for(ch=0;ch<o->sample_spec.channels;ch++) + streams[0].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ + streams[0].volume.channels = o->sample_spec.channels; + + streams[1].chunk = tchunk; + for(ch=0;ch<o->sample_spec.channels;ch++) + streams[1].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ + streams[1].volume.channels = o->sample_spec.channels; + + /* do mixing */ + pa_mix(streams, /* 2 streams to be mixed */ + 2, + target, /* put result in target chunk */ + chunk->length, /* same length as input */ + (const pa_sample_spec *)&o->sample_spec, /* same sample spec for input and output */ + NULL, /* no volume information */ + FALSE); /* no mute */ + + pa_memblock_release(target_chunk.memblock); + pa_memblock_unref(tchunk.memblock); /* clean-up */ + + /* forward the data to the virtual source */ + pa_source_post(u->source, &target_chunk); + + pa_memblock_unref(target_chunk.memblock); /* clean-up */ + + } else { + /* forward the data to the virtual source */ + pa_source_post(u->source, chunk); + } + + +} + +/* Called from input thread context */ +static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + /* FIXME, no idea what I am doing here */ +#if 0 + pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL); + u->send_counter -= (int64_t) nbytes; +#endif +} + +/* Called from output thread context */ +static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + + /* FIXME, nothing to do here ? */ + + return pa_source_output_process_msg(obj, code, data, offset, chunk); +} + +/* Called from output thread context */ +static void source_output_attach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll); + pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency); + pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency); + pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o)); + + pa_source_attach_within_thread(u->source); +} + +/* Called from output thread context */ +static void source_output_detach_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + pa_source_detach_within_thread(u->source); + pa_source_set_rtpoll(u->source, NULL); +} + +/* Called from output thread context */ +static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert_se(u = o->userdata); + + /* FIXME */ +#if 0 + if (PA_SOURCE_OUTPUT_IS_LINKED(state) && o->thread_info.state == PA_SOURCE_OUTPUT_INIT) { + + u->skip = pa_usec_to_bytes(PA_CLIP_SUB(pa_source_get_latency_within_thread(o->source), + u->latency), + &o->sample_spec); + + pa_log_info("Skipping %lu bytes", (unsigned long) u->skip); + } +#endif +} + +/* Called from main thread */ +static void source_output_kill_cb(pa_source_output *o) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + /* The order here matters! We first kill the source output, followed + * by the source. That means the source callbacks must be protected + * against an unconnected source output! */ + pa_source_output_unlink(u->source_output); + pa_source_unlink(u->source); + + pa_source_output_unref(u->source_output); + u->source_output = NULL; + + pa_source_unref(u->source); + u->source = NULL; + + pa_module_unload_request(u->module, TRUE); +} + +/* Called from main thread */ +static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + /* FIXME */ + //return dest != u->source_input->source->monitor_source; + + return TRUE; +} + +/* Called from main thread */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + struct userdata *u; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert_se(u = o->userdata); + + if (dest) { + pa_source_set_asyncmsgq(u->source, dest->asyncmsgq); + pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags); + } else + pa_source_set_asyncmsgq(u->source, NULL); + + if (u->auto_desc && dest) { + const char *z; + pa_proplist *pl; + + pl = pa_proplist_new(); + z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", + pa_proplist_gets(u->source->proplist, "device.vsource.name"), z ? z : dest->name); + + pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); + } +} + + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + pa_source *master=NULL; + pa_source_output_new_data source_output_data; + pa_source_new_data source_data; + pa_bool_t use_volume_sharing = FALSE; + pa_bool_t force_flat_volume = FALSE; + + /* optional for uplink_sink */ + pa_sink_new_data sink_data; + size_t nbytes; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SOURCE))) { + pa_log("Master source not found"); + goto fail; + } + + pa_assert(master); + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) { + pa_log("use_volume_sharing= expects a boolean argument"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + goto fail; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + if (!u) { + pa_log("Failed to alloc userdata"); + goto fail; + } + u->module = m; + m->userdata = u; + u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); + if (!u->memblockq) { + pa_log("Failed to create source memblockq."); + goto fail; + } + u->channels = ss.channels; + + /* Create source */ + pa_source_new_data_init(&source_data); + source_data.driver = __FILE__; + source_data.module = m; + if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) + source_data.name = pa_sprintf_malloc("%s.vsource", master->name); + pa_source_new_data_set_sample_spec(&source_data, &ss); + pa_source_new_data_set_channel_map(&source_data, &map); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + pa_proplist_sets(source_data.proplist, "device.vsource.name", source_data.name); + + if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_source_new_data_done(&source_data); + goto fail; + } + + if ((u->auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", source_data.name, z ? z : master->name); + } + + u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0) + | (force_flat_volume ? PA_SOURCE_FLAT_VOLUME : 0)); + + pa_source_new_data_done(&source_data); + + if (!u->source) { + pa_log("Failed to create source."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg_cb; + u->source->set_state = source_set_state_cb; + u->source->update_requested_latency = source_update_requested_latency_cb; + u->source->set_volume = use_volume_sharing ? NULL : source_set_volume_cb; + u->source->set_mute = source_set_mute_cb; + u->source->userdata = u; + + pa_source_set_asyncmsgq(u->source, master->asyncmsgq); + + /* Create source output */ + pa_source_output_new_data_init(&source_output_data); + source_output_data.driver = __FILE__; + source_output_data.module = m; + pa_source_output_new_data_set_source(&source_output_data, master, FALSE); + source_output_data.destination_source = u->source; + /* FIXME + source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */ + + pa_proplist_setf(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Virtual Source Stream of %s", pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_source_output_new_data_set_sample_spec(&source_output_data, &ss); + pa_source_output_new_data_set_channel_map(&source_output_data, &map); + + pa_source_output_new(&u->source_output, m->core, &source_output_data); + pa_source_output_new_data_done(&source_output_data); + + if (!u->source_output) + goto fail; + + u->source_output->parent.process_msg = source_output_process_msg_cb; + u->source_output->push = source_output_push_cb; + u->source_output->process_rewind = source_output_process_rewind_cb; + u->source_output->kill = source_output_kill_cb; + u->source_output->attach = source_output_attach_cb; + u->source_output->detach = source_output_detach_cb; + u->source_output->state_change = source_output_state_change_cb; + u->source_output->may_move_to = source_output_may_move_to_cb; + u->source_output->moving = source_output_moving_cb; + u->source_output->userdata = u; + + u->source->output_from_master = u->source_output; + + pa_source_put(u->source); + pa_source_output_put(u->source_output); + + /* Create optional uplink sink */ + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if ((sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "uplink_sink", NULL)))) { + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "uplink sink"); + pa_proplist_sets(sink_data.proplist, "device.uplink_sink.name", sink_data.name); + + if ((u->auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) { + const char *z; + + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Uplink Sink %s on %s", sink_data.name, z ? z : master->name); + } + + u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); + if (!u->sink_memblockq) { + pa_log("Failed to create sink memblockq."); + goto fail; + } + + u->sink = pa_sink_new(m->core, &sink_data, 0); /* FIXME, sink has no capabilities */ + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg_cb; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->request_rewind = sink_request_rewind_cb; + u->sink->set_state = sink_set_state_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + + /* FIXME: no idea what I am doing here */ + u->block_usec = BLOCK_USEC; + nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + pa_sink_set_max_rewind(u->sink, nbytes); + pa_sink_set_max_request(u->sink, nbytes); + + pa_sink_put(u->sink); + } else { + /* optional uplink sink not enabled */ + u->sink = NULL; + } + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_source_linked_by(u->source); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + /* See comments in source_output_kill_cb() above regarding + * destruction order! */ + + if (u->source_output) + pa_source_output_unlink(u->source_output); + + if (u->source) + pa_source_unlink(u->source); + + if (u->source_output) + pa_source_output_unref(u->source_output); + + if (u->source) + pa_source_unref(u->source); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->sink_memblockq) + pa_memblockq_free(u->sink_memblockq); + + pa_xfree(u); +} diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 91da598e..a344c5eb 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -48,6 +48,7 @@ static const char* const valid_modargs[] = { int pa__init(pa_module*m) { pa_modargs *ma = NULL; pa_bool_t restore_device = TRUE, restore_volume = TRUE; + pa_module *n; char *t; pa_assert(m); @@ -66,17 +67,19 @@ int pa__init(pa_module*m) { pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration."); t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device)); - pa_module_load(m->core, "module-stream-restore", t); + n = pa_module_load(m->core, "module-stream-restore", t); pa_xfree(t); - pa_module_unload_request(m, TRUE); + if (n) + pa_module_unload_request(m, TRUE); pa_modargs_free(ma); - return 0; + + return n ? 0 : -1; fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c index 2d35828d..cb02723c 100644 --- a/src/modules/module-waveout.c +++ b/src/modules/module-waveout.c @@ -27,8 +27,6 @@ #include <windows.h> #include <mmsystem.h> -#include <pulse/mainloop-api.h> - #include <pulse/xmalloc.h> #include <pulse/timeval.h> @@ -39,24 +37,27 @@ #include <pulsecore/sample-util.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> #include "module-waveout-symdef.h" -PA_MODULE_AUTHOR("Pierre Ossman") -PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Pierre Ossman"); +PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( "sink_name=<name for the sink> " "source_name=<name for the source> " "device=<device number> " + "device_name=<name of the device> " "record=<enable source?> " "playback=<enable sink?> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " - "fragment_size=<fragment size> " - "channel_map=<channel map>") + "fragment_size=<fragment size>"); #define DEFAULT_SINK_NAME "wave_output" #define DEFAULT_SOURCE_NAME "wave_input" @@ -67,10 +68,12 @@ struct userdata { pa_sink *sink; pa_source *source; pa_core *core; - pa_time_event *event; - pa_defer_event *defer; pa_usec_t poll_timeout; + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + uint32_t fragments, fragment_size; uint32_t free_ofrags, free_ifrags; @@ -92,6 +95,7 @@ static const char* const valid_modargs[] = { "sink_name", "source_name", "device", + "device_name", "record", "playback", "fragments", @@ -103,22 +107,19 @@ static const char* const valid_modargs[] = { NULL }; -static void update_usage(struct userdata *u) { - pa_module_set_used(u->module, - (u->sink ? pa_sink_used_by(u->sink) : 0) + - (u->source ? pa_source_used_by(u->source) : 0)); -} - -static void do_write(struct userdata *u) -{ +static void do_write(struct userdata *u) { uint32_t free_frags; pa_memchunk memchunk; WAVEHDR *hdr; MMRESULT res; + void *p; if (!u->sink) return; + if (!PA_SINK_IS_LINKED(u->sink->state)) + return; + EnterCriticalSection(&u->crit); free_frags = u->free_ofrags; LeaveCriticalSection(&u->crit); @@ -137,18 +138,17 @@ static void do_write(struct userdata *u) len = u->fragment_size - hdr->dwBufferLength; - if (pa_sink_render(u->sink, len, &memchunk) < 0) - break; + pa_sink_render(u->sink, len, &memchunk); - assert(memchunk.memblock); - assert(memchunk.memblock->data); - assert(memchunk.length); + pa_assert(memchunk.memblock); + pa_assert(memchunk.length); if (memchunk.length < len) len = memchunk.length; - memcpy(hdr->lpData + hdr->dwBufferLength, - (char*)memchunk.memblock->data + memchunk.index, len); + p = pa_memblock_acquire(memchunk.memblock); + memcpy(hdr->lpData + hdr->dwBufferLength, (char*) p + memchunk.index, len); + pa_memblock_release(memchunk.memblock); hdr->dwBufferLength += len; @@ -156,24 +156,20 @@ static void do_write(struct userdata *u) memchunk.memblock = NULL; } - /* Insufficient data in sink buffer? */ + /* Underflow detection */ if (hdr->dwBufferLength == 0) { u->sink_underflow = 1; break; } - u->sink_underflow = 0; res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR)); - if (res != MMSYSERR_NOERROR) { - pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d", - res); - } + if (res != MMSYSERR_NOERROR) + pa_log_error("Unable to prepare waveOut block: %d", res); + res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR)); - if (res != MMSYSERR_NOERROR) { - pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d", - res); - } + if (res != MMSYSERR_NOERROR) + pa_log_error("Unable to write waveOut block: %d", res); u->written_bytes += hdr->dwBufferLength; @@ -187,21 +183,22 @@ static void do_write(struct userdata *u) } } -static void do_read(struct userdata *u) -{ +static void do_read(struct userdata *u) { uint32_t free_frags; pa_memchunk memchunk; WAVEHDR *hdr; MMRESULT res; + void *p; if (!u->source) return; - EnterCriticalSection(&u->crit); + if (!PA_SOURCE_IS_LINKED(u->source->state)) + return; + EnterCriticalSection(&u->crit); free_frags = u->free_ifrags; u->free_ifrags = 0; - LeaveCriticalSection(&u->crit); if (free_frags == u->fragments) @@ -214,11 +211,13 @@ static void do_read(struct userdata *u) if (hdr->dwBytesRecorded) { memchunk.memblock = pa_memblock_new(u->core->mempool, hdr->dwBytesRecorded); - assert(memchunk.memblock); + pa_assert(memchunk.memblock); - memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded); + p = pa_memblock_acquire(memchunk.memblock); + memcpy((char*) p, hdr->lpData, hdr->dwBytesRecorded); + pa_memblock_release(memchunk.memblock); - memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded; + memchunk.length = hdr->dwBytesRecorded; memchunk.index = 0; pa_source_post(u->source, &memchunk); @@ -226,15 +225,12 @@ static void do_read(struct userdata *u) } res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR)); - if (res != MMSYSERR_NOERROR) { - pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d", - res); - } + if (res != MMSYSERR_NOERROR) + pa_log_error("Unable to prepare waveIn block: %d", res); + res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR)); - if (res != MMSYSERR_NOERROR) { - pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d", - res); - } + if (res != MMSYSERR_NOERROR) + pa_log_error("Unable to add waveIn block: %d", res); free_frags--; u->cur_ihdr++; @@ -242,172 +238,208 @@ static void do_read(struct userdata *u) } } -static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { +static void thread_func(void *userdata) { struct userdata *u = userdata; - struct timeval ntv; - assert(u); + pa_assert(u); + pa_assert(u->sink || u->source); - update_usage(u); + pa_log_debug("Thread starting up"); - do_write(u); - do_read(u); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); - pa_gettimeofday(&ntv); - pa_timeval_add(&ntv, u->poll_timeout); + pa_thread_mq_install(&u->thread_mq); - a->time_restart(e, &ntv); -} + for (;;) { + int ret; + pa_bool_t need_timer = FALSE; -static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { - struct userdata *u = userdata; + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + do_write(u); + need_timer = TRUE; + } + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + do_read(u); + need_timer = TRUE; + } + + if (need_timer) + pa_rtpoll_set_timer_relative(u->rtpoll, u->poll_timeout); + else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; - assert(u); + if (ret == 0) + goto finish; + } - a->defer_enable(e, 0); +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - do_write(u); - do_read(u); +finish: + pa_log_debug("Thread shutting down"); } static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) { - struct userdata *u = (struct userdata *)inst; + struct userdata *u = (struct userdata*) inst; + if (msg == WOM_OPEN) + pa_log_debug("WaveOut subsystem opened."); + if (msg == WOM_CLOSE) + pa_log_debug("WaveOut subsystem closed."); if (msg != WOM_DONE) return; EnterCriticalSection(&u->crit); - u->free_ofrags++; - assert(u->free_ofrags <= u->fragments); - + pa_assert(u->free_ofrags <= u->fragments); LeaveCriticalSection(&u->crit); } static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) { - struct userdata *u = (struct userdata *)inst; + struct userdata *u = (struct userdata*) inst; + if (msg == WIM_OPEN) + pa_log_debug("WaveIn subsystem opened."); + if (msg == WIM_CLOSE) + pa_log_debug("WaveIn subsystem closed."); if (msg != WIM_DATA) return; EnterCriticalSection(&u->crit); - u->free_ifrags++; - assert(u->free_ifrags <= u->fragments); - + pa_assert(u->free_ifrags <= u->fragments); LeaveCriticalSection(&u->crit); } -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; +static pa_usec_t sink_get_latency(struct userdata *u) { uint32_t free_frags; MMTIME mmt; - assert(s && u && u->sink); + pa_assert(u); + pa_assert(u->sink); memset(&mmt, 0, sizeof(mmt)); mmt.wType = TIME_BYTES; if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR) - return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec); + return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &u->sink->sample_spec); else { EnterCriticalSection(&u->crit); - free_frags = u->free_ofrags; - LeaveCriticalSection(&u->crit); - return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size, - &s->sample_spec); + return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size, &u->sink->sample_spec); } } -static pa_usec_t source_get_latency_cb(pa_source *s) { +static pa_usec_t source_get_latency(struct userdata *u) { pa_usec_t r = 0; - struct userdata *u = s->userdata; uint32_t free_frags; - assert(s && u && u->sink); + pa_assert(u); + pa_assert(u->source); EnterCriticalSection(&u->crit); - free_frags = u->free_ifrags; - LeaveCriticalSection(&u->crit); - r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec); + r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &u->source->sample_spec); return r; } -static void notify_sink_cb(pa_sink *s) { - struct userdata *u = s->userdata; - assert(u); +static int process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u; - u->core->mainloop->defer_enable(u->defer, 1); -} + if (pa_sink_isinstance(o)) { + u = PA_SINK(o)->userdata; -static void notify_source_cb(pa_source *s) { - struct userdata *u = s->userdata; - assert(u); + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + if (u->hwo) + r = sink_get_latency(u); + *((pa_usec_t*) data) = r; + return 0; + } + + } + + return pa_sink_process_msg(o, code, data, offset, chunk); + } + + if (pa_source_isinstance(o)) { + u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + if (u->hwi) + r = source_get_latency(u); + *((pa_usec_t*) data) = r; + return 0; + } + + } + + return pa_source_process_msg(o, code, data, offset, chunk); + } - u->core->mainloop->defer_enable(u->defer, 1); + return -1; } -static int sink_get_hw_volume_cb(pa_sink *s) { +static void sink_get_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; DWORD vol; pa_volume_t left, right; if (waveOutGetVolume(u->hwo, &vol) != MMSYSERR_NOERROR) - return -1; + return; - left = (vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME; - right = ((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME; + left = PA_CLAMP_VOLUME((vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME); + right = PA_CLAMP_VOLUME(((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME); /* Windows supports > 2 channels, except for volume control */ - if (s->hw_volume.channels > 2) - pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, (left + right)/2); - - s->hw_volume.values[0] = left; - if (s->hw_volume.channels > 1) - s->hw_volume.values[1] = right; + if (s->real_volume.channels > 2) + pa_cvolume_set(&s->real_volume, s->real_volume.channels, (left + right)/2); - return 0; + s->real_volume.values[0] = left; + if (s->real_volume.channels > 1) + s->real_volume.values[1] = right; } -static int sink_set_hw_volume_cb(pa_sink *s) { +static void sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; DWORD vol; - vol = s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM; - if (s->hw_volume.channels > 1) - vol |= (s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16; + vol = s->real_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM; + if (s->real_volume.channels > 1) + vol |= (s->real_volume.values[1] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16; if (waveOutSetVolume(u->hwo, vol) != MMSYSERR_NOERROR) - return -1; - - return 0; + return; } static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) { wf->wFormatTag = WAVE_FORMAT_PCM; if (ss->channels > 2) { - pa_log_error("ERROR: More than two channels not supported."); + pa_log_error("More than two channels not supported."); return -1; } wf->nChannels = ss->channels; - switch (ss->rate) { - case 8000: - case 11025: - case 22005: - case 44100: - break; - default: - pa_log_error("ERROR: Unsupported sample rate."); - return -1; - } - wf->nSamplesPerSec = ss->rate; if (ss->format == PA_SAMPLE_U8) @@ -415,7 +447,7 @@ static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) { else if (ss->format == PA_SAMPLE_S16NE) wf->wBitsPerSample = 16; else { - pa_log_error("ERROR: Unsupported sample format."); + pa_log_error("Unsupported sample format, only u8 and s16 are supported."); return -1; } @@ -427,21 +459,34 @@ static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) { return 0; } -int pa__init(pa_core *c, pa_module*m) { +int pa__get_n_used(pa_module *m) { + struct userdata *u; + pa_assert(m); + pa_assert(m->userdata); + u = (struct userdata*) m->userdata; + + return (u->sink ? pa_sink_used_by(u->sink) : 0) + + (u->source ? pa_source_used_by(u->source) : 0); +} + +int pa__init(pa_module *m) { struct userdata *u = NULL; HWAVEOUT hwo = INVALID_HANDLE_VALUE; HWAVEIN hwi = INVALID_HANDLE_VALUE; WAVEFORMATEX wf; + WAVEOUTCAPS pwoc; + MMRESULT result; int nfrags, frag_size; - int record = 1, playback = 1; + pa_bool_t record = TRUE, playback = TRUE; unsigned int device; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; + const char *device_name = NULL; unsigned int i; - struct timeval tv; - assert(c && m); + pa_assert(m); + pa_assert(m->core); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("failed to parse module arguments."); @@ -458,11 +503,31 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } + /* Set the device to be opened. If set device_name is used, + * else device if set and lastly WAVE_MAPPER is the default */ device = WAVE_MAPPER; if (pa_modargs_get_value_u32(ma, "device", &device) < 0) { pa_log("failed to parse device argument"); goto fail; } + if ((device_name = pa_modargs_get_value(ma, "device_name", NULL)) != NULL) { + unsigned int num_devices = waveOutGetNumDevs(); + for (i = 0; i < num_devices; i++) { + if (waveOutGetDevCaps(i, &pwoc, sizeof(pwoc)) == MMSYSERR_NOERROR) + if (_stricmp(device_name, pwoc.szPname) == 0) + break; + } + if (i < num_devices) + device = i; + else { + pa_log("device not found: %s", device_name); + goto fail; + } + } + if (waveOutGetDevCaps(device, &pwoc, sizeof(pwoc)) == MMSYSERR_NOERROR) + device_name = pwoc.szPname; + else + device_name = "unknown"; nfrags = 5; frag_size = 8192; @@ -471,7 +536,7 @@ int pa__init(pa_core *c, pa_module*m) { goto fail; } - ss = c->default_sample_spec; + ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_WAVEEX) < 0) { pa_log("failed to parse sample specification"); goto fail; @@ -483,56 +548,89 @@ int pa__init(pa_core *c, pa_module*m) { u = pa_xmalloc(sizeof(struct userdata)); if (record) { - if (waveInOpen(&hwi, device, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { - pa_log("failed to open waveIn"); + result = waveInOpen(&hwi, device, &wf, 0, 0, WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY); + if (result != MMSYSERR_NOERROR) { + pa_log_warn("Sample spec not supported by WaveIn, falling back to default sample rate."); + ss.rate = wf.nSamplesPerSec = m->core->default_sample_spec.rate; + } + result = waveInOpen(&hwi, device, &wf, (DWORD_PTR) chunk_ready_cb, (DWORD_PTR) u, CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + char errortext[MAXERRORLENGTH]; + pa_log("Failed to open WaveIn."); + if (waveInGetErrorText(result, errortext, sizeof(errortext)) == MMSYSERR_NOERROR) + pa_log("Error: %s", errortext); goto fail; } if (waveInStart(hwi) != MMSYSERR_NOERROR) { pa_log("failed to start waveIn"); goto fail; } - pa_log_debug("Opened waveIn subsystem."); } if (playback) { - if (waveOutOpen(&hwo, device, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { - pa_log("failed to open waveOut"); + result = waveOutOpen(&hwo, device, &wf, 0, 0, WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY); + if (result != MMSYSERR_NOERROR) { + pa_log_warn("Sample spec not supported by WaveOut, falling back to default sample rate."); + ss.rate = wf.nSamplesPerSec = m->core->default_sample_spec.rate; + } + result = waveOutOpen(&hwo, device, &wf, (DWORD_PTR) chunk_done_cb, (DWORD_PTR) u, CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + char errortext[MAXERRORLENGTH]; + pa_log("Failed to open WaveOut."); + if (waveOutGetErrorText(result, errortext, sizeof(errortext)) == MMSYSERR_NOERROR) + pa_log("Error: %s", errortext); goto fail; } - pa_log_debug("Opened waveOut subsystem."); } InitializeCriticalSection(&u->crit); if (hwi != INVALID_HANDLE_VALUE) { - u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map); - assert(u->source); + char *description = pa_sprintf_malloc("WaveIn on %s", device_name); + pa_source_new_data data; + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + pa_assert(u->source); u->source->userdata = u; - u->source->notify = notify_source_cb; - u->source->get_latency = source_get_latency_cb; - pa_source_set_owner(u->source, m); - pa_source_set_description(u->source, "Windows waveIn PCM"); - u->source->is_hardware = 1; + pa_source_set_description(u->source, description); + u->source->parent.process_msg = process_msg; + pa_xfree(description); } else u->source = NULL; if (hwo != INVALID_HANDLE_VALUE) { - u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map); - assert(u->sink); - u->sink->notify = notify_sink_cb; - u->sink->get_latency = sink_get_latency_cb; - u->sink->get_hw_volume = sink_get_hw_volume_cb; - u->sink->set_hw_volume = sink_set_hw_volume_cb; + char *description = pa_sprintf_malloc("WaveOut on %s", device_name); + pa_sink_new_data data; + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + pa_assert(u->sink); + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; u->sink->userdata = u; - pa_sink_set_owner(u->sink, m); - pa_sink_set_description(u->sink, "Windows waveOut PCM"); - u->sink->is_hardware = 1; + pa_sink_set_description(u->sink, description); + u->sink->parent.process_msg = process_msg; + pa_xfree(description); } else u->sink = NULL; - assert(u->source || u->sink); + pa_assert(u->source || u->sink); + pa_modargs_free(ma); - u->core = c; + u->core = m->core; u->hwi = hwi; u->hwo = hwo; @@ -545,83 +643,90 @@ int pa__init(pa_core *c, pa_module*m) { u->sink_underflow = 1; u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 10, &ss); - - pa_gettimeofday(&tv); - pa_timeval_add(&tv, u->poll_timeout); - - u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u); - assert(u->event); - - u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u); - assert(u->defer); - c->mainloop->defer_enable(u->defer, 0); + pa_log_debug("Poll timeout = %.1f ms", (double) u->poll_timeout / PA_USEC_PER_MSEC); u->cur_ihdr = 0; u->cur_ohdr = 0; u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments); - assert(u->ihdrs); + pa_assert(u->ihdrs); u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments); - assert(u->ohdrs); - for (i = 0;i < u->fragments;i++) { + pa_assert(u->ohdrs); + for (i = 0; i < u->fragments; i++) { u->ihdrs[i].dwBufferLength = u->fragment_size; u->ohdrs[i].dwBufferLength = u->fragment_size; u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size); - assert(u->ihdrs); + pa_assert(u->ihdrs); u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size); - assert(u->ohdrs); + pa_assert(u->ohdrs); } u->module = m; m->userdata = u; - pa_modargs_free(ma); - /* Read mixer settings */ if (u->sink) - sink_get_hw_volume_cb(u->sink); + sink_get_volume_cb(u->sink); - return 0; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); -fail: - if (hwi != INVALID_HANDLE_VALUE) - waveInClose(hwi); + if (u->sink) { + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + } + if (u->source) { + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + } + + if (!(u->thread = pa_thread_new("waveout", thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } - if (hwo != INVALID_HANDLE_VALUE) - waveOutClose(hwo); + if (u->sink) + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); - if (u) - pa_xfree(u); + return 0; +fail: if (ma) pa_modargs_free(ma); + pa__done(m); + return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module *m) { struct userdata *u; unsigned int i; - assert(c && m); + pa_assert(m); + pa_assert(m->core); if (!(u = m->userdata)) return; - if (u->event) - c->mainloop->time_free(u->event); + if (u->sink) + pa_sink_unlink(u->sink); + if (u->source) + pa_source_unlink(u->source); - if (u->defer) - c->mainloop->defer_free(u->defer); + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + if (u->thread) + pa_thread_free(u->thread); + pa_thread_mq_done(&u->thread_mq); - if (u->sink) { - pa_sink_disconnect(u->sink); + if (u->sink) pa_sink_unref(u->sink); - } - - if (u->source) { - pa_source_disconnect(u->source); + if (u->source) pa_source_unref(u->source); - } + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); if (u->hwi != INVALID_HANDLE_VALUE) { waveInReset(u->hwi); @@ -633,7 +738,7 @@ void pa__done(pa_core *c, pa_module*m) { waveOutClose(u->hwo); } - for (i = 0;i < u->fragments;i++) { + for (i = 0; i < u->fragments; i++) { pa_xfree(u->ihdrs[i].lpData); pa_xfree(u->ohdrs[i].lpData); } diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 1fdc1f46..cd076aab 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -36,14 +36,9 @@ #include <avahi-common/malloc.h> #include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulsecore/sink.h> -#include <pulsecore/source.h> -#include <pulsecore/native-common.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> -#include <pulsecore/core-subscribe.h> #include <pulsecore/hashmap.h> #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index d72d2647..0c20cf6c 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -25,7 +25,6 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <unistd.h> #include <avahi-client/client.h> @@ -43,11 +42,9 @@ #include <pulsecore/native-common.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> -#include <pulsecore/core-subscribe.h> #include <pulsecore/dynarray.h> #include <pulsecore/modargs.h> #include <pulsecore/avahi-wrap.h> -#include <pulsecore/endianmacros.h> #include <pulsecore/protocol-native.h> #include "module-zeroconf-publish-symdef.h" diff --git a/src/modules/oss/Makefile b/src/modules/oss/Makefile deleted file mode 120000 index efe5a336..00000000 --- a/src/modules/oss/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index b1afcfd6..2a99d119 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -45,15 +45,10 @@ #include <sys/soundcard.h> #include <sys/ioctl.h> #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> -#include <string.h> #include <fcntl.h> #include <unistd.h> -#include <limits.h> -#include <signal.h> -#include <poll.h> #include <pulse/xmalloc.h> #include <pulse/util.h> @@ -70,6 +65,7 @@ #include <pulsecore/macro.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> +#include <pulsecore/poll.h> #if defined(__NetBSD__) && !defined(SNDCTL_DSP_GETODELAY) #include <sys/audioio.h> @@ -169,7 +165,7 @@ static void trigger(struct userdata *u, pa_bool_t quick) { if (u->fd < 0) return; - pa_log_debug("trigger"); + pa_log_debug("trigger"); if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) enable_bits |= PCM_ENABLE_INPUT; @@ -812,11 +808,11 @@ static void sink_get_volume(pa_sink *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); if (u->mixer_devmask & SOUND_MASK_VOLUME) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_PCM) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); @@ -830,11 +826,11 @@ static void sink_set_volume(pa_sink *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); if (u->mixer_devmask & SOUND_MASK_VOLUME) - if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_PCM) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); @@ -848,11 +844,11 @@ static void source_get_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); @@ -866,11 +862,11 @@ static void source_set_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); @@ -889,7 +885,6 @@ static void thread_func(void *userdata) { pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); for (;;) { int ret; @@ -1457,7 +1452,7 @@ go_on: pa_memchunk_reset(&u->memchunk); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("oss", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/oss/oss-util.c b/src/modules/oss/oss-util.c index 5a109ae9..04899afe 100644 --- a/src/modules/oss/oss-util.c +++ b/src/modules/oss/oss-util.c @@ -31,7 +31,6 @@ #include <string.h> #include <unistd.h> #include <sys/types.h> -#include <sys/stat.h> #include <fcntl.h> #include <pulse/xmalloc.h> @@ -55,7 +54,7 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { pcaps = ∩︀ if (*mode == O_RDWR) { - if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { + if ((fd = pa_open_cloexec(device, O_RDWR|O_NDELAY, 0)) >= 0) { ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { @@ -71,14 +70,14 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) { pa_close(fd); } - if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { - if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = pa_open_cloexec(device, (*mode = O_WRONLY)|O_NDELAY, 0)) < 0) { + if ((fd = pa_open_cloexec(device, (*mode = O_RDONLY)|O_NDELAY, 0)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } } } else { - if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = pa_open_cloexec(device, *mode|O_NDELAY, 0)) < 0) { pa_log("open('%s'): %s", device, pa_cstrerror(errno)); goto fail; } @@ -145,8 +144,6 @@ success: pa_log_debug("capabilities:%s", t); pa_xfree(t); - pa_make_fd_cloexec(fd); - return fd; fail: @@ -273,10 +270,10 @@ int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_ pa_cvolume_reset(volume, ss->channels); - volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100; + volume->values[0] = PA_CLAMP_VOLUME(((vol & 0xFF) * PA_VOLUME_NORM) / 100); if (volume->channels >= 2) - volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100; + volume->values[1] = PA_CLAMP_VOLUME((((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100); pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); return 0; @@ -351,9 +348,9 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { if ((n = get_device_number(dev)) < 0) return -1; - if (!(f = fopen("/dev/sndstat", "r")) && - !(f = fopen("/proc/sndstat", "r")) && - !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && + !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { if (errno != ENOENT) pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); @@ -403,7 +400,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { static int open_mixer(const char *mixer) { int fd; - if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) + if ((fd = pa_open_cloexec(mixer, O_RDWR|O_NDELAY, 0)) >= 0) return fd; return -1; diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c index e1cbed02..37e47628 100644 --- a/src/modules/raop/base64.c +++ b/src/modules/raop/base64.c @@ -38,8 +38,7 @@ static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static int pos(char c) -{ +static int pos(char c) { if (c >= 'A' && c <= 'Z') return c - 'A' + 0; if (c >= 'a' && c <= 'z') return c - 'a' + 26; if (c >= '0' && c <= '9') return c - '0' + 52; @@ -48,8 +47,7 @@ static int pos(char c) return -1; } -int pa_base64_encode(const void *data, int size, char **str) -{ +int pa_base64_encode(const void *data, int size, char **str) { char *s, *p; int i; int c; @@ -57,7 +55,6 @@ int pa_base64_encode(const void *data, int size, char **str) p = s = pa_xnew(char, size * 4 / 3 + 4); q = (const unsigned char *) data; - i = 0; for (i = 0; i < size;) { c = q[i++]; c *= 256; @@ -85,8 +82,7 @@ int pa_base64_encode(const void *data, int size, char **str) #define DECODE_ERROR 0xffffffff -static unsigned int token_decode(const char *token) -{ +static unsigned int token_decode(const char *token) { int i; unsigned int val = 0; int marker = 0; @@ -110,8 +106,7 @@ static unsigned int token_decode(const char *token) return (marker << 24) | val; } -int pa_base64_decode(const char *str, void *data) -{ +int pa_base64_decode(const char *str, void *data) { const char *p; unsigned char *q; diff --git a/src/modules/module-raop-discover.c b/src/modules/raop/module-raop-discover.c index eaeb77fc..de1a2b1c 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/raop/module-raop-discover.c @@ -37,14 +37,9 @@ #include <avahi-common/malloc.h> #include <pulse/xmalloc.h> -#include <pulse/util.h> -#include <pulsecore/sink.h> -#include <pulsecore/source.h> -#include <pulsecore/native-common.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> -#include <pulsecore/core-subscribe.h> #include <pulsecore/hashmap.h> #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> @@ -200,7 +195,7 @@ static void resolver_cb( if (nicename) { args = pa_sprintf_malloc("server=%s " "sink_name=%s " - "description=\"%s\"", + "sink_properties=device.description=\"%s\"", avahi_address_snprint(at, sizeof(at), a), vname, nicename); @@ -265,7 +260,7 @@ static void browser_cb( struct tunnel *t2; if ((t2 = pa_hashmap_get(u->tunnels, t))) { - pa_module_unload_by_index(u->core, t2->module_index, TRUE); + pa_module_unload_request_by_index(u->core, t2->module_index, TRUE); pa_hashmap_remove(u->tunnels, t2); tunnel_free(t2); } @@ -386,7 +381,7 @@ void pa__done(pa_module*m) { struct tunnel *t; while ((t = pa_hashmap_steal_first(u->tunnels))) { - pa_module_unload_by_index(u->core, t->module_index, TRUE); + pa_module_unload_request_by_index(u->core, t->module_index, TRUE); tunnel_free(t); } diff --git a/src/modules/module-raop-sink.c b/src/modules/raop/module-raop-sink.c index 052a3a5e..87e7bc17 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/raop/module-raop-sink.c @@ -25,14 +25,10 @@ #endif #include <stdlib.h> -#include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <limits.h> -#include <poll.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> @@ -42,23 +38,21 @@ #include <linux/sockios.h> #endif -#include <pulse/xmalloc.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> +#include <pulse/xmalloc.h> #include <pulsecore/core-error.h> -#include <pulsecore/iochannel.h> #include <pulsecore/sink.h> #include <pulsecore/module.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/log.h> #include <pulsecore/socket-client.h> -#include <pulsecore/authkey.h> #include <pulsecore/thread-mq.h> #include <pulsecore/thread.h> #include <pulsecore/time-smoother.h> -#include <pulsecore/rtclock.h> -#include <pulsecore/socket-util.h> +#include <pulsecore/poll.h> #include "module-raop-sink-symdef.h" #include "rtp.h" @@ -124,7 +118,6 @@ static const char* const valid_modargs[] = { "format", "rate", "channels", - "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -181,7 +174,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_SUSPENDED: pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - pa_smoother_pause(u->smoother, pa_rtclock_usec()); + pa_smoother_pause(u->smoother, pa_rtclock_now()); /* Issue a FLUSH if we are connected */ if (u->fd >= 0) { @@ -193,7 +186,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_RUNNING: if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - pa_smoother_resume(u->smoother, pa_rtclock_usec(), TRUE); + pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE); /* The connection can be closed when idle, so check to see if we need to reestablish it */ @@ -216,7 +209,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t w, r; - r = pa_smoother_get(u->smoother, pa_rtclock_usec()); + r = pa_smoother_get(u->smoother, pa_rtclock_now()); w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec); *((pa_usec_t*) data) = w > r ? w - r : 0; @@ -282,15 +275,15 @@ static void sink_set_volume_cb(pa_sink *s) { /* Calculate the max volume of all channels. We'll use this as our (single) volume on the APEX device and emulate any variation in channel volumes in software */ - v = pa_cvolume_max(&s->virtual_volume); + v = pa_cvolume_max(&s->real_volume); /* Create a pa_cvolume version of our single value */ pa_cvolume_set(&hw, s->sample_spec.channels, v); /* Perform any software manipulation of the volume needed */ - pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &hw); + pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw); - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume)); + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &hw)); pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume)); @@ -323,9 +316,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_now()); /* Create a chunk of memory that is our encoded silence sample. */ pa_memchunk_reset(&silence); @@ -465,7 +457,7 @@ static void thread_func(void *userdata) { else usec = 0; - pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); + pa_smoother_put(u->smoother, pa_rtclock_now(), usec); } /* Hmm, nothing to do. Let's sleep */ @@ -514,7 +506,7 @@ int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_modargs *ma = NULL; - const char *server, *desc; + const char *server; pa_sink_new_data data; pa_assert(m); @@ -583,10 +575,8 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); pa_sink_new_data_set_sample_spec(&data, &ss); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); - if ((desc = pa_modargs_get_value(ma, "description", NULL))) - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, desc); - else - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music"); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "RAOP sink '%s'", server); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log("Invalid properties"); @@ -619,7 +609,7 @@ int pa__init(pa_module*m) { pa_raop_client_set_callback(u->raop, on_connection, u); pa_raop_client_set_closed_callback(u->raop, on_close, u); - if (!(u->thread = pa_thread_new(thread_func, u))) { + if (!(u->thread = pa_thread_new("raop-sink", thread_func, u))) { pa_log("Failed to create thread."); goto fail; } diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c index b3f243c3..cba7af9a 100644 --- a/src/modules/raop/raop_client.c +++ b/src/modules/raop/raop_client.c @@ -23,11 +23,9 @@ #include <config.h> #endif -#include <fcntl.h> #include <stdlib.h> #include <string.h> #include <errno.h> -#include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> @@ -46,18 +44,13 @@ #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> +#include <pulsecore/iochannel.h> #include <pulsecore/socket-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> -#include <pulsecore/strbuf.h> +#include <pulsecore/memchunk.h> #include <pulsecore/random.h> -#ifdef HAVE_POLL_H -#include <poll.h> -#else -#include <pulsecore/poll.h> -#endif - #include "raop_client.h" #include "rtsp_client.h" #include "base64.h" @@ -179,8 +172,7 @@ static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { return size; } -static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) -{ +static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) { uint8_t *buf; int i=0, j; @@ -199,8 +191,7 @@ static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) return i; } -static inline void rtrimchar(char *str, char rc) -{ +static inline void rtrimchar(char *str, char rc) { char *sp = str + strlen(str) - 1; while (sp >= str && *sp == rc) { *sp = '\0'; @@ -228,7 +219,6 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata c->fd = pa_iochannel_get_send_fd(io); pa_iochannel_set_noclose(io, TRUE); - pa_iochannel_socket_set_sndbuf(io, 1024); pa_iochannel_free(io); pa_make_tcp_socket_low_delay(c->fd); @@ -237,8 +227,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata c->callback(c->fd, c->userdata); } -static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) -{ +static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { pa_raop_client* c = userdata; pa_assert(c); pa_assert(rtsp); @@ -331,7 +320,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he uint32_t port = pa_rtsp_serverport(c->rtsp); pa_log_debug("RAOP: RECORDED"); - if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { + if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, TRUE, c->host, port))) { pa_log("failed to connect to server '%s:%d'", c->host, port); return; } @@ -373,8 +362,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he } } -pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) -{ +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) { pa_raop_client* c = pa_xnew0(pa_raop_client, 1); pa_assert(core); @@ -392,8 +380,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) } -void pa_raop_client_free(pa_raop_client* c) -{ +void pa_raop_client_free(pa_raop_client* c) { pa_assert(c); if (c->rtsp) @@ -403,8 +390,7 @@ void pa_raop_client_free(pa_raop_client* c) } -int pa_raop_connect(pa_raop_client* c) -{ +int pa_raop_connect(pa_raop_client* c) { char *sci; struct { uint32_t a; @@ -438,8 +424,7 @@ int pa_raop_connect(pa_raop_client* c) } -int pa_raop_flush(pa_raop_client* c) -{ +int pa_raop_flush(pa_raop_client* c) { pa_assert(c); pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); @@ -447,8 +432,7 @@ int pa_raop_flush(pa_raop_client* c) } -int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) -{ +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) { int rv; double db; char *param; @@ -470,8 +454,7 @@ int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) } -int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) -{ +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) { uint16_t len; size_t bufmax; uint8_t *bp, bpos; @@ -553,16 +536,14 @@ int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchun } -void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) -{ +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) { pa_assert(c); c->callback = callback; c->userdata = userdata; } -void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) -{ +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) { pa_assert(c); c->closed_callback = callback; diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h index 5ad3e3fa..ce81f392 100644 --- a/src/modules/raop/raop_client.h +++ b/src/modules/raop/raop_client.h @@ -22,8 +22,6 @@ USA. ***/ -#include <pulse/mainloop-api.h> -#include <pulsecore/iochannel.h> #include <pulsecore/core.h> typedef struct pa_raop_client pa_raop_client; diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c new file mode 100644 index 00000000..ab453e61 --- /dev/null +++ b/src/modules/reserve-monitor.c @@ -0,0 +1,256 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "reserve-monitor.h" + +struct rm_monitor { + int ref; + + char *device_name; + char *service_name; + char *match; + + DBusConnection *connection; + + unsigned busy:1; + unsigned filtering:1; + unsigned matching:1; + + rm_change_cb_t change_cb; + void *userdata; +}; + +#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." + +#define SERVICE_FILTER \ + "type='signal'," \ + "sender='" DBUS_SERVICE_DBUS "'," \ + "interface='" DBUS_INTERFACE_DBUS "'," \ + "member='NameOwnerChanged'," \ + "arg0='%s'" + +static DBusHandlerResult filter_handler( + DBusConnection *c, + DBusMessage *s, + void *userdata) { + + rm_monitor *m; + DBusError error; + + dbus_error_init(&error); + + m = userdata; + assert(m->ref >= 1); + + if (dbus_message_is_signal(s, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old, *new; + + if (!dbus_message_get_args( + s, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) + goto invalid; + + if (strcmp(name, m->service_name) == 0) { + m->busy = !!(new && *new); + + /* If we ourselves own the device, then don't consider this 'busy' */ + if (m->busy) { + const char *un; + + if ((un = dbus_bus_get_unique_name(c))) + if (strcmp(new, un) == 0) + m->busy = FALSE; + } + + if (m->change_cb) { + m->ref++; + m->change_cb(m); + rm_release(m); + } + } + } + +invalid: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int rm_watch( + rm_monitor **_m, + DBusConnection *connection, + const char*device_name, + rm_change_cb_t change_cb, + DBusError *error) { + + rm_monitor *m = NULL; + int r; + DBusError _error; + + if (!error) + error = &_error; + + dbus_error_init(error); + + if (!_m) + return -EINVAL; + + if (!connection) + return -EINVAL; + + if (!device_name) + return -EINVAL; + + if (!(m = calloc(sizeof(rm_monitor), 1))) + return -ENOMEM; + + m->ref = 1; + + if (!(m->device_name = strdup(device_name))) { + r = -ENOMEM; + goto fail; + } + + m->connection = dbus_connection_ref(connection); + m->change_cb = change_cb; + + if (!(m->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) { + r = -ENOMEM; + goto fail; + } + sprintf(m->service_name, SERVICE_PREFIX "%s", m->device_name); + + if (!(dbus_connection_add_filter(m->connection, filter_handler, m, NULL))) { + r = -ENOMEM; + goto fail; + } + + m->filtering = 1; + + if (!(m->match = malloc(sizeof(SERVICE_FILTER) - 2 + strlen(m->service_name)))) { + r = -ENOMEM; + goto fail; + } + + sprintf(m->match, SERVICE_FILTER, m->service_name); + dbus_bus_add_match(m->connection, m->match, error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + m->matching = 1; + + m->busy = dbus_bus_name_has_owner(m->connection, m->service_name, error); + + if (dbus_error_is_set(error)) { + r = -EIO; + goto fail; + } + + *_m = m; + return 0; + +fail: + if (&_error == error) + dbus_error_free(&_error); + + if (m) + rm_release(m); + + return r; +} + +void rm_release(rm_monitor *m) { + if (!m) + return; + + assert(m->ref > 0); + + if (--m->ref > 0) + return; + + if (m->matching) + dbus_bus_remove_match( + m->connection, + m->match, + NULL); + + if (m->filtering) + dbus_connection_remove_filter( + m->connection, + filter_handler, + m); + + free(m->device_name); + free(m->service_name); + free(m->match); + + if (m->connection) + dbus_connection_unref(m->connection); + + free(m); +} + +int rm_busy(rm_monitor *m) { + if (!m) + return -EINVAL; + + assert(m->ref > 0); + + return m->busy; +} + +void rm_set_userdata(rm_monitor *m, void *userdata) { + + if (!m) + return; + + assert(m->ref > 0); + m->userdata = userdata; +} + +void* rm_get_userdata(rm_monitor *m) { + + if (!m) + return NULL; + + assert(m->ref > 0); + + return m->userdata; +} diff --git a/src/modules/reserve-monitor.h b/src/modules/reserve-monitor.h new file mode 100644 index 00000000..3408680f --- /dev/null +++ b/src/modules/reserve-monitor.h @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + +#ifndef fooreservemonitorhfoo +#define fooreservemonitorhfoo + +/*** + Copyright 2009 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <dbus/dbus.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct rm_monitor rm_monitor; + +/* Prototype for a function that is called whenever the reservation + * device of a device changes. Use rm_monitor_busy() to find out the + * new state.*/ +typedef void (*rm_change_cb_t)(rm_monitor *m); + +/* Creates a monitor for watching the lock status of a device. Returns + * 0 on success, a negative errno style return value on error. The + * DBus error might be set as well if the error was caused D-Bus. */ +int rm_watch( + rm_monitor **m, /* On success a pointer to the newly allocated rm_device object will be filled in here */ + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ + const char *device_name, /* The device to monitor, e.g. "Audio0" */ + rm_change_cb_t change_cb, /* Will be called whenever the lock status changes. May be NULL */ + DBusError *error); /* If we fail due to a D-Bus related issue the error will be filled in here. May be NULL. */ + +/* Free a rm_monitor object */ +void rm_release(rm_monitor *m); + +/* Checks whether the device is currently reserved, and returns 1 + * then, 0 if not, negative errno style error code value on error. */ +int rm_busy(rm_monitor *m); + +/* Attach a userdata pointer to an rm_monitor */ +void rm_set_userdata(rm_monitor *m, void *userdata); + +/* Query the userdata pointer from an rm_monitor. Returns NULL if no + * userdata was set. */ +void* rm_get_userdata(rm_monitor *m); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index d0d014d3..4be19c73 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -35,6 +35,7 @@ #ifdef HAVE_DBUS #include <pulsecore/dbus-shared.h> #include "reserve.h" +#include "reserve-monitor.h" #endif #include "reserve-wrap.h" @@ -50,6 +51,17 @@ struct pa_reserve_wrapper { #endif }; +struct pa_reserve_monitor_wrapper { + PA_REFCNT_DECLARE; + pa_core *core; + pa_hook hook; + char *shared_name; +#ifdef HAVE_DBUS + pa_dbus_connection *connection; + struct rm_monitor *monitor; +#endif +}; + static void reserve_wrapper_free(pa_reserve_wrapper *r) { pa_assert(r); @@ -83,7 +95,7 @@ static int request_cb(rd_device *d, int forced) { PA_REFCNT_INC(r); k = pa_hook_fire(&r->hook, PA_INT_TO_PTR(forced)); - pa_log_debug("Device unlock has been requested and %s.", k < 0 ? "failed" : "succeeded"); + pa_log_debug("Device unlock of %s has been requested and %s.", r->shared_name, k < 0 ? "failed" : "succeeded"); pa_reserve_wrapper_unref(r); @@ -125,7 +137,7 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) #ifdef HAVE_DBUS if (!(r->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { - pa_log_warn("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); /* We don't treat this as error here because we want allow PA * to run even when no session bus is available. */ @@ -142,10 +154,10 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) NULL)) < 0) { if (k == -EBUSY) { - pa_log_error("Device '%s' already locked.", device_name); + pa_log_debug("Device '%s' already locked.", device_name); goto fail; } else { - pa_log_warn("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); + pa_log_debug("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); return r; } } @@ -191,3 +203,142 @@ void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const rd_set_application_device_name(r->device, name); #endif } + +static void reserve_monitor_wrapper_free(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + +#ifdef HAVE_DBUS + if (w->monitor) + rm_release(w->monitor); + + if (w->connection) + pa_dbus_connection_unref(w->connection); +#endif + + pa_hook_done(&w->hook); + + if (w->shared_name) { + pa_assert_se(pa_shared_remove(w->core, w->shared_name) >= 0); + pa_xfree(w->shared_name); + } + + pa_xfree(w); +} + +#ifdef HAVE_DBUS +static void change_cb(rm_monitor *m) { + pa_reserve_monitor_wrapper *w; + int k; + + pa_assert(m); + pa_assert_se(w = rm_get_userdata(m)); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + PA_REFCNT_INC(w); + + if ((k = rm_busy(w->monitor)) < 0) + return; + + pa_hook_fire(&w->hook, PA_INT_TO_PTR(!!k)); + pa_log_debug("Device lock status of %s changed: %s", w->shared_name, k ? "busy" : "not busy"); + + pa_reserve_monitor_wrapper_unref(w); +} +#endif + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name) { + pa_reserve_monitor_wrapper *w; + int k; + char *t; +#ifdef HAVE_DBUS + DBusError error; + + dbus_error_init(&error); +#endif + + pa_assert(c); + pa_assert(device_name); + + t = pa_sprintf_malloc("reserve-monitor-wrapper@%s", device_name); + + if ((w = pa_shared_get(c, t))) { + pa_xfree(t); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + PA_REFCNT_INC(w); + + return w; + } + + w = pa_xnew0(pa_reserve_monitor_wrapper, 1); + PA_REFCNT_INIT(w); + w->core = c; + pa_hook_init(&w->hook, w); + w->shared_name = t; + + pa_assert_se(pa_shared_set(c, w->shared_name, w) >= 0); + +#ifdef HAVE_DBUS + if (!(w->connection = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { + pa_log_debug("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); + + /* We don't treat this as error here because we want allow PA + * to run even when no session bus is available. */ + return w; + } + + if ((k = rm_watch( + &w->monitor, + pa_dbus_connection_get(w->connection), + device_name, + change_cb, + NULL)) < 0) { + + pa_log_debug("Failed to create watch on device '%s': %s", device_name, pa_cstrerror(-k)); + goto fail; + } + + pa_log_debug("Successfully create reservation lock monitor for device '%s'", device_name); + + rm_set_userdata(w->monitor, w); + return w; + +fail: + dbus_error_free(&error); + + reserve_monitor_wrapper_free(w); + + return NULL; +#else + return w; +#endif +} + +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + if (PA_REFCNT_DEC(w) > 0) + return; + + reserve_monitor_wrapper_free(w); +} + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + return &w->hook; +} + +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *w) { + pa_assert(w); + + pa_assert(PA_REFCNT_VALUE(w) >= 1); + +#ifdef HAVE_DBUS + return rm_busy(w->monitor) > 0; +#else + return FALSE; +#endif +} diff --git a/src/modules/reserve-wrap.h b/src/modules/reserve-wrap.h index 2b97c91c..2de6c093 100644 --- a/src/modules/reserve-wrap.h +++ b/src/modules/reserve-wrap.h @@ -28,11 +28,18 @@ typedef struct pa_reserve_wrapper pa_reserve_wrapper; pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name); - void pa_reserve_wrapper_unref(pa_reserve_wrapper *r); pa_hook* pa_reserve_wrapper_hook(pa_reserve_wrapper *r); void pa_reserve_wrapper_set_application_device_name(pa_reserve_wrapper *r, const char *name); +typedef struct pa_reserve_monitor_wrapper pa_reserve_monitor_wrapper; + +pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name); +void pa_reserve_monitor_wrapper_unref(pa_reserve_monitor_wrapper *m); + +pa_hook* pa_reserve_monitor_wrapper_hook(pa_reserve_monitor_wrapper *m); +pa_bool_t pa_reserve_monitor_wrapper_busy(pa_reserve_monitor_wrapper *m); + #endif diff --git a/src/modules/reserve.c b/src/modules/reserve.c index 9a9591d2..b4c168cf 100644 --- a/src/modules/reserve.c +++ b/src/modules/reserve.c @@ -1,3 +1,5 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + /*** Copyright 2009 Lennart Poettering @@ -43,16 +45,15 @@ struct rd_device { DBusConnection *connection; - int owning:1; - int registered:1; - int filtering:1; - int gave_up:1; + unsigned owning:1; + unsigned registered:1; + unsigned filtering:1; + unsigned gave_up:1; rd_request_cb_t request_cb; void *userdata; }; - #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" @@ -290,13 +291,13 @@ static DBusHandlerResult filter_handler( DBusMessage *m, void *userdata) { - DBusMessage *reply; rd_device *d; DBusError error; dbus_error_init(&error); d = userdata; + assert(d->ref >= 1); if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) { const char *name; @@ -321,35 +322,13 @@ static DBusHandlerResult filter_handler( rd_release(d); } - return DBUS_HANDLER_RESULT_HANDLED; } } - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - invalid: - if (!(reply = dbus_message_new_error( - m, - DBUS_ERROR_INVALID_ARGS, - "Invalid arguments"))) - goto oom; - - if (!dbus_connection_send(c, reply, NULL)) - goto oom; - - dbus_message_unref(reply); - dbus_error_free(&error); - return DBUS_HANDLER_RESULT_HANDLED; - -oom: - if (reply) - dbus_message_unref(reply); - - dbus_error_free(&error); - - return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -560,7 +539,7 @@ void rd_release( assert(d->ref > 0); - if (--d->ref) + if (--d->ref > 0) return; @@ -575,17 +554,11 @@ void rd_release( d->connection, d->object_path); - if (d->owning) { - DBusError error; - dbus_error_init(&error); - + if (d->owning) dbus_bus_release_name( d->connection, d->service_name, - &error); - - dbus_error_free(&error); - } + NULL); free(d->device_name); free(d->application_name); diff --git a/src/modules/reserve.h b/src/modules/reserve.h index b315a08c..9ae49cf5 100644 --- a/src/modules/reserve.h +++ b/src/modules/reserve.h @@ -1,3 +1,5 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/ + #ifndef fooreservehfoo #define fooreservehfoo @@ -28,6 +30,10 @@ #include <dbus/dbus.h> #include <inttypes.h> +#ifdef __cplusplus +extern "C" { +#endif + typedef struct rd_device rd_device; /* Prototype for a function that is called whenever someone else wants @@ -45,7 +51,7 @@ typedef int (*rd_request_cb_t)( * the error was caused D-Bus. */ int rd_acquire( rd_device **d, /* On success a pointer to the newly allocated rd_device object will be filled in here */ - DBusConnection *connection, + DBusConnection *connection, /* Session bus (when D-Bus learns about user busses we should switchg to user busses) */ const char *device_name, /* The device to lock, e.g. "Audio0" */ const char *application_name, /* A human readable name of the application, e.g. "PulseAudio Sound Server" */ int32_t priority, /* The priority for this application. If unsure use 0 */ @@ -66,4 +72,8 @@ void rd_set_userdata(rd_device *d, void *userdata); * userdata was set. */ void* rd_get_userdata(rd_device *d); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/modules/rtp/Makefile b/src/modules/rtp/Makefile deleted file mode 120000 index efe5a336..00000000 --- a/src/modules/rtp/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index c61d2d8b..7025c15a 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -27,12 +27,12 @@ #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <errno.h> #include <string.h> #include <unistd.h> -#include <poll.h> +#include <math.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> @@ -43,17 +43,17 @@ #include <pulsecore/sink-input.h> #include <pulsecore/memblockq.h> #include <pulsecore/log.h> +#include <pulsecore/core-rtclock.h> #include <pulsecore/core-util.h> #include <pulsecore/modargs.h> #include <pulsecore/namereg.h> #include <pulsecore/sample-util.h> #include <pulsecore/macro.h> -#include <pulsecore/atomic.h> -#include <pulsecore/rtclock.h> -#include <pulsecore/atomic.h> -#include <pulsecore/time-smoother.h> #include <pulsecore/socket-util.h> +#include <pulsecore/atomic.h> #include <pulsecore/once.h> +#include <pulsecore/poll.h> +#include <pulsecore/arpa-inet.h> #include "module-rtp-recv-symdef.h" @@ -62,7 +62,7 @@ #include "sap.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP"); +PA_MODULE_DESCRIPTION("Receive data from a network via RTP/SAP/SDP"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( @@ -103,15 +103,18 @@ struct session { pa_atomic_t timestamp; - pa_smoother *smoother; pa_usec_t intended_latency; pa_usec_t sink_latency; pa_usec_t last_rate_update; + pa_usec_t last_latency; + double estimated_rate; + double avg_estimated_rate; }; struct userdata { pa_module *module; + pa_core *core; pa_sap_context sap_context; pa_io_event* sap_event; @@ -192,10 +195,9 @@ static void sink_input_suspend_within_thread(pa_sink_input* i, pa_bool_t b) { pa_sink_input_assert_ref(i); pa_assert_se(s = i->userdata); - if (b) { - pa_smoother_pause(s->smoother, pa_rtclock_usec()); + if (b) pa_memblockq_flush_read(s->memblockq); - } else + else s->first_packet = FALSE; } @@ -264,11 +266,6 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { } else pa_rtclock_from_wallclock(&now); - pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); - - /* Tell the smoother that we are rolling now, in case it is still paused */ - pa_smoother_resume(s->smoother, pa_timeval_load(&now), TRUE); - if (pa_memblockq_push(s->memblockq, &chunk) < 0) { pa_log_warn("Queue overrun"); pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE); @@ -284,12 +281,15 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_atomic_store(&s->timestamp, (int) now.tv_sec); if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { - pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; - unsigned fix_samples; + pa_usec_t wi, ri, render_delay, sink_delay = 0, latency; + uint32_t base_rate = s->sink_input->sink->sample_spec.rate; + uint32_t current_rate = s->sink_input->sample_spec.rate; + uint32_t new_rate; + double estimated_rate, alpha = 0.02; pa_log_debug("Updating sample rate"); - wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); + wi = pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec); ri = pa_bytes_to_usec((uint64_t) pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); pa_log_debug("wi=%lu ri=%lu", (unsigned long) wi, (unsigned long) ri); @@ -307,30 +307,61 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { else latency = wi - ri; - pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC); - - /* Calculate deviation */ - if (latency < s->intended_latency) - fix = s->intended_latency - latency; - else - fix = latency - s->intended_latency; - - /* How many samples is this per second? */ - fix_samples = (unsigned) (fix * (pa_usec_t) s->sink_input->thread_info.sample_spec.rate / (pa_usec_t) RATE_UPDATE_INTERVAL); - - /* Check if deviation is in bounds */ - if (fix_samples > s->sink_input->sample_spec.rate*.50) - pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples); - else { - /* Fix up rate */ - if (latency < s->intended_latency) - s->sink_input->sample_spec.rate -= fix_samples; - else - s->sink_input->sample_spec.rate += fix_samples; - - if (s->sink_input->sample_spec.rate > PA_RATE_MAX) - s->sink_input->sample_spec.rate = PA_RATE_MAX; + pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC); + + /* The buffer is filling with some unknown rate R̂ samples/second. If the rate of reading in + * the last T seconds was Rⁿ, then the increase in buffer latency ΔLⁿ = Lⁿ - Lⁿ⁻ⁱ in that + * same period is ΔLⁿ = (TR̂ - TRⁿ) / R̂, giving the estimated target rate + * T + * R̂ = ─────────────── Rⁿ . (1) + * T - (Lⁿ - Lⁿ⁻ⁱ) + * + * Setting the sample rate to R̂ results in the latency being constant (if the estimate of R̂ + * is correct). But there is also the requirement to keep the buffer at a predefined target + * latency L̂. So instead of setting Rⁿ⁺ⁱ to R̂ immediately, the strategy will be to reduce R + * from Rⁿ⁺ⁱ to R̂ in a steps of T seconds, where Rⁿ⁺ⁱ is chosen such that in the total time + * aT the latency is reduced from Lⁿ to L̂. This strategy translates to the requirements + * ₐ R̂ - Rⁿ⁺ʲ a-j+1 j-1 + * Σ T ────────── = L̂ - Lⁿ with Rⁿ⁺ʲ = ───── Rⁿ⁺ⁱ + ───── R̂ . + * ʲ⁼ⁱ R̂ a a + * Solving for Rⁿ⁺ⁱ gives + * T - ²∕ₐ₊₁(L̂ - Lⁿ) + * Rⁿ⁺ⁱ = ───────────────── R̂ . (2) + * T + * In the code below a = 7 is used. + * + * Equation (1) is not directly used in (2), but instead an exponentially weighted average + * of the estimated rate R̂ is used. This average R̅ is defined as + * R̅ⁿ = α R̂ⁿ + (1-α) R̅ⁿ⁻ⁱ . + * Because it is difficult to find a fixed value for the coefficient α such that the + * averaging is without significant lag but oscillations are filtered out, a heuristic is + * used. When the successive estimates R̂ⁿ do not change much then α→1, but when there is a + * sudden spike in the estimated rate α→0, such that the deviation is given little weight. + */ + estimated_rate = (double) current_rate * (double) RATE_UPDATE_INTERVAL / (double) (RATE_UPDATE_INTERVAL + s->last_latency - latency); + if (fabs(s->estimated_rate - s->avg_estimated_rate) > 1) { + double ratio = (estimated_rate + s->estimated_rate - 2*s->avg_estimated_rate) / (s->estimated_rate - s->avg_estimated_rate); + alpha = PA_CLAMP(2 * (ratio + fabs(ratio)) / (4 + ratio*ratio), 0.02, 0.8); } + s->avg_estimated_rate = alpha * estimated_rate + (1-alpha) * s->avg_estimated_rate; + s->estimated_rate = estimated_rate; + pa_log_debug("Estimated target rate: %.0f Hz, using average of %.0f Hz (α=%.3f)", estimated_rate, s->avg_estimated_rate, alpha); + new_rate = (uint32_t) ((double) (RATE_UPDATE_INTERVAL + latency/4 - s->intended_latency/4) / (double) RATE_UPDATE_INTERVAL * s->avg_estimated_rate); + s->last_latency = latency; + + if (new_rate < (uint32_t) (base_rate*0.8) || new_rate > (uint32_t) (base_rate*1.25)) { + pa_log_warn("Sample rates too different, not adjusting (%u vs. %u).", base_rate, new_rate); + new_rate = base_rate; + } else { + if (base_rate < new_rate + 20 && new_rate < base_rate + 20) + new_rate = base_rate; + /* Do the adjustment in small steps; 2‰ can be considered inaudible */ + if (new_rate < (uint32_t) (current_rate*0.998) || new_rate > (uint32_t) (current_rate*1.002)) { + pa_log_info("New rate of %u Hz not within 2‰ of %u Hz, forcing smaller adjustment", new_rate, current_rate); + new_rate = PA_CLAMP(new_rate, (uint32_t) (current_rate*0.998), (uint32_t) (current_rate*1.002)); + } + } + s->sink_input->sample_spec.rate = new_rate; pa_assert(pa_sample_spec_valid(&s->sink_input->sample_spec)); @@ -344,7 +375,9 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { if (pa_memblockq_is_readable(s->memblockq) && s->sink_input->thread_info.underrun_for > 0) { pa_log_debug("Requesting rewind due to end of underrun"); - pa_sink_input_request_rewind(s->sink_input, 0, FALSE, TRUE, FALSE); + pa_sink_input_request_rewind(s->sink_input, + (size_t) (s->sink_input->thread_info.underrun_for == (uint64_t) -1 ? 0 : s->sink_input->thread_info.underrun_for), + FALSE, TRUE, FALSE); } return 1; @@ -359,7 +392,7 @@ static void sink_input_attach(pa_sink_input *i) { pa_assert_se(s = i->userdata); pa_assert(!s->rtpoll_item); - s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1); + s->rtpoll_item = pa_rtpoll_item_new(i->sink->thread_info.rtpoll, PA_RTPOLL_LATE, 1); p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL); p->fd = s->rtp_context.fd; @@ -388,7 +421,7 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { pa_assert(salen > 0); af = sa->sa_family; - if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("Failed to create socket: %s", pa_cstrerror(errno)); goto fail; } @@ -469,22 +502,17 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->sdp_info = *sdp_info; s->rtpoll_item = NULL; s->intended_latency = LATENCY_USEC; - s->smoother = pa_smoother_new( - PA_USEC_PER_SEC*5, - PA_USEC_PER_SEC*2, - TRUE, - TRUE, - 10, - pa_timeval_load(&now), - TRUE); s->last_rate_update = pa_timeval_load(&now); + s->last_latency = LATENCY_USEC; + s->estimated_rate = (double) sink->sample_spec.rate; + s->avg_estimated_rate = (double) sink->sample_spec.rate; pa_atomic_store(&s->timestamp, (int) now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) goto fail; pa_sink_input_new_data_init(&data); - data.sink = sink; + pa_sink_input_new_data_set_sink(&data, sink, FALSE); data.driver = __FILE__; pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "stream"); pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, @@ -499,8 +527,9 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload); data.module = u->module; pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec); + data.flags = PA_SINK_INPUT_VARIABLE_RATE; - pa_sink_input_new(&s->sink_input, u->module->core, &data, PA_SINK_INPUT_VARIABLE_RATE); + pa_sink_input_new(&s->sink_input, u->module->core, &data); pa_sink_input_new_data_done(&data); if (!s->sink_input) { @@ -576,8 +605,6 @@ static void session_free(struct session *s) { pa_sdp_info_destroy(&s->sdp_info); pa_rtp_context_destroy(&s->rtp_context); - pa_smoother_free(s->smoother); - pa_xfree(s); } @@ -621,15 +648,13 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event } } -static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) { +static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { struct session *s, *n; struct userdata *u = userdata; struct timeval now; - struct timeval tv; pa_assert(m); pa_assert(t); - pa_assert(ptv); pa_assert(u); pa_rtclock_get(&now); @@ -647,9 +672,7 @@ static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const str } /* Restart timer */ - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC); - m->time_restart(t, &tv); + pa_core_rttime_restart(u->module->core, t, pa_rtclock_now() + DEATH_TIMEOUT * PA_USEC_PER_SEC); } int pa__init(pa_module*m) { @@ -663,7 +686,6 @@ int pa__init(pa_module*m) { socklen_t salen; const char *sap_address; int fd = -1; - struct timeval tv; pa_assert(m); @@ -696,6 +718,7 @@ int pa__init(pa_module*m) { m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; + u->core = m->core; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); @@ -705,9 +728,7 @@ int pa__init(pa_module*m) { u->n_sessions = 0; u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - pa_gettimeofday(&tv); - pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC); - u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u); + u->check_death_event = pa_core_rttime_new(m->core, pa_rtclock_now() + DEATH_TIMEOUT * PA_USEC_PER_SEC, check_death_event_cb, u); pa_modargs_free(ma); diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index cdd2c57d..7131629c 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -26,18 +26,16 @@ #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <errno.h> -#include <string.h> #include <unistd.h> +#include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/util.h> #include <pulse/xmalloc.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> -#include <pulsecore/llist.h> #include <pulsecore/source.h> #include <pulsecore/source-output.h> #include <pulsecore/memblockq.h> @@ -48,6 +46,7 @@ #include <pulsecore/sample-util.h> #include <pulsecore/macro.h> #include <pulsecore/socket-util.h> +#include <pulsecore/arpa-inet.h> #include "module-rtp-send-symdef.h" @@ -77,7 +76,7 @@ PA_MODULE_USAGE( #define DEFAULT_DESTINATION "224.0.0.56" #define MEMBLOCKQ_MAXLENGTH (1024*170) #define DEFAULT_MTU 1280 -#define SAP_INTERVAL 5 +#define SAP_INTERVAL (5*PA_USEC_PER_SEC) static const char* const valid_modargs[] = { "source", @@ -151,18 +150,14 @@ static void source_output_kill(pa_source_output* o) { static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { struct userdata *u = userdata; - struct timeval next; pa_assert(m); pa_assert(t); - pa_assert(tv); pa_assert(u); pa_sap_send(&u->sap_context, 0); - pa_gettimeofday(&next); - pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC); - m->time_restart(t, &next); + pa_core_rttime_restart(u->module->core, t, pa_rtclock_now() + SAP_INTERVAL); } int pa__init(pa_module*m) { @@ -186,7 +181,6 @@ int pa__init(pa_module*m) { char *p; int r, j; socklen_t k; - struct timeval tv; char hn[128], *n; pa_bool_t loop = FALSE; pa_source_output_new_data data; @@ -266,7 +260,7 @@ int pa__init(pa_module*m) { goto fail; } - if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("socket() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -281,7 +275,7 @@ int pa__init(pa_module*m) { #endif } - if ((sap_fd = socket(af, SOCK_DGRAM, 0)) < 0) { + if ((sap_fd = pa_socket_cloexec(af, SOCK_DGRAM, 0)) < 0) { pa_log("socket() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -320,8 +314,6 @@ int pa__init(pa_module*m) { /* If the socket queue is full, let's drop packets */ pa_make_fd_nonblock(fd); pa_make_udp_socket_low_delay(fd); - pa_make_fd_cloexec(fd); - pa_make_fd_cloexec(sap_fd); pa_source_output_new_data_init(&data); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream"); @@ -331,11 +323,12 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, "rtp.ttl", "%lu", (unsigned long) ttl); data.driver = __FILE__; data.module = m; - data.source = s; + pa_source_output_new_data_set_source(&data, s, FALSE); pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); + data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; - pa_source_output_new(&o, m->core, &data, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND); + pa_source_output_new(&o, m->core, &data); pa_source_output_new_data_done(&data); if (!o) { @@ -347,8 +340,8 @@ int pa__init(pa_module*m) { o->push = source_output_push; o->kill = source_output_kill; - pa_log_info("Configured source latency of %lu ms.", - pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); + pa_log_info("Configured source latency of %llu ms.", + (unsigned long long) pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); m->userdata = o->userdata = u = pa_xnew(struct userdata, 1); u->module = m; @@ -395,9 +388,7 @@ int pa__init(pa_module*m) { pa_sap_send(&u->sap_context, 0); - pa_gettimeofday(&tv); - pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC); - u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u); + u->sap_event = pa_core_rttime_new(m->core, pa_rtclock_now() + SAP_INTERVAL, sap_event_cb, u); pa_source_output_put(u->source_output); diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 6706a10f..05c736a7 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -23,11 +23,9 @@ #include <config.h> #endif -#include <fcntl.h> #include <stdlib.h> #include <string.h> #include <errno.h> -#include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> @@ -43,6 +41,7 @@ #include <pulsecore/log.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/arpa-inet.h> #include "rtp.h" @@ -278,8 +277,8 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool, struct pa_memchunk_reset(&c->memchunk); } - for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) { - if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) + for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) + if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) { memcpy(tstamp, CMSG_DATA(cm), sizeof(struct timeval)); found_tstamp = TRUE; break; diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index b197e82f..e975e750 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -40,6 +40,9 @@ typedef struct pa_rtp_context { } pa_rtp_context; pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size); + +/* If the memblockq doesn't have a silence memchunk set, then the caller must + * guarantee that the current read index doesn't point to a hole. */ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q); pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size); diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index cb037de6..ecf85b89 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -23,11 +23,9 @@ #include <config.h> #endif -#include <fcntl.h> #include <stdlib.h> #include <string.h> #include <errno.h> -#include <arpa/inet.h> #include <unistd.h> #include <sys/ioctl.h> #include <netinet/in.h> @@ -40,17 +38,11 @@ #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> -#include <pulsecore/socket-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> #include <pulsecore/strbuf.h> #include <pulsecore/ioline.h> - -#ifdef HAVE_POLL_H -#include <poll.h> -#else -#include <pulsecore/poll.h> -#endif +#include <pulsecore/arpa-inet.h> #include "rtsp_client.h" @@ -60,7 +52,6 @@ struct pa_rtsp_client { uint16_t port; pa_socket_client *sc; - pa_iochannel *io; pa_ioline *ioline; pa_rtsp_cb_t callback; @@ -111,10 +102,8 @@ void pa_rtsp_client_free(pa_rtsp_client* c) { if (c->sc) pa_socket_client_unref(c->sc); - if (c->ioline) - pa_ioline_close(c->ioline); - else if (c->io) - pa_iochannel_free(c->io); + + pa_rtsp_disconnect(c); pa_xfree(c->hostname); pa_xfree(c->url); @@ -187,7 +176,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { if (!s) { /* Keep the ioline/iochannel open as they will be freed automatically */ c->ioline = NULL; - c->io = NULL; c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } @@ -214,11 +202,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { /* End of headers */ /* We will have a header left from our looping iteration, so add it in :) */ if (c->last_header) { + char *tmp = pa_strbuf_tostring_free(c->header_buffer); /* This is not a continuation header so let's dump it into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_headerlist_puts(c->response_headers, c->last_header, tmp); + pa_xfree(tmp); pa_xfree(c->last_header); c->last_header = NULL; - c->header_buffer= NULL; + c->header_buffer = NULL; } pa_log_debug("Full response received. Dispatching"); @@ -240,9 +230,11 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } if (c->last_header) { + char *tmp = pa_strbuf_tostring_free(c->header_buffer); /* This is not a continuation header so let's dump the full header/value into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_headerlist_puts(c->response_headers, c->last_header, tmp); + pa_xfree(tmp); pa_xfree(c->last_header); c->last_header = NULL; c->header_buffer = NULL; @@ -299,8 +291,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_log("Connection failed: %s", pa_cstrerror(errno)); return; } - pa_assert(!c->io); - c->io = io; + pa_assert(!c->ioline); c->ioline = pa_ioline_new(io); pa_ioline_set_callback(c->ioline, line_callback, c); @@ -333,7 +324,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) { pa_xfree(c->session); c->session = NULL; - if (!(c->sc = pa_socket_client_new_string(c->mainloop, c->hostname, c->port))) { + if (!(c->sc = pa_socket_client_new_string(c->mainloop, TRUE, c->hostname, c->port))) { pa_log("failed to connect to server '%s:%d'", c->hostname, c->port); return -1; } @@ -356,9 +347,6 @@ void pa_rtsp_disconnect(pa_rtsp_client *c) { if (c->ioline) pa_ioline_close(c->ioline); - else if (c->io) - pa_iochannel_free(c->io); - c->io = NULL; c->ioline = NULL; } @@ -381,8 +369,7 @@ void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) { c->url = pa_xstrdup(url); } -void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) -{ +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) { pa_assert(c); pa_assert(key); pa_assert(value); @@ -390,8 +377,7 @@ void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) pa_headerlist_puts(c->headers, key, value); } -void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) -{ +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) { pa_assert(c); pa_assert(key); @@ -404,13 +390,11 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd, pa_headerlist* headers) { pa_strbuf* buf; char* hdrs; - ssize_t l; pa_assert(c); pa_assert(c->url); - - if (!cmd) - return -1; + pa_assert(cmd); + pa_assert(c->ioline); pa_log_debug("Sending command: %s", cmd); @@ -449,7 +433,7 @@ static int rtsp_exec(pa_rtsp_client* c, const char* cmd, hdrs = pa_strbuf_tostring_free(buf); /*pa_log_debug("Submitting request:"); pa_log_debug(hdrs);*/ - l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); + pa_ioline_puts(c->ioline, hdrs); pa_xfree(hdrs); return 0; diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index b229f261..a56b9324 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -27,8 +27,6 @@ #include <sys/types.h> #include <netdb.h> -#include <pulsecore/memblockq.h> -#include <pulsecore/memchunk.h> #include <pulsecore/socket-client.h> #include <pulse/mainloop-api.h> diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index adde16df..4d8bf668 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -23,12 +23,10 @@ #include <config.h> #endif -#include <time.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <errno.h> #include <string.h> #include <unistd.h> @@ -48,6 +46,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> +#include <pulsecore/arpa-inet.h> #include "sap.h" #include "sdp.h" diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index 7fc7e38c..3e61d9b8 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -28,7 +28,6 @@ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> -#include <arpa/inet.h> #include <string.h> #include <pulse/xmalloc.h> @@ -37,6 +36,7 @@ #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> +#include <pulsecore/arpa-inet.h> #include "sdp.h" #include "rtp.h" diff --git a/src/modules/udev-util.c b/src/modules/udev-util.c index 144ad797..356f7736 100644 --- a/src/modules/udev-util.c +++ b/src/modules/udev-util.c @@ -58,7 +58,113 @@ static int read_id(struct udev_device *d, const char *n) { return u; } -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { +static int dehex(char x) { + if (x >= '0' && x <= '9') + return x - '0'; + + if (x >= 'A' && x <= 'F') + return x - 'A' + 10; + + if (x >= 'a' && x <= 'f') + return x - 'a' + 10; + + return -1; +} + +static void proplist_sets_unescape(pa_proplist *p, const char *prop, const char *s) { + const char *f; + char *t, *r; + int c = 0; + + enum { + TEXT, + BACKSLASH, + EX, + FIRST + } state = TEXT; + + /* The resulting string is definitely shorter than the source string */ + r = pa_xnew(char, strlen(s)+1); + + for (f = s, t = r; *f; f++) { + + switch (state) { + + case TEXT: + if (*f == '\\') + state = BACKSLASH; + else + *(t++) = *f; + break; + + case BACKSLASH: + if (*f == 'x') + state = EX; + else { + *(t++) = '\\'; + *(t++) = *f; + state = TEXT; + } + break; + + case EX: + c = dehex(*f); + + if (c < 0) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = *f; + state = TEXT; + } else + state = FIRST; + + break; + + case FIRST: { + int d = dehex(*f); + + if (d < 0) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = *(f-1); + *(t++) = *f; + } else + *(t++) = (char) (c << 4) | d; + + state = TEXT; + break; + } + } + } + + switch (state) { + + case TEXT: + break; + + case BACKSLASH: + *(t++) = '\\'; + break; + + case EX: + *(t++) = '\\'; + *(t++) = 'x'; + break; + + case FIRST: + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = *(f-1); + break; + } + + *t = 0; + + pa_proplist_sets(p, prop, r); + pa_xfree(r); +} + +int pa_udev_get_info(int card_idx, pa_proplist *p) { int r = -1; struct udev *udev; struct udev_device *card = NULL; @@ -66,7 +172,6 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { const char *v; int id; - pa_assert(core); pa_assert(p); pa_assert(card_idx >= 0); @@ -84,6 +189,19 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { goto finish; } + if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) + if (((v = udev_device_get_property_value(card, "ID_PATH")) && *v) || + (v = udev_device_get_devpath(card))) + pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); + + if (!pa_proplist_contains(p, "sysfs.path")) + if ((v = udev_device_get_devpath(card))) + pa_proplist_sets(p, "sysfs.path", v); + + if (!pa_proplist_contains(p, "udev.id")) + if ((v = udev_device_get_property_value(card, "ID_ID")) && *v) + pa_proplist_sets(p, "udev.id", v); + if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS)) if ((v = udev_device_get_property_value(card, "ID_BUS")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_BUS, v); @@ -95,6 +213,8 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_NAME)) { if ((v = udev_device_get_property_value(card, "ID_VENDOR_FROM_DATABASE")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v); + else if ((v = udev_device_get_property_value(card, "ID_VENDOR_ENC")) && *v) + proplist_sets_unescape(p, PA_PROP_DEVICE_VENDOR_NAME, v); else if ((v = udev_device_get_property_value(card, "ID_VENDOR")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v); } @@ -106,6 +226,8 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { if (!pa_proplist_contains(p, PA_PROP_DEVICE_PRODUCT_NAME)) { if ((v = udev_device_get_property_value(card, "ID_MODEL_FROM_DATABASE")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v); + else if ((v = udev_device_get_property_value(card, "ID_MODEL_ENC")) && *v) + proplist_sets_unescape(p, PA_PROP_DEVICE_PRODUCT_NAME, v); else if ((v = udev_device_get_property_value(card, "ID_MODEL")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v); } @@ -114,15 +236,15 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { if ((v = udev_device_get_property_value(card, "ID_SERIAL")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_SERIAL, v); + if (!pa_proplist_contains(p, PA_PROP_DEVICE_CLASS)) + if ((v = udev_device_get_property_value(card, "SOUND_CLASS")) && *v) + pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, v); + if (!pa_proplist_contains(p, PA_PROP_DEVICE_FORM_FACTOR)) if ((v = udev_device_get_property_value(card, "SOUND_FORM_FACTOR")) && *v) pa_proplist_sets(p, PA_PROP_DEVICE_FORM_FACTOR, v); - if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) - if ((v = udev_device_get_devpath(card))) - pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); - - /* This is normaly not set by th udev rules but may be useful to + /* This is normaly not set by the udev rules but may be useful to * allow administrators to overwrite the device description.*/ if (!pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION)) if ((v = udev_device_get_property_value(card, "SOUND_DESCRIPTION")) && *v) @@ -140,3 +262,40 @@ finish: return r; } + +char* pa_udev_get_property(int card_idx, const char *name) { + struct udev *udev; + struct udev_device *card = NULL; + char *t, *r = NULL; + const char *v; + + pa_assert(card_idx >= 0); + pa_assert(name); + + if (!(udev = udev_new())) { + pa_log_error("Failed to allocate udev context."); + goto finish; + } + + t = pa_sprintf_malloc("%s/class/sound/card%i", udev_get_sys_path(udev), card_idx); + card = udev_device_new_from_syspath(udev, t); + pa_xfree(t); + + if (!card) { + pa_log_error("Failed to get card object."); + goto finish; + } + + if ((v = udev_device_get_property_value(card, name)) && *v) + r = pa_xstrdup(v); + +finish: + + if (card) + udev_device_unref(card); + + if (udev) + udev_unref(udev); + + return r; +} diff --git a/src/modules/udev-util.h b/src/modules/udev-util.h index 5120abdd..a978178f 100644 --- a/src/modules/udev-util.h +++ b/src/modules/udev-util.h @@ -23,8 +23,9 @@ ***/ -#include <pulsecore/core.h> +#include <pulse/proplist.h> -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card); +int pa_udev_get_info(int card_idx, pa_proplist *p); +char* pa_udev_get_property(int card_idx, const char *name); #endif diff --git a/src/modules/x11/module-x11-bell.c b/src/modules/x11/module-x11-bell.c index ac303c3b..37ab2e78 100644 --- a/src/modules/x11/module-x11-bell.c +++ b/src/modules/x11/module-x11-bell.c @@ -25,18 +25,14 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <X11/Xlib.h> #include <X11/XKBlib.h> #include <pulse/xmalloc.h> -#include <pulsecore/iochannel.h> -#include <pulsecore/sink.h> #include <pulsecore/core-scache.h> #include <pulsecore/modargs.h> -#include <pulsecore/namereg.h> #include <pulsecore/log.h> #include <pulsecore/x11wrap.h> diff --git a/src/modules/x11/module-x11-cork-request.c b/src/modules/x11/module-x11-cork-request.c index c1380c27..0e67db00 100644 --- a/src/modules/x11/module-x11-cork-request.c +++ b/src/modules/x11/module-x11-cork-request.c @@ -25,7 +25,6 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <unistd.h> #include <X11/Xlib.h> @@ -33,7 +32,6 @@ #include <X11/XF86keysym.h> #include <X11/keysym.h> -#include <pulse/util.h> #include <pulse/xmalloc.h> #include <pulsecore/module.h> diff --git a/src/modules/x11/module-x11-publish.c b/src/modules/x11/module-x11-publish.c index 2c7fdc12..716fe0b8 100644 --- a/src/modules/x11/module-x11-publish.c +++ b/src/modules/x11/module-x11-publish.c @@ -28,17 +28,12 @@ #include <string.h> #include <unistd.h> -#include <X11/Xlib.h> -#include <X11/Xatom.h> +#include <xcb/xcb.h> -#include <pulse/util.h> #include <pulse/xmalloc.h> #include <pulsecore/module.h> -#include <pulsecore/sink.h> -#include <pulsecore/core-scache.h> #include <pulsecore/modargs.h> -#include <pulsecore/namereg.h> #include <pulsecore/log.h> #include <pulsecore/x11wrap.h> #include <pulsecore/core-util.h> @@ -46,7 +41,6 @@ #include <pulsecore/auth-cookie.h> #include <pulsecore/x11prop.h> #include <pulsecore/strlist.h> -#include <pulsecore/shared.h> #include <pulsecore/protocol-native.h> #include "module-x11-publish-symdef.h" @@ -85,27 +79,31 @@ struct userdata { static void publish_servers(struct userdata *u, pa_strlist *l) { + int screen = DefaultScreen(pa_x11_wrapper_get_display(u->x11_wrapper)); + if (l) { char *s; l = pa_strlist_reverse(l); s = pa_strlist_tostring(l); - l = pa_strlist_reverse(l); + pa_strlist_reverse(l); - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s); + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SERVER", s); pa_xfree(s); } else - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SERVER"); } static pa_hook_result_t servers_changed_cb(void *hook_data, void *call_data, void *slot_data) { pa_strlist *servers = call_data; struct userdata *u = slot_data; char t[256]; + int screen; pa_assert(u); - if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) { + screen = DefaultScreen(pa_x11_wrapper_get_display(u->x11_wrapper)); + if (!pa_x11_get_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) { pa_log_warn("PulseAudio information vanished from X11!"); return PA_HOOK_OK; } @@ -139,6 +137,7 @@ int pa__init(pa_module*m) { char *mid, *sid; char hx[PA_NATIVE_COOKIE_LENGTH*2+1]; const char *t; + int screen; pa_assert(m); @@ -164,26 +163,27 @@ int pa__init(pa_module*m) { if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) goto fail; + screen = DefaultScreen(pa_x11_wrapper_get_display(u->x11_wrapper)); mid = pa_machine_id(); u->id = pa_sprintf_malloc("%lu@%s/%lu", (unsigned long) getuid(), mid, (unsigned long) getpid()); pa_xfree(mid); - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", u->id); + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_ID", u->id); if ((sid = pa_session_id())) { - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SESSION_ID", sid); + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SESSION_ID", sid); pa_xfree(sid); } publish_servers(u, pa_native_protocol_servers(u->protocol)); if ((t = pa_modargs_get_value(ma, "source", NULL))) - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE", t); + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SOURCE", t); if ((t = pa_modargs_get_value(ma, "sink", NULL))) - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK", t); + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SINK", t); - pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE", + pa_x11_set_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_COOKIE", pa_hexstr(pa_auth_cookie_read(u->auth_cookie, PA_NATIVE_COOKIE_LENGTH), PA_NATIVE_COOKIE_LENGTH, hx, sizeof(hx))); u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u); @@ -214,18 +214,19 @@ void pa__done(pa_module*m) { if (u->x11_wrapper) { char t[256]; + int screen = DefaultScreen(pa_x11_wrapper_get_display(u->x11_wrapper)); /* Yes, here is a race condition */ - if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) + if (!pa_x11_get_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) pa_log_warn("PulseAudio information vanished from X11!"); else { - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID"); - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER"); - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK"); - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE"); - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE"); - pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SESSION_ID"); - XSync(pa_x11_wrapper_get_display(u->x11_wrapper), False); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_ID"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SERVER"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SINK"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SOURCE"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_COOKIE"); + pa_x11_del_prop(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper), screen, "PULSE_SESSION_ID"); + xcb_flush(pa_x11_wrapper_get_xcb_connection(u->x11_wrapper)); } pa_x11_wrapper_unref(u->x11_wrapper); diff --git a/src/modules/x11/module-x11-xsmp.c b/src/modules/x11/module-x11-xsmp.c index 28fd373a..6a6116ff 100644 --- a/src/modules/x11/module-x11-xsmp.c +++ b/src/modules/x11/module-x11-xsmp.c @@ -33,13 +33,8 @@ #include <pulse/xmalloc.h> #include <pulse/util.h> -#include <pulsecore/iochannel.h> -#include <pulsecore/sink.h> -#include <pulsecore/core-scache.h> #include <pulsecore/modargs.h> -#include <pulsecore/namereg.h> #include <pulsecore/log.h> -#include <pulsecore/core-util.h> #include <pulsecore/x11wrap.h> #include "module-x11-xsmp-symdef.h" @@ -129,7 +124,7 @@ int pa__init(pa_module*m) { pa_assert(m); if (ice_in_use) { - pa_log("module-x11-xsmp may no be loaded twice."); + pa_log("module-x11-xsmp may not be loaded twice."); return -1; } |
