xref: /openbsd-src/usr.bin/audioctl/audioctl.c (revision 4e1ee0786f11cc571bd0be17d38e46f635c719fc)
1 /*	$OpenBSD: audioctl.c,v 1.43 2021/07/12 15:09:19 beck 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 set;
47 } fields[] = {
48 	{"name",		&rname.name,		NULL,		STR},
49 	{"mode",		&rstatus.mode,		NULL,		MODE},
50 	{"pause",		&rstatus.pause,		NULL,		NUM},
51 	{"active",		&rstatus.active,	NULL,		NUM},
52 	{"nblks",		&rpar.nblks,		&wpar.nblks,	NUM},
53 	{"blksz",		&rpar.round,		&wpar.round,	NUM},
54 	{"rate",		&rpar.rate,		&wpar.rate,	NUM},
55 	{"encoding",		&rpar,			&wpar,		ENC},
56 	{"play.channels",	&rpar.pchan,		&wpar.pchan,	NUM},
57 	{"play.bytes",		&rpos.play_pos,		NULL,		NUM},
58 	{"play.errors",		&rpos.play_xrun,	NULL,		NUM},
59 	{"record.channels",	&rpar.rchan,		&wpar.rchan, 	NUM},
60 	{"record.bytes",	&rpos.rec_pos,		NULL,		NUM},
61 	{"record.errors",	&rpos.rec_xrun,		NULL,		NUM},
62 	{NULL,			NULL,			0}
63 };
64 
65 const char usagestr[] =
66 	"usage: audioctl [-f file]\n"
67 	"       audioctl [-n] [-f file] name ...\n"
68 	"       audioctl [-nq] [-f file] name=value ...\n";
69 
70 int fd, show_names = 1, quiet = 0;
71 
72 /*
73  * parse encoding string (examples: s8, u8, s16, s16le, s24be ...)
74  * and fill enconding fields of audio_swpar structure
75  */
76 int
77 strtoenc(struct audio_swpar *ap, char *p)
78 {
79 	/* expect "s" or "u" (signedness) */
80 	if (*p == 's')
81 		ap->sig = 1;
82 	else if (*p == 'u')
83 		ap->sig = 0;
84 	else
85 		return 0;
86 	p++;
87 
88 	/* expect 1-2 decimal digits (bits per sample) */
89 	ap->bits = 0;
90 	while (*p >= '0' && *p <= '9') {
91 		ap->bits = (ap->bits * 10) + *p++ - '0';
92 		if (ap->bits > 32)
93 			return 0;
94 	}
95 	if (ap->bits < 8)
96 		return 0;
97 
98 	/* set defaults as next tokens are optional */
99 	ap->bps = BPS(ap->bits);
100 	ap->le = (BYTE_ORDER == LITTLE_ENDIAN);
101 	ap->msb = 1;
102 	if (*p == '\0')
103 		return 1;
104 
105 	/* expect "le" or "be" (endianness) */
106 	if (p[0] == 'l' && p[1] == 'e')
107 		ap->le = 1;
108 	else if (p[0] == 'b' && p[1] == 'e')
109 		ap->le = 0;
110 	else
111 		return 0;
112 	p += 2;
113 	if (*p == '\0')
114 		return 1;
115 
116 	/* expect 1 decimal digit (number of bytes) */
117 	if (*p < '0' || *p > '9')
118 		return 0;
119 	ap->bps = *p - '0';
120 	if (ap->bps < ((ap->bits + 7) >> 3) || ap->bps > 4)
121 		return 0;
122 	if (*++p == '\0')
123 		return 1;
124 
125 	/* expect "msb" or "lsb" (alignment) */
126 	if (p[0] == 'm' && p[1] == 's' && p[2] == 'b')
127 		ap->msb = 1;
128 	else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b')
129 		ap->msb = 0;
130 	else if (*p == '\0')
131 		return 1;
132 	p += 3;
133 	if (*p == '\0')
134 		return 1;
135 
136 	/* must be no additional junk */
137 	return 0;
138 }
139 
140 void
141 print_field(struct field *p, void *addr)
142 {
143 	int mode;
144 	struct audio_swpar *ap;
145 
146 	switch (p->type) {
147 	case NUM:
148 		printf("%u", *(unsigned int *)addr);
149 		break;
150 	case STR:
151 		printf("%s", (char *)addr);
152 		break;
153 	case MODE:
154 		mode = *(unsigned int *)addr;
155 		if (mode & AUMODE_PLAY)
156 			printf("play");
157 		if (mode & AUMODE_RECORD) {
158 			if (mode & AUMODE_PLAY)
159 				printf(",");
160 			printf("record");
161 		}
162 		break;
163 	case ENC:
164 		ap = addr;
165 		printf("%s%u", ap->sig ? "s" : "u", ap->bits);
166 		if (ap->bps == 1)
167 			break;
168 		printf("%s", ap->le ? "le" : "be");
169 		if (ap->bps != BPS(ap->bits) || ap->bits < ap->bps * 8) {
170 			printf("%u", ap->bps);
171 			if (ap->bits < ap->bps * 8)
172 				printf("%s", ap->msb ? "msb" : "lsb");
173 		}
174 	}
175 }
176 
177 void
178 parse_field(struct field *f, void *addr, char *p)
179 {
180 	const char *strerr;
181 
182 	switch (f->type) {
183 	case NUM:
184 		*(unsigned int *)addr = strtonum(p, 0, UINT_MAX, &strerr);
185 		if (strerr)
186 			errx(1, "%s: %s", p, strerr);
187 		break;
188 	case ENC:
189 		if (!strtoenc((struct audio_swpar *)addr, p))
190 			errx(1, "%s: bad encoding", p);
191 	}
192 }
193 
194 void
195 audio_main(int argc, char **argv)
196 {
197 	struct field *f;
198 	char *lhs, *rhs;
199 	int set = 0;
200 
201 	if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1)
202 		err(1, "AUDIO_GETSTATUS");
203 	if (ioctl(fd, AUDIO_GETDEV, &rname) == -1)
204 		err(1, "AUDIO_GETDEV");
205 	if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1)
206 		err(1, "AUDIO_GETPAR");
207 	if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1)
208 		err(1, "AUDIO_GETPOS");
209 	if (argc == 0) {
210 		for (f = fields; f->name != NULL; f++) {
211 			printf("%s=", f->name);
212 			print_field(f, f->raddr);
213 			printf("\n");
214 		}
215 	}
216 	AUDIO_INITPAR(&wpar);
217 	for (; argc > 0; argc--, argv++) {
218 		lhs = *argv;
219 		rhs = strchr(*argv, '=');
220 		if (rhs)
221 			*rhs++ = '\0';
222 		for (f = fields;; f++) {
223 			if (f->name == NULL)
224 				errx(1, "%s: unknown parameter", lhs);
225 			if (strcmp(f->name, lhs) == 0)
226 				break;
227 		}
228 		if (rhs) {
229 			if (f->waddr == NULL)
230 				errx(1, "%s: is read only", f->name);
231 			parse_field(f, f->waddr, rhs);
232 			f->set = 1;
233 			set = 1;
234 		} else {
235 			if (show_names)
236 				printf("%s=", f->name);
237 			print_field(f, f->raddr);
238 			printf("\n");
239 		}
240 	}
241 	if (!set)
242 		return;
243 	if (ioctl(fd, AUDIO_SETPAR, &wpar) == -1)
244 		err(1, "AUDIO_SETPAR");
245 	if (ioctl(fd, AUDIO_GETPAR, &wpar) == -1)
246 		err(1, "AUDIO_GETPAR");
247 	for (f = fields; f->name != NULL; f++) {
248 		if (!f->set || quiet)
249 			continue;
250 		if (show_names) {
251 			printf("%s: ", f->name);
252 			print_field(f, f->raddr);
253 			printf(" -> ");
254 		}
255 		print_field(f, f->waddr);
256 		printf("\n");
257 	}
258 }
259 
260 int
261 main(int argc, char **argv)
262 {
263 	char *path = "/dev/audioctl0";
264 	int c;
265 
266 	while ((c = getopt(argc, argv, "anf:q")) != -1) {
267 		switch (c) {
268 		case 'a':	/* ignored, compat */
269 			break;
270 		case 'n':
271 			show_names = 0;
272 			break;
273 		case 'f':
274 			path = optarg;
275 			break;
276 		case 'q':
277 			quiet = 1;
278 			break;
279 		default:
280 			fputs(usagestr, stderr);
281 			return 1;
282 		}
283 	}
284 	argc -= optind;
285 	argv += optind;
286 
287 	if (unveil(path, "w") == -1)
288 		err(1, "unveil %s", path);
289 	if (unveil(NULL, NULL) == -1)
290 		err(1, "unveil");
291 
292 	fd = open(path, O_WRONLY);
293 	if (fd == -1)
294 		err(1, "%s", path);
295 
296 	audio_main(argc, argv);
297 
298 	close(fd);
299 	return 0;
300 }
301