1/* $NetBSD: audiodev.c,v 1.3 2010/09/02 02:17:35 jmcneill 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 <fcntl.h>
35#include <paths.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40
41#include "audiodev.h"
42#include "drvctl.h"
43#include "dtmf.h"
44
45static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist =
46    TAILQ_HEAD_INITIALIZER(audiodevlist);
47
48#define AUDIODEV_SAMPLE_RATE	44100
49
50static unsigned int
51audiodev_probe_pchans(struct audiodev *adev)
52{
53	audio_info_t info;
54	unsigned int nchans = 0, n;
55	int error;
56
57	AUDIO_INITINFO(&info);
58	info.play.sample_rate = AUDIODEV_SAMPLE_RATE;
59	info.play.precision = 16;
60	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
61	info.play.channels = 1;
62	info.mode = AUMODE_PLAY;
63	error = ioctl(adev->fd, AUDIO_SETINFO, &info);
64	if (error == -1)
65		return 0;
66	nchans = 1;
67
68	for (n = 2; n <= 16; n += 2) {
69		info.play.channels = n;
70		error = ioctl(adev->fd, AUDIO_SETINFO, &info);
71		if (error == -1)
72			break;
73		nchans = info.play.channels;
74	}
75
76	return nchans;
77}
78
79static int
80audiodev_getinfo(struct audiodev *adev)
81{
82	struct stat st;
83
84	if (stat(adev->path, &st) == -1)
85		return -1;
86	adev->dev = st.st_rdev;
87
88	if (stat(_PATH_AUDIO, &st) != -1 && st.st_rdev == adev->dev)
89		adev->defaultdev = true;
90
91	adev->fd = open(adev->path, O_RDWR);
92	if (adev->fd == -1)
93		return -1;
94	if (ioctl(adev->fd, AUDIO_GETDEV, &adev->audio_device) == -1) {
95		close(adev->fd);
96		return -1;
97	}
98
99	adev->pchan = audiodev_probe_pchans(adev);
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) - 1, "/dev/%s", dev);
116	adev->unit = unit;
117
118	if (audiodev_getinfo(adev) == -1) {
119		free(adev);
120		return -1;
121	}
122
123#ifdef DEBUG
124	printf("[%c] %s: %s\n", adev->defaultdev ? '*' : ' ',
125	    adev->path, adev->audio_device.name);
126#endif
127
128	TAILQ_INSERT_TAIL(&audiodevlist, adev, next);
129
130	return 0;
131}
132
133static void
134audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
135{
136	audiodev_add(pdev, dev, unit);
137}
138
139int
140audiodev_refresh(void)
141{
142	struct audiodev *adev;
143	int fd, error;
144
145	fd = open(DRVCTLDEV, O_RDONLY);
146	if (fd == -1) {
147		perror("open " DRVCTLDEV);
148		return -1;
149	}
150
151	while (!TAILQ_EMPTY(&audiodevlist)) {
152		adev = TAILQ_FIRST(&audiodevlist);
153		if (adev->fd != -1)
154			close(adev->fd);
155		TAILQ_REMOVE(&audiodevlist, adev, next);
156		free(adev);
157	}
158
159	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
160	if (error == -1) {
161		perror("drvctl");
162		return -1;
163	}
164
165	close(fd);
166
167	return 0;
168}
169
170unsigned int
171audiodev_count(void)
172{
173	struct audiodev *adev;
174	unsigned int n;
175
176	n = 0;
177	TAILQ_FOREACH(adev, &audiodevlist, next)
178		++n;
179
180	return n;
181}
182
183struct audiodev *
184audiodev_get(unsigned int i)
185{
186	struct audiodev *adev;
187	unsigned int n;
188
189	n = 0;
190	TAILQ_FOREACH(adev, &audiodevlist, next) {
191		if (n == i)
192			return adev;
193		++n;
194	}
195
196	return NULL;
197}
198
199int
200audiodev_set_default(struct audiodev *adev)
201{
202	char audiopath[PATH_MAX+1];
203	char soundpath[PATH_MAX+1];
204	char audioctlpath[PATH_MAX+1];
205	char mixerpath[PATH_MAX+1];
206
207	snprintf(audiopath, sizeof(audiopath) - 1,
208	    _PATH_AUDIO "%u", adev->unit);
209	snprintf(soundpath, sizeof(soundpath) - 1,
210	    _PATH_SOUND "%u", adev->unit);
211	snprintf(audioctlpath, sizeof(audioctlpath) - 1,
212	    _PATH_AUDIOCTL "%u", adev->unit);
213	snprintf(mixerpath, sizeof(mixerpath) - 1,
214	    _PATH_MIXER "%u", adev->unit);
215
216	unlink(_PATH_AUDIO);
217	unlink(_PATH_SOUND);
218	unlink(_PATH_AUDIOCTL);
219	unlink(_PATH_MIXER);
220
221	if (symlink(audiopath, _PATH_AUDIO) == -1) {
222		perror("symlink " _PATH_AUDIO);
223		return -1;
224	}
225	if (symlink(soundpath, _PATH_SOUND) == -1) {
226		perror("symlink " _PATH_SOUND);
227		return -1;
228	}
229	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
230		perror("symlink " _PATH_AUDIOCTL);
231		return -1;
232	}
233	if (symlink(mixerpath, _PATH_MIXER) == -1) {
234		perror("symlink " _PATH_MIXER);
235		return -1;
236	}
237
238	return 0;
239}
240
241int
242audiodev_test(struct audiodev *adev, unsigned int chanmask)
243{
244	audio_info_t info;
245	int16_t *buf;
246	size_t buflen;
247	off_t off;
248	int rv = 0;
249
250	AUDIO_INITINFO(&info);
251	info.play.sample_rate = AUDIODEV_SAMPLE_RATE;
252	info.play.channels = adev->pchan;
253	info.play.precision = 16;
254	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
255	info.mode = AUMODE_PLAY;
256	if (ioctl(adev->fd, AUDIO_SETINFO, &info) == -1) {
257		perror("ioctl AUDIO_SETINFO");
258		return -1;
259	}
260	if (ioctl(adev->fd, AUDIO_GETINFO, &info) == -1) {
261		perror("ioctl AUDIO_GETINFO");
262		return -1;
263	}
264
265	dtmf_new(&buf, &buflen, info.play.sample_rate, 2,
266	    adev->pchan, chanmask, 350.0, 440.0);
267	if (buf == NULL)
268		return -1;
269
270	off = 0;
271	while (buflen > 0) {
272		size_t wlen = info.play.buffer_size;
273		if (wlen > buflen)
274			wlen = buflen;
275		wlen = write(adev->fd, (char *)buf + off, wlen);
276		if (wlen == -1) {
277			perror("write");
278			rv = -1;
279			goto done;
280		}
281		off += wlen;
282		buflen -= wlen;
283	}
284
285	if (ioctl(adev->fd, AUDIO_DRAIN) == -1)
286		perror("ioctl AUDIO_DRAIN");
287
288done:
289	free(buf);
290
291	return rv;
292}
293