xref: /openbsd-src/usr.bin/audioctl/audioctl.c (revision 165474a8f240dde66437001365e588a270af0338)
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