chiark / gitweb /
Merge Core Audio fixes
[disorder] / lib / mixer-alsa.c
CommitLineData
b25aac59 1/*
2 * This file is part of DisOrder
5aff007d 3 * Copyright (C) 2007, 2008 Richard Kettlewell
b25aac59 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
b25aac59 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
b25aac59 8 * (at your option) any later version.
e7eb3a27
RK
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 *
b25aac59 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
b25aac59 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
05b75f8d 30#include "common.h"
b25aac59 31
32#if HAVE_ALSA_ASOUNDLIB_H
33
b25aac59 34#include <fcntl.h>
35#include <unistd.h>
b25aac59 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 */
48struct 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 */
69static 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 */
76static 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))) {
8899ace0
RK
104 error(0, "device '%s' mixer control '%s,%s' does not exist",
105 config->device, config->channel, config->mixer);
b25aac59 106 goto error;
107 }
108 if(!snd_mixer_selem_has_playback_volume(h->elem)) {
8899ace0
RK
109 error(0, "device '%s' mixer control '%s,%s' has no playback volume",
110 config->device, config->channel, config->mixer);
b25aac59 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)) {
8899ace0
RK
121 error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
122 config->device, config->channel, config->mixer);
b25aac59 123 goto error;
124 }
125 snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
126 return 0;
127error:
128 alsa_close(h);
129 return -1;
130}
131
132/** @brief Convert a level to a percentage */
133static 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 */
138static 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;
154error:
155 alsa_close(h);
156 return -1;
157}
158
159/** @brief Convert a percentage to a level */
160static 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 */
165static 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;
201error:
202 alsa_close(h);
203 return -1;
204}
205
206/** @brief ALSA mixer vtable */
207const struct mixer mixer_alsa = {
208 BACKEND_ALSA,
209 alsa_get,
210 alsa_set,
211 "0",
212 "PCM"
213};
214#endif
215
216/*
217Local Variables:
218c-basic-offset:2
219comment-column:40
220End:
221*/