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
47static int audiodev_test_chmask(struct audiodev *, unsigned int,
48	audio_info_t *);
49
50static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist =
51    TAILQ_HEAD_INITIALIZER(audiodevlist);
52
53static int
54audiodev_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
104static int
105audiodev_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
143static void
144audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
145{
146	audiodev_add(pdev, dev, unit);
147}
148
149int
150audiodev_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
180unsigned int
181audiodev_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
193struct audiodev *
194audiodev_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
209int
210audiodev_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
251int
252audiodev_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
296int
297audiodev_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;
335done:
336	close(adev->fd);
337	return rv;
338}
339
340static int
341audiodev_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;
381done:
382	free(buf);
383	return rv;
384}
385