diff options
author | ariff <ariff@FreeBSD.org> | 2006-11-26 12:24:06 +0000 |
---|---|---|
committer | ariff <ariff@FreeBSD.org> | 2006-11-26 12:24:06 +0000 |
commit | 7b36f6d96b710844a6d2abd90a94bc169f72a5d1 (patch) | |
tree | be8f05cc6b2e41975dd084107c4db8978e7c9734 /sys/dev/sound/pcm | |
parent | 70fe7b890e66532cef253d3bad928093268a24fd (diff) | |
download | FreeBSD-src-7b36f6d96b710844a6d2abd90a94bc169f72a5d1.zip FreeBSD-src-7b36f6d96b710844a6d2abd90a94bc169f72a5d1.tar.gz |
Welcome to Once-a-year Sound Mega-Commit. Enjoy numerous updates and fixes
in every sense.
General
-------
- Multichannel safe, endian safe, format safe
* Large part of critical pcm filters such as vchan.c, feeder_rate.c,
feeder_volume.c, feeder_fmt.c and feeder.c has been rewritten so that
using them does not cause the pcm data to be converted to 16bit little
endian.
* Macrosses for accessing pcm data safely are defined within sound.h in
the form of PCM_READ_* / PCM_WRITE_*
* Currently, most of them are probably limited for mono/stereo handling,
but the future addition of true multichannel will be much easier.
- Low latency operation
* Well, this require lot more works to do not just within sound driver,
but we're heading towards right direction. Buffer/block sizing within
channel.c is rewritten to calculate precise allocation for various
combination of sample/data/rate size. As a result, applying correct
SNDCTL_DSP_POLICY value will achive expected latency behaviour simmilar
to what commercial 4front driver do.
* Signal handling fix. ctrl+c of "cat /dev/zero > /dev/dsp" does not
result long delay.
* Eliminate sound truncation if the sound data is too small.
DIY:
1) Download / extract
http://people.freebsd.org/~ariff/lowlatency/shortfiles.tar.gz
2) Do a comparison between "cat state*.au > /dev/dsp" and
"for x in state*.au ; do cat $x > /dev/dsp ; done"
- there should be no "perceivable" differences.
Double close for PR kern/31445.
CAVEAT: Low latency come with (unbearable) price especially for poorly
written applications. Applications that trying to act smarter
by requesting (wrong) blocksize/blockcount will suffer the most.
Fixup samples/patches can be found at:
http://people.freebsd.org/~ariff/ports/
- Switch minimum/maximum sampling rate limit to "1" and "2016000" (48k * 42)
due to closer compatibility with 4front driver.
Discussed with: marcus@ (long time ago?)
- All driver specific sysctls in the form of "hw.snd.pcm%d.*" have been
moved to their own dev sysctl nodes, notably:
hw.snd.pcm%d.vchans -> dev.pcm.%d.vchans
Bump __FreeBSD_version.
Driver specific
---------------
- Ditto for sysctls.
- snd_atiixp, snd_es137x, snd_via8233, snd_hda
* Numerous cleanups and fixes.
* _EXPERIMENTAL_ polling mode support using simple callout_* mechanisme.
This was intended for pure debugging and latency measurement, but proven
good enough in few unexpected and rare cases (such as problematic shared
IRQ with GIANT devices - USB). Polling can be enabled/disabled through
dev.pcm.0.polling. Disabled by default.
- snd_ich
* Fix possible overflow during speed calibration. Delay final
initialization (pcm_setstatus) after calibration finished.
PR: kern/100169
Tested by: Kevin Overman <oberman@es.net>
* Inverted EAPD for few Nec VersaPro.
PR: kern/104715
Submitted by: KAWATA Masahiko <kawata@mta.biglobe.ne.jp>
Thanks to various people, notably Joel Dahl, Yuriy Tsibizov, Kevin Oberman,
those at #freebsd-azalia @ freenode and others for testing.
Joel Dahl will do the manpage update.
Diffstat (limited to 'sys/dev/sound/pcm')
-rw-r--r-- | sys/dev/sound/pcm/ac97.c | 104 | ||||
-rw-r--r-- | sys/dev/sound/pcm/ac97.h | 2 | ||||
-rw-r--r-- | sys/dev/sound/pcm/buffer.c | 86 | ||||
-rw-r--r-- | sys/dev/sound/pcm/buffer.h | 3 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 1032 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.h | 40 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 33 | ||||
-rw-r--r-- | sys/dev/sound/pcm/fake.c | 3 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.c | 584 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder.h | 29 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_fmt.c | 826 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_rate.c | 866 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_volume.c | 211 | ||||
-rw-r--r-- | sys/dev/sound/pcm/mixer.c | 8 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sndstat.c | 21 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.c | 88 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 287 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 589 |
18 files changed, 3319 insertions, 1493 deletions
diff --git a/sys/dev/sound/pcm/ac97.c b/sys/dev/sound/pcm/ac97.c index 9b3380b..46ceef0 100644 --- a/sys/dev/sound/pcm/ac97.c +++ b/sys/dev/sound/pcm/ac97.c @@ -28,6 +28,8 @@ #include <dev/sound/pcm/ac97.h> #include <dev/sound/pcm/ac97_patch.h> +#include <dev/pci/pcivar.h> + #include "mixer_if.h" SND_DECLARE_FILE("$FreeBSD$"); @@ -791,7 +793,7 @@ struct ac97_info * ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { struct ac97_info *codec; - int eapd_inv; + int eapdinv; codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT | M_ZERO); if (codec == NULL) @@ -811,8 +813,8 @@ ac97_create(device_t dev, void *devinfo, kobj_class_t cls) codec->devinfo = devinfo; codec->flags = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), - "ac97_eapd_inv", &eapd_inv) == 0) { - if (eapd_inv != 0) + "eapdinv", &eapdinv) == 0) { + if (eapdinv != 0) codec->flags |= AC97_F_EAPD_INV; } return codec; @@ -842,11 +844,66 @@ ac97_getflags(struct ac97_info *codec) /* -------------------------------------------------------------------- */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_ac97_eapd(SYSCTL_HANDLER_ARGS) +{ + struct ac97_info *codec; + int ea, inv, err = 0; + u_int16_t val; + + codec = oidp->oid_arg1; + if (codec == NULL || codec->id == 0 || codec->lock == NULL) + return EINVAL; + snd_mtxlock(codec->lock); + val = ac97_rdcd(codec, AC97_REG_POWER); + inv = (codec->flags & AC97_F_EAPD_INV) ? 0 : 1; + ea = (val >> 15) ^ inv; + snd_mtxunlock(codec->lock); + err = sysctl_handle_int(oidp, &ea, sizeof(ea), req); + if (err == 0 && req->newptr != NULL) { + if (ea != 0 && ea != 1) + return EINVAL; + if (ea != ((val >> 15) ^ inv)) { + snd_mtxlock(codec->lock); + ac97_wrcd(codec, AC97_REG_POWER, val ^ 0x8000); + snd_mtxunlock(codec->lock); + } + } + return err; +} +#endif + +static void +ac97_init_sysctl(struct ac97_info *codec) +{ +#ifdef SND_DYNSYSCTL + u_int16_t orig, val; + + if (codec == NULL || codec->dev == NULL) + return; + snd_mtxlock(codec->lock); + orig = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig ^ 0x8000); + val = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig); + snd_mtxunlock(codec->lock); + if ((val & 0x8000) == (orig & 0x8000)) + return; + SYSCTL_ADD_PROC(device_get_sysctl_ctx(codec->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(codec->dev)), + OID_AUTO, "eapd", CTLTYPE_INT | CTLFLAG_RW, + codec, sizeof(codec), sysctl_hw_snd_ac97_eapd, + "I", "AC97 External Amplifier"); +#endif +} + static int ac97mix_init(struct snd_mixer *m) { struct ac97_info *codec = mix_getdevinfo(m); struct snddev_info *d; + u_int32_t subvendor; u_int32_t i, mask; if (codec == NULL) @@ -857,20 +914,30 @@ ac97mix_init(struct snd_mixer *m) switch (codec->id) { case 0x41445374: /* AD1981B */ -#if 0 - mask = 0; - if (codec->mix[SOUND_MIXER_OGAIN].enable) - mask |= SOUND_MASK_OGAIN; - if (codec->mix[SOUND_MIXER_PHONEOUT].enable) - mask |= SOUND_MASK_PHONEOUT; - /* Tie ogain/phone to master volume */ - if (codec->mix[SOUND_MIXER_VOLUME].enable) - mix_setparentchild(m, SOUND_MIXER_VOLUME, mask); - else { - mix_setparentchild(m, SOUND_MIXER_VOLUME, mask); - mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + subvendor = (u_int32_t)pci_get_subdevice(codec->dev) << 16; + subvendor |= (u_int32_t)pci_get_subvendor(codec->dev) & + 0x0000ffff; + /* IBM Thinkcentre */ + if (subvendor == 0x02d91014) { + /* Enable headphone jack sensing */ + ac97_wrcd(codec, 0x72, ac97_rdcd(codec, 0x72) | + 0x0800); + mask = 0; + if (codec->mix[SOUND_MIXER_OGAIN].enable) + mask |= SOUND_MASK_OGAIN; + if (codec->mix[SOUND_MIXER_PHONEOUT].enable) + mask |= SOUND_MASK_PHONEOUT; + /* Tie ogain/phone to master volume */ + if (codec->mix[SOUND_MIXER_VOLUME].enable) + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + else { + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + mix_setrealdev(m, SOUND_MIXER_VOLUME, + SOUND_MIXER_NONE); + } } -#endif break; case 0x434d4941: /* CMI9738 */ case 0x434d4961: /* CMI9739 */ @@ -906,6 +973,9 @@ ac97mix_init(struct snd_mixer *m) for (i = 0; i < 32; i++) mask |= codec->mix[i].recidx? 1 << i : 0; mix_setrecdevs(m, mask); + + ac97_init_sysctl(codec); + return 0; } @@ -975,5 +1045,3 @@ ac97_getmixerclass(void) { return &ac97mixer_class; } - - diff --git a/sys/dev/sound/pcm/ac97.h b/sys/dev/sound/pcm/ac97.h index 8266fb5..f73dc76 100644 --- a/sys/dev/sound/pcm/ac97.h +++ b/sys/dev/sound/pcm/ac97.h @@ -82,7 +82,6 @@ #define AC97_F_EAPD_INV 0x00000001 #define AC97_F_RDCD_BUG 0x00000002 -#define AC97_F_SOFTVOL 0x00000004 #define AC97_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) #define AC97_CREATE(dev, devinfo, cls) ac97_create(dev, devinfo, &cls ## _class) @@ -105,4 +104,3 @@ u_int16_t ac97_getcaps(struct ac97_info *codec); u_int16_t ac97_rdcd(struct ac97_info *codec, int reg); void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val); - diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 11c1bfb..e12765e 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -254,10 +254,7 @@ sndbuf_clear(struct snd_dbuf *b, unsigned int length) if (length > b->bufsize) length = b->bufsize; - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; + data = sndbuf_zerodata(b->fmt); i = sndbuf_getfreeptr(b); p = sndbuf_getbuf(b); @@ -278,18 +275,8 @@ sndbuf_clear(struct snd_dbuf *b, unsigned int length) void sndbuf_fillsilence(struct snd_dbuf *b) { - int i; - u_char data, *p; - - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; - - i = 0; - p = sndbuf_getbuf(b); - while (i < b->bufsize) - p[i++] = data; + if (b->bufsize > 0) + memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = b->bufsize; } @@ -543,6 +530,52 @@ sndbuf_updateprevtotal(struct snd_dbuf *b) b->prev_total = b->total; } +unsigned int +snd_xbytes(unsigned int v, unsigned int from, unsigned int to) +{ + unsigned int w, x, y; + + if (from == to) + return v; + + if (from == 0 || to == 0 || v == 0) + return 0; + + x = from; + y = to; + while (y != 0) { + w = x % y; + x = y; + y = w; + } + from /= x; + to /= x; + + return (unsigned int)(((u_int64_t)v * to) / from); +} + +unsigned int +sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to) +{ + if (from == NULL || to == NULL || v == 0) + return 0; + + return snd_xbytes(v, sndbuf_getbps(from) * sndbuf_getspd(from), + sndbuf_getbps(to) * sndbuf_getspd(to)); +} + +u_int8_t +sndbuf_zerodata(u_int32_t fmt) +{ + if (fmt & AFMT_SIGNED) + return (0x00); + else if (fmt & AFMT_MU_LAW) + return (0x7f); + else if (fmt & AFMT_A_LAW) + return (0x55); + return (0x80); +} + /************************************************************/ /** @@ -624,15 +657,20 @@ sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) { + unsigned int cnt; + KASSERT(count > 0, ("can't feed 0 bytes")); if (sndbuf_getfree(to) < count) return EINVAL; - count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); - if (count) - sndbuf_acquire(to, to->tmpbuf, count); - /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + do { + cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); + if (cnt) + sndbuf_acquire(to, to->tmpbuf, cnt); + /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + count -= cnt; + } while (count && cnt); return 0; } @@ -682,12 +720,8 @@ sndbuf_clearshadow(struct snd_dbuf *b) KASSERT(b != NULL, ("b is a null pointer")); KASSERT(b->sl >= 0, ("illegal shadow length")); - if ((b->shadbuf != NULL) && (b->sl > 0)) { - if (b->fmt & AFMT_SIGNED) - memset(b->shadbuf, 0x00, b->sl); - else - memset(b->shadbuf, 0x80, b->sl); - } + if ((b->shadbuf != NULL) && (b->sl > 0)) + memset(b->shadbuf, sndbuf_zerodata(b->fmt), b->sl); } #ifdef OSSV4_EXPERIMENT diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index 07724be..67e08a7 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -107,6 +107,9 @@ unsigned int sndbuf_getreadyptr(struct snd_dbuf *b); unsigned int sndbuf_getblocks(struct snd_dbuf *b); unsigned int sndbuf_getprevblocks(struct snd_dbuf *b); unsigned int sndbuf_gettotal(struct snd_dbuf *b); +unsigned int snd_xbytes(unsigned int v, unsigned int from, unsigned int to); +unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to); +u_int8_t sndbuf_zerodata(u_int32_t fmt); void sndbuf_updateprevtotal(struct snd_dbuf *b); int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index 43ef365..d88f0e2 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -41,32 +41,60 @@ SND_DECLARE_FILE("$FreeBSD$"); #define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) +#define BUF_PARENT(c, b) \ + (((c) != NULL && (c)->parentchannel != NULL && \ + (c)->parentchannel->bufhard != NULL) ? \ + (c)->parentchannel->bufhard : (b)) + /* #define DEB(x) x */ -static int chn_targetirqrate = 32; -TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); +int report_soft_formats = 1; +SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, + &report_soft_formats, 1, "report software-emulated formats"); + +int chn_latency = CHN_LATENCY_DEFAULT; +TUNABLE_INT("hw.snd.latency", &chn_latency); static int -sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) { int err, val; - val = chn_targetirqrate; + val = chn_latency; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 16 || val > 512) + if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) err = EINVAL; else - chn_targetirqrate = val; + chn_latency = val; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); -static int report_soft_formats = 1; -SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, - &report_soft_formats, 1, "report software-emulated formats"); +SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency, "I", + "buffering latency (0=low ... 10=high)"); + +int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; +TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); + +static int +sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_latency_profile; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + 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_RW, + 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", + "buffering latency profile (0=aggresive 1=safe)"); /** * @brief Channel sync group lock @@ -173,7 +201,7 @@ chn_wakeup(struct pcm_channel *c) } } - wakeup(bs); + wakeup_one(bs); } static int @@ -220,13 +248,23 @@ chn_dmaupdate(struct pcm_channel *c) if (c->direction == PCMDIR_PLAY) { amt = MIN(delta, sndbuf_getready(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { amt = MIN(delta, sndbuf_getfree(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } + if (snd_verbose > 2 && (c->flags & CHN_F_TRIGGERED) && delta == 0) { + device_printf(c->dev, "WARNING: PCMDIR_%s DMA completion " + "too fast/slow ! hwptr=%u, old=%u " + "delta=%u amt=%u ready=%u free=%u\n", + (c->direction == PCMDIR_PLAY) ? "PLAY" : "REC", + hwptr, old, delta, amt, + sndbuf_getready(b), sndbuf_getfree(b)); + } return delta; } @@ -270,9 +308,11 @@ chn_wrfeed(struct pcm_channel *c) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); - KASSERT(amt <= sndbuf_getsize(bs), - ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, - amt, sndbuf_getsize(bs), c->flags)); + DEB(if (amt > sndbuf_getsize(bs) && + sndbuf_getbps(bs) >= sndbuf_getbps(b)) { + printf("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, + amt, sndbuf_getsize(bs), c->flags); + }); ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; /* @@ -313,10 +353,10 @@ chn_wrintr(struct pcm_channel *c) int chn_write(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, newsize, count, sz; + int ret, newsize, count, sz; struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int t, x, togo, p; CHN_LOCKASSERT(c); /* @@ -356,14 +396,13 @@ chn_write(struct pcm_channel *c, struct uio *buf) */ ret = EAGAIN; } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - timeout = 1; - ret = chn_sleep(c, "pcmwr", timeout); + ret = chn_sleep(c, "pcmwr", c->timeout); if (ret == EWOULDBLOCK) { - count -= timeout; + count -= c->timeout; ret = 0; + } else if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + return ret; } else if (ret == 0) count = hz; } @@ -378,7 +417,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) * unlock-uiomove-lock sequence. */ togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && togo > 0) { p = sndbuf_getfreeptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); @@ -389,7 +428,7 @@ chn_write(struct pcm_channel *c, struct uio *buf) x = sndbuf_acquire(bs, NULL, t); } ret = 0; - if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) + if (!(c->flags & CHN_F_TRIGGERED)) chn_start(c, 0); } } @@ -411,7 +450,7 @@ chn_rddump(struct pcm_channel *c, unsigned int cnt) CHN_LOCKASSERT(c); #if 0 - static uint32_t kk = 0; + static u_int32_t kk = 0; printf("%u: dumping %d bytes\n", ++kk, cnt); #endif c->xruns++; @@ -501,10 +540,10 @@ chn_rdintr(struct pcm_channel *c) int chn_read(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, sz, count; - struct snd_dbuf *bs = c->bufsoft; + int ret, sz, count; + struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int t, x, togo, p; CHN_LOCKASSERT(c); if (!(c->flags & CHN_F_TRIGGERED)) @@ -522,7 +561,7 @@ chn_read(struct pcm_channel *c, struct uio *buf) * unlock-uiomove-lock sequence. */ togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && togo > 0) { p = sndbuf_getreadyptr(bs); t = MIN(togo, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); @@ -534,20 +573,18 @@ chn_read(struct pcm_channel *c, struct uio *buf) } ret = 0; } else { - if (c->flags & CHN_F_NBIO) { + if (c->flags & CHN_F_NBIO) ret = EWOULDBLOCK; - } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - ret = chn_sleep(c, "pcmrd", timeout); + else { + ret = chn_sleep(c, "pcmrd", c->timeout); if (ret == EWOULDBLOCK) { - count -= timeout; + count -= c->timeout; ret = 0; - } else { + } else if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + return ret; + } else count = hz; - } - } } } @@ -584,27 +621,49 @@ chn_start(struct pcm_channel *c, int force) if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) return EINVAL; - i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); - j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); - if (force || (i >= j)) { - c->flags |= CHN_F_TRIGGERED; - /* - * if we're starting because a vchan started, don't feed any data - * or it becomes impossible to start vchans synchronised with the - * first one. the hardbuf should be empty so we top it up with - * silence to give it something to chew. the real data will be - * fed at the first irq. - */ - if (c->direction == PCMDIR_PLAY) { - /* - * Reduce pops during playback startup. - */ - sndbuf_fillsilence(b); - if (SLIST_EMPTY(&c->children)) - chn_wrfeed(c); + if (force) { + i = 1; + j = 0; + } else { + if (c->direction == PCMDIR_REC) { + i = sndbuf_getfree(bs); + j = sndbuf_getready(b); + } else { + struct snd_dbuf *pb; + + i = sndbuf_getready(bs); + + pb = BUF_PARENT(c, b); + j = min(sndbuf_xbytes(sndbuf_getsize(pb), pb, bs), + sndbuf_getsize(bs)); } + if (snd_verbose > 3 && SLIST_EMPTY(&c->children)) + printf("%s: PCMDIR_%s (%s) threshold i=%d j=%d\n", + __func__, + (c->direction == PCMDIR_PLAY) ? "PLAY" : "REC", + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + i, j); + } + + if (i >= j) { + c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); + c->feedcount = (c->flags & CHN_F_CLOSING) ? 2 : 0; + c->interrupts = 0; c->xruns = 0; + if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) { + chn_wrfeed(c); + if (snd_verbose > 3) + printf("%s: %s starting! (%s) (ready=%d " + "force=%d i=%d j=%d intrtimeout=%u)\n", + __func__, + (c->flags & CHN_F_HAS_VCHAN) ? + "VCHAN" : "HW", + (c->flags & CHN_F_CLOSING) ? "closing" : + "running", + sndbuf_getready(b), + force, i, j, c->timeout); + } chn_trigger(c, PCMTRIG_START); return 0; } @@ -631,34 +690,91 @@ chn_resetbuf(struct pcm_channel *c) int chn_sync(struct pcm_channel *c, int threshold) { - u_long rdy; - int ret; - struct snd_dbuf *bs = c->bufsoft; + int ret, count, hcount, minflush, resid, residp; + struct snd_dbuf *b, *bs; CHN_LOCKASSERT(c); + if (c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) + return 0; + + if (c->direction != PCMDIR_PLAY) + return EINVAL; + + bs = c->bufsoft; + /* if we haven't yet started and nothing is buffered, else start*/ if (!(c->flags & CHN_F_TRIGGERED)) { if (sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); if (ret) return ret; - } else { + } else return 0; - } } - for (;;) { - rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); - if (rdy <= threshold) { - ret = chn_sleep(c, "pcmsyn", 1); - if (ret == ERESTART || ret == EINTR) { - DEB(printf("chn_sync: tsleep returns %d\n", ret)); - return -1; - } - } else + b = BUF_PARENT(c, c->bufhard); + + threshold += sndbuf_getready(b); + minflush = sndbuf_xbytes(threshold, b, bs); + minflush -= minflush % sndbuf_getbps(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; + count = sndbuf_xbytes(minflush + resid, bs, b) / sndbuf_getblksz(b); + hcount = count; + ret = 0; + + while (count > 0 && resid > 0) { + ret = chn_sleep(c, "pcmsyn", c->timeout); + if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; break; - } + } + if (ret == 0 || ret == EWOULDBLOCK) { + resid = sndbuf_getready(bs); + if (resid == residp) { + --count; + if (snd_verbose > 3) + printf("%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) + printf("%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; + } + } + + if (snd_verbose > 3) + printf("%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; } @@ -724,48 +840,14 @@ chn_abort(struct pcm_channel *c) int chn_flush(struct pcm_channel *c) { - int ret, count, resid, resid_p; struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); - /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { - ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } - } - c->flags |= CHN_F_CLOSING; - resid = sndbuf_getready(bs) + sndbuf_getready(b); - resid_p = resid; - count = 10; - ret = 0; - while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { - /* still pending output data. */ - ret = chn_sleep(c, "pcmflu", hz / 10); - if (ret == EWOULDBLOCK) - ret = 0; - if (ret == 0) { - resid = sndbuf_getready(bs) + sndbuf_getready(b); - if (resid == resid_p) - count--; - if (resid > resid_p) - DEB(printf("chn_flush: buffer length increasind %d -> %d\n", resid_p, resid)); - resid_p = resid; - } - } - if (count == 0) - DEB(printf("chn_flush: timeout, hw %d, sw %d\n", - sndbuf_getready(b), sndbuf_getready(bs))); - + chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); @@ -786,14 +868,141 @@ fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) return 0; } +static struct afmtstr_table default_afmtstr_table[] = { + { "alaw", AFMT_A_LAW }, { "mulaw", AFMT_MU_LAW }, + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +int +afmtstr_swap_sign(char *s) +{ + if (s == NULL || strlen(s) < 2) /* full length of "s8" */ + return 0; + if (*s == 's') + *s = 'u'; + else if (*s == 'u') + *s = 's'; + else + return 0; + return 1; +} + +int +afmtstr_swap_endian(char *s) +{ + if (s == NULL || strlen(s) < 5) /* full length of "s16le" */ + return 0; + if (s[3] == 'l') + s[3] = 'b'; + else if (s[3] == 'b') + s[3] = 'l'; + else + return 0; + return 1; +} + +u_int32_t +afmtstr2afmt(struct afmtstr_table *tbl, const char *s, int stereo) +{ + size_t fsz, sz; + + sz = (s == NULL) ? 0 : strlen(s); + + if (sz > 1) { + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->fmtstr != NULL; tbl++) { + fsz = strlen(tbl->fmtstr); + if (sz < fsz) + continue; + if (strncmp(s, tbl->fmtstr, fsz) != 0) + continue; + if (fsz == sz) + return tbl->format | + ((stereo) ? AFMT_STEREO : 0); + if ((sz - fsz) < 2 || s[fsz] != ':') + break; + /* + * For now, just handle mono/stereo. + */ + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 'm' || + s[fsz + 1] == '1')) || + strcmp(s + fsz + 1, "mono") == 0) + return tbl->format; + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 's' || + s[fsz + 1] == '2')) || + strcmp(s + fsz + 1, "stereo") == 0) + return tbl->format | AFMT_STEREO; + break; + } + } + + return 0; +} + +u_int32_t +afmt2afmtstr(struct afmtstr_table *tbl, u_int32_t afmt, char *dst, + size_t len, int type, int stereo) +{ + u_int32_t fmt = 0; + char *fmtstr = NULL, *tag = ""; + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->format != 0; tbl++) { + if (tbl->format == 0) + break; + if ((afmt & ~AFMT_STEREO) != tbl->format) + continue; + fmt = afmt; + fmtstr = tbl->fmtstr; + break; + } + + if (fmt != 0 && fmtstr != NULL && dst != NULL && len > 0) { + strlcpy(dst, fmtstr, len); + switch (type) { + case AFMTSTR_SIMPLE: + tag = (fmt & AFMT_STEREO) ? ":s" : ":m"; + break; + case AFMTSTR_NUM: + tag = (fmt & AFMT_STEREO) ? ":2" : ":1"; + break; + case AFMTSTR_FULL: + tag = (fmt & AFMT_STEREO) ? ":stereo" : ":mono"; + break; + case AFMTSTR_NONE: + default: + break; + } + if (strlen(tag) > 0 && ((stereo && !(fmt & AFMT_STEREO)) || \ + (!stereo && (fmt & AFMT_STEREO)))) + strlcat(dst, tag, len); + } + + return fmt; +} + int chn_reset(struct pcm_channel *c, u_int32_t fmt) { int hwspd, r; CHN_LOCKASSERT(c); + c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; + c->timeout = 1; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); @@ -818,7 +1027,7 @@ chn_reset(struct pcm_channel *c, u_int32_t fmt) #endif } if (r == 0) - r = chn_setblocksize(c, 0, 0); + r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); @@ -839,6 +1048,8 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) bs = NULL; c->devinfo = NULL; c->feeder = NULL; + c->latency = -1; + c->timeout = 1; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); @@ -981,6 +1192,355 @@ chn_setvolume(struct pcm_channel *c, int left, int right) return 0; } +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; +} + +/* + * 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 + * aggresively 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) +{ + u_int32_t bufsz; + int blksz, blkcnt; + 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; + + if (CHN_LATENCY_MIN != 0 || CHN_LATENCY_MAX != 10 || + 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; + } + + if (dir == PCMDIR_PLAY) { + blkcnt = pblkcnts[chn_latency_profile][latency]; + bufsz = pbufszs[chn_latency_profile][latency]; + } else { + blkcnt = rblkcnts[chn_latency_profile][latency]; + bufsz = rbufszs[chn_latency_profile][latency]; + } + bufsz = snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate); + if (bufsz > max) + bufsz = max; + if (bufsz < 32) + bufsz = 32; + blksz = bufsz >> blkcnt; + blksz -= blksz % bps; + while (blksz < 16 || blksz < bps) + blksz += bps; + while ((blksz << blkcnt) > bufsz && blkcnt > 1) + blkcnt--; + if (rblksz != NULL) + *rblksz = blksz; + if (rblkcnt != NULL) + *rblkcnt = 1 << blkcnt; + + return blksz << blkcnt; +} + +/* + * Note that there is no strict requirement to align blksz to the + * nearest ^2, except for hardware CHANNEL_SETBLOCKSIZE. If the application + * trying to act smarter and requesting for specific blksz/blkcnt, so be it. + */ +static int +chn_resizebuf(struct pcm_channel *c, int latency, + int blkcnt, int blksz) +{ + struct snd_dbuf *b, *bs, *pb; + int sblksz, sblkcnt, hblksz, limit = 1; + int ret; + + CHN_LOCKASSERT(c); + + if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED) || + !(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; + limit = 0; + } + + bs = c->bufsoft; + b = c->bufhard; + + if (!(blksz == 0 || blkcnt == -1) && + (blksz < 16 || blksz < sndbuf_getbps(bs) || blkcnt < 2 || + (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) + return EINVAL; + + chn_calclatency(c->direction, latency, sndbuf_getbps(bs), + sndbuf_getbps(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_pow2(blksz); + sblksz -= sblksz % sndbuf_getbps(bs); + sblkcnt = blkcnt; + while (sblksz < 16 || sblksz < sndbuf_getbps(bs)) + sblksz += sndbuf_getbps(bs); + if (snd_verbose > 3 && !(blksz == 0 || blkcnt == -1)) + printf("%s: requested blksz=%d blkcnt=%d -> %d/%d\n", + __func__, blksz, blkcnt, sblksz, sblkcnt); + limit = 0; + } + + if (c->parentchannel != NULL) { + pb = BUF_PARENT(c, NULL); + CHN_UNLOCK(c); + chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); + CHN_LOCK(c); + limit = (limit != 0 && pb != NULL) ? + sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; + c->timeout = c->parentchannel->timeout; + } else { + if (c->flags & CHN_F_HAS_SIZE) { + hblksz = sndbuf_xbytes(sblksz, bs, b); + if (snd_verbose > 3) + printf("%s: sblksz=%d -> hblksz=%d\n", + __func__, sblksz, hblksz); + } else + chn_calclatency(c->direction, latency, + sndbuf_getbps(b), + sndbuf_getbps(b) * sndbuf_getspd(b), + CHN_2NDBUFMAXSIZE, &hblksz, NULL); + + hblksz = round_pow2(hblksz); + if ((hblksz << 1) > sndbuf_getmaxsize(b)) + hblksz = sndbuf_getmaxsize(b) >> 1; + hblksz -= hblksz % sndbuf_getbps(b); + while (hblksz < 16 || hblksz < sndbuf_getbps(b)) + hblksz += sndbuf_getbps(b); + +#if 0 + hblksz = sndbuf_getmaxsize(b) >> 1; +#endif + CHN_UNLOCK(c); + sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, + c->devinfo, hblksz)); + CHN_LOCK(c); + + if (!SLIST_EMPTY(&c->children)) { + /* + * Virtual channels underneath. Set the biggest + * possible value for their mixing space. + */ + sblksz = CHN_2NDBUFMAXSIZE >> 1; + sblksz -= sblksz % sndbuf_getbps(bs); + sblkcnt = 2; + limit = 0; + } else if (limit != 0) + limit = sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Interrupt timeout + */ + c->timeout = ((u_int64_t)hz * sndbuf_getblksz(b)) / + ((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b)); + if (c->timeout < 1) + c->timeout = 1; + c->timeout <<= 1; + } + + if (limit > CHN_2NDBUFMAXSIZE) + limit = CHN_2NDBUFMAXSIZE; + + hblksz = sblksz; + while ((sblksz * sblkcnt) < limit) { + sblksz += hblksz; + if ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { + sblksz -= hblksz; + break; + } + } + + if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || + sndbuf_getsize(bs) != (sblkcnt * sblksz)) { + ret = sndbuf_remalloc(bs, sblkcnt, sblksz); + if (ret != 0) { + printf("%s: Failed: %d %d\n", __func__, + sblkcnt, sblksz); + return ret; + } + } + + /* + * 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) + printf("%s: PCMDIR_%s (%s) timeout=%u " + "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", + __func__, (c->direction == PCMDIR_REC) ? "REC" : "PLAY", + (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); +} + static int chn_tryspeed(struct pcm_channel *c, int speed) { @@ -1012,7 +1572,7 @@ chn_tryspeed(struct pcm_channel *c, int speed) /* * Used to be 500. It was too big! */ - if (delta > 25) + if (delta > feeder_rate_round) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -1022,10 +1582,6 @@ chn_tryspeed(struct pcm_channel *c, int speed) if (r) goto out; - r = chn_setblocksize(c, 0, 0); - if (r) - goto out; - if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; @@ -1050,6 +1606,8 @@ out: sndbuf_getfmt(b)); if (!r) sndbuf_setfmt(bs, c->format); + if (!r) + r = chn_resizebuf(c, -2, 0, 0); DEB(printf("setspeed done, r = %d\n", r)); return r; } else @@ -1063,7 +1621,9 @@ chn_setspeed(struct pcm_channel *c, int speed) r = chn_tryspeed(c, speed); if (r) { - DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); + if (snd_verbose > 3) + printf("Failed to set speed %d falling back to %d\n", + speed, oldspeed); r = chn_tryspeed(c, oldspeed); } return r; @@ -1101,190 +1661,14 @@ chn_setformat(struct pcm_channel *c, u_int32_t fmt) r = chn_tryformat(c, fmt); if (r) { - DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); + if (snd_verbose > 3) + printf("Format change 0x%08x failed, reverting to 0x%08x\n", + fmt, oldfmt); chn_tryformat(c, oldfmt); } return r; } -/* - * given a bufsz value, round it to a power of 2 in the min-max range - * XXX only works if min and max are powers of 2 - */ -static int -round_bufsz(int bufsz, int min, int max) -{ - int tmp = min * 2; - - KASSERT((min & (min-1)) == 0, ("min %d must be power of 2\n", min)); - KASSERT((max & (max-1)) == 0, ("max %d must be power of 2\n", max)); - while (tmp <= bufsz) - tmp <<= 1; - tmp >>= 1; - if (tmp > max) - tmp = max; - return tmp; -} - -/* - * set the channel's blocksize both for soft and hard buffers. - * - * blksz should be a power of 2 between 2**4 and 2**16 -- it is useful - * that it has the same value for both bufsoft and bufhard. - * blksz == -1 computes values according to a target irq rate. - * blksz == 0 reuses previous values if available, otherwise - * behaves as for -1 - * - * blkcnt is set by the user, between 2 and (2**17)/blksz for bufsoft, - * but should be a power of 2 for bufhard to simplify life to low - * level drivers. - * Note, for the rec channel a large blkcnt is ok, - * but for the play channel we want blksz as small as possible to keep - * the delay small, because routines in the write path always try to - * keep bufhard full. - * - * Unless we have good reason to, use the values suggested by the caller. - */ -int -chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) -{ - struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; - int irqhz, ret, maxsz, maxsize, reqblksz; - - CHN_LOCKASSERT(c); - if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) { - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d", __func__, - c->name, sndbuf_getsize(bs), sndbuf_getsize(b))); - return EINVAL; - } - c->flags |= CHN_F_SETBLOCKSIZE; - - ret = 0; - DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); - if (blksz == 0 || blksz == -1) { /* let the driver choose values */ - if (blksz == -1) /* delete previous values */ - c->flags &= ~CHN_F_HAS_SIZE; - if (!(c->flags & CHN_F_HAS_SIZE)) { /* no previous value */ - /* - * compute a base blksz according to the target irq - * rate, then round to a suitable power of 2 - * in the range 16.. 2^17/2. - * Finally compute a suitable blkcnt. - */ - blksz = round_bufsz( (sndbuf_getbps(bs) * - sndbuf_getspd(bs)) / chn_targetirqrate, - 16, CHN_2NDBUFMAXSIZE / 2); - blkcnt = CHN_2NDBUFMAXSIZE / blksz; - } else { /* use previously defined value */ - blkcnt = sndbuf_getblkcnt(bs); - blksz = sndbuf_getblksz(bs); - } - } else { - /* - * use supplied values if reasonable. Note that here we - * might have blksz which is not a power of 2 if the - * ioctl() to compute it allows such values. - */ - ret = EINVAL; - if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) - goto out; - ret = 0; - c->flags |= CHN_F_HAS_SIZE; - } - - reqblksz = blksz; - if (reqblksz < sndbuf_getbps(bs)) - reqblksz = sndbuf_getbps(bs); - if (reqblksz % sndbuf_getbps(bs)) - reqblksz -= reqblksz % sndbuf_getbps(bs); - - /* adjust for different hw format/speed */ - /* - * Now compute the approx irq rate for the given (soft) blksz, - * reduce to the acceptable range and compute a corresponding blksz - * for the hard buffer. Then set the channel's blocksize and - * corresponding hardbuf value. The number of blocks used should - * be set by the device-specific routine. In fact, even the - * call to sndbuf_setblksz() should not be here! XXX - */ - - irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / blksz; - RANGE(irqhz, 16, 512); - - maxsz = sndbuf_getmaxsize(b); - if (maxsz == 0) /* virtual channels don't appear to allocate bufhard */ - maxsz = CHN_2NDBUFMAXSIZE; - blksz = round_bufsz( (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz, - 16, maxsz / 2); - - /* Increase the size of bufsoft if before increasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (sndbuf_getsize(bs) > maxsize) - maxsize = sndbuf_getsize(bs); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - CHN_UNLOCK(c); - sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); - CHN_LOCK(c); - - /* Decrease the size of bufsoft after decreasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (maxsize > sndbuf_getsize(bs)) - printf("Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE()\n", - c->name, sndbuf_getsize(bs), maxsize); - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - /* - * 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); -out1: - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", - __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, - blksz, maxsize, blkcnt)); -out: - c->flags &= ~CHN_F_SETBLOCKSIZE; -#if 0 - if (1) { - static uint32_t kk = 0; - printf("%u: b %d/%d/%d : (%d)%d/0x%0x | bs %d/%d/%d : (%d)%d/0x%0x\n", ++kk, - sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), - sndbuf_getbps(b), - sndbuf_getspd(b), sndbuf_getfmt(b), - sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), - sndbuf_getbps(bs), - sndbuf_getspd(bs), sndbuf_getfmt(bs)); - if (sndbuf_getsize(b) % sndbuf_getbps(b) || - sndbuf_getblksz(b) % sndbuf_getbps(b) || - sndbuf_getsize(bs) % sndbuf_getbps(bs) || - sndbuf_getblksz(b) % sndbuf_getbps(b)) { - printf("%u: bps/blksz alignment screwed!\n", kk); - } - } -#endif - return ret; -} - int chn_trigger(struct pcm_channel *c, int go) { @@ -1372,6 +1756,7 @@ chn_buildfeeder(struct pcm_channel *c) struct pcm_feederdesc desc; u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; int err; + char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); while (chn_removefeeder(c) == 0); @@ -1393,7 +1778,7 @@ chn_buildfeeder(struct pcm_channel *c) } else { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; - desc.in = 0; + desc.in = c->format; } else { DEB(printf("can't decide which feeder type to use!\n")); return EOPNOTSUPP; @@ -1432,7 +1817,42 @@ chn_buildfeeder(struct pcm_channel *c) desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); - fc = feeder_getclass(&desc); + if (type == FEEDER_VOLUME || type == FEEDER_RATE) { + if (c->feeder->desc->out & AFMT_32BIT) + strlcpy(fmtstr,"s32le", sizeof(fmtstr)); + else if (c->feeder->desc->out & AFMT_24BIT) + strlcpy(fmtstr, "s24le", sizeof(fmtstr)); + else { + /* + * 8bit doesn't provide enough headroom + * for proper processing without + * creating too much noises. Force to + * 16bit instead. + */ + strlcpy(fmtstr, "s16le", sizeof(fmtstr)); + } + if (!(c->feeder->desc->out & AFMT_8BIT) && + c->feeder->desc->out & AFMT_BIGENDIAN) + afmtstr_swap_endian(fmtstr); + if (!(c->feeder->desc->out & (AFMT_A_LAW | AFMT_MU_LAW)) && + !(c->feeder->desc->out & AFMT_SIGNED)) + afmtstr_swap_sign(fmtstr); + desc.in = afmtstr2afmt(NULL, fmtstr, AFMTSTR_MONO_RETURN); + if (desc.in == 0) + desc.in = AFMT_S16_LE; + /* feeder_volume need stereo processing */ + if (type == FEEDER_VOLUME || + c->feeder->desc->out & AFMT_STEREO) + desc.in |= AFMT_STEREO; + desc.out = desc.in; + fc = feeder_getclass(&desc); + if (fc != NULL && fc->desc != NULL) + desc.flags = fc->desc->flags; + } else { + fc = feeder_getclass(&desc); + if (fc != NULL && fc->desc != NULL) + desc = *fc->desc; + } DEB(printf("got %p\n", fc)); if (fc == NULL) { DEB(printf("can't find required feeder type %d\n", type)); @@ -1441,7 +1861,7 @@ chn_buildfeeder(struct pcm_channel *c) } DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); - tmp[0] = fc->desc->in; + tmp[0] = desc.in; tmp[1] = 0; if (chn_fmtchain(c, tmp) == 0) { DEB(printf("failed\n")); @@ -1450,7 +1870,7 @@ chn_buildfeeder(struct pcm_channel *c) } DEB(printf("ok\n")); - err = chn_addfeeder(c, fc, fc->desc); + err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); @@ -1475,7 +1895,7 @@ chn_buildfeeder(struct pcm_channel *c) sndbuf_setfmt(c->bufhard, hwfmt); if ((flags & (1 << FEEDER_VOLUME))) { - uint32_t parent = SOUND_MIXER_NONE; + u_int32_t parent = SOUND_MIXER_NONE; int vol, left, right; vol = 100 | (100 << 8); @@ -1553,20 +1973,10 @@ chn_notify(struct pcm_channel *c, u_int32_t flags) */ } if (flags & CHN_N_BLOCKSIZE) { - int blksz; /* - * scan the children, find the lowest blocksize and use that - * for the hard blocksize + * Set to default latency profile */ - blksz = sndbuf_getmaxsize(c->bufhard) / 2; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (sndbuf_getblksz(child->bufhard) < blksz) - blksz = sndbuf_getblksz(child->bufhard); - CHN_UNLOCK(child); - } - chn_setblocksize(c, 2, blksz); + chn_setlatency(c, chn_latency); } if (flags & CHN_N_TRIGGER) { int nrun; diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index d6426f7..3d4f537 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -79,6 +79,7 @@ struct pcm_channel { u_int32_t align; int volume; + int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; @@ -86,7 +87,8 @@ struct pcm_channel { u_int32_t blocks; int direction; - unsigned int interrupts, xruns; + unsigned int interrupts, xruns, feedcount; + unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; @@ -142,6 +144,7 @@ int chn_setvolume(struct pcm_channel *c, int left, int right); int chn_setspeed(struct pcm_channel *c, int speed); int chn_setformat(struct pcm_channel *c, u_int32_t fmt); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); +int chn_setlatency(struct pcm_channel *c, int latency); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); @@ -181,6 +184,30 @@ int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); +#define AFMTSTR_NONE 0 /* "s16le" */ +#define AFMTSTR_SIMPLE 1 /* "s16le:s" */ +#define AFMTSTR_NUM 2 /* "s16le:2" */ +#define AFMTSTR_FULL 3 /* "s16le:stereo" */ + +#define AFMTSTR_MAXSZ 13 /* include null terminator */ + +#define AFMTSTR_MONO_RETURN 0 +#define AFMTSTR_STEREO_RETURN 1 + +struct afmtstr_table { + char *fmtstr; + u_int32_t format; +}; + +int afmtstr_swap_sign(char *); +int afmtstr_swap_endian(char *); +u_int32_t afmtstr2afmt(struct afmtstr_table *, const char *, int); +u_int32_t afmt2afmtstr(struct afmtstr_table *, u_int32_t, char *, size_t, int, int); + +extern int chn_latency; +extern int chn_latency_profile; +extern int report_soft_formats; + #define PCMDIR_VIRTUAL 2 #define PCMDIR_PLAY 1 #define PCMDIR_REC -1 @@ -218,6 +245,17 @@ int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 +#define CHN_LATENCY_MIN 0 +#define CHN_LATENCY_MAX 10 +#define CHN_LATENCY_DEFAULT 5 +#define CHN_POLICY_MIN CHN_LATENCY_MIN +#define CHN_POLICY_MAX CHN_LATENCY_MAX +#define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT + +#define CHN_LATENCY_PROFILE_MIN 0 +#define CHN_LATENCY_PROFILE_MAX 1 +#define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX + /* * This should be large enough to hold all pcm data between * tsleeps in chn_{read,write} at the highest sample rate. diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 56f50a7..940c3fd 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -215,10 +215,12 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) fmt = 0; break; - case SND_DEV_DSPREC: + case SND_DEV_DSPHW: + /* + * HW *specific* access without channel numbering confusion + * caused by "first come first served" by dsp_clone(). + */ fmt = AFMT_U8; - if (flags & FWRITE) - return EINVAL; chnum = PCMCHAN(i_dev); break; @@ -768,7 +770,7 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); - chn_sync(wrch, sndbuf_getsize(wrch->bufsoft) - 4); + chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; @@ -1085,12 +1087,11 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * case SNDCTL_DSP_GETODELAY: if (wrch) { - struct snd_dbuf *b = wrch->bufhard; struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ - *arg_i = sndbuf_getready(b) + sndbuf_getready(bs); + *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; @@ -2131,31 +2132,29 @@ dsp_oss_syncstart(int sg_id) static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) { - int fragln, fragsz, maxfrags, ret; + int ret; + + if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) + return EIO; /* Default: success */ ret = 0; - /* Scale policy [0..10] to fragment size [2^4..2^16]. */ - fragln = policy; - RANGE(fragln, 0, 10); - fragln += 4; - fragsz = 1 << fragln; - - maxfrags = CHN_2NDBUFMAXSIZE / fragsz; - if (rdch) { CHN_LOCK(rdch); - ret = chn_setblocksize(rdch, maxfrags, fragsz); + ret = chn_setlatency(rdch, policy); CHN_UNLOCK(rdch); } if (wrch && ret == 0) { CHN_LOCK(wrch); - ret = chn_setblocksize(wrch, maxfrags, fragsz); + ret = chn_setlatency(wrch, policy); CHN_UNLOCK(wrch); } + if (ret) + ret = EIO; + return ret; } diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c index 7143902..0c94319 100644 --- a/sys/dev/sound/pcm/fake.c +++ b/sys/dev/sound/pcm/fake.c @@ -142,10 +142,11 @@ fkchan_setup(device_t dev) c->parentsnddev = d; /* * Fake channel is such a blessing in disguise. Using this, - * we can keep track prefered virtual channel speed without + * we can keep track prefered virtual channel speed / format without * querying kernel hint repetitively (see vchan_create / vchan.c). */ c->speed = 0; + c->format = 0; snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); return c; diff --git a/sys/dev/sound/pcm/feeder.c b/sys/dev/sound/pcm/feeder.c index b70bd58..4f0de4f 100644 --- a/sys/dev/sound/pcm/feeder.c +++ b/sys/dev/sound/pcm/feeder.c @@ -35,6 +35,11 @@ MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 #undef FEEDER_DEBUG +int feeder_buffersize = FEEDBUFSZ; +TUNABLE_INT("hw.snd.feeder_buffersize", &feeder_buffersize); +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_buffersize, CTLFLAG_RD, + &feeder_buffersize, FEEDBUFSZ, "feeder buffer size"); + struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; @@ -72,6 +77,53 @@ feeder_register(void *p) SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; + /* initialize global variables */ + + if (snd_verbose < 0 || snd_verbose > 3) + snd_verbose = 1; + + if (snd_unit < 0 || snd_unit > PCMMAXDEV) + snd_unit = 0; + + if (snd_maxautovchans < 0 || + snd_maxautovchans > SND_MAXVCHANS) + snd_maxautovchans = 0; + + if (chn_latency < CHN_LATENCY_MIN || + chn_latency > CHN_LATENCY_MAX) + chn_latency = CHN_LATENCY_DEFAULT; + + if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || + chn_latency_profile > CHN_LATENCY_PROFILE_MAX) + chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; + + if (feeder_buffersize < FEEDBUFSZ_MIN || + feeder_buffersize > FEEDBUFSZ_MAX) + feeder_buffersize = FEEDBUFSZ; + + if (feeder_rate_min < FEEDRATE_MIN || + feeder_rate_max < FEEDRATE_MIN || + feeder_rate_min > FEEDRATE_MAX || + feeder_rate_max > FEEDRATE_MAX || + !(feeder_rate_min < feeder_rate_max)) { + feeder_rate_min = FEEDRATE_RATEMIN; + feeder_rate_max = FEEDRATE_RATEMAX; + } + + if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || + feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) + feeder_rate_round = FEEDRATE_ROUNDHZ; + + if (bootverbose) + printf("%s: snd_unit=%d snd_maxautovchans=%d " + "latency=%d feeder_buffersize=%d " + "feeder_rate_min=%d feeder_rate_max=%d " + "feeder_rate_round=%d\n", + __func__, snd_unit, snd_maxautovchans, + chn_latency, feeder_buffersize, + feeder_rate_min, feeder_rate_max, + feeder_rate_round); + /* we've got our root feeder so don't veto pcm loading anymore */ pcm_veto_load = 0; @@ -259,122 +311,152 @@ chainok(struct pcm_feeder *test, struct pcm_feeder *stop) return 1; } -static struct pcm_feeder * -feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) -{ - struct feedertab_entry *fte; - struct pcm_feeder *try, *ret; - - DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); - if (fmtvalid(source->desc->out, to)) { - DEB(printf("got it\n")); - return source; - } - - if (maxdepth < 0) - return NULL; - - SLIST_FOREACH(fte, &feedertab, link) { - if (fte->desc == NULL) - continue; - if (fte->desc->type != FEEDER_FMT) - continue; - if (fte->desc->in == source->desc->out) { - try = feeder_create(fte->feederclass, fte->desc); - if (try) { - try->source = source; - ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; - if (ret != NULL) - return ret; - feeder_destroy(try); - } - } - } - /* printf("giving up %s...\n", source->class->name); */ +/* + * See feeder_fmtchain() for the mumbo-jumbo ridiculous explaination + * of what the heck is this FMT_Q_* + */ +#define FMT_Q_UP 1 +#define FMT_Q_DOWN 2 +#define FMT_Q_EQ 3 +#define FMT_Q_MULTI 4 - return NULL; -} +/* + * 14bit format scoring + * -------------------- + * + * 13 12 11 10 9 8 2 1 0 offset + * +---+---+---+---+---+---+-------------+---+---+ + * | X | X | X | X | X | X | X X X X X X | X | X | + * +---+---+---+---+---+---+-------------+---+---+ + * | | | | | | | | | + * | | | | | | | | +--> signed? + * | | | | | | | | + * | | | | | | | +------> bigendian? + * | | | | | | | + * | | | | | | +---------------> total channels + * | | | | | | + * | | | | | +------------------------> AFMT_A_LAW + * | | | | | + * | | | | +----------------------------> AFMT_MU_LAW + * | | | | + * | | | +--------------------------------> AFMT_8BIT + * | | | + * | | +------------------------------------> AFMT_16BIT + * | | + * | +----------------------------------------> AFMT_24BIT + * | + * +--------------------------------------------> AFMT_32BIT + */ +#define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) +#define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) +#define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) +#define score_val(s1) ((s1) & 0x3f00) +#define score_cse(s1) ((s1) & 0x7f) -int +u_int32_t chn_fmtscore(u_int32_t fmt) { - if (fmt & AFMT_32BIT) - return 60; - if (fmt & AFMT_24BIT) - return 50; - if (fmt & AFMT_16BIT) - return 40; - if (fmt & (AFMT_U8|AFMT_S8)) - return 30; - if (fmt & AFMT_MU_LAW) - return 20; + u_int32_t ret; + + ret = 0; + if (fmt & AFMT_SIGNED) + ret |= 1 << 0; + if (fmt & AFMT_BIGENDIAN) + ret |= 1 << 1; + if (fmt & AFMT_STEREO) + ret |= (2 & 0x3f) << 2; + else + ret |= (1 & 0x3f) << 2; if (fmt & AFMT_A_LAW) - return 10; - return 0; + ret |= 1 << 8; + else if (fmt & AFMT_MU_LAW) + ret |= 1 << 9; + else if (fmt & AFMT_8BIT) + ret |= 1 << 10; + else if (fmt & AFMT_16BIT) + ret |= 1 << 11; + else if (fmt & AFMT_24BIT) + ret |= 1 << 12; + else if (fmt & AFMT_32BIT) + ret |= 1 << 13; + + return ret; } -u_int32_t -chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) +static u_int32_t +chn_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) { - u_int32_t best; - int i, score, score2, oldscore; + u_int32_t best, score, score2, oldscore; + int i; + + if (fmt == 0 || fmts == NULL || fmts[0] == 0) + return 0; + + if (fmtvalid(fmt, fmts)) + return fmt; best = 0; score = chn_fmtscore(fmt); oldscore = 0; for (i = 0; fmts[i] != 0; i++) { score2 = chn_fmtscore(fmts[i]); - if (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; + if (cheq && !score_cheq(score, score2)) + continue; + if (oldscore == 0 || + (score_val(score2) == score_val(score)) || + (score_val(score2) == score_val(oldscore)) || + (score_val(score2) > score_val(oldscore) && + score_val(score2) < score_val(score)) || + (score_val(score2) < score_val(oldscore) && + score_val(score2) > score_val(score)) || + (score_val(oldscore) < score_val(score) && + score_val(score2) > score_val(oldscore))) { + if (score_val(oldscore) != score_val(score2) || + score_cse(score) == score_cse(score2) || + ((score_cse(oldscore) != score_cse(score) && + !score_endianeq(score, oldscore) && + (score_endianeq(score, score2) || + (!score_signeq(score, oldscore) && + score_signeq(score, score2)))))) { + best = fmts[i]; + oldscore = score2; + } } } return best; } u_int32_t -chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) { - u_int32_t best; - int i, score, score2, oldscore; + return chn_fmtbestfunc(fmt, fmts, 0); +} - best = 0; - score = chn_fmtscore(fmt); - oldscore = 0; - for (i = 0; fmts[i] != 0; i++) { - if ((fmt & AFMT_STEREO) == (fmts[i] & AFMT_STEREO)) { - score2 = chn_fmtscore(fmts[i]); - if (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; - } - } - } - return best; +u_int32_t +chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 1); } u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) { u_int32_t best1, best2; - int score, score1, score2; + u_int32_t score, score1, score2; + + if (fmtvalid(fmt, fmts)) + return fmt; best1 = chn_fmtbeststereo(fmt, fmts); best2 = chn_fmtbestbit(fmt, fmts); - if (best1 != 0 && best2 != 0) { + if (best1 != 0 && best2 != 0 && best1 != best2) { if (fmt & AFMT_STEREO) return best1; else { - score = chn_fmtscore(fmt); - score1 = chn_fmtscore(best1); - score2 = chn_fmtscore(best2); + score = score_val(chn_fmtscore(fmt)); + score1 = score_val(chn_fmtscore(best1)); + score2 = score_val(chn_fmtscore(best2)); if (score1 == score2 || score1 == score) return best1; else if (score2 == score) @@ -389,6 +471,184 @@ chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) return best2; } +static struct pcm_feeder * +feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) +{ + struct feedertab_entry *fte, *ftebest; + struct pcm_feeder *try, *ret; + uint32_t fl, qout, qsrc, qdst; + int qtype; + + if (to == NULL || to[0] == 0) + return NULL; + + DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); + if (fmtvalid(source->desc->out, to)) { + DEB(printf("got it\n")); + return source; + } + + if (maxdepth < 0) + return NULL; + + /* + * WARNING: THIS IS _NOT_ FOR THE FAINT HEART + * Disclaimer: I don't expect anybody could understand this + * without deep logical and mathematical analysis + * involving various unnamed probability theorem. + * + * This "Best Fit Random Chain Selection" (BLEHBLEHWHATEVER) algorithm + * is **extremely** difficult to digest especially when applied to + * large sets / numbers of random chains (feeders), each with + * unique characteristic providing different sets of in/out format. + * + * Basically, our FEEDER_FMT (see feeder_fmt.c) chains characteristic: + * 1) Format chains + * 1.1 "8bit to any, not to 8bit" + * 1.1.1 sign can remain consistent, e.g: u8 -> u16[le|be] + * 1.1.2 sign can be changed, e.g: u8 -> s16[le|be] + * 1.1.3 endian can be changed, e.g: u8 -> u16[le|be] + * 1.1.4 both can be changed, e.g: u8 -> [u|s]16[le|be] + * 1.2 "Any to 8bit, not from 8bit" + * 1.2.1 sign can remain consistent, e.g: s16le -> s8 + * 1.2.2 sign can be changed, e.g: s16le -> u8 + * 1.2.3 source endian can be anything e.g: s16[le|be] -> s8 + * 1.2.4 source endian / sign can be anything e.g: [u|s]16[le|be] -> u8 + * 1.3 "Any to any where BOTH input and output either 8bit or non-8bit" + * 1.3.1 endian MUST remain consistent + * 1.3.2 sign CAN be changed + * 1.4 "Long jump" is allowed, e.g: from 16bit to 32bit, excluding + * 16bit to 24bit . + * 2) Channel chains (mono <-> stereo) + * 2.1 Both endian and sign MUST remain consistent + * 3) Endian chains (big endian <-> little endian) + * 3.1 Channels and sign MUST remain consistent + * 4) Sign chains (signed <-> unsigned) + * 4.1 Channels and endian MUST remain consistent + * + * .. and the mother of all chaining rules: + * + * Rules 0: Source and destination MUST not contain multiple selections. + * (qtype != FMT_Q_MULTI) + * + * First of all, our caller ( chn_fmtchain() ) will reduce the possible + * multiple from/to formats to a single best format using chn_fmtbest(). + * Then, using chn_fmtscore(), we determine the chaining characteristic. + * Our main goal is to narrow it down until it reach FMT_Q_EQ chaining + * type while still adhering above chaining rules. + * + * The need for this complicated chaining procedures is inevitable, + * since currently we have more than 200 different types of FEEDER_FMT + * doing various unique format conversion. Without this (the old way), + * it is possible to generate broken chain since it doesn't do any + * sanity checking to ensure that the output format is "properly aligned" + * with the direction of conversion (quality up/down/equal). + * + * Conversion: s24le to s32le + * Possible chain: 1) s24le -> s32le (correct, optimized) + * 2) s24le -> s16le -> s32le + * (since we have feeder_24to16 and feeder_16to32) + * +-- obviously broken! + * + * Using scoring mechanisme, this will ensure that the chaining + * process do the right thing, or at least, give the best chain + * possible without causing quality (the 'Q') degradation. + */ + + qdst = chn_fmtscore(to[0]); + qsrc = chn_fmtscore(source->desc->out); + +#define score_q(s1) score_val(s1) +#define score_8bit(s1) ((s1) & 0x700) +#define score_non8bit(s1) (!score_8bit(s1)) +#define score_across8bit(s1, s2) ((score_8bit(s1) && score_non8bit(s2)) || \ + (score_8bit(s2) && score_non8bit(s1))) + +#define FMT_CHAIN_Q_UP(s1, s2) (score_q(s1) < score_q(s2)) +#define FMT_CHAIN_Q_DOWN(s1, s2) (score_q(s1) > score_q(s2)) +#define FMT_CHAIN_Q_EQ(s1, s2) (score_q(s1) == score_q(s2)) +#define FMT_Q_DOWN_FLAGS(s1, s2) (0x1 | (score_across8bit(s1, s2) ? \ + 0x2 : 0x0)) +#define FMT_Q_UP_FLAGS(s1, s2) FMT_Q_DOWN_FLAGS(s1, s2) +#define FMT_Q_EQ_FLAGS(s1, s2) (0x3ffc | \ + ((score_cheq(s1, s2) && \ + score_endianeq(s1, s2)) ? \ + 0x1 : 0x0) | \ + ((score_cheq(s1, s2) && \ + score_signeq(s1, s2)) ? \ + 0x2 : 0x0)) + + /* Determine chaining direction and set matching flag */ + fl = 0x3fff; + if (to[1] != 0) { + qtype = FMT_Q_MULTI; + printf("%s: WARNING: FMT_Q_MULTI chaining. Expect the unexpected.\n", __func__); + } else if (FMT_CHAIN_Q_DOWN(qsrc, qdst)) { + qtype = FMT_Q_DOWN; + fl = FMT_Q_DOWN_FLAGS(qsrc, qdst); + } else if (FMT_CHAIN_Q_UP(qsrc, qdst)) { + qtype = FMT_Q_UP; + fl = FMT_Q_UP_FLAGS(qsrc, qdst); + } else { + qtype = FMT_Q_EQ; + fl = FMT_Q_EQ_FLAGS(qsrc, qdst); + } + + ftebest = NULL; + + SLIST_FOREACH(fte, &feedertab, link) { + if (fte->desc == NULL) + continue; + if (fte->desc->type != FEEDER_FMT) + continue; + qout = chn_fmtscore(fte->desc->out); +#define FMT_Q_MULTI_VALIDATE(qt) ((qt) == FMT_Q_MULTI) +#define FMT_Q_FL_MATCH(qfl, s1, s2) (((s1) & (qfl)) == ((s2) & (qfl))) +#define FMT_Q_UP_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_UP && \ + score_q(s3) >= score_q(s1) && \ + score_q(s3) <= score_q(s2)) +#define FMT_Q_DOWN_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_DOWN && \ + score_q(s3) <= score_q(s1) && \ + score_q(s3) >= score_q(s2)) +#define FMT_Q_EQ_VALIDATE(qt, s1, s2) ((qt) == FMT_Q_EQ && \ + score_q(s1) == score_q(s2)) + if (fte->desc->in == source->desc->out && + (FMT_Q_MULTI_VALIDATE(qtype) || + (FMT_Q_FL_MATCH(fl, qout, qdst) && + (FMT_Q_UP_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_DOWN_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_EQ_VALIDATE(qtype, qdst, qout))))) { + try = feeder_create(fte->feederclass, fte->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } else if (fte->desc->in == source->desc->out) { + /* XXX quality must be considered! */ + if (ftebest == NULL) + ftebest = fte; + } + } + + if (ftebest != NULL) { + try = feeder_create(ftebest->feederclass, ftebest->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } + + /* printf("giving up %s...\n", source->class->name); */ + + return NULL; +} + u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to) { @@ -401,13 +661,15 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) KASSERT(to != NULL, ("to == NULL")); KASSERT(to[0] != 0, ("to[0] == 0")); + if (c == NULL || c->feeder == NULL || to == NULL || to[0] == 0) + return 0; + stop = c->feeder; + best = 0; if (c->direction == PCMDIR_REC && c->feeder->desc->type == FEEDER_ROOT) { from = chn_getcaps(c)->fmtlist; - if (fmtvalid(to[0], from)) - from = to; - else { + if (from[1] != 0) { best = chn_fmtbest(to[0], from); if (best != 0) { tmpfrom[0] = best; @@ -420,49 +682,60 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) tmpfrom[1] = 0; from = tmpfrom; if (to[1] != 0) { - if (fmtvalid(tmpfrom[0], to)) { - tmpto[0] = tmpfrom[0]; + best = chn_fmtbest(from[0], to); + if (best != 0) { + tmpto[0] = best; tmpto[1] = 0; to = tmpto; - } else { - best = chn_fmtbest(tmpfrom[0], to); - if (best != 0) { - tmpto[0] = best; - tmpto[1] = 0; - to = tmpto; - } } } } - i = 0; - best = 0; - bestmax = 100; - while (from[i] != 0) { - c->feeder->desc->out = from[i]; - try = NULL; +#define FEEDER_FMTCHAIN_MAXDEPTH 8 + + try = NULL; + + if (to[0] != 0 && from[0] != 0 && + to[1] == 0 && from[1] == 0) { max = 0; - while (try == NULL && max < 8) { + best = from[0]; + c->feeder->desc->out = best; + do { try = feeder_fmtchain(to, c->feeder, stop, max); - if (try == NULL) - max++; - } - if (try != NULL && max < bestmax) { - bestmax = max; - best = from[i]; - } - while (try != NULL && try != stop) { - del = try; - try = try->source; - feeder_destroy(del); + DEB(if (try != NULL) { + printf("%s: 0x%08x -> 0x%08x (maxdepth: %d)\n", + __func__, from[0], to[0], max) + }); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + } else { + printf("%s: Using the old-way format chaining!\n", __func__); + i = 0; + best = 0; + bestmax = 100; + while (from[i] != 0) { + c->feeder->desc->out = from[i]; + try = NULL; + max = 0; + do { + try = feeder_fmtchain(to, c->feeder, stop, max); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + if (try != NULL && max < bestmax) { + bestmax = max; + best = from[i]; + } + while (try != NULL && try != stop) { + del = try; + try = try->source; + feeder_destroy(del); + } + i++; } - i++; - } - if (best == 0) - return 0; + if (best == 0) + return 0; - c->feeder->desc->out = best; - try = feeder_fmtchain(to, c->feeder, stop, bestmax); + c->feeder->desc->out = best; + try = feeder_fmtchain(to, c->feeder, stop, bestmax); + } if (try == NULL) return 0; @@ -521,28 +794,78 @@ static int feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) { struct snd_dbuf *src = source; - int l; - u_int8_t x; + int l, offset; KASSERT(count > 0, ("feed_root: count == 0")); /* count &= ~((1 << ch->align) - 1); */ KASSERT(count > 0, ("feed_root: aligned count == 0 (align = %d)", ch->align)); + if (++ch->feedcount == 0) + ch->feedcount = 2; + l = min(count, sndbuf_getready(src)); - sndbuf_dispose(src, buffer, l); /* When recording only return as much data as available */ - if (ch->direction == PCMDIR_REC) + if (ch->direction == PCMDIR_REC) { + sndbuf_dispose(src, buffer, l); return l; + } -/* - if (l < count) - printf("appending %d bytes\n", count - l); -*/ - x = (sndbuf_getfmt(src) & AFMT_SIGNED)? 0 : 0x80; - while (l < count) - buffer[l++] = x; + offset = count - l; + + if (offset > 0) { + if (snd_verbose > 3) + printf("%s: (%s) %spending %d bytes " + "(count=%d l=%d feed=%d)\n", + __func__, + (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + (ch->feedcount == 1) ? "pre" : "ap", + offset, count, l, ch->feedcount); + + if (ch->feedcount == 1) { + if (offset > 0) + memset(buffer, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (l > 0) + sndbuf_dispose(src, buffer + offset, l); + else + ch->feedcount--; + } else { + if (l > 0) + sndbuf_dispose(src, buffer, l); + if (offset > 0) { +#if 1 + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; +#else + if (l < 1 || (ch->flags & CHN_F_CLOSING)) { + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; + } else { + int cp, tgt; + + tgt = l; + while (offset > 0) { + cp = min(l, offset); + memcpy(buffer + tgt, buffer, cp); + offset -= cp; + tgt += cp; + } + ch->xruns++; + } +#endif + } + } + } else if (l > 0) + sndbuf_dispose(src, buffer, l); return count; } @@ -561,8 +884,3 @@ static struct feeder_class feeder_root_class = { }; SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); - - - - - diff --git a/sys/dev/sound/pcm/feeder.h b/sys/dev/sound/pcm/feeder.h index 545fd24..a3985c0 100644 --- a/sys/dev/sound/pcm/feeder.h +++ b/sys/dev/sound/pcm/feeder.h @@ -53,7 +53,7 @@ struct pcm_feeder { void feeder_register(void *p); struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); -int chn_fmtscore(u_int32_t fmt); +u_int32_t chn_fmtscore(u_int32_t fmt); u_int32_t chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts); @@ -72,11 +72,11 @@ static struct feeder_class feeder ## _class = { \ .desc = feeder ## _desc, \ .data = pdata, \ }; \ -SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _class); +SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_ANY, feeder_register, &feeder ## _class); #define FEEDER_ROOT 1 #define FEEDER_FMT 2 -#define FEEDER_MIXER 3 +#define FEEDER_MIXER 3 #define FEEDER_RATE 4 #define FEEDER_FILTER 5 #define FEEDER_VOLUME 6 @@ -85,4 +85,27 @@ SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _cl #define FEEDRATE_SRC 1 #define FEEDRATE_DST 2 +#define FEEDRATE_RATEMIN 1 +#define FEEDRATE_RATEMAX 2016000 /* 48000 * 42 */ +#define FEEDRATE_MIN 1 +#define FEEDRATE_MAX 0x7fffff /* sign 24bit ~ 8ghz ! */ + +#define FEEDRATE_ROUNDHZ 25 +#define FEEDRATE_ROUNDHZ_MIN 0 +#define FEEDRATE_ROUNDHZ_MAX 500 + +/* + * Default buffer size for feeder processing. + * + * Big = less sndbuf_feed(), more memory usage. + * Small = aggresive sndbuf_feed() (perhaps too much), less memory usage. + */ +#define FEEDBUFSZ 16384 +#define FEEDBUFSZ_MIN 2048 +#define FEEDBUFSZ_MAX 131072 + +extern int feeder_rate_min; +extern int feeder_rate_max; +extern int feeder_rate_round; +extern int feeder_buffersize; diff --git a/sys/dev/sound/pcm/feeder_fmt.c b/sys/dev/sound/pcm/feeder_fmt.c index 17cd030..0ccc7b9 100644 --- a/sys/dev/sound/pcm/feeder_fmt.c +++ b/sys/dev/sound/pcm/feeder_fmt.c @@ -43,12 +43,13 @@ SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); -#define FEEDBUFSZ 8192 -#define FEEDBUF24SZ 8190 - #define FMT_TRACE(x...) /* printf(x) */ #define FMT_TEST(x, y...) /* if (x) FMT_TRACE(y) */ -#define FMT_ALIGNBYTE(x) /* x */ + +struct feed_fmt_info { + int bps, bufsz; + uint8_t *buffer; +}; /* * Sign inverted ulaw/alaw -> 8 table @@ -194,33 +195,48 @@ static uint8_t u8_to_alaw_tbl[] = { }; static int -feed_table_u8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 1) + return 0; + k = FEEDER_FEED(f->source, c, b, count, source); j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; while (j > 0) { j--; - b[j] = tbl[b[j]] ^ 0x80; + b[j] = tbl[b[j]] ^ sign; } return k; } static int -feed_table_s16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; k <<= 1; j = k; - while (i > 0) { - b[--j] = tbl[b[--i]]; - b[--j] = 0; + sign = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + if (f->desc->out & AFMT_BIGENDIAN) { + while (i > 0) { + b[--j] = 0; + b[--j] = tbl[b[--i]] ^ sign; + } + } else { + while (i > 0) { + b[--j] = tbl[b[--i]] ^ sign; + b[--j] = 0; + } } return k; } @@ -229,188 +245,286 @@ static int feed_table_xlaw(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; + if (count < 1) + return 0; + k = FEEDER_FEED(f->source, c, b, count, source); j = k; + sign = (f->desc->in & AFMT_SIGNED) ? 0x80 : 0x00; while (j > 0) { j--; - b[j] = tbl[b[j]]; + b[j] = tbl[b[j] ^ sign]; } return k; } -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { +static struct pcm_feederdesc feeder_ulawto8_desc[] = { {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_ulawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto8, 0, ulaw_to_s8_tbl); -static struct pcm_feederdesc feeder_alawtou8_desc[] = { +static struct pcm_feederdesc feeder_alawto8_desc[] = { {FEEDER_FMT, AFMT_A_LAW, AFMT_U8, 0}, {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_alawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_alawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_alawtou8, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto8, 0, alaw_to_s8_tbl); -static struct pcm_feederdesc feeder_ulawtos16le_desc[] = { +static struct pcm_feederdesc feeder_ulawto16_desc[] = { {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_ulawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtos16le, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto16, 0, ulaw_to_s8_tbl); -static struct pcm_feederdesc feeder_alawtos16le_desc[] = { +static struct pcm_feederdesc feeder_alawto16_desc[] = { {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_alawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_alawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_alawtos16le, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto16, 0, alaw_to_s8_tbl); -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { +static struct pcm_feederdesc feeder_8toulaw_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_MU_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_MU_LAW|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_u8toulaw_methods[] = { +static kobj_method_t feeder_8toulaw_methods[] = { KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw_tbl); +FEEDER_DECLARE(feeder_8toulaw, 0, u8_to_ulaw_tbl); -static struct pcm_feederdesc feeder_u8toalaw_desc[] = { +static struct pcm_feederdesc feeder_8toalaw_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_A_LAW, 0}, {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_A_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_A_LAW|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_u8toalaw_methods[] = { +static kobj_method_t feeder_8toalaw_methods[] = { KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toalaw, 0, u8_to_alaw_tbl); +FEEDER_DECLARE(feeder_8toalaw, 0, u8_to_alaw_tbl); /* - * Conversion rules:- - * 1. BE -> LE - * 2. if fmt == u8 , u8 -> s8 (economical) - * 3. Xle -> 16le - * 4. if fmt != u8 && fmt == u16le , u16le -> s16le - * 4. s16le mono -> s16le stereo - * * All conversion done in byte level to preserve endianess. */ static int -feed_common_init(struct pcm_feeder *f) +feed_fmt_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT|M_ZERO); - if (f->data == NULL) + struct feed_fmt_info *info; + + info = malloc(sizeof(*info), M_FMTFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + + info->bps = 1; + info->bps <<= (f->desc->in & AFMT_STEREO) ? 1 : 0; + if (f->desc->in & AFMT_16BIT) + info->bps <<= 1; + else if (f->desc->in & AFMT_24BIT) + info->bps *= 3; + else if (f->desc->in & AFMT_32BIT) + info->bps <<= 2; + info->bufsz = feeder_buffersize - (feeder_buffersize % info->bps); + + info->buffer = malloc(info->bufsz, M_FMTFEEDER, M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_FMTFEEDER); return ENOMEM; + } + + f->data = info; + return 0; } static int -feed_common_free(struct pcm_feeder *f) +feed_fmt_free(struct pcm_feeder *f) { - if (f->data) - free(f->data, M_FMTFEEDER); + struct feed_fmt_info *info = f->data; + + if (info) { + if (info->buffer) + free(info->buffer, M_FMTFEEDER); + free(info, M_FMTFEEDER); + } f->data = NULL; return 0; } +#define swap_sign(fdr) \ + (((((fdr)->desc->in & AFMT_SIGNED) && \ + !((fdr)->desc->out & AFMT_SIGNED)) || \ + (!((fdr)->desc->in & AFMT_SIGNED) && \ + ((fdr)->desc->out & AFMT_SIGNED))) ? 0x80 : 0x00) + /* * Bit conversion */ static int -feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_8to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, k; + int sign; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; k <<= 1; j = k; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = 0; + sign = swap_sign(f); + if (f->desc->out & AFMT_BIGENDIAN) { + while (i > 0) { + b[--j] = 0; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 0) { + b[--j] = b[--i] ^ sign; + b[--j] = 0; + } } return k; } -static struct pcm_feederdesc feeder_8to16le_desc[] = { +static struct pcm_feederdesc feeder_8to16_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), +static kobj_method_t feeder_8to16_methods[] = { + KOBJMETHOD(feeder_feed, feed_8to16), {0, 0} }; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); +FEEDER_DECLARE(feeder_8to16, 0, NULL); static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; + if (count < 1) + return 0; k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k >> 1; - while (i > 0) { - b[--j] = src[--i]; - i--; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + i--; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = src[--i] ^ sign; + i--; + } } return k >> 1; } -static struct pcm_feederdesc feeder_16leto8_desc[] = { +static struct pcm_feederdesc feeder_16to8_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_16leto8), +static kobj_method_t feeder_16to8_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_16to8), {0, 0} }; -FEEDER_DECLARE(feeder_16leto8, 0, NULL); +FEEDER_DECLARE(feeder_16to8, 0, NULL); static int -feed_16leto24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; + int i, j, k, sign; + if (count < 3) + return 0; k = (count / 3) << 1; k = FEEDER_FEED(f->source, c, b, k, source); if (k < 2) { @@ -419,146 +533,370 @@ feed_16leto24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, return 0; } FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = (k >> 1) * 3; k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = 0; + } } return k; } -static struct pcm_feederdesc feeder_16leto24le_desc[] = { +static struct pcm_feederdesc feeder_16to24_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U24_LE, 0}, {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S24_LE, 0}, {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_16leto24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto24le), +static kobj_method_t feeder_16to24_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to24), {0, 0} }; -FEEDER_DECLARE(feeder_16leto24le, 0, NULL); +FEEDER_DECLARE(feeder_16to24, 0, NULL); static int -feed_24leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_24to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; - k = (count * 3) >> 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (count < 2) + return 0; + k = (count >> 1) * 3; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 3) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 3); + k -= k % 3; i = (k / 3) << 1; j = i; - while (i > 0) { - b[--i] = src[--k]; - b[--i] = src[--k]; - k--; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + k--; + b[--i] = src[--k]; + b[--i] = src[--k] ^ sign; + } + } else { + while (i > 1) { + b[--i] = src[--k] ^ sign; + b[--i] = src[--k]; + k--; + } } return j; } -static struct pcm_feederdesc feeder_24leto16le_desc[] = { +static struct pcm_feederdesc feeder_24to16_desc[] = { {FEEDER_FMT, AFMT_U24_LE, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S24_LE, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_24leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_24leto16le), +static kobj_method_t feeder_24to16_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_24to16), {0, 0} }; -FEEDER_DECLARE(feeder_24leto16le, 1, NULL); +FEEDER_DECLARE(feeder_24to16, 1, NULL); static int -feed_16leto32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_16to32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, sign, k; + + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~3) >> 1, source); if (k < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k << 1; k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; - b[--j] = 0; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + b[--j] = 0; + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 1) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = 0; + b[--j] = 0; + } } return k; } -static struct pcm_feederdesc feeder_16leto32le_desc[] = { +static struct pcm_feederdesc feeder_16to32_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U32_LE, 0}, {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S32_LE, 0}, {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_16leto32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto32le), +static kobj_method_t feeder_16to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_16to32), {0, 0} }; -FEEDER_DECLARE(feeder_16leto32le, 0, NULL); +FEEDER_DECLARE(feeder_16to32, 0, NULL); static int -feed_32leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_32to16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 2) + return 0; + k = (count & ~1) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 4) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k; k >>= 1; j = k; - while (i > 0) { - b[--j] = src[--i]; - b[--j] = src[--i]; - i -= 2; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + i -= 2; + b[--j] = src[--i]; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 3) { + b[--j] = src[--i] ^ sign; + b[--j] = src[--i]; + i -= 2; + } } return k; } -static struct pcm_feederdesc feeder_32leto16le_desc[] = { +static struct pcm_feederdesc feeder_32to16_desc[] = { {FEEDER_FMT, AFMT_U32_LE, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_LE, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to16_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_32to16), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to16, 1, NULL); + +static int +feed_24to32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign; + + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count >> 2) * 3, source); + if (k < 3) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); + k -= k % 3; + i = k; + k = (k / 3) << 2; + j = k; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 2) { + b[--j] = 0; + b[--j] = b[--i]; + b[--j] = b[--i]; + b[--j] = b[--i] ^ sign; + } + } else { + while (i > 2) { + b[--j] = b[--i] ^ sign; + b[--j] = b[--i]; + b[--j] = b[--i]; + b[--j] = 0; + } + } + return k; +} +static struct pcm_feederdesc feeder_24to32_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_32leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_32leto16le), +static kobj_method_t feeder_24to32_methods[] = { + KOBJMETHOD(feeder_feed, feed_24to32), {0, 0} }; -FEEDER_DECLARE(feeder_32leto16le, 1, NULL); +FEEDER_DECLARE(feeder_24to32, 1, NULL); + +static int +feed_32to24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_fmt_info *info = f->data; + int i, j, k, sign; + uint8_t *src = info->buffer; + + if (count < 3) + return 0; + k = (count / 3) << 2; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; + } + FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); + k &= ~3; + i = k; + k = (k >> 2) * 3; + j = k; + sign = swap_sign(f); + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + --i; + b[--j] = src[--i]; + b[--j] = src[--i]; + b[--j] = src[--i] ^ sign; + } + } else { + while (i > 3) { + b[--j] = src[--i] ^ sign; + b[--j] = src[--i]; + b[--j] = src[--i]; + --i; + } + } + return k; +} +static struct pcm_feederdesc feeder_32to24_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to24_methods[] = { + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), + KOBJMETHOD(feeder_feed, feed_32to24), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to24, 1, NULL); /* * Bit conversion end */ @@ -570,8 +908,11 @@ static int feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, k; + if (count < 2) + return 0; + k = FEEDER_FEED(f->source, c, b, count >> 1, source); i = k; j = k << 1; while (i > 0) { @@ -583,6 +924,8 @@ feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, static struct pcm_feederdesc feeder_monotostereo8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_U8|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_MU_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_A_LAW|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo8_methods[] = { @@ -595,19 +938,22 @@ static int feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, k; uint8_t l, m; + if (count < 4) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~3) >> 1, source); if (k < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k; j = k << 1; - while (i > 0) { + while (i > 1) { l = b[--i]; m = b[--i]; b[--j] = l; @@ -634,19 +980,22 @@ static int feed_monotostereo24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, k; uint8_t l, m, n; + if (count < 6) + return 0; + k = FEEDER_FEED(f->source, c, b, ((count / 6) * 6) >> 1, source); if (k < 3) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 3); + k -= k % 3; i = k; j = k << 1; - while (i > 0) { + while (i > 2) { l = b[--i]; m = b[--i]; n = b[--i]; @@ -676,19 +1025,22 @@ static int feed_monotostereo32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, k; uint8_t l, m, n, o; + if (count < 8) + return 0; + k = FEEDER_FEED(f->source, c, b, (count & ~7) >> 1, source); if (k < 4) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k; j = k << 1; - while (i > 0) { + while (i > 3) { l = b[--i]; m = b[--i]; n = b[--i]; @@ -727,18 +1079,21 @@ static int feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; + if (count < 1) + return 0; k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 1, "%s: Bytes not 8bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); + k &= ~1; i = k >> 1; j = i; while (i > 0) { @@ -750,11 +1105,13 @@ feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, static struct pcm_feederdesc feeder_stereotomono8_desc[] = { {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U8, 0}, {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_A_LAW, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono8_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono8), {0, 0} }; @@ -764,21 +1121,24 @@ static int feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 2) + return 0; + k = (count & ~1) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 4) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 3, "%s: Bytes not 16bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); + k &= ~3; i = k >> 1; j = i; - while (i > 0) { + while (i > 1) { k -= 2; b[--i] = src[--k]; b[--i] = src[--k]; @@ -793,8 +1153,8 @@ static struct pcm_feederdesc feeder_stereotomono16_desc[] = { {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono16), {0, 0} }; @@ -804,21 +1164,24 @@ static int feed_stereotomono24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); + if (count < 3) + return 0; + k = ((count / 3) * 3) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 6) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k % 6, "%s: Bytes not 24bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 6); + k -= k % 6; i = k >> 1; j = i; - while (i > 0) { + while (i > 2) { k -= 3; b[--i] = src[--k]; b[--i] = src[--k]; @@ -834,8 +1197,8 @@ static struct pcm_feederdesc feeder_stereotomono24_desc[] = { {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono24_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono24), {0, 0} }; @@ -845,21 +1208,24 @@ static int feed_stereotomono32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { + struct feed_fmt_info *info = f->data; int i, j, k; - uint8_t *src = (uint8_t *)f->data; + uint8_t *src = info->buffer; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (count < 4) + return 0; + k = (count & ~3) << 1; + k = FEEDER_FEED(f->source, c, src, min(k, info->bufsz), source); if (k < 8) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, k); return 0; } FMT_TEST(k & 7, "%s: Bytes not 32bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~7); + k &= ~7; i = k >> 1; j = i; - while (i > 0) { + while (i > 3) { k -= 4; b[--i] = src[--k]; b[--i] = src[--k]; @@ -876,8 +1242,8 @@ static struct pcm_feederdesc feeder_stereotomono32_desc[] = { {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono32_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_init, feed_fmt_init), + KOBJMETHOD(feeder_free, feed_fmt_free), KOBJMETHOD(feeder_feed, feed_stereotomono32), {0, 0} }; @@ -893,8 +1259,11 @@ static int feed_sign8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; + if (count < 1) + return 0; + j = FEEDER_FEED(f->source, c, b, count, source); i = j; while (i > 0) b[--i] ^= 0x80; @@ -914,103 +1283,145 @@ static kobj_method_t feeder_sign8_methods[] = { FEEDER_DECLARE(feeder_sign8, 0, NULL); static int -feed_sign16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_sign16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; + if (count < 2) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~1, source); if (j < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~1); + j &= ~1; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i--; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 1) { + i--; + b[--i] ^= 0x80; + } + } else { + while (i > 1) { + b[--i] ^= 0x80; + i--; + } } return j; } -static struct pcm_feederdesc feeder_sign16le_desc[] = { +static struct pcm_feederdesc feeder_sign16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign16le), +static kobj_method_t feeder_sign16_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign16), {0, 0} }; -FEEDER_DECLARE(feeder_sign16le, 0, NULL); +FEEDER_DECLARE(feeder_sign16, 0, NULL); static int -feed_sign24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_sign24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; + if (count < 3) + return 0; + j = FEEDER_FEED(f->source, c, b, (count / 3) * 3, source); if (j < 3) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(j -= j % 3); + j -= j % 3; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 2; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 2) { + i -= 2; + b[--i] ^= 0x80; + } + } else { + while (i > 2) { + b[--i] ^= 0x80; + i -= 2; + } } return j; } -static struct pcm_feederdesc feeder_sign24le_desc[] = { +static struct pcm_feederdesc feeder_sign24_desc[] = { {FEEDER_FMT, AFMT_U24_LE, AFMT_S24_LE, 0}, {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S24_LE, AFMT_U24_LE, 0}, {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign24le), +static kobj_method_t feeder_sign24_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign24), {0, 0} }; -FEEDER_DECLARE(feeder_sign24le, 0, NULL); +FEEDER_DECLARE(feeder_sign24, 0, NULL); static int -feed_sign32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_sign32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; + if (count < 4) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~3, source); if (j < 4) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~3); + j &= ~3; i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 3; + if (f->desc->in & AFMT_BIGENDIAN) { + while (i > 3) { + i -= 3; + b[--i] ^= 0x80; + } + } else { + while (i > 3) { + b[--i] ^= 0x80; + i -= 3; + } } return j; } -static struct pcm_feederdesc feeder_sign32le_desc[] = { +static struct pcm_feederdesc feeder_sign32_desc[] = { {FEEDER_FMT, AFMT_U32_LE, AFMT_S32_LE, 0}, {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_LE, AFMT_U32_LE, 0}, {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign32le), +static kobj_method_t feeder_sign32_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign32), {0, 0} }; -FEEDER_DECLARE(feeder_sign32le, 0, NULL); +FEEDER_DECLARE(feeder_sign32, 0, NULL); /* * Sign conversion end. */ @@ -1022,18 +1433,21 @@ static int feed_endian16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; uint8_t v; + if (count < 2) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~1, source); if (j < 2) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~1); + j &= ~1; i = j; - while (i > 0) { + while (i > 1) { v = b[--i]; b[i] = b[i - 1]; b[--i] = v; @@ -1061,18 +1475,21 @@ static int feed_endian24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; uint8_t v; + if (count < 3) + return 0; + j = FEEDER_FEED(f->source, c, b, (count / 3) * 3, source); if (j < 3) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(j -= j % 3); + j -= j % 3; i = j; - while (i > 0) { + while (i > 2) { v = b[--i]; b[i] = b[i - 2]; b[i -= 2] = v; @@ -1100,18 +1517,21 @@ static int feed_endian32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j; uint8_t l, m; + if (count < 4) + return 0; + j = FEEDER_FEED(f->source, c, b, count & ~3, source); if (j < 4) { FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", __func__, j); return 0; } FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~3); + j &= ~3; i = j; - while (i > 0) { + while (i > 3) { l = b[--i]; m = b[--i]; b[i] = b[i - 1]; diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c index b4496d1..a3fe99d 100644 --- a/sys/dev/sound/pcm/feeder_rate.c +++ b/sys/dev/sound/pcm/feeder_rate.c @@ -25,6 +25,15 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * + * 2006-02-21: + * ========== + * + * Major cleanup and overhaul to remove much redundant codes. + * Highlights: + * 1) Support for signed / unsigned 16, 24 and 32 bit, + * big / little endian, + * 2) Unlimited channels. + * * 2005-06-11: * ========== * @@ -68,30 +77,24 @@ SND_DECLARE_FILE("$FreeBSD$"); -#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ -#define RATE_TEST(x, y) /* if (!(x)) printf y */ -#define RATE_TRACE(x...) /* printf(x) */ +#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ +#define RATE_TEST(x, y) /* if (!(x)) printf y */ +#define RATE_TRACE(x...) /* printf(x) */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); -#define FEEDBUFSZ 8192 -#define ROUNDHZ 25 -#define RATEMIN 4000 -/* 8000 * 138 or 11025 * 100 . This is insane, indeed! */ -#define RATEMAX 1102500 -#define MINGAIN 92 -#define MAXGAIN 96 - -#define FEEDRATE_CONVERT_64 0 -#define FEEDRATE_CONVERT_SCALE64 1 -#define FEEDRATE_CONVERT_SCALE32 2 -#define FEEDRATE_CONVERT_PLAIN 3 -#define FEEDRATE_CONVERT_FIXED 4 -#define FEEDRATE_CONVERT_OPTIMAL 5 -#define FEEDRATE_CONVERT_WORST 6 - -#define FEEDRATE_64_MAXROLL 32 -#define FEEDRATE_32_MAXROLL 16 +/* + * Don't overflow 32bit integer, since everything is done + * within 32bit arithmetic. + */ +#define RATE_FACTOR_MIN 1 +#define RATE_FACTOR_MAX PCM_S24_MAX +#define RATE_FACTOR_SAFE(val) (!((val) < RATE_FACTOR_MIN || \ + (val) > RATE_FACTOR_MAX)) + +struct feed_rate_info; + +typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, uint32_t); struct feed_rate_info { uint32_t src, dst; /* rounded source / destination rates */ @@ -99,138 +102,148 @@ struct feed_rate_info { uint32_t gx, gy; /* interpolation / decimation ratio */ uint32_t alpha; /* interpolation distance */ uint32_t pos, bpos; /* current sample / buffer positions */ - uint32_t bufsz; /* total buffer size */ + uint32_t bufsz; /* total buffer size limit */ + uint32_t bufsz_init; /* allocated buffer size */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample */ uint32_t stray; /* stray bytes */ - int32_t scale, roll; /* scale / roll factor */ - int16_t *buffer; - uint32_t (*convert)(struct feed_rate_info *, int16_t *, uint32_t); + uint8_t *buffer; + feed_rate_converter convert; }; -static uint32_t -feed_convert_64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale32(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_plain(struct feed_rate_info *, int16_t *, uint32_t); - -int feeder_rate_ratemin = RATEMIN; -int feeder_rate_ratemax = RATEMAX; -/* - * See 'Feeder Scaling Type' below.. - */ -static int feeder_rate_scaling = FEEDRATE_CONVERT_OPTIMAL; -static int feeder_rate_buffersize = FEEDBUFSZ & ~1; +int feeder_rate_min = FEEDRATE_RATEMIN; +int feeder_rate_max = FEEDRATE_RATEMAX; +int feeder_rate_round = FEEDRATE_ROUNDHZ; -/* - * sysctls.. I love sysctls.. - */ -TUNABLE_INT("hw.snd.feeder_rate_ratemin", &feeder_rate_ratemin); -TUNABLE_INT("hw.snd.feeder_rate_ratemax", &feeder_rate_ratemin); -TUNABLE_INT("hw.snd.feeder_rate_scaling", &feeder_rate_scaling); -TUNABLE_INT("hw.snd.feeder_rate_buffersize", &feeder_rate_buffersize); +TUNABLE_INT("hw.snd.feeder_rate_min", &feeder_rate_min); +TUNABLE_INT("hw.snd.feeder_rate_max", &feeder_rate_max); +TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round); static int -sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_ratemin; + val = feeder_rate_min; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 1 || val >= feeder_rate_ratemax) - err = EINVAL; + if (RATE_FACTOR_SAFE(val) && val < feeder_rate_max) + feeder_rate_min = val; else - feeder_rate_ratemin = val; - return err; -} -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemin, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemin, "I", ""); - -static int -sysctl_hw_snd_feeder_rate_ratemax(SYSCTL_HANDLER_ARGS) -{ - int err, val; - - val = feeder_rate_ratemax; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val <= feeder_rate_ratemin || val > 0x7fffff) err = EINVAL; - else - feeder_rate_ratemax = val; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemax, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemax, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I", + "minimum allowable rate"); static int -sysctl_hw_snd_feeder_rate_scaling(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_scaling; + val = feeder_rate_max; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Feeder Scaling Type - * =================== - * - * 1. Plain 64bit (high precision) - * 2. 64bit scaling (high precision, CPU friendly, but can - * cause gain up/down). - * 3. 32bit scaling (somehow can cause hz roundup, gain - * up/down). - * 4. Plain copy (default if src == dst. Except if src == dst, - * this is the worst / silly conversion method!). - * - * Sysctl options:- - * - * 0 - Plain 64bit - no fallback. - * 1 - 64bit scaling - no fallback. - * 2 - 32bit scaling - no fallback. - * 3 - Plain copy - no fallback. - * 4 - Fixed rate. Means that, choose optimal conversion method - * without causing hz roundup. - * 32bit scaling (as long as hz roundup does not occur), - * 64bit scaling, Plain 64bit. - * 5 - Optimal / CPU friendly (DEFAULT). - * 32bit scaling, 64bit scaling, Plain 64bit - * 6 - Optimal to worst, no 64bit arithmetic involved. - * 32bit scaling, Plain copy. - */ - if (val < FEEDRATE_CONVERT_64 || val > FEEDRATE_CONVERT_WORST) - err = EINVAL; + if (RATE_FACTOR_SAFE(val) && val > feeder_rate_min) + feeder_rate_max = val; else - feeder_rate_scaling = val; + err = EINVAL; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_scaling, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_scaling, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I", + "maximum allowable rate"); static int -sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_buffersize; + val = feeder_rate_round; err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Don't waste too much kernel space - */ - if (val < 2 || val > 65536) + if (val < FEEDRATE_ROUNDHZ_MIN || val > FEEDRATE_ROUNDHZ_MAX) err = EINVAL; else - feeder_rate_buffersize = val & ~1; + feeder_rate_round = val - (val % FEEDRATE_ROUNDHZ); return err; } -/* XXX: this should be settable by an user via a control tool, the sysadmin - needs a max and min sysctl to limit what an user can do */ -SYSCTL_PROC(_hw_snd, OID_AUTO, _feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", + "sample rate converter rounding threshold"); + +#define FEEDER_RATE_CONVERT(FMTBIT, RATE_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_convert_##SIGNS##FMTBIT##ENDIANS(struct feed_rate_info *info, \ + uint8_t *dst, uint32_t max) \ +{ \ + uint32_t ret, smpsz, bps, ch, pos, bpos, gx, gy, alpha, distance; \ + int32_t x, y; \ + int i; \ + uint8_t *src, *sx, *sy; \ + \ + ret = 0; \ + alpha = info->alpha; \ + gx = info->gx; \ + gy = info->gy; \ + pos = info->pos; \ + bpos = info->bpos; \ + src = info->buffer + pos; \ + ch = info->channels; \ + bps = info->bps; \ + smpsz = bps * ch; \ + for (;;) { \ + if (alpha < gx) { \ + alpha += gy; \ + pos += smpsz; \ + if (pos == bpos) \ + break; \ + src += smpsz; \ + } else { \ + alpha -= gx; \ + distance = (alpha << PCM_FXSHIFT) / gy; \ + sx = src - smpsz; \ + sy = src; \ + i = ch; \ + do { \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sx); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(sy); \ + x = (((RATE_INTCAST)x * distance) + \ + ((RATE_INTCAST)y * ((1 << PCM_FXSHIFT) - \ + distance))) >> PCM_FXSHIFT; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(dst, x); \ + dst += bps; \ + sx += bps; \ + sy += bps; \ + ret += bps; \ + } while (--i); \ + if (ret == max) \ + break; \ + } \ + } \ + info->alpha = alpha; \ + info->pos = pos; \ + return ret; \ +} + +FEEDER_RATE_CONVERT(8, int32_t, S, s, NE, ne) +FEEDER_RATE_CONVERT(16, int32_t, S, s, LE, le) +FEEDER_RATE_CONVERT(24, int32_t, S, s, LE, le) +FEEDER_RATE_CONVERT(32, intpcm_t, S, s, LE, le) +FEEDER_RATE_CONVERT(16, int32_t, S, s, BE, be) +FEEDER_RATE_CONVERT(24, int32_t, S, s, BE, be) +FEEDER_RATE_CONVERT(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_RATE_CONVERT(8, int32_t, U, u, NE, ne) +FEEDER_RATE_CONVERT(16, int32_t, U, u, LE, le) +FEEDER_RATE_CONVERT(24, int32_t, U, u, LE, le) +FEEDER_RATE_CONVERT(32, intpcm_t, U, u, LE, le) +FEEDER_RATE_CONVERT(16, int32_t, U, u, BE, be) +FEEDER_RATE_CONVERT(24, int32_t, U, u, BE, be) +FEEDER_RATE_CONVERT(32, intpcm_t, U, u, BE, be) static void -feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy) +feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy) { - uint32_t w, src = x, dst = y; + uint32_t w, x = src, y = dst; while (y != 0) { w = x % y; @@ -242,242 +255,112 @@ feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy) } static void -feed_scale_roll(uint32_t dst, int32_t *scale, int32_t *roll, int32_t max) -{ - int64_t k, tscale; - int32_t j, troll; - - *scale = *roll = -1; - for (j = MAXGAIN; j >= MINGAIN; j -= 3) { - for (troll = 0; troll < max; troll++) { - tscale = (1 << troll) / dst; - k = (tscale * dst * 100) >> troll; - if (k > j && k <= 100) { - *scale = tscale; - *roll = troll; - return; - } - } - } -} - -static int -feed_get_best_coef(uint32_t *src, uint32_t *dst, uint32_t *gx, uint32_t *gy, - int32_t *scale, int32_t *roll) -{ - uint32_t tsrc, tdst, sscale, dscale; - int32_t tscale, troll; - int i, j, hzmin, hzmax; - - *scale = *roll = -1; - for (i = 0; i < 2; i++) { - hzmin = (ROUNDHZ * i) + 1; - hzmax = hzmin + ROUNDHZ; - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst; - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src; - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - } -coef_failed: - feed_speed_ratio(*src, *dst, gx, gy); - feed_scale_roll(*gy, scale, roll, FEEDRATE_32_MAXROLL); - return 0; -} - -static void feed_rate_reset(struct feed_rate_info *info) { - info->scale = -1; - info->roll = -1; - info->src = info->rsrc; - info->dst = info->rdst; - info->gx = 0; - info->gy = 0; + info->src = info->rsrc - (info->rsrc % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->dst = info->rdst - (info->rdst % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->gx = 1; + info->gy = 1; + info->alpha = 0; + info->channels = 2; + info->bps = 2; + info->convert = NULL; + info->bufsz = info->bufsz_init; + info->pos = 4; + info->bpos = 8; + info->stray = 0; } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; - int r = 0; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_rate_converter convert; + } convtbl[] = { + { AFMT_S8, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_convert_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_convert_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; - info->pos = 2; - info->bpos = 4; - info->alpha = 0; - info->stray = 0; feed_rate_reset(info); - if (info->src == info->dst) { - /* - * No conversion ever needed. Just do plain copy. - */ - info->convert = feed_convert_plain; - info->gx = 1; - info->gy = 1; - } else { - switch (feeder_rate_scaling) { - case FEEDRATE_CONVERT_64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_64; - break; - case FEEDRATE_CONVERT_SCALE64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale == -1 || info->roll == -1) - return -1; - info->convert = feed_convert_scale64; - break; - case FEEDRATE_CONVERT_SCALE32: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r == 0) - return -1; - info->convert = feed_convert_scale32; - break; - case FEEDRATE_CONVERT_PLAIN: - feed_speed_ratio(info->src, info->dst, + + if (info->src != info->dst) + feed_speed_ratio(info->src, info->dst, &info->gx, &info->gy); - info->convert = feed_convert_plain; - break; - case FEEDRATE_CONVERT_FIXED: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0 && info->src == info->rsrc && - info->dst == info->rdst) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_OPTIMAL: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_WORST: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_plain; - } - break; - default: - return -1; - break; - } - /* No way! */ - if (info->gx == 0 || info->gy == 0) + + if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy))) + return -1; + + for (i = 0; i < sizeof(convtbl) / sizeof(*convtbl); i++) { + if (convtbl[i].format == 0) return -1; - /* - * No need to interpolate/decimate, just do plain copy. - * This probably caused by Hz roundup. - */ - if (info->gx == info->gy) - info->convert = feed_convert_plain; + if ((f->desc->out & ~AFMT_STEREO) == convtbl[i].format) { + info->bps = convtbl[i].bps; + info->convert = convtbl[i].convert; + break; + } } + + /* + * No need to interpolate/decimate, just do plain copy. + */ + if (info->gx == info->gy) + info->convert = NULL; + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->pos = info->bps * info->channels; + info->bpos = info->pos << 1; + info->bufsz -= info->bufsz % info->pos; + + memset(info->buffer, sndbuf_zerodata(f->desc->out), info->bpos); + + RATE_TRACE("%s: %u (%u) -> %u (%u) [%u/%u] , " + "format=0x%08x, channels=%u, bufsz=%u\n", + __func__, info->src, info->rsrc, info->dst, info->rdst, + info->gx, info->gy, + f->desc->out, info->channels, + info->bufsz - info->pos); + return 0; } static int -feed_rate_set(struct pcm_feeder *f, int what, int value) +feed_rate_set(struct pcm_feeder *f, int what, int32_t value) { struct feed_rate_info *info = f->data; - if (value < feeder_rate_ratemin || value > feeder_rate_ratemax) + if (value < feeder_rate_min || value > feeder_rate_max) return -1; - + switch (what) { - case FEEDRATE_SRC: - info->rsrc = value; - break; - case FEEDRATE_DST: - info->rdst = value; - break; - default: - return -1; + case FEEDRATE_SRC: + info->rsrc = value; + break; + case FEEDRATE_DST: + info->rdst = value; + break; + default: + return -1; } return feed_rate_setup(f); } @@ -487,16 +370,13 @@ feed_rate_get(struct pcm_feeder *f, int what) { struct feed_rate_info *info = f->data; - /* - * Return *real* src/dst rate. - */ switch (what) { - case FEEDRATE_SRC: - return info->rsrc; - case FEEDRATE_DST: - return info->rdst; - default: - return -1; + case FEEDRATE_SRC: + return info->rsrc; + case FEEDRATE_DST: + return info->rdst; + default: + return -1; } return -1; } @@ -506,14 +386,17 @@ feed_rate_init(struct pcm_feeder *f) { struct feed_rate_info *info; + if (f->desc->out != f->desc->in) + return EINVAL; + info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info == NULL) return ENOMEM; /* * bufsz = sample from last cycle + conversion space */ - info->bufsz = 2 + feeder_rate_buffersize; - info->buffer = malloc(sizeof(*info->buffer) * info->bufsz, + info->bufsz_init = 8 + feeder_buffersize; + info->buffer = malloc(sizeof(*info->buffer) * info->bufsz_init, M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info->buffer == NULL) { free(info, M_RATEFEEDER); @@ -539,233 +422,88 @@ feed_rate_free(struct pcm_feeder *f) return 0; } -static uint32_t -feed_convert_64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy; - int16_t *src; - /* - * Plain, straight forward 64bit arith. No bit-magic applied here. - */ - ret = 0; - alpha = info->alpha; - gx = info->gx; - gy = info->gy; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x / gy; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x / gy; - if (ret == max) - break; - } - } - info->alpha = alpha; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy, roll; - int16_t *src; - /* - * 64bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale32(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t x, pos, bpos, gx, gy, alpha, roll, distance; - int16_t *src; - /* - * 32bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_plain(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t pos, bpos, gx, gy, alpha; - int16_t *src; - /* - * Plain copy. - */ - ret = 0; - gx = info->gx; - gy = info->gy; - alpha = info->alpha; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - dst[ret++] = src[pos]; - dst[ret++] = src[pos + 1]; - if (ret == max) - break; - } - } - info->pos = pos; - info->alpha = alpha; - return ret; -} - -static int32_t +static int feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) + uint32_t count, void *source) { struct feed_rate_info *info = f->data; - uint32_t i; + uint32_t i, smpsz; int32_t fetch, slot; - int16_t *dst = (int16_t *)b; + + if (info->convert == NULL) + return FEEDER_FEED(f->source, c, b, count, source); + /* * This loop has been optimized to generalize both up / down * sampling without causing missing samples or excessive buffer - * feeding. + * feeding. The tricky part is to calculate *precise* (slot) value + * needed for the entire conversion space since we are bound to + * return and fill up the buffer according to the requested 'count'. + * Too much feeding will cause the extra buffer stay within temporary + * circular buffer forever and always manifest itself as a truncated + * sound during end of playback / recording. Too few, and we end up + * with possible underruns and waste of cpu cycles. + * + * 'Stray' management exist to combat with possible unaligned + * buffering by the caller. */ - RATE_TEST(count >= 4 && (count & 3) == 0, - ("%s: Count size not byte integral (%d)\n", __func__, count)); - if (count < 4) + smpsz = info->bps * info->channels; + RATE_TEST(count >= smpsz && (count % smpsz) == 0, + ("%s: Count size not sample integral (%d)\n", __func__, count)); + if (count < smpsz) return 0; - count >>= 1; - count &= ~1; - slot = (((info->gx * (count >> 1)) + info->gy - info->alpha - 1) / info->gy) << 1; - RATE_TEST((slot & 1) == 0, ("%s: Slot count not sample integral (%d)\n", - __func__, slot)); + count -= count % smpsz; /* - * Optimize buffer feeding aggressively to ensure calculated slot - * can be fitted nicely into available buffer free space, hence - * avoiding multiple feeding. + * This slot count formula will stay here for the next million years + * to come. This is the key of our circular buffering precision. */ + slot = (((info->gx * (count / smpsz)) + info->gy - info->alpha - 1) / info->gy) * smpsz; + RATE_TEST((slot % smpsz) == 0, ("%s: Slot count not sample integral (%d)\n", + __func__, slot)); RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n", __func__,info->stray)); - if (info->pos != 2 && info->bpos - info->pos == 2 && + if (info->pos != smpsz && info->bpos - info->pos == smpsz && info->bpos + slot > info->bufsz) { /* * Copy last unit sample and its previous to * beginning of buffer. */ - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->buffer[2] = info->buffer[info->pos]; - info->buffer[3] = info->buffer[info->pos + 1]; - info->pos = 2; - info->bpos = 4; + bcopy(info->buffer + info->pos - smpsz, info->buffer, + sizeof(*info->buffer) * (smpsz << 1)); + info->pos = smpsz; + info->bpos = smpsz << 1; } RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", __func__, slot)); i = 0; for (;;) { for (;;) { - fetch = (info->bufsz - info->bpos) << 1; + fetch = info->bufsz - info->bpos; fetch -= info->stray; RATE_ASSERT(fetch >= 0, ("%s: [1] Buffer overrun: %d > %d\n", __func__, info->bpos, info->bufsz)); - if ((slot << 1) < fetch) - fetch = slot << 1; + if (slot < fetch) + fetch = slot; if (fetch > 0) { - RATE_ASSERT(((info->bpos << 1) - info->stray) >= 0 && - ((info->bpos << 1) - info->stray) < (info->bufsz << 1), + RATE_ASSERT((int32_t)(info->bpos - info->stray) >= 0 && + (info->bpos - info->stray) < info->bufsz, ("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", __func__, - info->bufsz << 1, (info->bpos << 1) - info->stray)); + info->bufsz, info->bpos - info->stray)); fetch = FEEDER_FEED(f->source, c, - (uint8_t *)(info->buffer) + (info->bpos << 1) - info->stray, + info->buffer + info->bpos - info->stray, fetch, source); info->stray = 0; if (fetch == 0) break; - RATE_TEST((fetch & 3) == 0, - ("%s: Fetch size not byte integral (%d)\n", + RATE_TEST((fetch % smpsz) == 0, + ("%s: Fetch size not sample integral (%d)\n", __func__, fetch)); - info->stray += fetch & 3; + info->stray += fetch % smpsz; RATE_TEST(info->stray == 0, ("%s: Stray bytes detected (%d)\n", __func__, info->stray)); - fetch >>= 1; - fetch &= ~1; + fetch -= fetch % smpsz; info->bpos += fetch; slot -= fetch; RATE_ASSERT(slot >= 0, @@ -779,7 +517,7 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, break; } if (info->pos == info->bpos) { - RATE_TEST(info->pos == 2, + RATE_TEST(info->pos == smpsz, ("%s: EOF while in progress\n", __func__)); break; } @@ -788,10 +526,10 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, info->pos, info->bpos)); RATE_ASSERT(info->pos < info->bpos, ("%s: Zero buffer!\n", __func__)); - RATE_ASSERT(((info->bpos - info->pos) & 1) == 0, + RATE_ASSERT(((info->bpos - info->pos) % smpsz) == 0, ("%s: Buffer not sample integral (%d)\n", __func__, info->bpos - info->pos)); - i += info->convert(info, dst + i, count - i); + i += info->convert(info, b + i, count - i); RATE_ASSERT(info->pos <= info->bpos, ("%s: [3] Buffer overrun: %d > %d\n", __func__, info->pos, info->bpos)); @@ -802,31 +540,67 @@ feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, * interpolate using it. */ RATE_TEST(info->stray == 0, ("%s: [2] Stray bytes: %u\n", __func__, info->stray)); - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->bpos = 2; - info->pos = 2; + bcopy(info->buffer + info->pos - smpsz, info->buffer, + sizeof(*info->buffer) * smpsz); + info->bpos = smpsz; + info->pos = smpsz; } if (i == count) break; } -#if 0 - RATE_TEST(count == i, ("Expect: %u , Got: %u\n", count << 1, i << 1)); -#endif + + RATE_TEST((slot == 0 && count == i) || + (slot > 0 && count > i && + info->pos == info->bpos && info->pos == smpsz), + ("%s: Inconsistent slot/count! " + "Count Expect: %u , Got: %u, Slot Left: %d\n", + __func__, count, i, slot)); + RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, info->stray)); - return i << 1; + + return i; } static struct pcm_feederdesc feeder_rate_desc[] = { + {FEEDER_RATE, AFMT_S8, AFMT_S8, 0}, + {FEEDER_RATE, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_RATE, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_RATE, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_RATE, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_RATE, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_RATE, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_RATE, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_RATE, AFMT_U8, AFMT_U8, 0}, + {FEEDER_RATE, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_RATE, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_RATE, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_RATE, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_RATE, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_RATE, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_RATE, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; + static kobj_method_t feeder_rate_methods[] = { - KOBJMETHOD(feeder_init, feed_rate_init), - KOBJMETHOD(feeder_free, feed_rate_free), - KOBJMETHOD(feeder_set, feed_rate_set), - KOBJMETHOD(feeder_get, feed_rate_get), - KOBJMETHOD(feeder_feed, feed_rate), + KOBJMETHOD(feeder_init, feed_rate_init), + KOBJMETHOD(feeder_free, feed_rate_free), + KOBJMETHOD(feeder_set, feed_rate_set), + KOBJMETHOD(feeder_get, feed_rate_get), + KOBJMETHOD(feeder_feed, feed_rate), {0, 0} }; + FEEDER_DECLARE(feeder_rate, 2, NULL); diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c index bb46ea0..544194c 100644 --- a/sys/dev/sound/pcm/feeder_volume.c +++ b/sys/dev/sound/pcm/feeder_volume.c @@ -31,48 +31,193 @@ SND_DECLARE_FILE("$FreeBSD$"); +MALLOC_DEFINE(M_VOLUMEFEEDER, "volumefeed", "pcm volume feeder"); + +#define FVOL_TRACE(x...) /* device_printf(c->dev, x) */ +#define FVOL_TEST(x, y...) /* if (x) FVOL_TRACE(y) */ + +#define FVOL_RESOLUTION 6 /* 6bit volume resolution */ +#define FVOL_CLAMP(val) (((val) << FVOL_RESOLUTION) / 100) +#define FVOL_LEFT(val) FVOL_CLAMP((val) & 0x7f) +#define FVOL_RIGHT(val) FVOL_LEFT((val) >> 8) +#define FVOL_MAX (1 << FVOL_RESOLUTION) +#define FVOL_CALC(sval, vval) (((sval) * (vval)) >> FVOL_RESOLUTION) + +struct feed_volume_info; + +typedef uint32_t (*feed_volume_filter)(struct feed_volume_info *, + uint8_t *, int *, uint32_t); + +struct feed_volume_info { + uint32_t bps, channels; + feed_volume_filter filter; +}; + +#define FEEDER_VOLUME_FILTER(FMTBIT, VOL_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_volume_filter_##SIGNS##FMTBIT##ENDIANS(struct feed_volume_info *info, \ + uint8_t *b, int *vol, uint32_t count) \ +{ \ + uint32_t bps; \ + int32_t j; \ + int i; \ + \ + bps = info->bps; \ + i = count; \ + b += i; \ + while (i > 0) { \ + b -= bps; \ + i -= bps; \ + j = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(b); \ + j = FVOL_CALC((VOL_INTCAST)j, vol[(i / bps) & 1]); \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(b, j); \ + } \ + return count; \ +} + +FEEDER_VOLUME_FILTER(8, int32_t, S, s, NE, ne) +FEEDER_VOLUME_FILTER(16, int32_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(24, int32_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(32, intpcm_t, S, s, LE, le) +FEEDER_VOLUME_FILTER(16, int32_t, S, s, BE, be) +FEEDER_VOLUME_FILTER(24, int32_t, S, s, BE, be) +FEEDER_VOLUME_FILTER(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_VOLUME_FILTER(8, int32_t, U, u, NE, ne) +FEEDER_VOLUME_FILTER(16, int32_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(24, int32_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(32, intpcm_t, U, u, LE, le) +FEEDER_VOLUME_FILTER(16, int32_t, U, u, BE, be) +FEEDER_VOLUME_FILTER(24, int32_t, U, u, BE, be) +FEEDER_VOLUME_FILTER(32, intpcm_t, U, u, BE, be) + +static int +feed_volume_setup(struct pcm_feeder *f) +{ + struct feed_volume_info *info = f->data; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_volume_filter filter; + } voltbl[] = { + { AFMT_S8, PCM_8_BPS, feed_volume_filter_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_volume_filter_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_volume_filter_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_volume_filter_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_volume_filter_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_volume_filter_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_volume_filter_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_volume_filter_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_volume_filter_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_volume_filter_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_volume_filter_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_volume_filter_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_volume_filter_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_volume_filter_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; + + for (i = 0; i < sizeof(voltbl) / sizeof(*voltbl); i++) { + if (voltbl[i].format == 0) + return -1; + if ((f->desc->out & ~AFMT_STEREO) == voltbl[i].format) { + info->bps = voltbl[i].bps; + info->filter = voltbl[i].filter; + break; + } + } + + /* For now, this is mandatory! */ + info->channels = 2; + + return 0; +} + static int -feed_volume_s16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) +feed_volume_init(struct pcm_feeder *f) { - int i, j, k, vol[2]; - int16_t *buf; + struct feed_volume_info *info; + + if (f->desc->in != f->desc->out) + return EINVAL; + + /* Mandatory */ + if (!(f->desc->out & AFMT_STEREO)) + return EINVAL; + + info = malloc(sizeof(*info), M_VOLUMEFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + f->data = info; + return feed_volume_setup(f); +} - k = FEEDER_FEED(f->source, c, b, count & ~1, source); - if (k < 2) { -#if 0 - device_printf(c->dev, "%s: Not enough data (Got: %d bytes)\n", +static int +feed_volume_free(struct pcm_feeder *f) +{ + struct feed_volume_info *info = f->data; + + if (info) + free(info, M_VOLUMEFEEDER); + f->data = NULL; + return 0; +} + +static int +feed_volume(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_volume_info *info = f->data; + uint32_t k, smpsz; + int vol[2]; + + vol[0] = FVOL_LEFT(c->volume); + vol[1] = FVOL_RIGHT(c->volume); + + if (vol[0] == FVOL_MAX && vol[1] == FVOL_MAX) + return FEEDER_FEED(f->source, c, b, count, source); + + smpsz = info->bps * info->channels; + if (count < smpsz) + return 0; + count -= count % smpsz; + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < smpsz) { + FVOL_TRACE("%s: Not enough data (Got: %u bytes)\n", __func__, k); -#endif return 0; } -#if 0 - if (k & 1) - device_printf(c->dev, "%s: Bytes not 16bit aligned.\n", __func__); -#endif - k &= ~1; - i = k >> 1; - buf = (int16_t *)b; - vol[0] = c->volume & 0x7f; - vol[1] = (c->volume >> 8) & 0x7f; - while (i > 0) { - i--; - j = (vol[i & 1] * buf[i]) / 100; - if (j > 32767) - j = 32767; - if (j < -32768) - j = -32768; - buf[i] = j; - } - return k; + FVOL_TEST(k % smpsz, "%s: Bytes not %dbit (stereo) aligned.\n", + __func__, info->bps << 3); + k -= k % smpsz; + return info->filter(info, b, vol, k); } -static struct pcm_feederdesc feeder_volume_s16_desc[] = { - {FEEDER_VOLUME, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, +static struct pcm_feederdesc feeder_volume_desc[] = { + {FEEDER_VOLUME, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_VOLUME, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_volume_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_volume_s16), +static kobj_method_t feeder_volume_methods[] = { + KOBJMETHOD(feeder_init, feed_volume_init), + KOBJMETHOD(feeder_free, feed_volume_free), + KOBJMETHOD(feeder_feed, feed_volume), {0, 0} }; -FEEDER_DECLARE(feeder_volume_s16, 2, NULL); +FEEDER_DECLARE(feeder_volume, 2, NULL); diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index d0f6946..3deded7 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -631,7 +631,7 @@ sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) m = oidp->oid_arg1; snd_mtxlock(m->lock); - strncpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); + strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); @@ -663,9 +663,11 @@ mixer_hwvol_init(device_t dev) m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); #endif diff --git a/sys/dev/sound/pcm/sndstat.c b/sys/dev/sound/pcm/sndstat.c index 5b07ece..32188ff 100644 --- a/sys/dev/sound/pcm/sndstat.c +++ b/sys/dev/sound/pcm/sndstat.c @@ -72,11 +72,11 @@ static int sndstat_files = 0; static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); -static int sndstat_verbose = 1; +int snd_verbose = 1; #ifdef USING_MUTEX -TUNABLE_INT("hw.snd.verbose", &sndstat_verbose); +TUNABLE_INT("hw.snd.verbose", &snd_verbose); #else -TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose); +TUNABLE_INT_DECL("hw.snd.verbose", 1, snd_verbose); #endif static int sndstat_prepare(struct sbuf *s); @@ -86,20 +86,20 @@ sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) { int error, verbose; - verbose = sndstat_verbose; + verbose = snd_verbose; error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); if (error == 0 && req->newptr != NULL) { sx_xlock(&sndstat_lock); - if (verbose < 0 || verbose > 3) + if (verbose < 0 || verbose > 4) error = EINVAL; else - sndstat_verbose = verbose; + snd_verbose = verbose; sx_xunlock(&sndstat_lock); } return error; } SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_sndverbose, "I", ""); + 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) @@ -300,7 +300,8 @@ sndstat_prepare(struct sbuf *s) struct sndstat_entry *ent; int i, j; - sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n"); + sbuf_printf(s, "FreeBSD Audio Driver (newpcm: %ubit)\n", + (unsigned int)sizeof(intpcm_t) << 3); if (SLIST_EMPTY(&sndstat_devlist)) { sbuf_printf(s, "No devices installed.\n"); sbuf_finish(s); @@ -318,14 +319,14 @@ sndstat_prepare(struct sbuf *s) sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); sbuf_printf(s, " %s", ent->str); if (ent->handler) - ent->handler(s, ent->dev, sndstat_verbose); + ent->handler(s, ent->dev, snd_verbose); else sbuf_printf(s, " [no handler]"); sbuf_printf(s, "\n"); } } - if (sndstat_verbose >= 3 && sndstat_files > 0) { + if (snd_verbose >= 3 && sndstat_files > 0) { sbuf_printf(s, "\nFile Versions:\n"); SLIST_FOREACH(ent, &sndstat_devlist, link) { diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c index 5ab8343..719bd93 100644 --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -26,6 +26,7 @@ */ #include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/ac97.h> #include <dev/sound/pcm/vchan.h> #include <dev/sound/pcm/dsp.h> #include <sys/limits.h> @@ -59,22 +60,6 @@ struct unrhdr *pcmsg_unrhdr = NULL; static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); -struct sysctl_ctx_list * -snd_sysctl_tree(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); - - return &d->sysctl_tree; -} - -struct sysctl_oid * -snd_sysctl_tree_top(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); - - return d->sysctl_tree_top; -} - void * snd_mtxcreate(const char *desc, const char *type) { @@ -176,6 +161,11 @@ pcm_setvchans(struct snddev_info *d, int newcnt) pcm_inprog(d, 1); + if (d->playcount < 1) { + err = ENODEV; + goto setvchans_out; + } + if (!(d->flags & SD_F_AUTOVCHAN)) { err = EINVAL; goto setvchans_out; @@ -238,7 +228,7 @@ addok: ORPHAN_CDEVT(sce->dsp_devt) && ORPHAN_CDEVT(sce->dspW_devt) && ORPHAN_CDEVT(sce->audio_devt) && - ORPHAN_CDEVT(sce->dspr_devt)) + ORPHAN_CDEVT(sce->dspHW_devt)) goto remok; /* * Either we're busy, or our cdev @@ -392,7 +382,7 @@ sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) } /* XXX: do we need a way to let the user change the default unit? */ SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_default_unit, "I", ""); + 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); #endif static int @@ -419,7 +409,7 @@ sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); + 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) @@ -556,6 +546,7 @@ pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) unsigned rdevcount; int device = device_get_unit(d->dev); size_t namelen; + char dtype; /* * Note it's confusing nomenclature. @@ -647,11 +638,20 @@ retry_chan_num_search_out: } #endif + if (ch->flags & CHN_F_VIRTUAL) + dtype = 'v'; + else if (ch->direction == PCMDIR_PLAY) + dtype = 'p'; + else if (ch->direction == PCMDIR_REC) + dtype = 'r'; + else + dtype = 'u'; /* we're screwed */ + namelen = strlen(ch->name); - if ((CHN_NAMELEN - namelen) > 10) { /* ":dspXX.YYY" */ + if ((CHN_NAMELEN - namelen) > 11) { /* ":dspXX.TYYY" */ snprintf(ch->name + namelen, - CHN_NAMELEN - namelen, ":dsp%d.%d", - device, sce->chan_num); + CHN_NAMELEN - namelen, ":dsp%d.%c%d", + device, dtype, ch->num); } snd_mtxunlock(d->lock); @@ -673,11 +673,11 @@ retry_chan_num_search_out: UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", device, sce->chan_num); - if (ch->direction == PCMDIR_REC) - sce->dspr_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSPREC, - sce->chan_num), UID_ROOT, GID_WHEEL, - 0666, "dspr%d.%d", device, sce->chan_num); + /* Except this. */ + sce->dspHW_devt = make_dev(&dsp_cdevsw, + PCMMKMINOR(device, SND_DEV_DSPHW, sce->chan_num), + UID_ROOT, GID_WHEEL, 0666, "dsp%d.%c%d", + device, dtype, ch->num); return 0; } @@ -770,7 +770,7 @@ pcm_setstatus(device_t dev, char *str) struct snddev_info *d = device_get_softc(dev); snd_mtxlock(d->lock); - strncpy(d->status, str, SND_STATUSLEN); + strlcpy(d->status, str, SND_STATUSLEN); snd_mtxunlock(d->lock); if (snd_maxautovchans > 0) pcm_setvchans(d, 1); @@ -869,18 +869,11 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL - sysctl_ctx_init(&d->sysctl_tree); - d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, - SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, - device_get_nameunit(dev), CTLFLAG_RD, 0, ""); - if (d->sysctl_tree_top == NULL) { - sysctl_ctx_free(&d->sysctl_tree); - goto no; - } /* XXX: an user should be able to set this with a control tool, the sysadmin then needs min+max sysctls for this */ - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "_buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); #endif if (numplay > 0) { d->flags |= SD_F_AUTOVCHAN; @@ -889,9 +882,6 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) sndstat_register(dev, d->status, sndstat_prepare_pcm); return 0; -no: - snd_mtxfree(d->lock); - return ENXIO; } int @@ -945,9 +935,9 @@ pcm_unregister(device_t dev) destroy_dev(sce->audio_devt); sce->audio_devt = NULL; } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; } d->devcount--; ch = sce->channel; @@ -967,9 +957,11 @@ pcm_unregister(device_t dev) } #ifdef SND_DYNSYSCTL +#if 0 d->sysctl_tree_top = NULL; sysctl_ctx_free(&d->sysctl_tree); #endif +#endif #if 0 SLIST_FOREACH(sce, &d->channels, link) { @@ -1068,15 +1060,15 @@ sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) sbuf_printf(s, "interrupts %d, ", c->interrupts); if (c->direction == PCMDIR_REC) - sbuf_printf(s, "overruns %d, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", - c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), + sbuf_printf(s, "overruns %d, feed %u, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), sndbuf_getblkcnt(c->bufsoft)); else - sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", - c->xruns, sndbuf_getready(c->bufsoft), + sbuf_printf(s, "underruns %d, feed %u, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", + c->xruns, c->feedcount, sndbuf_getready(c->bufsoft), sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), sndbuf_getblkcnt(c->bufhard), sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index eeb5c20..b62b1a5 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -58,6 +58,7 @@ #include <machine/resource.h> #include <machine/bus.h> #include <sys/rman.h> +#include <sys/limits.h> #include <sys/mman.h> #include <sys/poll.h> #include <sys/sbuf.h> @@ -148,6 +149,281 @@ nomenclature: (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) #define DSP_BUFFSIZE (8192) +/* + * Macros for reading/writing PCM sample / int 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. To avoid overflow, + * we truncate 32bit (and only 32bit) samples down to 24bit (see below + * for the reason), unless PCM_USE_64BIT_ARITH is defined. + */ + +/* + * Automatically turn on 64bit arithmetic on suitable archs + * (amd64 64bit, ia64, etc..) for wider 32bit samples / integer processing. + */ +#if LONG_BIT >= 64 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#else +#if 0 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#endif +#endif + +#ifdef PCM_USE_64BIT_ARITH +typedef int64_t intpcm_t; +#else +typedef int32_t intpcm_t; +#endif + +/* 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 PCM_USE_64BIT_ARITH +#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 + +#if BYTE_ORDER == LITTLE_ENDIAN +#define PCM_READ_S16_LE(b8) *((int16_t *)(b8)) +#define _PCM_READ_S32_LE(b8) *((int32_t *)(b8)) +#define PCM_READ_S16_BE(b8) \ + ((int32_t)((b8)[1] | ((int8_t)((b8)[0])) << 8)) +#define _PCM_READ_S32_BE(b8) \ + ((int32_t)((b8)[3] | (b8)[2] << 8 | (b8)[1] << 16 | \ + ((int8_t)((b8)[0])) << 24)) + +#define PCM_WRITE_S16_LE(b8, val) *((int16_t *)(b8)) = (val) +#define _PCM_WRITE_S32_LE(b8, val) *((int32_t *)(b8)) = (val) +#define PCM_WRITE_S16_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = val >> 8; \ + } while(0) +#define _PCM_WRITE_S32_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = val >> 24; \ + } while(0) + +#define PCM_READ_U16_LE(b8) ((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_LE(b8) ((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) +#define PCM_READ_U16_BE(b8) \ + ((int32_t)((b8)[1] | ((int8_t)((b8)[0] ^ 0x80)) << 8)) +#define _PCM_READ_U32_BE(b8) \ + ((int32_t)((b8)[3] | (b8)[2] << 8 | (b8)[1] << 16 | \ + ((int8_t)((b8)[0] ^ 0x80)) << 24)) + +#define PCM_WRITE_U16_LE(b8, val) *((uint16_t *)(b8)) = (val) ^ 0x8000 +#define _PCM_WRITE_U32_LE(b8, val) *((uint32_t *)(b8)) = (val) ^ 0x80000000 +#define PCM_WRITE_U16_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = (val >> 8) ^ 0x80; \ + } while(0) +#define _PCM_WRITE_U32_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = (val >> 24) ^ 0x80; \ + } while(0) +#else /* !LITTLE_ENDIAN */ +#define PCM_READ_S16_LE(b8) \ + ((int32_t)((b8)[0] | ((int8_t)((b8)[1])) << 8)) +#define _PCM_READ_S32_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | (b8)[2] << 16 | \ + ((int8_t)((b8)[3])) << 24)) +#define PCM_READ_S16_BE(b8) *((int16_t *)(b8)) +#define _PCM_READ_S32_BE(b8) *((int32_t *)(b8)) + +#define PCM_WRITE_S16_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + } while(0) +#define _PCM_WRITE_S32_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = val >> 24; \ + } while(0) +#define PCM_WRITE_S16_BE(b8, val) *((int16_t *)(b8)) = (val) +#define _PCM_WRITE_S32_BE(b8, val) *((int32_t *)(b8)) = (val) + +#define PCM_READ_U16_LE(b8) \ + ((int32_t)((b8)[0] | ((int8_t)((b8)[1] ^ 0x80)) << 8)) +#define _PCM_READ_U32_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | (b8)[2] << 16 | \ + ((int8_t)((b8)[3] ^ 0x80)) << 24)) +#define PCM_READ_U16_BE(b8) ((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_BE(b8) ((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) + +#define PCM_WRITE_U16_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = (val >> 8) ^ 0x80; \ + } while(0) +#define _PCM_WRITE_U32_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = (val >> 24) ^ 0x80; \ + } while(0) +#define PCM_WRITE_U16_BE(b8, val) *((uint16_t *)(b8)) = (val) ^ 0x8000 +#define _PCM_WRITE_U32_BE(b8, val) *((uint32_t *)(b8)) = (val) ^ 0x80000000 +#endif + +#define PCM_READ_S24_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | ((int8_t)((b8)[2])) << 16)) +#define PCM_READ_S24_BE(b8) \ + ((int32_t)((b8)[2] | (b8)[1] << 8 | ((int8_t)((b8)[0])) << 16)) + +#define PCM_WRITE_S24_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + } while(0) +#define PCM_WRITE_S24_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = val >> 16; \ + } while(0) + +#define PCM_READ_U24_LE(b8) \ + ((int32_t)((b8)[0] | (b8)[1] << 8 | \ + ((int8_t)((b8)[2] ^ 0x80)) << 16)) +#define PCM_READ_U24_BE(b8) \ + ((int32_t)((b8)[2] | (b8)[1] << 8 | \ + ((int8_t)((b8)[0] ^ 0x80)) << 16)) + +#define PCM_WRITE_U24_LE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = (val >> 16) ^ 0x80; \ + } while(0) +#define PCM_WRITE_U24_BE(bb8, vval) do { \ + int32_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = (val >> 16) ^ 0x80; \ + } while(0) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_READ_S32_LE(b8) _PCM_READ_S32_LE(b8) +#define PCM_READ_S32_BE(b8) _PCM_READ_S32_BE(b8) +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) + +#define PCM_READ_U32_LE(b8) _PCM_READ_U32_LE(b8) +#define PCM_READ_U32_BE(b8) _PCM_READ_U32_BE(b8) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) +#else /* !PCM_USE_64BIT_ARITH */ +/* + * 24bit integer ?!? This is quite unfortunate, eh? Get the fact straight: + * Dynamic range for: + * 1) Human =~ 140db + * 2) 16bit = 96db (close enough) + * 3) 24bit = 144db (perfect) + * 4) 32bit = 196db (way too much) + * 5) Bugs Bunny = Gazillion!@%$Erbzzztt-EINVAL db + * Since we're not Bugs Bunny ..uh..err.. avoiding 64bit arithmetic, 24bit + * is pretty much sufficient for our signed integer processing. + */ +#define PCM_READ_S32_LE(b8) (_PCM_READ_S32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_S32_BE(b8) (_PCM_READ_S32_BE(b8) >> PCM_FXSHIFT) +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, (val) << PCM_FXSHIFT) + +#define PCM_READ_U32_LE(b8) (_PCM_READ_U32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_BE(b8) (_PCM_READ_U32_BE(b8) >> PCM_FXSHIFT) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, (val) << PCM_FXSHIFT) +#endif + +/* + * 8bit sample is pretty much useless since it doesn't provide + * sufficient dynamic range throughout our filtering process. + * For the sake of completeness, declare it anyway. + */ +#define PCM_READ_S8(b8) *((int8_t *)(b8)) +#define PCM_READ_S8_NE(b8) PCM_READ_S8(b8) +#define PCM_READ_U8(b8) ((int8_t)(*((uint8_t *)(b8)) ^ 0x80)) +#define PCM_READ_U8_NE(b8) PCM_READ_U8(b8) + +#define PCM_WRITE_S8(b8, val) *((int8_t *)(b8)) = (val) +#define PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) +#define PCM_WRITE_U8(b8, val) *((uint8_t *)(b8)) = (val) ^ 0x80 +#define PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) + +#define PCM_CLAMP_S8(val) \ + (((val) > PCM_S8_MAX) ? PCM_S8_MAX : \ + (((val) < PCM_S8_MIN) ? PCM_S8_MIN : (val))) +#define PCM_CLAMP_S16(val) \ + (((val) > PCM_S16_MAX) ? PCM_S16_MAX : \ + (((val) < PCM_S16_MIN) ? PCM_S16_MIN : (val))) +#define PCM_CLAMP_S24(val) \ + (((val) > PCM_S24_MAX) ? PCM_S24_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S24_MIN : (val))) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S32_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S32_MIN) ? PCM_S32_MIN : (val))) +#else +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S24_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S32_MIN : \ + ((val) << PCM_FXSHIFT))) +#endif + +#define PCM_CLAMP_U8(val) PCM_CLAMP_S8(val) +#define PCM_CLAMP_U16(val) PCM_CLAMP_S16(val) +#define PCM_CLAMP_U24(val) PCM_CLAMP_S24(val) +#define PCM_CLAMP_U32(val) PCM_CLAMP_S32(val) + /* 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_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) @@ -185,7 +461,7 @@ int fkchan_kill(struct pcm_channel *c); #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 -#define SND_DEV_DSPREC 11 /* recording channels */ +#define SND_DEV_DSPHW 11 /* specific channel request */ #define DSP_DEFAULT_SPEED 8000 @@ -194,6 +470,8 @@ int fkchan_kill(struct pcm_channel *c); extern int pcm_veto_load; extern int snd_unit; +extern int snd_maxautovchans; +extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; @@ -210,9 +488,6 @@ extern struct unrhdr *pcmsg_unrhdr; SYSCTL_DECL(_hw_snd); -struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); -struct sysctl_oid *snd_sysctl_tree_top(device_t dev); - struct pcm_channel *pcm_getfakechan(struct snddev_info *d); int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int chnum); int pcm_chnrelease(struct pcm_channel *c); @@ -287,7 +562,7 @@ struct snddev_channel { struct cdev *dsp_devt; struct cdev *dspW_devt; struct cdev *audio_devt; - struct cdev *dspr_devt; + struct cdev *dspHW_devt; }; struct snddev_info { @@ -300,8 +575,6 @@ struct snddev_info { void *devinfo; device_t dev; char status[SND_STATUSLEN]; - struct sysctl_ctx_list sysctl_tree; - struct sysctl_oid *sysctl_tree_top; struct mtx *lock; struct cdev *mixer_dev; diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index b3fbd25..b210dca 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2001 Cameron Grant <cg@freebsd.org> + * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org> + * Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,6 +23,9 @@ * 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. + * + * Almost entirely rewritten to add multi-format/channels mixing support. + * */ #include <dev/sound/pcm/sound.h> @@ -30,74 +34,210 @@ SND_DECLARE_FILE("$FreeBSD$"); +MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); + /* - * Default speed + * Default speed / format */ #define VCHAN_DEFAULT_SPEED 48000 +#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) +#define VCHAN_DEFAULT_STRFMT "s16le" -extern int feeder_rate_ratemin; -extern int feeder_rate_ratemax; +struct feed_vchan_info; + +typedef uint32_t (*feed_vchan_mixer)(struct feed_vchan_info *, + uint8_t *, uint8_t *, uint32_t); + +struct feed_vchan_info { + uint32_t bps, channels, zero_sample; + feed_vchan_mixer mix; +}; struct vchinfo { - u_int32_t spd, fmt, blksz, bps, run; + uint32_t spd, fmt, fmts[2], blksz, bps, run; struct pcm_channel *channel, *parent; struct pcmchan_caps caps; }; -static u_int32_t vchan_fmt[] = { - AFMT_STEREO | AFMT_S16_LE, - 0 +/* support everything (mono / stereo), except a-law / mu-law */ +static struct afmtstr_table vchan_supported_fmts[] = { + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +/* alias table, shorter. */ +static const struct { + char *alias, *fmtstr; +} vchan_fmtstralias[] = { + { "8", "u8" }, { "16", "s16le" }, + { "24", "s24le" }, { "32", "s32le" }, + { NULL, NULL }, }; +#define vchan_valid_format(fmt) \ + afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ + AFMTSTR_STEREO_RETURN) +#define vchan_valid_strformat(strfmt) \ + afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); + +/* + * Need specialized WRITE macros since 32bit might involved saturation + * if calculation is done within 32bit arithmetic. + */ +#define VCHAN_PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) +#define VCHAN_PCM_WRITE_S16_LE(b8, val) PCM_WRITE_S16_LE(b8, val) +#define VCHAN_PCM_WRITE_S24_LE(b8, val) PCM_WRITE_S24_LE(b8, val) +#define VCHAN_PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define VCHAN_PCM_WRITE_S16_BE(b8, val) PCM_WRITE_S16_BE(b8, val) +#define VCHAN_PCM_WRITE_S24_BE(b8, val) PCM_WRITE_S24_BE(b8, val) +#define VCHAN_PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) +#define VCHAN_PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) +#define VCHAN_PCM_WRITE_U16_LE(b8, val) PCM_WRITE_U16_LE(b8, val) +#define VCHAN_PCM_WRITE_U24_LE(b8, val) PCM_WRITE_U24_LE(b8, val) +#define VCHAN_PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define VCHAN_PCM_WRITE_U16_BE(b8, val) PCM_WRITE_U16_BE(b8, val) +#define VCHAN_PCM_WRITE_U24_BE(b8, val) PCM_WRITE_U24_BE(b8, val) +#define VCHAN_PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) + +#define FEEDER_VCHAN_MIX(FMTBIT, VCHAN_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS(struct feed_vchan_info *info, \ + uint8_t *to, uint8_t *tmp, uint32_t count) \ +{ \ + uint32_t bps; \ + int32_t x, y; \ + VCHAN_INTCAST z; \ + int i; \ + \ + bps = info->bps; \ + i = count; \ + tmp += i; \ + to += i; \ + while (i > 0) { \ + tmp -= bps; \ + to -= bps; \ + i -= bps; \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(tmp); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(to); \ + z = (VCHAN_INTCAST)x + y; \ + x = PCM_CLAMP_##SIGN##FMTBIT(z); \ + VCHAN_PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(to, x); \ + } \ + return count; \ +} + +FEEDER_VCHAN_MIX(8, int32_t, S, s, NE, ne) +FEEDER_VCHAN_MIX(16, int32_t, S, s, LE, le) +FEEDER_VCHAN_MIX(24, int32_t, S, s, LE, le) +FEEDER_VCHAN_MIX(32, intpcm_t, S, s, LE, le) +FEEDER_VCHAN_MIX(16, int32_t, S, s, BE, be) +FEEDER_VCHAN_MIX(24, int32_t, S, s, BE, be) +FEEDER_VCHAN_MIX(32, intpcm_t, S, s, BE, be) +/* unsigned */ +FEEDER_VCHAN_MIX(8, int32_t, U, u, NE, ne) +FEEDER_VCHAN_MIX(16, int32_t, U, u, LE, le) +FEEDER_VCHAN_MIX(24, int32_t, U, u, LE, le) +FEEDER_VCHAN_MIX(32, intpcm_t, U, u, LE, le) +FEEDER_VCHAN_MIX(16, int32_t, U, u, BE, be) +FEEDER_VCHAN_MIX(24, int32_t, U, u, BE, be) +FEEDER_VCHAN_MIX(32, intpcm_t, U, u, BE, be) + static int -vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +feed_vchan_setup(struct pcm_feeder *f) { - /* - * to is the output buffer, tmp is the input buffer - * count is the number of 16bit samples to mix - */ - int i; - int x; - - for(i = 0; i < count; i++) { - x = to[i]; - x += tmp[i]; - if (x < -32768) { - /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ - x = -32768; - } - if (x > 32767) { - /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ - x = 32767; + struct feed_vchan_info *info = f->data; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_vchan_mixer mix; + } vchan_mix_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, + /* unsigned */ + { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; + + for (i = 0; i < sizeof(vchan_mix_tbl) / sizeof(*vchan_mix_tbl); i++) { + if (vchan_mix_tbl[i].format == 0) + return -1; + if ((f->desc->out & ~AFMT_STEREO) == vchan_mix_tbl[i].format) { + info->bps = vchan_mix_tbl[i].bps; + info->mix = vchan_mix_tbl[i].mix; + break; } - to[i] = x & 0x0000ffff; } + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->zero_sample = (f->desc->out & AFMT_SIGNED) ? 0x00 : 0x80; + + return 0; +} + +static int +feed_vchan_init(struct pcm_feeder *f) +{ + struct feed_vchan_info *info; + + if (f->desc->out != f->desc->in) + return EINVAL; + + info = malloc(sizeof(*info), M_VCHANFEEDER, M_NOWAIT | M_ZERO); + if (info == NULL) + return ENOMEM; + f->data = info; + return feed_vchan_setup(f); +} + +static int +feed_vchan_free(struct pcm_feeder *f) +{ + struct feed_vchan_info *info = f->data; + + if (info) + free(info, M_VCHANFEEDER); + f->data = NULL; return 0; } static int -feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - /* we're going to abuse things a bit */ + struct feed_vchan_info *info = f->data; struct snd_dbuf *src = source; struct pcmchan_children *cce; struct pcm_channel *ch; - uint32_t sz; - int16_t *tmp, *dst; - unsigned int cnt, rcnt = 0; - - #if 0 - if (sndbuf_getsize(src) < count) - panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x", - c->name, sndbuf_getsize(src), count, c->flags); - #endif + uint32_t cnt, rcnt = 0, sz; + uint8_t *tmp; + sz = sndbuf_getsize(src); if (sz < count) count = sz; - count &= ~1; - if (count < 2) + + sz = info->bps * info->channels; + count -= count % sz; + if (count < sz) return 0; - bzero(b, count); /* * we are going to use our source as a temporary buffer since it's @@ -105,35 +245,68 @@ feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32 * list of children and calling vchan_mix_* to mix count bytes from each * into our destination buffer, b */ - dst = (int16_t *)b; - tmp = (int16_t *)sndbuf_getbuf(src); - bzero(tmp, count); + tmp = sndbuf_getbuf(src); + memset(b, info->zero_sample, count); SLIST_FOREACH(cce, &c->children, link) { ch = cce->channel; - CHN_LOCK(ch); + CHN_LOCK(ch); if (ch->flags & CHN_F_TRIGGERED) { if (ch->flags & CHN_F_MAPPED) sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); - cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); - vchan_mix_s16(dst, tmp, cnt >> 1); + cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, ch->bufsoft); + cnt -= cnt % sz; + cnt = info->mix(info, b, tmp, cnt); if (cnt > rcnt) rcnt = cnt; } - CHN_UNLOCK(ch); + CHN_UNLOCK(ch); } - return rcnt & ~1; + if (++c->feedcount == 0) + c->feedcount = 2; + + return rcnt; } -static struct pcm_feederdesc feeder_vchan_s16_desc[] = { +static struct pcm_feederdesc feeder_vchan_desc[] = { + {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, + {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, + {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + /* unsigned */ + {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, + {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_vchan_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_vchan_s16), - { 0, 0 } +static kobj_method_t feeder_vchan_methods[] = { + KOBJMETHOD(feeder_init, feed_vchan_init), + KOBJMETHOD(feeder_free, feed_vchan_free), + KOBJMETHOD(feeder_feed, feed_vchan), + {0, 0} }; -FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); +FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ @@ -165,7 +338,7 @@ vchan_free(kobj_t obj, void *data) } static int -vchan_setformat(kobj_t obj, void *data, u_int32_t format) +vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; struct pcm_channel *parent = ch->parent; @@ -180,15 +353,15 @@ vchan_setformat(kobj_t obj, void *data, u_int32_t format) ch->bps *= 3; else if (ch->fmt & AFMT_32BIT) ch->bps <<= 2; - CHN_UNLOCK(channel); + CHN_UNLOCK(channel); chn_notify(parent, CHN_N_FORMAT); - CHN_LOCK(channel); + CHN_LOCK(channel); sndbuf_setfmt(channel->bufsoft, format); return 0; } static int -vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; struct pcm_channel *parent = ch->parent; @@ -204,7 +377,7 @@ vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) } static int -vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +vchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct vchinfo *ch = data; struct pcm_channel *channel = ch->channel; @@ -213,18 +386,27 @@ vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) int prate, crate; ch->blksz = blocksize; - /* CHN_UNLOCK(channel); */ + /* CHN_UNLOCK(channel); */ sndbuf_setblksz(channel->bufhard, blocksize); chn_notify(parent, CHN_N_BLOCKSIZE); - CHN_LOCK(parent); - /* CHN_LOCK(channel); */ + CHN_LOCK(parent); + /* CHN_LOCK(channel); */ crate = ch->spd * ch->bps; prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft); blocksize = sndbuf_getblksz(parent->bufsoft); - CHN_UNLOCK(parent); + CHN_UNLOCK(parent); blocksize *= prate; blocksize /= crate; + blocksize += ch->bps; + prate = 0; + while (blocksize >> prate) + prate++; + blocksize = 1 << (prate - 1); + blocksize -= blocksize % ch->bps; + /* XXX screwed !@#$ */ + if (blocksize < ch->bps) + blocksize = 4096 - (4096 % ch->bps); return blocksize; } @@ -240,9 +422,9 @@ vchan_trigger(kobj_t obj, void *data, int go) return 0; ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); + CHN_UNLOCK(channel); chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + CHN_LOCK(channel); return 0; } @@ -251,24 +433,34 @@ static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + uint32_t fmt; ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; - ch->caps.fmtlist = vchan_fmt; ch->caps.caps = 0; + ch->fmts[1] = 0; + fmt = sndbuf_getfmt(ch->parent->bufsoft); + if (fmt != vchan_valid_format(fmt)) { + device_printf(ch->parent->dev, + "%s: WARNING: invalid vchan format! (0x%08x)\n", + __func__, fmt); + fmt = VCHAN_DEFAULT_AFMT; + } + ch->fmts[0] = fmt; + ch->caps.fmtlist = ch->fmts; return &ch->caps; } static kobj_method_t vchan_methods[] = { - KOBJMETHOD(channel_init, vchan_init), - KOBJMETHOD(channel_free, vchan_free), - KOBJMETHOD(channel_setformat, vchan_setformat), - KOBJMETHOD(channel_setspeed, vchan_setspeed), - KOBJMETHOD(channel_setblocksize, vchan_setblocksize), - KOBJMETHOD(channel_trigger, vchan_trigger), - KOBJMETHOD(channel_getcaps, vchan_getcaps), - { 0, 0 } + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_setblocksize, vchan_setblocksize), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + {0, 0} }; CHANNEL_DECLARE(vchan); @@ -280,7 +472,7 @@ static int sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; + struct snddev_channel *sce; struct pcm_channel *c, *ch = NULL, *fake; struct pcmchan_caps *caps; int err = 0; @@ -329,18 +521,20 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) } err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); if (err == 0 && req->newptr != NULL) { - if (newspd < 1 || newspd < feeder_rate_ratemin || - newspd > feeder_rate_ratemax) { + if (newspd < 1 || newspd < feeder_rate_min || + newspd > feeder_rate_max) { pcm_inprog(d, -1); return EINVAL; } CHN_LOCK(ch); - caps = chn_getcaps(ch); - if (caps == NULL || newspd < caps->minspeed || - newspd > caps->maxspeed) { - CHN_UNLOCK(ch); - pcm_inprog(d, -1); - return EINVAL; + if (feeder_rate_round) { + caps = chn_getcaps(ch); + if (caps == NULL || newspd < caps->minspeed || + newspd > caps->maxspeed) { + CHN_UNLOCK(ch); + pcm_inprog(d, -1); + return EINVAL; + } } if (newspd != ch->speed) { err = chn_setspeed(ch, newspd); @@ -348,7 +542,8 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ - if (!err && (ch->feederflags & (1 << FEEDER_RATE))) { + if (!err && feeder_rate_round && + (ch->feederflags & (1 << FEEDER_RATE))) { newspd = sndbuf_getspd(ch->bufhard); err = chn_setspeed(ch, newspd); } @@ -367,6 +562,97 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) pcm_inprog(d, -1); return err; } + +static int +sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c, *ch = NULL, *fake; + uint32_t newfmt, spd; + char fmtstr[AFMTSTR_MAXSZ]; + int err = 0, i; + + d = oidp->oid_arg1; + if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) + return EINVAL; + if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { + pcm_inprog(d, -1); + return EINPROGRESS; + } + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + if (req->newptr != NULL && + (c->flags & CHN_F_BUSY)) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EBUSY; + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EINVAL; + } + ch = c; + if (ch->format != afmt2afmtstr(vchan_supported_fmts, + ch->format, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { + strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, sizeof(fmtstr)); + } + } + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + pcm_inprog(d, -1); + return EINVAL; + } + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + if (err == 0 && req->newptr != NULL) { + for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { + if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { + strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, sizeof(fmtstr)); + break; + } + } + newfmt = vchan_valid_strformat(fmtstr); + if (newfmt == 0) { + pcm_inprog(d, -1); + return EINVAL; + } + CHN_LOCK(ch); + if (newfmt != ch->format) { + /* Get channel speed, before chn_reset() screw it. */ + spd = ch->speed; + err = chn_reset(ch, newfmt); + if (err == 0) + err = chn_setspeed(ch, spd); + CHN_UNLOCK(ch); + if (err == 0) { + fake = pcm_getfakechan(d); + if (fake != NULL) { + CHN_LOCK(fake); + fake->format = newfmt; + CHN_UNLOCK(fake); + } + } + } else + CHN_UNLOCK(ch); + } + pcm_inprog(d, -1); + return err; +} #endif /* virtual channel interface */ @@ -374,11 +660,12 @@ sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) int vchan_create(struct pcm_channel *parent) { - struct snddev_info *d = parent->parentsnddev; + struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; struct pcm_channel *child, *fake; struct pcmchan_caps *parent_caps; - int err, first, speed = 0; + uint32_t vchanfmt = 0; + int err, first, speed = 0, r; if (!(parent->flags & CHN_F_BUSY)) return EBUSY; @@ -388,7 +675,7 @@ vchan_create(struct pcm_channel *parent) pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); if (!pce) { - CHN_LOCK(parent); + CHN_LOCK(parent); return ENOMEM; } @@ -396,7 +683,7 @@ vchan_create(struct pcm_channel *parent) child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); if (!child) { free(pce, M_DEVBUF); - CHN_LOCK(parent); + CHN_LOCK(parent); return ENODEV; } pce->channel = child; @@ -413,7 +700,7 @@ vchan_create(struct pcm_channel *parent) return err; } - CHN_LOCK(parent); + CHN_LOCK(parent); /* add us to our parent channel's children */ first = SLIST_EMPTY(&parent->children); SLIST_INSERT_HEAD(&parent->children, pce, link); @@ -424,23 +711,50 @@ vchan_create(struct pcm_channel *parent) if (parent_caps == NULL) err = EINVAL; - if (!err) - err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); + fake = pcm_getfakechan(d); + + if (!err && fake != NULL) { + /* + * Avoid querying kernel hint, use saved value + * from fake channel. + */ + CHN_UNLOCK(parent); + CHN_LOCK(fake); + speed = fake->speed; + vchanfmt = fake->format; + CHN_UNLOCK(fake); + CHN_LOCK(parent); + } if (!err) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - /* - * Avoid querying kernel hint, use saved value - * from fake channel. - */ + if (vchanfmt == 0) { + const char *vfmt; + CHN_UNLOCK(parent); - CHN_LOCK(fake); - speed = fake->speed; - CHN_UNLOCK(fake); + r = resource_string_value(device_get_name(parent->dev), + device_get_unit(parent->dev), + "vchanformat", &vfmt); CHN_LOCK(parent); + if (r != 0) + vfmt = NULL; + if (vfmt != NULL) { + vchanfmt = vchan_valid_strformat(vfmt); + for (r = 0; vchanfmt == 0 && + vchan_fmtstralias[r].alias != NULL; + r++) { + if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { + vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); + break; + } + } + } + if (vchanfmt == 0) + vchanfmt = VCHAN_DEFAULT_AFMT; } + err = chn_reset(parent, vchanfmt); + } + if (!err) { /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in @@ -448,7 +762,6 @@ vchan_create(struct pcm_channel *parent) * to set sane value via kernel hints or sysctl. */ if (speed < 1) { - int r; CHN_UNLOCK(parent); r = resource_int_value(device_get_name(parent->dev), device_get_unit(parent->dev), @@ -456,6 +769,9 @@ vchan_create(struct pcm_channel *parent) CHN_LOCK(parent); if (r != 0) { /* + * No saved value from fake channel, + * no hint, NOTHING. + * * Workaround for sb16 running * poorly at 45k / 49k. */ @@ -466,38 +782,45 @@ vchan_create(struct pcm_channel *parent) break; default: speed = VCHAN_DEFAULT_SPEED; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; break; } + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; } } - /* - * Limit speed based on driver caps. - * This is supposed to help fixed rate, non-VRA - * AC97 cards, but.. (see below) - */ - if (speed < parent_caps->minspeed) - speed = parent_caps->minspeed; - if (speed > parent_caps->maxspeed) - speed = parent_caps->maxspeed; + if (feeder_rate_round) { + /* + * Limit speed based on driver caps. + * This is supposed to help fixed rate, non-VRA + * AC97 cards, but.. (see below) + */ + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; + } /* * We still need to limit the speed between - * feeder_rate_ratemin <-> feeder_rate_ratemax. This is + * feeder_rate_min <-> feeder_rate_max. This is * just an escape goat if all of the above failed * miserably. */ - if (speed < feeder_rate_ratemin) - speed = feeder_rate_ratemin; - if (speed > feeder_rate_ratemax) - speed = feeder_rate_ratemax; + if (speed < feeder_rate_min) + speed = feeder_rate_min; + if (speed > feeder_rate_max) + speed = feeder_rate_max; err = chn_setspeed(parent, speed); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ - if (!err && (parent->feederflags & (1 << FEEDER_RATE))) { + if (!err && feeder_rate_round && + (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); err = chn_setspeed(parent, speed); } @@ -509,6 +832,7 @@ vchan_create(struct pcm_channel *parent) CHN_UNLOCK(parent); CHN_LOCK(fake); fake->speed = speed; + fake->format = vchanfmt; CHN_UNLOCK(fake); CHN_LOCK(parent); } @@ -533,7 +857,7 @@ int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent = c->parentchannel; - struct snddev_info *d = parent->parentsnddev; + struct snddev_info *d = parent->parentsnddev; struct pcmchan_children *pce; struct snddev_channel *sce; uint32_t spd; @@ -571,9 +895,9 @@ gotch: destroy_dev(sce->audio_devt); sce->audio_devt = NULL; } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; + if (sce->dspHW_devt) { + destroy_dev(sce->dspHW_devt); + sce->dspHW_devt = NULL; } d->devcount--; break; @@ -606,16 +930,19 @@ vchan_initsys(device_t dev) #ifdef SND_DYNSYSCTL struct snddev_info *d; - d = device_get_softc(dev); - /* XXX: the user should be able to set this with a control tool, the - sysadmin needs a sysctl so set a max value for "vchan" and min+max - values for "vchanrate" to limit what an user can do */ - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "_vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchans, "I", ""); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "_vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchanrate, "I", ""); + d = device_get_softc(dev); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, d, sizeof(d), + sysctl_hw_snd_vchanformat, "A", "virtual channel format"); #endif return 0; |