chiark / gitweb /
ALSA mixer support.
[disorder] / lib / mixer-alsa.c
CommitLineData
b25aac59 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 */
55struct 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 */
76static 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 */
83static 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;
131error:
132 alsa_close(h);
133 return -1;
134}
135
136/** @brief Convert a level to a percentage */
137static 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 */
142static 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;
158error:
159 alsa_close(h);
160 return -1;
161}
162
163/** @brief Convert a percentage to a level */
164static 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 */
169static 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;
205error:
206 alsa_close(h);
207 return -1;
208}
209
210/** @brief ALSA mixer vtable */
211const struct mixer mixer_alsa = {
212 BACKEND_ALSA,
213 alsa_get,
214 alsa_set,
215 "0",
216 "PCM"
217};
218#endif
219
220/*
221Local Variables:
222c-basic-offset:2
223comment-column:40
224End:
225*/