diff options
Diffstat (limited to 'src/modules')
48 files changed, 6570 insertions, 1631 deletions
| diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c new file mode 100644 index 00000000..6f21e103 --- /dev/null +++ b/src/modules/alsa/alsa-mixer.c @@ -0,0 +1,3382 @@ +/*** +  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 <limits.h> +#include <asoundlib.h> + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/i18n.h> +#include <pulse/utf8.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#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/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; +} + +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 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); +    } + +    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)) +                    r = snd_mixer_selem_get_playback_dB(me, c, &value); +                else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) +                    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; +} + +static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +    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; + +        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) +                    f = v->values[k]; + +        if (e->has_dB) { +            long value = to_alsa_dB(f); + +            if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { +                /* If we call set_play_volume() without checking first +                 * if the channel is available, ALSA behaves ver +                 * strangely and doesn't fail the call */ +                if (snd_mixer_selem_has_playback_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_playback_dB(me, c, value, +1)) >= 0) +                        r = snd_mixer_selem_get_playback_dB(me, c, &value); +                } else +                    r = -1; +            } else { +                if (snd_mixer_selem_has_capture_channel(me, c)) { +                    if ((r = snd_mixer_selem_set_capture_dB(me, c, value, +1)) >= 0) +                        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; + +            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_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) < 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; +} + +static int element_mute_volume(pa_alsa_element *e, snd_mixer_t *m) { +    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_volume_all(me, e->min_volume); +    else +        r = snd_mixer_selem_set_capture_volume_all(me, e->min_volume); + +    if (r < 0) +        pa_log_warn("Faile to set volume to muted of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); + +    return r; +} + +/* The volume to 0dB */ +static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) { +    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_dB_all(me, 0, +1); +    else +        r = snd_mixer_selem_set_capture_dB_all(me, 0, +1); + +    if (r < 0) +        pa_log_warn("Faile to set volume to 0dB 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; + +    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_MUTE: +            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_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_MERGE: +                r = element_mute_volume(e, m); +                break; + +            case PA_ALSA_VOLUME_ZERO: +                r = element_zero_volume(e, m); +                break; + +            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; + +    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); + +    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_VOLUME_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) +                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; + +            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_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->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 { +                pa_bool_t is_mono; +                pa_channel_position_t p; + +                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++) +                            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->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++) +                        e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; +                } +            } +        } + +    } + +    if (check_required(e, me) < 0) +        return -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; +            } +        } +    } + +    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; + +    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 { +        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_required_t req; + +    pa_assert(p); + +    if (!(e = element_get(p, section, TRUE))) { +        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")) +        req = PA_ALSA_REQUIRED_SWITCH; +    else if (pa_streq(rvalue, "volume")) +        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")) +        e->required_absent = req; +    else +        e->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 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("Faile 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("Faile 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-linein",              N_("Line-In") }, +        { "input-microphone",          N_("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",             "" }, +        { "input-boost-on",            N_("Boost") }, +        { "input-boost-off",           "" }, +        { "output-amplifier-on",       N_("Amplifier") }, +        { "output-amplifier-off",      "" } +    }; + +    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); + +    if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) || +        (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-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") } +    }; + +    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-absent",     element_parse_required,            NULL, NULL }, +        { "direction",           element_parse_direction,           NULL, NULL }, +        { "direction-try-other", element_parse_direction_try_other, 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_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->switch_use = PA_ALSA_SWITCH_MUTE; +    e->volume_use = PA_ALSA_VOLUME_MERGE; + +    PA_LLIST_PREPEND(pa_alsa_element, p->elements, 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_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; +        } + +        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; +                        } + +                    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; +                            } +                    } 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; +                } +            } else if (p->has_volume) +                /* We can't use this volume, so let's ignore it */ +                e->volume_use = PA_ALSA_VOLUME_IGNORE; + +            p->has_volume = TRUE; +        } + +        if (e->switch_use == PA_ALSA_SWITCH_MUTE) +            p->has_mute = TRUE; +    } + +    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 (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, enumeration=%i, required=%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->enumeration_use, +                 e->required, +                 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_assert(m); +    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); +        } + +        return ps; +    } + +    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; +            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; + +            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; +    } + +    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); +    } + +    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 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_xstrdup(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 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-surround-40",     N_("Digital Surround 4.0 (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 elment, 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 += 5000; +        else if (m->channel_map.channels == bonus->channels) +            m->priority += 4000; +    } + +    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 ((p = 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",                     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 = p->output_mapping_names; *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); +        } + +        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 = p->input_mapping_names; *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); +        } + +        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); +} + +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; +    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 }, +        { 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); + +    items[0].data = &ps->auto_profiles; + +    if (!fname) +        fname = "default.conf"; + +    fn = pa_maybe_prefix_path(fname, 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; + +    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) { +    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; +        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; + +                if (!(m ->output_pcm = pa_alsa_open_by_template( +                              m->device_strings, +                              dev_id, +                              NULL, +                              &try_ss, &try_map, +                              SND_PCM_STREAM_PLAYBACK, +                              NULL, NULL, 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; + +                if (!(m ->input_pcm = pa_alsa_open_by_template( +                              m->device_strings, +                              dev_id, +                              NULL, +                              &try_ss, &try_map, +                              SND_PCM_STREAM_CAPTURE, +                              NULL, NULL, 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; +    void *state; + +    pa_assert(ps); + +    pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%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_FOREACH(m, ps->mappings, state) +        pa_alsa_mapping_dump(m); + +    PA_HASHMAP_FOREACH(p, ps->profiles, state) +        pa_alsa_profile_dump(p); +} + +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..76788183 --- /dev/null +++ b/src/modules/alsa/alsa-mixer.h @@ -0,0 +1,292 @@ +#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/volume.h> +#include <pulse/mainloop-api.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> +#include <pulse/volume.h> + +#include <pulsecore/llist.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/core.h> +#include <pulsecore/log.h> + +typedef struct pa_alsa_fdlist pa_alsa_fdlist; +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_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_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; +}; + +/* And 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 includes 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_absent; + +    pa_bool_t override_map:1; +    pa_bool_t direction_try_other:1; + +    pa_bool_t has_dB:1; +    long min_volume, max_volume; +    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); +}; + +/* 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; + +    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); +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_profile_set { +    pa_hashmap *mappings; +    pa_hashmap *profiles; + +    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); + +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); +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); + +/* 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 59a5ca75..2226bc6f 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -80,11 +80,9 @@ struct userdata {      pa_alsa_fdlist *mixer_fdl;      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;      size_t @@ -100,7 +98,8 @@ struct userdata {      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; @@ -991,191 +990,58 @@ 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) { - -    return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / -                               (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { -    long alsa_vol; - -    alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) -                            / PA_VOLUME_NORM) + u->hw_volume_min; - -    return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} -  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];      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; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    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, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); -    if (!pa_cvolume_equal(&u->hardware_volume, &r)) { +    if (pa_cvolume_equal(&u->hardware_volume, &r)) +        return; -        s->virtual_volume = u->hardware_volume = r; +    s->virtual_volume = u->hardware_volume = r; -        if (u->hw_dB_supported) { -            pa_cvolume reset; +    if (u->mixer_path->has_dB) { +        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); -        } +        /* 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);      } - -    return; - -fail: -    pa_log_error("Unable to read volume: %s", pa_alsa_strerror(err));  }  static void sink_set_volume_cb(pa_sink *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r; +    char t[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; -            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; - -                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 { -                alsa_vol = to_alsa_volume(u, vol); - -                if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) -                    goto fail; - -                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 { -        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; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { -            alsa_vol = to_alsa_volume(u, vol); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) -                goto fail; +    /* Shift up by the base volume */ +    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); -            if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      u->hardware_volume = r; -    if (u->hw_dB_supported) { -        char t[PA_CVOLUME_SNPRINT_MAX]; +    if (u->mixer_path->has_dB) {          /* Match exactly what the user requested by software */          pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume); @@ -1184,45 +1050,75 @@ static void sink_set_volume_cb(pa_sink *s) {          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 +    } else { +        pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));          /* 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 = 0; +    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; + +        if (u->mixer_path->max_dB > 0.0) +            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); +        else +            pa_log_info("No particular base volume set, fixing to 0 dB"); +    } 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) { @@ -1465,77 +1361,127 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de      pa_xfree(t);  } +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_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; + +            if (u->mixer_path->max_dB > 0.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"); + +        } 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->flags |= PA_SINK_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SINK_DECIBEL_VOLUME : 0); +        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(); @@ -1544,13 +1490,15 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {          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; @@ -1561,7 +1509,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      size_t frame_size;      pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;      pa_sink_new_data data; -    char *control_device = NULL; +    pa_alsa_profile_set *profile_set = NULL;      pa_assert(m);      pa_assert(ma); @@ -1646,32 +1594,35 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      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))) +                      &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))) +                      &b, &d, profile_set, &mapping)))              goto fail; @@ -1685,7 +1636,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca                        &nfrags, &period_frames, tsched_frames,                        &b, &d, FALSE)))              goto fail; -      }      pa_assert(u->device_name); @@ -1696,8 +1646,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."); @@ -1723,7 +1673,7 @@ 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; @@ -1733,23 +1683,21 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      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_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"); @@ -1757,6 +1705,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); @@ -1768,6 +1719,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca      u->sink->parent.process_msg = sink_process_msg;      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); @@ -1836,6 +1788,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: @@ -1843,6 +1798,9 @@ fail:      if (u)          userdata_free(u); +    if (profile_set) +        pa_alsa_profile_set_free(profile_set); +      return NULL;  } @@ -1871,17 +1829,22 @@ static void userdata_free(struct userdata *u) {      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); @@ -1889,6 +1852,7 @@ static void userdata_free(struct userdata *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..b9a4ac2a 100644 --- a/src/modules/alsa/alsa-sink.h +++ b/src/modules/alsa/alsa-sink.h @@ -28,8 +28,9 @@  #include <pulsecore/sink.h>  #include "alsa-util.h" +#include "alsa-mixer.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 c176309e..f2e4e234 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -28,10 +28,6 @@  #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> @@ -81,11 +77,8 @@ struct userdata {      pa_alsa_fdlist *mixer_fdl;      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; @@ -102,6 +95,7 @@ struct userdata {      unsigned nfragments;      char *device_name; +    char *control_device;      pa_bool_t use_mmap:1, use_tsched:1; @@ -949,239 +943,135 @@ 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) { - -    return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / -                               (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { -    long alsa_vol; - -    alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) -                            / PA_VOLUME_NORM) + u->hw_volume_min; - -    return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} -  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];      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; - -#ifdef HAVE_VALGRIND_MEMCHECK_H -            VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    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, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); -    if (!pa_cvolume_equal(&u->hardware_volume, &r)) { +    if (pa_cvolume_equal(&u->hardware_volume, &r)) +        return; -        s->virtual_volume = u->hardware_volume = r; +    s->virtual_volume = u->hardware_volume = r; -        if (u->hw_dB_supported) { -            pa_cvolume reset; +    if (u->mixer_path->has_dB) { +        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); -        } +        /* 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);      } - -    return; - -fail: -    pa_log_error("Unable to read volume: %s", pa_alsa_strerror(err));  }  static void source_set_volume_cb(pa_source *s) {      struct userdata *u = s->userdata; -    int err; -    unsigned i;      pa_cvolume r; +    char t[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; -            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; - -#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 { -                alsa_vol = to_alsa_volume(u, vol); - -                if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) -                    goto fail; - -                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 { -        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_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 - -            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - -        } else { -            alsa_vol = to_alsa_volume(u, vol); +    pa_assert(u->mixer_path); +    pa_assert(u->mixer_handle); -            if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) -                goto fail; +    /* Shift up by the base volume */ +    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume); -            if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) -                goto fail; +    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) +        return; -            pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); -        } -    } +    /* Shift down by the base volume, so that 0dB becomes maximum volume */ +    pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);      u->hardware_volume = r; -    if (u->hw_dB_supported) { -        char t[PA_CVOLUME_SNPRINT_MAX]; +    if (u->mixer_path->has_dB) {          /* 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 +    } else { +        pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));          /* 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 = 0; +    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; + +        if (u->mixer_path->max_dB > 0.0) +            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume)); +        else +            pa_log_info("No particular base volume set, fixing to 0 dB"); +    } 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) { @@ -1323,77 +1213,127 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char      pa_xfree(t);  } +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_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 (!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; + +            if (u->mixer_path->max_dB > 0.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"); + +        } 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->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SOURCE_DECIBEL_VOLUME : 0); +        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");      } -    if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { +    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(); @@ -1402,13 +1342,15 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {          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_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; @@ -1419,7 +1361,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      size_t frame_size;      pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;      pa_source_new_data data; -    char *control_device = NULL; +    pa_alsa_profile_set *profile_set = NULL;      pa_assert(m);      pa_assert(ma); @@ -1480,7 +1422,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      u->use_tsched = use_tsched;      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, @@ -1504,31 +1445,34 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      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))) +                      &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))) +                      &b, &d, profile_set, &mapping)))              goto fail;      } else { @@ -1551,8 +1495,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."); @@ -1578,7 +1522,7 @@ 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; @@ -1588,23 +1532,21 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      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_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"); @@ -1612,6 +1554,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); @@ -1623,6 +1568,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p      u->source->parent.process_msg = source_process_msg;      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); @@ -1687,6 +1633,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: @@ -1694,6 +1643,9 @@ fail:      if (u)          userdata_free(u); +    if (profile_set) +        pa_alsa_profile_set_free(profile_set); +      return NULL;  } @@ -1719,17 +1671,22 @@ static void userdata_free(struct userdata *u) {      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); @@ -1737,6 +1694,7 @@ static void userdata_free(struct userdata *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..0204c28b 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -42,8 +42,10 @@  #include <pulsecore/core-error.h>  #include <pulsecore/once.h>  #include <pulsecore/thread.h> +#include <pulsecore/conf-parser.h>  #include "alsa-util.h" +#include "alsa-mixer.h"  #ifdef HAVE_HAL  #include "hal-util.h" @@ -53,182 +55,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,11 +86,11 @@ 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(f); @@ -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) @@ -393,12 +218,12 @@ int pa_alsa_set_hw_params(      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)); +            pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", c, pa_alsa_strerror(ret));              goto finish;          }      } else {          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", c, pa_alsa_strerror(ret));              goto finish;          }      } @@ -415,10 +240,12 @@ int pa_alsa_set_hw_params(          tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate);          if (_use_tsched) { -            snd_pcm_uframes_t buffer_size; +            snd_pcm_uframes_t buffer_size = 0; -            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 ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 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 %u ms", (unsigned) buffer_size * 1000 / r);              _period_size = tsched_size;              _periods = 1; @@ -464,12 +291,16 @@ int pa_alsa_set_hw_params(      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 ((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_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_periods(hwparams, &_periods, &dir)) < 0) { +        pa_log_info("snd_pcm_hw_params_get_period{s|_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) @@ -553,167 +384,6 @@ 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, -        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, -        pa_bool_t require_exact_channel_number) { - -    snd_pcm_t *pcm_handle; -    char *d; - -    d = pa_sprintf_malloc("%s:%s", prefix, dev_id); - -    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); - -    if (!pcm_handle && prefix_fallback) { - -        d = pa_sprintf_malloc("%s:%s", prefix_fallback, dev_id); - -        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); -    } - -    return pcm_handle; -} -  snd_pcm_t *pa_alsa_open_by_device_id_auto(          const char *dev_id,          char **dev, @@ -725,12 +395,13 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(          snd_pcm_uframes_t tsched_size,          pa_bool_t *use_mmap,          pa_bool_t *use_tsched, -        const pa_alsa_profile_info **profile) { +        pa_alsa_profile_set *ps, +        pa_alsa_mapping **mapping) { -    int i; -    int direction = 1;      char *d;      snd_pcm_t *pcm_handle; +    void *state; +    pa_alsa_mapping *m;      pa_assert(dev_id);      pa_assert(dev); @@ -738,113 +409,82 @@ snd_pcm_t *pa_alsa_open_by_device_id_auto(      pa_assert(map);      pa_assert(nfrags);      pa_assert(period_size); +    pa_assert(ps);      /* 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; +     * 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.*/ -            pa_log_debug("Checking for %s (%s)", device_table[i].name, device_table[i].alsa_name); +    PA_HASHMAP_FOREACH(m, ps->mappings, state) { +        if (!pa_channel_map_superset(&m->channel_map, map)) +            continue; -            try_ss.channels = device_table[i].map.channels; -            try_ss.rate = ss->rate; -            try_ss.format = ss->format; +        pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]); -            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; -            } +        pcm_handle = pa_alsa_open_by_device_id_mapping( +                dev_id, +                dev, +                ss, +                map, +                mode, +                nfrags, +                period_size, +                tsched_size, +                use_mmap, +                use_tsched, +                m); -        } +        if (pcm_handle) { +            if (mapping) +                *mapping = m; -        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, +                nfrags, +                period_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);      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, @@ -855,10 +495,11 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile(          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); @@ -866,19 +507,19 @@ snd_pcm_t *pa_alsa_open_by_device_id_profile(      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, @@ -891,7 +532,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; @@ -924,14 +565,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) { @@ -951,7 +586,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 +622,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, +        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, +        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, +                nfrags, +                period_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 +689,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,...) { @@ -1544,7 +795,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,16 +832,14 @@ 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]); @@ -1610,7 +859,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 +875,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 @@ -1656,10 +902,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); diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 27f43712..c2f0e5b7 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -30,95 +30,86 @@  #include <pulse/mainloop-api.h>  #include <pulse/channelmap.h>  #include <pulse/proplist.h> +#include <pulse/volume.h> +#include <pulsecore/llist.h>  #include <pulsecore/rtpoll.h>  #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 */ +        uint32_t *periods,                 /* modified at return */ +        snd_pcm_uframes_t *period_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_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); +int pa_alsa_set_sw_params( +        snd_pcm_t *pcm, +        snd_pcm_uframes_t avail_min); -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, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_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, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_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, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_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, +        uint32_t *nfrags,                 /* modified at return */ +        snd_pcm_uframes_t *period_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); @@ -128,7 +119,8 @@ void pa_alsa_redirect_errors_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); @@ -140,13 +132,11 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si  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); 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..8f480567 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-aux.conf @@ -0,0 +1,32 @@ +# For devices, where we have an Aux element + +[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 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 + +.include analog-input.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..0f78f39f --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-fm.conf @@ -0,0 +1,44 @@ +# For devices where we have an FM element + +[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 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-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf new file mode 100644 index 00000000..b6ba738c --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf @@ -0,0 +1,43 @@ +# For devices, where we have  a Line element + +[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 Line] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[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 + +.include analog-input.conf.common 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..7d4addf7 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf @@ -0,0 +1,44 @@ +# For devices where we have a Mic/Lineb element + +[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 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 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..004cd24a --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf @@ -0,0 +1,45 @@ +# For devices where we have  a Mic element + +[General] +priority = 100 +name = analog-input-microphone + +[Element Capture] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[Element Mic] +required = any +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right + +[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] +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.common b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common new file mode 100644 index 00000000..d67ee4e3 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common @@ -0,0 +1,41 @@ +;;; 'Mic Select' + +[Element Mic Select] +enumeration = select + +[Option Mic Select:Mic1] +name = input-microphone +priority = 20 + +[Option Mic Select:Mic2] +name = input-microphone +priority = 19 + +;;; Various Boosts + +[Element Mic Boost (+20dB)] +switch = select + +[Option Mic Boost (+20dB):on] +name = input-boost-on + +[Option Mic Boost (+20dB):off] +name = input-boost-off + +[Element Mic Boost] +switch = select + +[Option Mic Boost:on] +name = input-boost-on + +[Option Mic Boost:off] +name = input-boost-off + +[Element Front Mic Boost] +switch = select + +[Option Front Mic Boost:on] +name = input-boost-on + +[Option Front Mic Boost:off] +name = input-boost-off 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..ea0a0b72 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf @@ -0,0 +1,44 @@ +# For devices, where we have a TV Tuner element + +[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 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..27acc254 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input-video.conf @@ -0,0 +1,31 @@ +# For devices, where we have a Video element + +[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 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 + +.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..b221bb44 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf @@ -0,0 +1,35 @@ +# A fallback for devices that lack seperate Mic/Line/Aux/Video elements + +[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 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 +.include analog-input-mic.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..d34afd04 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-input.conf.common @@ -0,0 +1,239 @@ +# Mixer path for PulseAudio's ALSA backend. 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 +# + +;;; '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:Mic] +name = input-microphone +priority = 20 + +[Option Input Source:Microphone] +name = input-microphone +priority = 20 + +[Option Input Source:Front Mic] +name = input-microphone +priority = 19 + +[Option Input Source:Front Microphone] +name = input-microphone +priority = 19 + +[Option Input Source:Line] +name = input-linein +priority = 18 + +[Option Input Source:Line-In] +name = input-linein +priority = 18 + +[Option Input Source:Line In] +name = input-linein +priority = 18 + +;;; ' 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:Mic] +name = input-microphone + +[Option Capture Source:Microphone] +name = input-microphone + +[Option Capture Source:Int Mic] +name = input-microphone-internal + +[Option Capture Source:Int DMic] +name = input-microphone-internal + +[Option Capture Source:Internal Mic] +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 Mic] +name = input-microphone + +[Option Capture Source:Front Microphone] +name = input-microphone + +[Option Capture Source:Rear Mic] +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] +name = input-linein + +[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 + +[Option Capture Source:Dock Mic] +name = input-docking-microphone + +;;; 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-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf new file mode 100644 index 00000000..1a172d4c --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf @@ -0,0 +1,53 @@ +# Path for mixers that have a Headphone slider + +[General] +priority = 90 + +[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 + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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..67031762 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf @@ -0,0 +1,54 @@ +# Intended for usage in laptops that have a seperate LFE speaker +# connected to the Master mono connector + +[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 + +[Element Headphone] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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..a23d9b79 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf @@ -0,0 +1,51 @@ +# Intended for usage on boards that have a seperate Mono output plug. + +[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 + +[Element Headphone] +switch = off +volume = off + +[Element Front] +switch = off +volume = off + +[Element Rear] +switch = off +volume = off + +[Element Sourround] +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.conf b/src/modules/alsa/mixer/paths/analog-output.conf new file mode 100644 index 00000000..15e703c4 --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf @@ -0,0 +1,62 @@ +# Intended for the 'default' output + +[General] +priority = 100 + +[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] +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.common b/src/modules/alsa/mixer/paths/analog-output.conf.common new file mode 100644 index 00000000..c38eccde --- /dev/null +++ b/src/modules/alsa/mixer/paths/analog-output.conf.common @@ -0,0 +1,40 @@ +# Common part of all paths + +# [General] +# priority = ... +# description = ... +# +# [Option ...:...] +# name = ... +# priority = ... +# +# [Element ...] +# required = ignore | switch | volume | enumeration | any +# required-absent = ignore | switch | volume +# +# switch = ignore | mute | off | on | select +# volume = ignore | merge | off | zero +# enumeration = ignore | select +# +# direction = playback | capture +# direction-try-other = no | yes +# +# override-map.1 = ... +# override-map.2 = ... + +[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 = 0 + +[Option External Amplifier:off] +name = output-amplifier-off +priority = 10 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..bbe53410 --- /dev/null +++ b/src/modules/alsa/mixer/profile-sets/default.conf @@ -0,0 +1,105 @@ +# Profile definitions for PulseAudio's ALSA backend +# +# [Mapping id] +# device-strings = ... +# channel-map = ... +# description = ... +# paths-input = ... +# paths-output = ... +# element-input = ... +# element-output = ... +# priority = ... +# direction = any | input | output +# +# [Profile id] +# input-mappings = ... +# output-mappings = ... +# description = ... +# priority = ... +# skip-probe = no | yes + +[General] +auto-profiles = yes + +[Mapping analog-mono] +device-strings = hw:%f +channel-map = mono +paths-output = analog-output analog-output-headphones analog-output-mono analog-output-lfe-on-mono +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 analog-output-headphones analog-output-mono analog-output-lfe-on-mono +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 + +[Mapping analog-surround-40] +device-strings = surround40:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-output = analog-output 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-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-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-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-lfe-on-mono +priority = 7 +direction = output + +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +priority = 5 + +[Mapping iec958-surround-40] +device-strings = iec958:%f +channel-map = front-left,front-right,rear-left,rear-right +priority = 1 + +[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 + +#[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/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..e8a7f206 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" @@ -92,81 +96,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) { +static void add_profiles(struct userdata *u, pa_hashmap *h) { +    pa_alsa_profile *ap; +    void *state; -    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; -    } - -    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_assert(u); +    pa_assert(h); -    pa_log_info("Found profile '%s'", t); +    PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) { +        struct profile_data *d; +        pa_card_profile *cp; +        pa_alsa_mapping *m; +        uint32_t idx; -    p = pa_card_profile_new(n, t, sizeof(struct profile_data)); +        cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); +        cp->priority = ap->priority; -    pa_xfree(t); -    pa_xfree(n); +        if (ap->output_mappings) { +            cp->n_sinks = pa_idxset_size(ap->output_mappings); -    p->priority = -        (sink ? sink->priority : 0) * 100 + -        (source ? source->priority : 0) + -        bonus; - -    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 +152,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 +160,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 +171,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 (outputs) { -                if (u->source) -                    pa_source_move_all_finish(u->source, outputs, FALSE); -                else -                    pa_source_move_all_fail(outputs); +            if (!am->source) +                am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am); + +            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,9 +283,9 @@ 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_alsa_redirect_errors_inc();      snd_config_update_free_global(); @@ -300,13 +297,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,16 +308,36 @@ 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 + +    u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); +    pa_xfree(fn); + +    if (!u->profile_set) +        goto fail; -    if (!pa_in_system_mode()) -        if (!(reserve = pa_reserve_wrapper_get(m->core, rname))) -            goto fail; +    pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec);      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); @@ -332,11 +346,8 @@ int pa__init(pa_module *m) {          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."); @@ -379,13 +390,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); + +    PA_IDXSET_FOREACH(sink, u->card->sinks, idx) +        n += pa_sink_linked_by(sink); -    return -        (u->sink ? pa_sink_linked_by(u->sink) : 0) + -        (u->source ? pa_source_linked_by(u->source) : 0); +    PA_IDXSET_FOREACH(source, u->card->sources, idx) +        n += pa_source_linked_by(source); + +    return n;  }  void pa__done(pa_module*m) { @@ -396,11 +416,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->card && u->card->sources) { +        pa_source *s; -    if (u->source) -        pa_alsa_source_free(u->source); +        while ((s = pa_idxset_steal_first(u->card->sources, NULL))) +            pa_alsa_source_free(s); +    }      if (u->card)          pa_card_free(u->card); @@ -408,6 +436,9 @@ 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); diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index dbec00d4..6bcd0b80 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1443,12 +1443,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us              if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) {                  pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); -                pa_sink_volume_changed(u->sink, &v); +                pa_sink_volume_changed(u->sink, &v, TRUE);              } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {                  pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); -                pa_source_volume_changed(u->source, &v); +                pa_source_volume_changed(u->source, &v, TRUE);              }          }      } @@ -1622,6 +1622,8 @@ static int add_sink(struct userdata *u) {          data.module = u->module;          pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);          pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); +        if (u->profile == PROFILE_HSP) +            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");          data.card = u->card;          data.name = get_name("sink", u->modargs, u->address, &b);          data.namereg_fail = b; @@ -1680,6 +1682,8 @@ static int add_source(struct userdata *u) {          data.module = u->module;          pa_source_new_data_set_sample_spec(&data, &u->sample_spec);          pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp"); +        if (u->profile == PROFILE_HSP) +            pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");          data.card = u->card;          data.name = get_name("source", u->modargs, u->address, &b);          data.namereg_fail = b; @@ -1916,7 +1920,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      if (!(device = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) {          pa_log_error("Failed to get device object."); -        return -1; +        return -PA_ERR_IO;      }      /* The state signal is sent by bluez, so it is racy to check @@ -1926,15 +1930,15 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {         module will be unloaded. */      if (device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) {          pa_log_warn("HSP is not connected, refused to switch profile"); -        return -1; +        return -PA_ERR_IO;      }      else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) {          pa_log_warn("A2DP is not connected, refused to switch profile"); -        return -1; +        return -PA_ERR_IO;      }      if (u->sink) { -        inputs = pa_sink_move_all_start(u->sink); +        inputs = pa_sink_move_all_start(u->sink, NULL);  #ifdef NOKIA          if (!USE_SCO_OVER_PCM(u))  #endif @@ -1942,7 +1946,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {      }      if (u->source) { -        outputs = pa_source_move_all_start(u->source); +        outputs = pa_source_move_all_start(u->source, NULL);  #ifdef NOKIA          if (!USE_SCO_OVER_PCM(u))  #endif diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c index 9993c8dc..c4cfd733 100644 --- a/src/modules/bluetooth/module-bluetooth-proximity.c +++ b/src/modules/bluetooth/module-bluetooth-proximity.c @@ -109,7 +109,7 @@ static void update_volume(struct userdata *u) {          }          pa_log_info("Found %u BT devices, unmuting.", u->n_found); -        pa_sink_set_mute(s, FALSE); +        pa_sink_set_mute(s, FALSE, FALSE);      } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {          pa_sink *s; @@ -122,7 +122,7 @@ static void update_volume(struct userdata *u) {          }          pa_log_info("No BT devices found, muting."); -        pa_sink_set_mute(s, TRUE); +        pa_sink_set_mute(s, TRUE, FALSE);      } else          pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 15af74a6..21f4a8f1 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -27,6 +27,7 @@  #endif  #include <pulse/xmalloc.h> +#include <pulse/i18n.h>  #include <pulsecore/core-error.h>  #include <pulsecore/namereg.h> @@ -45,20 +46,20 @@  #include "ladspa.h"  PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Virtual LADSPA sink"); +PA_MODULE_DESCRIPTION(_("Virtual LADSPA sink"));  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( -        "sink_name=<name for the sink> " -        "sink_properties=<properties for the sink> " -        "master=<name of sink to remap> " -        "format=<sample format> " -        "rate=<sample rate> " -        "channels=<number of channels> " -        "channel_map=<channel map> " -        "plugin=<ladspa plugin name> " -        "label=<ladspa plugin label> " -        "control=<comma seperated list of input control values>"); +        _("sink_name=<name for the sink> " +          "sink_properties=<properties for the sink> " +          "master=<name of sink to filter> " +          "format=<sample format> " +          "rate=<sample rate> " +          "channels=<number of channels> " +          "channel_map=<channel map> " +          "plugin=<ladspa plugin name> " +          "label=<ladspa plugin label> " +          "control=<comma seperated list of input control values>"));  #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index a1a8726f..06efeb8f 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -112,7 +112,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                  volchange = RESET;              if (volchange == INVALID) -                pa_log_warn("Recieved unknown IR code '%s'", name); +                pa_log_warn("Received unknown IR code '%s'", name);              else {                  pa_sink *s; @@ -133,7 +133,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                                      cv.values[i] = PA_VOLUME_MAX;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case DOWN: @@ -144,20 +144,20 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                                      cv.values[i] = PA_VOLUME_MUTED;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case MUTE: -                            pa_sink_set_mute(s, TRUE); +                            pa_sink_set_mute(s, TRUE, TRUE);                              break;                          case RESET: -                            pa_sink_set_mute(s, FALSE); +                            pa_sink_set_mute(s, FALSE, TRUE);                              break;                          case MUTE_TOGGLE: -                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); +                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);                              break;                          case INVALID: diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index d8b9c79e..b30fae51 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -115,7 +115,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                                      cv.values[i] = PA_VOLUME_MAX;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case DOWN: @@ -126,12 +126,12 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event                                      cv.values[i] = PA_VOLUME_MUTED;                              } -                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE); +                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);                              break;                          case MUTE_TOGGLE: -                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); +                            pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE), TRUE);                              break;                          case INVALID: diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 6f525da3..c493d9bb 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -730,7 +730,7 @@ static void command_request(pa_pdispatch *pd, uint32_t command,  uint32_t tag, p      }      if (channel != u->channel) { -        pa_log("Recieved data for invalid channel"); +        pa_log("Received data for invalid channel");          goto fail;      } @@ -1157,10 +1157,10 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag          pa_cvolume_equal(&volume, &u->sink->virtual_volume))          return; -    pa_sink_volume_changed(u->sink, &volume); +    pa_sink_volume_changed(u->sink, &volume, FALSE);      if (u->version >= 11) -        pa_sink_mute_changed(u->sink, mute); +        pa_sink_mute_changed(u->sink, mute, FALSE);      return; @@ -1675,7 +1675,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o      pa_assert(u);      if (channel != u->channel) { -        pa_log("Recieved memory block on bad channel."); +        pa_log("Received memory block on bad channel.");          pa_module_unload_request(u->module, TRUE);          return;      } diff --git a/src/modules/module-raop-discover.c b/src/modules/raop/module-raop-discover.c index eaeb77fc..eaeb77fc 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/raop/module-raop-discover.c diff --git a/src/modules/module-raop-sink.c b/src/modules/raop/module-raop-sink.c index 052a3a5e..54de42c2 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/raop/module-raop-sink.c @@ -583,6 +583,7 @@ int pa__init(pa_module*m) {      pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));      pa_sink_new_data_set_sample_spec(&data, &ss);      pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); +    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "music");      if ((desc = pa_modargs_get_value(ma, "description", NULL)))          pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, desc);      else diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index c61d2d8b..b86923fb 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -62,7 +62,7 @@  #include "sap.h"  PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP"); +PA_MODULE_DESCRIPTION("Receive data from a network via RTP/SAP/SDP");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(FALSE);  PA_MODULE_USAGE( diff --git a/src/modules/udev-util.c b/src/modules/udev-util.c index 144ad797..cc824465 100644 --- a/src/modules/udev-util.c +++ b/src/modules/udev-util.c @@ -58,7 +58,7 @@ static int read_id(struct udev_device *d, const char *n) {      return u;  } -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) { +int pa_udev_get_info(int card_idx, pa_proplist *p) {      int r = -1;      struct udev *udev;      struct udev_device *card = NULL; @@ -66,7 +66,6 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) {      const char *v;      int id; -    pa_assert(core);      pa_assert(p);      pa_assert(card_idx >= 0); @@ -84,6 +83,19 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) {          goto finish;      } +    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) +        if (((v = udev_device_get_property_value(card, "ID_PATH")) && *v) || +            (v = udev_device_get_devpath(card))) +            pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); + +    if (!pa_proplist_contains(p, "sysfs.path")) +        if ((v = udev_device_get_devpath(card))) +            pa_proplist_sets(p, "sysfs.path", v); + +    if (!pa_proplist_contains(p, "udev.id")) +        if ((v = udev_device_get_property_value(card, "ID_ID")) && *v) +            pa_proplist_sets(p, "udev.id", v); +      if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS))          if ((v = udev_device_get_property_value(card, "ID_BUS")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_BUS, v); @@ -114,15 +126,15 @@ int pa_udev_get_info(pa_core *core, pa_proplist *p, int card_idx) {          if ((v = udev_device_get_property_value(card, "ID_SERIAL")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_SERIAL, v); +    if (!pa_proplist_contains(p, PA_PROP_DEVICE_CLASS)) +        if ((v = udev_device_get_property_value(card, "SOUND_CLASS")) && *v) +            pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, v); +      if (!pa_proplist_contains(p, PA_PROP_DEVICE_FORM_FACTOR))          if ((v = udev_device_get_property_value(card, "SOUND_FORM_FACTOR")) && *v)              pa_proplist_sets(p, PA_PROP_DEVICE_FORM_FACTOR, v); -    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH)) -        if ((v = udev_device_get_devpath(card))) -            pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v); - -    /* This is normaly not set by th udev rules but may be useful to +    /* This is normaly not set by the udev rules but may be useful to       * allow administrators to overwrite the device description.*/      if (!pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))          if ((v = udev_device_get_property_value(card, "SOUND_DESCRIPTION")) && *v) @@ -140,3 +152,40 @@ finish:      return r;  } + +char* pa_udev_get_property(int card_idx, const char *name) { +    struct udev *udev; +    struct udev_device *card = NULL; +    char *t, *r = NULL; +    const char *v; + +    pa_assert(card_idx >= 0); +    pa_assert(name); + +    if (!(udev = udev_new())) { +        pa_log_error("Failed to allocate udev context."); +        goto finish; +    } + +    t = pa_sprintf_malloc("%s/class/sound/card%i", udev_get_sys_path(udev), card_idx); +    card = udev_device_new_from_syspath(udev, t); +    pa_xfree(t); + +    if (!card) { +        pa_log_error("Failed to get card object."); +        goto finish; +    } + +    if ((v = udev_device_get_property_value(card, name)) && *v) +        r = pa_xstrdup(v); + +finish: + +    if (card) +        udev_device_unref(card); + +    if (udev) +        udev_unref(udev); + +    return r; +} diff --git a/src/modules/udev-util.h b/src/modules/udev-util.h index 5120abdd..8523bc4d 100644 --- a/src/modules/udev-util.h +++ b/src/modules/udev-util.h @@ -25,6 +25,7 @@  #include <pulsecore/core.h> -int pa_udev_get_info(pa_core *core, pa_proplist *p, int card); +int pa_udev_get_info(int card_idx, pa_proplist *p); +char* pa_udev_get_property(int card_idx, const char *name);  #endif | 
