1 /* $OpenBSD: mixerctl.c,v 1.28 2008/06/26 05:42:21 ray Exp $ */ 2 /* $NetBSD: mixerctl.c,v 1.11 1998/04/27 16:55:23 augustss Exp $ */ 3 4 /* 5 * Copyright (c) 1997 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * Author: Lennart Augustsson, with some code and ideas from Chuck Cranor. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * mixerctl(1) - a program to control audio mixing. 34 */ 35 36 #include <sys/types.h> 37 #include <sys/ioctl.h> 38 #include <sys/audioio.h> 39 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 49 struct field *findfield(char *); 50 void adjlevel(char **, u_char *, int); 51 void catstr(char *, char *, char *); 52 void prfield(struct field *, char *, int); 53 void rdfield(int, struct field *, char *, int); 54 __dead void usage(void); 55 56 #define FIELD_NAME_MAX 64 57 58 struct field { 59 char name[FIELD_NAME_MAX]; 60 mixer_ctrl_t *valp; 61 mixer_devinfo_t *infp; 62 } *fields, *rfields; 63 64 mixer_ctrl_t *values; 65 mixer_devinfo_t *infos; 66 67 void 68 catstr(char *p, char *q, char *out) 69 { 70 char tmp[FIELD_NAME_MAX]; 71 72 snprintf(tmp, FIELD_NAME_MAX, "%s.%s", p, q); 73 strlcpy(out, tmp, FIELD_NAME_MAX); 74 } 75 76 struct field * 77 findfield(char *name) 78 { 79 int i; 80 for (i = 0; fields[i].name[0] != '\0'; i++) 81 if (strcmp(fields[i].name, name) == 0) 82 return &fields[i]; 83 return (0); 84 } 85 86 #define e_member_name un.e.member[i].label.name 87 #define s_member_name un.s.member[i].label.name 88 89 void 90 prfield(struct field *p, char *sep, int prvalset) 91 { 92 mixer_ctrl_t *m; 93 int i, n; 94 95 if (sep) 96 printf("%s%s", p->name, sep); 97 m = p->valp; 98 switch (m->type) { 99 case AUDIO_MIXER_ENUM: 100 for (i = 0; i < p->infp->un.e.num_mem; i++) 101 if (p->infp->un.e.member[i].ord == m->un.ord) 102 printf("%s", 103 p->infp->e_member_name); 104 if (prvalset) { 105 printf(" [ "); 106 for (i = 0; i < p->infp->un.e.num_mem; i++) 107 printf("%s ", p->infp->e_member_name); 108 printf("]"); 109 } 110 break; 111 case AUDIO_MIXER_SET: 112 for (n = i = 0; i < p->infp->un.s.num_mem; i++) 113 if (m->un.mask & p->infp->un.s.member[i].mask) 114 printf("%s%s", n++ ? "," : "", 115 p->infp->s_member_name); 116 if (prvalset) { 117 printf(" { "); 118 for (i = 0; i < p->infp->un.s.num_mem; i++) 119 printf("%s ", p->infp->s_member_name); 120 printf("}"); 121 } 122 break; 123 case AUDIO_MIXER_VALUE: 124 if (m->un.value.num_channels == 1) 125 printf("%d", m->un.value.level[0]); 126 else 127 printf("%d,%d", m->un.value.level[0], 128 m->un.value.level[1]); 129 if (prvalset) 130 printf(" %s", p->infp->un.v.units.name); 131 break; 132 default: 133 errx(1, "Invalid format."); 134 } 135 } 136 137 void 138 adjlevel(char **p, u_char *olevel, int more) 139 { 140 char *ep, *cp = *p; 141 long inc; 142 u_char level; 143 144 if (*cp != '+' && *cp != '-') 145 *olevel = 0; /* absolute setting */ 146 147 errno = 0; 148 inc = strtol(cp, &ep, 10); 149 if (*cp == '\0' || (*ep != '\0' && *ep != ',') || 150 (errno == ERANGE && (inc == LONG_MAX || inc == LONG_MIN))) 151 errx(1, "Bad number %s", cp); 152 if (*ep == ',' && !more) 153 errx(1, "Too many values"); 154 *p = ep; 155 156 if (inc < AUDIO_MIN_GAIN - *olevel) 157 level = AUDIO_MIN_GAIN; 158 else if (inc > AUDIO_MAX_GAIN - *olevel) 159 level = AUDIO_MAX_GAIN; 160 else 161 level = *olevel + inc; 162 *olevel = level; 163 } 164 165 void 166 rdfield(int fd, struct field *p, char *q, int quiet) 167 { 168 mixer_ctrl_t *m, oldval; 169 int i, mask; 170 char *s; 171 172 oldval = *p->valp; 173 m = p->valp; 174 175 switch (m->type) { 176 case AUDIO_MIXER_ENUM: 177 if (strcmp(q, "toggle") == 0) { 178 for (i = 0; i < p->infp->un.e.num_mem; i++) { 179 if (m->un.ord == p->infp->un.e.member[i].ord) 180 break; 181 } 182 if (i < p->infp->un.e.num_mem) 183 i++; 184 else 185 i = 0; 186 m->un.ord = p->infp->un.e.member[i].ord; 187 break; 188 } 189 for (i = 0; i < p->infp->un.e.num_mem; i++) 190 if (strcmp(p->infp->e_member_name, q) == 0) 191 break; 192 if (i < p->infp->un.e.num_mem) 193 m->un.ord = p->infp->un.e.member[i].ord; 194 else 195 errx(1, "Bad enum value %s", q); 196 break; 197 case AUDIO_MIXER_SET: 198 mask = 0; 199 for (; q && *q; q = s) { 200 if ((s = strchr(q, ',')) != NULL) 201 *s++ = 0; 202 for (i = 0; i < p->infp->un.s.num_mem; i++) 203 if (strcmp(p->infp->s_member_name, q) == 0) 204 break; 205 if (i < p->infp->un.s.num_mem) 206 mask |= p->infp->un.s.member[i].mask; 207 else 208 errx(1, "Bad set value %s", q); 209 } 210 m->un.mask = mask; 211 break; 212 case AUDIO_MIXER_VALUE: 213 if (m->un.value.num_channels == 1) { 214 adjlevel(&q, &m->un.value.level[0], 0); 215 } else { 216 adjlevel(&q, &m->un.value.level[0], 1); 217 if (*q++ == ',') 218 adjlevel(&q, &m->un.value.level[1], 0); 219 else 220 m->un.value.level[1] = m->un.value.level[0]; 221 } 222 break; 223 default: 224 errx(1, "Invalid format."); 225 } 226 227 if (ioctl(fd, AUDIO_MIXER_WRITE, p->valp) < 0) { 228 warn("AUDIO_MIXER_WRITE"); 229 } else if (!quiet) { 230 *p->valp = oldval; 231 prfield(p, ": ", 0); 232 if (ioctl(fd, AUDIO_MIXER_READ, p->valp) < 0) { 233 warn("AUDIO_MIXER_READ"); 234 } else { 235 printf(" -> "); 236 prfield(p, NULL, 0); 237 printf("\n"); 238 } 239 } 240 } 241 242 int 243 main(int argc, char **argv) 244 { 245 int fd, i, j, ch, pos; 246 int aflag = 0, qflag = 0, vflag = 0, tflag = 0; 247 char *file; 248 char *sep = "="; 249 mixer_devinfo_t dinfo; 250 int ndev; 251 252 if ((file = getenv("MIXERDEVICE")) == 0 || *file == '\0') 253 file = "/dev/mixer"; 254 255 while ((ch = getopt(argc, argv, "af:nqtvw")) != -1) { 256 switch (ch) { 257 case 'a': 258 aflag++; 259 break; 260 case 'w': 261 /* compat */ 262 break; 263 case 'v': 264 vflag++; 265 break; 266 case 'n': 267 sep = 0; 268 break; 269 case 'f': 270 file = optarg; 271 break; 272 case 'q': 273 qflag = 1; 274 break; 275 case 't': 276 tflag = 1; 277 break; 278 default: 279 usage(); 280 } 281 } 282 argc -= optind; 283 argv += optind; 284 285 if (argc == 0 && tflag == 0) 286 aflag++; 287 288 if ((fd = open(file, O_RDWR)) == -1) 289 if ((fd = open(file, O_RDONLY)) == -1) 290 err(1, "%s", file); 291 292 for (ndev = 0; ; ndev++) { 293 dinfo.index = ndev; 294 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) < 0) 295 break; 296 } 297 298 if (!ndev) 299 errx(1, "no mixer devices configured"); 300 301 if ((rfields = calloc(ndev, sizeof *rfields)) == NULL || 302 (fields = calloc(ndev, sizeof *fields)) == NULL || 303 (infos = calloc(ndev, sizeof *infos)) == NULL || 304 (values = calloc(ndev, sizeof *values)) == NULL) 305 err(1, "calloc()"); 306 307 for (i = 0; i < ndev; i++) { 308 infos[i].index = i; 309 if (ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]) < 0) { 310 ndev--; 311 i--; 312 continue; 313 } 314 } 315 316 for (i = 0; i < ndev; i++) { 317 strlcpy(rfields[i].name, infos[i].label.name, FIELD_NAME_MAX); 318 rfields[i].valp = &values[i]; 319 rfields[i].infp = &infos[i]; 320 } 321 322 for (i = 0; i < ndev; i++) { 323 values[i].dev = i; 324 values[i].type = infos[i].type; 325 if (infos[i].type != AUDIO_MIXER_CLASS) { 326 values[i].un.value.num_channels = 2; 327 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) { 328 values[i].un.value.num_channels = 1; 329 if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) < 0) 330 err(1, "AUDIO_MIXER_READ"); 331 } 332 } 333 } 334 335 for (j = i = 0; i < ndev; i++) { 336 if (infos[i].type != AUDIO_MIXER_CLASS && 337 infos[i].prev == AUDIO_MIXER_LAST) { 338 fields[j++] = rfields[i]; 339 for (pos = infos[i].next; pos != AUDIO_MIXER_LAST; 340 pos = infos[pos].next) { 341 fields[j] = rfields[pos]; 342 catstr(rfields[i].name, infos[pos].label.name, 343 fields[j].name); 344 j++; 345 } 346 } 347 } 348 349 for (i = 0; i < j; i++) { 350 int cls = fields[i].infp->mixer_class; 351 if (cls >= 0 && cls < ndev) 352 catstr(infos[cls].label.name, fields[i].name, 353 fields[i].name); 354 } 355 356 if (!argc && aflag) { 357 for (i = 0; fields[i].name[0] != '\0'; i++) { 358 prfield(&fields[i], sep, vflag); 359 printf("\n"); 360 } 361 } else if (argc > 0 && !aflag) { 362 struct field *p; 363 364 while (argc--) { 365 char *q; 366 367 ch = 0; 368 if ((q = strchr(*argv, '=')) != NULL) { 369 *q++ = '\0'; 370 ch = 1; 371 } 372 373 if ((p = findfield(*argv)) == NULL) { 374 warnx("field %s does not exist", *argv); 375 } else if (ch || tflag) { 376 if (tflag && q == NULL) 377 q = "toggle"; 378 rdfield(fd, p, q, qflag); 379 } else { 380 prfield(p, sep, vflag); 381 printf("\n"); 382 } 383 384 argv++; 385 } 386 } else 387 usage(); 388 exit(0); 389 } 390 391 __dead void 392 usage(void) 393 { 394 extern char *__progname; /* from crt0.o */ 395 396 fprintf(stderr, 397 "usage: %s [-anv] [-f file]\n" 398 " %s [-nv] [-f file] name ...\n" 399 " %s [-qt] [-f file] name ...\n" 400 " %s [-q] [-f file] name=value ...\n", 401 __progname, __progname, __progname, __progname); 402 403 exit(1); 404 } 405