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
strtoenc(struct audio_swpar * ap,char * p)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
print_field(struct field * p,void * addr)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
parse_field(struct field * f,void * addr,char * p)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
audio_main(int argc,char ** argv)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
main(int argc,char ** argv)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