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 47static int audiodev_test_chmask(struct audiodev *, unsigned int, 48 audio_info_t *); 49 50static TAILQ_HEAD(audiodevhead, audiodev) audiodevlist = 51 TAILQ_HEAD_INITIALIZER(audiodevlist); 52 53static int 54audiodev_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 104static int 105audiodev_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 143static void 144audiodev_cb(void *args, const char *pdev, const char *dev, unsigned int unit) 145{ 146 audiodev_add(pdev, dev, unit); 147} 148 149int 150audiodev_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 180unsigned int 181audiodev_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 193struct audiodev * 194audiodev_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 209int 210audiodev_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 251int 252audiodev_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 296int 297audiodev_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; 335done: 336 close(adev->fd); 337 return rv; 338} 339 340static int 341audiodev_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; 381done: 382 free(buf); 383 return rv; 384} 385