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 */ |
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 | */ |