Home | History | Annotate | Line # | Download | only in audiocfg
audiodev.c revision 1.7.2.1
      1 /* $NetBSD: audiodev.c,v 1.7.2.1 2019/09/28 07:41:18 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 <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 
     52 #define AUDIODEV_SAMPLE_RATE	44100
     53 
     54 static int
     55 audiodev_getinfo(struct audiodev *adev)
     56 {
     57 	struct stat st;
     58 	struct audiofmt *f;
     59 	audio_format_query_t query;
     60 	int i;
     61 
     62 	if (stat(adev->ctlpath, &st) == -1)
     63 		return -1;
     64 	adev->dev = st.st_rdev;
     65 
     66 	if (stat(_PATH_AUDIOCTL, &st) != -1 && st.st_rdev == adev->dev)
     67 		adev->defaultdev = true;
     68 
     69 	adev->ctlfd = open(adev->ctlpath, O_RDONLY);
     70 	if (adev->ctlfd == -1) {
     71 			return -1;
     72 	}
     73 	if (ioctl(adev->ctlfd, AUDIO_GETDEV, &adev->audio_device) == -1) {
     74 		close(adev->ctlfd);
     75 		return -1;
     76 	}
     77 
     78 	for (i = 0; ;i++) {
     79 		memset(&query, 0, sizeof(query));
     80 		query.index = i;
     81 		if (ioctl(adev->ctlfd, AUDIO_QUERYFORMAT, &query) == -1) {
     82 			if (errno == ENODEV) {
     83 				/* QUERYFORMAT not supported. */
     84 				break;
     85 			}
     86 			if (errno == EINVAL)
     87 				break;
     88 			close(adev->ctlfd);
     89 			return -1;
     90 		}
     91 
     92 		f = calloc(1, sizeof(*f));
     93 		f->fmt = query.fmt;
     94 		TAILQ_INSERT_TAIL(&adev->formats, f, next);
     95 	}
     96 
     97 	if (ioctl(adev->ctlfd, AUDIO_GETFORMAT, &adev->hwinfo) == -1) {
     98 		close(adev->ctlfd);
     99 		return -1;
    100 	}
    101 
    102 	return 0;
    103 }
    104 
    105 static int
    106 audiodev_add(const char *pdev, const char *dev, unsigned int unit)
    107 {
    108 	struct audiodev *adev;
    109 
    110 	adev = calloc(1, sizeof(*adev));
    111 	if (adev == NULL)
    112 		return -1;
    113 
    114 	strlcpy(adev->pxname, pdev, sizeof(adev->pxname));
    115 	strlcpy(adev->xname, dev, sizeof(adev->xname));
    116 	snprintf(adev->path, sizeof(adev->path), "/dev/%s", dev);
    117 	snprintf(adev->ctlpath, sizeof(adev->ctlpath), "/dev/audioctl%d", unit);
    118 	adev->unit = unit;
    119 	TAILQ_INIT(&adev->formats);
    120 
    121 	if (audiodev_getinfo(adev) == -1) {
    122 		free(adev);
    123 		return -1;
    124 	}
    125 
    126 #ifdef DEBUG
    127 	printf("DEBUG: [%c] %s(%s): %s\n", adev->defaultdev ? '*' : ' ',
    128 	    adev->path, adev->ctlpath, adev->audio_device.name);
    129 	struct audiofmt *f;
    130 	TAILQ_FOREACH(f, &adev->formats, next) {
    131 		printf("DEBUG: enc%d, %d/%d, %dch\n",
    132 		    f->fmt.encoding,
    133 		    f->fmt.validbits,
    134 		    f->fmt.precision,
    135 		    f->fmt.channels);
    136 	}
    137 #endif
    138 
    139 	TAILQ_INSERT_TAIL(&audiodevlist, adev, next);
    140 
    141 	return 0;
    142 }
    143 
    144 static void
    145 audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit)
    146 {
    147 	audiodev_add(pdev, dev, unit);
    148 }
    149 
    150 int
    151 audiodev_refresh(void)
    152 {
    153 	struct audiodev *adev;
    154 	int fd, error;
    155 
    156 	fd = open(DRVCTLDEV, O_RDONLY);
    157 	if (fd == -1) {
    158 		perror("open " DRVCTLDEV);
    159 		return -1;
    160 	}
    161 
    162 	while (!TAILQ_EMPTY(&audiodevlist)) {
    163 		adev = TAILQ_FIRST(&audiodevlist);
    164 		if (adev->ctlfd != -1)
    165 			close(adev->ctlfd);
    166 		TAILQ_REMOVE(&audiodevlist, adev, next);
    167 		free(adev);
    168 	}
    169 
    170 	error = drvctl_foreach(fd, "audio", audiodev_cb, NULL);
    171 	if (error == -1) {
    172 		perror("drvctl");
    173 		return -1;
    174 	}
    175 
    176 	close(fd);
    177 
    178 	return 0;
    179 }
    180 
    181 unsigned int
    182 audiodev_count(void)
    183 {
    184 	struct audiodev *adev;
    185 	unsigned int n;
    186 
    187 	n = 0;
    188 	TAILQ_FOREACH(adev, &audiodevlist, next)
    189 		++n;
    190 
    191 	return n;
    192 }
    193 
    194 struct audiodev *
    195 audiodev_get(unsigned int i)
    196 {
    197 	struct audiodev *adev;
    198 	unsigned int n;
    199 
    200 	n = 0;
    201 	TAILQ_FOREACH(adev, &audiodevlist, next) {
    202 		if (n == i)
    203 			return adev;
    204 		++n;
    205 	}
    206 
    207 	return NULL;
    208 }
    209 
    210 int
    211 audiodev_set_default(struct audiodev *adev)
    212 {
    213 	char audiopath[PATH_MAX+1];
    214 	char soundpath[PATH_MAX+1];
    215 	char audioctlpath[PATH_MAX+1];
    216 	char mixerpath[PATH_MAX+1];
    217 
    218 	snprintf(audiopath, sizeof(audiopath),
    219 	    _PATH_AUDIO "%u", adev->unit);
    220 	snprintf(soundpath, sizeof(soundpath),
    221 	    _PATH_SOUND "%u", adev->unit);
    222 	snprintf(audioctlpath, sizeof(audioctlpath),
    223 	    _PATH_AUDIOCTL "%u", adev->unit);
    224 	snprintf(mixerpath, sizeof(mixerpath),
    225 	    _PATH_MIXER "%u", adev->unit);
    226 
    227 	unlink(_PATH_AUDIO);
    228 	unlink(_PATH_SOUND);
    229 	unlink(_PATH_AUDIOCTL);
    230 	unlink(_PATH_MIXER);
    231 
    232 	if (symlink(audiopath, _PATH_AUDIO) == -1) {
    233 		perror("symlink " _PATH_AUDIO);
    234 		return -1;
    235 	}
    236 	if (symlink(soundpath, _PATH_SOUND) == -1) {
    237 		perror("symlink " _PATH_SOUND);
    238 		return -1;
    239 	}
    240 	if (symlink(audioctlpath, _PATH_AUDIOCTL) == -1) {
    241 		perror("symlink " _PATH_AUDIOCTL);
    242 		return -1;
    243 	}
    244 	if (symlink(mixerpath, _PATH_MIXER) == -1) {
    245 		perror("symlink " _PATH_MIXER);
    246 		return -1;
    247 	}
    248 
    249 	return 0;
    250 }
    251 
    252 int
    253 audiodev_set_param(struct audiodev *adev, int mode,
    254 	const char *encname, unsigned int prec, unsigned int ch, unsigned int freq)
    255 {
    256 	audio_info_t ai;
    257 	int setmode;
    258 	u_int enc;
    259 
    260 	setmode = 0;
    261 	ai = adev->hwinfo;
    262 
    263 	for (enc = 0; enc < encoding_max; enc++) {
    264 		if (strcmp(encname, encoding_names[enc]) == 0)
    265 			break;
    266 	}
    267 	if (enc >= encoding_max) {
    268 		fprintf(stderr, "unknown encoding name: %s\n", encname);
    269 		errno = EINVAL;
    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 		perror("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 		perror("open");
    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 		perror("ioctl AUDIO_SETINFO");
    326 		goto done;
    327 	}
    328 	if (ioctl(adev->fd, AUDIO_GETINFO, &info) == -1) {
    329 		perror("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 			perror("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 		perror("ioctl AUDIO_DRAIN");
    384 		goto done;
    385 	}
    386 
    387 	rv = 0;
    388 done:
    389 	free(buf);
    390 	return rv;
    391 }
    392