Home | History | Annotate | Line # | Download | only in audiocfg
audiodev.c revision 1.7.2.2
      1 /* $NetBSD: audiodev.c,v 1.7.2.2 2019/09/29 07:34:09 martin 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 #define AUDIODEV_SAMPLE_RATE	44100
     54 
     55 static int
     56 audiodev_getinfo(struct audiodev *adev)
     57 {
     58 	struct stat st;
     59 	struct audiofmt *f;
     60 	audio_format_query_t query;
     61 	int i;
     62 
     63 	if (stat(adev->ctlpath, &st) == -1)
     64 		return -1;
     65 	adev->dev = st.st_rdev;
     66 
     67 	if (stat(_PATH_AUDIOCTL, &st) != -1 && st.st_rdev == adev->dev)
     68 		adev->defaultdev = true;
     69 
     70 	adev->ctlfd = open(adev->ctlpath, O_RDONLY);
     71 	if (adev->ctlfd == -1) {
     72 			return -1;
     73 	}
     74 	if (ioctl(adev->ctlfd, AUDIO_GETDEV, &adev->audio_device) == -1) {
     75 		close(adev->ctlfd);
     76 		return -1;
     77 	}
     78 
     79 	for (i = 0; ;i++) {
     80 		memset(&query, 0, sizeof(query));
     81 		query.index = i;
     82 		if (ioctl(adev->ctlfd, AUDIO_QUERYFORMAT, &query) == -1) {
     83 			if (errno == ENODEV) {
     84 				/* QUERYFORMAT not supported. */
     85 				break;
     86 			}
     87 			if (errno == EINVAL)
     88 				break;
     89 			close(adev->ctlfd);
     90 			return -1;
     91 		}
     92 
     93 		f = calloc(1, sizeof(*f));
     94 		f->fmt = query.fmt;
     95 		TAILQ_INSERT_TAIL(&adev->formats, f, next);
     96 	}
     97 
     98 	if (ioctl(adev->ctlfd, AUDIO_GETFORMAT, &adev->hwinfo) == -1) {
     99 		close(adev->ctlfd);
    100 		return -1;
    101 	}
    102 
    103 	return 0;
    104 }
    105 
    106 static int
    107 audiodev_add(const char *pdev, const char *dev, unsigned int unit)
    108 {
    109 	struct audiodev *adev;
    110 
    111 	adev = calloc(1, sizeof(*adev));
    112 	if (adev == NULL)
    113 		return -1;
    114 
    115 	strlcpy(adev->pxname, pdev, sizeof(adev->pxname));
    116 	strlcpy(adev->xname, dev, sizeof(adev->xname));
    117 	snprintf(adev->path, sizeof(adev->path), "/dev/%s", dev);
    118 	snprintf(adev->ctlpath, sizeof(adev->ctlpath), "/dev/audioctl%d", unit);
    119 	adev->unit = unit;
    120 	TAILQ_INIT(&adev->formats);
    121 
    122 	if (audiodev_getinfo(adev) == -1) {
    123 		free(adev);
    124 		return -1;
    125 	}
    126 
    127 #ifdef DEBUG
    128 	printf("DEBUG: [%c] %s(%s): %s\n", adev->defaultdev ? '*' : ' ',
    129 	    adev->path, adev->ctlpath, adev->audio_device.name);
    130 	struct audiofmt *f;
    131 	TAILQ_FOREACH(f, &adev->formats, next) {
    132 		printf("DEBUG: enc%d, %d/%d, %dch\n",
    133 		    f->fmt.encoding,
    134 		    f->fmt.validbits,
    135 		    f->fmt.precision,
    136 		    f->fmt.channels);
    137 	}
    138 #endif
    139 
    140 	TAILQ_INSERT_TAIL(&audiodevlist, adev, next);
    141 
    142 	return 0;
    143 }
    144 
    145 static void
    146 audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
    147 {
    148 	audiodev_add(pdev, dev, unit);
    149 }
    150 
    151 int
    152 audiodev_refresh(void)
    153 {
    154 	struct audiodev *adev;
    155 	int fd, error;
    156 
    157 	fd = open(DRVCTLDEV, O_RDONLY);
    158 	if (fd == -1) {
    159 		warn("open %s", DRVCTLDEV);
    160 		return -1;
    161 	}
    162 
    163 	while (!TAILQ_EMPTY(&audiodevlist)) {
    164 		adev = TAILQ_FIRST(&audiodevlist);
    165 		if (adev->ctlfd != -1)
    166 			close(adev->ctlfd);
    167 		TAILQ_REMOVE(&audiodevlist, adev, next);
    168 		free(adev);
    169 	}
    170 
    171 	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
    172 	if (error == -1) {
    173 		warnx("drvctl failed");
    174 		return -1;
    175 	}
    176 
    177 	close(fd);
    178 
    179 	return 0;
    180 }
    181 
    182 unsigned int
    183 audiodev_count(void)
    184 {
    185 	struct audiodev *adev;
    186 	unsigned int n;
    187 
    188 	n = 0;
    189 	TAILQ_FOREACH(adev, &audiodevlist, next)
    190 		++n;
    191 
    192 	return n;
    193 }
    194 
    195 struct audiodev *
    196 audiodev_get(unsigned int i)
    197 {
    198 	struct audiodev *adev;
    199 	unsigned int n;
    200 
    201 	n = 0;
    202 	TAILQ_FOREACH(adev, &audiodevlist, next) {
    203 		if (n == i)
    204 			return adev;
    205 		++n;
    206 	}
    207 
    208 	return NULL;
    209 }
    210 
    211 int
    212 audiodev_set_default(struct audiodev *adev)
    213 {
    214 	char audiopath[PATH_MAX+1];
    215 	char soundpath[PATH_MAX+1];
    216 	char audioctlpath[PATH_MAX+1];
    217 	char mixerpath[PATH_MAX+1];
    218 
    219 	snprintf(audiopath, sizeof(audiopath),
    220 	    _PATH_AUDIO "%u", adev->unit);
    221 	snprintf(soundpath, sizeof(soundpath),
    222 	    _PATH_SOUND "%u", adev->unit);
    223 	snprintf(audioctlpath, sizeof(audioctlpath),
    224 	    _PATH_AUDIOCTL "%u", adev->unit);
    225 	snprintf(mixerpath, sizeof(mixerpath),
    226 	    _PATH_MIXER "%u", adev->unit);
    227 
    228 	unlink(_PATH_AUDIO);
    229 	unlink(_PATH_SOUND);
    230 	unlink(_PATH_AUDIOCTL);
    231 	unlink(_PATH_MIXER);
    232 
    233 	if (symlink(audiopath, _PATH_AUDIO) == -1) {
    234 		warn("symlink %s", _PATH_AUDIO);
    235 		return -1;
    236 	}
    237 	if (symlink(soundpath, _PATH_SOUND) == -1) {
    238 		warn("symlink %s", _PATH_SOUND);
    239 		return -1;
    240 	}
    241 	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
    242 		warn("symlink %s", _PATH_AUDIOCTL);
    243 		return -1;
    244 	}
    245 	if (symlink(mixerpath, _PATH_MIXER) == -1) {
    246 		warn("symlink %s", _PATH_MIXER);
    247 		return -1;
    248 	}
    249 
    250 	return 0;
    251 }
    252 
    253 int
    254 audiodev_set_param(struct audiodev *adev, int mode,
    255 	const char *encname, unsigned int prec, unsigned int ch, unsigned int freq)
    256 {
    257 	audio_info_t ai;
    258 	int setmode;
    259 	u_int enc;
    260 
    261 	setmode = 0;
    262 	ai = adev->hwinfo;
    263 
    264 	for (enc = 0; enc < encoding_max; enc++) {
    265 		if (strcmp(encname, encoding_names[enc]) == 0)
    266 			break;
    267 	}
    268 	if (enc >= encoding_max) {
    269 		warnx("unknown encoding name: %s", encname);
    270 		return -1;
    271 	}
    272 
    273 	if ((ai.mode & mode & AUMODE_PLAY)) {
    274 		setmode |= AUMODE_PLAY;
    275 		ai.play.encoding = enc;
    276 		ai.play.precision = prec;
    277 		ai.play.channels = ch;
    278 		ai.play.sample_rate = freq;
    279 	}
    280 	if ((ai.mode & mode & AUMODE_RECORD)) {
    281 		setmode |= AUMODE_RECORD;
    282 		ai.record.encoding = enc;
    283 		ai.record.precision = prec;
    284 		ai.record.channels = ch;
    285 		ai.record.sample_rate = freq;
    286 	}
    287 
    288 	if (setmode == 0) {
    289 		errno = EINVAL;
    290 		return -1;
    291 	}
    292 
    293 	ai.mode = setmode;
    294 	printf("setting %s to %s:%u, %uch, %uHz\n",
    295 	    adev->xname, encname, prec, ch, freq);
    296 	if (ioctl(adev->ctlfd, AUDIO_SETFORMAT, &ai) == -1) {
    297 		warn("ioctl AUDIO_SETFORMAT");
    298 		return -1;
    299 	}
    300 	return 0;
    301 }
    302 
    303 int
    304 audiodev_test(struct audiodev *adev)
    305 {
    306 	audio_info_t info;
    307 	unsigned int i;
    308 	int rv;
    309 
    310 	rv = -1;
    311 
    312 	adev->fd = open(adev->path, O_WRONLY);
    313 	if (adev->fd == -1) {
    314 		warn("open %s", adev->path);
    315 		return -1;
    316 	}
    317 
    318 	AUDIO_INITINFO(&info);
    319 	info.play.sample_rate = AUDIODEV_SAMPLE_RATE;
    320 	info.play.channels = adev->hwinfo.play.channels;
    321 	info.play.precision = 16;
    322 	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
    323 	info.mode = AUMODE_PLAY;
    324 	if (ioctl(adev->fd, AUDIO_SETINFO, &info) == -1) {
    325 		warn("ioctl AUDIO_SETINFO");
    326 		goto done;
    327 	}
    328 	if (ioctl(adev->fd, AUDIO_GETINFO, &info) == -1) {
    329 		warn("ioctl AUDIO_GETINFO");
    330 		goto done;
    331 	}
    332 
    333 	for (i = 0; i < adev->hwinfo.play.channels; i++) {
    334 		printf("  testing channel %u...", i);
    335 		fflush(stdout);
    336 		if (audiodev_test_chmask(adev, 1 << i, &info) == -1)
    337 			goto done;
    338 		printf(" done\n");
    339 	}
    340 
    341 	rv = 0;
    342 done:
    343 	close(adev->fd);
    344 	return rv;
    345 }
    346 
    347 static int
    348 audiodev_test_chmask(struct audiodev *adev, unsigned int chanmask,
    349 	audio_info_t *info)
    350 {
    351 	int16_t *buf;
    352 	size_t buflen;
    353 	off_t off;
    354 	int rv;
    355 
    356 	rv = -1;
    357 
    358 	dtmf_new(&buf, &buflen, info->play.sample_rate, 2,
    359 	    adev->hwinfo.play.channels, chanmask, 350.0, 440.0);
    360 	if (buf == NULL) {
    361 		return -1;
    362 	}
    363 
    364 	off = 0;
    365 	while (buflen > 0) {
    366 		size_t wlen;
    367 		ssize_t ret;
    368 
    369 		wlen = info->play.buffer_size;
    370 		if (wlen > buflen)
    371 			wlen = buflen;
    372 		ret = write(adev->fd, (char *)buf + off, wlen);
    373 		if (ret == -1) {
    374 			warn("write");
    375 			goto done;
    376 		}
    377 		wlen = ret;
    378 		off += wlen;
    379 		buflen -= wlen;
    380 	}
    381 
    382 	if (ioctl(adev->fd, AUDIO_DRAIN) == -1) {
    383 		warn("ioctl AUDIO_DRAIN");
    384 		goto done;
    385 	}
    386 
    387 	rv = 0;
    388 done:
    389 	free(buf);
    390 	return rv;
    391 }
    392