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
= para_calloc(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
,