1 1.30 rillig /* $NetBSD: mixerctl.c,v 1.30 2021/12/26 15:36:49 rillig Exp $ */ 2 1.1 augustss 3 1.1 augustss /* 4 1.1 augustss * Copyright (c) 1997 The NetBSD Foundation, Inc. 5 1.1 augustss * All rights reserved. 6 1.1 augustss * 7 1.14 augustss * This code is derived from software contributed to The NetBSD Foundation 8 1.20 salo * by Lennart Augustsson (augustss (at) NetBSD.org) and Chuck Cranor. 9 1.1 augustss * 10 1.1 augustss * Redistribution and use in source and binary forms, with or without 11 1.1 augustss * modification, are permitted provided that the following conditions 12 1.1 augustss * are met: 13 1.1 augustss * 1. Redistributions of source code must retain the above copyright 14 1.1 augustss * notice, this list of conditions and the following disclaimer. 15 1.1 augustss * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 augustss * notice, this list of conditions and the following disclaimer in the 17 1.1 augustss * documentation and/or other materials provided with the distribution. 18 1.1 augustss * 19 1.1 augustss * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 augustss * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 augustss * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.7 jtc * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.7 jtc * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 augustss * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 augustss * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 augustss * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 augustss * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 augustss * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 augustss * POSSIBILITY OF SUCH DAMAGE. 30 1.1 augustss */ 31 1.18 agc #include <sys/cdefs.h> 32 1.18 agc 33 1.18 agc #ifndef lint 34 1.30 rillig __RCSID("$NetBSD: mixerctl.c,v 1.30 2021/12/26 15:36:49 rillig Exp $"); 35 1.18 agc #endif 36 1.18 agc 37 1.1 augustss #include <stdio.h> 38 1.1 augustss #include <stdlib.h> 39 1.1 augustss #include <fcntl.h> 40 1.1 augustss #include <err.h> 41 1.1 augustss #include <unistd.h> 42 1.1 augustss #include <string.h> 43 1.1 augustss #include <sys/types.h> 44 1.1 augustss #include <sys/ioctl.h> 45 1.1 augustss #include <sys/audioio.h> 46 1.1 augustss 47 1.17 augustss #include <paths.h> 48 1.12 augustss 49 1.29 christos static FILE *out = stdout; 50 1.29 christos static int vflag = 0; 51 1.1 augustss 52 1.29 christos static struct field { 53 1.1 augustss char *name; 54 1.1 augustss mixer_ctrl_t *valp; 55 1.1 augustss mixer_devinfo_t *infp; 56 1.4 augustss char changed; 57 1.1 augustss } *fields, *rfields; 58 1.1 augustss 59 1.29 christos static mixer_ctrl_t *values; 60 1.29 christos static mixer_devinfo_t *infos; 61 1.1 augustss 62 1.27 kre static const char mixer_path[] = _PATH_MIXER; 63 1.27 kre 64 1.15 augustss static char * 65 1.15 augustss catstr(char *p, char *q) 66 1.1 augustss { 67 1.19 itojun char *r; 68 1.19 itojun 69 1.19 itojun asprintf(&r, "%s.%s", p, q); 70 1.19 itojun if (!r) 71 1.29 christos err(EXIT_FAILURE, "malloc"); 72 1.1 augustss return r; 73 1.1 augustss } 74 1.1 augustss 75 1.15 augustss static struct field * 76 1.29 christos findfield(const char *name) 77 1.1 augustss { 78 1.1 augustss int i; 79 1.26 isaki for (i = 0; fields[i].name; i++) 80 1.1 augustss if (strcmp(fields[i].name, name) == 0) 81 1.1 augustss return &fields[i]; 82 1.1 augustss return 0; 83 1.1 augustss } 84 1.1 augustss 85 1.15 augustss static void 86 1.23 lukem prfield(struct field *p, const char *sep, int prvalset) 87 1.1 augustss { 88 1.1 augustss mixer_ctrl_t *m; 89 1.3 augustss int i, n; 90 1.1 augustss 91 1.1 augustss if (sep) 92 1.1 augustss fprintf(out, "%s%s", p->name, sep); 93 1.1 augustss m = p->valp; 94 1.1 augustss switch(m->type) { 95 1.1 augustss case AUDIO_MIXER_ENUM: 96 1.26 isaki for (i = 0; i < p->infp->un.e.num_mem; i++) 97 1.4 augustss if (p->infp->un.e.member[i].ord == m->un.ord) 98 1.4 augustss fprintf(out, "%s", 99 1.26 isaki p->infp->un.e.member[i].label.name); 100 1.1 augustss if (prvalset) { 101 1.1 augustss fprintf(out, " [ "); 102 1.26 isaki for (i = 0; i < p->infp->un.e.num_mem; i++) 103 1.15 augustss fprintf(out, "%s ", 104 1.15 augustss p->infp->un.e.member[i].label.name); 105 1.1 augustss fprintf(out, "]"); 106 1.1 augustss } 107 1.1 augustss break; 108 1.1 augustss case AUDIO_MIXER_SET: 109 1.26 isaki for (n = i = 0; i < p->infp->un.s.num_mem; i++) 110 1.3 augustss if (m->un.mask & p->infp->un.s.member[i].mask) 111 1.3 augustss fprintf(out, "%s%s", n++ ? "," : "", 112 1.26 isaki p->infp->un.s.member[i].label.name); 113 1.1 augustss if (prvalset) { 114 1.1 augustss fprintf(out, " { "); 115 1.26 isaki for (i = 0; i < p->infp->un.s.num_mem; i++) 116 1.15 augustss fprintf(out, "%s ", 117 1.15 augustss p->infp->un.s.member[i].label.name); 118 1.1 augustss fprintf(out, "}"); 119 1.1 augustss } 120 1.1 augustss break; 121 1.1 augustss case AUDIO_MIXER_VALUE: 122 1.1 augustss if (m->un.value.num_channels == 1) 123 1.1 augustss fprintf(out, "%d", m->un.value.level[0]); 124 1.1 augustss else 125 1.1 augustss fprintf(out, "%d,%d", m->un.value.level[0], 126 1.26 isaki m->un.value.level[1]); 127 1.15 augustss if (prvalset) { 128 1.9 augustss fprintf(out, " %s", p->infp->un.v.units.name); 129 1.15 augustss if (p->infp->un.v.delta) 130 1.15 augustss fprintf(out, " delta=%d", p->infp->un.v.delta); 131 1.15 augustss } 132 1.1 augustss break; 133 1.1 augustss default: 134 1.5 augustss printf("\n"); 135 1.29 christos errx(EXIT_FAILURE, "Invalid format %d", m->type); 136 1.1 augustss } 137 1.1 augustss } 138 1.1 augustss 139 1.15 augustss static int 140 1.28 christos clip(int vol) 141 1.28 christos { 142 1.28 christos if (vol <= AUDIO_MIN_GAIN) 143 1.28 christos return AUDIO_MIN_GAIN; 144 1.28 christos if (vol >= AUDIO_MAX_GAIN) 145 1.28 christos return AUDIO_MAX_GAIN; 146 1.28 christos return vol; 147 1.28 christos } 148 1.28 christos 149 1.28 christos static int 150 1.15 augustss rdfield(struct field *p, char *q) 151 1.1 augustss { 152 1.1 augustss mixer_ctrl_t *m; 153 1.4 augustss int v, v0, v1, mask; 154 1.1 augustss int i; 155 1.1 augustss char *s; 156 1.1 augustss 157 1.1 augustss m = p->valp; 158 1.1 augustss switch(m->type) { 159 1.1 augustss case AUDIO_MIXER_ENUM: 160 1.26 isaki for (i = 0; i < p->infp->un.e.num_mem; i++) 161 1.1 augustss if (strcmp(p->infp->un.e.member[i].label.name, q) == 0) 162 1.1 augustss break; 163 1.1 augustss if (i < p->infp->un.e.num_mem) 164 1.3 augustss m->un.ord = p->infp->un.e.member[i].ord; 165 1.4 augustss else { 166 1.1 augustss warnx("Bad enum value %s", q); 167 1.4 augustss return 0; 168 1.4 augustss } 169 1.1 augustss break; 170 1.1 augustss case AUDIO_MIXER_SET: 171 1.4 augustss mask = 0; 172 1.26 isaki for (v = 0; q && *q; q = s) { 173 1.1 augustss s = strchr(q, ','); 174 1.1 augustss if (s) 175 1.1 augustss *s++ = 0; 176 1.26 isaki for (i = 0; i < p->infp->un.s.num_mem; i++) 177 1.15 augustss if (strcmp(p->infp->un.s.member[i].label.name, 178 1.26 isaki q) == 0) 179 1.1 augustss break; 180 1.3 augustss if (i < p->infp->un.s.num_mem) { 181 1.4 augustss mask |= p->infp->un.s.member[i].mask; 182 1.4 augustss } else { 183 1.2 augustss warnx("Bad set value %s", q); 184 1.4 augustss return 0; 185 1.4 augustss } 186 1.1 augustss } 187 1.4 augustss m->un.mask = mask; 188 1.1 augustss break; 189 1.1 augustss case AUDIO_MIXER_VALUE: 190 1.1 augustss if (m->un.value.num_channels == 1) { 191 1.1 augustss if (sscanf(q, "%d", &v) == 1) { 192 1.28 christos m->un.value.level[0] = clip(v); 193 1.1 augustss } else { 194 1.1 augustss warnx("Bad number %s", q); 195 1.4 augustss return 0; 196 1.1 augustss } 197 1.1 augustss } else { 198 1.1 augustss if (sscanf(q, "%d,%d", &v0, &v1) == 2) { 199 1.28 christos m->un.value.level[0] = clip(v0); 200 1.28 christos m->un.value.level[1] = clip(v1); 201 1.1 augustss } else if (sscanf(q, "%d", &v) == 1) { 202 1.28 christos m->un.value.level[0] = 203 1.28 christos m->un.value.level[1] = clip(v); 204 1.1 augustss } else { 205 1.1 augustss warnx("Bad numbers %s", q); 206 1.4 augustss return 0; 207 1.1 augustss } 208 1.1 augustss } 209 1.1 augustss break; 210 1.1 augustss default: 211 1.29 christos errx(EXIT_FAILURE, "Invalid format %d", m->type); 212 1.1 augustss } 213 1.4 augustss p->changed = 1; 214 1.4 augustss return 1; 215 1.1 augustss } 216 1.1 augustss 217 1.15 augustss static int 218 1.15 augustss incfield(struct field *p, int inc) 219 1.15 augustss { 220 1.15 augustss mixer_ctrl_t *m; 221 1.15 augustss int i, v; 222 1.15 augustss 223 1.15 augustss m = p->valp; 224 1.15 augustss switch(m->type) { 225 1.15 augustss case AUDIO_MIXER_ENUM: 226 1.15 augustss m->un.ord += inc; 227 1.15 augustss if (m->un.ord < 0) 228 1.26 isaki m->un.ord = p->infp->un.e.num_mem - 1; 229 1.15 augustss if (m->un.ord >= p->infp->un.e.num_mem) 230 1.15 augustss m->un.ord = 0; 231 1.15 augustss break; 232 1.15 augustss case AUDIO_MIXER_SET: 233 1.15 augustss m->un.mask += inc; 234 1.15 augustss if (m->un.mask < 0) 235 1.15 augustss m->un.mask = (1 << p->infp->un.s.num_mem) - 1; 236 1.15 augustss if (m->un.mask >= (1 << p->infp->un.s.num_mem)) 237 1.15 augustss m->un.mask = 0; 238 1.15 augustss warnx("Can't ++/-- %s", p->name); 239 1.15 augustss return 0; 240 1.15 augustss case AUDIO_MIXER_VALUE: 241 1.15 augustss if (p->infp->un.v.delta) 242 1.15 augustss inc *= p->infp->un.v.delta; 243 1.15 augustss for (i = 0; i < m->un.value.num_channels; i++) { 244 1.15 augustss v = m->un.value.level[i]; 245 1.15 augustss v += inc; 246 1.28 christos m->un.value.level[i] = clip(v); 247 1.15 augustss } 248 1.15 augustss break; 249 1.15 augustss default: 250 1.29 christos errx(EXIT_FAILURE, "Invalid format %d", m->type); 251 1.15 augustss } 252 1.15 augustss p->changed = 1; 253 1.15 augustss return 1; 254 1.15 augustss } 255 1.15 augustss 256 1.15 augustss static void 257 1.23 lukem wrarg(int fd, char *arg, const char *sep) 258 1.15 augustss { 259 1.15 augustss char *q; 260 1.15 augustss struct field *p; 261 1.15 augustss mixer_ctrl_t val; 262 1.15 augustss int incdec, r; 263 1.15 augustss 264 1.15 augustss q = strchr(arg, '='); 265 1.15 augustss if (q == NULL) { 266 1.15 augustss int l = strlen(arg); 267 1.15 augustss incdec = 0; 268 1.15 augustss if (l > 2 && arg[l-2] == '+' && arg[l-1] == '+') 269 1.15 augustss incdec = 1; 270 1.15 augustss else if (l > 2 && arg[l-2] == '-' && arg[l-1] == '-') 271 1.15 augustss incdec = -1; 272 1.15 augustss else { 273 1.15 augustss warnx("No `=' in %s", arg); 274 1.15 augustss return; 275 1.15 augustss } 276 1.15 augustss arg[l-2] = 0; 277 1.21 cube } else if (q > arg && (*(q-1) == '+' || *(q-1) == '-')) { 278 1.21 cube if (sscanf(q+1, "%d", &incdec) != 1) { 279 1.21 cube warnx("Bad number %s", q+1); 280 1.21 cube return; 281 1.21 cube } 282 1.21 cube if (*(q-1) == '-') 283 1.21 cube incdec *= -1; 284 1.21 cube *(q-1) = 0; 285 1.21 cube q = NULL; 286 1.26 isaki } else 287 1.15 augustss *q++ = 0; 288 1.15 augustss 289 1.15 augustss p = findfield(arg); 290 1.15 augustss if (p == NULL) { 291 1.15 augustss warnx("field %s does not exist", arg); 292 1.15 augustss return; 293 1.15 augustss } 294 1.15 augustss 295 1.15 augustss val = *p->valp; 296 1.15 augustss if (q != NULL) 297 1.15 augustss r = rdfield(p, q); 298 1.15 augustss else 299 1.15 augustss r = incfield(p, incdec); 300 1.15 augustss if (r) { 301 1.29 christos if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) == -1) 302 1.15 augustss warn("AUDIO_MIXER_WRITE"); 303 1.15 augustss else if (sep) { 304 1.15 augustss *p->valp = val; 305 1.15 augustss prfield(p, ": ", 0); 306 1.29 christos if (ioctl(fd, AUDIO_MIXER_READ, p->valp) == -1) 307 1.29 christos warn("AUDIO_MIXER_READ"); 308 1.15 augustss printf(" -> "); 309 1.15 augustss prfield(p, 0, 0); 310 1.15 augustss printf("\n"); 311 1.15 augustss } 312 1.15 augustss } 313 1.15 augustss } 314 1.15 augustss 315 1.15 augustss static void 316 1.23 lukem prarg(int fd, char *arg, const char *sep) 317 1.15 augustss { 318 1.15 augustss struct field *p; 319 1.15 augustss 320 1.15 augustss p = findfield(arg); 321 1.15 augustss if (p == NULL) 322 1.15 augustss warnx("field %s does not exist", arg); 323 1.15 augustss else 324 1.15 augustss prfield(p, sep, vflag), fprintf(out, "\n"); 325 1.15 augustss } 326 1.15 augustss 327 1.27 kre static inline void __dead 328 1.27 kre usage(void) 329 1.27 kre { 330 1.29 christos const char *prog = getprogname(); 331 1.29 christos 332 1.30 rillig fprintf(stderr, "Usage:\t%s [-d file] [-v] [-n] name ...\n", prog); 333 1.30 rillig fprintf(stderr, "\t%s [-d file] [-v] [-n] -w name=value ...\n", prog); 334 1.29 christos fprintf(stderr, "\t%s [-d file] [-v] [-n] -a\n", prog); 335 1.29 christos exit(EXIT_FAILURE); 336 1.27 kre } 337 1.27 kre 338 1.6 augustss int 339 1.15 augustss main(int argc, char **argv) 340 1.1 augustss { 341 1.6 augustss int fd, i, j, ch, pos; 342 1.15 augustss int aflag = 0, wflag = 0; 343 1.23 lukem const char *file; 344 1.23 lukem const char *sep = "="; 345 1.1 augustss mixer_devinfo_t dinfo; 346 1.1 augustss int ndev; 347 1.10 augustss 348 1.11 augustss file = getenv("MIXERDEVICE"); 349 1.17 augustss if (file == NULL) 350 1.27 kre file = mixer_path; 351 1.1 augustss 352 1.1 augustss 353 1.16 jdolecek while ((ch = getopt(argc, argv, "ad:f:nvw")) != -1) { 354 1.1 augustss switch(ch) { 355 1.1 augustss case 'a': 356 1.1 augustss aflag++; 357 1.1 augustss break; 358 1.1 augustss case 'w': 359 1.1 augustss wflag++; 360 1.1 augustss break; 361 1.1 augustss case 'v': 362 1.1 augustss vflag++; 363 1.1 augustss break; 364 1.1 augustss case 'n': 365 1.1 augustss sep = 0; 366 1.1 augustss break; 367 1.16 jdolecek case 'f': /* compatibility */ 368 1.16 jdolecek case 'd': 369 1.1 augustss file = optarg; 370 1.1 augustss break; 371 1.1 augustss case '?': 372 1.1 augustss default: 373 1.27 kre usage(); 374 1.1 augustss } 375 1.1 augustss } 376 1.1 augustss argc -= optind; 377 1.1 augustss argv += optind; 378 1.26 isaki 379 1.27 kre if (aflag ? (argc != 0 || wflag) : argc == 0) 380 1.27 kre usage(); 381 1.27 kre 382 1.1 augustss fd = open(file, O_RDWR); 383 1.27 kre /* Try with mixer0 but only if using the default device. */ 384 1.29 christos if (fd == -1 && file == mixer_path) { 385 1.26 isaki file = _PATH_MIXER0; 386 1.26 isaki fd = open(file, O_RDWR); 387 1.26 isaki } 388 1.17 augustss 389 1.29 christos if (fd == -1) 390 1.29 christos err(EXIT_FAILURE, "Can't open `%s'", file); 391 1.1 augustss 392 1.26 isaki for (ndev = 0; ; ndev++) { 393 1.1 augustss dinfo.index = ndev; 394 1.29 christos if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1) 395 1.1 augustss break; 396 1.1 augustss } 397 1.1 augustss rfields = calloc(ndev, sizeof *rfields); 398 1.1 augustss fields = calloc(ndev, sizeof *fields); 399 1.1 augustss infos = calloc(ndev, sizeof *infos); 400 1.1 augustss values = calloc(ndev, sizeof *values); 401 1.1 augustss 402 1.26 isaki for (i = 0; i < ndev; i++) { 403 1.1 augustss infos[i].index = i; 404 1.29 christos if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) == -1) 405 1.29 christos warn("AUDIO_MIXER_DEVINFO for %d", i); 406 1.1 augustss } 407 1.1 augustss 408 1.26 isaki for (i = 0; i < ndev; i++) { 409 1.5 augustss rfields[i].name = infos[i].label.name; 410 1.1 augustss rfields[i].valp = &values[i]; 411 1.1 augustss rfields[i].infp = &infos[i]; 412 1.1 augustss } 413 1.1 augustss 414 1.26 isaki for (i = 0; i < ndev; i++) { 415 1.1 augustss values[i].dev = i; 416 1.1 augustss values[i].type = infos[i].type; 417 1.1 augustss if (infos[i].type != AUDIO_MIXER_CLASS) { 418 1.1 augustss values[i].un.value.num_channels = 2; 419 1.29 christos if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) { 420 1.1 augustss values[i].un.value.num_channels = 1; 421 1.29 christos if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) 422 1.29 christos == -1) 423 1.29 christos err(EXIT_FAILURE, "AUDIO_MIXER_READ"); 424 1.1 augustss } 425 1.1 augustss } 426 1.1 augustss } 427 1.1 augustss 428 1.26 isaki for (j = i = 0; i < ndev; i++) { 429 1.1 augustss if (infos[i].type != AUDIO_MIXER_CLASS && 430 1.1 augustss infos[i].type != -1) { 431 1.1 augustss fields[j++] = rfields[i]; 432 1.26 isaki for (pos = infos[i].next; pos != AUDIO_MIXER_LAST; 433 1.1 augustss pos = infos[pos].next) { 434 1.1 augustss fields[j] = rfields[pos]; 435 1.26 isaki fields[j].name = catstr(rfields[i].name, 436 1.26 isaki infos[pos].label.name); 437 1.1 augustss infos[pos].type = -1; 438 1.1 augustss j++; 439 1.1 augustss } 440 1.1 augustss } 441 1.5 augustss } 442 1.5 augustss 443 1.26 isaki for (i = 0; i < j; i++) { 444 1.8 augustss int cls = fields[i].infp->mixer_class; 445 1.8 augustss if (cls >= 0 && cls < ndev) 446 1.8 augustss fields[i].name = catstr(infos[cls].label.name, 447 1.26 isaki fields[i].name); 448 1.1 augustss } 449 1.1 augustss 450 1.1 augustss if (argc == 0 && aflag && !wflag) { 451 1.26 isaki for (i = 0; i < j; i++) { 452 1.1 augustss prfield(&fields[i], sep, vflag); 453 1.1 augustss fprintf(out, "\n"); 454 1.1 augustss } 455 1.1 augustss } else if (argc > 0 && !aflag) { 456 1.26 isaki while (argc--) { 457 1.15 augustss if (wflag) 458 1.15 augustss wrarg(fd, *argv, sep); 459 1.15 augustss else 460 1.15 augustss prarg(fd, *argv, sep); 461 1.15 augustss argv++; 462 1.1 augustss } 463 1.1 augustss } else 464 1.27 kre usage(); 465 1.29 christos return EXIT_SUCCESS; 466 1.1 augustss } 467