xref: /netbsd-src/usr.bin/audiocfg/audiodev.c (revision 3dbad28612dad296816d69bdfc98c25dbc330574)
1*3dbad286Sisaki /* $NetBSD: audiodev.c,v 1.15 2019/08/24 07:39:42 isaki Exp $ */
2172ca3ccSmrg 
3172ca3ccSmrg /*
4172ca3ccSmrg  * Copyright (c) 2010 Jared D. McNeill <jmcneill@invisible.ca>
5172ca3ccSmrg  * All rights reserved.
6172ca3ccSmrg  *
7172ca3ccSmrg  * Redistribution and use in source and binary forms, with or without
8172ca3ccSmrg  * modification, are permitted provided that the following conditions
9172ca3ccSmrg  * are met:
10172ca3ccSmrg  * 1. Redistributions of source code must retain the above copyright
11172ca3ccSmrg  *    notice, this list of conditions and the following disclaimer.
12172ca3ccSmrg  * 2. Redistributions in binary form must reproduce the above copyright
13172ca3ccSmrg  *    notice, this list of conditions and the following disclaimer in the
14172ca3ccSmrg  *    documentation and/or other materials provided with the distribution.
15172ca3ccSmrg  *
16172ca3ccSmrg  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17172ca3ccSmrg  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18172ca3ccSmrg  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19172ca3ccSmrg  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20172ca3ccSmrg  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21172ca3ccSmrg  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22172ca3ccSmrg  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23172ca3ccSmrg  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24172ca3ccSmrg  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25172ca3ccSmrg  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26172ca3ccSmrg  * POSSIBILITY OF SUCH DAMAGE.
27172ca3ccSmrg  */
28172ca3ccSmrg 
29172ca3ccSmrg #include <sys/queue.h>
30172ca3ccSmrg #include <sys/ioctl.h>
31172ca3ccSmrg #include <sys/stat.h>
32172ca3ccSmrg #include <sys/drvctlio.h>
33172ca3ccSmrg 
3416e14080Sisaki #include <err.h>
350b88ff99Sisaki #include <errno.h>
36172ca3ccSmrg #include <fcntl.h>
37172ca3ccSmrg #include <paths.h>
38172ca3ccSmrg #include <stdio.h>
39172ca3ccSmrg #include <stdlib.h>
40172ca3ccSmrg #include <string.h>
41172ca3ccSmrg #include <unistd.h>
42172ca3ccSmrg 
43172ca3ccSmrg #include "audiodev.h"
44172ca3ccSmrg #include "drvctl.h"
45e405ac8dSjmcneill #include "dtmf.h"
46172ca3ccSmrg 
47ab49aa4fSisaki static int audiodev_test_chmask(struct audiodev *, unsigned int,
48ab49aa4fSisaki 	audio_info_t *);
49ab49aa4fSisaki 
50172ca3ccSmrg static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist =
51172ca3ccSmrg     TAILQ_HEAD_INITIALIZER(audiodevlist);
52172ca3ccSmrg 
53172ca3ccSmrg static int
audiodev_getinfo(struct audiodev * adev)54172ca3ccSmrg audiodev_getinfo(struct audiodev *adev)
55172ca3ccSmrg {
56172ca3ccSmrg 	struct stat st;
570b88ff99Sisaki 	struct audiofmt *f;
580b88ff99Sisaki 	audio_format_query_t query;
590b88ff99Sisaki 	int i;
60172ca3ccSmrg 
610b88ff99Sisaki 	if (stat(adev->ctlpath, &st) == -1)
62172ca3ccSmrg 		return -1;
63172ca3ccSmrg 	adev->dev = st.st_rdev;
64172ca3ccSmrg 
650b88ff99Sisaki 	if (stat(_PATH_AUDIOCTL, &st) != -1 && st.st_rdev == adev->dev)
66172ca3ccSmrg 		adev->defaultdev = true;
67172ca3ccSmrg 
68923f5af2Sisaki 	adev->ctlfd = open(adev->ctlpath, O_RDONLY);
69923f5af2Sisaki 	if (adev->ctlfd == -1) {
70172ca3ccSmrg 			return -1;
7135f21013Smrg 	}
72923f5af2Sisaki 	if (ioctl(adev->ctlfd, AUDIO_GETDEV, &adev->audio_device) == -1) {
73923f5af2Sisaki 		close(adev->ctlfd);
74172ca3ccSmrg 		return -1;
75172ca3ccSmrg 	}
76172ca3ccSmrg 
770b88ff99Sisaki 	for (i = 0; ;i++) {
780b88ff99Sisaki 		memset(&query, 0, sizeof(query));
790b88ff99Sisaki 		query.index = i;
80923f5af2Sisaki 		if (ioctl(adev->ctlfd, AUDIO_QUERYFORMAT, &query) == -1) {
810b88ff99Sisaki 			if (errno == ENODEV) {
820b88ff99Sisaki 				/* QUERYFORMAT not supported. */
830b88ff99Sisaki 				break;
840b88ff99Sisaki 			}
850b88ff99Sisaki 			if (errno == EINVAL)
860b88ff99Sisaki 				break;
87923f5af2Sisaki 			close(adev->ctlfd);
880b88ff99Sisaki 			return -1;
890b88ff99Sisaki 		}
900b88ff99Sisaki 
910b88ff99Sisaki 		f = calloc(1, sizeof(*f));
920b88ff99Sisaki 		f->fmt = query.fmt;
930b88ff99Sisaki 		TAILQ_INSERT_TAIL(&adev->formats, f, next);
940b88ff99Sisaki 	}
950b88ff99Sisaki 
96923f5af2Sisaki 	if (ioctl(adev->ctlfd, AUDIO_GETFORMAT, &adev->hwinfo) == -1) {
97923f5af2Sisaki 		close(adev->ctlfd);
980b88ff99Sisaki 		return -1;
990b88ff99Sisaki 	}
100e405ac8dSjmcneill 
101172ca3ccSmrg 	return 0;
102172ca3ccSmrg }
103172ca3ccSmrg 
104172ca3ccSmrg static int
audiodev_add(const char * pdev,const char * dev,unsigned int unit)105e90b0e00Sjmcneill audiodev_add(const char *pdev, const char *dev, unsigned int unit)
106172ca3ccSmrg {
107172ca3ccSmrg 	struct audiodev *adev;
108172ca3ccSmrg 
109172ca3ccSmrg 	adev = calloc(1, sizeof(*adev));
110172ca3ccSmrg 	if (adev == NULL)
111172ca3ccSmrg 		return -1;
112172ca3ccSmrg 
113e90b0e00Sjmcneill 	strlcpy(adev->pxname, pdev, sizeof(adev->pxname));
114172ca3ccSmrg 	strlcpy(adev->xname, dev, sizeof(adev->xname));
1150b88ff99Sisaki 	snprintf(adev->path, sizeof(adev->path), "/dev/%s", dev);
1160b88ff99Sisaki 	snprintf(adev->ctlpath, sizeof(adev->ctlpath), "/dev/audioctl%d", unit);
117172ca3ccSmrg 	adev->unit = unit;
1180b88ff99Sisaki 	TAILQ_INIT(&adev->formats);
119172ca3ccSmrg 
120172ca3ccSmrg 	if (audiodev_getinfo(adev) == -1) {
121172ca3ccSmrg 		free(adev);
122172ca3ccSmrg 		return -1;
123172ca3ccSmrg 	}
124172ca3ccSmrg 
125172ca3ccSmrg #ifdef DEBUG
1260b88ff99Sisaki 	printf("DEBUG: [%c] %s(%s): %s\n", adev->defaultdev ? '*' : ' ',
1270b88ff99Sisaki 	    adev->path, adev->ctlpath, adev->audio_device.name);
1280b88ff99Sisaki 	struct audiofmt *f;
1290b88ff99Sisaki 	TAILQ_FOREACH(f, &adev->formats, next) {
1300b88ff99Sisaki 		printf("DEBUG: enc%d, %d/%d, %dch\n",
1310b88ff99Sisaki 		    f->fmt.encoding,
1320b88ff99Sisaki 		    f->fmt.validbits,
1330b88ff99Sisaki 		    f->fmt.precision,
1340b88ff99Sisaki 		    f->fmt.channels);
1350b88ff99Sisaki 	}
136172ca3ccSmrg #endif
137172ca3ccSmrg 
138172ca3ccSmrg 	TAILQ_INSERT_TAIL(&audiodevlist, adev, next);
139172ca3ccSmrg 
140172ca3ccSmrg 	return 0;
141172ca3ccSmrg }
142172ca3ccSmrg 
143172ca3ccSmrg static void
audiodev_cb(void * args,const char * pdev,const char * dev,unsigned int unit)144e90b0e00Sjmcneill audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
145172ca3ccSmrg {
146e90b0e00Sjmcneill 	audiodev_add(pdev, dev, unit);
147172ca3ccSmrg }
148172ca3ccSmrg 
149172ca3ccSmrg int
audiodev_refresh(void)150172ca3ccSmrg audiodev_refresh(void)
151172ca3ccSmrg {
152172ca3ccSmrg 	struct audiodev *adev;
153172ca3ccSmrg 	int fd, error;
154172ca3ccSmrg 
155e405ac8dSjmcneill 	fd = open(DRVCTLDEV, O_RDONLY);
156172ca3ccSmrg 	if (fd == -1) {
15716e14080Sisaki 		warn("open %s", DRVCTLDEV);
158172ca3ccSmrg 		return -1;
159172ca3ccSmrg 	}
160172ca3ccSmrg 
161172ca3ccSmrg 	while (!TAILQ_EMPTY(&audiodevlist)) {
162172ca3ccSmrg 		adev = TAILQ_FIRST(&audiodevlist);
163923f5af2Sisaki 		if (adev->ctlfd != -1)
164923f5af2Sisaki 			close(adev->ctlfd);
165172ca3ccSmrg 		TAILQ_REMOVE(&audiodevlist, adev, next);
166172ca3ccSmrg 		free(adev);
167172ca3ccSmrg 	}
168172ca3ccSmrg 
169172ca3ccSmrg 	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
170172ca3ccSmrg 	if (error == -1) {
17116e14080Sisaki 		warnx("drvctl failed");
172172ca3ccSmrg 		return -1;
173172ca3ccSmrg 	}
174172ca3ccSmrg 
175172ca3ccSmrg 	close(fd);
176172ca3ccSmrg 
177172ca3ccSmrg 	return 0;
178172ca3ccSmrg }
179172ca3ccSmrg 
180172ca3ccSmrg unsigned int
audiodev_count(void)181*3dbad286Sisaki audiodev_count(void)
182172ca3ccSmrg {
183*3dbad286Sisaki 	struct audiodev *adev;
184*3dbad286Sisaki 	unsigned int n;
185*3dbad286Sisaki 
186*3dbad286Sisaki 	n = 0;
187*3dbad286Sisaki 	TAILQ_FOREACH(adev, &audiodevlist, next)
188*3dbad286Sisaki 		++n;
189*3dbad286Sisaki 
190*3dbad286Sisaki 	return n;
191172ca3ccSmrg }
192172ca3ccSmrg 
193172ca3ccSmrg struct audiodev *
audiodev_get(unsigned int i)194172ca3ccSmrg audiodev_get(unsigned int i)
195172ca3ccSmrg {
196172ca3ccSmrg 	struct audiodev *adev;
197*3dbad286Sisaki 	unsigned int n;
198172ca3ccSmrg 
199*3dbad286Sisaki 	n = 0;
200172ca3ccSmrg 	TAILQ_FOREACH(adev, &audiodevlist, next) {
201*3dbad286Sisaki 		if (n == i)
202172ca3ccSmrg 			return adev;
203*3dbad286Sisaki 		++n;
204172ca3ccSmrg 	}
205172ca3ccSmrg 
206172ca3ccSmrg 	return NULL;
207172ca3ccSmrg }
208172ca3ccSmrg 
209172ca3ccSmrg int
audiodev_set_default(struct audiodev * adev)210172ca3ccSmrg audiodev_set_default(struct audiodev *adev)
211172ca3ccSmrg {
212172ca3ccSmrg 	char audiopath[PATH_MAX+1];
213172ca3ccSmrg 	char soundpath[PATH_MAX+1];
214172ca3ccSmrg 	char audioctlpath[PATH_MAX+1];
215172ca3ccSmrg 	char mixerpath[PATH_MAX+1];
216172ca3ccSmrg 
2170b88ff99Sisaki 	snprintf(audiopath, sizeof(audiopath),
218172ca3ccSmrg 	    _PATH_AUDIO "%u", adev->unit);
2190b88ff99Sisaki 	snprintf(soundpath, sizeof(soundpath),
220172ca3ccSmrg 	    _PATH_SOUND "%u", adev->unit);
2210b88ff99Sisaki 	snprintf(audioctlpath, sizeof(audioctlpath),
222172ca3ccSmrg 	    _PATH_AUDIOCTL "%u", adev->unit);
2230b88ff99Sisaki 	snprintf(mixerpath, sizeof(mixerpath),
224172ca3ccSmrg 	    _PATH_MIXER "%u", adev->unit);
225172ca3ccSmrg 
226172ca3ccSmrg 	unlink(_PATH_AUDIO);
227172ca3ccSmrg 	unlink(_PATH_SOUND);
228172ca3ccSmrg 	unlink(_PATH_AUDIOCTL);
229172ca3ccSmrg 	unlink(_PATH_MIXER);
230172ca3ccSmrg 
231172ca3ccSmrg 	if (symlink(audiopath, _PATH_AUDIO) == -1) {
23216e14080Sisaki 		warn("symlink %s", _PATH_AUDIO);
233172ca3ccSmrg 		return -1;
234172ca3ccSmrg 	}
235172ca3ccSmrg 	if (symlink(soundpath, _PATH_SOUND) == -1) {
23616e14080Sisaki 		warn("symlink %s", _PATH_SOUND);
237172ca3ccSmrg 		return -1;
238172ca3ccSmrg 	}
239172ca3ccSmrg 	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
24016e14080Sisaki 		warn("symlink %s", _PATH_AUDIOCTL);
241172ca3ccSmrg 		return -1;
242172ca3ccSmrg 	}
243172ca3ccSmrg 	if (symlink(mixerpath, _PATH_MIXER) == -1) {
24416e14080Sisaki 		warn("symlink %s", _PATH_MIXER);
245172ca3ccSmrg 		return -1;
246172ca3ccSmrg 	}
247172ca3ccSmrg 
248172ca3ccSmrg 	return 0;
249172ca3ccSmrg }
250e405ac8dSjmcneill 
251e405ac8dSjmcneill int
audiodev_set_param(struct audiodev * adev,int mode,const char * encname,unsigned int prec,unsigned int ch,unsigned int freq)2520b88ff99Sisaki audiodev_set_param(struct audiodev *adev, int mode,
2530b88ff99Sisaki 	const char *encname, unsigned int prec, unsigned int ch, unsigned int freq)
2540b88ff99Sisaki {
255923f5af2Sisaki 	audio_info_t ai;
2560b88ff99Sisaki 	int setmode;
2570b88ff99Sisaki 	u_int enc;
2580b88ff99Sisaki 
2590b88ff99Sisaki 	setmode = 0;
260923f5af2Sisaki 	ai = adev->hwinfo;
2610b88ff99Sisaki 
2620b88ff99Sisaki 	for (enc = 0; enc < encoding_max; enc++) {
2630b88ff99Sisaki 		if (strcmp(encname, encoding_names[enc]) == 0)
2640b88ff99Sisaki 			break;
2650b88ff99Sisaki 	}
2660b88ff99Sisaki 	if (enc >= encoding_max) {
26716e14080Sisaki 		warnx("unknown encoding name: %s", encname);
2680b88ff99Sisaki 		return -1;
2690b88ff99Sisaki 	}
2700b88ff99Sisaki 
2710b88ff99Sisaki 	if ((ai.mode & mode & AUMODE_PLAY)) {
2720b88ff99Sisaki 		setmode |= AUMODE_PLAY;
2730b88ff99Sisaki 		ai.play.encoding = enc;
2740b88ff99Sisaki 		ai.play.precision = prec;
2750b88ff99Sisaki 		ai.play.channels = ch;
2760b88ff99Sisaki 		ai.play.sample_rate = freq;
2770b88ff99Sisaki 	}
2780b88ff99Sisaki 	if ((ai.mode & mode & AUMODE_RECORD)) {
2790b88ff99Sisaki 		setmode |= AUMODE_RECORD;
2800b88ff99Sisaki 		ai.record.encoding = enc;
2810b88ff99Sisaki 		ai.record.precision = prec;
2820b88ff99Sisaki 		ai.record.channels = ch;
2830b88ff99Sisaki 		ai.record.sample_rate = freq;
2840b88ff99Sisaki 	}
2850b88ff99Sisaki 
2860b88ff99Sisaki 	ai.mode = setmode;
2870b88ff99Sisaki 	printf("setting %s to %s:%u, %uch, %uHz\n",
2880b88ff99Sisaki 	    adev->xname, encname, prec, ch, freq);
289923f5af2Sisaki 	if (ioctl(adev->ctlfd, AUDIO_SETFORMAT, &ai) == -1) {
29016e14080Sisaki 		warn("ioctl AUDIO_SETFORMAT");
2910b88ff99Sisaki 		return -1;
2920b88ff99Sisaki 	}
2930b88ff99Sisaki 	return 0;
2940b88ff99Sisaki }
2950b88ff99Sisaki 
2960b88ff99Sisaki int
audiodev_test(struct audiodev * adev)297ab49aa4fSisaki audiodev_test(struct audiodev *adev)
298e405ac8dSjmcneill {
299e405ac8dSjmcneill 	audio_info_t info;
300ab49aa4fSisaki 	unsigned int i;
301ab49aa4fSisaki 	int rv;
3020b88ff99Sisaki 
303ab49aa4fSisaki 	rv = -1;
304ab49aa4fSisaki 
305ab49aa4fSisaki 	adev->fd = open(adev->path, O_WRONLY);
306ab49aa4fSisaki 	if (adev->fd == -1) {
30716e14080Sisaki 		warn("open %s", adev->path);
3080b88ff99Sisaki 		return -1;
3090b88ff99Sisaki 	}
310e405ac8dSjmcneill 
311e405ac8dSjmcneill 	AUDIO_INITINFO(&info);
31252ec5b07Sisaki 	info.play.sample_rate = adev->hwinfo.play.sample_rate;
313923f5af2Sisaki 	info.play.channels = adev->hwinfo.play.channels;
314e405ac8dSjmcneill 	info.play.precision = 16;
315e405ac8dSjmcneill 	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
316e405ac8dSjmcneill 	info.mode = AUMODE_PLAY;
317ab49aa4fSisaki 	if (ioctl(adev->fd, AUDIO_SETINFO, &info) == -1) {
31816e14080Sisaki 		warn("ioctl AUDIO_SETINFO");
319ab49aa4fSisaki 		goto done;
320e405ac8dSjmcneill 	}
3210c69e485Sisaki 	if (ioctl(adev->fd, AUDIO_GETBUFINFO, &info) == -1) {
3220c69e485Sisaki 		warn("ioctl AUDIO_GETBUFINFO");
323ab49aa4fSisaki 		goto done;
324e405ac8dSjmcneill 	}
325e405ac8dSjmcneill 
326ab49aa4fSisaki 	for (i = 0; i < adev->hwinfo.play.channels; i++) {
327ab49aa4fSisaki 		printf("  testing channel %u...", i);
328ab49aa4fSisaki 		fflush(stdout);
329ab49aa4fSisaki 		if (audiodev_test_chmask(adev, 1 << i, &info) == -1)
330ab49aa4fSisaki 			goto done;
331ab49aa4fSisaki 		printf(" done\n");
332ab49aa4fSisaki 	}
333ab49aa4fSisaki 
334ab49aa4fSisaki 	rv = 0;
335ab49aa4fSisaki done:
336ab49aa4fSisaki 	close(adev->fd);
337ab49aa4fSisaki 	return rv;
338ab49aa4fSisaki }
339ab49aa4fSisaki 
340ab49aa4fSisaki static int
audiodev_test_chmask(struct audiodev * adev,unsigned int chanmask,audio_info_t * info)341ab49aa4fSisaki audiodev_test_chmask(struct audiodev *adev, unsigned int chanmask,
342ab49aa4fSisaki 	audio_info_t *info)
343ab49aa4fSisaki {
344ab49aa4fSisaki 	int16_t *buf;
345ab49aa4fSisaki 	size_t buflen;
346ab49aa4fSisaki 	off_t off;
347ab49aa4fSisaki 	int rv;
348ab49aa4fSisaki 
349ab49aa4fSisaki 	rv = -1;
350ab49aa4fSisaki 
35152ec5b07Sisaki 	dtmf_new(&buf, &buflen, adev->hwinfo.play.sample_rate, 2,
352923f5af2Sisaki 	    adev->hwinfo.play.channels, chanmask, 350.0, 440.0);
3530b88ff99Sisaki 	if (buf == NULL) {
354ab49aa4fSisaki 		return -1;
3550b88ff99Sisaki 	}
356e405ac8dSjmcneill 
357e405ac8dSjmcneill 	off = 0;
358e405ac8dSjmcneill 	while (buflen > 0) {
3591a91378aSdholland 		size_t wlen;
3601a91378aSdholland 		ssize_t ret;
3611a91378aSdholland 
362ab49aa4fSisaki 		wlen = info->play.buffer_size;
363e405ac8dSjmcneill 		if (wlen > buflen)
364e405ac8dSjmcneill 			wlen = buflen;
365ab49aa4fSisaki 		ret = write(adev->fd, (char *)buf + off, wlen);
3661a91378aSdholland 		if (ret == -1) {
36716e14080Sisaki 			warn("write");
368d148107cSjmcneill 			goto done;
369d148107cSjmcneill 		}
3701a91378aSdholland 		wlen = ret;
371e405ac8dSjmcneill 		off += wlen;
372e405ac8dSjmcneill 		buflen -= wlen;
373e405ac8dSjmcneill 	}
374e405ac8dSjmcneill 
375ab49aa4fSisaki 	if (ioctl(adev->fd, AUDIO_DRAIN) == -1) {
37616e14080Sisaki 		warn("ioctl AUDIO_DRAIN");
3770b88ff99Sisaki 		goto done;
3780b88ff99Sisaki 	}
379e405ac8dSjmcneill 
3800b88ff99Sisaki 	rv = 0;
381d148107cSjmcneill done:
382e405ac8dSjmcneill 	free(buf);
383d148107cSjmcneill 	return rv;
384e405ac8dSjmcneill }
385