configure: Print opus audio file handler if opus lib was found.
[paraslash.git] / alsa_mix.c
1 /*
2  * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file alsa_mix.c The ALSA mixer plugin. */
8
9 #include <regex.h>
10 #include <alsa/asoundlib.h>
11
12 #include "para.h"
13 #include "error.h"
14 #include "mix.h"
15 #include "string.h"
16
17 struct mixer_handle {
18         /* The ALSA mixer handle */
19         snd_mixer_t *mixer;
20         /* Copy of the alsa device name, e.g. "hw:0". */
21         char *card;
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(). */
25         long pmin, pmax;
26 };
27
28 static void alsa_mix_close(struct mixer_handle **handle)
29 {
30         struct mixer_handle *h;
31
32         if (!handle)
33                 return;
34         h = *handle;
35         if (h) {
36                 PARA_INFO_LOG("closing mixer handle\n");
37                 if (h->mixer) /* nec. */
38                         snd_mixer_close(h->mixer);
39                 free(h->card);
40                 free(h);
41                 /*
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.
45                  */
46                 snd_config_update_free_global();
47         }
48         *handle = NULL;
49 }
50
51 static int alsa_mix_open(const char *dev, struct mixer_handle **handle)
52 {
53         int ret;
54         char *msg;
55         struct mixer_handle *h;
56
57         PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
58         *handle = NULL;
59         h = para_calloc(sizeof(*h));
60         h->card = para_strdup(dev? dev : "hw:0");
61         ret = snd_mixer_open(&h->mixer, 0);
62         if (ret < 0) {
63                 msg = make_message("snd_mixer_open() failed: %s",
64                         snd_strerror(ret));
65                 goto fail;
66         }
67         ret = snd_mixer_attach(h->mixer, h->card);
68         if (ret < 0) {
69                 msg = make_message("mixer attach error (%s): %s", h->card,
70                         snd_strerror(ret));
71                 goto fail;
72         }
73         ret = snd_mixer_selem_register(h->mixer, NULL, NULL);
74         if (ret < 0) {
75                 msg = make_message("mixer register error (%s): %s", h->card,
76                         snd_strerror(ret));
77                 goto fail;
78         }
79         ret = snd_mixer_load(h->mixer);
80         if (ret < 0) {
81                 msg = make_message("mixer load error (%s): %s", h->card,
82                         snd_strerror(ret));
83                 goto fail;
84         }
85         /* success */
86         *handle = h;
87         return 1;
88 fail:
89         PARA_NOTICE_LOG("%s\n", msg);
90         free(msg);
91         alsa_mix_close(&h);
92         return -E_ALSA_MIX_OPEN;
93 }
94
95 static bool channel_has_playback(snd_mixer_selem_channel_id_t chn,
96                 snd_mixer_elem_t *elem)
97 {
98         return snd_mixer_selem_has_playback_channel(elem, chn);
99 }
100
101 static char *alsa_mix_get_channels(struct mixer_handle *handle)
102 {
103         char *list = NULL;
104         snd_mixer_selem_id_t *sid;
105         snd_mixer_elem_t *elem;
106
107         snd_mixer_selem_id_alloca(&sid);
108         elem = snd_mixer_first_elem(handle->mixer);
109         for (; elem; elem = snd_mixer_elem_next(elem)) {
110                 char *tmp = list;
111                 const char *name;
112
113                 if (!channel_has_playback(0, elem))
114                         continue;
115                 if (!snd_mixer_selem_has_playback_volume(elem))
116                         continue;
117                 snd_mixer_selem_get_id(elem, sid);
118                 name = snd_mixer_selem_id_get_name(sid);
119                 list = make_message("%s%s%s",
120                         list? list : "",
121                         list? ", " : "",
122                         name);
123                 free(tmp);
124         }
125         return list;
126 }
127
128 static int alsa_mix_set_channel(struct mixer_handle *h,
129                 const char *mixer_channel)
130 {
131         int ret, selem_id = 0;
132         snd_mixer_selem_id_t *sid;
133
134         if (!mixer_channel)
135                 mixer_channel = "Master";
136         snd_mixer_selem_id_alloca(&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);
140         if (!h->elem) {
141                 PARA_NOTICE_LOG("unable to find simple control '%s',%i\n",
142                         snd_mixer_selem_id_get_name(sid),
143                         snd_mixer_selem_id_get_index(sid));
144                 return -E_ALSA_MIX_BAD_ELEM;
145         }
146         ret = snd_mixer_selem_get_playback_volume_range(h->elem,
147                 &h->pmin, &h->pmax);
148         if (ret < 0) {
149                 PARA_NOTICE_LOG("unable to get %s range (%s): %s\n",
150                         mixer_channel, h->card, snd_strerror(ret));
151                 return -E_ALSA_MIX_BAD_ELEM;
152         }
153         if (h->pmin < 0 || h->pmax < 0 || h->pmin >= h->pmax) {
154                 PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n",
155                         mixer_channel, h->pmin, h->pmax, h->card);
156                 return -E_ALSA_MIX_BAD_ELEM;
157         }
158         return 1;
159 }
160
161 static int alsa_mix_get(struct mixer_handle *h)
162 {
163         snd_mixer_selem_channel_id_t chn;
164         int n = 0;
165         float avg = 0;
166
167         /* compute average over all channels */
168         for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
169                 long val;
170                 int ret;
171
172                 if (!channel_has_playback(chn, h->elem))
173                         continue;
174                 ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val);
175                 if (ret < 0) {
176                         PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n",
177                                 (int)chn, h->card, snd_strerror(ret));
178                         return -E_ALSA_MIX_GET_VAL;
179                 }
180                 /* update the rolling average */
181                 avg = (val + n * avg) / (n + 1);
182                 n++;
183         }
184         /* scale to 0..100 */
185         avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin);
186         return avg;
187 }
188
189 static int alsa_mix_set(struct mixer_handle *h, int val)
190 {
191         int ret;
192         snd_mixer_selem_channel_id_t chn;
193         long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin;
194
195         PARA_INFO_LOG("new value: %d\n", val);
196         for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
197                 if (!channel_has_playback(chn, h->elem))
198                         continue;
199                 ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled);
200                 if (ret < 0) {
201                         PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n",
202                                 (int)chn, h->card, snd_strerror(ret));
203                         return -E_ALSA_MIX_SET_VAL;
204                 }
205         }
206         return 1;
207 }
208
209 /**
210  * The init function of the ALSA mixer.
211  *
212  * \param self The structure to initialize.
213  *
214  * \sa struct \ref mixer, \ref oss_mix_init().
215  */
216 void alsa_mix_init(struct mixer *self)
217 {
218         self->open = alsa_mix_open;
219         self->get_channels = alsa_mix_get_channels;
220         self->set_channel = alsa_mix_set_channel;
221         self->close = alsa_mix_close;
222         self->get = alsa_mix_get;
223         self->set = alsa_mix_set;
224 }