2 * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
4 * Licensed under the GPL v2. For licencing details see COPYING.
7 /** \file alsa_mix.c The ALSA mixer plugin. */
10 #include <alsa/asoundlib.h>
18 /* The ALSA mixer handle */
20 /* Copy of the alsa device name, e.g. "hw:0". */
22 /* ALSA's representation of the given mixer channel. */
23 snd_mixer_elem_t
*elem
;
24 /* Set in ->set_channel(), used for ->get() and ->set(). */
28 static void alsa_mix_close(struct mixer_handle
**handle
)
30 struct mixer_handle
*h
;
36 PARA_INFO_LOG("closing mixer handle\n");
37 if (h
->mixer
) /* nec. */
38 snd_mixer_close(h
->mixer
);
42 * The global ALSA configuration is cached for next usage,
43 * which causes valgrind to report many memory leaks. Calling
44 * snd_config_update_free_global() frees the cache.
46 snd_config_update_free_global();
51 static int alsa_mix_open(const char *dev
, struct mixer_handle
**handle
)
55 struct mixer_handle
*h
;
57 PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
59 h
= para_calloc(sizeof(*h
));
60 h
->card
= para_strdup(dev
? dev
: "hw:0");
61 ret
= snd_mixer_open(&h
->mixer
, 0);
63 msg
= make_message("snd_mixer_open() failed: %s",
67 ret
= snd_mixer_attach(h
->mixer
, h
->card
);
69 msg
= make_message("mixer attach error (%s): %s", h
->card
,
73 ret
= snd_mixer_selem_register(h
->mixer
, NULL
, NULL
);
75 msg
= make_message("mixer register error (%s): %s", h
->card
,
79 ret
= snd_mixer_load(h
->mixer
);
81 msg
= make_message("mixer load error (%s): %s", h
->card
,
89 PARA_NOTICE_LOG("%s\n", msg
);
92 return -E_ALSA_MIX_OPEN
;
95 static bool channel_has_playback(snd_mixer_selem_channel_id_t chn
,
96 snd_mixer_elem_t
*elem
)
98 return snd_mixer_selem_has_playback_channel(elem
, chn
);
101 static char *alsa_mix_get_channels(struct mixer_handle
*handle
)
105 snd_mixer_selem_id_t
*sid
;
106 snd_mixer_elem_t
*elem
;
108 ret
= snd_mixer_selem_id_malloc(&sid
);
110 elem
= snd_mixer_first_elem(handle
->mixer
);
111 for (; elem
; elem
= snd_mixer_elem_next(elem
)) {
115 if (!channel_has_playback(0, elem
))
117 if (!snd_mixer_selem_has_playback_volume(elem
))
119 snd_mixer_selem_get_id(elem
, sid
);
120 name
= snd_mixer_selem_id_get_name(sid
);
121 list
= make_message("%s%s%s",
127 snd_mixer_selem_id_free(sid
);
131 static int alsa_mix_set_channel(struct mixer_handle
*h
,
132 const char *mixer_channel
)
134 int ret
, selem_id
= 0;
135 snd_mixer_selem_id_t
*sid
;
138 mixer_channel
= "Master";
139 ret
= snd_mixer_selem_id_malloc(&sid
);
141 snd_mixer_selem_id_set_index(sid
, selem_id
);
142 snd_mixer_selem_id_set_name(sid
, mixer_channel
);
143 h
->elem
= snd_mixer_find_selem(h
->mixer
, sid
);
145 PARA_NOTICE_LOG("unable to find simple control '%s',%i\n",
146 snd_mixer_selem_id_get_name(sid
),
147 snd_mixer_selem_id_get_index(sid
));
148 ret
= -E_BAD_CHANNEL
;
151 ret
= snd_mixer_selem_get_playback_volume_range(h
->elem
,
154 PARA_NOTICE_LOG("unable to get %s range (%s): %s\n",
155 mixer_channel
, h
->card
, snd_strerror(ret
));
156 ret
= -E_ALSA_MIX_RANGE
;
159 if (h
->pmin
>= h
->pmax
) {
160 PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n",
161 mixer_channel
, h
->pmin
, h
->pmax
, h
->card
);
162 ret
= -E_ALSA_MIX_RANGE
;
167 snd_mixer_selem_id_free(sid
);
171 static int alsa_mix_get(struct mixer_handle
*h
)
173 snd_mixer_selem_channel_id_t chn
;
177 /* compute average over all channels */
178 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; chn
++) {
182 if (!channel_has_playback(chn
, h
->elem
))
184 ret
= snd_mixer_selem_get_playback_volume(h
->elem
, chn
, &val
);
186 PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n",
187 (int)chn
, h
->card
, snd_strerror(ret
));
188 return -E_ALSA_MIX_GET_VAL
;
190 /* update the rolling average */
191 avg
= (val
+ n
* avg
) / (n
+ 1);
194 /* scale to 0..100 */
195 avg
= 100 * (avg
- h
->pmin
) / (h
->pmax
- h
->pmin
);
199 static int alsa_mix_set(struct mixer_handle
*h
, int val
)
202 snd_mixer_selem_channel_id_t chn
;
203 long scaled
= val
/ 100.0 * (h
->pmax
- h
->pmin
) + h
->pmin
;
205 PARA_INFO_LOG("new value: %d\n", val
);
206 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; chn
++) {
207 if (!channel_has_playback(chn
, h
->elem
))
209 ret
= snd_mixer_selem_set_playback_volume(h
->elem
, chn
, scaled
);
211 PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n",
212 (int)chn
, h
->card
, snd_strerror(ret
));
213 return -E_ALSA_MIX_SET_VAL
;
220 * The init function of the ALSA mixer.
222 * \param self The structure to initialize.
224 * \sa struct \ref mixer, \ref oss_mix_init().
226 void alsa_mix_init(struct mixer
*self
)
228 self
->open
= alsa_mix_open
;
229 self
->get_channels
= alsa_mix_get_channels
;
230 self
->set_channel
= alsa_mix_set_channel
;
231 self
->close
= alsa_mix_close
;
232 self
->get
= alsa_mix_get
;
233 self
->set
= alsa_mix_set
;