chiark / gitweb /
restore db4.3 support (broken in previous commit)
[disorder] / lib / mixer-alsa.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007, 2008 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file lib/mixer-alsa.c
19  * @brief ALSA mixer support
20  *
21  * The documentation for ALSA's mixer support is completely hopeless,
22  * which is a particular nuisnace given it's got an incredibly verbose
23  * API.  Much of this code is cribbed from
24  * alsa-utils-1.0.13/amixer/amixer.c.
25  *
26  * Mono output devices are supported, but the support is not tested
27  * (as I don't one).
28  */
29
30 #include "common.h"
31
32 #if HAVE_ALSA_ASOUNDLIB_H
33
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <errno.h>
37 #include <stddef.h>
38 #include <ctype.h>
39 #include <sys/ioctl.h>
40 #include <alsa/asoundlib.h>
41
42 #include "configuration.h"
43 #include "mixer.h"
44 #include "log.h"
45 #include "syscalls.h"
46
47 /** @brief Shared state for ALSA mixer support */
48 struct alsa_mixer_state {
49   /** @brief Mixer handle */
50   snd_mixer_t *handle;
51
52   /** @brief Mixer control */
53   snd_mixer_elem_t *elem;
54
55   /** @brief Left channel */
56   snd_mixer_selem_channel_id_t left;
57
58   /** @brief Right channel */
59   snd_mixer_selem_channel_id_t right;
60
61   /** @brief Minimum level */
62   long min;
63
64   /** @brief Maximum level */
65   long max;
66 };
67
68 /** @brief Destroy a @ref alsa_mixer_state */
69 static void alsa_close(struct alsa_mixer_state *h) {
70   /* TODO h->elem */
71   if(h->handle)
72     snd_mixer_close(h->handle);
73 }
74
75 /** @brief Initialize a @ref alsa_mixer_state */
76 static int alsa_open(struct alsa_mixer_state *h) {
77   int err;
78   snd_mixer_selem_id_t *id;
79
80   snd_mixer_selem_id_alloca(&id);
81   memset(h, 0, sizeof h);
82   if((err = snd_mixer_open(&h->handle, 0))) {
83     error(0, "snd_mixer_open: %s", snd_strerror(err));
84     return -1;
85   }
86   if((err = snd_mixer_attach(h->handle, config->device))) {
87     error(0, "snd_mixer_attach %s: %s",
88           config->device, snd_strerror(err));
89     goto error;
90   }
91   if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
92     error(0, "snd_mixer_selem_register %s: %s",
93           config->device, snd_strerror(err));
94     goto error;
95   }
96   if((err = snd_mixer_load(h->handle))) {
97     error(0, "snd_mixer_load %s: %s",
98           config->device, snd_strerror(err));
99     goto error;
100   }
101   snd_mixer_selem_id_set_name(id, config->channel);
102   snd_mixer_selem_id_set_index(id, atoi(config->mixer));
103   if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
104     error(0, "device '%s' mixer control '%s,%s' does not exist",
105           config->device, config->channel, config->mixer);
106     goto error;
107   }
108   if(!snd_mixer_selem_has_playback_volume(h->elem)) {
109     error(0, "device '%s' mixer control '%s,%s' has no playback volume",
110           config->device, config->channel, config->mixer);
111     goto error;
112   }
113   if(snd_mixer_selem_is_playback_mono(h->elem)) {
114     h->left = h->right = SND_MIXER_SCHN_MONO;
115   } else {
116     h->left = SND_MIXER_SCHN_FRONT_LEFT;
117     h->right = SND_MIXER_SCHN_FRONT_RIGHT;
118   }
119   if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
120      || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
121     error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
122           config->device, config->channel, config->mixer);
123     goto error;
124   }
125   snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
126   return 0;
127 error:
128   alsa_close(h);
129   return -1;
130 }
131
132 /** @brief Convert a level to a percentage */
133 static int to_percent(const struct alsa_mixer_state *h, long n) {
134   return (n - h->min) * 100 / (h->max - h->min);
135 }
136
137 /** @brief Get ALSA volume */
138 static int alsa_get(int *left, int *right) {
139   struct alsa_mixer_state h[1];
140   long l, r;
141   int err;
142   
143   if(alsa_open(h))
144     return -1;
145   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
146      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
147     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
148     goto error;
149   }
150   *left = to_percent(h, l);
151   *right = to_percent(h, r);
152   alsa_close(h);
153   return 0;
154 error:
155   alsa_close(h);
156   return -1;
157 }
158
159 /** @brief Convert a percentage to a level */
160 static int from_percent(const struct alsa_mixer_state *h, int n) {
161   return h->min + n * (h->max - h->min) / 100;
162 }
163
164 /** @brief Set ALSA volume */
165 static int alsa_set(int *left, int *right) {
166   struct alsa_mixer_state h[1];
167   long l, r;
168   int err;
169   
170   if(alsa_open(h))
171     return -1;
172   /* Set the volume */
173   if(h->left == h->right) {
174     /* Mono output - just use the loudest */
175     if((err = snd_mixer_selem_set_playback_volume
176         (h->elem, h->left,
177          from_percent(h, *left > *right ? *left : *right)))) {
178       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
179       goto error;
180     }
181   } else {
182     /* Stereo output */
183     if((err = snd_mixer_selem_set_playback_volume
184         (h->elem, h->left, from_percent(h, *left)))
185        || (err = snd_mixer_selem_set_playback_volume
186            (h->elem, h->right, from_percent(h, *right)))) {
187       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
188       goto error;
189     }
190   }
191   /* Read it back to see what we ended up at */
192   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
193      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
194     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
195     goto error;
196   }
197   *left = to_percent(h, l);
198   *right = to_percent(h, r);
199   alsa_close(h);
200   return 0;
201 error:
202   alsa_close(h);
203   return -1;
204 }
205
206 /** @brief ALSA mixer vtable */
207 const struct mixer mixer_alsa = {
208   BACKEND_ALSA,
209   alsa_get,
210   alsa_set,
211   "0",
212   "PCM"
213 };
214 #endif
215
216 /*
217 Local Variables:
218 c-basic-offset:2
219 comment-column:40
220 End:
221 */