Home | History | Annotate | Line # | Download | only in audiocfg
audiodev.c revision 1.10
      1 /* $NetBSD: audiodev.c,v 1.10 2019/08/24 05:45:24 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 <errno.h>
     35 #include <fcntl.h>
     36 #include <paths.h>
     37 #include <stdio.h>
     38 #include <stdlib.h>
     39 #include <string.h>
     40 #include <unistd.h>
     41 
     42 #include "audiodev.h"
     43 #include "drvctl.h"
     44 #include "dtmf.h"
     45 
     46 static int audiodev_test_chmask(struct audiodev *, unsigned int,
     47 	audio_info_t *);
     48 
     49 static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist =
     50     TAILQ_HEAD_INITIALIZER(audiodevlist);
     51 static unsigned int maxunit;
     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 	if (unit > maxunit)
    143 		maxunit = unit;
    144 
    145 	return 0;
    146 }
    147 
    148 static void
    149 audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
    150 {
    151 	audiodev_add(pdev, dev, unit);
    152 }
    153 
    154 int
    155 audiodev_refresh(void)
    156 {
    157 	struct audiodev *adev;
    158 	int fd, error;
    159 
    160 	fd = open(DRVCTLDEV, O_RDONLY);
    161 	if (fd == -1) {
    162 		perror("open " DRVCTLDEV);
    163 		return -1;
    164 	}
    165 
    166 	while (!TAILQ_EMPTY(&audiodevlist)) {
    167 		adev = TAILQ_FIRST(&audiodevlist);
    168 		if (adev->ctlfd != -1)
    169 			close(adev->ctlfd);
    170 		TAILQ_REMOVE(&audiodevlist, adev, next);
    171 		free(adev);
    172 	}
    173 
    174 	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
    175 	if (error == -1) {
    176 		perror("drvctl");
    177 		return -1;
    178 	}
    179 
    180 	close(fd);
    181 
    182 	return 0;
    183 }
    184 
    185 unsigned int
    186 audiodev_maxunit(void)
    187 {
    188 	return maxunit;
    189 }
    190 
    191 /*
    192  * Get audiodev corresponding to audio<i> device.
    193  */
    194 struct audiodev *
    195 audiodev_get(unsigned int i)
    196 {
    197 	struct audiodev *adev;
    198 
    199 	TAILQ_FOREACH(adev, &audiodevlist, next) {
    200 		if (i == adev->unit)
    201 			return adev;
    202 	}
    203 
    204 	return NULL;
    205 }
    206 
    207 int
    208 audiodev_set_default(struct audiodev *adev)
    209 {
    210 	char audiopath[PATH_MAX+1];
    211 	char soundpath[PATH_MAX+1];
    212 	char audioctlpath[PATH_MAX+1];
    213 	char mixerpath[PATH_MAX+1];
    214 
    215 	snprintf(audiopath, sizeof(audiopath),
    216 	    _PATH_AUDIO "%u", adev->unit);
    217 	snprintf(soundpath, sizeof(soundpath),
    218 	    _PATH_SOUND "%u", adev->unit);
    219 	snprintf(audioctlpath, sizeof(audioctlpath),
    220 	    _PATH_AUDIOCTL "%u", adev->unit);
    221 	snprintf(mixerpath, sizeof(mixerpath),
    222 	    _PATH_MIXER "%u", adev->unit);
    223 
    224 	unlink(_PATH_AUDIO);
    225 	unlink(_PATH_SOUND);
    226 	unlink(_PATH_AUDIOCTL);
    227 	unlink(_PATH_MIXER);
    228 
    229 	if (symlink(audiopath, _PATH_AUDIO) == -1) {
    230 		perror("symlink " _PATH_AUDIO);
    231 		return -1;
    232 	}
    233 	if (symlink(soundpath, _PATH_SOUND) == -1) {
    234 		perror("symlink " _PATH_SOUND);
    235 		return -1;
    236 	}
    237 	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
    238 		perror("symlink " _PATH_AUDIOCTL);
    239 		return -1;
    240 	}
    241 	if (symlink(mixerpath, _PATH_MIXER) == -1) {
    242 		perror("symlink " _PATH_MIXER);
    243 		return -1;
    244 	}
    245 
    246 	return 0;
    247 }
    248 
    249 int
    250 audiodev_set_param(struct audiodev *adev, int mode,
    251 	const char *encname, unsigned int prec, unsigned int ch, unsigned int freq)
    252 {
    253 	audio_info_t ai;
    254 	int setmode;
    255 	u_int enc;
    256 
    257 	setmode = 0;
    258 	ai = adev->hwinfo;
    259 
    260 	for (enc = 0; enc < encoding_max; enc++) {
    261 		if (strcmp(encname, encoding_names[enc]) == 0)
    262 			break;
    263 	}
    264 	if (enc >= encoding_max) {
    265 		fprintf(stderr, "unknown encoding name: %s\n", encname);
    266 		errno = EINVAL;
    267 		return -1;
    268 	}
    269 
    270 	if ((ai.mode & mode & AUMODE_PLAY)) {
    271 		setmode |= AUMODE_PLAY;
    272 		ai.play.encoding = enc;
    273 		ai.play.precision = prec;
    274 		ai.play.channels = ch;
    275 		ai.play.sample_rate = freq;
    276 	}
    277 	if ((ai.mode & mode & AUMODE_RECORD)) {
    278 		setmode |= AUMODE_RECORD;
    279 		ai.record.encoding = enc;
    280 		ai.record.precision = prec;
    281 		ai.record.channels = ch;
    282 		ai.record.sample_rate = freq;
    283 	}
    284 
    285 	if (setmode == 0) {
    286 		errno = EINVAL;
    287 		return -1;
    288 	}
    289 
    290 	ai.mode = setmode;
    291 	printf("setting %s to %s:%u, %uch, %uHz\n",
    292 	    adev->xname, encname, prec, ch, freq);
    293 	if (ioctl(adev->ctlfd, AUDIO_SETFORMAT, &ai) == -1) {
    294 		perror("ioctl AUDIO_SETFORMAT");
    295 		return -1;
    296 	}
    297 	return 0;
    298 }
    299 
    300 int
    301 audiodev_test(struct audiodev *adev)
    302 {
    303 	audio_info_t info;
    304 	unsigned int i;
    305 	int rv;
    306 
    307 	rv = -1;
    308 
    309 	adev->fd = open(adev->path, O_WRONLY);
    310 	if (adev->fd == -1) {
    311 		perror("open");
    312 		return -1;
    313 	}
    314 
    315 	AUDIO_INITINFO(&info);
    316 	info.play.sample_rate = AUDIODEV_SAMPLE_RATE;
    317 	info.play.channels = adev->hwinfo.play.channels;
    318 	info.play.precision = 16;
    319 	info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
    320 	info.mode = AUMODE_PLAY;
    321 	if (ioctl(adev->fd, AUDIO_SETINFO, &info) == -1) {
    322 		perror("ioctl AUDIO_SETINFO");
    323 		goto done;
    324 	}
    325 	if (ioctl(adev->fd, AUDIO_GETINFO, &info) == -1) {
    326 		perror("ioctl AUDIO_GETINFO");
    327 		goto done;
    328 	}
    329 
    330 	for (i = 0; i < adev->hwinfo.play.channels; i++) {
    331 		printf("  testing channel %u...", i);
    332 		fflush(stdout);
    333 		if (audiodev_test_chmask(adev, 1 << i, &info) == -1)
    334 			goto done;
    335 		printf(" done\n");
    336 	}
    337 
    338 	rv = 0;
    339 done:
    340 	close(adev->fd);
    341 	return rv;
    342 }
    343 
    344 static int
    345 audiodev_test_chmask(struct audiodev *adev, unsigned int chanmask,
    346 	audio_info_t *info)
    347 {
    348 	int16_t *buf;
    349 	size_t buflen;
    350 	off_t off;
    351 	int rv;
    352 
    353 	rv = -1;
    354 
    355 	dtmf_new(&buf, &buflen, info->play.sample_rate, 2,
    356 	    adev->hwinfo.play.channels, chanmask, 350.0, 440.0);
    357 	if (buf == NULL) {
    358 		return -1;
    359 	}
    360 
    361 	off = 0;
    362 	while (buflen > 0) {
    363 		size_t wlen;
    364 		ssize_t ret;
    365 
    366 		wlen = info->play.buffer_size;
    367 		if (wlen > buflen)
    368 			wlen = buflen;
    369 		ret = write(adev->fd, (char *)buf + off, wlen);
    370 		if (ret == -1) {
    371 			perror("write");
    372 			goto done;
    373 		}
    374 		wlen = ret;
    375 		off += wlen;
    376 		buflen -= wlen;
    377 	}
    378 
    379 	if (ioctl(adev->fd, AUDIO_DRAIN) == -1) {
    380 		perror("ioctl AUDIO_DRAIN");
    381 		goto done;
    382 	}
    383 
    384 	rv = 0;
    385 done:
    386 	free(buf);
    387 	return rv;
    388 }
    389