1 /* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
3 /** \file alsa_mix.c The ALSA mixer plugin. */
6 #include <alsa/asoundlib.h>
14 /* The ALSA mixer handle */
16 /* Copy of the alsa device name, e.g. "hw:0". */
18 /* ALSA's representation of the given mixer channel. */
19 snd_mixer_elem_t *elem;
20 /* Set in ->set_channel(), used for ->get() and ->set(). */
24 static void alsa_mix_close(struct mixer_handle **handle)
26 struct mixer_handle *h;
32 PARA_INFO_LOG("closing mixer handle\n");
33 if (h->mixer) /* nec. */
34 snd_mixer_close(h->mixer);
38 * The global ALSA configuration is cached for next usage,
39 * which causes valgrind to report many memory leaks. Calling
40 * snd_config_update_free_global() frees the cache.
42 snd_config_update_free_global();
47 static int alsa_mix_open(const char *dev, struct mixer_handle **handle)
51 struct mixer_handle *h;
53 PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
55 h = zalloc(sizeof(*h));
56 h->card = para_strdup(dev? dev : "hw:0");
57 ret = snd_mixer_open(&h->mixer, 0);
59 msg = make_message("snd_mixer_open() failed: %s",
63 ret = snd_mixer_attach(h->mixer, h->card);
65 msg = make_message("mixer attach error (%s): %s", h->card,
69 ret = snd_mixer_selem_register(h->mixer, NULL, NULL);
71 msg = make_message("mixer register error (%s): %s", h->card,
75 ret = snd_mixer_load(h->mixer);
77 msg = make_message("mixer load error (%s): %s", h->card,
85 PARA_NOTICE_LOG("%s\n", msg);
88 return -E_ALSA_MIX_OPEN;
91 static bool channel_has_playback(snd_mixer_selem_channel_id_t chn,
92 snd_mixer_elem_t *elem)
94 return snd_mixer_selem_has_playback_channel(elem, chn);
97 static char *alsa_mix_get_channels(struct mixer_handle *handle)
101 snd_mixer_selem_id_t *sid;
102 snd_mixer_elem_t *elem;
104 ret = snd_mixer_selem_id_malloc(&sid);
106 elem = snd_mixer_first_elem(handle->mixer);
107 for (; elem; elem = snd_mixer_elem_next(elem)) {
111 if (!channel_has_playback(0, elem))
113 if (!snd_mixer_selem_has_playback_volume(elem))
115 snd_mixer_selem_get_id(elem, sid);
116 name = snd_mixer_selem_id_get_name(sid);
117 list = make_message("%s%s%s",
123 snd_mixer_selem_id_free(sid);
127 static int alsa_mix_set_channel(struct mixer_handle *h,
128 const char *mixer_channel)
130 int ret, selem_id = 0;
131 snd_mixer_selem_id_t *sid;
134 mixer_channel = "Master";
135 ret = snd_mixer_selem_id_malloc(&sid);
137 snd_mixer_selem_id_set_index(sid, selem_id);
138 snd_mixer_selem_id_set_name(sid, mixer_channel);
139 h->elem = snd_mixer_find_selem(h->mixer, sid);
141 PARA_NOTICE_LOG("unable to find simple control '%s',%u\n",
142 snd_mixer_selem_id_get_name(sid),
143 snd_mixer_selem_id_get_index(sid));
144 ret = -E_BAD_CHANNEL;
147 ret = snd_mixer_selem_get_playback_volume_range(h->elem,
150 PARA_NOTICE_LOG("unable to get %s range (%s): %s\n",
151 mixer_channel, h->card, snd_strerror(ret));
152 ret = -E_ALSA_MIX_RANGE;
155 if (h->pmin >= h->pmax) {
156 PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n",
157 mixer_channel, h->pmin, h->pmax, h->card);
158 ret = -E_ALSA_MIX_RANGE;
163 snd_mixer_selem_id_free(sid);
167 static int alsa_mix_get(struct mixer_handle *h)
169 snd_mixer_selem_channel_id_t chn;
173 /* compute average over all channels */
174 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
178 if (!channel_has_playback(chn, h->elem))
180 ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val);
182 PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n",
183 (int)chn, h->card, snd_strerror(ret));
184 return -E_ALSA_MIX_GET_VAL;
186 /* update the rolling average */
187 avg = (val + n * avg) / (n + 1);
190 /* scale to 0..100 */
191 avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin);
195 static int alsa_mix_set(struct mixer_handle *h, int val)
198 snd_mixer_selem_channel_id_t chn;
199 long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin;
201 PARA_INFO_LOG("new value: %d\n", val);
202 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
203 if (!channel_has_playback(chn, h->elem))
205 ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled);
207 PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n",
208 (int)chn, h->card, snd_strerror(ret));
209 return -E_ALSA_MIX_SET_VAL;
215 /** The mixer operations for the ALSA mixer. */
216 const struct mixer alsa_mixer = {
218 .open = alsa_mix_open,
219 .get_channels = alsa_mix_get_channels,
220 .set_channel = alsa_mix_set_channel,
221 .close = alsa_mix_close,