alsa: Add support for sound cards with 4-channel input.
[pulseaudio-raopUDP/pulseaudio-raop-alac.git] / src / modules / alsa / alsa-mixer.c
blob8b54f751d115790afa19b05ed6fda3dcf770add2
1 /***
2 This file is part of PulseAudio.
4 Copyright 2004-2009 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <sys/types.h>
28 #include <asoundlib.h>
29 #include <math.h>
31 #ifdef HAVE_VALGRIND_MEMCHECK_H
32 #include <valgrind/memcheck.h>
33 #endif
35 #include <pulse/mainloop-api.h>
36 #include <pulse/sample.h>
37 #include <pulse/timeval.h>
38 #include <pulse/util.h>
39 #include <pulse/volume.h>
40 #include <pulse/xmalloc.h>
41 #include <pulse/utf8.h>
43 #include <pulsecore/i18n.h>
44 #include <pulsecore/log.h>
45 #include <pulsecore/macro.h>
46 #include <pulsecore/core-util.h>
47 #include <pulsecore/conf-parser.h>
48 #include <pulsecore/strbuf.h>
50 #include "alsa-mixer.h"
51 #include "alsa-util.h"
53 struct description_map {
54 const char *name;
55 const char *description;
58 static const char *lookup_description(const char *name, const struct description_map dm[], unsigned n) {
59 unsigned i;
61 for (i = 0; i < n; i++)
62 if (pa_streq(dm[i].name, name))
63 return _(dm[i].description);
65 return NULL;
68 struct pa_alsa_fdlist {
69 unsigned num_fds;
70 struct pollfd *fds;
71 /* This is a temporary buffer used to avoid lots of mallocs */
72 struct pollfd *work_fds;
74 snd_mixer_t *mixer;
75 snd_hctl_t *hctl;
77 pa_mainloop_api *m;
78 pa_defer_event *defer;
79 pa_io_event **ios;
81 pa_bool_t polled;
83 void (*cb)(void *userdata);
84 void *userdata;
87 static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
89 struct pa_alsa_fdlist *fdl = userdata;
90 int err;
91 unsigned i;
92 unsigned short revents;
94 pa_assert(a);
95 pa_assert(fdl);
96 pa_assert(fdl->mixer || fdl->hctl);
97 pa_assert(fdl->fds);
98 pa_assert(fdl->work_fds);
100 if (fdl->polled)
101 return;
103 fdl->polled = TRUE;
105 memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
107 for (i = 0; i < fdl->num_fds; i++) {
108 if (e == fdl->ios[i]) {
109 if (events & PA_IO_EVENT_INPUT)
110 fdl->work_fds[i].revents |= POLLIN;
111 if (events & PA_IO_EVENT_OUTPUT)
112 fdl->work_fds[i].revents |= POLLOUT;
113 if (events & PA_IO_EVENT_ERROR)
114 fdl->work_fds[i].revents |= POLLERR;
115 if (events & PA_IO_EVENT_HANGUP)
116 fdl->work_fds[i].revents |= POLLHUP;
117 break;
121 pa_assert(i != fdl->num_fds);
123 if (fdl->hctl)
124 err = snd_hctl_poll_descriptors_revents(fdl->hctl, fdl->work_fds, fdl->num_fds, &revents);
125 else
126 err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents);
128 if (err < 0) {
129 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
130 return;
133 a->defer_enable(fdl->defer, 1);
135 if (revents) {
136 if (fdl->hctl)
137 snd_hctl_handle_events(fdl->hctl);
138 else
139 snd_mixer_handle_events(fdl->mixer);
143 static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) {
144 struct pa_alsa_fdlist *fdl = userdata;
145 unsigned num_fds, i;
146 int err, n;
147 struct pollfd *temp;
149 pa_assert(a);
150 pa_assert(fdl);
151 pa_assert(fdl->mixer || fdl->hctl);
153 a->defer_enable(fdl->defer, 0);
155 if (fdl->hctl)
156 n = snd_hctl_poll_descriptors_count(fdl->hctl);
157 else
158 n = snd_mixer_poll_descriptors_count(fdl->mixer);
160 if (n < 0) {
161 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
162 return;
164 num_fds = (unsigned) n;
166 if (num_fds != fdl->num_fds) {
167 if (fdl->fds)
168 pa_xfree(fdl->fds);
169 if (fdl->work_fds)
170 pa_xfree(fdl->work_fds);
171 fdl->fds = pa_xnew0(struct pollfd, num_fds);
172 fdl->work_fds = pa_xnew(struct pollfd, num_fds);
175 memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
177 if (fdl->hctl)
178 err = snd_hctl_poll_descriptors(fdl->hctl, fdl->work_fds, num_fds);
179 else
180 err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds);
182 if (err < 0) {
183 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
184 return;
187 fdl->polled = FALSE;
189 if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0)
190 return;
192 if (fdl->ios) {
193 for (i = 0; i < fdl->num_fds; i++)
194 a->io_free(fdl->ios[i]);
196 if (num_fds != fdl->num_fds) {
197 pa_xfree(fdl->ios);
198 fdl->ios = NULL;
202 if (!fdl->ios)
203 fdl->ios = pa_xnew(pa_io_event*, num_fds);
205 /* Swap pointers */
206 temp = fdl->work_fds;
207 fdl->work_fds = fdl->fds;
208 fdl->fds = temp;
210 fdl->num_fds = num_fds;
212 for (i = 0;i < num_fds;i++)
213 fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
214 ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
215 ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
216 io_cb, fdl);
219 struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
220 struct pa_alsa_fdlist *fdl;
222 fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
224 return fdl;
227 void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
228 pa_assert(fdl);
230 if (fdl->defer) {
231 pa_assert(fdl->m);
232 fdl->m->defer_free(fdl->defer);
235 if (fdl->ios) {
236 unsigned i;
237 pa_assert(fdl->m);
238 for (i = 0; i < fdl->num_fds; i++)
239 fdl->m->io_free(fdl->ios[i]);
240 pa_xfree(fdl->ios);
243 if (fdl->fds)
244 pa_xfree(fdl->fds);
245 if (fdl->work_fds)
246 pa_xfree(fdl->work_fds);
248 pa_xfree(fdl);
251 /* We can listen to either a snd_hctl_t or a snd_mixer_t, but not both */
252 int pa_alsa_fdlist_set_handle(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api *m) {
253 pa_assert(fdl);
254 pa_assert(hctl_handle || mixer_handle);
255 pa_assert(!(hctl_handle && mixer_handle));
256 pa_assert(m);
257 pa_assert(!fdl->m);
259 fdl->hctl = hctl_handle;
260 fdl->mixer = mixer_handle;
261 fdl->m = m;
262 fdl->defer = m->defer_new(m, defer_cb, fdl);
264 return 0;
267 struct pa_alsa_mixer_pdata {
268 pa_rtpoll *rtpoll;
269 pa_rtpoll_item *poll_item;
270 snd_mixer_t *mixer;
274 struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) {
275 struct pa_alsa_mixer_pdata *pd;
277 pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1);
279 return pd;
282 void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) {
283 pa_assert(pd);
285 if (pd->poll_item) {
286 pa_rtpoll_item_free(pd->poll_item);
289 pa_xfree(pd);
292 static int rtpoll_work_cb(pa_rtpoll_item *i) {
293 struct pa_alsa_mixer_pdata *pd;
294 struct pollfd *p;
295 unsigned n_fds;
296 unsigned short revents = 0;
297 int err, ret = 0;
299 pd = pa_rtpoll_item_get_userdata(i);
300 pa_assert_fp(pd);
301 pa_assert_fp(i == pd->poll_item);
303 p = pa_rtpoll_item_get_pollfd(i, &n_fds);
305 if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) {
306 pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
307 ret = -1;
308 goto fail;
311 if (revents) {
312 if (revents & (POLLNVAL | POLLERR)) {
313 pa_log_debug("Device disconnected, stopping poll on mixer");
314 goto fail;
315 } else if (revents & POLLERR) {
316 /* This shouldn't happen. */
317 pa_log_error("Got a POLLERR (revents = %04x), stopping poll on mixer", revents);
318 goto fail;
321 err = snd_mixer_handle_events(pd->mixer);
323 if (PA_LIKELY(err >= 0)) {
324 pa_rtpoll_item_free(i);
325 pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll);
326 } else {
327 pa_log_error("Error handling mixer event: %s", pa_alsa_strerror(err));
328 ret = -1;
329 goto fail;
333 return ret;
335 fail:
336 pa_rtpoll_item_free(i);
338 pd->poll_item = NULL;
339 pd->rtpoll = NULL;
340 pd->mixer = NULL;
342 return ret;
345 int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) {
346 pa_rtpoll_item *i;
347 struct pollfd *p;
348 int err, n;
350 pa_assert(pd);
351 pa_assert(mixer);
352 pa_assert(rtp);
354 if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) {
355 pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
356 return -1;
359 i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n);
361 p = pa_rtpoll_item_get_pollfd(i, NULL);
363 memset(p, 0, sizeof(struct pollfd) * n);
365 if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) {
366 pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
367 pa_rtpoll_item_free(i);
368 return -1;
371 pd->rtpoll = rtp;
372 pd->poll_item = i;
373 pd->mixer = mixer;
375 pa_rtpoll_item_set_userdata(i, pd);
376 pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
378 return 0;
383 static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
384 [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
386 [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER,
387 [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT,
388 [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT,
390 [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER,
391 [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT,
392 [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT,
394 [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER,
396 [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
397 [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
399 [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT,
400 [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT,
402 [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN,
403 [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN,
404 [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN,
405 [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN,
406 [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN,
407 [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN,
408 [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN,
409 [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN,
410 [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN,
411 [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN,
412 [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN,
413 [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN,
414 [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN,
415 [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN,
416 [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN,
417 [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN,
418 [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN,
419 [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN,
420 [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN,
421 [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN,
422 [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN,
423 [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN,
424 [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN,
425 [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN,
426 [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN,
427 [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN,
428 [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN,
429 [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN,
430 [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN,
431 [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN,
432 [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN,
433 [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN,
435 [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN,
437 [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN,
438 [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN,
439 [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN,
441 [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN,
442 [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN,
443 [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
446 static void setting_free(pa_alsa_setting *s) {
447 pa_assert(s);
449 if (s->options)
450 pa_idxset_free(s->options, NULL, NULL);
452 pa_xfree(s->name);
453 pa_xfree(s->description);
454 pa_xfree(s);
457 static void option_free(pa_alsa_option *o) {
458 pa_assert(o);
460 pa_xfree(o->alsa_name);
461 pa_xfree(o->name);
462 pa_xfree(o->description);
463 pa_xfree(o);
466 static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
467 pa_assert(db_fix);
469 pa_xfree(db_fix->name);
470 pa_xfree(db_fix->db_values);
472 pa_xfree(db_fix);
475 static void jack_free(pa_alsa_jack *j) {
476 pa_assert(j);
478 pa_xfree(j->alsa_name);
479 pa_xfree(j->name);
480 pa_xfree(j);
483 static void element_free(pa_alsa_element *e) {
484 pa_alsa_option *o;
485 pa_assert(e);
487 while ((o = e->options)) {
488 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
489 option_free(o);
492 if (e->db_fix)
493 decibel_fix_free(e->db_fix);
495 pa_xfree(e->alsa_name);
496 pa_xfree(e);
499 void pa_alsa_path_free(pa_alsa_path *p) {
500 pa_alsa_jack *j;
501 pa_alsa_element *e;
502 pa_alsa_setting *s;
504 pa_assert(p);
506 while ((j = p->jacks)) {
507 PA_LLIST_REMOVE(pa_alsa_jack, p->jacks, j);
508 jack_free(j);
511 while ((e = p->elements)) {
512 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
513 element_free(e);
516 while ((s = p->settings)) {
517 PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s);
518 setting_free(s);
521 pa_xfree(p->name);
522 pa_xfree(p->description);
523 pa_xfree(p);
526 void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
527 pa_assert(ps);
529 if (ps->paths)
530 pa_hashmap_free(ps->paths, NULL, NULL);
532 pa_xfree(ps);
535 static long to_alsa_dB(pa_volume_t v) {
536 return (long) (pa_sw_volume_to_dB(v) * 100.0);
539 static pa_volume_t from_alsa_dB(long v) {
540 return pa_sw_volume_from_dB((double) v / 100.0);
543 static long to_alsa_volume(pa_volume_t v, long min, long max) {
544 long w;
546 w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min;
547 return PA_CLAMP_UNLIKELY(w, min, max);
550 static pa_volume_t from_alsa_volume(long v, long min, long max) {
551 return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
554 #define SELEM_INIT(sid, name) \
555 do { \
556 snd_mixer_selem_id_alloca(&(sid)); \
557 snd_mixer_selem_id_set_name((sid), (name)); \
558 snd_mixer_selem_id_set_index((sid), 0); \
559 } while(FALSE)
561 static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
562 snd_mixer_selem_id_t *sid;
563 snd_mixer_elem_t *me;
564 snd_mixer_selem_channel_id_t c;
565 pa_channel_position_mask_t mask = 0;
566 unsigned k;
568 pa_assert(m);
569 pa_assert(e);
570 pa_assert(cm);
571 pa_assert(v);
573 SELEM_INIT(sid, e->alsa_name);
574 if (!(me = snd_mixer_find_selem(m, sid))) {
575 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
576 return -1;
579 pa_cvolume_mute(v, cm->channels);
581 /* We take the highest volume of all channels that match */
583 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
584 int r;
585 pa_volume_t f;
587 if (e->has_dB) {
588 long value = 0;
590 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
591 if (snd_mixer_selem_has_playback_channel(me, c)) {
592 if (e->db_fix) {
593 if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) {
594 /* If the channel volume is outside the limits set
595 * by the dB fix, we clamp the hw volume to be
596 * within the limits. */
597 if (value < e->db_fix->min_step) {
598 value = e->db_fix->min_step;
599 snd_mixer_selem_set_playback_volume(me, c, value);
600 pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
601 "Volume reset to %0.2f dB.", e->alsa_name, c,
602 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
603 } else if (value > e->db_fix->max_step) {
604 value = e->db_fix->max_step;
605 snd_mixer_selem_set_playback_volume(me, c, value);
606 pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
607 "Volume reset to %0.2f dB.", e->alsa_name, c,
608 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
611 /* Volume step -> dB value conversion. */
612 value = e->db_fix->db_values[value - e->db_fix->min_step];
614 } else
615 r = snd_mixer_selem_get_playback_dB(me, c, &value);
616 } else
617 r = -1;
618 } else {
619 if (snd_mixer_selem_has_capture_channel(me, c)) {
620 if (e->db_fix) {
621 if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) {
622 /* If the channel volume is outside the limits set
623 * by the dB fix, we clamp the hw volume to be
624 * within the limits. */
625 if (value < e->db_fix->min_step) {
626 value = e->db_fix->min_step;
627 snd_mixer_selem_set_capture_volume(me, c, value);
628 pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
629 "Volume reset to %0.2f dB.", e->alsa_name, c,
630 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
631 } else if (value > e->db_fix->max_step) {
632 value = e->db_fix->max_step;
633 snd_mixer_selem_set_capture_volume(me, c, value);
634 pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
635 "Volume reset to %0.2f dB.", e->alsa_name, c,
636 e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
639 /* Volume step -> dB value conversion. */
640 value = e->db_fix->db_values[value - e->db_fix->min_step];
642 } else
643 r = snd_mixer_selem_get_capture_dB(me, c, &value);
644 } else
645 r = -1;
648 if (r < 0)
649 continue;
651 #ifdef HAVE_VALGRIND_MEMCHECK_H
652 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
653 #endif
655 f = from_alsa_dB(value);
657 } else {
658 long value = 0;
660 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
661 if (snd_mixer_selem_has_playback_channel(me, c))
662 r = snd_mixer_selem_get_playback_volume(me, c, &value);
663 else
664 r = -1;
665 } else {
666 if (snd_mixer_selem_has_capture_channel(me, c))
667 r = snd_mixer_selem_get_capture_volume(me, c, &value);
668 else
669 r = -1;
672 if (r < 0)
673 continue;
675 f = from_alsa_volume(value, e->min_volume, e->max_volume);
678 for (k = 0; k < cm->channels; k++)
679 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
680 if (v->values[k] < f)
681 v->values[k] = f;
683 mask |= e->masks[c][e->n_channels-1];
686 for (k = 0; k < cm->channels; k++)
687 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
688 v->values[k] = PA_VOLUME_NORM;
690 return 0;
693 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
694 pa_alsa_element *e;
696 pa_assert(m);
697 pa_assert(p);
698 pa_assert(cm);
699 pa_assert(v);
701 if (!p->has_volume)
702 return -1;
704 pa_cvolume_reset(v, cm->channels);
706 PA_LLIST_FOREACH(e, p->elements) {
707 pa_cvolume ev;
709 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
710 continue;
712 pa_assert(!p->has_dB || e->has_dB);
714 if (element_get_volume(e, m, cm, &ev) < 0)
715 return -1;
717 /* If we have no dB information all we can do is take the first element and leave */
718 if (!p->has_dB) {
719 *v = ev;
720 return 0;
723 pa_sw_cvolume_multiply(v, v, &ev);
726 return 0;
729 static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t *b) {
730 snd_mixer_selem_id_t *sid;
731 snd_mixer_elem_t *me;
732 snd_mixer_selem_channel_id_t c;
734 pa_assert(m);
735 pa_assert(e);
736 pa_assert(b);
738 SELEM_INIT(sid, e->alsa_name);
739 if (!(me = snd_mixer_find_selem(m, sid))) {
740 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
741 return -1;
744 /* We return muted if at least one channel is muted */
746 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
747 int r;
748 int value = 0;
750 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
751 if (snd_mixer_selem_has_playback_channel(me, c))
752 r = snd_mixer_selem_get_playback_switch(me, c, &value);
753 else
754 r = -1;
755 } else {
756 if (snd_mixer_selem_has_capture_channel(me, c))
757 r = snd_mixer_selem_get_capture_switch(me, c, &value);
758 else
759 r = -1;
762 if (r < 0)
763 continue;
765 if (!value) {
766 *b = FALSE;
767 return 0;
771 *b = TRUE;
772 return 0;
775 int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
776 pa_alsa_element *e;
778 pa_assert(m);
779 pa_assert(p);
780 pa_assert(muted);
782 if (!p->has_mute)
783 return -1;
785 PA_LLIST_FOREACH(e, p->elements) {
786 pa_bool_t b;
788 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
789 continue;
791 if (element_get_switch(e, m, &b) < 0)
792 return -1;
794 if (!b) {
795 *muted = TRUE;
796 return 0;
800 *muted = FALSE;
801 return 0;
804 /* Finds the closest item in db_fix->db_values and returns the corresponding
805 * step. *db_value is replaced with the value from the db_values table.
806 * Rounding is done based on the rounding parameter: -1 means rounding down and
807 * +1 means rounding up. */
808 static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) {
809 unsigned i = 0;
810 unsigned max_i = 0;
812 pa_assert(db_fix);
813 pa_assert(db_value);
814 pa_assert(rounding != 0);
816 max_i = db_fix->max_step - db_fix->min_step;
818 if (rounding > 0) {
819 for (i = 0; i < max_i; i++) {
820 if (db_fix->db_values[i] >= *db_value)
821 break;
823 } else {
824 for (i = 0; i < max_i; i++) {
825 if (db_fix->db_values[i + 1] > *db_value)
826 break;
830 *db_value = db_fix->db_values[i];
832 return i + db_fix->min_step;
835 /* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument,
836 * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above".
837 * But even with accurate nearest dB volume step is not selected, so that is why we need
838 * this function. Returns 0 and nearest selectable volume in *value_dB on success or
839 * negative error code if fails. */
840 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) {
842 long alsa_val;
843 long value_high;
844 long value_low;
845 int r = -1;
847 pa_assert(me);
848 pa_assert(value_dB);
850 if (d == PA_ALSA_DIRECTION_OUTPUT) {
851 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
852 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high);
854 if (r < 0)
855 return r;
857 if (value_high == *value_dB)
858 return r;
860 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
861 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low);
862 } else {
863 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0)
864 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high);
866 if (r < 0)
867 return r;
869 if (value_high == *value_dB)
870 return r;
872 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0)
873 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low);
876 if (r < 0)
877 return r;
879 if (labs(value_high - *value_dB) < labs(value_low - *value_dB))
880 *value_dB = value_high;
881 else
882 *value_dB = value_low;
884 return r;
887 static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t deferred_volume, pa_bool_t write_to_hw) {
889 snd_mixer_selem_id_t *sid;
890 pa_cvolume rv;
891 snd_mixer_elem_t *me;
892 snd_mixer_selem_channel_id_t c;
893 pa_channel_position_mask_t mask = 0;
894 unsigned k;
896 pa_assert(m);
897 pa_assert(e);
898 pa_assert(cm);
899 pa_assert(v);
900 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
902 SELEM_INIT(sid, e->alsa_name);
903 if (!(me = snd_mixer_find_selem(m, sid))) {
904 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
905 return -1;
908 pa_cvolume_mute(&rv, cm->channels);
910 for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) {
911 int r;
912 pa_volume_t f = PA_VOLUME_MUTED;
913 pa_bool_t found = FALSE;
915 for (k = 0; k < cm->channels; k++)
916 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) {
917 found = TRUE;
918 if (v->values[k] > f)
919 f = v->values[k];
922 if (!found) {
923 /* Hmm, so this channel does not exist in the volume
924 * struct, so let's bind it to the overall max of the
925 * volume. */
926 f = pa_cvolume_max(v);
929 if (e->has_dB) {
930 long value = to_alsa_dB(f);
931 int rounding;
933 if (e->volume_limit >= 0 && value > (e->max_dB * 100))
934 value = e->max_dB * 100;
936 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
937 /* If we call set_playback_volume() without checking first
938 * if the channel is available, ALSA behaves very
939 * strangely and doesn't fail the call */
940 if (snd_mixer_selem_has_playback_channel(me, c)) {
941 rounding = +1;
942 if (e->db_fix) {
943 if (write_to_hw)
944 r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
945 else {
946 decibel_fix_get_step(e->db_fix, &value, rounding);
947 r = 0;
950 } else {
951 if (write_to_hw) {
952 if (deferred_volume) {
953 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0)
954 r = snd_mixer_selem_set_playback_dB(me, c, value, 0);
955 } else {
956 if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
957 r = snd_mixer_selem_get_playback_dB(me, c, &value);
959 } else {
960 long alsa_val;
961 if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
962 r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
965 } else
966 r = -1;
967 } else {
968 if (snd_mixer_selem_has_capture_channel(me, c)) {
969 rounding = -1;
970 if (e->db_fix) {
971 if (write_to_hw)
972 r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
973 else {
974 decibel_fix_get_step(e->db_fix, &value, rounding);
975 r = 0;
978 } else {
979 if (write_to_hw) {
980 if (deferred_volume) {
981 if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0)
982 r = snd_mixer_selem_set_capture_dB(me, c, value, 0);
983 } else {
984 if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
985 r = snd_mixer_selem_get_capture_dB(me, c, &value);
987 } else {
988 long alsa_val;
989 if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
990 r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
993 } else
994 r = -1;
997 if (r < 0)
998 continue;
1000 #ifdef HAVE_VALGRIND_MEMCHECK_H
1001 VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value));
1002 #endif
1004 f = from_alsa_dB(value);
1006 } else {
1007 long value;
1009 value = to_alsa_volume(f, e->min_volume, e->max_volume);
1011 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1012 if (snd_mixer_selem_has_playback_channel(me, c)) {
1013 if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0)
1014 r = snd_mixer_selem_get_playback_volume(me, c, &value);
1015 } else
1016 r = -1;
1017 } else {
1018 if (snd_mixer_selem_has_capture_channel(me, c)) {
1019 if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0)
1020 r = snd_mixer_selem_get_capture_volume(me, c, &value);
1021 } else
1022 r = -1;
1025 if (r < 0)
1026 continue;
1028 f = from_alsa_volume(value, e->min_volume, e->max_volume);
1031 for (k = 0; k < cm->channels; k++)
1032 if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k]))
1033 if (rv.values[k] < f)
1034 rv.values[k] = f;
1036 mask |= e->masks[c][e->n_channels-1];
1039 for (k = 0; k < cm->channels; k++)
1040 if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k])))
1041 rv.values[k] = PA_VOLUME_NORM;
1043 *v = rv;
1044 return 0;
1047 int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t deferred_volume, pa_bool_t write_to_hw) {
1049 pa_alsa_element *e;
1050 pa_cvolume rv;
1052 pa_assert(m);
1053 pa_assert(p);
1054 pa_assert(cm);
1055 pa_assert(v);
1056 pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
1058 if (!p->has_volume)
1059 return -1;
1061 rv = *v; /* Remaining adjustment */
1062 pa_cvolume_reset(v, cm->channels); /* Adjustment done */
1064 PA_LLIST_FOREACH(e, p->elements) {
1065 pa_cvolume ev;
1067 if (e->volume_use != PA_ALSA_VOLUME_MERGE)
1068 continue;
1070 pa_assert(!p->has_dB || e->has_dB);
1072 ev = rv;
1073 if (element_set_volume(e, m, cm, &ev, deferred_volume, write_to_hw) < 0)
1074 return -1;
1076 if (!p->has_dB) {
1077 *v = ev;
1078 return 0;
1081 pa_sw_cvolume_multiply(v, v, &ev);
1082 pa_sw_cvolume_divide(&rv, &rv, &ev);
1085 return 0;
1088 static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, pa_bool_t b) {
1089 snd_mixer_elem_t *me;
1090 snd_mixer_selem_id_t *sid;
1091 int r;
1093 pa_assert(m);
1094 pa_assert(e);
1096 SELEM_INIT(sid, e->alsa_name);
1097 if (!(me = snd_mixer_find_selem(m, sid))) {
1098 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1099 return -1;
1102 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1103 r = snd_mixer_selem_set_playback_switch_all(me, b);
1104 else
1105 r = snd_mixer_selem_set_capture_switch_all(me, b);
1107 if (r < 0)
1108 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1110 return r;
1113 int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t muted) {
1114 pa_alsa_element *e;
1116 pa_assert(m);
1117 pa_assert(p);
1119 if (!p->has_mute)
1120 return -1;
1122 PA_LLIST_FOREACH(e, p->elements) {
1124 if (e->switch_use != PA_ALSA_SWITCH_MUTE)
1125 continue;
1127 if (element_set_switch(e, m, !muted) < 0)
1128 return -1;
1131 return 0;
1134 /* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this
1135 * function sets all channels of the volume element to e->min_volume, 0 dB or
1136 * e->constant_volume. */
1137 static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
1138 snd_mixer_elem_t *me = NULL;
1139 snd_mixer_selem_id_t *sid = NULL;
1140 int r = 0;
1141 long volume = -1;
1142 pa_bool_t volume_set = FALSE;
1144 pa_assert(m);
1145 pa_assert(e);
1147 SELEM_INIT(sid, e->alsa_name);
1148 if (!(me = snd_mixer_find_selem(m, sid))) {
1149 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
1150 return -1;
1153 switch (e->volume_use) {
1154 case PA_ALSA_VOLUME_OFF:
1155 volume = e->min_volume;
1156 volume_set = TRUE;
1157 break;
1159 case PA_ALSA_VOLUME_ZERO:
1160 if (e->db_fix) {
1161 long dB = 0;
1163 volume = decibel_fix_get_step(e->db_fix, &dB, (e->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1));
1164 volume_set = TRUE;
1166 break;
1168 case PA_ALSA_VOLUME_CONSTANT:
1169 volume = e->constant_volume;
1170 volume_set = TRUE;
1171 break;
1173 default:
1174 pa_assert_not_reached();
1177 if (volume_set) {
1178 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1179 r = snd_mixer_selem_set_playback_volume_all(me, volume);
1180 else
1181 r = snd_mixer_selem_set_capture_volume_all(me, volume);
1182 } else {
1183 pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO);
1184 pa_assert(!e->db_fix);
1186 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1187 r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
1188 else
1189 r = snd_mixer_selem_set_capture_dB_all(me, 0, -1);
1192 if (r < 0)
1193 pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
1195 return r;
1198 int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
1199 pa_alsa_element *e;
1200 int r = 0;
1202 pa_assert(m);
1203 pa_assert(p);
1205 pa_log_debug("Activating path %s", p->name);
1206 pa_alsa_path_dump(p);
1208 PA_LLIST_FOREACH(e, p->elements) {
1210 switch (e->switch_use) {
1211 case PA_ALSA_SWITCH_OFF:
1212 r = element_set_switch(e, m, FALSE);
1213 break;
1215 case PA_ALSA_SWITCH_ON:
1216 r = element_set_switch(e, m, TRUE);
1217 break;
1219 case PA_ALSA_SWITCH_MUTE:
1220 case PA_ALSA_SWITCH_IGNORE:
1221 case PA_ALSA_SWITCH_SELECT:
1222 r = 0;
1223 break;
1226 if (r < 0)
1227 return -1;
1229 switch (e->volume_use) {
1230 case PA_ALSA_VOLUME_OFF:
1231 case PA_ALSA_VOLUME_ZERO:
1232 case PA_ALSA_VOLUME_CONSTANT:
1233 r = element_set_constant_volume(e, m);
1234 break;
1236 case PA_ALSA_VOLUME_MERGE:
1237 case PA_ALSA_VOLUME_IGNORE:
1238 r = 0;
1239 break;
1242 if (r < 0)
1243 return -1;
1246 return 0;
1249 static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) {
1250 pa_bool_t has_switch;
1251 pa_bool_t has_enumeration;
1252 pa_bool_t has_volume;
1254 pa_assert(e);
1255 pa_assert(me);
1257 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1258 has_switch =
1259 snd_mixer_selem_has_playback_switch(me) ||
1260 (e->direction_try_other && snd_mixer_selem_has_capture_switch(me));
1261 } else {
1262 has_switch =
1263 snd_mixer_selem_has_capture_switch(me) ||
1264 (e->direction_try_other && snd_mixer_selem_has_playback_switch(me));
1267 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1268 has_volume =
1269 snd_mixer_selem_has_playback_volume(me) ||
1270 (e->direction_try_other && snd_mixer_selem_has_capture_volume(me));
1271 } else {
1272 has_volume =
1273 snd_mixer_selem_has_capture_volume(me) ||
1274 (e->direction_try_other && snd_mixer_selem_has_playback_volume(me));
1277 has_enumeration = snd_mixer_selem_is_enumerated(me);
1279 if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) ||
1280 (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) ||
1281 (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration))
1282 return -1;
1284 if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration))
1285 return -1;
1287 if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) ||
1288 (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) ||
1289 (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration))
1290 return -1;
1292 if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration))
1293 return -1;
1295 if (e->required_any != PA_ALSA_REQUIRED_IGNORE) {
1296 switch (e->required_any) {
1297 case PA_ALSA_REQUIRED_VOLUME:
1298 e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE);
1299 break;
1300 case PA_ALSA_REQUIRED_SWITCH:
1301 e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE);
1302 break;
1303 case PA_ALSA_REQUIRED_ENUMERATION:
1304 e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1305 break;
1306 case PA_ALSA_REQUIRED_ANY:
1307 e->path->req_any_present |=
1308 (e->volume_use != PA_ALSA_VOLUME_IGNORE) ||
1309 (e->switch_use != PA_ALSA_SWITCH_IGNORE) ||
1310 (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE);
1311 break;
1312 default:
1313 pa_assert_not_reached();
1317 if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1318 pa_alsa_option *o;
1319 PA_LLIST_FOREACH(o, e->options) {
1320 e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) &&
1321 (o->alsa_idx >= 0);
1322 if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0)
1323 return -1;
1324 if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0)
1325 return -1;
1329 return 0;
1332 static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
1333 snd_mixer_selem_id_t *sid;
1334 snd_mixer_elem_t *me;
1336 pa_assert(m);
1337 pa_assert(e);
1338 pa_assert(e->path);
1340 SELEM_INIT(sid, e->alsa_name);
1342 if (!(me = snd_mixer_find_selem(m, sid))) {
1344 if (e->required != PA_ALSA_REQUIRED_IGNORE)
1345 return -1;
1347 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1348 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1349 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1351 return 0;
1354 if (e->switch_use != PA_ALSA_SWITCH_IGNORE) {
1355 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1357 if (!snd_mixer_selem_has_playback_switch(me)) {
1358 if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me))
1359 e->direction = PA_ALSA_DIRECTION_INPUT;
1360 else
1361 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1364 } else {
1366 if (!snd_mixer_selem_has_capture_switch(me)) {
1367 if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me))
1368 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1369 else
1370 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1374 if (e->switch_use != PA_ALSA_SWITCH_IGNORE)
1375 e->direction_try_other = FALSE;
1378 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1380 if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
1382 if (!snd_mixer_selem_has_playback_volume(me)) {
1383 if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me))
1384 e->direction = PA_ALSA_DIRECTION_INPUT;
1385 else
1386 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1389 } else {
1391 if (!snd_mixer_selem_has_capture_volume(me)) {
1392 if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me))
1393 e->direction = PA_ALSA_DIRECTION_OUTPUT;
1394 else
1395 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1399 if (e->volume_use != PA_ALSA_VOLUME_IGNORE) {
1400 long min_dB = 0, max_dB = 0;
1401 int r;
1403 e->direction_try_other = FALSE;
1405 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1406 r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume);
1407 else
1408 r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
1410 if (r < 0) {
1411 pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1412 return -1;
1415 if (e->min_volume >= e->max_volume) {
1416 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);
1417 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1419 } else if (e->volume_use == PA_ALSA_VOLUME_CONSTANT &&
1420 (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
1421 pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
1422 e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
1423 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1425 } else {
1426 pa_bool_t is_mono;
1427 pa_channel_position_t p;
1429 if (e->db_fix &&
1430 ((e->min_volume > e->db_fix->min_step) ||
1431 (e->max_volume < e->db_fix->max_step))) {
1432 pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
1433 "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
1434 e->db_fix->min_step, e->db_fix->max_step,
1435 e->min_volume, e->max_volume);
1437 decibel_fix_free(e->db_fix);
1438 e->db_fix = NULL;
1441 if (e->db_fix) {
1442 e->has_dB = TRUE;
1443 e->min_volume = e->db_fix->min_step;
1444 e->max_volume = e->db_fix->max_step;
1445 min_dB = e->db_fix->db_values[0];
1446 max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step];
1447 } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1448 e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
1449 else
1450 e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
1452 /* Check that the kernel driver returns consistent limits with
1453 * both _get_*_dB_range() and _ask_*_vol_dB(). */
1454 if (e->has_dB && !e->db_fix) {
1455 long min_dB_checked = 0;
1456 long max_dB_checked = 0;
1458 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1459 r = snd_mixer_selem_ask_playback_vol_dB(me, e->min_volume, &min_dB_checked);
1460 else
1461 r = snd_mixer_selem_ask_capture_vol_dB(me, e->min_volume, &min_dB_checked);
1463 if (r < 0) {
1464 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
1465 return -1;
1468 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1469 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB_checked);
1470 else
1471 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB_checked);
1473 if (r < 0) {
1474 pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
1475 return -1;
1478 if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
1479 pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
1480 "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
1481 "%0.2f dB at level %li.",
1482 e->alsa_name,
1483 min_dB / 100.0, max_dB / 100.0,
1484 min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
1485 return -1;
1489 if (e->has_dB) {
1490 #ifdef HAVE_VALGRIND_MEMCHECK_H
1491 VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
1492 VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
1493 #endif
1495 e->min_dB = ((double) min_dB) / 100.0;
1496 e->max_dB = ((double) max_dB) / 100.0;
1498 if (min_dB >= max_dB) {
1499 pa_assert(!e->db_fix);
1500 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);
1501 e->has_dB = FALSE;
1505 if (e->volume_limit >= 0) {
1506 if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
1507 pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
1508 "%li-%li. The volume limit is ignored.",
1509 e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
1511 else {
1512 e->max_volume = e->volume_limit;
1514 if (e->has_dB) {
1515 if (e->db_fix) {
1516 e->db_fix->max_step = e->max_volume;
1517 e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
1519 } else {
1520 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1521 r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB);
1522 else
1523 r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB);
1525 if (r < 0) {
1526 pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
1527 e->has_dB = FALSE;
1528 } else
1529 e->max_dB = ((double) max_dB) / 100.0;
1535 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1536 is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
1537 else
1538 is_mono = snd_mixer_selem_is_capture_mono(me) > 0;
1540 if (is_mono) {
1541 e->n_channels = 1;
1543 if (!e->override_map) {
1544 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1545 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1546 continue;
1548 e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0;
1551 e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL;
1554 e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1];
1555 } else {
1556 e->n_channels = 0;
1557 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1559 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1560 continue;
1562 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1563 e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1564 else
1565 e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1568 if (e->n_channels <= 0) {
1569 pa_log_warn("Volume element %s with no channels?", e->alsa_name);
1570 return -1;
1573 if (e->n_channels > 2) {
1574 /* FIXME: In some places code like this is used:
1576 * e->masks[alsa_channel_ids[p]][e->n_channels-1]
1578 * The definition of e->masks is
1580 * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
1582 * Since the array size is fixed at 2, we obviously
1583 * don't support elements with more than two
1584 * channels... */
1585 pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
1586 return -1;
1589 if (!e->override_map) {
1590 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1591 pa_bool_t has_channel;
1593 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1594 continue;
1596 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
1597 has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0;
1598 else
1599 has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0;
1601 e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0;
1605 e->merged_mask = 0;
1606 for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) {
1607 if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN)
1608 continue;
1610 e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1];
1618 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
1619 pa_alsa_option *o;
1621 PA_LLIST_FOREACH(o, e->options)
1622 o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0;
1623 } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
1624 int n;
1625 pa_alsa_option *o;
1627 if ((n = snd_mixer_selem_get_enum_items(me)) < 0) {
1628 pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n));
1629 return -1;
1632 PA_LLIST_FOREACH(o, e->options) {
1633 int i;
1635 for (i = 0; i < n; i++) {
1636 char buf[128];
1638 if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0)
1639 continue;
1641 if (!pa_streq(buf, o->alsa_name))
1642 continue;
1644 o->alsa_idx = i;
1649 if (check_required(e, me) < 0)
1650 return -1;
1652 return 0;
1655 static int jack_probe(pa_alsa_jack *j, snd_hctl_t *h) {
1656 pa_assert(h);
1657 pa_assert(j);
1658 pa_assert(j->path);
1660 j->has_control = pa_alsa_find_jack(h, j->alsa_name) != NULL;
1662 if (j->has_control) {
1663 if (j->required_absent != PA_ALSA_REQUIRED_IGNORE)
1664 return -1;
1665 if (j->required_any != PA_ALSA_REQUIRED_IGNORE)
1666 j->path->req_any_present = TRUE;
1667 } else {
1668 if (j->required != PA_ALSA_REQUIRED_IGNORE)
1669 return -1;
1672 return 0;
1675 static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_bool_t prefixed) {
1676 pa_alsa_element *e;
1678 pa_assert(p);
1679 pa_assert(section);
1681 if (prefixed) {
1682 if (!pa_startswith(section, "Element "))
1683 return NULL;
1685 section += 8;
1688 /* This is not an element section, but an enum section? */
1689 if (strchr(section, ':'))
1690 return NULL;
1692 if (p->last_element && pa_streq(p->last_element->alsa_name, section))
1693 return p->last_element;
1695 PA_LLIST_FOREACH(e, p->elements)
1696 if (pa_streq(e->alsa_name, section))
1697 goto finish;
1699 e = pa_xnew0(pa_alsa_element, 1);
1700 e->path = p;
1701 e->alsa_name = pa_xstrdup(section);
1702 e->direction = p->direction;
1703 e->volume_limit = -1;
1705 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
1707 finish:
1708 p->last_element = e;
1709 return e;
1712 static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) {
1713 pa_alsa_jack *j;
1715 if (!pa_startswith(section, "Jack "))
1716 return NULL;
1717 section += 5;
1719 if (p->last_jack && pa_streq(p->last_jack->name, section))
1720 return p->last_jack;
1722 PA_LLIST_FOREACH(j, p->jacks)
1723 if (pa_streq(j->name, section))
1724 goto finish;
1726 j = pa_xnew0(pa_alsa_jack, 1);
1727 j->state_unplugged = PA_PORT_AVAILABLE_NO;
1728 j->state_plugged = PA_PORT_AVAILABLE_YES;
1729 j->path = p;
1730 j->name = pa_xstrdup(section);
1731 j->alsa_name = pa_sprintf_malloc("%s Jack", section);
1732 PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j);
1734 finish:
1735 p->last_jack = j;
1736 return j;
1740 static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
1741 char *en;
1742 const char *on;
1743 pa_alsa_option *o;
1744 pa_alsa_element *e;
1746 if (!pa_startswith(section, "Option "))
1747 return NULL;
1749 section += 7;
1751 /* This is not an enum section, but an element section? */
1752 if (!(on = strchr(section, ':')))
1753 return NULL;
1755 en = pa_xstrndup(section, on - section);
1756 on++;
1758 if (p->last_option &&
1759 pa_streq(p->last_option->element->alsa_name, en) &&
1760 pa_streq(p->last_option->alsa_name, on)) {
1761 pa_xfree(en);
1762 return p->last_option;
1765 pa_assert_se(e = element_get(p, en, FALSE));
1766 pa_xfree(en);
1768 PA_LLIST_FOREACH(o, e->options)
1769 if (pa_streq(o->alsa_name, on))
1770 goto finish;
1772 o = pa_xnew0(pa_alsa_option, 1);
1773 o->element = e;
1774 o->alsa_name = pa_xstrdup(on);
1775 o->alsa_idx = -1;
1777 if (p->last_option && p->last_option->element == e)
1778 PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o);
1779 else
1780 PA_LLIST_PREPEND(pa_alsa_option, e->options, o);
1782 finish:
1783 p->last_option = o;
1784 return o;
1787 static int element_parse_switch(
1788 const char *filename,
1789 unsigned line,
1790 const char *section,
1791 const char *lvalue,
1792 const char *rvalue,
1793 void *data,
1794 void *userdata) {
1796 pa_alsa_path *p = userdata;
1797 pa_alsa_element *e;
1799 pa_assert(p);
1801 if (!(e = element_get(p, section, TRUE))) {
1802 pa_log("[%s:%u] Switch makes no sense in '%s'", filename, line, section);
1803 return -1;
1806 if (pa_streq(rvalue, "ignore"))
1807 e->switch_use = PA_ALSA_SWITCH_IGNORE;
1808 else if (pa_streq(rvalue, "mute"))
1809 e->switch_use = PA_ALSA_SWITCH_MUTE;
1810 else if (pa_streq(rvalue, "off"))
1811 e->switch_use = PA_ALSA_SWITCH_OFF;
1812 else if (pa_streq(rvalue, "on"))
1813 e->switch_use = PA_ALSA_SWITCH_ON;
1814 else if (pa_streq(rvalue, "select"))
1815 e->switch_use = PA_ALSA_SWITCH_SELECT;
1816 else {
1817 pa_log("[%s:%u] Switch invalid of '%s'", filename, line, section);
1818 return -1;
1821 return 0;
1824 static int element_parse_volume(
1825 const char *filename,
1826 unsigned line,
1827 const char *section,
1828 const char *lvalue,
1829 const char *rvalue,
1830 void *data,
1831 void *userdata) {
1833 pa_alsa_path *p = userdata;
1834 pa_alsa_element *e;
1836 pa_assert(p);
1838 if (!(e = element_get(p, section, TRUE))) {
1839 pa_log("[%s:%u] Volume makes no sense in '%s'", filename, line, section);
1840 return -1;
1843 if (pa_streq(rvalue, "ignore"))
1844 e->volume_use = PA_ALSA_VOLUME_IGNORE;
1845 else if (pa_streq(rvalue, "merge"))
1846 e->volume_use = PA_ALSA_VOLUME_MERGE;
1847 else if (pa_streq(rvalue, "off"))
1848 e->volume_use = PA_ALSA_VOLUME_OFF;
1849 else if (pa_streq(rvalue, "zero"))
1850 e->volume_use = PA_ALSA_VOLUME_ZERO;
1851 else {
1852 uint32_t constant;
1854 if (pa_atou(rvalue, &constant) >= 0) {
1855 e->volume_use = PA_ALSA_VOLUME_CONSTANT;
1856 e->constant_volume = constant;
1857 } else {
1858 pa_log("[%s:%u] Volume invalid of '%s'", filename, line, section);
1859 return -1;
1863 return 0;
1866 static int element_parse_enumeration(
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_element *e;
1878 pa_assert(p);
1880 if (!(e = element_get(p, section, TRUE))) {
1881 pa_log("[%s:%u] Enumeration makes no sense in '%s'", filename, line, section);
1882 return -1;
1885 if (pa_streq(rvalue, "ignore"))
1886 e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE;
1887 else if (pa_streq(rvalue, "select"))
1888 e->enumeration_use = PA_ALSA_ENUMERATION_SELECT;
1889 else {
1890 pa_log("[%s:%u] Enumeration invalid of '%s'", filename, line, section);
1891 return -1;
1894 return 0;
1897 static int option_parse_priority(
1898 const char *filename,
1899 unsigned line,
1900 const char *section,
1901 const char *lvalue,
1902 const char *rvalue,
1903 void *data,
1904 void *userdata) {
1906 pa_alsa_path *p = userdata;
1907 pa_alsa_option *o;
1908 uint32_t prio;
1910 pa_assert(p);
1912 if (!(o = option_get(p, section))) {
1913 pa_log("[%s:%u] Priority makes no sense in '%s'", filename, line, section);
1914 return -1;
1917 if (pa_atou(rvalue, &prio) < 0) {
1918 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
1919 return -1;
1922 o->priority = prio;
1923 return 0;
1926 static int option_parse_name(
1927 const char *filename,
1928 unsigned line,
1929 const char *section,
1930 const char *lvalue,
1931 const char *rvalue,
1932 void *data,
1933 void *userdata) {
1935 pa_alsa_path *p = userdata;
1936 pa_alsa_option *o;
1938 pa_assert(p);
1940 if (!(o = option_get(p, section))) {
1941 pa_log("[%s:%u] Name makes no sense in '%s'", filename, line, section);
1942 return -1;
1945 pa_xfree(o->name);
1946 o->name = pa_xstrdup(rvalue);
1948 return 0;
1951 static int element_parse_required(
1952 const char *filename,
1953 unsigned line,
1954 const char *section,
1955 const char *lvalue,
1956 const char *rvalue,
1957 void *data,
1958 void *userdata) {
1960 pa_alsa_path *p = userdata;
1961 pa_alsa_element *e;
1962 pa_alsa_option *o;
1963 pa_alsa_jack *j;
1964 pa_alsa_required_t req;
1966 pa_assert(p);
1968 e = element_get(p, section, TRUE);
1969 o = option_get(p, section);
1970 j = jack_get(p, section);
1971 if (!e && !o && !j) {
1972 pa_log("[%s:%u] Required makes no sense in '%s'", filename, line, section);
1973 return -1;
1976 if (pa_streq(rvalue, "ignore"))
1977 req = PA_ALSA_REQUIRED_IGNORE;
1978 else if (pa_streq(rvalue, "switch") && e)
1979 req = PA_ALSA_REQUIRED_SWITCH;
1980 else if (pa_streq(rvalue, "volume") && e)
1981 req = PA_ALSA_REQUIRED_VOLUME;
1982 else if (pa_streq(rvalue, "enumeration"))
1983 req = PA_ALSA_REQUIRED_ENUMERATION;
1984 else if (pa_streq(rvalue, "any"))
1985 req = PA_ALSA_REQUIRED_ANY;
1986 else {
1987 pa_log("[%s:%u] Required invalid of '%s'", filename, line, section);
1988 return -1;
1991 if (pa_streq(lvalue, "required-absent")) {
1992 if (e)
1993 e->required_absent = req;
1994 if (o)
1995 o->required_absent = req;
1996 if (j)
1997 j->required_absent = req;
1999 else if (pa_streq(lvalue, "required-any")) {
2000 if (e) {
2001 e->required_any = req;
2002 e->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE);
2004 if (o) {
2005 o->required_any = req;
2006 o->element->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE);
2008 if (j) {
2009 j->required_any = req;
2010 j->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE);
2014 else {
2015 if (e)
2016 e->required = req;
2017 if (o)
2018 o->required = req;
2019 if (j)
2020 j->required = req;
2023 return 0;
2026 static int element_parse_direction(
2027 const char *filename,
2028 unsigned line,
2029 const char *section,
2030 const char *lvalue,
2031 const char *rvalue,
2032 void *data,
2033 void *userdata) {
2035 pa_alsa_path *p = userdata;
2036 pa_alsa_element *e;
2038 pa_assert(p);
2040 if (!(e = element_get(p, section, TRUE))) {
2041 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2042 return -1;
2045 if (pa_streq(rvalue, "playback"))
2046 e->direction = PA_ALSA_DIRECTION_OUTPUT;
2047 else if (pa_streq(rvalue, "capture"))
2048 e->direction = PA_ALSA_DIRECTION_INPUT;
2049 else {
2050 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2051 return -1;
2054 return 0;
2057 static int element_parse_direction_try_other(
2058 const char *filename,
2059 unsigned line,
2060 const char *section,
2061 const char *lvalue,
2062 const char *rvalue,
2063 void *data,
2064 void *userdata) {
2066 pa_alsa_path *p = userdata;
2067 pa_alsa_element *e;
2068 int yes;
2070 if (!(e = element_get(p, section, TRUE))) {
2071 pa_log("[%s:%u] Direction makes no sense in '%s'", filename, line, section);
2072 return -1;
2075 if ((yes = pa_parse_boolean(rvalue)) < 0) {
2076 pa_log("[%s:%u] Direction invalid of '%s'", filename, line, section);
2077 return -1;
2080 e->direction_try_other = !!yes;
2081 return 0;
2084 static int element_parse_volume_limit(
2085 const char *filename,
2086 unsigned line,
2087 const char *section,
2088 const char *lvalue,
2089 const char *rvalue,
2090 void *data,
2091 void *userdata) {
2093 pa_alsa_path *p = userdata;
2094 pa_alsa_element *e;
2095 long volume_limit;
2097 if (!(e = element_get(p, section, TRUE))) {
2098 pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section);
2099 return -1;
2102 if (pa_atol(rvalue, &volume_limit) < 0 || volume_limit < 0) {
2103 pa_log("[%s:%u] Invalid value for volume-limit", filename, line);
2104 return -1;
2107 e->volume_limit = volume_limit;
2108 return 0;
2111 static pa_channel_position_mask_t parse_mask(const char *m) {
2112 pa_channel_position_mask_t v;
2114 if (pa_streq(m, "all-left"))
2115 v = PA_CHANNEL_POSITION_MASK_LEFT;
2116 else if (pa_streq(m, "all-right"))
2117 v = PA_CHANNEL_POSITION_MASK_RIGHT;
2118 else if (pa_streq(m, "all-center"))
2119 v = PA_CHANNEL_POSITION_MASK_CENTER;
2120 else if (pa_streq(m, "all-front"))
2121 v = PA_CHANNEL_POSITION_MASK_FRONT;
2122 else if (pa_streq(m, "all-rear"))
2123 v = PA_CHANNEL_POSITION_MASK_REAR;
2124 else if (pa_streq(m, "all-side"))
2125 v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER;
2126 else if (pa_streq(m, "all-top"))
2127 v = PA_CHANNEL_POSITION_MASK_TOP;
2128 else if (pa_streq(m, "all-no-lfe"))
2129 v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE);
2130 else if (pa_streq(m, "all"))
2131 v = PA_CHANNEL_POSITION_MASK_ALL;
2132 else {
2133 pa_channel_position_t p;
2135 if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID)
2136 return 0;
2138 v = PA_CHANNEL_POSITION_MASK(p);
2141 return v;
2144 static int element_parse_override_map(
2145 const char *filename,
2146 unsigned line,
2147 const char *section,
2148 const char *lvalue,
2149 const char *rvalue,
2150 void *data,
2151 void *userdata) {
2153 pa_alsa_path *p = userdata;
2154 pa_alsa_element *e;
2155 const char *state = NULL;
2156 unsigned i = 0;
2157 char *n;
2159 if (!(e = element_get(p, section, TRUE))) {
2160 pa_log("[%s:%u] Override map makes no sense in '%s'", filename, line, section);
2161 return -1;
2164 while ((n = pa_split(rvalue, ",", &state))) {
2165 pa_channel_position_mask_t m;
2167 if (!*n)
2168 m = 0;
2169 else {
2170 if ((m = parse_mask(n)) == 0) {
2171 pa_log("[%s:%u] Override map '%s' invalid in '%s'", filename, line, n, section);
2172 pa_xfree(n);
2173 return -1;
2177 if (pa_streq(lvalue, "override-map.1"))
2178 e->masks[i++][0] = m;
2179 else
2180 e->masks[i++][1] = m;
2182 /* Later on we might add override-map.3 and so on here ... */
2184 pa_xfree(n);
2187 e->override_map = TRUE;
2189 return 0;
2192 static int jack_parse_state(
2193 const char *filename,
2194 unsigned line,
2195 const char *section,
2196 const char *lvalue,
2197 const char *rvalue,
2198 void *data,
2199 void *userdata) {
2201 pa_alsa_path *p = userdata;
2202 pa_alsa_jack *j;
2203 pa_port_available_t pa;
2205 if (!(j = jack_get(p, section))) {
2206 pa_log("[%s:%u] state makes no sense in '%s'", filename, line, section);
2207 return -1;
2210 if (!strcmp(rvalue,"yes"))
2211 pa = PA_PORT_AVAILABLE_YES;
2212 else if (!strcmp(rvalue,"no"))
2213 pa = PA_PORT_AVAILABLE_NO;
2214 else if (!strcmp(rvalue,"unknown"))
2215 pa = PA_PORT_AVAILABLE_UNKNOWN;
2216 else {
2217 pa_log("[%s:%u] state must be 'yes','no' or 'unknown' in '%s'", filename, line, section);
2218 return -1;
2221 if (!strcmp(lvalue, "state.unplugged"))
2222 j->state_unplugged = pa;
2223 else {
2224 j->state_plugged = pa;
2225 pa_assert(!strcmp(lvalue, "state.plugged"));
2228 return 0;
2231 static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
2232 snd_mixer_selem_id_t *sid;
2233 snd_mixer_elem_t *me;
2234 int r;
2236 pa_assert(e);
2237 pa_assert(m);
2239 SELEM_INIT(sid, e->alsa_name);
2240 if (!(me = snd_mixer_find_selem(m, sid))) {
2241 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2242 return -1;
2245 if (e->switch_use == PA_ALSA_SWITCH_SELECT) {
2247 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
2248 r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx);
2249 else
2250 r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
2252 if (r < 0)
2253 pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2255 } else {
2256 pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
2258 if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
2259 pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
2262 return r;
2265 int pa_alsa_setting_select(pa_alsa_setting *s, snd_mixer_t *m) {
2266 pa_alsa_option *o;
2267 uint32_t idx;
2269 pa_assert(s);
2270 pa_assert(m);
2272 PA_IDXSET_FOREACH(o, s->options, idx)
2273 element_set_option(o->element, m, o->alsa_idx);
2275 return 0;
2278 static int option_verify(pa_alsa_option *o) {
2279 static const struct description_map well_known_descriptions[] = {
2280 { "input", N_("Input") },
2281 { "input-docking", N_("Docking Station Input") },
2282 { "input-docking-microphone", N_("Docking Station Microphone") },
2283 { "input-docking-linein", N_("Docking Station Line In") },
2284 { "input-linein", N_("Line In") },
2285 { "input-microphone", N_("Microphone") },
2286 { "input-microphone-front", N_("Front Microphone") },
2287 { "input-microphone-rear", N_("Rear Microphone") },
2288 { "input-microphone-external", N_("External Microphone") },
2289 { "input-microphone-internal", N_("Internal Microphone") },
2290 { "input-radio", N_("Radio") },
2291 { "input-video", N_("Video") },
2292 { "input-agc-on", N_("Automatic Gain Control") },
2293 { "input-agc-off", N_("No Automatic Gain Control") },
2294 { "input-boost-on", N_("Boost") },
2295 { "input-boost-off", N_("No Boost") },
2296 { "output-amplifier-on", N_("Amplifier") },
2297 { "output-amplifier-off", N_("No Amplifier") },
2298 { "output-bass-boost-on", N_("Bass Boost") },
2299 { "output-bass-boost-off", N_("No Bass Boost") },
2300 { "output-speaker", N_("Speaker") },
2301 { "output-headphones", N_("Headphones") }
2304 pa_assert(o);
2306 if (!o->name) {
2307 pa_log("No name set for option %s", o->alsa_name);
2308 return -1;
2311 if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
2312 o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
2313 pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
2314 return -1;
2317 if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
2318 !pa_streq(o->alsa_name, "on") &&
2319 !pa_streq(o->alsa_name, "off")) {
2320 pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
2321 return -1;
2324 if (!o->description)
2325 o->description = pa_xstrdup(lookup_description(o->name,
2326 well_known_descriptions,
2327 PA_ELEMENTSOF(well_known_descriptions)));
2328 if (!o->description)
2329 o->description = pa_xstrdup(o->name);
2331 return 0;
2334 static int element_verify(pa_alsa_element *e) {
2335 pa_alsa_option *o;
2337 pa_assert(e);
2339 // 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);
2340 if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) ||
2341 (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
2342 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
2343 (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
2344 pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
2345 return -1;
2348 if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
2349 pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
2350 return -1;
2353 PA_LLIST_FOREACH(o, e->options)
2354 if (option_verify(o) < 0)
2355 return -1;
2357 return 0;
2360 static int path_verify(pa_alsa_path *p) {
2361 static const struct description_map well_known_descriptions[] = {
2362 { "analog-input", N_("Analog Input") },
2363 { "analog-input-microphone", N_("Microphone") },
2364 { "analog-input-microphone-front", N_("Front Microphone") },
2365 { "analog-input-microphone-rear", N_("Rear Microphone") },
2366 { "analog-input-microphone-dock", N_("Dock Microphone") },
2367 { "analog-input-microphone-internal", N_("Internal Microphone") },
2368 { "analog-input-linein", N_("Line In") },
2369 { "analog-input-radio", N_("Radio") },
2370 { "analog-input-video", N_("Video") },
2371 { "analog-output", N_("Analog Output") },
2372 { "analog-output-headphones", N_("Headphones") },
2373 { "analog-output-lfe-on-mono", N_("LFE on Separate Mono Output") },
2374 { "analog-output-lineout", N_("Line Out") },
2375 { "analog-output-mono", N_("Analog Mono Output") },
2376 { "analog-output-speaker", N_("Speakers") },
2377 { "hdmi-output", N_("HDMI / DisplayPort") },
2378 { "iec958-stereo-output", N_("Digital Output (S/PDIF)") },
2379 { "iec958-passthrough-output", N_("Digital Passthrough (S/PDIF)") }
2382 pa_alsa_element *e;
2384 pa_assert(p);
2386 PA_LLIST_FOREACH(e, p->elements)
2387 if (element_verify(e) < 0)
2388 return -1;
2390 if (!p->description)
2391 p->description = pa_xstrdup(lookup_description(p->name,
2392 well_known_descriptions,
2393 PA_ELEMENTSOF(well_known_descriptions)));
2395 if (!p->description)
2396 p->description = pa_xstrdup(p->name);
2398 return 0;
2401 static const char *get_default_paths_dir(void) {
2402 if (pa_run_from_build_tree())
2403 return PA_BUILDDIR "/modules/alsa/mixer/paths/";
2404 else
2405 return PA_ALSA_PATHS_DIR;
2408 pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) {
2409 pa_alsa_path *p;
2410 char *fn;
2411 int r;
2412 const char *n;
2414 pa_config_item items[] = {
2415 /* [General] */
2416 { "priority", pa_config_parse_unsigned, NULL, "General" },
2417 { "description", pa_config_parse_string, NULL, "General" },
2418 { "name", pa_config_parse_string, NULL, "General" },
2420 /* [Option ...] */
2421 { "priority", option_parse_priority, NULL, NULL },
2422 { "name", option_parse_name, NULL, NULL },
2424 /* [Jack ...] */
2425 { "state.plugged", jack_parse_state, NULL, NULL },
2426 { "state.unplugged", jack_parse_state, NULL, NULL },
2428 /* [Element ...] */
2429 { "switch", element_parse_switch, NULL, NULL },
2430 { "volume", element_parse_volume, NULL, NULL },
2431 { "enumeration", element_parse_enumeration, NULL, NULL },
2432 { "override-map.1", element_parse_override_map, NULL, NULL },
2433 { "override-map.2", element_parse_override_map, NULL, NULL },
2434 /* ... later on we might add override-map.3 and so on here ... */
2435 { "required", element_parse_required, NULL, NULL },
2436 { "required-any", element_parse_required, NULL, NULL },
2437 { "required-absent", element_parse_required, NULL, NULL },
2438 { "direction", element_parse_direction, NULL, NULL },
2439 { "direction-try-other", element_parse_direction_try_other, NULL, NULL },
2440 { "volume-limit", element_parse_volume_limit, NULL, NULL },
2441 { NULL, NULL, NULL, NULL }
2444 pa_assert(fname);
2446 p = pa_xnew0(pa_alsa_path, 1);
2447 n = pa_path_get_filename(fname);
2448 p->name = pa_xstrndup(n, strcspn(n, "."));
2449 p->direction = direction;
2451 items[0].data = &p->priority;
2452 items[1].data = &p->description;
2453 items[2].data = &p->name;
2455 if (!paths_dir)
2456 paths_dir = get_default_paths_dir();
2458 fn = pa_maybe_prefix_path(fname, paths_dir);
2460 r = pa_config_parse(fn, NULL, items, p);
2461 pa_xfree(fn);
2463 if (r < 0)
2464 goto fail;
2466 if (path_verify(p) < 0)
2467 goto fail;
2469 return p;
2471 fail:
2472 pa_alsa_path_free(p);
2473 return NULL;
2476 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
2477 pa_alsa_path *p;
2478 pa_alsa_element *e;
2480 pa_assert(element);
2482 p = pa_xnew0(pa_alsa_path, 1);
2483 p->name = pa_xstrdup(element);
2484 p->direction = direction;
2486 e = pa_xnew0(pa_alsa_element, 1);
2487 e->path = p;
2488 e->alsa_name = pa_xstrdup(element);
2489 e->direction = direction;
2490 e->volume_limit = -1;
2492 e->switch_use = PA_ALSA_SWITCH_MUTE;
2493 e->volume_use = PA_ALSA_VOLUME_MERGE;
2495 PA_LLIST_PREPEND(pa_alsa_element, p->elements, e);
2496 p->last_element = e;
2497 return p;
2500 static pa_bool_t element_drop_unsupported(pa_alsa_element *e) {
2501 pa_alsa_option *o, *n;
2503 pa_assert(e);
2505 for (o = e->options; o; o = n) {
2506 n = o->next;
2508 if (o->alsa_idx < 0) {
2509 PA_LLIST_REMOVE(pa_alsa_option, e->options, o);
2510 option_free(o);
2514 return
2515 e->switch_use != PA_ALSA_SWITCH_IGNORE ||
2516 e->volume_use != PA_ALSA_VOLUME_IGNORE ||
2517 e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE;
2520 static void path_drop_unsupported(pa_alsa_path *p) {
2521 pa_alsa_element *e, *n;
2523 pa_assert(p);
2525 for (e = p->elements; e; e = n) {
2526 n = e->next;
2528 if (!element_drop_unsupported(e)) {
2529 PA_LLIST_REMOVE(pa_alsa_element, p->elements, e);
2530 element_free(e);
2535 static void path_make_options_unique(pa_alsa_path *p) {
2536 pa_alsa_element *e;
2537 pa_alsa_option *o, *u;
2539 PA_LLIST_FOREACH(e, p->elements) {
2540 PA_LLIST_FOREACH(o, e->options) {
2541 unsigned i;
2542 char *m;
2544 for (u = o->next; u; u = u->next)
2545 if (pa_streq(u->name, o->name))
2546 break;
2548 if (!u)
2549 continue;
2551 m = pa_xstrdup(o->name);
2553 /* OK, this name is not unique, hence let's rename */
2554 for (i = 1, u = o; u; u = u->next) {
2555 char *nn, *nd;
2557 if (!pa_streq(u->name, m))
2558 continue;
2560 nn = pa_sprintf_malloc("%s-%u", m, i);
2561 pa_xfree(u->name);
2562 u->name = nn;
2564 nd = pa_sprintf_malloc("%s %u", u->description, i);
2565 pa_xfree(u->description);
2566 u->description = nd;
2568 i++;
2571 pa_xfree(m);
2576 static pa_bool_t element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) {
2577 pa_alsa_option *o;
2579 for (; e; e = e->next)
2580 if (e->switch_use == PA_ALSA_SWITCH_SELECT ||
2581 e->enumeration_use == PA_ALSA_ENUMERATION_SELECT)
2582 break;
2584 if (!e)
2585 return FALSE;
2587 for (o = e->options; o; o = o->next) {
2588 pa_alsa_setting *s;
2590 if (template) {
2591 s = pa_xnewdup(pa_alsa_setting, template, 1);
2592 s->options = pa_idxset_copy(template->options);
2593 s->name = pa_sprintf_malloc("%s+%s", template->name, o->name);
2594 s->description =
2595 (template->description[0] && o->description[0])
2596 ? pa_sprintf_malloc("%s / %s", template->description, o->description)
2597 : (template->description[0]
2598 ? pa_xstrdup(template->description)
2599 : pa_xstrdup(o->description));
2601 s->priority = PA_MAX(template->priority, o->priority);
2602 } else {
2603 s = pa_xnew0(pa_alsa_setting, 1);
2604 s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
2605 s->name = pa_xstrdup(o->name);
2606 s->description = pa_xstrdup(o->description);
2607 s->priority = o->priority;
2610 pa_idxset_put(s->options, o, NULL);
2612 if (element_create_settings(e->next, s))
2613 /* This is not a leaf, so let's get rid of it */
2614 setting_free(s);
2615 else {
2616 /* This is a leaf, so let's add it */
2617 PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s);
2619 e->path->last_setting = s;
2623 return TRUE;
2626 static void path_create_settings(pa_alsa_path *p) {
2627 pa_assert(p);
2629 element_create_settings(p->elements, NULL);
2632 int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, snd_hctl_t *hctl, pa_bool_t ignore_dB) {
2633 pa_alsa_element *e;
2634 pa_alsa_jack *j;
2635 double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
2636 pa_channel_position_t t;
2637 pa_channel_position_mask_t path_volume_channels = 0;
2639 pa_assert(p);
2640 pa_assert(m);
2642 if (p->probed)
2643 return p->supported ? 0 : -1;
2644 p->probed = TRUE;
2646 pa_zero(min_dB);
2647 pa_zero(max_dB);
2649 pa_log_debug("Probing path '%s'", p->name);
2651 PA_LLIST_FOREACH(j, p->jacks) {
2652 if (jack_probe(j, hctl) < 0) {
2653 p->supported = FALSE;
2654 pa_log_debug("Probe of jack '%s' failed.", j->alsa_name);
2655 return -1;
2657 pa_log_debug("Probe of jack '%s' succeeded (%s)", j->alsa_name, j->has_control ? "found!" : "not found");
2660 PA_LLIST_FOREACH(e, p->elements) {
2661 if (element_probe(e, m) < 0) {
2662 p->supported = FALSE;
2663 pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
2664 return -1;
2666 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);
2668 if (ignore_dB)
2669 e->has_dB = FALSE;
2671 if (e->volume_use == PA_ALSA_VOLUME_MERGE) {
2673 if (!p->has_volume) {
2674 p->min_volume = e->min_volume;
2675 p->max_volume = e->max_volume;
2678 if (e->has_dB) {
2679 if (!p->has_volume) {
2680 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2681 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2682 min_dB[t] = e->min_dB;
2683 max_dB[t] = e->max_dB;
2684 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2687 p->has_dB = TRUE;
2688 } else {
2690 if (p->has_dB) {
2691 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++)
2692 if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) {
2693 min_dB[t] += e->min_dB;
2694 max_dB[t] += e->max_dB;
2695 path_volume_channels |= PA_CHANNEL_POSITION_MASK(t);
2697 } else {
2698 /* Hmm, there's another element before us
2699 * which cannot do dB volumes, so we we need
2700 * to 'neutralize' this slider */
2701 e->volume_use = PA_ALSA_VOLUME_ZERO;
2702 pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
2705 } else if (p->has_volume) {
2706 /* We can't use this volume, so let's ignore it */
2707 e->volume_use = PA_ALSA_VOLUME_IGNORE;
2708 pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
2710 p->has_volume = TRUE;
2713 if (e->switch_use == PA_ALSA_SWITCH_MUTE)
2714 p->has_mute = TRUE;
2717 if (p->has_req_any && !p->req_any_present) {
2718 p->supported = FALSE;
2719 pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name);
2720 return -1;
2723 path_drop_unsupported(p);
2724 path_make_options_unique(p);
2725 path_create_settings(p);
2727 p->supported = TRUE;
2729 p->min_dB = INFINITY;
2730 p->max_dB = -INFINITY;
2732 for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) {
2733 if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) {
2734 if (p->min_dB > min_dB[t])
2735 p->min_dB = min_dB[t];
2737 if (p->max_dB < max_dB[t])
2738 p->max_dB = max_dB[t];
2742 return 0;
2745 void pa_alsa_setting_dump(pa_alsa_setting *s) {
2746 pa_assert(s);
2748 pa_log_debug("Setting %s (%s) priority=%u",
2749 s->name,
2750 pa_strnull(s->description),
2751 s->priority);
2754 void pa_alsa_jack_dump(pa_alsa_jack *j) {
2755 pa_assert(j);
2757 pa_log_debug("Jack %s, alsa_name='%s', detection %s", j->name, j->alsa_name, j->has_control ? "possible" : "unavailable");
2760 void pa_alsa_option_dump(pa_alsa_option *o) {
2761 pa_assert(o);
2763 pa_log_debug("Option %s (%s/%s) index=%i, priority=%u",
2764 o->alsa_name,
2765 pa_strnull(o->name),
2766 pa_strnull(o->description),
2767 o->alsa_idx,
2768 o->priority);
2771 void pa_alsa_element_dump(pa_alsa_element *e) {
2772 pa_alsa_option *o;
2773 pa_assert(e);
2775 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",
2776 e->alsa_name,
2777 e->direction,
2778 e->switch_use,
2779 e->volume_use,
2780 e->volume_limit,
2781 e->enumeration_use,
2782 e->required,
2783 e->required_any,
2784 e->required_absent,
2785 (long long unsigned) e->merged_mask,
2786 e->n_channels,
2787 pa_yes_no(e->override_map));
2789 PA_LLIST_FOREACH(o, e->options)
2790 pa_alsa_option_dump(o);
2793 void pa_alsa_path_dump(pa_alsa_path *p) {
2794 pa_alsa_element *e;
2795 pa_alsa_jack *j;
2796 pa_alsa_setting *s;
2797 pa_assert(p);
2799 pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, "
2800 "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g",
2801 p->name,
2802 pa_strnull(p->description),
2803 p->direction,
2804 p->priority,
2805 pa_yes_no(p->probed),
2806 pa_yes_no(p->supported),
2807 pa_yes_no(p->has_mute),
2808 pa_yes_no(p->has_volume),
2809 pa_yes_no(p->has_dB),
2810 p->min_volume, p->max_volume,
2811 p->min_dB, p->max_dB);
2813 PA_LLIST_FOREACH(e, p->elements)
2814 pa_alsa_element_dump(e);
2816 PA_LLIST_FOREACH(j, p->jacks)
2817 pa_alsa_jack_dump(j);
2819 PA_LLIST_FOREACH(s, p->settings)
2820 pa_alsa_setting_dump(s);
2823 static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2824 snd_mixer_selem_id_t *sid;
2825 snd_mixer_elem_t *me;
2827 pa_assert(e);
2828 pa_assert(m);
2829 pa_assert(cb);
2831 SELEM_INIT(sid, e->alsa_name);
2832 if (!(me = snd_mixer_find_selem(m, sid))) {
2833 pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
2834 return;
2837 snd_mixer_elem_set_callback(me, cb);
2838 snd_mixer_elem_set_callback_private(me, userdata);
2841 void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2842 pa_alsa_element *e;
2844 pa_assert(p);
2845 pa_assert(m);
2846 pa_assert(cb);
2848 PA_LLIST_FOREACH(e, p->elements)
2849 element_set_callback(e, m, cb, userdata);
2852 void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
2853 pa_alsa_path *p;
2854 void *state;
2856 pa_assert(ps);
2857 pa_assert(m);
2858 pa_assert(cb);
2860 PA_HASHMAP_FOREACH(p, ps->paths, state)
2861 pa_alsa_path_set_callback(p, m, cb, userdata);
2864 pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir) {
2865 pa_alsa_path_set *ps;
2866 char **pn = NULL, **en = NULL, **ie;
2867 pa_alsa_decibel_fix *db_fix;
2868 void *state, *state2;
2869 pa_hashmap *cache;
2871 pa_assert(m);
2872 pa_assert(m->profile_set);
2873 pa_assert(m->profile_set->decibel_fixes);
2874 pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT);
2876 if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction)
2877 return NULL;
2879 ps = pa_xnew0(pa_alsa_path_set, 1);
2880 ps->direction = direction;
2881 ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
2883 if (direction == PA_ALSA_DIRECTION_OUTPUT) {
2884 pn = m->output_path_names;
2885 cache = m->profile_set->output_paths;
2887 else if (direction == PA_ALSA_DIRECTION_INPUT) {
2888 pn = m->input_path_names;
2889 cache = m->profile_set->input_paths;
2892 if (pn) {
2893 char **in;
2895 for (in = pn; *in; in++) {
2896 pa_alsa_path *p = NULL;
2897 pa_bool_t duplicate = FALSE;
2898 char **kn;
2900 for (kn = pn; kn < in; kn++)
2901 if (pa_streq(*kn, *in)) {
2902 duplicate = TRUE;
2903 break;
2906 if (duplicate)
2907 continue;
2909 p = pa_hashmap_get(cache, *in);
2910 if (!p) {
2911 char *fn = pa_sprintf_malloc("%s.conf", *in);
2912 p = pa_alsa_path_new(paths_dir, fn, direction);
2913 pa_xfree(fn);
2914 if (p)
2915 pa_hashmap_put(cache, *in, p);
2917 pa_assert(pa_hashmap_get(cache, *in) == p);
2918 if (p)
2919 pa_hashmap_put(ps->paths, p, p);
2923 goto finish;
2926 if (direction == PA_ALSA_DIRECTION_OUTPUT)
2927 en = m->output_element;
2928 else if (direction == PA_ALSA_DIRECTION_INPUT)
2929 en = m->input_element;
2931 if (!en) {
2932 pa_alsa_path_set_free(ps);
2933 return NULL;
2936 for (ie = en; *ie; ie++) {
2937 char **je;
2938 pa_alsa_path *p;
2940 p = pa_alsa_path_synthesize(*ie, direction);
2942 /* Mark all other passed elements for require-absent */
2943 for (je = en; *je; je++) {
2944 pa_alsa_element *e;
2946 if (je == ie)
2947 continue;
2949 e = pa_xnew0(pa_alsa_element, 1);
2950 e->path = p;
2951 e->alsa_name = pa_xstrdup(*je);
2952 e->direction = direction;
2953 e->required_absent = PA_ALSA_REQUIRED_ANY;
2954 e->volume_limit = -1;
2956 PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
2957 p->last_element = e;
2960 pa_hashmap_put(ps->paths, *ie, p);
2963 finish:
2964 /* Assign decibel fixes to elements. */
2965 PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) {
2966 pa_alsa_path *p;
2968 PA_HASHMAP_FOREACH(p, ps->paths, state2) {
2969 pa_alsa_element *e;
2971 PA_LLIST_FOREACH(e, p->elements) {
2972 if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
2973 /* The profile set that contains the dB fix may be freed
2974 * before the element, so we have to copy the dB fix
2975 * object. */
2976 e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1);
2977 e->db_fix->profile_set = NULL;
2978 e->db_fix->name = pa_xstrdup(db_fix->name);
2979 e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long));
2985 return ps;
2988 void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
2989 pa_alsa_path *p;
2990 void *state;
2991 pa_assert(ps);
2993 pa_log_debug("Path Set %p, direction=%i",
2994 (void*) ps,
2995 ps->direction);
2997 PA_HASHMAP_FOREACH(p, ps->paths, state)
2998 pa_alsa_path_dump(p);
3002 static pa_bool_t options_have_option(pa_alsa_option *options, const char *alsa_name) {
3003 pa_alsa_option *o;
3005 pa_assert(options);
3006 pa_assert(alsa_name);
3008 PA_LLIST_FOREACH(o, options) {
3009 if (pa_streq(o->alsa_name, alsa_name))
3010 return TRUE;
3012 return FALSE;
3015 static pa_bool_t enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_options) {
3016 pa_alsa_option *oa, *ob;
3018 if (!a_options) return TRUE;
3019 if (!b_options) return FALSE;
3021 /* If there is an option A offers that B does not, then A is not a subset of B. */
3022 PA_LLIST_FOREACH(oa, a_options) {
3023 pa_bool_t found = FALSE;
3024 PA_LLIST_FOREACH(ob, b_options) {
3025 if (pa_streq(oa->alsa_name, ob->alsa_name)) {
3026 found = TRUE;
3027 break;
3030 if (!found)
3031 return FALSE;
3033 return TRUE;
3037 * Compares two elements to see if a is a subset of b
3039 static pa_bool_t element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
3040 pa_assert(a);
3041 pa_assert(b);
3042 pa_assert(m);
3044 /* General rules:
3045 * Every state is a subset of itself (with caveats for volume_limits and options)
3046 * IGNORE is a subset of every other state */
3048 /* Check the volume_use */
3049 if (a->volume_use != PA_ALSA_VOLUME_IGNORE) {
3051 /* "Constant" is subset of "Constant" only when their constant values are equal */
3052 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume)
3053 return FALSE;
3055 /* Different volume uses when b is not "Merge" means we are definitely not a subset */
3056 if (a->volume_use != b->volume_use && b->volume_use != PA_ALSA_VOLUME_MERGE)
3057 return FALSE;
3059 /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant.
3060 * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge"
3061 * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit" */
3062 if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) {
3063 long a_limit;
3065 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT)
3066 a_limit = a->constant_volume;
3067 else if (a->volume_use == PA_ALSA_VOLUME_ZERO) {
3068 long dB = 0;
3070 if (a->db_fix) {
3071 int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1);
3072 a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding);
3073 } else {
3074 snd_mixer_selem_id_t *sid;
3075 snd_mixer_elem_t *me;
3077 SELEM_INIT(sid, a->alsa_name);
3078 if (!(me = snd_mixer_find_selem(m, sid))) {
3079 pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
3080 return FALSE;
3083 if (a->direction == PA_ALSA_DIRECTION_OUTPUT) {
3084 if (snd_mixer_selem_ask_playback_dB_vol(me, dB, +1, &a_limit) < 0)
3085 return FALSE;
3086 } else {
3087 if (snd_mixer_selem_ask_capture_dB_vol(me, dB, -1, &a_limit) < 0)
3088 return FALSE;
3091 } else if (a->volume_use == PA_ALSA_VOLUME_OFF)
3092 a_limit = a->min_volume;
3093 else if (a->volume_use == PA_ALSA_VOLUME_MERGE)
3094 a_limit = a->volume_limit;
3095 else
3096 /* This should never be reached */
3097 pa_assert(FALSE);
3099 if (a_limit > b->volume_limit)
3100 return FALSE;
3103 if (a->volume_use == PA_ALSA_VOLUME_MERGE) {
3104 int s;
3105 /* If override-maps are different, they're not subsets */
3106 if (a->n_channels != b->n_channels)
3107 return FALSE;
3108 for (s = 0; s <= SND_MIXER_SCHN_LAST; s++)
3109 if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) {
3110 pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d",
3111 a->alsa_name, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
3112 return FALSE;
3117 if (a->switch_use != PA_ALSA_SWITCH_IGNORE) {
3118 /* "On" is a subset of "Mute".
3119 * "Off" is a subset of "Mute".
3120 * "On" is a subset of "Select", if there is an "Option:On" in B.
3121 * "Off" is a subset of "Select", if there is an "Option:Off" in B.
3122 * "Select" is a subset of "Select", if they have the same options (is this always true?). */
3124 if (a->switch_use != b->switch_use) {
3126 if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE
3127 || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON)
3128 return FALSE;
3130 if (b->switch_use == PA_ALSA_SWITCH_SELECT) {
3131 if (a->switch_use == PA_ALSA_SWITCH_ON) {
3132 if (!options_have_option(b->options, "on"))
3133 return FALSE;
3134 } else if (a->switch_use == PA_ALSA_SWITCH_OFF) {
3135 if (!options_have_option(b->options, "off"))
3136 return FALSE;
3139 } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) {
3140 if (!enumeration_is_subset(a->options, b->options))
3141 return FALSE;
3145 if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) {
3146 if (b->enumeration_use == PA_ALSA_ENUMERATION_IGNORE)
3147 return FALSE;
3148 if (!enumeration_is_subset(a->options, b->options))
3149 return FALSE;
3152 return TRUE;
3155 static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
3156 pa_alsa_path *p;
3157 void *state;
3159 pa_assert(ps);
3160 pa_assert(m);
3162 /* If we only have one path, then don't bother */
3163 if (pa_hashmap_size(ps->paths) < 2)
3164 return;
3166 PA_HASHMAP_FOREACH(p, ps->paths, state) {
3167 pa_alsa_path *p2;
3168 void *state2;
3170 PA_HASHMAP_FOREACH(p2, ps->paths, state2) {
3171 pa_alsa_element *ea, *eb;
3172 pa_alsa_jack *ja, *jb;
3173 pa_bool_t is_subset = TRUE;
3175 if (p == p2)
3176 continue;
3178 /* If a has a jack that b does not have, a is not a subset */
3179 PA_LLIST_FOREACH(ja, p->jacks) {
3180 pa_bool_t exists = FALSE;
3182 if (!ja->has_control)
3183 continue;
3185 PA_LLIST_FOREACH(jb, p2->jacks) {
3186 if (jb->has_control && !strcmp(jb->alsa_name, ja->alsa_name) &&
3187 (ja->state_plugged == jb->state_plugged) &&
3188 (ja->state_unplugged == jb->state_unplugged)) {
3189 exists = TRUE;
3190 break;
3194 if (!exists) {
3195 is_subset = FALSE;
3196 break;
3200 /* Compare the elements of each set... */
3201 pa_assert_se(ea = p->elements);
3202 pa_assert_se(eb = p2->elements);
3204 while (is_subset) {
3205 if (pa_streq(ea->alsa_name, eb->alsa_name)) {
3206 if (element_is_subset(ea, eb, m)) {
3207 ea = ea->next;
3208 eb = eb->next;
3209 if ((ea && !eb) || (!ea && eb))
3210 is_subset = FALSE;
3211 else if (!ea && !eb)
3212 break;
3213 } else
3214 is_subset = FALSE;
3216 } else
3217 is_subset = FALSE;
3220 if (is_subset) {
3221 pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name);
3222 pa_hashmap_remove(ps->paths, p);
3223 break;
3229 static pa_alsa_path* path_set_find_path_by_name(pa_alsa_path_set *ps, const char* name, pa_alsa_path *ignore)
3231 pa_alsa_path* p;
3232 void *state;
3234 PA_HASHMAP_FOREACH(p, ps->paths, state)
3235 if (p != ignore && pa_streq(p->name, name))
3236 return p;
3237 return NULL;
3240 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
3241 pa_alsa_path *p, *q;
3242 void *state, *state2;
3244 PA_HASHMAP_FOREACH(p, ps->paths, state) {
3245 unsigned i;
3246 char *m;
3248 q = path_set_find_path_by_name(ps, p->name, p);
3250 if (!q)
3251 continue;
3253 m = pa_xstrdup(p->name);
3255 /* OK, this name is not unique, hence let's rename */
3256 i = 1;
3257 PA_HASHMAP_FOREACH(q, ps->paths, state2) {
3258 char *nn, *nd;
3260 if (!pa_streq(q->name, m))
3261 continue;
3263 nn = pa_sprintf_malloc("%s-%u", m, i);
3264 pa_xfree(q->name);
3265 q->name = nn;
3267 nd = pa_sprintf_malloc("%s %u", q->description, i);
3268 pa_xfree(q->description);
3269 q->description = nd;
3271 i++;
3274 pa_xfree(m);
3278 static void mapping_free(pa_alsa_mapping *m) {
3279 pa_assert(m);
3281 pa_xfree(m->name);
3282 pa_xfree(m->description);
3284 pa_xstrfreev(m->device_strings);
3285 pa_xstrfreev(m->input_path_names);
3286 pa_xstrfreev(m->output_path_names);
3287 pa_xstrfreev(m->input_element);
3288 pa_xstrfreev(m->output_element);
3289 if (m->input_path_set)
3290 pa_alsa_path_set_free(m->input_path_set);
3291 if (m->output_path_set)
3292 pa_alsa_path_set_free(m->output_path_set);
3294 pa_assert(!m->input_pcm);
3295 pa_assert(!m->output_pcm);
3297 pa_xfree(m);
3300 static void profile_free(pa_alsa_profile *p) {
3301 pa_assert(p);
3303 pa_xfree(p->name);
3304 pa_xfree(p->description);
3306 pa_xstrfreev(p->input_mapping_names);
3307 pa_xstrfreev(p->output_mapping_names);
3309 if (p->input_mappings)
3310 pa_idxset_free(p->input_mappings, NULL, NULL);
3312 if (p->output_mappings)
3313 pa_idxset_free(p->output_mappings, NULL, NULL);
3315 pa_xfree(p);
3318 void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
3319 pa_assert(ps);
3321 if (ps->input_paths) {
3322 pa_alsa_path *p;
3324 while ((p = pa_hashmap_steal_first(ps->input_paths)))
3325 pa_alsa_path_free(p);
3327 pa_hashmap_free(ps->input_paths, NULL, NULL);
3330 if (ps->output_paths) {
3331 pa_alsa_path *p;
3333 while ((p = pa_hashmap_steal_first(ps->output_paths)))
3334 pa_alsa_path_free(p);
3336 pa_hashmap_free(ps->output_paths, NULL, NULL);
3339 if (ps->profiles) {
3340 pa_alsa_profile *p;
3342 while ((p = pa_hashmap_steal_first(ps->profiles)))
3343 profile_free(p);
3345 pa_hashmap_free(ps->profiles, NULL, NULL);
3348 if (ps->mappings) {
3349 pa_alsa_mapping *m;
3351 while ((m = pa_hashmap_steal_first(ps->mappings)))
3352 mapping_free(m);
3354 pa_hashmap_free(ps->mappings, NULL, NULL);
3357 if (ps->decibel_fixes) {
3358 pa_alsa_decibel_fix *db_fix;
3360 while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes)))
3361 decibel_fix_free(db_fix);
3363 pa_hashmap_free(ps->decibel_fixes, NULL, NULL);
3366 pa_xfree(ps);
3369 static pa_alsa_mapping *mapping_get(pa_alsa_profile_set *ps, const char *name) {
3370 pa_alsa_mapping *m;
3372 if (!pa_startswith(name, "Mapping "))
3373 return NULL;
3375 name += 8;
3377 if ((m = pa_hashmap_get(ps->mappings, name)))
3378 return m;
3380 m = pa_xnew0(pa_alsa_mapping, 1);
3381 m->profile_set = ps;
3382 m->name = pa_xstrdup(name);
3383 pa_channel_map_init(&m->channel_map);
3385 pa_hashmap_put(ps->mappings, m->name, m);
3387 return m;
3390 static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
3391 pa_alsa_profile *p;
3393 if (!pa_startswith(name, "Profile "))
3394 return NULL;
3396 name += 8;
3398 if ((p = pa_hashmap_get(ps->profiles, name)))
3399 return p;
3401 p = pa_xnew0(pa_alsa_profile, 1);
3402 p->profile_set = ps;
3403 p->name = pa_xstrdup(name);
3405 pa_hashmap_put(ps->profiles, p->name, p);
3407 return p;
3410 static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
3411 pa_alsa_decibel_fix *db_fix;
3413 if (!pa_startswith(name, "DecibelFix "))
3414 return NULL;
3416 name += 11;
3418 if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
3419 return db_fix;
3421 db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
3422 db_fix->profile_set = ps;
3423 db_fix->name = pa_xstrdup(name);
3425 pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
3427 return db_fix;
3430 static int mapping_parse_device_strings(
3431 const char *filename,
3432 unsigned line,
3433 const char *section,
3434 const char *lvalue,
3435 const char *rvalue,
3436 void *data,
3437 void *userdata) {
3439 pa_alsa_profile_set *ps = userdata;
3440 pa_alsa_mapping *m;
3442 pa_assert(ps);
3444 if (!(m = mapping_get(ps, section))) {
3445 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3446 return -1;
3449 pa_xstrfreev(m->device_strings);
3450 if (!(m->device_strings = pa_split_spaces_strv(rvalue))) {
3451 pa_log("[%s:%u] Device string list empty of '%s'", filename, line, section);
3452 return -1;
3455 return 0;
3458 static int mapping_parse_channel_map(
3459 const char *filename,
3460 unsigned line,
3461 const char *section,
3462 const char *lvalue,
3463 const char *rvalue,
3464 void *data,
3465 void *userdata) {
3467 pa_alsa_profile_set *ps = userdata;
3468 pa_alsa_mapping *m;
3470 pa_assert(ps);
3472 if (!(m = mapping_get(ps, section))) {
3473 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3474 return -1;
3477 if (!(pa_channel_map_parse(&m->channel_map, rvalue))) {
3478 pa_log("[%s:%u] Channel map invalid of '%s'", filename, line, section);
3479 return -1;
3482 return 0;
3485 static int mapping_parse_paths(
3486 const char *filename,
3487 unsigned line,
3488 const char *section,
3489 const char *lvalue,
3490 const char *rvalue,
3491 void *data,
3492 void *userdata) {
3494 pa_alsa_profile_set *ps = userdata;
3495 pa_alsa_mapping *m;
3497 pa_assert(ps);
3499 if (!(m = mapping_get(ps, section))) {
3500 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3501 return -1;
3504 if (pa_streq(lvalue, "paths-input")) {
3505 pa_xstrfreev(m->input_path_names);
3506 m->input_path_names = pa_split_spaces_strv(rvalue);
3507 } else {
3508 pa_xstrfreev(m->output_path_names);
3509 m->output_path_names = pa_split_spaces_strv(rvalue);
3512 return 0;
3515 static int mapping_parse_element(
3516 const char *filename,
3517 unsigned line,
3518 const char *section,
3519 const char *lvalue,
3520 const char *rvalue,
3521 void *data,
3522 void *userdata) {
3524 pa_alsa_profile_set *ps = userdata;
3525 pa_alsa_mapping *m;
3527 pa_assert(ps);
3529 if (!(m = mapping_get(ps, section))) {
3530 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3531 return -1;
3534 if (pa_streq(lvalue, "element-input")) {
3535 pa_xstrfreev(m->input_element);
3536 m->input_element = pa_split_spaces_strv(rvalue);
3537 } else {
3538 pa_xstrfreev(m->output_element);
3539 m->output_element = pa_split_spaces_strv(rvalue);
3542 return 0;
3545 static int mapping_parse_direction(
3546 const char *filename,
3547 unsigned line,
3548 const char *section,
3549 const char *lvalue,
3550 const char *rvalue,
3551 void *data,
3552 void *userdata) {
3554 pa_alsa_profile_set *ps = userdata;
3555 pa_alsa_mapping *m;
3557 pa_assert(ps);
3559 if (!(m = mapping_get(ps, section))) {
3560 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3561 return -1;
3564 if (pa_streq(rvalue, "input"))
3565 m->direction = PA_ALSA_DIRECTION_INPUT;
3566 else if (pa_streq(rvalue, "output"))
3567 m->direction = PA_ALSA_DIRECTION_OUTPUT;
3568 else if (pa_streq(rvalue, "any"))
3569 m->direction = PA_ALSA_DIRECTION_ANY;
3570 else {
3571 pa_log("[%s:%u] Direction %s invalid.", filename, line, rvalue);
3572 return -1;
3575 return 0;
3578 static int mapping_parse_description(
3579 const char *filename,
3580 unsigned line,
3581 const char *section,
3582 const char *lvalue,
3583 const char *rvalue,
3584 void *data,
3585 void *userdata) {
3587 pa_alsa_profile_set *ps = userdata;
3588 pa_alsa_profile *p;
3589 pa_alsa_mapping *m;
3591 pa_assert(ps);
3593 if ((m = mapping_get(ps, section))) {
3594 pa_xfree(m->description);
3595 m->description = pa_xstrdup(rvalue);
3596 } else if ((p = profile_get(ps, section))) {
3597 pa_xfree(p->description);
3598 p->description = pa_xstrdup(rvalue);
3599 } else {
3600 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3601 return -1;
3604 return 0;
3607 static int mapping_parse_priority(
3608 const char *filename,
3609 unsigned line,
3610 const char *section,
3611 const char *lvalue,
3612 const char *rvalue,
3613 void *data,
3614 void *userdata) {
3616 pa_alsa_profile_set *ps = userdata;
3617 pa_alsa_profile *p;
3618 pa_alsa_mapping *m;
3619 uint32_t prio;
3621 pa_assert(ps);
3623 if (pa_atou(rvalue, &prio) < 0) {
3624 pa_log("[%s:%u] Priority invalid of '%s'", filename, line, section);
3625 return -1;
3628 if ((m = mapping_get(ps, section)))
3629 m->priority = prio;
3630 else if ((p = profile_get(ps, section)))
3631 p->priority = prio;
3632 else {
3633 pa_log("[%s:%u] Section name %s invalid.", filename, line, section);
3634 return -1;
3637 return 0;
3640 static int profile_parse_mappings(
3641 const char *filename,
3642 unsigned line,
3643 const char *section,
3644 const char *lvalue,
3645 const char *rvalue,
3646 void *data,
3647 void *userdata) {
3649 pa_alsa_profile_set *ps = userdata;
3650 pa_alsa_profile *p;
3652 pa_assert(ps);
3654 if (!(p = profile_get(ps, section))) {
3655 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3656 return -1;
3659 if (pa_streq(lvalue, "input-mappings")) {
3660 pa_xstrfreev(p->input_mapping_names);
3661 p->input_mapping_names = pa_split_spaces_strv(rvalue);
3662 } else {
3663 pa_xstrfreev(p->output_mapping_names);
3664 p->output_mapping_names = pa_split_spaces_strv(rvalue);
3667 return 0;
3670 static int profile_parse_skip_probe(
3671 const char *filename,
3672 unsigned line,
3673 const char *section,
3674 const char *lvalue,
3675 const char *rvalue,
3676 void *data,
3677 void *userdata) {
3679 pa_alsa_profile_set *ps = userdata;
3680 pa_alsa_profile *p;
3681 int b;
3683 pa_assert(ps);
3685 if (!(p = profile_get(ps, section))) {
3686 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3687 return -1;
3690 if ((b = pa_parse_boolean(rvalue)) < 0) {
3691 pa_log("[%s:%u] Skip probe invalid of '%s'", filename, line, section);
3692 return -1;
3695 p->supported = b;
3697 return 0;
3700 static int decibel_fix_parse_db_values(
3701 const char *filename,
3702 unsigned line,
3703 const char *section,
3704 const char *lvalue,
3705 const char *rvalue,
3706 void *data,
3707 void *userdata) {
3709 pa_alsa_profile_set *ps = userdata;
3710 pa_alsa_decibel_fix *db_fix;
3711 char **items;
3712 char *item;
3713 long *db_values;
3714 unsigned n = 8; /* Current size of the db_values table. */
3715 unsigned min_step = 0;
3716 unsigned max_step = 0;
3717 unsigned i = 0; /* Index to the items table. */
3718 unsigned prev_step = 0;
3719 double prev_db = 0;
3721 pa_assert(filename);
3722 pa_assert(section);
3723 pa_assert(lvalue);
3724 pa_assert(rvalue);
3725 pa_assert(ps);
3727 if (!(db_fix = decibel_fix_get(ps, section))) {
3728 pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
3729 return -1;
3732 if (!(items = pa_split_spaces_strv(rvalue))) {
3733 pa_log("[%s:%u] Value missing", pa_strnull(filename), line);
3734 return -1;
3737 db_values = pa_xnew(long, n);
3739 while ((item = items[i++])) {
3740 char *s = item; /* Step value string. */
3741 char *d = item; /* dB value string. */
3742 uint32_t step;
3743 double db;
3745 /* Move d forward until it points to a colon or to the end of the item. */
3746 for (; *d && *d != ':'; ++d);
3748 if (d == s) {
3749 /* item started with colon. */
3750 pa_log("[%s:%u] No step value found in %s", filename, line, item);
3751 goto fail;
3754 if (!*d || !*(d + 1)) {
3755 /* No colon found, or it was the last character in item. */
3756 pa_log("[%s:%u] No dB value found in %s", filename, line, item);
3757 goto fail;
3760 /* pa_atou() needs a null-terminating string. Let's replace the colon
3761 * with a zero byte. */
3762 *d++ = '\0';
3764 if (pa_atou(s, &step) < 0) {
3765 pa_log("[%s:%u] Invalid step value: %s", filename, line, s);
3766 goto fail;
3769 if (pa_atod(d, &db) < 0) {
3770 pa_log("[%s:%u] Invalid dB value: %s", filename, line, d);
3771 goto fail;
3774 if (step <= prev_step && i != 1) {
3775 pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step);
3776 goto fail;
3779 if (db < prev_db && i != 1) {
3780 pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db);
3781 goto fail;
3784 if (i == 1) {
3785 min_step = step;
3786 db_values[0] = (long) (db * 100.0);
3787 prev_step = step;
3788 prev_db = db;
3789 } else {
3790 /* Interpolate linearly. */
3791 double db_increment = (db - prev_db) / (step - prev_step);
3793 for (; prev_step < step; ++prev_step, prev_db += db_increment) {
3795 /* Reallocate the db_values table if it's about to overflow. */
3796 if (prev_step + 1 - min_step == n) {
3797 n *= 2;
3798 db_values = pa_xrenew(long, db_values, n);
3801 db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0);
3805 max_step = step;
3808 db_fix->min_step = min_step;
3809 db_fix->max_step = max_step;
3810 pa_xfree(db_fix->db_values);
3811 db_fix->db_values = db_values;
3813 pa_xstrfreev(items);
3815 return 0;
3817 fail:
3818 pa_xstrfreev(items);
3819 pa_xfree(db_values);
3821 return -1;
3824 static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile,
3825 pa_alsa_direction_t direction) {
3827 pa_alsa_path *p;
3828 void *state;
3829 snd_pcm_t *pcm_handle;
3830 pa_alsa_path_set *ps;
3831 snd_mixer_t *mixer_handle;
3832 snd_hctl_t *hctl_handle;
3834 if (direction == PA_ALSA_DIRECTION_OUTPUT) {
3835 if (m->output_path_set)
3836 return; /* Already probed */
3837 m->output_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */
3838 pcm_handle = m->output_pcm;
3839 } else {
3840 if (m->input_path_set)
3841 return; /* Already probed */
3842 m->input_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */
3843 pcm_handle = m->input_pcm;
3846 if (!ps)
3847 return; /* No paths */
3849 pa_assert(pcm_handle);
3851 mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL, &hctl_handle);
3852 if (!mixer_handle || !hctl_handle) {
3853 /* Cannot open mixer, remove all entries */
3854 while (pa_hashmap_steal_first(ps->paths));
3855 return;
3859 PA_HASHMAP_FOREACH(p, ps->paths, state) {
3860 if (pa_alsa_path_probe(p, mixer_handle, hctl_handle, m->profile_set->ignore_dB) < 0) {
3861 pa_hashmap_remove(ps->paths, p);
3865 path_set_condense(ps, mixer_handle);
3866 path_set_make_paths_unique(ps);
3868 if (mixer_handle)
3869 snd_mixer_close(mixer_handle);
3871 pa_log_debug("Available mixer paths (after tidying):");
3872 pa_alsa_path_set_dump(ps);
3875 static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
3877 static const struct description_map well_known_descriptions[] = {
3878 { "analog-mono", N_("Analog Mono") },
3879 { "analog-stereo", N_("Analog Stereo") },
3880 { "analog-surround-21", N_("Analog Surround 2.1") },
3881 { "analog-surround-30", N_("Analog Surround 3.0") },
3882 { "analog-surround-31", N_("Analog Surround 3.1") },
3883 { "analog-surround-40", N_("Analog Surround 4.0") },
3884 { "analog-surround-41", N_("Analog Surround 4.1") },
3885 { "analog-surround-50", N_("Analog Surround 5.0") },
3886 { "analog-surround-51", N_("Analog Surround 5.1") },
3887 { "analog-surround-61", N_("Analog Surround 6.0") },
3888 { "analog-surround-61", N_("Analog Surround 6.1") },
3889 { "analog-surround-70", N_("Analog Surround 7.0") },
3890 { "analog-surround-71", N_("Analog Surround 7.1") },
3891 { "analog-4-channel-input", N_("Analog 4-channel Input") },
3892 { "iec958-stereo", N_("Digital Stereo (IEC958)") },
3893 { "iec958-passthrough", N_("Digital Passthrough (IEC958)") },
3894 { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") },
3895 { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") },
3896 { "iec958-dts-surround-51", N_("Digital Surround 5.1 (IEC958/DTS)") },
3897 { "hdmi-stereo", N_("Digital Stereo (HDMI)") },
3898 { "hdmi-surround-51", N_("Digital Surround 5.1 (HDMI)") }
3901 pa_assert(m);
3903 if (!pa_channel_map_valid(&m->channel_map)) {
3904 pa_log("Mapping %s is missing channel map.", m->name);
3905 return -1;
3908 if (!m->device_strings) {
3909 pa_log("Mapping %s is missing device strings.", m->name);
3910 return -1;
3913 if ((m->input_path_names && m->input_element) ||
3914 (m->output_path_names && m->output_element)) {
3915 pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name);
3916 return -1;
3919 if (!m->description)
3920 m->description = pa_xstrdup(lookup_description(m->name,
3921 well_known_descriptions,
3922 PA_ELEMENTSOF(well_known_descriptions)));
3924 if (!m->description)
3925 m->description = pa_xstrdup(m->name);
3927 if (bonus) {
3928 if (pa_channel_map_equal(&m->channel_map, bonus))
3929 m->priority += 50;
3930 else if (m->channel_map.channels == bonus->channels)
3931 m->priority += 30;
3934 return 0;
3937 void pa_alsa_mapping_dump(pa_alsa_mapping *m) {
3938 char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
3940 pa_assert(m);
3942 pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i",
3943 m->name,
3944 pa_strnull(m->description),
3945 m->priority,
3946 pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map),
3947 pa_yes_no(m->supported),
3948 m->direction);
3951 static void profile_set_add_auto_pair(
3952 pa_alsa_profile_set *ps,
3953 pa_alsa_mapping *m, /* output */
3954 pa_alsa_mapping *n /* input */) {
3956 char *name;
3957 pa_alsa_profile *p;
3959 pa_assert(ps);
3960 pa_assert(m || n);
3962 if (m && m->direction == PA_ALSA_DIRECTION_INPUT)
3963 return;
3965 if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT)
3966 return;
3968 if (m && n)
3969 name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name);
3970 else if (m)
3971 name = pa_sprintf_malloc("output:%s", m->name);
3972 else
3973 name = pa_sprintf_malloc("input:%s", n->name);
3975 if (pa_hashmap_get(ps->profiles, name)) {
3976 pa_xfree(name);
3977 return;
3980 p = pa_xnew0(pa_alsa_profile, 1);
3981 p->profile_set = ps;
3982 p->name = name;
3984 if (m) {
3985 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3986 pa_idxset_put(p->output_mappings, m, NULL);
3987 p->priority += m->priority * 100;
3990 if (n) {
3991 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
3992 pa_idxset_put(p->input_mappings, n, NULL);
3993 p->priority += n->priority;
3996 pa_hashmap_put(ps->profiles, p->name, p);
3999 static void profile_set_add_auto(pa_alsa_profile_set *ps) {
4000 pa_alsa_mapping *m, *n;
4001 void *m_state, *n_state;
4003 pa_assert(ps);
4005 PA_HASHMAP_FOREACH(m, ps->mappings, m_state) {
4006 profile_set_add_auto_pair(ps, m, NULL);
4008 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
4009 profile_set_add_auto_pair(ps, m, n);
4012 PA_HASHMAP_FOREACH(n, ps->mappings, n_state)
4013 profile_set_add_auto_pair(ps, NULL, n);
4016 static int profile_verify(pa_alsa_profile *p) {
4018 static const struct description_map well_known_descriptions[] = {
4019 { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") },
4020 { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") },
4021 { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") },
4022 { "off", N_("Off") }
4025 pa_assert(p);
4027 /* Replace the output mapping names by the actual mappings */
4028 if (p->output_mapping_names) {
4029 char **name;
4031 pa_assert(!p->output_mappings);
4032 p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
4034 for (name = p->output_mapping_names; *name; name++) {
4035 pa_alsa_mapping *m;
4036 char **in;
4037 pa_bool_t duplicate = FALSE;
4039 for (in = name + 1; *in; in++)
4040 if (pa_streq(*name, *in)) {
4041 duplicate = TRUE;
4042 break;
4045 if (duplicate)
4046 continue;
4048 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) {
4049 pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name);
4050 return -1;
4053 pa_idxset_put(p->output_mappings, m, NULL);
4055 if (p->supported)
4056 m->supported++;
4059 pa_xstrfreev(p->output_mapping_names);
4060 p->output_mapping_names = NULL;
4063 /* Replace the input mapping names by the actual mappings */
4064 if (p->input_mapping_names) {
4065 char **name;
4067 pa_assert(!p->input_mappings);
4068 p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
4070 for (name = p->input_mapping_names; *name; name++) {
4071 pa_alsa_mapping *m;
4072 char **in;
4073 pa_bool_t duplicate = FALSE;
4075 for (in = name + 1; *in; in++)
4076 if (pa_streq(*name, *in)) {
4077 duplicate = TRUE;
4078 break;
4081 if (duplicate)
4082 continue;
4084 if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) {
4085 pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name);
4086 return -1;
4089 pa_idxset_put(p->input_mappings, m, NULL);
4091 if (p->supported)
4092 m->supported++;
4095 pa_xstrfreev(p->input_mapping_names);
4096 p->input_mapping_names = NULL;
4099 if (!p->input_mappings && !p->output_mappings) {
4100 pa_log("Profile '%s' lacks mappings.", p->name);
4101 return -1;
4104 if (!p->description)
4105 p->description = pa_xstrdup(lookup_description(p->name,
4106 well_known_descriptions,
4107 PA_ELEMENTSOF(well_known_descriptions)));
4109 if (!p->description) {
4110 pa_strbuf *sb;
4111 uint32_t idx;
4112 pa_alsa_mapping *m;
4114 sb = pa_strbuf_new();
4116 if (p->output_mappings)
4117 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
4118 if (!pa_strbuf_isempty(sb))
4119 pa_strbuf_puts(sb, " + ");
4121 pa_strbuf_printf(sb, _("%s Output"), m->description);
4124 if (p->input_mappings)
4125 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
4126 if (!pa_strbuf_isempty(sb))
4127 pa_strbuf_puts(sb, " + ");
4129 pa_strbuf_printf(sb, _("%s Input"), m->description);
4132 p->description = pa_strbuf_tostring_free(sb);
4135 return 0;
4138 void pa_alsa_profile_dump(pa_alsa_profile *p) {
4139 uint32_t idx;
4140 pa_alsa_mapping *m;
4141 pa_assert(p);
4143 pa_log_debug("Profile %s (%s), priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u",
4144 p->name,
4145 pa_strnull(p->description),
4146 p->priority,
4147 pa_yes_no(p->supported),
4148 p->input_mappings ? pa_idxset_size(p->input_mappings) : 0,
4149 p->output_mappings ? pa_idxset_size(p->output_mappings) : 0);
4151 if (p->input_mappings)
4152 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
4153 pa_log_debug("Input %s", m->name);
4155 if (p->output_mappings)
4156 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
4157 pa_log_debug("Output %s", m->name);
4160 static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) {
4161 pa_assert(db_fix);
4163 /* Check that the dB mapping has been configured. Since "db-values" is
4164 * currently the only option in the DecibelFix section, and decibel fix
4165 * objects don't get created if a DecibelFix section is empty, this is
4166 * actually a redundant check. Having this may prevent future bugs,
4167 * however. */
4168 if (!db_fix->db_values) {
4169 pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name);
4170 return -1;
4173 return 0;
4176 void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) {
4177 char *db_values = NULL;
4179 pa_assert(db_fix);
4181 if (db_fix->db_values) {
4182 pa_strbuf *buf;
4183 unsigned long i, nsteps;
4185 pa_assert(db_fix->min_step <= db_fix->max_step);
4186 nsteps = db_fix->max_step - db_fix->min_step + 1;
4188 buf = pa_strbuf_new();
4189 for (i = 0; i < nsteps; ++i)
4190 pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0);
4192 db_values = pa_strbuf_tostring_free(buf);
4195 pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s",
4196 db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values));
4198 pa_xfree(db_values);
4201 pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) {
4202 pa_alsa_profile_set *ps;
4203 pa_alsa_profile *p;
4204 pa_alsa_mapping *m;
4205 pa_alsa_decibel_fix *db_fix;
4206 char *fn;
4207 int r;
4208 void *state;
4210 static pa_config_item items[] = {
4211 /* [General] */
4212 { "auto-profiles", pa_config_parse_bool, NULL, "General" },
4214 /* [Mapping ...] */
4215 { "device-strings", mapping_parse_device_strings, NULL, NULL },
4216 { "channel-map", mapping_parse_channel_map, NULL, NULL },
4217 { "paths-input", mapping_parse_paths, NULL, NULL },
4218 { "paths-output", mapping_parse_paths, NULL, NULL },
4219 { "element-input", mapping_parse_element, NULL, NULL },
4220 { "element-output", mapping_parse_element, NULL, NULL },
4221 { "direction", mapping_parse_direction, NULL, NULL },
4223 /* Shared by [Mapping ...] and [Profile ...] */
4224 { "description", mapping_parse_description, NULL, NULL },
4225 { "priority", mapping_parse_priority, NULL, NULL },
4227 /* [Profile ...] */
4228 { "input-mappings", profile_parse_mappings, NULL, NULL },
4229 { "output-mappings", profile_parse_mappings, NULL, NULL },
4230 { "skip-probe", profile_parse_skip_probe, NULL, NULL },
4232 /* [DecibelFix ...] */
4233 { "db-values", decibel_fix_parse_db_values, NULL, NULL },
4234 { NULL, NULL, NULL, NULL }
4237 ps = pa_xnew0(pa_alsa_profile_set, 1);
4238 ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4239 ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4240 ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4241 ps->input_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4242 ps->output_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4244 items[0].data = &ps->auto_profiles;
4246 if (!fname)
4247 fname = "default.conf";
4249 fn = pa_maybe_prefix_path(fname,
4250 pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
4251 PA_ALSA_PROFILE_SETS_DIR);
4253 r = pa_config_parse(fn, NULL, items, ps);
4254 pa_xfree(fn);
4256 if (r < 0)
4257 goto fail;
4259 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4260 if (mapping_verify(m, bonus) < 0)
4261 goto fail;
4263 if (ps->auto_profiles)
4264 profile_set_add_auto(ps);
4266 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4267 if (profile_verify(p) < 0)
4268 goto fail;
4270 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
4271 if (decibel_fix_verify(db_fix) < 0)
4272 goto fail;
4274 return ps;
4276 fail:
4277 pa_alsa_profile_set_free(ps);
4278 return NULL;
4281 static void profile_finalize_probing(pa_alsa_profile *to_be_finalized, pa_alsa_profile *next) {
4282 pa_alsa_mapping *m;
4283 uint32_t idx;
4285 if (!to_be_finalized)
4286 return;
4288 if (to_be_finalized->output_mappings)
4289 PA_IDXSET_FOREACH(m, to_be_finalized->output_mappings, idx) {
4291 if (!m->output_pcm)
4292 continue;
4294 if (to_be_finalized->supported)
4295 m->supported++;
4297 /* If this mapping is also in the next profile, we won't close the
4298 * pcm handle here, because it would get immediately reopened
4299 * anyway. */
4300 if (next && next->output_mappings && pa_idxset_get_by_data(next->output_mappings, m, NULL))
4301 continue;
4303 snd_pcm_close(m->output_pcm);
4304 m->output_pcm = NULL;
4307 if (to_be_finalized->input_mappings)
4308 PA_IDXSET_FOREACH(m, to_be_finalized->input_mappings, idx) {
4310 if (!m->input_pcm)
4311 continue;
4313 if (to_be_finalized->supported)
4314 m->supported++;
4316 /* If this mapping is also in the next profile, we won't close the
4317 * pcm handle here, because it would get immediately reopened
4318 * anyway. */
4319 if (next && next->input_mappings && pa_idxset_get_by_data(next->input_mappings, m, NULL))
4320 continue;
4322 snd_pcm_close(m->input_pcm);
4323 m->input_pcm = NULL;
4327 static snd_pcm_t* mapping_open_pcm(pa_alsa_mapping *m,
4328 const pa_sample_spec *ss,
4329 const char *dev_id,
4330 int mode,
4331 unsigned default_n_fragments,
4332 unsigned default_fragment_size_msec) {
4334 pa_sample_spec try_ss = *ss;
4335 pa_channel_map try_map = m->channel_map;
4336 snd_pcm_uframes_t try_period_size, try_buffer_size;
4338 try_ss.channels = try_map.channels;
4340 try_period_size =
4341 pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
4342 pa_frame_size(&try_ss);
4343 try_buffer_size = default_n_fragments * try_period_size;
4345 return pa_alsa_open_by_template(
4346 m->device_strings, dev_id, NULL, &try_ss,
4347 &try_map, mode, &try_period_size,
4348 &try_buffer_size, 0, NULL, NULL, TRUE);
4351 static void paths_drop_unsupported(pa_hashmap* h) {
4353 void* state = NULL;
4354 const void* key;
4355 pa_alsa_path* p;
4357 pa_assert(h);
4358 p = pa_hashmap_iterate(h, &state, &key);
4359 while (p) {
4360 if (p->supported <= 0) {
4361 pa_hashmap_remove(h, key);
4362 pa_alsa_path_free(p);
4364 p = pa_hashmap_iterate(h, &state, &key);
4368 void pa_alsa_profile_set_probe(
4369 pa_alsa_profile_set *ps,
4370 const char *dev_id,
4371 const pa_sample_spec *ss,
4372 unsigned default_n_fragments,
4373 unsigned default_fragment_size_msec) {
4375 void *state;
4376 pa_alsa_profile *p, *last = NULL;
4377 pa_alsa_mapping *m;
4379 pa_assert(ps);
4380 pa_assert(dev_id);
4381 pa_assert(ss);
4383 if (ps->probed)
4384 return;
4386 PA_HASHMAP_FOREACH(p, ps->profiles, state) {
4387 uint32_t idx;
4389 /* Skip if this is already marked that it is supported (i.e. from the config file) */
4390 if (!p->supported) {
4392 pa_log_debug("Looking at profile %s", p->name);
4393 profile_finalize_probing(last, p);
4394 p->supported = TRUE;
4396 /* Check if we can open all new ones */
4397 if (p->output_mappings)
4398 PA_IDXSET_FOREACH(m, p->output_mappings, idx) {
4400 if (m->output_pcm)
4401 continue;
4403 pa_log_debug("Checking for playback on %s (%s)", m->description, m->name);
4404 if (!(m->output_pcm = mapping_open_pcm(m, ss, dev_id,
4405 SND_PCM_STREAM_PLAYBACK,
4406 default_n_fragments,
4407 default_fragment_size_msec))) {
4408 p->supported = FALSE;
4409 break;
4413 if (p->input_mappings && p->supported)
4414 PA_IDXSET_FOREACH(m, p->input_mappings, idx) {
4416 if (m->input_pcm)
4417 continue;
4419 pa_log_debug("Checking for recording on %s (%s)", m->description, m->name);
4420 if (!(m->input_pcm = mapping_open_pcm(m, ss, dev_id,
4421 SND_PCM_STREAM_CAPTURE,
4422 default_n_fragments,
4423 default_fragment_size_msec))) {
4424 p->supported = FALSE;
4425 break;
4429 last = p;
4431 if (!p->supported)
4432 continue;
4435 pa_log_debug("Profile %s supported.", p->name);
4437 if (p->output_mappings)
4438 PA_IDXSET_FOREACH(m, p->output_mappings, idx)
4439 if (m->output_pcm)
4440 mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT);
4442 if (p->input_mappings)
4443 PA_IDXSET_FOREACH(m, p->input_mappings, idx)
4444 if (m->input_pcm)
4445 mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT);
4448 /* Clean up */
4449 profile_finalize_probing(last, NULL);
4451 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4452 if (!p->supported) {
4453 pa_hashmap_remove(ps->profiles, p->name);
4454 profile_free(p);
4457 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4458 if (m->supported <= 0) {
4459 pa_hashmap_remove(ps->mappings, m->name);
4460 mapping_free(m);
4463 paths_drop_unsupported(ps->input_paths);
4464 paths_drop_unsupported(ps->output_paths);
4466 ps->probed = TRUE;
4469 void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
4470 pa_alsa_profile *p;
4471 pa_alsa_mapping *m;
4472 pa_alsa_decibel_fix *db_fix;
4473 void *state;
4475 pa_assert(ps);
4477 pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u",
4478 (void*)
4480 pa_yes_no(ps->auto_profiles),
4481 pa_yes_no(ps->probed),
4482 pa_hashmap_size(ps->mappings),
4483 pa_hashmap_size(ps->profiles),
4484 pa_hashmap_size(ps->decibel_fixes));
4486 PA_HASHMAP_FOREACH(m, ps->mappings, state)
4487 pa_alsa_mapping_dump(m);
4489 PA_HASHMAP_FOREACH(p, ps->profiles, state)
4490 pa_alsa_profile_dump(p);
4492 PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
4493 pa_alsa_decibel_fix_dump(db_fix);
4496 static pa_device_port* device_port_alsa_init(pa_hashmap *ports,
4497 const char* name,
4498 const char* description,
4499 pa_alsa_path *path,
4500 pa_alsa_setting *setting,
4501 pa_card_profile *cp,
4502 pa_hashmap *extra,
4503 pa_core *core) {
4505 pa_device_port * p = pa_hashmap_get(ports, name);
4506 if (!p) {
4507 pa_alsa_port_data *data;
4509 p = pa_device_port_new(core, name, description, sizeof(pa_alsa_port_data));
4510 pa_assert(p);
4511 pa_hashmap_put(ports, p->name, p);
4512 p->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4514 data = PA_DEVICE_PORT_DATA(p);
4515 data->path = path;
4516 data->setting = setting;
4517 path->port = p;
4520 p->is_input |= path->direction == PA_ALSA_DIRECTION_ANY || path->direction == PA_ALSA_DIRECTION_INPUT;
4521 p->is_output |= path->direction == PA_ALSA_DIRECTION_ANY || path->direction == PA_ALSA_DIRECTION_OUTPUT;
4523 if (cp)
4524 pa_hashmap_put(p->profiles, cp->name, cp);
4526 if (extra) {
4527 pa_hashmap_put(extra, p->name, p);
4528 pa_device_port_ref(p);
4531 return p;
4534 void pa_alsa_path_set_add_ports(
4535 pa_alsa_path_set *ps,
4536 pa_card_profile *cp,
4537 pa_hashmap *ports,
4538 pa_hashmap *extra,
4539 pa_core *core) {
4541 pa_alsa_path *path;
4542 void *state;
4544 pa_assert(ports);
4546 if (!ps)
4547 return;
4549 PA_HASHMAP_FOREACH(path, ps->paths, state) {
4550 if (!path->settings || !path->settings->next) {
4551 /* If there is no or just one setting we only need a
4552 * single entry */
4553 pa_device_port *port = device_port_alsa_init(ports, path->name,
4554 path->description, path, path->settings, cp, extra, core);
4555 port->priority = path->priority * 100;
4557 } else {
4558 pa_alsa_setting *s;
4559 PA_LLIST_FOREACH(s, path->settings) {
4560 pa_device_port *port;
4561 char *n, *d;
4563 n = pa_sprintf_malloc("%s;%s", path->name, s->name);
4565 if (s->description[0])
4566 d = pa_sprintf_malloc("%s / %s", path->description, s->description);
4567 else
4568 d = pa_xstrdup(path->description);
4570 port = device_port_alsa_init(ports, n, d, path, s, cp, extra, core);
4571 port->priority = path->priority * 100 + s->priority;
4573 pa_xfree(n);
4574 pa_xfree(d);
4580 void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps, pa_card *card) {
4582 pa_assert(p);
4583 pa_assert(!*p);
4584 pa_assert(ps);
4586 if (ps->paths && pa_hashmap_size(ps->paths) > 0) {
4587 pa_assert(card);
4588 *p = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
4589 pa_alsa_path_set_add_ports(ps, NULL, card->ports, *p, card->core);
4592 pa_log_debug("Added %u ports", *p ? pa_hashmap_size(*p) : 0);