Home | History | Annotate | Line # | Download | only in audiocfg
      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 (at) 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
     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
    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
    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
    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
    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 *
    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
    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
    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
    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
    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