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