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