1 /* $OpenBSD: audioctl.c,v 1.47 2023/01/09 17:13:46 jmc Exp $ */ 2 /* 3 * Copyright (c) 2016 Alexandre Ratchov <alex@caoua.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #include <sys/types.h> 18 #include <sys/ioctl.h> 19 #include <sys/audioio.h> 20 #include <fcntl.h> 21 #include <limits.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <unistd.h> 25 #include <string.h> 26 #include <err.h> 27 28 /* 29 * Default bytes per sample for the given bits per sample. 30 */ 31 #define BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4)) 32 33 struct audio_device rname; 34 struct audio_status rstatus; 35 struct audio_swpar rpar, wpar; 36 struct audio_pos rpos; 37 38 struct field { 39 char *name; 40 void *raddr, *waddr; 41 #define MODE 0 42 #define NUM 1 43 #define STR 2 44 #define ENC 3 45 int type; 46 int show; 47 int set; 48 } fields[] = { 49 {"name", &rname.name, NULL, STR}, 50 {"mode", &rstatus.mode, NULL, MODE}, 51 {"pause", &rstatus.pause, NULL, NUM}, 52 {"active", &rstatus.active, NULL, NUM}, 53 {"nblks", &rpar.nblks, &wpar.nblks, NUM}, 54 {"blksz", &rpar.round, &wpar.round, NUM}, 55 {"rate", &rpar.rate, &wpar.rate, NUM}, 56 {"encoding", &rpar, &wpar, ENC}, 57 {"play.channels", &rpar.pchan, &wpar.pchan, NUM}, 58 {"play.bytes", &rpos.play_pos, NULL, NUM}, 59 {"play.errors", &rpos.play_xrun, NULL, NUM}, 60 {"record.channels", &rpar.rchan, &wpar.rchan, NUM}, 61 {"record.bytes", &rpos.rec_pos, NULL, NUM}, 62 {"record.errors", &rpos.rec_xrun, NULL, NUM}, 63 {NULL, NULL, 0} 64 }; 65 66 const char usagestr[] = 67 "usage: audioctl [-nq] [-f file] [-w wait] [name[=value] ...]\n"; 68 69 int fd, show_names = 1, quiet = 0, wait_sec = 0; 70 71 /* 72 * parse encoding string (examples: s8, u8, s16, s16le, s24be ...) 73 * and fill encoding fields of audio_swpar structure 74 */ 75 int 76 strtoenc(struct audio_swpar *ap, char *p) 77 { 78 /* expect "s" or "u" (signedness) */ 79 if (*p == 's') 80 ap->sig = 1; 81 else if (*p == 'u') 82 ap->sig = 0; 83 else 84 return 0; 85 p++; 86 87 /* expect 1-2 decimal digits (bits per sample) */ 88 ap->bits = 0; 89 while (*p >= '0' && *p <= '9') { 90 ap->bits = (ap->bits * 10) + *p++ - '0'; 91 if (ap->bits > 32) 92 return 0; 93 } 94 if (ap->bits < 8) 95 return 0; 96 97 /* set defaults as next tokens are optional */ 98 ap->bps = BPS(ap->bits); 99 ap->le = (BYTE_ORDER == LITTLE_ENDIAN); 100 ap->msb = 1; 101 if (*p == '\0') 102 return 1; 103 104 /* expect "le" or "be" (endianness) */ 105 if (p[0] == 'l' && p[1] == 'e') 106 ap->le = 1; 107 else if (p[0] == 'b' && p[1] == 'e') 108 ap->le = 0; 109 else 110 return 0; 111 p += 2; 112 if (*p == '\0') 113 return 1; 114 115 /* expect 1 decimal digit (number of bytes) */ 116 if (*p < '0' || *p > '9') 117 return 0; 118 ap->bps = *p - '0'; 119 if (ap->bps < ((ap->bits + 7) >> 3) || ap->bps > 4) 120 return 0; 121 if (*++p == '\0') 122 return 1; 123 124 /* expect "msb" or "lsb" (alignment) */ 125 if (p[0] == 'm' && p[1] == 's' && p[2] == 'b') 126 ap->msb = 1; 127 else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b') 128 ap->msb = 0; 129 else if (*p == '\0') 130 return 1; 131 p += 3; 132 if (*p == '\0') 133 return 1; 134 135 /* must be no additional junk */ 136 return 0; 137 } 138 139 void 140 print_field(struct field *p, void *addr) 141 { 142 int mode; 143 struct audio_swpar *ap; 144 145 switch (p->type) { 146 case NUM: 147 printf("%u", *(unsigned int *)addr); 148 break; 149 case STR: 150 printf("%s", (char *)addr); 151 break; 152 case MODE: 153 mode = *(unsigned int *)addr; 154 if (mode & AUMODE_PLAY) 155 printf("play"); 156 if (mode & AUMODE_RECORD) { 157 if (mode & AUMODE_PLAY) 158 printf(","); 159 printf("record"); 160 } 161 break; 162 case ENC: 163 ap = addr; 164 printf("%s%u", ap->sig ? "s" : "u", ap->bits); 165 if (ap->bps == 1) 166 break; 167 printf("%s", ap->le ? "le" : "be"); 168 if (ap->bps != BPS(ap->bits) || ap->bits < ap->bps * 8) { 169 printf("%u", ap->bps); 170 if (ap->bits < ap->bps * 8) 171 printf("%s", ap->msb ? "msb" : "lsb"); 172 } 173 } 174 } 175 176 void 177 parse_field(struct field *f, void *addr, char *p) 178 { 179 const char *strerr; 180 181 switch (f->type) { 182 case NUM: 183 *(unsigned int *)addr = strtonum(p, 0, UINT_MAX, &strerr); 184 if (strerr) 185 errx(1, "%s: %s", p, strerr); 186 break; 187 case ENC: 188 if (!strtoenc((struct audio_swpar *)addr, p)) 189 errx(1, "%s: bad encoding", p); 190 } 191 } 192 193 void 194 audio_main(int argc, char **argv) 195 { 196 struct field *f; 197 char *lhs, *rhs; 198 int set = 0; 199 200 if (argc == 0) { 201 for (f = fields; f->name != NULL; f++) 202 f->show = 1; 203 } 204 AUDIO_INITPAR(&wpar); 205 for (; argc > 0; argc--, argv++) { 206 lhs = *argv; 207 rhs = strchr(*argv, '='); 208 if (rhs) 209 *rhs++ = '\0'; 210 for (f = fields;; f++) { 211 if (f->name == NULL) 212 errx(1, "%s: unknown parameter", lhs); 213 if (strcmp(f->name, lhs) == 0) 214 break; 215 } 216 if (rhs) { 217 if (f->waddr == NULL) 218 errx(1, "%s: is read only", f->name); 219 parse_field(f, f->waddr, rhs); 220 f->set = 1; 221 set = 1; 222 } else 223 f->show = 1; 224 } 225 226 if (set && wait_sec) 227 errx(1, "Can't set variables wait_secically"); 228 229 while (1) { 230 if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1) 231 err(1, "AUDIO_GETSTATUS"); 232 if (ioctl(fd, AUDIO_GETDEV, &rname) == -1) 233 err(1, "AUDIO_GETDEV"); 234 if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1) 235 err(1, "AUDIO_GETPAR"); 236 if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1) 237 err(1, "AUDIO_GETPOS"); 238 for (f = fields; f->name != NULL; f++) { 239 if (!f->show) 240 continue; 241 if (show_names) 242 printf("%s=", f->name); 243 print_field(f, f->raddr); 244 printf("\n"); 245 } 246 247 if (wait_sec == 0) 248 break; 249 250 /* ioctls are fast, we neglect drift from real-time clock */ 251 sleep(wait_sec); 252 } 253 254 if (!set) 255 return; 256 257 if (ioctl(fd, AUDIO_SETPAR, &wpar) == -1) 258 err(1, "AUDIO_SETPAR"); 259 if (ioctl(fd, AUDIO_GETPAR, &wpar) == -1) 260 err(1, "AUDIO_GETPAR"); 261 for (f = fields; f->name != NULL; f++) { 262 if (!f->set || quiet) 263 continue; 264 if (show_names) { 265 printf("%s: ", f->name); 266 print_field(f, f->raddr); 267 printf(" -> "); 268 } 269 print_field(f, f->waddr); 270 printf("\n"); 271 } 272 } 273 274 int 275 main(int argc, char **argv) 276 { 277 char *path = "/dev/audioctl0"; 278 const char *errstr; 279 int c; 280 281 while ((c = getopt(argc, argv, "anf:qw:")) != -1) { 282 switch (c) { 283 case 'a': /* ignored, compat */ 284 break; 285 case 'n': 286 show_names = 0; 287 break; 288 case 'f': 289 path = optarg; 290 break; 291 case 'q': 292 quiet = 1; 293 break; 294 case 'w': 295 wait_sec = strtonum(optarg, 1, INT_MAX, &errstr); 296 if (errstr != NULL) 297 errx(1, "wait is %s: %s", errstr, optarg); 298 break; 299 default: 300 fputs(usagestr, stderr); 301 return 1; 302 } 303 } 304 argc -= optind; 305 argv += optind; 306 307 if (unveil(path, "w") == -1) 308 err(1, "unveil %s", path); 309 if (unveil(NULL, NULL) == -1) 310 err(1, "unveil"); 311 312 fd = open(path, O_WRONLY); 313 if (fd == -1) 314 err(1, "%s", path); 315 316 audio_main(argc, argv); 317 318 close(fd); 319 return 0; 320 } 321