alsa: New modarg "paths_dir" for module-alsa-card
[pulseaudio-raopUDP/pulseaudio-raop-alac.git] / src / modules / alsa / alsa-mixer.c
blob151eef51c61b0319b818ebba659f1154bcdd14a8
1 /***
2 This file is part of PulseAudio.
4 Copyright 2004-2009 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <sys/types.h>
28 #include <asoundlib.h>
29 #include <math.h>
31 #ifdef HAVE_VALGRIND_MEMCHECK_H
32 #include <valgrind/memcheck.h>
33 #endif
35 #include <pulse/mainloop-api.h>
36 #include <pulse/sample.h>
37 #include <pulse/timeval.h>
38 #include <pulse/util.h>
39 #include <pulse/volume.h>
40 #include <pulse/xmalloc.h>
41 #include <pulse/utf8.h>
43 #include <pulsecore/i18n.h>
44 #include <pulsecore/log.h>
45 #include <pulsecore/macro.h>
46 #include <pulsecore/core-util.h>
47 #include <pulsecore/conf-parser.h>
48 #include <pulsecore/strbuf.h>
50 #include "alsa-mixer.h"
51 #include "alsa-util.h"
53 struct description_map {
54 const char *name;
55 const char *description;
58 static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) {
59 unsigned i;
61 for (i = 0; i < n; i++)
62 if (pa_streq(dm[i].name, name))
63 return _(dm[i].description);
65 return NULL;
68 struct pa_alsa_fdlist {
69 unsigned num_fds;
70 struct pollfd *fds;
71 /* This is a temporary buffer used to avoid lots of mallocs */
72 struct pollfd *work_fds;
74 snd_mixer_t *mixer;
76 pa_mainloop_api *m;
77 pa_defer_event *defer;
78 pa_io_event **ios;
80 pa_bool_t polled;
82 void (*cb)(void *userdata);
83 void *userdata;
86 static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
88 struct pa_alsa_fdlist *fdl = userdata;
89 int err;
90 unsigned i;
91 unsigned short revents;
93 pa_assert(a);
94 pa_assert(fdl);
95 pa_assert(fdl->mixer);
96 pa_assert(fdl->fds);
97 pa_assert(fdl->work_fds);
99 if (fdl->polled)
100 return;
102 fdl->polled = TRUE;
104 memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
106 for (i = 0; i < fdl->num_fds; i++) {
107 if (e == fdl->ios[i]) {
108 if (events & PA_IO_EVENT_INPUT)
109 fdl->work_fds[i].revents |= POLLIN;
110 if (events & PA_IO_EVENT_OUTPUT)
111 fdl->work_fds[i].revents |= POLLOUT;
112 if (events & PA_IO_EVENT_ERROR)
113 fdl->work_fds[i].revents |= POLLERR;
114 if (events & PA_IO_EVENT_HANGUP)
115 fdl->work_fds[i].revents |= POLLHUP;
116 break;
120 pa_assert(i != fdl->num_fds);
122 if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
123 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
124 return;
127 a->defer_enable(fdl->defer, 1);
129 if (revents)
130 snd_mixer_handle_events(fdl->mixer);
133 static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
134 struct pa_alsa_fdlist *fdl = userdata;
135 unsigned num_fds, i;
136 int err, n;
137 struct pollfd *temp;
139 pa_assert(a);
140 pa_assert(fdl);
141 pa_assert(fdl->mixer);
143 a->defer_enable(fdl->defer, 0);
145 if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) {
146 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
147 return;
149 num_fds = (unsigned) n;
151 if (num_fds != fdl->num_fds) {
152 if (fdl->fds)
153 pa_xfree(fdl->fds);
154 if (fdl->work_fds)
155 pa_xfree(fdl->work_fds);
156 fdl->fds = pa_xnew0(struct pollfd, num_fds);
157 fdl->work_fds = pa_xnew(struct pollfd, num_fds);
160 memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
162 if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
163 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
164 return;
167 fdl->polled = FALSE;
169 if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0)
170 return;
172 if (fdl->ios) {
173 for (i = 0; i < fdl->num_fds; i++)
174 a->io_free(fdl->ios[i]);
176 if (num_fds != fdl->num_fds) {
177 pa_xfree(fdl->ios);
178 fdl->ios = NULL;
182 if (!fdl->ios)
183 fdl->ios = pa_xnew(pa_io_event*, num_fds);
185 /* Swap pointers */
186 temp = fdl->work_fds;
187 fdl->work_fds = fdl->fds;
188 fdl->fds = temp;
190 fdl->num_fds = num_fds;
192 for (i = 0;i < num_fds;i++)
193 fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
194 ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
195 ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
196 io_cb, fdl);
199 struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
200 struct pa_alsa_fdlist *fdl;
202 fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
204 return fdl;
207 void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
208 pa_assert(fdl);
210 if (fdl->defer) {
211 pa_assert(fdl->m);
212 fdl->m->defer_free(fdl->defer);
215 if (fdl->ios) {
216 unsigned i;
217 pa_assert(fdl->m);
218 for (i = 0; i < fdl->num_fds; i++)
219 fdl->m->io_free(fdl->ios[i]);
220 pa_xfree(fdl->ios);
223 if (fdl->fds)
224 pa_xfree(fdl->fds);
225 if (fdl->work_fds)
226 pa_xfree(fdl->work_fds);
228 pa_xfree(fdl);
231 int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) {
232 pa_assert(fdl);
233 pa_assert(mixer_handle);
234 pa_assert(m);
235 pa_assert(!fdl->m);
237 fdl->mixer = mixer_handle;
238 fdl->m = m;
239 fdl->defer = m->defer_new(m, defer_cb, fdl);
241 return 0;
244 struct pa_alsa_mixer_pdata {
245 pa_rtpoll *rtpoll;
246 pa_rtpoll_item *poll_item;
247 snd_mixer_t *mixer;
251 struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) {
252 struct pa_alsa_mixer_pdata *pd;
254 pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1);
256 return pd;
259 void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) {
260 pa_assert(pd);
262 if (pd->poll_item) {
263 pa_rtpoll_item_free(pd->poll_item);
266 pa_xfree(pd);
269 static int rtpoll_work_cb(pa_rtpoll_item *i) {
270 struct pa_alsa_mixer_pdata *pd;
271 struct pollfd *p;
272 unsigned n_fds;
273 unsigned short revents = 0;
274 int err, ret = 0;
276 pd = pa_rtpoll_item_get_userdata(i);
277 pa_assert_fp(pd);
278 pa_assert_fp(i == pd->poll_item);
280 p = pa_rtpoll_item_get_pollfd(i, &n_fds);
282 if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) {
283 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
284 ret = -1;
285 goto fail;
288 if (revents) {
289 if (revents & (POLLNVAL | POLLERR)) {
290 pa_log_debug("Device disconnected, stopping poll on mixer");
291 goto fail;
292 } else if (revents & POLLERR) {
293 /* This shouldn't happen. */
294 pa_log_error("Got a POLLERR (revents = %04x), stopping poll on mixer", revents);
295 goto fail;
298 err = snd_mixer_handle_events(pd->mixer);
300 if (PA_LIKELY(err >= 0)) {
301 pa_rtpoll_item_free(i);
302 pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll);
303 } else {
304 pa_log_error("Error handling mixer event: %s", pa_alsa_strerror(err));
305 ret = -1;
306 goto fail;
310 return ret;
312 fail:
313 pa_rtpoll_item_free(i);
315 pd->poll_item = NULL;
316 pd->rtpoll = NULL;
317 pd->mixer = NULL;
319 return ret;
322 int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) {
323 pa_rtpoll_item *i;
324 struct pollfd *p;
325 int err, n;
327 pa_assert(pd);
328 pa_assert(mixer);
329 pa_assert(rtp);
331 if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) {
332 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
333 return -1;
336 i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n);
338 p = pa_rtpoll_item_get_pollfd(i, NULL);
340 memset(p, 0, sizeof(struct pollfd) * n);
342 if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) {
343 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
344 pa_rtpoll_item_free(i);
345 return -1;
348 pd->rtpoll = rtp;
349 pd->poll_item = i;
350 pd->mixer = mixer;
352 pa_rtpoll_item_set_userdata(i, pd);
353 pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
355 return 0;
358 static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
359 int err;
361 pa_assert(mixer);
362 pa_assert(dev);
364 if ((err = snd_mixer_attach(mixer, dev)) < 0) {
365 pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
366 return -1;
369 if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
370 pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
371 return -1;
374 if ((err = snd_mixer_load(mixer)) < 0) {
375 pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
376 return -1;
379 pa_log_info("Successfully attached to mixer '%s'", dev);
380 return 0;
383 snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
384 int err;
385 snd_mixer_t *m;
386 const char *dev;
387 snd_pcm_info_t* info;
388 snd_pcm_info_alloca(&info);
390 pa_assert(pcm);
392 if ((err = snd_mixer_open(&m, 0)) < 0) {
393 pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
394 return NULL;
397 /* First, try by name */
398 if ((dev = snd_pcm_name(pcm)))
399 if (prepare_mixer(m, dev) >= 0) {
400 if (ctl_device)
401 *ctl_device = pa_xstrdup(dev);
403 return m;
406 /* Then, try by card index */
407 if (snd_pcm_info(pcm, info) >= 0) {
408 char *md;
409 int card_idx;
411 if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
413 md = pa_sprintf_malloc("hw:%i", card_idx);
415 if (!dev || !pa_streq(dev, md))
416 if (prepare_mixer(m, md) >= 0) {
418 if (ctl_device)
419 *ctl_device = md;
420 else
421 pa_xfree(md);
423 return m;
426 pa_xfree(md);
430 snd_mixer_close(m);
431 return NULL;
434 static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
435 [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
437 [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER,
438 [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT,
439 [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT,
441 [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER,
442 [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT,
443 [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT,
445 [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER,
447 [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
448 [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
450 [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT,
451 [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT,
453 [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN,
454 [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN,
455 [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN,
456 [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN,
457 [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN,
458 [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN,
459 [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN,
460 [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN,
461 [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN,
462 [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN,
463 [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN,
464 [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN,
465 [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN,
466 [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN,
467 [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN,
468 [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN,
469 [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN,
470 [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN,
471 [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN,
472 [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN,
473 [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN,
474 [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN,
475 [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN,
476 [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN,
477 [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN,
478 [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN,
479 [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN,
480 [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN,
481 [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN,
482 [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN,
483 [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN,
484 [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN,
486 [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN,
488 [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN,
489 [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN,
490 [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN,
492 [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN,
493 [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN,
494 [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
497 static void setting_free(pa_alsa_setting *s) {
498 pa_assert(s);
500 if (s->options)
501 pa_idxset_free(s->options, NULL, NULL);
503 pa_xfree(s->name);
504 pa_xfree(s->description);
505 pa_xfree(s);
508 static void option_free(pa_alsa_option *o) {
509 pa_assert(o);
511 pa_xfree(o->alsa_name);
512 pa_xfree(o->name);
513 pa_xfree(o->description);
514 pa_xfree(o);
517 static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
518 pa_assert(db_fix);
520 pa_xfree(db_fix->name);
521 pa_xfree(db_fix->db_values);
523 pa_xfree(db_fix);
526 static void element_free(pa_alsa_element *e) {
527 pa_alsa_option *o;
528 pa_assert(e);
530 while ((o = e->options)) {
531 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
532 option_free(o);
535 if (e->db_fix)
536 decibel_fix_free(e->db_fix);
538 pa_xfree(e->alsa_name);
539 pa_xfree(e);
542 void pa_alsa_path_free(pa_alsa_path *p) {
543 pa_alsa_element *e;
544 pa_alsa_setting *s;
546 pa_assert(p);
548 while ((e = p->elements)) {
549 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
550 element_free(e);
553 while ((s = p->settings)) {
554 PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s);
555 setting_free(s);
558 pa_xfree(p->name);
559 pa_xfree(p->description);
560 pa_xfree(p);
563 void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
564 pa_alsa_path *p;
565 pa_assert(ps);
567 while ((p = ps->paths)) {
568 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
569 pa_alsa_path_free(p);
572 pa_xfree(ps);
575 static long to_alsa_dB(pa_volume_t v) {
576 return (long) (pa_sw_volume_to_dB(v) * 100.0);
579 static pa_volume_t from_alsa_dB(long v) {
580 return pa_sw_volume_from_dB((double) v / 100.0);
583 static long to_alsa_volume(pa_volume_t v, long min, long max) {
584 long w;
586 w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min;
587 return PA_CLAMP_UNLIKELY(w, min, max);
590 static pa_volume_t from_alsa_volume(long v, long min, long max) {
591 return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
594 #define SELEM_INIT(sid, name) \
595 do { \
596 snd_mixer_selem_id_alloca(&(sid)); \
597 snd_mixer_selem_id_set_name((sid), (name)); \
598 snd_mixer_selem_id_set_index((sid), 0); \
599 } while(FALSE)
601 static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
602 snd_mixer_selem_id_t *sid;
603 snd_mixer_elem_t *me;
604 snd_mixer_selem_channel_id_t c;
605 pa_channel_position_mask_t mask = 0;
606 unsigned k;
608 pa_assert(m);
609 pa_assert(e);
610 pa_assert(cm);
611 pa_assert(v);
613 SELEM_INIT(sid, e->alsa_name);
614 if (!(me = snd_mixer_find_selem(m, sid))) {
615 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
616 return -1;
619 pa_cvolume_mute(v, cm->channels);
621 /* We take the highest volume of all channels that match */
623 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
624 int r;
625 pa_volume_t f;
627 if (e->has_dB) {
628 long value = 0;
630 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
631 if (snd_mixer_selem_has_playback_channel(me, c)) {
632 if (e->db_fix) {
633 if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) {
634 /* If the channel volume is outside the limits set
635 * by the dB fix, we clamp the hw volume to be
636 * within the limits. */
637 if (value < e->db_fix->min_step) {
638 value = e->db_fix->min_step;
639 snd_mixer_selem_set_playback_volume(me, c, value);
640 pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
641 "Volume reset to %0.2f dB.", e->alsa_name, c,
642 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
643 } else if (value > e->db_fix->max_step) {
644 value = e->db_fix->max_step;
645 snd_mixer_selem_set_playback_volume(me, c, value);
646 pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
647 "Volume reset to %0.2f dB.", e->alsa_name, c,
648 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
651 /* Volume step -> dB value conversion. */
652 value = e->db_fix->db_values[value - e->db_fix->min_step];
654 } else
655 r = snd_mixer_selem_get_playback_dB(me, c, &value);
656 } else
657 r = -1;
658 } else {
659 if (snd_mixer_selem_has_capture_channel(me, c)) {
660 if (e->db_fix) {
661 if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) {
662 /* If the channel volume is outside the limits set
663 * by the dB fix, we clamp the hw volume to be
664 * within the limits. */
665 if (value < e->db_fix->min_step) {
666 value = e->db_fix->min_step;
667 snd_mixer_selem_set_capture_volume(me, c, value);
668 pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
669 "Volume reset to %0.2f dB.", e->alsa_name, c,
670 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
671 } else if (value > e->db_fix->max_step) {
672 value = e->db_fix->max_step;
673 snd_mixer_selem_set_capture_volume(me, c, value);
674 pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
675 "Volume reset to %0.2f dB.", e->alsa_name, c,
676 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
679 /* Volume step -> dB value conversion. */
680 value = e->db_fix->db_values[value - e->db_fix->min_step];
682 } else
683 r = snd_mixer_selem_get_capture_dB(me, c, &value);
684 } else
685 r = -1;
688 if (r < 0)
689 continue;
691 #ifdef HAVE_VALGRIND_MEMCHECK_H
692 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
693 #endif
695 f = from_alsa_dB(value);
697 } else {
698 long value = 0;
700 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
701 if (snd_mixer_selem_has_playback_channel(me, c))
702 r = snd_mixer_selem_get_playback_volume(me, c, &value);
703 else
704 r = -1;
705 } else {
706 if (snd_mixer_selem_has_capture_channel(me, c))
707 r = snd_mixer_selem_get_capture_volume(me, c, &value);
708 else
709 r = -1;
712 if (r < 0)
713 continue;
715 f = from_alsa_volume(value, e->min_volume, e->max_volume);
718 for (k = 0; k < cm->channels; k++)
719 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
720 if (v->values[k] < f)
721 v->values[k] = f;
723 mask |= e->masks[c][e->n_channels-1];
726 for (k = 0; k < cm->channels; k++)
727 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
728 v->values[k] = PA_VOLUME_NORM;
730 return 0;
733 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
734 pa_alsa_element *e;
736 pa_assert(m);
737 pa_assert(p);
738 pa_assert(cm);
739 pa_assert(v);
741 if (!p->has_volume)
742 return -1;
744 pa_cvolume_reset(v, cm->channels);
746 PA_LLIST_FOREACH(e, p->elements) {
747 pa_cvolume ev;
749 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
750 continue;
752 pa_assert(!p->has_dB || e->has_dB);
754 if (element_get_volume(e, m, cm, &ev) < 0)
755 return -1;
757 /* If we have no dB information all we can do is take the first element and leave */
758 if (!p->has_dB) {
759 *v = ev;
760 return 0;
763 pa_sw_cvolume_multiply(v, v, &ev);
766 return 0;
769 static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) {
770 snd_mixer_selem_id_t *sid;
771 snd_mixer_elem_t *me;
772 snd_mixer_selem_channel_id_t c;
774 pa_assert(m);
775 pa_assert(e);
776 pa_assert(b);
778 SELEM_INIT(sid, e->alsa_name);
779 if (!(me = snd_mixer_find_selem(m, sid))) {
780 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
781 return -1;
784 /* We return muted if at least one channel is muted */
786 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
787 int r;
788 int value = 0;
790 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
791 if (snd_mixer_selem_has_playback_channel(me, c))
792 r = snd_mixer_selem_get_playback_switch(me, c, &value);
793 else
794 r = -1;
795 } else {
796 if (snd_mixer_selem_has_capture_channel(me, c))
797 r = snd_mixer_selem_get_capture_switch(me, c, &value);
798 else
799 r = -1;
802 if (r < 0)
803 continue;
805 if (!value) {
806 *b = FALSE;
807 return 0;
811 *b = TRUE;
812 return 0;
815 int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
816 pa_alsa_element *e;
818 pa_assert(m);
819 pa_assert(p);
820 pa_assert(muted);
822 if (!p->has_mute)
823 return -1;
825 PA_LLIST_FOREACH(e, p->elements) {
826 pa_bool_t b;
828 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
829 continue;
831 if (element_get_switch(e, m, &b) < 0)
832 return -1;
834 if (!b) {
835 *muted = TRUE;
836 return 0;
840 *muted = FALSE;
841 return 0;
844 /* Finds the closest item in db_fix->db_values and returns the corresponding
845 * step. *db_value is replaced with the value from the db_values table.
846 * Rounding is done based on the rounding parameter: -1 means rounding down and
847 * +1 means rounding up. */
848 static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) {
849 unsigned i = 0;
850 unsigned max_i = 0;
852 pa_assert(db_fix);
853 pa_assert(db_value);
854 pa_assert(rounding != 0);
856 max_i = db_fix->max_step - db_fix->min_step;
858 if (rounding > 0) {
859 for (i = 0; i < max_i; i++) {
860 if (db_fix->db_values[i] >= *db_value)
861 break;
863 } else {
864 for (i = 0; i < max_i; i++) {
865 if (db_fix->db_values[i + 1] > *db_value)
866 break;
870 *db_value = db_fix->db_values[i];
872 return i + db_fix->min_step;
875 /* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument,
876 * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above".
877 * But even with accurate nearest dB volume step is not selected, so that is why we need
878 * this function. Returns 0 and nearest selectable volume in *value_dB on success or
879 * negative error code if fails. */
880 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) {
882 long alsa_val;
883 long value_high;
884 long value_low;
885 int r = -1;
887 pa_assert(me);
888 pa_assert(value_dB);
890 if (d == PA_ALSA_DIRECTION_OUTPUT) {
891 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
892 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high);
894 if (r < 0)
895 return r;
897 if (value_high == *value_dB)
898 return r;
900 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
901 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low);
902 } else {
903 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
904 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high);
906 if (r < 0)
907 return r;
909 if (value_high == *value_dB)
910 return r;
912 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
913 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low);
916 if (r < 0)
917 return r;
919 if (labs(value_high - *value_dB) < labs(value_low - *value_dB))
920 *value_dB = value_high;
921 else
922 *value_dB = value_low;
924 return r;
927 static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t deferred_volume, pa_bool_t write_to_hw) {
929 snd_mixer_selem_id_t *sid;
930 pa_cvolume rv;
931 snd_mixer_elem_t *me;
932 snd_mixer_selem_channel_id_t c;
933 pa_channel_position_mask_t mask = 0;
934 unsigned k;
936 pa_assert(m);
937 pa_assert(e);
938 pa_assert(cm);
939 pa_assert(v);
940 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
942 SELEM_INIT(sid, e->alsa_name);
943 if (!(me = snd_mixer_find_selem(m, sid))) {
944 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
945 return -1;
948 pa_cvolume_mute(&rv, cm->channels);
950 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
951 int r;
952 pa_volume_t f = PA_VOLUME_MUTED;
953 pa_bool_t found = FALSE;
955 for (k = 0; k < cm->channels; k++)
956 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) {
957 found = TRUE;
958 if (v->values[k] > f)
959 f = v->values[k];
962 if (!found) {
963 /* Hmm, so this channel does not exist in the volume
964 * struct, so let's bind it to the overall max of the
965 * volume. */
966 f = pa_cvolume_max(v);
969 if (e->has_dB) {
970 long value = to_alsa_dB(f);
971 int rounding;
973 if (e->volume_limit >= 0 && value > (e->max_dB * 100))
974 value = e->max_dB * 100;
976 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
977 /* If we call set_playback_volume() without checking first
978 * if the channel is available, ALSA behaves very
979 * strangely and doesn't fail the call */
980 if (snd_mixer_selem_has_playback_channel(me, c)) {
981 rounding = +1;
982 if (e->db_fix) {
983 if (write_to_hw)
984 r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
985 else {
986 decibel_fix_get_step(e->db_fix, &value, rounding);
987 r = 0;
990 } else {
991 if (write_to_hw) {
992 if (deferred_volume) {
993 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0)
994 r = snd_mixer_selem_set_playback_dB(me, c, value, 0);
995 } else {
996 if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
997 r = snd_mixer_selem_get_playback_dB(me, c, &value);
999 } else {
1000 long alsa_val;
1001 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
1002 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
1005 } else
1006 r = -1;
1007 } else {
1008 if (snd_mixer_selem_has_capture_channel(me, c)) {
1009 rounding = -1;
1010 if (e->db_fix) {
1011 if (write_to_hw)
1012 r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
1013 else {
1014 decibel_fix_get_step(e->db_fix, &value, rounding);
1015 r = 0;
1018 } else {
1019 if (write_to_hw) {
1020 if (deferred_volume) {
1021 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0)
1022 r = snd_mixer_selem_set_capture_dB(me, c, value, 0);
1023 } else {
1024 if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
1025 r = snd_mixer_selem_get_capture_dB(me, c, &value);
1027 } else {
1028 long alsa_val;
1029 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
1030 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
1033 } else
1034 r = -1;
1037 if (r < 0)
1038 continue;
1040 #ifdef HAVE_VALGRIND_MEMCHECK_H
1041 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
1042 #endif
1044 f = from_alsa_dB(value);
1046 } else {
1047 long value;
1049 value = to_alsa_volume(f, e->min_volume, e->max_volume);
1051 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1052 if (snd_mixer_selem_has_playback_channel(me, c)) {
1053 if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0)
1054 r = snd_mixer_selem_get_playback_volume(me, c, &value);
1055 } else
1056 r = -1;
1057 } else {
1058 if (snd_mixer_selem_has_capture_channel(me, c)) {
1059 if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0)
1060 r = snd_mixer_selem_get_capture_volume(me, c, &value);
1061 } else
1062 r = -1;
1065 if (r < 0)
1066 continue;
1068 f = from_alsa_volume(value, e->min_volume, e->max_volume);
1071 for (k = 0; k < cm->channels; k++)
1072 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
1073 if (rv.values[k] < f)
1074 rv.values[k] = f;
1076 mask |= e->masks[c][e->n_channels-1];
1079 for (k = 0; k < cm->channels; k++)
1080 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
1081 rv.values[k] = PA_VOLUME_NORM;
1083 *v = rv;
1084 return 0;
1087 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 deferred_volume, pa_bool_t write_to_hw) {
1089 pa_alsa_element *e;
1090 pa_cvolume rv;
1092 pa_assert(m);
1093 pa_assert(p);
1094 pa_assert(cm);
1095 pa_assert(v);
1096 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
1098 if (!p->has_volume)
1099 return -1;
1101 rv = *v; /* Remaining adjustment */
1102 pa_cvolume_reset(v, cm->channels); /* Adjustment done */
1104 PA_LLIST_FOREACH(e, p->elements) {
1105 pa_cvolume ev;
1107 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
1108 continue;
1110 pa_assert(!p->has_dB || e->has_dB);
1112 ev = rv;
1113 if (element_set_volume(e, m, cm, &ev, deferred_volume, write_to_hw) < 0)
1114 return -1;
1116 if (!p->has_dB) {
1117 *v = ev;
1118 return 0;
1121 pa_sw_cvolume_multiply(v, v, &ev);
1122 pa_sw_cvolume_divide(&rv, &rv, &ev);
1125 return 0;
1128 static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) {
1129 snd_mixer_elem_t *me;
1130 snd_mixer_selem_id_t *sid;
1131 int r;
1133 pa_assert(m);
1134 pa_assert(e);
1136 SELEM_INIT(sid, e->alsa_name);
1137 if (!(me = snd_mixer_find_selem(m, sid))) {
1138 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1139 return -1;
1142 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1143 r = snd_mixer_selem_set_playback_switch_all(me, b);
1144 else
1145 r = snd_mixer_selem_set_capture_switch_all(me, b);
1147 if (r < 0)
1148 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1150 return r;
1153 int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) {
1154 pa_alsa_element *e;
1156 pa_assert(m);
1157 pa_assert(p);
1159 if (!p->has_mute)
1160 return -1;
1162 PA_LLIST_FOREACH(e, p->elements) {
1164 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
1165 continue;
1167 if (element_set_switch(e, m, !muted) < 0)
1168 return -1;
1171 return 0;
1174 /* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this
1175 * function sets all channels of the volume element to e->min_volume, 0 dB or
1176 * e->constant_volume. */
1177 static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
1178 snd_mixer_elem_t *me = NULL;
1179 snd_mixer_selem_id_t *sid = NULL;
1180 int r = 0;
1181 long volume = -1;
1182 pa_bool_t volume_set = FALSE;
1184 pa_assert(m);
1185 pa_assert(e);
1187 SELEM_INIT(sid, e->alsa_name);
1188 if (!(me = snd_mixer_find_selem(m, sid))) {
1189 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1190 return -1;
1193 switch (e->volume_use) {
1194 case PA_ALSA_VOLUME_OFF:
1195 volume = e->min_volume;
1196 volume_set = TRUE;
1197 break;
1199 case PA_ALSA_VOLUME_ZERO:
1200 if (e->db_fix) {
1201 long dB = 0;
1203 volume = decibel_fix_get_step(e->db_fix, &dB, (e->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1));
1204 volume_set = TRUE;
1206 break;
1208 case PA_ALSA_VOLUME_CONSTANT:
1209 volume = e->constant_volume;
1210 volume_set = TRUE;
1211 break;
1213 default:
1214 pa_assert_not_reached();
1217 if (volume_set) {
1218 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1219 r = snd_mixer_selem_set_playback_volume_all(me, volume);
1220 else
1221 r = snd_mixer_selem_set_capture_volume_all(me, volume);
1222 } else {
1223 pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO);
1224 pa_assert(!e->db_fix);
1226 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1227 r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
1228 else
1229 r = snd_mixer_selem_set_capture_dB_all(me, 0, -1);
1232 if (r < 0)
1233 pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1235 return r;
1238 int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
1239 pa_alsa_element *e;
1240 int r = 0;
1242 pa_assert(m);
1243 pa_assert(p);
1245 pa_log_debug("Activating path %s", p->name);
1246 pa_alsa_path_dump(p);
1248 PA_LLIST_FOREACH(e, p->elements) {
1250 switch (e->switch_use) {
1251 case PA_ALSA_SWITCH_OFF:
1252 r = element_set_switch(e, m, FALSE);
1253 break;
1255 case PA_ALSA_SWITCH_ON:
1256 r = element_set_switch(e, m, TRUE);
1257 break;
1259 case PA_ALSA_SWITCH_MUTE:
1260 case PA_ALSA_SWITCH_IGNORE:
1261 case PA_ALSA_SWITCH_SELECT:
1262 r = 0;
1263 break;
1266 if (r < 0)
1267 return -1;
1269 switch (e->volume_use) {
1270 case PA_ALSA_VOLUME_OFF:
1271 case PA_ALSA_VOLUME_ZERO:
1272 case PA_ALSA_VOLUME_CONSTANT:
1273 r = element_set_constant_volume(e, m);
1274 break;
1276 case PA_ALSA_VOLUME_MERGE:
1277 case PA_ALSA_VOLUME_IGNORE:
1278 r = 0;
1279 break;
1282 if (r < 0)
1283 return -1;
1286 return 0;
1289 static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) {
1290 pa_bool_t has_switch;
1291 pa_bool_t has_enumeration;
1292 pa_bool_t has_volume;
1294 pa_assert(e);
1295 pa_assert(me);
1297 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1298 has_switch =
1299 snd_mixer_selem_has_playback_switch(me) ||
1300 (e->direction_try_other && snd_mixer_selem_has_capture_switch(me));
1301 } else {
1302 has_switch =
1303 snd_mixer_selem_has_capture_switch(me) ||
1304 (e->direction_try_other && snd_mixer_selem_has_playback_switch(me));
1307 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1308 has_volume =
1309 snd_mixer_selem_has_playback_volume(me) ||
1310 (e->direction_try_other && snd_mixer_selem_has_capture_volume(me));
1311 } else {
1312 has_volume =
1313 snd_mixer_selem_has_capture_volume(me) ||
1314 (e->direction_try_other && snd_mixer_selem_has_playback_volume(me));
1317 has_enumeration = snd_mixer_selem_is_enumerated(me);
1319 if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) ||
1320 (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) ||
1321 (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration))
1322 return -1;
1324 if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration))
1325 return -1;
1327 if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) ||
1328 (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) ||
1329 (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration))
1330 return -1;
1332 if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration))
1333 return -1;
1335 if (e->required_any != PA_ALSA_REQUIRED_IGNORE) {
1336 switch (e->required_any) {
1337 case PA_ALSA_REQUIRED_VOLUME:
1338 e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE);
1339 break;
1340 case PA_ALSA_REQUIRED_SWITCH:
1341 e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE);
1342 break;
1343 case PA_ALSA_REQUIRED_ENUMERATION:
1344 e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1345 break;
1346 case PA_ALSA_REQUIRED_ANY:
1347 e->path->req_any_present |=
1348 (e->volume_use != PA_ALSA_VOLUME_IGNORE) ||
1349 (e->switch_use != PA_ALSA_SWITCH_IGNORE) ||
1350 (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1351 break;
1352 default:
1353 pa_assert_not_reached();
1357 if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1358 pa_alsa_option *o;
1359 PA_LLIST_FOREACH(o, e->options) {
1360 e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) &&
1361 (o->alsa_idx >= 0);
1362 if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0)
1363 return -1;
1364 if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0)
1365 return -1;
1369 return 0;
1372 static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
1373 snd_mixer_selem_id_t *sid;
1374 snd_mixer_elem_t *me;
1376 pa_assert(m);
1377 pa_assert(e);
1378 pa_assert(e->path);
1380 SELEM_INIT(sid, e->alsa_name);
1382 if (!(me = snd_mixer_find_selem(m, sid))) {
1384 if (e->required != PA_ALSA_REQUIRED_IGNORE)
1385 return -1;
1387 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1388 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1389 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1391 return 0;
1394 if (e->switch_use != PA_ALSA_SWITCH_IGNORE) {
1395 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1397 if (!snd_mixer_selem_has_playback_switch(me)) {
1398 if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me))
1399 e->direction = PA_ALSA_DIRECTION_INPUT;
1400 else
1401 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1404 } else {
1406 if (!snd_mixer_selem_has_capture_switch(me)) {
1407 if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me))
1408 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1409 else
1410 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1414 if (e->switch_use != PA_ALSA_SWITCH_IGNORE)
1415 e->direction_try_other = FALSE;
1418 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1420 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1422 if (!snd_mixer_selem_has_playback_volume(me)) {
1423 if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me))
1424 e->direction = PA_ALSA_DIRECTION_INPUT;
1425 else
1426 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1429 } else {
1431 if (!snd_mixer_selem_has_capture_volume(me)) {
1432 if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me))
1433 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1434 else
1435 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1439 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1440 long min_dB = 0, max_dB = 0;
1441 int r;
1443 e->direction_try_other = FALSE;
1445 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1446 r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume);
1447 else
1448 r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
1450 if (r < 0) {
1451 pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1452 return -1;
1455 if (e->min_volume >= e->max_volume) {
1456 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);
1457 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1459 } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT &&
1460 (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
1461 pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
1462 e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
1463 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1465 } else {
1466 pa_bool_t is_mono;
1467 pa_channel_position_t p;
1469 if (e->db_fix &&
1470 ((e->min_volume > e->db_fix->min_step) ||
1471 (e->max_volume < e->db_fix->max_step))) {
1472 pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
1473 "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
1474 e->db_fix->min_step, e->db_fix->max_step,
1475 e->min_volume, e->max_volume);
1477 decibel_fix_free(e->db_fix);
1478 e->db_fix = NULL;
1481 if (e->db_fix) {
1482 e->has_dB = TRUE;
1483 e->min_volume = e->db_fix->min_step;
1484 e->max_volume = e->db_fix->max_step;
1485 min_dB = e->db_fix->db_values[0];
1486 max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step];
1487 } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1488 e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
1489 else
1490 e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
1492 /* Check that the kernel driver returns consistent limits with
1493 * both _get_*_dB_range() and _ask_*_vol_dB(). */
1494 if (e->has_dB && !e->db_fix) {
1495 long min_dB_checked = 0;
1496 long max_dB_checked = 0;
1498 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1499 r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked);
1500 else
1501 r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked);
1503 if (r < 0) {
1504 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
1505 return -1;
1508 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1509 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked);
1510 else
1511 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked);
1513 if (r < 0) {
1514 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
1515 return -1;
1518 if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
1519 pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
1520 "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
1521 "%0.2f dB at level %li.",
1522 e->alsa_name,
1523 min_dB / 100.0, max_dB / 100.0,
1524 min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
1525 return -1;
1529 if (e->has_dB) {
1530 #ifdef HAVE_VALGRIND_MEMCHECK_H
1531 VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
1532 VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
1533 #endif
1535 e->min_dB = ((double) min_dB) / 100.0;
1536 e->max_dB = ((double) max_dB) / 100.0;
1538 if (min_dB >= max_dB) {
1539 pa_assert(!e->db_fix);
1540 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);
1541 e->has_dB = FALSE;
1545 if (e->volume_limit >= 0) {
1546 if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
1547 pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
1548 "%li-%li. The volume limit is ignored.",
1549 e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
1551 else {
1552 e->max_volume = e->volume_limit;
1554 if (e->has_dB) {
1555 if (e->db_fix) {
1556 e->db_fix->max_step = e->max_volume;
1557 e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
1559 } else {
1560 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1561 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB);
1562 else
1563 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB);
1565 if (r < 0) {
1566 pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1567 e->has_dB = FALSE;
1568 } else
1569 e->max_dB = ((double) max_dB) / 100.0;
1575 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1576 is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
1577 else
1578 is_mono = snd_mixer_selem_is_capture_mono(me) > 0;
1580 if (is_mono) {
1581 e->n_channels = 1;
1583 if (!e->override_map) {
1584 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1585 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1586 continue;
1588 e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0;
1591 e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL;
1594 e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1];
1595 } else {
1596 e->n_channels = 0;
1597 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1599 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1600 continue;
1602 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1603 e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1604 else
1605 e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1608 if (e->n_channels <= 0) {
1609 pa_log_warn("Volume element %s with no channels?", e->alsa_name);
1610 return -1;
1613 if (e->n_channels > 2) {
1614 /* FIXME: In some places code like this is used:
1616 * e->masks[alsa_channel_ids[p]][e->n_channels-1]
1618 * The definition of e->masks is
1620 * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2];
1622 * Since the array size is fixed at 2, we obviously
1623 * don't support elements with more than two
1624 * channels... */
1625 pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
1626 return -1;
1629 if (!e->override_map) {
1630 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1631 pa_bool_t has_channel;
1633 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1634 continue;
1636 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1637 has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1638 else
1639 has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1641 e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0;
1645 e->merged_mask = 0;
1646 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1647 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1648 continue;
1650 e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
1658 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
1659 pa_alsa_option *o;
1661 PA_LLIST_FOREACH(o, e->options)
1662 o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0;
1663 } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1664 int n;
1665 pa_alsa_option *o;
1667 if ((n = snd_mixer_selem_get_enum_items(me)) < 0) {
1668 pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n));
1669 return -1;
1672 PA_LLIST_FOREACH(o, e->options) {
1673 int i;
1675 for (i = 0; i < n; i++) {
1676 char buf[128];
1678 if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0)
1679 continue;
1681 if (!pa_streq(buf, o->alsa_name))
1682 continue;
1684 o->alsa_idx = i;
1689 if (check_required(e, me) < 0)
1690 return -1;
1692 return 0;
1695 static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
1696 pa_alsa_element *e;
1698 pa_assert(p);
1699 pa_assert(section);
1701 if (prefixed) {
1702 if (!pa_startswith(section, "Element "))
1703 return NULL;
1705 section += 8;
1708 /* This is not an element section, but an enum section? */
1709 if (strchr(section, ':'))
1710 return NULL;
1712 if (p->last_element && pa_streq(p->last_element->alsa_name, section))
1713 return p->last_element;
1715 PA_LLIST_FOREACH(e, p->elements)
1716 if (pa_streq(e->alsa_name, section))
1717 goto finish;
1719 e = pa_xnew0(pa_alsa_element, 1);
1720 e->path = p;
1721 e->alsa_name = pa_xstrdup(section);
1722 e->direction = p->direction;
1723 e->volume_limit = -1;
1725 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
1727 finish:
1728 p->last_element = e;
1729 return e;
1732 static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
1733 char *en;
1734 const char *on;
1735 pa_alsa_option *o;
1736 pa_alsa_element *e;
1738 if (!pa_startswith(section, "Option "))
1739 return NULL;
1741 section += 7;
1743 /* This is not an enum section, but an element section? */
1744 if (!(on = strchr(section, ':')))
1745 return NULL;
1747 en = pa_xstrndup(section, on - section);
1748 on++;
1750 if (p->last_option &&
1751 pa_streq(p->last_option->element->alsa_name, en) &&
1752 pa_streq(p->last_option->alsa_name, on)) {
1753 pa_xfree(en);
1754 return p->last_option;
1757 pa_assert_se(e = element_get(p, en, FALSE));
1758 pa_xfree(en);
1760 PA_LLIST_FOREACH(o, e->options)
1761 if (pa_streq(o->alsa_name, on))
1762 goto finish;
1764 o = pa_xnew0(pa_alsa_option, 1);
1765 o->element = e;
1766 o->alsa_name = pa_xstrdup(on);
1767 o->alsa_idx = -1;
1769 if (p->last_option && p->last_option->element == e)
1770 PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o);
1771 else
1772 PA_LLIST_PREPEND(pa_alsa_option, e->options, o);
1774 finish:
1775 p->last_option = o;
1776 return o;
1779 static int element_parse_switch(
1780 const char *filename,
1781 unsigned line,
1782 const char *section,
1783 const char *lvalue,
1784 const char *rvalue,
1785 void *data,
1786 void *userdata) {
1788 pa_alsa_path *p = userdata;
1789 pa_alsa_element *e;
1791 pa_assert(p);
1793 if (!(e = element_get(p, section, TRUE))) {
1794 pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section);
1795 return -1;
1798 if (pa_streq(rvalue, "ignore"))
1799 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1800 else if (pa_streq(rvalue, "mute"))
1801 e->switch_use = PA_ALSA_SWITCH_MUTE;
1802 else if (pa_streq(rvalue, "off"))
1803 e->switch_use = PA_ALSA_SWITCH_OFF;
1804 else if (pa_streq(rvalue, "on"))
1805 e->switch_use = PA_ALSA_SWITCH_ON;
1806 else if (pa_streq(rvalue, "select"))
1807 e->switch_use = PA_ALSA_SWITCH_SELECT;
1808 else {
1809 pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section);
1810 return -1;
1813 return 0;
1816 static int element_parse_volume(
1817 const char *filename,
1818 unsigned line,
1819 const char *section,
1820 const char *lvalue,
1821 const char *rvalue,
1822 void *data,
1823 void *userdata) {
1825 pa_alsa_path *p = userdata;
1826 pa_alsa_element *e;
1828 pa_assert(p);
1830 if (!(e = element_get(p, section, TRUE))) {
1831 pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section);
1832 return -1;
1835 if (pa_streq(rvalue, "ignore"))
1836 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1837 else if (pa_streq(rvalue, "merge"))
1838 e->volume_use = PA_ALSA_VOLUME_MERGE;
1839 else if (pa_streq(rvalue, "off"))
1840 e->volume_use = PA_ALSA_VOLUME_OFF;
1841 else if (pa_streq(rvalue, "zero"))
1842 e->volume_use = PA_ALSA_VOLUME_ZERO;
1843 else {
1844 uint32_t constant;
1846 if (pa_atou(rvalue, &constant) >= 0) {
1847 e->volume_use = PA_ALSA_VOLUME_CONSTANT;
1848 e->constant_volume = constant;
1849 } else {
1850 pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section);
1851 return -1;
1855 return 0;
1858 static int element_parse_enumeration(
1859 const char *filename,
1860 unsigned line,
1861 const char *section,
1862 const char *lvalue,
1863 const char *rvalue,
1864 void *data,
1865 void *userdata) {
1867 pa_alsa_path *p = userdata;
1868 pa_alsa_element *e;
1870 pa_assert(p);
1872 if (!(e = element_get(p, section, TRUE))) {
1873 pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section);
1874 return -1;
1877 if (pa_streq(rvalue, "ignore"))
1878 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1879 else if (pa_streq(rvalue, "select"))
1880 e->enumeration_use = PA_ALSA_ENUMERATION_SELECT;
1881 else {
1882 pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section);
1883 return -1;
1886 return 0;
1889 static int option_parse_priority(
1890 const char *filename,
1891 unsigned line,
1892 const char *section,
1893 const char *lvalue,
1894 const char *rvalue,
1895 void *data,
1896 void *userdata) {
1898 pa_alsa_path *p = userdata;
1899 pa_alsa_option *o;
1900 uint32_t prio;
1902 pa_assert(p);
1904 if (!(o = option_get(p, section))) {
1905 pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section);
1906 return -1;
1909 if (pa_atou(rvalue, &prio) < 0) {
1910 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
1911 return -1;
1914 o->priority = prio;
1915 return 0;
1918 static int option_parse_name(
1919 const char *filename,
1920 unsigned line,
1921 const char *section,
1922 const char *lvalue,
1923 const char *rvalue,
1924 void *data,
1925 void *userdata) {
1927 pa_alsa_path *p = userdata;
1928 pa_alsa_option *o;
1930 pa_assert(p);
1932 if (!(o = option_get(p, section))) {
1933 pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section);
1934 return -1;
1937 pa_xfree(o->name);
1938 o->name = pa_xstrdup(rvalue);
1940 return 0;
1943 static int element_parse_required(
1944 const char *filename,
1945 unsigned line,
1946 const char *section,
1947 const char *lvalue,
1948 const char *rvalue,
1949 void *data,
1950 void *userdata) {
1952 pa_alsa_path *p = userdata;
1953 pa_alsa_element *e;
1954 pa_alsa_option *o;
1955 pa_alsa_required_t req;
1957 pa_assert(p);
1959 e = element_get(p, section, TRUE);
1960 o = option_get(p, section);
1961 if (!e && !o) {
1962 pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
1963 return -1;
1966 if (pa_streq(rvalue, "ignore"))
1967 req = PA_ALSA_REQUIRED_IGNORE;
1968 else if (pa_streq(rvalue, "switch") && e)
1969 req = PA_ALSA_REQUIRED_SWITCH;
1970 else if (pa_streq(rvalue, "volume") && e)
1971 req = PA_ALSA_REQUIRED_VOLUME;
1972 else if (pa_streq(rvalue, "enumeration"))
1973 req = PA_ALSA_REQUIRED_ENUMERATION;
1974 else if (pa_streq(rvalue, "any"))
1975 req = PA_ALSA_REQUIRED_ANY;
1976 else {
1977 pa_log("[%s:%u] Required invalid of '%s'", filename, line, section);
1978 return -1;
1981 if (pa_streq(lvalue, "required-absent")) {
1982 if (e)
1983 e->required_absent = req;
1984 if (o)
1985 o->required_absent = req;
1987 else if (pa_streq(lvalue, "required-any")) {
1988 if (e) {
1989 e->required_any = req;
1990 e->path->has_req_any = TRUE;
1992 if (o) {
1993 o->required_any = req;
1994 o->element->path->has_req_any = TRUE;
1997 else {
1998 if (e)
1999 e->required = req;
2000 if (o)
2001 o->required = req;
2004 return 0;
2007 static int element_parse_direction(
2008 const char *filename,
2009 unsigned line,
2010 const char *section,
2011 const char *lvalue,
2012 const char *rvalue,
2013 void *data,
2014 void *userdata) {
2016 pa_alsa_path *p = userdata;
2017 pa_alsa_element *e;
2019 pa_assert(p);
2021 if (!(e = element_get(p, section, TRUE))) {
2022 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2023 return -1;
2026 if (pa_streq(rvalue, "playback"))
2027 e->direction = PA_ALSA_DIRECTION_OUTPUT;
2028 else if (pa_streq(rvalue, "capture"))
2029 e->direction = PA_ALSA_DIRECTION_INPUT;
2030 else {
2031 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2032 return -1;
2035 return 0;
2038 static int element_parse_direction_try_other(
2039 const char *filename,
2040 unsigned line,
2041 const char *section,
2042 const char *lvalue,
2043 const char *rvalue,
2044 void *data,
2045 void *userdata) {
2047 pa_alsa_path *p = userdata;
2048 pa_alsa_element *e;
2049 int yes;
2051 if (!(e = element_get(p, section, TRUE))) {
2052 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2053 return -1;
2056 if ((yes = pa_parse_boolean(rvalue)) < 0) {
2057 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2058 return -1;
2061 e->direction_try_other = !!yes;
2062 return 0;
2065 static int element_parse_volume_limit(
2066 const char *filename,
2067 unsigned line,
2068 const char *section,
2069 const char *lvalue,
2070 const char *rvalue,
2071 void *data,
2072 void *userdata) {
2074 pa_alsa_path *p = userdata;
2075 pa_alsa_element *e;
2076 long volume_limit;
2078 if (!(e = element_get(p, section, TRUE))) {
2079 pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section);
2080 return -1;
2083 if (pa_atol(rvalue, &volume_limit) < 0 || volume_limit < 0) {
2084 pa_log("[%s:%u] Invalid value for volume-limit", filename, line);
2085 return -1;
2088 e->volume_limit = volume_limit;
2089 return 0;
2092 static pa_channel_position_mask_t parse_mask(const char *m) {
2093 pa_channel_position_mask_t v;
2095 if (pa_streq(m, "all-left"))
2096 v = PA_CHANNEL_POSITION_MASK_LEFT;
2097 else if (pa_streq(m, "all-right"))
2098 v = PA_CHANNEL_POSITION_MASK_RIGHT;
2099 else if (pa_streq(m, "all-center"))
2100 v = PA_CHANNEL_POSITION_MASK_CENTER;
2101 else if (pa_streq(m, "all-front"))
2102 v = PA_CHANNEL_POSITION_MASK_FRONT;
2103 else if (pa_streq(m, "all-rear"))
2104 v = PA_CHANNEL_POSITION_MASK_REAR;
2105 else if (pa_streq(m, "all-side"))
2106 v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER;
2107 else if (pa_streq(m, "all-top"))
2108 v = PA_CHANNEL_POSITION_MASK_TOP;
2109 else if (pa_streq(m, "all-no-lfe"))
2110 v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE);
2111 else if (pa_streq(m, "all"))
2112 v = PA_CHANNEL_POSITION_MASK_ALL;
2113 else {
2114 pa_channel_position_t p;
2116 if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
2117 return 0;
2119 v = PA_CHANNEL_POSITION_MASK(p);
2122 return v;
2125 static int element_parse_override_map(
2126 const char *filename,
2127 unsigned line,
2128 const char *section,
2129 const char *lvalue,
2130 const char *rvalue,
2131 void *data,
2132 void *userdata) {
2134 pa_alsa_path *p = userdata;
2135 pa_alsa_element *e;
2136 const char *state = NULL;
2137 unsigned i = 0;
2138 char *n;
2140 if (!(e = element_get(p, section, TRUE))) {
2141 pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section);
2142 return -1;
2145 while ((n = pa_split(rvalue, ",", &state))) {
2146 pa_channel_position_mask_t m;
2148 if (!*n)
2149 m = 0;
2150 else {
2151 if ((m = parse_mask(n)) == 0) {
2152 pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section);
2153 pa_xfree(n);
2154 return -1;
2158 if (pa_streq(lvalue, "override-map.1"))
2159 e->masks[i++][0] = m;
2160 else
2161 e->masks[i++][1] = m;
2163 /* Later on we might add override-map.3 and so on here ... */
2165 pa_xfree(n);
2168 e->override_map = TRUE;
2170 return 0;
2173 static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
2174 snd_mixer_selem_id_t *sid;
2175 snd_mixer_elem_t *me;
2176 int r;
2178 pa_assert(e);
2179 pa_assert(m);
2181 SELEM_INIT(sid, e->alsa_name);
2182 if (!(me = snd_mixer_find_selem(m, sid))) {
2183 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2184 return -1;
2187 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
2189 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
2190 r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx);
2191 else
2192 r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
2194 if (r < 0)
2195 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2197 } else {
2198 pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
2200 if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
2201 pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2204 return r;
2207 int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) {
2208 pa_alsa_option *o;
2209 uint32_t idx;
2211 pa_assert(s);
2212 pa_assert(m);
2214 PA_IDXSET_FOREACH(o, s->options, idx)
2215 element_set_option(o->element, m, o->alsa_idx);
2217 return 0;
2220 static int option_verify(pa_alsa_option *o) {
2221 static const struct description_map well_known_descriptions[] = {
2222 { "input", N_("Input") },
2223 { "input-docking", N_("Docking Station Input") },
2224 { "input-docking-microphone", N_("Docking Station Microphone") },
2225 { "input-docking-linein", N_("Docking Station Line-In") },
2226 { "input-linein", N_("Line-In") },
2227 { "input-microphone", N_("Microphone") },
2228 { "input-microphone-front", N_("Front Microphone") },
2229 { "input-microphone-rear", N_("Rear Microphone") },
2230 { "input-microphone-external", N_("External Microphone") },
2231 { "input-microphone-internal", N_("Internal Microphone") },
2232 { "input-radio", N_("Radio") },
2233 { "input-video", N_("Video") },
2234 { "input-agc-on", N_("Automatic Gain Control") },
2235 { "input-agc-off", N_("No Automatic Gain Control") },
2236 { "input-boost-on", N_("Boost") },
2237 { "input-boost-off", N_("No Boost") },
2238 { "output-amplifier-on", N_("Amplifier") },
2239 { "output-amplifier-off", N_("No Amplifier") },
2240 { "output-bass-boost-on", N_("Bass Boost") },
2241 { "output-bass-boost-off", N_("No Bass Boost") },
2242 { "output-speaker", N_("Speaker") },
2243 { "output-headphones", N_("Headphones") }
2246 pa_assert(o);
2248 if (!o->name) {
2249 pa_log("No name set for option %s", o->alsa_name);
2250 return -1;
2253 if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
2254 o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
2255 pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
2256 return -1;
2259 if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
2260 !pa_streq(o->alsa_name, "on") &&
2261 !pa_streq(o->alsa_name, "off")) {
2262 pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
2263 return -1;
2266 if (!o->description)
2267 o->description = pa_xstrdup(lookup_description(o->name,
2268 well_known_descriptions,
2269 PA_ELEMENTSOF(well_known_descriptions)));
2270 if (!o->description)
2271 o->description = pa_xstrdup(o->name);
2273 return 0;
2276 static int element_verify(pa_alsa_element *e) {
2277 pa_alsa_option *o;
2279 pa_assert(e);
2281 // 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);
2282 if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) ||
2283 (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
2284 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
2285 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
2286 pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
2287 return -1;
2290 if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
2291 pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
2292 return -1;
2295 PA_LLIST_FOREACH(o, e->options)
2296 if (option_verify(o) < 0)
2297 return -1;
2299 return 0;
2302 static int path_verify(pa_alsa_path *p) {
2303 static const struct description_map well_known_descriptions[] = {
2304 { "analog-input", N_("Analog Input") },
2305 { "analog-input-microphone", N_("Analog Microphone") },
2306 { "analog-input-microphone-front", N_("Front Microphone") },
2307 { "analog-input-microphone-rear", N_("Rear Microphone") },
2308 { "analog-input-microphone-dock", N_("Docking Station Microphone") },
2309 { "analog-input-microphone-internal", N_("Internal Microphone") },
2310 { "analog-input-linein", N_("Analog Line-In") },
2311 { "analog-input-radio", N_("Analog Radio") },
2312 { "analog-input-video", N_("Analog Video") },
2313 { "analog-output", N_("Analog Output") },
2314 { "analog-output-headphones", N_("Analog Headphones") },
2315 { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") },
2316 { "analog-output-mono", N_("Analog Mono Output") },
2317 { "analog-output-speaker", N_("Analog Speakers") },
2318 { "iec958-stereo-output", N_("Digital Output (IEC958)") },
2319 { "iec958-passthrough-output", N_("Digital Passthrough (IEC958)") }
2322 pa_alsa_element *e;
2324 pa_assert(p);
2326 PA_LLIST_FOREACH(e, p->elements)
2327 if (element_verify(e) < 0)
2328 return -1;
2330 if (!p->description)
2331 p->description = pa_xstrdup(lookup_description(p->name,
2332 well_known_descriptions,
2333 PA_ELEMENTSOF(well_known_descriptions)));
2335 if (!p->description)
2336 p->description = pa_xstrdup(p->name);
2338 return 0;
2341 static const char *get_default_paths_dir(void) {
2342 if (pa_run_from_build_tree())
2343 return PA_BUILDDIR "/modules/alsa/mixer/paths/";
2344 else
2345 return PA_ALSA_PATHS_DIR;
2348 pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) {
2349 pa_alsa_path *p;
2350 char *fn;
2351 int r;
2352 const char *n;
2354 pa_config_item items[] = {
2355 /* [General] */
2356 { "priority", pa_config_parse_unsigned, NULL, "General" },
2357 { "description", pa_config_parse_string, NULL, "General" },
2358 { "name", pa_config_parse_string, NULL, "General" },
2360 /* [Option ...] */
2361 { "priority", option_parse_priority, NULL, NULL },
2362 { "name", option_parse_name, NULL, NULL },
2364 /* [Element ...] */
2365 { "switch", element_parse_switch, NULL, NULL },
2366 { "volume", element_parse_volume, NULL, NULL },
2367 { "enumeration", element_parse_enumeration, NULL, NULL },
2368 { "override-map.1", element_parse_override_map, NULL, NULL },
2369 { "override-map.2", element_parse_override_map, NULL, NULL },
2370 /* ... later on we might add override-map.3 and so on here ... */
2371 { "required", element_parse_required, NULL, NULL },
2372 { "required-any", element_parse_required, NULL, NULL },
2373 { "required-absent", element_parse_required, NULL, NULL },
2374 { "direction", element_parse_direction, NULL, NULL },
2375 { "direction-try-other", element_parse_direction_try_other, NULL, NULL },
2376 { "volume-limit", element_parse_volume_limit, NULL, NULL },
2377 { NULL, NULL, NULL, NULL }
2380 pa_assert(fname);
2382 p = pa_xnew0(pa_alsa_path, 1);
2383 n = pa_path_get_filename(fname);
2384 p->name = pa_xstrndup(n, strcspn(n, "."));
2385 p->direction = direction;
2387 items[0].data = &p->priority;
2388 items[1].data = &p->description;
2389 items[2].data = &p->name;
2391 if (!paths_dir)
2392 paths_dir = get_default_paths_dir();
2394 fn = pa_maybe_prefix_path(fname, paths_dir);
2396 r = pa_config_parse(fn, NULL, items, p);
2397 pa_xfree(fn);
2399 if (r < 0)
2400 goto fail;
2402 if (path_verify(p) < 0)
2403 goto fail;
2405 return p;
2407 fail:
2408 pa_alsa_path_free(p);
2409 return NULL;
2412 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
2413 pa_alsa_path *p;
2414 pa_alsa_element *e;
2416 pa_assert(element);
2418 p = pa_xnew0(pa_alsa_path, 1);
2419 p->name = pa_xstrdup(element);
2420 p->direction = direction;
2422 e = pa_xnew0(pa_alsa_element, 1);
2423 e->path = p;
2424 e->alsa_name = pa_xstrdup(element);
2425 e->direction = direction;
2426 e->volume_limit = -1;
2428 e->switch_use = PA_ALSA_SWITCH_MUTE;
2429 e->volume_use = PA_ALSA_VOLUME_MERGE;
2431 PA_LLIST_PREPEND(pa_alsa_element, p->elements, e);
2432 p->last_element = e;
2433 return p;
2436 static pa_bool_t element_drop_unsupported(pa_alsa_element *e) {
2437 pa_alsa_option *o, *n;
2439 pa_assert(e);
2441 for (o = e->options; o; o = n) {
2442 n = o->next;
2444 if (o->alsa_idx < 0) {
2445 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
2446 option_free(o);
2450 return
2451 e->switch_use != PA_ALSA_SWITCH_IGNORE ||
2452 e->volume_use != PA_ALSA_VOLUME_IGNORE ||
2453 e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE;
2456 static void path_drop_unsupported(pa_alsa_path *p) {
2457 pa_alsa_element *e, *n;
2459 pa_assert(p);
2461 for (e = p->elements; e; e = n) {
2462 n = e->next;
2464 if (!element_drop_unsupported(e)) {
2465 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
2466 element_free(e);
2471 static void path_make_options_unique(pa_alsa_path *p) {
2472 pa_alsa_element *e;
2473 pa_alsa_option *o, *u;
2475 PA_LLIST_FOREACH(e, p->elements) {
2476 PA_LLIST_FOREACH(o, e->options) {
2477 unsigned i;
2478 char *m;
2480 for (u = o->next; u; u = u->next)
2481 if (pa_streq(u->name, o->name))
2482 break;
2484 if (!u)
2485 continue;
2487 m = pa_xstrdup(o->name);
2489 /* OK, this name is not unique, hence let's rename */
2490 for (i = 1, u = o; u; u = u->next) {
2491 char *nn, *nd;
2493 if (!pa_streq(u->name, m))
2494 continue;
2496 nn = pa_sprintf_malloc("%s-%u", m, i);
2497 pa_xfree(u->name);
2498 u->name = nn;
2500 nd = pa_sprintf_malloc("%s %u", u->description, i);
2501 pa_xfree(u->description);
2502 u->description = nd;
2504 i++;
2507 pa_xfree(m);
2512 static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) {
2513 pa_alsa_option *o;
2515 for (; e; e = e->next)
2516 if (e->switch_use == PA_ALSA_SWITCH_SELECT ||
2517 e->enumeration_use == PA_ALSA_ENUMERATION_SELECT)
2518 break;
2520 if (!e)
2521 return FALSE;
2523 for (o = e->options; o; o = o->next) {
2524 pa_alsa_setting *s;
2526 if (template) {
2527 s = pa_xnewdup(pa_alsa_setting, template, 1);
2528 s->options = pa_idxset_copy(template->options);
2529 s->name = pa_sprintf_malloc(_("%s+%s"), template->name, o->name);
2530 s->description =
2531 (template->description[0] && o->description[0])
2532 ? pa_sprintf_malloc(_("%s / %s"), template->description, o->description)
2533 : (template->description[0]
2534 ? pa_xstrdup(template->description)
2535 : pa_xstrdup(o->description));
2537 s->priority = PA_MAX(template->priority, o->priority);
2538 } else {
2539 s = pa_xnew0(pa_alsa_setting, 1);
2540 s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
2541 s->name = pa_xstrdup(o->name);
2542 s->description = pa_xstrdup(o->description);
2543 s->priority = o->priority;
2546 pa_idxset_put(s->options, o, NULL);
2548 if (element_create_settings(e->next, s))
2549 /* This is not a leaf, so let's get rid of it */
2550 setting_free(s);
2551 else {
2552 /* This is a leaf, so let's add it */
2553 PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s);
2555 e->path->last_setting = s;
2559 return TRUE;
2562 static void path_create_settings(pa_alsa_path *p) {
2563 pa_assert(p);
2565 element_create_settings(p->elements, NULL);
2568 int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
2569 pa_alsa_element *e;
2570 double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
2571 pa_channel_position_t t;
2572 pa_channel_position_mask_t path_volume_channels = 0;
2574 pa_assert(p);
2575 pa_assert(m);
2577 if (p->probed)
2578 return 0;
2580 pa_zero(min_dB);
2581 pa_zero(max_dB);
2583 pa_log_debug("Probing path '%s'", p->name);
2585 PA_LLIST_FOREACH(e, p->elements) {
2586 if (element_probe(e, m) < 0) {
2587 p->supported = FALSE;
2588 pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
2589 return -1;
2591 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);
2593 if (ignore_dB)
2594 e->has_dB = FALSE;
2596 if (e->volume_use == PA_ALSA_VOLUME_MERGE) {
2598 if (!p->has_volume) {
2599 p->min_volume = e->min_volume;
2600 p->max_volume = e->max_volume;
2603 if (e->has_dB) {
2604 if (!p->has_volume) {
2605 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2606 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2607 min_dB[t] = e->min_dB;
2608 max_dB[t] = e->max_dB;
2609 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2612 p->has_dB = TRUE;
2613 } else {
2615 if (p->has_dB) {
2616 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2617 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2618 min_dB[t] += e->min_dB;
2619 max_dB[t] += e->max_dB;
2620 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2622 } else {
2623 /* Hmm, there's another element before us
2624 * which cannot do dB volumes, so we we need
2625 * to 'neutralize' this slider */
2626 e->volume_use = PA_ALSA_VOLUME_ZERO;
2627 pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
2630 } else if (p->has_volume) {
2631 /* We can't use this volume, so let's ignore it */
2632 e->volume_use = PA_ALSA_VOLUME_IGNORE;
2633 pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
2635 p->has_volume = TRUE;
2638 if (e->switch_use == PA_ALSA_SWITCH_MUTE)
2639 p->has_mute = TRUE;
2642 if (p->has_req_any && !p->req_any_present) {
2643 p->supported = FALSE;
2644 pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name);
2645 return -1;
2648 path_drop_unsupported(p);
2649 path_make_options_unique(p);
2650 path_create_settings(p);
2652 p->supported = TRUE;
2653 p->probed = TRUE;
2655 p->min_dB = INFINITY;
2656 p->max_dB = -INFINITY;
2658 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
2659 if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
2660 if (p->min_dB > min_dB[t])
2661 p->min_dB = min_dB[t];
2663 if (p->max_dB < max_dB[t])
2664 p->max_dB = max_dB[t];
2668 return 0;
2671 void pa_alsa_setting_dump(pa_alsa_setting *s) {
2672 pa_assert(s);
2674 pa_log_debug("Setting %s (%s) priority=%u",
2675 s->name,
2676 pa_strnull(s->description),
2677 s->priority);
2680 void pa_alsa_option_dump(pa_alsa_option *o) {
2681 pa_assert(o);
2683 pa_log_debug("Option %s (%s/%s) index=%i, priority=%u",
2684 o->alsa_name,
2685 pa_strnull(o->name),
2686 pa_strnull(o->description),
2687 o->alsa_idx,
2688 o->priority);
2691 void pa_alsa_element_dump(pa_alsa_element *e) {
2692 pa_alsa_option *o;
2693 pa_assert(e);
2695 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",
2696 e->alsa_name,
2697 e->direction,
2698 e->switch_use,
2699 e->volume_use,
2700 e->volume_limit,
2701 e->enumeration_use,
2702 e->required,
2703 e->required_any,
2704 e->required_absent,
2705 (long long unsigned) e->merged_mask,
2706 e->n_channels,
2707 pa_yes_no(e->override_map));
2709 PA_LLIST_FOREACH(o, e->options)
2710 pa_alsa_option_dump(o);
2713 void pa_alsa_path_dump(pa_alsa_path *p) {
2714 pa_alsa_element *e;
2715 pa_alsa_setting *s;
2716 pa_assert(p);
2718 pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, "
2719 "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g",
2720 p->name,
2721 pa_strnull(p->description),
2722 p->direction,
2723 p->priority,
2724 pa_yes_no(p->probed),
2725 pa_yes_no(p->supported),
2726 pa_yes_no(p->has_mute),
2727 pa_yes_no(p->has_volume),
2728 pa_yes_no(p->has_dB),
2729 p->min_volume, p->max_volume,
2730 p->min_dB, p->max_dB);
2732 PA_LLIST_FOREACH(e, p->elements)
2733 pa_alsa_element_dump(e);
2735 PA_LLIST_FOREACH(s, p->settings)
2736 pa_alsa_setting_dump(s);
2739 static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2740 snd_mixer_selem_id_t *sid;
2741 snd_mixer_elem_t *me;
2743 pa_assert(e);
2744 pa_assert(m);
2745 pa_assert(cb);
2747 SELEM_INIT(sid, e->alsa_name);
2748 if (!(me = snd_mixer_find_selem(m, sid))) {
2749 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2750 return;
2753 snd_mixer_elem_set_callback(me, cb);
2754 snd_mixer_elem_set_callback_private(me, userdata);
2757 void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2758 pa_alsa_element *e;
2760 pa_assert(p);
2761 pa_assert(m);
2762 pa_assert(cb);
2764 PA_LLIST_FOREACH(e, p->elements)
2765 element_set_callback(e, m, cb, userdata);
2768 void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2769 pa_alsa_path *p;
2771 pa_assert(ps);
2772 pa_assert(m);
2773 pa_assert(cb);
2775 PA_LLIST_FOREACH(p, ps->paths)
2776 pa_alsa_path_set_callback(p, m, cb, userdata);
2779 pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir) {
2780 pa_alsa_path_set *ps;
2781 char **pn = NULL, **en = NULL, **ie;
2782 pa_alsa_decibel_fix *db_fix;
2783 void *state;
2785 pa_assert(m);
2786 pa_assert(m->profile_set);
2787 pa_assert(m->profile_set->decibel_fixes);
2788 pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT);
2790 if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction)
2791 return NULL;
2793 ps = pa_xnew0(pa_alsa_path_set, 1);
2794 ps->direction = direction;
2796 if (direction == PA_ALSA_DIRECTION_OUTPUT)
2797 pn = m->output_path_names;
2798 else if (direction == PA_ALSA_DIRECTION_INPUT)
2799 pn = m->input_path_names;
2801 if (pn) {
2802 char **in;
2804 for (in = pn; *in; in++) {
2805 pa_alsa_path *p;
2806 pa_bool_t duplicate = FALSE;
2807 char **kn, *fn;
2809 for (kn = pn; kn < in; kn++)
2810 if (pa_streq(*kn, *in)) {
2811 duplicate = TRUE;
2812 break;
2815 if (duplicate)
2816 continue;
2818 fn = pa_sprintf_malloc("%s.conf", *in);
2820 if ((p = pa_alsa_path_new(paths_dir, fn, direction))) {
2821 p->path_set = ps;
2822 PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
2823 ps->last_path = p;
2826 pa_xfree(fn);
2829 goto finish;
2832 if (direction == PA_ALSA_DIRECTION_OUTPUT)
2833 en = m->output_element;
2834 else if (direction == PA_ALSA_DIRECTION_INPUT)
2835 en = m->input_element;
2837 if (!en) {
2838 pa_alsa_path_set_free(ps);
2839 return NULL;
2842 for (ie = en; *ie; ie++) {
2843 char **je;
2844 pa_alsa_path *p;
2846 p = pa_alsa_path_synthesize(*ie, direction);
2847 p->path_set = ps;
2849 /* Mark all other passed elements for require-absent */
2850 for (je = en; *je; je++) {
2851 pa_alsa_element *e;
2853 if (je == ie)
2854 continue;
2856 e = pa_xnew0(pa_alsa_element, 1);
2857 e->path = p;
2858 e->alsa_name = pa_xstrdup(*je);
2859 e->direction = direction;
2860 e->required_absent = PA_ALSA_REQUIRED_ANY;
2861 e->volume_limit = -1;
2863 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
2864 p->last_element = e;
2867 PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
2868 ps->last_path = p;
2871 finish:
2872 /* Assign decibel fixes to elements. */
2873 PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) {
2874 pa_alsa_path *p;
2876 PA_LLIST_FOREACH(p, ps->paths) {
2877 pa_alsa_element *e;
2879 PA_LLIST_FOREACH(e, p->elements) {
2880 if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
2881 /* The profile set that contains the dB fix may be freed
2882 * before the element, so we have to copy the dB fix
2883 * object. */
2884 e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1);
2885 e->db_fix->profile_set = NULL;
2886 e->db_fix->name = pa_xstrdup(db_fix->name);
2887 e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long));
2893 return ps;
2896 void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
2897 pa_alsa_path *p;
2898 pa_assert(ps);
2900 pa_log_debug("Path Set %p, direction=%i, probed=%s",
2901 (void*) ps,
2902 ps->direction,
2903 pa_yes_no(ps->probed));
2905 PA_LLIST_FOREACH(p, ps->paths)
2906 pa_alsa_path_dump(p);
2910 static pa_bool_t options_have_option(pa_alsa_option *options, const char *alsa_name) {
2911 pa_alsa_option *o;
2913 pa_assert(options);
2914 pa_assert(alsa_name);
2916 PA_LLIST_FOREACH(o, options) {
2917 if (pa_streq(o->alsa_name, alsa_name))
2918 return TRUE;
2920 return FALSE;
2923 static pa_bool_t enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_options) {
2924 pa_alsa_option *oa, *ob;
2926 if (!a_options) return TRUE;
2927 if (!b_options) return FALSE;
2929 /* If there is an option A offers that B does not, then A is not a subset of B. */
2930 PA_LLIST_FOREACH(oa, a_options) {
2931 pa_bool_t found = FALSE;
2932 PA_LLIST_FOREACH(ob, b_options) {
2933 if (pa_streq(oa->alsa_name, ob->alsa_name)) {
2934 found = TRUE;
2935 break;
2938 if (!found)
2939 return FALSE;
2941 return TRUE;
2945 * Compares two elements to see if a is a subset of b
2947 static pa_bool_t element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
2948 pa_assert(a);
2949 pa_assert(b);
2950 pa_assert(m);
2952 /* General rules:
2953 * Every state is a subset of itself (with caveats for volume_limits and options)
2954 * IGNORE is a subset of every other state */
2956 /* Check the volume_use */
2957 if (a->volume_use != PA_ALSA_VOLUME_IGNORE) {
2959 /* "Constant" is subset of "Constant" only when their constant values are equal */
2960 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume)
2961 return FALSE;
2963 /* Different volume uses when b is not "Merge" means we are definitely not a subset */
2964 if (a->volume_use != b->volume_use && b->volume_use != PA_ALSA_VOLUME_MERGE)
2965 return FALSE;
2967 /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant.
2968 * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge"
2969 * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit" */
2970 if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) {
2971 long a_limit;
2973 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT)
2974 a_limit = a->constant_volume;
2975 else if (a->volume_use == PA_ALSA_VOLUME_ZERO) {
2976 long dB = 0;
2978 if (a->db_fix) {
2979 int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1);
2980 a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding);
2981 } else {
2982 snd_mixer_selem_id_t *sid;
2983 snd_mixer_elem_t *me;
2985 SELEM_INIT(sid, a->alsa_name);
2986 if (!(me = snd_mixer_find_selem(m, sid))) {
2987 pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
2988 return FALSE;
2991 if (a->direction == PA_ALSA_DIRECTION_OUTPUT) {
2992 if (snd_mixer_selem_ask_playback_dB_vol(me, dB, +1, &a_limit) < 0)
2993 return FALSE;
2994 } else {
2995 if (snd_mixer_selem_ask_capture_dB_vol(me, dB, -1, &a_limit) < 0)
2996 return FALSE;
2999 } else if (a->volume_use == PA_ALSA_VOLUME_OFF)
3000 a_limit = a->min_volume;
3001 else if (a->volume_use == PA_ALSA_VOLUME_MERGE)
3002 a_limit = a->volume_limit;
3003 else
3004 /* This should never be reached */
3005 pa_assert(FALSE);
3007 if (a_limit > b->volume_limit)
3008 return FALSE;
3012 if (a->switch_use != PA_ALSA_SWITCH_IGNORE) {
3013 /* "On" is a subset of "Mute".
3014 * "Off" is a subset of "Mute".
3015 * "On" is a subset of "Select", if there is an "Option:On" in B.
3016 * "Off" is a subset of "Select", if there is an "Option:Off" in B.
3017 * "Select" is a subset of "Select", if they have the same options (is this always true?). */
3019 if (a->switch_use != b->switch_use) {
3021 if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE
3022 || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON)
3023 return FALSE;
3025 if (b->switch_use == PA_ALSA_SWITCH_SELECT) {
3026 if (a->switch_use == PA_ALSA_SWITCH_ON) {
3027 if (!options_have_option(b->options, "on"))
3028 return FALSE;
3029 } else if (a->switch_use == PA_ALSA_SWITCH_OFF) {
3030 if (!options_have_option(b->options, "off"))
3031 return FALSE;
3034 } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) {
3035 if (!enumeration_is_subset(a->options, b->options))
3036 return FALSE;
3040 if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) {
3041 if (b->enumeration_use == PA_ALSA_ENUMERATION_IGNORE)
3042 return FALSE;
3043 if (!enumeration_is_subset(a->options, b->options))
3044 return FALSE;
3047 return TRUE;
3050 static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
3051 pa_alsa_path *p, *np;
3053 pa_assert(ps);
3054 pa_assert(m);
3056 /* If we only have one path, then don't bother */
3057 if (!ps->paths || !ps->paths->next)
3058 return;
3060 for (p = ps->paths; p; p = np) {
3061 pa_alsa_path *p2;
3062 np = p->next;
3064 PA_LLIST_FOREACH(p2, ps->paths) {
3065 pa_alsa_element *ea, *eb;
3066 pa_bool_t is_subset = TRUE;
3068 if (p == p2)
3069 continue;
3071 /* Compare the elements of each set... */
3072 pa_assert_se(ea = p->elements);
3073 pa_assert_se(eb = p2->elements);
3075 while (is_subset) {
3076 if (pa_streq(ea->alsa_name, eb->alsa_name)) {
3077 if (element_is_subset(ea, eb, m)) {
3078 ea = ea->next;
3079 eb = eb->next;
3080 if ((ea && !eb) || (!ea && eb))
3081 is_subset = FALSE;
3082 else if (!ea && !eb)
3083 break;
3084 } else
3085 is_subset = FALSE;
3087 } else
3088 is_subset = FALSE;
3091 if (is_subset) {
3092 pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name);
3093 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
3094 pa_alsa_path_free(p);
3095 break;
3101 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
3102 pa_alsa_path *p, *q;
3104 PA_LLIST_FOREACH(p, ps->paths) {
3105 unsigned i;
3106 char *m;
3108 for (q = p->next; q; q = q->next)
3109 if (pa_streq(q->name, p->name))
3110 break;
3112 if (!q)
3113 continue;
3115 m = pa_xstrdup(p->name);
3117 /* OK, this name is not unique, hence let's rename */
3118 for (i = 1, q = p; q; q = q->next) {
3119 char *nn, *nd;
3121 if (!pa_streq(q->name, m))
3122 continue;
3124 nn = pa_sprintf_malloc("%s-%u", m, i);
3125 pa_xfree(q->name);
3126 q->name = nn;
3128 nd = pa_sprintf_malloc("%s %u", q->description, i);
3129 pa_xfree(q->description);
3130 q->description = nd;
3132 i++;
3135 pa_xfree(m);
3139 void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t ignore_dB) {
3140 pa_alsa_path *p, *n;
3142 pa_assert(ps);
3144 if (ps->probed)
3145 return;
3147 for (p = ps->paths; p; p = n) {
3148 n = p->next;
3150 if (pa_alsa_path_probe(p, m, ignore_dB) < 0) {
3151 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
3152 pa_alsa_path_free(p);
3156 pa_log_debug("Found mixer paths (before tidying):");
3157 pa_alsa_path_set_dump(ps);
3159 path_set_condense(ps, m);
3160 path_set_make_paths_unique(ps);
3161 ps->probed = TRUE;
3163 pa_log_debug("Available mixer paths (after tidying):");
3164 pa_alsa_path_set_dump(ps);
3167 static void mapping_free(pa_alsa_mapping *m) {
3168 pa_assert(m);
3170 pa_xfree(m->name);
3171 pa_xfree(m->description);
3173 pa_xstrfreev(m->device_strings);
3174 pa_xstrfreev(m->input_path_names);
3175 pa_xstrfreev(m->output_path_names);
3176 pa_xstrfreev(m->input_element);
3177 pa_xstrfreev(m->output_element);
3179 pa_assert(!m->input_pcm);
3180 pa_assert(!m->output_pcm);
3182 pa_xfree(m);
3185 static void profile_free(pa_alsa_profile *p) {
3186 pa_assert(p);
3188 pa_xfree(p->name);
3189 pa_xfree(p->description);
3191 pa_xstrfreev(p->input_mapping_names);
3192 pa_xstrfreev(p->output_mapping_names);
3194 if (p->input_mappings)
3195 pa_idxset_free(p->input_mappings, NULL, NULL);
3197 if (p->output_mappings)
3198 pa_idxset_free(p->output_mappings, NULL, NULL);
3200 pa_xfree(p);
3203 void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
3204 pa_assert(ps);
3206 if (ps->profiles) {
3207 pa_alsa_profile *p;
3209 while ((p = pa_hashmap_steal_first(ps->profiles)))
3210 profile_free(p);
3212 pa_hashmap_free(ps->profiles, NULL, NULL);
3215 if (ps->mappings) {
3216 pa_alsa_mapping *m;
3218 while ((m = pa_hashmap_steal_first(ps->mappings)))
3219 mapping_free(m);
3221 pa_hashmap_free(ps->mappings, NULL, NULL);
3224 if (ps->decibel_fixes) {
3225 pa_alsa_decibel_fix *db_fix;
3227 while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes)))
3228 decibel_fix_free(db_fix);
3230 pa_hashmap_free(ps->decibel_fixes, NULL, NULL);
3233 pa_xfree(ps);
3236 static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) {
3237 pa_alsa_mapping *m;
3239 if (!pa_startswith(name, "Mapping "))
3240 return NULL;
3242 name += 8;
3244 if ((m = pa_hashmap_get(ps->mappings, name)))
3245 return m;
3247 m = pa_xnew0(pa_alsa_mapping, 1);
3248 m->profile_set = ps;
3249 m->name = pa_xstrdup(name);
3250 pa_channel_map_init(&m->channel_map);
3252 pa_hashmap_put(ps->mappings, m->name, m);
3254 return m;
3257 static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
3258 pa_alsa_profile *p;
3260 if (!pa_startswith(name, "Profile "))
3261 return NULL;
3263 name += 8;
3265 if ((p = pa_hashmap_get(ps->profiles, name)))
3266 return p;
3268 p = pa_xnew0(pa_alsa_profile, 1);
3269 p->profile_set = ps;
3270 p->name = pa_xstrdup(name);
3272 pa_hashmap_put(ps->profiles, p->name, p);
3274 return p;
3277 static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
3278 pa_alsa_decibel_fix *db_fix;
3280 if (!pa_startswith(name, "DecibelFix "))
3281 return NULL;
3283 name += 11;
3285 if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
3286 return db_fix;
3288 db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
3289 db_fix->profile_set = ps;
3290 db_fix->name = pa_xstrdup(name);
3292 pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
3294 return db_fix;
3297 static int mapping_parse_device_strings(
3298 const char *filename,
3299 unsigned line,
3300 const char *section,
3301 const char *lvalue,
3302 const char *rvalue,
3303 void *data,
3304 void *userdata) {
3306 pa_alsa_profile_set *ps = userdata;
3307 pa_alsa_mapping *m;
3309 pa_assert(ps);
3311 if (!(m = mapping_get(ps, section))) {
3312 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3313 return -1;
3316 pa_xstrfreev(m->device_strings);
3317 if (!(m->device_strings = pa_split_spaces_strv(rvalue))) {
3318 pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section);
3319 return -1;
3322 return 0;
3325 static int mapping_parse_channel_map(
3326 const char *filename,
3327 unsigned line,
3328 const char *section,
3329 const char *lvalue,
3330 const char *rvalue,
3331 void *data,
3332 void *userdata) {
3334 pa_alsa_profile_set *ps = userdata;
3335 pa_alsa_mapping *m;
3337 pa_assert(ps);
3339 if (!(m = mapping_get(ps, section))) {
3340 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3341 return -1;
3344 if (!(pa_channel_map_parse(&m->channel_map, rvalue))) {
3345 pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section);
3346 return -1;
3349 return 0;
3352 static int mapping_parse_paths(
3353 const char *filename,
3354 unsigned line,
3355 const char *section,
3356 const char *lvalue,
3357 const char *rvalue,
3358 void *data,
3359 void *userdata) {
3361 pa_alsa_profile_set *ps = userdata;
3362 pa_alsa_mapping *m;
3364 pa_assert(ps);
3366 if (!(m = mapping_get(ps, section))) {
3367 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3368 return -1;
3371 if (pa_streq(lvalue, "paths-input")) {
3372 pa_xstrfreev(m->input_path_names);
3373 m->input_path_names = pa_split_spaces_strv(rvalue);
3374 } else {
3375 pa_xstrfreev(m->output_path_names);
3376 m->output_path_names = pa_split_spaces_strv(rvalue);
3379 return 0;
3382 static int mapping_parse_element(
3383 const char *filename,
3384 unsigned line,
3385 const char *section,
3386 const char *lvalue,
3387 const char *rvalue,
3388 void *data,
3389 void *userdata) {
3391 pa_alsa_profile_set *ps = userdata;
3392 pa_alsa_mapping *m;
3394 pa_assert(ps);
3396 if (!(m = mapping_get(ps, section))) {
3397 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3398 return -1;
3401 if (pa_streq(lvalue, "element-input")) {
3402 pa_xstrfreev(m->input_element);
3403 m->input_element = pa_split_spaces_strv(rvalue);
3404 } else {
3405 pa_xstrfreev(m->output_element);
3406 m->output_element = pa_split_spaces_strv(rvalue);
3409 return 0;
3412 static int mapping_parse_direction(
3413 const char *filename,
3414 unsigned line,
3415 const char *section,
3416 const char *lvalue,
3417 const char *rvalue,
3418 void *data,
3419 void *userdata) {
3421 pa_alsa_profile_set *ps = userdata;
3422 pa_alsa_mapping *m;
3424 pa_assert(ps);
3426 if (!(m = mapping_get(ps, section))) {
3427 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3428 return -1;
3431 if (pa_streq(rvalue, "input"))
3432 m->direction = PA_ALSA_DIRECTION_INPUT;
3433 else if (pa_streq(rvalue, "output"))
3434 m->direction = PA_ALSA_DIRECTION_OUTPUT;
3435 else if (pa_streq(rvalue, "any"))
3436 m->direction = PA_ALSA_DIRECTION_ANY;
3437 else {
3438 pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue);
3439 return -1;
3442 return 0;
3445 static int mapping_parse_description(
3446 const char *filename,
3447 unsigned line,
3448 const char *section,
3449 const char *lvalue,
3450 const char *rvalue,
3451 void *data,
3452 void *userdata) {
3454 pa_alsa_profile_set *ps = userdata;
3455 pa_alsa_profile *p;
3456 pa_alsa_mapping *m;
3458 pa_assert(ps);
3460 if ((m = mapping_get(ps, section))) {
3461 pa_xfree(m->description);
3462 m->description = pa_xstrdup(rvalue);
3463 } else if ((p = profile_get(ps, section))) {
3464 pa_xfree(p->description);
3465 p->description = pa_xstrdup(rvalue);
3466 } else {
3467 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3468 return -1;
3471 return 0;
3474 static int mapping_parse_priority(
3475 const char *filename,
3476 unsigned line,
3477 const char *section,
3478 const char *lvalue,
3479 const char *rvalue,
3480 void *data,
3481 void *userdata) {
3483 pa_alsa_profile_set *ps = userdata;
3484 pa_alsa_profile *p;
3485 pa_alsa_mapping *m;
3486 uint32_t prio;
3488 pa_assert(ps);
3490 if (pa_atou(rvalue, &prio) < 0) {
3491 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
3492 return -1;
3495 if ((m = mapping_get(ps, section)))
3496 m->priority = prio;
3497 else if ((p = profile_get(ps, section)))
3498 p->priority = prio;
3499 else {
3500 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3501 return -1;
3504 return 0;
3507 static int profile_parse_mappings(
3508 const char *filename,
3509 unsigned line,
3510 const char *section,
3511 const char *lvalue,
3512 const char *rvalue,
3513 void *data,
3514 void *userdata) {
3516 pa_alsa_profile_set *ps = userdata;
3517 pa_alsa_profile *p;
3519 pa_assert(ps);
3521 if (!(p = profile_get(ps, section))) {
3522 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3523 return -1;
3526 if (pa_streq(lvalue, "input-mappings")) {
3527 pa_xstrfreev(p->input_mapping_names);
3528 p->input_mapping_names = pa_split_spaces_strv(rvalue);
3529 } else {
3530 pa_xstrfreev(p->output_mapping_names);
3531 p->output_mapping_names = pa_split_spaces_strv(rvalue);
3534 return 0;
3537 static int profile_parse_skip_probe(
3538 const char *filename,
3539 unsigned line,
3540 const char *section,
3541 const char *lvalue,
3542 const char *rvalue,
3543 void *data,
3544 void *userdata) {
3546 pa_alsa_profile_set *ps = userdata;
3547 pa_alsa_profile *p;
3548 int b;
3550 pa_assert(ps);
3552 if (!(p = profile_get(ps, section))) {
3553 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3554 return -1;
3557 if ((b = pa_parse_boolean(rvalue)) < 0) {
3558 pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section);
3559 return -1;
3562 p->supported = b;
3564 return 0;
3567 static int decibel_fix_parse_db_values(
3568 const char *filename,
3569 unsigned line,
3570 const char *section,
3571 const char *lvalue,
3572 const char *rvalue,
3573 void *data,
3574 void *userdata) {
3576 pa_alsa_profile_set *ps = userdata;
3577 pa_alsa_decibel_fix *db_fix;
3578 char **items;
3579 char *item;
3580 long *db_values;
3581 unsigned n = 8; /* Current size of the db_values table. */
3582 unsigned min_step = 0;
3583 unsigned max_step = 0;
3584 unsigned i = 0; /* Index to the items table. */
3585 unsigned prev_step = 0;
3586 double prev_db = 0;
3588 pa_assert(filename);
3589 pa_assert(section);
3590 pa_assert(lvalue);
3591 pa_assert(rvalue);
3592 pa_assert(ps);
3594 if (!(db_fix = decibel_fix_get(ps, section))) {
3595 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3596 return -1;
3599 if (!(items = pa_split_spaces_strv(rvalue))) {
3600 pa_log("[%s:%u] Value missing", pa_strnull(filename), line);
3601 return -1;
3604 db_values = pa_xnew(long, n);
3606 while ((item = items[i++])) {
3607 char *s = item; /* Step value string. */
3608 char *d = item; /* dB value string. */
3609 uint32_t step;
3610 double db;
3612 /* Move d forward until it points to a colon or to the end of the item. */
3613 for (; *d && *d != ':'; ++d);
3615 if (d == s) {
3616 /* item started with colon. */
3617 pa_log("[%s:%u] No step value found in %s", filename, line, item);
3618 goto fail;
3621 if (!*d || !*(d + 1)) {
3622 /* No colon found, or it was the last character in item. */
3623 pa_log("[%s:%u] No dB value found in %s", filename, line, item);
3624 goto fail;
3627 /* pa_atou() needs a null-terminating string. Let's replace the colon
3628 * with a zero byte. */
3629 *d++ = '\0';
3631 if (pa_atou(s, &step) < 0) {
3632 pa_log("[%s:%u] Invalid step value: %s", filename, line, s);
3633 goto fail;
3636 if (pa_atod(d, &db) < 0) {
3637 pa_log("[%s:%u] Invalid dB value: %s", filename, line, d);
3638 goto fail;
3641 if (step <= prev_step && i != 1) {
3642 pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step);
3643 goto fail;
3646 if (db < prev_db && i != 1) {
3647 pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db);
3648 goto fail;
3651 if (i == 1) {
3652 min_step = step;
3653 db_values[0] = (long) (db * 100.0);
3654 prev_step = step;
3655 prev_db = db;
3656 } else {
3657 /* Interpolate linearly. */
3658 double db_increment = (db - prev_db) / (step - prev_step);
3660 for (; prev_step < step; ++prev_step, prev_db += db_increment) {
3662 /* Reallocate the db_values table if it's about to overflow. */
3663 if (prev_step + 1 - min_step == n) {
3664 n *= 2;
3665 db_values = pa_xrenew(long, db_values, n);
3668 db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0);
3672 max_step = step;
3675 db_fix->min_step = min_step;
3676 db_fix->max_step = max_step;
3677 pa_xfree(db_fix->db_values);
3678 db_fix->db_values = db_values;
3680 pa_xstrfreev(items);
3682 return 0;
3684 fail:
3685 pa_xstrfreev(items);
3686 pa_xfree(db_values);
3688 return -1;
3691 static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
3693 static const struct description_map well_known_descriptions[] = {
3694 { "analog-mono", N_("Analog Mono") },
3695 { "analog-stereo", N_("Analog Stereo") },
3696 { "analog-surround-21", N_("Analog Surround 2.1") },
3697 { "analog-surround-30", N_("Analog Surround 3.0") },
3698 { "analog-surround-31", N_("Analog Surround 3.1") },
3699 { "analog-surround-40", N_("Analog Surround 4.0") },
3700 { "analog-surround-41", N_("Analog Surround 4.1") },
3701 { "analog-surround-50", N_("Analog Surround 5.0") },
3702 { "analog-surround-51", N_("Analog Surround 5.1") },
3703 { "analog-surround-61", N_("Analog Surround 6.0") },
3704 { "analog-surround-61", N_("Analog Surround 6.1") },
3705 { "analog-surround-70", N_("Analog Surround 7.0") },
3706 { "analog-surround-71", N_("Analog Surround 7.1") },
3707 { "iec958-stereo", N_("Digital Stereo (IEC958)") },
3708 { "iec958-passthrough", N_("Digital Passthrough (IEC958)") },
3709 { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") },
3710 { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") },
3711 { "hdmi-stereo", N_("Digital Stereo (HDMI)") }
3714 pa_assert(m);
3716 if (!pa_channel_map_valid(&m->channel_map)) {
3717 pa_log("Mapping %s is missing channel map.", m->name);
3718 return -1;
3721 if (!m->device_strings) {
3722 pa_log("Mapping %s is missing device strings.", m->name);
3723 return -1;
3726 if ((m->input_path_names && m->input_element) ||
3727 (m->output_path_names && m->output_element)) {
3728 pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name);
3729 return -1;
3732 if (!m->description)
3733 m->description = pa_xstrdup(lookup_description(m->name,
3734 well_known_descriptions,
3735 PA_ELEMENTSOF(well_known_descriptions)));
3737 if (!m->description)
3738 m->description = pa_xstrdup(m->name);
3740 if (bonus) {
3741 if (pa_channel_map_equal(&m->channel_map, bonus))
3742 m->priority += 50;
3743 else if (m->channel_map.channels == bonus->channels)
3744 m->priority += 30;
3747 return 0;
3750 void pa_alsa_mapping_dump(pa_alsa_mapping *m) {
3751 char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
3753 pa_assert(m);
3755 pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i",
3756 m->name,
3757 pa_strnull(m->description),
3758 m->priority,
3759 pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map),
3760 pa_yes_no(m->supported),
3761 m->direction);
3764 static void profile_set_add_auto_pair(
3765 pa_alsa_profile_set *ps,
3766 pa_alsa_mapping *m, /* output */
3767 pa_alsa_mapping *n /* input */) {
3769 char *name;
3770 pa_alsa_profile *p;
3772 pa_assert(ps);
3773 pa_assert(m || n);
3775 if (m && m->direction == PA_ALSA_DIRECTION_INPUT)
3776 return;
3778 if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT)
3779 return;
3781 if (m && n)
3782 name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name);
3783 else if (m)
3784 name = pa_sprintf_malloc("output:%s", m->name);
3785 else
3786 name = pa_sprintf_malloc("input:%s", n->name);
3788 if (pa_hashmap_get(ps->profiles, name)) {
3789 pa_xfree(name);
3790 return;
3793 p = pa_xnew0(pa_alsa_profile, 1);
3794 p->profile_set = ps;
3795 p->name = name;
3797 if (m) {
3798 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3799 pa_idxset_put(p->output_mappings, m, NULL);
3800 p->priority += m->priority * 100;
3803 if (n) {
3804 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3805 pa_idxset_put(p->input_mappings, n, NULL);
3806 p->priority += n->priority;
3809 pa_hashmap_put(ps->profiles, p->name, p);
3812 static void profile_set_add_auto(pa_alsa_profile_set *ps) {
3813 pa_alsa_mapping *m, *n;
3814 void *m_state, *n_state;
3816 pa_assert(ps);
3818 PA_HASHMAP_FOREACH(m, ps->mappings, m_state) {
3819 profile_set_add_auto_pair(ps, m, NULL);
3821 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
3822 profile_set_add_auto_pair(ps, m, n);
3825 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
3826 profile_set_add_auto_pair(ps, NULL, n);
3829 static int profile_verify(pa_alsa_profile *p) {
3831 static const struct description_map well_known_descriptions[] = {
3832 { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") },
3833 { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") },
3834 { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") },
3835 { "off", N_("Off") }
3838 pa_assert(p);
3840 /* Replace the output mapping names by the actual mappings */
3841 if (p->output_mapping_names) {
3842 char **name;
3844 pa_assert(!p->output_mappings);
3845 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3847 for (name = p->output_mapping_names; *name; name++) {
3848 pa_alsa_mapping *m;
3849 char **in;
3850 pa_bool_t duplicate = FALSE;
3852 for (in = name + 1; *in; in++)
3853 if (pa_streq(*name, *in)) {
3854 duplicate = TRUE;
3855 break;
3858 if (duplicate)
3859 continue;
3861 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) {
3862 pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name);
3863 return -1;
3866 pa_idxset_put(p->output_mappings, m, NULL);
3868 if (p->supported)
3869 m->supported++;
3872 pa_xstrfreev(p->output_mapping_names);
3873 p->output_mapping_names = NULL;
3876 /* Replace the input mapping names by the actual mappings */
3877 if (p->input_mapping_names) {
3878 char **name;
3880 pa_assert(!p->input_mappings);
3881 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3883 for (name = p->input_mapping_names; *name; name++) {
3884 pa_alsa_mapping *m;
3885 char **in;
3886 pa_bool_t duplicate = FALSE;
3888 for (in = name + 1; *in; in++)
3889 if (pa_streq(*name, *in)) {
3890 duplicate = TRUE;
3891 break;
3894 if (duplicate)
3895 continue;
3897 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) {
3898 pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name);
3899 return -1;
3902 pa_idxset_put(p->input_mappings, m, NULL);
3904 if (p->supported)
3905 m->supported++;
3908 pa_xstrfreev(p->input_mapping_names);
3909 p->input_mapping_names = NULL;
3912 if (!p->input_mappings && !p->output_mappings) {
3913 pa_log("Profile '%s' lacks mappings.", p->name);
3914 return -1;
3917 if (!p->description)
3918 p->description = pa_xstrdup(lookup_description(p->name,
3919 well_known_descriptions,
3920 PA_ELEMENTSOF(well_known_descriptions)));
3922 if (!p->description) {
3923 pa_strbuf *sb;
3924 uint32_t idx;
3925 pa_alsa_mapping *m;
3927 sb = pa_strbuf_new();
3929 if (p->output_mappings)
3930 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
3931 if (!pa_strbuf_isempty(sb))
3932 pa_strbuf_puts(sb, " + ");
3934 pa_strbuf_printf(sb, _("%s Output"), m->description);
3937 if (p->input_mappings)
3938 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
3939 if (!pa_strbuf_isempty(sb))
3940 pa_strbuf_puts(sb, " + ");
3942 pa_strbuf_printf(sb, _("%s Input"), m->description);
3945 p->description = pa_strbuf_tostring_free(sb);
3948 return 0;
3951 void pa_alsa_profile_dump(pa_alsa_profile *p) {
3952 uint32_t idx;
3953 pa_alsa_mapping *m;
3954 pa_assert(p);
3956 pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u",
3957 p->name,
3958 pa_strnull(p->description),
3959 p->priority,
3960 pa_yes_no(p->supported),
3961 p->input_mappings ? pa_idxset_size(p->input_mappings) : 0,
3962 p->output_mappings ? pa_idxset_size(p->output_mappings) : 0);
3964 if (p->input_mappings)
3965 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
3966 pa_log_debug("Input %s", m->name);
3968 if (p->output_mappings)
3969 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
3970 pa_log_debug("Output %s", m->name);
3973 static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) {
3974 pa_assert(db_fix);
3976 /* Check that the dB mapping has been configured. Since "db-values" is
3977 * currently the only option in the DecibelFix section, and decibel fix
3978 * objects don't get created if a DecibelFix section is empty, this is
3979 * actually a redundant check. Having this may prevent future bugs,
3980 * however. */
3981 if (!db_fix->db_values) {
3982 pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name);
3983 return -1;
3986 return 0;
3989 void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) {
3990 char *db_values = NULL;
3992 pa_assert(db_fix);
3994 if (db_fix->db_values) {
3995 pa_strbuf *buf;
3996 unsigned long i, nsteps;
3998 pa_assert(db_fix->min_step <= db_fix->max_step);
3999 nsteps = db_fix->max_step - db_fix->min_step + 1;
4001 buf = pa_strbuf_new();
4002 for (i = 0; i < nsteps; ++i)
4003 pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0);
4005 db_values = pa_strbuf_tostring_free(buf);
4008 pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s",
4009 db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values));
4011 pa_xfree(db_values);
4014 pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) {
4015 pa_alsa_profile_set *ps;
4016 pa_alsa_profile *p;
4017 pa_alsa_mapping *m;
4018 pa_alsa_decibel_fix *db_fix;
4019 char *fn;
4020 int r;
4021 void *state;
4023 static pa_config_item items[] = {
4024 /* [General] */
4025 { "auto-profiles", pa_config_parse_bool, NULL, "General" },
4027 /* [Mapping ...] */
4028 { "device-strings", mapping_parse_device_strings, NULL, NULL },
4029 { "channel-map", mapping_parse_channel_map, NULL, NULL },
4030 { "paths-input", mapping_parse_paths, NULL, NULL },
4031 { "paths-output", mapping_parse_paths, NULL, NULL },
4032 { "element-input", mapping_parse_element, NULL, NULL },
4033 { "element-output", mapping_parse_element, NULL, NULL },
4034 { "direction", mapping_parse_direction, NULL, NULL },
4036 /* Shared by [Mapping ...] and [Profile ...] */
4037 { "description", mapping_parse_description, NULL, NULL },
4038 { "priority", mapping_parse_priority, NULL, NULL },
4040 /* [Profile ...] */
4041 { "input-mappings", profile_parse_mappings, NULL, NULL },
4042 { "output-mappings", profile_parse_mappings, NULL, NULL },
4043 { "skip-probe", profile_parse_skip_probe, NULL, NULL },
4045 /* [DecibelFix ...] */
4046 { "db-values", decibel_fix_parse_db_values, NULL, NULL },
4047 { NULL, NULL, NULL, NULL }
4050 ps = pa_xnew0(pa_alsa_profile_set, 1);
4051 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4052 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4053 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4055 items[0].data = &ps->auto_profiles;
4057 if (!fname)
4058 fname = "default.conf";
4060 fn = pa_maybe_prefix_path(fname,
4061 pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
4062 PA_ALSA_PROFILE_SETS_DIR);
4064 r = pa_config_parse(fn, NULL, items, ps);
4065 pa_xfree(fn);
4067 if (r < 0)
4068 goto fail;
4070 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4071 if (mapping_verify(m, bonus) < 0)
4072 goto fail;
4074 if (ps->auto_profiles)
4075 profile_set_add_auto(ps);
4077 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4078 if (profile_verify(p) < 0)
4079 goto fail;
4081 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
4082 if (decibel_fix_verify(db_fix) < 0)
4083 goto fail;
4085 return ps;
4087 fail:
4088 pa_alsa_profile_set_free(ps);
4089 return NULL;
4092 void pa_alsa_profile_set_probe(
4093 pa_alsa_profile_set *ps,
4094 const char *dev_id,
4095 const pa_sample_spec *ss,
4096 unsigned default_n_fragments,
4097 unsigned default_fragment_size_msec) {
4099 void *state;
4100 pa_alsa_profile *p, *last = NULL;
4101 pa_alsa_mapping *m;
4103 pa_assert(ps);
4104 pa_assert(dev_id);
4105 pa_assert(ss);
4107 if (ps->probed)
4108 return;
4110 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
4111 pa_sample_spec try_ss;
4112 pa_channel_map try_map;
4113 snd_pcm_uframes_t try_period_size, try_buffer_size;
4114 uint32_t idx;
4116 /* Is this already marked that it is supported? (i.e. from the config file) */
4117 if (p->supported)
4118 continue;
4120 pa_log_debug("Looking at profile %s", p->name);
4122 /* Close PCMs from the last iteration we don't need anymore */
4123 if (last && last->output_mappings)
4124 PA_IDXSET_FOREACH(m, last->output_mappings, idx) {
4126 if (!m->output_pcm)
4127 break;
4129 if (last->supported)
4130 m->supported++;
4132 if (!p->output_mappings || !pa_idxset_get_by_data(p->output_mappings, m, NULL)) {
4133 snd_pcm_close(m->output_pcm);
4134 m->output_pcm = NULL;
4138 if (last && last->input_mappings)
4139 PA_IDXSET_FOREACH(m, last->input_mappings, idx) {
4141 if (!m->input_pcm)
4142 break;
4144 if (last->supported)
4145 m->supported++;
4147 if (!p->input_mappings || !pa_idxset_get_by_data(p->input_mappings, m, NULL)) {
4148 snd_pcm_close(m->input_pcm);
4149 m->input_pcm = NULL;
4153 p->supported = TRUE;
4155 /* Check if we can open all new ones */
4156 if (p->output_mappings)
4157 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
4159 if (m->output_pcm)
4160 continue;
4162 pa_log_debug("Checking for playback on %s (%s)", m->description, m->name);
4163 try_map = m->channel_map;
4164 try_ss = *ss;
4165 try_ss.channels = try_map.channels;
4167 try_period_size =
4168 pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
4169 pa_frame_size(&try_ss);
4170 try_buffer_size = default_n_fragments * try_period_size;
4172 if (!(m ->output_pcm = pa_alsa_open_by_template(
4173 m->device_strings,
4174 dev_id,
4175 NULL,
4176 &try_ss, &try_map,
4177 SND_PCM_STREAM_PLAYBACK,
4178 &try_period_size, &try_buffer_size, 0, NULL, NULL,
4179 TRUE))) {
4180 p->supported = FALSE;
4181 break;
4185 if (p->input_mappings && p->supported)
4186 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
4188 if (m->input_pcm)
4189 continue;
4191 pa_log_debug("Checking for recording on %s (%s)", m->description, m->name);
4192 try_map = m->channel_map;
4193 try_ss = *ss;
4194 try_ss.channels = try_map.channels;
4196 try_period_size =
4197 pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) /
4198 pa_frame_size(&try_ss);
4199 try_buffer_size = default_n_fragments * try_period_size;
4201 if (!(m ->input_pcm = pa_alsa_open_by_template(
4202 m->device_strings,
4203 dev_id,
4204 NULL,
4205 &try_ss, &try_map,
4206 SND_PCM_STREAM_CAPTURE,
4207 &try_period_size, &try_buffer_size, 0, NULL, NULL,
4208 TRUE))) {
4209 p->supported = FALSE;
4210 break;
4214 last = p;
4216 if (p->supported)
4217 pa_log_debug("Profile %s supported.", p->name);
4220 /* Clean up */
4221 if (last) {
4222 uint32_t idx;
4224 if (last->output_mappings)
4225 PA_IDXSET_FOREACH(m, last->output_mappings, idx)
4226 if (m->output_pcm) {
4228 if (last->supported)
4229 m->supported++;
4231 snd_pcm_close(m->output_pcm);
4232 m->output_pcm = NULL;
4235 if (last->input_mappings)
4236 PA_IDXSET_FOREACH(m, last->input_mappings, idx)
4237 if (m->input_pcm) {
4239 if (last->supported)
4240 m->supported++;
4242 snd_pcm_close(m->input_pcm);
4243 m->input_pcm = NULL;
4247 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4248 if (!p->supported) {
4249 pa_hashmap_remove(ps->profiles, p->name);
4250 profile_free(p);
4253 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4254 if (m->supported <= 0) {
4255 pa_hashmap_remove(ps->mappings, m->name);
4256 mapping_free(m);
4259 ps->probed = TRUE;
4262 void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
4263 pa_alsa_profile *p;
4264 pa_alsa_mapping *m;
4265 pa_alsa_decibel_fix *db_fix;
4266 void *state;
4268 pa_assert(ps);
4270 pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u",
4271 (void*)
4273 pa_yes_no(ps->auto_profiles),
4274 pa_yes_no(ps->probed),
4275 pa_hashmap_size(ps->mappings),
4276 pa_hashmap_size(ps->profiles),
4277 pa_hashmap_size(ps->decibel_fixes));
4279 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4280 pa_alsa_mapping_dump(m);
4282 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4283 pa_alsa_profile_dump(p);
4285 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
4286 pa_alsa_decibel_fix_dump(db_fix);
4289 void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) {
4290 pa_alsa_path *path;
4292 pa_assert(p);
4293 pa_assert(!*p);
4294 pa_assert(ps);
4296 /* if there is no path, we don't want a port list */
4297 if (!ps->paths)
4298 return;
4300 if (!ps->paths->next){
4301 pa_alsa_setting *s;
4303 /* If there is only one path, but no or only one setting, then
4304 * we want a port list either */
4305 if (!ps->paths->settings || !ps->paths->settings->next)
4306 return;
4308 /* Ok, there is only one path, however with multiple settings,
4309 * so let's create a port for each setting */
4310 *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4312 PA_LLIST_FOREACH(s, ps->paths->settings) {
4313 pa_device_port *port;
4314 pa_alsa_port_data *data;
4316 port = pa_device_port_new(s->name, s->description, sizeof(pa_alsa_port_data));
4317 port->priority = s->priority;
4319 data = PA_DEVICE_PORT_DATA(port);
4320 data->path = ps->paths;
4321 data->setting = s;
4323 pa_hashmap_put(*p, port->name, port);
4326 } else {
4328 /* We have multiple paths, so let's create a port for each
4329 * one, and each of each settings */
4330 *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4332 PA_LLIST_FOREACH(path, ps->paths) {
4334 if (!path->settings || !path->settings->next) {
4335 pa_device_port *port;
4336 pa_alsa_port_data *data;
4338 /* If there is no or just one setting we only need a
4339 * single entry */
4341 port = pa_device_port_new(path->name, path->description, sizeof(pa_alsa_port_data));
4342 port->priority = path->priority * 100;
4345 data = PA_DEVICE_PORT_DATA(port);
4346 data->path = path;
4347 data->setting = path->settings;
4349 pa_hashmap_put(*p, port->name, port);
4350 } else {
4351 pa_alsa_setting *s;
4353 PA_LLIST_FOREACH(s, path->settings) {
4354 pa_device_port *port;
4355 pa_alsa_port_data *data;
4356 char *n, *d;
4358 n = pa_sprintf_malloc("%s;%s", path->name, s->name);
4360 if (s->description[0])
4361 d = pa_sprintf_malloc(_("%s / %s"), path->description, s->description);
4362 else
4363 d = pa_xstrdup(path->description);
4365 port = pa_device_port_new(n, d, sizeof(pa_alsa_port_data));
4366 port->priority = path->priority * 100 + s->priority;
4368 pa_xfree(n);
4369 pa_xfree(d);
4371 data = PA_DEVICE_PORT_DATA(port);
4372 data->path = path;
4373 data->setting = s;
4375 pa_hashmap_put(*p, port->name, port);
4381 pa_log_debug("Added %u ports", pa_hashmap_size(*p));