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