1/*- 2 * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23#include <sys/types.h> 24#include <sys/ioctl.h> 25#include <sys/sysctl.h> 26 27#include <errno.h> 28#include <fcntl.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#include <unistd.h> 33 34#include "mixer.h" 35 36#define BASEPATH "/dev/mixer" 37 38static int _mixer_readvol(struct mix_dev *); 39 40/* 41 * Fetch volume from the device. 42 */ 43static int 44_mixer_readvol(struct mix_dev *dev) 45{ 46 int v; 47 48 if (ioctl(dev->parent_mixer->fd, MIXER_READ(dev->devno), &v) < 0) 49 return (-1); 50 dev->vol.left = MIX_VOLNORM(v & 0x00ff); 51 dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff); 52 53 return (0); 54} 55 56/* 57 * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer. 58 * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the 59 * mixer for pcm0, and so on. 60 * 61 * @param name path to mixer device. NULL or "/dev/mixer" for the 62 * the default mixer (i.e `hw.snd.default_unit`). 63 */ 64struct mixer * 65mixer_open(const char *name) 66{ 67 struct mixer *m = NULL; 68 struct mix_dev *dp; 69 const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; 70 int i; 71 72 if ((m = calloc(1, sizeof(struct mixer))) == NULL) 73 goto fail; 74 75 if (name != NULL) { 76 /* `name` does not start with "/dev/mixer". */ 77 if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) { 78 m->unit = -1; 79 } else { 80 /* `name` is "/dev/mixer" so, we'll use the default unit. */ 81 if (strncmp(name, BASEPATH, strlen(name)) == 0) 82 goto dunit; 83 m->unit = strtol(name + strlen(BASEPATH), NULL, 10); 84 } 85 (void)strlcpy(m->name, name, sizeof(m->name)); 86 } else { 87dunit: 88 if ((m->unit = mixer_get_dunit()) < 0) 89 goto fail; 90 (void)snprintf(m->name, sizeof(m->name), BASEPATH "%d", m->unit); 91 } 92 93 if ((m->fd = open(m->name, O_RDWR)) < 0) 94 goto fail; 95 96 m->devmask = m->recmask = m->recsrc = 0; 97 m->f_default = m->unit == mixer_get_dunit(); 98 m->mode = mixer_get_mode(m->unit); 99 /* The unit number _must_ be set before the ioctl. */ 100 m->mi.dev = m->unit; 101 m->ci.card = m->unit; 102 if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) { 103 memset(&m->mi, 0, sizeof(m->mi)); 104 strlcpy(m->mi.name, m->name, sizeof(m->mi.name)); 105 } 106 if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0) 107 memset(&m->ci, 0, sizeof(m->ci)); 108 if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 || 109 ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 || 110 ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 || 111 ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) 112 goto fail; 113 114 TAILQ_INIT(&m->devs); 115 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 116 if (!MIX_ISDEV(m, i)) 117 continue; 118 if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL) 119 goto fail; 120 dp->parent_mixer = m; 121 dp->devno = i; 122 dp->nctl = 0; 123 if (_mixer_readvol(dp) < 0) 124 goto fail; 125 (void)strlcpy(dp->name, names[i], sizeof(dp->name)); 126 TAILQ_INIT(&dp->ctls); 127 TAILQ_INSERT_TAIL(&m->devs, dp, devs); 128 m->ndev++; 129 } 130 131 /* The default device is always "vol". */ 132 m->dev = TAILQ_FIRST(&m->devs); 133 134 return (m); 135fail: 136 if (m != NULL) 137 (void)mixer_close(m); 138 139 return (NULL); 140} 141 142/* 143 * Free resources and close the mixer. 144 */ 145int 146mixer_close(struct mixer *m) 147{ 148 struct mix_dev *dp; 149 int r; 150 151 r = close(m->fd); 152 while (!TAILQ_EMPTY(&m->devs)) { 153 dp = TAILQ_FIRST(&m->devs); 154 TAILQ_REMOVE(&m->devs, dp, devs); 155 while (!TAILQ_EMPTY(&dp->ctls)) 156 (void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls)); 157 free(dp); 158 } 159 free(m); 160 161 return (r); 162} 163 164/* 165 * Select a mixer device. The mixer structure keeps a list of all the devices 166 * the mixer has, but only one can be manipulated at a time -- this is what 167 * the `dev` in the mixer structure field is for. Each time a device is to be 168 * manipulated, `dev` has to point to it first. 169 * 170 * The caller must manually assign the return value to `m->dev`. 171 */ 172struct mix_dev * 173mixer_get_dev(struct mixer *m, int dev) 174{ 175 struct mix_dev *dp; 176 177 if (dev < 0 || dev >= m->ndev) { 178 errno = ERANGE; 179 return (NULL); 180 } 181 TAILQ_FOREACH(dp, &m->devs, devs) { 182 if (dp->devno == dev) 183 return (dp); 184 } 185 errno = EINVAL; 186 187 return (NULL); 188} 189 190/* 191 * Select a device by name. 192 * 193 * @param name device name (e.g vol, pcm, ...) 194 */ 195struct mix_dev * 196mixer_get_dev_byname(struct mixer *m, const char *name) 197{ 198 struct mix_dev *dp; 199 200 TAILQ_FOREACH(dp, &m->devs, devs) { 201 if (!strncmp(dp->name, name, sizeof(dp->name))) 202 return (dp); 203 } 204 errno = EINVAL; 205 206 return (NULL); 207} 208 209/* 210 * Add a mixer control to a device. 211 */ 212int 213mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name, 214 int (*mod)(struct mix_dev *, void *), 215 int (*print)(struct mix_dev *, void *)) 216{ 217 struct mix_dev *dp; 218 mix_ctl_t *ctl, *cp; 219 220 /* XXX: should we accept NULL name? */ 221 if (parent_dev == NULL) { 222 errno = EINVAL; 223 return (-1); 224 } 225 if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL) 226 return (-1); 227 ctl->parent_dev = parent_dev; 228 ctl->id = id; 229 if (name != NULL) 230 (void)strlcpy(ctl->name, name, sizeof(ctl->name)); 231 ctl->mod = mod; 232 ctl->print = print; 233 dp = ctl->parent_dev; 234 /* Make sure the same ID or name doesn't exist already. */ 235 TAILQ_FOREACH(cp, &dp->ctls, ctls) { 236 if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) { 237 errno = EINVAL; 238 return (-1); 239 } 240 } 241 TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls); 242 dp->nctl++; 243 244 return (0); 245} 246 247/* 248 * Same as `mixer_add_ctl`. 249 */ 250int 251mixer_add_ctl_s(mix_ctl_t *ctl) 252{ 253 if (ctl == NULL) 254 return (-1); 255 256 return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name, 257 ctl->mod, ctl->print)); 258} 259 260/* 261 * Remove a mixer control from a device. 262 */ 263int 264mixer_remove_ctl(mix_ctl_t *ctl) 265{ 266 struct mix_dev *p; 267 268 if (ctl == NULL) { 269 errno = EINVAL; 270 return (-1); 271 } 272 p = ctl->parent_dev; 273 if (!TAILQ_EMPTY(&p->ctls)) { 274 TAILQ_REMOVE(&p->ctls, ctl, ctls); 275 free(ctl); 276 } 277 278 return (0); 279} 280 281/* 282 * Get a mixer control by id. 283 */ 284mix_ctl_t * 285mixer_get_ctl(struct mix_dev *d, int id) 286{ 287 mix_ctl_t *cp; 288 289 TAILQ_FOREACH(cp, &d->ctls, ctls) { 290 if (cp->id == id) 291 return (cp); 292 } 293 errno = EINVAL; 294 295 return (NULL); 296} 297 298/* 299 * Get a mixer control by name. 300 */ 301mix_ctl_t * 302mixer_get_ctl_byname(struct mix_dev *d, const char *name) 303{ 304 mix_ctl_t *cp; 305 306 TAILQ_FOREACH(cp, &d->ctls, ctls) { 307 if (!strncmp(cp->name, name, sizeof(cp->name))) 308 return (cp); 309 } 310 errno = EINVAL; 311 312 return (NULL); 313} 314 315/* 316 * Change the mixer's left and right volume. The allowed volume values are 317 * between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires 318 * an integer value between 0 and 100 stored as `lvol | rvol << 8` -- for 319 * that reason, we de-normalize the 32-bit float volume value, before 320 * we pass it to the `ioctl`. 321 * 322 * Volume clumping should be done by the caller. 323 */ 324int 325mixer_set_vol(struct mixer *m, mix_volume_t vol) 326{ 327 int v; 328 329 if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX || 330 vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) { 331 errno = ERANGE; 332 return (-1); 333 } 334 v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8; 335 if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0) 336 return (-1); 337 if (_mixer_readvol(m->dev) < 0) 338 return (-1); 339 340 return (0); 341} 342 343/* 344 * Manipulate a device's mute. 345 * 346 * @param opt MIX_MUTE mute device 347 * MIX_UNMUTE unmute device 348 * MIX_TOGGLEMUTE toggle device's mute 349 */ 350int 351mixer_set_mute(struct mixer *m, int opt) 352{ 353 switch (opt) { 354 case MIX_MUTE: 355 m->mutemask |= (1 << m->dev->devno); 356 break; 357 case MIX_UNMUTE: 358 m->mutemask &= ~(1 << m->dev->devno); 359 break; 360 case MIX_TOGGLEMUTE: 361 m->mutemask ^= (1 << m->dev->devno); 362 break; 363 default: 364 errno = EINVAL; 365 return (-1); 366 } 367 if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0) 368 return (-1); 369 if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0) 370 return (-1); 371 372 return 0; 373} 374 375/* 376 * Modify a recording device. The selected device has to be a recording device, 377 * otherwise the function will fail. 378 * 379 * @param opt MIX_ADDRECSRC add device to recording sources 380 * MIX_REMOVERECSRC remove device from recording sources 381 * MIX_SETRECSRC set device as the only recording source 382 * MIX_TOGGLERECSRC toggle device from recording sources 383 */ 384int 385mixer_mod_recsrc(struct mixer *m, int opt) 386{ 387 if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) { 388 errno = ENODEV; 389 return (-1); 390 } 391 switch (opt) { 392 case MIX_ADDRECSRC: 393 m->recsrc |= (1 << m->dev->devno); 394 break; 395 case MIX_REMOVERECSRC: 396 m->recsrc &= ~(1 << m->dev->devno); 397 break; 398 case MIX_SETRECSRC: 399 m->recsrc = (1 << m->dev->devno); 400 break; 401 case MIX_TOGGLERECSRC: 402 m->recsrc ^= (1 << m->dev->devno); 403 break; 404 default: 405 errno = EINVAL; 406 return (-1); 407 } 408 if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0) 409 return (-1); 410 if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) 411 return (-1); 412 413 return (0); 414} 415 416/* 417 * Get default audio card's number. This is used to open the default mixer 418 * and set the mixer structure's `f_default` flag. 419 */ 420int 421mixer_get_dunit(void) 422{ 423 size_t size; 424 int unit; 425 426 size = sizeof(int); 427 if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0) 428 return (-1); 429 430 return (unit); 431} 432 433/* 434 * Change the default audio card. This is normally _not_ a mixer feature, but 435 * it's useful to have, so the caller can avoid having to manually use 436 * the sysctl API. 437 * 438 * @param unit the audio card number (e.g pcm0, pcm1, ...). 439 */ 440int 441mixer_set_dunit(struct mixer *m, int unit) 442{ 443 size_t size; 444 445 size = sizeof(int); 446 if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0) 447 return (-1); 448 /* XXX: how will other mixers get updated? */ 449 m->f_default = m->unit == unit; 450 451 return (0); 452} 453 454/* 455 * Get sound device mode (none, play, rec, play+rec). Userland programs can 456 * use the MIX_MODE_* flags to determine the mode of the device. 457 */ 458int 459mixer_get_mode(int unit) 460{ 461 char buf[64]; 462 size_t size; 463 unsigned int mode; 464 465 (void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit); 466 size = sizeof(unsigned int); 467 if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0) 468 return (0); 469 470 return (mode); 471} 472 473/* 474 * Get the total number of mixers in the system. 475 */ 476int 477mixer_get_nmixers(void) 478{ 479 struct mixer *m; 480 oss_sysinfo si; 481 482 /* 483 * Open a dummy mixer because we need the `fd` field for the 484 * `ioctl` to work. 485 */ 486 if ((m = mixer_open(NULL)) == NULL) 487 return (-1); 488 if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) { 489 (void)mixer_close(m); 490 return (-1); 491 } 492 (void)mixer_close(m); 493 494 return (si.nummixers); 495} 496 497/* 498 * Get the full path to a mixer device. 499 */ 500int 501mixer_get_path(char *buf, size_t size, int unit) 502{ 503 size_t n; 504 505 if (!(unit == -1 || (unit >= 0 && unit < mixer_get_nmixers()))) { 506 errno = EINVAL; 507 return (-1); 508 } 509 if (unit == -1) 510 n = strlcpy(buf, BASEPATH, size); 511 else 512 n = snprintf(buf, size, BASEPATH "%d", unit); 513 514 if (n >= size) { 515 errno = ENOMEM; 516 return (-1); 517 } 518 519 return (0); 520} 521