xref: /netbsd-src/usr.bin/audiocfg/audiodev.c (revision 3dbad28612dad296816d69bdfc98c25dbc330574)
1 /* $NetBSD: audiodev.c,v 1.15 2019/08/24 07:39:42 isaki Exp $ */
2 
3 /*
4  * Copyright (c) 2010 Jared D. McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/queue.h>
30 #include <sys/ioctl.h>
31 #include <sys/stat.h>
32 #include <sys/drvctlio.h>
33 
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <paths.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include "audiodev.h"
44 #include "drvctl.h"
45 #include "dtmf.h"
46 
47 static int audiodev_test_chmask(struct audiodev *, unsigned int,
48 	audio_info_t *);
49 
50 static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist =
51     TAILQ_HEAD_INITIALIZER(audiodevlist);
52 
53 static int
audiodev_getinfo(struct audiodev * adev)54 audiodev_getinfo(struct audiodev *adev)
55 {
56 	struct stat st;
57 	struct audiofmt *f;
58 	audio_format_query_t query;
59 	int i;
60 
61 	if (stat(adev->ctlpath, &st) == -1)
62 		return -1;
63 	adev->dev = st.st_rdev;
64 
65 	if (stat(_PATH_AUDIOCTL, &st) != -1 && st.st_rdev == adev->dev)
66 		adev->defaultdev = true;
67 
68 	adev->ctlfd = open(adev->ctlpath, O_RDONLY);
69 	if (adev->ctlfd == -1) {
70 			return -1;
71 	}
72 	if (ioctl(adev->ctlfd, AUDIO_GETDEV, &adev->audio_device) == -1) {
73 		close(adev->ctlfd);
74 		return -1;
75 	}
76 
77 	for (i = 0; ;i++) {
78 		memset(&query, 0, sizeof(query));
79 		query.index = i;
80 		if (ioctl(adev->ctlfd, AUDIO_QUERYFORMAT, &query) == -1) {
81 			if (errno == ENODEV) {
82 				/* QUERYFORMAT not supported. */
83 				break;
84 			}
85 			if (errno == EINVAL)
86 				break;
87 			close(adev->ctlfd);
88 			return -1;
89 		}
90 
91 		f = calloc(1, sizeof(*f));
92 		f->fmt = query.fmt;
93 		TAILQ_INSERT_TAIL(&adev->formats, f, next);
94 	}
95 
96 	if (ioctl(adev->ctlfd, AUDIO_GETFORMAT, &adev->hwinfo) == -1) {
97 		close(adev->ctlfd);
98 		return -1;
99 	}
100 
101 	return 0;
102 }
103 
104 static int
audiodev_add(const char * pdev,const char * dev,unsigned int unit)105 audiodev_add(const char *pdev, const char *dev, unsigned int unit)
106 {
107 	struct audiodev *adev;
108 
109 	adev = calloc(1, sizeof(*adev));
110 	if (adev == NULL)
111 		return -1;
112 
113 	strlcpy(adev->pxname, pdev, sizeof(adev->pxname));
114 	strlcpy(adev->xname, dev, sizeof(adev->xname));
115 	snprintf(adev->path, sizeof(adev->path), "/dev/%s", dev);
116 	snprintf(adev->ctlpath, sizeof(adev->ctlpath), "/dev/audioctl%d", unit);
117 	adev->unit = unit;
118 	TAILQ_INIT(&adev->formats);
119 
120 	if (audiodev_getinfo(adev) == -1) {
121 		free(adev);
122 		return -1;
123 	}
124 
125 #ifdef DEBUG
126 	printf("DEBUG: [%c] %s(%s): %s\n", adev->defaultdev ? '*' : ' ',
127 	    adev->path, adev->ctlpath, adev->audio_device.name);
128 	struct audiofmt *f;
129 	TAILQ_FOREACH(f, &adev->formats, next) {
130 		printf("DEBUG: enc%d, %d/%d, %dch\n",
131 		    f->fmt.encoding,
132 		    f->fmt.validbits,
133 		    f->fmt.precision,
134 		    f->fmt.channels);
135 	}
136 #endif
137 
138 	TAILQ_INSERT_TAIL(&audiodevlist, adev, next);
139 
140 	return 0;
141 }
142 
143 static void
audiodev_cb(void * args,const char * pdev,const char * dev,unsigned int unit)144 audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
145 {
146 	audiodev_add(pdev, dev, unit);
147 }
148 
149 int
audiodev_refresh(void)150 audiodev_refresh(void)
151 {
152 	struct audiodev *adev;
153 	int fd, error;
154 
155 	fd = open(DRVCTLDEV, O_RDONLY);
156 	if (fd == -1) {
157 		warn("open %s", DRVCTLDEV);
158 		return -1;
159 	}
160 
161 	while (!TAILQ_EMPTY(&audiodevlist)) {
162 		adev = TAILQ_FIRST(&audiodevlist);
163 		if (adev->ctlfd != -1)
164 			close(adev->ctlfd);
165 		TAILQ_REMOVE(&audiodevlist, adev, next);
166 		free(adev);
167 	}
168 
169 	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
170 	if (error == -1) {
171 		warnx("drvctl failed");
172 		return -1;
173 	}
174 
175 	close(fd);
176 
177 	return 0;
178 }
179 
180 unsigned int
audiodev_count(void)181 audiodev_count(void)
182 {
183 	struct audiodev *adev;
184 	unsigned int n;
185 
186 	n = 0;
187 	TAILQ_FOREACH(adev, &audiodevlist, next)
188 		++n;
189 
190 	return n;
191 }
192 
193 struct audiodev *
audiodev_get(unsigned int i)194 audiodev_get(unsigned int i)
195 {
196 	struct audiodev *adev;
197 	unsigned int n;
198 
199 	n = 0;
200 	TAILQ_FOREACH(adev, &audiodevlist, next) {
201 		if (n == i)
202 			return adev;
203 		++n;
204 	}
205 
206 	return NULL;
207 }
208 
209 int
audiodev_set_default(struct audiodev * adev)210 audiodev_set_default(struct audiodev *adev)
211 {
212 	char audiopath[PATH_MAX+1];
213 	char soundpath[PATH_MAX+1];
214 	char audioctlpath[PATH_MAX+1];
215 	char mixerpath[PATH_MAX+1];
216 
217 	snprintf(audiopath, sizeof(audiopath),
218 	    _PATH_AUDIO "%u", adev->unit);
219 	snprintf(soundpath, sizeof(soundpath),
220 	    _PATH_SOUND "%u", adev->unit);
221 	snprintf(audioctlpath, sizeof(audioctlpath),
222 	    _PATH_AUDIOCTL "%u", adev->unit);
223 	snprintf(mixerpath, sizeof(mixerpath),
224 	    _PATH_MIXER "%u", adev->unit);
225 
226 	unlink(_PATH_AUDIO);
227 	unlink(_PATH_SOUND);
228 	unlink(_PATH_AUDIOCTL);
229 	unlink(_PATH_MIXER);
230 
231 	if (symlink(audiopath, _PATH_AUDIO) == -1) {
232 		warn("symlink %s", _PATH_AUDIO);
233 		return -1;
234 	}
235 	if (symlink(soundpath, _PATH_SOUND) == -1) {
236 		warn("symlink %s", _PATH_SOUND);
237 		return -1;
238 	}
239 	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
240 		warn("symlink %s", _PATH_AUDIOCTL);
241 		return -1;
242 	}
243 	if (symlink(mixerpath, _PATH_MIXER) == -1) {
244 		warn("symlink %s", _PATH_MIXER);
245 		return -1;
246 	}
247 
248 	return 0;
249 }
250 
251 int
audiodev_set_param(struct audiodev * adev,int mode,const char * encname,unsigned int prec,unsigned int ch,unsigned int freq)252 audiodev_set_param(struct audiodev *adev, int mode,
253 	const char *encname, unsigned int prec, unsigned int ch, unsigned int freq)
254 {
255 	audio_info_t ai;
256 	int setmode;
257 	u_int enc;
258 
259 	setmode = 0;
260 	ai = adev->hwinfo;
261 
262 	for (enc = 0; enc < encoding_max; enc++) {
263 		if (strcmp(encname, encoding_names[enc]) == 0)
264 			break;
265 	}
266 	if (enc >= encoding_max) {
267 		warnx("unknown encoding name: %s", encname);
268 		return -1;
269 	}
270 
271 	if ((ai.mode & mode & AUMODE_PLAY)) {
272 		setmode |= AUMODE_PLAY;
273 		ai.play.encoding = enc;
274 		ai.play.precision = prec;
275 		ai.play.channels = ch;
276 		ai.play.sample_rate = freq;
277 	}
278 	if ((ai.mode & mode & AUMODE_RECORD)) {
279 		setmode |= AUMODE_RECORD;
280 		ai.record.encoding = enc;
281 		ai.record.precision = prec;
282 		ai.record.channels = ch;
283 		ai.record.sample_rate = freq;
284 	}
285 
286 	ai.mode = setmode;
287 	printf("setting %s to %s:%u, %uch, %uHz\n",
288 	    adev->xname, encname, prec, ch, freq);
289 	if (ioctl(adev->ctlfd, AUDIO_SETFORMAT, &ai) == -1) {
290 		warn("ioctl AUDIO_SETFORMAT");
291 		return -1;
292 	}
293 	return 0;
294 }
295 
296 int
audiodev_test(struct audiodev * adev)297 audiodev_test(struct audiodev *adev)
298 {
299 	audio_info_t info;
300 	unsigned int i;
301 	int rv;
302 
303 	rv = -1;
304 
305 	adev->fd = open(adev->path, O_WRONLY);
306 	if (adev->fd == -1) {
307 		warn("open %s", adev->path);
308 		return -1;
309 	}
310 
311 	AUDIO_INITINFO(&info);
312 	info.play.sample_rate = adev->hwinfo.play.sample_rate;
313 	info.play.channels = adev->hwinfo.play.channels;
314 	info.play.precision = 16;
315 	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
316 	info.mode = AUMODE_PLAY;
317 	if (ioctl(adev->fd, AUDIO_SETINFO, &info) == -1) {
318 		warn("ioctl AUDIO_SETINFO");
319 		goto done;
320 	}
321 	if (ioctl(adev->fd, AUDIO_GETBUFINFO, &info) == -1) {
322 		warn("ioctl AUDIO_GETBUFINFO");
323 		goto done;
324 	}
325 
326 	for (i = 0; i < adev->hwinfo.play.channels; i++) {
327 		printf("  testing channel %u...", i);
328 		fflush(stdout);
329 		if (audiodev_test_chmask(adev, 1 << i, &info) == -1)
330 			goto done;
331 		printf(" done\n");
332 	}
333 
334 	rv = 0;
335 done:
336 	close(adev->fd);
337 	return rv;
338 }
339 
340 static int
audiodev_test_chmask(struct audiodev * adev,unsigned int chanmask,audio_info_t * info)341 audiodev_test_chmask(struct audiodev *adev, unsigned int chanmask,
342 	audio_info_t *info)
343 {
344 	int16_t *buf;
345 	size_t buflen;
346 	off_t off;
347 	int rv;
348 
349 	rv = -1;
350 
351 	dtmf_new(&buf, &buflen, adev->hwinfo.play.sample_rate, 2,
352 	    adev->hwinfo.play.channels, chanmask, 350.0, 440.0);
353 	if (buf == NULL) {
354 		return -1;
355 	}
356 
357 	off = 0;
358 	while (buflen > 0) {
359 		size_t wlen;
360 		ssize_t ret;
361 
362 		wlen = info->play.buffer_size;
363 		if (wlen > buflen)
364 			wlen = buflen;
365 		ret = write(adev->fd, (char *)buf + off, wlen);
366 		if (ret == -1) {
367 			warn("write");
368 			goto done;
369 		}
370 		wlen = ret;
371 		off += wlen;
372 		buflen -= wlen;
373 	}
374 
375 	if (ioctl(adev->fd, AUDIO_DRAIN) == -1) {
376 		warn("ioctl AUDIO_DRAIN");
377 		goto done;
378 	}
379 
380 	rv = 0;
381 done:
382 	free(buf);
383 	return rv;
384 }
385