chiark / gitweb /
Low level playlist database operations: read, createwrite, list, delete.
[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 *
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
05b75f8d 32#include "common.h"
b25aac59 33
34#if HAVE_ALSA_ASOUNDLIB_H
35
b25aac59 36#include <fcntl.h>
37#include <unistd.h>
b25aac59 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 */
50struct 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 */
71static 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 */
78static 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))) {
8899ace0
RK
106 error(0, "device '%s' mixer control '%s,%s' does not exist",
107 config->device, config->channel, config->mixer);
b25aac59 108 goto error;
109 }
110 if(!snd_mixer_selem_has_playback_volume(h->elem)) {
8899ace0
RK
111 error(0, "device '%s' mixer control '%s,%s' has no playback volume",
112 config->device, config->channel, config->mixer);
b25aac59 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)) {
8899ace0
RK
123 error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
124 config->device, config->channel, config->mixer);
b25aac59 125 goto error;
126 }
127 snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
128 return 0;
129error:
130 alsa_close(h);
131 return -1;
132}
133
134/** @brief Convert a level to a percentage */
135static 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 */
140static 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;
156error:
157 alsa_close(h);
158 return -1;
159}
160
161/** @brief Convert a percentage to a level */
162static 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 */
167static 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;
203error:
204 alsa_close(h);
205 return -1;
206}
207
208/** @brief ALSA mixer vtable */
209const struct mixer mixer_alsa = {
210 BACKEND_ALSA,
211 alsa_get,
212 alsa_set,
213 "0",
214 "PCM"
215};
216#endif
217
218/*
219Local Variables:
220c-basic-offset:2
221comment-column:40
222End:
223*/