tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

control.c (12208B)


      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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
     21  *
     22  */
     23 
     24 #include <ctype.h>
     25 #include <syslog.h>
     26 #include <alsa/asoundlib.h>
     27 #include "alsaloop.h"
     28 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 }