xref: /third_party/alsa-utils/alsaloop/control.c (revision c72fcc34)
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
32static 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
46int 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
143int 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
163static 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
187static 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
214static 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
242static 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
314int 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
343int 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
366static 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
407int 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