mixer: Use lsu_merge_config_file_options().
[paraslash.git] / alsa_mix.c
1 /* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /** \file alsa_mix.c The ALSA mixer plugin. */
4
5 #include <regex.h>
6 #include <alsa/asoundlib.h>
7
8 #include "para.h"
9 #include "error.h"
10 #include "mix.h"
11 #include "string.h"
12
13 struct mixer_handle {
14         /* The ALSA mixer handle */
15         snd_mixer_t *mixer;
16         /* Copy of the alsa device name, e.g. "hw:0". */
17         char *card;
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(). */
21         long pmin, pmax;
22 };
23
24 static void alsa_mix_close(struct mixer_handle **handle)
25 {
26         struct mixer_handle *h;
27
28         if (!handle)
29                 return;
30         h = *handle;
31         if (h) {
32                 PARA_INFO_LOG("closing mixer handle\n");
33                 if (h->mixer) /* nec. */
34                         snd_mixer_close(h->mixer);
35                 free(h->card);
36                 free(h);
37                 /*
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.
41                  */
42                 snd_config_update_free_global();
43         }
44         *handle = NULL;
45 }
46
47 static int alsa_mix_open(const char *dev, struct mixer_handle **handle)
48 {
49         int ret;
50         char *msg;
51         struct mixer_handle *h;
52
53         PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
54         *handle = NULL;
55         h = para_calloc(sizeof(*h));
56         h->card = para_strdup(dev? dev : "hw:0");
57         ret = snd_mixer_open(&h->mixer, 0);
58         if (ret < 0) {
59                 msg = make_message("snd_mixer_open() failed: %s",
60                         snd_strerror(ret));
61                 goto fail;
62         }
63         ret = snd_mixer_attach(h->mixer, h->card);
64         if (ret < 0) {
65                 msg = make_message("mixer attach error (%s): %s", h->card,
66                         snd_strerror(ret));
67                 goto fail;
68         }
69         ret = snd_mixer_selem_register(h->mixer, NULL, NULL);
70         if (ret < 0) {
71                 msg = make_message("mixer register error (%s): %s", h->card,
72                         snd_strerror(ret));
73                 goto fail;
74         }
75         ret = snd_mixer_load(h->mixer);
76         if (ret < 0) {
77                 msg = make_message("mixer load error (%s): %s", h->card,
78                         snd_strerror(ret));
79                 goto fail;
80         }
81         /* success */
82         *handle = h;
83         return 1;
84 fail:
85         PARA_NOTICE_LOG("%s\n", msg);
86         free(msg);
87         alsa_mix_close(&h);
88         return -E_ALSA_MIX_OPEN;
89 }
90
91 static bool channel_has_playback(snd_mixer_selem_channel_id_t chn,
92                 snd_mixer_elem_t *elem)
93 {
94         return snd_mixer_selem_has_playback_channel(elem, chn);
95 }
96
97 static char *alsa_mix_get_channels(struct mixer_handle *handle)
98 {
99         int ret;
100         char *list = NULL;
101         snd_mixer_selem_id_t *sid;
102         snd_mixer_elem_t *elem;
103
104         ret = snd_mixer_selem_id_malloc(&sid);
105         assert(ret >= 0);
106         elem = snd_mixer_first_elem(handle->mixer);
107         for (; elem; elem = snd_mixer_elem_next(elem)) {
108                 char *tmp = list;
109                 const char *name;
110
111                 if (!channel_has_playback(0, elem))
112                         continue;
113                 if (!snd_mixer_selem_has_playback_volume(elem))
114                         continue;
115                 snd_mixer_selem_get_id(elem, sid);
116                 name = snd_mixer_selem_id_get_name(sid);
117                 list = make_message("%s%s%s",
118                         list? list : "",
119                         list? ", " : "",
120                         name);
121                 free(tmp);
122         }
123         snd_mixer_selem_id_free(sid);
124         return list;
125 }
126
127 static int alsa_mix_set_channel(struct mixer_handle *h,
128                 const char *mixer_channel)
129 {
130         int ret, selem_id = 0;
131         snd_mixer_selem_id_t *sid;
132
133         if (!mixer_channel)
134                 mixer_channel = "Master";
135         ret = snd_mixer_selem_id_malloc(&sid);
136         assert(ret >= 0);
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);
140         if (!h->elem) {
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;
145                 goto out;
146         }
147         ret = snd_mixer_selem_get_playback_volume_range(h->elem,
148                 &h->pmin, &h->pmax);
149         if (ret < 0) {
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;
153                 goto out;
154         }
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;
159                 goto out;
160         }
161         ret = 1;
162 out:
163         snd_mixer_selem_id_free(sid);
164         return ret;
165 }
166
167 static int alsa_mix_get(struct mixer_handle *h)
168 {
169         snd_mixer_selem_channel_id_t chn;
170         int n = 0;
171         float avg = 0;
172
173         /* compute average over all channels */
174         for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
175                 long val;
176                 int ret;
177
178                 if (!channel_has_playback(chn, h->elem))
179                         continue;
180                 ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val);
181                 if (ret < 0) {
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;
185                 }
186                 /* update the rolling average */
187                 avg = (val + n * avg) / (n + 1);
188                 n++;
189         }
190         /* scale to 0..100 */
191         avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin);
192         return avg;
193 }
194
195 static int alsa_mix_set(struct mixer_handle *h, int val)
196 {
197         int ret;
198         snd_mixer_selem_channel_id_t chn;
199         long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin;
200
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))
204                         continue;
205                 ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled);
206                 if (ret < 0) {
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;
210                 }
211         }
212         return 1;
213 }
214
215 /** The mixer operations for the ALSA mixer. */
216 const struct mixer alsa_mixer = {
217         .name = "alsa",
218         .open = alsa_mix_open,
219         .get_channels = alsa_mix_get_channels,
220         .set_channel = alsa_mix_set_channel,
221         .close = alsa_mix_close,
222         .get = alsa_mix_get,
223         .set = alsa_mix_set
224 };