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