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