1 /*
2 * A simple PCM loopback utility
3 * Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
4 *
5 * Author: Jaroslav Kysela <perex@perex.cz>
6 *
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 *
22 */
23
24 #include <ctype.h>
25 #include <syslog.h>
26 #include <alsa/asoundlib.h>
27 #include "alsaloop.h"
28
id_str(snd_ctl_elem_id_t * id)29 static char *id_str(snd_ctl_elem_id_t *id)
30 {
31 static char str[128];
32
33 sprintf(str, "%i,%s,%i,%i,%s,%i",
34 snd_ctl_elem_id_get_numid(id),
35 snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)),
36 snd_ctl_elem_id_get_device(id),
37 snd_ctl_elem_id_get_subdevice(id),
38 snd_ctl_elem_id_get_name(id),
39 snd_ctl_elem_id_get_index(id));
40 return str;
41 }
42
control_parse_id(const char * str,snd_ctl_elem_id_t * id)43 int control_parse_id(const char *str, snd_ctl_elem_id_t *id)
44 {
45 int c, size, numid;
46 char *ptr;
47
48 while (*str == ' ' || *str == '\t')
49 str++;
50 if (!(*str))
51 return -EINVAL;
52 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); /* default */
53 while (*str) {
54 if (!strncasecmp(str, "numid=", 6)) {
55 str += 6;
56 numid = atoi(str);
57 if (numid <= 0) {
58 logit(LOG_CRIT, "Invalid numid %d\n", numid);
59 return -EINVAL;
60 }
61 snd_ctl_elem_id_set_numid(id, atoi(str));
62 while (isdigit(*str))
63 str++;
64 } else if (!strncasecmp(str, "iface=", 6)) {
65 str += 6;
66 if (!strncasecmp(str, "card", 4)) {
67 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
68 str += 4;
69 } else if (!strncasecmp(str, "mixer", 5)) {
70 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
71 str += 5;
72 } else if (!strncasecmp(str, "pcm", 3)) {
73 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
74 str += 3;
75 } else if (!strncasecmp(str, "rawmidi", 7)) {
76 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_RAWMIDI);
77 str += 7;
78 } else if (!strncasecmp(str, "timer", 5)) {
79 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_TIMER);
80 str += 5;
81 } else if (!strncasecmp(str, "sequencer", 9)) {
82 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_SEQUENCER);
83 str += 9;
84 } else {
85 return -EINVAL;
86 }
87 } else if (!strncasecmp(str, "name=", 5)) {
88 char buf[64];
89 str += 5;
90 ptr = buf;
91 size = 0;
92 if (*str == '\'' || *str == '\"') {
93 c = *str++;
94 while (*str && *str != c) {
95 if (size < (int)sizeof(buf)) {
96 *ptr++ = *str;
97 size++;
98 }
99 str++;
100 }
101 if (*str == c)
102 str++;
103 } else {
104 while (*str && *str != ',') {
105 if (size < (int)sizeof(buf)) {
106 *ptr++ = *str;
107 size++;
108 }
109 str++;
110 }
111 }
112 *ptr = '\0';
113 snd_ctl_elem_id_set_name(id, buf);
114 } else if (!strncasecmp(str, "index=", 6)) {
115 str += 6;
116 snd_ctl_elem_id_set_index(id, atoi(str));
117 while (isdigit(*str))
118 str++;
119 } else if (!strncasecmp(str, "device=", 7)) {
120 str += 7;
121 snd_ctl_elem_id_set_device(id, atoi(str));
122 while (isdigit(*str))
123 str++;
124 } else if (!strncasecmp(str, "subdevice=", 10)) {
125 str += 10;
126 snd_ctl_elem_id_set_subdevice(id, atoi(str));
127 while (isdigit(*str))
128 str++;
129 }
130 if (*str == ',') {
131 str++;
132 } else {
133 if (*str)
134 return -EINVAL;
135 }
136 }
137 return 0;
138 }
139
control_id_match(snd_ctl_elem_id_t * id1,snd_ctl_elem_id_t * id2)140 int control_id_match(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2)
141 {
142 if (snd_ctl_elem_id_get_interface(id1) !=
143 snd_ctl_elem_id_get_interface(id2))
144 return 0;
145 if (snd_ctl_elem_id_get_device(id1) !=
146 snd_ctl_elem_id_get_device(id2))
147 return 0;
148 if (snd_ctl_elem_id_get_subdevice(id1) !=
149 snd_ctl_elem_id_get_subdevice(id2))
150 return 0;
151 if (strcmp(snd_ctl_elem_id_get_name(id1),
152 snd_ctl_elem_id_get_name(id2)) != 0)
153 return 0;
154 if (snd_ctl_elem_id_get_index(id1) !=
155 snd_ctl_elem_id_get_index(id2))
156 return 0;
157 return 1;
158 }
159
control_init1(struct loopback_handle * lhandle,struct loopback_control * ctl)160 static int control_init1(struct loopback_handle *lhandle,
161 struct loopback_control *ctl)
162 {
163 int err;
164
165 snd_ctl_elem_info_set_id(ctl->info, ctl->id);
166 snd_ctl_elem_value_set_id(ctl->value, ctl->id);
167 if (lhandle->ctl == NULL) {
168 logit(LOG_WARNING, "Unable to read control info for '%s'\n", id_str(ctl->id));
169 return -EIO;
170 }
171 err = snd_ctl_elem_info(lhandle->ctl, ctl->info);
172 if (err < 0) {
173 logit(LOG_WARNING, "Unable to read control info '%s': %s\n", id_str(ctl->id), snd_strerror(err));
174 return err;
175 }
176 err = snd_ctl_elem_read(lhandle->ctl, ctl->value);
177 if (err < 0) {
178 logit(LOG_WARNING, "Unable to read control value (init1) '%s': %s\n", id_str(ctl->id), snd_strerror(err));
179 return err;
180 }
181 return 0;
182 }
183
copy_value(struct loopback_control * dst,struct loopback_control * src)184 static int copy_value(struct loopback_control *dst,
185 struct loopback_control *src)
186 {
187 snd_ctl_elem_type_t type;
188 unsigned int count;
189 int i;
190
191 type = snd_ctl_elem_info_get_type(dst->info);
192 count = snd_ctl_elem_info_get_count(dst->info);
193 switch (type) {
194 case SND_CTL_ELEM_TYPE_BOOLEAN:
195 for (i = 0; i < count; i++)
196 snd_ctl_elem_value_set_boolean(dst->value,
197 i, snd_ctl_elem_value_get_boolean(src->value, i));
198 break;
199 case SND_CTL_ELEM_TYPE_INTEGER:
200 for (i = 0; i < count; i++) {
201 snd_ctl_elem_value_set_integer(dst->value,
202 i, snd_ctl_elem_value_get_integer(src->value, i));
203 }
204 break;
205 default:
206 logit(LOG_CRIT, "Unable to copy control value for type %s\n", snd_ctl_elem_type_name(type));
207 return -EINVAL;
208 }
209 return 0;
210 }
211
oss_set(struct loopback * loop,struct loopback_ossmixer * ossmix,int enable)212 static int oss_set(struct loopback *loop,
213 struct loopback_ossmixer *ossmix,
214 int enable)
215 {
216 char buf[128], file[128];
217 int fd;
218
219 if (loop->capt->card_number < 0)
220 return 0;
221 if (!enable) {
222 sprintf(buf, "%s \"\" 0\n", ossmix->oss_id);
223 } else {
224 sprintf(buf, "%s \"%s\" %i\n", ossmix->oss_id, ossmix->alsa_id, ossmix->alsa_index);
225 }
226 sprintf(file, "/proc/asound/card%i/oss_mixer", loop->capt->card_number);
227 if (verbose)
228 snd_output_printf(loop->output, "%s: Initialize OSS volume %s: %s", loop->id, file, buf);
229 fd = open(file, O_WRONLY);
230 if (fd >= 0 && write(fd, buf, strlen(buf)) == strlen(buf)) {
231 close(fd);
232 return 0;
233 }
234 if (fd >= 0)
235 close(fd);
236 logit(LOG_INFO, "%s: Unable to initialize OSS Mixer ID '%s'\n", loop->id, ossmix->oss_id);
237 return -1;
238 }
239
control_init2(struct loopback * loop,struct loopback_mixer * mix)240 static int control_init2(struct loopback *loop,
241 struct loopback_mixer *mix)
242 {
243 snd_ctl_elem_type_t type;
244 unsigned int count;
245 int err;
246
247 snd_ctl_elem_info_copy(mix->dst.info, mix->src.info);
248 snd_ctl_elem_info_set_id(mix->dst.info, mix->dst.id);
249 snd_ctl_elem_value_clear(mix->dst.value);
250 snd_ctl_elem_value_set_id(mix->dst.value, mix->dst.id);
251 type = snd_ctl_elem_info_get_type(mix->dst.info);
252 count = snd_ctl_elem_info_get_count(mix->dst.info);
253 snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
254 switch (type) {
255 case SND_CTL_ELEM_TYPE_BOOLEAN:
256 err = snd_ctl_elem_add_boolean(loop->capt->ctl,
257 mix->dst.id, count);
258 copy_value(&mix->dst, &mix->src);
259 break;
260 case SND_CTL_ELEM_TYPE_INTEGER:
261 err = snd_ctl_elem_add_integer(loop->capt->ctl,
262 mix->dst.id, count,
263 snd_ctl_elem_info_get_min(mix->dst.info),
264 snd_ctl_elem_info_get_max(mix->dst.info),
265 snd_ctl_elem_info_get_step(mix->dst.info));
266 copy_value(&mix->dst, &mix->src);
267 break;
268 default:
269 logit(LOG_CRIT, "Unable to handle control type %s\n", snd_ctl_elem_type_name(type));
270 err = -EINVAL;
271 break;
272 }
273 if (err < 0) {
274 logit(LOG_CRIT, "Unable to create control '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
275 return err;
276 }
277 err = snd_ctl_elem_unlock(loop->capt->ctl, mix->dst.id);
278 if (err < 0) {
279 logit(LOG_CRIT, "Unable to unlock control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
280 return err;
281 }
282 err = snd_ctl_elem_info(loop->capt->ctl, mix->dst.info);
283 if (err < 0) {
284 logit(LOG_CRIT, "Unable to read control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
285 return err;
286 }
287 if (snd_ctl_elem_info_is_tlv_writable(mix->dst.info)) {
288 unsigned int tlv[64];
289 err = snd_ctl_elem_tlv_read(loop->play->ctl,
290 mix->src.id,
291 tlv, sizeof(tlv));
292 if (err < 0) {
293 logit(LOG_CRIT, "Unable to read TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
294 tlv[0] = tlv[1] = 0;
295 }
296 err = snd_ctl_elem_tlv_write(loop->capt->ctl,
297 mix->dst.id,
298 tlv);
299 if (err < 0) {
300 logit(LOG_CRIT, "Unable to write TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
301 return err;
302 }
303 }
304 err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
305 if (err < 0) {
306 logit(LOG_CRIT, "Unable to write control value '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
307 return err;
308 }
309 return 0;
310 }
311
control_init(struct loopback * loop)312 int control_init(struct loopback *loop)
313 {
314 struct loopback_mixer *mix;
315 struct loopback_ossmixer *ossmix;
316 int err;
317
318 for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next)
319 oss_set(loop, ossmix, 0);
320 for (mix = loop->controls; mix; mix = mix->next) {
321 err = control_init1(loop->play, &mix->src);
322 if (err < 0) {
323 logit(LOG_WARNING, "%s: Disabling playback control '%s'\n", loop->id, id_str(mix->src.id));
324 mix->skip = 1;
325 continue;
326 }
327 err = control_init2(loop, mix);
328 if (err < 0)
329 return err;
330 }
331 for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
332 err = oss_set(loop, ossmix, 1);
333 if (err < 0) {
334 ossmix->skip = 1;
335 logit(LOG_WARNING, "%s: Disabling OSS mixer ID '%s'\n", loop->id, ossmix->oss_id);
336 }
337 }
338 return 0;
339 }
340
control_done(struct loopback * loop)341 int control_done(struct loopback *loop)
342 {
343 struct loopback_mixer *mix;
344 struct loopback_ossmixer *ossmix;
345 int err;
346
347 if (loop->capt->ctl == NULL)
348 return 0;
349 for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
350 err = oss_set(loop, ossmix, 0);
351 if (err < 0)
352 logit(LOG_WARNING, "%s: Unable to remove OSS control '%s'\n", loop->id, ossmix->oss_id);
353 }
354 for (mix = loop->controls; mix; mix = mix->next) {
355 if (mix->skip)
356 continue;
357 err = snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
358 if (err < 0)
359 logit(LOG_WARNING, "%s: Unable to remove control '%s': %s\n", loop->id, id_str(mix->dst.id), snd_strerror(err));
360 }
361 return 0;
362 }
363
control_event1(struct loopback * loop,struct loopback_mixer * mix,snd_ctl_event_t * ev,int capture)364 static int control_event1(struct loopback *loop,
365 struct loopback_mixer *mix,
366 snd_ctl_event_t *ev,
367 int capture)
368 {
369 unsigned int mask = snd_ctl_event_elem_get_mask(ev);
370 int err;
371
372 if (mask == SND_CTL_EVENT_MASK_REMOVE)
373 return 0;
374 if ((mask & SND_CTL_EVENT_MASK_VALUE) == 0)
375 return 0;
376 if (!capture) {
377 snd_ctl_elem_value_set_id(mix->src.value, mix->src.id);
378 err = snd_ctl_elem_read(loop->play->ctl, mix->src.value);
379 if (err < 0) {
380 logit(LOG_CRIT, "Unable to read control value (event1) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
381 return err;
382 }
383 copy_value(&mix->dst, &mix->src);
384 err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
385 if (err < 0) {
386 logit(LOG_CRIT, "Unable to write control value (event1) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
387 return err;
388 }
389 } else {
390 err = snd_ctl_elem_read(loop->capt->ctl, mix->dst.value);
391 if (err < 0) {
392 logit(LOG_CRIT, "Unable to read control value (event2) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
393 return err;
394 }
395 copy_value(&mix->src, &mix->dst);
396 err = snd_ctl_elem_write(loop->play->ctl, mix->src.value);
397 if (err < 0) {
398 logit(LOG_CRIT, "Unable to write control value (event2) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
399 return err;
400 }
401 }
402 return 0;
403 }
404
control_event(struct loopback_handle * lhandle,snd_ctl_event_t * ev)405 int control_event(struct loopback_handle *lhandle, snd_ctl_event_t *ev)
406 {
407 snd_ctl_elem_id_t *id2;
408 struct loopback_mixer *mix;
409 int capt = lhandle == lhandle->loopback->capt;
410 int err;
411
412 snd_ctl_elem_id_alloca(&id2);
413 snd_ctl_event_elem_get_id(ev, id2);
414 for (mix = lhandle->loopback->controls; mix; mix = mix->next) {
415 if (mix->skip)
416 continue;
417 if (control_id_match(id2, capt ? mix->dst.id : mix->src.id)) {
418 err = control_event1(lhandle->loopback, mix, ev, capt);
419 if (err < 0)
420 return err;
421 }
422 }
423 return 0;
424 }
425