diff options
Diffstat (limited to 'src/modules/alsa')
57 files changed, 10872 insertions, 2005 deletions
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(); } |
