chiark / gitweb /
update README.upgrades
[disorder] / lib / mixer-alsa.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007 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 <config.h>
33
34 #if HAVE_ALSA_ASOUNDLIB_H
35
36 #include "types.h"
37
38 #include <stdio.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <stddef.h>
45 #include <ctype.h>
46 #include <sys/ioctl.h>
47 #include <alsa/asoundlib.h>
48
49 #include "configuration.h"
50 #include "mixer.h"
51 #include "log.h"
52 #include "syscalls.h"
53
54 /** @brief Shared state for ALSA mixer support */
55 struct alsa_mixer_state {
56   /** @brief Mixer handle */
57   snd_mixer_t *handle;
58
59   /** @brief Mixer control */
60   snd_mixer_elem_t *elem;
61
62   /** @brief Left channel */
63   snd_mixer_selem_channel_id_t left;
64
65   /** @brief Right channel */
66   snd_mixer_selem_channel_id_t right;
67
68   /** @brief Minimum level */
69   long min;
70
71   /** @brief Maximum level */
72   long max;
73 };
74
75 /** @brief Destroy a @ref alsa_mixer_state */
76 static void alsa_close(struct alsa_mixer_state *h) {
77   /* TODO h->elem */
78   if(h->handle)
79     snd_mixer_close(h->handle);
80 }
81
82 /** @brief Initialize a @ref alsa_mixer_state */
83 static int alsa_open(struct alsa_mixer_state *h) {
84   int err;
85   snd_mixer_selem_id_t *id;
86
87   snd_mixer_selem_id_alloca(&id);
88   memset(h, 0, sizeof h);
89   if((err = snd_mixer_open(&h->handle, 0))) {
90     error(0, "snd_mixer_open: %s", snd_strerror(err));
91     return -1;
92   }
93   if((err = snd_mixer_attach(h->handle, config->device))) {
94     error(0, "snd_mixer_attach %s: %s",
95           config->device, snd_strerror(err));
96     goto error;
97   }
98   if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
99     error(0, "snd_mixer_selem_register %s: %s",
100           config->device, snd_strerror(err));
101     goto error;
102   }
103   if((err = snd_mixer_load(h->handle))) {
104     error(0, "snd_mixer_load %s: %s",
105           config->device, snd_strerror(err));
106     goto error;
107   }
108   snd_mixer_selem_id_set_name(id, config->channel);
109   snd_mixer_selem_id_set_index(id, atoi(config->mixer));
110   if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
111     error(0, "snd_mixer_find_selem returned NULL");
112     goto error;
113   }
114   if(!snd_mixer_selem_has_playback_volume(h->elem)) {
115     error(0, "configured mixer control has no playback volume");
116     goto error;
117   }
118   if(snd_mixer_selem_is_playback_mono(h->elem)) {
119     h->left = h->right = SND_MIXER_SCHN_MONO;
120   } else {
121     h->left = SND_MIXER_SCHN_FRONT_LEFT;
122     h->right = SND_MIXER_SCHN_FRONT_RIGHT;
123   }
124   if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
125      || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
126     error(0, "configured mixer control lacks required playback channels");
127     goto error;
128   }
129   snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
130   return 0;
131 error:
132   alsa_close(h);
133   return -1;
134 }
135
136 /** @brief Convert a level to a percentage */
137 static int to_percent(const struct alsa_mixer_state *h, long n) {
138   return (n - h->min) * 100 / (h->max - h->min);
139 }
140
141 /** @brief Get ALSA volume */
142 static int alsa_get(int *left, int *right) {
143   struct alsa_mixer_state h[1];
144   long l, r;
145   int err;
146   
147   if(alsa_open(h))
148     return -1;
149   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
150      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
151     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
152     goto error;
153   }
154   *left = to_percent(h, l);
155   *right = to_percent(h, r);
156   alsa_close(h);
157   return 0;
158 error:
159   alsa_close(h);
160   return -1;
161 }
162
163 /** @brief Convert a percentage to a level */
164 static int from_percent(const struct alsa_mixer_state *h, int n) {
165   return h->min + n * (h->max - h->min) / 100;
166 }
167
168 /** @brief Set ALSA volume */
169 static int alsa_set(int *left, int *right) {
170   struct alsa_mixer_state h[1];
171   long l, r;
172   int err;
173   
174   if(alsa_open(h))
175     return -1;
176   /* Set the volume */
177   if(h->left == h->right) {
178     /* Mono output - just use the loudest */
179     if((err = snd_mixer_selem_set_playback_volume
180         (h->elem, h->left,
181          from_percent(h, *left > *right ? *left : *right)))) {
182       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
183       goto error;
184     }
185   } else {
186     /* Stereo output */
187     if((err = snd_mixer_selem_set_playback_volume
188         (h->elem, h->left, from_percent(h, *left)))
189        || (err = snd_mixer_selem_set_playback_volume
190            (h->elem, h->right, from_percent(h, *right)))) {
191       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
192       goto error;
193     }
194   }
195   /* Read it back to see what we ended up at */
196   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
197      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
198     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
199     goto error;
200   }
201   *left = to_percent(h, l);
202   *right = to_percent(h, r);
203   alsa_close(h);
204   return 0;
205 error:
206   alsa_close(h);
207   return -1;
208 }
209
210 /** @brief ALSA mixer vtable */
211 const struct mixer mixer_alsa = {
212   BACKEND_ALSA,
213   alsa_get,
214   alsa_set,
215   "0",
216   "PCM"
217 };
218 #endif
219
220 /*
221 Local Variables:
222 c-basic-offset:2
223 comment-column:40
224 End:
225 */