summaryrefslogtreecommitdiffstats
path: root/src/modules/alsa
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/alsa')
-rw-r--r--src/modules/alsa/alsa-mixer.c4194
-rw-r--r--src/modules/alsa/alsa-mixer.h328
-rw-r--r--src/modules/alsa/alsa-sink.c2246
-rw-r--r--src/modules/alsa/alsa-sink.h36
-rw-r--r--src/modules/alsa/alsa-source.c2003
-rw-r--r--src/modules/alsa/alsa-source.h36
-rw-r--r--src/modules/alsa/alsa-util.c1401
-rw-r--r--src/modules/alsa/alsa-util.h143
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-aux.conf66
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-dock-mic.conf81
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-fm.conf66
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-front-mic.conf81
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-internal-mic.conf111
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-linein.conf93
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic-line.conf67
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic.conf104
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-mic.conf.common54
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-rear-mic.conf81
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-tvtuner.conf66
-rw-r--r--src/modules/alsa/mixer/paths/analog-input-video.conf65
-rw-r--r--src/modules/alsa/mixer/paths/analog-input.conf83
-rw-r--r--src/modules/alsa/mixer/paths/analog-input.conf.common290
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf99
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones-2.conf87
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-headphones.conf87
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf89
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-mono.conf86
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-speaker.conf99
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf96
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf.common147
-rw-r--r--src/modules/alsa/mixer/paths/iec958-stereo-output.conf19
-rw-r--r--src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules40
-rw-r--r--src/modules/alsa/mixer/profile-sets/default.conf180
-rw-r--r--src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf85
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf91
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf162
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf85
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf131
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf92
-rw-r--r--src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf81
-rw-r--r--src/modules/alsa/mixer/profile-sets/usb-headset.conf35
-rw-r--r--src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0150
-rw-r--r--src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x24
-rw-r--r--src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3135
-rw-r--r--src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI4
-rw-r--r--src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD198162
-rw-r--r--src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A113
-rw-r--r--src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A128
-rw-r--r--src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer27
-rw-r--r--src/modules/alsa/mixer/samples/USB Audio--USB Mixer37
-rw-r--r--src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer5
-rw-r--r--src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888211
-rw-r--r--src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+160
-rw-r--r--src/modules/alsa/module-alsa-card.c479
-rw-r--r--src/modules/alsa/module-alsa-sink.c136
-rw-r--r--src/modules/alsa/module-alsa-source.c143
56 files changed, 15300 insertions, 0 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
new file mode 100644
index 00000000..348f037f
--- /dev/null
+++ b/src/modules/alsa/alsa-mixer.c
@@ -0,0 +1,4194 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2009 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <asoundlib.h>
+#include <math.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <pulse/mainloop-api.h>
+#include <pulse/sample.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
+#include <pulse/utf8.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/strbuf.h>
+
+#include "alsa-mixer.h"
+#include "alsa-util.h"
+
+struct description_map {
+ const char *name;
+ const char *description;
+};
+
+static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) {
+ unsigned i;
+
+ for (i = 0; i < n; i++)
+ if (pa_streq(dm[i].name, name))
+ return _(dm[i].description);
+
+ return NULL;
+}
+
+struct pa_alsa_fdlist {
+ unsigned num_fds;
+ struct pollfd *fds;
+ /* This is a temporary buffer used to avoid lots of mallocs */
+ struct pollfd *work_fds;
+
+ snd_mixer_t *mixer;
+
+ pa_mainloop_api *m;
+ pa_defer_event *defer;
+ pa_io_event **ios;
+
+ pa_bool_t polled;
+
+ void (*cb)(void *userdata);
+ void *userdata;
+};
+
+static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+
+ struct pa_alsa_fdlist *fdl = userdata;
+ int err;
+ unsigned i;
+ unsigned short revents;
+
+ pa_assert(a);
+ pa_assert(fdl);
+ pa_assert(fdl->mixer);
+ pa_assert(fdl->fds);
+ pa_assert(fdl->work_fds);
+
+ if (fdl->polled)
+ return;
+
+ fdl->polled = TRUE;
+
+ memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
+
+ for (i = 0; i < fdl->num_fds; i++) {
+ if (e == fdl->ios[i]) {
+ if (events & PA_IO_EVENT_INPUT)
+ fdl->work_fds[i].revents |= POLLIN;
+ if (events & PA_IO_EVENT_OUTPUT)
+ fdl->work_fds[i].revents |= POLLOUT;
+ if (events & PA_IO_EVENT_ERROR)
+ fdl->work_fds[i].revents |= POLLERR;
+ if (events & PA_IO_EVENT_HANGUP)
+ fdl->work_fds[i].revents |= POLLHUP;
+ break;
+ }
+ }
+
+ pa_assert(i != fdl->num_fds);
+
+ if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
+ pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
+ return;
+ }
+
+ a->defer_enable(fdl->defer, 1);
+
+ if (revents)
+ snd_mixer_handle_events(fdl->mixer);
+}
+
+static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
+ struct pa_alsa_fdlist *fdl = userdata;
+ unsigned num_fds, i;
+ int err, n;
+ struct pollfd *temp;
+
+ pa_assert(a);
+ pa_assert(fdl);
+ pa_assert(fdl->mixer);
+
+ a->defer_enable(fdl->defer, 0);
+
+ if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) {
+ pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
+ return;
+ }
+ num_fds = (unsigned) n;
+
+ if (num_fds != fdl->num_fds) {
+ if (fdl->fds)
+ pa_xfree(fdl->fds);
+ if (fdl->work_fds)
+ pa_xfree(fdl->work_fds);
+ fdl->fds = pa_xnew0(struct pollfd, num_fds);
+ fdl->work_fds = pa_xnew(struct pollfd, num_fds);
+ }
+
+ memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
+
+ if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
+ pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
+ return;
+ }
+
+ fdl->polled = FALSE;
+
+ if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0)
+ return;
+
+ if (fdl->ios) {
+ for (i = 0; i < fdl->num_fds; i++)
+ a->io_free(fdl->ios[i]);
+
+ if (num_fds != fdl->num_fds) {
+ pa_xfree(fdl->ios);
+ fdl->ios = NULL;
+ }
+ }
+
+ if (!fdl->ios)
+ fdl->ios = pa_xnew(pa_io_event*, num_fds);
+
+ /* Swap pointers */
+ temp = fdl->work_fds;
+ fdl->work_fds = fdl->fds;
+ fdl->fds = temp;
+
+ fdl->num_fds = num_fds;
+
+ for (i = 0;i < num_fds;i++)
+ fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
+ ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
+ ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
+ io_cb, fdl);
+}
+
+struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
+ struct pa_alsa_fdlist *fdl;
+
+ fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
+
+ return fdl;
+}
+
+void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
+ pa_assert(fdl);
+
+ if (fdl->defer) {
+ pa_assert(fdl->m);
+ fdl->m->defer_free(fdl->defer);
+ }
+
+ if (fdl->ios) {
+ unsigned i;
+ pa_assert(fdl->m);
+ for (i = 0; i < fdl->num_fds; i++)
+ fdl->m->io_free(fdl->ios[i]);
+ pa_xfree(fdl->ios);
+ }
+
+ if (fdl->fds)
+ pa_xfree(fdl->fds);
+ if (fdl->work_fds)
+ pa_xfree(fdl->work_fds);
+
+ pa_xfree(fdl);
+}
+
+int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) {
+ pa_assert(fdl);
+ pa_assert(mixer_handle);
+ pa_assert(m);
+ pa_assert(!fdl->m);
+
+ fdl->mixer = mixer_handle;
+ fdl->m = m;
+ fdl->defer = m->defer_new(m, defer_cb, fdl);
+
+ return 0;
+}
+
+struct pa_alsa_mixer_pdata {
+ pa_rtpoll *rtpoll;
+ pa_rtpoll_item *poll_item;
+ snd_mixer_t *mixer;
+};
+
+
+struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) {
+ struct pa_alsa_mixer_pdata *pd;
+
+ pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1);
+
+ return pd;
+}
+
+void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) {
+ pa_assert(pd);
+
+ if (pd->poll_item) {
+ pa_rtpoll_item_free(pd->poll_item);
+ }
+
+ pa_xfree(pd);
+}
+
+static int rtpoll_work_cb(pa_rtpoll_item *i) {
+ struct pa_alsa_mixer_pdata *pd;
+ struct pollfd *p;
+ unsigned n_fds;
+ unsigned short revents = 0;
+ int err;
+
+ pd = pa_rtpoll_item_get_userdata(i);
+ pa_assert_fp(pd);
+ pa_assert_fp(i == pd->poll_item);
+
+ p = pa_rtpoll_item_get_pollfd(i, &n_fds);
+
+ if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) {
+ pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
+ pa_rtpoll_item_free(i);
+ return -1;
+ }
+
+ if (revents) {
+ snd_mixer_handle_events(pd->mixer);
+ pa_rtpoll_item_free(i);
+ pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll);
+ }
+
+ return 0;
+}
+
+int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) {
+ pa_rtpoll_item *i;
+ struct pollfd *p;
+ int err, n;
+
+ pa_assert(pd);
+ pa_assert(mixer);
+ pa_assert(rtp);
+
+ if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) {
+ pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
+ return -1;
+ }
+
+ i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n);
+
+ p = pa_rtpoll_item_get_pollfd(i, NULL);
+
+ memset(p, 0, sizeof(struct pollfd) * n);
+
+ if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) {
+ pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
+ pa_rtpoll_item_free(i);
+ return -1;
+ }
+
+ pd->rtpoll = rtp;
+ pd->poll_item = i;
+ pd->mixer = mixer;
+
+ pa_rtpoll_item_set_userdata(i, pd);
+ pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
+
+ return 0;
+}
+
+static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
+ int err;
+
+ pa_assert(mixer);
+ pa_assert(dev);
+
+ if ((err = snd_mixer_attach(mixer, dev)) < 0) {
+ pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
+ pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_load(mixer)) < 0) {
+ pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+
+ pa_log_info("Successfully attached to mixer '%s'", dev);
+ return 0;
+}
+
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
+ int err;
+ snd_mixer_t *m;
+ const char *dev;
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ pa_assert(pcm);
+
+ if ((err = snd_mixer_open(&m, 0)) < 0) {
+ pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
+ return NULL;
+ }
+
+ /* First, try by name */
+ if ((dev = snd_pcm_name(pcm)))
+ if (prepare_mixer(m, dev) >= 0) {
+ if (ctl_device)
+ *ctl_device = pa_xstrdup(dev);
+
+ return m;
+ }
+
+ /* Then, try by card index */
+ if (snd_pcm_info(pcm, info) >= 0) {
+ char *md;
+ int card_idx;
+
+ if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
+
+ md = pa_sprintf_malloc("hw:%i", card_idx);
+
+ if (!dev || !pa_streq(dev, md))
+ if (prepare_mixer(m, md) >= 0) {
+
+ if (ctl_device)
+ *ctl_device = md;
+ else
+ pa_xfree(md);
+
+ return m;
+ }
+
+ pa_xfree(md);
+ }
+ }
+
+ snd_mixer_close(m);
+ return NULL;
+}
+
+static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
+ [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
+
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER,
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT,
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER,
+ [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT,
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT,
+
+ [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER,
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT,
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT,
+
+ [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
+};
+
+static void setting_free(pa_alsa_setting *s) {
+ pa_assert(s);
+
+ if (s->options)
+ pa_idxset_free(s->options, NULL, NULL);
+
+ pa_xfree(s->name);
+ pa_xfree(s->description);
+ pa_xfree(s);
+}
+
+static void option_free(pa_alsa_option *o) {
+ pa_assert(o);
+
+ pa_xfree(o->alsa_name);
+ pa_xfree(o->name);
+ pa_xfree(o->description);
+ pa_xfree(o);
+}
+
+static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
+ pa_assert(db_fix);
+
+ pa_xfree(db_fix->name);
+ pa_xfree(db_fix->db_values);
+
+ pa_xfree(db_fix);
+}
+
+static void element_free(pa_alsa_element *e) {
+ pa_alsa_option *o;
+ pa_assert(e);
+
+ while ((o = e->options)) {
+ PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
+ option_free(o);
+ }
+
+ if (e->db_fix)
+ decibel_fix_free(e->db_fix);
+
+ pa_xfree(e->alsa_name);
+ pa_xfree(e);
+}
+
+void pa_alsa_path_free(pa_alsa_path *p) {
+ pa_alsa_element *e;
+ pa_alsa_setting *s;
+
+ pa_assert(p);
+
+ while ((e = p->elements)) {
+ PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
+ element_free(e);
+ }
+
+ while ((s = p->settings)) {
+ PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s);
+ setting_free(s);
+ }
+
+ pa_xfree(p->name);
+ pa_xfree(p->description);
+ pa_xfree(p);
+}
+
+void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
+ pa_alsa_path *p;
+ pa_assert(ps);
+
+ while ((p = ps->paths)) {
+ PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
+ pa_alsa_path_free(p);
+ }
+
+ pa_xfree(ps);
+}
+
+static long to_alsa_dB(pa_volume_t v) {
+ return (long) (pa_sw_volume_to_dB(v) * 100.0);
+}
+
+static pa_volume_t from_alsa_dB(long v) {
+ return pa_sw_volume_from_dB((double) v / 100.0);
+}
+
+static long to_alsa_volume(pa_volume_t v, long min, long max) {
+ long w;
+
+ w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min;
+ return PA_CLAMP_UNLIKELY(w, min, max);
+}
+
+static pa_volume_t from_alsa_volume(long v, long min, long max) {
+ return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
+}
+
+#define SELEM_INIT(sid, name) \
+ do { \
+ snd_mixer_selem_id_alloca(&(sid)); \
+ snd_mixer_selem_id_set_name((sid), (name)); \
+ snd_mixer_selem_id_set_index((sid), 0); \
+ } while(FALSE)
+
+static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_elem_t *me;
+ snd_mixer_selem_channel_id_t c;
+ pa_channel_position_mask_t mask = 0;
+ unsigned k;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(cm);
+ pa_assert(v);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ pa_cvolume_mute(v, cm->channels);
+
+ /* We take the highest volume of all channels that match */
+
+ for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
+ int r;
+ pa_volume_t f;
+
+ if (e->has_dB) {
+ long value = 0;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ if (snd_mixer_selem_has_playback_channel(me, c)) {
+ if (e->db_fix) {
+ if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) {
+ /* If the channel volume is outside the limits set
+ * by the dB fix, we clamp the hw volume to be
+ * within the limits. */
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+ pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+ pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ }
+
+ /* Volume step -> dB value conversion. */
+ value = e->db_fix->db_values[value - e->db_fix->min_step];
+ }
+ } else
+ r = snd_mixer_selem_get_playback_dB(me, c, &value);
+ } else
+ r = -1;
+ } else {
+ if (snd_mixer_selem_has_capture_channel(me, c)) {
+ if (e->db_fix) {
+ if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) {
+ /* If the channel volume is outside the limits set
+ * by the dB fix, we clamp the hw volume to be
+ * within the limits. */
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+ pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+ pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ }
+
+ /* Volume step -> dB value conversion. */
+ value = e->db_fix->db_values[value - e->db_fix->min_step];
+ }
+ } else
+ r = snd_mixer_selem_get_capture_dB(me, c, &value);
+ } else
+ r = -1;
+ }
+
+ if (r < 0)
+ continue;
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
+#endif
+
+ f = from_alsa_dB(value);
+
+ } else {
+ long value = 0;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ if (snd_mixer_selem_has_playback_channel(me, c))
+ r = snd_mixer_selem_get_playback_volume(me, c, &value);
+ else
+ r = -1;
+ } else {
+ if (snd_mixer_selem_has_capture_channel(me, c))
+ r = snd_mixer_selem_get_capture_volume(me, c, &value);
+ else
+ r = -1;
+ }
+
+ if (r < 0)
+ continue;
+
+ f = from_alsa_volume(value, e->min_volume, e->max_volume);
+ }
+
+ for (k = 0; k < cm->channels; k++)
+ if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
+ if (v->values[k] < f)
+ v->values[k] = f;
+
+ mask |= e->masks[c][e->n_channels-1];
+ }
+
+ for (k = 0; k < cm->channels; k++)
+ if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
+ v->values[k] = PA_VOLUME_NORM;
+
+ return 0;
+}
+
+int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
+ pa_alsa_element *e;
+
+ pa_assert(m);
+ pa_assert(p);
+ pa_assert(cm);
+ pa_assert(v);
+
+ if (!p->has_volume)
+ return -1;
+
+ pa_cvolume_reset(v, cm->channels);
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ pa_cvolume ev;
+
+ if (e->volume_use != PA_ALSA_VOLUME_MERGE)
+ continue;
+
+ pa_assert(!p->has_dB || e->has_dB);
+
+ if (element_get_volume(e, m, cm, &ev) < 0)
+ return -1;
+
+ /* If we have no dB information all we can do is take the first element and leave */
+ if (!p->has_dB) {
+ *v = ev;
+ return 0;
+ }
+
+ pa_sw_cvolume_multiply(v, v, &ev);
+ }
+
+ return 0;
+}
+
+static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) {
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_elem_t *me;
+ snd_mixer_selem_channel_id_t c;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(b);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ /* We return muted if at least one channel is muted */
+
+ for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
+ int r;
+ int value = 0;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ if (snd_mixer_selem_has_playback_channel(me, c))
+ r = snd_mixer_selem_get_playback_switch(me, c, &value);
+ else
+ r = -1;
+ } else {
+ if (snd_mixer_selem_has_capture_channel(me, c))
+ r = snd_mixer_selem_get_capture_switch(me, c, &value);
+ else
+ r = -1;
+ }
+
+ if (r < 0)
+ continue;
+
+ if (!value) {
+ *b = FALSE;
+ return 0;
+ }
+ }
+
+ *b = TRUE;
+ return 0;
+}
+
+int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
+ pa_alsa_element *e;
+
+ pa_assert(m);
+ pa_assert(p);
+ pa_assert(muted);
+
+ if (!p->has_mute)
+ return -1;
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ pa_bool_t b;
+
+ if (e->switch_use != PA_ALSA_SWITCH_MUTE)
+ continue;
+
+ if (element_get_switch(e, m, &b) < 0)
+ return -1;
+
+ if (!b) {
+ *muted = TRUE;
+ return 0;
+ }
+ }
+
+ *muted = FALSE;
+ return 0;
+}
+
+/* Finds the closest item in db_fix->db_values and returns the corresponding
+ * step. *db_value is replaced with the value from the db_values table.
+ * Rounding is done based on the rounding parameter: -1 means rounding down and
+ * +1 means rounding up. */
+static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) {
+ unsigned i = 0;
+ unsigned max_i = 0;
+
+ pa_assert(db_fix);
+ pa_assert(db_value);
+ pa_assert(rounding != 0);
+
+ max_i = db_fix->max_step - db_fix->min_step;
+
+ if (rounding > 0) {
+ for (i = 0; i < max_i; i++) {
+ if (db_fix->db_values[i] >= *db_value)
+ break;
+ }
+ } else {
+ for (i = 0; i < max_i; i++) {
+ if (db_fix->db_values[i + 1] > *db_value)
+ break;
+ }
+ }
+
+ *db_value = db_fix->db_values[i];
+
+ return i + db_fix->min_step;
+}
+
+/* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument,
+ * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above".
+ * But even with accurate nearest dB volume step is not selected, so that is why we need
+ * this function. Returns 0 and nearest selectable volume in *value_dB on success or
+ * negative error code if fails. */
+static int element_get_nearest_alsa_dB(snd_mixer_elem_t *me, snd_mixer_selem_channel_id_t c, pa_alsa_direction_t d, long *value_dB) {
+
+ long alsa_val;
+ long value_high;
+ long value_low;
+ int r = -1;
+
+ pa_assert(me);
+ pa_assert(value_dB);
+
+ if (d == PA_ALSA_DIRECTION_OUTPUT) {
+ if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high);
+
+ if (r < 0)
+ return r;
+
+ if (value_high == *value_dB)
+ return r;
+
+ if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low);
+ } else {
+ if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high);
+
+ if (r < 0)
+ return r;
+
+ if (value_high == *value_dB)
+ return r;
+
+ if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low);
+ }
+
+ if (r < 0)
+ return r;
+
+ if (labs(value_high - *value_dB) < labs(value_low - *value_dB))
+ *value_dB = value_high;
+ else
+ *value_dB = value_low;
+
+ return r;
+}
+
+static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) {
+
+ snd_mixer_selem_id_t *sid;
+ pa_cvolume rv;
+ snd_mixer_elem_t *me;
+ snd_mixer_selem_channel_id_t c;
+ pa_channel_position_mask_t mask = 0;
+ unsigned k;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(cm);
+ pa_assert(v);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ pa_cvolume_mute(&rv, cm->channels);
+
+ for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
+ int r;
+ pa_volume_t f = PA_VOLUME_MUTED;
+ pa_bool_t found = FALSE;
+
+ for (k = 0; k < cm->channels; k++)
+ if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) {
+ found = TRUE;
+ if (v->values[k] > f)
+ f = v->values[k];
+ }
+
+ if (!found) {
+ /* Hmm, so this channel does not exist in the volume
+ * struct, so let's bind it to the overall max of the
+ * volume. */
+ f = pa_cvolume_max(v);
+ }
+
+ if (e->has_dB) {
+ long value = to_alsa_dB(f);
+ int rounding;
+
+ if (e->volume_limit >= 0 && value > (e->max_dB * 100))
+ value = e->max_dB * 100;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ /* If we call set_playback_volume() without checking first
+ * if the channel is available, ALSA behaves very
+ * strangely and doesn't fail the call */
+ if (snd_mixer_selem_has_playback_channel(me, c)) {
+ rounding = +1;
+ if (e->db_fix) {
+ if (write_to_hw)
+ r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
+ else {
+ decibel_fix_get_step(e->db_fix, &value, rounding);
+ r = 0;
+ }
+
+ } else {
+ if (write_to_hw) {
+ if (sync_volume) {
+ if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0)
+ r = snd_mixer_selem_set_playback_dB(me, c, value, 0);
+ } else {
+ if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
+ r = snd_mixer_selem_get_playback_dB(me, c, &value);
+ }
+ } else {
+ long alsa_val;
+ if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
+ }
+ }
+ } else
+ r = -1;
+ } else {
+ if (snd_mixer_selem_has_capture_channel(me, c)) {
+ rounding = -1;
+ if (e->db_fix) {
+ if (write_to_hw)
+ r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
+ else {
+ decibel_fix_get_step(e->db_fix, &value, rounding);
+ r = 0;
+ }
+
+ } else {
+ if (write_to_hw) {
+ if (sync_volume) {
+ if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0)
+ r = snd_mixer_selem_set_capture_dB(me, c, value, 0);
+ } else {
+ if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
+ r = snd_mixer_selem_get_capture_dB(me, c, &value);
+ }
+ } else {
+ long alsa_val;
+ if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
+ }
+ }
+ } else
+ r = -1;
+ }
+
+ if (r < 0)
+ continue;
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
+#endif
+
+ f = from_alsa_dB(value);
+
+ } else {
+ long value;
+
+ value = to_alsa_volume(f, e->min_volume, e->max_volume);
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ if (snd_mixer_selem_has_playback_channel(me, c)) {
+ if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0)
+ r = snd_mixer_selem_get_playback_volume(me, c, &value);
+ } else
+ r = -1;
+ } else {
+ if (snd_mixer_selem_has_capture_channel(me, c)) {
+ if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0)
+ r = snd_mixer_selem_get_capture_volume(me, c, &value);
+ } else
+ r = -1;
+ }
+
+ if (r < 0)
+ continue;
+
+ f = from_alsa_volume(value, e->min_volume, e->max_volume);
+ }
+
+ for (k = 0; k < cm->channels; k++)
+ if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
+ if (rv.values[k] < f)
+ rv.values[k] = f;
+
+ mask |= e->masks[c][e->n_channels-1];
+ }
+
+ for (k = 0; k < cm->channels; k++)
+ if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
+ rv.values[k] = PA_VOLUME_NORM;
+
+ *v = rv;
+ return 0;
+}
+
+int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) {
+
+ pa_alsa_element *e;
+ pa_cvolume rv;
+
+ pa_assert(m);
+ pa_assert(p);
+ pa_assert(cm);
+ pa_assert(v);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
+
+ if (!p->has_volume)
+ return -1;
+
+ rv = *v; /* Remaining adjustment */
+ pa_cvolume_reset(v, cm->channels); /* Adjustment done */
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ pa_cvolume ev;
+
+ if (e->volume_use != PA_ALSA_VOLUME_MERGE)
+ continue;
+
+ pa_assert(!p->has_dB || e->has_dB);
+
+ ev = rv;
+ if (element_set_volume(e, m, cm, &ev, sync_volume, write_to_hw) < 0)
+ return -1;
+
+ if (!p->has_dB) {
+ *v = ev;
+ return 0;
+ }
+
+ pa_sw_cvolume_multiply(v, v, &ev);
+ pa_sw_cvolume_divide(&rv, &rv, &ev);
+ }
+
+ return 0;
+}
+
+static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) {
+ snd_mixer_elem_t *me;
+ snd_mixer_selem_id_t *sid;
+ int r;
+
+ pa_assert(m);
+ pa_assert(e);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_set_playback_switch_all(me, b);
+ else
+ r = snd_mixer_selem_set_capture_switch_all(me, b);
+
+ if (r < 0)
+ pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+
+ return r;
+}
+
+int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) {
+ pa_alsa_element *e;
+
+ pa_assert(m);
+ pa_assert(p);
+
+ if (!p->has_mute)
+ return -1;
+
+ PA_LLIST_FOREACH(e, p->elements) {
+
+ if (e->switch_use != PA_ALSA_SWITCH_MUTE)
+ continue;
+
+ if (element_set_switch(e, m, !muted) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this
+ * function sets all channels of the volume element to e->min_volume, 0 dB or
+ * e->constant_volume. */
+static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
+ snd_mixer_elem_t *me = NULL;
+ snd_mixer_selem_id_t *sid = NULL;
+ int r = 0;
+ long volume = -1;
+ pa_bool_t volume_set = FALSE;
+
+ pa_assert(m);
+ pa_assert(e);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ switch (e->volume_use) {
+ case PA_ALSA_VOLUME_OFF:
+ volume = e->min_volume;
+ volume_set = TRUE;
+ break;
+
+ case PA_ALSA_VOLUME_ZERO:
+ if (e->db_fix) {
+ long dB = 0;
+
+ volume = decibel_fix_get_step(e->db_fix, &dB, +1);
+ volume_set = TRUE;
+ }
+ break;
+
+ case PA_ALSA_VOLUME_CONSTANT:
+ volume = e->constant_volume;
+ volume_set = TRUE;
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ if (volume_set) {
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_set_playback_volume_all(me, volume);
+ else
+ r = snd_mixer_selem_set_capture_volume_all(me, volume);
+ } else {
+ pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO);
+ pa_assert(!e->db_fix);
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
+ else
+ r = snd_mixer_selem_set_capture_dB_all(me, 0, +1);
+ }
+
+ if (r < 0)
+ pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+
+ return r;
+}
+
+int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
+ pa_alsa_element *e;
+ int r = 0;
+
+ pa_assert(m);
+ pa_assert(p);
+
+ pa_log_debug("Activating path %s", p->name);
+ pa_alsa_path_dump(p);
+
+ PA_LLIST_FOREACH(e, p->elements) {
+
+ switch (e->switch_use) {
+ case PA_ALSA_SWITCH_OFF:
+ r = element_set_switch(e, m, FALSE);
+ break;
+
+ case PA_ALSA_SWITCH_ON:
+ r = element_set_switch(e, m, TRUE);
+ break;
+
+ case PA_ALSA_SWITCH_MUTE:
+ case PA_ALSA_SWITCH_IGNORE:
+ case PA_ALSA_SWITCH_SELECT:
+ r = 0;
+ break;
+ }
+
+ if (r < 0)
+ return -1;
+
+ switch (e->volume_use) {
+ case PA_ALSA_VOLUME_OFF:
+ case PA_ALSA_VOLUME_ZERO:
+ case PA_ALSA_VOLUME_CONSTANT:
+ r = element_set_constant_volume(e, m);
+ break;
+
+ case PA_ALSA_VOLUME_MERGE:
+ case PA_ALSA_VOLUME_IGNORE:
+ r = 0;
+ break;
+ }
+
+ if (r < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) {
+ pa_bool_t has_switch;
+ pa_bool_t has_enumeration;
+ pa_bool_t has_volume;
+
+ pa_assert(e);
+ pa_assert(me);
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ has_switch =
+ snd_mixer_selem_has_playback_switch(me) ||
+ (e->direction_try_other && snd_mixer_selem_has_capture_switch(me));
+ } else {
+ has_switch =
+ snd_mixer_selem_has_capture_switch(me) ||
+ (e->direction_try_other && snd_mixer_selem_has_playback_switch(me));
+ }
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ has_volume =
+ snd_mixer_selem_has_playback_volume(me) ||
+ (e->direction_try_other && snd_mixer_selem_has_capture_volume(me));
+ } else {
+ has_volume =
+ snd_mixer_selem_has_capture_volume(me) ||
+ (e->direction_try_other && snd_mixer_selem_has_playback_volume(me));
+ }
+
+ has_enumeration = snd_mixer_selem_is_enumerated(me);
+
+ if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) ||
+ (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) ||
+ (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration))
+ return -1;
+
+ if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration))
+ return -1;
+
+ if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) ||
+ (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) ||
+ (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration))
+ return -1;
+
+ if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration))
+ return -1;
+
+ if (e->required_any != PA_ALSA_REQUIRED_IGNORE) {
+ switch (e->required_any) {
+ case PA_ALSA_REQUIRED_VOLUME:
+ e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE);
+ break;
+ case PA_ALSA_REQUIRED_SWITCH:
+ e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE);
+ break;
+ case PA_ALSA_REQUIRED_ENUMERATION:
+ e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
+ break;
+ case PA_ALSA_REQUIRED_ANY:
+ e->path->req_any_present |=
+ (e->volume_use != PA_ALSA_VOLUME_IGNORE) ||
+ (e->switch_use != PA_ALSA_SWITCH_IGNORE) ||
+ (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+ }
+
+ if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
+ pa_alsa_option *o;
+ PA_LLIST_FOREACH(o, e->options) {
+ e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) &&
+ (o->alsa_idx >= 0);
+ if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0)
+ return -1;
+ if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_elem_t *me;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(e->path);
+
+ SELEM_INIT(sid, e->alsa_name);
+
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+
+ if (e->required != PA_ALSA_REQUIRED_IGNORE)
+ return -1;
+
+ e->switch_use = PA_ALSA_SWITCH_IGNORE;
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+ e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
+
+ return 0;
+ }
+
+ if (e->switch_use != PA_ALSA_SWITCH_IGNORE) {
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+
+ if (!snd_mixer_selem_has_playback_switch(me)) {
+ if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me))
+ e->direction = PA_ALSA_DIRECTION_INPUT;
+ else
+ e->switch_use = PA_ALSA_SWITCH_IGNORE;
+ }
+
+ } else {
+
+ if (!snd_mixer_selem_has_capture_switch(me)) {
+ if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me))
+ e->direction = PA_ALSA_DIRECTION_OUTPUT;
+ else
+ e->switch_use = PA_ALSA_SWITCH_IGNORE;
+ }
+ }
+
+ if (e->switch_use != PA_ALSA_SWITCH_IGNORE)
+ e->direction_try_other = FALSE;
+ }
+
+ if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
+
+ if (!snd_mixer_selem_has_playback_volume(me)) {
+ if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me))
+ e->direction = PA_ALSA_DIRECTION_INPUT;
+ else
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+ }
+
+ } else {
+
+ if (!snd_mixer_selem_has_capture_volume(me)) {
+ if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me))
+ e->direction = PA_ALSA_DIRECTION_OUTPUT;
+ else
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+ }
+ }
+
+ if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
+ long min_dB = 0, max_dB = 0;
+ int r;
+
+ e->direction_try_other = FALSE;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume);
+ else
+ r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
+
+ if (r < 0) {
+ pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+ return -1;
+ }
+
+ if (e->min_volume >= e->max_volume) {
+ pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", e->min_volume, e->max_volume);
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+
+ } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT &&
+ (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
+ pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
+ e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+
+ } else {
+ pa_bool_t is_mono;
+ pa_channel_position_t p;
+
+ if (e->db_fix &&
+ ((e->min_volume > e->db_fix->min_step) ||
+ (e->max_volume < e->db_fix->max_step))) {
+ pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
+ "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
+ e->db_fix->min_step, e->db_fix->max_step,
+ e->min_volume, e->max_volume);
+
+ decibel_fix_free(e->db_fix);
+ e->db_fix = NULL;
+ }
+
+ if (e->db_fix) {
+ e->has_dB = TRUE;
+ e->min_volume = e->db_fix->min_step;
+ e->max_volume = e->db_fix->max_step;
+ min_dB = e->db_fix->db_values[0];
+ max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step];
+ } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
+ else
+ e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
+
+ /* Check that the kernel driver returns consistent limits with
+ * both _get_*_dB_range() and _ask_*_vol_dB(). */
+ if (e->has_dB && !e->db_fix) {
+ long min_dB_checked = 0;
+ long max_dB_checked = 0;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked);
+ else
+ r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked);
+
+ if (r < 0) {
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
+ return -1;
+ }
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked);
+ else
+ r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked);
+
+ if (r < 0) {
+ pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
+ return -1;
+ }
+
+ if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
+ pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
+ "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
+ "%0.2f dB at level %li.",
+ e->alsa_name,
+ min_dB / 100.0, max_dB / 100.0,
+ min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
+ return -1;
+ }
+ }
+
+ if (e->has_dB) {
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
+ VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
+#endif
+
+ e->min_dB = ((double) min_dB) / 100.0;
+ e->max_dB = ((double) max_dB) / 100.0;
+
+ if (min_dB >= max_dB) {
+ pa_assert(!e->db_fix);
+ pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB);
+ e->has_dB = FALSE;
+ }
+ }
+
+ if (e->volume_limit >= 0) {
+ if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
+ pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
+ "%li-%li. The volume limit is ignored.",
+ e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
+
+ else {
+ e->max_volume = e->volume_limit;
+
+ if (e->has_dB) {
+ if (e->db_fix) {
+ e->db_fix->max_step = e->max_volume;
+ e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
+
+ } else {
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB);
+ else
+ r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB);
+
+ if (r < 0) {
+ pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+ e->has_dB = FALSE;
+ } else
+ e->max_dB = ((double) max_dB) / 100.0;
+ }
+ }
+ }
+ }
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
+ else
+ is_mono = snd_mixer_selem_is_capture_mono(me) > 0;
+
+ if (is_mono) {
+ e->n_channels = 1;
+
+ if (!e->override_map) {
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+ if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
+ continue;
+
+ e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0;
+ }
+
+ e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL;
+ }
+
+ e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1];
+ } else {
+ e->n_channels = 0;
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+
+ if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
+ continue;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
+ else
+ e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
+ }
+
+ if (e->n_channels <= 0) {
+ pa_log_warn("Volume element %s with no channels?", e->alsa_name);
+ return -1;
+ }
+
+ if (e->n_channels > 2) {
+ /* FIXME: In some places code like this is used:
+ *
+ * e->masks[alsa_channel_ids[p]][e->n_channels-1]
+ *
+ * The definition of e->masks is
+ *
+ * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2];
+ *
+ * Since the array size is fixed at 2, we obviously
+ * don't support elements with more than two
+ * channels... */
+ pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
+ return -1;
+ }
+
+ if (!e->override_map) {
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+ pa_bool_t has_channel;
+
+ if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
+ continue;
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
+ else
+ has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
+
+ e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0;
+ }
+ }
+
+ e->merged_mask = 0;
+ for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
+ if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
+ continue;
+
+ e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
+ }
+ }
+ }
+ }
+
+ }
+
+ if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
+ pa_alsa_option *o;
+
+ PA_LLIST_FOREACH(o, e->options)
+ o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0;
+ } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
+ int n;
+ pa_alsa_option *o;
+
+ if ((n = snd_mixer_selem_get_enum_items(me)) < 0) {
+ pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n));
+ return -1;
+ }
+
+ PA_LLIST_FOREACH(o, e->options) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ char buf[128];
+
+ if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0)
+ continue;
+
+ if (!pa_streq(buf, o->alsa_name))
+ continue;
+
+ o->alsa_idx = i;
+ }
+ }
+ }
+
+ if (check_required(e, me) < 0)
+ return -1;
+
+ return 0;
+}
+
+static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
+ pa_alsa_element *e;
+
+ pa_assert(p);
+ pa_assert(section);
+
+ if (prefixed) {
+ if (!pa_startswith(section, "Element "))
+ return NULL;
+
+ section += 8;
+ }
+
+ /* This is not an element section, but an enum section? */
+ if (strchr(section, ':'))
+ return NULL;
+
+ if (p->last_element && pa_streq(p->last_element->alsa_name, section))
+ return p->last_element;
+
+ PA_LLIST_FOREACH(e, p->elements)
+ if (pa_streq(e->alsa_name, section))
+ goto finish;
+
+ e = pa_xnew0(pa_alsa_element, 1);
+ e->path = p;
+ e->alsa_name = pa_xstrdup(section);
+ e->direction = p->direction;
+ e->volume_limit = -1;
+
+ PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
+
+finish:
+ p->last_element = e;
+ return e;
+}
+
+static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
+ char *en;
+ const char *on;
+ pa_alsa_option *o;
+ pa_alsa_element *e;
+
+ if (!pa_startswith(section, "Option "))
+ return NULL;
+
+ section += 7;
+
+ /* This is not an enum section, but an element section? */
+ if (!(on = strchr(section, ':')))
+ return NULL;
+
+ en = pa_xstrndup(section, on - section);
+ on++;
+
+ if (p->last_option &&
+ pa_streq(p->last_option->element->alsa_name, en) &&
+ pa_streq(p->last_option->alsa_name, on)) {
+ pa_xfree(en);
+ return p->last_option;
+ }
+
+ pa_assert_se(e = element_get(p, en, FALSE));
+ pa_xfree(en);
+
+ PA_LLIST_FOREACH(o, e->options)
+ if (pa_streq(o->alsa_name, on))
+ goto finish;
+
+ o = pa_xnew0(pa_alsa_option, 1);
+ o->element = e;
+ o->alsa_name = pa_xstrdup(on);
+ o->alsa_idx = -1;
+
+ if (p->last_option && p->last_option->element == e)
+ PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o);
+ else
+ PA_LLIST_PREPEND(pa_alsa_option, e->options, o);
+
+finish:
+ p->last_option = o;
+ return o;
+}
+
+static int element_parse_switch(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+
+ pa_assert(p);
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "ignore"))
+ e->switch_use = PA_ALSA_SWITCH_IGNORE;
+ else if (pa_streq(rvalue, "mute"))
+ e->switch_use = PA_ALSA_SWITCH_MUTE;
+ else if (pa_streq(rvalue, "off"))
+ e->switch_use = PA_ALSA_SWITCH_OFF;
+ else if (pa_streq(rvalue, "on"))
+ e->switch_use = PA_ALSA_SWITCH_ON;
+ else if (pa_streq(rvalue, "select"))
+ e->switch_use = PA_ALSA_SWITCH_SELECT;
+ else {
+ pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int element_parse_volume(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+
+ pa_assert(p);
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "ignore"))
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+ else if (pa_streq(rvalue, "merge"))
+ e->volume_use = PA_ALSA_VOLUME_MERGE;
+ else if (pa_streq(rvalue, "off"))
+ e->volume_use = PA_ALSA_VOLUME_OFF;
+ else if (pa_streq(rvalue, "zero"))
+ e->volume_use = PA_ALSA_VOLUME_ZERO;
+ else {
+ uint32_t constant;
+
+ if (pa_atou(rvalue, &constant) >= 0) {
+ e->volume_use = PA_ALSA_VOLUME_CONSTANT;
+ e->constant_volume = constant;
+ } else {
+ pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int element_parse_enumeration(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+
+ pa_assert(p);
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "ignore"))
+ e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
+ else if (pa_streq(rvalue, "select"))
+ e->enumeration_use = PA_ALSA_ENUMERATION_SELECT;
+ else {
+ pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int option_parse_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_option *o;
+ uint32_t prio;
+
+ pa_assert(p);
+
+ if (!(o = option_get(p, section))) {
+ pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_atou(rvalue, &prio) < 0) {
+ pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ o->priority = prio;
+ return 0;
+}
+
+static int option_parse_name(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_option *o;
+
+ pa_assert(p);
+
+ if (!(o = option_get(p, section))) {
+ pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ pa_xfree(o->name);
+ o->name = pa_xstrdup(rvalue);
+
+ return 0;
+}
+
+static int element_parse_required(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+ pa_alsa_option *o;
+ pa_alsa_required_t req;
+
+ pa_assert(p);
+
+ e = element_get(p, section, TRUE);
+ o = option_get(p, section);
+ if (!e && !o) {
+ pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "ignore"))
+ req = PA_ALSA_REQUIRED_IGNORE;
+ else if (pa_streq(rvalue, "switch") && e)
+ req = PA_ALSA_REQUIRED_SWITCH;
+ else if (pa_streq(rvalue, "volume") && e)
+ req = PA_ALSA_REQUIRED_VOLUME;
+ else if (pa_streq(rvalue, "enumeration"))
+ req = PA_ALSA_REQUIRED_ENUMERATION;
+ else if (pa_streq(rvalue, "any"))
+ req = PA_ALSA_REQUIRED_ANY;
+ else {
+ pa_log("[%s:%u] Required invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(lvalue, "required-absent")) {
+ if (e)
+ e->required_absent = req;
+ if (o)
+ o->required_absent = req;
+ }
+ else if (pa_streq(lvalue, "required-any")) {
+ if (e) {
+ e->required_any = req;
+ e->path->has_req_any = TRUE;
+ }
+ if (o) {
+ o->required_any = req;
+ o->element->path->has_req_any = TRUE;
+ }
+ }
+ else {
+ if (e)
+ e->required = req;
+ if (o)
+ o->required = req;
+ }
+
+ return 0;
+}
+
+static int element_parse_direction(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+
+ pa_assert(p);
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "playback"))
+ e->direction = PA_ALSA_DIRECTION_OUTPUT;
+ else if (pa_streq(rvalue, "capture"))
+ e->direction = PA_ALSA_DIRECTION_INPUT;
+ else {
+ pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int element_parse_direction_try_other(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+ int yes;
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if ((yes = pa_parse_boolean(rvalue)) < 0) {
+ pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ e->direction_try_other = !!yes;
+ return 0;
+}
+
+static int element_parse_volume_limit(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+ long volume_limit;
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ if (pa_atol(rvalue, &volume_limit) < 0 || volume_limit < 0) {
+ pa_log("[%s:%u] Invalid value for volume-limit", filename, line);
+ return -1;
+ }
+
+ e->volume_limit = volume_limit;
+ return 0;
+}
+
+static pa_channel_position_mask_t parse_mask(const char *m) {
+ pa_channel_position_mask_t v;
+
+ if (pa_streq(m, "all-left"))
+ v = PA_CHANNEL_POSITION_MASK_LEFT;
+ else if (pa_streq(m, "all-right"))
+ v = PA_CHANNEL_POSITION_MASK_RIGHT;
+ else if (pa_streq(m, "all-center"))
+ v = PA_CHANNEL_POSITION_MASK_CENTER;
+ else if (pa_streq(m, "all-front"))
+ v = PA_CHANNEL_POSITION_MASK_FRONT;
+ else if (pa_streq(m, "all-rear"))
+ v = PA_CHANNEL_POSITION_MASK_REAR;
+ else if (pa_streq(m, "all-side"))
+ v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER;
+ else if (pa_streq(m, "all-top"))
+ v = PA_CHANNEL_POSITION_MASK_TOP;
+ else if (pa_streq(m, "all-no-lfe"))
+ v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE);
+ else if (pa_streq(m, "all"))
+ v = PA_CHANNEL_POSITION_MASK_ALL;
+ else {
+ pa_channel_position_t p;
+
+ if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
+ return 0;
+
+ v = PA_CHANNEL_POSITION_MASK(p);
+ }
+
+ return v;
+}
+
+static int element_parse_override_map(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_path *p = userdata;
+ pa_alsa_element *e;
+ const char *state = NULL;
+ unsigned i = 0;
+ char *n;
+
+ if (!(e = element_get(p, section, TRUE))) {
+ pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section);
+ return -1;
+ }
+
+ while ((n = pa_split(rvalue, ",", &state))) {
+ pa_channel_position_mask_t m;
+
+ if (!*n)
+ m = 0;
+ else {
+ if ((m = parse_mask(n)) == 0) {
+ pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section);
+ pa_xfree(n);
+ return -1;
+ }
+ }
+
+ if (pa_streq(lvalue, "override-map.1"))
+ e->masks[i++][0] = m;
+ else
+ e->masks[i++][1] = m;
+
+ /* Later on we might add override-map.3 and so on here ... */
+
+ pa_xfree(n);
+ }
+
+ e->override_map = TRUE;
+
+ return 0;
+}
+
+static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_elem_t *me;
+ int r;
+
+ pa_assert(e);
+ pa_assert(m);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return -1;
+ }
+
+ if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
+
+ if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx);
+ else
+ r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
+
+ if (r < 0)
+ pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+
+ } else {
+ pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
+
+ if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
+ pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+ }
+
+ return r;
+}
+
+int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) {
+ pa_alsa_option *o;
+ uint32_t idx;
+
+ pa_assert(s);
+ pa_assert(m);
+
+ PA_IDXSET_FOREACH(o, s->options, idx)
+ element_set_option(o->element, m, o->alsa_idx);
+
+ return 0;
+}
+
+static int option_verify(pa_alsa_option *o) {
+ static const struct description_map well_known_descriptions[] = {
+ { "input", N_("Input") },
+ { "input-docking", N_("Docking Station Input") },
+ { "input-docking-microphone", N_("Docking Station Microphone") },
+ { "input-docking-linein", N_("Docking Station Line-In") },
+ { "input-linein", N_("Line-In") },
+ { "input-microphone", N_("Microphone") },
+ { "input-microphone-front", N_("Front Microphone") },
+ { "input-microphone-rear", N_("Rear Microphone") },
+ { "input-microphone-external", N_("External Microphone") },
+ { "input-microphone-internal", N_("Internal Microphone") },
+ { "input-radio", N_("Radio") },
+ { "input-video", N_("Video") },
+ { "input-agc-on", N_("Automatic Gain Control") },
+ { "input-agc-off", N_("No Automatic Gain Control") },
+ { "input-boost-on", N_("Boost") },
+ { "input-boost-off", N_("No Boost") },
+ { "output-amplifier-on", N_("Amplifier") },
+ { "output-amplifier-off", N_("No Amplifier") },
+ { "output-bass-boost-on", N_("Bass Boost") },
+ { "output-bass-boost-off", N_("No Bass Boost") },
+ { "output-speaker", N_("Speaker") },
+ { "output-headphones", N_("Headphones") }
+ };
+
+ pa_assert(o);
+
+ if (!o->name) {
+ pa_log("No name set for option %s", o->alsa_name);
+ return -1;
+ }
+
+ if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
+ o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
+ pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
+ return -1;
+ }
+
+ if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
+ !pa_streq(o->alsa_name, "on") &&
+ !pa_streq(o->alsa_name, "off")) {
+ pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
+ return -1;
+ }
+
+ if (!o->description)
+ o->description = pa_xstrdup(lookup_description(o->name,
+ well_known_descriptions,
+ PA_ELEMENTSOF(well_known_descriptions)));
+ if (!o->description)
+ o->description = pa_xstrdup(o->name);
+
+ return 0;
+}
+
+static int element_verify(pa_alsa_element *e) {
+ pa_alsa_option *o;
+
+ pa_assert(e);
+
+// pa_log_debug("Element %s, path %s: r=%d, r-any=%d, r-abs=%d", e->alsa_name, e->path->name, e->required, e->required_any, e->required_absent);
+ if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) ||
+ (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
+ (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
+ (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
+ pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
+ return -1;
+ }
+
+ if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
+ pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
+ return -1;
+ }
+
+ PA_LLIST_FOREACH(o, e->options)
+ if (option_verify(o) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int path_verify(pa_alsa_path *p) {
+ static const struct description_map well_known_descriptions[] = {
+ { "analog-input", N_("Analog Input") },
+ { "analog-input-microphone", N_("Analog Microphone") },
+ { "analog-input-microphone-front", N_("Front Microphone") },
+ { "analog-input-microphone-rear", N_("Rear Microphone") },
+ { "analog-input-microphone-dock", N_("Docking Station Microphone") },
+ { "analog-input-microphone-internal", N_("Internal Microphone") },
+ { "analog-input-linein", N_("Analog Line-In") },
+ { "analog-input-radio", N_("Analog Radio") },
+ { "analog-input-video", N_("Analog Video") },
+ { "analog-output", N_("Analog Output") },
+ { "analog-output-headphones", N_("Analog Headphones") },
+ { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") },
+ { "analog-output-mono", N_("Analog Mono Output") },
+ { "analog-output-speaker", N_("Analog Speakers") },
+ { "iec958-stereo-output", N_("Digital Output (IEC958)") },
+ { "iec958-passthrough-output", N_("Digital Passthrough (IEC958)") }
+ };
+
+ pa_alsa_element *e;
+
+ pa_assert(p);
+
+ PA_LLIST_FOREACH(e, p->elements)
+ if (element_verify(e) < 0)
+ return -1;
+
+ if (!p->description)
+ p->description = pa_xstrdup(lookup_description(p->name,
+ well_known_descriptions,
+ PA_ELEMENTSOF(well_known_descriptions)));
+
+ if (!p->description)
+ p->description = pa_xstrdup(p->name);
+
+ return 0;
+}
+
+pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction) {
+ pa_alsa_path *p;
+ char *fn;
+ int r;
+ const char *n;
+
+ pa_config_item items[] = {
+ /* [General] */
+ { "priority", pa_config_parse_unsigned, NULL, "General" },
+ { "description", pa_config_parse_string, NULL, "General" },
+ { "name", pa_config_parse_string, NULL, "General" },
+
+ /* [Option ...] */
+ { "priority", option_parse_priority, NULL, NULL },
+ { "name", option_parse_name, NULL, NULL },
+
+ /* [Element ...] */
+ { "switch", element_parse_switch, NULL, NULL },
+ { "volume", element_parse_volume, NULL, NULL },
+ { "enumeration", element_parse_enumeration, NULL, NULL },
+ { "override-map.1", element_parse_override_map, NULL, NULL },
+ { "override-map.2", element_parse_override_map, NULL, NULL },
+ /* ... later on we might add override-map.3 and so on here ... */
+ { "required", element_parse_required, NULL, NULL },
+ { "required-any", element_parse_required, NULL, NULL },
+ { "required-absent", element_parse_required, NULL, NULL },
+ { "direction", element_parse_direction, NULL, NULL },
+ { "direction-try-other", element_parse_direction_try_other, NULL, NULL },
+ { "volume-limit", element_parse_volume_limit, NULL, NULL },
+ { NULL, NULL, NULL, NULL }
+ };
+
+ pa_assert(fname);
+
+ p = pa_xnew0(pa_alsa_path, 1);
+ n = pa_path_get_filename(fname);
+ p->name = pa_xstrndup(n, strcspn(n, "."));
+ p->direction = direction;
+
+ items[0].data = &p->priority;
+ items[1].data = &p->description;
+ items[2].data = &p->name;
+
+ fn = pa_maybe_prefix_path(fname,
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" :
+ PA_ALSA_PATHS_DIR);
+
+ r = pa_config_parse(fn, NULL, items, p);
+ pa_xfree(fn);
+
+ if (r < 0)
+ goto fail;
+
+ if (path_verify(p) < 0)
+ goto fail;
+
+ return p;
+
+fail:
+ pa_alsa_path_free(p);
+ return NULL;
+}
+
+pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
+ pa_alsa_path *p;
+ pa_alsa_element *e;
+
+ pa_assert(element);
+
+ p = pa_xnew0(pa_alsa_path, 1);
+ p->name = pa_xstrdup(element);
+ p->direction = direction;
+
+ e = pa_xnew0(pa_alsa_element, 1);
+ e->path = p;
+ e->alsa_name = pa_xstrdup(element);
+ e->direction = direction;
+ e->volume_limit = -1;
+
+ e->switch_use = PA_ALSA_SWITCH_MUTE;
+ e->volume_use = PA_ALSA_VOLUME_MERGE;
+
+ PA_LLIST_PREPEND(pa_alsa_element, p->elements, e);
+ p->last_element = e;
+ return p;
+}
+
+static pa_bool_t element_drop_unsupported(pa_alsa_element *e) {
+ pa_alsa_option *o, *n;
+
+ pa_assert(e);
+
+ for (o = e->options; o; o = n) {
+ n = o->next;
+
+ if (o->alsa_idx < 0) {
+ PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
+ option_free(o);
+ }
+ }
+
+ return
+ e->switch_use != PA_ALSA_SWITCH_IGNORE ||
+ e->volume_use != PA_ALSA_VOLUME_IGNORE ||
+ e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE;
+}
+
+static void path_drop_unsupported(pa_alsa_path *p) {
+ pa_alsa_element *e, *n;
+
+ pa_assert(p);
+
+ for (e = p->elements; e; e = n) {
+ n = e->next;
+
+ if (!element_drop_unsupported(e)) {
+ PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
+ element_free(e);
+ }
+ }
+}
+
+static void path_make_options_unique(pa_alsa_path *p) {
+ pa_alsa_element *e;
+ pa_alsa_option *o, *u;
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ PA_LLIST_FOREACH(o, e->options) {
+ unsigned i;
+ char *m;
+
+ for (u = o->next; u; u = u->next)
+ if (pa_streq(u->name, o->name))
+ break;
+
+ if (!u)
+ continue;
+
+ m = pa_xstrdup(o->name);
+
+ /* OK, this name is not unique, hence let's rename */
+ for (i = 1, u = o; u; u = u->next) {
+ char *nn, *nd;
+
+ if (!pa_streq(u->name, m))
+ continue;
+
+ nn = pa_sprintf_malloc("%s-%u", m, i);
+ pa_xfree(u->name);
+ u->name = nn;
+
+ nd = pa_sprintf_malloc("%s %u", u->description, i);
+ pa_xfree(u->description);
+ u->description = nd;
+
+ i++;
+ }
+
+ pa_xfree(m);
+ }
+ }
+}
+
+static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) {
+ pa_alsa_option *o;
+
+ for (; e; e = e->next)
+ if (e->switch_use == PA_ALSA_SWITCH_SELECT ||
+ e->enumeration_use == PA_ALSA_ENUMERATION_SELECT)
+ break;
+
+ if (!e)
+ return FALSE;
+
+ for (o = e->options; o; o = o->next) {
+ pa_alsa_setting *s;
+
+ if (template) {
+ s = pa_xnewdup(pa_alsa_setting, template, 1);
+ s->options = pa_idxset_copy(template->options);
+ s->name = pa_sprintf_malloc(_("%s+%s"), template->name, o->name);
+ s->description =
+ (template->description[0] && o->description[0])
+ ? pa_sprintf_malloc(_("%s / %s"), template->description, o->description)
+ : (template->description[0]
+ ? pa_xstrdup(template->description)
+ : pa_xstrdup(o->description));
+
+ s->priority = PA_MAX(template->priority, o->priority);
+ } else {
+ s = pa_xnew0(pa_alsa_setting, 1);
+ s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ s->name = pa_xstrdup(o->name);
+ s->description = pa_xstrdup(o->description);
+ s->priority = o->priority;
+ }
+
+ pa_idxset_put(s->options, o, NULL);
+
+ if (element_create_settings(e->next, s))
+ /* This is not a leaf, so let's get rid of it */
+ setting_free(s);
+ else {
+ /* This is a leaf, so let's add it */
+ PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s);
+
+ e->path->last_setting = s;
+ }
+ }
+
+ return TRUE;
+}
+
+static void path_create_settings(pa_alsa_path *p) {
+ pa_assert(p);
+
+ element_create_settings(p->elements, NULL);
+}
+
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
+ pa_alsa_element *e;
+ double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
+ pa_channel_position_t t;
+ pa_channel_position_mask_t path_volume_channels = 0;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ if (p->probed)
+ return 0;
+
+ pa_zero(min_dB);
+ pa_zero(max_dB);
+
+ pa_log_debug("Probing path '%s'", p->name);
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ if (element_probe(e, m) < 0) {
+ p->supported = FALSE;
+ pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
+ return -1;
+ }
+ pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use);
+
+ if (ignore_dB)
+ e->has_dB = FALSE;
+
+ if (e->volume_use == PA_ALSA_VOLUME_MERGE) {
+
+ if (!p->has_volume) {
+ p->min_volume = e->min_volume;
+ p->max_volume = e->max_volume;
+ }
+
+ if (e->has_dB) {
+ if (!p->has_volume) {
+ for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
+ if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
+ min_dB[t] = e->min_dB;
+ max_dB[t] = e->max_dB;
+ path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
+ }
+
+ p->has_dB = TRUE;
+ } else {
+
+ if (p->has_dB) {
+ for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
+ if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
+ min_dB[t] += e->min_dB;
+ max_dB[t] += e->max_dB;
+ path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
+ }
+ } else {
+ /* Hmm, there's another element before us
+ * which cannot do dB volumes, so we we need
+ * to 'neutralize' this slider */
+ e->volume_use = PA_ALSA_VOLUME_ZERO;
+ pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
+ }
+ }
+ } else if (p->has_volume) {
+ /* We can't use this volume, so let's ignore it */
+ e->volume_use = PA_ALSA_VOLUME_IGNORE;
+ pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
+ }
+ p->has_volume = TRUE;
+ }
+
+ if (e->switch_use == PA_ALSA_SWITCH_MUTE)
+ p->has_mute = TRUE;
+ }
+
+ if (p->has_req_any && !p->req_any_present) {
+ p->supported = FALSE;
+ pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name);
+ return -1;
+ }
+
+ path_drop_unsupported(p);
+ path_make_options_unique(p);
+ path_create_settings(p);
+
+ p->supported = TRUE;
+ p->probed = TRUE;
+
+ p->min_dB = INFINITY;
+ p->max_dB = -INFINITY;
+
+ for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
+ if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
+ if (p->min_dB > min_dB[t])
+ p->min_dB = min_dB[t];
+
+ if (p->max_dB < max_dB[t])
+ p->max_dB = max_dB[t];
+ }
+ }
+
+ return 0;
+}
+
+void pa_alsa_setting_dump(pa_alsa_setting *s) {
+ pa_assert(s);
+
+ pa_log_debug("Setting %s (%s) priority=%u",
+ s->name,
+ pa_strnull(s->description),
+ s->priority);
+}
+
+void pa_alsa_option_dump(pa_alsa_option *o) {
+ pa_assert(o);
+
+ pa_log_debug("Option %s (%s/%s) index=%i, priority=%u",
+ o->alsa_name,
+ pa_strnull(o->name),
+ pa_strnull(o->description),
+ o->alsa_idx,
+ o->priority);
+}
+
+void pa_alsa_element_dump(pa_alsa_element *e) {
+ pa_alsa_option *o;
+ pa_assert(e);
+
+ pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
+ e->alsa_name,
+ e->direction,
+ e->switch_use,
+ e->volume_use,
+ e->volume_limit,
+ e->enumeration_use,
+ e->required,
+ e->required_any,
+ e->required_absent,
+ (long long unsigned) e->merged_mask,
+ e->n_channels,
+ pa_yes_no(e->override_map));
+
+ PA_LLIST_FOREACH(o, e->options)
+ pa_alsa_option_dump(o);
+}
+
+void pa_alsa_path_dump(pa_alsa_path *p) {
+ pa_alsa_element *e;
+ pa_alsa_setting *s;
+ pa_assert(p);
+
+ pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, "
+ "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g",
+ p->name,
+ pa_strnull(p->description),
+ p->direction,
+ p->priority,
+ pa_yes_no(p->probed),
+ pa_yes_no(p->supported),
+ pa_yes_no(p->has_mute),
+ pa_yes_no(p->has_volume),
+ pa_yes_no(p->has_dB),
+ p->min_volume, p->max_volume,
+ p->min_dB, p->max_dB);
+
+ PA_LLIST_FOREACH(e, p->elements)
+ pa_alsa_element_dump(e);
+
+ PA_LLIST_FOREACH(s, p->settings)
+ pa_alsa_setting_dump(s);
+}
+
+static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_elem_t *me;
+
+ pa_assert(e);
+ pa_assert(m);
+ pa_assert(cb);
+
+ SELEM_INIT(sid, e->alsa_name);
+ if (!(me = snd_mixer_find_selem(m, sid))) {
+ pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+ return;
+ }
+
+ snd_mixer_elem_set_callback(me, cb);
+ snd_mixer_elem_set_callback_private(me, userdata);
+}
+
+void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
+ pa_alsa_element *e;
+
+ pa_assert(p);
+ pa_assert(m);
+ pa_assert(cb);
+
+ PA_LLIST_FOREACH(e, p->elements)
+ element_set_callback(e, m, cb, userdata);
+}
+
+void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
+ pa_alsa_path *p;
+
+ pa_assert(ps);
+ pa_assert(m);
+ pa_assert(cb);
+
+ PA_LLIST_FOREACH(p, ps->paths)
+ pa_alsa_path_set_callback(p, m, cb, userdata);
+}
+
+pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction) {
+ pa_alsa_path_set *ps;
+ char **pn = NULL, **en = NULL, **ie;
+ pa_alsa_decibel_fix *db_fix;
+ void *state;
+
+ pa_assert(m);
+ pa_assert(m->profile_set);
+ pa_assert(m->profile_set->decibel_fixes);
+ pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT);
+
+ if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction)
+ return NULL;
+
+ ps = pa_xnew0(pa_alsa_path_set, 1);
+ ps->direction = direction;
+
+ if (direction == PA_ALSA_DIRECTION_OUTPUT)
+ pn = m->output_path_names;
+ else if (direction == PA_ALSA_DIRECTION_INPUT)
+ pn = m->input_path_names;
+
+ if (pn) {
+ char **in;
+
+ for (in = pn; *in; in++) {
+ pa_alsa_path *p;
+ pa_bool_t duplicate = FALSE;
+ char **kn, *fn;
+
+ for (kn = pn; kn < in; kn++)
+ if (pa_streq(*kn, *in)) {
+ duplicate = TRUE;
+ break;
+ }
+
+ if (duplicate)
+ continue;
+
+ fn = pa_sprintf_malloc("%s.conf", *in);
+
+ if ((p = pa_alsa_path_new(fn, direction))) {
+ p->path_set = ps;
+ PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
+ ps->last_path = p;
+ }
+
+ pa_xfree(fn);
+ }
+
+ goto finish;
+ }
+
+ if (direction == PA_ALSA_DIRECTION_OUTPUT)
+ en = m->output_element;
+ else if (direction == PA_ALSA_DIRECTION_INPUT)
+ en = m->input_element;
+
+ if (!en) {
+ pa_alsa_path_set_free(ps);
+ return NULL;
+ }
+
+ for (ie = en; *ie; ie++) {
+ char **je;
+ pa_alsa_path *p;
+
+ p = pa_alsa_path_synthesize(*ie, direction);
+ p->path_set = ps;
+
+ /* Mark all other passed elements for require-absent */
+ for (je = en; *je; je++) {
+ pa_alsa_element *e;
+
+ if (je == ie)
+ continue;
+
+ e = pa_xnew0(pa_alsa_element, 1);
+ e->path = p;
+ e->alsa_name = pa_xstrdup(*je);
+ e->direction = direction;
+ e->required_absent = PA_ALSA_REQUIRED_ANY;
+ e->volume_limit = -1;
+
+ PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
+ p->last_element = e;
+ }
+
+ PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
+ ps->last_path = p;
+ }
+
+finish:
+ /* Assign decibel fixes to elements. */
+ PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) {
+ pa_alsa_path *p;
+
+ PA_LLIST_FOREACH(p, ps->paths) {
+ pa_alsa_element *e;
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
+ /* The profile set that contains the dB fix may be freed
+ * before the element, so we have to copy the dB fix
+ * object. */
+ e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1);
+ e->db_fix->profile_set = NULL;
+ e->db_fix->name = pa_xstrdup(db_fix->name);
+ e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long));
+ }
+ }
+ }
+ }
+
+ return ps;
+}
+
+void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
+ pa_alsa_path *p;
+ pa_assert(ps);
+
+ pa_log_debug("Path Set %p, direction=%i, probed=%s",
+ (void*) ps,
+ ps->direction,
+ pa_yes_no(ps->probed));
+
+ PA_LLIST_FOREACH(p, ps->paths)
+ pa_alsa_path_dump(p);
+}
+
+static void path_set_unify(pa_alsa_path_set *ps) {
+ pa_alsa_path *p;
+ pa_bool_t has_dB = TRUE, has_volume = TRUE, has_mute = TRUE;
+ pa_assert(ps);
+
+ /* We have issues dealing with paths that vary too wildly. That
+ * means for now we have to have all paths support volume/mute/dB
+ * or none. */
+
+ PA_LLIST_FOREACH(p, ps->paths) {
+ pa_assert(p->probed);
+
+ if (!p->has_volume)
+ has_volume = FALSE;
+ else if (!p->has_dB)
+ has_dB = FALSE;
+
+ if (!p->has_mute)
+ has_mute = FALSE;
+ }
+
+ if (!has_volume || !has_dB || !has_mute) {
+
+ if (!has_volume)
+ pa_log_debug("Some paths of the device lack hardware volume control, disabling hardware control altogether.");
+ else if (!has_dB)
+ pa_log_debug("Some paths of the device lack dB information, disabling dB logic altogether.");
+
+ if (!has_mute)
+ pa_log_debug("Some paths of the device lack hardware mute control, disabling hardware control altogether.");
+
+ PA_LLIST_FOREACH(p, ps->paths) {
+ if (!has_volume)
+ p->has_volume = FALSE;
+ else if (!has_dB)
+ p->has_dB = FALSE;
+
+ if (!has_mute)
+ p->has_mute = FALSE;
+ }
+ }
+}
+
+static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
+ pa_alsa_path *p, *q;
+
+ PA_LLIST_FOREACH(p, ps->paths) {
+ unsigned i;
+ char *m;
+
+ for (q = p->next; q; q = q->next)
+ if (pa_streq(q->name, p->name))
+ break;
+
+ if (!q)
+ continue;
+
+ m = pa_xstrdup(p->name);
+
+ /* OK, this name is not unique, hence let's rename */
+ for (i = 1, q = p; q; q = q->next) {
+ char *nn, *nd;
+
+ if (!pa_streq(q->name, m))
+ continue;
+
+ nn = pa_sprintf_malloc("%s-%u", m, i);
+ pa_xfree(q->name);
+ q->name = nn;
+
+ nd = pa_sprintf_malloc("%s %u", q->description, i);
+ pa_xfree(q->description);
+ q->description = nd;
+
+ i++;
+ }
+
+ pa_xfree(m);
+ }
+}
+
+void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t ignore_dB) {
+ pa_alsa_path *p, *n;
+
+ pa_assert(ps);
+
+ if (ps->probed)
+ return;
+
+ for (p = ps->paths; p; p = n) {
+ n = p->next;
+
+ if (pa_alsa_path_probe(p, m, ignore_dB) < 0) {
+ PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
+ pa_alsa_path_free(p);
+ }
+ }
+
+ path_set_unify(ps);
+ path_set_make_paths_unique(ps);
+ ps->probed = TRUE;
+}
+
+static void mapping_free(pa_alsa_mapping *m) {
+ pa_assert(m);
+
+ pa_xfree(m->name);
+ pa_xfree(m->description);
+
+ pa_xstrfreev(m->device_strings);
+ pa_xstrfreev(m->input_path_names);
+ pa_xstrfreev(m->output_path_names);
+ pa_xstrfreev(m->input_element);
+ pa_xstrfreev(m->output_element);
+
+ pa_assert(!m->input_pcm);
+ pa_assert(!m->output_pcm);
+
+ pa_xfree(m);
+}
+
+static void profile_free(pa_alsa_profile *p) {
+ pa_assert(p);
+
+ pa_xfree(p->name);
+ pa_xfree(p->description);
+
+ pa_xstrfreev(p->input_mapping_names);
+ pa_xstrfreev(p->output_mapping_names);
+
+ if (p->input_mappings)
+ pa_idxset_free(p->input_mappings, NULL, NULL);
+
+ if (p->output_mappings)
+ pa_idxset_free(p->output_mappings, NULL, NULL);
+
+ pa_xfree(p);
+}
+
+void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
+ pa_assert(ps);
+
+ if (ps->profiles) {
+ pa_alsa_profile *p;
+
+ while ((p = pa_hashmap_steal_first(ps->profiles)))
+ profile_free(p);
+
+ pa_hashmap_free(ps->profiles, NULL, NULL);
+ }
+
+ if (ps->mappings) {
+ pa_alsa_mapping *m;
+
+ while ((m = pa_hashmap_steal_first(ps->mappings)))
+ mapping_free(m);
+
+ pa_hashmap_free(ps->mappings, NULL, NULL);
+ }
+
+ if (ps->decibel_fixes) {
+ pa_alsa_decibel_fix *db_fix;
+
+ while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes)))
+ decibel_fix_free(db_fix);
+
+ pa_hashmap_free(ps->decibel_fixes, NULL, NULL);
+ }
+
+ pa_xfree(ps);
+}
+
+static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) {
+ pa_alsa_mapping *m;
+
+ if (!pa_startswith(name, "Mapping "))
+ return NULL;
+
+ name += 8;
+
+ if ((m = pa_hashmap_get(ps->mappings, name)))
+ return m;
+
+ m = pa_xnew0(pa_alsa_mapping, 1);
+ m->profile_set = ps;
+ m->name = pa_xstrdup(name);
+ pa_channel_map_init(&m->channel_map);
+
+ pa_hashmap_put(ps->mappings, m->name, m);
+
+ return m;
+}
+
+static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
+ pa_alsa_profile *p;
+
+ if (!pa_startswith(name, "Profile "))
+ return NULL;
+
+ name += 8;
+
+ if ((p = pa_hashmap_get(ps->profiles, name)))
+ return p;
+
+ p = pa_xnew0(pa_alsa_profile, 1);
+ p->profile_set = ps;
+ p->name = pa_xstrdup(name);
+
+ pa_hashmap_put(ps->profiles, p->name, p);
+
+ return p;
+}
+
+static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
+ pa_alsa_decibel_fix *db_fix;
+
+ if (!pa_startswith(name, "DecibelFix "))
+ return NULL;
+
+ name += 11;
+
+ if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
+ return db_fix;
+
+ db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
+ db_fix->profile_set = ps;
+ db_fix->name = pa_xstrdup(name);
+
+ pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
+
+ return db_fix;
+}
+
+static int mapping_parse_device_strings(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if (!(m = mapping_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ pa_xstrfreev(m->device_strings);
+ if (!(m->device_strings = pa_split_spaces_strv(rvalue))) {
+ pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mapping_parse_channel_map(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if (!(m = mapping_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if (!(pa_channel_map_parse(&m->channel_map, rvalue))) {
+ pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mapping_parse_paths(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if (!(m = mapping_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if (pa_streq(lvalue, "paths-input")) {
+ pa_xstrfreev(m->input_path_names);
+ m->input_path_names = pa_split_spaces_strv(rvalue);
+ } else {
+ pa_xstrfreev(m->output_path_names);
+ m->output_path_names = pa_split_spaces_strv(rvalue);
+ }
+
+ return 0;
+}
+
+static int mapping_parse_element(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if (!(m = mapping_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if (pa_streq(lvalue, "element-input")) {
+ pa_xstrfreev(m->input_element);
+ m->input_element = pa_split_spaces_strv(rvalue);
+ } else {
+ pa_xstrfreev(m->output_element);
+ m->output_element = pa_split_spaces_strv(rvalue);
+ }
+
+ return 0;
+}
+
+static int mapping_parse_direction(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if (!(m = mapping_get(ps, section))) {
+ pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
+ return -1;
+ }
+
+ if (pa_streq(rvalue, "input"))
+ m->direction = PA_ALSA_DIRECTION_INPUT;
+ else if (pa_streq(rvalue, "output"))
+ m->direction = PA_ALSA_DIRECTION_OUTPUT;
+ else if (pa_streq(rvalue, "any"))
+ m->direction = PA_ALSA_DIRECTION_ANY;
+ else {
+ pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mapping_parse_description(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_profile *p;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+
+ if ((m = mapping_get(ps, section))) {
+ pa_xfree(m->description);
+ m->description = pa_xstrdup(rvalue);
+ } else if ((p = profile_get(ps, section))) {
+ pa_xfree(p->description);
+ p->description = pa_xstrdup(rvalue);
+ } else {
+ pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mapping_parse_priority(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_profile *p;
+ pa_alsa_mapping *m;
+ uint32_t prio;
+
+ pa_assert(ps);
+
+ if (pa_atou(rvalue, &prio) < 0) {
+ pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ if ((m = mapping_get(ps, section)))
+ m->priority = prio;
+ else if ((p = profile_get(ps, section)))
+ p->priority = prio;
+ else {
+ pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int profile_parse_mappings(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_profile *p;
+
+ pa_assert(ps);
+
+ if (!(p = profile_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if (pa_streq(lvalue, "input-mappings")) {
+ pa_xstrfreev(p->input_mapping_names);
+ p->input_mapping_names = pa_split_spaces_strv(rvalue);
+ } else {
+ pa_xstrfreev(p->output_mapping_names);
+ p->output_mapping_names = pa_split_spaces_strv(rvalue);
+ }
+
+ return 0;
+}
+
+static int profile_parse_skip_probe(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_profile *p;
+ int b;
+
+ pa_assert(ps);
+
+ if (!(p = profile_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if ((b = pa_parse_boolean(rvalue)) < 0) {
+ pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section);
+ return -1;
+ }
+
+ p->supported = b;
+
+ return 0;
+}
+
+static int decibel_fix_parse_db_values(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ const char *lvalue,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ pa_alsa_profile_set *ps = userdata;
+ pa_alsa_decibel_fix *db_fix;
+ char **items;
+ char *item;
+ long *db_values;
+ unsigned n = 8; /* Current size of the db_values table. */
+ unsigned min_step = 0;
+ unsigned max_step = 0;
+ unsigned i = 0; /* Index to the items table. */
+ unsigned prev_step = 0;
+ double prev_db = 0;
+
+ pa_assert(filename);
+ pa_assert(section);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(ps);
+
+ if (!(db_fix = decibel_fix_get(ps, section))) {
+ pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+ return -1;
+ }
+
+ if (!(items = pa_split_spaces_strv(rvalue))) {
+ pa_log("[%s:%u] Value missing", pa_strnull(filename), line);
+ return -1;
+ }
+
+ db_values = pa_xnew(long, n);
+
+ while ((item = items[i++])) {
+ char *s = item; /* Step value string. */
+ char *d = item; /* dB value string. */
+ uint32_t step;
+ double db;
+
+ /* Move d forward until it points to a colon or to the end of the item. */
+ for (; *d && *d != ':'; ++d);
+
+ if (d == s) {
+ /* item started with colon. */
+ pa_log("[%s:%u] No step value found in %s", filename, line, item);
+ goto fail;
+ }
+
+ if (!*d || !*(d + 1)) {
+ /* No colon found, or it was the last character in item. */
+ pa_log("[%s:%u] No dB value found in %s", filename, line, item);
+ goto fail;
+ }
+
+ /* pa_atou() needs a null-terminating string. Let's replace the colon
+ * with a zero byte. */
+ *d++ = '\0';
+
+ if (pa_atou(s, &step) < 0) {
+ pa_log("[%s:%u] Invalid step value: %s", filename, line, s);
+ goto fail;
+ }
+
+ if (pa_atod(d, &db) < 0) {
+ pa_log("[%s:%u] Invalid dB value: %s", filename, line, d);
+ goto fail;
+ }
+
+ if (step <= prev_step && i != 1) {
+ pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step);
+ goto fail;
+ }
+
+ if (db < prev_db && i != 1) {
+ pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db);
+ goto fail;
+ }
+
+ if (i == 1) {
+ min_step = step;
+ db_values[0] = (long) (db * 100.0);
+ prev_step = step;
+ prev_db = db;
+ } else {
+ /* Interpolate linearly. */
+ double db_increment = (db - prev_db) / (step - prev_step);
+
+ for (; prev_step < step; ++prev_step, prev_db += db_increment) {
+
+ /* Reallocate the db_values table if it's about to overflow. */
+ if (prev_step + 1 - min_step == n) {
+ n *= 2;
+ db_values = pa_xrenew(long, db_values, n);
+ }
+
+ db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0);
+ }
+ }
+
+ max_step = step;
+ }
+
+ db_fix->min_step = min_step;
+ db_fix->max_step = max_step;
+ pa_xfree(db_fix->db_values);
+ db_fix->db_values = db_values;
+
+ pa_xstrfreev(items);
+
+ return 0;
+
+fail:
+ pa_xstrfreev(items);
+ pa_xfree(db_values);
+
+ return -1;
+}
+
+static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
+
+ static const struct description_map well_known_descriptions[] = {
+ { "analog-mono", N_("Analog Mono") },
+ { "analog-stereo", N_("Analog Stereo") },
+ { "analog-surround-21", N_("Analog Surround 2.1") },
+ { "analog-surround-30", N_("Analog Surround 3.0") },
+ { "analog-surround-31", N_("Analog Surround 3.1") },
+ { "analog-surround-40", N_("Analog Surround 4.0") },
+ { "analog-surround-41", N_("Analog Surround 4.1") },
+ { "analog-surround-50", N_("Analog Surround 5.0") },
+ { "analog-surround-51", N_("Analog Surround 5.1") },
+ { "analog-surround-61", N_("Analog Surround 6.0") },
+ { "analog-surround-61", N_("Analog Surround 6.1") },
+ { "analog-surround-70", N_("Analog Surround 7.0") },
+ { "analog-surround-71", N_("Analog Surround 7.1") },
+ { "iec958-stereo", N_("Digital Stereo (IEC958)") },
+ { "iec958-passthrough", N_("Digital Passthrough (IEC958)") },
+ { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") },
+ { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") },
+ { "hdmi-stereo", N_("Digital Stereo (HDMI)") }
+ };
+
+ pa_assert(m);
+
+ if (!pa_channel_map_valid(&m->channel_map)) {
+ pa_log("Mapping %s is missing channel map.", m->name);
+ return -1;
+ }
+
+ if (!m->device_strings) {
+ pa_log("Mapping %s is missing device strings.", m->name);
+ return -1;
+ }
+
+ if ((m->input_path_names && m->input_element) ||
+ (m->output_path_names && m->output_element)) {
+ pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name);
+ return -1;
+ }
+
+ if (!m->description)
+ m->description = pa_xstrdup(lookup_description(m->name,
+ well_known_descriptions,
+ PA_ELEMENTSOF(well_known_descriptions)));
+
+ if (!m->description)
+ m->description = pa_xstrdup(m->name);
+
+ if (bonus) {
+ if (pa_channel_map_equal(&m->channel_map, bonus))
+ m->priority += 50;
+ else if (m->channel_map.channels == bonus->channels)
+ m->priority += 30;
+ }
+
+ return 0;
+}
+
+void pa_alsa_mapping_dump(pa_alsa_mapping *m) {
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(m);
+
+ pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i",
+ m->name,
+ pa_strnull(m->description),
+ m->priority,
+ pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map),
+ pa_yes_no(m->supported),
+ m->direction);
+}
+
+static void profile_set_add_auto_pair(
+ pa_alsa_profile_set *ps,
+ pa_alsa_mapping *m, /* output */
+ pa_alsa_mapping *n /* input */) {
+
+ char *name;
+ pa_alsa_profile *p;
+
+ pa_assert(ps);
+ pa_assert(m || n);
+
+ if (m && m->direction == PA_ALSA_DIRECTION_INPUT)
+ return;
+
+ if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT)
+ return;
+
+ if (m && n)
+ name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name);
+ else if (m)
+ name = pa_sprintf_malloc("output:%s", m->name);
+ else
+ name = pa_sprintf_malloc("input:%s", n->name);
+
+ if (pa_hashmap_get(ps->profiles, name)) {
+ pa_xfree(name);
+ return;
+ }
+
+ p = pa_xnew0(pa_alsa_profile, 1);
+ p->profile_set = ps;
+ p->name = name;
+
+ if (m) {
+ p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ pa_idxset_put(p->output_mappings, m, NULL);
+ p->priority += m->priority * 100;
+ }
+
+ if (n) {
+ p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ pa_idxset_put(p->input_mappings, n, NULL);
+ p->priority += n->priority;
+ }
+
+ pa_hashmap_put(ps->profiles, p->name, p);
+}
+
+static void profile_set_add_auto(pa_alsa_profile_set *ps) {
+ pa_alsa_mapping *m, *n;
+ void *m_state, *n_state;
+
+ pa_assert(ps);
+
+ PA_HASHMAP_FOREACH(m, ps->mappings, m_state) {
+ profile_set_add_auto_pair(ps, m, NULL);
+
+ PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
+ profile_set_add_auto_pair(ps, m, n);
+ }
+
+ PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
+ profile_set_add_auto_pair(ps, NULL, n);
+}
+
+static int profile_verify(pa_alsa_profile *p) {
+
+ static const struct description_map well_known_descriptions[] = {
+ { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") },
+ { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") },
+ { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") },
+ { "off", N_("Off") }
+ };
+
+ pa_assert(p);
+
+ /* Replace the output mapping names by the actual mappings */
+ if (p->output_mapping_names) {
+ char **name;
+
+ pa_assert(!p->output_mappings);
+ p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ for (name = p->output_mapping_names; *name; name++) {
+ pa_alsa_mapping *m;
+ char **in;
+ pa_bool_t duplicate = FALSE;
+
+ for (in = name + 1; *in; in++)
+ if (pa_streq(*name, *in)) {
+ duplicate = TRUE;
+ break;
+ }
+
+ if (duplicate)
+ continue;
+
+ if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) {
+ pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name);
+ return -1;
+ }
+
+ pa_idxset_put(p->output_mappings, m, NULL);
+
+ if (p->supported)
+ m->supported++;
+ }
+
+ pa_xstrfreev(p->output_mapping_names);
+ p->output_mapping_names = NULL;
+ }
+
+ /* Replace the input mapping names by the actual mappings */
+ if (p->input_mapping_names) {
+ char **name;
+
+ pa_assert(!p->input_mappings);
+ p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ for (name = p->input_mapping_names; *name; name++) {
+ pa_alsa_mapping *m;
+ char **in;
+ pa_bool_t duplicate = FALSE;
+
+ for (in = name + 1; *in; in++)
+ if (pa_streq(*name, *in)) {
+ duplicate = TRUE;
+ break;
+ }
+
+ if (duplicate)
+ continue;
+
+ if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) {
+ pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name);
+ return -1;
+ }
+
+ pa_idxset_put(p->input_mappings, m, NULL);
+
+ if (p->supported)
+ m->supported++;
+ }
+
+ pa_xstrfreev(p->input_mapping_names);
+ p->input_mapping_names = NULL;
+ }
+
+ if (!p->input_mappings && !p->output_mappings) {
+ pa_log("Profile '%s' lacks mappings.", p->name);
+ return -1;
+ }
+
+ if (!p->description)
+ p->description = pa_xstrdup(lookup_description(p->name,
+ well_known_descriptions,
+ PA_ELEMENTSOF(well_known_descriptions)));
+
+ if (!p->description) {
+ pa_strbuf *sb;
+ uint32_t idx;
+ pa_alsa_mapping *m;
+
+ sb = pa_strbuf_new();
+
+ if (p->output_mappings)
+ PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
+ if (!pa_strbuf_isempty(sb))
+ pa_strbuf_puts(sb, " + ");
+
+ pa_strbuf_printf(sb, _("%s Output"), m->description);
+ }
+
+ if (p->input_mappings)
+ PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
+ if (!pa_strbuf_isempty(sb))
+ pa_strbuf_puts(sb, " + ");
+
+ pa_strbuf_printf(sb, _("%s Input"), m->description);
+ }
+
+ p->description = pa_strbuf_tostring_free(sb);
+ }
+
+ return 0;
+}
+
+void pa_alsa_profile_dump(pa_alsa_profile *p) {
+ uint32_t idx;
+ pa_alsa_mapping *m;
+ pa_assert(p);
+
+ pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u",
+ p->name,
+ pa_strnull(p->description),
+ p->priority,
+ pa_yes_no(p->supported),
+ p->input_mappings ? pa_idxset_size(p->input_mappings) : 0,
+ p->output_mappings ? pa_idxset_size(p->output_mappings) : 0);
+
+ if (p->input_mappings)
+ PA_IDXSET_FOREACH(m, p->input_mappings, idx)
+ pa_log_debug("Input %s", m->name);
+
+ if (p->output_mappings)
+ PA_IDXSET_FOREACH(m, p->output_mappings, idx)
+ pa_log_debug("Output %s", m->name);
+}
+
+static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) {
+ pa_assert(db_fix);
+
+ /* Check that the dB mapping has been configured. Since "db-values" is
+ * currently the only option in the DecibelFix section, and decibel fix
+ * objects don't get created if a DecibelFix section is empty, this is
+ * actually a redundant check. Having this may prevent future bugs,
+ * however. */
+ if (!db_fix->db_values) {
+ pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name);
+ return -1;
+ }
+
+ return 0;
+}
+
+void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) {
+ char *db_values = NULL;
+
+ pa_assert(db_fix);
+
+ if (db_fix->db_values) {
+ pa_strbuf *buf;
+ unsigned long i, nsteps;
+
+ pa_assert(db_fix->min_step <= db_fix->max_step);
+ nsteps = db_fix->max_step - db_fix->min_step + 1;
+
+ buf = pa_strbuf_new();
+ for (i = 0; i < nsteps; ++i)
+ pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0);
+
+ db_values = pa_strbuf_tostring_free(buf);
+ }
+
+ pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s",
+ db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values));
+
+ pa_xfree(db_values);
+}
+
+pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) {
+ pa_alsa_profile_set *ps;
+ pa_alsa_profile *p;
+ pa_alsa_mapping *m;
+ pa_alsa_decibel_fix *db_fix;
+ char *fn;
+ int r;
+ void *state;
+
+ static pa_config_item items[] = {
+ /* [General] */
+ { "auto-profiles", pa_config_parse_bool, NULL, "General" },
+
+ /* [Mapping ...] */
+ { "device-strings", mapping_parse_device_strings, NULL, NULL },
+ { "channel-map", mapping_parse_channel_map, NULL, NULL },
+ { "paths-input", mapping_parse_paths, NULL, NULL },
+ { "paths-output", mapping_parse_paths, NULL, NULL },
+ { "element-input", mapping_parse_element, NULL, NULL },
+ { "element-output", mapping_parse_element, NULL, NULL },
+ { "direction", mapping_parse_direction, NULL, NULL },
+
+ /* Shared by [Mapping ...] and [Profile ...] */
+ { "description", mapping_parse_description, NULL, NULL },
+ { "priority", mapping_parse_priority, NULL, NULL },
+
+ /* [Profile ...] */
+ { "input-mappings", profile_parse_mappings, NULL, NULL },
+ { "output-mappings", profile_parse_mappings, NULL, NULL },
+ { "skip-probe", profile_parse_skip_probe, NULL, NULL },
+
+ /* [DecibelFix ...] */
+ { "db-values", decibel_fix_parse_db_values, NULL, NULL },
+ { NULL, NULL, NULL, NULL }
+ };
+
+ ps = pa_xnew0(pa_alsa_profile_set, 1);
+ ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ items[0].data = &ps->auto_profiles;
+
+ if (!fname)
+ fname = "default.conf";
+
+ fn = pa_maybe_prefix_path(fname,
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
+ PA_ALSA_PROFILE_SETS_DIR);
+
+ r = pa_config_parse(fn, NULL, items, ps);
+ pa_xfree(fn);
+
+ if (r < 0)
+ goto fail;
+
+ PA_HASHMAP_FOREACH(m, ps->mappings, state)
+ if (mapping_verify(m, bonus) < 0)
+ goto fail;
+
+ if (ps->auto_profiles)
+ profile_set_add_auto(ps);
+
+ PA_HASHMAP_FOREACH(p, ps->profiles, state)
+ if (profile_verify(p) < 0)
+ goto fail;
+
+ PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
+ if (decibel_fix_verify(db_fix) < 0)
+ goto fail;
+
+ return ps;
+
+fail:
+ pa_alsa_profile_set_free(ps);
+ return NULL;
+}
+
+void pa_alsa_profile_set_probe(
+ pa_alsa_profile_set *ps,
+ const char *dev_id,
+ const pa_sample_spec *ss,
+ unsigned default_n_fragments,
+ unsigned default_fragment_size_msec) {
+
+ void *state;
+ pa_alsa_profile *p, *last = NULL;
+ pa_alsa_mapping *m;
+
+ pa_assert(ps);
+ pa_assert(dev_id);
+ pa_assert(ss);
+
+ if (ps->probed)
+ return;
+
+ PA_HASHMAP_FOREACH(p, ps->profiles, state) {
+ pa_sample_spec try_ss;
+ pa_channel_map try_map;
+ snd_pcm_uframes_t try_period_size, try_buffer_size;
+ uint32_t idx;
+
+ /* Is this already marked that it is supported? (i.e. from the config file) */
+ if (p->supported)
+ continue;
+
+ pa_log_debug("Looking at profile %s", p->name);
+
+ /* Close PCMs from the last iteration we don't need anymore */
+ if (last && last->output_mappings)
+ PA_IDXSET_FOREACH(m, last->output_mappings, idx) {
+
+ if (!m->output_pcm)
+ break;
+
+ if (last->supported)
+ m->supported++;
+
+ if (!p->output_mappings || !pa_idxset_get_by_data(p->output_mappings, m, NULL)) {
+ snd_pcm_close(m->output_pcm);
+ m->output_pcm = NULL;
+ }
+ }
+
+ if (last && last->input_mappings)
+ PA_IDXSET_FOREACH(m, last->input_mappings, idx) {
+
+ if (!m->input_pcm)
+ break;
+
+ if (last->supported)
+ m->supported++;
+
+ if (!p->input_mappings || !pa_idxset_get_by_data(p->input_mappings, m, NULL)) {
+ snd_pcm_close(m->input_pcm);
+ m->input_pcm = NULL;
+ }
+ }
+
+ p->supported = TRUE;
+
+ /* Check if we can open all new ones */
+ if (p->output_mappings)
+ PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
+
+ if (m->output_pcm)
+ continue;
+
+ pa_log_debug("Checking for playback on %s (%s)", m->description, m->name);
+ try_map = m->channel_map;
+ try_ss = *ss;
+ try_ss.channels = try_map.channels;
+
+ try_period_size =
+ pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
+ pa_frame_size(&try_ss);
+ try_buffer_size = default_n_fragments * try_period_size;
+
+ if (!(m ->output_pcm = pa_alsa_open_by_template(
+ m->device_strings,
+ dev_id,
+ NULL,
+ &try_ss, &try_map,
+ SND_PCM_STREAM_PLAYBACK,
+ &try_period_size, &try_buffer_size, 0, NULL, NULL,
+ TRUE))) {
+ p->supported = FALSE;
+ break;
+ }
+ }
+
+ if (p->input_mappings && p->supported)
+ PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
+
+ if (m->input_pcm)
+ continue;
+
+ pa_log_debug("Checking for recording on %s (%s)", m->description, m->name);
+ try_map = m->channel_map;
+ try_ss = *ss;
+ try_ss.channels = try_map.channels;
+
+ try_period_size =
+ pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) /
+ pa_frame_size(&try_ss);
+ try_buffer_size = default_n_fragments * try_period_size;
+
+ if (!(m ->input_pcm = pa_alsa_open_by_template(
+ m->device_strings,
+ dev_id,
+ NULL,
+ &try_ss, &try_map,
+ SND_PCM_STREAM_CAPTURE,
+ &try_period_size, &try_buffer_size, 0, NULL, NULL,
+ TRUE))) {
+ p->supported = FALSE;
+ break;
+ }
+ }
+
+ last = p;
+
+ if (p->supported)
+ pa_log_debug("Profile %s supported.", p->name);
+ }
+
+ /* Clean up */
+ if (last) {
+ uint32_t idx;
+
+ if (last->output_mappings)
+ PA_IDXSET_FOREACH(m, last->output_mappings, idx)
+ if (m->output_pcm) {
+
+ if (last->supported)
+ m->supported++;
+
+ snd_pcm_close(m->output_pcm);
+ m->output_pcm = NULL;
+ }
+
+ if (last->input_mappings)
+ PA_IDXSET_FOREACH(m, last->input_mappings, idx)
+ if (m->input_pcm) {
+
+ if (last->supported)
+ m->supported++;
+
+ snd_pcm_close(m->input_pcm);
+ m->input_pcm = NULL;
+ }
+ }
+
+ PA_HASHMAP_FOREACH(p, ps->profiles, state)
+ if (!p->supported) {
+ pa_hashmap_remove(ps->profiles, p->name);
+ profile_free(p);
+ }
+
+ PA_HASHMAP_FOREACH(m, ps->mappings, state)
+ if (m->supported <= 0) {
+ pa_hashmap_remove(ps->mappings, m->name);
+ mapping_free(m);
+ }
+
+ ps->probed = TRUE;
+}
+
+void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
+ pa_alsa_profile *p;
+ pa_alsa_mapping *m;
+ pa_alsa_decibel_fix *db_fix;
+ void *state;
+
+ pa_assert(ps);
+
+ pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u",
+ (void*)
+ ps,
+ pa_yes_no(ps->auto_profiles),
+ pa_yes_no(ps->probed),
+ pa_hashmap_size(ps->mappings),
+ pa_hashmap_size(ps->profiles),
+ pa_hashmap_size(ps->decibel_fixes));
+
+ PA_HASHMAP_FOREACH(m, ps->mappings, state)
+ pa_alsa_mapping_dump(m);
+
+ PA_HASHMAP_FOREACH(p, ps->profiles, state)
+ pa_alsa_profile_dump(p);
+
+ PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
+ pa_alsa_decibel_fix_dump(db_fix);
+}
+
+void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) {
+ pa_alsa_path *path;
+
+ pa_assert(p);
+ pa_assert(!*p);
+ pa_assert(ps);
+
+ /* if there is no path, we don't want a port list */
+ if (!ps->paths)
+ return;
+
+ if (!ps->paths->next){
+ pa_alsa_setting *s;
+
+ /* If there is only one path, but no or only one setting, then
+ * we want a port list either */
+ if (!ps->paths->settings || !ps->paths->settings->next)
+ return;
+
+ /* Ok, there is only one path, however with multiple settings,
+ * so let's create a port for each setting */
+ *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ PA_LLIST_FOREACH(s, ps->paths->settings) {
+ pa_device_port *port;
+ pa_alsa_port_data *data;
+
+ port = pa_device_port_new(s->name, s->description, sizeof(pa_alsa_port_data));
+ port->priority = s->priority;
+
+ data = PA_DEVICE_PORT_DATA(port);
+ data->path = ps->paths;
+ data->setting = s;
+
+ pa_hashmap_put(*p, port->name, port);
+ }
+
+ } else {
+
+ /* We have multiple paths, so let's create a port for each
+ * one, and each of each settings */
+ *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ PA_LLIST_FOREACH(path, ps->paths) {
+
+ if (!path->settings || !path->settings->next) {
+ pa_device_port *port;
+ pa_alsa_port_data *data;
+
+ /* If there is no or just one setting we only need a
+ * single entry */
+
+ port = pa_device_port_new(path->name, path->description, sizeof(pa_alsa_port_data));
+ port->priority = path->priority * 100;
+
+
+ data = PA_DEVICE_PORT_DATA(port);
+ data->path = path;
+ data->setting = path->settings;
+
+ pa_hashmap_put(*p, port->name, port);
+ } else {
+ pa_alsa_setting *s;
+
+ PA_LLIST_FOREACH(s, path->settings) {
+ pa_device_port *port;
+ pa_alsa_port_data *data;
+ char *n, *d;
+
+ n = pa_sprintf_malloc("%s;%s", path->name, s->name);
+
+ if (s->description[0])
+ d = pa_sprintf_malloc(_("%s / %s"), path->description, s->description);
+ else
+ d = pa_xstrdup(path->description);
+
+ port = pa_device_port_new(n, d, sizeof(pa_alsa_port_data));
+ port->priority = path->priority * 100 + s->priority;
+
+ pa_xfree(n);
+ pa_xfree(d);
+
+ data = PA_DEVICE_PORT_DATA(port);
+ data->path = path;
+ data->setting = s;
+
+ pa_hashmap_put(*p, port->name, port);
+ }
+ }
+ }
+ }
+
+ pa_log_debug("Added %u ports", pa_hashmap_size(*p));
+}
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
new file mode 100644
index 00000000..d92d3e98
--- /dev/null
+++ b/src/modules/alsa/alsa-mixer.h
@@ -0,0 +1,328 @@
+#ifndef fooalsamixerhfoo
+#define fooalsamixerhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <asoundlib.h>
+
+#include <pulse/sample.h>
+#include <pulse/mainloop-api.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/llist.h>
+#include <pulsecore/rtpoll.h>
+
+typedef struct pa_alsa_fdlist pa_alsa_fdlist;
+typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
+typedef struct pa_alsa_setting pa_alsa_setting;
+typedef struct pa_alsa_option pa_alsa_option;
+typedef struct pa_alsa_element pa_alsa_element;
+typedef struct pa_alsa_path pa_alsa_path;
+typedef struct pa_alsa_path_set pa_alsa_path_set;
+typedef struct pa_alsa_mapping pa_alsa_mapping;
+typedef struct pa_alsa_profile pa_alsa_profile;
+typedef struct pa_alsa_decibel_fix pa_alsa_decibel_fix;
+typedef struct pa_alsa_profile_set pa_alsa_profile_set;
+typedef struct pa_alsa_port_data pa_alsa_port_data;
+
+#include "alsa-util.h"
+
+typedef enum pa_alsa_switch_use {
+ PA_ALSA_SWITCH_IGNORE,
+ PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */
+ PA_ALSA_SWITCH_OFF, /* set this switch to 'off' unconditionally */
+ PA_ALSA_SWITCH_ON, /* set this switch to 'on' unconditionally */
+ PA_ALSA_SWITCH_SELECT /* allow the user to select switch status through a setting */
+} pa_alsa_switch_use_t;
+
+typedef enum pa_alsa_volume_use {
+ PA_ALSA_VOLUME_IGNORE,
+ PA_ALSA_VOLUME_MERGE, /* merge this volume slider into the global volume slider */
+ PA_ALSA_VOLUME_OFF, /* set this volume to minimal unconditionally */
+ PA_ALSA_VOLUME_ZERO, /* set this volume to 0dB unconditionally */
+ PA_ALSA_VOLUME_CONSTANT /* set this volume to a constant value unconditionally */
+} pa_alsa_volume_use_t;
+
+typedef enum pa_alsa_enumeration_use {
+ PA_ALSA_ENUMERATION_IGNORE,
+ PA_ALSA_ENUMERATION_SELECT
+} pa_alsa_enumeration_use_t;
+
+typedef enum pa_alsa_required {
+ PA_ALSA_REQUIRED_IGNORE,
+ PA_ALSA_REQUIRED_SWITCH,
+ PA_ALSA_REQUIRED_VOLUME,
+ PA_ALSA_REQUIRED_ENUMERATION,
+ PA_ALSA_REQUIRED_ANY
+} pa_alsa_required_t;
+
+typedef enum pa_alsa_direction {
+ PA_ALSA_DIRECTION_ANY,
+ PA_ALSA_DIRECTION_OUTPUT,
+ PA_ALSA_DIRECTION_INPUT
+} pa_alsa_direction_t;
+
+/* A setting combines a couple of options into a single entity that
+ * may be selected. Only one setting can be active at the same
+ * time. */
+struct pa_alsa_setting {
+ pa_alsa_path *path;
+ PA_LLIST_FIELDS(pa_alsa_setting);
+
+ pa_idxset *options;
+
+ char *name;
+ char *description;
+ unsigned priority;
+};
+
+/* An option belongs to an element and refers to one enumeration item
+ * of the element is an enumeration item, or a switch status if the
+ * element is a switch item. */
+struct pa_alsa_option {
+ pa_alsa_element *element;
+ PA_LLIST_FIELDS(pa_alsa_option);
+
+ char *alsa_name;
+ int alsa_idx;
+
+ char *name;
+ char *description;
+ unsigned priority;
+
+ pa_alsa_required_t required;
+ pa_alsa_required_t required_any;
+ pa_alsa_required_t required_absent;
+};
+
+/* An element wraps one specific ALSA element. A series of elements
+ * make up a path (see below). If the element is an enumeration or switch
+ * element it may include a list of options. */
+struct pa_alsa_element {
+ pa_alsa_path *path;
+ PA_LLIST_FIELDS(pa_alsa_element);
+
+ char *alsa_name;
+ pa_alsa_direction_t direction;
+
+ pa_alsa_switch_use_t switch_use;
+ pa_alsa_volume_use_t volume_use;
+ pa_alsa_enumeration_use_t enumeration_use;
+
+ pa_alsa_required_t required;
+ pa_alsa_required_t required_any;
+ pa_alsa_required_t required_absent;
+
+ long constant_volume;
+
+ pa_bool_t override_map:1;
+ pa_bool_t direction_try_other:1;
+
+ pa_bool_t has_dB:1;
+ long min_volume, max_volume;
+ long volume_limit; /* -1 for no configured limit */
+ double min_dB, max_dB;
+
+ pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2];
+ unsigned n_channels;
+
+ pa_channel_position_mask_t merged_mask;
+
+ PA_LLIST_HEAD(pa_alsa_option, options);
+
+ pa_alsa_decibel_fix *db_fix;
+};
+
+/* A path wraps a series of elements into a single entity which can be
+ * used to control it as if it had a single volume slider, a single
+ * mute switch and a single list of selectable options. */
+struct pa_alsa_path {
+ pa_alsa_path_set *path_set;
+ PA_LLIST_FIELDS(pa_alsa_path);
+
+ pa_alsa_direction_t direction;
+
+ char *name;
+ char *description;
+ unsigned priority;
+
+ pa_bool_t probed:1;
+ pa_bool_t supported:1;
+ pa_bool_t has_mute:1;
+ pa_bool_t has_volume:1;
+ pa_bool_t has_dB:1;
+ /* These two are used during probing only */
+ pa_bool_t has_req_any:1;
+ pa_bool_t req_any_present:1;
+
+ long min_volume, max_volume;
+ double min_dB, max_dB;
+
+ /* This is used during parsing only, as a shortcut so that we
+ * don't have to iterate the list all the time */
+ pa_alsa_element *last_element;
+ pa_alsa_option *last_option;
+ pa_alsa_setting *last_setting;
+
+ PA_LLIST_HEAD(pa_alsa_element, elements);
+ PA_LLIST_HEAD(pa_alsa_setting, settings);
+};
+
+/* A path set is simply a set of paths that are applicable to a
+ * device */
+struct pa_alsa_path_set {
+ PA_LLIST_HEAD(pa_alsa_path, paths);
+ pa_alsa_direction_t direction;
+ pa_bool_t probed:1;
+
+ /* This is used during parsing only, as a shortcut so that we
+ * don't have to iterate the list all the time */
+ pa_alsa_path *last_path;
+};
+
+int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m);
+void pa_alsa_setting_dump(pa_alsa_setting *s);
+
+void pa_alsa_option_dump(pa_alsa_option *o);
+
+void pa_alsa_element_dump(pa_alsa_element *e);
+
+pa_alsa_path *pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction);
+pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
+int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB);
+void pa_alsa_path_dump(pa_alsa_path *p);
+int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
+int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted);
+int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw);
+int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t muted);
+int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m);
+void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
+void pa_alsa_path_free(pa_alsa_path *p);
+
+pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction);
+void pa_alsa_path_set_probe(pa_alsa_path_set *s, snd_mixer_t *m, pa_bool_t ignore_dB);
+void pa_alsa_path_set_dump(pa_alsa_path_set *s);
+void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
+void pa_alsa_path_set_free(pa_alsa_path_set *s);
+
+struct pa_alsa_mapping {
+ pa_alsa_profile_set *profile_set;
+
+ char *name;
+ char *description;
+ unsigned priority;
+ pa_alsa_direction_t direction;
+
+ pa_channel_map channel_map;
+
+ char **device_strings;
+
+ char **input_path_names;
+ char **output_path_names;
+ char **input_element; /* list of fallbacks */
+ char **output_element;
+
+ unsigned supported;
+
+ /* Temporarily used during probing */
+ snd_pcm_t *input_pcm;
+ snd_pcm_t *output_pcm;
+
+ pa_sink *sink;
+ pa_source *source;
+};
+
+struct pa_alsa_profile {
+ pa_alsa_profile_set *profile_set;
+
+ char *name;
+ char *description;
+ unsigned priority;
+
+ pa_bool_t supported:1;
+
+ char **input_mapping_names;
+ char **output_mapping_names;
+
+ pa_idxset *input_mappings;
+ pa_idxset *output_mappings;
+};
+
+struct pa_alsa_decibel_fix {
+ pa_alsa_profile_set *profile_set;
+
+ char *name; /* Alsa volume element name. */
+ long min_step;
+ long max_step;
+
+ /* An array that maps alsa volume element steps to decibels. The steps can
+ * be used as indices to this array, after substracting min_step from the
+ * real value.
+ *
+ * The values are actually stored as integers representing millibels,
+ * because that's the format the alsa API uses. */
+ long *db_values;
+};
+
+struct pa_alsa_profile_set {
+ pa_hashmap *mappings;
+ pa_hashmap *profiles;
+ pa_hashmap *decibel_fixes;
+
+ pa_bool_t auto_profiles;
+ pa_bool_t probed:1;
+};
+
+void pa_alsa_mapping_dump(pa_alsa_mapping *m);
+void pa_alsa_profile_dump(pa_alsa_profile *p);
+void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix);
+
+pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
+void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
+void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
+void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
+
+snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device);
+
+pa_alsa_fdlist *pa_alsa_fdlist_new(void);
+void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
+int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
+
+/* Alternative for handling alsa mixer events in io-thread. */
+
+pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void);
+void pa_alsa_mixer_pdata_free(pa_alsa_mixer_pdata *pd);
+int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp);
+
+/* Data structure for inclusion in pa_device_port for alsa
+ * sinks/sources. This contains nothing that needs to be freed
+ * individually */
+struct pa_alsa_port_data {
+ pa_alsa_path *path;
+ pa_alsa_setting *setting;
+};
+
+void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps);
+
+#endif
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
new file mode 100644
index 00000000..0164040d
--- /dev/null
+++ b/src/modules/alsa/alsa-sink.c
@@ -0,0 +1,2246 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 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 <stdio.h>
+
+#include <asoundlib.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <pulse/i18n.h>
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/time-smoother.h>
+
+#include <modules/reserve-wrap.h>
+
+#include "alsa-util.h"
+#include "alsa-sink.h"
+
+/* #define DEBUG_TIMING */
+
+#define DEFAULT_DEVICE "default"
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms -- If the buffer level ever below this theshold, increase the watermark */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */
+
+/* Note that TSCHED_WATERMARK_INC_THRESHOLD_USEC == 0 means tht we
+ * will increase the watermark only if we hit a real underrun. */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
+
+#define SMOOTHER_WINDOW_USEC (10*PA_USEC_PER_SEC) /* 10s -- smoother windows size */
+#define SMOOTHER_ADJUST_USEC (1*PA_USEC_PER_SEC) /* 1s -- smoother adjust time */
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update interval */
+
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
+
+#define DEFAULT_REWIND_SAFEGUARD_BYTES (256U) /* 1.33ms @48kHz, we'll never rewind less than this */
+#define DEFAULT_REWIND_SAFEGUARD_USEC (1330) /* 1.33ms, depending on channels/rate/sample we may rewind more than 256 above */
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ snd_pcm_t *pcm_handle;
+
+ pa_alsa_fdlist *mixer_fdl;
+ pa_alsa_mixer_pdata *mixer_pd;
+ snd_mixer_t *mixer_handle;
+ pa_alsa_path_set *mixer_path_set;
+ pa_alsa_path *mixer_path;
+
+ pa_cvolume hardware_volume;
+
+ uint32_t old_rate;
+
+ size_t
+ frame_size,
+ fragment_size,
+ hwbuf_size,
+ tsched_watermark,
+ hwbuf_unused,
+ min_sleep,
+ min_wakeup,
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold,
+ rewind_safeguard;
+
+ pa_usec_t watermark_dec_not_before;
+
+ pa_memchunk memchunk;
+
+ char *device_name; /* name of the PCM device */
+ char *control_device; /* name of the control device */
+
+ pa_bool_t use_mmap:1, use_tsched:1;
+
+ pa_bool_t first, after_rewind;
+
+ pa_rtpoll_item *alsa_rtpoll_item;
+
+ snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
+
+ pa_smoother *smoother;
+ uint64_t write_count;
+ uint64_t since_start;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
+
+ pa_reserve_wrapper *reserve;
+ pa_hook_slot *reserve_slot;
+ pa_reserve_monitor_wrapper *monitor;
+ pa_hook_slot *monitor_slot;
+};
+
+static void userdata_free(struct userdata *u);
+
+static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
+ pa_assert(r);
+ pa_assert(u);
+
+ if (pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_APPLICATION) < 0)
+ return PA_HOOK_CANCEL;
+
+ return PA_HOOK_OK;
+}
+
+static void reserve_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->reserve_slot) {
+ pa_hook_slot_free(u->reserve_slot);
+ u->reserve_slot = NULL;
+ }
+
+ if (u->reserve) {
+ pa_reserve_wrapper_unref(u->reserve);
+ u->reserve = NULL;
+ }
+}
+
+static void reserve_update(struct userdata *u) {
+ const char *description;
+ pa_assert(u);
+
+ if (!u->sink || !u->reserve)
+ return;
+
+ if ((description = pa_proplist_gets(u->sink->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+ pa_reserve_wrapper_set_application_device_name(u->reserve, description);
+}
+
+static int reserve_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (u->reserve)
+ return 0;
+
+ if (pa_in_system_mode())
+ return 0;
+
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ u->reserve = pa_reserve_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->reserve))
+ return -1;
+
+ reserve_update(u);
+
+ pa_assert(!u->reserve_slot);
+ u->reserve_slot = pa_hook_connect(pa_reserve_wrapper_hook(u->reserve), PA_HOOK_NORMAL, (pa_hook_cb_t) reserve_cb, u);
+
+ return 0;
+}
+
+static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) {
+ pa_bool_t b;
+
+ pa_assert(w);
+ pa_assert(u);
+
+ b = PA_PTR_TO_UINT(busy) && !u->reserve;
+
+ pa_sink_suspend(u->sink, b, PA_SUSPEND_APPLICATION);
+ return PA_HOOK_OK;
+}
+
+static void monitor_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->monitor_slot) {
+ pa_hook_slot_free(u->monitor_slot);
+ u->monitor_slot = NULL;
+ }
+
+ if (u->monitor) {
+ pa_reserve_monitor_wrapper_unref(u->monitor);
+ u->monitor = NULL;
+ }
+}
+
+static int reserve_monitor_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (pa_in_system_mode())
+ return 0;
+
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->monitor))
+ return -1;
+
+ pa_assert(!u->monitor_slot);
+ u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u);
+
+ return 0;
+}
+
+static void fix_min_sleep_wakeup(struct userdata *u) {
+ size_t max_use, max_use_2;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+ max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec);
+
+ u->min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec);
+ u->min_sleep = PA_CLAMP(u->min_sleep, u->frame_size, max_use_2);
+
+ u->min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec);
+ u->min_wakeup = PA_CLAMP(u->min_wakeup, u->frame_size, max_use_2);
+}
+
+static void fix_tsched_watermark(struct userdata *u) {
+ size_t max_use;
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+
+ if (u->tsched_watermark > max_use - u->min_sleep)
+ u->tsched_watermark = max_use - u->min_sleep;
+
+ if (u->tsched_watermark < u->min_wakeup)
+ u->tsched_watermark = u->min_wakeup;
+}
+
+static void increase_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t old_min_latency, new_min_latency;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ /* First, just try to increase the watermark */
+ old_watermark = u->tsched_watermark;
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark) {
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+ return;
+ }
+
+ /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
+ old_min_latency = u->sink->thread_info.min_latency;
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
+ new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency);
+
+ if (old_min_latency != new_min_latency) {
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
+
+ pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);
+ }
+
+ /* When we reach this we're officialy fucked! */
+}
+
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
+static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
+ pa_usec_t usec, wm;
+
+ pa_assert(sleep_usec);
+ pa_assert(process_usec);
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ usec = pa_sink_get_requested_latency_within_thread(u->sink);
+
+ if (usec == (pa_usec_t) -1)
+ usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec);
+
+ wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec);
+
+ if (wm > usec)
+ wm = usec/2;
+
+ *sleep_usec = usec - wm;
+ *process_usec = wm;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Buffer time: %lu ms; Sleep time: %lu ms; Process time: %lu ms",
+ (unsigned long) (usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*sleep_usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*process_usec / PA_USEC_PER_MSEC));
+#endif
+}
+
+static int try_recover(struct userdata *u, const char *call, int err) {
+ pa_assert(u);
+ pa_assert(call);
+ pa_assert(err < 0);
+
+ pa_log_debug("%s: %s", call, pa_alsa_strerror(err));
+
+ pa_assert(err != -EAGAIN);
+
+ if (err == -EPIPE)
+ pa_log_debug("%s: Buffer underrun!", call);
+
+ if (err == -ESTRPIPE)
+ pa_log_debug("%s: System suspended!", call);
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
+ pa_log("%s: %s", call, pa_alsa_strerror(err));
+ return -1;
+ }
+
+ u->first = TRUE;
+ u->since_start = 0;
+ return 0;
+}
+
+static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
+ size_t left_to_play;
+ pa_bool_t underrun = FALSE;
+
+ /* We use <= instead of < for this check here because an underrun
+ * only happens after the last sample was processed, not already when
+ * it is removed from the buffer. This is particularly important
+ * when block transfer is used. */
+
+ if (n_bytes <= u->hwbuf_size)
+ left_to_play = u->hwbuf_size - n_bytes;
+ else {
+
+ /* We got a dropout. What a mess! */
+ left_to_play = 0;
+ underrun = TRUE;
+
+#ifdef DEBUG_TIMING
+ PA_DEBUG_TRAP;
+#endif
+
+ if (!u->first && !u->after_rewind)
+ if (pa_log_ratelimit(PA_LOG_INFO))
+ pa_log_info("Underrun!");
+ }
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms",
+ (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+#endif
+
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
+ if (!u->first && !u->after_rewind) {
+ if (underrun || left_to_play < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_play > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
+
+ /* We decrease the watermark only if have actually
+ * been woken up by a timeout. If something else woke
+ * us up it's too easy to fulfill the deadlines... */
+
+ if (on_timeout)
+ decrease_watermark(u);
+ }
+ }
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
+ }
+
+ return left_to_play;
+}
+
+static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
+ pa_bool_t work_done = FALSE;
+ pa_usec_t max_sleep_usec = 0, process_usec = 0;
+ size_t left_to_play;
+ unsigned j = 0;
+
+ pa_assert(u);
+ pa_sink_assert_ref(u->sink);
+
+ if (u->use_tsched)
+ hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
+ for (;;) {
+ snd_pcm_sframes_t n;
+ size_t n_bytes;
+ int r;
+ pa_bool_t after_avail = TRUE;
+
+ /* First we determine how many samples are missing to fill the
+ * buffer up to 100% */
+
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
+
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ continue;
+
+ return r;
+ }
+
+ n_bytes = (size_t) n * u->frame_size;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("avail: %lu", (unsigned long) n_bytes);
+#endif
+
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
+
+ if (u->use_tsched)
+
+ /* We won't fill up the playback buffer before at least
+ * half the sleep time is over because otherwise we might
+ * ask for more data from the clients then they expect. We
+ * need to guarantee that clients only have to keep around
+ * a single hw buffer length. */
+
+ if (!polled &&
+ pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because too early.");
+#endif
+ break;
+ }
+
+ if (PA_UNLIKELY(n_bytes <= u->hwbuf_unused)) {
+
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to write new data to the device, but there was actually nothing to write!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because not necessary.");
+#endif
+ break;
+ }
+
+
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
+
+ break;
+ }
+
+ n_bytes -= u->hwbuf_unused;
+ polled = FALSE;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Filling up");
+#endif
+
+ for (;;) {
+ pa_memchunk chunk;
+ void *p;
+ int err;
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t offset, frames;
+ snd_pcm_sframes_t sframes;
+
+ frames = (snd_pcm_uframes_t) (n_bytes / u->frame_size);
+/* pa_log_debug("%lu frames to write", (unsigned long) frames); */
+
+ if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
+
+ if (!after_avail && err == -EAGAIN)
+ break;
+
+ if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
+ continue;
+
+ return r;
+ }
+
+ /* Make sure that if these memblocks need to be copied they will fit into one slot */
+ if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size)
+ frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size;
+
+ if (!after_avail && frames == 0)
+ break;
+
+ pa_assert(frames > 0);
+ after_avail = FALSE;
+
+ /* Check these are multiples of 8 bit */
+ pa_assert((areas[0].first & 7) == 0);
+ pa_assert((areas[0].step & 7)== 0);
+
+ /* We assume a single interleaved memory buffer */
+ pa_assert((areas[0].first >> 3) == 0);
+ pa_assert((areas[0].step >> 3) == u->frame_size);
+
+ p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+
+ chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE);
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_sink_render_into_full(u->sink, &chunk);
+ pa_memblock_unref_fixed(chunk.memblock);
+
+ if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) {
+
+ if (!after_avail && (int) sframes == -EAGAIN)
+ break;
+
+ if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0)
+ continue;
+
+ return r;
+ }
+
+ work_done = TRUE;
+
+ u->write_count += frames * u->frame_size;
+ u->since_start += frames * u->frame_size;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Wrote %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes);
+#endif
+
+ if ((size_t) frames * u->frame_size >= n_bytes)
+ break;
+
+ n_bytes -= (size_t) frames * u->frame_size;
+ }
+ }
+
+ if (u->use_tsched) {
+ *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+ } else
+ *sleep_usec = 0;
+
+ return work_done ? 1 : 0;
+}
+
+static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
+ pa_bool_t work_done = FALSE;
+ pa_usec_t max_sleep_usec = 0, process_usec = 0;
+ size_t left_to_play;
+ unsigned j = 0;
+
+ pa_assert(u);
+ pa_sink_assert_ref(u->sink);
+
+ if (u->use_tsched)
+ hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
+ for (;;) {
+ snd_pcm_sframes_t n;
+ size_t n_bytes;
+ int r;
+ pa_bool_t after_avail = TRUE;
+
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
+
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ continue;
+
+ return r;
+ }
+
+ n_bytes = (size_t) n * u->frame_size;
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
+
+ if (u->use_tsched)
+
+ /* We won't fill up the playback buffer before at least
+ * half the sleep time is over because otherwise we might
+ * ask for more data from the clients then they expect. We
+ * need to guarantee that clients only have to keep around
+ * a single hw buffer length. */
+
+ if (!polled &&
+ pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2)
+ break;
+
+ if (PA_UNLIKELY(n_bytes <= u->hwbuf_unused)) {
+
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to write new data to the device, but there was actually nothing to write!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
+
+ break;
+ }
+
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
+
+ break;
+ }
+
+ n_bytes -= u->hwbuf_unused;
+ polled = FALSE;
+
+ for (;;) {
+ snd_pcm_sframes_t frames;
+ void *p;
+
+/* pa_log_debug("%lu frames to write", (unsigned long) frames); */
+
+ if (u->memchunk.length <= 0)
+ pa_sink_render(u->sink, n_bytes, &u->memchunk);
+
+ pa_assert(u->memchunk.length > 0);
+
+ frames = (snd_pcm_sframes_t) (u->memchunk.length / u->frame_size);
+
+ if (frames > (snd_pcm_sframes_t) (n_bytes/u->frame_size))
+ frames = (snd_pcm_sframes_t) (n_bytes/u->frame_size);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames);
+ pa_memblock_release(u->memchunk.memblock);
+
+ if (PA_UNLIKELY(frames < 0)) {
+
+ if (!after_avail && (int) frames == -EAGAIN)
+ break;
+
+ if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0)
+ continue;
+
+ return r;
+ }
+
+ if (!after_avail && frames == 0)
+ break;
+
+ pa_assert(frames > 0);
+ after_avail = FALSE;
+
+ u->memchunk.index += (size_t) frames * u->frame_size;
+ u->memchunk.length -= (size_t) frames * u->frame_size;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ work_done = TRUE;
+
+ u->write_count += frames * u->frame_size;
+ u->since_start += frames * u->frame_size;
+
+/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */
+
+ if ((size_t) frames * u->frame_size >= n_bytes)
+ break;
+
+ n_bytes -= (size_t) frames * u->frame_size;
+ }
+ }
+
+ if (u->use_tsched) {
+ *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+ } else
+ *sleep_usec = 0;
+
+ return work_done ? 1 : 0;
+}
+
+static void update_smoother(struct userdata *u) {
+ snd_pcm_sframes_t delay = 0;
+ int64_t position;
+ int err;
+ pa_usec_t now1 = 0, now2;
+ snd_pcm_status_t *status;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ /* Let's update the time smoother */
+
+ if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->sink->sample_spec, FALSE)) < 0)) {
+ pa_log_warn("Failed to query DSP status data: %s", pa_alsa_strerror(err));
+ return;
+ }
+
+ if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0))
+ pa_log_warn("Failed to get timestamp: %s", pa_alsa_strerror(err));
+ else {
+ snd_htimestamp_t htstamp = { 0, 0 };
+ snd_pcm_status_get_htstamp(status, &htstamp);
+ now1 = pa_timespec_load(&htstamp);
+ }
+
+ /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
+ if (now1 <= 0)
+ now1 = pa_rtclock_now();
+
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
+ position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size);
+
+ if (PA_UNLIKELY(position < 0))
+ position = 0;
+
+ now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec);
+
+ pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
+}
+
+static pa_usec_t sink_get_latency(struct userdata *u) {
+ pa_usec_t r;
+ int64_t delay;
+ pa_usec_t now1, now2;
+
+ pa_assert(u);
+
+ now1 = pa_rtclock_now();
+ now2 = pa_smoother_get(u->smoother, now1);
+
+ delay = (int64_t) pa_bytes_to_usec(u->write_count, &u->sink->sample_spec) - (int64_t) now2;
+
+ r = delay >= 0 ? (pa_usec_t) delay : 0;
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+
+ return r;
+}
+
+static int build_pollfd(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll)))
+ return -1;
+
+ return 0;
+}
+
+/* Called from IO context */
+static int suspend(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
+
+ /* Let's suspend -- we don't call snd_pcm_drain() here since that might
+ * take awfully long with our long buffer sizes today. */
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+
+ /* We reset max_rewind/max_request here to make sure that while we
+ * are suspended the old max_request/max_rewind values set before
+ * the suspend can influence the per-stream buffer of newly
+ * created streams, without their requirements having any
+ * influence on them. */
+ pa_sink_set_max_rewind_within_thread(u->sink, 0);
+ pa_sink_set_max_request_within_thread(u->sink, 0);
+
+ pa_log_info("Device suspended...");
+
+ return 0;
+}
+
+/* Called from IO context */
+static int update_sw_params(struct userdata *u) {
+ snd_pcm_uframes_t avail_min;
+ int err;
+
+ pa_assert(u);
+
+ /* Use the full buffer if noone asked us for anything specific */
+ u->hwbuf_unused = 0;
+
+ if (u->use_tsched) {
+ pa_usec_t latency;
+
+ if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) {
+ size_t b;
+
+ pa_log_debug("Latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC);
+
+ b = pa_usec_to_bytes(latency, &u->sink->sample_spec);
+
+ /* We need at least one sample in our buffer */
+
+ if (PA_UNLIKELY(b < u->frame_size))
+ b = u->frame_size;
+
+ u->hwbuf_unused = PA_LIKELY(b < u->hwbuf_size) ? (u->hwbuf_size - b) : 0;
+ }
+
+ fix_min_sleep_wakeup(u);
+ fix_tsched_watermark(u);
+ }
+
+ pa_log_debug("hwbuf_unused=%lu", (unsigned long) u->hwbuf_unused);
+
+ /* We need at last one frame in the used part of the buffer */
+ avail_min = (snd_pcm_uframes_t) u->hwbuf_unused / u->frame_size + 1;
+
+ if (u->use_tsched) {
+ pa_usec_t sleep_usec, process_usec;
+
+ hw_sleep_time(u, &sleep_usec, &process_usec);
+ avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec) / u->frame_size;
+ }
+
+ pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min);
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) {
+ pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));
+ return err;
+ }
+
+ pa_sink_set_max_request_within_thread(u->sink, u->hwbuf_size - u->hwbuf_unused);
+ if (pa_alsa_pcm_is_hw(u->pcm_handle))
+ pa_sink_set_max_rewind_within_thread(u->sink, u->hwbuf_size);
+ else {
+ pa_log_info("Disabling rewind_within_thread for device %s", u->device_name);
+ pa_sink_set_max_rewind_within_thread(u->sink, 0);
+ }
+
+ return 0;
+}
+
+/* Called from IO context */
+static int unsuspend(struct userdata *u) {
+ pa_sample_spec ss;
+ int err;
+ pa_bool_t b, d;
+ snd_pcm_uframes_t period_size, buffer_size;
+
+ pa_assert(u);
+ pa_assert(!u->pcm_handle);
+
+ pa_log_info("Trying resume...");
+
+ if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK|
+ SND_PCM_NO_AUTO_RESAMPLE|
+ SND_PCM_NO_AUTO_CHANNELS|
+ SND_PCM_NO_AUTO_FORMAT)) < 0) {
+ pa_log("Error opening PCM device %s: %s", u->device_name, pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ ss = u->sink->sample_spec;
+ period_size = u->fragment_size / u->frame_size;
+ buffer_size = u->hwbuf_size / u->frame_size;
+ b = u->use_mmap;
+ d = u->use_tsched;
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
+ pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ if (b != u->use_mmap || d != u->use_tsched) {
+ pa_log_warn("Resume failed, couldn't get original access mode.");
+ goto fail;
+ }
+
+ if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) {
+ pa_log_warn("Resume failed, couldn't restore original sample settings.");
+ goto fail;
+ }
+
+ if (period_size*u->frame_size != u->fragment_size ||
+ buffer_size*u->frame_size != u->hwbuf_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
+ (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
+ (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size));
+ goto fail;
+ }
+
+ if (update_sw_params(u) < 0)
+ goto fail;
+
+ if (build_pollfd(u) < 0)
+ goto fail;
+
+ u->write_count = 0;
+ pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
+
+ u->first = TRUE;
+ u->since_start = 0;
+
+ pa_log_info("Resumed successfully...");
+
+ return 0;
+
+fail:
+ if (u->pcm_handle) {
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+ }
+
+ return -PA_ERR_IO;
+}
+
+/* Called from IO context */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_FINISH_MOVE:
+ case PA_SINK_MESSAGE_ADD_INPUT: {
+ pa_sink_input *i = PA_SINK_INPUT(data);
+ int r = 0;
+
+ if (PA_LIKELY(!pa_sink_input_is_passthrough(i)))
+ break;
+
+ u->old_rate = u->sink->sample_spec.rate;
+
+ /* Passthrough format, see if we need to reset sink sample rate */
+ if (u->sink->sample_spec.rate == i->thread_info.sample_spec.rate)
+ break;
+
+ /* .. we do */
+ if ((r = suspend(u)) < 0)
+ return r;
+
+ u->sink->sample_spec.rate = i->thread_info.sample_spec.rate;
+
+ if ((r = unsuspend(u)) < 0)
+ return r;
+
+ break;
+ }
+
+ case PA_SINK_MESSAGE_START_MOVE:
+ case PA_SINK_MESSAGE_REMOVE_INPUT: {
+ pa_sink_input *i = PA_SINK_INPUT(data);
+ int r = 0;
+
+ if (PA_LIKELY(!pa_sink_input_is_passthrough(i)))
+ break;
+
+ /* Passthrough format, see if we need to reset sink sample rate */
+ if (u->sink->sample_spec.rate == u->old_rate)
+ break;
+
+ /* .. we do */
+ if ((r = suspend(u)) < 0)
+ return r;
+
+ u->sink->sample_spec.rate = u->old_rate;
+
+ if ((r = unsuspend(u)) < 0)
+ return r;
+
+ break;
+ }
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->pcm_handle)
+ r = sink_get_latency(u);
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SINK_SUSPENDED: {
+ int r;
+
+ pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+
+ if ((r = suspend(u)) < 0)
+ return r;
+
+ break;
+ }
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING: {
+ int r;
+
+ if (u->sink->thread_info.state == PA_SINK_INIT) {
+ if (build_pollfd(u) < 0)
+ return -PA_ERR_IO;
+ }
+
+ if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+ if ((r = unsuspend(u)) < 0)
+ return r;
+ }
+
+ break;
+ }
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ case PA_SINK_INVALID_STATE:
+ ;
+ }
+
+ break;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) {
+ pa_sink_state_t old_state;
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ old_state = pa_sink_get_state(u->sink);
+
+ if (PA_SINK_IS_OPENED(old_state) && new_state == PA_SINK_SUSPENDED)
+ reserve_done(u);
+ else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
+ if (reserve_init(u, u->device_name) < 0)
+ return -PA_ERR_BUSY;
+
+ return 0;
+}
+
+static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (u->sink->suspend_cause & PA_SUSPEND_SESSION)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE) {
+ pa_sink_get_volume(u->sink, TRUE);
+ pa_sink_get_mute(u->sink, TRUE);
+ }
+
+ return 0;
+}
+
+static int io_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (u->sink->suspend_cause & PA_SUSPEND_SESSION)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE)
+ pa_sink_update_volume_and_mute(u->sink);
+
+ return 0;
+}
+
+static void sink_get_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume r;
+ char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX];
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+ return;
+
+ /* 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(vol_str_pcnt, sizeof(vol_str_pcnt), &r));
+
+ if (u->mixer_path->has_dB) {
+ char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX];
+
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &r));
+ }
+
+ if (pa_cvolume_equal(&u->hardware_volume, &r))
+ return;
+
+ s->real_volume = u->hardware_volume = r;
+
+ /* Hmm, so the hardware volume changed, let's reset our software volume */
+ if (u->mixer_path->has_dB)
+ pa_sink_set_soft_volume(s, NULL);
+}
+
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume r;
+ char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX];
+ pa_bool_t sync_volume = !!(s->flags & PA_SINK_SYNC_VOLUME);
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ /* Shift up by the base volume */
+ pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
+
+ if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, sync_volume, !sync_volume) < 0)
+ return;
+
+ /* 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->mixer_path->has_dB) {
+ pa_cvolume new_soft_volume;
+ pa_bool_t accurate_enough;
+ char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX];
+
+ /* Match exactly what the user requested by software */
+ pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume);
+
+ /* If the adjustment to do in software is only minimal we
+ * can skip it. That saves us CPU at the expense of a bit of
+ * accuracy */
+ accurate_enough =
+ (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+
+ pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->real_volume));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->real_volume));
+ pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &u->hardware_volume));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &u->hardware_volume));
+ pa_log_debug("Calculated software volume: %s (accurate-enough=%s)",
+ pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &new_soft_volume),
+ pa_yes_no(accurate_enough));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &new_soft_volume));
+
+ if (!accurate_enough)
+ s->soft_volume = new_soft_volume;
+
+ } else {
+ pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r));
+
+ /* We can't match exactly what the user requested, hence let's
+ * at least tell the user about it */
+
+ s->real_volume = r;
+ }
+}
+
+static void sink_write_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume hw_vol = s->thread_info.current_hw_volume;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+ pa_assert(s->flags & PA_SINK_SYNC_VOLUME);
+
+ /* Shift up by the base volume */
+ pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume);
+
+ if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &hw_vol, TRUE, TRUE) < 0)
+ pa_log_error("Writing HW volume failed");
+ else {
+ pa_cvolume tmp_vol;
+ pa_bool_t accurate_enough;
+
+ /* Shift down by the base volume, so that 0dB becomes maximum volume */
+ pa_sw_cvolume_multiply_scalar(&hw_vol, &hw_vol, s->base_volume);
+
+ pa_sw_cvolume_divide(&tmp_vol, &hw_vol, &s->thread_info.current_hw_volume);
+ accurate_enough =
+ (pa_cvolume_min(&tmp_vol) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&tmp_vol) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+
+ if (!accurate_enough) {
+ union {
+ char db[2][PA_SW_CVOLUME_SNPRINT_DB_MAX];
+ char pcnt[2][PA_CVOLUME_SNPRINT_MAX];
+ } vol;
+
+ pa_log_debug("Written HW volume did not match with the request: %s (request) != %s",
+ pa_cvolume_snprint(vol.pcnt[0], sizeof(vol.pcnt[0]), &s->thread_info.current_hw_volume),
+ pa_cvolume_snprint(vol.pcnt[1], sizeof(vol.pcnt[1]), &hw_vol));
+ pa_log_debug(" in dB: %s (request) != %s",
+ pa_sw_cvolume_snprint_dB(vol.db[0], sizeof(vol.db[0]), &s->thread_info.current_hw_volume),
+ pa_sw_cvolume_snprint_dB(vol.db[1], sizeof(vol.db[1]), &hw_vol));
+ }
+ }
+}
+
+static void sink_get_mute_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ pa_bool_t b;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)
+ return;
+
+ s->muted = b;
+}
+
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
+}
+
+static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
+ struct userdata *u = s->userdata;
+ pa_alsa_port_data *data;
+
+ pa_assert(u);
+ pa_assert(p);
+ pa_assert(u->mixer_handle);
+
+ data = PA_DEVICE_PORT_DATA(p);
+
+ pa_assert_se(u->mixer_path = data->path);
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
+
+ if (u->mixer_path->has_volume && u->mixer_path->has_dB) {
+ s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+ s->n_volume_steps = PA_VOLUME_NORM+1;
+
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume));
+ } else {
+ s->base_volume = PA_VOLUME_NORM;
+ s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+ }
+
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
+
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->set_volume)
+ s->set_volume(s);
+
+ return 0;
+}
+
+static void sink_update_requested_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ size_t before;
+ pa_assert(u);
+ pa_assert(u->use_tsched); /* only when timer scheduling is used
+ * we can dynamically adjust the
+ * latency */
+
+ if (!u->pcm_handle)
+ return;
+
+ before = u->hwbuf_unused;
+ update_sw_params(u);
+
+ /* Let's check whether we now use only a smaller part of the
+ buffer then before. If so, we need to make sure that subsequent
+ rewinds are relative to the new maximum fill level and not to the
+ current fill level. Thus, let's do a full rewind once, to clear
+ things up. */
+
+ if (u->hwbuf_unused > before) {
+ pa_log_debug("Requesting rewind due to latency change.");
+ pa_sink_request_rewind(s, (size_t) -1);
+ }
+}
+
+static int process_rewind(struct userdata *u) {
+ snd_pcm_sframes_t unused;
+ size_t rewind_nbytes, unused_nbytes, limit_nbytes;
+ pa_assert(u);
+
+ /* Figure out how much we shall rewind and reset the counter */
+ rewind_nbytes = u->sink->thread_info.rewind_nbytes;
+
+ pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
+
+ if (PA_UNLIKELY((unused = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
+ pa_log("snd_pcm_avail() failed: %s", pa_alsa_strerror((int) unused));
+ return -1;
+ }
+
+ unused_nbytes = (size_t) unused * u->frame_size;
+
+ /* make sure rewind doesn't go too far, can cause issues with DMAs */
+ unused_nbytes += u->rewind_safeguard;
+
+ if (u->hwbuf_size > unused_nbytes)
+ limit_nbytes = u->hwbuf_size - unused_nbytes;
+ else
+ limit_nbytes = 0;
+
+ if (rewind_nbytes > limit_nbytes)
+ rewind_nbytes = limit_nbytes;
+
+ if (rewind_nbytes > 0) {
+ snd_pcm_sframes_t in_frames, out_frames;
+
+ pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes);
+
+ in_frames = (snd_pcm_sframes_t) (rewind_nbytes / u->frame_size);
+ pa_log_debug("before: %lu", (unsigned long) in_frames);
+ if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) {
+ pa_log("snd_pcm_rewind() failed: %s", pa_alsa_strerror((int) out_frames));
+ if (try_recover(u, "process_rewind", out_frames) < 0)
+ return -1;
+ out_frames = 0;
+ }
+
+ pa_log_debug("after: %lu", (unsigned long) out_frames);
+
+ rewind_nbytes = (size_t) out_frames * u->frame_size;
+
+ if (rewind_nbytes <= 0)
+ pa_log_info("Tried rewind, but was apparently not possible.");
+ else {
+ u->write_count -= rewind_nbytes;
+ pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes);
+ pa_sink_process_rewind(u->sink, rewind_nbytes);
+
+ u->after_rewind = TRUE;
+ return 0;
+ }
+ } else
+ pa_log_debug("Mhmm, actually there is nothing to rewind.");
+
+ pa_sink_process_rewind(u->sink, 0);
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ unsigned short revents = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+
+ for (;;) {
+ int ret;
+ pa_usec_t rtpoll_sleep = 0;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Loop");
+#endif
+
+ /* Render some data and write it to the dsp */
+ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+ int work_done;
+ pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
+
+ if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
+ if (process_rewind(u) < 0)
+ goto fail;
+
+ if (u->use_mmap)
+ work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
+ else
+ work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
+
+ if (work_done < 0)
+ goto fail;
+
+/* pa_log_debug("work_done = %i", work_done); */
+
+ if (work_done) {
+
+ if (u->first) {
+ pa_log_info("Starting playback.");
+ snd_pcm_start(u->pcm_handle);
+
+ pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
+
+ u->first = FALSE;
+ }
+
+ update_smoother(u);
+ }
+
+ if (u->use_tsched) {
+ pa_usec_t cusec;
+
+ if (u->since_start <= u->hwbuf_size) {
+
+ /* USB devices on ALSA seem to hit a buffer
+ * underrun during the first iterations much
+ * quicker then we calculate here, probably due to
+ * the transport latency. To accommodate for that
+ * we artificially decrease the sleep time until
+ * we have filled the buffer at least once
+ * completely.*/
+
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Cutting sleep time for the initial iterations by half.");
+ sleep_usec /= 2;
+ }
+
+ /* OK, the playback buffer is now full, let's
+ * calculate when to wake up next */
+/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */
+
+ /* Convert from the sound card time domain to the
+ * system time domain */
+ cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec);
+
+/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
+
+ /* We don't trust the conversion, so we wake up whatever comes first */
+ rtpoll_sleep = PA_MIN(sleep_usec, cusec);
+ }
+
+ u->after_rewind = FALSE;
+
+ }
+
+ if (u->sink->flags & PA_SINK_SYNC_VOLUME) {
+ pa_usec_t volume_sleep;
+ pa_sink_volume_change_apply(u->sink, &volume_sleep);
+ if (volume_sleep > 0)
+ rtpoll_sleep = PA_MIN(volume_sleep, rtpoll_sleep);
+ }
+
+ if (rtpoll_sleep > 0)
+ pa_rtpoll_set_timer_relative(u->rtpoll, rtpoll_sleep);
+ else
+ pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (u->sink->flags & PA_SINK_SYNC_VOLUME)
+ pa_sink_volume_change_apply(u->sink, NULL);
+
+ if (ret == 0)
+ goto finish;
+
+ /* Tell ALSA about this and process its response */
+ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+ struct pollfd *pollfd;
+ int err;
+ unsigned n;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);
+
+ if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_revents() failed: %s", pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ if (revents & ~POLLOUT) {
+ if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+ goto fail;
+
+ u->first = TRUE;
+ u->since_start = 0;
+ } else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Wakeup from ALSA!");
+
+ } else
+ revents = 0;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) {
+ const char *n;
+ char *t;
+
+ pa_assert(data);
+ pa_assert(ma);
+ pa_assert(device_name);
+
+ if ((n = pa_modargs_get_value(ma, "sink_name", NULL))) {
+ pa_sink_new_data_set_name(data, n);
+ data->namereg_fail = TRUE;
+ return;
+ }
+
+ if ((n = pa_modargs_get_value(ma, "name", NULL)))
+ data->namereg_fail = TRUE;
+ else {
+ n = device_id ? device_id : device_name;
+ data->namereg_fail = FALSE;
+ }
+
+ if (mapping)
+ t = pa_sprintf_malloc("alsa_output.%s.%s", n, mapping->name);
+ else
+ t = pa_sprintf_malloc("alsa_output.%s", n);
+
+ pa_sink_new_data_set_name(data, t);
+ pa_xfree(t);
+}
+
+static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+
+ if (!mapping && !element)
+ return;
+
+ if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ pa_log_info("Failed to find a working mixer device.");
+ return;
+ }
+
+ if (element) {
+
+ if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT)))
+ goto fail;
+
+ if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+ goto fail;
+
+ pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
+ pa_alsa_path_dump(u->mixer_path);
+ } else {
+
+ if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_OUTPUT)))
+ goto fail;
+
+ pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
+
+ pa_log_debug("Probed mixer paths:");
+ pa_alsa_path_set_dump(u->mixer_path_set);
+ }
+
+ return;
+
+fail:
+
+ if (u->mixer_path_set) {
+ pa_alsa_path_set_free(u->mixer_path_set);
+ u->mixer_path_set = NULL;
+ } else if (u->mixer_path) {
+ pa_alsa_path_free(u->mixer_path);
+ u->mixer_path = NULL;
+ }
+
+ if (u->mixer_handle) {
+ snd_mixer_close(u->mixer_handle);
+ u->mixer_handle = NULL;
+ }
+}
+
+static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) {
+ pa_assert(u);
+
+ if (!u->mixer_handle)
+ return 0;
+
+ if (u->sink->active_port) {
+ pa_alsa_port_data *data;
+
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
+
+ data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+ u->mixer_path = data->path;
+
+ pa_alsa_path_select(data->path, u->mixer_handle);
+
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
+
+ } else {
+
+ if (!u->mixer_path && u->mixer_path_set)
+ u->mixer_path = u->mixer_path_set->paths;
+
+ if (u->mixer_path) {
+ /* Hmm, we have only a single path, then let's activate it */
+
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
+
+ if (u->mixer_path->settings)
+ pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle);
+ } else
+ 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->sink->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+ u->sink->n_volume_steps = PA_VOLUME_NORM+1;
+
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume));
+
+ } else {
+ pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
+ u->sink->base_volume = PA_VOLUME_NORM;
+ u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+ }
+
+ u->sink->get_volume = sink_get_volume_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->write_volume = sink_write_volume_cb;
+
+ u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+ if (u->mixer_path->has_dB) {
+ u->sink->flags |= PA_SINK_DECIBEL_VOLUME;
+ if (sync_volume) {
+ u->sink->flags |= PA_SINK_SYNC_VOLUME;
+ pa_log_info("Successfully enabled synchronous volume.");
+ }
+ }
+
+ pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
+ }
+
+ if (!u->mixer_path->has_mute) {
+ pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
+ } else {
+ u->sink->get_mute = sink_get_mute_cb;
+ u->sink->set_mute = sink_set_mute_cb;
+ u->sink->flags |= PA_SINK_HW_MUTE_CTRL;
+ pa_log_info("Using hardware mute control.");
+ }
+
+ if (u->sink->flags & (PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL)) {
+ int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
+ if (u->sink->flags & PA_SINK_SYNC_VOLUME) {
+ u->mixer_pd = pa_alsa_mixer_pdata_new();
+ mixer_callback = io_mixer_callback;
+
+ if (pa_alsa_set_mixer_rtpoll(u->mixer_pd, u->mixer_handle, u->rtpoll) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ return -1;
+ }
+ } else {
+ u->mixer_fdl = pa_alsa_fdlist_new();
+ mixer_callback = ctl_mixer_callback;
+
+ if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ return -1;
+ }
+ }
+
+ 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, pa_alsa_mapping *mapping) {
+
+ struct userdata *u = NULL;
+ const char *dev_id = NULL;
+ pa_sample_spec ss, requested_ss;
+ pa_channel_map map;
+ uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark, rewind_safeguard;
+ snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
+ size_t frame_size;
+ pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE;
+ pa_sink_new_data data;
+ pa_alsa_profile_set *profile_set = NULL;
+
+ pa_assert(m);
+ pa_assert(ma);
+
+ ss = m->core->default_sample_spec;
+ map = m->core->default_channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) {
+ pa_log("Failed to parse sample specification and channel map");
+ goto fail;
+ }
+
+ requested_ss = ss;
+ frame_size = pa_frame_size(&ss);
+
+ nfrags = m->core->default_n_fragments;
+ frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
+ if (frag_size <= 0)
+ frag_size = (uint32_t) frame_size;
+ tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss);
+ tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss);
+
+ if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 ||
+ pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 ||
+ pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 ||
+ pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) {
+ pa_log("Failed to parse buffer metrics");
+ goto fail;
+ }
+
+ buffer_size = nfrags * frag_size;
+
+ period_frames = frag_size/frame_size;
+ buffer_frames = buffer_size/frame_size;
+ tsched_frames = tsched_size/frame_size;
+
+ if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
+ pa_log("Failed to parse mmap argument.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+ pa_log("Failed to parse tsched argument.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
+ pa_log("Failed to parse ignore_dB argument.");
+ goto fail;
+ }
+
+ rewind_safeguard = PA_MAX(DEFAULT_REWIND_SAFEGUARD_BYTES, pa_usec_to_bytes(DEFAULT_REWIND_SAFEGUARD_USEC, &ss));
+ if (pa_modargs_get_value_u32(ma, "rewind_safeguard", &rewind_safeguard) < 0) {
+ pa_log("Failed to parse rewind_safeguard argument");
+ goto fail;
+ }
+
+ sync_volume = m->core->sync_volume;
+ if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) {
+ pa_log("Failed to parse sync_volume argument.");
+ goto fail;
+ }
+
+ use_tsched = pa_alsa_may_tsched(use_tsched);
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->use_mmap = use_mmap;
+ u->use_tsched = use_tsched;
+ u->first = TRUE;
+ u->rewind_safeguard = rewind_safeguard;
+ u->rtpoll = pa_rtpoll_new();
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+
+ u->smoother = pa_smoother_new(
+ SMOOTHER_ADJUST_USEC,
+ SMOOTHER_WINDOW_USEC,
+ TRUE,
+ TRUE,
+ 5,
+ pa_rtclock_now(),
+ TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+
+ dev_id = pa_modargs_get_value(
+ ma, "device_id",
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE));
+
+ if (reserve_init(u, dev_id) < 0)
+ goto fail;
+
+ if (reserve_monitor_init(u, dev_id) < 0)
+ goto fail;
+
+ b = use_mmap;
+ d = use_tsched;
+
+ 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_mapping(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_PLAYBACK,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, mapping)))
+ goto fail;
+
+ } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
+
+ if (!(profile_set = pa_alsa_profile_set_new(NULL, &map)))
+ goto fail;
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_PLAYBACK,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, profile_set, &mapping)))
+ goto fail;
+
+ } else {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_string(
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE),
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_PLAYBACK,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, FALSE)))
+ goto fail;
+ }
+
+ pa_assert(u->device_name);
+ pa_log_info("Successfully opened device %s.", u->device_name);
+
+ if (pa_alsa_pcm_is_modem(u->pcm_handle)) {
+ pa_log_notice("Device %s is modem, refusing further initialization.", u->device_name);
+ goto fail;
+ }
+
+ 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.");
+ u->use_mmap = use_mmap = FALSE;
+ }
+
+ if (use_tsched && (!b || !d)) {
+ pa_log_info("Cannot enable timer-based scheduling, falling back to sound IRQ scheduling.");
+ u->use_tsched = use_tsched = FALSE;
+ }
+
+ if (u->use_mmap)
+ pa_log_info("Successfully enabled mmap() mode.");
+
+ if (u->use_tsched)
+ pa_log_info("Successfully enabled timer-based scheduling mode.");
+
+ /* ALSA might tweak the sample spec, so recalculate the frame size */
+ frame_size = pa_frame_size(&ss);
+
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+
+ pa_sink_new_data_init(&data);
+ data.driver = driver;
+ data.module = m;
+ data.card = card;
+ set_sink_name(&data, ma, dev_id, u->device_name, mapping);
+
+ /* We need to give pa_modargs_get_value_boolean() a pointer to a local
+ * variable instead of using &data.namereg_fail directly, because
+ * data.namereg_fail is a bitfield and taking the address of a bitfield
+ * variable is impossible. */
+ namereg_fail = data.namereg_fail;
+ if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) {
+ pa_log("Failed to parse boolean argument namereg_fail.");
+ pa_sink_new_data_done(&data);
+ goto fail;
+ }
+ data.namereg_fail = namereg_fail;
+
+ pa_sink_new_data_set_sample_spec(&data, &ss);
+ pa_sink_new_data_set_channel_map(&data, &map);
+
+ pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
+ 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) (buffer_frames * frame_size));
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
+
+ if (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 (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");
+ pa_sink_new_data_done(&data);
+ 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);
+
+ if (!u->sink) {
+ pa_log("Failed to create sink object");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin",
+ &u->sink->thread_info.volume_change_safety_margin) < 0) {
+ pa_log("Failed to parse sync_volume_safety_margin parameter");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_s32(ma, "sync_volume_extra_delay",
+ &u->sink->thread_info.volume_change_extra_delay) < 0) {
+ pa_log("Failed to parse sync_volume_extra_delay parameter");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ if (u->use_tsched)
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->set_port = sink_set_port_cb;
+ u->sink->userdata = u;
+
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+
+ u->frame_size = frame_size;
+ u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
+ u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
+ pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels);
+
+ pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
+ (double) u->hwbuf_size / (double) u->fragment_size,
+ (long unsigned) u->fragment_size,
+ (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
+ (long unsigned) u->hwbuf_size,
+ (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
+
+ pa_sink_set_max_request(u->sink, u->hwbuf_size);
+ if (pa_alsa_pcm_is_hw(u->pcm_handle))
+ pa_sink_set_max_rewind(u->sink, u->hwbuf_size);
+ else {
+ pa_log_info("Disabling rewind for device %s", u->device_name);
+ pa_sink_set_max_rewind(u->sink, 0);
+ }
+
+ if (u->use_tsched) {
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec);
+
+ fix_min_sleep_wakeup(u);
+ fix_tsched_watermark(u);
+
+ pa_sink_set_latency_range(u->sink,
+ 0,
+ pa_bytes_to_usec(u->hwbuf_size, &ss));
+
+ pa_log_info("Time scheduling watermark is %0.2fms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC);
+ } else
+ pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss));
+
+ reserve_update(u);
+
+ if (update_sw_params(u) < 0)
+ goto fail;
+
+ if (setup_mixer(u, ignore_dB, sync_volume) < 0)
+ goto fail;
+
+ pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
+
+ if (!(u->thread = pa_thread_new("alsa-sink", thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Get initial mixer settings */
+ if (data.volume_is_set) {
+ if (u->sink->set_volume)
+ u->sink->set_volume(u->sink);
+ } else {
+ if (u->sink->get_volume)
+ u->sink->get_volume(u->sink);
+ }
+
+ if (data.muted_is_set) {
+ if (u->sink->set_mute)
+ u->sink->set_mute(u->sink);
+ } else {
+ if (u->sink->get_mute)
+ u->sink->get_mute(u->sink);
+ }
+
+ pa_sink_put(u->sink);
+
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
+
+ return u->sink;
+
+fail:
+
+ if (u)
+ userdata_free(u);
+
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
+
+ return NULL;
+}
+
+static void userdata_free(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->mixer_pd)
+ pa_alsa_mixer_pdata_free(u->mixer_pd);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->mixer_fdl)
+ pa_alsa_fdlist_free(u->mixer_fdl);
+
+ if (u->mixer_path_set)
+ pa_alsa_path_set_free(u->mixer_path_set);
+ else if (u->mixer_path)
+ pa_alsa_path_free(u->mixer_path);
+
+ if (u->mixer_handle)
+ snd_mixer_close(u->mixer_handle);
+
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
+ reserve_done(u);
+ monitor_done(u);
+
+ pa_xfree(u->device_name);
+ pa_xfree(u->control_device);
+ pa_xfree(u);
+}
+
+void pa_alsa_sink_free(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ userdata_free(u);
+}
diff --git a/src/modules/alsa/alsa-sink.h b/src/modules/alsa/alsa-sink.h
new file mode 100644
index 00000000..e640b624
--- /dev/null
+++ b/src/modules/alsa/alsa-sink.h
@@ -0,0 +1,36 @@
+#ifndef fooalsasinkhfoo
+#define fooalsasinkhfoo
+
+/***
+ 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 <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/sink.h>
+
+#include "alsa-util.h"
+
+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);
+
+#endif
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
new file mode 100644
index 00000000..f847b1ee
--- /dev/null
+++ b/src/modules/alsa/alsa-source.c
@@ -0,0 +1,2003 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 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 <stdio.h>
+
+#include <asoundlib.h>
+
+#include <pulse/i18n.h>
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/time-smoother.h>
+
+#include <modules/reserve-wrap.h>
+
+#include "alsa-util.h"
+#include "alsa-source.h"
+
+/* #define DEBUG_TIMING */
+
+#define DEFAULT_DEVICE "default"
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (0*PA_USEC_PER_MSEC) /* 0ms */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */
+#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
+
+#define SMOOTHER_WINDOW_USEC (10*PA_USEC_PER_SEC) /* 10s */
+#define SMOOTHER_ADJUST_USEC (1*PA_USEC_PER_SEC) /* 1s */
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */
+
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_source *source;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ snd_pcm_t *pcm_handle;
+
+ pa_alsa_fdlist *mixer_fdl;
+ pa_alsa_mixer_pdata *mixer_pd;
+ snd_mixer_t *mixer_handle;
+ pa_alsa_path_set *mixer_path_set;
+ pa_alsa_path *mixer_path;
+
+ pa_cvolume hardware_volume;
+
+ size_t
+ frame_size,
+ fragment_size,
+ hwbuf_size,
+ tsched_watermark,
+ hwbuf_unused,
+ min_sleep,
+ min_wakeup,
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold;
+
+ pa_usec_t watermark_dec_not_before;
+
+ char *device_name; /* name of the PCM device */
+ char *control_device; /* name of the control device */
+
+ pa_bool_t use_mmap:1, use_tsched:1;
+
+ pa_bool_t first;
+
+ pa_rtpoll_item *alsa_rtpoll_item;
+
+ snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
+
+ pa_smoother *smoother;
+ uint64_t read_count;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
+
+ pa_reserve_wrapper *reserve;
+ pa_hook_slot *reserve_slot;
+ pa_reserve_monitor_wrapper *monitor;
+ pa_hook_slot *monitor_slot;
+};
+
+static void userdata_free(struct userdata *u);
+
+static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
+ pa_assert(r);
+ pa_assert(u);
+
+ if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0)
+ return PA_HOOK_CANCEL;
+
+ return PA_HOOK_OK;
+}
+
+static void reserve_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->reserve_slot) {
+ pa_hook_slot_free(u->reserve_slot);
+ u->reserve_slot = NULL;
+ }
+
+ if (u->reserve) {
+ pa_reserve_wrapper_unref(u->reserve);
+ u->reserve = NULL;
+ }
+}
+
+static void reserve_update(struct userdata *u) {
+ const char *description;
+ pa_assert(u);
+
+ if (!u->source || !u->reserve)
+ return;
+
+ if ((description = pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+ pa_reserve_wrapper_set_application_device_name(u->reserve, description);
+}
+
+static int reserve_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (u->reserve)
+ return 0;
+
+ if (pa_in_system_mode())
+ return 0;
+
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ u->reserve = pa_reserve_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->reserve))
+ return -1;
+
+ reserve_update(u);
+
+ pa_assert(!u->reserve_slot);
+ u->reserve_slot = pa_hook_connect(pa_reserve_wrapper_hook(u->reserve), PA_HOOK_NORMAL, (pa_hook_cb_t) reserve_cb, u);
+
+ return 0;
+}
+
+static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) {
+ pa_bool_t b;
+
+ pa_assert(w);
+ pa_assert(u);
+
+ b = PA_PTR_TO_UINT(busy) && !u->reserve;
+
+ pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION);
+ return PA_HOOK_OK;
+}
+
+static void monitor_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->monitor_slot) {
+ pa_hook_slot_free(u->monitor_slot);
+ u->monitor_slot = NULL;
+ }
+
+ if (u->monitor) {
+ pa_reserve_monitor_wrapper_unref(u->monitor);
+ u->monitor = NULL;
+ }
+}
+
+static int reserve_monitor_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (pa_in_system_mode())
+ return 0;
+
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->monitor))
+ return -1;
+
+ pa_assert(!u->monitor_slot);
+ u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u);
+
+ return 0;
+}
+
+static void fix_min_sleep_wakeup(struct userdata *u) {
+ size_t max_use, max_use_2;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+ max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec);
+
+ u->min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec);
+ u->min_sleep = PA_CLAMP(u->min_sleep, u->frame_size, max_use_2);
+
+ u->min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec);
+ u->min_wakeup = PA_CLAMP(u->min_wakeup, u->frame_size, max_use_2);
+}
+
+static void fix_tsched_watermark(struct userdata *u) {
+ size_t max_use;
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+
+ if (u->tsched_watermark > max_use - u->min_sleep)
+ u->tsched_watermark = max_use - u->min_sleep;
+
+ if (u->tsched_watermark < u->min_wakeup)
+ u->tsched_watermark = u->min_wakeup;
+}
+
+static void increase_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t old_min_latency, new_min_latency;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ /* First, just try to increase the watermark */
+ old_watermark = u->tsched_watermark;
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark) {
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+ return;
+ }
+
+ /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
+ old_min_latency = u->source->thread_info.min_latency;
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
+ new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
+
+ if (old_min_latency != new_min_latency) {
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
+
+ pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
+ }
+
+ /* When we reach this we're officialy fucked! */
+}
+
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
+static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
+ pa_usec_t wm, usec;
+
+ pa_assert(sleep_usec);
+ pa_assert(process_usec);
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ usec = pa_source_get_requested_latency_within_thread(u->source);
+
+ if (usec == (pa_usec_t) -1)
+ usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->sample_spec);
+
+ wm = pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec);
+
+ if (wm > usec)
+ wm = usec/2;
+
+ *sleep_usec = usec - wm;
+ *process_usec = wm;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Buffer time: %lu ms; Sleep time: %lu ms; Process time: %lu ms",
+ (unsigned long) (usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*sleep_usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*process_usec / PA_USEC_PER_MSEC));
+#endif
+}
+
+static int try_recover(struct userdata *u, const char *call, int err) {
+ pa_assert(u);
+ pa_assert(call);
+ pa_assert(err < 0);
+
+ pa_log_debug("%s: %s", call, pa_alsa_strerror(err));
+
+ pa_assert(err != -EAGAIN);
+
+ if (err == -EPIPE)
+ pa_log_debug("%s: Buffer overrun!", call);
+
+ if (err == -ESTRPIPE)
+ pa_log_debug("%s: System suspended!", call);
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
+ pa_log("%s: %s", call, pa_alsa_strerror(err));
+ return -1;
+ }
+
+ u->first = TRUE;
+ return 0;
+}
+
+static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
+ size_t left_to_record;
+ size_t rec_space = u->hwbuf_size - u->hwbuf_unused;
+ pa_bool_t overrun = FALSE;
+
+ /* We use <= instead of < for this check here because an overrun
+ * only happens after the last sample was processed, not already when
+ * it is removed from the buffer. This is particularly important
+ * when block transfer is used. */
+
+ if (n_bytes <= rec_space)
+ left_to_record = rec_space - n_bytes;
+ else {
+
+ /* We got a dropout. What a mess! */
+ left_to_record = 0;
+ overrun = TRUE;
+
+#ifdef DEBUG_TIMING
+ PA_DEBUG_TRAP;
+#endif
+
+ if (pa_log_ratelimit(PA_LOG_INFO))
+ pa_log_info("Overrun!");
+ }
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+#endif
+
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
+ if (overrun || left_to_record < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_record > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
+
+ /* We decrease the watermark only if have actually
+ * been woken up by a timeout. If something else woke
+ * us up it's too easy to fulfill the deadlines... */
+
+ if (on_timeout)
+ decrease_watermark(u);
+ }
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
+ }
+
+ return left_to_record;
+}
+
+static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
+ pa_bool_t work_done = FALSE;
+ pa_usec_t max_sleep_usec = 0, process_usec = 0;
+ size_t left_to_record;
+ unsigned j = 0;
+
+ pa_assert(u);
+ pa_source_assert_ref(u->source);
+
+ if (u->use_tsched)
+ hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
+ for (;;) {
+ snd_pcm_sframes_t n;
+ size_t n_bytes;
+ int r;
+ pa_bool_t after_avail = TRUE;
+
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ continue;
+
+ return r;
+ }
+
+ n_bytes = (size_t) n * u->frame_size;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("avail: %lu", (unsigned long) n_bytes);
+#endif
+
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
+
+ if (u->use_tsched)
+ if (!polled &&
+ pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not reading, because too early.");
+#endif
+ break;
+ }
+
+ if (PA_UNLIKELY(n_bytes <= 0)) {
+
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not reading, because not necessary.");
+#endif
+ break;
+ }
+
+
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
+
+ break;
+ }
+
+ polled = FALSE;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Reading");
+#endif
+
+ for (;;) {
+ pa_memchunk chunk;
+ void *p;
+ int err;
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t offset, frames;
+ snd_pcm_sframes_t sframes;
+
+ frames = (snd_pcm_uframes_t) (n_bytes / u->frame_size);
+/* pa_log_debug("%lu frames to read", (unsigned long) frames); */
+
+ if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+
+ if (!after_avail && err == -EAGAIN)
+ break;
+
+ if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
+ continue;
+
+ return r;
+ }
+
+ /* Make sure that if these memblocks need to be copied they will fit into one slot */
+ if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size)
+ frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size;
+
+ if (!after_avail && frames == 0)
+ break;
+
+ pa_assert(frames > 0);
+ after_avail = FALSE;
+
+ /* Check these are multiples of 8 bit */
+ pa_assert((areas[0].first & 7) == 0);
+ pa_assert((areas[0].step & 7)== 0);
+
+ /* We assume a single interleaved memory buffer */
+ pa_assert((areas[0].first >> 3) == 0);
+ pa_assert((areas[0].step >> 3) == u->frame_size);
+
+ p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+
+ chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE);
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_source_post(u->source, &chunk);
+ pa_memblock_unref_fixed(chunk.memblock);
+
+ if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) {
+
+ if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0)
+ continue;
+
+ return r;
+ }
+
+ work_done = TRUE;
+
+ u->read_count += frames * u->frame_size;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Read %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes);
+#endif
+
+ if ((size_t) frames * u->frame_size >= n_bytes)
+ break;
+
+ n_bytes -= (size_t) frames * u->frame_size;
+ }
+ }
+
+ if (u->use_tsched) {
+ *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+ }
+
+ return work_done ? 1 : 0;
+}
+
+static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
+ int work_done = FALSE;
+ pa_usec_t max_sleep_usec = 0, process_usec = 0;
+ size_t left_to_record;
+ unsigned j = 0;
+
+ pa_assert(u);
+ pa_source_assert_ref(u->source);
+
+ if (u->use_tsched)
+ hw_sleep_time(u, &max_sleep_usec, &process_usec);
+
+ for (;;) {
+ snd_pcm_sframes_t n;
+ size_t n_bytes;
+ int r;
+ pa_bool_t after_avail = TRUE;
+
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ continue;
+
+ return r;
+ }
+
+ n_bytes = (size_t) n * u->frame_size;
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
+
+ if (u->use_tsched)
+ if (!polled &&
+ pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2)
+ break;
+
+ if (PA_UNLIKELY(n_bytes <= 0)) {
+
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
+
+ break;
+ }
+
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
+
+ break;
+ }
+
+ polled = FALSE;
+
+ for (;;) {
+ void *p;
+ snd_pcm_sframes_t frames;
+ pa_memchunk chunk;
+
+ chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1);
+
+ frames = (snd_pcm_sframes_t) (pa_memblock_get_length(chunk.memblock) / u->frame_size);
+
+ if (frames > (snd_pcm_sframes_t) (n_bytes/u->frame_size))
+ frames = (snd_pcm_sframes_t) (n_bytes/u->frame_size);
+
+/* pa_log_debug("%lu frames to read", (unsigned long) n); */
+
+ p = pa_memblock_acquire(chunk.memblock);
+ frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames);
+ pa_memblock_release(chunk.memblock);
+
+ if (PA_UNLIKELY(frames < 0)) {
+ pa_memblock_unref(chunk.memblock);
+
+ if (!after_avail && (int) frames == -EAGAIN)
+ break;
+
+ if ((r = try_recover(u, "snd_pcm_readi", (int) frames)) == 0)
+ continue;
+
+ return r;
+ }
+
+ if (!after_avail && frames == 0) {
+ pa_memblock_unref(chunk.memblock);
+ break;
+ }
+
+ pa_assert(frames > 0);
+ after_avail = FALSE;
+
+ chunk.index = 0;
+ chunk.length = (size_t) frames * u->frame_size;
+
+ pa_source_post(u->source, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ work_done = TRUE;
+
+ u->read_count += frames * u->frame_size;
+
+/* pa_log_debug("read %lu frames", (unsigned long) frames); */
+
+ if ((size_t) frames * u->frame_size >= n_bytes)
+ break;
+
+ n_bytes -= (size_t) frames * u->frame_size;
+ }
+ }
+
+ if (u->use_tsched) {
+ *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+ }
+
+ return work_done ? 1 : 0;
+}
+
+static void update_smoother(struct userdata *u) {
+ snd_pcm_sframes_t delay = 0;
+ uint64_t position;
+ int err;
+ pa_usec_t now1 = 0, now2;
+ snd_pcm_status_t *status;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ /* Let's update the time smoother */
+
+ if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->source->sample_spec, TRUE)) < 0)) {
+ pa_log_warn("Failed to get delay: %s", pa_alsa_strerror(err));
+ return;
+ }
+
+ if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0))
+ pa_log_warn("Failed to get timestamp: %s", pa_alsa_strerror(err));
+ else {
+ snd_htimestamp_t htstamp = { 0, 0 };
+ snd_pcm_status_get_htstamp(status, &htstamp);
+ now1 = pa_timespec_load(&htstamp);
+ }
+
+ /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
+ if (now1 <= 0)
+ now1 = pa_rtclock_now();
+
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
+ position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
+ now2 = pa_bytes_to_usec(position, &u->source->sample_spec);
+
+ pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
+}
+
+static pa_usec_t source_get_latency(struct userdata *u) {
+ int64_t delay;
+ pa_usec_t now1, now2;
+
+ pa_assert(u);
+
+ now1 = pa_rtclock_now();
+ now2 = pa_smoother_get(u->smoother, now1);
+
+ delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec);
+
+ return delay >= 0 ? (pa_usec_t) delay : 0;
+}
+
+static int build_pollfd(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll)))
+ return -1;
+
+ return 0;
+}
+
+/* Called from IO context */
+static int suspend(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
+
+ /* Let's suspend */
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+
+ pa_log_info("Device suspended...");
+
+ return 0;
+}
+
+/* Called from IO context */
+static int update_sw_params(struct userdata *u) {
+ snd_pcm_uframes_t avail_min;
+ int err;
+
+ pa_assert(u);
+
+ /* Use the full buffer if noone asked us for anything specific */
+ u->hwbuf_unused = 0;
+
+ if (u->use_tsched) {
+ pa_usec_t latency;
+
+ if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) {
+ size_t b;
+
+ pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC);
+
+ b = pa_usec_to_bytes(latency, &u->source->sample_spec);
+
+ /* We need at least one sample in our buffer */
+
+ if (PA_UNLIKELY(b < u->frame_size))
+ b = u->frame_size;
+
+ u->hwbuf_unused = PA_LIKELY(b < u->hwbuf_size) ? (u->hwbuf_size - b) : 0;
+ }
+
+ fix_min_sleep_wakeup(u);
+ fix_tsched_watermark(u);
+ }
+
+ pa_log_debug("hwbuf_unused=%lu", (unsigned long) u->hwbuf_unused);
+
+ avail_min = 1;
+
+ if (u->use_tsched) {
+ pa_usec_t sleep_usec, process_usec;
+
+ hw_sleep_time(u, &sleep_usec, &process_usec);
+ avail_min += pa_usec_to_bytes(sleep_usec, &u->source->sample_spec) / u->frame_size;
+ }
+
+ pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min);
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min, !u->use_tsched)) < 0) {
+ pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
+
+/* Called from IO context */
+static int unsuspend(struct userdata *u) {
+ pa_sample_spec ss;
+ int err;
+ pa_bool_t b, d;
+ snd_pcm_uframes_t period_size, buffer_size;
+
+ pa_assert(u);
+ pa_assert(!u->pcm_handle);
+
+ pa_log_info("Trying resume...");
+
+ if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE,
+ SND_PCM_NONBLOCK|
+ SND_PCM_NO_AUTO_RESAMPLE|
+ SND_PCM_NO_AUTO_CHANNELS|
+ SND_PCM_NO_AUTO_FORMAT)) < 0) {
+ pa_log("Error opening PCM device %s: %s", u->device_name, pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ ss = u->source->sample_spec;
+ period_size = u->fragment_size / u->frame_size;
+ buffer_size = u->hwbuf_size / u->frame_size;
+ b = u->use_mmap;
+ d = u->use_tsched;
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &period_size, &buffer_size, 0, &b, &d, TRUE)) < 0) {
+ pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ if (b != u->use_mmap || d != u->use_tsched) {
+ pa_log_warn("Resume failed, couldn't get original access mode.");
+ goto fail;
+ }
+
+ if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) {
+ pa_log_warn("Resume failed, couldn't restore original sample settings.");
+ goto fail;
+ }
+
+ if (period_size*u->frame_size != u->fragment_size ||
+ buffer_size*u->frame_size != u->hwbuf_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu/%lu, New %lu/%lu)",
+ (unsigned long) u->hwbuf_size, (unsigned long) u->fragment_size,
+ (unsigned long) (buffer_size*u->frame_size), (unsigned long) (period_size*u->frame_size));
+ goto fail;
+ }
+
+ if (update_sw_params(u) < 0)
+ goto fail;
+
+ if (build_pollfd(u) < 0)
+ goto fail;
+
+ /* FIXME: We need to reload the volume somehow */
+
+ u->read_count = 0;
+ pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
+
+ u->first = TRUE;
+
+ pa_log_info("Resumed successfully...");
+
+ return 0;
+
+fail:
+ if (u->pcm_handle) {
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+ }
+
+ return -PA_ERR_IO;
+}
+
+/* Called from IO context */
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->pcm_handle)
+ r = source_get_latency(u);
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_STATE:
+
+ switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SOURCE_SUSPENDED: {
+ int r;
+
+ pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
+
+ if ((r = suspend(u)) < 0)
+ return r;
+
+ break;
+ }
+
+ case PA_SOURCE_IDLE:
+ case PA_SOURCE_RUNNING: {
+ int r;
+
+ if (u->source->thread_info.state == PA_SOURCE_INIT) {
+ if (build_pollfd(u) < 0)
+ return -PA_ERR_IO;
+ }
+
+ if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
+ if ((r = unsuspend(u)) < 0)
+ return r;
+ }
+
+ break;
+ }
+
+ case PA_SOURCE_UNLINKED:
+ case PA_SOURCE_INIT:
+ case PA_SOURCE_INVALID_STATE:
+ ;
+ }
+
+ break;
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) {
+ pa_source_state_t old_state;
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ old_state = pa_source_get_state(u->source);
+
+ if (PA_SOURCE_IS_OPENED(old_state) && new_state == PA_SOURCE_SUSPENDED)
+ reserve_done(u);
+ else if (old_state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(new_state))
+ if (reserve_init(u, u->device_name) < 0)
+ return -PA_ERR_BUSY;
+
+ return 0;
+}
+
+static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (u->source->suspend_cause & PA_SUSPEND_SESSION)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE) {
+ pa_source_get_volume(u->source, TRUE);
+ pa_source_get_mute(u->source, TRUE);
+ }
+
+ return 0;
+}
+
+static int io_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (u->source->suspend_cause & PA_SUSPEND_SESSION)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE)
+ pa_source_update_volume_and_mute(u->source);
+
+ return 0;
+}
+
+static void source_get_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume r;
+ char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX];
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+ return;
+
+ /* 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(vol_str_pcnt, sizeof(vol_str_pcnt), &r));
+
+ if (u->mixer_path->has_dB) {
+ char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX];
+
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &r));
+ }
+
+ if (pa_cvolume_equal(&u->hardware_volume, &r))
+ return;
+
+ s->real_volume = u->hardware_volume = r;
+
+ /* Hmm, so the hardware volume changed, let's reset our software volume */
+ if (u->mixer_path->has_dB)
+ pa_source_set_soft_volume(s, NULL);
+}
+
+static void source_set_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume r;
+ char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX];
+ pa_bool_t sync_volume = !!(s->flags & PA_SOURCE_SYNC_VOLUME);
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ /* Shift up by the base volume */
+ pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
+
+ if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, sync_volume, !sync_volume) < 0)
+ return;
+
+ /* 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->mixer_path->has_dB) {
+ pa_cvolume new_soft_volume;
+ pa_bool_t accurate_enough;
+ char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX];
+
+ /* Match exactly what the user requested by software */
+ pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume);
+
+ /* If the adjustment to do in software is only minimal we
+ * can skip it. That saves us CPU at the expense of a bit of
+ * accuracy */
+ accurate_enough =
+ (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+
+ pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->real_volume));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->real_volume));
+ pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &u->hardware_volume));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &u->hardware_volume));
+ pa_log_debug("Calculated software volume: %s (accurate-enough=%s)",
+ pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &new_soft_volume),
+ pa_yes_no(accurate_enough));
+ pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &new_soft_volume));
+
+ if (!accurate_enough)
+ s->soft_volume = new_soft_volume;
+
+ } else {
+ pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &r));
+
+ /* We can't match exactly what the user requested, hence let's
+ * at least tell the user about it */
+
+ s->real_volume = r;
+ }
+}
+
+static void source_write_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ pa_cvolume hw_vol = s->thread_info.current_hw_volume;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+ pa_assert(s->flags & PA_SOURCE_SYNC_VOLUME);
+
+ /* Shift up by the base volume */
+ pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume);
+
+ if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &hw_vol, TRUE, TRUE) < 0)
+ pa_log_error("Writing HW volume failed");
+ else {
+ pa_cvolume tmp_vol;
+ pa_bool_t accurate_enough;
+
+ /* Shift down by the base volume, so that 0dB becomes maximum volume */
+ pa_sw_cvolume_multiply_scalar(&hw_vol, &hw_vol, s->base_volume);
+
+ pa_sw_cvolume_divide(&tmp_vol, &hw_vol, &s->thread_info.current_hw_volume);
+ accurate_enough =
+ (pa_cvolume_min(&tmp_vol) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+ (pa_cvolume_max(&tmp_vol) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+
+ if (!accurate_enough) {
+ union {
+ char db[2][PA_SW_CVOLUME_SNPRINT_DB_MAX];
+ char pcnt[2][PA_CVOLUME_SNPRINT_MAX];
+ } vol;
+
+ pa_log_debug("Written HW volume did not match with the request: %s (request) != %s",
+ pa_cvolume_snprint(vol.pcnt[0], sizeof(vol.pcnt[0]), &s->thread_info.current_hw_volume),
+ pa_cvolume_snprint(vol.pcnt[1], sizeof(vol.pcnt[1]), &hw_vol));
+ pa_log_debug(" in dB: %s (request) != %s",
+ pa_sw_cvolume_snprint_dB(vol.db[0], sizeof(vol.db[0]), &s->thread_info.current_hw_volume),
+ pa_sw_cvolume_snprint_dB(vol.db[1], sizeof(vol.db[1]), &hw_vol));
+ }
+ }
+}
+
+static void source_get_mute_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ pa_bool_t b;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)
+ return;
+
+ s->muted = b;
+}
+
+static void source_set_mute_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+
+ pa_assert(u);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
+
+ pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
+}
+
+static int source_set_port_cb(pa_source *s, pa_device_port *p) {
+ struct userdata *u = s->userdata;
+ pa_alsa_port_data *data;
+
+ pa_assert(u);
+ pa_assert(p);
+ pa_assert(u->mixer_handle);
+
+ data = PA_DEVICE_PORT_DATA(p);
+
+ pa_assert_se(u->mixer_path = data->path);
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
+
+ if (u->mixer_path->has_volume && u->mixer_path->has_dB) {
+ s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+ s->n_volume_steps = PA_VOLUME_NORM+1;
+
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume));
+ } else {
+ s->base_volume = PA_VOLUME_NORM;
+ s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+ }
+
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
+
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->set_volume)
+ s->set_volume(s);
+
+ return 0;
+}
+
+static void source_update_requested_latency_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ pa_assert(u);
+ pa_assert(u->use_tsched); /* only when timer scheduling is used
+ * we can dynamically adjust the
+ * latency */
+
+ if (!u->pcm_handle)
+ return;
+
+ update_sw_params(u);
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ unsigned short revents = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+
+ for (;;) {
+ int ret;
+ pa_usec_t rtpoll_sleep = 0;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Loop");
+#endif
+
+ /* Read some data and pass it to the sources */
+ if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+ int work_done;
+ pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
+
+ if (u->first) {
+ pa_log_info("Starting capture.");
+ snd_pcm_start(u->pcm_handle);
+
+ pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
+
+ u->first = FALSE;
+ }
+
+ if (u->use_mmap)
+ work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout);
+ else
+ work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout);
+
+ if (work_done < 0)
+ goto fail;
+
+/* pa_log_debug("work_done = %i", work_done); */
+
+ if (work_done)
+ update_smoother(u);
+
+ if (u->use_tsched) {
+ pa_usec_t cusec;
+
+ /* OK, the capture buffer is now empty, let's
+ * calculate when to wake up next */
+
+/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */
+
+ /* Convert from the sound card time domain to the
+ * system time domain */
+ cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec);
+
+/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
+
+ /* We don't trust the conversion, so we wake up whatever comes first */
+ rtpoll_sleep = PA_MIN(sleep_usec, cusec);
+ }
+ }
+
+ if (u->source->flags & PA_SOURCE_SYNC_VOLUME) {
+ pa_usec_t volume_sleep;
+ pa_source_volume_change_apply(u->source, &volume_sleep);
+ if (volume_sleep > 0)
+ rtpoll_sleep = PA_MIN(volume_sleep, rtpoll_sleep);
+ }
+
+ if (rtpoll_sleep > 0)
+ pa_rtpoll_set_timer_relative(u->rtpoll, rtpoll_sleep);
+ else
+ pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (u->source->flags & PA_SOURCE_SYNC_VOLUME)
+ pa_source_volume_change_apply(u->source, NULL);
+
+ if (ret == 0)
+ goto finish;
+
+ /* Tell ALSA about this and process its response */
+ if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+ struct pollfd *pollfd;
+ int err;
+ unsigned n;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);
+
+ if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_revents() failed: %s", pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ if (revents & ~POLLIN) {
+ if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+ goto fail;
+
+ u->first = TRUE;
+ } else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Wakeup from ALSA!");
+
+ } else
+ revents = 0;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) {
+ const char *n;
+ char *t;
+
+ pa_assert(data);
+ pa_assert(ma);
+ pa_assert(device_name);
+
+ if ((n = pa_modargs_get_value(ma, "source_name", NULL))) {
+ pa_source_new_data_set_name(data, n);
+ data->namereg_fail = TRUE;
+ return;
+ }
+
+ if ((n = pa_modargs_get_value(ma, "name", NULL)))
+ data->namereg_fail = TRUE;
+ else {
+ n = device_id ? device_id : device_name;
+ data->namereg_fail = FALSE;
+ }
+
+ if (mapping)
+ t = pa_sprintf_malloc("alsa_input.%s.%s", n, mapping->name);
+ else
+ t = pa_sprintf_malloc("alsa_input.%s", n);
+
+ pa_source_new_data_set_name(data, t);
+ pa_xfree(t);
+}
+
+static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+
+ if (!mapping && !element)
+ return;
+
+ if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ pa_log_info("Failed to find a working mixer device.");
+ return;
+ }
+
+ if (element) {
+
+ if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
+ goto fail;
+
+ if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+ goto fail;
+
+ pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
+ pa_alsa_path_dump(u->mixer_path);
+ } else {
+
+ if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_INPUT)))
+ goto fail;
+
+ pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
+
+ pa_log_debug("Probed mixer paths:");
+ pa_alsa_path_set_dump(u->mixer_path_set);
+ }
+
+ return;
+
+fail:
+
+ if (u->mixer_path_set) {
+ pa_alsa_path_set_free(u->mixer_path_set);
+ u->mixer_path_set = NULL;
+ } else if (u->mixer_path) {
+ pa_alsa_path_free(u->mixer_path);
+ u->mixer_path = NULL;
+ }
+
+ if (u->mixer_handle) {
+ snd_mixer_close(u->mixer_handle);
+ u->mixer_handle = NULL;
+ }
+}
+
+static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) {
+ pa_assert(u);
+
+ if (!u->mixer_handle)
+ return 0;
+
+ if (u->source->active_port) {
+ pa_alsa_port_data *data;
+
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
+
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+ u->mixer_path = data->path;
+
+ pa_alsa_path_select(data->path, u->mixer_handle);
+
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
+
+ } else {
+
+ if (!u->mixer_path && u->mixer_path_set)
+ u->mixer_path = u->mixer_path_set->paths;
+
+ if (u->mixer_path) {
+ /* Hmm, we have only a single path, then let's activate it */
+
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
+
+ if (u->mixer_path->settings)
+ pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle);
+ } else
+ 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;
+
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume));
+
+ } else {
+ pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
+ u->source->base_volume = PA_VOLUME_NORM;
+ u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+ }
+
+ u->source->get_volume = source_get_volume_cb;
+ u->source->set_volume = source_set_volume_cb;
+ u->source->write_volume = source_write_volume_cb;
+
+ u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+ if (u->mixer_path->has_dB) {
+ u->source->flags |= PA_SOURCE_DECIBEL_VOLUME;
+ if (sync_volume) {
+ u->source->flags |= PA_SOURCE_SYNC_VOLUME;
+ pa_log_info("Successfully enabled synchronous volume.");
+ }
+ }
+
+ pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
+ }
+
+ if (!u->mixer_path->has_mute) {
+ pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
+ } else {
+ u->source->get_mute = source_get_mute_cb;
+ u->source->set_mute = source_set_mute_cb;
+ u->source->flags |= PA_SOURCE_HW_MUTE_CTRL;
+ pa_log_info("Using hardware mute control.");
+ }
+
+ if (u->source->flags & (PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_HW_MUTE_CTRL)) {
+ int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
+ if (u->source->flags & PA_SOURCE_SYNC_VOLUME) {
+ u->mixer_pd = pa_alsa_mixer_pdata_new();
+ mixer_callback = io_mixer_callback;
+
+ if (pa_alsa_set_mixer_rtpoll(u->mixer_pd, u->mixer_handle, u->rtpoll) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ return -1;
+ }
+ } else {
+ u->mixer_fdl = pa_alsa_fdlist_new();
+ mixer_callback = ctl_mixer_callback;
+
+ if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ return -1;
+ }
+ }
+
+ 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, pa_alsa_mapping *mapping) {
+
+ struct userdata *u = NULL;
+ const char *dev_id = NULL;
+ pa_sample_spec ss, requested_ss;
+ pa_channel_map map;
+ uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark;
+ snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
+ size_t frame_size;
+ pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE;
+ pa_source_new_data data;
+ pa_alsa_profile_set *profile_set = NULL;
+
+ pa_assert(m);
+ pa_assert(ma);
+
+ ss = m->core->default_sample_spec;
+ map = m->core->default_channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) {
+ pa_log("Failed to parse sample specification and channel map");
+ goto fail;
+ }
+
+ requested_ss = ss;
+ frame_size = pa_frame_size(&ss);
+
+ nfrags = m->core->default_n_fragments;
+ frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss);
+ if (frag_size <= 0)
+ frag_size = (uint32_t) frame_size;
+ tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss);
+ tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss);
+
+ if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 ||
+ pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 ||
+ pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 ||
+ pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) {
+ pa_log("Failed to parse buffer metrics");
+ goto fail;
+ }
+
+ buffer_size = nfrags * frag_size;
+
+ period_frames = frag_size/frame_size;
+ buffer_frames = buffer_size/frame_size;
+ tsched_frames = tsched_size/frame_size;
+
+ if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
+ pa_log("Failed to parse mmap argument.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+ pa_log("Failed to parse tsched argument.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
+ pa_log("Failed to parse ignore_dB argument.");
+ goto fail;
+ }
+
+ sync_volume = m->core->sync_volume;
+ if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) {
+ pa_log("Failed to parse sync_volume argument.");
+ goto fail;
+ }
+
+ use_tsched = pa_alsa_may_tsched(use_tsched);
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->use_mmap = use_mmap;
+ u->use_tsched = use_tsched;
+ u->first = TRUE;
+ u->rtpoll = pa_rtpoll_new();
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+
+ u->smoother = pa_smoother_new(
+ SMOOTHER_ADJUST_USEC,
+ SMOOTHER_WINDOW_USEC,
+ TRUE,
+ TRUE,
+ 5,
+ pa_rtclock_now(),
+ TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+
+ dev_id = pa_modargs_get_value(
+ ma, "device_id",
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE));
+
+ if (reserve_init(u, dev_id) < 0)
+ goto fail;
+
+ if (reserve_monitor_init(u, dev_id) < 0)
+ goto fail;
+
+ b = use_mmap;
+ d = use_tsched;
+
+ 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_mapping(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_CAPTURE,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, mapping)))
+ goto fail;
+
+ } else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
+
+ if (!(profile_set = pa_alsa_profile_set_new(NULL, &map)))
+ goto fail;
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_CAPTURE,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, profile_set, &mapping)))
+ goto fail;
+
+ } else {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_string(
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE),
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_CAPTURE,
+ &period_frames, &buffer_frames, tsched_frames,
+ &b, &d, FALSE)))
+ goto fail;
+ }
+
+ pa_assert(u->device_name);
+ pa_log_info("Successfully opened device %s.", u->device_name);
+
+ if (pa_alsa_pcm_is_modem(u->pcm_handle)) {
+ pa_log_notice("Device %s is modem, refusing further initialization.", u->device_name);
+ goto fail;
+ }
+
+ 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.");
+ u->use_mmap = use_mmap = FALSE;
+ }
+
+ if (use_tsched && (!b || !d)) {
+ pa_log_info("Cannot enable timer-based scheduling, falling back to sound IRQ scheduling.");
+ u->use_tsched = use_tsched = FALSE;
+ }
+
+ if (u->use_mmap)
+ pa_log_info("Successfully enabled mmap() mode.");
+
+ if (u->use_tsched)
+ pa_log_info("Successfully enabled timer-based scheduling mode.");
+
+ /* ALSA might tweak the sample spec, so recalculate the frame size */
+ frame_size = pa_frame_size(&ss);
+
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+
+ pa_source_new_data_init(&data);
+ data.driver = driver;
+ data.module = m;
+ data.card = card;
+ set_source_name(&data, ma, dev_id, u->device_name, mapping);
+
+ /* We need to give pa_modargs_get_value_boolean() a pointer to a local
+ * variable instead of using &data.namereg_fail directly, because
+ * data.namereg_fail is a bitfield and taking the address of a bitfield
+ * variable is impossible. */
+ namereg_fail = data.namereg_fail;
+ if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) {
+ pa_log("Failed to parse boolean argument namereg_fail.");
+ pa_source_new_data_done(&data);
+ goto fail;
+ }
+ data.namereg_fail = namereg_fail;
+
+ pa_source_new_data_set_sample_spec(&data, &ss);
+ pa_source_new_data_set_channel_map(&data, &map);
+
+ pa_alsa_init_proplist_pcm(m->core, data.proplist, u->pcm_handle);
+ 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) (buffer_frames * frame_size));
+ pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size));
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial"));
+
+ if (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 (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");
+ pa_source_new_data_done(&data);
+ 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);
+
+ if (!u->source) {
+ pa_log("Failed to create source object");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin",
+ &u->source->thread_info.volume_change_safety_margin) < 0) {
+ pa_log("Failed to parse sync_volume_safety_margin parameter");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_s32(ma, "sync_volume_extra_delay",
+ &u->source->thread_info.volume_change_extra_delay) < 0) {
+ pa_log("Failed to parse sync_volume_extra_delay parameter");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg;
+ if (u->use_tsched)
+ u->source->update_requested_latency = source_update_requested_latency_cb;
+ u->source->set_state = source_set_state_cb;
+ u->source->set_port = source_set_port_cb;
+ u->source->userdata = u;
+
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+
+ u->frame_size = frame_size;
+ u->fragment_size = frag_size = (size_t) (period_frames * frame_size);
+ u->hwbuf_size = buffer_size = (size_t) (buffer_frames * frame_size);
+ pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels);
+
+ pa_log_info("Using %0.1f fragments of size %lu bytes (%0.2fms), buffer size is %lu bytes (%0.2fms)",
+ (double) u->hwbuf_size / (double) u->fragment_size,
+ (long unsigned) u->fragment_size,
+ (double) pa_bytes_to_usec(u->fragment_size, &ss) / PA_USEC_PER_MSEC,
+ (long unsigned) u->hwbuf_size,
+ (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
+
+ if (u->use_tsched) {
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec);
+
+ fix_min_sleep_wakeup(u);
+ fix_tsched_watermark(u);
+
+ pa_source_set_latency_range(u->source,
+ 0,
+ pa_bytes_to_usec(u->hwbuf_size, &ss));
+
+ pa_log_info("Time scheduling watermark is %0.2fms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC);
+ } else
+ pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->hwbuf_size, &ss));
+
+ reserve_update(u);
+
+ if (update_sw_params(u) < 0)
+ goto fail;
+
+ if (setup_mixer(u, ignore_dB, sync_volume) < 0)
+ goto fail;
+
+ pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
+
+ if (!(u->thread = pa_thread_new("alsa-source", thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Get initial mixer settings */
+ if (data.volume_is_set) {
+ if (u->source->set_volume)
+ u->source->set_volume(u->source);
+ } else {
+ if (u->source->get_volume)
+ u->source->get_volume(u->source);
+ }
+
+ if (data.muted_is_set) {
+ if (u->source->set_mute)
+ u->source->set_mute(u->source);
+ } else {
+ if (u->source->get_mute)
+ u->source->get_mute(u->source);
+ }
+
+ pa_source_put(u->source);
+
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
+
+ return u->source;
+
+fail:
+
+ if (u)
+ userdata_free(u);
+
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
+
+ return NULL;
+}
+
+static void userdata_free(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->mixer_pd)
+ pa_alsa_mixer_pdata_free(u->mixer_pd);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ if (u->mixer_fdl)
+ pa_alsa_fdlist_free(u->mixer_fdl);
+
+ if (u->mixer_path_set)
+ pa_alsa_path_set_free(u->mixer_path_set);
+ else if (u->mixer_path)
+ pa_alsa_path_free(u->mixer_path);
+
+ if (u->mixer_handle)
+ snd_mixer_close(u->mixer_handle);
+
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
+ reserve_done(u);
+ monitor_done(u);
+
+ pa_xfree(u->device_name);
+ pa_xfree(u->control_device);
+ pa_xfree(u);
+}
+
+void pa_alsa_source_free(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ userdata_free(u);
+}
diff --git a/src/modules/alsa/alsa-source.h b/src/modules/alsa/alsa-source.h
new file mode 100644
index 00000000..5d9409e2
--- /dev/null
+++ b/src/modules/alsa/alsa-source.h
@@ -0,0 +1,36 @@
+#ifndef fooalsasourcehfoo
+#define fooalsasourcehfoo
+
+/***
+ 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 <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/source.h>
+
+#include "alsa-util.h"
+
+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);
+
+#endif
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
new file mode 100644
index 00000000..883c26f9
--- /dev/null
+++ b/src/modules/alsa/alsa-util.c
@@ -0,0 +1,1401 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2009 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <asoundlib.h>
+
+#include <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/thread.h>
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/core-rtclock.h>
+
+#include "alsa-util.h"
+#include "alsa-mixer.h"
+
+#ifdef HAVE_HAL
+#include "hal-util.h"
+#endif
+
+#ifdef HAVE_UDEV
+#include "udev-util.h"
+#endif
+
+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[] = {
+ [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8,
+ [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW,
+ [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW,
+ [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE,
+ [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE,
+ [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE,
+ [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE,
+ [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE,
+ [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE,
+ [PA_SAMPLE_S24LE] = SND_PCM_FORMAT_S24_3LE,
+ [PA_SAMPLE_S24BE] = SND_PCM_FORMAT_S24_3BE,
+ [PA_SAMPLE_S24_32LE] = SND_PCM_FORMAT_S24_LE,
+ [PA_SAMPLE_S24_32BE] = SND_PCM_FORMAT_S24_BE,
+ };
+
+ static const pa_sample_format_t try_order[] = {
+ PA_SAMPLE_FLOAT32NE,
+ PA_SAMPLE_FLOAT32RE,
+ PA_SAMPLE_S32NE,
+ PA_SAMPLE_S32RE,
+ PA_SAMPLE_S24_32NE,
+ PA_SAMPLE_S24_32RE,
+ PA_SAMPLE_S24NE,
+ PA_SAMPLE_S24RE,
+ PA_SAMPLE_S16NE,
+ PA_SAMPLE_S16RE,
+ PA_SAMPLE_ALAW,
+ PA_SAMPLE_ULAW,
+ PA_SAMPLE_U8
+ };
+
+ unsigned i;
+ int ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(hwparams);
+ pa_assert(f);
+
+ if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
+ return ret;
+
+ pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %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)
+ *f = PA_SAMPLE_FLOAT32BE;
+ else if (*f == PA_SAMPLE_S24BE)
+ *f = PA_SAMPLE_S24LE;
+ else if (*f == PA_SAMPLE_S24LE)
+ *f = PA_SAMPLE_S24BE;
+ else if (*f == PA_SAMPLE_S24_32BE)
+ *f = PA_SAMPLE_S24_32LE;
+ else if (*f == PA_SAMPLE_S24_32LE)
+ *f = PA_SAMPLE_S24_32BE;
+ else if (*f == PA_SAMPLE_S16BE)
+ *f = PA_SAMPLE_S16LE;
+ else if (*f == PA_SAMPLE_S16LE)
+ *f = PA_SAMPLE_S16BE;
+ else if (*f == PA_SAMPLE_S32BE)
+ *f = PA_SAMPLE_S32LE;
+ else if (*f == PA_SAMPLE_S32LE)
+ *f = PA_SAMPLE_S32BE;
+ else
+ goto try_auto;
+
+ if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
+ return ret;
+
+ pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s",
+ snd_pcm_format_description(format_trans[*f]),
+ pa_alsa_strerror(ret));
+
+try_auto:
+
+ 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)
+ return ret;
+
+ pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s",
+ snd_pcm_format_description(format_trans[*f]),
+ pa_alsa_strerror(ret));
+ }
+
+ return -1;
+}
+
+static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
+ snd_pcm_uframes_t s;
+ int d, ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(hwparams);
+
+ s = size;
+ d = 0;
+ if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
+ s = size;
+ d = -1;
+ if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) {
+ s = size;
+ d = 1;
+ if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) {
+ pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret));
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) {
+ int ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(hwparams);
+
+ if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) {
+ pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Set the hardware parameters of the given ALSA device. Returns the
+ * selected fragment settings in *buffer_size and *period_size. If tsched mode can be enabled */
+int pa_alsa_set_hw_params(
+ snd_pcm_t *pcm_handle,
+ pa_sample_spec *ss,
+ snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t *use_tsched,
+ pa_bool_t require_exact_channel_number) {
+
+ int ret = -1;
+ snd_pcm_hw_params_t *hwparams, *hwparams_copy;
+ int dir;
+ snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;
+ snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0;
+ pa_bool_t _use_mmap = use_mmap && *use_mmap;
+ pa_bool_t _use_tsched = use_tsched && *use_tsched;
+ pa_sample_spec _ss = *ss;
+
+ pa_assert(pcm_handle);
+ pa_assert(ss);
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_hw_params_alloca(&hwparams_copy);
+
+ if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ if (_use_mmap) {
+
+ if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
+
+ /* mmap() didn't work, fall back to interleaved */
+
+ if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ _use_mmap = FALSE;
+ }
+
+ } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ if (!_use_mmap)
+ _use_tsched = FALSE;
+
+ if (!pa_alsa_pcm_is_hw(pcm_handle))
+ _use_tsched = FALSE;
+
+#if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */
+ if (_use_tsched) {
+
+ /* try to disable period wakeups if hardware can do so */
+ if (snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) {
+
+ if (snd_pcm_hw_params_set_period_wakeup(pcm_handle, hwparams, FALSE) < 0)
+ /* don't bail, keep going with default mode with period wakeups */
+ pa_log_debug("snd_pcm_hw_params_set_period_wakeup() failed: %s", pa_alsa_strerror(ret));
+ else
+ pa_log_info("Trying to disable ALSA period wakeups, using timers only");
+ } else
+ pa_log_info("cannot disable ALSA period wakeups");
+ }
+#endif
+
+ if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 0)
+ goto finish;
+
+ if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &_ss.rate, NULL)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ /* We ignore very small sampling rate deviations */
+ if (_ss.rate >= ss->rate*.95 && _ss.rate <= ss->rate*1.05)
+ _ss.rate = ss->rate;
+
+ if (require_exact_channel_number) {
+ if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
+ goto finish;
+ }
+ } else {
+ unsigned int c = _ss.channels;
+
+ if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) {
+ pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ _ss.channels = c;
+ }
+
+ if (_use_tsched && tsched_size > 0) {
+ _buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate);
+ _period_size = _buffer_size;
+ } else {
+ _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate);
+ _buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate);
+ }
+
+ if (_buffer_size > 0 || _period_size > 0) {
+ snd_pcm_uframes_t max_frames = 0;
+
+ if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
+ pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret));
+ else
+ pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) (max_frames * PA_MSEC_PER_SEC / _ss.rate));
+
+ /* Some ALSA drivers really don't like if we set the buffer
+ * size first and the number of periods second. (which would
+ * make a lot more sense to me) So, try a few combinations
+ * before we give up. */
+
+ if (_buffer_size > 0 && _period_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* First try: set buffer size first, followed by period size */
+ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set buffer size first (to %lu samples), period size second (to %lu samples).", (unsigned long) _buffer_size, (unsigned long) _period_size);
+ goto success;
+ }
+
+ /* Second try: set period size first, followed by buffer size */
+ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set period size first (to %lu samples), buffer size second (to %lu samples).", (unsigned long) _period_size, (unsigned long) _buffer_size);
+ goto success;
+ }
+ }
+
+ if (_buffer_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* Third try: set only buffer size */
+ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set only buffer size (to %lu samples).", (unsigned long) _buffer_size);
+ goto success;
+ }
+ }
+
+ if (_period_size > 0) {
+ snd_pcm_hw_params_copy(hwparams_copy, hwparams);
+
+ /* Fourth try: set only period size */
+ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 &&
+ snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) {
+ pa_log_debug("Set only period size (to %lu samples).", (unsigned long) _period_size);
+ goto success;
+ }
+ }
+ }
+
+ pa_log_debug("Set neither period nor buffer size.");
+
+ /* Last chance, set nothing */
+ if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
+ pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+success:
+
+ if (ss->rate != _ss.rate)
+ pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate);
+
+ if (ss->channels != _ss.channels)
+ pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels);
+
+ if (ss->format != _ss.format)
+ pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format));
+
+ if ((ret = snd_pcm_prepare(pcm_handle)) < 0) {
+ pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) {
+ pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+ if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
+ (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) {
+ pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret));
+ goto finish;
+ }
+
+#if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */
+ if (_use_tsched) {
+ unsigned int no_wakeup;
+ /* see if period wakeups were disabled */
+ snd_pcm_hw_params_get_period_wakeup(pcm_handle, hwparams, &no_wakeup);
+ if (no_wakeup == 0)
+ pa_log_info("ALSA period wakeups disabled");
+ else
+ pa_log_info("ALSA period wakeups were not disabled");
+ }
+#endif
+
+ ss->rate = _ss.rate;
+ ss->channels = _ss.channels;
+ ss->format = _ss.format;
+
+ pa_assert(_period_size > 0);
+ pa_assert(_buffer_size > 0);
+
+ if (buffer_size)
+ *buffer_size = _buffer_size;
+
+ if (period_size)
+ *period_size = _period_size;
+
+ if (use_mmap)
+ *use_mmap = _use_mmap;
+
+ if (use_tsched)
+ *use_tsched = _use_tsched;
+
+ ret = 0;
+
+finish:
+
+ return ret;
+}
+
+int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, pa_bool_t period_event) {
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_uframes_t boundary;
+ int err;
+
+ pa_assert(pcm);
+
+ snd_pcm_sw_params_alloca(&swparams);
+
+ if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) {
+ pa_log_warn("Unable to determine current swparams: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) {
+ pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) {
+ pa_log_warn("Unable to enable time stamping: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_get_boundary(swparams, &boundary)) < 0) {
+ pa_log_warn("Unable to get boundary: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary)) < 0) {
+ pa_log_warn("Unable to set stop threshold: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
+ pa_log_warn("Unable to set start threshold: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) {
+ pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", pa_alsa_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
+ pa_log_warn("Unable to set sw params: %s\n", pa_alsa_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
+
+snd_pcm_t *pa_alsa_open_by_device_id_auto(
+ const char *dev_id,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t *use_tsched,
+ pa_alsa_profile_set *ps,
+ pa_alsa_mapping **mapping) {
+
+ char *d;
+ snd_pcm_t *pcm_handle;
+ void *state;
+ pa_alsa_mapping *m;
+
+ pa_assert(dev_id);
+ pa_assert(dev);
+ pa_assert(ss);
+ pa_assert(map);
+ pa_assert(ps);
+
+ /* First we try to find a device string with a superset of the
+ * requested channel map. We iterate through our device table from
+ * top to bottom and take the first that matches. If we didn't
+ * find a working device that way, we iterate backwards, and check
+ * all devices that do not provide a superset of the requested
+ * channel map.*/
+
+ PA_HASHMAP_FOREACH(m, ps->mappings, state) {
+ if (!pa_channel_map_superset(&m->channel_map, map))
+ continue;
+
+ pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]);
+
+ pcm_handle = pa_alsa_open_by_device_id_mapping(
+ dev_id,
+ dev,
+ ss,
+ map,
+ mode,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ m);
+
+ if (pcm_handle) {
+ if (mapping)
+ *mapping = m;
+
+ return pcm_handle;
+ }
+ }
+
+ PA_HASHMAP_FOREACH_BACKWARDS(m, ps->mappings, state) {
+ if (pa_channel_map_superset(&m->channel_map, map))
+ continue;
+
+ pa_log_debug("Checking for subset %s (%s)", m->name, m->device_strings[0]);
+
+ pcm_handle = pa_alsa_open_by_device_id_mapping(
+ dev_id,
+ dev,
+ ss,
+ map,
+ mode,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ m);
+
+ if (pcm_handle) {
+ if (mapping)
+ *mapping = m;
+
+ return pcm_handle;
+ }
+ }
+
+ /* 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,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ FALSE);
+ pa_xfree(d);
+
+ if (pcm_handle && mapping)
+ *mapping = NULL;
+
+ return pcm_handle;
+}
+
+snd_pcm_t *pa_alsa_open_by_device_id_mapping(
+ const char *dev_id,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t *use_tsched,
+ pa_alsa_mapping *m) {
+
+ snd_pcm_t *pcm_handle;
+ pa_sample_spec try_ss;
+ pa_channel_map try_map;
+
+ pa_assert(dev_id);
+ pa_assert(dev);
+ pa_assert(ss);
+ pa_assert(map);
+ pa_assert(m);
+
+ try_ss.channels = m->channel_map.channels;
+ try_ss.rate = ss->rate;
+ try_ss.format = ss->format;
+ try_map = m->channel_map;
+
+ pcm_handle = pa_alsa_open_by_template(
+ m->device_strings,
+ dev_id,
+ dev,
+ &try_ss,
+ &try_map,
+ mode,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ TRUE);
+
+ if (!pcm_handle)
+ return NULL;
+
+ *ss = try_ss;
+ *map = try_map;
+ pa_assert(map->channels == ss->channels);
+
+ return pcm_handle;
+}
+
+snd_pcm_t *pa_alsa_open_by_device_string(
+ const char *device,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t *use_tsched,
+ pa_bool_t require_exact_channel_number) {
+
+ int err;
+ char *d;
+ snd_pcm_t *pcm_handle;
+ pa_bool_t reformat = FALSE;
+
+ pa_assert(device);
+ pa_assert(ss);
+ pa_assert(map);
+
+ d = pa_xstrdup(device);
+
+ for (;;) {
+ pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with");
+
+ if ((err = snd_pcm_open(&pcm_handle, d, mode,
+ SND_PCM_NONBLOCK|
+ SND_PCM_NO_AUTO_RESAMPLE|
+ SND_PCM_NO_AUTO_CHANNELS|
+ (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) {
+ pa_log_info("Error opening PCM device %s: %s", d, pa_alsa_strerror(err));
+ goto fail;
+ }
+
+ pa_log_debug("Managed to open %s", d);
+
+ if ((err = pa_alsa_set_hw_params(
+ pcm_handle,
+ ss,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ require_exact_channel_number)) < 0) {
+
+ if (!reformat) {
+ reformat = TRUE;
+
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
+ /* 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;
+
+ t = pa_sprintf_malloc("plug:%s", d);
+ pa_xfree(d);
+ d = t;
+
+ reformat = FALSE;
+
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
+ pa_log_info("Failed to set hardware parameters on %s: %s", d, pa_alsa_strerror(err));
+ snd_pcm_close(pcm_handle);
+
+ goto fail;
+ }
+
+ if (dev)
+ *dev = d;
+ else
+ pa_xfree(d);
+
+ if (ss->channels != map->channels)
+ pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA);
+
+ return pcm_handle;
+ }
+
+fail:
+ pa_xfree(d);
+
+ return NULL;
+}
+
+snd_pcm_t *pa_alsa_open_by_template(
+ char **template,
+ const char *dev_id,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ snd_pcm_uframes_t *period_size,
+ snd_pcm_uframes_t *buffer_size,
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t *use_tsched,
+ pa_bool_t require_exact_channel_number) {
+
+ snd_pcm_t *pcm_handle;
+ char **i;
+
+ for (i = template; *i; i++) {
+ char *d;
+
+ d = pa_replace(*i, "%f", dev_id);
+
+ pcm_handle = pa_alsa_open_by_device_string(
+ d,
+ dev,
+ ss,
+ map,
+ mode,
+ period_size,
+ buffer_size,
+ tsched_size,
+ use_mmap,
+ use_tsched,
+ require_exact_channel_number);
+
+ pa_xfree(d);
+
+ if (pcm_handle)
+ return pcm_handle;
+ }
+
+ return NULL;
+}
+
+void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) {
+ int err;
+ snd_output_t *out;
+
+ pa_assert(pcm);
+
+ pa_assert_se(snd_output_buffer_open(&out) == 0);
+
+ if ((err = snd_pcm_dump(pcm, out)) < 0)
+ pa_logl(level, "snd_pcm_dump(): %s", pa_alsa_strerror(err));
+ else {
+ char *s = NULL;
+ snd_output_buffer_string(out, &s);
+ pa_logl(level, "snd_pcm_dump():\n%s", pa_strnull(s));
+ }
+
+ pa_assert_se(snd_output_close(out) == 0);
+}
+
+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);
+
+ if ((err = snd_output_buffer_open(&out)) < 0) {
+ pa_log_debug("snd_output_buffer_open() failed: %s", pa_cstrerror(err));
+ return;
+ }
+
+ 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) {
+ pa_log_debug("snd_pcm_dump(): %s", pa_alsa_strerror(err));
+ goto finish;
+ }
+
+ 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,...) {
+ va_list ap;
+ char *alsa_file;
+
+ alsa_file = pa_sprintf_malloc("(alsa-lib)%s", file);
+
+ va_start(ap, fmt);
+
+ pa_log_levelv_meta(PA_LOG_INFO, alsa_file, line, function, fmt, ap);
+
+ va_end(ap);
+
+ pa_xfree(alsa_file);
+}
+
+static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0);
+
+void pa_alsa_refcnt_inc(void) {
+ /* This is not really thread safe, but we do our best */
+
+ if (pa_atomic_inc(&n_error_handler_installed) == 0)
+ snd_lib_error_set_handler(alsa_error_handler);
+}
+
+void pa_alsa_refcnt_dec(void) {
+ int r;
+
+ pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1);
+
+ if (r == 1) {
+ snd_lib_error_set_handler(NULL);
+ snd_config_update_free_global();
+ }
+}
+
+pa_bool_t pa_alsa_init_description(pa_proplist *p) {
+ const char *d, *k;
+ pa_assert(p);
+
+ if (pa_device_init_description(p))
+ return TRUE;
+
+ if (!(d = pa_proplist_gets(p, "alsa.card_name")))
+ d = pa_proplist_gets(p, "alsa.name");
+
+ if (!d)
+ return FALSE;
+
+ k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION);
+
+ if (d && k)
+ pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k);
+ else if (d)
+ pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);
+
+ return FALSE;
+}
+
+void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) {
+ char *cn, *lcn, *dn;
+
+ pa_assert(p);
+ pa_assert(card >= 0);
+
+ pa_proplist_setf(p, "alsa.card", "%i", card);
+
+ if (snd_card_get_name(card, &cn) >= 0) {
+ pa_proplist_sets(p, "alsa.card_name", pa_strip(cn));
+ free(cn);
+ }
+
+ if (snd_card_get_longname(card, &lcn) >= 0) {
+ pa_proplist_sets(p, "alsa.long_card_name", pa_strip(lcn));
+ free(lcn);
+ }
+
+ if ((dn = pa_alsa_get_driver_name(card))) {
+ pa_proplist_sets(p, "alsa.driver_name", dn);
+ pa_xfree(dn);
+ }
+
+#ifdef HAVE_UDEV
+ pa_udev_get_info(card, p);
+#endif
+
+#ifdef HAVE_HAL
+ pa_hal_get_info(c, p, card);
+#endif
+}
+
+void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info) {
+
+ static const char * const alsa_class_table[SND_PCM_CLASS_LAST+1] = {
+ [SND_PCM_CLASS_GENERIC] = "generic",
+ [SND_PCM_CLASS_MULTI] = "multi",
+ [SND_PCM_CLASS_MODEM] = "modem",
+ [SND_PCM_CLASS_DIGITIZER] = "digitizer"
+ };
+ static const char * const class_table[SND_PCM_CLASS_LAST+1] = {
+ [SND_PCM_CLASS_GENERIC] = "sound",
+ [SND_PCM_CLASS_MULTI] = NULL,
+ [SND_PCM_CLASS_MODEM] = "modem",
+ [SND_PCM_CLASS_DIGITIZER] = NULL
+ };
+ static const char * const alsa_subclass_table[SND_PCM_SUBCLASS_LAST+1] = {
+ [SND_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix",
+ [SND_PCM_SUBCLASS_MULTI_MIX] = "multi-mix"
+ };
+
+ snd_pcm_class_t class;
+ snd_pcm_subclass_t subclass;
+ const char *n, *id, *sdn;
+ int card;
+
+ pa_assert(p);
+ pa_assert(pcm_info);
+
+ pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa");
+
+ 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]);
+ }
+
+ if ((subclass = snd_pcm_info_get_subclass(pcm_info)) <= SND_PCM_SUBCLASS_LAST)
+ if (alsa_subclass_table[subclass])
+ pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]);
+
+ if ((n = snd_pcm_info_get_name(pcm_info))) {
+ char *t = pa_xstrdup(n);
+ pa_proplist_sets(p, "alsa.name", pa_strip(t));
+ pa_xfree(t);
+ }
+
+ if ((id = snd_pcm_info_get_id(pcm_info)))
+ pa_proplist_sets(p, "alsa.id", id);
+
+ pa_proplist_setf(p, "alsa.subdevice", "%u", snd_pcm_info_get_subdevice(pcm_info));
+ if ((sdn = snd_pcm_info_get_subdevice_name(pcm_info)))
+ pa_proplist_sets(p, "alsa.subdevice_name", sdn);
+
+ pa_proplist_setf(p, "alsa.device", "%u", snd_pcm_info_get_device(pcm_info));
+
+ if ((card = snd_pcm_info_get_card(pcm_info)) >= 0)
+ 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_pcm_hw_params_t *hwparams;
+ snd_pcm_info_t *info;
+ int bits, err;
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_info_alloca(&info);
+
+ if ((err = snd_pcm_hw_params_current(pcm, hwparams)) < 0)
+ pa_log_warn("Error fetching hardware parameter info: %s", pa_alsa_strerror(err));
+ else {
+
+ if ((bits = snd_pcm_hw_params_get_sbits(hwparams)) >= 0)
+ pa_proplist_setf(p, "alsa.resolution_bits", "%i", bits);
+ }
+
+ if ((err = snd_pcm_info(pcm, info)) < 0)
+ pa_log_warn("Error fetching PCM info: %s", pa_alsa_strerror(err));
+ else
+ pa_alsa_init_proplist_pcm_info(c, p, info);
+}
+
+void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) {
+ int err;
+ snd_ctl_t *ctl;
+ snd_ctl_card_info_t *info;
+ const char *t;
+
+ pa_assert(p);
+
+ snd_ctl_card_info_alloca(&info);
+
+ if ((err = snd_ctl_open(&ctl, name, 0)) < 0) {
+ pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err));
+ return;
+ }
+
+ if ((err = snd_ctl_card_info(ctl, info)) < 0) {
+ pa_log_warn("Control device %s card info: %s", name, snd_strerror(err));
+ snd_ctl_close(ctl);
+ return;
+ }
+
+ 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)) && *t)
+ pa_proplist_sets(p, "alsa.components", t);
+
+ snd_ctl_close(ctl);
+}
+
+int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
+ snd_pcm_state_t state;
+ int err;
+
+ pa_assert(pcm);
+
+ if (revents & POLLERR)
+ pa_log_debug("Got POLLERR from ALSA");
+ if (revents & POLLNVAL)
+ pa_log_warn("Got POLLNVAL from ALSA");
+ if (revents & POLLHUP)
+ pa_log_warn("Got POLLHUP from ALSA");
+ if (revents & POLLPRI)
+ pa_log_warn("Got POLLPRI from ALSA");
+ if (revents & POLLIN)
+ pa_log_debug("Got POLLIN from ALSA");
+ if (revents & POLLOUT)
+ pa_log_debug("Got POLLOUT from ALSA");
+
+ state = snd_pcm_state(pcm);
+ pa_log_debug("PCM state is %s", snd_pcm_state_name(state));
+
+ /* Try to recover from this error */
+
+ switch (state) {
+
+ case SND_PCM_STATE_XRUN:
+ if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ if ((err = snd_pcm_recover(pcm, -ESTRPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", pa_alsa_strerror(err));
+ return -1;
+ }
+ break;
+
+ default:
+
+ snd_pcm_drop(pcm);
+
+ if ((err = snd_pcm_prepare(pcm)) < 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", pa_alsa_strerror(err));
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {
+ int n, err;
+ struct pollfd *pollfd;
+ pa_rtpoll_item *item;
+
+ pa_assert(pcm);
+
+ if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
+ return NULL;
+ }
+
+ item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, (unsigned) n);
+ pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
+
+ if ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) {
+ pa_log("snd_pcm_poll_descriptors() failed: %s", pa_alsa_strerror(err));
+ pa_rtpoll_item_free(item);
+ return NULL;
+ }
+
+ return item;
+}
+
+snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss) {
+ snd_pcm_sframes_t n;
+ size_t k;
+
+ pa_assert(pcm);
+ pa_assert(hwbuf_size > 0);
+ pa_assert(ss);
+
+ /* Some ALSA driver expose weird bugs, let's inform the user about
+ * what is going on */
+
+ n = snd_pcm_avail(pcm);
+
+ if (n <= 0)
+ return n;
+
+ k = (size_t) n * pa_frame_size(ss);
+
+ if (PA_UNLIKELY(k >= hwbuf_size * 5 ||
+ k >= pa_bytes_per_second(ss)*10)) {
+
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
+ pa_log(_("snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
+ (unsigned long) k,
+ (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ pa_alsa_dump(PA_LOG_ERROR, pcm);
+ } PA_ONCE_END;
+
+ /* Mhmm, let's try not to fail completely */
+ n = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
+ }
+
+ return n;
+}
+
+int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, pa_bool_t capture) {
+ ssize_t k;
+ size_t abs_k;
+ int r;
+ snd_pcm_sframes_t avail = 0;
+
+ pa_assert(pcm);
+ pa_assert(delay);
+ pa_assert(hwbuf_size > 0);
+ pa_assert(ss);
+
+ /* Some ALSA driver expose weird bugs, let's inform the user about
+ * what is going on. We're going to get both the avail and delay values so
+ * that we can compare and check them for capture */
+
+ if ((r = snd_pcm_avail_delay(pcm, &avail, delay)) < 0)
+ return r;
+
+ k = (ssize_t) *delay * (ssize_t) pa_frame_size(ss);
+
+ abs_k = k >= 0 ? (size_t) k : (size_t) -k;
+
+ if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 ||
+ abs_k >= pa_bytes_per_second(ss)*10)) {
+
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
+ pa_log(_("snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
+ (signed long) k,
+ k < 0 ? "-" : "",
+ (unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ pa_alsa_dump(PA_LOG_ERROR, pcm);
+ } PA_ONCE_END;
+
+ /* Mhmm, let's try not to fail completely */
+ if (k < 0)
+ *delay = -(snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
+ else
+ *delay = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
+ }
+
+ if (capture) {
+ abs_k = (size_t) avail * pa_frame_size(ss);
+
+ if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 ||
+ abs_k >= pa_bytes_per_second(ss)*10)) {
+
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
+ pa_log(_("snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
+ (unsigned long) k,
+ (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ pa_alsa_dump(PA_LOG_ERROR, pcm);
+ } PA_ONCE_END;
+
+ /* Mhmm, let's try not to fail completely */
+ avail = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
+ }
+
+ if (PA_UNLIKELY(*delay < avail)) {
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
+ pa_log(_("snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
+ (unsigned long) *delay,
+ (unsigned long) avail,
+ pa_strnull(dn));
+ pa_xfree(dn);
+ pa_alsa_dump(PA_LOG_ERROR, pcm);
+ } PA_ONCE_END;
+
+ /* try to fixup */
+ *delay = avail;
+ }
+ }
+
+ return 0;
+}
+
+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) {
+ int r;
+ snd_pcm_uframes_t before;
+ size_t k;
+
+ pa_assert(pcm);
+ pa_assert(areas);
+ pa_assert(offset);
+ pa_assert(frames);
+ pa_assert(hwbuf_size > 0);
+ pa_assert(ss);
+
+ before = *frames;
+
+ r = snd_pcm_mmap_begin(pcm, areas, offset, frames);
+
+ if (r < 0)
+ return r;
+
+ k = (size_t) *frames * pa_frame_size(ss);
+
+ if (PA_UNLIKELY(*frames > before ||
+ k >= hwbuf_size * 3 ||
+ k >= pa_bytes_per_second(ss)*10))
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
+ pa_log(_("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
+ (unsigned long) k,
+ (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ pa_alsa_dump(PA_LOG_ERROR, pcm);
+ } PA_ONCE_END;
+
+ return r;
+}
+
+char *pa_alsa_get_driver_name(int card) {
+ char *t, *m, *n;
+
+ pa_assert(card >= 0);
+
+ t = pa_sprintf_malloc("/sys/class/sound/card%i/device/driver/module", card);
+ m = pa_readlink(t);
+ pa_xfree(t);
+
+ if (!m)
+ return NULL;
+
+ n = pa_xstrdup(pa_path_get_filename(m));
+ pa_xfree(m);
+
+ return n;
+}
+
+char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm) {
+ int card;
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ pa_assert(pcm);
+
+ if (snd_pcm_info(pcm, info) < 0)
+ return NULL;
+
+ if ((card = snd_pcm_info_get_card(info)) < 0)
+ return NULL;
+
+ return pa_alsa_get_driver_name(card);
+}
+
+char *pa_alsa_get_reserve_name(const char *device) {
+ const char *t;
+ int i;
+
+ pa_assert(device);
+
+ if ((t = strchr(device, ':')))
+ device = t+1;
+
+ if ((i = snd_card_get_index(device)) < 0) {
+ int32_t k;
+
+ if (pa_atoi(device, &k) < 0)
+ return NULL;
+
+ i = (int) k;
+ }
+
+ return pa_sprintf_malloc("Audio%i", i);
+}
+
+pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm) {
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ pa_assert(pcm);
+
+ if (snd_pcm_info(pcm, info) < 0)
+ return FALSE;
+
+ return snd_pcm_info_get_card(info) >= 0;
+}
+
+pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm) {
+ snd_pcm_info_t* info;
+ snd_pcm_info_alloca(&info);
+
+ pa_assert(pcm);
+
+ if (snd_pcm_info(pcm, info) < 0)
+ return FALSE;
+
+ return snd_pcm_info_get_class(info) == SND_PCM_CLASS_MODEM;
+}
+
+PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree);
+
+const char* pa_alsa_strerror(int errnum) {
+ const char *original = NULL;
+ char *translated, *t;
+ char errbuf[128];
+
+ if ((t = PA_STATIC_TLS_GET(cstrerror)))
+ pa_xfree(t);
+
+ original = snd_strerror(errnum);
+
+ if (!original) {
+ pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", errnum);
+ original = errbuf;
+ }
+
+ if (!(translated = pa_locale_to_utf8(original))) {
+ pa_log_warn("Unable to convert error string to locale, filtering.");
+ translated = pa_utf8_filter(original);
+ }
+
+ PA_STATIC_TLS_SET(cstrerror, translated);
+
+ return translated;
+}
+
+pa_bool_t pa_alsa_may_tsched(pa_bool_t want) {
+
+ if (!want)
+ return FALSE;
+
+ if (!pa_rtclock_hrtimer()) {
+ /* We cannot depend on being woken up in time when the timers
+ are inaccurate, so let's fallback to classic IO based playback
+ then. */
+ pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel.");
+ return FALSE; }
+
+ if (pa_running_in_vm()) {
+ /* We cannot depend on being woken up when we ask for in a VM,
+ * so let's fallback to classic IO based playback then. */
+ pa_log_notice("Disabling timer-based scheduling because running inside a VM.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
new file mode 100644
index 00000000..ee5e781e
--- /dev/null
+++ b/src/modules/alsa/alsa-util.h
@@ -0,0 +1,143 @@
+#ifndef fooalsautilhfoo
+#define fooalsautilhfoo
+
+/***
+ 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/channelmap.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/core.h>
+#include <pulsecore/log.h>
+
+#include "alsa-mixer.h"
+
+int pa_alsa_set_hw_params(
+ snd_pcm_t *pcm_handle,
+ pa_sample_spec *ss, /* modified at return */
+ snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap, /* 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,
+ pa_bool_t period_event);
+
+/* 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, /* modified at return */
+ pa_sample_spec *ss, /* modified at return */
+ pa_channel_map* map, /* modified at return */
+ int mode,
+ snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap, /* modified at return */
+ pa_bool_t *use_tsched, /* modified at return */
+ pa_alsa_profile_set *ps,
+ pa_alsa_mapping **mapping); /* modified at return */
+
+/* Uses the specified mapping */
+snd_pcm_t *pa_alsa_open_by_device_id_mapping(
+ const char *dev_id,
+ char **dev, /* modified at return */
+ pa_sample_spec *ss, /* modified at return */
+ pa_channel_map* map, /* modified at return */
+ int mode,
+ snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap, /* modified at return */
+ pa_bool_t *use_tsched, /* modified at return */
+ pa_alsa_mapping *mapping);
+
+/* Opens the explicit ALSA device */
+snd_pcm_t *pa_alsa_open_by_device_string(
+ const char *dir,
+ char **dev, /* modified at return */
+ pa_sample_spec *ss, /* modified at return */
+ pa_channel_map* map, /* modified at return */
+ int mode,
+ snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap, /* modified at return */
+ pa_bool_t *use_tsched, /* modified at return */
+ pa_bool_t require_exact_channel_number);
+
+/* Opens the explicit ALSA device with a fallback list */
+snd_pcm_t *pa_alsa_open_by_template(
+ char **template,
+ const char *dev_id,
+ char **dev, /* modified at return */
+ pa_sample_spec *ss, /* modified at return */
+ pa_channel_map* map, /* modified at return */
+ int mode,
+ snd_pcm_uframes_t *period_size, /* modified at return */
+ snd_pcm_uframes_t *buffer_size, /* modified at return */
+ snd_pcm_uframes_t tsched_size,
+ pa_bool_t *use_mmap, /* modified at return */
+ pa_bool_t *use_tsched, /* modified at return */
+ pa_bool_t require_exact_channel_number);
+
+void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm);
+void pa_alsa_dump_status(snd_pcm_t *pcm);
+
+void pa_alsa_refcnt_inc(void);
+void pa_alsa_refcnt_dec(void);
+
+void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info);
+void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card);
+void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm);
+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);
+
+pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll);
+
+snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss);
+int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, pa_bool_t capture);
+int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss);
+
+char *pa_alsa_get_driver_name(int card);
+char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm);
+
+char *pa_alsa_get_reserve_name(const char *device);
+
+pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm);
+pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm);
+
+const char* pa_alsa_strerror(int errnum);
+
+pa_bool_t pa_alsa_may_tsched(pa_bool_t want);
+
+#endif
diff --git a/src/modules/alsa/mixer/paths/analog-input-aux.conf b/src/modules/alsa/mixer/paths/analog-input-aux.conf
new file mode 100644
index 00000000..3a7cb7b2
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-aux.conf
@@ -0,0 +1,66 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where an 'Aux' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 90
+name = analog-input
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Aux]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+switch = off
+volume = off
+
+.include analog-input.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
new file mode 100644
index 00000000..74826a96
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-dock-mic.conf
@@ -0,0 +1,81 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Dock Mic' or 'Dock Mic Boost' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 80
+name = analog-input-microphone-dock
+
+[Element Dock Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Dock Mic Boost:on]
+name = input-boost-on
+
+[Option Dock Mic Boost:off]
+name = input-boost-off
+
+[Element Dock Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Dock Mic]
+name = analog-input-microphone-dock
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Dock Mic]
+name = analog-input-microphone-dock
+required-any = any
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Front Mic]
+switch = off
+volume = off
+
+[Element Rear Mic]
+switch = off
+volume = off
+
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-fm.conf b/src/modules/alsa/mixer/paths/analog-input-fm.conf
new file mode 100644
index 00000000..7f150e36
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-fm.conf
@@ -0,0 +1,66 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where an 'FM' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 70
+name = analog-input-radio
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+.include analog-input.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-front-mic.conf b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
new file mode 100644
index 00000000..6c58ece1
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-front-mic.conf
@@ -0,0 +1,81 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Front Mic' or 'Front Mic Boost' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 90
+name = analog-input-microphone-front
+
+[Element Front Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Front Mic Boost:on]
+name = input-boost-on
+
+[Option Front Mic Boost:off]
+name = input-boost-off
+
+[Element Front Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Front Mic]
+name = analog-input-microphone-front
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Front Mic]
+name = analog-input-microphone-front
+required-any = any
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Rear Mic]
+switch = off
+volume = off
+
+[Element Dock Mic]
+switch = off
+volume = off
+
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf
new file mode 100644
index 00000000..70a1cd12
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-internal-mic.conf
@@ -0,0 +1,111 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Internal Mic' or 'Internal Mic Boost' element exists
+; 'Int Mic' and 'Int Mic Boost' are for compatibility with kernels < 2.6.38
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 89
+name = analog-input-microphone-internal
+
+[Element Internal Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Internal Mic Boost:on]
+name = input-boost-on
+
+[Option Internal Mic Boost:off]
+name = input-boost-off
+
+[Element Int Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Int Mic Boost:on]
+name = input-boost-on
+
+[Option Int Mic Boost:off]
+name = input-boost-off
+
+
+[Element Internal Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Int Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Internal Mic]
+name = analog-input-microphone-internal
+required-any = any
+
+[Option Input Source:Int Mic]
+name = analog-input-microphone-internal
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Internal Mic]
+name = analog-input-microphone-internal
+required-any = any
+
+[Option Capture Source:Int Mic]
+name = analog-input-microphone-internal
+required-any = any
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Dock Mic]
+switch = off
+volume = off
+
+[Element Front Mic]
+switch = off
+volume = off
+
+[Element Rear Mic]
+switch = off
+volume = off
+
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-linein.conf b/src/modules/alsa/mixer/paths/analog-input-linein.conf
new file mode 100644
index 00000000..461cebdb
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-linein.conf
@@ -0,0 +1,93 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Line' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 90
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line Boost]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Line]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Line]
+name = analog-input-linein
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Line]
+name = analog-input-linein
+required-any = any
+
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+switch = off
+volume = off
+
+[Element Mic Jack Mode]
+enumeration = select
+
+[Option Mic Jack Mode:Line In]
+priority = 19
+required-any = any
+name = input-linein
diff --git a/src/modules/alsa/mixer/paths/analog-input-mic-line.conf b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf
new file mode 100644
index 00000000..fa680aab
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-mic-line.conf
@@ -0,0 +1,67 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Mic/Line' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 90
+name = analog-input
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+switch = off
+volume = off
+
+.include analog-input.conf.common
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf b/src/modules/alsa/mixer/paths/analog-input-mic.conf
new file mode 100644
index 00000000..d88028bf
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf
@@ -0,0 +1,104 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Mic' or 'Mic Boost' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 89
+name = analog-input-microphone
+
+[Element Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Mic Boost:on]
+name = input-boost-on
+
+[Option Mic Boost:off]
+name = input-boost-off
+
+[Element Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Mic]
+name = analog-input-microphone
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Mic]
+name = analog-input-microphone
+required-any = any
+
+;;; Some AC'97s have "Mic Select" and "Mic Boost (+20dB)"
+
+[Element Mic Select]
+enumeration = select
+
+[Option Mic Select:Mic1]
+name = input-microphone
+priority = 20
+
+[Option Mic Select:Mic2]
+name = input-microphone
+priority = 19
+
+[Element Mic Boost (+20dB)]
+switch = select
+volume = merge
+
+[Option Mic Boost (+20dB):on]
+name = input-boost-on
+
+[Option Mic Boost (+20dB):off]
+name = input-boost-off
+
+[Element Front Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Rear Mic]
+switch = off
+volume = off
+
+[Element Dock Mic]
+switch = off
+volume = off
+
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-mic.conf.common b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common
new file mode 100644
index 00000000..2e4f0d81
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-mic.conf.common
@@ -0,0 +1,54 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Common element for all microphone inputs
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Line Boost]
+switch = off
+volume = off
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+switch = off
+volume = off
+
+[Element Mic Jack Mode]
+enumeration = select
+
+[Option Mic Jack Mode:Mic In]
+priority = 19
+name = input-microphone
diff --git a/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
new file mode 100644
index 00000000..75ed61b0
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-rear-mic.conf
@@ -0,0 +1,81 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Rear Mic' or 'Rear Mic Boost' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 89
+name = analog-input-microphone-rear
+
+[Element Rear Mic Boost]
+required-any = any
+switch = select
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Option Rear Mic Boost:on]
+name = input-boost-on
+
+[Option Rear Mic Boost:off]
+name = input-boost-off
+
+[Element Rear Mic]
+required-any = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Rear Mic]
+name = analog-input-microphone-rear
+required-any = any
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:Rear Mic]
+name = analog-input-microphone-rear
+required-any = any
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Front Mic]
+switch = off
+volume = off
+
+[Element Dock Mic]
+switch = off
+volume = off
+
+.include analog-input-mic.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf
new file mode 100644
index 00000000..fae3ce83
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-tvtuner.conf
@@ -0,0 +1,66 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'TV Tuner' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 70
+name = analog-input-video
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+switch = off
+volume = off
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element FM]
+switch = off
+volume = off
+
+.include analog-input.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input-video.conf b/src/modules/alsa/mixer/paths/analog-input-video.conf
new file mode 100644
index 00000000..19f18099
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input-video.conf
@@ -0,0 +1,65 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; For devices where a 'Video' element exists
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 70
+
+[Element Capture]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+switch = off
+volume = off
+
+[Element Internal Mic]
+switch = off
+volume = off
+
+[Element Line]
+switch = off
+volume = off
+
+[Element Aux]
+switch = off
+volume = off
+
+[Element Video]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic/Line]
+switch = off
+volume = off
+
+[Element TV Tuner]
+switch = off
+volume = off
+
+[Element FM]
+switch = off
+volume = off
+
+.include analog-input.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input.conf b/src/modules/alsa/mixer/paths/analog-input.conf
new file mode 100644
index 00000000..b86c3564
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input.conf
@@ -0,0 +1,83 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; A fallback for devices that lack seperate Mic/Line/Aux/Video/TV
+; Tuner/FM elements
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 100
+
+[Element Capture]
+required = volume
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Mic]
+required-absent = any
+
+[Element Dock Mic]
+required-absent = any
+
+[Element Dock Mic Boost]
+required-absent = any
+
+[Element Front Mic]
+required-absent = any
+
+[Element Front Mic Boost]
+required-absent = any
+
+[Element Int Mic]
+required-absent = any
+
+[Element Int Mic Boost]
+required-absent = any
+
+[Element Internal Mic]
+required-absent = any
+
+[Element Internal Mic Boost]
+required-absent = any
+
+[Element Rear Mic]
+required-absent = any
+
+[Element Rear Mic Boost]
+required-absent = any
+
+[Element Line]
+required-absent = any
+
+[Element Aux]
+required-absent = any
+
+[Element Video]
+required-absent = any
+
+[Element Mic/Line]
+required-absent = any
+
+[Element TV Tuner]
+required-absent = any
+
+[Element FM]
+required-absent = any
+
+.include analog-input.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-input.conf.common b/src/modules/alsa/mixer/paths/analog-input.conf.common
new file mode 100644
index 00000000..94165776
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-input.conf.common
@@ -0,0 +1,290 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Mixer path for PulseAudio's ALSA backend, common elements for all
+; input paths. If multiple options by the same id are discovered they
+; will be suffixed with a number to distuingish them, in the same
+; order they appear here.
+;
+; Source selection should use the following names:
+;
+; input -- If we don't know the exact kind of input
+; input-microphone
+; input-microphone-internal
+; input-microphone-external
+; input-linein
+; input-video
+; input-radio
+; input-docking-microphone
+; input-docking-linein
+; input-docking
+;
+; We explicitly don't want to wrap the following sources:
+;
+; CD
+; Synth/MIDI
+; Phone
+; Mix
+; Digital/SPDIF
+; Master
+; PC Speaker
+;
+; See analog-output.conf.common for an explanation on the directives
+
+;;; 'Input Source Select'
+
+[Element Input Source Select]
+enumeration = select
+
+[Option Input Source Select:Input1]
+name = input
+priority = 10
+
+[Option Input Source Select:Input2]
+name = input
+priority = 5
+
+;;; 'Input Source'
+
+[Element Input Source]
+enumeration = select
+
+[Option Input Source:Digital Mic]
+name = input-microphone
+priority = 20
+
+[Option Input Source:Microphone]
+name = input-microphone
+priority = 20
+
+[Option Input Source:Front Microphone]
+name = input-microphone
+priority = 19
+
+[Option Input Source:Internal Mic 1]
+name = input-microphone
+priority = 19
+
+[Option Input Source:Line-In]
+name = input-linein
+priority = 18
+
+[Option Input Source:Line In]
+name = input-linein
+priority = 18
+
+[Option Input Source:Docking-Station]
+name = input-docking
+priority = 17
+
+[Option Input Source:AUX IN]
+name = input
+priority = 10
+
+;;; 'Capture Source'
+
+[Element Capture Source]
+enumeration = select
+
+[Option Capture Source:TV Tuner]
+name = input-video
+
+[Option Capture Source:FM]
+name = input-radio
+
+[Option Capture Source:Mic/Line]
+name = input
+
+[Option Capture Source:Line/Mic]
+name = input
+
+[Option Capture Source:Microphone]
+name = input-microphone
+
+[Option Capture Source:Int DMic]
+name = input-microphone-internal
+
+[Option Capture Source:iMic]
+name = input-microphone-internal
+
+[Option Capture Source:i-Mic]
+name = input-microphone-internal
+
+[Option Capture Source:Internal Microphone]
+name = input-microphone-internal
+
+[Option Capture Source:Front Microphone]
+name = input-microphone
+
+[Option Capture Source:Mic1]
+name = input-microphone
+
+[Option Capture Source:Mic2]
+name = input-microphone
+
+[Option Capture Source:D-Mic]
+name = input-microphone
+
+[Option Capture Source:IntMic]
+name = input-microphone-internal
+
+[Option Capture Source:ExtMic]
+name = input-microphone-external
+
+[Option Capture Source:Ext Mic]
+name = input-microphone-external
+
+[Option Capture Source:E-Mic]
+name = input-microphone-external
+
+[Option Capture Source:e-Mic]
+name = input-microphone-external
+
+[Option Capture Source:LineIn]
+name = input-linein
+
+[Option Capture Source:Analog]
+name = input
+
+[Option Capture Source:Line-In]
+name = input-linein
+
+[Option Capture Source:Line In]
+name = input-linein
+
+[Option Capture Source:Video]
+name = input-video
+
+[Option Capture Source:Aux]
+name = input
+
+[Option Capture Source:Aux0]
+name = input
+
+[Option Capture Source:Aux1]
+name = input
+
+[Option Capture Source:Aux2]
+name = input
+
+[Option Capture Source:Aux3]
+name = input
+
+[Option Capture Source:AUX IN]
+name = input
+
+[Option Capture Source:Aux In]
+name = input
+
+[Option Capture Source:AOUT]
+name = input
+
+[Option Capture Source:AUX]
+name = input
+
+[Option Capture Source:Cam Mic]
+name = input-microphone
+
+[Option Capture Source:Digital Mic]
+name = input-microphone
+
+[Option Capture Source:Digital Mic 1]
+name = input-microphone
+
+[Option Capture Source:Digital Mic 2]
+name = input-microphone
+
+[Option Capture Source:Analog Inputs]
+name = input
+
+[Option Capture Source:Unknown1]
+name = input
+
+[Option Capture Source:Unknown2]
+name = input
+
+[Option Capture Source:Docking-Station]
+name = input-docking
+
+;;; 'Mic Jack Mode'
+
+[Element Mic Jack Mode]
+enumeration = select
+
+[Option Mic Jack Mode:Mic In]
+name = input-microphone
+
+[Option Mic Jack Mode:Line In]
+name = input-linein
+
+;;; 'Digital Input Source'
+
+[Element Digital Input Source]
+enumeration = select
+
+[Option Digital Input Source:Analog Inputs]
+name = input
+
+[Option Digital Input Source:Digital Mic 1]
+name = input-microphone
+
+[Option Digital Input Source:Digital Mic 2]
+name = input-microphone
+
+;;; 'Analog Source'
+
+[Element Analog Source]
+enumeration = select
+
+[Option Analog Source:Mic]
+name = input-microphone
+
+[Option Analog Source:Line in]
+name = input-linein
+
+[Option Analog Source:Aux]
+name = input
+
+;;; 'Shared Mic/Line in'
+
+[Element Shared Mic/Line in]
+enumeration = select
+
+[Option Shared Mic/Line in:Mic in]
+name = input-microphone
+
+[Option Shared Mic/Line in:Line in]
+name = input-linein
+
+;;; Various Boosts
+
+[Element Capture Boost]
+switch = select
+
+[Option Capture Boost:on]
+name = input-boost-on
+
+[Option Capture Boost:off]
+name = input-boost-off
+
+[Element Auto Gain Control]
+switch = select
+
+[Option Auto Gain Control:on]
+name = input-agc-on
+
+[Option Auto Gain Control:off]
+name = input-agc-off
diff --git a/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf
new file mode 100644
index 00000000..dfdecf41
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-desktop-speaker.conf
@@ -0,0 +1,99 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Desktop Speaker' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 101
+name = analog-output-speaker
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the desktop speaker, not
+; the headphones. But it should not hurt if we leave the headphone
+; jack enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+switch = off
+volume = off
+
+[Element Desktop Speaker]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Front]
+switch = mute
+volume = merge
+override-map.1 = all-front
+override-map.2 = front-left,front-right
+
+[Element Rear]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Surround]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Side]
+switch = mute
+volume = merge
+override-map.1 = all-side
+override-map.2 = side-left,side-right
+
+[Element Center]
+switch = mute
+volume = merge
+override-map.1 = all-center
+override-map.2 = all-center,all-center
+
+[Element LFE]
+switch = mute
+volume = merge
+override-map.1 = lfe
+override-map.2 = lfe,lfe
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
new file mode 100644
index 00000000..e47543f5
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones-2.conf
@@ -0,0 +1,87 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Headphone2' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 89
+name = analog-output-headphones
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the second headphones, not
+; the first headphones. But it should not hurt if we leave the
+; headphone jack enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Speaker]
+switch = off
+volume = off
+
+[Element Desktop Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = off
+volume = off
+
+[Element Rear]
+switch = off
+volume = off
+
+[Element Surround]
+switch = off
+volume = off
+
+[Element Side]
+switch = off
+volume = off
+
+[Element Center]
+switch = off
+volume = off
+
+[Element LFE]
+switch = off
+volume = off
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-headphones.conf b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
new file mode 100644
index 00000000..1d7bb0ba
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-headphones.conf
@@ -0,0 +1,87 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Headphone' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 90
+name = analog-output-headphones
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+[Element Headphone]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+; This profile path is intended to control the first headphones, not
+; the second headphones. But it should not hurt if we leave the second
+; headphone jack enabled nonetheless.
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+switch = off
+volume = off
+
+[Element Desktop Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = off
+volume = off
+
+[Element Rear]
+switch = off
+volume = off
+
+[Element Surround]
+switch = off
+volume = off
+
+[Element Side]
+switch = off
+volume = off
+
+[Element Center]
+switch = off
+volume = off
+
+[Element LFE]
+switch = off
+volume = off
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
new file mode 100644
index 00000000..67ee32f7
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
@@ -0,0 +1,89 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Intended for usage in laptops that have a seperate LFE speaker
+; connected to the Master mono connector
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 40
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all-no-lfe
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+required = any
+switch = mute
+volume = merge
+override-map.1 = lfe
+override-map.2 = lfe,lfe
+
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Desktop Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = off
+volume = off
+
+[Element Rear]
+switch = off
+volume = off
+
+[Element Surround]
+switch = off
+volume = off
+
+[Element Side]
+switch = off
+volume = off
+
+[Element Center]
+switch = off
+volume = off
+
+[Element LFE]
+switch = off
+volume = off
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf
new file mode 100644
index 00000000..13a2d6aa
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf
@@ -0,0 +1,86 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Intended for usage on boards that have a seperate Mono output plug.
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 50
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = off
+volume = off
+
+[Element Master Mono]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Desktop Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = off
+volume = off
+
+[Element Rear]
+switch = off
+volume = off
+
+[Element Surround]
+switch = off
+volume = off
+
+[Element Side]
+switch = off
+volume = off
+
+[Element Center]
+switch = off
+volume = off
+
+[Element LFE]
+switch = off
+volume = off
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
new file mode 100644
index 00000000..c6916d6b
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf
@@ -0,0 +1,99 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Path for mixers that have a 'Speaker' control
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 100
+name = analog-output-speaker
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+required = any
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Desktop Speaker]
+switch = off
+volume = off
+
+[Element Front]
+switch = mute
+volume = merge
+override-map.1 = all-front
+override-map.2 = front-left,front-right
+
+[Element Rear]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Surround]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Side]
+switch = mute
+volume = merge
+override-map.1 = all-side
+override-map.2 = side-left,side-right
+
+[Element Center]
+switch = mute
+volume = merge
+override-map.1 = all-center
+override-map.2 = all-center,all-center
+
+[Element LFE]
+switch = mute
+volume = merge
+override-map.1 = lfe
+override-map.2 = lfe,lfe
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf
new file mode 100644
index 00000000..50fc88ea
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output.conf
@@ -0,0 +1,96 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Intended for the 'default' output. Note that a-o-speaker.conf has a
+; higher priority than this
+;
+; See analog-output.conf.common for an explanation on the directives
+
+[General]
+priority = 99
+
+[Element Hardware Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element Master Mono]
+switch = off
+volume = off
+
+; This profile path is intended to control the default output, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
+[Element Headphone]
+switch = mute
+volume = zero
+
+[Element Headphone2]
+switch = mute
+volume = zero
+
+[Element Speaker]
+switch = mute
+volume = off
+
+[Element Desktop Speaker]
+switch = mute
+volume = off
+
+[Element Front]
+switch = mute
+volume = merge
+override-map.1 = all-front
+override-map.2 = front-left,front-right
+
+[Element Rear]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Surround]
+switch = mute
+volume = merge
+override-map.1 = all-rear
+override-map.2 = rear-left,rear-right
+
+[Element Side]
+switch = mute
+volume = merge
+override-map.1 = all-side
+override-map.2 = side-left,side-right
+
+[Element Center]
+switch = mute
+volume = merge
+override-map.1 = all-center
+override-map.2 = all-center,all-center
+
+[Element LFE]
+switch = mute
+volume = merge
+override-map.1 = lfe
+override-map.2 = lfe,lfe
+
+.include analog-output.conf.common
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
new file mode 100644
index 00000000..ccaa494b
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -0,0 +1,147 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Common part of all paths
+
+; So here's generally how mixer paths are used by PA: PA goes through
+; a mixer path file from top to bottom and checks if a mixer element
+; described therein exists. If so it is added to the list of mixer
+; elements PA will control, keeping the order it read them in. If a
+; mixer element described here has set the required= or
+; required-absent= directives a path might not be accepted as valid
+; and is ignored in its entirety (see below). However usually if a
+; element listed here is missing this one element is ignored but not
+; the entire path.
+;
+; When a device shall be muted/unmuted *all* elements listed in a path
+; file with "switch = mute" will be toggled.
+;
+; When a device shall change its volume, PA will got through the list
+; of all elements with "volume = merge" and set the volume on the
+; first element. If that element does not support dB volumes, this is
+; where the story ends. If it does support dB volumes, PA divides the
+; requested volume by the volume that was set on this element, and
+; then go on to the next element with "volume = merge" and then set
+; that there, and so on. That way the first volume element in the
+; path will be the one that does the 'biggest' part of the overall
+; volume adjustment, with the remaining elements usually being set to
+; some value next to 0dB. This logic makes sure we get the full range
+; over all volume sliders and a very high granularity of volumes
+; already in hardware.
+;
+; All switches and enumerations set to "select" are exposed via the
+; "port" functionality of sinks/sources. Basically every possible
+; switch setting and every possible enumeration setting will be
+; combined and made into a "port". So make sure you don't list too
+; many switches/enums for exposing, because the number of ports might
+; rise exponentially.
+;
+; Only one path can be selected at a time. All paths that are valid
+; for an audio device will be exposed as "port" for the sink/source.
+
+
+; [General]
+; priority = ... # Priority for this path
+; description = ...
+;
+; [Option ...:...] # For each option of an enumeration or switch element
+; # that shall be exposed as a sink/source port. Needs to
+; # be named after the Element, followed by a colon, followed
+; # by the option name, resp. on/off if the element is a switch.
+; name = ... # Logical name to use in the path identifier
+; priority = ... # Priority if this is made into a device port
+; required = ignore | enumeration | any # In this element, this option must exist or the path will be invalid. ("any" is an alias for "enumeration".)
+; required-any = ignore | enumeration | any # In this element, either this or another option must exist (or an element)
+; required-absent = ignore | enumeration | any # In this element, this option must not exist or the path will be invalid
+;
+; [Element ...] # For each element that we shall control
+; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available,
+; # otherwise don't consider this path valid for the card
+; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements with required-any in this
+; # path must be present, otherwise this path is invalid for the card
+; required-absent = ignore | switch | volume # If set, require this element to not be of this kind and not
+; # available, otherwise don't consider this path valid for the card
+;
+; switch = ignore | mute | off | on | select # What to do with this switch: ignore it, make it follow mute status,
+; # always set it to off, always to on, or make it selectable as port.
+; # If set to 'select' you need to define an Option section for on
+; # and off
+; volume = ignore | merge | off | zero | <volume step> # What to do with this volume: ignore it, merge it into the device
+; # volume slider, always set it to the lowest value possible, or always
+; # set it to 0 dB (for whatever that means), or always set it to
+; # <volume step> (this only makes sense in path configurations where
+; # the exact hardware and driver are known beforehand).
+; volume-limit = <volume step> # Limit the maximum volume by disabling the volume steps above <volume step>.
+; enumeration = ignore | select # What to do with this enumeration, ignore it or make it selectable
+; # via device ports. If set to 'select' you need to define an Option section
+; # for each of the items you want to expose
+; direction = playback | capture # Is this relevant only for playback or capture? If not set this will implicitly be
+; # set the direction of the PCM device is opened as. Generally this doesn't need to be set
+; # unless you have a broken driver that has playback controls marked for capture or vice
+; # versa
+; direction-try-other = no | yes # If the element does not supported what is requested, try the other direction, too?
+;
+; override-map.1 = ... # Override the channel mask of the mixer control if the control only exposes a single channel
+; override-map.2 = ... # Override the channel masks of the mixer control if the control only exposes two channels
+; # Override maps should list for each element channel which high-level channels it controls via a
+; # channel mask. A channel mask may either be the name of a single channel, or the words "all-left",
+; # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of
+; # channels in a mask
+
+[Element PCM]
+switch = mute
+volume = merge
+override-map.1 = all
+override-map.2 = all-left,all-right
+
+[Element External Amplifier]
+switch = select
+
+[Option External Amplifier:on]
+name = output-amplifier-on
+priority = 10
+
+[Option External Amplifier:off]
+name = output-amplifier-off
+priority = 0
+
+[Element Bass Boost]
+switch = select
+
+[Option Bass Boost:on]
+name = output-bass-boost-on
+priority = 0
+
+[Option Bass Boost:off]
+name = output-bass-boost-off
+priority = 10
+
+;;; 'Analog Output'
+
+[Element Analog Output]
+enumeration = select
+
+[Option Analog Output:Speakers]
+name = output-speaker
+priority = 10
+
+[Option Analog Output:Headphones]
+name = output-headphones
+priority = 9
+
+[Option Analog Output:FP Headphones]
+name = output-headphones
+priority = 8
diff --git a/src/modules/alsa/mixer/paths/iec958-stereo-output.conf b/src/modules/alsa/mixer/paths/iec958-stereo-output.conf
new file mode 100644
index 00000000..8506a580
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/iec958-stereo-output.conf
@@ -0,0 +1,19 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+[Element IEC958]
+switch = mute
diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
new file mode 100644
index 00000000..e1da3314
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -0,0 +1,40 @@
+# do not edit this file, it will be overwritten on update
+
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+SUBSYSTEM!="sound", GOTO="pulseaudio_end"
+ACTION!="change", GOTO="pulseaudio_end"
+KERNEL!="card*", GOTO="pulseaudio_end"
+
+# Some specific work arounds until we can handle heasets/handsets properly (i.e. "Speaker" only, no "master")
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="01ab", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a0c", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0002", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
+# UAC1.0 Sennheiser Dongle
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="3554", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
+# BT Agile Handset
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1885", ATTRS{idProduct}=="0501", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
+
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{PULSE_PROFILE_SET}="native-instruments-korecontroller.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio10.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
+
+LABEL="pulseaudio_end"
diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf
new file mode 100644
index 00000000..283edfb3
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/default.conf
@@ -0,0 +1,180 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Default profile definitions for the ALSA backend of PulseAudio. This
+; is used as fallback for all cards that have no special mapping
+; assigned (and should be good enough for the vast majority of
+; cards). If you want to assign a different profile set than this one
+; to a device, either set the udev property PULSE_PROFILE_SET for the
+; card, or use the "profile_set" module argument when loading
+; module-alsa-card.
+;
+; So what is this about? Simply, what we do here is map ALSA devices
+; to how they are exposed in PA. We say which ALSA device string to
+; use to open a device, which channel mapping to use then, and which
+; mixer path to use. This is encoded in a 'mapping'. Multiple of these
+; mappings can be bound together in a 'profile' which is then directly
+; exposed in the UI as a card profile. Each mapping assigned to a
+; profile will result in one sink/source to be created if the profile
+; is selected for the card.
+;
+; Additionally, the path set configuration files can describe the
+; decibel values assigned to the steps of the volume elements. This
+; can be used to work around situations when the alsa driver doesn't
+; provide any decibel information, or when the information is
+; incorrect.
+
+
+; [General]
+; auto-profiles = no | yes # Instead of defining all profiles manually, autogenerate
+; # them by combining every input mapping with every output mapping.
+;
+; [Mapping id]
+; device-strings = ... # ALSA device string. %f will be replaced by the card identifier.
+; channel-map = ... # Channel mapping to use for this device
+; description = ...
+; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed.
+; # If multiple are found to be working they will be available as device ports
+; paths-output = ...
+; element-input = ... # Instead of configuring a full mixer path simply configure a single
+; # mixer element for volume/mute handling
+; element-output = ...
+; priority = ...
+; direction = any | input | output # Only useful for?
+;
+; [Profile id]
+; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be
+; # defined in this file too
+; output-mappings = ... # Lists mappings for sinks on this profile, those mappings must be
+; # defined in this file too
+; description = ...
+; priority = ... # Numeric value to deduce priority for this profile
+; skip-probe = no | yes # Skip probing for availability? If this is yes then this profile
+; # will be assumed as working without probing. Makes initialization
+; # a bit faster but only works if the card is really known well.
+;
+; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB
+; # information from alsa. A decibel fix is a table that maps volume steps
+; # to decibel values for one volume element. The "element" part in the
+; # section title is the name of the volume element.
+; #
+; # NOTE: This feature is meant just as a help for figuring out the correct
+; # decibel values. Pulseaudio is not the correct place to maintain the
+; # decibel mappings!
+; #
+; # If you need this feature, then you should make sure that when you have
+; # the correct values figured out, the alsa driver developers get informed
+; # too, so that they can fix the driver.
+;
+; db-values = ... # The option value consists of pairs of step numbers and decibel values.
+; # The pairs are separated with whitespace, and steps are separated from
+; # the corresponding decibel values with a colon. The values must be in an
+; # increasing order. Here's an example of a valid string:
+; #
+; # "0:-40.50 1:-38.70 3:-33.00 11:0"
+; #
+; # The lowest step imposes a lower limit for hardware volume and the
+; # highest step correspondingly imposes a higher limit. That means that
+; # that the mixer will never be set outside those values - the rest of the
+; # volume scale is done using software volume.
+; #
+; # As can be seen in the example, you don't need to specify a dB value for
+; # each step. The dB values for skipped steps will be linearly interpolated
+; # using the nearest steps that are given.
+
+[General]
+auto-profiles = yes
+
+[Mapping analog-mono]
+device-strings = hw:%f
+channel-map = mono
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
+paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 1
+
+[Mapping analog-stereo]
+device-strings = front:%f hw:%f
+channel-map = left,right
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono analog-output-lfe-on-mono
+paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 10
+
+[Mapping analog-surround-40]
+device-strings = surround40:%f
+channel-map = front-left,front-right,rear-left,rear-right
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping analog-surround-41]
+device-strings = surround41:%f
+channel-map = front-left,front-right,rear-left,rear-right,lfe
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 8
+direction = output
+
+[Mapping analog-surround-50]
+device-strings = surround50:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping analog-surround-51]
+device-strings = surround51:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 8
+direction = output
+
+[Mapping analog-surround-71]
+device-strings = surround71:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
+description = Analog Surround 7.1
+paths-output = analog-output analog-output-speaker analog-output-desktop-speaker analog-output-lfe-on-mono
+priority = 7
+direction = output
+
+[Mapping iec958-stereo]
+device-strings = iec958:%f
+channel-map = left,right
+paths-input = iec958-stereo-input
+paths-output = iec958-stereo-output
+priority = 5
+
+[Mapping iec958-ac3-surround-40]
+device-strings = a52:%f
+channel-map = front-left,front-right,rear-left,rear-right
+priority = 2
+direction = output
+
+[Mapping iec958-ac3-surround-51]
+device-strings = a52:%f
+channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
+priority = 3
+direction = output
+
+[Mapping hdmi-stereo]
+device-strings = hdmi:%f
+channel-map = left,right
+priority = 4
+direction = output
+
+; An example for defining multiple-sink profiles
+#[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo]
+#description = Foobar
+#output-mappings = analog-stereo iec958-stereo
+#input-mappings = analog-stereo
diff --git a/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf b/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf
new file mode 100644
index 00000000..75f51121
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf
@@ -0,0 +1,85 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; M-Audio FastTrack Pro
+;
+; This card has one duplex stereo channel called A and an additional
+; stereo output channel called B.
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-a-output]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-a-input]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-b-output]
+description = Analog Stereo Channel B
+device-strings = hw:%f,1,0
+channel-map = left,right
+direction = output
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Channel A, Analog Stereo output Channel B
+output-mappings = analog-stereo-a-output analog-stereo-b-output
+input-mappings = analog-stereo-a-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-a-output+input:analog-stereo-a-input]
+description = Analog Stereo Duplex Channel A
+output-mappings = analog-stereo-a-output
+input-mappings = analog-stereo-a-input
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-b+input:analog-stereo-b]
+description = Analog Stereo Output Channel B
+output-mappings = analog-stereo-b-output
+input-mappings =
+priority = 50
+skip-probe = yes
+
+[Profile output:analog-stereo-a]
+description = Analog Stereo Output Channel A
+output-mappings = analog-stereo-a-output
+priority = 5
+skip-probe = yes
+
+[Profile output:analog-stereo-b]
+description = Analog Stereo Output Channel B
+output-mappings = analog-stereo-b-output
+priority = 6
+skip-probe = yes
+
+[Profile input:analog-stereo-a]
+description = Analog Stereo Input Channel A
+input-mappings = analog-stereo-a-input
+priority = 2
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf
new file mode 100644
index 00000000..2b835308
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf
@@ -0,0 +1,91 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Audio 4 DJ
+;
+; This card has two stereo pairs of input and two stereo pairs of
+; output, named channels A and B. Channel B has an additional
+; Headphone connector.
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-b-output]
+description = Analog Stereo Channel B (Headphones)
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-b-input]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Channels A, B (Headphones)
+output-mappings = analog-stereo-a analog-stereo-b-output
+input-mappings = analog-stereo-a analog-stereo-b-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-a]
+description = Analog Stereo Duplex Channel A
+output-mappings = analog-stereo-a
+input-mappings = analog-stereo-a
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-b+input:analog-stereo-b]
+description = Analog Stereo Duplex Channel B (Headphones)
+output-mappings = analog-stereo-b-output
+input-mappings = analog-stereo-b-input
+priority = 50
+skip-probe = yes
+
+[Profile output:analog-stereo-a]
+description = Analog Stereo Output Channel A
+output-mappings = analog-stereo-a
+priority = 5
+skip-probe = yes
+
+[Profile output:analog-stereo-b]
+description = Analog Stereo Output Channel B (Headphones)
+output-mappings = analog-stereo-b-output
+priority = 6
+skip-probe = yes
+
+[Profile input:analog-stereo-a]
+description = Analog Stereo Input Channel A
+input-mappings = analog-stereo-a
+priority = 2
+skip-probe = yes
+
+[Profile input:analog-stereo-b]
+description = Analog Stereo Input Channel B
+input-mappings = analog-stereo-b-input
+priority = 1
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf
new file mode 100644
index 00000000..3fe3cc56
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf
@@ -0,0 +1,162 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Audio 8 DJ
+;
+; This card has four stereo pairs of input and four stereo pairs of
+; output, named channels A to D. Channel C has an additional Mic/Line
+; connector, channel D an additional Headphone connector.
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-b]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+
+# Since we want to set a different description for channel C's/D's input
+# and output we define two seperate mappings for them
+[Mapping analog-stereo-c-output]
+description = Analog Stereo Channel C
+device-strings = hw:%f,0,2
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-c-input]
+description = Analog Stereo Channel C (Line/Mic)
+device-strings = hw:%f,0,2
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-d-output]
+description = Analog Stereo Channel D (Headphones)
+device-strings = hw:%f,0,3
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-d-input]
+description = Analog Stereo Channel D
+device-strings = hw:%f,0,3
+channel-map = left,right
+direction = input
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Channels A, B, C (Line/Mic), D (Headphones)
+output-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-output analog-stereo-d-output
+input-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-input analog-stereo-d-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-d+input:analog-stereo-c]
+description = Analog Stereo Channel D (Headphones) Output, Channel C (Line/Mic) Input
+output-mappings = analog-stereo-d-output
+input-mappings = analog-stereo-c-input
+priority = 90
+skip-probe = yes
+
+[Profile output:analog-stereo-c-d+input:analog-stereo-c-d]
+description = Analog Stereo Duplex Channels C (Line/Mic), D (Line/Mic)
+output-mappings = analog-stereo-c-output analog-stereo-d-output
+input-mappings = analog-stereo-c-input analog-stereo-d-input
+priority = 80
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-a]
+description = Analog Stereo Duplex Channel A
+output-mappings = analog-stereo-a
+input-mappings = analog-stereo-a
+priority = 50
+skip-probe = yes
+
+[Profile output:analog-stereo-b+input:analog-stereo-b]
+description = Analog Stereo Duplex Channel B
+output-mappings = analog-stereo-b
+input-mappings = analog-stereo-b
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-c+input:analog-stereo-c]
+description = Analog Stereo Duplex Channel C (Line/Mic)
+output-mappings = analog-stereo-c-output
+input-mappings = analog-stereo-c-input
+priority = 60
+skip-probe = yes
+
+[Profile output:analog-stereo-d+input:analog-stereo-d]
+description = Analog Stereo Duplex Channel D (Headphones)
+output-mappings = analog-stereo-d-output
+input-mappings = analog-stereo-d-input
+priority = 70
+skip-probe = yes
+
+[Profile output:analog-stereo-a]
+description = Analog Stereo Output Channel A
+output-mappings = analog-stereo-a
+priority = 6
+skip-probe = yes
+
+[Profile output:analog-stereo-b]
+description = Analog Stereo Output Channel B
+output-mappings = analog-stereo-b
+priority = 5
+skip-probe = yes
+
+[Profile output:analog-stereo-c]
+description = Analog Stereo Output Channel C
+output-mappings = analog-stereo-c-output
+priority = 7
+skip-probe = yes
+
+[Profile output:analog-stereo-d]
+description = Analog Stereo Output Channel D (Headphones)
+output-mappings = analog-stereo-d-output
+priority = 8
+skip-probe = yes
+
+[Profile input:analog-stereo-a]
+description = Analog Stereo Input Channel A
+input-mappings = analog-stereo-a
+priority = 2
+skip-probe = yes
+
+[Profile input:analog-stereo-b]
+description = Analog Stereo Input Channel B
+input-mappings = analog-stereo-b
+priority = 1
+skip-probe = yes
+
+[Profile input:analog-stereo-c]
+description = Analog Stereo Input Channel C (Line/Mic)
+input-mappings = analog-stereo-c-input
+priority = 4
+skip-probe = yes
+
+[Profile input:analog-stereo-d]
+description = Analog Stereo Input Channel D
+input-mappings = analog-stereo-d-input
+priority = 3
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
new file mode 100644
index 00000000..904357d0
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
@@ -0,0 +1,85 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Kore Controller
+;
+; This card has one stereo pairs of input and two stereo pairs of
+; output, named "Master" and "Headphone". The master channel has
+; an additional Coax S/PDIF connector which is always on.
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-master-out]
+description = Analog Stereo Master Channel
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-headphone-out]
+description = Analog Stereo Headphone Channel
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-input]
+description = Analog Stereo
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = input
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Master Output, Headphones Output
+output-mappings = analog-stereo-master-out analog-stereo-headphone-out
+input-mappings = analog-stereo-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-master+input:analog-stereo-input]
+description = Analog Stereo Duplex Master Output
+output-mappings = analog-stereo-master-out
+input-mappings = analog-stereo-input
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone-out+input:analog-stereo-input]
+description = Analog Stereo Headphones Output
+output-mappings = analog-stereo-headphone-out
+input-mappings = analog-stereo-input
+priority = 30
+skip-probe = yes
+
+[Profile output:analog-stereo-master]
+description = Analog Stereo Master Output
+output-mappings = analog-stereo-master-out
+priority = 3
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone]
+description = Analog Stereo Headphones Output
+output-mappings = analog-stereo-headphone-out
+priority = 2
+skip-probe = yes
+
+[Profile input:analog-stereo-input]
+description = Analog Stereo Input
+input-mappings = analog-stereo-input
+priority = 1
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf
new file mode 100644
index 00000000..4deb65da
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf
@@ -0,0 +1,131 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Audio 10 DJ
+;
+; This card has five stereo pairs of input and five stereo pairs of
+; output
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-out-main]
+description = Analog Stereo Main
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-out-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-out-b]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-out-c]
+description = Analog Stereo Channel C
+device-strings = hw:%f,0,2
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-out-d]
+description = Analog Stereo Channel D
+device-strings = hw:%f,0,3
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-in-main]
+description = Analog Stereo Main
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-in-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-in-b]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-in-c]
+description = Analog Stereo Channel C
+device-strings = hw:%f,0,2
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-in-d]
+description = Analog Stereo Channel D
+device-strings = hw:%f,0,3
+channel-map = left,right
+direction = input
+
+
+
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Channels Main, A, B, C, D
+output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b analog-stereo-out-c analog-stereo-out-d
+input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b analog-stereo-in-c analog-stereo-in-d
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-main+input:analog-stereo-main]
+description = Analog Stereo Duplex Main
+output-mappings = analog-stereo-out-main
+input-mappings = analog-stereo-in-main
+priority = 50
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-a]
+description = Analog Stereo Duplex Channel A
+output-mappings = analog-stereo-out-a
+input-mappings = analog-stereo-in-a
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-b+input:analog-stereo-b]
+description = Analog Stereo Duplex Channel B
+output-mappings = analog-stereo-out-b
+input-mappings = analog-stereo-in-b
+priority = 30
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-c]
+description = Analog Stereo Duplex Channel C
+output-mappings = analog-stereo-out-c
+input-mappings = analog-stereo-in-c
+priority = 20
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-d]
+description = Analog Stereo Duplex Channel D
+output-mappings = analog-stereo-out-d
+input-mappings = analog-stereo-in-d
+priority = 10
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf
new file mode 100644
index 00000000..48d9058b
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf
@@ -0,0 +1,92 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Audio 6 DJ
+;
+; This card has three stereo pairs of input and three stereo pairs of
+; output
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-out-main]
+description = Analog Stereo Main
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-out-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-out-b]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-in-main]
+description = Analog Stereo Main
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-in-a]
+description = Analog Stereo Channel A
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-in-b]
+description = Analog Stereo Channel B
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Channels A, B (Headphones)
+output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b
+input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-main+input:analog-stereo-main]
+description = Analog Stereo Duplex Channel Main
+output-mappings = analog-stereo-out-main
+input-mappings = analog-stereo-in-main
+priority = 50
+skip-probe = yes
+
+[Profile output:analog-stereo-a+input:analog-stereo-a]
+description = Analog Stereo Duplex Channel A
+output-mappings = analog-stereo-out-a
+input-mappings = analog-stereo-in-a
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-b+input:analog-stereo-b]
+description = Analog Stereo Duplex Channel B
+output-mappings = analog-stereo-out-b
+input-mappings = analog-stereo-in-b
+priority = 30
+skip-probe = yes
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf
new file mode 100644
index 00000000..1da843a1
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf
@@ -0,0 +1,81 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Traktor Kontrol S4
+;
+; This controller has two stereo pairs of input (named "Channel C" and
+; "Channel D") and two stereo pairs of output, one "Main Out" and
+; "Headphone Out".
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-output-main]
+description = Analog Stereo Main Out
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-output-headphone]
+description = Analog Stereo Headphones Out
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-c-input]
+description = Analog Stereo Channel C
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Mapping analog-stereo-d-input]
+description = Analog Stereo Channel D
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = input
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex
+output-mappings = analog-stereo-output-main analog-stereo-output-headphone
+input-mappings = analog-stereo-c-input analog-stereo-d-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-main]
+description = Analog Stereo Main Output
+output-mappings = analog-stereo-output-main
+priority = 4
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone]
+description = Analog Stereo Output Headphones Out
+output-mappings = analog-stereo-output-headphone
+priority = 3
+skip-probe = yes
+
+[Profile input:analog-stereo-c]
+description = Analog Stereo Input Channel C
+input-mappings = analog-stereo-c-input
+priority = 2
+skip-probe = yes
+
+[Profile input:analog-stereo-d]
+description = Analog Stereo Input Channel D
+input-mappings = analog-stereo-d-input
+priority = 1
+skip-probe = yes
+
diff --git a/src/modules/alsa/mixer/profile-sets/usb-headset.conf b/src/modules/alsa/mixer/profile-sets/usb-headset.conf
new file mode 100644
index 00000000..adf78d17
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/usb-headset.conf
@@ -0,0 +1,35 @@
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; This is a workaround - these usb headsets have one output volume control only, labeled "Speaker".
+; This causes the default profile set to not control the volume at all, which is a bug.
+
+[General]
+auto-profiles = yes
+
+[Mapping analog-mono]
+device-strings = hw:%f
+channel-map = mono
+paths-output = analog-output-speaker
+paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 1
+
+[Mapping analog-stereo]
+device-strings = front:%f hw:%f
+channel-map = left,right
+paths-output = analog-output-speaker
+paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
+priority = 10
diff --git a/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0 b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0
new file mode 100644
index 00000000..082c9a1b
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/ATI IXP--Realtek ALC655 rev 0
@@ -0,0 +1,150 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 29 [94%] [-3.00dB] [on]
+ Front Right: Playback 29 [94%] [-3.00dB] [on]
+Simple mixer control 'Master Mono',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 23 [74%] [0.00dB] [on]
+ Front Right: Playback 23 [74%] [0.00dB] [on]
+Simple mixer control 'Surround',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-46.50dB] [off]
+ Front Right: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Surround Jack Mode',0
+ Capabilities: enum
+ Items: 'Shared' 'Independent'
+ Item0: 'Shared'
+Simple mixer control 'Center',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'LFE',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [on]
+ Front Right: Capture [on]
+Simple mixer control 'Mic Boost (+20dB)',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Mic Select',0
+ Capabilities: enum
+ Items: 'Mic1' 'Mic2'
+ Item0: 'Mic1'
+Simple mixer control 'Video',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Phone',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 31 [100%] [12.00dB] [off]
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined cswitch cswitch-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Mono: Playback [off] Capture [off]
+Simple mixer control 'IEC958 Playback AC97-SPSA',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 3
+ Mono: 0 [0%]
+Simple mixer control 'IEC958 Playback Source',0
+ Capabilities: enum
+ Items: 'PCM' 'Analog In' 'IEC958 In'
+ Item0: 'PCM'
+Simple mixer control 'PC Speaker',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 15
+ Mono: Playback 0 [0%] [-45.00dB] [on]
+Simple mixer control 'Aux',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [on] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [on] Capture [off]
+Simple mixer control 'Mono Output Select',0
+ Capabilities: enum
+ Items: 'Mix' 'Mic'
+ Item0: 'Mix'
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch cswitch-joined
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 12 [80%] [18.00dB] [on]
+ Front Right: Capture 12 [80%] [18.00dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mix Mono',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Channel Mode',0
+ Capabilities: enum
+ Items: '2ch' '4ch' '6ch'
+ Item0: '2ch'
+Simple mixer control 'Duplicate Front',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'External Amplifier',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
diff --git a/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x
new file mode 100644
index 00000000..b8f61fab
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/Brooktree Bt878--Bt87x
@@ -0,0 +1,24 @@
+Simple mixer control 'FM',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [off]
+Simple mixer control 'Mic/Line',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [off]
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cvolume-joined
+ Capture channels: Mono
+ Limits: Capture 0 - 15
+ Mono: Capture 13 [87%]
+Simple mixer control 'Capture Boost',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
+Simple mixer control 'TV Tuner',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [on]
diff --git a/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3 b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3
new file mode 100644
index 00000000..a500a817
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3
@@ -0,0 +1,135 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 63
+ Mono:
+ Front Left: Playback 63 [100%] [0.00dB] [on]
+ Front Right: Playback 63 [100%] [0.00dB] [on]
+Simple mixer control 'Master Mono',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Headphone',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-46.50dB] [off]
+ Front Right: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control '3D Control - Center',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 15
+ Mono: 0 [0%]
+Simple mixer control '3D Control - Depth',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 15
+ Mono: 0 [0%]
+Simple mixer control '3D Control - Switch',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 23 [74%] [0.00dB] [on]
+ Front Right: Playback 23 [74%] [0.00dB] [on]
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 23 [74%] [0.00dB] [on]
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mic Boost (+20dB)',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Mic Select',0
+ Capabilities: enum
+ Items: 'Mic1' 'Mic2'
+ Item0: 'Mic1'
+Simple mixer control 'Video',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Phone',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'PC Speaker',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 15
+ Mono: Playback 0 [0%] [-45.00dB] [off]
+Simple mixer control 'Aux',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mono Output Select',0
+ Capabilities: enum
+ Items: 'Mix' 'Mic'
+ Item0: 'Mic'
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch cswitch-joined
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 15 [100%] [22.50dB] [on]
+ Front Right: Capture 15 [100%] [22.50dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mix Mono',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'External Amplifier',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
diff --git a/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI
new file mode 100644
index 00000000..244f24a8
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/HDA ATI HDMI--ATI R6xx HDMI
@@ -0,0 +1,4 @@
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981 b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981
new file mode 100644
index 00000000..165522fa
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/HDA Intel--Analog Devices AD1981
@@ -0,0 +1,62 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 63
+ Mono:
+ Front Left: Playback 63 [100%] [3.00dB] [on]
+ Front Right: Playback 63 [100%] [3.00dB] [on]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 23 [74%] [0.00dB] [on]
+ Front Right: Playback 23 [74%] [0.00dB] [on]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Capture [off]
+ Front Left: Playback 0 [0%] [-34.50dB] [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Capture [on]
+ Front Left: Playback 0 [0%] [-34.50dB] [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off]
+Simple mixer control 'Mic Boost',0
+ Capabilities: volume
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: 0 - 3
+ Front Left: 0 [0%]
+ Front Right: 0 [0%]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Default PCM',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Playback Source',0
+ Capabilities: enum
+ Items: 'PCM' 'ADC'
+ Item0: 'PCM'
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 0 [0%] [0.00dB] [on]
+ Front Right: Capture 0 [0%] [0.00dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [off]
diff --git a/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A
new file mode 100644
index 00000000..28a2e73c
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/HDA Intel--Realtek ALC889A
@@ -0,0 +1,113 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 64
+ Mono: Playback 64 [100%] [0.00dB] [on]
+Simple mixer control 'Headphone',0
+ Capabilities: pswitch
+ Playback channels: Front Left - Front Right
+ Mono:
+ Front Left: Playback [on]
+ Front Right: Playback [on]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 255
+ Mono:
+ Front Left: Playback 255 [100%] [0.00dB]
+ Front Right: Playback 255 [100%] [0.00dB]
+Simple mixer control 'Front',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 64
+ Mono:
+ Front Left: Playback 44 [69%] [-20.00dB] [on]
+ Front Right: Playback 44 [69%] [-20.00dB] [on]
+Simple mixer control 'Front Mic',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-34.50dB] [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off]
+Simple mixer control 'Front Mic Boost',0
+ Capabilities: volume
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: 0 - 3
+ Front Left: 0 [0%]
+ Front Right: 0 [0%]
+Simple mixer control 'Surround',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 64
+ Mono:
+ Front Left: Playback 0 [0%] [-64.00dB] [on]
+ Front Right: Playback 0 [0%] [-64.00dB] [on]
+Simple mixer control 'Center',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 64
+ Mono: Playback 0 [0%] [-64.00dB] [on]
+Simple mixer control 'LFE',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 64
+ Mono: Playback 0 [0%] [-64.00dB] [on]
+Simple mixer control 'Side',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 64
+ Mono:
+ Front Left: Playback 0 [0%] [-64.00dB] [on]
+ Front Right: Playback 0 [0%] [-64.00dB] [on]
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-34.50dB] [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-34.50dB] [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off]
+Simple mixer control 'Mic Boost',0
+ Capabilities: volume
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: 0 - 3
+ Front Left: 0 [0%]
+ Front Right: 0 [0%]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined cswitch cswitch-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Mono: Playback [on] Capture [on]
+Simple mixer control 'IEC958 Default PCM',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 46
+ Front Left: Capture 23 [50%] [7.00dB] [on]
+ Front Right: Capture 23 [50%] [7.00dB] [on]
+Simple mixer control 'Capture',1
+ Capabilities: cvolume cswitch
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 46
+ Front Left: Capture 0 [0%] [-16.00dB] [off]
+ Front Right: Capture 0 [0%] [-16.00dB] [off]
+Simple mixer control 'Input Source',0
+ Capabilities: cenum
+ Items: 'Mic' 'Front Mic' 'Line'
+ Item0: 'Mic'
+Simple mixer control 'Input Source',1
+ Capabilities: cenum
+ Items: 'Mic' 'Front Mic' 'Line'
+ Item0: 'Mic'
diff --git a/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A
new file mode 100644
index 00000000..3ddd8af6
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/Intel 82801CA-ICH3--Analog Devices AD1881A
@@ -0,0 +1,128 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 63
+ Mono:
+ Front Left: Playback 44 [70%] [-28.50dB] [on]
+ Front Right: Playback 60 [95%] [-4.50dB] [on]
+Simple mixer control 'Master Mono',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 17 [55%] [-21.00dB] [on]
+Simple mixer control '3D Control - Center',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 15
+ Mono: 0 [0%]
+Simple mixer control '3D Control - Depth',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 15
+ Mono: 0 [0%]
+Simple mixer control '3D Control - Switch',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 9 [29%] [-21.00dB] [on]
+ Front Right: Playback 9 [29%] [-21.00dB] [on]
+Simple mixer control 'PCM Out Path & Mute',0
+ Capabilities: enum
+ Items: 'pre 3D' 'post 3D'
+ Item0: 'pre 3D'
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 9 [29%] [-21.00dB] [on] Capture [off]
+ Front Right: Playback 9 [29%] [-21.00dB] [on] Capture [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [on]
+ Front Right: Capture [on]
+Simple mixer control 'Mic Boost (+20dB)',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Mic Select',0
+ Capabilities: enum
+ Items: 'Mic1' 'Mic2'
+ Item0: 'Mic1'
+Simple mixer control 'Video',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Phone',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'PC Speaker',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 15
+ Mono: Playback 8 [53%] [-21.00dB] [on]
+Simple mixer control 'Aux',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mono Output Select',0
+ Capabilities: enum
+ Items: 'Mix' 'Mic'
+ Item0: 'Mix'
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch cswitch-joined
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 13 [87%] [19.50dB] [on]
+ Front Right: Capture 13 [87%] [19.50dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mix Mono',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'External Amplifier',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
diff --git a/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer
new file mode 100644
index 00000000..38cf6778
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/Logitech USB Speaker--USB Mixer
@@ -0,0 +1,27 @@
+Simple mixer control 'Bass',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 48
+ Mono: 22 [46%]
+Simple mixer control 'Bass Boost',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Treble',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 48
+ Mono: 25 [52%]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 44
+ Mono:
+ Front Left: Playback 10 [23%] [-31.00dB] [on]
+ Front Right: Playback 10 [23%] [-31.00dB] [on]
+Simple mixer control 'Auto Gain Control',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
diff --git a/src/modules/alsa/mixer/samples/USB Audio--USB Mixer b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer
new file mode 100644
index 00000000..9cb4fa7f
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/USB Audio--USB Mixer
@@ -0,0 +1,37 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 255
+ Mono: Playback 105 [41%] [-28.97dB] [on]
+Simple mixer control 'Line',0
+ Capabilities: pvolume cvolume pswitch pswitch-joined cswitch cswitch-joined
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 255 Capture 0 - 128
+ Front Left: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off]
+ Front Right: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pvolume-joined cvolume cvolume-joined pswitch pswitch-joined cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: Playback 0 - 255 Capture 0 - 128
+ Mono: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [on]
+Simple mixer control 'Mic Capture',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 In',0
+ Capabilities: cswitch cswitch-joined
+ Capture channels: Mono
+ Mono: Capture [off]
+Simple mixer control 'Input 1',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [off]
+Simple mixer control 'Input 2',0
+ Capabilities: cswitch cswitch-joined cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Mono
+ Mono: Capture [off]
diff --git a/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer
new file mode 100644
index 00000000..783f826f
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer
@@ -0,0 +1,5 @@
+Simple mixer control 'Mic',0
+ Capabilities: cvolume cvolume-joined cswitch cswitch-joined
+ Capture channels: Mono
+ Limits: Capture 0 - 3072
+ Mono: Capture 1536 [50%] [23.00dB] [on]
diff --git a/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888 b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888
new file mode 100644
index 00000000..15e7b5a6
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/VIA 8237--Analog Devices AD1888
@@ -0,0 +1,211 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [0.00dB] [on]
+ Front Right: Playback 31 [100%] [0.00dB] [on]
+Simple mixer control 'Master Mono',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Master Surround',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-46.50dB] [off]
+ Front Right: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Headphone Jack Sense',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 23 [74%] [0.00dB] [on]
+ Front Right: Playback 23 [74%] [0.00dB] [on]
+Simple mixer control 'Surround',0
+ Capabilities: pvolume pswitch
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-46.50dB] [off]
+ Front Right: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Surround Jack Mode',0
+ Capabilities: enum
+ Items: 'Shared' 'Independent'
+ Item0: 'Shared'
+Simple mixer control 'Center',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 31 [100%] [0.00dB] [off]
+Simple mixer control 'LFE',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Line Jack Sense',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [on]
+ Front Right: Capture [on]
+Simple mixer control 'Mic Boost (+20dB)',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Mic Select',0
+ Capabilities: enum
+ Items: 'Mic1' 'Mic2'
+ Item0: 'Mic1'
+Simple mixer control 'Video',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Phone',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Mono
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-34.50dB] [off]
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Output',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Playback AC97-SPSA',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 3
+ Mono: 3 [100%]
+Simple mixer control 'IEC958 Playback Source',0
+ Capabilities: enum
+ Items: 'AC-Link' 'A/D Converter'
+ Item0: 'AC-Link'
+Simple mixer control 'Aux',0
+ Capabilities: pvolume pswitch cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 0 [0%] [0.00dB] [on]
+ Front Right: Capture 0 [0%] [0.00dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mix Mono',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Channel Mode',0
+ Capabilities: enum
+ Items: '2ch' '4ch' '6ch'
+ Item0: '2ch'
+Simple mixer control 'Downmix',0
+ Capabilities: enum
+ Items: 'Off' '6 -> 4' '6 -> 2'
+ Item0: 'Off'
+Simple mixer control 'Exchange Front/Surround',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'External Amplifier',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
+Simple mixer control 'High Pass Filter Enable',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Input Source Select',0
+ Capabilities: enum
+ Items: 'Input1' 'Input2'
+ Item0: 'Input1'
+Simple mixer control 'Input Source Select',1
+ Capabilities: enum
+ Items: 'Input1' 'Input2'
+ Item0: 'Input1'
+Simple mixer control 'Spread Front to Surround and Center/LFE',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'VIA DXS',0
+ Capabilities: pvolume
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [-48.00dB]
+ Front Right: Playback 31 [100%] [-48.00dB]
+Simple mixer control 'VIA DXS',1
+ Capabilities: pvolume
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [-48.00dB]
+ Front Right: Playback 31 [100%] [-48.00dB]
+Simple mixer control 'VIA DXS',2
+ Capabilities: pvolume
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [-48.00dB]
+ Front Right: Playback 31 [100%] [-48.00dB]
+Simple mixer control 'VIA DXS',3
+ Capabilities: pvolume
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [-48.00dB]
+ Front Right: Playback 31 [100%] [-48.00dB]
+Simple mixer control 'V_REFOUT Enable',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
diff --git a/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+
new file mode 100644
index 00000000..d4f3db62
--- /dev/null
+++ b/src/modules/alsa/mixer/samples/VIA 8237--C-Media Electronics CMI9761A+
@@ -0,0 +1,160 @@
+Simple mixer control 'Master',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 0 [0%] [-46.50dB] [off]
+ Front Right: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'PCM',0
+ Capabilities: pvolume pswitch pswitch-joined
+ Playback channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Mono:
+ Front Left: Playback 31 [100%] [-48.00dB] [off]
+ Front Right: Playback 31 [100%] [-48.00dB] [off]
+Simple mixer control 'Surround',0
+ Capabilities: pswitch
+ Playback channels: Front Left - Front Right
+ Mono:
+ Front Left: Playback [off]
+ Front Right: Playback [off]
+Simple mixer control 'Surround Jack Mode',0
+ Capabilities: enum
+ Items: 'Shared' 'Independent'
+ Item0: 'Shared'
+Simple mixer control 'Center',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 31 [100%] [0.00dB] [off]
+Simple mixer control 'LFE',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 31
+ Mono: Playback 0 [0%] [-46.50dB] [off]
+Simple mixer control 'Line',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'CD',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mic',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on]
+Simple mixer control 'Mic Boost (+20dB)',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'Mic Select',0
+ Capabilities: enum
+ Items: 'Mic1' 'Mic2'
+ Item0: 'Mic1'
+Simple mixer control 'Video',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Phone',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'IEC958',0
+ Capabilities: pswitch pswitch-joined cswitch cswitch-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Mono: Playback [off] Capture [off]
+Simple mixer control 'IEC958 Capture Monitor',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Capture Valid',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Output',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [off]
+Simple mixer control 'IEC958 Playback AC97-SPSA',0
+ Capabilities: volume volume-joined
+ Playback channels: Mono
+ Capture channels: Mono
+ Limits: 0 - 3
+ Mono: 3 [100%]
+Simple mixer control 'IEC958 Playback Source',0
+ Capabilities: enum
+ Items: 'AC-Link' 'ADC' 'SPDIF-In'
+ Item0: 'AC-Link'
+Simple mixer control 'PC Speaker',0
+ Capabilities: pvolume pvolume-joined pswitch pswitch-joined
+ Playback channels: Mono
+ Limits: Playback 0 - 15
+ Mono: Playback 0 [0%] [-45.00dB] [off]
+Simple mixer control 'Aux',0
+ Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Playback channels: Front Left - Front Right
+ Capture channels: Front Left - Front Right
+ Limits: Playback 0 - 31
+ Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+ Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
+Simple mixer control 'Mono Output Select',0
+ Capabilities: enum
+ Items: 'Mix' 'Mic'
+ Item0: 'Mix'
+Simple mixer control 'Capture',0
+ Capabilities: cvolume cswitch cswitch-joined
+ Capture channels: Front Left - Front Right
+ Limits: Capture 0 - 15
+ Front Left: Capture 0 [0%] [0.00dB] [on]
+ Front Right: Capture 0 [0%] [0.00dB] [on]
+Simple mixer control 'Mix',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Mix Mono',0
+ Capabilities: cswitch cswitch-exclusive
+ Capture exclusive group: 0
+ Capture channels: Front Left - Front Right
+ Front Left: Capture [off]
+ Front Right: Capture [off]
+Simple mixer control 'Channel Mode',0
+ Capabilities: enum
+ Items: '2ch' '4ch' '6ch'
+ Item0: '2ch'
+Simple mixer control 'DAC Clock Source',0
+ Capabilities: enum
+ Items: 'AC-Link' 'SPDIF-In' 'Both'
+ Item0: 'AC-Link'
+Simple mixer control 'External Amplifier',0
+ Capabilities: pswitch pswitch-joined
+ Playback channels: Mono
+ Mono: Playback [on]
+Simple mixer control 'Input Source Select',0
+ Capabilities: enum
+ Items: 'Input1' 'Input2'
+ Item0: 'Input1'
+Simple mixer control 'Input Source Select',1
+ Capabilities: enum
+ Items: 'Input1' 'Input2'
+ Item0: 'Input1'
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
new file mode 100644
index 00000000..e60aa5ef
--- /dev/null
+++ b/src/modules/alsa/module-alsa-card.c
@@ -0,0 +1,479 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/queue.h>
+
+#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"
+#include "module-alsa-card-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ALSA Card");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "name=<name for the card/sink/source, to be prefixed> "
+ "card_name=<name for the card> "
+ "card_properties=<properties for the card> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "namereg_fail=<pa_namereg_register() fail parameter value> "
+ "device_id=<ALSA card index> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "mmap=<enable memory mapping?> "
+ "tsched=<enable system timer based scheduling mode?> "
+ "tsched_buffer_size=<buffer size when using timer based scheduling> "
+ "tsched_buffer_watermark=<lower fill watermark> "
+ "profile=<profile name> "
+ "ignore_dB=<ignore dB information from the device?> "
+ "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> "
+ "profile_set=<profile set configuration file> ");
+
+static const char* const valid_modargs[] = {
+ "name",
+ "card_name",
+ "card_properties",
+ "sink_name",
+ "sink_properties",
+ "source_name",
+ "source_properties",
+ "namereg_fail",
+ "device_id",
+ "format",
+ "rate",
+ "fragments",
+ "fragment_size",
+ "mmap",
+ "tsched",
+ "tsched_buffer_size",
+ "tsched_buffer_watermark",
+ "profile",
+ "ignore_dB",
+ "sync_volume",
+ "profile_set",
+ NULL
+};
+
+#define DEFAULT_DEVICE_ID "0"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ char *device_id;
+
+ pa_card *card;
+
+ pa_modargs *modargs;
+
+ pa_alsa_profile_set *profile_set;
+};
+
+struct profile_data {
+ pa_alsa_profile *profile;
+};
+
+static void add_profiles(struct userdata *u, pa_hashmap *h) {
+ pa_alsa_profile *ap;
+ void *state;
+
+ pa_assert(u);
+ pa_assert(h);
+
+ PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) {
+ struct profile_data *d;
+ pa_card_profile *cp;
+ pa_alsa_mapping *m;
+ uint32_t idx;
+
+ cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data));
+ cp->priority = ap->priority;
+
+ if (ap->output_mappings) {
+ cp->n_sinks = pa_idxset_size(ap->output_mappings);
+
+ 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 (ap->input_mappings) {
+ cp->n_sources = pa_idxset_size(ap->input_mappings);
+
+ 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 = PA_CARD_PROFILE_DATA(cp);
+ d->profile = ap;
+
+ pa_hashmap_put(h, cp->name, cp);
+ }
+}
+
+static void add_disabled_profile(pa_hashmap *profiles) {
+ pa_card_profile *p;
+ struct profile_data *d;
+
+ p = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data));
+
+ d = PA_CARD_PROFILE_DATA(p);
+ d->profile = NULL;
+
+ pa_hashmap_put(profiles, p->name, p);
+}
+
+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);
+ pa_assert_se(u = c->userdata);
+
+ nd = PA_CARD_PROFILE_DATA(new_profile);
+ od = PA_CARD_PROFILE_DATA(c->active_profile);
+
+ if (od->profile && od->profile->output_mappings)
+ PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) {
+ if (!am->sink)
+ continue;
+
+ if (nd->profile &&
+ nd->profile->output_mappings &&
+ pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL))
+ continue;
+
+ sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs);
+ pa_alsa_sink_free(am->sink);
+ am->sink = NULL;
+ }
+
+ if (od->profile && od->profile->input_mappings)
+ PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) {
+ if (!am->source)
+ continue;
+
+ 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 (nd->profile && nd->profile->output_mappings)
+ PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) {
+
+ if (!am->sink)
+ am->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, am);
+
+ if (sink_inputs && am->sink) {
+ pa_sink_move_all_finish(am->sink, sink_inputs, FALSE);
+ sink_inputs = NULL;
+ }
+ }
+
+ if (nd->profile && nd->profile->input_mappings)
+ PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) {
+
+ if (!am->source)
+ am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am);
+
+ if (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->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->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) {
+ char *t;
+ const char *n;
+
+ pa_assert(data);
+ pa_assert(ma);
+ pa_assert(device_id);
+
+ if ((n = pa_modargs_get_value(ma, "card_name", NULL))) {
+ pa_card_new_data_set_name(data, n);
+ data->namereg_fail = TRUE;
+ return;
+ }
+
+ if ((n = pa_modargs_get_value(ma, "name", NULL)))
+ data->namereg_fail = TRUE;
+ else {
+ n = device_id;
+ data->namereg_fail = FALSE;
+ }
+
+ t = pa_sprintf_malloc("alsa_card.%s", n);
+ pa_card_new_data_set_name(data, t);
+ pa_xfree(t);
+}
+
+int pa__init(pa_module *m) {
+ pa_card_new_data data;
+ pa_modargs *ma;
+ int alsa_card_index;
+ struct userdata *u;
+ pa_reserve_wrapper *reserve = NULL;
+ const char *description;
+ char *fn = NULL;
+ pa_bool_t namereg_fail = FALSE;
+
+ pa_alsa_refcnt_inc();
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID));
+ u->modargs = ma;
+
+ if ((alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
+ pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(alsa_card_index));
+ goto fail;
+ }
+
+ if (!pa_in_system_mode()) {
+ char *rname;
+
+ if ((rname = pa_alsa_get_reserve_name(u->device_id))) {
+ reserve = pa_reserve_wrapper_get(m->core, rname);
+ pa_xfree(rname);
+
+ if (!reserve)
+ goto fail;
+ }
+ }
+
+#ifdef HAVE_UDEV
+ fn = pa_udev_get_property(alsa_card_index, "PULSE_PROFILE_SET");
+#endif
+
+ if (pa_modargs_get_value(ma, "profile_set", NULL)) {
+ pa_xfree(fn);
+ fn = pa_xstrdup(pa_modargs_get_value(ma, "profile_set", NULL));
+ }
+
+ u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map);
+ pa_xfree(fn);
+
+ if (!u->profile_set)
+ goto fail;
+
+ pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec);
+ pa_alsa_profile_set_dump(u->profile_set);
+
+ pa_card_new_data_init(&data);
+ data.driver = __FILE__;
+ data.module = m;
+
+ pa_alsa_init_proplist_card(m->core, data.proplist, alsa_card_index);
+
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id);
+ pa_alsa_init_description(data.proplist);
+ set_card_name(&data, ma, u->device_id);
+
+ /* We need to give pa_modargs_get_value_boolean() a pointer to a local
+ * variable instead of using &data.namereg_fail directly, because
+ * data.namereg_fail is a bitfield and taking the address of a bitfield
+ * variable is impossible. */
+ namereg_fail = data.namereg_fail;
+ if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) {
+ pa_log("Failed to parse boolean argument namereg_fail.");
+ pa_card_new_data_done(&data);
+ goto fail;
+ }
+ data.namereg_fail = namereg_fail;
+
+ if (reserve)
+ if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)))
+ pa_reserve_wrapper_set_application_device_name(reserve, description);
+
+ 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.");
+ pa_card_new_data_done(&data);
+ goto fail;
+ }
+
+ add_disabled_profile(data.profiles);
+
+ if (pa_modargs_get_proplist(ma, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_card_new_data_done(&data);
+ goto fail;
+ }
+
+ u->card = pa_card_new(m->core, &data);
+ pa_card_new_data_done(&data);
+
+ if (!u->card)
+ goto fail;
+
+ u->card->userdata = u;
+ u->card->set_profile = card_set_profile;
+
+ init_profile(u);
+
+ if (reserve)
+ pa_reserve_wrapper_unref(reserve);
+
+ if (!pa_hashmap_isempty(u->profile_set->decibel_fixes))
+ pa_log_warn("Card %s uses decibel fixes (i.e. overrides the decibel information for some alsa volume elements). "
+ "Please note that this feature is meant just as a help for figuring out the correct decibel values. "
+ "Pulseaudio is not the correct place to maintain the decibel mappings! The fixed decibel values "
+ "should be sent to ALSA developers so that they can fix the driver. If it turns out that this feature "
+ "is abused (i.e. fixes are not pushed to ALSA), the decibel fix feature may be removed in some future "
+ "Pulseaudio version.", u->card->name);
+
+ return 0;
+
+fail:
+ if (reserve)
+ pa_reserve_wrapper_unref(reserve);
+
+ pa__done(m);
+
+ return -1;
+}
+
+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);
+
+ PA_IDXSET_FOREACH(source, u->card->sources, idx)
+ n += pa_source_linked_by(source);
+
+ return n;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ goto finish;
+
+ 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;
+
+ while ((s = pa_idxset_steal_first(u->card->sources, NULL)))
+ pa_alsa_source_free(s);
+ }
+
+ if (u->card)
+ pa_card_free(u->card);
+
+ if (u->modargs)
+ pa_modargs_free(u->modargs);
+
+ if (u->profile_set)
+ pa_alsa_profile_set_free(u->profile_set);
+
+ pa_xfree(u->device_id);
+ pa_xfree(u);
+
+finish:
+ pa_alsa_refcnt_dec();
+}
diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c
new file mode 100644
index 00000000..6e64ab31
--- /dev/null
+++ b/src/modules/alsa/module-alsa-sink.c
@@ -0,0 +1,136 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 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 <pulsecore/module.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+
+#include "alsa-util.h"
+#include "alsa-sink.h"
+#include "module-alsa-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ALSA Sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "name=<name of the sink, to be prefixed> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "namereg_fail=<pa_namereg_register() fail parameter value> "
+ "device=<ALSA device> "
+ "device_id=<ALSA card index> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "mmap=<enable memory mapping?> "
+ "tsched=<enable system timer based scheduling mode?> "
+ "tsched_buffer_size=<buffer size when using timer based scheduling> "
+ "tsched_buffer_watermark=<lower fill watermark> "
+ "ignore_dB=<ignore dB information from the device?> "
+ "control=<name of mixer control> "
+ "rewind_safeguard=<number of bytes that cannot be rewound> "
+ "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> "
+ "sync_volume_safety_margin=<usec adjustment depending on volume direction> "
+ "sync_volume_extra_delay=<usec adjustment to HW volume changes>");
+
+static const char* const valid_modargs[] = {
+ "name",
+ "sink_name",
+ "sink_properties",
+ "namereg_fail",
+ "device",
+ "device_id",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ "fragments",
+ "fragment_size",
+ "mmap",
+ "tsched",
+ "tsched_buffer_size",
+ "tsched_buffer_watermark",
+ "ignore_dB",
+ "control",
+ "rewind_safeguard",
+ "sync_volume",
+ "sync_volume_safety_margin",
+ "sync_volume_extra_delay",
+ NULL
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+
+ pa_assert(m);
+
+ pa_alsa_refcnt_inc();
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (!(m->userdata = pa_alsa_sink_new(m, ma, __FILE__, NULL, NULL)))
+ goto fail;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+ pa_sink *sink;
+
+ pa_assert(m);
+ pa_assert_se(sink = m->userdata);
+
+ return pa_sink_linked_by(sink);
+}
+
+void pa__done(pa_module*m) {
+ pa_sink *sink;
+
+ pa_assert(m);
+
+ if ((sink = m->userdata))
+ pa_alsa_sink_free(sink);
+
+ pa_alsa_refcnt_dec();
+}
diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c
new file mode 100644
index 00000000..5ecd1e34
--- /dev/null
+++ b/src/modules/alsa/module-alsa-source.c
@@ -0,0 +1,143 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 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 <stdio.h>
+
+#include <asoundlib.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "alsa-util.h"
+#include "alsa-source.h"
+#include "module-alsa-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ALSA Source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "name=<name for the source, to be prefixed> "
+ "source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "namereg_fail=<pa_namereg_register() fail parameter value> "
+ "device=<ALSA device> "
+ "device_id=<ALSA card index> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "mmap=<enable memory mapping?> "
+ "tsched=<enable system timer based scheduling mode?> "
+ "tsched_buffer_size=<buffer size when using timer based scheduling> "
+ "tsched_buffer_watermark=<upper fill watermark> "
+ "ignore_dB=<ignore dB information from the device?> "
+ "control=<name of mixer control>"
+ "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> "
+ "sync_volume_safety_margin=<usec adjustment depending on volume direction> "
+ "sync_volume_extra_delay=<usec adjustment to HW volume changes>");
+
+static const char* const valid_modargs[] = {
+ "name",
+ "source_name",
+ "source_properties",
+ "namereg_fail",
+ "device",
+ "device_id",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ "fragments",
+ "fragment_size",
+ "mmap",
+ "tsched",
+ "tsched_buffer_size",
+ "tsched_buffer_watermark",
+ "ignore_dB",
+ "control",
+ "sync_volume",
+ "sync_volume_safety_margin",
+ "sync_volume_extra_delay",
+ NULL
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+
+ pa_assert(m);
+
+ pa_alsa_refcnt_inc();
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (!(m->userdata = pa_alsa_source_new(m, ma, __FILE__, NULL, NULL)))
+ goto fail;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+ pa_source *source;
+
+ pa_assert(m);
+ pa_assert_se(source = m->userdata);
+
+ return pa_source_linked_by(source);
+}
+
+void pa__done(pa_module*m) {
+ pa_source *source;
+
+ pa_assert(m);
+
+ if ((source = m->userdata))
+ pa_alsa_source_free(source);
+
+ pa_alsa_refcnt_dec();
+}