alsa-mixer: select nearest alsa volume step in sync-volume mode
[pulseaudio-mirror.git] / src / modules / alsa / alsa-mixer.c
blobdada1223108f9bfba19c94bca611c799d5698935
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 <limits.h>
29 #include <asoundlib.h>
31 #ifdef HAVE_VALGRIND_MEMCHECK_H
32 #include <valgrind/memcheck.h>
33 #endif
35 #include <pulse/sample.h>
36 #include <pulse/xmalloc.h>
37 #include <pulse/timeval.h>
38 #include <pulse/util.h>
39 #include <pulse/i18n.h>
40 #include <pulse/utf8.h>
42 #include <pulsecore/log.h>
43 #include <pulsecore/macro.h>
44 #include <pulsecore/core-util.h>
45 #include <pulsecore/atomic.h>
46 #include <pulsecore/core-error.h>
47 #include <pulsecore/once.h>
48 #include <pulsecore/thread.h>
49 #include <pulsecore/conf-parser.h>
50 #include <pulsecore/strbuf.h>
52 #include "alsa-mixer.h"
53 #include "alsa-util.h"
55 struct description_map {
56 const char *name;
57 const char *description;
60 static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) {
61 unsigned i;
63 for (i = 0; i < n; i++)
64 if (pa_streq(dm[i].name, name))
65 return _(dm[i].description);
67 return NULL;
70 struct pa_alsa_fdlist {
71 unsigned num_fds;
72 struct pollfd *fds;
73 /* This is a temporary buffer used to avoid lots of mallocs */
74 struct pollfd *work_fds;
76 snd_mixer_t *mixer;
78 pa_mainloop_api *m;
79 pa_defer_event *defer;
80 pa_io_event **ios;
82 pa_bool_t polled;
84 void (*cb)(void *userdata);
85 void *userdata;
88 static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
90 struct pa_alsa_fdlist *fdl = userdata;
91 int err;
92 unsigned i;
93 unsigned short revents;
95 pa_assert(a);
96 pa_assert(fdl);
97 pa_assert(fdl->mixer);
98 pa_assert(fdl->fds);
99 pa_assert(fdl->work_fds);
101 if (fdl->polled)
102 return;
104 fdl->polled = TRUE;
106 memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
108 for (i = 0; i < fdl->num_fds; i++) {
109 if (e == fdl->ios[i]) {
110 if (events & PA_IO_EVENT_INPUT)
111 fdl->work_fds[i].revents |= POLLIN;
112 if (events & PA_IO_EVENT_OUTPUT)
113 fdl->work_fds[i].revents |= POLLOUT;
114 if (events & PA_IO_EVENT_ERROR)
115 fdl->work_fds[i].revents |= POLLERR;
116 if (events & PA_IO_EVENT_HANGUP)
117 fdl->work_fds[i].revents |= POLLHUP;
118 break;
122 pa_assert(i != fdl->num_fds);
124 if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
125 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
126 return;
129 a->defer_enable(fdl->defer, 1);
131 if (revents)
132 snd_mixer_handle_events(fdl->mixer);
135 static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
136 struct pa_alsa_fdlist *fdl = userdata;
137 unsigned num_fds, i;
138 int err, n;
139 struct pollfd *temp;
141 pa_assert(a);
142 pa_assert(fdl);
143 pa_assert(fdl->mixer);
145 a->defer_enable(fdl->defer, 0);
147 if ((n = snd_mixer_poll_descriptors_count(fdl->mixer)) < 0) {
148 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
149 return;
151 num_fds = (unsigned) n;
153 if (num_fds != fdl->num_fds) {
154 if (fdl->fds)
155 pa_xfree(fdl->fds);
156 if (fdl->work_fds)
157 pa_xfree(fdl->work_fds);
158 fdl->fds = pa_xnew0(struct pollfd, num_fds);
159 fdl->work_fds = pa_xnew(struct pollfd, num_fds);
162 memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
164 if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
165 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
166 return;
169 fdl->polled = FALSE;
171 if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0)
172 return;
174 if (fdl->ios) {
175 for (i = 0; i < fdl->num_fds; i++)
176 a->io_free(fdl->ios[i]);
178 if (num_fds != fdl->num_fds) {
179 pa_xfree(fdl->ios);
180 fdl->ios = NULL;
184 if (!fdl->ios)
185 fdl->ios = pa_xnew(pa_io_event*, num_fds);
187 /* Swap pointers */
188 temp = fdl->work_fds;
189 fdl->work_fds = fdl->fds;
190 fdl->fds = temp;
192 fdl->num_fds = num_fds;
194 for (i = 0;i < num_fds;i++)
195 fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
196 ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
197 ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
198 io_cb, fdl);
201 struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
202 struct pa_alsa_fdlist *fdl;
204 fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
206 return fdl;
209 void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
210 pa_assert(fdl);
212 if (fdl->defer) {
213 pa_assert(fdl->m);
214 fdl->m->defer_free(fdl->defer);
217 if (fdl->ios) {
218 unsigned i;
219 pa_assert(fdl->m);
220 for (i = 0; i < fdl->num_fds; i++)
221 fdl->m->io_free(fdl->ios[i]);
222 pa_xfree(fdl->ios);
225 if (fdl->fds)
226 pa_xfree(fdl->fds);
227 if (fdl->work_fds)
228 pa_xfree(fdl->work_fds);
230 pa_xfree(fdl);
233 int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api *m) {
234 pa_assert(fdl);
235 pa_assert(mixer_handle);
236 pa_assert(m);
237 pa_assert(!fdl->m);
239 fdl->mixer = mixer_handle;
240 fdl->m = m;
241 fdl->defer = m->defer_new(m, defer_cb, fdl);
243 return 0;
246 struct pa_alsa_mixer_pdata {
247 pa_rtpoll *rtpoll;
248 pa_rtpoll_item *poll_item;
249 snd_mixer_t *mixer;
253 struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) {
254 struct pa_alsa_mixer_pdata *pd;
256 pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1);
258 return pd;
261 void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) {
262 pa_assert(pd);
264 if (pd->poll_item) {
265 pa_rtpoll_item_free(pd->poll_item);
268 pa_xfree(pd);
271 static int rtpoll_work_cb(pa_rtpoll_item *i) {
272 struct pa_alsa_mixer_pdata *pd;
273 struct pollfd *p;
274 unsigned n_fds;
275 unsigned short revents = 0;
276 int err;
278 pd = pa_rtpoll_item_get_userdata(i);
279 pa_assert_fp(pd);
280 pa_assert_fp(i == pd->poll_item);
282 p = pa_rtpoll_item_get_pollfd(i, &n_fds);
284 if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) {
285 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
286 pa_rtpoll_item_free(i);
287 return -1;
290 if (revents) {
291 snd_mixer_handle_events(pd->mixer);
292 pa_rtpoll_item_free(i);
293 pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll);
296 return 0;
299 int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) {
300 pa_rtpoll_item *i;
301 struct pollfd *p;
302 int err, n;
304 pa_assert(pd);
305 pa_assert(mixer);
306 pa_assert(rtp);
308 if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) {
309 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
310 return -1;
313 i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n);
315 p = pa_rtpoll_item_get_pollfd(i, NULL);
317 memset(p, 0, sizeof(struct pollfd) * n);
319 if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) {
320 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
321 pa_rtpoll_item_free(i);
322 return -1;
325 pd->rtpoll = rtp;
326 pd->poll_item = i;
327 pd->mixer = mixer;
329 pa_rtpoll_item_set_userdata(i, pd);
330 pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
332 return 0;
335 static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
336 int err;
338 pa_assert(mixer);
339 pa_assert(dev);
341 if ((err = snd_mixer_attach(mixer, dev)) < 0) {
342 pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err));
343 return -1;
346 if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
347 pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err));
348 return -1;
351 if ((err = snd_mixer_load(mixer)) < 0) {
352 pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err));
353 return -1;
356 pa_log_info("Successfully attached to mixer '%s'", dev);
357 return 0;
360 snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) {
361 int err;
362 snd_mixer_t *m;
363 const char *dev;
364 snd_pcm_info_t* info;
365 snd_pcm_info_alloca(&info);
367 pa_assert(pcm);
369 if ((err = snd_mixer_open(&m, 0)) < 0) {
370 pa_log("Error opening mixer: %s", pa_alsa_strerror(err));
371 return NULL;
374 /* First, try by name */
375 if ((dev = snd_pcm_name(pcm)))
376 if (prepare_mixer(m, dev) >= 0) {
377 if (ctl_device)
378 *ctl_device = pa_xstrdup(dev);
380 return m;
383 /* Then, try by card index */
384 if (snd_pcm_info(pcm, info) >= 0) {
385 char *md;
386 int card_idx;
388 if ((card_idx = snd_pcm_info_get_card(info)) >= 0) {
390 md = pa_sprintf_malloc("hw:%i", card_idx);
392 if (!dev || !pa_streq(dev, md))
393 if (prepare_mixer(m, md) >= 0) {
395 if (ctl_device)
396 *ctl_device = md;
397 else
398 pa_xfree(md);
400 return m;
403 pa_xfree(md);
407 snd_mixer_close(m);
408 return NULL;
411 static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
412 [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
414 [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER,
415 [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT,
416 [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT,
418 [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER,
419 [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT,
420 [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT,
422 [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER,
424 [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
425 [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
427 [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT,
428 [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT,
430 [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN,
431 [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN,
432 [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN,
433 [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN,
434 [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN,
435 [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN,
436 [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN,
437 [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN,
438 [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN,
439 [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN,
440 [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN,
441 [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN,
442 [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN,
443 [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN,
444 [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN,
445 [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN,
446 [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN,
447 [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN,
448 [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN,
449 [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN,
450 [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN,
451 [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN,
452 [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN,
453 [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN,
454 [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN,
455 [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN,
456 [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN,
457 [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN,
458 [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN,
459 [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN,
460 [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN,
461 [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN,
463 [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN,
465 [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN,
466 [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN,
467 [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN,
469 [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN,
470 [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN,
471 [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
474 static void setting_free(pa_alsa_setting *s) {
475 pa_assert(s);
477 if (s->options)
478 pa_idxset_free(s->options, NULL, NULL);
480 pa_xfree(s->name);
481 pa_xfree(s->description);
482 pa_xfree(s);
485 static void option_free(pa_alsa_option *o) {
486 pa_assert(o);
488 pa_xfree(o->alsa_name);
489 pa_xfree(o->name);
490 pa_xfree(o->description);
491 pa_xfree(o);
494 static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
495 pa_assert(db_fix);
497 pa_xfree(db_fix->name);
498 pa_xfree(db_fix->db_values);
500 pa_xfree(db_fix);
503 static void element_free(pa_alsa_element *e) {
504 pa_alsa_option *o;
505 pa_assert(e);
507 while ((o = e->options)) {
508 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
509 option_free(o);
512 if (e->db_fix)
513 decibel_fix_free(e->db_fix);
515 pa_xfree(e->alsa_name);
516 pa_xfree(e);
519 void pa_alsa_path_free(pa_alsa_path *p) {
520 pa_alsa_element *e;
521 pa_alsa_setting *s;
523 pa_assert(p);
525 while ((e = p->elements)) {
526 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
527 element_free(e);
530 while ((s = p->settings)) {
531 PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s);
532 setting_free(s);
535 pa_xfree(p->name);
536 pa_xfree(p->description);
537 pa_xfree(p);
540 void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
541 pa_alsa_path *p;
542 pa_assert(ps);
544 while ((p = ps->paths)) {
545 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
546 pa_alsa_path_free(p);
549 pa_xfree(ps);
552 static long to_alsa_dB(pa_volume_t v) {
553 return (long) (pa_sw_volume_to_dB(v) * 100.0);
556 static pa_volume_t from_alsa_dB(long v) {
557 return pa_sw_volume_from_dB((double) v / 100.0);
560 static long to_alsa_volume(pa_volume_t v, long min, long max) {
561 long w;
563 w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min;
564 return PA_CLAMP_UNLIKELY(w, min, max);
567 static pa_volume_t from_alsa_volume(long v, long min, long max) {
568 return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
571 #define SELEM_INIT(sid, name) \
572 do { \
573 snd_mixer_selem_id_alloca(&(sid)); \
574 snd_mixer_selem_id_set_name((sid), (name)); \
575 snd_mixer_selem_id_set_index((sid), 0); \
576 } while(FALSE)
578 static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
579 snd_mixer_selem_id_t *sid;
580 snd_mixer_elem_t *me;
581 snd_mixer_selem_channel_id_t c;
582 pa_channel_position_mask_t mask = 0;
583 unsigned k;
585 pa_assert(m);
586 pa_assert(e);
587 pa_assert(cm);
588 pa_assert(v);
590 SELEM_INIT(sid, e->alsa_name);
591 if (!(me = snd_mixer_find_selem(m, sid))) {
592 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
593 return -1;
596 pa_cvolume_mute(v, cm->channels);
598 /* We take the highest volume of all channels that match */
600 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
601 int r;
602 pa_volume_t f;
604 if (e->has_dB) {
605 long value = 0;
607 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
608 if (snd_mixer_selem_has_playback_channel(me, c)) {
609 if (e->db_fix) {
610 if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) {
611 /* If the channel volume is outside the limits set
612 * by the dB fix, we clamp the hw volume to be
613 * within the limits. */
614 if (value < e->db_fix->min_step) {
615 value = e->db_fix->min_step;
616 snd_mixer_selem_set_playback_volume(me, c, value);
617 pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
618 "Volume reset to %0.2f dB.", e->alsa_name, c,
619 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
620 } else if (value > e->db_fix->max_step) {
621 value = e->db_fix->max_step;
622 snd_mixer_selem_set_playback_volume(me, c, value);
623 pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
624 "Volume reset to %0.2f dB.", e->alsa_name, c,
625 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
628 /* Volume step -> dB value conversion. */
629 value = e->db_fix->db_values[value - e->db_fix->min_step];
631 } else
632 r = snd_mixer_selem_get_playback_dB(me, c, &value);
633 } else
634 r = -1;
635 } else {
636 if (snd_mixer_selem_has_capture_channel(me, c)) {
637 if (e->db_fix) {
638 if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) {
639 /* If the channel volume is outside the limits set
640 * by the dB fix, we clamp the hw volume to be
641 * within the limits. */
642 if (value < e->db_fix->min_step) {
643 value = e->db_fix->min_step;
644 snd_mixer_selem_set_capture_volume(me, c, value);
645 pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
646 "Volume reset to %0.2f dB.", e->alsa_name, c,
647 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
648 } else if (value > e->db_fix->max_step) {
649 value = e->db_fix->max_step;
650 snd_mixer_selem_set_capture_volume(me, c, value);
651 pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
652 "Volume reset to %0.2f dB.", e->alsa_name, c,
653 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
656 /* Volume step -> dB value conversion. */
657 value = e->db_fix->db_values[value - e->db_fix->min_step];
659 } else
660 r = snd_mixer_selem_get_capture_dB(me, c, &value);
661 } else
662 r = -1;
665 if (r < 0)
666 continue;
668 #ifdef HAVE_VALGRIND_MEMCHECK_H
669 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
670 #endif
672 f = from_alsa_dB(value);
674 } else {
675 long value = 0;
677 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
678 if (snd_mixer_selem_has_playback_channel(me, c))
679 r = snd_mixer_selem_get_playback_volume(me, c, &value);
680 else
681 r = -1;
682 } else {
683 if (snd_mixer_selem_has_capture_channel(me, c))
684 r = snd_mixer_selem_get_capture_volume(me, c, &value);
685 else
686 r = -1;
689 if (r < 0)
690 continue;
692 f = from_alsa_volume(value, e->min_volume, e->max_volume);
695 for (k = 0; k < cm->channels; k++)
696 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
697 if (v->values[k] < f)
698 v->values[k] = f;
700 mask |= e->masks[c][e->n_channels-1];
703 for (k = 0; k < cm->channels; k++)
704 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
705 v->values[k] = PA_VOLUME_NORM;
707 return 0;
710 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
711 pa_alsa_element *e;
713 pa_assert(m);
714 pa_assert(p);
715 pa_assert(cm);
716 pa_assert(v);
718 if (!p->has_volume)
719 return -1;
721 pa_cvolume_reset(v, cm->channels);
723 PA_LLIST_FOREACH(e, p->elements) {
724 pa_cvolume ev;
726 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
727 continue;
729 pa_assert(!p->has_dB || e->has_dB);
731 if (element_get_volume(e, m, cm, &ev) < 0)
732 return -1;
734 /* If we have no dB information all we can do is take the first element and leave */
735 if (!p->has_dB) {
736 *v = ev;
737 return 0;
740 pa_sw_cvolume_multiply(v, v, &ev);
743 return 0;
746 static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) {
747 snd_mixer_selem_id_t *sid;
748 snd_mixer_elem_t *me;
749 snd_mixer_selem_channel_id_t c;
751 pa_assert(m);
752 pa_assert(e);
753 pa_assert(b);
755 SELEM_INIT(sid, e->alsa_name);
756 if (!(me = snd_mixer_find_selem(m, sid))) {
757 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
758 return -1;
761 /* We return muted if at least one channel is muted */
763 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
764 int r;
765 int value = 0;
767 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
768 if (snd_mixer_selem_has_playback_channel(me, c))
769 r = snd_mixer_selem_get_playback_switch(me, c, &value);
770 else
771 r = -1;
772 } else {
773 if (snd_mixer_selem_has_capture_channel(me, c))
774 r = snd_mixer_selem_get_capture_switch(me, c, &value);
775 else
776 r = -1;
779 if (r < 0)
780 continue;
782 if (!value) {
783 *b = FALSE;
784 return 0;
788 *b = TRUE;
789 return 0;
792 int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
793 pa_alsa_element *e;
795 pa_assert(m);
796 pa_assert(p);
797 pa_assert(muted);
799 if (!p->has_mute)
800 return -1;
802 PA_LLIST_FOREACH(e, p->elements) {
803 pa_bool_t b;
805 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
806 continue;
808 if (element_get_switch(e, m, &b) < 0)
809 return -1;
811 if (!b) {
812 *muted = TRUE;
813 return 0;
817 *muted = FALSE;
818 return 0;
821 /* Finds the closest item in db_fix->db_values and returns the corresponding
822 * step. *db_value is replaced with the value from the db_values table.
823 * Rounding is done based on the rounding parameter: -1 means rounding down and
824 * +1 means rounding up. */
825 static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) {
826 unsigned i = 0;
827 unsigned max_i = 0;
829 pa_assert(db_fix);
830 pa_assert(db_value);
831 pa_assert(rounding != 0);
833 max_i = db_fix->max_step - db_fix->min_step;
835 if (rounding > 0) {
836 for (i = 0; i < max_i; i++) {
837 if (db_fix->db_values[i] >= *db_value)
838 break;
840 } else {
841 for (i = 0; i < max_i; i++) {
842 if (db_fix->db_values[i + 1] > *db_value)
843 break;
847 *db_value = db_fix->db_values[i];
849 return i + db_fix->min_step;
852 /* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument,
853 * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above".
854 * But even with accurate nearest dB volume step is not selected, so that is why we need
855 * this function. Returns 0 and nearest selectable volume in *value_dB on success or
856 * negative error code if fails. */
857 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) {
859 long alsa_val;
860 long value_high;
861 long value_low;
862 int r = -1;
864 pa_assert(me);
865 pa_assert(value_dB);
867 if (d == PA_ALSA_DIRECTION_OUTPUT) {
868 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
869 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high);
871 if (r < 0)
872 return r;
874 if (value_high == *value_dB)
875 return r;
877 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
878 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low);
879 } else {
880 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
881 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high);
883 if (r < 0)
884 return r;
886 if (value_high == *value_dB)
887 return r;
889 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
890 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low);
893 if (r < 0)
894 return r;
896 if (labs(value_high - *value_dB) < labs(value_low - *value_dB))
897 *value_dB = value_high;
898 else
899 *value_dB = value_low;
901 return r;
904 static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) {
906 snd_mixer_selem_id_t *sid;
907 pa_cvolume rv;
908 snd_mixer_elem_t *me;
909 snd_mixer_selem_channel_id_t c;
910 pa_channel_position_mask_t mask = 0;
911 unsigned k;
913 pa_assert(m);
914 pa_assert(e);
915 pa_assert(cm);
916 pa_assert(v);
917 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
919 SELEM_INIT(sid, e->alsa_name);
920 if (!(me = snd_mixer_find_selem(m, sid))) {
921 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
922 return -1;
925 pa_cvolume_mute(&rv, cm->channels);
927 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
928 int r;
929 pa_volume_t f = PA_VOLUME_MUTED;
930 pa_bool_t found = FALSE;
932 for (k = 0; k < cm->channels; k++)
933 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) {
934 found = TRUE;
935 if (v->values[k] > f)
936 f = v->values[k];
939 if (!found) {
940 /* Hmm, so this channel does not exist in the volume
941 * struct, so let's bind it to the overall max of the
942 * volume. */
943 f = pa_cvolume_max(v);
946 if (e->has_dB) {
947 long value = to_alsa_dB(f);
948 int rounding;
950 if (e->volume_limit >= 0 && value > (e->max_dB * 100))
951 value = e->max_dB * 100;
953 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
954 /* If we call set_playback_volume() without checking first
955 * if the channel is available, ALSA behaves very
956 * strangely and doesn't fail the call */
957 if (snd_mixer_selem_has_playback_channel(me, c)) {
958 rounding = +1;
959 if (e->db_fix) {
960 if (write_to_hw)
961 r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
962 else {
963 decibel_fix_get_step(e->db_fix, &value, rounding);
964 r = 0;
967 } else {
968 if (write_to_hw) {
969 if (sync_volume) {
970 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0)
971 r = snd_mixer_selem_set_playback_dB(me, c, value, 0);
972 } else {
973 if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
974 r = snd_mixer_selem_get_playback_dB(me, c, &value);
976 } else {
977 long alsa_val;
978 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
979 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
982 } else
983 r = -1;
984 } else {
985 if (snd_mixer_selem_has_capture_channel(me, c)) {
986 rounding = -1;
987 if (e->db_fix) {
988 if (write_to_hw)
989 r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
990 else {
991 decibel_fix_get_step(e->db_fix, &value, rounding);
992 r = 0;
995 } else {
996 if (write_to_hw) {
997 if (sync_volume) {
998 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0)
999 r = snd_mixer_selem_set_capture_dB(me, c, value, 0);
1000 } else {
1001 if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
1002 r = snd_mixer_selem_get_capture_dB(me, c, &value);
1004 } else {
1005 long alsa_val;
1006 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
1007 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
1010 } else
1011 r = -1;
1014 if (r < 0)
1015 continue;
1017 #ifdef HAVE_VALGRIND_MEMCHECK_H
1018 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
1019 #endif
1021 f = from_alsa_dB(value);
1023 } else {
1024 long value;
1026 value = to_alsa_volume(f, e->min_volume, e->max_volume);
1028 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1029 if (snd_mixer_selem_has_playback_channel(me, c)) {
1030 if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0)
1031 r = snd_mixer_selem_get_playback_volume(me, c, &value);
1032 } else
1033 r = -1;
1034 } else {
1035 if (snd_mixer_selem_has_capture_channel(me, c)) {
1036 if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0)
1037 r = snd_mixer_selem_get_capture_volume(me, c, &value);
1038 } else
1039 r = -1;
1042 if (r < 0)
1043 continue;
1045 f = from_alsa_volume(value, e->min_volume, e->max_volume);
1048 for (k = 0; k < cm->channels; k++)
1049 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
1050 if (rv.values[k] < f)
1051 rv.values[k] = f;
1053 mask |= e->masks[c][e->n_channels-1];
1056 for (k = 0; k < cm->channels; k++)
1057 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
1058 rv.values[k] = PA_VOLUME_NORM;
1060 *v = rv;
1061 return 0;
1064 int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t sync_volume, pa_bool_t write_to_hw) {
1066 pa_alsa_element *e;
1067 pa_cvolume rv;
1069 pa_assert(m);
1070 pa_assert(p);
1071 pa_assert(cm);
1072 pa_assert(v);
1073 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
1075 if (!p->has_volume)
1076 return -1;
1078 rv = *v; /* Remaining adjustment */
1079 pa_cvolume_reset(v, cm->channels); /* Adjustment done */
1081 PA_LLIST_FOREACH(e, p->elements) {
1082 pa_cvolume ev;
1084 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
1085 continue;
1087 pa_assert(!p->has_dB || e->has_dB);
1089 ev = rv;
1090 if (element_set_volume(e, m, cm, &ev, sync_volume, write_to_hw) < 0)
1091 return -1;
1093 if (!p->has_dB) {
1094 *v = ev;
1095 return 0;
1098 pa_sw_cvolume_multiply(v, v, &ev);
1099 pa_sw_cvolume_divide(&rv, &rv, &ev);
1102 return 0;
1105 static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) {
1106 snd_mixer_elem_t *me;
1107 snd_mixer_selem_id_t *sid;
1108 int r;
1110 pa_assert(m);
1111 pa_assert(e);
1113 SELEM_INIT(sid, e->alsa_name);
1114 if (!(me = snd_mixer_find_selem(m, sid))) {
1115 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1116 return -1;
1119 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1120 r = snd_mixer_selem_set_playback_switch_all(me, b);
1121 else
1122 r = snd_mixer_selem_set_capture_switch_all(me, b);
1124 if (r < 0)
1125 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1127 return r;
1130 int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) {
1131 pa_alsa_element *e;
1133 pa_assert(m);
1134 pa_assert(p);
1136 if (!p->has_mute)
1137 return -1;
1139 PA_LLIST_FOREACH(e, p->elements) {
1141 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
1142 continue;
1144 if (element_set_switch(e, m, !muted) < 0)
1145 return -1;
1148 return 0;
1151 /* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this
1152 * function sets all channels of the volume element to e->min_volume, 0 dB or
1153 * e->constant_volume. */
1154 static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
1155 snd_mixer_elem_t *me = NULL;
1156 snd_mixer_selem_id_t *sid = NULL;
1157 int r = 0;
1158 long volume = -1;
1159 pa_bool_t volume_set = FALSE;
1161 pa_assert(m);
1162 pa_assert(e);
1164 SELEM_INIT(sid, e->alsa_name);
1165 if (!(me = snd_mixer_find_selem(m, sid))) {
1166 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1167 return -1;
1170 switch (e->volume_use) {
1171 case PA_ALSA_VOLUME_OFF:
1172 volume = e->min_volume;
1173 volume_set = TRUE;
1174 break;
1176 case PA_ALSA_VOLUME_ZERO:
1177 if (e->db_fix) {
1178 long dB = 0;
1180 volume = decibel_fix_get_step(e->db_fix, &dB, +1);
1181 volume_set = TRUE;
1183 break;
1185 case PA_ALSA_VOLUME_CONSTANT:
1186 volume = e->constant_volume;
1187 volume_set = TRUE;
1188 break;
1190 default:
1191 pa_assert_not_reached();
1194 if (volume_set) {
1195 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1196 r = snd_mixer_selem_set_playback_volume_all(me, volume);
1197 else
1198 r = snd_mixer_selem_set_capture_volume_all(me, volume);
1199 } else {
1200 pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO);
1201 pa_assert(!e->db_fix);
1203 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1204 r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
1205 else
1206 r = snd_mixer_selem_set_capture_dB_all(me, 0, +1);
1209 if (r < 0)
1210 pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1212 return r;
1215 int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
1216 pa_alsa_element *e;
1217 int r = 0;
1219 pa_assert(m);
1220 pa_assert(p);
1222 pa_log_debug("Activating path %s", p->name);
1223 pa_alsa_path_dump(p);
1225 PA_LLIST_FOREACH(e, p->elements) {
1227 switch (e->switch_use) {
1228 case PA_ALSA_SWITCH_OFF:
1229 r = element_set_switch(e, m, FALSE);
1230 break;
1232 case PA_ALSA_SWITCH_ON:
1233 r = element_set_switch(e, m, TRUE);
1234 break;
1236 case PA_ALSA_SWITCH_MUTE:
1237 case PA_ALSA_SWITCH_IGNORE:
1238 case PA_ALSA_SWITCH_SELECT:
1239 r = 0;
1240 break;
1243 if (r < 0)
1244 return -1;
1246 switch (e->volume_use) {
1247 case PA_ALSA_VOLUME_OFF:
1248 case PA_ALSA_VOLUME_ZERO:
1249 case PA_ALSA_VOLUME_CONSTANT:
1250 r = element_set_constant_volume(e, m);
1251 break;
1253 case PA_ALSA_VOLUME_MERGE:
1254 case PA_ALSA_VOLUME_IGNORE:
1255 r = 0;
1256 break;
1259 if (r < 0)
1260 return -1;
1263 return 0;
1266 static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) {
1267 pa_bool_t has_switch;
1268 pa_bool_t has_enumeration;
1269 pa_bool_t has_volume;
1271 pa_assert(e);
1272 pa_assert(me);
1274 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1275 has_switch =
1276 snd_mixer_selem_has_playback_switch(me) ||
1277 (e->direction_try_other && snd_mixer_selem_has_capture_switch(me));
1278 } else {
1279 has_switch =
1280 snd_mixer_selem_has_capture_switch(me) ||
1281 (e->direction_try_other && snd_mixer_selem_has_playback_switch(me));
1284 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1285 has_volume =
1286 snd_mixer_selem_has_playback_volume(me) ||
1287 (e->direction_try_other && snd_mixer_selem_has_capture_volume(me));
1288 } else {
1289 has_volume =
1290 snd_mixer_selem_has_capture_volume(me) ||
1291 (e->direction_try_other && snd_mixer_selem_has_playback_volume(me));
1294 has_enumeration = snd_mixer_selem_is_enumerated(me);
1296 if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) ||
1297 (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) ||
1298 (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration))
1299 return -1;
1301 if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration))
1302 return -1;
1304 if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) ||
1305 (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) ||
1306 (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration))
1307 return -1;
1309 if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration))
1310 return -1;
1312 if (e->required_any != PA_ALSA_REQUIRED_IGNORE) {
1313 switch (e->required_any) {
1314 case PA_ALSA_REQUIRED_VOLUME:
1315 e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE);
1316 break;
1317 case PA_ALSA_REQUIRED_SWITCH:
1318 e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE);
1319 break;
1320 case PA_ALSA_REQUIRED_ENUMERATION:
1321 e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1322 break;
1323 case PA_ALSA_REQUIRED_ANY:
1324 e->path->req_any_present |=
1325 (e->volume_use != PA_ALSA_VOLUME_IGNORE) ||
1326 (e->switch_use != PA_ALSA_SWITCH_IGNORE) ||
1327 (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1328 break;
1329 default:
1330 pa_assert_not_reached();
1334 if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1335 pa_alsa_option *o;
1336 PA_LLIST_FOREACH(o, e->options) {
1337 e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) &&
1338 (o->alsa_idx >= 0);
1339 if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0)
1340 return -1;
1341 if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0)
1342 return -1;
1346 return 0;
1349 static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
1350 snd_mixer_selem_id_t *sid;
1351 snd_mixer_elem_t *me;
1353 pa_assert(m);
1354 pa_assert(e);
1355 pa_assert(e->path);
1357 SELEM_INIT(sid, e->alsa_name);
1359 if (!(me = snd_mixer_find_selem(m, sid))) {
1361 if (e->required != PA_ALSA_REQUIRED_IGNORE)
1362 return -1;
1364 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1365 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1366 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1368 return 0;
1371 if (e->switch_use != PA_ALSA_SWITCH_IGNORE) {
1372 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1374 if (!snd_mixer_selem_has_playback_switch(me)) {
1375 if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me))
1376 e->direction = PA_ALSA_DIRECTION_INPUT;
1377 else
1378 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1381 } else {
1383 if (!snd_mixer_selem_has_capture_switch(me)) {
1384 if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me))
1385 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1386 else
1387 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1391 if (e->switch_use != PA_ALSA_SWITCH_IGNORE)
1392 e->direction_try_other = FALSE;
1395 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1397 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1399 if (!snd_mixer_selem_has_playback_volume(me)) {
1400 if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me))
1401 e->direction = PA_ALSA_DIRECTION_INPUT;
1402 else
1403 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1406 } else {
1408 if (!snd_mixer_selem_has_capture_volume(me)) {
1409 if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me))
1410 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1411 else
1412 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1416 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1417 long min_dB = 0, max_dB = 0;
1418 int r;
1420 e->direction_try_other = FALSE;
1422 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1423 r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume);
1424 else
1425 r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
1427 if (r < 0) {
1428 pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1429 return -1;
1432 if (e->min_volume >= e->max_volume) {
1433 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);
1434 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1436 } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT &&
1437 (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
1438 pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
1439 e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
1440 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1442 } else {
1443 pa_bool_t is_mono;
1444 pa_channel_position_t p;
1446 if (e->db_fix &&
1447 ((e->min_volume > e->db_fix->min_step) ||
1448 (e->max_volume < e->db_fix->max_step))) {
1449 pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
1450 "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
1451 e->db_fix->min_step, e->db_fix->max_step,
1452 e->min_volume, e->max_volume);
1454 decibel_fix_free(e->db_fix);
1455 e->db_fix = NULL;
1458 if (e->db_fix) {
1459 e->has_dB = TRUE;
1460 e->min_volume = e->db_fix->min_step;
1461 e->max_volume = e->db_fix->max_step;
1462 min_dB = e->db_fix->db_values[0];
1463 max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step];
1464 } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1465 e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
1466 else
1467 e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
1469 /* Check that the kernel driver returns consistent limits with
1470 * both _get_*_dB_range() and _ask_*_vol_dB(). */
1471 if (e->has_dB && !e->db_fix) {
1472 long min_dB_checked = 0;
1473 long max_dB_checked = 0;
1475 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1476 r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked);
1477 else
1478 r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked);
1480 if (r < 0) {
1481 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
1482 return -1;
1485 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1486 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked);
1487 else
1488 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked);
1490 if (r < 0) {
1491 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
1492 return -1;
1495 if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
1496 pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
1497 "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
1498 "%0.2f dB at level %li.",
1499 e->alsa_name,
1500 min_dB / 100.0, max_dB / 100.0,
1501 min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
1502 return -1;
1506 if (e->has_dB) {
1507 #ifdef HAVE_VALGRIND_MEMCHECK_H
1508 VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
1509 VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
1510 #endif
1512 e->min_dB = ((double) min_dB) / 100.0;
1513 e->max_dB = ((double) max_dB) / 100.0;
1515 if (min_dB >= max_dB) {
1516 pa_assert(!e->db_fix);
1517 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);
1518 e->has_dB = FALSE;
1522 if (e->volume_limit >= 0) {
1523 if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
1524 pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
1525 "%li-%li. The volume limit is ignored.",
1526 e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
1528 else {
1529 e->max_volume = e->volume_limit;
1531 if (e->has_dB) {
1532 if (e->db_fix) {
1533 e->db_fix->max_step = e->max_volume;
1534 e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
1536 } else {
1537 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1538 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB);
1539 else
1540 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB);
1542 if (r < 0) {
1543 pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1544 e->has_dB = FALSE;
1545 } else
1546 e->max_dB = ((double) max_dB) / 100.0;
1552 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1553 is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
1554 else
1555 is_mono = snd_mixer_selem_is_capture_mono(me) > 0;
1557 if (is_mono) {
1558 e->n_channels = 1;
1560 if (!e->override_map) {
1561 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1562 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1563 continue;
1565 e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0;
1568 e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL;
1571 e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1];
1572 } else {
1573 e->n_channels = 0;
1574 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1576 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1577 continue;
1579 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1580 e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1581 else
1582 e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1585 if (e->n_channels <= 0) {
1586 pa_log_warn("Volume element %s with no channels?", e->alsa_name);
1587 return -1;
1590 if (e->n_channels > 2) {
1591 /* FIXME: In some places code like this is used:
1593 * e->masks[alsa_channel_ids[p]][e->n_channels-1]
1595 * The definition of e->masks is
1597 * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2];
1599 * Since the array size is fixed at 2, we obviously
1600 * don't support elements with more than two
1601 * channels... */
1602 pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
1603 return -1;
1606 if (!e->override_map) {
1607 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1608 pa_bool_t has_channel;
1610 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1611 continue;
1613 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1614 has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1615 else
1616 has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1618 e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0;
1622 e->merged_mask = 0;
1623 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1624 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1625 continue;
1627 e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
1635 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
1636 pa_alsa_option *o;
1638 PA_LLIST_FOREACH(o, e->options)
1639 o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0;
1640 } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1641 int n;
1642 pa_alsa_option *o;
1644 if ((n = snd_mixer_selem_get_enum_items(me)) < 0) {
1645 pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n));
1646 return -1;
1649 PA_LLIST_FOREACH(o, e->options) {
1650 int i;
1652 for (i = 0; i < n; i++) {
1653 char buf[128];
1655 if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0)
1656 continue;
1658 if (!pa_streq(buf, o->alsa_name))
1659 continue;
1661 o->alsa_idx = i;
1666 if (check_required(e, me) < 0)
1667 return -1;
1669 return 0;
1672 static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
1673 pa_alsa_element *e;
1675 pa_assert(p);
1676 pa_assert(section);
1678 if (prefixed) {
1679 if (!pa_startswith(section, "Element "))
1680 return NULL;
1682 section += 8;
1685 /* This is not an element section, but an enum section? */
1686 if (strchr(section, ':'))
1687 return NULL;
1689 if (p->last_element && pa_streq(p->last_element->alsa_name, section))
1690 return p->last_element;
1692 PA_LLIST_FOREACH(e, p->elements)
1693 if (pa_streq(e->alsa_name, section))
1694 goto finish;
1696 e = pa_xnew0(pa_alsa_element, 1);
1697 e->path = p;
1698 e->alsa_name = pa_xstrdup(section);
1699 e->direction = p->direction;
1700 e->volume_limit = -1;
1702 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
1704 finish:
1705 p->last_element = e;
1706 return e;
1709 static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
1710 char *en;
1711 const char *on;
1712 pa_alsa_option *o;
1713 pa_alsa_element *e;
1715 if (!pa_startswith(section, "Option "))
1716 return NULL;
1718 section += 7;
1720 /* This is not an enum section, but an element section? */
1721 if (!(on = strchr(section, ':')))
1722 return NULL;
1724 en = pa_xstrndup(section, on - section);
1725 on++;
1727 if (p->last_option &&
1728 pa_streq(p->last_option->element->alsa_name, en) &&
1729 pa_streq(p->last_option->alsa_name, on)) {
1730 pa_xfree(en);
1731 return p->last_option;
1734 pa_assert_se(e = element_get(p, en, FALSE));
1735 pa_xfree(en);
1737 PA_LLIST_FOREACH(o, e->options)
1738 if (pa_streq(o->alsa_name, on))
1739 goto finish;
1741 o = pa_xnew0(pa_alsa_option, 1);
1742 o->element = e;
1743 o->alsa_name = pa_xstrdup(on);
1744 o->alsa_idx = -1;
1746 if (p->last_option && p->last_option->element == e)
1747 PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o);
1748 else
1749 PA_LLIST_PREPEND(pa_alsa_option, e->options, o);
1751 finish:
1752 p->last_option = o;
1753 return o;
1756 static int element_parse_switch(
1757 const char *filename,
1758 unsigned line,
1759 const char *section,
1760 const char *lvalue,
1761 const char *rvalue,
1762 void *data,
1763 void *userdata) {
1765 pa_alsa_path *p = userdata;
1766 pa_alsa_element *e;
1768 pa_assert(p);
1770 if (!(e = element_get(p, section, TRUE))) {
1771 pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section);
1772 return -1;
1775 if (pa_streq(rvalue, "ignore"))
1776 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1777 else if (pa_streq(rvalue, "mute"))
1778 e->switch_use = PA_ALSA_SWITCH_MUTE;
1779 else if (pa_streq(rvalue, "off"))
1780 e->switch_use = PA_ALSA_SWITCH_OFF;
1781 else if (pa_streq(rvalue, "on"))
1782 e->switch_use = PA_ALSA_SWITCH_ON;
1783 else if (pa_streq(rvalue, "select"))
1784 e->switch_use = PA_ALSA_SWITCH_SELECT;
1785 else {
1786 pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section);
1787 return -1;
1790 return 0;
1793 static int element_parse_volume(
1794 const char *filename,
1795 unsigned line,
1796 const char *section,
1797 const char *lvalue,
1798 const char *rvalue,
1799 void *data,
1800 void *userdata) {
1802 pa_alsa_path *p = userdata;
1803 pa_alsa_element *e;
1805 pa_assert(p);
1807 if (!(e = element_get(p, section, TRUE))) {
1808 pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section);
1809 return -1;
1812 if (pa_streq(rvalue, "ignore"))
1813 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1814 else if (pa_streq(rvalue, "merge"))
1815 e->volume_use = PA_ALSA_VOLUME_MERGE;
1816 else if (pa_streq(rvalue, "off"))
1817 e->volume_use = PA_ALSA_VOLUME_OFF;
1818 else if (pa_streq(rvalue, "zero"))
1819 e->volume_use = PA_ALSA_VOLUME_ZERO;
1820 else {
1821 uint32_t constant;
1823 if (pa_atou(rvalue, &constant) >= 0) {
1824 e->volume_use = PA_ALSA_VOLUME_CONSTANT;
1825 e->constant_volume = constant;
1826 } else {
1827 pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section);
1828 return -1;
1832 return 0;
1835 static int element_parse_enumeration(
1836 const char *filename,
1837 unsigned line,
1838 const char *section,
1839 const char *lvalue,
1840 const char *rvalue,
1841 void *data,
1842 void *userdata) {
1844 pa_alsa_path *p = userdata;
1845 pa_alsa_element *e;
1847 pa_assert(p);
1849 if (!(e = element_get(p, section, TRUE))) {
1850 pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section);
1851 return -1;
1854 if (pa_streq(rvalue, "ignore"))
1855 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1856 else if (pa_streq(rvalue, "select"))
1857 e->enumeration_use = PA_ALSA_ENUMERATION_SELECT;
1858 else {
1859 pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section);
1860 return -1;
1863 return 0;
1866 static int option_parse_priority(
1867 const char *filename,
1868 unsigned line,
1869 const char *section,
1870 const char *lvalue,
1871 const char *rvalue,
1872 void *data,
1873 void *userdata) {
1875 pa_alsa_path *p = userdata;
1876 pa_alsa_option *o;
1877 uint32_t prio;
1879 pa_assert(p);
1881 if (!(o = option_get(p, section))) {
1882 pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section);
1883 return -1;
1886 if (pa_atou(rvalue, &prio) < 0) {
1887 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
1888 return -1;
1891 o->priority = prio;
1892 return 0;
1895 static int option_parse_name(
1896 const char *filename,
1897 unsigned line,
1898 const char *section,
1899 const char *lvalue,
1900 const char *rvalue,
1901 void *data,
1902 void *userdata) {
1904 pa_alsa_path *p = userdata;
1905 pa_alsa_option *o;
1907 pa_assert(p);
1909 if (!(o = option_get(p, section))) {
1910 pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section);
1911 return -1;
1914 pa_xfree(o->name);
1915 o->name = pa_xstrdup(rvalue);
1917 return 0;
1920 static int element_parse_required(
1921 const char *filename,
1922 unsigned line,
1923 const char *section,
1924 const char *lvalue,
1925 const char *rvalue,
1926 void *data,
1927 void *userdata) {
1929 pa_alsa_path *p = userdata;
1930 pa_alsa_element *e;
1931 pa_alsa_option *o;
1932 pa_alsa_required_t req;
1934 pa_assert(p);
1936 e = element_get(p, section, TRUE);
1937 o = option_get(p, section);
1938 if (!e && !o) {
1939 pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
1940 return -1;
1943 if (pa_streq(rvalue, "ignore"))
1944 req = PA_ALSA_REQUIRED_IGNORE;
1945 else if (pa_streq(rvalue, "switch") && e)
1946 req = PA_ALSA_REQUIRED_SWITCH;
1947 else if (pa_streq(rvalue, "volume") && e)
1948 req = PA_ALSA_REQUIRED_VOLUME;
1949 else if (pa_streq(rvalue, "enumeration"))
1950 req = PA_ALSA_REQUIRED_ENUMERATION;
1951 else if (pa_streq(rvalue, "any"))
1952 req = PA_ALSA_REQUIRED_ANY;
1953 else {
1954 pa_log("[%s:%u] Required invalid of '%s'", filename, line, section);
1955 return -1;
1958 if (pa_streq(lvalue, "required-absent")) {
1959 if (e)
1960 e->required_absent = req;
1961 if (o)
1962 o->required_absent = req;
1964 else if (pa_streq(lvalue, "required-any")) {
1965 if (e) {
1966 e->required_any = req;
1967 e->path->has_req_any = TRUE;
1969 if (o) {
1970 o->required_any = req;
1971 o->element->path->has_req_any = TRUE;
1974 else {
1975 if (e)
1976 e->required = req;
1977 if (o)
1978 o->required = req;
1981 return 0;
1984 static int element_parse_direction(
1985 const char *filename,
1986 unsigned line,
1987 const char *section,
1988 const char *lvalue,
1989 const char *rvalue,
1990 void *data,
1991 void *userdata) {
1993 pa_alsa_path *p = userdata;
1994 pa_alsa_element *e;
1996 pa_assert(p);
1998 if (!(e = element_get(p, section, TRUE))) {
1999 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2000 return -1;
2003 if (pa_streq(rvalue, "playback"))
2004 e->direction = PA_ALSA_DIRECTION_OUTPUT;
2005 else if (pa_streq(rvalue, "capture"))
2006 e->direction = PA_ALSA_DIRECTION_INPUT;
2007 else {
2008 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2009 return -1;
2012 return 0;
2015 static int element_parse_direction_try_other(
2016 const char *filename,
2017 unsigned line,
2018 const char *section,
2019 const char *lvalue,
2020 const char *rvalue,
2021 void *data,
2022 void *userdata) {
2024 pa_alsa_path *p = userdata;
2025 pa_alsa_element *e;
2026 int yes;
2028 if (!(e = element_get(p, section, TRUE))) {
2029 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2030 return -1;
2033 if ((yes = pa_parse_boolean(rvalue)) < 0) {
2034 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2035 return -1;
2038 e->direction_try_other = !!yes;
2039 return 0;
2042 static int element_parse_volume_limit(
2043 const char *filename,
2044 unsigned line,
2045 const char *section,
2046 const char *lvalue,
2047 const char *rvalue,
2048 void *data,
2049 void *userdata) {
2051 pa_alsa_path *p = userdata;
2052 pa_alsa_element *e;
2053 long volume_limit;
2055 if (!(e = element_get(p, section, TRUE))) {
2056 pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section);
2057 return -1;
2060 if (pa_atol(rvalue, &volume_limit) < 0 || volume_limit < 0) {
2061 pa_log("[%s:%u] Invalid value for volume-limit", filename, line);
2062 return -1;
2065 e->volume_limit = volume_limit;
2066 return 0;
2069 static pa_channel_position_mask_t parse_mask(const char *m) {
2070 pa_channel_position_mask_t v;
2072 if (pa_streq(m, "all-left"))
2073 v = PA_CHANNEL_POSITION_MASK_LEFT;
2074 else if (pa_streq(m, "all-right"))
2075 v = PA_CHANNEL_POSITION_MASK_RIGHT;
2076 else if (pa_streq(m, "all-center"))
2077 v = PA_CHANNEL_POSITION_MASK_CENTER;
2078 else if (pa_streq(m, "all-front"))
2079 v = PA_CHANNEL_POSITION_MASK_FRONT;
2080 else if (pa_streq(m, "all-rear"))
2081 v = PA_CHANNEL_POSITION_MASK_REAR;
2082 else if (pa_streq(m, "all-side"))
2083 v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER;
2084 else if (pa_streq(m, "all-top"))
2085 v = PA_CHANNEL_POSITION_MASK_TOP;
2086 else if (pa_streq(m, "all-no-lfe"))
2087 v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE);
2088 else if (pa_streq(m, "all"))
2089 v = PA_CHANNEL_POSITION_MASK_ALL;
2090 else {
2091 pa_channel_position_t p;
2093 if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
2094 return 0;
2096 v = PA_CHANNEL_POSITION_MASK(p);
2099 return v;
2102 static int element_parse_override_map(
2103 const char *filename,
2104 unsigned line,
2105 const char *section,
2106 const char *lvalue,
2107 const char *rvalue,
2108 void *data,
2109 void *userdata) {
2111 pa_alsa_path *p = userdata;
2112 pa_alsa_element *e;
2113 const char *state = NULL;
2114 unsigned i = 0;
2115 char *n;
2117 if (!(e = element_get(p, section, TRUE))) {
2118 pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section);
2119 return -1;
2122 while ((n = pa_split(rvalue, ",", &state))) {
2123 pa_channel_position_mask_t m;
2125 if (!*n)
2126 m = 0;
2127 else {
2128 if ((m = parse_mask(n)) == 0) {
2129 pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section);
2130 pa_xfree(n);
2131 return -1;
2135 if (pa_streq(lvalue, "override-map.1"))
2136 e->masks[i++][0] = m;
2137 else
2138 e->masks[i++][1] = m;
2140 /* Later on we might add override-map.3 and so on here ... */
2142 pa_xfree(n);
2145 e->override_map = TRUE;
2147 return 0;
2150 static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
2151 snd_mixer_selem_id_t *sid;
2152 snd_mixer_elem_t *me;
2153 int r;
2155 pa_assert(e);
2156 pa_assert(m);
2158 SELEM_INIT(sid, e->alsa_name);
2159 if (!(me = snd_mixer_find_selem(m, sid))) {
2160 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2161 return -1;
2164 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
2166 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
2167 r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx);
2168 else
2169 r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
2171 if (r < 0)
2172 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2174 } else {
2175 pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
2177 if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
2178 pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2181 return r;
2184 int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) {
2185 pa_alsa_option *o;
2186 uint32_t idx;
2188 pa_assert(s);
2189 pa_assert(m);
2191 PA_IDXSET_FOREACH(o, s->options, idx)
2192 element_set_option(o->element, m, o->alsa_idx);
2194 return 0;
2197 static int option_verify(pa_alsa_option *o) {
2198 static const struct description_map well_known_descriptions[] = {
2199 { "input", N_("Input") },
2200 { "input-docking", N_("Docking Station Input") },
2201 { "input-docking-microphone", N_("Docking Station Microphone") },
2202 { "input-docking-linein", N_("Docking Station Line-In") },
2203 { "input-linein", N_("Line-In") },
2204 { "input-microphone", N_("Microphone") },
2205 { "input-microphone-front", N_("Front Microphone") },
2206 { "input-microphone-rear", N_("Rear Microphone") },
2207 { "input-microphone-external", N_("External Microphone") },
2208 { "input-microphone-internal", N_("Internal Microphone") },
2209 { "input-radio", N_("Radio") },
2210 { "input-video", N_("Video") },
2211 { "input-agc-on", N_("Automatic Gain Control") },
2212 { "input-agc-off", N_("No Automatic Gain Control") },
2213 { "input-boost-on", N_("Boost") },
2214 { "input-boost-off", N_("No Boost") },
2215 { "output-amplifier-on", N_("Amplifier") },
2216 { "output-amplifier-off", N_("No Amplifier") },
2217 { "output-bass-boost-on", N_("Bass Boost") },
2218 { "output-bass-boost-off", N_("No Bass Boost") },
2219 { "output-speaker", N_("Speaker") },
2220 { "output-headphones", N_("Headphones") }
2223 pa_assert(o);
2225 if (!o->name) {
2226 pa_log("No name set for option %s", o->alsa_name);
2227 return -1;
2230 if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
2231 o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
2232 pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
2233 return -1;
2236 if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
2237 !pa_streq(o->alsa_name, "on") &&
2238 !pa_streq(o->alsa_name, "off")) {
2239 pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
2240 return -1;
2243 if (!o->description)
2244 o->description = pa_xstrdup(lookup_description(o->name,
2245 well_known_descriptions,
2246 PA_ELEMENTSOF(well_known_descriptions)));
2247 if (!o->description)
2248 o->description = pa_xstrdup(o->name);
2250 return 0;
2253 static int element_verify(pa_alsa_element *e) {
2254 pa_alsa_option *o;
2256 pa_assert(e);
2258 // 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);
2259 if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) ||
2260 (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
2261 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
2262 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
2263 pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
2264 return -1;
2267 if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
2268 pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
2269 return -1;
2272 PA_LLIST_FOREACH(o, e->options)
2273 if (option_verify(o) < 0)
2274 return -1;
2276 return 0;
2279 static int path_verify(pa_alsa_path *p) {
2280 static const struct description_map well_known_descriptions[] = {
2281 { "analog-input", N_("Analog Input") },
2282 { "analog-input-microphone", N_("Analog Microphone") },
2283 { "analog-input-microphone-front", N_("Front Microphone") },
2284 { "analog-input-microphone-rear", N_("Rear Microphone") },
2285 { "analog-input-microphone-dock", N_("Docking Station Microphone") },
2286 { "analog-input-microphone-internal", N_("Internal Microphone") },
2287 { "analog-input-linein", N_("Analog Line-In") },
2288 { "analog-input-radio", N_("Analog Radio") },
2289 { "analog-input-video", N_("Analog Video") },
2290 { "analog-output", N_("Analog Output") },
2291 { "analog-output-headphones", N_("Analog Headphones") },
2292 { "analog-output-lfe-on-mono", N_("Analog Output (LFE)") },
2293 { "analog-output-mono", N_("Analog Mono Output") },
2294 { "analog-output-speaker", N_("Analog Speakers") },
2295 { "iec958-stereo-output", N_("Digital Output (IEC958)") },
2296 { "iec958-passthrough-output", N_("Digital Passthrough (IEC958)") }
2299 pa_alsa_element *e;
2301 pa_assert(p);
2303 PA_LLIST_FOREACH(e, p->elements)
2304 if (element_verify(e) < 0)
2305 return -1;
2307 if (!p->description)
2308 p->description = pa_xstrdup(lookup_description(p->name,
2309 well_known_descriptions,
2310 PA_ELEMENTSOF(well_known_descriptions)));
2312 if (!p->description)
2313 p->description = pa_xstrdup(p->name);
2315 return 0;
2318 pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction) {
2319 pa_alsa_path *p;
2320 char *fn;
2321 int r;
2322 const char *n;
2324 pa_config_item items[] = {
2325 /* [General] */
2326 { "priority", pa_config_parse_unsigned, NULL, "General" },
2327 { "description", pa_config_parse_string, NULL, "General" },
2328 { "name", pa_config_parse_string, NULL, "General" },
2330 /* [Option ...] */
2331 { "priority", option_parse_priority, NULL, NULL },
2332 { "name", option_parse_name, NULL, NULL },
2334 /* [Element ...] */
2335 { "switch", element_parse_switch, NULL, NULL },
2336 { "volume", element_parse_volume, NULL, NULL },
2337 { "enumeration", element_parse_enumeration, NULL, NULL },
2338 { "override-map.1", element_parse_override_map, NULL, NULL },
2339 { "override-map.2", element_parse_override_map, NULL, NULL },
2340 /* ... later on we might add override-map.3 and so on here ... */
2341 { "required", element_parse_required, NULL, NULL },
2342 { "required-any", element_parse_required, NULL, NULL },
2343 { "required-absent", element_parse_required, NULL, NULL },
2344 { "direction", element_parse_direction, NULL, NULL },
2345 { "direction-try-other", element_parse_direction_try_other, NULL, NULL },
2346 { "volume-limit", element_parse_volume_limit, NULL, NULL },
2347 { NULL, NULL, NULL, NULL }
2350 pa_assert(fname);
2352 p = pa_xnew0(pa_alsa_path, 1);
2353 n = pa_path_get_filename(fname);
2354 p->name = pa_xstrndup(n, strcspn(n, "."));
2355 p->direction = direction;
2357 items[0].data = &p->priority;
2358 items[1].data = &p->description;
2359 items[2].data = &p->name;
2361 fn = pa_maybe_prefix_path(fname,
2362 pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" :
2363 PA_ALSA_PATHS_DIR);
2365 r = pa_config_parse(fn, NULL, items, p);
2366 pa_xfree(fn);
2368 if (r < 0)
2369 goto fail;
2371 if (path_verify(p) < 0)
2372 goto fail;
2374 return p;
2376 fail:
2377 pa_alsa_path_free(p);
2378 return NULL;
2381 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
2382 pa_alsa_path *p;
2383 pa_alsa_element *e;
2385 pa_assert(element);
2387 p = pa_xnew0(pa_alsa_path, 1);
2388 p->name = pa_xstrdup(element);
2389 p->direction = direction;
2391 e = pa_xnew0(pa_alsa_element, 1);
2392 e->path = p;
2393 e->alsa_name = pa_xstrdup(element);
2394 e->direction = direction;
2395 e->volume_limit = -1;
2397 e->switch_use = PA_ALSA_SWITCH_MUTE;
2398 e->volume_use = PA_ALSA_VOLUME_MERGE;
2400 PA_LLIST_PREPEND(pa_alsa_element, p->elements, e);
2401 p->last_element = e;
2402 return p;
2405 static pa_bool_t element_drop_unsupported(pa_alsa_element *e) {
2406 pa_alsa_option *o, *n;
2408 pa_assert(e);
2410 for (o = e->options; o; o = n) {
2411 n = o->next;
2413 if (o->alsa_idx < 0) {
2414 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
2415 option_free(o);
2419 return
2420 e->switch_use != PA_ALSA_SWITCH_IGNORE ||
2421 e->volume_use != PA_ALSA_VOLUME_IGNORE ||
2422 e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE;
2425 static void path_drop_unsupported(pa_alsa_path *p) {
2426 pa_alsa_element *e, *n;
2428 pa_assert(p);
2430 for (e = p->elements; e; e = n) {
2431 n = e->next;
2433 if (!element_drop_unsupported(e)) {
2434 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
2435 element_free(e);
2440 static void path_make_options_unique(pa_alsa_path *p) {
2441 pa_alsa_element *e;
2442 pa_alsa_option *o, *u;
2444 PA_LLIST_FOREACH(e, p->elements) {
2445 PA_LLIST_FOREACH(o, e->options) {
2446 unsigned i;
2447 char *m;
2449 for (u = o->next; u; u = u->next)
2450 if (pa_streq(u->name, o->name))
2451 break;
2453 if (!u)
2454 continue;
2456 m = pa_xstrdup(o->name);
2458 /* OK, this name is not unique, hence let's rename */
2459 for (i = 1, u = o; u; u = u->next) {
2460 char *nn, *nd;
2462 if (!pa_streq(u->name, m))
2463 continue;
2465 nn = pa_sprintf_malloc("%s-%u", m, i);
2466 pa_xfree(u->name);
2467 u->name = nn;
2469 nd = pa_sprintf_malloc("%s %u", u->description, i);
2470 pa_xfree(u->description);
2471 u->description = nd;
2473 i++;
2476 pa_xfree(m);
2481 static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) {
2482 pa_alsa_option *o;
2484 for (; e; e = e->next)
2485 if (e->switch_use == PA_ALSA_SWITCH_SELECT ||
2486 e->enumeration_use == PA_ALSA_ENUMERATION_SELECT)
2487 break;
2489 if (!e)
2490 return FALSE;
2492 for (o = e->options; o; o = o->next) {
2493 pa_alsa_setting *s;
2495 if (template) {
2496 s = pa_xnewdup(pa_alsa_setting, template, 1);
2497 s->options = pa_idxset_copy(template->options);
2498 s->name = pa_sprintf_malloc(_("%s+%s"), template->name, o->name);
2499 s->description =
2500 (template->description[0] && o->description[0])
2501 ? pa_sprintf_malloc(_("%s / %s"), template->description, o->description)
2502 : (template->description[0]
2503 ? pa_xstrdup(template->description)
2504 : pa_xstrdup(o->description));
2506 s->priority = PA_MAX(template->priority, o->priority);
2507 } else {
2508 s = pa_xnew0(pa_alsa_setting, 1);
2509 s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
2510 s->name = pa_xstrdup(o->name);
2511 s->description = pa_xstrdup(o->description);
2512 s->priority = o->priority;
2515 pa_idxset_put(s->options, o, NULL);
2517 if (element_create_settings(e->next, s))
2518 /* This is not a leaf, so let's get rid of it */
2519 setting_free(s);
2520 else {
2521 /* This is a leaf, so let's add it */
2522 PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s);
2524 e->path->last_setting = s;
2528 return TRUE;
2531 static void path_create_settings(pa_alsa_path *p) {
2532 pa_assert(p);
2534 element_create_settings(p->elements, NULL);
2537 int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB) {
2538 pa_alsa_element *e;
2539 double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
2540 pa_channel_position_t t;
2541 pa_channel_position_mask_t path_volume_channels = 0;
2543 pa_assert(p);
2544 pa_assert(m);
2546 if (p->probed)
2547 return 0;
2549 pa_zero(min_dB);
2550 pa_zero(max_dB);
2552 pa_log_debug("Probing path '%s'", p->name);
2554 PA_LLIST_FOREACH(e, p->elements) {
2555 if (element_probe(e, m) < 0) {
2556 p->supported = FALSE;
2557 pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
2558 return -1;
2560 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);
2562 if (ignore_dB)
2563 e->has_dB = FALSE;
2565 if (e->volume_use == PA_ALSA_VOLUME_MERGE) {
2567 if (!p->has_volume) {
2568 p->min_volume = e->min_volume;
2569 p->max_volume = e->max_volume;
2572 if (e->has_dB) {
2573 if (!p->has_volume) {
2574 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2575 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2576 min_dB[t] = e->min_dB;
2577 max_dB[t] = e->max_dB;
2578 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2581 p->has_dB = TRUE;
2582 } else {
2584 if (p->has_dB) {
2585 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2586 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2587 min_dB[t] += e->min_dB;
2588 max_dB[t] += e->max_dB;
2589 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2591 } else {
2592 /* Hmm, there's another element before us
2593 * which cannot do dB volumes, so we we need
2594 * to 'neutralize' this slider */
2595 e->volume_use = PA_ALSA_VOLUME_ZERO;
2596 pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
2599 } else if (p->has_volume) {
2600 /* We can't use this volume, so let's ignore it */
2601 e->volume_use = PA_ALSA_VOLUME_IGNORE;
2602 pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
2604 p->has_volume = TRUE;
2607 if (e->switch_use == PA_ALSA_SWITCH_MUTE)
2608 p->has_mute = TRUE;
2611 if (p->has_req_any && !p->req_any_present) {
2612 p->supported = FALSE;
2613 pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name);
2614 return -1;
2617 path_drop_unsupported(p);
2618 path_make_options_unique(p);
2619 path_create_settings(p);
2621 p->supported = TRUE;
2622 p->probed = TRUE;
2624 p->min_dB = INFINITY;
2625 p->max_dB = -INFINITY;
2627 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
2628 if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
2629 if (p->min_dB > min_dB[t])
2630 p->min_dB = min_dB[t];
2632 if (p->max_dB < max_dB[t])
2633 p->max_dB = max_dB[t];
2637 return 0;
2640 void pa_alsa_setting_dump(pa_alsa_setting *s) {
2641 pa_assert(s);
2643 pa_log_debug("Setting %s (%s) priority=%u",
2644 s->name,
2645 pa_strnull(s->description),
2646 s->priority);
2649 void pa_alsa_option_dump(pa_alsa_option *o) {
2650 pa_assert(o);
2652 pa_log_debug("Option %s (%s/%s) index=%i, priority=%u",
2653 o->alsa_name,
2654 pa_strnull(o->name),
2655 pa_strnull(o->description),
2656 o->alsa_idx,
2657 o->priority);
2660 void pa_alsa_element_dump(pa_alsa_element *e) {
2661 pa_alsa_option *o;
2662 pa_assert(e);
2664 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",
2665 e->alsa_name,
2666 e->direction,
2667 e->switch_use,
2668 e->volume_use,
2669 e->volume_limit,
2670 e->enumeration_use,
2671 e->required,
2672 e->required_any,
2673 e->required_absent,
2674 (long long unsigned) e->merged_mask,
2675 e->n_channels,
2676 pa_yes_no(e->override_map));
2678 PA_LLIST_FOREACH(o, e->options)
2679 pa_alsa_option_dump(o);
2682 void pa_alsa_path_dump(pa_alsa_path *p) {
2683 pa_alsa_element *e;
2684 pa_alsa_setting *s;
2685 pa_assert(p);
2687 pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, "
2688 "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g",
2689 p->name,
2690 pa_strnull(p->description),
2691 p->direction,
2692 p->priority,
2693 pa_yes_no(p->probed),
2694 pa_yes_no(p->supported),
2695 pa_yes_no(p->has_mute),
2696 pa_yes_no(p->has_volume),
2697 pa_yes_no(p->has_dB),
2698 p->min_volume, p->max_volume,
2699 p->min_dB, p->max_dB);
2701 PA_LLIST_FOREACH(e, p->elements)
2702 pa_alsa_element_dump(e);
2704 PA_LLIST_FOREACH(s, p->settings)
2705 pa_alsa_setting_dump(s);
2708 static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2709 snd_mixer_selem_id_t *sid;
2710 snd_mixer_elem_t *me;
2712 pa_assert(e);
2713 pa_assert(m);
2714 pa_assert(cb);
2716 SELEM_INIT(sid, e->alsa_name);
2717 if (!(me = snd_mixer_find_selem(m, sid))) {
2718 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2719 return;
2722 snd_mixer_elem_set_callback(me, cb);
2723 snd_mixer_elem_set_callback_private(me, userdata);
2726 void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2727 pa_alsa_element *e;
2729 pa_assert(p);
2730 pa_assert(m);
2731 pa_assert(cb);
2733 PA_LLIST_FOREACH(e, p->elements)
2734 element_set_callback(e, m, cb, userdata);
2737 void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2738 pa_alsa_path *p;
2740 pa_assert(ps);
2741 pa_assert(m);
2742 pa_assert(cb);
2744 PA_LLIST_FOREACH(p, ps->paths)
2745 pa_alsa_path_set_callback(p, m, cb, userdata);
2748 pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction) {
2749 pa_alsa_path_set *ps;
2750 char **pn = NULL, **en = NULL, **ie;
2751 pa_alsa_decibel_fix *db_fix;
2752 void *state;
2754 pa_assert(m);
2755 pa_assert(m->profile_set);
2756 pa_assert(m->profile_set->decibel_fixes);
2757 pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT);
2759 if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction)
2760 return NULL;
2762 ps = pa_xnew0(pa_alsa_path_set, 1);
2763 ps->direction = direction;
2765 if (direction == PA_ALSA_DIRECTION_OUTPUT)
2766 pn = m->output_path_names;
2767 else if (direction == PA_ALSA_DIRECTION_INPUT)
2768 pn = m->input_path_names;
2770 if (pn) {
2771 char **in;
2773 for (in = pn; *in; in++) {
2774 pa_alsa_path *p;
2775 pa_bool_t duplicate = FALSE;
2776 char **kn, *fn;
2778 for (kn = pn; kn < in; kn++)
2779 if (pa_streq(*kn, *in)) {
2780 duplicate = TRUE;
2781 break;
2784 if (duplicate)
2785 continue;
2787 fn = pa_sprintf_malloc("%s.conf", *in);
2789 if ((p = pa_alsa_path_new(fn, direction))) {
2790 p->path_set = ps;
2791 PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
2792 ps->last_path = p;
2795 pa_xfree(fn);
2798 goto finish;
2801 if (direction == PA_ALSA_DIRECTION_OUTPUT)
2802 en = m->output_element;
2803 else if (direction == PA_ALSA_DIRECTION_INPUT)
2804 en = m->input_element;
2806 if (!en) {
2807 pa_alsa_path_set_free(ps);
2808 return NULL;
2811 for (ie = en; *ie; ie++) {
2812 char **je;
2813 pa_alsa_path *p;
2815 p = pa_alsa_path_synthesize(*ie, direction);
2816 p->path_set = ps;
2818 /* Mark all other passed elements for require-absent */
2819 for (je = en; *je; je++) {
2820 pa_alsa_element *e;
2822 if (je == ie)
2823 continue;
2825 e = pa_xnew0(pa_alsa_element, 1);
2826 e->path = p;
2827 e->alsa_name = pa_xstrdup(*je);
2828 e->direction = direction;
2829 e->required_absent = PA_ALSA_REQUIRED_ANY;
2830 e->volume_limit = -1;
2832 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
2833 p->last_element = e;
2836 PA_LLIST_INSERT_AFTER(pa_alsa_path, ps->paths, ps->last_path, p);
2837 ps->last_path = p;
2840 finish:
2841 /* Assign decibel fixes to elements. */
2842 PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) {
2843 pa_alsa_path *p;
2845 PA_LLIST_FOREACH(p, ps->paths) {
2846 pa_alsa_element *e;
2848 PA_LLIST_FOREACH(e, p->elements) {
2849 if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
2850 /* The profile set that contains the dB fix may be freed
2851 * before the element, so we have to copy the dB fix
2852 * object. */
2853 e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1);
2854 e->db_fix->profile_set = NULL;
2855 e->db_fix->name = pa_xstrdup(db_fix->name);
2856 e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long));
2862 return ps;
2865 void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
2866 pa_alsa_path *p;
2867 pa_assert(ps);
2869 pa_log_debug("Path Set %p, direction=%i, probed=%s",
2870 (void*) ps,
2871 ps->direction,
2872 pa_yes_no(ps->probed));
2874 PA_LLIST_FOREACH(p, ps->paths)
2875 pa_alsa_path_dump(p);
2878 static void path_set_unify(pa_alsa_path_set *ps) {
2879 pa_alsa_path *p;
2880 pa_bool_t has_dB = TRUE, has_volume = TRUE, has_mute = TRUE;
2881 pa_assert(ps);
2883 /* We have issues dealing with paths that vary too wildly. That
2884 * means for now we have to have all paths support volume/mute/dB
2885 * or none. */
2887 PA_LLIST_FOREACH(p, ps->paths) {
2888 pa_assert(p->probed);
2890 if (!p->has_volume)
2891 has_volume = FALSE;
2892 else if (!p->has_dB)
2893 has_dB = FALSE;
2895 if (!p->has_mute)
2896 has_mute = FALSE;
2899 if (!has_volume || !has_dB || !has_mute) {
2901 if (!has_volume)
2902 pa_log_debug("Some paths of the device lack hardware volume control, disabling hardware control altogether.");
2903 else if (!has_dB)
2904 pa_log_debug("Some paths of the device lack dB information, disabling dB logic altogether.");
2906 if (!has_mute)
2907 pa_log_debug("Some paths of the device lack hardware mute control, disabling hardware control altogether.");
2909 PA_LLIST_FOREACH(p, ps->paths) {
2910 if (!has_volume)
2911 p->has_volume = FALSE;
2912 else if (!has_dB)
2913 p->has_dB = FALSE;
2915 if (!has_mute)
2916 p->has_mute = FALSE;
2921 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
2922 pa_alsa_path *p, *q;
2924 PA_LLIST_FOREACH(p, ps->paths) {
2925 unsigned i;
2926 char *m;
2928 for (q = p->next; q; q = q->next)
2929 if (pa_streq(q->name, p->name))
2930 break;
2932 if (!q)
2933 continue;
2935 m = pa_xstrdup(p->name);
2937 /* OK, this name is not unique, hence let's rename */
2938 for (i = 1, q = p; q; q = q->next) {
2939 char *nn, *nd;
2941 if (!pa_streq(q->name, m))
2942 continue;
2944 nn = pa_sprintf_malloc("%s-%u", m, i);
2945 pa_xfree(q->name);
2946 q->name = nn;
2948 nd = pa_sprintf_malloc("%s %u", q->description, i);
2949 pa_xfree(q->description);
2950 q->description = nd;
2952 i++;
2955 pa_xfree(m);
2959 void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t ignore_dB) {
2960 pa_alsa_path *p, *n;
2962 pa_assert(ps);
2964 if (ps->probed)
2965 return;
2967 for (p = ps->paths; p; p = n) {
2968 n = p->next;
2970 if (pa_alsa_path_probe(p, m, ignore_dB) < 0) {
2971 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
2972 pa_alsa_path_free(p);
2976 path_set_unify(ps);
2977 path_set_make_paths_unique(ps);
2978 ps->probed = TRUE;
2981 static void mapping_free(pa_alsa_mapping *m) {
2982 pa_assert(m);
2984 pa_xfree(m->name);
2985 pa_xfree(m->description);
2987 pa_xstrfreev(m->device_strings);
2988 pa_xstrfreev(m->input_path_names);
2989 pa_xstrfreev(m->output_path_names);
2990 pa_xstrfreev(m->input_element);
2991 pa_xstrfreev(m->output_element);
2993 pa_assert(!m->input_pcm);
2994 pa_assert(!m->output_pcm);
2996 pa_xfree(m);
2999 static void profile_free(pa_alsa_profile *p) {
3000 pa_assert(p);
3002 pa_xfree(p->name);
3003 pa_xfree(p->description);
3005 pa_xstrfreev(p->input_mapping_names);
3006 pa_xstrfreev(p->output_mapping_names);
3008 if (p->input_mappings)
3009 pa_idxset_free(p->input_mappings, NULL, NULL);
3011 if (p->output_mappings)
3012 pa_idxset_free(p->output_mappings, NULL, NULL);
3014 pa_xfree(p);
3017 void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
3018 pa_assert(ps);
3020 if (ps->profiles) {
3021 pa_alsa_profile *p;
3023 while ((p = pa_hashmap_steal_first(ps->profiles)))
3024 profile_free(p);
3026 pa_hashmap_free(ps->profiles, NULL, NULL);
3029 if (ps->mappings) {
3030 pa_alsa_mapping *m;
3032 while ((m = pa_hashmap_steal_first(ps->mappings)))
3033 mapping_free(m);
3035 pa_hashmap_free(ps->mappings, NULL, NULL);
3038 if (ps->decibel_fixes) {
3039 pa_alsa_decibel_fix *db_fix;
3041 while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes)))
3042 decibel_fix_free(db_fix);
3044 pa_hashmap_free(ps->decibel_fixes, NULL, NULL);
3047 pa_xfree(ps);
3050 static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) {
3051 pa_alsa_mapping *m;
3053 if (!pa_startswith(name, "Mapping "))
3054 return NULL;
3056 name += 8;
3058 if ((m = pa_hashmap_get(ps->mappings, name)))
3059 return m;
3061 m = pa_xnew0(pa_alsa_mapping, 1);
3062 m->profile_set = ps;
3063 m->name = pa_xstrdup(name);
3064 pa_channel_map_init(&m->channel_map);
3066 pa_hashmap_put(ps->mappings, m->name, m);
3068 return m;
3071 static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
3072 pa_alsa_profile *p;
3074 if (!pa_startswith(name, "Profile "))
3075 return NULL;
3077 name += 8;
3079 if ((p = pa_hashmap_get(ps->profiles, name)))
3080 return p;
3082 p = pa_xnew0(pa_alsa_profile, 1);
3083 p->profile_set = ps;
3084 p->name = pa_xstrdup(name);
3086 pa_hashmap_put(ps->profiles, p->name, p);
3088 return p;
3091 static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
3092 pa_alsa_decibel_fix *db_fix;
3094 if (!pa_startswith(name, "DecibelFix "))
3095 return NULL;
3097 name += 11;
3099 if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
3100 return db_fix;
3102 db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
3103 db_fix->profile_set = ps;
3104 db_fix->name = pa_xstrdup(name);
3106 pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
3108 return db_fix;
3111 static int mapping_parse_device_strings(
3112 const char *filename,
3113 unsigned line,
3114 const char *section,
3115 const char *lvalue,
3116 const char *rvalue,
3117 void *data,
3118 void *userdata) {
3120 pa_alsa_profile_set *ps = userdata;
3121 pa_alsa_mapping *m;
3123 pa_assert(ps);
3125 if (!(m = mapping_get(ps, section))) {
3126 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3127 return -1;
3130 pa_xstrfreev(m->device_strings);
3131 if (!(m->device_strings = pa_split_spaces_strv(rvalue))) {
3132 pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section);
3133 return -1;
3136 return 0;
3139 static int mapping_parse_channel_map(
3140 const char *filename,
3141 unsigned line,
3142 const char *section,
3143 const char *lvalue,
3144 const char *rvalue,
3145 void *data,
3146 void *userdata) {
3148 pa_alsa_profile_set *ps = userdata;
3149 pa_alsa_mapping *m;
3151 pa_assert(ps);
3153 if (!(m = mapping_get(ps, section))) {
3154 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3155 return -1;
3158 if (!(pa_channel_map_parse(&m->channel_map, rvalue))) {
3159 pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section);
3160 return -1;
3163 return 0;
3166 static int mapping_parse_paths(
3167 const char *filename,
3168 unsigned line,
3169 const char *section,
3170 const char *lvalue,
3171 const char *rvalue,
3172 void *data,
3173 void *userdata) {
3175 pa_alsa_profile_set *ps = userdata;
3176 pa_alsa_mapping *m;
3178 pa_assert(ps);
3180 if (!(m = mapping_get(ps, section))) {
3181 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3182 return -1;
3185 if (pa_streq(lvalue, "paths-input")) {
3186 pa_xstrfreev(m->input_path_names);
3187 m->input_path_names = pa_split_spaces_strv(rvalue);
3188 } else {
3189 pa_xstrfreev(m->output_path_names);
3190 m->output_path_names = pa_split_spaces_strv(rvalue);
3193 return 0;
3196 static int mapping_parse_element(
3197 const char *filename,
3198 unsigned line,
3199 const char *section,
3200 const char *lvalue,
3201 const char *rvalue,
3202 void *data,
3203 void *userdata) {
3205 pa_alsa_profile_set *ps = userdata;
3206 pa_alsa_mapping *m;
3208 pa_assert(ps);
3210 if (!(m = mapping_get(ps, section))) {
3211 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3212 return -1;
3215 if (pa_streq(lvalue, "element-input")) {
3216 pa_xstrfreev(m->input_element);
3217 m->input_element = pa_split_spaces_strv(rvalue);
3218 } else {
3219 pa_xstrfreev(m->output_element);
3220 m->output_element = pa_split_spaces_strv(rvalue);
3223 return 0;
3226 static int mapping_parse_direction(
3227 const char *filename,
3228 unsigned line,
3229 const char *section,
3230 const char *lvalue,
3231 const char *rvalue,
3232 void *data,
3233 void *userdata) {
3235 pa_alsa_profile_set *ps = userdata;
3236 pa_alsa_mapping *m;
3238 pa_assert(ps);
3240 if (!(m = mapping_get(ps, section))) {
3241 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3242 return -1;
3245 if (pa_streq(rvalue, "input"))
3246 m->direction = PA_ALSA_DIRECTION_INPUT;
3247 else if (pa_streq(rvalue, "output"))
3248 m->direction = PA_ALSA_DIRECTION_OUTPUT;
3249 else if (pa_streq(rvalue, "any"))
3250 m->direction = PA_ALSA_DIRECTION_ANY;
3251 else {
3252 pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue);
3253 return -1;
3256 return 0;
3259 static int mapping_parse_description(
3260 const char *filename,
3261 unsigned line,
3262 const char *section,
3263 const char *lvalue,
3264 const char *rvalue,
3265 void *data,
3266 void *userdata) {
3268 pa_alsa_profile_set *ps = userdata;
3269 pa_alsa_profile *p;
3270 pa_alsa_mapping *m;
3272 pa_assert(ps);
3274 if ((m = mapping_get(ps, section))) {
3275 pa_xfree(m->description);
3276 m->description = pa_xstrdup(rvalue);
3277 } else if ((p = profile_get(ps, section))) {
3278 pa_xfree(p->description);
3279 p->description = pa_xstrdup(rvalue);
3280 } else {
3281 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3282 return -1;
3285 return 0;
3288 static int mapping_parse_priority(
3289 const char *filename,
3290 unsigned line,
3291 const char *section,
3292 const char *lvalue,
3293 const char *rvalue,
3294 void *data,
3295 void *userdata) {
3297 pa_alsa_profile_set *ps = userdata;
3298 pa_alsa_profile *p;
3299 pa_alsa_mapping *m;
3300 uint32_t prio;
3302 pa_assert(ps);
3304 if (pa_atou(rvalue, &prio) < 0) {
3305 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
3306 return -1;
3309 if ((m = mapping_get(ps, section)))
3310 m->priority = prio;
3311 else if ((p = profile_get(ps, section)))
3312 p->priority = prio;
3313 else {
3314 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3315 return -1;
3318 return 0;
3321 static int profile_parse_mappings(
3322 const char *filename,
3323 unsigned line,
3324 const char *section,
3325 const char *lvalue,
3326 const char *rvalue,
3327 void *data,
3328 void *userdata) {
3330 pa_alsa_profile_set *ps = userdata;
3331 pa_alsa_profile *p;
3333 pa_assert(ps);
3335 if (!(p = profile_get(ps, section))) {
3336 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3337 return -1;
3340 if (pa_streq(lvalue, "input-mappings")) {
3341 pa_xstrfreev(p->input_mapping_names);
3342 p->input_mapping_names = pa_split_spaces_strv(rvalue);
3343 } else {
3344 pa_xstrfreev(p->output_mapping_names);
3345 p->output_mapping_names = pa_split_spaces_strv(rvalue);
3348 return 0;
3351 static int profile_parse_skip_probe(
3352 const char *filename,
3353 unsigned line,
3354 const char *section,
3355 const char *lvalue,
3356 const char *rvalue,
3357 void *data,
3358 void *userdata) {
3360 pa_alsa_profile_set *ps = userdata;
3361 pa_alsa_profile *p;
3362 int b;
3364 pa_assert(ps);
3366 if (!(p = profile_get(ps, section))) {
3367 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3368 return -1;
3371 if ((b = pa_parse_boolean(rvalue)) < 0) {
3372 pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section);
3373 return -1;
3376 p->supported = b;
3378 return 0;
3381 static int decibel_fix_parse_db_values(
3382 const char *filename,
3383 unsigned line,
3384 const char *section,
3385 const char *lvalue,
3386 const char *rvalue,
3387 void *data,
3388 void *userdata) {
3390 pa_alsa_profile_set *ps = userdata;
3391 pa_alsa_decibel_fix *db_fix;
3392 char **items;
3393 char *item;
3394 long *db_values;
3395 unsigned n = 8; /* Current size of the db_values table. */
3396 unsigned min_step = 0;
3397 unsigned max_step = 0;
3398 unsigned i = 0; /* Index to the items table. */
3399 unsigned prev_step = 0;
3400 double prev_db = 0;
3402 pa_assert(filename);
3403 pa_assert(section);
3404 pa_assert(lvalue);
3405 pa_assert(rvalue);
3406 pa_assert(ps);
3408 if (!(db_fix = decibel_fix_get(ps, section))) {
3409 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3410 return -1;
3413 if (!(items = pa_split_spaces_strv(rvalue))) {
3414 pa_log("[%s:%u] Value missing", pa_strnull(filename), line);
3415 return -1;
3418 db_values = pa_xnew(long, n);
3420 while ((item = items[i++])) {
3421 char *s = item; /* Step value string. */
3422 char *d = item; /* dB value string. */
3423 uint32_t step;
3424 double db;
3426 /* Move d forward until it points to a colon or to the end of the item. */
3427 for (; *d && *d != ':'; ++d);
3429 if (d == s) {
3430 /* item started with colon. */
3431 pa_log("[%s:%u] No step value found in %s", filename, line, item);
3432 goto fail;
3435 if (!*d || !*(d + 1)) {
3436 /* No colon found, or it was the last character in item. */
3437 pa_log("[%s:%u] No dB value found in %s", filename, line, item);
3438 goto fail;
3441 /* pa_atou() needs a null-terminating string. Let's replace the colon
3442 * with a zero byte. */
3443 *d++ = '\0';
3445 if (pa_atou(s, &step) < 0) {
3446 pa_log("[%s:%u] Invalid step value: %s", filename, line, s);
3447 goto fail;
3450 if (pa_atod(d, &db) < 0) {
3451 pa_log("[%s:%u] Invalid dB value: %s", filename, line, d);
3452 goto fail;
3455 if (step <= prev_step && i != 1) {
3456 pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step);
3457 goto fail;
3460 if (db < prev_db && i != 1) {
3461 pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db);
3462 goto fail;
3465 if (i == 1) {
3466 min_step = step;
3467 db_values[0] = (long) (db * 100.0);
3468 prev_step = step;
3469 prev_db = db;
3470 } else {
3471 /* Interpolate linearly. */
3472 double db_increment = (db - prev_db) / (step - prev_step);
3474 for (; prev_step < step; ++prev_step, prev_db += db_increment) {
3476 /* Reallocate the db_values table if it's about to overflow. */
3477 if (prev_step + 1 - min_step == n) {
3478 n *= 2;
3479 db_values = pa_xrenew(long, db_values, n);
3482 db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0);
3486 max_step = step;
3489 db_fix->min_step = min_step;
3490 db_fix->max_step = max_step;
3491 pa_xfree(db_fix->db_values);
3492 db_fix->db_values = db_values;
3494 pa_xstrfreev(items);
3496 return 0;
3498 fail:
3499 pa_xstrfreev(items);
3500 pa_xfree(db_values);
3502 return -1;
3505 static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
3507 static const struct description_map well_known_descriptions[] = {
3508 { "analog-mono", N_("Analog Mono") },
3509 { "analog-stereo", N_("Analog Stereo") },
3510 { "analog-surround-21", N_("Analog Surround 2.1") },
3511 { "analog-surround-30", N_("Analog Surround 3.0") },
3512 { "analog-surround-31", N_("Analog Surround 3.1") },
3513 { "analog-surround-40", N_("Analog Surround 4.0") },
3514 { "analog-surround-41", N_("Analog Surround 4.1") },
3515 { "analog-surround-50", N_("Analog Surround 5.0") },
3516 { "analog-surround-51", N_("Analog Surround 5.1") },
3517 { "analog-surround-61", N_("Analog Surround 6.0") },
3518 { "analog-surround-61", N_("Analog Surround 6.1") },
3519 { "analog-surround-70", N_("Analog Surround 7.0") },
3520 { "analog-surround-71", N_("Analog Surround 7.1") },
3521 { "iec958-stereo", N_("Digital Stereo (IEC958)") },
3522 { "iec958-passthrough", N_("Digital Passthrough (IEC958)") },
3523 { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") },
3524 { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") },
3525 { "hdmi-stereo", N_("Digital Stereo (HDMI)") }
3528 pa_assert(m);
3530 if (!pa_channel_map_valid(&m->channel_map)) {
3531 pa_log("Mapping %s is missing channel map.", m->name);
3532 return -1;
3535 if (!m->device_strings) {
3536 pa_log("Mapping %s is missing device strings.", m->name);
3537 return -1;
3540 if ((m->input_path_names && m->input_element) ||
3541 (m->output_path_names && m->output_element)) {
3542 pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name);
3543 return -1;
3546 if (!m->description)
3547 m->description = pa_xstrdup(lookup_description(m->name,
3548 well_known_descriptions,
3549 PA_ELEMENTSOF(well_known_descriptions)));
3551 if (!m->description)
3552 m->description = pa_xstrdup(m->name);
3554 if (bonus) {
3555 if (pa_channel_map_equal(&m->channel_map, bonus))
3556 m->priority += 50;
3557 else if (m->channel_map.channels == bonus->channels)
3558 m->priority += 30;
3561 return 0;
3564 void pa_alsa_mapping_dump(pa_alsa_mapping *m) {
3565 char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
3567 pa_assert(m);
3569 pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i",
3570 m->name,
3571 pa_strnull(m->description),
3572 m->priority,
3573 pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map),
3574 pa_yes_no(m->supported),
3575 m->direction);
3578 static void profile_set_add_auto_pair(
3579 pa_alsa_profile_set *ps,
3580 pa_alsa_mapping *m, /* output */
3581 pa_alsa_mapping *n /* input */) {
3583 char *name;
3584 pa_alsa_profile *p;
3586 pa_assert(ps);
3587 pa_assert(m || n);
3589 if (m && m->direction == PA_ALSA_DIRECTION_INPUT)
3590 return;
3592 if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT)
3593 return;
3595 if (m && n)
3596 name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name);
3597 else if (m)
3598 name = pa_sprintf_malloc("output:%s", m->name);
3599 else
3600 name = pa_sprintf_malloc("input:%s", n->name);
3602 if (pa_hashmap_get(ps->profiles, name)) {
3603 pa_xfree(name);
3604 return;
3607 p = pa_xnew0(pa_alsa_profile, 1);
3608 p->profile_set = ps;
3609 p->name = name;
3611 if (m) {
3612 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3613 pa_idxset_put(p->output_mappings, m, NULL);
3614 p->priority += m->priority * 100;
3617 if (n) {
3618 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3619 pa_idxset_put(p->input_mappings, n, NULL);
3620 p->priority += n->priority;
3623 pa_hashmap_put(ps->profiles, p->name, p);
3626 static void profile_set_add_auto(pa_alsa_profile_set *ps) {
3627 pa_alsa_mapping *m, *n;
3628 void *m_state, *n_state;
3630 pa_assert(ps);
3632 PA_HASHMAP_FOREACH(m, ps->mappings, m_state) {
3633 profile_set_add_auto_pair(ps, m, NULL);
3635 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
3636 profile_set_add_auto_pair(ps, m, n);
3639 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
3640 profile_set_add_auto_pair(ps, NULL, n);
3643 static int profile_verify(pa_alsa_profile *p) {
3645 static const struct description_map well_known_descriptions[] = {
3646 { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") },
3647 { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") },
3648 { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") },
3649 { "off", N_("Off") }
3652 pa_assert(p);
3654 /* Replace the output mapping names by the actual mappings */
3655 if (p->output_mapping_names) {
3656 char **name;
3658 pa_assert(!p->output_mappings);
3659 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3661 for (name = p->output_mapping_names; *name; name++) {
3662 pa_alsa_mapping *m;
3663 char **in;
3664 pa_bool_t duplicate = FALSE;
3666 for (in = name + 1; *in; in++)
3667 if (pa_streq(*name, *in)) {
3668 duplicate = TRUE;
3669 break;
3672 if (duplicate)
3673 continue;
3675 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) {
3676 pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name);
3677 return -1;
3680 pa_idxset_put(p->output_mappings, m, NULL);
3682 if (p->supported)
3683 m->supported++;
3686 pa_xstrfreev(p->output_mapping_names);
3687 p->output_mapping_names = NULL;
3690 /* Replace the input mapping names by the actual mappings */
3691 if (p->input_mapping_names) {
3692 char **name;
3694 pa_assert(!p->input_mappings);
3695 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3697 for (name = p->input_mapping_names; *name; name++) {
3698 pa_alsa_mapping *m;
3699 char **in;
3700 pa_bool_t duplicate = FALSE;
3702 for (in = name + 1; *in; in++)
3703 if (pa_streq(*name, *in)) {
3704 duplicate = TRUE;
3705 break;
3708 if (duplicate)
3709 continue;
3711 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) {
3712 pa_log("Profile '%s' refers to unexistant mapping '%s'.", p->name, *name);
3713 return -1;
3716 pa_idxset_put(p->input_mappings, m, NULL);
3718 if (p->supported)
3719 m->supported++;
3722 pa_xstrfreev(p->input_mapping_names);
3723 p->input_mapping_names = NULL;
3726 if (!p->input_mappings && !p->output_mappings) {
3727 pa_log("Profile '%s' lacks mappings.", p->name);
3728 return -1;
3731 if (!p->description)
3732 p->description = pa_xstrdup(lookup_description(p->name,
3733 well_known_descriptions,
3734 PA_ELEMENTSOF(well_known_descriptions)));
3736 if (!p->description) {
3737 pa_strbuf *sb;
3738 uint32_t idx;
3739 pa_alsa_mapping *m;
3741 sb = pa_strbuf_new();
3743 if (p->output_mappings)
3744 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
3745 if (!pa_strbuf_isempty(sb))
3746 pa_strbuf_puts(sb, " + ");
3748 pa_strbuf_printf(sb, _("%s Output"), m->description);
3751 if (p->input_mappings)
3752 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
3753 if (!pa_strbuf_isempty(sb))
3754 pa_strbuf_puts(sb, " + ");
3756 pa_strbuf_printf(sb, _("%s Input"), m->description);
3759 p->description = pa_strbuf_tostring_free(sb);
3762 return 0;
3765 void pa_alsa_profile_dump(pa_alsa_profile *p) {
3766 uint32_t idx;
3767 pa_alsa_mapping *m;
3768 pa_assert(p);
3770 pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u",
3771 p->name,
3772 pa_strnull(p->description),
3773 p->priority,
3774 pa_yes_no(p->supported),
3775 p->input_mappings ? pa_idxset_size(p->input_mappings) : 0,
3776 p->output_mappings ? pa_idxset_size(p->output_mappings) : 0);
3778 if (p->input_mappings)
3779 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
3780 pa_log_debug("Input %s", m->name);
3782 if (p->output_mappings)
3783 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
3784 pa_log_debug("Output %s", m->name);
3787 static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) {
3788 pa_assert(db_fix);
3790 /* Check that the dB mapping has been configured. Since "db-values" is
3791 * currently the only option in the DecibelFix section, and decibel fix
3792 * objects don't get created if a DecibelFix section is empty, this is
3793 * actually a redundant check. Having this may prevent future bugs,
3794 * however. */
3795 if (!db_fix->db_values) {
3796 pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name);
3797 return -1;
3800 return 0;
3803 void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) {
3804 char *db_values = NULL;
3806 pa_assert(db_fix);
3808 if (db_fix->db_values) {
3809 pa_strbuf *buf;
3810 unsigned long i, nsteps;
3812 pa_assert(db_fix->min_step <= db_fix->max_step);
3813 nsteps = db_fix->max_step - db_fix->min_step + 1;
3815 buf = pa_strbuf_new();
3816 for (i = 0; i < nsteps; ++i)
3817 pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0);
3819 db_values = pa_strbuf_tostring_free(buf);
3822 pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s",
3823 db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values));
3825 pa_xfree(db_values);
3828 pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) {
3829 pa_alsa_profile_set *ps;
3830 pa_alsa_profile *p;
3831 pa_alsa_mapping *m;
3832 pa_alsa_decibel_fix *db_fix;
3833 char *fn;
3834 int r;
3835 void *state;
3837 static pa_config_item items[] = {
3838 /* [General] */
3839 { "auto-profiles", pa_config_parse_bool, NULL, "General" },
3841 /* [Mapping ...] */
3842 { "device-strings", mapping_parse_device_strings, NULL, NULL },
3843 { "channel-map", mapping_parse_channel_map, NULL, NULL },
3844 { "paths-input", mapping_parse_paths, NULL, NULL },
3845 { "paths-output", mapping_parse_paths, NULL, NULL },
3846 { "element-input", mapping_parse_element, NULL, NULL },
3847 { "element-output", mapping_parse_element, NULL, NULL },
3848 { "direction", mapping_parse_direction, NULL, NULL },
3850 /* Shared by [Mapping ...] and [Profile ...] */
3851 { "description", mapping_parse_description, NULL, NULL },
3852 { "priority", mapping_parse_priority, NULL, NULL },
3854 /* [Profile ...] */
3855 { "input-mappings", profile_parse_mappings, NULL, NULL },
3856 { "output-mappings", profile_parse_mappings, NULL, NULL },
3857 { "skip-probe", profile_parse_skip_probe, NULL, NULL },
3859 /* [DecibelFix ...] */
3860 { "db-values", decibel_fix_parse_db_values, NULL, NULL },
3861 { NULL, NULL, NULL, NULL }
3864 ps = pa_xnew0(pa_alsa_profile_set, 1);
3865 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
3866 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
3867 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
3869 items[0].data = &ps->auto_profiles;
3871 if (!fname)
3872 fname = "default.conf";
3874 fn = pa_maybe_prefix_path(fname,
3875 pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
3876 PA_ALSA_PROFILE_SETS_DIR);
3878 r = pa_config_parse(fn, NULL, items, ps);
3879 pa_xfree(fn);
3881 if (r < 0)
3882 goto fail;
3884 PA_HASHMAP_FOREACH(m, ps->mappings, state)
3885 if (mapping_verify(m, bonus) < 0)
3886 goto fail;
3888 if (ps->auto_profiles)
3889 profile_set_add_auto(ps);
3891 PA_HASHMAP_FOREACH(p, ps->profiles, state)
3892 if (profile_verify(p) < 0)
3893 goto fail;
3895 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
3896 if (decibel_fix_verify(db_fix) < 0)
3897 goto fail;
3899 return ps;
3901 fail:
3902 pa_alsa_profile_set_free(ps);
3903 return NULL;
3906 void pa_alsa_profile_set_probe(
3907 pa_alsa_profile_set *ps,
3908 const char *dev_id,
3909 const pa_sample_spec *ss,
3910 unsigned default_n_fragments,
3911 unsigned default_fragment_size_msec) {
3913 void *state;
3914 pa_alsa_profile *p, *last = NULL;
3915 pa_alsa_mapping *m;
3917 pa_assert(ps);
3918 pa_assert(dev_id);
3919 pa_assert(ss);
3921 if (ps->probed)
3922 return;
3924 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
3925 pa_sample_spec try_ss;
3926 pa_channel_map try_map;
3927 snd_pcm_uframes_t try_period_size, try_buffer_size;
3928 uint32_t idx;
3930 /* Is this already marked that it is supported? (i.e. from the config file) */
3931 if (p->supported)
3932 continue;
3934 pa_log_debug("Looking at profile %s", p->name);
3936 /* Close PCMs from the last iteration we don't need anymore */
3937 if (last && last->output_mappings)
3938 PA_IDXSET_FOREACH(m, last->output_mappings, idx) {
3940 if (!m->output_pcm)
3941 break;
3943 if (last->supported)
3944 m->supported++;
3946 if (!p->output_mappings || !pa_idxset_get_by_data(p->output_mappings, m, NULL)) {
3947 snd_pcm_close(m->output_pcm);
3948 m->output_pcm = NULL;
3952 if (last && last->input_mappings)
3953 PA_IDXSET_FOREACH(m, last->input_mappings, idx) {
3955 if (!m->input_pcm)
3956 break;
3958 if (last->supported)
3959 m->supported++;
3961 if (!p->input_mappings || !pa_idxset_get_by_data(p->input_mappings, m, NULL)) {
3962 snd_pcm_close(m->input_pcm);
3963 m->input_pcm = NULL;
3967 p->supported = TRUE;
3969 /* Check if we can open all new ones */
3970 if (p->output_mappings)
3971 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
3973 if (m->output_pcm)
3974 continue;
3976 pa_log_debug("Checking for playback on %s (%s)", m->description, m->name);
3977 try_map = m->channel_map;
3978 try_ss = *ss;
3979 try_ss.channels = try_map.channels;
3981 try_period_size =
3982 pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
3983 pa_frame_size(&try_ss);
3984 try_buffer_size = default_n_fragments * try_period_size;
3986 if (!(m ->output_pcm = pa_alsa_open_by_template(
3987 m->device_strings,
3988 dev_id,
3989 NULL,
3990 &try_ss, &try_map,
3991 SND_PCM_STREAM_PLAYBACK,
3992 &try_period_size, &try_buffer_size, 0, NULL, NULL,
3993 TRUE))) {
3994 p->supported = FALSE;
3995 break;
3999 if (p->input_mappings && p->supported)
4000 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
4002 if (m->input_pcm)
4003 continue;
4005 pa_log_debug("Checking for recording on %s (%s)", m->description, m->name);
4006 try_map = m->channel_map;
4007 try_ss = *ss;
4008 try_ss.channels = try_map.channels;
4010 try_period_size =
4011 pa_usec_to_bytes(default_fragment_size_msec*PA_USEC_PER_MSEC, &try_ss) /
4012 pa_frame_size(&try_ss);
4013 try_buffer_size = default_n_fragments * try_period_size;
4015 if (!(m ->input_pcm = pa_alsa_open_by_template(
4016 m->device_strings,
4017 dev_id,
4018 NULL,
4019 &try_ss, &try_map,
4020 SND_PCM_STREAM_CAPTURE,
4021 &try_period_size, &try_buffer_size, 0, NULL, NULL,
4022 TRUE))) {
4023 p->supported = FALSE;
4024 break;
4028 last = p;
4030 if (p->supported)
4031 pa_log_debug("Profile %s supported.", p->name);
4034 /* Clean up */
4035 if (last) {
4036 uint32_t idx;
4038 if (last->output_mappings)
4039 PA_IDXSET_FOREACH(m, last->output_mappings, idx)
4040 if (m->output_pcm) {
4042 if (last->supported)
4043 m->supported++;
4045 snd_pcm_close(m->output_pcm);
4046 m->output_pcm = NULL;
4049 if (last->input_mappings)
4050 PA_IDXSET_FOREACH(m, last->input_mappings, idx)
4051 if (m->input_pcm) {
4053 if (last->supported)
4054 m->supported++;
4056 snd_pcm_close(m->input_pcm);
4057 m->input_pcm = NULL;
4061 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4062 if (!p->supported) {
4063 pa_hashmap_remove(ps->profiles, p->name);
4064 profile_free(p);
4067 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4068 if (m->supported <= 0) {
4069 pa_hashmap_remove(ps->mappings, m->name);
4070 mapping_free(m);
4073 ps->probed = TRUE;
4076 void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
4077 pa_alsa_profile *p;
4078 pa_alsa_mapping *m;
4079 pa_alsa_decibel_fix *db_fix;
4080 void *state;
4082 pa_assert(ps);
4084 pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u",
4085 (void*)
4087 pa_yes_no(ps->auto_profiles),
4088 pa_yes_no(ps->probed),
4089 pa_hashmap_size(ps->mappings),
4090 pa_hashmap_size(ps->profiles),
4091 pa_hashmap_size(ps->decibel_fixes));
4093 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4094 pa_alsa_mapping_dump(m);
4096 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4097 pa_alsa_profile_dump(p);
4099 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
4100 pa_alsa_decibel_fix_dump(db_fix);
4103 void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) {
4104 pa_alsa_path *path;
4106 pa_assert(p);
4107 pa_assert(!*p);
4108 pa_assert(ps);
4110 /* if there is no path, we don't want a port list */
4111 if (!ps->paths)
4112 return;
4114 if (!ps->paths->next){
4115 pa_alsa_setting *s;
4117 /* If there is only one path, but no or only one setting, then
4118 * we want a port list either */
4119 if (!ps->paths->settings || !ps->paths->settings->next)
4120 return;
4122 /* Ok, there is only one path, however with multiple settings,
4123 * so let's create a port for each setting */
4124 *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4126 PA_LLIST_FOREACH(s, ps->paths->settings) {
4127 pa_device_port *port;
4128 pa_alsa_port_data *data;
4130 port = pa_device_port_new(s->name, s->description, sizeof(pa_alsa_port_data));
4131 port->priority = s->priority;
4133 data = PA_DEVICE_PORT_DATA(port);
4134 data->path = ps->paths;
4135 data->setting = s;
4137 pa_hashmap_put(*p, port->name, port);
4140 } else {
4142 /* We have multiple paths, so let's create a port for each
4143 * one, and each of each settings */
4144 *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4146 PA_LLIST_FOREACH(path, ps->paths) {
4148 if (!path->settings || !path->settings->next) {
4149 pa_device_port *port;
4150 pa_alsa_port_data *data;
4152 /* If there is no or just one setting we only need a
4153 * single entry */
4155 port = pa_device_port_new(path->name, path->description, sizeof(pa_alsa_port_data));
4156 port->priority = path->priority * 100;
4159 data = PA_DEVICE_PORT_DATA(port);
4160 data->path = path;
4161 data->setting = path->settings;
4163 pa_hashmap_put(*p, port->name, port);
4164 } else {
4165 pa_alsa_setting *s;
4167 PA_LLIST_FOREACH(s, path->settings) {
4168 pa_device_port *port;
4169 pa_alsa_port_data *data;
4170 char *n, *d;
4172 n = pa_sprintf_malloc("%s;%s", path->name, s->name);
4174 if (s->description[0])
4175 d = pa_sprintf_malloc(_("%s / %s"), path->description, s->description);
4176 else
4177 d = pa_xstrdup(path->description);
4179 port = pa_device_port_new(n, d, sizeof(pa_alsa_port_data));
4180 port->priority = path->priority * 100 + s->priority;
4182 pa_xfree(n);
4183 pa_xfree(d);
4185 data = PA_DEVICE_PORT_DATA(port);
4186 data->path = path;
4187 data->setting = s;
4189 pa_hashmap_put(*p, port->name, port);
4195 pa_log_debug("Added %u ports", pa_hashmap_size(*p));