diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 4a96505ada66..3aa7cf219d81 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1,2649 +1,2653 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Portions Copyright (c) Ryan Beasley - GSoC 2006 * Copyright (c) 1999 Cameron Grant * Portions Copyright (c) Luigi Rizzo - 1997-99 * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" int report_soft_formats = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, &report_soft_formats, 0, "report software-emulated formats"); int report_soft_matrix = 1; SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_matrix, CTLFLAG_RW, &report_soft_matrix, 0, "report software-emulated channel matrixing"); int chn_latency = CHN_LATENCY_DEFAULT; static int sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else chn_latency = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency, "I", "buffering latency (0=low ... 10=high)"); int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; static int sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_latency_profile; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else chn_latency_profile = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", "buffering latency profile (0=aggressive 1=safe)"); static int chn_timeout = CHN_TIMEOUT; static int sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_timeout; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return err; if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) err = EINVAL; else chn_timeout = val; return err; } SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_timeout, "I", "interrupt timeout (1 - 10) seconds"); static int chn_vpc_autoreset = 1; SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RWTUN, &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; static void chn_vpc_proc(int reset, int db) { struct snddev_info *d; struct pcm_channel *c; int i; for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db); if (reset != 0) chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); } PCM_RELEASE(d); PCM_UNLOCK(d); } } static int sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS) { int err, val; val = chn_vol_0db_pcm; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL) return (err); if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX) return (EINVAL); chn_vol_0db_pcm = val; chn_vpc_proc(0, val); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_vpc_0db, "I", "0db relative level"); static int sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS) { int err, val; val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == 0) return (err); chn_vol_0db_pcm = SND_VOL_0DB_PCM; chn_vpc_proc(1, SND_VOL_0DB_PCM); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", "reset volume on all channels"); static int chn_usefrags = 0; static int chn_syncdelay = -1; SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RWTUN, &chn_usefrags, 0, "prefer setfragments() over setblocksize()"); SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RWTUN, &chn_syncdelay, 0, "append (0-1000) millisecond trailing buffer delay on each sync"); /** * @brief Channel sync group lock * * Clients should acquire this lock @b without holding any channel locks * before touching syncgroups or the main syncgroup list. */ struct mtx snd_pcm_syncgroups_mtx; MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); /** * @brief syncgroups' master list * * Each time a channel syncgroup is created, it's added to this list. This * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. * * See SNDCTL_DSP_SYNCGROUP for more information. */ struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(snd_pcm_syncgroups); static void chn_lockinit(struct pcm_channel *c, int dir) { switch (dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); cv_init(&c->intr_cv, "pcmwr"); break; case PCMDIR_PLAY_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); cv_init(&c->intr_cv, "pcmwrv"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); cv_init(&c->intr_cv, "pcmrd"); break; case PCMDIR_REC_VIRTUAL: c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); cv_init(&c->intr_cv, "pcmrdv"); break; default: panic("%s(): Invalid direction=%d", __func__, dir); break; } cv_init(&c->cv, "pcmchn"); } static void chn_lockdestroy(struct pcm_channel *c) { CHN_LOCKASSERT(c); CHN_BROADCAST(&c->cv); CHN_BROADCAST(&c->intr_cv); cv_destroy(&c->cv); cv_destroy(&c->intr_cv); snd_mtxfree(c->lock); } /** * @brief Determine channel is ready for I/O * * @retval 1 = ready for I/O * @retval 0 = not ready for I/O */ static int chn_polltrigger(struct pcm_channel *c) { struct snd_dbuf *bs = c->bufsoft; u_int delta; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) { if (sndbuf_getprevtotal(bs) < c->lw) delta = c->lw; else delta = sndbuf_gettotal(bs) - sndbuf_getprevtotal(bs); } else { if (c->direction == PCMDIR_PLAY) delta = sndbuf_getfree(bs); else delta = sndbuf_getready(bs); } return ((delta < c->lw) ? 0 : 1); } static void chn_pollreset(struct pcm_channel *c) { CHN_LOCKASSERT(c); sndbuf_updateprevtotal(c->bufsoft); } static void chn_wakeup(struct pcm_channel *c) { struct snd_dbuf *bs; struct pcm_channel *ch; CHN_LOCKASSERT(c); bs = c->bufsoft; if (CHN_EMPTY(c, children.busy)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); CHN_BROADCAST(&c->intr_cv); } else { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); chn_wakeup(ch); CHN_UNLOCK(ch); } } } static int chn_sleep(struct pcm_channel *c, int timeout) { int ret; CHN_LOCKASSERT(c); if (c->flags & CHN_F_DEAD) return (EINVAL); c->sleeping++; ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout); c->sleeping--; return ((c->flags & CHN_F_DEAD) ? EINVAL : ret); } /* * chn_dmaupdate() tracks the status of a dma transfer, * updating pointers. */ static unsigned int chn_dmaupdate(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; unsigned int delta, old, hwptr, amt; KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0")); CHN_LOCKASSERT(c); old = sndbuf_gethwptr(b); hwptr = chn_getptr(c); delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); if (c->direction == PCMDIR_PLAY) { amt = min(delta, sndbuf_getready(b)); amt -= amt % sndbuf_getalign(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = min(delta, sndbuf_getfree(b)); amt -= amt % sndbuf_getalign(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { device_printf(c->dev, "WARNING: %s DMA completion " "too fast/slow ! hwptr=%u, old=%u " "delta=%u amt=%u ready=%u free=%u\n", CHN_DIRSTR(c), hwptr, old, delta, amt, sndbuf_getready(b), sndbuf_getfree(b)); } return delta; } static void chn_wrfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt, want, wasfree; CHN_LOCKASSERT(c); if ((c->flags & CHN_F_MMAP) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); wasfree = sndbuf_getfree(b); want = min(sndbuf_getsize(b), imax(0, sndbuf_xbytes(sndbuf_getsize(bs), bs, b) - sndbuf_getready(b))); amt = min(wasfree, want); if (amt > 0) sndbuf_feed(bs, b, c, c->feeder, amt); /* * Possible xruns. There should be no empty space left in buffer. */ if (sndbuf_getready(b) < want) c->xruns++; if (sndbuf_getfree(b) < wasfree) chn_wakeup(c); } static void chn_wrintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from secondary to primary */ chn_wrfeed(c); /* tell the driver we've updated the primary buffer */ chn_trigger(c, PCMTRIG_EMLDMAWR); } /* * user write routine - uiomove data into secondary buffer, trigger if necessary * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_write(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getfree(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_acquire(bs, NULL, t); } ret = 0; if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) c->flags |= CHN_F_DEAD; } } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { /** * @todo Evaluate whether EAGAIN is truly desirable. * 4Front drivers behave like this, but I'm * not sure if it at all violates the "write * should be allowed to block" model. * * The idea is that, while set with CHN_F_NOTRIGGER, * a channel isn't playing, *but* without this we * end up with "interrupt timeout / channel dead". */ ret = EAGAIN; } else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "play interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } /* * Feed new data from the read buffer. Can be called in the bottom half. */ static void chn_rdfeed(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; unsigned int amt; CHN_LOCKASSERT(c); if (c->flags & CHN_F_MMAP) sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); amt = sndbuf_getfree(bs); if (amt > 0) sndbuf_feed(b, bs, c, c->feeder, amt); amt = sndbuf_getready(b); if (amt > 0) { c->xruns++; sndbuf_dispose(b, NULL, amt); } if (sndbuf_getready(bs) > 0) chn_wakeup(c); } /* read interrupt routine. Must be called with interrupts blocked. */ static void chn_rdintr(struct pcm_channel *c) { CHN_LOCKASSERT(c); /* tell the driver to update the primary buffer if non-dma */ chn_trigger(c, PCMTRIG_EMLDMARD); /* update pointers in primary buffer */ chn_dmaupdate(c); /* ...and feed from primary to secondary */ chn_rdfeed(c); } /* * user read routine - trigger if necessary, uiomove data from secondary buffer * if blocking, sleep, rinse and repeat. * * called externally, so must handle locking */ int chn_read(struct pcm_channel *c, struct uio *buf) { struct snd_dbuf *bs = c->bufsoft; void *off; int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); if (CHN_STOPPED(c) && !(c->flags & CHN_F_NOTRIGGER)) { ret = chn_start(c, 0); if (ret != 0) { c->flags |= CHN_F_DEAD; return (ret); } } ret = 0; timeout = chn_timeout * hz; while (ret == 0 && buf->uio_resid > 0) { sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); sz -= t; sndbuf_dispose(bs, NULL, t); } ret = 0; } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) ret = EAGAIN; else { ret = chn_sleep(c, timeout); if (ret == EAGAIN) { ret = EINVAL; c->flags |= CHN_F_DEAD; device_printf(c->dev, "%s(): %s: " "record interrupt timeout, channel dead\n", __func__, c->name); } else if (ret == ERESTART || ret == EINTR) c->flags |= CHN_F_ABORTING; } } return (ret); } void chn_intr_locked(struct pcm_channel *c) { CHN_LOCKASSERT(c); c->interrupts++; if (c->direction == PCMDIR_PLAY) chn_wrintr(c); else chn_rdintr(c); } void chn_intr(struct pcm_channel *c) { if (CHN_LOCKOWNED(c)) { chn_intr_locked(c); return; } CHN_LOCK(c); chn_intr_locked(c); CHN_UNLOCK(c); } u_int32_t chn_start(struct pcm_channel *c, int force) { u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; int err; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return (EINVAL); err = 0; if (force) { i = 1; j = 0; } else { if (c->direction == PCMDIR_REC) { i = sndbuf_getfree(bs); j = (i > 0) ? 1 : sndbuf_getready(b); } else { if (sndbuf_getfree(bs) == 0) { i = 1; j = 0; } else { struct snd_dbuf *pb; pb = CHN_BUF_PARENT(c, b); i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); j = sndbuf_getalign(pb); } } if (snd_verbose > 3 && CHN_EMPTY(c, children)) device_printf(c->dev, "%s(): %s (%s) threshold " "i=%d j=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", i, j); } if (i >= j) { c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); if (c->flags & CHN_F_CLOSING) c->feedcount = 2; else { c->feedcount = 0; c->interrupts = 0; c->xruns = 0; } if (c->parentchannel == NULL) { if (c->direction == PCMDIR_PLAY) sndbuf_fillsilence_rl(b, sndbuf_xbytes(sndbuf_getsize(bs), bs, b)); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s starting! (%s/%s) " "(ready=%d force=%d i=%d j=%d " "intrtimeout=%u latency=%dms)\n", __func__, (c->flags & CHN_F_HAS_VCHAN) ? "VCHAN PARENT" : "HW", CHN_DIRSTR(c), (c->flags & CHN_F_CLOSING) ? "closing" : "running", sndbuf_getready(b), force, i, j, c->timeout, (sndbuf_getsize(b) * 1000) / (sndbuf_getalign(b) * sndbuf_getspd(b))); } err = chn_trigger(c, PCMTRIG_START); } return (err); } void chn_resetbuf(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; c->blocks = 0; sndbuf_reset(b); sndbuf_reset(bs); } /* * chn_sync waits until the space in the given channel goes above * a threshold. The threshold is checked against fl or rl respectively. * Assume that the condition can become true, do not check here... */ int chn_sync(struct pcm_channel *c, int threshold) { struct snd_dbuf *b, *bs; int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; u_int32_t cflag; CHN_LOCKASSERT(c); if (c->direction != PCMDIR_PLAY) return (EINVAL); bs = c->bufsoft; if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || (threshold < 1 && sndbuf_getready(bs) < 1)) return (0); /* if we haven't yet started and nothing is buffered, else start*/ if (CHN_STOPPED(c)) { if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret != 0) return (ret); } else return (0); } b = CHN_BUF_PARENT(c, c->bufhard); minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); syncdelay = chn_syncdelay; if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); /* * Append (0-1000) millisecond trailing buffer (if needed) * for slower / high latency hardwares (notably USB audio) * to avoid audible truncation. */ if (syncdelay > 0) minflush += (sndbuf_getalign(bs) * sndbuf_getspd(bs) * ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; minflush -= minflush % sndbuf_getalign(bs); if (minflush > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); minflush -= threshold; } resid = sndbuf_getready(bs); residp = resid; blksz = sndbuf_getblksz(b); if (blksz < 1) { device_printf(c->dev, "%s(): WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b)); if (sndbuf_getblkcnt(b) > 0) blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); if (blksz < 1) blksz = 1; } count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; hcount = count; ret = 0; if (snd_verbose > 3) device_printf(c->dev, "%s(): [begin] timeout=%d count=%d " "minflush=%d resid=%d\n", __func__, c->timeout, count, minflush, resid); cflag = c->flags & CHN_F_CLOSING; c->flags |= CHN_F_CLOSING; while (count > 0 && (resid > 0 || minflush > 0)) { ret = chn_sleep(c, c->timeout); if (ret == ERESTART || ret == EINTR) { c->flags |= CHN_F_ABORTING; break; } else if (ret == 0 || ret == EAGAIN) { resid = sndbuf_getready(bs); if (resid == residp) { --count; if (snd_verbose > 3) device_printf(c->dev, "%s(): [stalled] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } else if (resid < residp && count < hcount) { ++count; if (snd_verbose > 3) device_printf(c->dev, "%s((): [resume] timeout=%d " "count=%d hcount=%d " "resid=%d minflush=%d\n", __func__, c->timeout, count, hcount, resid, minflush); } if (minflush > 0 && sndbuf_getfree(bs) > 0) { threshold = min(minflush, sndbuf_getfree(bs)); sndbuf_clear(bs, threshold); sndbuf_acquire(bs, NULL, threshold); resid = sndbuf_getready(bs); minflush -= threshold; } residp = resid; } else break; } c->flags &= ~CHN_F_CLOSING; c->flags |= cflag; if (snd_verbose > 3) device_printf(c->dev, "%s(): timeout=%d count=%d hcount=%d resid=%d residp=%d " "minflush=%d ret=%d\n", __func__, c->timeout, count, hcount, resid, residp, minflush, ret); return (0); } /* called externally, handle locking */ int chn_poll(struct pcm_channel *c, int ev, struct thread *td) { struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); if (!(c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED))) { ret = chn_start(c, 1); if (ret != 0) return (0); } ret = 0; if (chn_polltrigger(c)) { chn_pollreset(c); ret = ev; } else selrecord(td, sndbuf_getsel(bs)); return (ret); } /* * chn_abort terminates a running dma transfer. it may sleep up to 200ms. * it returns the number of bytes that have not been transferred. * * called from: dsp_close, dsp_ioctl, with channel locked */ int chn_abort(struct pcm_channel *c) { int missing = 0; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; } /* * this routine tries to flush the dma transfer. It is called * on a close of a playback channel. * first, if there is data in the buffer, but the dma has not yet * begun, we need to start it. * next, we wait for the play buffer to drain * finally, we stop the dma. * * called from: dsp_close, not valid for record channels. */ int chn_flush(struct pcm_channel *c) { struct snd_dbuf *b = c->bufhard; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); c->flags |= CHN_F_CLOSING; chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); sndbuf_setrun(b, 0); c->flags &= ~CHN_F_CLOSING; return 0; } int snd_fmtvalid(uint32_t fmt, uint32_t *fmtlist) { int i; for (i = 0; fmtlist[i] != 0; i++) { if (fmt == fmtlist[i] || ((fmt & AFMT_PASSTHROUGH) && (AFMT_ENCODING(fmt) & fmtlist[i]))) return (1); } return (0); } static const struct { char *name, *alias1, *alias2; uint32_t afmt; } afmt_tab[] = { { "alaw", NULL, NULL, AFMT_A_LAW }, { "mulaw", NULL, NULL, AFMT_MU_LAW }, { "u8", "8", NULL, AFMT_U8 }, { "s8", NULL, NULL, AFMT_S8 }, #if BYTE_ORDER == LITTLE_ENDIAN { "s16le", "s16", "16", AFMT_S16_LE }, { "s16be", NULL, NULL, AFMT_S16_BE }, #else { "s16le", NULL, NULL, AFMT_S16_LE }, { "s16be", "s16", "16", AFMT_S16_BE }, #endif { "u16le", NULL, NULL, AFMT_U16_LE }, { "u16be", NULL, NULL, AFMT_U16_BE }, { "s24le", NULL, NULL, AFMT_S24_LE }, { "s24be", NULL, NULL, AFMT_S24_BE }, { "u24le", NULL, NULL, AFMT_U24_LE }, { "u24be", NULL, NULL, AFMT_U24_BE }, #if BYTE_ORDER == LITTLE_ENDIAN { "s32le", "s32", "32", AFMT_S32_LE }, { "s32be", NULL, NULL, AFMT_S32_BE }, + { "f32le", "f32", NULL, AFMT_F32_LE }, + { "f32be", NULL, NULL, AFMT_F32_BE }, #else { "s32le", NULL, NULL, AFMT_S32_LE }, { "s32be", "s32", "32", AFMT_S32_BE }, + { "f32le", NULL, NULL, AFMT_F32_LE }, + { "f32be", "f32", NULL, AFMT_F32_BE }, #endif { "u32le", NULL, NULL, AFMT_U32_LE }, { "u32be", NULL, NULL, AFMT_U32_BE }, { "ac3", NULL, NULL, AFMT_AC3 }, { NULL, NULL, NULL, 0 } }; uint32_t snd_str2afmt(const char *req) { int ext; int ch; int i; char b1[8]; char b2[8]; memset(b1, 0, sizeof(b1)); memset(b2, 0, sizeof(b2)); i = sscanf(req, "%5[^:]:%6s", b1, b2); if (i == 1) { if (strlen(req) != strlen(b1)) return (0); strlcpy(b2, "2.0", sizeof(b2)); } else if (i == 2) { if (strlen(req) != (strlen(b1) + 1 + strlen(b2))) return (0); } else return (0); i = sscanf(b2, "%d.%d", &ch, &ext); if (i == 0) { if (strcasecmp(b2, "mono") == 0) { ch = 1; ext = 0; } else if (strcasecmp(b2, "stereo") == 0) { ch = 2; ext = 0; } else if (strcasecmp(b2, "quad") == 0) { ch = 4; ext = 0; } else return (0); } else if (i == 1) { if (ch < 1 || ch > AFMT_CHANNEL_MAX) return (0); ext = 0; } else if (i == 2) { if (ext < 0 || ext > AFMT_EXTCHANNEL_MAX) return (0); if (ch < 1 || (ch + ext) > AFMT_CHANNEL_MAX) return (0); } else return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (strcasecmp(afmt_tab[i].name, b1) != 0) { if (afmt_tab[i].alias1 == NULL) continue; if (strcasecmp(afmt_tab[i].alias1, b1) != 0) { if (afmt_tab[i].alias2 == NULL) continue; if (strcasecmp(afmt_tab[i].alias2, b1) != 0) continue; } } /* found a match */ return (SND_FORMAT(afmt_tab[i].afmt, ch + ext, ext)); } /* not a valid format */ return (0); } uint32_t snd_afmt2str(uint32_t afmt, char *buf, size_t len) { uint32_t enc; uint32_t ext; uint32_t ch; int i; if (buf == NULL || len < AFMTSTR_LEN) return (0); memset(buf, 0, len); enc = AFMT_ENCODING(afmt); ch = AFMT_CHANNEL(afmt); ext = AFMT_EXTCHANNEL(afmt); /* check there is at least one channel */ if (ch <= ext) return (0); for (i = 0; afmt_tab[i].name != NULL; i++) { if (enc != afmt_tab[i].afmt) continue; /* found a match */ snprintf(buf, len, "%s:%d.%d", afmt_tab[i].name, ch - ext, ext); return (SND_FORMAT(enc, ch, ext)); } return (0); } int chn_reset(struct pcm_channel *c, uint32_t fmt, uint32_t spd) { int r; CHN_LOCKASSERT(c); c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; c->timeout = 1; c->xruns = 0; c->flags |= (pcm_getflags(c->dev) & SD_F_BITPERFECT) ? CHN_F_BITPERFECT : 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (r == 0 && fmt != 0 && spd != 0) { r = chn_setparam(c, fmt, spd); fmt = 0; spd = 0; } if (r == 0 && fmt != 0) r = chn_setformat(c, fmt); if (r == 0 && spd != 0) r = chn_setspeed(c, spd); if (r == 0) r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); } return r; } static struct unrhdr * chn_getunr(struct snddev_info *d, int type) { switch (type) { case PCMDIR_PLAY: return (d->p_unr); case PCMDIR_PLAY_VIRTUAL: return (d->vp_unr); case PCMDIR_REC: return (d->r_unr); case PCMDIR_REC_VIRTUAL: return (d->vr_unr); default: __assert_unreachable(); } } char * chn_mkname(char *buf, size_t len, struct pcm_channel *c) { const char *str; KASSERT(buf != NULL && len != 0, ("%s(): bogus buf=%p len=%zu", __func__, buf, len)); switch (c->type) { case PCMDIR_PLAY: str = "play"; break; case PCMDIR_PLAY_VIRTUAL: str = "virtual_play"; break; case PCMDIR_REC: str = "record"; break; case PCMDIR_REC_VIRTUAL: str = "virtual_record"; break; default: __assert_unreachable(); } snprintf(buf, len, "dsp%d.%s.%d", device_get_unit(c->dev), str, c->unit); return (buf); } struct pcm_channel * chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) { struct pcm_channel *c; struct feeder_class *fc; struct snd_dbuf *b, *bs; char buf[CHN_NAMELEN]; int err, i, direction, *vchanrate, *vchanformat; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); switch (dir) { case PCMDIR_PLAY: d->playcount++; /* FALLTHROUGH */ case PCMDIR_PLAY_VIRTUAL: if (dir == PCMDIR_PLAY_VIRTUAL) d->pvchancount++; direction = PCMDIR_PLAY; vchanrate = &d->pvchanrate; vchanformat = &d->pvchanformat; break; case PCMDIR_REC: d->reccount++; /* FALLTHROUGH */ case PCMDIR_REC_VIRTUAL: if (dir == PCMDIR_REC_VIRTUAL) d->rvchancount++; direction = PCMDIR_REC; vchanrate = &d->rvchanrate; vchanformat = &d->rvchanformat; break; default: device_printf(d->dev, "%s(): invalid channel direction: %d\n", __func__, dir); return (NULL); } PCM_UNLOCK(d); b = NULL; bs = NULL; c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); c->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); chn_lockinit(c, dir); CHN_INIT(c, children); CHN_INIT(c, children.busy); c->direction = direction; c->type = dir; c->unit = alloc_unr(chn_getunr(d, c->type)); c->format = SND_FORMAT(AFMT_S16_LE, 2, 0); c->speed = 48000; c->pid = -1; c->latency = -1; c->timeout = 1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); c->parentsnddev = d; c->parentchannel = parent; c->dev = d->dev; c->trigger = PCMTRIG_STOP; strlcpy(c->name, chn_mkname(buf, sizeof(buf), c), sizeof(c->name)); c->matrix = *feeder_matrix_id_map(SND_CHN_MATRIX_1_0); c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; for (i = 0; i < SND_CHN_T_MAX; i++) c->volume[SND_VOL_C_MASTER][i] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; CHN_LOCK(c); chn_vpc_reset(c, SND_VOL_C_PCM, 1); CHN_UNLOCK(c); fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(d->dev, "%s(): failed to get feeder class\n", __func__); goto fail; } if (feeder_add(c, fc, NULL)) { device_printf(d->dev, "%s(): failed to add feeder\n", __func__); goto fail; } b = sndbuf_create(c->dev, c->name, "primary", c); bs = sndbuf_create(c->dev, c->name, "secondary", c); if (b == NULL || bs == NULL) { device_printf(d->dev, "%s(): failed to create %s buffer\n", __func__, b == NULL ? "hardware" : "software"); goto fail; } c->bufhard = b; c->bufsoft = bs; c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, direction); if (c->devinfo == NULL) { device_printf(d->dev, "%s(): CHANNEL_INIT() failed\n", __func__); goto fail; } if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0)) { device_printf(d->dev, "%s(): hardware buffer's size is 0\n", __func__); goto fail; } sndbuf_setfmt(b, c->format); sndbuf_setspd(b, c->speed); sndbuf_setfmt(bs, c->format); sndbuf_setspd(bs, c->speed); sndbuf_setup(bs, NULL, 0); /** * @todo Should this be moved somewhere else? The primary buffer * is allocated by the driver or via DMA map setup, and tmpbuf * seems to only come into existence in sndbuf_resize(). */ if (c->direction == PCMDIR_PLAY) { bs->sl = sndbuf_getmaxsize(bs); bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_WAITOK); } if ((c->flags & CHN_F_VIRTUAL) == 0) { CHN_LOCK(c); err = chn_reset(c, c->format, c->speed); CHN_UNLOCK(c); if (err != 0) goto fail; } PCM_LOCK(d); CHN_INSERT_SORT_ASCEND(d, c, channels.pcm); if ((c->flags & CHN_F_VIRTUAL) == 0) { CHN_INSERT_SORT_ASCEND(d, c, channels.pcm.primary); /* Initialize the *vchanrate/vchanformat parameters. */ *vchanrate = sndbuf_getspd(c->bufsoft); *vchanformat = sndbuf_getfmt(c->bufsoft); } return (c); fail: chn_kill(c); PCM_LOCK(d); return (NULL); } void chn_kill(struct pcm_channel *c) { struct snddev_info *d = c->parentsnddev; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; PCM_BUSYASSERT(c->parentsnddev); PCM_LOCK(d); CHN_REMOVE(d, c, channels.pcm); if ((c->flags & CHN_F_VIRTUAL) == 0) CHN_REMOVE(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: d->playcount--; break; case PCMDIR_PLAY_VIRTUAL: d->pvchancount--; break; case PCMDIR_REC: d->reccount--; break; case PCMDIR_REC_VIRTUAL: d->rvchancount--; break; default: __assert_unreachable(); } PCM_UNLOCK(d); if (CHN_STARTED(c)) { CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); CHN_UNLOCK(c); } free_unr(chn_getunr(d, c->type), c->unit); feeder_remove(c); if (c->devinfo && CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); if (bs) sndbuf_destroy(bs); if (b) sndbuf_destroy(b); CHN_LOCK(c); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); kobj_delete(c->methods, M_DEVBUF); free(c, M_DEVBUF); } void chn_shutdown(struct pcm_channel *c) { CHN_LOCKASSERT(c); chn_wakeup(c); c->flags |= CHN_F_DEAD; } /* release a locked channel and unlock it */ int chn_release(struct pcm_channel *c) { PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); c->flags &= ~CHN_F_BUSY; c->pid = -1; strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); return (0); } int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right, int center) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setvolume_matrix(c, vc, i, left); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setvolume_matrix(c, vc, i, right) << 8; else ret |= chn_setvolume_matrix(c, vc, i, center) << 16; } return (ret); } int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB || (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d", __func__, c, vc, vt, val)); CHN_LOCKASSERT(c); if (val < 0) val = 0; if (val > 100) val = 100; c->volume[vc][vt] = val; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->volume[SND_VOL_C_VAL(vc)][i] = SND_VOL_CALC_VAL(c->volume, vc, i); } else c->volume[SND_VOL_C_VAL(vc)][vt] = SND_VOL_CALC_VAL(c->volume, vc, vt); } return (val); } int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid volume matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->volume[vc][vt]); } int chn_setmute_multi(struct pcm_channel *c, int vc, int mute) { int i, ret; ret = 0; for (i = 0; i < SND_CHN_T_MAX; i++) { if ((1 << i) & SND_CHN_LEFT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute); else if ((1 << i) & SND_CHN_RIGHT_MASK) ret |= chn_setmute_matrix(c, vc, i, mute) << 8; else ret |= chn_setmute_matrix(c, vc, i, mute) << 16; } return (ret); } int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vc == SND_VOL_C_MASTER || (vc & 1)) && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d", __func__, c, vc, vt, mute)); CHN_LOCKASSERT(c); mute = (mute != 0); c->muted[vc][vt] = mute; /* * Do relative calculation here and store it into class + 1 * to ease the job of feeder_volume. */ if (vc == SND_VOL_C_MASTER) { for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; vc += SND_VOL_C_STEP) c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } else if (vc & 1) { if (vt == SND_CHN_T_VOL_0DB) { for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) { c->muted[SND_VOL_C_VAL(vc)][i] = mute; } } else { c->muted[SND_VOL_C_VAL(vc)][vt] = mute; } } return (mute); } int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt) { KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ("%s(): invalid mute matrix c=%p vc=%d vt=%d", __func__, c, vc, vt)); CHN_LOCKASSERT(c); return (c->muted[vc][vt]); } struct pcmchan_matrix * chn_getmatrix(struct pcm_channel *c) { KASSERT(c != NULL, ("%s(): NULL channel", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (NULL); return (&c->matrix); } int chn_setmatrix(struct pcm_channel *c, struct pcmchan_matrix *m) { KASSERT(c != NULL && m != NULL, ("%s(): NULL channel or matrix", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); c->matrix = *m; c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; return (chn_setformat(c, SND_FORMAT(c->format, m->channels, m->ext))); } /* * XXX chn_oss_* exists for the sake of compatibility. */ int chn_oss_getorder(struct pcm_channel *c, unsigned long long *map) { KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); return (feeder_matrix_oss_get_channel_order(&c->matrix, map)); } int chn_oss_setorder(struct pcm_channel *c, unsigned long long *map) { struct pcmchan_matrix m; int ret; KASSERT(c != NULL && map != NULL, ("%s(): NULL channel or map", __func__)); CHN_LOCKASSERT(c); if (!(c->format & AFMT_CONVERTIBLE)) return (EINVAL); m = c->matrix; ret = feeder_matrix_oss_set_channel_order(&m, map); if (ret != 0) return (ret); return (chn_setmatrix(c, &m)); } #define SND_CHN_OSS_FRONT (SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR) #define SND_CHN_OSS_SURR (SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR) #define SND_CHN_OSS_CENTER_LFE (SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF) #define SND_CHN_OSS_REAR (SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR) int chn_oss_getmask(struct pcm_channel *c, uint32_t *retmask) { struct pcmchan_matrix *m; struct pcmchan_caps *caps; uint32_t i, format; KASSERT(c != NULL && retmask != NULL, ("%s(): NULL channel or retmask", __func__)); CHN_LOCKASSERT(c); caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) return (ENODEV); for (i = 0; caps->fmtlist[i] != 0; i++) { format = caps->fmtlist[i]; if (!(format & AFMT_CONVERTIBLE)) { *retmask |= DSP_BIND_SPDIF; continue; } m = CHANNEL_GETMATRIX(c->methods, c->devinfo, format); if (m == NULL) continue; if (m->mask & SND_CHN_OSS_FRONT) *retmask |= DSP_BIND_FRONT; if (m->mask & SND_CHN_OSS_SURR) *retmask |= DSP_BIND_SURR; if (m->mask & SND_CHN_OSS_CENTER_LFE) *retmask |= DSP_BIND_CENTER_LFE; if (m->mask & SND_CHN_OSS_REAR) *retmask |= DSP_BIND_REAR; } /* report software-supported binding mask */ if (!CHN_BITPERFECT(c) && report_soft_matrix) *retmask |= DSP_BIND_FRONT | DSP_BIND_SURR | DSP_BIND_CENTER_LFE | DSP_BIND_REAR; return (0); } void chn_vpc_reset(struct pcm_channel *c, int vc, int force) { int i; KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END, ("%s(): invalid reset c=%p vc=%d", __func__, c, vc)); CHN_LOCKASSERT(c); if (force == 0 && chn_vpc_autoreset == 0) return; for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; i += SND_CHN_T_STEP) CHN_SETVOLUME(c, vc, i, c->volume[vc][SND_CHN_T_VOL_0DB]); } static u_int32_t round_pow2(u_int32_t v) { u_int32_t ret; if (v < 2) v = 2; ret = 0; while (v >> ret) ret++; ret = 1 << (ret - 1); while (ret < v) ret <<= 1; return ret; } static u_int32_t round_blksz(u_int32_t v, int round) { u_int32_t ret, tmp; if (round < 1) round = 1; ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) ret >>= 1; tmp = ret - (ret % round); while (tmp < 16 || tmp < round) { ret <<= 1; tmp = ret - (ret % round); } return ret; } /* * 4Front call it DSP Policy, while we call it "Latency Profile". The idea * is to keep 2nd buffer short so that it doesn't cause long queue during * buffer transfer. * * Latency reference table for 48khz stereo 16bit: (PLAY) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 2 | 64 | 128 | * +---------+------------+-----------+------------+ * | 1 | 4 | 128 | 512 | * +---------+------------+-----------+------------+ * | 2 | 8 | 512 | 4096 | * +---------+------------+-----------+------------+ * | 3 | 16 | 512 | 8192 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Recording need a different reference table. All we care is * gobbling up everything within reasonable buffering threshold. * * Latency reference table for 48khz stereo 16bit: (REC) * * +---------+------------+-----------+------------+ * | Latency | Blockcount | Blocksize | Buffersize | * +---------+------------+-----------+------------+ * | 0 | 512 | 32 | 16384 | * +---------+------------+-----------+------------+ * | 1 | 256 | 64 | 16384 | * +---------+------------+-----------+------------+ * | 2 | 128 | 128 | 16384 | * +---------+------------+-----------+------------+ * | 3 | 64 | 256 | 16384 | * +---------+------------+-----------+------------+ * | 4 | 32 | 512 | 16384 | * +---------+------------+-----------+------------+ * | 5 | 32 | 1024 | 32768 | * +---------+------------+-----------+------------+ * | 6 | 16 | 2048 | 32768 | * +---------+------------+-----------+------------+ * | 7 | 8 | 4096 | 32768 | * +---------+------------+-----------+------------+ * | 8 | 4 | 8192 | 32768 | * +---------+------------+-----------+------------+ * | 9 | 2 | 16384 | 32768 | * +---------+------------+-----------+------------+ * | 10 | 2 | 32768 | 65536 | * +---------+------------+-----------+------------+ * * Calculations for other data rate are entirely based on these reference * tables. For normal operation, Latency 5 seems give the best, well * balanced performance for typical workload. Anything below 5 will * eat up CPU to keep up with increasing context switches because of * shorter buffer space and usually require the application to handle it * aggressively through possibly real time programming technique. * */ #define CHN_LATENCY_PBLKCNT_REF \ {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_PBUFSZ_REF \ {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_RBLKCNT_REF \ {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} #define CHN_LATENCY_RBUFSZ_REF \ {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} #define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ static int chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, u_int32_t max, int *rblksz, int *rblkcnt) { static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBLKCNT_REF; static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_PBUFSZ_REF; static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBLKCNT_REF; static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = CHN_LATENCY_RBUFSZ_REF; u_int32_t bufsz; int lprofile, blksz, blkcnt; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || bps < 1 || datarate < 1 || !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { if (rblksz != NULL) *rblksz = CHN_2NDBUFMAXSIZE >> 1; if (rblkcnt != NULL) *rblkcnt = 2; printf("%s(): FAILED dir=%d latency=%d bps=%d " "datarate=%u max=%u\n", __func__, dir, latency, bps, datarate, max); return CHN_2NDBUFMAXSIZE; } lprofile = chn_latency_profile; if (dir == PCMDIR_PLAY) { blkcnt = pblkcnts[lprofile][latency]; bufsz = pbufszs[lprofile][latency]; } else { blkcnt = rblkcnts[lprofile][latency]; bufsz = rbufszs[lprofile][latency]; } bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate)); if (bufsz > max) bufsz = max; blksz = round_blksz(bufsz >> blkcnt, bps); if (rblksz != NULL) *rblksz = blksz; if (rblkcnt != NULL) *rblkcnt = 1 << blkcnt; return blksz << blkcnt; } static int chn_resizebuf(struct pcm_channel *c, int latency, int blkcnt, int blksz) { struct snd_dbuf *b, *bs, *pb; int sblksz, sblkcnt, hblksz, hblkcnt, limit = 0, nsblksz, nsblkcnt; int ret; CHN_LOCKASSERT(c); if ((c->flags & (CHN_F_MMAP | CHN_F_TRIGGERED)) || !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) return EINVAL; if (latency == -1) { c->latency = -1; latency = chn_latency; } else if (latency == -2) { latency = c->latency; if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) latency = chn_latency; } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) return EINVAL; else { c->latency = latency; } bs = c->bufsoft; b = c->bufhard; if (!(blksz == 0 || blkcnt == -1) && (blksz < 16 || blksz < sndbuf_getalign(bs) || blkcnt < 2 || (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) return EINVAL; chn_calclatency(c->direction, latency, sndbuf_getalign(bs), sndbuf_getalign(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, &sblksz, &sblkcnt); if (blksz == 0 || blkcnt == -1) { if (blkcnt == -1) c->flags &= ~CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { blksz = sndbuf_getblksz(bs); blkcnt = sndbuf_getblkcnt(bs); } } else c->flags |= CHN_F_HAS_SIZE; if (c->flags & CHN_F_HAS_SIZE) { /* * The application has requested their own blksz/blkcnt. * Just obey with it, and let them toast alone. We can * clamp it to the nearest latency profile, but that would * defeat the purpose of having custom control. The least * we can do is round it to the nearest ^2 and align it. */ sblksz = round_blksz(blksz, sndbuf_getalign(bs)); sblkcnt = round_pow2(blkcnt); } if (c->parentchannel != NULL) { pb = c->parentchannel->bufsoft; CHN_UNLOCK(c); CHN_LOCK(c->parentchannel); chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); CHN_UNLOCK(c->parentchannel); CHN_LOCK(c); if (c->direction == PCMDIR_PLAY) { limit = (pb != NULL) ? sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; } else { limit = (pb != NULL) ? sndbuf_xbytes(sndbuf_getblksz(pb), pb, bs) * 2 : 0; } } else { hblkcnt = 2; if (c->flags & CHN_F_HAS_SIZE) { hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), sndbuf_getalign(b)); hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); } else chn_calclatency(c->direction, latency, sndbuf_getalign(b), sndbuf_getalign(b) * sndbuf_getspd(b), CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); if ((hblksz << 1) > sndbuf_getmaxsize(b)) hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, sndbuf_getalign(b)); while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { if (hblkcnt < 4) hblksz >>= 1; else hblkcnt >>= 1; } hblksz -= hblksz % sndbuf_getalign(b); CHN_UNLOCK(c); if (chn_usefrags == 0 || CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, hblksz, hblkcnt) != 0) sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, hblksz)); CHN_LOCK(c); if (!CHN_EMPTY(c, children)) { nsblksz = round_blksz( sndbuf_xbytes(sndbuf_getblksz(b), b, bs), sndbuf_getalign(bs)); nsblkcnt = sndbuf_getblkcnt(b); if (c->direction == PCMDIR_PLAY) { do { nsblkcnt--; } while (nsblkcnt >= 2 && nsblksz * nsblkcnt >= sblksz * sblkcnt); nsblkcnt++; } sblksz = nsblksz; sblkcnt = nsblkcnt; limit = 0; } else limit = sndbuf_xbytes(sndbuf_getblksz(b), b, bs) * 2; } if (limit > CHN_2NDBUFMAXSIZE) limit = CHN_2NDBUFMAXSIZE; while ((sblksz * sblkcnt) < limit) sblkcnt <<= 1; while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { if (sblkcnt < 4) sblksz >>= 1; else sblkcnt >>= 1; } sblksz -= sblksz % sndbuf_getalign(bs); if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || sndbuf_getsize(bs) != (sblkcnt * sblksz)) { ret = sndbuf_remalloc(bs, sblkcnt, sblksz); if (ret != 0) { device_printf(c->dev, "%s(): Failed: %d %d\n", __func__, sblkcnt, sblksz); return ret; } } /* * Interrupt timeout */ c->timeout = ((u_int64_t)hz * sndbuf_getsize(bs)) / ((u_int64_t)sndbuf_getspd(bs) * sndbuf_getalign(bs)); if (c->parentchannel != NULL) c->timeout = min(c->timeout, c->parentchannel->timeout); if (c->timeout < 1) c->timeout = 1; /* * OSSv4 docs: "By default OSS will set the low water level equal * to the fragment size which is optimal in most cases." */ c->lw = sndbuf_getblksz(bs); chn_resetbuf(c); if (snd_verbose > 3) device_printf(c->dev, "%s(): %s (%s) timeout=%u " "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", __func__, CHN_DIRSTR(c), (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", c->timeout, sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), limit); return 0; } int chn_setlatency(struct pcm_channel *c, int latency) { CHN_LOCKASSERT(c); /* Destroy blksz/blkcnt, enforce latency profile. */ return chn_resizebuf(c, latency, -1, 0); } int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) { CHN_LOCKASSERT(c); /* Destroy latency profile, enforce blksz/blkcnt */ return chn_resizebuf(c, -1, blkcnt, blksz); } int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed) { struct pcmchan_caps *caps; uint32_t hwspeed, delta; int ret; CHN_LOCKASSERT(c); if (speed < 1 || format == 0 || CHN_STARTED(c)) return (EINVAL); c->format = format; c->speed = speed; caps = chn_getcaps(c); hwspeed = speed; RANGE(hwspeed, caps->minspeed, caps->maxspeed); sndbuf_setspd(c->bufhard, CHANNEL_SETSPEED(c->methods, c->devinfo, hwspeed)); hwspeed = sndbuf_getspd(c->bufhard); delta = (hwspeed > speed) ? (hwspeed - speed) : (speed - hwspeed); if (delta <= feeder_rate_round) c->speed = hwspeed; ret = feeder_chain(c); if (ret == 0) ret = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(c->bufhard)); if (ret == 0) ret = chn_resizebuf(c, -2, 0, 0); return (ret); } int chn_setspeed(struct pcm_channel *c, uint32_t speed) { uint32_t oldformat, oldspeed; int ret; oldformat = c->format; oldspeed = c->speed; if (c->speed == speed) return (0); ret = chn_setparam(c, c->format, speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Setting speed %d failed, " "falling back to %d\n", __func__, speed, oldspeed); chn_setparam(c, oldformat, oldspeed); } return (ret); } int chn_setformat(struct pcm_channel *c, uint32_t format) { uint32_t oldformat, oldspeed; int ret; /* XXX force stereo */ if ((format & AFMT_PASSTHROUGH) && AFMT_CHANNEL(format) < 2) { format = SND_FORMAT(format, AFMT_PASSTHROUGH_CHANNEL, AFMT_PASSTHROUGH_EXTCHANNEL); } oldformat = c->format; oldspeed = c->speed; if (c->format == format) return (0); ret = chn_setparam(c, format, c->speed); if (ret != 0) { if (snd_verbose > 3) device_printf(c->dev, "%s(): Format change 0x%08x failed, " "falling back to 0x%08x\n", __func__, format, oldformat); chn_setparam(c, oldformat, oldspeed); } return (ret); } void chn_syncstate(struct pcm_channel *c) { struct snddev_info *d; struct snd_mixer *m; d = (c != NULL) ? c->parentsnddev : NULL; m = (d != NULL && d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; if (d == NULL || m == NULL) return; CHN_LOCKASSERT(c); if (c->feederflags & (1 << FEEDER_VOLUME)) { uint32_t parent; int vol, pvol, left, right, center; if (c->direction == PCMDIR_PLAY && (d->flags & SD_F_SOFTPCMVOL)) { /* CHN_UNLOCK(c); */ vol = mix_get(m, SOUND_MIXER_PCM); parent = mix_getparent(m, SOUND_MIXER_PCM); if (parent != SOUND_MIXER_NONE) pvol = mix_get(m, parent); else pvol = 100 | (100 << 8); /* CHN_LOCK(c); */ } else { vol = 100 | (100 << 8); pvol = vol; } if (vol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read pcm " "default value\n"); vol = 100 | (100 << 8); } if (pvol == -1) { device_printf(c->dev, "Soft PCM Volume: Failed to read parent " "default value\n"); pvol = 100 | (100 << 8); } left = ((vol & 0x7f) * (pvol & 0x7f)) / 100; right = (((vol >> 8) & 0x7f) * ((pvol >> 8) & 0x7f)) / 100; center = (left + right) >> 1; chn_setvolume_multi(c, SND_VOL_C_MASTER, left, right, center); } if (c->feederflags & (1 << FEEDER_EQ)) { struct pcm_feeder *f; int treble, bass, state; /* CHN_UNLOCK(c); */ treble = mix_get(m, SOUND_MIXER_TREBLE); bass = mix_get(m, SOUND_MIXER_BASS); /* CHN_LOCK(c); */ if (treble == -1) treble = 50; else treble = ((treble & 0x7f) + ((treble >> 8) & 0x7f)) >> 1; if (bass == -1) bass = 50; else bass = ((bass & 0x7f) + ((bass >> 8) & 0x7f)) >> 1; f = feeder_find(c, FEEDER_EQ); if (f != NULL) { if (FEEDER_SET(f, FEEDEQ_TREBLE, treble) != 0) device_printf(c->dev, "EQ: Failed to set treble -- %d\n", treble); if (FEEDER_SET(f, FEEDEQ_BASS, bass) != 0) device_printf(c->dev, "EQ: Failed to set bass -- %d\n", bass); if (FEEDER_SET(f, FEEDEQ_PREAMP, d->eqpreamp) != 0) device_printf(c->dev, "EQ: Failed to set preamp -- %d\n", d->eqpreamp); if (d->flags & SD_F_EQ_BYPASSED) state = FEEDEQ_BYPASS; else if (d->flags & SD_F_EQ_ENABLED) state = FEEDEQ_ENABLE; else state = FEEDEQ_DISABLE; if (FEEDER_SET(f, FEEDEQ_STATE, state) != 0) device_printf(c->dev, "EQ: Failed to set state -- %d\n", state); } } } int chn_trigger(struct pcm_channel *c, int go) { struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go)) return (CHANNEL_TRIGGER(c->methods, c->devinfo, go)); if (go == c->trigger) return (0); if (snd_verbose > 3) { device_printf(c->dev, "%s() %s: calling go=0x%08x , " "prev=0x%08x\n", __func__, c->name, go, c->trigger); } c->trigger = go; ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); if (ret != 0) return (ret); CHN_UNLOCK(c); PCM_LOCK(d); CHN_LOCK(c); /* * Do nothing if another thread set a different trigger while we had * dropped the mutex. */ if (go != c->trigger) { PCM_UNLOCK(d); return (0); } /* * Use the SAFE variants to prevent inserting/removing an already * existing/missing element. */ switch (go) { case PCMTRIG_START: CHN_INSERT_HEAD_SAFE(d, c, channels.pcm.busy); PCM_UNLOCK(d); chn_syncstate(c); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: CHN_REMOVE(d, c, channels.pcm.busy); PCM_UNLOCK(d); break; default: PCM_UNLOCK(d); break; } return (0); } /** * @brief Queries sound driver for sample-aligned hardware buffer pointer index * * This function obtains the hardware pointer location, then aligns it to * the current bytes-per-sample value before returning. (E.g., a channel * running in 16 bit stereo mode would require 4 bytes per sample, so a * hwptr value ranging from 32-35 would be returned as 32.) * * @param c PCM channel context * @returns sample-aligned hardware buffer pointer index */ int chn_getptr(struct pcm_channel *c) { int hwptr; CHN_LOCKASSERT(c); hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; return (hwptr - (hwptr % sndbuf_getalign(c->bufhard))); } struct pcmchan_caps * chn_getcaps(struct pcm_channel *c) { CHN_LOCKASSERT(c); return CHANNEL_GETCAPS(c->methods, c->devinfo); } u_int32_t chn_getformats(struct pcm_channel *c) { u_int32_t *fmtlist, fmts; int i; fmtlist = chn_getcaps(c)->fmtlist; fmts = 0; for (i = 0; fmtlist[i]; i++) fmts |= fmtlist[i]; /* report software-supported formats */ if (!CHN_BITPERFECT(c) && report_soft_formats) fmts |= AFMT_CONVERTIBLE; return (AFMT_ENCODING(fmts)); } int chn_notify(struct pcm_channel *c, u_int32_t flags) { struct pcm_channel *ch; struct pcmchan_caps *caps; uint32_t bestformat, bestspeed, besthwformat, *vchanformat, *vchanrate; uint32_t vpflags; int dirty, err, run, nrun; CHN_LOCKASSERT(c); if (CHN_EMPTY(c, children)) return (0); err = 0; /* * If the hwchan is running, we can't change its rate, format or * blocksize */ run = (CHN_STARTED(c)) ? 1 : 0; if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_FORMAT) { /* * XXX I'll make good use of this someday. * However this is currently being superseded by * the availability of CHN_F_VCHAN_DYNAMIC. */ } if (flags & CHN_N_VOLUME) { /* * XXX I'll make good use of this someday, though * soft volume control is currently pretty much * integrated. */ } if (flags & CHN_N_BLOCKSIZE) { /* * Set to default latency profile */ chn_setlatency(c, chn_latency); } if ((flags & CHN_N_TRIGGER) && !(c->flags & CHN_F_VCHAN_DYNAMIC)) { nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) err = chn_start(c, 1); if (!nrun && run) chn_abort(c); flags &= ~CHN_N_TRIGGER; } if (flags & CHN_N_TRIGGER) { if (c->direction == PCMDIR_PLAY) { vchanformat = &c->parentsnddev->pvchanformat; vchanrate = &c->parentsnddev->pvchanrate; } else { vchanformat = &c->parentsnddev->rvchanformat; vchanrate = &c->parentsnddev->rvchanrate; } /* Dynamic Virtual Channel */ if (!(c->flags & CHN_F_VCHAN_ADAPTIVE)) { bestformat = *vchanformat; bestspeed = *vchanrate; } else { bestformat = 0; bestspeed = 0; } besthwformat = 0; nrun = 0; caps = chn_getcaps(c); dirty = 0; vpflags = 0; CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if ((ch->format & AFMT_PASSTHROUGH) && snd_fmtvalid(ch->format, caps->fmtlist)) { bestformat = ch->format; bestspeed = ch->speed; CHN_UNLOCK(ch); vpflags = CHN_F_PASSTHROUGH; nrun++; break; } if ((ch->flags & CHN_F_EXCLUSIVE) && vpflags == 0) { if (c->flags & CHN_F_VCHAN_ADAPTIVE) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (besthwformat != 0) bestformat = besthwformat; } CHN_UNLOCK(ch); vpflags = CHN_F_EXCLUSIVE; nrun++; continue; } if (!(c->flags & CHN_F_VCHAN_ADAPTIVE) || vpflags != 0) { CHN_UNLOCK(ch); nrun++; continue; } if (ch->speed > bestspeed) { bestspeed = ch->speed; RANGE(bestspeed, caps->minspeed, caps->maxspeed); } besthwformat = snd_fmtbest(ch->format, caps->fmtlist); if (!(besthwformat & AFMT_VCHAN)) { CHN_UNLOCK(ch); nrun++; continue; } if (AFMT_CHANNEL(besthwformat) > AFMT_CHANNEL(bestformat)) bestformat = besthwformat; else if (AFMT_CHANNEL(besthwformat) == AFMT_CHANNEL(bestformat) && AFMT_BIT(besthwformat) > AFMT_BIT(bestformat)) bestformat = besthwformat; CHN_UNLOCK(ch); nrun++; } if (bestformat == 0) bestformat = c->format; if (bestspeed == 0) bestspeed = c->speed; if (bestformat != c->format || bestspeed != c->speed) dirty = 1; c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); c->flags |= vpflags; if (nrun && !run) { if (dirty) { bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); } if (err == 0 && dirty) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { if (dirty) c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (nrun && run && dirty) { chn_abort(c); bestspeed = CHANNEL_SETSPEED(c->methods, c->devinfo, bestspeed); err = chn_reset(c, bestformat, bestspeed); if (err == 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } } if (err == 0) { c->flags |= CHN_F_DIRTY; err = chn_start(c, 1); } } if (err == 0 && !(bestformat & AFMT_PASSTHROUGH) && (bestformat & AFMT_VCHAN)) { *vchanformat = bestformat; *vchanrate = bestspeed; } if (!nrun && run) { c->flags &= ~(CHN_F_PASSTHROUGH | CHN_F_EXCLUSIVE); bestformat = *vchanformat; bestspeed = *vchanrate; chn_abort(c); if (c->format != bestformat || c->speed != bestspeed) chn_reset(c, bestformat, bestspeed); } } return (err); } /** * @brief Fetch array of supported discrete sample rates * * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for * detailed information. * * @note If the operation isn't supported, this function will just return 0 * (no rates in the array), and *rates will be set to NULL. Callers * should examine rates @b only if this function returns non-zero. * * @param c pcm channel to examine * @param rates pointer to array of integers; rate table will be recorded here * * @return number of rates in the array pointed to be @c rates */ int chn_getrates(struct pcm_channel *c, int **rates) { KASSERT(rates != NULL, ("rates is null")); CHN_LOCKASSERT(c); return CHANNEL_GETRATES(c->methods, c->devinfo, rates); } /** * @brief Remove channel from a sync group, if there is one. * * This function is initially intended for the following conditions: * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) * - Closing a device. (A channel can't be destroyed if it's still in use.) * * @note Before calling this function, the syncgroup list mutex must be * held. (Consider pcm_channel::sm protected by the SG list mutex * whether @c c is locked or not.) * * @param c channel device to be started or closed * @returns If this channel was the only member of a group, the group ID * is returned to the caller so that the caller can release it * via free_unr() after giving up the syncgroup lock. Else it * returns 0. */ int chn_syncdestroy(struct pcm_channel *c) { struct pcmchan_syncmember *sm; struct pcmchan_syncgroup *sg; int sg_id; sg_id = 0; PCM_SG_LOCKASSERT(MA_OWNED); if (c->sm != NULL) { sm = c->sm; sg = sm->parent; c->sm = NULL; KASSERT(sg != NULL, ("syncmember has null parent")); SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); free(sm, M_DEVBUF); if (SLIST_EMPTY(&sg->members)) { SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); sg_id = sg->id; free(sg, M_DEVBUF); } } return sg_id; } #ifdef OSSV4_EXPERIMENT int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) { CHN_LOCKASSERT(c); return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); } #endif diff --git a/sys/dev/sound/pcm/feeder_chain.c b/sys/dev/sound/pcm/feeder_chain.c index 1c4ddca6cdd5..56de32441de7 100644 --- a/sys/dev/sound/pcm/feeder_chain.c +++ b/sys/dev/sound/pcm/feeder_chain.c @@ -1,867 +1,869 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008-2009 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include "feeder_if.h" /* chain state */ struct feeder_chain_state { uint32_t afmt; /* audio format */ uint32_t rate; /* sampling rate */ struct pcmchan_matrix *matrix; /* matrix map */ }; /* * chain descriptor that will be passed around from the beginning until the * end of chain process. */ struct feeder_chain_desc { struct feeder_chain_state origin; /* original state */ struct feeder_chain_state current; /* current state */ struct feeder_chain_state target; /* target state */ struct pcm_feederdesc desc; /* feeder descriptor */ uint32_t afmt_ne; /* preferred native endian */ int mode; /* chain mode */ int use_eq; /* need EQ? */ int use_matrix; /* need channel matrixing? */ int use_volume; /* need softpcmvol? */ int dummy; /* dummy passthrough */ int expensive; /* possibly expensive */ }; #define FEEDER_CHAIN_LEAN 0 #define FEEDER_CHAIN_16 1 #define FEEDER_CHAIN_32 2 #define FEEDER_CHAIN_MULTI 3 #define FEEDER_CHAIN_FULLMULTI 4 #define FEEDER_CHAIN_LAST 5 #if defined(SND_FEEDER_FULL_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_FULLMULTI #elif defined(SND_FEEDER_MULTIFORMAT) #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_MULTI #else #define FEEDER_CHAIN_DEFAULT FEEDER_CHAIN_LEAN #endif /* * List of preferred formats that might be required during * processing. It will be decided through snd_fmtbest(). */ /* 'Lean' mode, signed 16 or 32 bit native endian. */ static uint32_t feeder_chain_formats_lean[] = { AFMT_S16_NE, AFMT_S32_NE, 0 }; /* Force everything to signed 16 bit native endian. */ static uint32_t feeder_chain_formats_16[] = { AFMT_S16_NE, 0 }; /* Force everything to signed 32 bit native endian. */ static uint32_t feeder_chain_formats_32[] = { AFMT_S32_NE, 0 }; /* Multiple choices, all except 8 bit. */ static uint32_t feeder_chain_formats_multi[] = { AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, + AFMT_F32_LE, AFMT_F32_BE, 0 }; /* Everything that is convertible. */ static uint32_t feeder_chain_formats_fullmulti[] = { AFMT_S8, AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE, AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE, AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE, + AFMT_F32_LE, AFMT_F32_BE, 0 }; static uint32_t *feeder_chain_formats[FEEDER_CHAIN_LAST] = { [FEEDER_CHAIN_LEAN] = feeder_chain_formats_lean, [FEEDER_CHAIN_16] = feeder_chain_formats_16, [FEEDER_CHAIN_32] = feeder_chain_formats_32, [FEEDER_CHAIN_MULTI] = feeder_chain_formats_multi, [FEEDER_CHAIN_FULLMULTI] = feeder_chain_formats_fullmulti }; static int feeder_chain_mode = FEEDER_CHAIN_DEFAULT; #if defined(_KERNEL) && defined(SND_DEBUG) && defined(SND_FEEDER_FULL_MULTIFORMAT) SYSCTL_INT(_hw_snd, OID_AUTO, feeder_chain_mode, CTLFLAG_RWTUN, &feeder_chain_mode, 0, "feeder chain mode " "(0=lean, 1=16bit, 2=32bit, 3=multiformat, 4=fullmultiformat)"); #endif /* * feeder_build_format(): Chain any format converter. */ static int feeder_build_format(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_FORMAT; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_format\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = cdesc->target.afmt; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_format\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_FORMAT; cdesc->current.afmt = cdesc->target.afmt; return (0); } /* * feeder_build_formatne(): Chain format converter that suite best for native * endian format. */ static int feeder_build_formatne(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_chain_state otarget; int ret; if (cdesc->afmt_ne == 0 || AFMT_ENCODING(cdesc->current.afmt) == cdesc->afmt_ne) return (0); otarget = cdesc->target; cdesc->target = cdesc->current; cdesc->target.afmt = SND_FORMAT(cdesc->afmt_ne, cdesc->current.matrix->channels, cdesc->current.matrix->ext); ret = feeder_build_format(c, cdesc); if (ret != 0) return (ret); cdesc->target = otarget; return (0); } /* * feeder_build_rate(): Chain sample rate converter. */ static int feeder_build_rate(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_RATE; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_rate\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_rate\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set the * conversion quality to the lowest possible (should be fastest) since * listener won't be hearing anything. Theoretically we can just * disable it, but that will cause weird runtime behaviour: * application appear to play something that is either too fast or too * slow. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDRATE_QUALITY, 0); if (ret != 0) { device_printf(c->dev, "%s(): can't set resampling quality\n", __func__); return (ret); } } ret = FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set source rate\n", __func__); return (ret); } ret = FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set destination rate\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_RATE; cdesc->current.rate = cdesc->target.rate; return (0); } /* * feeder_build_matrix(): Chain channel matrixing converter. */ static int feeder_build_matrix(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_MATRIX; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_matrix\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = SND_FORMAT(cdesc->current.afmt, cdesc->target.matrix->channels, cdesc->target.matrix->ext); ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_matrix\n", __func__); return (ret); } f = c->feeder; ret = feeder_matrix_setup(f, cdesc->current.matrix, cdesc->target.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_matrix_setup() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MATRIX; cdesc->current.afmt = desc->out; cdesc->current.matrix = cdesc->target.matrix; cdesc->use_matrix = 0; return (0); } /* * feeder_build_volume(): Chain soft volume. */ static int feeder_build_volume(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_VOLUME; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_volume\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_volume\n", __func__); return (ret); } f = c->feeder; /* * If in 'dummy' mode (possibly due to passthrough mode), set BYPASS * mode since listener won't be hearing anything. Theoretically we can * just disable it, but that will confuse volume per channel mixer. */ if (cdesc->dummy != 0) { ret = FEEDER_SET(f, FEEDVOLUME_STATE, FEEDVOLUME_BYPASS); if (ret != 0) { device_printf(c->dev, "%s(): can't set volume bypass\n", __func__); return (ret); } } ret = feeder_volume_apply_matrix(f, cdesc->current.matrix); if (ret != 0) { device_printf(c->dev, "%s(): feeder_volume_apply_matrix() failed\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_VOLUME; cdesc->use_volume = 0; return (0); } /* * feeder_build_eq(): Chain parametric software equalizer. */ static int feeder_build_eq(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feeder *f; struct pcm_feederdesc *desc; int ret; ret = feeder_build_formatne(c, cdesc); if (ret != 0) return (ret); desc = &(cdesc->desc); desc->type = FEEDER_EQ; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_eq\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_eq\n", __func__); return (ret); } f = c->feeder; ret = FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate); if (ret != 0) { device_printf(c->dev, "%s(): can't set rate on feeder_eq\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_EQ; cdesc->use_eq = 0; return (0); } /* * feeder_build_root(): Chain root feeder, the top, father of all. */ static int feeder_build_root(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; int ret; fc = feeder_getclass(NULL); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_root\n", __func__); return (ENOTSUP); } ret = feeder_add(c, fc, NULL); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_root\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_ROOT; c->feeder->desc->in = cdesc->current.afmt; c->feeder->desc->out = cdesc->current.afmt; return (0); } /* * feeder_build_mixer(): Chain software mixer for virtual channels. */ static int feeder_build_mixer(struct pcm_channel *c, struct feeder_chain_desc *cdesc) { struct feeder_class *fc; struct pcm_feederdesc *desc; int ret; desc = &(cdesc->desc); desc->type = FEEDER_MIXER; desc->in = 0; desc->out = 0; desc->flags = 0; fc = feeder_getclass(desc); if (fc == NULL) { device_printf(c->dev, "%s(): can't find feeder_mixer\n", __func__); return (ENOTSUP); } desc->in = cdesc->current.afmt; desc->out = desc->in; ret = feeder_add(c, fc, desc); if (ret != 0) { device_printf(c->dev, "%s(): can't add feeder_mixer\n", __func__); return (ret); } c->feederflags |= 1 << FEEDER_MIXER; return (0); } /* Macrosses to ease our job doing stuffs later. */ #define FEEDER_BW(c, t) ((c)->t.matrix->channels * (c)->t.rate) #define FEEDRATE_UP(c) ((c)->target.rate > (c)->current.rate) #define FEEDRATE_DOWN(c) ((c)->target.rate < (c)->current.rate) #define FEEDRATE_REQUIRED(c) (FEEDRATE_UP(c) || FEEDRATE_DOWN(c)) #define FEEDMATRIX_UP(c) ((c)->target.matrix->channels > \ (c)->current.matrix->channels) #define FEEDMATRIX_DOWN(c) ((c)->target.matrix->channels < \ (c)->current.matrix->channels) #define FEEDMATRIX_REQUIRED(c) (FEEDMATRIX_UP(c) || \ FEEDMATRIX_DOWN(c) || (c)->use_matrix != 0) #define FEEDFORMAT_REQUIRED(c) (AFMT_ENCODING((c)->current.afmt) != \ AFMT_ENCODING((c)->target.afmt)) #define FEEDVOLUME_REQUIRED(c) ((c)->use_volume != 0) #define FEEDEQ_VALIDRATE(c, t) (feeder_eq_validrate((c)->t.rate) != 0) #define FEEDEQ_ECONOMY(c) (FEEDER_BW(c, current) < FEEDER_BW(c, target)) #define FEEDEQ_REQUIRED(c) ((c)->use_eq != 0 && \ FEEDEQ_VALIDRATE(c, current)) #define FEEDFORMAT_NE_REQUIRED(c) \ ((c)->afmt_ne != AFMT_S32_NE && \ (((c)->mode == FEEDER_CHAIN_16 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S16_NE) || \ ((c)->mode == FEEDER_CHAIN_32 && \ AFMT_ENCODING((c)->current.afmt) != AFMT_S32_NE) || \ (c)->mode == FEEDER_CHAIN_FULLMULTI || \ ((c)->mode == FEEDER_CHAIN_MULTI && \ ((c)->current.afmt & AFMT_8BIT)) || \ ((c)->mode == FEEDER_CHAIN_LEAN && \ !((c)->current.afmt & (AFMT_S16_NE | AFMT_S32_NE))))) static void feeder_default_matrix(struct pcmchan_matrix *m, uint32_t fmt, int id) { int x; memset(m, 0, sizeof(*m)); m->id = id; m->channels = AFMT_CHANNEL(fmt); m->ext = AFMT_EXTCHANNEL(fmt); for (x = 0; x != SND_CHN_T_MAX; x++) m->offset[x] = -1; } int feeder_chain(struct pcm_channel *c) { struct snddev_info *d; struct pcmchan_caps *caps; struct feeder_chain_desc cdesc; struct pcmchan_matrix *hwmatrix, *softmatrix; uint32_t hwfmt, softfmt; int ret; CHN_LOCKASSERT(c); /* Remove everything first. */ feeder_remove(c); KASSERT(c->feeder == NULL, ("feeder chain not empty")); /* clear and populate chain descriptor. */ bzero(&cdesc, sizeof(cdesc)); switch (feeder_chain_mode) { case FEEDER_CHAIN_LEAN: case FEEDER_CHAIN_16: case FEEDER_CHAIN_32: #if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_MULTI: #endif #if defined(SND_FEEDER_FULL_MULTIFORMAT) case FEEDER_CHAIN_FULLMULTI: #endif break; default: feeder_chain_mode = FEEDER_CHAIN_DEFAULT; break; } cdesc.mode = feeder_chain_mode; cdesc.expensive = 1; /* XXX faster.. */ #define VCHAN_PASSTHROUGH(c) (((c)->flags & (CHN_F_VIRTUAL | \ CHN_F_PASSTHROUGH)) == \ (CHN_F_VIRTUAL | CHN_F_PASSTHROUGH)) /* Get the best possible hardware format. */ if (VCHAN_PASSTHROUGH(c)) hwfmt = c->parentchannel->format; else { caps = chn_getcaps(c); if (caps == NULL || caps->fmtlist == NULL) { device_printf(c->dev, "%s(): failed to get channel caps\n", __func__); return (ENODEV); } if ((c->format & AFMT_PASSTHROUGH) && !snd_fmtvalid(c->format, caps->fmtlist)) return (ENODEV); hwfmt = snd_fmtbest(c->format, caps->fmtlist); if (hwfmt == 0 || !snd_fmtvalid(hwfmt, caps->fmtlist)) { device_printf(c->dev, "%s(): invalid hardware format 0x%08x\n", __func__, hwfmt); { int i; for (i = 0; caps->fmtlist[i] != 0; i++) printf("0x%08x\n", caps->fmtlist[i]); printf("Req: 0x%08x\n", c->format); } return (ENODEV); } } /* * The 'hardware' possibly have different interpretation of channel * matrixing, so get it first ..... */ hwmatrix = CHANNEL_GETMATRIX(c->methods, c->devinfo, hwfmt); if (hwmatrix == NULL) { /* setup a default matrix */ hwmatrix = &c->matrix_scratch; feeder_default_matrix(hwmatrix, hwfmt, SND_CHN_MATRIX_UNKNOWN); } /* ..... and rebuild hwfmt. */ hwfmt = SND_FORMAT(hwfmt, hwmatrix->channels, hwmatrix->ext); /* Reset and rebuild default channel format/matrix map. */ softfmt = c->format; softmatrix = &c->matrix; if (softmatrix->channels != AFMT_CHANNEL(softfmt) || softmatrix->ext != AFMT_EXTCHANNEL(softfmt)) { softmatrix = feeder_matrix_format_map(softfmt); if (softmatrix == NULL) { /* setup a default matrix */ softmatrix = &c->matrix; feeder_default_matrix(softmatrix, softfmt, SND_CHN_MATRIX_PCMCHANNEL); } else { c->matrix = *softmatrix; c->matrix.id = SND_CHN_MATRIX_PCMCHANNEL; } } softfmt = SND_FORMAT(softfmt, softmatrix->channels, softmatrix->ext); if (softfmt != c->format) device_printf(c->dev, "%s(): WARNING: %s Soft format 0x%08x -> 0x%08x\n", __func__, CHN_DIRSTR(c), c->format, softfmt); /* * PLAY and REC are opposite. */ if (c->direction == PCMDIR_PLAY) { cdesc.origin.afmt = softfmt; cdesc.origin.matrix = softmatrix; cdesc.origin.rate = c->speed; cdesc.target.afmt = hwfmt; cdesc.target.matrix = hwmatrix; cdesc.target.rate = sndbuf_getspd(c->bufhard); } else { cdesc.origin.afmt = hwfmt; cdesc.origin.matrix = hwmatrix; cdesc.origin.rate = sndbuf_getspd(c->bufhard); cdesc.target.afmt = softfmt; cdesc.target.matrix = softmatrix; cdesc.target.rate = c->speed; } d = c->parentsnddev; /* * If channel is in bitperfect or passthrough mode, make it appear * that 'origin' and 'target' identical, skipping mostly chain * procedures. */ if (CHN_BITPERFECT(c) || (c->format & AFMT_PASSTHROUGH)) { if (c->direction == PCMDIR_PLAY) cdesc.origin = cdesc.target; else cdesc.target = cdesc.origin; c->format = cdesc.target.afmt; c->speed = cdesc.target.rate; } else { /* * Bail out early if we do not support either of those formats. */ if ((cdesc.origin.afmt & AFMT_CONVERTIBLE) == 0 || (cdesc.target.afmt & AFMT_CONVERTIBLE) == 0) { device_printf(c->dev, "%s(): unsupported formats: in=0x%08x, out=0x%08x\n", __func__, cdesc.origin.afmt, cdesc.target.afmt); return (ENODEV); } /* hwfmt is not convertible, so 'dummy' it. */ if (hwfmt & AFMT_PASSTHROUGH) cdesc.dummy = 1; if ((softfmt & AFMT_CONVERTIBLE) && (((d->flags & SD_F_VPC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_volume = 1; if (feeder_matrix_compare(cdesc.origin.matrix, cdesc.target.matrix) != 0) cdesc.use_matrix = 1; /* Soft EQ only applicable for PLAY. */ if (cdesc.dummy == 0 && c->direction == PCMDIR_PLAY && (d->flags & SD_F_EQ) && (((d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_HAS_VCHAN)) || (!(d->flags & SD_F_EQ_PC) && !(c->flags & CHN_F_VIRTUAL)))) cdesc.use_eq = 1; if (FEEDFORMAT_NE_REQUIRED(&cdesc)) { cdesc.afmt_ne = (cdesc.dummy != 0) ? snd_fmtbest(AFMT_ENCODING(softfmt), feeder_chain_formats[cdesc.mode]) : snd_fmtbest(AFMT_ENCODING(cdesc.target.afmt), feeder_chain_formats[cdesc.mode]); if (cdesc.afmt_ne == 0) { device_printf(c->dev, "%s(): snd_fmtbest failed!\n", __func__); cdesc.afmt_ne = (((cdesc.dummy != 0) ? softfmt : cdesc.target.afmt) & (AFMT_24BIT | AFMT_32BIT)) ? AFMT_S32_NE : AFMT_S16_NE; } } } cdesc.current = cdesc.origin; /* Build everything. */ c->feederflags = 0; #define FEEDER_BUILD(t) do { \ ret = feeder_build_##t(c, &cdesc); \ if (ret != 0) \ return (ret); \ } while (0) if (!(c->flags & CHN_F_HAS_VCHAN) || c->direction == PCMDIR_REC) FEEDER_BUILD(root); else if (c->direction == PCMDIR_PLAY && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); else return (ENOTSUP); /* * The basic idea is: The smaller the bandwidth, the cheaper the * conversion process, with following constraints:- * * 1) Almost all feeders work best in 16/32 native endian. * 2) Try to avoid 8bit feeders due to poor dynamic range. * 3) Avoid volume, format, matrix and rate in BITPERFECT or * PASSTHROUGH mode. * 4) Try putting volume before EQ or rate. Should help to * avoid/reduce possible clipping. * 5) EQ require specific, valid rate, unless it allow sloppy * conversion. */ if (FEEDMATRIX_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || (cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc)))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else if (FEEDMATRIX_DOWN(&cdesc)) { FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDEQ_REQUIRED(&cdesc) && (!FEEDEQ_VALIDRATE(&cdesc, target) || FEEDEQ_ECONOMY(&cdesc))) FEEDER_BUILD(eq); if (FEEDRATE_REQUIRED(&cdesc)) FEEDER_BUILD(rate); if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } else { if (FEEDRATE_DOWN(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) { if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); FEEDER_BUILD(eq); } FEEDER_BUILD(rate); } if (FEEDMATRIX_REQUIRED(&cdesc)) FEEDER_BUILD(matrix); if (FEEDVOLUME_REQUIRED(&cdesc)) FEEDER_BUILD(volume); if (FEEDRATE_UP(&cdesc)) { if (FEEDEQ_REQUIRED(&cdesc) && !FEEDEQ_VALIDRATE(&cdesc, target)) FEEDER_BUILD(eq); FEEDER_BUILD(rate); } if (FEEDEQ_REQUIRED(&cdesc)) FEEDER_BUILD(eq); } if (FEEDFORMAT_REQUIRED(&cdesc)) FEEDER_BUILD(format); if (c->direction == PCMDIR_REC && (c->flags & CHN_F_HAS_VCHAN)) FEEDER_BUILD(mixer); sndbuf_setfmt(c->bufsoft, c->format); sndbuf_setspd(c->bufsoft, c->speed); sndbuf_setfmt(c->bufhard, hwfmt); chn_syncstate(c); return (0); } diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c index 1610211ff5f5..9ea454cdee1e 100644 --- a/sys/dev/sound/pcm/feeder_rate.c +++ b/sys/dev/sound/pcm/feeder_rate.c @@ -1,1716 +1,1720 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * feeder_rate: (Codename: Z Resampler), which means any effort to create * future replacement for this resampler are simply absurd unless * the world decide to add new alphabet after Z. * * FreeBSD bandlimited sinc interpolator, technically based on * "Digital Audio Resampling" by Julius O. Smith III * - http://ccrma.stanford.edu/~jos/resample/ * * The Good: * + all out fixed point integer operations, no soft-float or anything like * that. * + classic polyphase converters with high quality coefficient's polynomial * interpolators. * + fast, faster, or the fastest of its kind. * + compile time configurable. * + etc etc.. * * The Bad: * - The z, z_, and Z_ . Due to mental block (or maybe just 0x7a69), I * couldn't think of anything simpler than that (feeder_rate_xxx is just * too long). Expect possible clashes with other zitizens (any?). */ #ifdef _KERNEL #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" #define SND_USE_FXDIV #include "snd_fxdiv_gen.h" #endif #include "feeder_rate_gen.h" #if !defined(_KERNEL) && defined(SND_DIAGNOSTIC) #undef Z_DIAGNOSTIC #define Z_DIAGNOSTIC 1 #elif defined(_KERNEL) #undef Z_DIAGNOSTIC #endif #ifndef Z_QUALITY_DEFAULT #define Z_QUALITY_DEFAULT Z_QUALITY_LINEAR #endif #define Z_RESERVOIR 2048 #define Z_RESERVOIR_MAX 131072 #define Z_SINC_MAX 0x3fffff #define Z_SINC_DOWNMAX 48 /* 384000 / 8000 */ #ifdef _KERNEL #define Z_POLYPHASE_MAX 183040 /* 286 taps, 640 phases */ #else #define Z_POLYPHASE_MAX 1464320 /* 286 taps, 5120 phases */ #endif #define Z_RATE_DEFAULT 48000 #define Z_RATE_MIN FEEDRATE_RATEMIN #define Z_RATE_MAX FEEDRATE_RATEMAX #define Z_ROUNDHZ FEEDRATE_ROUNDHZ #define Z_ROUNDHZ_MIN FEEDRATE_ROUNDHZ_MIN #define Z_ROUNDHZ_MAX FEEDRATE_ROUNDHZ_MAX #define Z_RATE_SRC FEEDRATE_SRC #define Z_RATE_DST FEEDRATE_DST #define Z_RATE_QUALITY FEEDRATE_QUALITY #define Z_RATE_CHANNELS FEEDRATE_CHANNELS #define Z_PARANOID 1 #define Z_MULTIFORMAT 1 #ifdef _KERNEL #undef Z_USE_ALPHADRIFT #define Z_USE_ALPHADRIFT 1 #endif #define Z_FACTOR_MIN 1 #define Z_FACTOR_MAX Z_MASK #define Z_FACTOR_SAFE(v) (!((v) < Z_FACTOR_MIN || (v) > Z_FACTOR_MAX)) struct z_info; typedef void (*z_resampler_t)(struct z_info *, uint8_t *); struct z_info { int32_t rsrc, rdst; /* original source / destination rates */ int32_t src, dst; /* rounded source / destination rates */ int32_t channels; /* total channels */ int32_t bps; /* bytes-per-sample */ int32_t quality; /* resampling quality */ int32_t z_gx, z_gy; /* interpolation / decimation ratio */ int32_t z_alpha; /* output sample time phase / drift */ uint8_t *z_delay; /* FIR delay line / linear buffer */ int32_t *z_coeff; /* FIR coefficients */ int32_t *z_dcoeff; /* FIR coefficients differences */ int32_t *z_pcoeff; /* FIR polyphase coefficients */ int32_t z_scale; /* output scaling */ int32_t z_dx; /* input sample drift increment */ int32_t z_dy; /* output sample drift increment */ #ifdef Z_USE_ALPHADRIFT int32_t z_alphadrift; /* alpha drift rate */ int32_t z_startdrift; /* buffer start position drift rate */ #endif int32_t z_mask; /* delay line full length mask */ int32_t z_size; /* half width of FIR taps */ int32_t z_full; /* full size of delay line */ int32_t z_alloc; /* largest allocated full size of delay line */ int32_t z_start; /* buffer processing start position */ int32_t z_pos; /* current position for the next feed */ #ifdef Z_DIAGNOSTIC uint32_t z_cycle; /* output cycle, purely for statistical */ #endif int32_t z_maxfeed; /* maximum feed to avoid 32bit overflow */ z_resampler_t z_resample; }; int feeder_rate_min = Z_RATE_MIN; int feeder_rate_max = Z_RATE_MAX; int feeder_rate_round = Z_ROUNDHZ; int feeder_rate_quality = Z_QUALITY_DEFAULT; static int feeder_rate_polyphase_max = Z_POLYPHASE_MAX; #ifdef _KERNEL static char feeder_rate_presets[] = FEEDER_RATE_PRESETS; SYSCTL_STRING(_hw_snd, OID_AUTO, feeder_rate_presets, CTLFLAG_RD, &feeder_rate_presets, 0, "compile-time rate presets"); SYSCTL_INT(_hw_snd, OID_AUTO, feeder_rate_polyphase_max, CTLFLAG_RWTUN, &feeder_rate_polyphase_max, 0, "maximum allowable polyphase entries"); static int sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS) { int err, val; val = feeder_rate_min; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == feeder_rate_min) return (err); if (!(Z_FACTOR_SAFE(val) && val < feeder_rate_max)) return (EINVAL); feeder_rate_min = val; return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I", "minimum allowable rate"); static int sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS) { int err, val; val = feeder_rate_max; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == feeder_rate_max) return (err); if (!(Z_FACTOR_SAFE(val) && val > feeder_rate_min)) return (EINVAL); feeder_rate_max = val; return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I", "maximum allowable rate"); static int sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) { int err, val; val = feeder_rate_round; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == feeder_rate_round) return (err); if (val < Z_ROUNDHZ_MIN || val > Z_ROUNDHZ_MAX) return (EINVAL); feeder_rate_round = val - (val % Z_ROUNDHZ); return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", "sample rate converter rounding threshold"); static int sysctl_hw_snd_feeder_rate_quality(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c; struct pcm_feeder *f; int i, err, val; val = feeder_rate_quality; err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0 || req->newptr == NULL || val == feeder_rate_quality) return (err); if (val < Z_QUALITY_MIN || val > Z_QUALITY_MAX) return (EINVAL); feeder_rate_quality = val; /* * Traverse all available channels on each device and try to * set resampler quality if and only if it is exist as * part of feeder chains and the channel is idle. */ for (i = 0; pcm_devclass != NULL && i < devclass_get_maxunit(pcm_devclass); i++) { d = devclass_get_softc(pcm_devclass, i); if (!PCM_REGISTERED(d)) continue; PCM_LOCK(d); PCM_WAIT(d); PCM_ACQUIRE(d); CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); f = feeder_find(c, FEEDER_RATE); if (f == NULL || f->data == NULL || CHN_STARTED(c)) { CHN_UNLOCK(c); continue; } (void)FEEDER_SET(f, FEEDRATE_QUALITY, val); CHN_UNLOCK(c); } PCM_RELEASE(d); PCM_UNLOCK(d); } return (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_quality, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_feeder_rate_quality, "I", "sample rate converter quality ("__XSTRING(Z_QUALITY_MIN)"=low .. " __XSTRING(Z_QUALITY_MAX)"=high)"); #endif /* _KERNEL */ /* * Resampler type. */ #define Z_IS_ZOH(i) ((i)->quality == Z_QUALITY_ZOH) #define Z_IS_LINEAR(i) ((i)->quality == Z_QUALITY_LINEAR) #define Z_IS_SINC(i) ((i)->quality > Z_QUALITY_LINEAR) /* * Macroses for accurate sample time drift calculations. * * gy2gx : given the amount of output, return the _exact_ required amount of * input. * gx2gy : given the amount of input, return the _maximum_ amount of output * that will be generated. * drift : given the amount of input and output, return the elapsed * sample-time. */ #define _Z_GCAST(x) ((uint64_t)(x)) #if defined(__i386__) /* * This is where i386 being beaten to a pulp. Fortunately this function is * rarely being called and if it is, it will decide the best (hopefully) * fastest way to do the division. If we can ensure that everything is dword * aligned, letting the compiler to call udivdi3 to do the division can be * faster compared to this. * * amd64 is the clear winner here, no question about it. */ static __inline uint32_t Z_DIV(uint64_t v, uint32_t d) { uint32_t hi, lo, quo, rem; hi = v >> 32; lo = v & 0xffffffff; /* * As much as we can, try to avoid long division like a plague. */ if (hi == 0) quo = lo / d; else __asm("divl %2" : "=a" (quo), "=d" (rem) : "r" (d), "0" (lo), "1" (hi)); return (quo); } #else #define Z_DIV(x, y) ((x) / (y)) #endif #define _Z_GY2GX(i, a, v) \ Z_DIV(((_Z_GCAST((i)->z_gx) * (v)) + ((i)->z_gy - (a) - 1)), \ (i)->z_gy) #define _Z_GX2GY(i, a, v) \ Z_DIV(((_Z_GCAST((i)->z_gy) * (v)) + (a)), (i)->z_gx) #define _Z_DRIFT(i, x, y) \ ((_Z_GCAST((i)->z_gy) * (x)) - (_Z_GCAST((i)->z_gx) * (y))) #define z_gy2gx(i, v) _Z_GY2GX(i, (i)->z_alpha, v) #define z_gx2gy(i, v) _Z_GX2GY(i, (i)->z_alpha, v) #define z_drift(i, x, y) _Z_DRIFT(i, x, y) /* * Macroses for SINC coefficients table manipulations.. whatever. */ #define Z_SINC_COEFF_IDX(i) ((i)->quality - Z_QUALITY_LINEAR - 1) #define Z_SINC_LEN(i) \ ((int32_t)(((uint64_t)z_coeff_tab[Z_SINC_COEFF_IDX(i)].len << \ Z_SHIFT) / (i)->z_dy)) #define Z_SINC_BASE_LEN(i) \ ((z_coeff_tab[Z_SINC_COEFF_IDX(i)].len - 1) >> (Z_DRIFT_SHIFT - 1)) /* * Macroses for linear delay buffer operations. Alignment is not * really necessary since we're not using true circular buffer, but it * will help us guard against possible trespasser. To be honest, * the linear block operations does not need guarding at all due to * accurate drifting! */ #define z_align(i, v) ((v) & (i)->z_mask) #define z_next(i, o, v) z_align(i, (o) + (v)) #define z_prev(i, o, v) z_align(i, (o) - (v)) #define z_fetched(i) (z_align(i, (i)->z_pos - (i)->z_start) - 1) #define z_free(i) ((i)->z_full - (i)->z_pos) /* * Macroses for Bla Bla .. :) */ #define z_copy(src, dst, sz) (void)memcpy(dst, src, sz) #define z_feed(...) FEEDER_FEED(__VA_ARGS__) static __inline uint32_t z_min(uint32_t x, uint32_t y) { return ((x < y) ? x : y); } static int32_t z_gcd(int32_t x, int32_t y) { int32_t w; while (y != 0) { w = x % y; x = y; y = w; } return (x); } static int32_t z_roundpow2(int32_t v) { int32_t i; i = 1; /* * Let it overflow at will.. */ while (i > 0 && i < v) i <<= 1; return (i); } /* * Zero Order Hold, the worst of the worst, an insult against quality, * but super fast. */ static void z_feed_zoh(struct z_info *info, uint8_t *dst) { uint32_t cnt; uint8_t *src; cnt = info->channels * info->bps; src = info->z_delay + (info->z_start * cnt); /* * This is a bit faster than doing bcopy() since we're dealing * with possible unaligned samples. */ do { *dst++ = *src++; } while (--cnt != 0); } /* * Linear Interpolation. This at least sounds better (perceptually) and fast, * but without any proper filtering which means aliasing still exist and * could become worst with a right sample. Interpolation centered within * Z_LINEAR_ONE between the present and previous sample and everything is * done with simple 32bit scaling arithmetic. */ #define Z_DECLARE_LINEAR(SIGN, BIT, ENDIAN) \ static void \ z_feed_linear_##SIGN##BIT##ENDIAN(struct z_info *info, uint8_t *dst) \ { \ int32_t z; \ intpcm_t x, y; \ uint32_t ch; \ uint8_t *sx, *sy; \ \ z = ((uint32_t)info->z_alpha * info->z_dx) >> Z_LINEAR_UNSHIFT; \ \ sx = info->z_delay + (info->z_start * info->channels * \ PCM_##BIT##_BPS); \ sy = sx - (info->channels * PCM_##BIT##_BPS); \ \ ch = info->channels; \ \ do { \ x = pcm_sample_read(sx, AFMT_##SIGN##BIT##_##ENDIAN); \ y = pcm_sample_read(sy, AFMT_##SIGN##BIT##_##ENDIAN); \ x = Z_LINEAR_INTERPOLATE_##BIT(z, x, y); \ pcm_sample_write(dst, x, AFMT_##SIGN##BIT##_##ENDIAN); \ sx += PCM_##BIT##_BPS; \ sy += PCM_##BIT##_BPS; \ dst += PCM_##BIT##_BPS; \ } while (--ch != 0); \ } /* * Userland clipping diagnostic check, not enabled in kernel compilation. * While doing sinc interpolation, unrealistic samples like full scale sine * wav will clip, but for other things this will not make any noise at all. * Everybody should learn how to normalized perceived loudness of their own * music/sounds/samples (hint: ReplayGain). */ #ifdef Z_DIAGNOSTIC #define Z_CLIP_CHECK(v, BIT) do { \ if ((v) > PCM_S##BIT##_MAX) { \ fprintf(stderr, "Overflow: v=%jd, max=%jd\n", \ (intmax_t)(v), (intmax_t)PCM_S##BIT##_MAX); \ } else if ((v) < PCM_S##BIT##_MIN) { \ fprintf(stderr, "Underflow: v=%jd, min=%jd\n", \ (intmax_t)(v), (intmax_t)PCM_S##BIT##_MIN); \ } \ } while (0) #else #define Z_CLIP_CHECK(...) #endif /* * Sine Cardinal (SINC) Interpolation. Scaling is done in 64 bit, so * there's no point to hold the plate any longer. All samples will be * shifted to a full 32 bit, scaled and restored during write for * maximum dynamic range (only for downsampling). */ #define _Z_SINC_ACCUMULATE(SIGN, BIT, ENDIAN, adv) \ c += z >> Z_SHIFT; \ z &= Z_MASK; \ coeff = Z_COEFF_INTERPOLATE(z, z_coeff[c], z_dcoeff[c]); \ x = pcm_sample_read(p, AFMT_##SIGN##BIT##_##ENDIAN); \ v += Z_NORM_##BIT((intpcm64_t)x * coeff); \ z += info->z_dy; \ p adv##= info->channels * PCM_##BIT##_BPS /* * XXX GCC4 optimization is such a !@#$%, need manual unrolling. */ #if defined(__GNUC__) && __GNUC__ >= 4 #define Z_SINC_ACCUMULATE(...) do { \ _Z_SINC_ACCUMULATE(__VA_ARGS__); \ _Z_SINC_ACCUMULATE(__VA_ARGS__); \ } while (0) #define Z_SINC_ACCUMULATE_DECR 2 #else #define Z_SINC_ACCUMULATE(...) do { \ _Z_SINC_ACCUMULATE(__VA_ARGS__); \ } while (0) #define Z_SINC_ACCUMULATE_DECR 1 #endif #define Z_DECLARE_SINC(SIGN, BIT, ENDIAN) \ static void \ z_feed_sinc_##SIGN##BIT##ENDIAN(struct z_info *info, uint8_t *dst) \ { \ intpcm64_t v; \ intpcm_t x; \ uint8_t *p; \ int32_t coeff, z, *z_coeff, *z_dcoeff; \ uint32_t c, center, ch, i; \ \ z_coeff = info->z_coeff; \ z_dcoeff = info->z_dcoeff; \ center = z_prev(info, info->z_start, info->z_size); \ ch = info->channels * PCM_##BIT##_BPS; \ dst += ch; \ \ do { \ dst -= PCM_##BIT##_BPS; \ ch -= PCM_##BIT##_BPS; \ v = 0; \ z = info->z_alpha * info->z_dx; \ c = 0; \ p = info->z_delay + (z_next(info, center, 1) * \ info->channels * PCM_##BIT##_BPS) + ch; \ for (i = info->z_size; i != 0; i -= Z_SINC_ACCUMULATE_DECR) \ Z_SINC_ACCUMULATE(SIGN, BIT, ENDIAN, +); \ z = info->z_dy - (info->z_alpha * info->z_dx); \ c = 0; \ p = info->z_delay + (center * info->channels * \ PCM_##BIT##_BPS) + ch; \ for (i = info->z_size; i != 0; i -= Z_SINC_ACCUMULATE_DECR) \ Z_SINC_ACCUMULATE(SIGN, BIT, ENDIAN, -); \ if (info->z_scale != Z_ONE) \ v = Z_SCALE_##BIT(v, info->z_scale); \ else \ v >>= Z_COEFF_SHIFT - Z_GUARD_BIT_##BIT; \ Z_CLIP_CHECK(v, BIT); \ pcm_sample_write(dst, pcm_clamp(v, AFMT_##SIGN##BIT##_##ENDIAN),\ AFMT_##SIGN##BIT##_##ENDIAN); \ } while (ch != 0); \ } #define Z_DECLARE_SINC_POLYPHASE(SIGN, BIT, ENDIAN) \ static void \ z_feed_sinc_polyphase_##SIGN##BIT##ENDIAN(struct z_info *info, uint8_t *dst) \ { \ intpcm64_t v; \ intpcm_t x; \ uint8_t *p; \ int32_t ch, i, start, *z_pcoeff; \ \ ch = info->channels * PCM_##BIT##_BPS; \ dst += ch; \ start = z_prev(info, info->z_start, (info->z_size << 1) - 1) * ch; \ \ do { \ dst -= PCM_##BIT##_BPS; \ ch -= PCM_##BIT##_BPS; \ v = 0; \ p = info->z_delay + start + ch; \ z_pcoeff = info->z_pcoeff + \ ((info->z_alpha * info->z_size) << 1); \ for (i = info->z_size; i != 0; i--) { \ x = pcm_sample_read(p, AFMT_##SIGN##BIT##_##ENDIAN); \ v += Z_NORM_##BIT((intpcm64_t)x * *z_pcoeff); \ z_pcoeff++; \ p += info->channels * PCM_##BIT##_BPS; \ x = pcm_sample_read(p, AFMT_##SIGN##BIT##_##ENDIAN); \ v += Z_NORM_##BIT((intpcm64_t)x * *z_pcoeff); \ z_pcoeff++; \ p += info->channels * PCM_##BIT##_BPS; \ } \ if (info->z_scale != Z_ONE) \ v = Z_SCALE_##BIT(v, info->z_scale); \ else \ v >>= Z_COEFF_SHIFT - Z_GUARD_BIT_##BIT; \ Z_CLIP_CHECK(v, BIT); \ pcm_sample_write(dst, pcm_clamp(v, AFMT_##SIGN##BIT##_##ENDIAN),\ AFMT_##SIGN##BIT##_##ENDIAN); \ } while (ch != 0); \ } #define Z_DECLARE(SIGN, BIT, ENDIAN) \ Z_DECLARE_LINEAR(SIGN, BIT, ENDIAN) \ Z_DECLARE_SINC(SIGN, BIT, ENDIAN) \ Z_DECLARE_SINC_POLYPHASE(SIGN, BIT, ENDIAN) #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) Z_DECLARE(S, 16, LE) Z_DECLARE(S, 32, LE) #endif #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) Z_DECLARE(S, 16, BE) Z_DECLARE(S, 32, BE) #endif #ifdef SND_FEEDER_MULTIFORMAT Z_DECLARE(S, 8, NE) Z_DECLARE(S, 24, LE) Z_DECLARE(S, 24, BE) Z_DECLARE(U, 8, NE) Z_DECLARE(U, 16, LE) Z_DECLARE(U, 24, LE) Z_DECLARE(U, 32, LE) Z_DECLARE(U, 16, BE) Z_DECLARE(U, 24, BE) Z_DECLARE(U, 32, BE) +Z_DECLARE(F, 32, LE) +Z_DECLARE(F, 32, BE) #endif enum { Z_RESAMPLER_ZOH, Z_RESAMPLER_LINEAR, Z_RESAMPLER_SINC, Z_RESAMPLER_SINC_POLYPHASE, Z_RESAMPLER_LAST }; #define Z_RESAMPLER_IDX(i) \ (Z_IS_SINC(i) ? Z_RESAMPLER_SINC : (i)->quality) #define Z_RESAMPLER_ENTRY(SIGN, BIT, ENDIAN) \ { \ AFMT_##SIGN##BIT##_##ENDIAN, \ { \ [Z_RESAMPLER_ZOH] = z_feed_zoh, \ [Z_RESAMPLER_LINEAR] = z_feed_linear_##SIGN##BIT##ENDIAN, \ [Z_RESAMPLER_SINC] = z_feed_sinc_##SIGN##BIT##ENDIAN, \ [Z_RESAMPLER_SINC_POLYPHASE] = \ z_feed_sinc_polyphase_##SIGN##BIT##ENDIAN \ } \ } static const struct { uint32_t format; z_resampler_t resampler[Z_RESAMPLER_LAST]; } z_resampler_tab[] = { #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) Z_RESAMPLER_ENTRY(S, 16, LE), Z_RESAMPLER_ENTRY(S, 32, LE), #endif #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) Z_RESAMPLER_ENTRY(S, 16, BE), Z_RESAMPLER_ENTRY(S, 32, BE), #endif #ifdef SND_FEEDER_MULTIFORMAT Z_RESAMPLER_ENTRY(S, 8, NE), Z_RESAMPLER_ENTRY(S, 24, LE), Z_RESAMPLER_ENTRY(S, 24, BE), Z_RESAMPLER_ENTRY(U, 8, NE), Z_RESAMPLER_ENTRY(U, 16, LE), Z_RESAMPLER_ENTRY(U, 24, LE), Z_RESAMPLER_ENTRY(U, 32, LE), Z_RESAMPLER_ENTRY(U, 16, BE), Z_RESAMPLER_ENTRY(U, 24, BE), Z_RESAMPLER_ENTRY(U, 32, BE), + Z_RESAMPLER_ENTRY(F, 32, LE), + Z_RESAMPLER_ENTRY(F, 32, BE), #endif }; #define Z_RESAMPLER_TAB_SIZE \ ((int32_t)(sizeof(z_resampler_tab) / sizeof(z_resampler_tab[0]))) static void z_resampler_reset(struct z_info *info) { info->src = info->rsrc - (info->rsrc % ((feeder_rate_round > 0 && info->rsrc > feeder_rate_round) ? feeder_rate_round : 1)); info->dst = info->rdst - (info->rdst % ((feeder_rate_round > 0 && info->rdst > feeder_rate_round) ? feeder_rate_round : 1)); info->z_gx = 1; info->z_gy = 1; info->z_alpha = 0; info->z_resample = NULL; info->z_size = 1; info->z_coeff = NULL; info->z_dcoeff = NULL; if (info->z_pcoeff != NULL) { free(info->z_pcoeff, M_DEVBUF); info->z_pcoeff = NULL; } info->z_scale = Z_ONE; info->z_dx = Z_FULL_ONE; info->z_dy = Z_FULL_ONE; #ifdef Z_DIAGNOSTIC info->z_cycle = 0; #endif if (info->quality < Z_QUALITY_MIN) info->quality = Z_QUALITY_MIN; else if (info->quality > Z_QUALITY_MAX) info->quality = Z_QUALITY_MAX; } #ifdef Z_PARANOID static int32_t z_resampler_sinc_len(struct z_info *info) { int32_t c, z, len, lmax; if (!Z_IS_SINC(info)) return (1); /* * A rather careful (or useless) way to calculate filter length. * Z_SINC_LEN() itself is accurate enough to do its job. Extra * sanity checking is not going to hurt though.. */ c = 0; z = info->z_dy; len = 0; lmax = z_coeff_tab[Z_SINC_COEFF_IDX(info)].len; do { c += z >> Z_SHIFT; z &= Z_MASK; z += info->z_dy; } while (c < lmax && ++len > 0); if (len != Z_SINC_LEN(info)) { #ifdef _KERNEL printf("%s(): sinc l=%d != Z_SINC_LEN=%d\n", __func__, len, Z_SINC_LEN(info)); #else fprintf(stderr, "%s(): sinc l=%d != Z_SINC_LEN=%d\n", __func__, len, Z_SINC_LEN(info)); return (-1); #endif } return (len); } #else #define z_resampler_sinc_len(i) (Z_IS_SINC(i) ? Z_SINC_LEN(i) : 1) #endif #define Z_POLYPHASE_COEFF_SHIFT 0 /* * Pick suitable polynomial interpolators based on filter oversampled ratio * (2 ^ Z_DRIFT_SHIFT). */ #if !(defined(Z_COEFF_INTERP_ZOH) || defined(Z_COEFF_INTERP_LINEAR) || \ defined(Z_COEFF_INTERP_QUADRATIC) || defined(Z_COEFF_INTERP_HERMITE) || \ defined(Z_COEFF_INTER_BSPLINE) || defined(Z_COEFF_INTERP_OPT32X) || \ defined(Z_COEFF_INTERP_OPT16X) || defined(Z_COEFF_INTERP_OPT8X) || \ defined(Z_COEFF_INTERP_OPT4X) || defined(Z_COEFF_INTERP_OPT2X)) #if Z_DRIFT_SHIFT >= 6 #define Z_COEFF_INTERP_BSPLINE 1 #elif Z_DRIFT_SHIFT >= 5 #define Z_COEFF_INTERP_OPT32X 1 #elif Z_DRIFT_SHIFT == 4 #define Z_COEFF_INTERP_OPT16X 1 #elif Z_DRIFT_SHIFT == 3 #define Z_COEFF_INTERP_OPT8X 1 #elif Z_DRIFT_SHIFT == 2 #define Z_COEFF_INTERP_OPT4X 1 #elif Z_DRIFT_SHIFT == 1 #define Z_COEFF_INTERP_OPT2X 1 #else #error "Z_DRIFT_SHIFT screwed!" #endif #endif /* * In classic polyphase mode, the actual coefficients for each phases need to * be calculated based on default prototype filters. For highly oversampled * filter, linear or quadradatic interpolator should be enough. Anything less * than that require 'special' interpolators to reduce interpolation errors. * * "Polynomial Interpolators for High-Quality Resampling of Oversampled Audio" * by Olli Niemitalo * - http://www.student.oulu.fi/~oniemita/dsp/deip.pdf * */ static int32_t z_coeff_interpolate(int32_t z, int32_t *z_coeff) { int32_t coeff; #if defined(Z_COEFF_INTERP_ZOH) /* 1-point, 0th-order (Zero Order Hold) */ z = z; coeff = z_coeff[0]; #elif defined(Z_COEFF_INTERP_LINEAR) int32_t zl0, zl1; /* 2-point, 1st-order Linear */ zl0 = z_coeff[0]; zl1 = z_coeff[1] - z_coeff[0]; coeff = Z_RSHIFT((int64_t)zl1 * z, Z_SHIFT) + zl0; #elif defined(Z_COEFF_INTERP_QUADRATIC) int32_t zq0, zq1, zq2; /* 3-point, 2nd-order Quadratic */ zq0 = z_coeff[0]; zq1 = z_coeff[1] - z_coeff[-1]; zq2 = z_coeff[1] + z_coeff[-1] - (z_coeff[0] << 1); coeff = Z_RSHIFT((Z_RSHIFT((int64_t)zq2 * z, Z_SHIFT) + zq1) * z, Z_SHIFT + 1) + zq0; #elif defined(Z_COEFF_INTERP_HERMITE) int32_t zh0, zh1, zh2, zh3; /* 4-point, 3rd-order Hermite */ zh0 = z_coeff[0]; zh1 = z_coeff[1] - z_coeff[-1]; zh2 = (z_coeff[-1] << 1) - (z_coeff[0] * 5) + (z_coeff[1] << 2) - z_coeff[2]; zh3 = z_coeff[2] - z_coeff[-1] + ((z_coeff[0] - z_coeff[1]) * 3); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((int64_t)zh3 * z, Z_SHIFT) + zh2) * z, Z_SHIFT) + zh1) * z, Z_SHIFT + 1) + zh0; #elif defined(Z_COEFF_INTERP_BSPLINE) int32_t zb0, zb1, zb2, zb3; /* 4-point, 3rd-order B-Spline */ zb0 = Z_RSHIFT(0x15555555LL * (((int64_t)z_coeff[0] << 2) + z_coeff[-1] + z_coeff[1]), 30); zb1 = z_coeff[1] - z_coeff[-1]; zb2 = z_coeff[-1] + z_coeff[1] - (z_coeff[0] << 1); zb3 = Z_RSHIFT(0x15555555LL * (((z_coeff[0] - z_coeff[1]) * 3) + z_coeff[2] - z_coeff[-1]), 30); coeff = (Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((int64_t)zb3 * z, Z_SHIFT) + zb2) * z, Z_SHIFT) + zb1) * z, Z_SHIFT) + zb0 + 1) >> 1; #elif defined(Z_COEFF_INTERP_OPT32X) int32_t zoz, zoe1, zoe2, zoe3, zoo1, zoo2, zoo3; int32_t zoc0, zoc1, zoc2, zoc3, zoc4, zoc5; /* 6-point, 5th-order Optimal 32x */ zoz = z - (Z_ONE >> 1); zoe1 = z_coeff[1] + z_coeff[0]; zoe2 = z_coeff[2] + z_coeff[-1]; zoe3 = z_coeff[3] + z_coeff[-2]; zoo1 = z_coeff[1] - z_coeff[0]; zoo2 = z_coeff[2] - z_coeff[-1]; zoo3 = z_coeff[3] - z_coeff[-2]; zoc0 = Z_RSHIFT((0x1ac2260dLL * zoe1) + (0x0526cdcaLL * zoe2) + (0x00170c29LL * zoe3), 30); zoc1 = Z_RSHIFT((0x14f8a49aLL * zoo1) + (0x0d6d1109LL * zoo2) + (0x008cd4dcLL * zoo3), 30); zoc2 = Z_RSHIFT((-0x0d3e94a4LL * zoe1) + (0x0bddded4LL * zoe2) + (0x0160b5d0LL * zoe3), 30); zoc3 = Z_RSHIFT((-0x0de10cc4LL * zoo1) + (0x019b2a7dLL * zoo2) + (0x01cfe914LL * zoo3), 30); zoc4 = Z_RSHIFT((0x02aa12d7LL * zoe1) + (-0x03ff1bb3LL * zoe2) + (0x015508ddLL * zoe3), 30); zoc5 = Z_RSHIFT((0x051d29e5LL * zoo1) + (-0x028e7647LL * zoo2) + (0x0082d81aLL * zoo3), 30); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT( (int64_t)zoc5 * zoz, Z_SHIFT) + zoc4) * zoz, Z_SHIFT) + zoc3) * zoz, Z_SHIFT) + zoc2) * zoz, Z_SHIFT) + zoc1) * zoz, Z_SHIFT) + zoc0; #elif defined(Z_COEFF_INTERP_OPT16X) int32_t zoz, zoe1, zoe2, zoe3, zoo1, zoo2, zoo3; int32_t zoc0, zoc1, zoc2, zoc3, zoc4, zoc5; /* 6-point, 5th-order Optimal 16x */ zoz = z - (Z_ONE >> 1); zoe1 = z_coeff[1] + z_coeff[0]; zoe2 = z_coeff[2] + z_coeff[-1]; zoe3 = z_coeff[3] + z_coeff[-2]; zoo1 = z_coeff[1] - z_coeff[0]; zoo2 = z_coeff[2] - z_coeff[-1]; zoo3 = z_coeff[3] - z_coeff[-2]; zoc0 = Z_RSHIFT((0x1ac2260dLL * zoe1) + (0x0526cdcaLL * zoe2) + (0x00170c29LL * zoe3), 30); zoc1 = Z_RSHIFT((0x14f8a49aLL * zoo1) + (0x0d6d1109LL * zoo2) + (0x008cd4dcLL * zoo3), 30); zoc2 = Z_RSHIFT((-0x0d3e94a4LL * zoe1) + (0x0bddded4LL * zoe2) + (0x0160b5d0LL * zoe3), 30); zoc3 = Z_RSHIFT((-0x0de10cc4LL * zoo1) + (0x019b2a7dLL * zoo2) + (0x01cfe914LL * zoo3), 30); zoc4 = Z_RSHIFT((0x02aa12d7LL * zoe1) + (-0x03ff1bb3LL * zoe2) + (0x015508ddLL * zoe3), 30); zoc5 = Z_RSHIFT((0x051d29e5LL * zoo1) + (-0x028e7647LL * zoo2) + (0x0082d81aLL * zoo3), 30); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT( (int64_t)zoc5 * zoz, Z_SHIFT) + zoc4) * zoz, Z_SHIFT) + zoc3) * zoz, Z_SHIFT) + zoc2) * zoz, Z_SHIFT) + zoc1) * zoz, Z_SHIFT) + zoc0; #elif defined(Z_COEFF_INTERP_OPT8X) int32_t zoz, zoe1, zoe2, zoe3, zoo1, zoo2, zoo3; int32_t zoc0, zoc1, zoc2, zoc3, zoc4, zoc5; /* 6-point, 5th-order Optimal 8x */ zoz = z - (Z_ONE >> 1); zoe1 = z_coeff[1] + z_coeff[0]; zoe2 = z_coeff[2] + z_coeff[-1]; zoe3 = z_coeff[3] + z_coeff[-2]; zoo1 = z_coeff[1] - z_coeff[0]; zoo2 = z_coeff[2] - z_coeff[-1]; zoo3 = z_coeff[3] - z_coeff[-2]; zoc0 = Z_RSHIFT((0x1aa9b47dLL * zoe1) + (0x053d9944LL * zoe2) + (0x0018b23fLL * zoe3), 30); zoc1 = Z_RSHIFT((0x14a104d1LL * zoo1) + (0x0d7d2504LL * zoo2) + (0x0094b599LL * zoo3), 30); zoc2 = Z_RSHIFT((-0x0d22530bLL * zoe1) + (0x0bb37a2cLL * zoe2) + (0x016ed8e0LL * zoe3), 30); zoc3 = Z_RSHIFT((-0x0d744b1cLL * zoo1) + (0x01649591LL * zoo2) + (0x01dae93aLL * zoo3), 30); zoc4 = Z_RSHIFT((0x02a7ee1bLL * zoe1) + (-0x03fbdb24LL * zoe2) + (0x0153ed07LL * zoe3), 30); zoc5 = Z_RSHIFT((0x04cf9b6cLL * zoo1) + (-0x0266b378LL * zoo2) + (0x007a7c26LL * zoo3), 30); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT( (int64_t)zoc5 * zoz, Z_SHIFT) + zoc4) * zoz, Z_SHIFT) + zoc3) * zoz, Z_SHIFT) + zoc2) * zoz, Z_SHIFT) + zoc1) * zoz, Z_SHIFT) + zoc0; #elif defined(Z_COEFF_INTERP_OPT4X) int32_t zoz, zoe1, zoe2, zoe3, zoo1, zoo2, zoo3; int32_t zoc0, zoc1, zoc2, zoc3, zoc4, zoc5; /* 6-point, 5th-order Optimal 4x */ zoz = z - (Z_ONE >> 1); zoe1 = z_coeff[1] + z_coeff[0]; zoe2 = z_coeff[2] + z_coeff[-1]; zoe3 = z_coeff[3] + z_coeff[-2]; zoo1 = z_coeff[1] - z_coeff[0]; zoo2 = z_coeff[2] - z_coeff[-1]; zoo3 = z_coeff[3] - z_coeff[-2]; zoc0 = Z_RSHIFT((0x1a8eda43LL * zoe1) + (0x0556ee38LL * zoe2) + (0x001a3784LL * zoe3), 30); zoc1 = Z_RSHIFT((0x143d863eLL * zoo1) + (0x0d910e36LL * zoo2) + (0x009ca889LL * zoo3), 30); zoc2 = Z_RSHIFT((-0x0d026821LL * zoe1) + (0x0b837773LL * zoe2) + (0x017ef0c6LL * zoe3), 30); zoc3 = Z_RSHIFT((-0x0cef1502LL * zoo1) + (0x01207a8eLL * zoo2) + (0x01e936dbLL * zoo3), 30); zoc4 = Z_RSHIFT((0x029fe643LL * zoe1) + (-0x03ef3fc8LL * zoe2) + (0x014f5923LL * zoe3), 30); zoc5 = Z_RSHIFT((0x043a9d08LL * zoo1) + (-0x02154febLL * zoo2) + (0x00670dbdLL * zoo3), 30); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT( (int64_t)zoc5 * zoz, Z_SHIFT) + zoc4) * zoz, Z_SHIFT) + zoc3) * zoz, Z_SHIFT) + zoc2) * zoz, Z_SHIFT) + zoc1) * zoz, Z_SHIFT) + zoc0; #elif defined(Z_COEFF_INTERP_OPT2X) int32_t zoz, zoe1, zoe2, zoe3, zoo1, zoo2, zoo3; int32_t zoc0, zoc1, zoc2, zoc3, zoc4, zoc5; /* 6-point, 5th-order Optimal 2x */ zoz = z - (Z_ONE >> 1); zoe1 = z_coeff[1] + z_coeff[0]; zoe2 = z_coeff[2] + z_coeff[-1]; zoe3 = z_coeff[3] + z_coeff[-2]; zoo1 = z_coeff[1] - z_coeff[0]; zoo2 = z_coeff[2] - z_coeff[-1]; zoo3 = z_coeff[3] - z_coeff[-2]; zoc0 = Z_RSHIFT((0x19edb6fdLL * zoe1) + (0x05ebd062LL * zoe2) + (0x00267881LL * zoe3), 30); zoc1 = Z_RSHIFT((0x1223af76LL * zoo1) + (0x0de3dd6bLL * zoo2) + (0x00d683cdLL * zoo3), 30); zoc2 = Z_RSHIFT((-0x0c3ee068LL * zoe1) + (0x0a5c3769LL * zoe2) + (0x01e2aceaLL * zoe3), 30); zoc3 = Z_RSHIFT((-0x0a8ab614LL * zoo1) + (-0x0019522eLL * zoo2) + (0x022cefc7LL * zoo3), 30); zoc4 = Z_RSHIFT((0x0276187dLL * zoe1) + (-0x03a801e8LL * zoe2) + (0x0131d935LL * zoe3), 30); zoc5 = Z_RSHIFT((0x02c373f5LL * zoo1) + (-0x01275f83LL * zoo2) + (0x0018ee79LL * zoo3), 30); coeff = Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT((Z_RSHIFT( (int64_t)zoc5 * zoz, Z_SHIFT) + zoc4) * zoz, Z_SHIFT) + zoc3) * zoz, Z_SHIFT) + zoc2) * zoz, Z_SHIFT) + zoc1) * zoz, Z_SHIFT) + zoc0; #else #error "Interpolation type screwed!" #endif #if Z_POLYPHASE_COEFF_SHIFT > 0 coeff = Z_RSHIFT(coeff, Z_POLYPHASE_COEFF_SHIFT); #endif return (coeff); } static int z_resampler_build_polyphase(struct z_info *info) { int32_t alpha, c, i, z, idx; /* Let this be here first. */ if (info->z_pcoeff != NULL) { free(info->z_pcoeff, M_DEVBUF); info->z_pcoeff = NULL; } if (feeder_rate_polyphase_max < 1) return (ENOTSUP); if (((int64_t)info->z_size * info->z_gy * 2) > feeder_rate_polyphase_max) { #ifndef _KERNEL fprintf(stderr, "Polyphase entries exceed: [%d/%d] %jd > %d\n", info->z_gx, info->z_gy, (intmax_t)info->z_size * info->z_gy * 2, feeder_rate_polyphase_max); #endif return (E2BIG); } info->z_pcoeff = malloc(sizeof(int32_t) * info->z_size * info->z_gy * 2, M_DEVBUF, M_NOWAIT | M_ZERO); if (info->z_pcoeff == NULL) return (ENOMEM); for (alpha = 0; alpha < info->z_gy; alpha++) { z = alpha * info->z_dx; c = 0; for (i = info->z_size; i != 0; i--) { c += z >> Z_SHIFT; z &= Z_MASK; idx = (alpha * info->z_size * 2) + (info->z_size * 2) - i; info->z_pcoeff[idx] = z_coeff_interpolate(z, info->z_coeff + c); z += info->z_dy; } z = info->z_dy - (alpha * info->z_dx); c = 0; for (i = info->z_size; i != 0; i--) { c += z >> Z_SHIFT; z &= Z_MASK; idx = (alpha * info->z_size * 2) + i - 1; info->z_pcoeff[idx] = z_coeff_interpolate(z, info->z_coeff + c); z += info->z_dy; } } #ifndef _KERNEL fprintf(stderr, "Polyphase: [%d/%d] %d entries\n", info->z_gx, info->z_gy, info->z_size * info->z_gy * 2); #endif return (0); } static int z_resampler_setup(struct pcm_feeder *f) { struct z_info *info; int64_t gy2gx_max, gx2gy_max; uint32_t format; int32_t align, i, z_scale; int adaptive; info = f->data; z_resampler_reset(info); if (info->src == info->dst) return (0); /* Shrink by greatest common divisor. */ i = z_gcd(info->src, info->dst); info->z_gx = info->src / i; info->z_gy = info->dst / i; /* Too big, or too small. Bail out. */ if (!(Z_FACTOR_SAFE(info->z_gx) && Z_FACTOR_SAFE(info->z_gy))) return (EINVAL); format = f->desc->in; adaptive = 0; z_scale = 0; /* * Setup everything: filter length, conversion factor, etc. */ if (Z_IS_SINC(info)) { /* * Downsampling, or upsampling scaling factor. As long as the * factor can be represented by a fraction of 1 << Z_SHIFT, * we're pretty much in business. Scaling is not needed for * upsampling, so we just slap Z_ONE there. */ if (info->z_gx > info->z_gy) /* * If the downsampling ratio is beyond sanity, * enable semi-adaptive mode. Although handling * extreme ratio is possible, the result of the * conversion is just pointless, unworthy, * nonsensical noises, etc. */ if ((info->z_gx / info->z_gy) > Z_SINC_DOWNMAX) z_scale = Z_ONE / Z_SINC_DOWNMAX; else z_scale = ((uint64_t)info->z_gy << Z_SHIFT) / info->z_gx; else z_scale = Z_ONE; /* * This is actually impossible, unless anything above * overflow. */ if (z_scale < 1) return (E2BIG); /* * Calculate sample time/coefficients index drift. It is * a constant for upsampling, but downsampling require * heavy duty filtering with possible too long filters. * If anything goes wrong, revisit again and enable * adaptive mode. */ z_setup_adaptive_sinc: if (info->z_pcoeff != NULL) { free(info->z_pcoeff, M_DEVBUF); info->z_pcoeff = NULL; } if (adaptive == 0) { info->z_dy = z_scale << Z_DRIFT_SHIFT; if (info->z_dy < 1) return (E2BIG); info->z_scale = z_scale; } else { info->z_dy = Z_FULL_ONE; info->z_scale = Z_ONE; } /* Smallest drift increment. */ info->z_dx = info->z_dy / info->z_gy; /* * Overflow or underflow. Try adaptive, let it continue and * retry. */ if (info->z_dx < 1) { if (adaptive == 0) { adaptive = 1; goto z_setup_adaptive_sinc; } return (E2BIG); } /* * Round back output drift. */ info->z_dy = info->z_dx * info->z_gy; for (i = 0; i < Z_COEFF_TAB_SIZE; i++) { if (Z_SINC_COEFF_IDX(info) != i) continue; /* * Calculate required filter length and guard * against possible abusive result. Note that * this represents only 1/2 of the entire filter * length. */ info->z_size = z_resampler_sinc_len(info); /* * Multiple of 2 rounding, for better accumulator * performance. */ info->z_size &= ~1; if (info->z_size < 2 || info->z_size > Z_SINC_MAX) { if (adaptive == 0) { adaptive = 1; goto z_setup_adaptive_sinc; } return (E2BIG); } info->z_coeff = z_coeff_tab[i].coeff + Z_COEFF_OFFSET; info->z_dcoeff = z_coeff_tab[i].dcoeff; break; } if (info->z_coeff == NULL || info->z_dcoeff == NULL) return (EINVAL); } else if (Z_IS_LINEAR(info)) { /* * Don't put much effort if we're doing linear interpolation. * Just center the interpolation distance within Z_LINEAR_ONE, * and be happy about it. */ info->z_dx = Z_LINEAR_FULL_ONE / info->z_gy; } /* * We're safe for now, lets continue.. Look for our resampler * depending on configured format and quality. */ for (i = 0; i < Z_RESAMPLER_TAB_SIZE; i++) { int ridx; if (AFMT_ENCODING(format) != z_resampler_tab[i].format) continue; if (Z_IS_SINC(info) && adaptive == 0 && z_resampler_build_polyphase(info) == 0) ridx = Z_RESAMPLER_SINC_POLYPHASE; else ridx = Z_RESAMPLER_IDX(info); info->z_resample = z_resampler_tab[i].resampler[ridx]; break; } if (info->z_resample == NULL) return (EINVAL); info->bps = AFMT_BPS(format); align = info->channels * info->bps; /* * Calculate largest value that can be fed into z_gy2gx() and * z_gx2gy() without causing (signed) 32bit overflow. z_gy2gx() will * be called early during feeding process to determine how much input * samples that is required to generate requested output, while * z_gx2gy() will be called just before samples filtering / * accumulation process based on available samples that has been * calculated using z_gx2gy(). * * Now that is damn confusing, I guess ;-) . */ gy2gx_max = (((uint64_t)info->z_gy * INT32_MAX) - info->z_gy + 1) / info->z_gx; if ((gy2gx_max * align) > SND_FXDIV_MAX) gy2gx_max = SND_FXDIV_MAX / align; if (gy2gx_max < 1) return (E2BIG); gx2gy_max = (((uint64_t)info->z_gx * INT32_MAX) - info->z_gy) / info->z_gy; if (gx2gy_max > INT32_MAX) gx2gy_max = INT32_MAX; if (gx2gy_max < 1) return (E2BIG); /* * Ensure that z_gy2gx() at its largest possible calculated value * (alpha = 0) will not cause overflow further late during z_gx2gy() * stage. */ if (z_gy2gx(info, gy2gx_max) > _Z_GCAST(gx2gy_max)) return (E2BIG); info->z_maxfeed = gy2gx_max * align; #ifdef Z_USE_ALPHADRIFT info->z_startdrift = z_gy2gx(info, 1); info->z_alphadrift = z_drift(info, info->z_startdrift, 1); #endif i = z_gy2gx(info, 1); info->z_full = z_roundpow2((info->z_size << 1) + i); /* * Too big to be true, and overflowing left and right like mad .. */ if ((info->z_full * align) < 1) { if (adaptive == 0 && Z_IS_SINC(info)) { adaptive = 1; goto z_setup_adaptive_sinc; } return (E2BIG); } /* * Increase full buffer size if its too small to reduce cyclic * buffer shifting in main conversion/feeder loop. */ while (info->z_full < Z_RESERVOIR_MAX && (info->z_full - (info->z_size << 1)) < Z_RESERVOIR) info->z_full <<= 1; /* Initialize buffer position. */ info->z_mask = info->z_full - 1; info->z_start = z_prev(info, info->z_size << 1, 1); info->z_pos = z_next(info, info->z_start, 1); /* * Allocate or reuse delay line buffer, whichever makes sense. */ i = info->z_full * align; if (i < 1) return (E2BIG); if (info->z_delay == NULL || info->z_alloc < i || i <= (info->z_alloc >> 1)) { if (info->z_delay != NULL) free(info->z_delay, M_DEVBUF); info->z_delay = malloc(i, M_DEVBUF, M_NOWAIT | M_ZERO); if (info->z_delay == NULL) return (ENOMEM); info->z_alloc = i; } /* * Zero out head of buffer to avoid pops and clicks. */ memset(info->z_delay, sndbuf_zerodata(f->desc->out), info->z_pos * align); #ifdef Z_DIAGNOSTIC /* * XXX Debuging mess !@#$%^ */ #define dumpz(x) fprintf(stderr, "\t%12s = %10u : %-11d\n", \ "z_"__STRING(x), (uint32_t)info->z_##x, \ (int32_t)info->z_##x) fprintf(stderr, "\n%s():\n", __func__); fprintf(stderr, "\tchannels=%d, bps=%d, format=0x%08x, quality=%d\n", info->channels, info->bps, format, info->quality); fprintf(stderr, "\t%d (%d) -> %d (%d), ", info->src, info->rsrc, info->dst, info->rdst); fprintf(stderr, "[%d/%d]\n", info->z_gx, info->z_gy); fprintf(stderr, "\tminreq=%d, ", z_gy2gx(info, 1)); if (adaptive != 0) z_scale = Z_ONE; fprintf(stderr, "factor=0x%08x/0x%08x (%f)\n", z_scale, Z_ONE, (double)z_scale / Z_ONE); fprintf(stderr, "\tbase_length=%d, ", Z_SINC_BASE_LEN(info)); fprintf(stderr, "adaptive=%s\n", (adaptive != 0) ? "YES" : "NO"); dumpz(size); dumpz(alloc); if (info->z_alloc < 1024) fprintf(stderr, "\t%15s%10d Bytes\n", "", info->z_alloc); else if (info->z_alloc < (1024 << 10)) fprintf(stderr, "\t%15s%10d KBytes\n", "", info->z_alloc >> 10); else if (info->z_alloc < (1024 << 20)) fprintf(stderr, "\t%15s%10d MBytes\n", "", info->z_alloc >> 20); else fprintf(stderr, "\t%15s%10d GBytes\n", "", info->z_alloc >> 30); fprintf(stderr, "\t%12s %10d (min output samples)\n", "", (int32_t)z_gx2gy(info, info->z_full - (info->z_size << 1))); fprintf(stderr, "\t%12s %10d (min allocated output samples)\n", "", (int32_t)z_gx2gy(info, (info->z_alloc / align) - (info->z_size << 1))); fprintf(stderr, "\t%12s = %10d\n", "z_gy2gx()", (int32_t)z_gy2gx(info, 1)); fprintf(stderr, "\t%12s = %10d -> z_gy2gx() -> %d\n", "Max", (int32_t)gy2gx_max, (int32_t)z_gy2gx(info, gy2gx_max)); fprintf(stderr, "\t%12s = %10d\n", "z_gx2gy()", (int32_t)z_gx2gy(info, 1)); fprintf(stderr, "\t%12s = %10d -> z_gx2gy() -> %d\n", "Max", (int32_t)gx2gy_max, (int32_t)z_gx2gy(info, gx2gy_max)); dumpz(maxfeed); dumpz(full); dumpz(start); dumpz(pos); dumpz(scale); fprintf(stderr, "\t%12s %10f\n", "", (double)info->z_scale / Z_ONE); dumpz(dx); fprintf(stderr, "\t%12s %10f\n", "", (double)info->z_dx / info->z_dy); dumpz(dy); fprintf(stderr, "\t%12s %10d (drift step)\n", "", info->z_dy >> Z_SHIFT); fprintf(stderr, "\t%12s %10d (scaling differences)\n", "", (z_scale << Z_DRIFT_SHIFT) - info->z_dy); fprintf(stderr, "\t%12s = %u bytes\n", "intpcm32_t", sizeof(intpcm32_t)); fprintf(stderr, "\t%12s = 0x%08x, smallest=%.16lf\n", "Z_ONE", Z_ONE, (double)1.0 / (double)Z_ONE); #endif return (0); } static int z_resampler_set(struct pcm_feeder *f, int what, int32_t value) { struct z_info *info; int32_t oquality; info = f->data; switch (what) { case Z_RATE_SRC: if (value < feeder_rate_min || value > feeder_rate_max) return (E2BIG); if (value == info->rsrc) return (0); info->rsrc = value; break; case Z_RATE_DST: if (value < feeder_rate_min || value > feeder_rate_max) return (E2BIG); if (value == info->rdst) return (0); info->rdst = value; break; case Z_RATE_QUALITY: if (value < Z_QUALITY_MIN || value > Z_QUALITY_MAX) return (EINVAL); if (value == info->quality) return (0); /* * If we failed to set the requested quality, restore * the old one. We cannot afford leaving it broken since * passive feeder chains like vchans never reinitialize * itself. */ oquality = info->quality; info->quality = value; if (z_resampler_setup(f) == 0) return (0); info->quality = oquality; break; case Z_RATE_CHANNELS: if (value < SND_CHN_MIN || value > SND_CHN_MAX) return (EINVAL); if (value == info->channels) return (0); info->channels = value; break; default: return (EINVAL); break; } return (z_resampler_setup(f)); } static int z_resampler_get(struct pcm_feeder *f, int what) { struct z_info *info; info = f->data; switch (what) { case Z_RATE_SRC: return (info->rsrc); break; case Z_RATE_DST: return (info->rdst); break; case Z_RATE_QUALITY: return (info->quality); break; case Z_RATE_CHANNELS: return (info->channels); break; default: break; } return (-1); } static int z_resampler_init(struct pcm_feeder *f) { struct z_info *info; int ret; if (f->desc->in != f->desc->out) return (EINVAL); info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO); if (info == NULL) return (ENOMEM); info->rsrc = Z_RATE_DEFAULT; info->rdst = Z_RATE_DEFAULT; info->quality = feeder_rate_quality; info->channels = AFMT_CHANNEL(f->desc->in); f->data = info; ret = z_resampler_setup(f); if (ret != 0) { if (info->z_pcoeff != NULL) free(info->z_pcoeff, M_DEVBUF); if (info->z_delay != NULL) free(info->z_delay, M_DEVBUF); free(info, M_DEVBUF); f->data = NULL; } return (ret); } static int z_resampler_free(struct pcm_feeder *f) { struct z_info *info; info = f->data; if (info != NULL) { if (info->z_pcoeff != NULL) free(info->z_pcoeff, M_DEVBUF); if (info->z_delay != NULL) free(info->z_delay, M_DEVBUF); free(info, M_DEVBUF); } f->data = NULL; return (0); } static uint32_t z_resampler_feed_internal(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { struct z_info *info; int32_t alphadrift, startdrift, reqout, ocount, reqin, align; int32_t fetch, fetched, start, cp; uint8_t *dst; info = f->data; if (info->z_resample == NULL) return (z_feed(f->source, c, b, count, source)); /* * Calculate sample size alignment and amount of sample output. * We will do everything in sample domain, but at the end we * will jump back to byte domain. */ align = info->channels * info->bps; ocount = SND_FXDIV(count, align); if (ocount == 0) return (0); /* * Calculate amount of input samples that is needed to generate * exact amount of output. */ reqin = z_gy2gx(info, ocount) - z_fetched(info); #ifdef Z_USE_ALPHADRIFT startdrift = info->z_startdrift; alphadrift = info->z_alphadrift; #else startdrift = _Z_GY2GX(info, 0, 1); alphadrift = z_drift(info, startdrift, 1); #endif dst = b; do { if (reqin != 0) { fetch = z_min(z_free(info), reqin); if (fetch == 0) { /* * No more free spaces, so wind enough * samples back to the head of delay line * in byte domain. */ fetched = z_fetched(info); start = z_prev(info, info->z_start, (info->z_size << 1) - 1); cp = (info->z_size << 1) + fetched; z_copy(info->z_delay + (start * align), info->z_delay, cp * align); info->z_start = z_prev(info, info->z_size << 1, 1); info->z_pos = z_next(info, info->z_start, fetched + 1); fetch = z_min(z_free(info), reqin); #ifdef Z_DIAGNOSTIC if (1) { static uint32_t kk = 0; fprintf(stderr, "Buffer Move: " "start=%d fetched=%d cp=%d " "cycle=%u [%u]\r", start, fetched, cp, info->z_cycle, ++kk); } info->z_cycle = 0; #endif } if (fetch != 0) { /* * Fetch in byte domain and jump back * to sample domain. */ fetched = SND_FXDIV(z_feed(f->source, c, info->z_delay + (info->z_pos * align), fetch * align, source), align); /* * Prepare to convert fetched buffer, * or mark us done if we cannot fulfill * the request. */ reqin -= fetched; info->z_pos += fetched; if (fetched != fetch) reqin = 0; } } reqout = z_min(z_gx2gy(info, z_fetched(info)), ocount); if (reqout != 0) { ocount -= reqout; /* * Drift.. drift.. drift.. * * Notice that there are 2 methods of doing the drift * operations: The former is much cleaner (in a sense * of mathematical readings of my eyes), but slower * due to integer division in z_gy2gx(). Nevertheless, * both should give the same exact accurate drifting * results, so the later is favourable. */ do { info->z_resample(info, dst); info->z_alpha += alphadrift; if (info->z_alpha < info->z_gy) info->z_start += startdrift; else { info->z_start += startdrift - 1; info->z_alpha -= info->z_gy; } dst += align; #ifdef Z_DIAGNOSTIC info->z_cycle++; #endif } while (--reqout != 0); } } while (reqin != 0 && ocount != 0); /* * Back to byte domain.. */ return (dst - b); } static int z_resampler_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { uint32_t feed, maxfeed, left; /* * Split count to smaller chunks to avoid possible 32bit overflow. */ maxfeed = ((struct z_info *)(f->data))->z_maxfeed; left = count; do { feed = z_resampler_feed_internal(f, c, b, z_min(maxfeed, left), source); b += feed; left -= feed; } while (left != 0 && feed != 0); return (count - left); } static struct pcm_feederdesc feeder_rate_desc[] = { { FEEDER_RATE, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, }; static kobj_method_t feeder_rate_methods[] = { KOBJMETHOD(feeder_init, z_resampler_init), KOBJMETHOD(feeder_free, z_resampler_free), KOBJMETHOD(feeder_set, z_resampler_set), KOBJMETHOD(feeder_get, z_resampler_get), KOBJMETHOD(feeder_feed, z_resampler_feed), KOBJMETHOD_END }; FEEDER_DECLARE(feeder_rate, NULL); diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c index f72c6aa7ef4f..2d35bb56ef8f 100644 --- a/sys/dev/sound/pcm/feeder_volume.c +++ b/sys/dev/sound/pcm/feeder_volume.c @@ -1,353 +1,355 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* feeder_volume, a long 'Lost Technology' rather than a new feature. */ #ifdef _KERNEL #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "feeder_if.h" #define SND_USE_FXDIV #include "snd_fxdiv_gen.h" #endif typedef void (*feed_volume_t)(int *, int *, uint32_t, uint8_t *, uint32_t); #define FEEDVOLUME_CALC8(s, v) (SND_VOL_CALC_SAMPLE((intpcm_t) \ (s) << 8, v) >> 8) #define FEEDVOLUME_CALC16(s, v) SND_VOL_CALC_SAMPLE((intpcm_t)(s), v) #define FEEDVOLUME_CALC24(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) #define FEEDVOLUME_CALC32(s, v) SND_VOL_CALC_SAMPLE((intpcm64_t)(s), v) #define FEEDVOLUME_DECLARE(SIGN, BIT, ENDIAN) \ static void \ feed_volume_##SIGN##BIT##ENDIAN(int *vol, int *matrix, \ uint32_t channels, uint8_t *dst, uint32_t count) \ { \ intpcm##BIT##_t v; \ intpcm_t x; \ uint32_t i; \ \ dst += count * PCM_##BIT##_BPS * channels; \ do { \ i = channels; \ do { \ dst -= PCM_##BIT##_BPS; \ i--; \ x = pcm_sample_read_calc(dst, \ AFMT_##SIGN##BIT##_##ENDIAN); \ v = FEEDVOLUME_CALC##BIT(x, vol[matrix[i]]); \ x = pcm_clamp_calc(v, \ AFMT_##SIGN##BIT##_##ENDIAN); \ pcm_sample_write(dst, x, \ AFMT_##SIGN##BIT##_##ENDIAN); \ } while (i != 0); \ } while (--count != 0); \ } #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) FEEDVOLUME_DECLARE(S, 16, LE) FEEDVOLUME_DECLARE(S, 32, LE) #endif #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) FEEDVOLUME_DECLARE(S, 16, BE) FEEDVOLUME_DECLARE(S, 32, BE) #endif #ifdef SND_FEEDER_MULTIFORMAT FEEDVOLUME_DECLARE(S, 8, NE) FEEDVOLUME_DECLARE(S, 24, LE) FEEDVOLUME_DECLARE(S, 24, BE) FEEDVOLUME_DECLARE(U, 8, NE) FEEDVOLUME_DECLARE(U, 16, LE) FEEDVOLUME_DECLARE(U, 24, LE) FEEDVOLUME_DECLARE(U, 32, LE) FEEDVOLUME_DECLARE(U, 16, BE) FEEDVOLUME_DECLARE(U, 24, BE) FEEDVOLUME_DECLARE(U, 32, BE) +FEEDVOLUME_DECLARE(F, 32, LE) +FEEDVOLUME_DECLARE(F, 32, BE) #endif struct feed_volume_info { uint32_t bps, channels; feed_volume_t apply; int volume_class; int state; int matrix[SND_CHN_MAX]; }; #define FEEDVOLUME_ENTRY(SIGN, BIT, ENDIAN) \ { \ AFMT_##SIGN##BIT##_##ENDIAN, \ feed_volume_##SIGN##BIT##ENDIAN \ } static const struct { uint32_t format; feed_volume_t apply; } feed_volume_info_tab[] = { #if BYTE_ORDER == LITTLE_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) FEEDVOLUME_ENTRY(S, 16, LE), FEEDVOLUME_ENTRY(S, 32, LE), #endif #if BYTE_ORDER == BIG_ENDIAN || defined(SND_FEEDER_MULTIFORMAT) FEEDVOLUME_ENTRY(S, 16, BE), FEEDVOLUME_ENTRY(S, 32, BE), #endif #ifdef SND_FEEDER_MULTIFORMAT FEEDVOLUME_ENTRY(S, 8, NE), FEEDVOLUME_ENTRY(S, 24, LE), FEEDVOLUME_ENTRY(S, 24, BE), FEEDVOLUME_ENTRY(U, 8, NE), FEEDVOLUME_ENTRY(U, 16, LE), FEEDVOLUME_ENTRY(U, 24, LE), FEEDVOLUME_ENTRY(U, 32, LE), FEEDVOLUME_ENTRY(U, 16, BE), FEEDVOLUME_ENTRY(U, 24, BE), FEEDVOLUME_ENTRY(U, 32, BE) #endif }; #define FEEDVOLUME_TAB_SIZE ((int32_t) \ (sizeof(feed_volume_info_tab) / \ sizeof(feed_volume_info_tab[0]))) static int feed_volume_init(struct pcm_feeder *f) { struct feed_volume_info *info; struct pcmchan_matrix *m; uint32_t i; int ret; if (f->desc->in != f->desc->out || AFMT_CHANNEL(f->desc->in) > SND_CHN_MAX) return (EINVAL); for (i = 0; i < FEEDVOLUME_TAB_SIZE; i++) { if (AFMT_ENCODING(f->desc->in) == feed_volume_info_tab[i].format) { info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO); if (info == NULL) return (ENOMEM); info->bps = AFMT_BPS(f->desc->in); info->channels = AFMT_CHANNEL(f->desc->in); info->apply = feed_volume_info_tab[i].apply; info->volume_class = SND_VOL_C_PCM; info->state = FEEDVOLUME_ENABLE; f->data = info; m = feeder_matrix_default_channel_map(info->channels); if (m == NULL) { free(info, M_DEVBUF); return (EINVAL); } ret = feeder_volume_apply_matrix(f, m); if (ret != 0) free(info, M_DEVBUF); return (ret); } } return (EINVAL); } static int feed_volume_free(struct pcm_feeder *f) { struct feed_volume_info *info; info = f->data; if (info != NULL) free(info, M_DEVBUF); f->data = NULL; return (0); } static int feed_volume_set(struct pcm_feeder *f, int what, int value) { struct feed_volume_info *info; struct pcmchan_matrix *m; int ret; info = f->data; ret = 0; switch (what) { case FEEDVOLUME_CLASS: if (value < SND_VOL_C_BEGIN || value > SND_VOL_C_END) return (EINVAL); info->volume_class = value; break; case FEEDVOLUME_CHANNELS: if (value < SND_CHN_MIN || value > SND_CHN_MAX) return (EINVAL); m = feeder_matrix_default_channel_map(value); if (m == NULL) return (EINVAL); ret = feeder_volume_apply_matrix(f, m); break; case FEEDVOLUME_STATE: if (!(value == FEEDVOLUME_ENABLE || value == FEEDVOLUME_BYPASS)) return (EINVAL); info->state = value; break; default: return (EINVAL); break; } return (ret); } static int feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { int temp_vol[SND_CHN_T_VOL_MAX]; struct feed_volume_info *info; uint32_t j, align; int i, *matrix; uint8_t *dst; const int16_t *vol; const int8_t *muted; /* * Fetch filter data operation. */ info = f->data; if (info->state == FEEDVOLUME_BYPASS) return (FEEDER_FEED(f->source, c, b, count, source)); vol = c->volume[SND_VOL_C_VAL(info->volume_class)]; muted = c->muted[SND_VOL_C_VAL(info->volume_class)]; matrix = info->matrix; /* * First, let see if we really need to apply gain at all. */ j = 0; i = info->channels; while (i--) { if (vol[matrix[i]] != SND_VOL_FLAT || muted[matrix[i]] != 0) { j = 1; break; } } /* Nope, just bypass entirely. */ if (j == 0) return (FEEDER_FEED(f->source, c, b, count, source)); /* Check if any controls are muted. */ for (j = 0; j != SND_CHN_T_VOL_MAX; j++) temp_vol[j] = muted[j] ? 0 : vol[j]; dst = b; align = info->bps * info->channels; do { if (count < align) break; j = SND_FXDIV(FEEDER_FEED(f->source, c, dst, count, source), align); if (j == 0) break; info->apply(temp_vol, matrix, info->channels, dst, j); j *= align; dst += j; count -= j; } while (count != 0); return (dst - b); } static struct pcm_feederdesc feeder_volume_desc[] = { { FEEDER_VOLUME, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; static kobj_method_t feeder_volume_methods[] = { KOBJMETHOD(feeder_init, feed_volume_init), KOBJMETHOD(feeder_free, feed_volume_free), KOBJMETHOD(feeder_set, feed_volume_set), KOBJMETHOD(feeder_feed, feed_volume_feed), KOBJMETHOD_END }; FEEDER_DECLARE(feeder_volume, NULL); /* Extern */ /* * feeder_volume_apply_matrix(): For given matrix map, apply its configuration * to feeder_volume matrix structure. There are * possibilites that feeder_volume be inserted * before or after feeder_matrix, which in this * case feeder_volume must be in a good terms * with _current_ matrix. */ int feeder_volume_apply_matrix(struct pcm_feeder *f, struct pcmchan_matrix *m) { struct feed_volume_info *info; uint32_t i; if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_VOLUME || f->data == NULL || m == NULL || m->channels < SND_CHN_MIN || m->channels > SND_CHN_MAX) return (EINVAL); info = f->data; for (i = 0; i < nitems(info->matrix); i++) { if (i < m->channels) info->matrix[i] = m->map[i].type; else info->matrix[i] = SND_CHN_T_FL; } info->channels = m->channels; return (0); } diff --git a/sys/dev/sound/pcm/pcm.h b/sys/dev/sound/pcm/pcm.h index 1de686b04097..7d0a8f0f431b 100644 --- a/sys/dev/sound/pcm/pcm.h +++ b/sys/dev/sound/pcm/pcm.h @@ -1,389 +1,450 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006-2009 Ariff Abdullah * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _SND_PCM_H_ #define _SND_PCM_H_ #include #include #ifndef _KERNEL #include /* for __assert_unreachable() */ #endif /* * Automatically turn on 64bit arithmetic on suitable archs * (amd64 64bit, etc..) for wider 32bit samples / integer processing. */ #if LONG_BIT >= 64 #undef SND_PCM_64 #define SND_PCM_64 1 #endif typedef int32_t intpcm_t; typedef int32_t intpcm8_t; typedef int32_t intpcm16_t; typedef int32_t intpcm24_t; typedef uint32_t uintpcm_t; typedef uint32_t uintpcm8_t; typedef uint32_t uintpcm16_t; typedef uint32_t uintpcm24_t; #ifdef SND_PCM_64 typedef int64_t intpcm32_t; typedef uint64_t uintpcm32_t; #else typedef int32_t intpcm32_t; typedef uint32_t uintpcm32_t; #endif typedef int64_t intpcm64_t; typedef uint64_t uintpcm64_t; /* 32bit fixed point shift */ #define PCM_FXSHIFT 8 #define PCM_S8_MAX 0x7f #define PCM_S8_MIN -0x80 #define PCM_S16_MAX 0x7fff #define PCM_S16_MIN -0x8000 #define PCM_S24_MAX 0x7fffff #define PCM_S24_MIN -0x800000 #ifdef SND_PCM_64 #if LONG_BIT >= 64 #define PCM_S32_MAX 0x7fffffffL #define PCM_S32_MIN -0x80000000L #else #define PCM_S32_MAX 0x7fffffffLL #define PCM_S32_MIN -0x80000000LL #endif #else #define PCM_S32_MAX 0x7fffffff #define PCM_S32_MIN (-0x7fffffff - 1) #endif /* Bytes-per-sample definition */ #define PCM_8_BPS 1 #define PCM_16_BPS 2 #define PCM_24_BPS 3 #define PCM_32_BPS 4 #define INTPCM_T(v) ((intpcm_t)(v)) #define INTPCM8_T(v) ((intpcm8_t)(v)) #define INTPCM16_T(v) ((intpcm16_t)(v)) #define INTPCM24_T(v) ((intpcm24_t)(v)) #define INTPCM32_T(v) ((intpcm32_t)(v)) static const struct { const uint8_t ulaw_to_u8[G711_TABLE_SIZE]; const uint8_t alaw_to_u8[G711_TABLE_SIZE]; const uint8_t u8_to_ulaw[G711_TABLE_SIZE]; const uint8_t u8_to_alaw[G711_TABLE_SIZE]; } xlaw_conv_tables = { ULAW_TO_U8, ALAW_TO_U8, U8_TO_ULAW, U8_TO_ALAW }; /* * Functions for reading/writing PCM integer sample values from bytes array. * Since every process is done using signed integer (and to make our life less * miserable), unsigned sample will be converted to its signed counterpart and * restored during writing back. */ static __always_inline __unused intpcm_t pcm_sample_read(const uint8_t *src, uint32_t fmt) { - intpcm_t v; + intpcm_t v, e, m; + bool s; fmt = AFMT_ENCODING(fmt); switch (fmt) { case AFMT_AC3: v = 0; break; case AFMT_MU_LAW: v = _G711_TO_INTPCM(xlaw_conv_tables.ulaw_to_u8, *src); break; case AFMT_A_LAW: v = _G711_TO_INTPCM(xlaw_conv_tables.alaw_to_u8, *src); break; case AFMT_S8: v = INTPCM_T((int8_t)*src); break; case AFMT_U8: v = INTPCM_T((int8_t)(*src ^ 0x80)); break; case AFMT_S16_LE: v = INTPCM_T(src[0] | (int8_t)src[1] << 8); break; case AFMT_S16_BE: v = INTPCM_T(src[1] | (int8_t)src[0] << 8); break; case AFMT_U16_LE: v = INTPCM_T(src[0] | (int8_t)(src[1] ^ 0x80) << 8); break; case AFMT_U16_BE: v = INTPCM_T(src[1] | (int8_t)(src[0] ^ 0x80) << 8); break; case AFMT_S24_LE: v = INTPCM_T(src[0] | src[1] << 8 | (int8_t)src[2] << 16); break; case AFMT_S24_BE: v = INTPCM_T(src[2] | src[1] << 8 | (int8_t)src[0] << 16); break; case AFMT_U24_LE: v = INTPCM_T(src[0] | src[1] << 8 | (int8_t)(src[2] ^ 0x80) << 16); break; case AFMT_U24_BE: v = INTPCM_T(src[2] | src[1] << 8 | (int8_t)(src[0] ^ 0x80) << 16); break; case AFMT_S32_LE: v = INTPCM_T(src[0] | src[1] << 8 | src[2] << 16 | (int8_t)src[3] << 24); break; case AFMT_S32_BE: v = INTPCM_T(src[3] | src[2] << 8 | src[1] << 16 | (int8_t)src[0] << 24); break; case AFMT_U32_LE: v = INTPCM_T(src[0] | src[1] << 8 | src[2] << 16 | (int8_t)(src[3] ^ 0x80) << 24); break; case AFMT_U32_BE: v = INTPCM_T(src[3] | src[2] << 8 | src[1] << 16 | (int8_t)(src[0] ^ 0x80) << 24); break; + case AFMT_F32_LE: /* FALLTHROUGH */ + case AFMT_F32_BE: + if (fmt == AFMT_F32_LE) { + v = INTPCM_T(src[0] | src[1] << 8 | src[2] << 16 | + (int8_t)src[3] << 24); + } else { + v = INTPCM_T(src[3] | src[2] << 8 | src[1] << 16 | + (int8_t)src[0] << 24); + } + e = (v >> 23) & 0xff; + /* NaN, +/- Inf or too small */ + if (e == 0xff || e < 96) { + v = INTPCM_T(0); + break; + } + s = v & 0x80000000U; + if (e > 126) { + v = INTPCM_T((s == 0) ? PCM_S32_MAX : PCM_S32_MIN); + break; + } + m = 0x800000 | (v & 0x7fffff); + e += 8 - 127; + if (e < 0) + m >>= -e; + else + m <<= e; + v = INTPCM_T((s == 0) ? m : -m); + break; default: v = 0; printf("%s(): unknown format: 0x%08x\n", __func__, fmt); __assert_unreachable(); } return (v); } /* * Read sample and normalize to 32-bit magnitude. */ static __always_inline __unused intpcm_t pcm_sample_read_norm(const uint8_t *src, uint32_t fmt) { return (pcm_sample_read(src, fmt) << (32 - AFMT_BIT(fmt))); } /* * Read sample and restrict magnitude to 24 bits. */ static __always_inline __unused intpcm_t pcm_sample_read_calc(const uint8_t *src, uint32_t fmt) { intpcm_t v; v = pcm_sample_read(src, fmt); #ifndef SND_PCM_64 /* * Dynamic range for humans: ~140db. * * 16bit = 96db (close enough) * 24bit = 144db (perfect) * 32bit = 196db (way too much) * * 24bit is pretty much sufficient for our signed integer processing. * Also, to avoid overflow, we truncate 32bit (and only 32bit) samples * down to 24bit (see below for the reason), unless SND_PCM_64 is * defined. */ if (fmt & AFMT_32BIT) v >>= PCM_FXSHIFT; #endif return (v); } static __always_inline __unused void pcm_sample_write(uint8_t *dst, intpcm_t v, uint32_t fmt) { + intpcm_t r, e; + fmt = AFMT_ENCODING(fmt); + if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) { + if (v == 0) + r = 0; + else if (v == PCM_S32_MAX) + r = 0x3f800000; + else if (v == PCM_S32_MIN) + r = 0x80000000U | 0x3f800000; + else { + r = 0; + if (v < 0) { + r |= 0x80000000U; + v = -v; + } + e = 127 - 8; + while ((v & 0x7f000000) != 0) { + v >>= 1; + e++; + } + while ((v & 0x7f800000) == 0) { + v <<= 1; + e--; + } + r |= (e & 0xff) << 23; + r |= v & 0x7fffff; + } + v = r; + } + switch (fmt) { case AFMT_AC3: *(int16_t *)dst = 0; break; case AFMT_MU_LAW: *dst = _INTPCM_TO_G711(xlaw_conv_tables.u8_to_ulaw, v); break; case AFMT_A_LAW: *dst = _INTPCM_TO_G711(xlaw_conv_tables.u8_to_alaw, v); break; case AFMT_S8: *(int8_t *)dst = v; break; case AFMT_U8: *(int8_t *)dst = v ^ 0x80; break; case AFMT_S16_LE: dst[0] = v; dst[1] = v >> 8; break; case AFMT_S16_BE: dst[1] = v; dst[0] = v >> 8; break; case AFMT_U16_LE: dst[0] = v; dst[1] = (v >> 8) ^ 0x80; break; case AFMT_U16_BE: dst[1] = v; dst[0] = (v >> 8) ^ 0x80; break; case AFMT_S24_LE: dst[0] = v; dst[1] = v >> 8; dst[2] = v >> 16; break; case AFMT_S24_BE: dst[2] = v; dst[1] = v >> 8; dst[0] = v >> 16; break; case AFMT_U24_LE: dst[0] = v; dst[1] = v >> 8; dst[2] = (v >> 16) ^ 0x80; break; case AFMT_U24_BE: dst[2] = v; dst[1] = v >> 8; dst[0] = (v >> 16) ^ 0x80; break; - case AFMT_S32_LE: + case AFMT_S32_LE: /* FALLTHROUGH */ + case AFMT_F32_LE: dst[0] = v; dst[1] = v >> 8; dst[2] = v >> 16; dst[3] = v >> 24; break; - case AFMT_S32_BE: + case AFMT_S32_BE: /* FALLTHROUGH */ + case AFMT_F32_BE: dst[3] = v; dst[2] = v >> 8; dst[1] = v >> 16; dst[0] = v >> 24; break; case AFMT_U32_LE: dst[0] = v; dst[1] = v >> 8; dst[2] = v >> 16; dst[3] = (v >> 24) ^ 0x80; break; case AFMT_U32_BE: dst[3] = v; dst[2] = v >> 8; dst[1] = v >> 16; dst[0] = (v >> 24) ^ 0x80; break; default: printf("%s(): unknown format: 0x%08x\n", __func__, fmt); __assert_unreachable(); } } /* * Write sample and normalize to original magnitude. */ static __always_inline __unused void pcm_sample_write_norm(uint8_t *dst, intpcm_t v, uint32_t fmt) { pcm_sample_write(dst, v >> (32 - AFMT_BIT(fmt)), fmt); } /* * To be used with pcm_sample_read_calc(). */ static __always_inline __unused void pcm_sample_write_calc(uint8_t *dst, intpcm_t v, uint32_t fmt) { #ifndef SND_PCM_64 /* Shift back to 32-bit magnitude. */ if (fmt & AFMT_32BIT) v <<= PCM_FXSHIFT; #endif pcm_sample_write(dst, v, fmt); } static __always_inline __unused intpcm_t pcm_clamp(intpcm32_t sample, uint32_t fmt) { fmt = AFMT_ENCODING(fmt); switch (AFMT_BIT(fmt)) { case 8: return ((sample > PCM_S8_MAX) ? PCM_S8_MAX : ((sample < PCM_S8_MIN) ? PCM_S8_MIN : sample)); case 16: return ((sample > PCM_S16_MAX) ? PCM_S16_MAX : ((sample < PCM_S16_MIN) ? PCM_S16_MIN : sample)); case 24: return ((sample > PCM_S24_MAX) ? PCM_S24_MAX : ((sample < PCM_S24_MIN) ? PCM_S24_MIN : sample)); case 32: return ((sample > PCM_S32_MAX) ? PCM_S32_MAX : ((sample < PCM_S32_MIN) ? PCM_S32_MIN : sample)); default: printf("%s(): unknown format: 0x%08x\n", __func__, fmt); __assert_unreachable(); } } static __always_inline __unused intpcm_t pcm_clamp_calc(intpcm32_t sample, uint32_t fmt) { #ifndef SND_PCM_64 if (fmt & AFMT_32BIT) { return ((sample > PCM_S24_MAX) ? PCM_S32_MAX : ((sample < PCM_S24_MIN) ? PCM_S32_MIN : sample << PCM_FXSHIFT)); } #endif return (pcm_clamp(sample, fmt)); } #endif /* !_SND_PCM_H_ */ diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 637bcbbe1b00..a1370180f350 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,517 +1,520 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER #define SD_F_SIMPLEX 0x00000001 /* unused 0x00000002 */ #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_BUSY 0x00000008 #define SD_F_MPSAFE 0x00000010 #define SD_F_REGISTERED 0x00000020 #define SD_F_BITPERFECT 0x00000040 #define SD_F_VPC 0x00000080 /* volume-per-channel */ #define SD_F_EQ 0x00000100 /* EQ */ #define SD_F_EQ_ENABLED 0x00000200 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00000400 /* EQ bypassed */ #define SD_F_EQ_PC 0x00000800 /* EQ per-channel */ #define SD_F_PVCHANS 0x00001000 /* Playback vchans enabled */ #define SD_F_RVCHANS 0x00002000 /* Recording vchans enabled */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ /* "\002 */ \ "\003SOFTPCMVOL" \ "\004BUSY" \ "\005MPSAFE" \ "\006REGISTERED" \ "\007BITPERFECT" \ "\010VPC" \ "\011EQ" \ "\012EQ_ENABLED" \ "\013EQ_BYPASSED" \ "\014EQ_PC" \ "\015PVCHANS" \ "\016RVCHANS" \ "\035PRIO_RD" \ "\036PRIO_WR" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && ((x)->flags & SD_F_REGISTERED)) #define PCM_MAXCHANS 10000 #define PCM_CHANCOUNT(d) \ (d->playcount + d->pvchancount + d->reccount + d->rvchancount) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) enum { SND_DEV_CTL = 0, /* Control port /dev/mixer */ SND_DEV_SEQ, /* Sequencer /dev/sequencer */ SND_DEV_MIDIN, /* Raw midi access */ SND_DEV_DSP, /* Digitized voice /dev/dsp */ SND_DEV_STATUS, /* /dev/sndstat */ }; #define DSP_DEFAULT_SPEED 8000 extern int snd_unit; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); void pcm_init(device_t dev, void *devinfo); int pcm_register(device_t dev, char *str); int pcm_unregister(device_t dev); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) int sndstat_register(device_t dev, char *str); int sndstat_unregister(device_t dev); /* These are the function codes assigned to the children of sound cards. */ enum { SCF_PCM, SCF_MIDI, SCF_SYNTH, }; /* * This is the device information struct, used by a bridge device to pass the * device function code to the children. */ struct sndcard_func { int func; /* The function code. */ void *varinfo; /* Bridge-specific information. */ }; /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; struct { SLIST_HEAD(, pcm_channel) head; } primary; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; unsigned flags; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; struct cdev *dsp_dev; uint32_t pvchanrate, pvchanformat, pvchanmode; uint32_t rvchanrate, rvchanformat, rvchanmode; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; struct unrhdr *p_unr; struct unrhdr *vp_unr; struct unrhdr *r_unr; struct unrhdr *vr_unr; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_MODE_MIXER 0x01 #define PCM_MODE_PLAY 0x02 #define PCM_MODE_REC 0x04 #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* !SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #endif /* _KERNEL */ /* make figuring out what a format is easier. got AFMT_STEREO already */ -#define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) +#define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \ + AFMT_F32_LE | AFMT_F32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) -#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ +#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_F32_LE | AFMT_F32_BE | \ + AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) -#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ - AFMT_S16_BE | AFMT_U16_BE) +#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_F32_BE | \ + AFMT_S24_BE | AFMT_U24_BE | AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_CHANNEL_MAX 0x7f #define AFMT_EXTCHANNEL_MASK 0x08000000 #define AFMT_EXTCHANNEL_SHIFT 27 #define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 -#define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) +#define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | \ + AFMT_S32_NE | AFMT_F32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) #endif /* _OS_H_ */ diff --git a/sys/sys/soundcard.h b/sys/sys/soundcard.h index 304ae38c08d4..aab67532503e 100644 --- a/sys/sys/soundcard.h +++ b/sys/sys/soundcard.h @@ -1,2003 +1,2011 @@ /* * soundcard.h */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright by Hannu Savolainen 1993 / 4Front Technologies 1993-2006 * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Unless coordinating changes with 4Front Technologies, do NOT make any * modifications to ioctl commands, types, etc. that would break * compatibility with the OSS API. */ #ifndef _SYS_SOUNDCARD_H_ #define _SYS_SOUNDCARD_H_ /* * If you make modifications to this file, please contact me before * distributing the modified version. There is already enough * diversity in the world. * * Regards, * Hannu Savolainen * hannu@voxware.pp.fi * ********************************************************************** * PS. The Hacker's Guide to VoxWare available from * nic.funet.fi:pub/Linux/ALPHA/sound. The file is * snd-sdk-doc-0.1.ps.gz (gzipped postscript). It contains * some useful information about programming with VoxWare. * (NOTE! The pub/Linux/ALPHA/ directories are hidden. You have * to cd inside them before the files are accessible.) ********************************************************************** */ /* * SOUND_VERSION is only used by the voxware driver. Hopefully apps * should not depend on it, but rather look at the capabilities * of the driver in the kernel! */ #define SOUND_VERSION 301 #define VOXWARE /* does this have any use ? */ /* * Supported card ID numbers (Should be somewhere else? We keep * them here just for compativility with the old driver, but these * constants are of little or no use). */ #define SNDCARD_ADLIB 1 #define SNDCARD_SB 2 #define SNDCARD_PAS 3 #define SNDCARD_GUS 4 #define SNDCARD_MPU401 5 #define SNDCARD_SB16 6 #define SNDCARD_SB16MIDI 7 #define SNDCARD_UART6850 8 #define SNDCARD_GUS16 9 #define SNDCARD_MSS 10 #define SNDCARD_PSS 11 #define SNDCARD_SSCAPE 12 #define SNDCARD_PSS_MPU 13 #define SNDCARD_PSS_MSS 14 #define SNDCARD_SSCAPE_MSS 15 #define SNDCARD_TRXPRO 16 #define SNDCARD_TRXPRO_SB 17 #define SNDCARD_TRXPRO_MPU 18 #define SNDCARD_MAD16 19 #define SNDCARD_MAD16_MPU 20 #define SNDCARD_CS4232 21 #define SNDCARD_CS4232_MPU 22 #define SNDCARD_MAUI 23 #define SNDCARD_PSEUDO_MSS 24 #define SNDCARD_AWE32 25 #define SNDCARD_NSS 26 #define SNDCARD_UART16550 27 #define SNDCARD_OPL 28 #include #include #ifndef _IOWR #include #endif /* !_IOWR */ /* * The first part of this file contains the new FreeBSD sound ioctl * interface. Tries to minimize the number of different ioctls, and * to be reasonably general. * * 970821: some of the new calls have not been implemented yet. */ /* * the following three calls extend the generic file descriptor * interface. AIONWRITE is the dual of FIONREAD, i.e. returns the max * number of bytes for a write operation to be non-blocking. * * AIOGSIZE/AIOSSIZE are used to change the behaviour of the device, * from a character device (default) to a block device. In block mode, * (not to be confused with blocking mode) the main difference for the * application is that select() will return only when a complete * block can be read/written to the device, whereas in character mode * select will return true when one byte can be exchanged. For audio * devices, character mode makes select almost useless since one byte * will always be ready by the next sample time (which is often only a * handful of microseconds away). * Use a size of 0 or 1 to return to character mode. */ #define AIONWRITE _IOR('A', 10, int) /* get # bytes to write */ struct snd_size { int play_size; int rec_size; }; #define AIOGSIZE _IOR('A', 11, struct snd_size)/* read current blocksize */ #define AIOSSIZE _IOWR('A', 11, struct snd_size) /* sets blocksize */ /* * The following constants define supported audio formats. The * encoding follows voxware conventions, i.e. 1 bit for each supported * format. We extend it by using bit 31 (RO) to indicate full-duplex * capability, and bit 29 (RO) to indicate that the card supports/ * needs different formats on capture & playback channels. * Bit 29 (RW) is used to indicate/ask stereo. * * The number of bits required to store the sample is: * o 4 bits for the IDA ADPCM format, * o 8 bits for 8-bit formats, mu-law and A-law, * o 16 bits for the 16-bit formats, and * o 32 bits for the 24/32-bit formats. * o undefined for the MPEG audio format. */ #define AFMT_QUERY 0x00000000 /* Return current format */ #define AFMT_MU_LAW 0x00000001 /* Logarithmic mu-law */ #define AFMT_A_LAW 0x00000002 /* Logarithmic A-law */ #define AFMT_IMA_ADPCM 0x00000004 /* A 4:1 compressed format where 16-bit * squence represented using the * the average 4 bits per sample */ #define AFMT_U8 0x00000008 /* Unsigned 8-bit */ #define AFMT_S16_LE 0x00000010 /* Little endian signed 16-bit */ #define AFMT_S16_BE 0x00000020 /* Big endian signed 16-bit */ #define AFMT_S8 0x00000040 /* Signed 8-bit */ #define AFMT_U16_LE 0x00000080 /* Little endian unsigned 16-bit */ #define AFMT_U16_BE 0x00000100 /* Big endian unsigned 16-bit */ #define AFMT_MPEG 0x00000200 /* MPEG MP2/MP3 audio */ #define AFMT_AC3 0x00000400 /* Dolby Digital AC3 */ /* * 32-bit formats below used for 24-bit audio data where the data is stored * in the 24 most significant bits and the least significant bits are not used * (should be set to 0). */ #define AFMT_S32_LE 0x00001000 /* Little endian signed 32-bit */ #define AFMT_S32_BE 0x00002000 /* Big endian signed 32-bit */ #define AFMT_U32_LE 0x00004000 /* Little endian unsigned 32-bit */ #define AFMT_U32_BE 0x00008000 /* Big endian unsigned 32-bit */ #define AFMT_S24_LE 0x00010000 /* Little endian signed 24-bit */ #define AFMT_S24_BE 0x00020000 /* Big endian signed 24-bit */ #define AFMT_U24_LE 0x00040000 /* Little endian unsigned 24-bit */ #define AFMT_U24_BE 0x00080000 /* Big endian unsigned 24-bit */ +#define AFMT_F32_LE 0x10000000 /* Little endian 32-bit floating point */ +#define AFMT_F32_BE 0x20000000 /* Big endian 32-bit floating point */ /* Machine dependent AFMT_* definitions. */ #if BYTE_ORDER == LITTLE_ENDIAN #define AFMT_S16_NE AFMT_S16_LE #define AFMT_S24_NE AFMT_S24_LE #define AFMT_S32_NE AFMT_S32_LE #define AFMT_U16_NE AFMT_U16_LE #define AFMT_U24_NE AFMT_U24_LE #define AFMT_U32_NE AFMT_U32_LE #define AFMT_S16_OE AFMT_S16_BE #define AFMT_S24_OE AFMT_S24_BE #define AFMT_S32_OE AFMT_S32_BE #define AFMT_U16_OE AFMT_U16_BE #define AFMT_U24_OE AFMT_U24_BE #define AFMT_U32_OE AFMT_U32_BE +#define AFMT_F32_NE AFMT_F32_LE +#define AFMT_F32_OE AFMT_F32_BE #else #define AFMT_S16_OE AFMT_S16_LE #define AFMT_S24_OE AFMT_S24_LE #define AFMT_S32_OE AFMT_S32_LE #define AFMT_U16_OE AFMT_U16_LE #define AFMT_U24_OE AFMT_U24_LE #define AFMT_U32_OE AFMT_U32_LE #define AFMT_S16_NE AFMT_S16_BE #define AFMT_S24_NE AFMT_S24_BE #define AFMT_S32_NE AFMT_S32_BE #define AFMT_U16_NE AFMT_U16_BE #define AFMT_U24_NE AFMT_U24_BE #define AFMT_U32_NE AFMT_U32_BE +#define AFMT_F32_NE AFMT_F32_BE +#define AFMT_F32_OE AFMT_F32_LE #endif +#define AFMT_FLOAT AFMT_F32_NE /* compatibility alias */ + #define AFMT_STEREO 0x10000000 /* can do/want stereo */ /* * the following are really capabilities */ #define AFMT_WEIRD 0x20000000 /* weird hardware... */ /* * AFMT_WEIRD reports that the hardware might need to operate * with different formats in the playback and capture * channels when operating in full duplex. * As an example, SoundBlaster16 cards only support U8 in one * direction and S16 in the other one, and applications should * be aware of this limitation. */ #define AFMT_FULLDUPLEX 0x80000000 /* can do full duplex */ /* * The following structure is used to get/set format and sampling rate. * While it would be better to have things such as stereo, bits per * sample, endiannes, etc split in different variables, it turns out * that formats are not that many, and not all combinations are possible. * So we followed the Voxware approach of associating one bit to each * format. */ typedef struct _snd_chan_param { u_long play_rate; /* sampling rate */ u_long rec_rate; /* sampling rate */ u_long play_format; /* everything describing the format */ u_long rec_format; /* everything describing the format */ } snd_chan_param; #define AIOGFMT _IOR('f', 12, snd_chan_param) /* get format */ #define AIOSFMT _IOWR('f', 12, snd_chan_param) /* sets format */ /* * The following structure is used to get/set the mixer setting. * Up to 32 mixers are supported, each one with up to 32 channels. */ typedef struct _snd_mix_param { u_char subdev; /* which output */ u_char line; /* which input */ u_char left,right; /* volumes, 0..255, 0 = mute */ } snd_mix_param ; /* XXX AIOGMIX, AIOSMIX not implemented yet */ #define AIOGMIX _IOWR('A', 13, snd_mix_param) /* return mixer status */ #define AIOSMIX _IOWR('A', 14, snd_mix_param) /* sets mixer status */ /* * channel specifiers used in AIOSTOP and AIOSYNC */ #define AIOSYNC_PLAY 0x1 /* play chan */ #define AIOSYNC_CAPTURE 0x2 /* capture chan */ /* AIOSTOP stop & flush a channel, returns the residual count */ #define AIOSTOP _IOWR ('A', 15, int) /* alternate method used to notify the sync condition */ #define AIOSYNC_SIGNAL 0x100 #define AIOSYNC_SELECT 0x200 /* what the 'pos' field refers to */ #define AIOSYNC_READY 0x400 #define AIOSYNC_FREE 0x800 typedef struct _snd_sync_parm { long chan ; /* play or capture channel, plus modifier */ long pos; } snd_sync_parm; #define AIOSYNC _IOWR ('A', 15, snd_sync_parm) /* misc. synchronization */ /* * The following is used to return device capabilities. If the structure * passed to the ioctl is zeroed, default values are returned for rate * and formats, a bitmap of available mixers is returned, and values * (inputs, different levels) for the first one are returned. * * If formats, mixers, inputs are instantiated, then detailed info * are returned depending on the call. */ typedef struct _snd_capabilities { u_long rate_min, rate_max; /* min-max sampling rate */ u_long formats; u_long bufsize; /* DMA buffer size */ u_long mixers; /* bitmap of available mixers */ u_long inputs; /* bitmap of available inputs (per mixer) */ u_short left, right; /* how many levels are supported */ } snd_capabilities; #define AIOGCAP _IOWR('A', 15, snd_capabilities) /* get capabilities */ /* * here is the old (Voxware) ioctl interface */ /* * IOCTL Commands for /dev/sequencer */ #define SNDCTL_SEQ_RESET _IO ('Q', 0) #define SNDCTL_SEQ_SYNC _IO ('Q', 1) #define SNDCTL_SYNTH_INFO _IOWR('Q', 2, struct synth_info) #define SNDCTL_SEQ_CTRLRATE _IOWR('Q', 3, int) /* Set/get timer res.(hz) */ #define SNDCTL_SEQ_GETOUTCOUNT _IOR ('Q', 4, int) #define SNDCTL_SEQ_GETINCOUNT _IOR ('Q', 5, int) #define SNDCTL_SEQ_PERCMODE _IOW ('Q', 6, int) #define SNDCTL_FM_LOAD_INSTR _IOW ('Q', 7, struct sbi_instrument) /* Valid for FM only */ #define SNDCTL_SEQ_TESTMIDI _IOW ('Q', 8, int) #define SNDCTL_SEQ_RESETSAMPLES _IOW ('Q', 9, int) #define SNDCTL_SEQ_NRSYNTHS _IOR ('Q',10, int) #define SNDCTL_SEQ_NRMIDIS _IOR ('Q',11, int) #define SNDCTL_MIDI_INFO _IOWR('Q',12, struct midi_info) #define SNDCTL_SEQ_THRESHOLD _IOW ('Q',13, int) #define SNDCTL_SEQ_TRESHOLD SNDCTL_SEQ_THRESHOLD /* there was once a typo */ #define SNDCTL_SYNTH_MEMAVL _IOWR('Q',14, int) /* in=dev#, out=memsize */ #define SNDCTL_FM_4OP_ENABLE _IOW ('Q',15, int) /* in=dev# */ #define SNDCTL_PMGR_ACCESS _IOWR('Q',16, struct patmgr_info) #define SNDCTL_SEQ_PANIC _IO ('Q',17) #define SNDCTL_SEQ_OUTOFBAND _IOW ('Q',18, struct seq_event_rec) #define SNDCTL_SEQ_GETTIME _IOR ('Q',19, int) struct seq_event_rec { u_char arr[8]; }; #define SNDCTL_TMR_TIMEBASE _IOWR('T', 1, int) #define SNDCTL_TMR_START _IO ('T', 2) #define SNDCTL_TMR_STOP _IO ('T', 3) #define SNDCTL_TMR_CONTINUE _IO ('T', 4) #define SNDCTL_TMR_TEMPO _IOWR('T', 5, int) #define SNDCTL_TMR_SOURCE _IOWR('T', 6, int) # define TMR_INTERNAL 0x00000001 # define TMR_EXTERNAL 0x00000002 # define TMR_MODE_MIDI 0x00000010 # define TMR_MODE_FSK 0x00000020 # define TMR_MODE_CLS 0x00000040 # define TMR_MODE_SMPTE 0x00000080 #define SNDCTL_TMR_METRONOME _IOW ('T', 7, int) #define SNDCTL_TMR_SELECT _IOW ('T', 8, int) /* * Endian aware patch key generation algorithm. */ #if defined(_AIX) || defined(AIX) # define _PATCHKEY(id) (0xfd00|id) #else # define _PATCHKEY(id) ((id<<8)|0xfd) #endif /* * Sample loading mechanism for internal synthesizers (/dev/sequencer) * The following patch_info structure has been designed to support * Gravis UltraSound. It tries to be universal format for uploading * sample based patches but is probably too limited. */ struct patch_info { /* u_short key; Use GUS_PATCH here */ short key; /* Use GUS_PATCH here */ #define GUS_PATCH _PATCHKEY(0x04) #define OBSOLETE_GUS_PATCH _PATCHKEY(0x02) short device_no; /* Synthesizer number */ short instr_no; /* Midi pgm# */ u_long mode; /* * The least significant byte has the same format than the GUS .PAT * files */ #define WAVE_16_BITS 0x01 /* bit 0 = 8 or 16 bit wave data. */ #define WAVE_UNSIGNED 0x02 /* bit 1 = Signed - Unsigned data. */ #define WAVE_LOOPING 0x04 /* bit 2 = looping enabled-1. */ #define WAVE_BIDIR_LOOP 0x08 /* bit 3 = Set is bidirectional looping. */ #define WAVE_LOOP_BACK 0x10 /* bit 4 = Set is looping backward. */ #define WAVE_SUSTAIN_ON 0x20 /* bit 5 = Turn sustaining on. (Env. pts. 3)*/ #define WAVE_ENVELOPES 0x40 /* bit 6 = Enable envelopes - 1 */ /* (use the env_rate/env_offs fields). */ /* Linux specific bits */ #define WAVE_VIBRATO 0x00010000 /* The vibrato info is valid */ #define WAVE_TREMOLO 0x00020000 /* The tremolo info is valid */ #define WAVE_SCALE 0x00040000 /* The scaling info is valid */ /* Other bits must be zeroed */ long len; /* Size of the wave data in bytes */ long loop_start, loop_end; /* Byte offsets from the beginning */ /* * The base_freq and base_note fields are used when computing the * playback speed for a note. The base_note defines the tone frequency * which is heard if the sample is played using the base_freq as the * playback speed. * * The low_note and high_note fields define the minimum and maximum note * frequencies for which this sample is valid. It is possible to define * more than one samples for an instrument number at the same time. The * low_note and high_note fields are used to select the most suitable one. * * The fields base_note, high_note and low_note should contain * the note frequency multiplied by 1000. For example value for the * middle A is 440*1000. */ u_int base_freq; u_long base_note; u_long high_note; u_long low_note; int panning; /* -128=left, 127=right */ int detuning; /* New fields introduced in version 1.99.5 */ /* Envelope. Enabled by mode bit WAVE_ENVELOPES */ u_char env_rate[ 6 ]; /* GUS HW ramping rate */ u_char env_offset[ 6 ]; /* 255 == 100% */ /* * The tremolo, vibrato and scale info are not supported yet. * Enable by setting the mode bits WAVE_TREMOLO, WAVE_VIBRATO or * WAVE_SCALE */ u_char tremolo_sweep; u_char tremolo_rate; u_char tremolo_depth; u_char vibrato_sweep; u_char vibrato_rate; u_char vibrato_depth; int scale_frequency; u_int scale_factor; /* from 0 to 2048 or 0 to 2 */ int volume; int spare[4]; char data[1]; /* The waveform data starts here */ }; struct sysex_info { short key; /* Use GUS_PATCH here */ #define SYSEX_PATCH _PATCHKEY(0x05) #define MAUI_PATCH _PATCHKEY(0x06) short device_no; /* Synthesizer number */ long len; /* Size of the sysex data in bytes */ u_char data[1]; /* Sysex data starts here */ }; /* * Patch management interface (/dev/sequencer, /dev/patmgr#) * Don't use these calls if you want to maintain compatibility with * the future versions of the driver. */ #define PS_NO_PATCHES 0 /* No patch support on device */ #define PS_MGR_NOT_OK 1 /* Plain patch support (no mgr) */ #define PS_MGR_OK 2 /* Patch manager supported */ #define PS_MANAGED 3 /* Patch manager running */ #define SNDCTL_PMGR_IFACE _IOWR('P', 1, struct patmgr_info) /* * The patmgr_info is a fixed size structure which is used for two * different purposes. The intended use is for communication between * the application using /dev/sequencer and the patch manager daemon * associated with a synthesizer device (ioctl(SNDCTL_PMGR_ACCESS)). * * This structure is also used with ioctl(SNDCTL_PGMR_IFACE) which allows * a patch manager daemon to read and write device parameters. This * ioctl available through /dev/sequencer also. Avoid using it since it's * extremely hardware dependent. In addition access through /dev/sequencer * may confuse the patch manager daemon. */ struct patmgr_info { /* Note! size must be < 4k since kmalloc() is used */ u_long key; /* Don't worry. Reserved for communication between the patch manager and the driver. */ #define PM_K_EVENT 1 /* Event from the /dev/sequencer driver */ #define PM_K_COMMAND 2 /* Request from an application */ #define PM_K_RESPONSE 3 /* From patmgr to application */ #define PM_ERROR 4 /* Error returned by the patmgr */ int device; int command; /* * Commands 0x000 to 0xfff reserved for patch manager programs */ #define PM_GET_DEVTYPE 1 /* Returns type of the patch mgr interface of dev */ #define PMTYPE_FM2 1 /* 2 OP fm */ #define PMTYPE_FM4 2 /* Mixed 4 or 2 op FM (OPL-3) */ #define PMTYPE_WAVE 3 /* Wave table synthesizer (GUS) */ #define PM_GET_NRPGM 2 /* Returns max # of midi programs in parm1 */ #define PM_GET_PGMMAP 3 /* Returns map of loaded midi programs in data8 */ #define PM_GET_PGM_PATCHES 4 /* Return list of patches of a program (parm1) */ #define PM_GET_PATCH 5 /* Return patch header of patch parm1 */ #define PM_SET_PATCH 6 /* Set patch header of patch parm1 */ #define PM_READ_PATCH 7 /* Read patch (wave) data */ #define PM_WRITE_PATCH 8 /* Write patch (wave) data */ /* * Commands 0x1000 to 0xffff are for communication between the patch manager * and the client */ #define _PM_LOAD_PATCH 0x100 /* * Commands above 0xffff reserved for device specific use */ long parm1; long parm2; long parm3; union { u_char data8[4000]; u_short data16[2000]; u_long data32[1000]; struct patch_info patch; } data; }; /* * When a patch manager daemon is present, it will be informed by the * driver when something important happens. For example when the * /dev/sequencer is opened or closed. A record with key == PM_K_EVENT is * returned. The command field contains the event type: */ #define PM_E_OPENED 1 /* /dev/sequencer opened */ #define PM_E_CLOSED 2 /* /dev/sequencer closed */ #define PM_E_PATCH_RESET 3 /* SNDCTL_RESETSAMPLES called */ #define PM_E_PATCH_LOADED 4 /* A patch has been loaded by appl */ /* * /dev/sequencer input events. * * The data written to the /dev/sequencer is a stream of events. Events * are records of 4 or 8 bytes. The first byte defines the size. * Any number of events can be written with a write call. There * is a set of macros for sending these events. Use these macros if you * want to maximize portability of your program. * * Events SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO. Are also input events. * (All input events are currently 4 bytes long. Be prepared to support * 8 byte events also. If you receive any event having first byte >= 128, * it's a 8 byte event. * * The events are documented at the end of this file. * * Normal events (4 bytes) * There is also a 8 byte version of most of the 4 byte events. The * 8 byte one is recommended. */ #define SEQ_NOTEOFF 0 #define SEQ_FMNOTEOFF SEQ_NOTEOFF /* Just old name */ #define SEQ_NOTEON 1 #define SEQ_FMNOTEON SEQ_NOTEON #define SEQ_WAIT TMR_WAIT_ABS #define SEQ_PGMCHANGE 3 #define SEQ_FMPGMCHANGE SEQ_PGMCHANGE #define SEQ_SYNCTIMER TMR_START #define SEQ_MIDIPUTC 5 #define SEQ_DRUMON 6 /*** OBSOLETE ***/ #define SEQ_DRUMOFF 7 /*** OBSOLETE ***/ #define SEQ_ECHO TMR_ECHO /* For synching programs with output */ #define SEQ_AFTERTOUCH 9 #define SEQ_CONTROLLER 10 /* * Midi controller numbers * * Controllers 0 to 31 (0x00 to 0x1f) and 32 to 63 (0x20 to 0x3f) * are continuous controllers. * In the MIDI 1.0 these controllers are sent using two messages. * Controller numbers 0 to 31 are used to send the MSB and the * controller numbers 32 to 63 are for the LSB. Note that just 7 bits * are used in MIDI bytes. */ #define CTL_BANK_SELECT 0x00 #define CTL_MODWHEEL 0x01 #define CTL_BREATH 0x02 /* undefined 0x03 */ #define CTL_FOOT 0x04 #define CTL_PORTAMENTO_TIME 0x05 #define CTL_DATA_ENTRY 0x06 #define CTL_MAIN_VOLUME 0x07 #define CTL_BALANCE 0x08 /* undefined 0x09 */ #define CTL_PAN 0x0a #define CTL_EXPRESSION 0x0b /* undefined 0x0c - 0x0f */ #define CTL_GENERAL_PURPOSE1 0x10 #define CTL_GENERAL_PURPOSE2 0x11 #define CTL_GENERAL_PURPOSE3 0x12 #define CTL_GENERAL_PURPOSE4 0x13 /* undefined 0x14 - 0x1f */ /* undefined 0x20 */ /* * The controller numbers 0x21 to 0x3f are reserved for the * least significant bytes of the controllers 0x00 to 0x1f. * These controllers are not recognised by the driver. * * Controllers 64 to 69 (0x40 to 0x45) are on/off switches. * 0=OFF and 127=ON (intermediate values are possible) */ #define CTL_DAMPER_PEDAL 0x40 #define CTL_SUSTAIN CTL_DAMPER_PEDAL /* Alias */ #define CTL_HOLD CTL_DAMPER_PEDAL /* Alias */ #define CTL_PORTAMENTO 0x41 #define CTL_SOSTENUTO 0x42 #define CTL_SOFT_PEDAL 0x43 /* undefined 0x44 */ #define CTL_HOLD2 0x45 /* undefined 0x46 - 0x4f */ #define CTL_GENERAL_PURPOSE5 0x50 #define CTL_GENERAL_PURPOSE6 0x51 #define CTL_GENERAL_PURPOSE7 0x52 #define CTL_GENERAL_PURPOSE8 0x53 /* undefined 0x54 - 0x5a */ #define CTL_EXT_EFF_DEPTH 0x5b #define CTL_TREMOLO_DEPTH 0x5c #define CTL_CHORUS_DEPTH 0x5d #define CTL_DETUNE_DEPTH 0x5e #define CTL_CELESTE_DEPTH CTL_DETUNE_DEPTH /* Alias for the above one */ #define CTL_PHASER_DEPTH 0x5f #define CTL_DATA_INCREMENT 0x60 #define CTL_DATA_DECREMENT 0x61 #define CTL_NONREG_PARM_NUM_LSB 0x62 #define CTL_NONREG_PARM_NUM_MSB 0x63 #define CTL_REGIST_PARM_NUM_LSB 0x64 #define CTL_REGIST_PARM_NUM_MSB 0x65 /* undefined 0x66 - 0x78 */ /* reserved 0x79 - 0x7f */ /* Pseudo controllers (not midi compatible) */ #define CTRL_PITCH_BENDER 255 #define CTRL_PITCH_BENDER_RANGE 254 #define CTRL_EXPRESSION 253 /* Obsolete */ #define CTRL_MAIN_VOLUME 252 /* Obsolete */ #define SEQ_BALANCE 11 #define SEQ_VOLMODE 12 /* * Volume mode decides how volumes are used */ #define VOL_METHOD_ADAGIO 1 #define VOL_METHOD_LINEAR 2 /* * Note! SEQ_WAIT, SEQ_MIDIPUTC and SEQ_ECHO are used also as * input events. */ /* * Event codes 0xf0 to 0xfc are reserved for future extensions. */ #define SEQ_FULLSIZE 0xfd /* Long events */ /* * SEQ_FULLSIZE events are used for loading patches/samples to the * synthesizer devices. These events are passed directly to the driver * of the associated synthesizer device. There is no limit to the size * of the extended events. These events are not queued but executed * immediately when the write() is called (execution can take several * seconds of time). * * When a SEQ_FULLSIZE message is written to the device, it must * be written using exactly one write() call. Other events cannot * be mixed to the same write. * * For FM synths (YM3812/OPL3) use struct sbi_instrument and write * it to the /dev/sequencer. Don't write other data together with * the instrument structure Set the key field of the structure to * FM_PATCH. The device field is used to route the patch to the * corresponding device. * * For Gravis UltraSound use struct patch_info. Initialize the key field * to GUS_PATCH. */ #define SEQ_PRIVATE 0xfe /* Low level HW dependent events (8 bytes) */ #define SEQ_EXTENDED 0xff /* Extended events (8 bytes) OBSOLETE */ /* * Record for FM patches */ typedef u_char sbi_instr_data[32]; struct sbi_instrument { u_short key; /* FM_PATCH or OPL3_PATCH */ #define FM_PATCH _PATCHKEY(0x01) #define OPL3_PATCH _PATCHKEY(0x03) short device; /* Synth# (0-4) */ int channel; /* Program# to be initialized */ sbi_instr_data operators; /* Reg. settings for operator cells * (.SBI format) */ }; struct synth_info { /* Read only */ char name[30]; int device; /* 0-N. INITIALIZE BEFORE CALLING */ int synth_type; #define SYNTH_TYPE_FM 0 #define SYNTH_TYPE_SAMPLE 1 #define SYNTH_TYPE_MIDI 2 /* Midi interface */ int synth_subtype; #define FM_TYPE_ADLIB 0x00 #define FM_TYPE_OPL3 0x01 #define MIDI_TYPE_MPU401 0x401 #define SAMPLE_TYPE_BASIC 0x10 #define SAMPLE_TYPE_GUS SAMPLE_TYPE_BASIC #define SAMPLE_TYPE_AWE32 0x20 int perc_mode; /* No longer supported */ int nr_voices; int nr_drums; /* Obsolete field */ int instr_bank_size; u_long capabilities; #define SYNTH_CAP_PERCMODE 0x00000001 /* No longer used */ #define SYNTH_CAP_OPL3 0x00000002 /* Set if OPL3 supported */ #define SYNTH_CAP_INPUT 0x00000004 /* Input (MIDI) device */ int dummies[19]; /* Reserve space */ }; struct sound_timer_info { char name[32]; int caps; }; struct midi_info { char name[30]; int device; /* 0-N. INITIALIZE BEFORE CALLING */ u_long capabilities; /* To be defined later */ int dev_type; int dummies[18]; /* Reserve space */ }; /* * ioctl commands for the /dev/midi## */ typedef struct { u_char cmd; char nr_args, nr_returns; u_char data[30]; } mpu_command_rec; #define SNDCTL_MIDI_PRETIME _IOWR('m', 0, int) #define SNDCTL_MIDI_MPUMODE _IOWR('m', 1, int) #define SNDCTL_MIDI_MPUCMD _IOWR('m', 2, mpu_command_rec) #define MIOSPASSTHRU _IOWR('m', 3, int) #define MIOGPASSTHRU _IOWR('m', 4, int) /* * IOCTL commands for /dev/dsp and /dev/audio */ #define SNDCTL_DSP_HALT _IO ('P', 0) #define SNDCTL_DSP_RESET SNDCTL_DSP_HALT #define SNDCTL_DSP_SYNC _IO ('P', 1) #define SNDCTL_DSP_SPEED _IOWR('P', 2, int) #define SNDCTL_DSP_STEREO _IOWR('P', 3, int) #define SNDCTL_DSP_GETBLKSIZE _IOR('P', 4, int) #define SNDCTL_DSP_SETBLKSIZE _IOW('P', 4, int) #define SNDCTL_DSP_SETFMT _IOWR('P',5, int) /* Selects ONE fmt*/ /* * SOUND_PCM_WRITE_CHANNELS is not that different * from SNDCTL_DSP_STEREO */ #define SOUND_PCM_WRITE_CHANNELS _IOWR('P', 6, int) #define SNDCTL_DSP_CHANNELS SOUND_PCM_WRITE_CHANNELS #define SOUND_PCM_WRITE_FILTER _IOWR('P', 7, int) #define SNDCTL_DSP_POST _IO ('P', 8) /* * SNDCTL_DSP_SETBLKSIZE and the following two calls mostly do * the same thing, i.e. set the block size used in DMA transfers. */ #define SNDCTL_DSP_SUBDIVIDE _IOWR('P', 9, int) #define SNDCTL_DSP_SETFRAGMENT _IOWR('P',10, int) #define SNDCTL_DSP_GETFMTS _IOR ('P',11, int) /* Returns a mask */ /* * Buffer status queries. */ typedef struct audio_buf_info { int fragments; /* # of avail. frags (partly used ones not counted) */ int fragstotal; /* Total # of fragments allocated */ int fragsize; /* Size of a fragment in bytes */ int bytes; /* Avail. space in bytes (includes partly used fragments) */ /* Note! 'bytes' could be more than fragments*fragsize */ } audio_buf_info; #define SNDCTL_DSP_GETOSPACE _IOR ('P',12, audio_buf_info) #define SNDCTL_DSP_GETISPACE _IOR ('P',13, audio_buf_info) /* * SNDCTL_DSP_NONBLOCK is the same (but less powerful, since the * action cannot be undone) of FIONBIO. The same can be achieved * by opening the device with O_NDELAY */ #define SNDCTL_DSP_NONBLOCK _IO ('P',14) #define SNDCTL_DSP_GETCAPS _IOR ('P',15, int) # define PCM_CAP_REVISION 0x000000ff /* Bits for revision level (0 to 255) */ # define PCM_CAP_DUPLEX 0x00000100 /* Full duplex record/playback */ # define PCM_CAP_REALTIME 0x00000200 /* Not in use */ # define PCM_CAP_BATCH 0x00000400 /* Device has some kind of */ /* internal buffers which may */ /* cause some delays and */ /* decrease precision of timing */ # define PCM_CAP_COPROC 0x00000800 /* Has a coprocessor */ /* Sometimes it's a DSP */ /* but usually not */ # define PCM_CAP_TRIGGER 0x00001000 /* Supports SETTRIGGER */ # define PCM_CAP_MMAP 0x00002000 /* Supports mmap() */ # define PCM_CAP_MULTI 0x00004000 /* Supports multiple open */ # define PCM_CAP_BIND 0x00008000 /* Supports binding to front/rear/center/lfe */ # define PCM_CAP_INPUT 0x00010000 /* Supports recording */ # define PCM_CAP_OUTPUT 0x00020000 /* Supports playback */ # define PCM_CAP_VIRTUAL 0x00040000 /* Virtual device */ /* 0x00040000 and 0x00080000 reserved for future use */ /* Analog/digital control capabilities */ # define PCM_CAP_ANALOGOUT 0x00100000 # define PCM_CAP_ANALOGIN 0x00200000 # define PCM_CAP_DIGITALOUT 0x00400000 # define PCM_CAP_DIGITALIN 0x00800000 # define PCM_CAP_ADMASK 0x00f00000 /* * NOTE! (capabilities & PCM_CAP_ADMASK)==0 means just that the * digital/analog interface control features are not supported by the * device/driver. However the device still supports analog, digital or * both inputs/outputs (depending on the device). See the OSS Programmer's * Guide for full details. */ # define PCM_CAP_SPECIAL 0x01000000 /* Not for ordinary "multimedia" use */ # define PCM_CAP_SHADOW 0x00000000 /* OBSOLETE */ /* * Preferred channel usage. These bits can be used to * give recommendations to the application. Used by few drivers. * For example if ((caps & DSP_CH_MASK) == DSP_CH_MONO) means that * the device works best in mono mode. However it doesn't necessarily mean * that the device cannot be used in stereo. These bits should only be used * by special applications such as multi track hard disk recorders to find * out the initial setup. However the user should be able to override this * selection. * * To find out which modes are actually supported the application should * try to select them using SNDCTL_DSP_CHANNELS. */ # define DSP_CH_MASK 0x06000000 /* Mask */ # define DSP_CH_ANY 0x00000000 /* No preferred mode */ # define DSP_CH_MONO 0x02000000 # define DSP_CH_STEREO 0x04000000 # define DSP_CH_MULTI 0x06000000 /* More than two channels */ # define PCM_CAP_HIDDEN 0x08000000 /* Hidden device */ # define PCM_CAP_FREERATE 0x10000000 # define PCM_CAP_MODEM 0x20000000 /* Modem device */ # define PCM_CAP_DEFAULT 0x40000000 /* "Default" device */ /* * The PCM_CAP_* capability names were known as DSP_CAP_* prior OSS 4.0 * so it's necessary to define the older names too. */ #define DSP_CAP_ADMASK PCM_CAP_ADMASK #define DSP_CAP_ANALOGIN PCM_CAP_ANALOGIN #define DSP_CAP_ANALOGOUT PCM_CAP_ANALOGOUT #define DSP_CAP_BATCH PCM_CAP_BATCH #define DSP_CAP_BIND PCM_CAP_BIND #define DSP_CAP_COPROC PCM_CAP_COPROC #define DSP_CAP_DEFAULT PCM_CAP_DEFAULT #define DSP_CAP_DIGITALIN PCM_CAP_DIGITALIN #define DSP_CAP_DIGITALOUT PCM_CAP_DIGITALOUT #define DSP_CAP_DUPLEX PCM_CAP_DUPLEX #define DSP_CAP_FREERATE PCM_CAP_FREERATE #define DSP_CAP_HIDDEN PCM_CAP_HIDDEN #define DSP_CAP_INPUT PCM_CAP_INPUT #define DSP_CAP_MMAP PCM_CAP_MMAP #define DSP_CAP_MODEM PCM_CAP_MODEM #define DSP_CAP_MULTI PCM_CAP_MULTI #define DSP_CAP_OUTPUT PCM_CAP_OUTPUT #define DSP_CAP_REALTIME PCM_CAP_REALTIME #define DSP_CAP_REVISION PCM_CAP_REVISION #define DSP_CAP_SHADOW PCM_CAP_SHADOW #define DSP_CAP_TRIGGER PCM_CAP_TRIGGER #define DSP_CAP_VIRTUAL PCM_CAP_VIRTUAL /* * What do these function do ? */ #define SNDCTL_DSP_GETTRIGGER _IOR ('P',16, int) #define SNDCTL_DSP_SETTRIGGER _IOW ('P',16, int) #define PCM_ENABLE_INPUT 0x00000001 #define PCM_ENABLE_OUTPUT 0x00000002 typedef struct count_info { int bytes; /* Total # of bytes processed */ int blocks; /* # of fragment transitions since last time */ int ptr; /* Current DMA pointer value */ } count_info; /* * GETIPTR and GETISPACE are not that different... same for out. */ #define SNDCTL_DSP_GETIPTR _IOR ('P',17, count_info) #define SNDCTL_DSP_GETOPTR _IOR ('P',18, count_info) typedef struct buffmem_desc { caddr_t buffer; int size; } buffmem_desc; #define SNDCTL_DSP_MAPINBUF _IOR ('P', 19, buffmem_desc) #define SNDCTL_DSP_MAPOUTBUF _IOR ('P', 20, buffmem_desc) #define SNDCTL_DSP_SETSYNCRO _IO ('P', 21) #define SNDCTL_DSP_SETDUPLEX _IO ('P', 22) #define SNDCTL_DSP_GETODELAY _IOR ('P', 23, int) /* * I guess these are the readonly version of the same * functions that exist above as SNDCTL_DSP_... */ #define SOUND_PCM_READ_RATE _IOR ('P', 2, int) #define SOUND_PCM_READ_CHANNELS _IOR ('P', 6, int) #define SOUND_PCM_READ_BITS _IOR ('P', 5, int) #define SOUND_PCM_READ_FILTER _IOR ('P', 7, int) /* * ioctl calls to be used in communication with coprocessors and * DSP chips. */ typedef struct copr_buffer { int command; /* Set to 0 if not used */ int flags; #define CPF_NONE 0x0000 #define CPF_FIRST 0x0001 /* First block */ #define CPF_LAST 0x0002 /* Last block */ int len; int offs; /* If required by the device (0 if not used) */ u_char data[4000]; /* NOTE! 4000 is not 4k */ } copr_buffer; typedef struct copr_debug_buf { int command; /* Used internally. Set to 0 */ int parm1; int parm2; int flags; int len; /* Length of data in bytes */ } copr_debug_buf; typedef struct copr_msg { int len; u_char data[4000]; } copr_msg; #define SNDCTL_COPR_RESET _IO ('C', 0) #define SNDCTL_COPR_LOAD _IOWR('C', 1, copr_buffer) #define SNDCTL_COPR_RDATA _IOWR('C', 2, copr_debug_buf) #define SNDCTL_COPR_RCODE _IOWR('C', 3, copr_debug_buf) #define SNDCTL_COPR_WDATA _IOW ('C', 4, copr_debug_buf) #define SNDCTL_COPR_WCODE _IOW ('C', 5, copr_debug_buf) #define SNDCTL_COPR_RUN _IOWR('C', 6, copr_debug_buf) #define SNDCTL_COPR_HALT _IOWR('C', 7, copr_debug_buf) #define SNDCTL_COPR_SENDMSG _IOW ('C', 8, copr_msg) #define SNDCTL_COPR_RCVMSG _IOR ('C', 9, copr_msg) /* * IOCTL commands for /dev/mixer */ /* * Mixer devices * * There can be up to 20 different analog mixer channels. The * SOUND_MIXER_NRDEVICES gives the currently supported maximum. * The SOUND_MIXER_READ_DEVMASK returns a bitmask which tells * the devices supported by the particular mixer. */ #define SOUND_MIXER_NRDEVICES 25 #define SOUND_MIXER_VOLUME 0 /* Master output level */ #define SOUND_MIXER_BASS 1 /* Treble level of all output channels */ #define SOUND_MIXER_TREBLE 2 /* Bass level of all output channels */ #define SOUND_MIXER_SYNTH 3 /* Volume of synthesier input */ #define SOUND_MIXER_PCM 4 /* Output level for the audio device */ #define SOUND_MIXER_SPEAKER 5 /* Output level for the PC speaker * signals */ #define SOUND_MIXER_LINE 6 /* Volume level for the line in jack */ #define SOUND_MIXER_MIC 7 /* Volume for the signal coming from * the microphone jack */ #define SOUND_MIXER_CD 8 /* Volume level for the input signal * connected to the CD audio input */ #define SOUND_MIXER_IMIX 9 /* Recording monitor. It controls the * output volume of the selected * recording sources while recording */ #define SOUND_MIXER_ALTPCM 10 /* Volume of the alternative codec * device */ #define SOUND_MIXER_RECLEV 11 /* Global recording level */ #define SOUND_MIXER_IGAIN 12 /* Input gain */ #define SOUND_MIXER_OGAIN 13 /* Output gain */ /* * The AD1848 codec and compatibles have three line level inputs * (line, aux1 and aux2). Since each card manufacturer have assigned * different meanings to these inputs, it's inpractical to assign * specific meanings (line, cd, synth etc.) to them. */ #define SOUND_MIXER_LINE1 14 /* Input source 1 (aux1) */ #define SOUND_MIXER_LINE2 15 /* Input source 2 (aux2) */ #define SOUND_MIXER_LINE3 16 /* Input source 3 (line) */ #define SOUND_MIXER_DIGITAL1 17 /* Digital (input) 1 */ #define SOUND_MIXER_DIGITAL2 18 /* Digital (input) 2 */ #define SOUND_MIXER_DIGITAL3 19 /* Digital (input) 3 */ #define SOUND_MIXER_PHONEIN 20 /* Phone input */ #define SOUND_MIXER_PHONEOUT 21 /* Phone output */ #define SOUND_MIXER_VIDEO 22 /* Video/TV (audio) in */ #define SOUND_MIXER_RADIO 23 /* Radio in */ #define SOUND_MIXER_MONITOR 24 /* Monitor (usually mic) volume */ /* * Some on/off settings (SOUND_SPECIAL_MIN - SOUND_SPECIAL_MAX) * Not counted to SOUND_MIXER_NRDEVICES, but use the same number space */ #define SOUND_ONOFF_MIN 28 #define SOUND_ONOFF_MAX 30 #define SOUND_MIXER_MUTE 28 /* 0 or 1 */ #define SOUND_MIXER_ENHANCE 29 /* Enhanced stereo (0, 40, 60 or 80) */ #define SOUND_MIXER_LOUD 30 /* 0 or 1 */ /* Note! Number 31 cannot be used since the sign bit is reserved */ #define SOUND_MIXER_NONE 31 #define SOUND_DEVICE_LABELS { \ "Vol ", "Bass ", "Trebl", "Synth", "Pcm ", "Spkr ", "Line ", \ "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ "Line1", "Line2", "Line3", "Digital1", "Digital2", "Digital3", \ "PhoneIn", "PhoneOut", "Video", "Radio", "Monitor"} #define SOUND_DEVICE_NAMES { \ "vol", "bass", "treble", "synth", "pcm", "speaker", "line", \ "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", \ "line1", "line2", "line3", "dig1", "dig2", "dig3", \ "phin", "phout", "video", "radio", "monitor"} /* Device bitmask identifiers */ #define SOUND_MIXER_RECSRC 0xff /* 1 bit per recording source */ #define SOUND_MIXER_DEVMASK 0xfe /* 1 bit per supported device */ #define SOUND_MIXER_RECMASK 0xfd /* 1 bit per supp. recording source */ #define SOUND_MIXER_CAPS 0xfc #define SOUND_CAP_EXCL_INPUT 0x00000001 /* Only 1 rec. src at a time */ #define SOUND_MIXER_STEREODEVS 0xfb /* Mixer channels supporting stereo */ /* Device mask bits */ #define SOUND_MASK_VOLUME (1 << SOUND_MIXER_VOLUME) #define SOUND_MASK_BASS (1 << SOUND_MIXER_BASS) #define SOUND_MASK_TREBLE (1 << SOUND_MIXER_TREBLE) #define SOUND_MASK_SYNTH (1 << SOUND_MIXER_SYNTH) #define SOUND_MASK_PCM (1 << SOUND_MIXER_PCM) #define SOUND_MASK_SPEAKER (1 << SOUND_MIXER_SPEAKER) #define SOUND_MASK_LINE (1 << SOUND_MIXER_LINE) #define SOUND_MASK_MIC (1 << SOUND_MIXER_MIC) #define SOUND_MASK_CD (1 << SOUND_MIXER_CD) #define SOUND_MASK_IMIX (1 << SOUND_MIXER_IMIX) #define SOUND_MASK_ALTPCM (1 << SOUND_MIXER_ALTPCM) #define SOUND_MASK_RECLEV (1 << SOUND_MIXER_RECLEV) #define SOUND_MASK_IGAIN (1 << SOUND_MIXER_IGAIN) #define SOUND_MASK_OGAIN (1 << SOUND_MIXER_OGAIN) #define SOUND_MASK_LINE1 (1 << SOUND_MIXER_LINE1) #define SOUND_MASK_LINE2 (1 << SOUND_MIXER_LINE2) #define SOUND_MASK_LINE3 (1 << SOUND_MIXER_LINE3) #define SOUND_MASK_DIGITAL1 (1 << SOUND_MIXER_DIGITAL1) #define SOUND_MASK_DIGITAL2 (1 << SOUND_MIXER_DIGITAL2) #define SOUND_MASK_DIGITAL3 (1 << SOUND_MIXER_DIGITAL3) #define SOUND_MASK_PHONEIN (1 << SOUND_MIXER_PHONEIN) #define SOUND_MASK_PHONEOUT (1 << SOUND_MIXER_PHONEOUT) #define SOUND_MASK_RADIO (1 << SOUND_MIXER_RADIO) #define SOUND_MASK_VIDEO (1 << SOUND_MIXER_VIDEO) #define SOUND_MASK_MONITOR (1 << SOUND_MIXER_MONITOR) /* Obsolete macros */ #define SOUND_MASK_MUTE (1 << SOUND_MIXER_MUTE) #define SOUND_MASK_ENHANCE (1 << SOUND_MIXER_ENHANCE) #define SOUND_MASK_LOUD (1 << SOUND_MIXER_LOUD) #define MIXER_READ(dev) _IOR('M', dev, int) #define SOUND_MIXER_READ_VOLUME MIXER_READ(SOUND_MIXER_VOLUME) #define SOUND_MIXER_READ_BASS MIXER_READ(SOUND_MIXER_BASS) #define SOUND_MIXER_READ_TREBLE MIXER_READ(SOUND_MIXER_TREBLE) #define SOUND_MIXER_READ_SYNTH MIXER_READ(SOUND_MIXER_SYNTH) #define SOUND_MIXER_READ_PCM MIXER_READ(SOUND_MIXER_PCM) #define SOUND_MIXER_READ_SPEAKER MIXER_READ(SOUND_MIXER_SPEAKER) #define SOUND_MIXER_READ_LINE MIXER_READ(SOUND_MIXER_LINE) #define SOUND_MIXER_READ_MIC MIXER_READ(SOUND_MIXER_MIC) #define SOUND_MIXER_READ_CD MIXER_READ(SOUND_MIXER_CD) #define SOUND_MIXER_READ_IMIX MIXER_READ(SOUND_MIXER_IMIX) #define SOUND_MIXER_READ_ALTPCM MIXER_READ(SOUND_MIXER_ALTPCM) #define SOUND_MIXER_READ_RECLEV MIXER_READ(SOUND_MIXER_RECLEV) #define SOUND_MIXER_READ_IGAIN MIXER_READ(SOUND_MIXER_IGAIN) #define SOUND_MIXER_READ_OGAIN MIXER_READ(SOUND_MIXER_OGAIN) #define SOUND_MIXER_READ_LINE1 MIXER_READ(SOUND_MIXER_LINE1) #define SOUND_MIXER_READ_LINE2 MIXER_READ(SOUND_MIXER_LINE2) #define SOUND_MIXER_READ_LINE3 MIXER_READ(SOUND_MIXER_LINE3) #define SOUND_MIXER_READ_DIGITAL1 MIXER_READ(SOUND_MIXER_DIGITAL1) #define SOUND_MIXER_READ_DIGITAL2 MIXER_READ(SOUND_MIXER_DIGITAL2) #define SOUND_MIXER_READ_DIGITAL3 MIXER_READ(SOUND_MIXER_DIGITAL3) #define SOUND_MIXER_READ_PHONEIN MIXER_READ(SOUND_MIXER_PHONEIN) #define SOUND_MIXER_READ_PHONEOUT MIXER_READ(SOUND_MIXER_PHONEOUT) #define SOUND_MIXER_READ_RADIO MIXER_READ(SOUND_MIXER_RADIO) #define SOUND_MIXER_READ_VIDEO MIXER_READ(SOUND_MIXER_VIDEO) #define SOUND_MIXER_READ_MONITOR MIXER_READ(SOUND_MIXER_MONITOR) /* Obsolete macros */ #define SOUND_MIXER_READ_MUTE MIXER_READ(SOUND_MIXER_MUTE) #define SOUND_MIXER_READ_ENHANCE MIXER_READ(SOUND_MIXER_ENHANCE) #define SOUND_MIXER_READ_LOUD MIXER_READ(SOUND_MIXER_LOUD) #define SOUND_MIXER_READ_RECSRC MIXER_READ(SOUND_MIXER_RECSRC) #define SOUND_MIXER_READ_DEVMASK MIXER_READ(SOUND_MIXER_DEVMASK) #define SOUND_MIXER_READ_RECMASK MIXER_READ(SOUND_MIXER_RECMASK) #define SOUND_MIXER_READ_STEREODEVS MIXER_READ(SOUND_MIXER_STEREODEVS) #define SOUND_MIXER_READ_CAPS MIXER_READ(SOUND_MIXER_CAPS) #define MIXER_WRITE(dev) _IOWR('M', dev, int) #define SOUND_MIXER_WRITE_VOLUME MIXER_WRITE(SOUND_MIXER_VOLUME) #define SOUND_MIXER_WRITE_BASS MIXER_WRITE(SOUND_MIXER_BASS) #define SOUND_MIXER_WRITE_TREBLE MIXER_WRITE(SOUND_MIXER_TREBLE) #define SOUND_MIXER_WRITE_SYNTH MIXER_WRITE(SOUND_MIXER_SYNTH) #define SOUND_MIXER_WRITE_PCM MIXER_WRITE(SOUND_MIXER_PCM) #define SOUND_MIXER_WRITE_SPEAKER MIXER_WRITE(SOUND_MIXER_SPEAKER) #define SOUND_MIXER_WRITE_LINE MIXER_WRITE(SOUND_MIXER_LINE) #define SOUND_MIXER_WRITE_MIC MIXER_WRITE(SOUND_MIXER_MIC) #define SOUND_MIXER_WRITE_CD MIXER_WRITE(SOUND_MIXER_CD) #define SOUND_MIXER_WRITE_IMIX MIXER_WRITE(SOUND_MIXER_IMIX) #define SOUND_MIXER_WRITE_ALTPCM MIXER_WRITE(SOUND_MIXER_ALTPCM) #define SOUND_MIXER_WRITE_RECLEV MIXER_WRITE(SOUND_MIXER_RECLEV) #define SOUND_MIXER_WRITE_IGAIN MIXER_WRITE(SOUND_MIXER_IGAIN) #define SOUND_MIXER_WRITE_OGAIN MIXER_WRITE(SOUND_MIXER_OGAIN) #define SOUND_MIXER_WRITE_LINE1 MIXER_WRITE(SOUND_MIXER_LINE1) #define SOUND_MIXER_WRITE_LINE2 MIXER_WRITE(SOUND_MIXER_LINE2) #define SOUND_MIXER_WRITE_LINE3 MIXER_WRITE(SOUND_MIXER_LINE3) #define SOUND_MIXER_WRITE_DIGITAL1 MIXER_WRITE(SOUND_MIXER_DIGITAL1) #define SOUND_MIXER_WRITE_DIGITAL2 MIXER_WRITE(SOUND_MIXER_DIGITAL2) #define SOUND_MIXER_WRITE_DIGITAL3 MIXER_WRITE(SOUND_MIXER_DIGITAL3) #define SOUND_MIXER_WRITE_PHONEIN MIXER_WRITE(SOUND_MIXER_PHONEIN) #define SOUND_MIXER_WRITE_PHONEOUT MIXER_WRITE(SOUND_MIXER_PHONEOUT) #define SOUND_MIXER_WRITE_RADIO MIXER_WRITE(SOUND_MIXER_RADIO) #define SOUND_MIXER_WRITE_VIDEO MIXER_WRITE(SOUND_MIXER_VIDEO) #define SOUND_MIXER_WRITE_MONITOR MIXER_WRITE(SOUND_MIXER_MONITOR) #define SOUND_MIXER_WRITE_MUTE MIXER_WRITE(SOUND_MIXER_MUTE) #define SOUND_MIXER_WRITE_ENHANCE MIXER_WRITE(SOUND_MIXER_ENHANCE) #define SOUND_MIXER_WRITE_LOUD MIXER_WRITE(SOUND_MIXER_LOUD) #define SOUND_MIXER_WRITE_RECSRC MIXER_WRITE(SOUND_MIXER_RECSRC) typedef struct mixer_info { char id[16]; char name[32]; int modify_counter; int fillers[10]; } mixer_info; #define SOUND_MIXER_INFO _IOR('M', 101, mixer_info) #define LEFT_CHN 0 #define RIGHT_CHN 1 /* * Level 2 event types for /dev/sequencer */ /* * The 4 most significant bits of byte 0 specify the class of * the event: * * 0x8X = system level events, * 0x9X = device/port specific events, event[1] = device/port, * The last 4 bits give the subtype: * 0x02 = Channel event (event[3] = chn). * 0x01 = note event (event[4] = note). * (0x01 is not used alone but always with bit 0x02). * event[2] = MIDI message code (0x80=note off etc.) * */ #define EV_SEQ_LOCAL 0x80 #define EV_TIMING 0x81 #define EV_CHN_COMMON 0x92 #define EV_CHN_VOICE 0x93 #define EV_SYSEX 0x94 /* * Event types 200 to 220 are reserved for application use. * These numbers will not be used by the driver. */ /* * Events for event type EV_CHN_VOICE */ #define MIDI_NOTEOFF 0x80 #define MIDI_NOTEON 0x90 #define MIDI_KEY_PRESSURE 0xA0 /* * Events for event type EV_CHN_COMMON */ #define MIDI_CTL_CHANGE 0xB0 #define MIDI_PGM_CHANGE 0xC0 #define MIDI_CHN_PRESSURE 0xD0 #define MIDI_PITCH_BEND 0xE0 #define MIDI_SYSTEM_PREFIX 0xF0 /* * Timer event types */ #define TMR_WAIT_REL 1 /* Time relative to the prev time */ #define TMR_WAIT_ABS 2 /* Absolute time since TMR_START */ #define TMR_STOP 3 #define TMR_START 4 #define TMR_CONTINUE 5 #define TMR_TEMPO 6 #define TMR_ECHO 8 #define TMR_CLOCK 9 /* MIDI clock */ #define TMR_SPP 10 /* Song position pointer */ #define TMR_TIMESIG 11 /* Time signature */ /* * Local event types */ #define LOCL_STARTAUDIO 1 #if !defined(_KERNEL) || defined(USE_SEQ_MACROS) /* * Some convenience macros to simplify programming of the * /dev/sequencer interface * * These macros define the API which should be used when possible. */ #ifndef USE_SIMPLE_MACROS void seqbuf_dump(void); /* This function must be provided by programs */ /* Sample seqbuf_dump() implementation: * * SEQ_DEFINEBUF (2048); -- Defines a buffer for 2048 bytes * * int seqfd; -- The file descriptor for /dev/sequencer. * * void * seqbuf_dump () * { * if (_seqbufptr) * if (write (seqfd, _seqbuf, _seqbufptr) == -1) * { * perror ("write /dev/sequencer"); * exit (-1); * } * _seqbufptr = 0; * } */ #define SEQ_DEFINEBUF(len) \ u_char _seqbuf[len]; int _seqbuflen = len;int _seqbufptr = 0 #define SEQ_USE_EXTBUF() \ extern u_char _seqbuf[]; \ extern int _seqbuflen;extern int _seqbufptr #define SEQ_DECLAREBUF() SEQ_USE_EXTBUF() #define SEQ_PM_DEFINES struct patmgr_info _pm_info #define _SEQ_NEEDBUF(len) \ if ((_seqbufptr+(len)) > _seqbuflen) \ seqbuf_dump() #define _SEQ_ADVBUF(len) _seqbufptr += len #define SEQ_DUMPBUF seqbuf_dump #else /* * This variation of the sequencer macros is used just to format one event * using fixed buffer. * * The program using the macro library must define the following macros before * using this library. * * #define _seqbuf name of the buffer (u_char[]) * #define _SEQ_ADVBUF(len) If the applic needs to know the exact * size of the event, this macro can be used. * Otherwise this must be defined as empty. * #define _seqbufptr Define the name of index variable or 0 if * not required. */ #define _SEQ_NEEDBUF(len) /* empty */ #endif #define PM_LOAD_PATCH(dev, bank, pgm) \ (SEQ_DUMPBUF(), _pm_info.command = _PM_LOAD_PATCH, \ _pm_info.device=dev, _pm_info.data.data8[0]=pgm, \ _pm_info.parm1 = bank, _pm_info.parm2 = 1, \ ioctl(seqfd, SNDCTL_PMGR_ACCESS, &_pm_info)) #define PM_LOAD_PATCHES(dev, bank, pgm) \ (SEQ_DUMPBUF(), _pm_info.command = _PM_LOAD_PATCH, \ _pm_info.device=dev, bcopy( pgm, _pm_info.data.data8, 128), \ _pm_info.parm1 = bank, _pm_info.parm2 = 128, \ ioctl(seqfd, SNDCTL_PMGR_ACCESS, &_pm_info)) #define SEQ_VOLUME_MODE(dev, mode) { \ _SEQ_NEEDBUF(8);\ _seqbuf[_seqbufptr] = SEQ_EXTENDED;\ _seqbuf[_seqbufptr+1] = SEQ_VOLMODE;\ _seqbuf[_seqbufptr+2] = (dev);\ _seqbuf[_seqbufptr+3] = (mode);\ _seqbuf[_seqbufptr+4] = 0;\ _seqbuf[_seqbufptr+5] = 0;\ _seqbuf[_seqbufptr+6] = 0;\ _seqbuf[_seqbufptr+7] = 0;\ _SEQ_ADVBUF(8);} /* * Midi voice messages */ #define _CHN_VOICE(dev, event, chn, note, parm) { \ _SEQ_NEEDBUF(8);\ _seqbuf[_seqbufptr] = EV_CHN_VOICE;\ _seqbuf[_seqbufptr+1] = (dev);\ _seqbuf[_seqbufptr+2] = (event);\ _seqbuf[_seqbufptr+3] = (chn);\ _seqbuf[_seqbufptr+4] = (note);\ _seqbuf[_seqbufptr+5] = (parm);\ _seqbuf[_seqbufptr+6] = (0);\ _seqbuf[_seqbufptr+7] = 0;\ _SEQ_ADVBUF(8);} #define SEQ_START_NOTE(dev, chn, note, vol) \ _CHN_VOICE(dev, MIDI_NOTEON, chn, note, vol) #define SEQ_STOP_NOTE(dev, chn, note, vol) \ _CHN_VOICE(dev, MIDI_NOTEOFF, chn, note, vol) #define SEQ_KEY_PRESSURE(dev, chn, note, pressure) \ _CHN_VOICE(dev, MIDI_KEY_PRESSURE, chn, note, pressure) /* * Midi channel messages */ #define _CHN_COMMON(dev, event, chn, p1, p2, w14) { \ _SEQ_NEEDBUF(8);\ _seqbuf[_seqbufptr] = EV_CHN_COMMON;\ _seqbuf[_seqbufptr+1] = (dev);\ _seqbuf[_seqbufptr+2] = (event);\ _seqbuf[_seqbufptr+3] = (chn);\ _seqbuf[_seqbufptr+4] = (p1);\ _seqbuf[_seqbufptr+5] = (p2);\ *(short *)&_seqbuf[_seqbufptr+6] = (w14);\ _SEQ_ADVBUF(8);} /* * SEQ_SYSEX permits sending of sysex messages. (It may look that it permits * sending any MIDI bytes but it's absolutely not possible. Trying to do * so _will_ cause problems with MPU401 intelligent mode). * * Sysex messages are sent in blocks of 1 to 6 bytes. Longer messages must be * sent by calling SEQ_SYSEX() several times (there must be no other events * between them). First sysex fragment must have 0xf0 in the first byte * and the last byte (buf[len-1] of the last fragment must be 0xf7. No byte * between these sysex start and end markers cannot be larger than 0x7f. Also * lengths of each fragments (except the last one) must be 6. * * Breaking the above rules may work with some MIDI ports but is likely to * cause fatal problems with some other devices (such as MPU401). */ #define SEQ_SYSEX(dev, buf, len) { \ int i, l=(len); if (l>6)l=6;\ _SEQ_NEEDBUF(8);\ _seqbuf[_seqbufptr] = EV_SYSEX;\ _seqbuf[_seqbufptr+1] = (dev);\ for(i=0;i #include #include #include #include #include #include #include /* Generic test data, with buffer content matching the sample values. */ static struct afmt_test_data { const char *label; uint8_t buffer[4]; size_t size; int format; intpcm_t value; _Static_assert((sizeof(intpcm_t) == 4), "Test data assumes 32bit, adjust negative values to new size."); } const afmt_tests[] = { /* 8 bit sample formats. */ {"s8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0x00000001}, {"s8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_S8, 0xffffff81}, {"u8_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0xffffff81}, {"u8_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_U8, 0x00000001}, /* 16 bit sample formats. */ {"s16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_LE, 0x00000201}, {"s16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_LE, 0xffff8281}, {"s16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_S16_BE, 0x00000102}, {"s16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_S16_BE, 0xffff8182}, {"u16le_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_LE, 0xffff8201}, {"u16le_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_LE, 0x00000281}, {"u16be_1", {0x01, 0x02, 0x00, 0x00}, 2, AFMT_U16_BE, 0xffff8102}, {"u16be_2", {0x81, 0x82, 0x00, 0x00}, 2, AFMT_U16_BE, 0x00000182}, /* 24 bit sample formats. */ {"s24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_LE, 0x00030201}, {"s24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_LE, 0xff838281}, {"s24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_S24_BE, 0x00010203}, {"s24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_S24_BE, 0xff818283}, {"u24le_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_LE, 0xff830201}, {"u24le_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_LE, 0x00038281}, {"u24be_1", {0x01, 0x02, 0x03, 0x00}, 3, AFMT_U24_BE, 0xff810203}, {"u24be_2", {0x81, 0x82, 0x83, 0x00}, 3, AFMT_U24_BE, 0x00018283}, /* 32 bit sample formats. */ {"s32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_LE, 0x04030201}, {"s32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_LE, 0x84838281}, {"s32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_S32_BE, 0x01020304}, {"s32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_S32_BE, 0x81828384}, {"u32le_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_LE, 0x84030201}, {"u32le_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_LE, 0x04838281}, {"u32be_1", {0x01, 0x02, 0x03, 0x04}, 4, AFMT_U32_BE, 0x81020304}, {"u32be_2", {0x81, 0x82, 0x83, 0x84}, 4, AFMT_U32_BE, 0x01828384}, + /* 32 bit floating point sample formats. */ + {"f32le_1", {0x00, 0x00, 0x00, 0x3f}, 4, AFMT_F32_LE, 0x40000000}, + {"f32le_2", {0x00, 0x00, 0x00, 0xbf}, 4, AFMT_F32_LE, 0xc0000000}, + {"f32be_1", {0x3f, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0x40000000}, + {"f32be_2", {0xbf, 0x00, 0x00, 0x00}, 4, AFMT_F32_BE, 0xc0000000}, + /* u-law and A-law sample formats. */ {"mulaw_1", {0x01, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0xffffff87}, {"mulaw_2", {0x81, 0x00, 0x00, 0x00}, 1, AFMT_MU_LAW, 0x00000079}, {"alaw_1", {0x2a, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0xffffff83}, {"alaw_2", {0xab, 0x00, 0x00, 0x00}, 1, AFMT_A_LAW, 0x00000079} }; /* Normalize sample values in strictly correct (but slow) c. */ static intpcm_t local_normalize(intpcm_t value, int val_bits, int norm_bits) { int32_t divisor; intpcm_t remainder; /* Avoid undefined or implementation defined behavior. */ if (val_bits < norm_bits) /* Multiply instead of left shift (value may be negative). */ return (value * (1 << (norm_bits - val_bits))); else if (val_bits > norm_bits) { divisor = (1 << (val_bits - norm_bits)); /* Positive remainder, to discard lowest bits from value. */ remainder = value % divisor; remainder = (remainder + divisor) % divisor; /* Divide instead of right shift (value may be negative). */ return ((value - remainder) / divisor); } return value; } /* Restrict magnitude of sample value to 24bit for 32bit calculations. */ static intpcm_t local_calc_limit(intpcm_t value, int val_bits) { /* * When intpcm32_t is defined to be 32bit, calculations for mixing and * volume changes use 32bit integers instead of 64bit. To get some * headroom for calculations, 32bit sample values are restricted to * 24bit magnitude in that case. Also avoid implementation defined * behavior here. */ if (sizeof(intpcm32_t) == (32 / 8) && val_bits == 32) return (local_normalize(value, 32, 24)); return value; } ATF_TC(pcm_read); ATF_TC_HEAD(pcm_read, tc) { atf_tc_set_md_var(tc, "descr", "Read and verify different pcm sample formats."); } ATF_TC_BODY(pcm_read, tc) { const struct afmt_test_data *test; uint8_t src[4]; intpcm_t expected, result; size_t i; for (i = 0; i < nitems(afmt_tests); i++) { test = &afmt_tests[i]; /* Copy byte representation, fill with distinctive pattern. */ memset(src, 0x66, sizeof(src)); memcpy(src, test->buffer, test->size); /* Read sample at format magnitude. */ expected = test->value; result = pcm_sample_read(src, test->format); ATF_CHECK_MSG(result == expected, "pcm_read[\"%s\"].value: expected=0x%08x, result=0x%08x", test->label, expected, result); /* Read sample at format magnitude, for calculations. */ expected = local_calc_limit(test->value, test->size * 8); result = pcm_sample_read_calc(src, test->format); ATF_CHECK_MSG(result == expected, "pcm_read[\"%s\"].calc: expected=0x%08x, result=0x%08x", test->label, expected, result); /* Read sample at full 32 bit magnitude. */ expected = local_normalize(test->value, test->size * 8, 32); result = pcm_sample_read_norm(src, test->format); ATF_CHECK_MSG(result == expected, "pcm_read[\"%s\"].norm: expected=0x%08x, result=0x%08x", test->label, expected, result); } } ATF_TC(pcm_write); ATF_TC_HEAD(pcm_write, tc) { atf_tc_set_md_var(tc, "descr", "Write and verify different pcm sample formats."); } ATF_TC_BODY(pcm_write, tc) { const struct afmt_test_data *test; uint8_t expected[4]; uint8_t dst[4]; intpcm_t value; size_t i; for (i = 0; i < nitems(afmt_tests); i++) { test = &afmt_tests[i]; /* Write sample of format specific magnitude. */ memcpy(expected, test->buffer, sizeof(expected)); memset(dst, 0x00, sizeof(dst)); value = test->value; pcm_sample_write(dst, value, test->format); ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, "pcm_write[\"%s\"].value: " "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, expected[0], expected[1], expected[2], expected[3], dst[0], dst[1], dst[2], dst[3]); /* Write sample of format specific, calculation magnitude. */ memcpy(expected, test->buffer, sizeof(expected)); memset(dst, 0x00, sizeof(dst)); value = local_calc_limit(test->value, test->size * 8); if (value != test->value) { /* * 32 bit sample was reduced to 24 bit resolution * for calculation, least significant byte is lost. */ if (test->format & AFMT_BIGENDIAN) expected[3] = 0x00; else expected[0] = 0x00; } pcm_sample_write_calc(dst, value, test->format); ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, "pcm_write[\"%s\"].calc: " "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, expected[0], expected[1], expected[2], expected[3], dst[0], dst[1], dst[2], dst[3]); /* Write normalized sample of full 32 bit magnitude. */ memcpy(expected, test->buffer, sizeof(expected)); memset(dst, 0x00, sizeof(dst)); value = local_normalize(test->value, test->size * 8, 32); pcm_sample_write_norm(dst, value, test->format); ATF_CHECK_MSG(memcmp(dst, expected, sizeof(dst)) == 0, "pcm_write[\"%s\"].norm: " "expected={0x%02x, 0x%02x, 0x%02x, 0x%02x}, " "result={0x%02x, 0x%02x, 0x%02x, 0x%02x}, ", test->label, expected[0], expected[1], expected[2], expected[3], dst[0], dst[1], dst[2], dst[3]); } } ATF_TC(pcm_format_bits); ATF_TC_HEAD(pcm_format_bits, tc) { atf_tc_set_md_var(tc, "descr", "Verify bit width of different pcm sample formats."); } ATF_TC_BODY(pcm_format_bits, tc) { const struct afmt_test_data *test; size_t bits; size_t i; for (i = 0; i < nitems(afmt_tests); i++) { test = &afmt_tests[i]; /* Check bit width determined for given sample format. */ bits = AFMT_BIT(test->format); ATF_CHECK_MSG(bits == test->size * 8, "format_bits[%zu].size: expected=%zu, result=%zu", i, test->size * 8, bits); } } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, pcm_read); ATF_TP_ADD_TC(tp, pcm_write); ATF_TP_ADD_TC(tp, pcm_format_bits); return atf_no_error(); }