diff options
author | netchild <netchild@FreeBSD.org> | 2005-07-31 16:16:22 +0000 |
---|---|---|
committer | netchild <netchild@FreeBSD.org> | 2005-07-31 16:16:22 +0000 |
commit | cc689da0ed27b52420a7b350f4a41e87a4964b6d (patch) | |
tree | 59863de2ac961d9640c6289dae9a602ef569c2a1 /sys | |
parent | 8a757cf4981fe5a5c672907593f21538b1fc3d44 (diff) | |
download | FreeBSD-src-cc689da0ed27b52420a7b350f4a41e87a4964b6d.zip FreeBSD-src-cc689da0ed27b52420a7b350f4a41e87a4964b6d.tar.gz |
Whats New:
1. Support wide range sampling rate, as low as 1hz up to int32 max
(which is, insane) through new feeder_rate, multiple precisions
choice (32/64 bit converter). This is indeed, quite insane, but it
does give us more room and flexibility. Plenty sysctl options to
adjust resampling characteristics.
2. Support 24/32 bit pcm format conversion through new, much improved,
simplified and optimized feeder_fmt.
Changes:
1. buffer.c / dsp.c / sound.h
* Support for 24/32 AFMT.
2. feeder_rate.c
* New implementation of sampling rate conversion with 32/64 bit
precision, 1 - int32max hz (which is, ridiculous, yet very
addictive). Much improved / smarter buffer management to not
cause any missing samples at the end of conversion process
* Tunable sysctls for various aspect:
hw.snd.feeder_rate_ratemin - minimum allowable sampling rate
(default to 4000)
hw.snd.feeder_rate_ratemax - maximum allowable sampling rate
(default to 1102500)
hw.snd.feeder_rate_buffersize - conversion buffer size
(default to 8192)
hw.snd.feeder_rate_scaling - scaling / conversion method
(please refer to the source for explaination). Default to
previous implementation type.
3. feeder_fmt.c / sound.h
* New implementation, support for 24/32bit conversion, optimized,
and simplified. Few routines has been removed (8 to xlaw, 16 to
8). It just doesn't make sense.
4. channel.c
* Support for 24/32 AFMT
* Fix wrong xruns increment, causing incorrect underruns statistic
while using vchans.
5. vchan.c
* Support for 24/32 AFMT
* Proper speed / rate detection especially for fixed rate ac97.
User can override it using kernel hint:
hint.pcm.<unit>.vchanrate="xxxx".
Notes / Issues:
* Virtual Channels (vchans)
Enabling vchans can really, really help to solve overrun
issues. This is quite understandable, because it operates
entirely within its own buffering system without relying on
hardware interrupt / state. Even if you don't need vchan,
just enable single channel can help much. Few soundcards
(notably via8233x, sblive, possibly others) have their own
hardware multi channel, and this is unfortunately beyond
vchan reachability.
* The arrival of 24/32 also come with a price. Applications
that can do 24/32bit playback need to be recompiled (notably
mplayer). Use (recompiled) mplayer to experiment / test /
debug this various format using -af format=fmt. Note that
24bit seeking in mplayer is a little bit broken, sometimes
can cause silence or loud static noise. Pausing / seeking
few times can solve this problem.
You don't have to rebuild world entirely for this. Simply
copy /usr/src/sys/sys/soundcard.h to
/usr/include/sys/soundcard.h would suffice. Few drivers also
need recompilation, and this can be done via
/usr/src/sys/modules/sound/.
Support for 24bit hardware playback is beyond the scope of
this changes. That would require spessific hardware driver
changes.
* Don't expect playing 9999999999hz is a wise decision. Be
reasonable. The new feeder_rate implemention provide
flexibility, not insanity. You can easily chew up your CPU
with this kind of mind instability. Please use proper
mosquito repellent device for this obvious cracked brain
attempt. As for testing purposes, you can use (again)
mplayer to generate / play with different sampling rate. Use
something like "mplayer -af resample=192000:0:0 <files>".
Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
Tested by: multimedia@
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/sound/pcm/buffer.c | 8 | ||||
-rw-r--r-- | sys/dev/sound/pcm/channel.c | 53 | ||||
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 9 | ||||
-rw-r--r-- | sys/dev/sound/pcm/fake.c | 22 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_fmt.c | 982 | ||||
-rw-r--r-- | sys/dev/sound/pcm/feeder_rate.c | 961 | ||||
-rw-r--r-- | sys/dev/sound/pcm/sound.h | 7 | ||||
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 194 |
8 files changed, 1496 insertions, 740 deletions
diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index 9ca6a32..b8570f7 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -286,8 +286,12 @@ sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt) b->fmt = fmt; b->bps = 1; b->bps <<= (b->fmt & AFMT_STEREO)? 1 : 0; - b->bps <<= (b->fmt & AFMT_16BIT)? 1 : 0; - b->bps <<= (b->fmt & AFMT_32BIT)? 2 : 0; + if (b->fmt & AFMT_16BIT) + b->bps <<= 1; + else if (b->fmt & AFMT_24BIT) + b->bps *= 3; + else if (b->fmt & AFMT_32BIT) + b->bps <<= 2; return 0; } diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index b43680c..fa56199 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -240,10 +240,30 @@ chn_wrfeed(struct pcm_channel *c) KASSERT(amt <= sndbuf_getsize(bs), ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, amt, sndbuf_getsize(bs), c->flags)); - if (sndbuf_getready(bs) < amt) - c->xruns++; - ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; + if (SLIST_EMPTY(&c->children)) { + /* + * Hardware channel + */ + if (sndbuf_getready(bs) < amt) + c->xruns++; + ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; + } else { + /* + * vchan + */ + if (amt > 0) { + ret = sndbuf_feed(bs, b, c, c->feeder, amt); + /* + * Possible vchan xruns. There should be no empty space + * left in buffer. + */ + if (sndbuf_getfree(b) > 0) + c->xruns++; + } else + ret = ENOSPC; + } + if (ret == 0 && sndbuf_getfree(b) < amt) chn_wakeup(c); @@ -918,7 +938,10 @@ chn_tryspeed(struct pcm_channel *c, int speed) delta = -delta; c->feederflags &= ~(1 << FEEDER_RATE); - if (delta > 500) + /* + * Used to be 500. It was too big! + */ + if (delta > 25) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -932,8 +955,13 @@ chn_tryspeed(struct pcm_channel *c, int speed) if (r) goto out; - if (!(c->feederflags & (1 << FEEDER_RATE))) + if (!(c->feederflags & (1 << FEEDER_RATE))) { + if (c->direction == PCMDIR_PLAY && + !(c->flags & CHN_F_VIRTUAL)) + r = CHANNEL_SETFORMAT(c->methods, c->devinfo, + c->feeder->desc->out); goto out; + } r = EINVAL; f = chn_findfeeder(c, FEEDER_RATE); @@ -950,6 +978,17 @@ chn_tryspeed(struct pcm_channel *c, int speed) x = (c->direction == PCMDIR_REC)? bs : b; r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); + if (c->direction == PCMDIR_PLAY && !(c->flags & CHN_F_VIRTUAL) + && !((c->format & AFMT_S16_LE) && + (c->format & AFMT_STEREO))) { + uint32_t fmt; + + fmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist); + if (fmt != 0) + r = CHANNEL_SETFORMAT(c->methods, c->devinfo, fmt); + else + r = EINVAL; + } out: DEB(printf("setspeed done, r = %d\n", r)); return r; @@ -1176,7 +1215,9 @@ chn_getformats(struct pcm_channel *c) /* report software-supported formats */ if (report_soft_formats) - fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE| + fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U32_LE|AFMT_U32_BE| + AFMT_S32_LE|AFMT_S32_BE|AFMT_U24_LE|AFMT_U24_BE| + AFMT_S24_LE|AFMT_S24_BE|AFMT_U16_LE|AFMT_U16_BE| AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; return fmts; diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 689bec6..c57b59f 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -942,7 +942,14 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread * case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; CHN_LOCK(chn); - *arg_i = (chn->format & AFMT_16BIT) ? 16 : 8; + if (chn->format & AFMT_8BIT) + *arg_i = 8; + else if (chn->format & AFMT_16BIT) + *arg_i = 16; + else if (chn->format & AFMT_24BIT) + *arg_i = 24; + else if (chn->format & AFMT_32BIT) + *arg_i = 32; CHN_UNLOCK(chn); break; diff --git a/sys/dev/sound/pcm/fake.c b/sys/dev/sound/pcm/fake.c index ba9c4b6..ee27c5c 100644 --- a/sys/dev/sound/pcm/fake.c +++ b/sys/dev/sound/pcm/fake.c @@ -41,6 +41,22 @@ static u_int32_t fk_fmt[] = { AFMT_STEREO | AFMT_S16_BE, AFMT_U16_BE, AFMT_STEREO | AFMT_U16_BE, + AFMT_S24_LE, + AFMT_STEREO | AFMT_S24_LE, + AFMT_U24_LE, + AFMT_STEREO | AFMT_U24_LE, + AFMT_S24_BE, + AFMT_STEREO | AFMT_S24_BE, + AFMT_U24_BE, + AFMT_STEREO | AFMT_U24_BE, + AFMT_S32_LE, + AFMT_STEREO | AFMT_S32_LE, + AFMT_U32_LE, + AFMT_STEREO | AFMT_U32_LE, + AFMT_S32_BE, + AFMT_STEREO | AFMT_S32_BE, + AFMT_U32_BE, + AFMT_STEREO | AFMT_U32_BE, 0 }; static struct pcmchan_caps fk_caps = {0, 1000000, fk_fmt, 0}; @@ -120,6 +136,12 @@ fkchan_setup(device_t dev) c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK); c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); c->parentsnddev = d; + /* + * Fake channel is such a blessing in disguise. Using this, + * we can keep track prefered virtual channel speed without + * querying kernel hint repetitively (see vchan_create / vchan.c). + */ + c->speed = 0; snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); return c; diff --git a/sys/dev/sound/pcm/feeder_fmt.c b/sys/dev/sound/pcm/feeder_fmt.c index 7eb66f5..d1e001c 100644 --- a/sys/dev/sound/pcm/feeder_fmt.c +++ b/sys/dev/sound/pcm/feeder_fmt.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant <cg@freebsd.org> + * Copyright (c) 2005 Ariff Abdullah <skywizard@MyBSD.org.my> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,10 +23,20 @@ * 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. + * + * *New* and rewritten soft format converter, supporting 24/32bit pcm data, + * simplified and optimized. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * */ #include <dev/sound/pcm/sound.h> - #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); @@ -33,190 +44,183 @@ SND_DECLARE_FILE("$FreeBSD$"); MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); #define FEEDBUFSZ 8192 +#define FEEDBUF24SZ 8190 -static unsigned char ulaw_to_u8[] = { - 3, 7, 11, 15, 19, 23, 27, 31, - 35, 39, 43, 47, 51, 55, 59, 63, - 66, 68, 70, 72, 74, 76, 78, 80, - 82, 84, 86, 88, 90, 92, 94, 96, - 98, 99, 100, 101, 102, 103, 104, 105, - 106, 107, 108, 109, 110, 111, 112, 113, - 113, 114, 114, 115, 115, 116, 116, 117, - 117, 118, 118, 119, 119, 120, 120, 121, - 121, 121, 122, 122, 122, 122, 123, 123, - 123, 123, 124, 124, 124, 124, 125, 125, - 125, 125, 125, 125, 126, 126, 126, 126, - 126, 126, 126, 126, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 253, 249, 245, 241, 237, 233, 229, 225, - 221, 217, 213, 209, 205, 201, 197, 193, - 190, 188, 186, 184, 182, 180, 178, 176, - 174, 172, 170, 168, 166, 164, 162, 160, - 158, 157, 156, 155, 154, 153, 152, 151, - 150, 149, 148, 147, 146, 145, 144, 143, - 143, 142, 142, 141, 141, 140, 140, 139, - 139, 138, 138, 137, 137, 136, 136, 135, - 135, 135, 134, 134, 134, 134, 133, 133, - 133, 133, 132, 132, 132, 132, 131, 131, - 131, 131, 131, 131, 130, 130, 130, 130, - 130, 130, 130, 130, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, -}; - -static unsigned char u8_to_ulaw[] = { - 0, 0, 0, 0, 0, 1, 1, 1, - 1, 2, 2, 2, 2, 3, 3, 3, - 3, 4, 4, 4, 4, 5, 5, 5, - 5, 6, 6, 6, 6, 7, 7, 7, - 7, 8, 8, 8, 8, 9, 9, 9, - 9, 10, 10, 10, 10, 11, 11, 11, - 11, 12, 12, 12, 12, 13, 13, 13, - 13, 14, 14, 14, 14, 15, 15, 15, - 15, 16, 16, 17, 17, 18, 18, 19, - 19, 20, 20, 21, 21, 22, 22, 23, - 23, 24, 24, 25, 25, 26, 26, 27, - 27, 28, 28, 29, 29, 30, 30, 31, - 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 43, 44, 45, 46, - 47, 49, 51, 53, 55, 57, 59, 61, - 63, 66, 70, 74, 78, 84, 92, 104, - 254, 231, 219, 211, 205, 201, 197, 193, - 190, 188, 186, 184, 182, 180, 178, 176, - 175, 174, 173, 172, 171, 170, 169, 168, - 167, 166, 165, 164, 163, 162, 161, 160, - 159, 159, 158, 158, 157, 157, 156, 156, - 155, 155, 154, 154, 153, 153, 152, 152, - 151, 151, 150, 150, 149, 149, 148, 148, - 147, 147, 146, 146, 145, 145, 144, 144, - 143, 143, 143, 143, 142, 142, 142, 142, - 141, 141, 141, 141, 140, 140, 140, 140, - 139, 139, 139, 139, 138, 138, 138, 138, - 137, 137, 137, 137, 136, 136, 136, 136, - 135, 135, 135, 135, 134, 134, 134, 134, - 133, 133, 133, 133, 132, 132, 132, 132, - 131, 131, 131, 131, 130, 130, 130, 130, - 129, 129, 129, 129, 128, 128, 128, 128, -}; - -static unsigned char alaw_to_ulaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 57, 58, 55, 56, 61, 62, 59, 60, - 49, 50, 48, 48, 53, 54, 51, 52, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 26, 27, 24, 25, 30, 31, 28, 29, - 18, 19, 16, 17, 22, 23, 20, 21, - 98, 99, 96, 97, 102, 103, 100, 101, - 93, 93, 92, 92, 95, 95, 94, 94, - 116, 118, 112, 114, 124, 126, 120, 122, - 106, 107, 104, 105, 110, 111, 108, 109, - 72, 73, 70, 71, 76, 77, 74, 75, - 64, 65, 63, 63, 68, 69, 66, 67, - 86, 87, 84, 85, 90, 91, 88, 89, - 79, 79, 78, 78, 82, 83, 80, 81, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 185, 186, 183, 184, 189, 190, 187, 188, - 177, 178, 176, 176, 181, 182, 179, 180, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 154, 155, 152, 153, 158, 159, 156, 157, - 146, 147, 144, 145, 150, 151, 148, 149, - 226, 227, 224, 225, 230, 231, 228, 229, - 221, 221, 220, 220, 223, 223, 222, 222, - 244, 246, 240, 242, 252, 254, 248, 250, - 234, 235, 232, 233, 238, 239, 236, 237, - 200, 201, 198, 199, 204, 205, 202, 203, - 192, 193, 191, 191, 196, 197, 194, 195, - 214, 215, 212, 213, 218, 219, 216, 217, - 207, 207, 206, 206, 210, 211, 208, 209, -}; - -static unsigned char ulaw_to_alaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 58, 59, 56, 57, 62, 63, 60, 61, - 50, 51, 48, 49, 54, 55, 52, 53, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 27, 24, 25, 30, 31, 28, 29, 18, - 19, 16, 17, 22, 23, 20, 21, 106, - 104, 105, 110, 111, 108, 109, 98, 99, - 96, 97, 102, 103, 100, 101, 122, 120, - 126, 127, 124, 125, 114, 115, 112, 113, - 118, 119, 116, 117, 75, 73, 79, 77, - 66, 67, 64, 65, 70, 71, 68, 69, - 90, 91, 88, 89, 94, 95, 92, 93, - 82, 82, 83, 83, 80, 80, 81, 81, - 86, 86, 87, 87, 84, 84, 85, 85, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 186, 187, 184, 185, 190, 191, 188, 189, - 178, 179, 176, 177, 182, 183, 180, 181, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 155, 152, 153, 158, 159, 156, 157, 146, - 147, 144, 145, 150, 151, 148, 149, 234, - 232, 233, 238, 239, 236, 237, 226, 227, - 224, 225, 230, 231, 228, 229, 250, 248, - 254, 255, 252, 253, 242, 243, 240, 241, - 246, 247, 244, 245, 203, 201, 207, 205, - 194, 195, 192, 193, 198, 199, 196, 197, - 218, 219, 216, 217, 222, 223, 220, 221, - 210, 210, 211, 211, 208, 208, 209, 209, - 214, 214, 215, 215, 212, 212, 213, 213, -}; - -/*****************************************************************************/ +#define FMT_TRACE(x...) /* fprintf(stderr, x) */ +#define FMT_TEST(x, y...) /* if (x) FMT_TRACE(y) */ +#define FMT_ALIGNBYTE(x) /* x */ + +/* + * Sign inverted ulaw/alaw -> 8 table + */ +static uint8_t ulaw_to_s8_tbl[] = { + 131, 135, 139, 143, 147, 151, 155, 159, + 163, 167, 171, 175, 179, 183, 187, 191, + 194, 196, 198, 200, 202, 204, 206, 208, + 210, 212, 214, 216, 218, 220, 222, 224, + 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, + 241, 242, 242, 243, 243, 244, 244, 245, + 245, 246, 246, 247, 247, 248, 248, 249, + 249, 249, 250, 250, 250, 250, 251, 251, + 251, 251, 252, 252, 252, 252, 253, 253, + 253, 253, 253, 253, 254, 254, 254, 254, + 254, 254, 254, 254, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 125, 121, 117, 113, 109, 105, 101, 97, + 93, 89, 85, 81, 77, 73, 69, 65, + 62, 60, 58, 56, 54, 52, 50, 48, + 46, 44, 42, 40, 38, 36, 34, 32, + 30, 29, 28, 27, 26, 25, 24, 23, + 22, 21, 20, 19, 18, 17, 16, 15, + 15, 14, 14, 13, 13, 12, 12, 11, + 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 6, 6, 5, 5, + 5, 5, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static uint8_t alaw_to_s8_tbl[] = { + 236, 237, 234, 235, 240, 241, 238, 239, + 228, 229, 226, 227, 232, 233, 230, 231, + 246, 246, 245, 245, 248, 248, 247, 247, + 242, 242, 241, 241, 244, 244, 243, 243, + 171, 175, 163, 167, 187, 191, 179, 183, + 139, 143, 131, 135, 155, 159, 147, 151, + 214, 216, 210, 212, 222, 224, 218, 220, + 198, 200, 194, 196, 206, 208, 202, 204, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 251, 251, 251, 251, 252, 252, 252, 252, + 249, 249, 249, 249, 250, 250, 250, 250, + 254, 254, 254, 254, 254, 254, 254, 254, + 253, 253, 253, 253, 253, 253, 253, 253, + 20, 19, 22, 21, 16, 15, 18, 17, + 28, 27, 30, 29, 24, 23, 26, 25, + 10, 10, 11, 11, 8, 8, 9, 9, + 14, 14, 15, 15, 12, 12, 13, 13, + 85, 81, 93, 89, 69, 65, 77, 73, + 117, 113, 125, 121, 101, 97, 109, 105, + 42, 40, 46, 44, 34, 32, 38, 36, + 58, 56, 62, 60, 50, 48, 54, 52, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 4, 4, 4, 4, + 7, 7, 7, 7, 6, 6, 6, 6, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, +}; static int -feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_table_u8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i, j, k; + int j, k = FEEDER_FEED(f->source, c, b, count, source); + uint8_t *tbl = (uint8_t *)f->data; + + j = k; + while (j > 0) { + j--; + b[j] = tbl[b[j]] ^ 0x80; + } + return k; +} - k = FEEDER_FEED(f->source, c, b, count / 2, source); - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j--]; - b[i--] = 0; +static int +feed_table_s16le(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); + uint8_t *tbl = (uint8_t *)f->data; + + i = k; + k <<= 1; + j = k; + while (i > 0) { + b[--j] = tbl[b[--i]]; + b[--j] = 0; } - return k * 2; + return k; } -static struct pcm_feederdesc feeder_8to16le_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}, - {0}, +static struct pcm_feederdesc feeder_ulawtou8_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), - { 0, 0 } +static kobj_method_t feeder_ulawtou8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_u8), + {0, 0} }; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); +FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_s8_tbl); -/*****************************************************************************/ +static struct pcm_feederdesc feeder_ulawtos16le_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_ulawtos16le_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_s16le), + {0, 0} +}; +FEEDER_DECLARE(feeder_ulawtos16le, 0, ulaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_alawtos16le_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawtos16le_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_s16le), + {0, 0} +}; +FEEDER_DECLARE(feeder_alawtos16le, 0, alaw_to_s8_tbl); + +static struct pcm_feederdesc feeder_alawtou8_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawtou8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_u8), + {0, 0} +}; +FEEDER_DECLARE(feeder_alawtou8, 0, alaw_to_s8_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_16to8_init(struct pcm_feeder *f) +feed_common_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); + f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT|M_ZERO); if (f->data == NULL) return ENOMEM; return 0; } static int -feed_16to8_free(struct pcm_feeder *f) +feed_common_free(struct pcm_feeder *f) { if (f->data) free(f->data, M_FMTFEEDER); @@ -224,326 +228,498 @@ feed_16to8_free(struct pcm_feeder *f) return 0; } +/* + * Bit conversion + */ static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 1, k; - - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; + int i, j, 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; } - return i; + return k; } +static struct pcm_feederdesc feeder_8to16le_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}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to16le_methods[] = { + KOBJMETHOD(feeder_feed, feed_8to16le), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to16le, 0, NULL); +static int +feed_16leto8(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; + + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), 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); + i = k; + j = k >> 1; + while (i > 0) { + b[--j] = src[--i]; + i--; + } + return k >> 1; +} static struct pcm_feederdesc feeder_16leto8_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 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}, - {0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_16to8_init), - KOBJMETHOD(feeder_free, feed_16to8_free), - KOBJMETHOD(feeder_feed, feed_16leto8), - { 0, 0 } + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_16leto8), + {0, 0} }; -FEEDER_DECLARE(feeder_16leto8, 1, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_16leto8, 0, NULL); static int -feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_24leto16le(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 / 2, source); - - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j]; - b[i--] = b[j]; - j--; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; + + k = (count * 3) >> 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), 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); + i = (k / 3) << 1; + j = i; + while (i > 0) { + b[--i] = src[--k]; + b[--i] = src[--k]; + k--; } - return k * 2; + return j; } - -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}, - {0}, +static struct pcm_feederdesc feeder_24leto16le_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}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_monotostereo8_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo8), - { 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), + {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_24leto16le, 1, NULL); static int -feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_32leto16le(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 / 2, source); - u_int8_t x, y; - - j = k - 1; - i = j * 2 + 1; - while (i >= 3 && j >= 1) { - x = b[j--]; - y = b[j--]; - b[i--] = x; - b[i--] = y; - b[i--] = x; - b[i--] = y; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; + + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), 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); + i = k; + k >>= 1; + j = k; + while (i > 0) { + b[--j] = src[--i]; + b[--j] = src[--i]; + i -= 2; } - return k * 2; + return k; } - -static struct pcm_feederdesc feeder_monotostereo16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, 0}, - {0}, +static struct pcm_feederdesc feeder_32leto16le_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}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_monotostereo16_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo16), - { 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), + {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); - -/*****************************************************************************/ - -static int -feed_stereotomono8_init(struct pcm_feeder *f) -{ - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; -} - -static int -feed_stereotomono8_free(struct pcm_feeder *f) -{ - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; -} +FEEDER_DECLARE(feeder_32leto16le, 1, NULL); +/* + * Bit conversion end + */ +/* + * Channel conversion (mono -> stereo) + */ static int -feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 0, k; + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; + i = k; + j = k << 1; + while (i > 0) { + b[--j] = b[--i]; + b[--j] = b[i]; } - return i; + return k << 1; } - -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}, - {0}, +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}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_stereotomono8_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono8_init), - KOBJMETHOD(feeder_free, feed_stereotomono8_free), - KOBJMETHOD(feeder_feed, feed_stereotomono8), - { 0, 0 } +static kobj_method_t feeder_monotostereo8_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo8), + {0, 0} }; -FEEDER_DECLARE(feeder_stereotomono8, 1, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); static int -feed_stereotomono16_init(struct pcm_feeder *f) +feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; -} + int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + uint8_t l, m; -static int -feed_stereotomono16_free(struct pcm_feeder *f) -{ - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; + 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); + i = k; + j = k << 1; + while (i > 0) { + l = b[--i]; + m = b[--i]; + b[--j] = l; + b[--j] = m; + b[--j] = l; + b[--j] = m; + } + return k << 1; } +static struct pcm_feederdesc feeder_monotostereo16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_monotostereo16_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo16), + {0, 0} +}; +FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); +/* + * Channel conversion (mono -> stereo) end + */ +/* + * Channel conversion (stereo -> mono) + */ static int -feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 0, k; - - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - b[i++] = ((u_int8_t *)f->data)[j + 1]; - j += 4; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; + + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 2) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; } - return i; + FMT_TEST(k & 1, "%s: Bytes not 8bit (stereo) aligned.\n", __func__); + FMT_ALIGNBYTE(k &= ~1); + i = k >> 1; + j = i; + while (i > 0) { + k--; + b[--i] = src[--k]; + } + return j; } - -static struct pcm_feederdesc feeder_stereotomono16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE, 0}, - {0}, +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}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono16_init), - KOBJMETHOD(feeder_free, feed_stereotomono16_free), - KOBJMETHOD(feeder_feed, feed_stereotomono16), - { 0, 0 } +static kobj_method_t feeder_stereotomono8_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono8), + {0, 0} }; -FEEDER_DECLARE(feeder_stereotomono16, 1, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_stereotomono8, 0, NULL); static int -feed_endian(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int8_t t; - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - - while (i < j) { - t = b[i]; - b[i] = b[i + 1]; - b[i + 1] = t; - i += 2; + int i, j, k; + uint8_t *src = (uint8_t *)f->data; + + k = count << 1; + k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); + if (k < 4) { + FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", + __func__, k); + return 0; } - return i; + FMT_TEST(k & 3, "%s: Bytes not 16bit (stereo) aligned.\n", __func__); + FMT_ALIGNBYTE(k &= ~3); + i = k >> 1; + j = i; + while (i > 0) { + k -= 2; + b[--i] = src[--k]; + b[--i] = src[--k]; + } + return j; } - -static struct pcm_feederdesc feeder_endian_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, +static struct pcm_feederdesc feeder_stereotomono16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_BE, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_endian_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian), - { 0, 0 } +static kobj_method_t feeder_stereotomono16_methods[] = { + KOBJMETHOD(feeder_init, feed_common_init), + KOBJMETHOD(feeder_free, feed_common_free), + KOBJMETHOD(feeder_feed, feed_stereotomono16), + {0, 0} }; -FEEDER_DECLARE(feeder_endian, 0, NULL); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_stereotomono16, 0, NULL); +/* + * Channel conversion (stereo -> mono) end + */ +/* + * Sign conversion + */ static int -feed_sign(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_sign8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - intptr_t ssz = (intptr_t)f->data, ofs = ssz - 1; + int i, j = FEEDER_FEED(f->source, c, b, count, source); - while (i < j) { - b[i + ofs] ^= 0x80; - i += ssz; - } - return i; + i = j; + while (i > 0) + b[--i] ^= 0x80; + return j; } - static struct pcm_feederdesc feeder_sign8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_S8, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_sign8_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_sign8), + {0, 0} }; -FEEDER_DECLARE(feeder_sign8, 0, (void *)1); +FEEDER_DECLARE(feeder_sign8, 0, NULL); +static int +feed_sign16le(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); + + 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); + i = j; + while (i > 0) { + b[--i] ^= 0x80; + i--; + } + return j; +} static struct pcm_feederdesc feeder_sign16le_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_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}, - {0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_sign16le), + {0, 0} }; -FEEDER_DECLARE(feeder_sign16le, -1, (void *)2); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_sign16le, 0, NULL); +/* + * Sign conversion end. + */ +/* + * Endian conversion. + */ static int -feed_table(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_endian16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j = FEEDER_FEED(f->source, c, b, count, source); + uint8_t v; - while (i < j) { - b[i] = ((u_int8_t *)f->data)[b[i]]; - i++; + 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); + i = j; + while (i > 0) { + v = b[--i]; + b[i] = b[i - 1]; + b[--i] = v; } - return i; + return j; } - -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, +static struct pcm_feederdesc feeder_endian16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } +static kobj_method_t feeder_endian16_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian16), + {0, 0} }; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_u8); +FEEDER_DECLARE(feeder_endian16, 0, NULL); -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_u8toulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw); +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); + uint8_t v; -static struct pcm_feederdesc feeder_alawtoulaw_desc[] = { - {FEEDER_FMT, AFMT_A_LAW, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, + 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); + i = j; + while (i > 0) { + v = b[--i]; + b[i] = b[i - 2]; + b[i -= 2] = v; + } + return j; +} +static struct pcm_feederdesc feeder_endian24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_alawtoulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } +static kobj_method_t feeder_endian24_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian24), + {0, 0} }; -FEEDER_DECLARE(feeder_alawtoulaw, 0, alaw_to_ulaw); +FEEDER_DECLARE(feeder_endian24, 0, NULL); -static struct pcm_feederdesc feeder_ulawtoalaw_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_A_LAW, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, - {0}, +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); + uint8_t l, m; + + 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); + i = j; + while (i > 0) { + l = b[--i]; + m = b[--i]; + b[i] = b[i - 1]; + b[i + 1] = b[i - 2]; + b[--i] = m; + b[--i] = l; + } + return j; +} +static struct pcm_feederdesc feeder_endian32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtoalaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } +static kobj_method_t feeder_endian32_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian32), + {0, 0} }; -FEEDER_DECLARE(feeder_ulawtoalaw, 0, ulaw_to_alaw); - - - +FEEDER_DECLARE(feeder_endian32, 0, NULL); +/* + * Endian conversion end + */ diff --git a/sys/dev/sound/pcm/feeder_rate.c b/sys/dev/sound/pcm/feeder_rate.c index 55d51fc..34069ea 100644 --- a/sys/dev/sound/pcm/feeder_rate.c +++ b/sys/dev/sound/pcm/feeder_rate.c @@ -1,5 +1,7 @@ /*- + * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> * Copyright (c) 2003 Orion Hodson <orion@freebsd.org> + * Copyright (c) 2005 Ariff Abdullah <skywizard@MyBSD.org.my> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,7 +25,25 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * MAINTAINER: Orion Hodson <orion@freebsd.org> + * 2005-06-11: + * ========== + * + * *New* and rewritten soft sample rate converter supporting arbitary sample + * rate, fine grained scalling/coefficients and unified up/down stereo + * converter. Most of disclaimers from orion's previous version also applied + * here, regarding with linear interpolation deficiencies, pre/post + * anti-aliasing filtering issues. This version comes with much simpler and + * tighter interface, although it works almost exactly like the older one. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Orion's notes: + * ============= * * This rate conversion code uses linear interpolation without any * pre- or post- interpolation filtering to combat aliasing. This @@ -37,194 +57,423 @@ * with several portions based on whether _KERNEL is defined. It's a * little ugly, but exceedingly useful. The testsuite and its * revisions can be found at: - * http://people.freebsd.org/~orion/feedrate/ + * http://people.freebsd.org/~orion/files/feedrate/ * * Special thanks to Ken Marx for exposing flaws in the code and for * testing revisions. */ -#ifdef _KERNEL - #include <dev/sound/pcm/sound.h> #include "feeder_if.h" SND_DECLARE_FILE("$FreeBSD$"); -#endif /* _KERNEL */ +#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ +#define RATE_TRACE(x...) /* printf(x) */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); -#ifndef RATE_ASSERT -#define RATE_ASSERT(x, y) /* KASSERT(x) */ -#endif /* RATE_ASSERT */ +#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 -#ifndef RATE_TRACE -#define RATE_TRACE(x...) /* printf(x) */ -#endif +struct feed_rate_info { + uint32_t src, dst; /* rounded source / destination rates */ + uint32_t rsrc, rdst; /* original source / destination rates */ + 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 */ + int32_t scale, roll; /* scale / roll factor */ + int16_t *buffer; + uint32_t (*convert)(struct feed_rate_info *, int16_t *, uint32_t); +}; -/*****************************************************************************/ +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); -/* The following coefficients are coupled. They are chosen to be - * guarantee calculable factors for the interpolation routine. They - * have been tested over the range of RATEMIN-RATEMAX Hz. Decreasing - * the granularity increases the required buffer size and affects the - * gain values at different points in the space. These values were - * found by running the test program with -p (probe) and some trial - * and error. - * - * ROUNDHZ the granularity of sample rates (fits n*11025 and n*8000). - * FEEDBUFSZ the amount of buffer space. - * MINGAIN the minimum acceptable gain in coefficients search. +int feeder_rate_ratemin = RATEMIN; +int feeder_rate_ratemax = RATEMAX; +/* + * See 'Feeder Scaling Type' below.. */ -#define ROUNDHZ 25 -#define FEEDBUFSZ 8192 -#define MINGAIN 92 - -#define RATEMIN 4000 -#define RATEMAX 48000 +static int feeder_rate_scaling = FEEDRATE_CONVERT_OPTIMAL; +static int feeder_rate_buffersize = FEEDBUFSZ & ~1; -struct feed_rate_info; +/* + * 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); -typedef int (*rate_convert_method)(struct feed_rate_info *, - uint32_t, uint32_t, int16_t *); +static int +sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = feeder_rate_ratemin; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + if (val < 1 || val >= feeder_rate_ratemax) + err = EINVAL; + 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 -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +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", ""); static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +sysctl_hw_snd_feeder_rate_scaling(SYSCTL_HANDLER_ARGS) +{ + int err, val; -struct feed_rate_info { - uint32_t src, dst; /* source and destination rates */ - uint16_t buffer_ticks; /* number of available samples in buffer */ - uint16_t buffer_pos; /* next available sample in buffer */ - uint16_t rounds; /* maximum number of cycle rounds w buffer */ - uint16_t alpha; /* interpolation distance */ - uint16_t sscale; /* src clock scale */ - uint16_t dscale; /* dst clock scale */ - uint16_t mscale; /* scale factor to avoid divide per sample */ - uint16_t mroll; /* roll to again avoid divide per sample */ - uint16_t channels; /* 1 = mono, 2 = stereo */ - - rate_convert_method convert; - int16_t buffer[FEEDBUFSZ]; -}; + val = feeder_rate_scaling; + 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; + else + feeder_rate_scaling = val; + 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", ""); -#define bytes_per_sample 2 -#define src_ticks_per_cycle(info) (info->dscale * info->rounds) -#define dst_ticks_per_cycle(info) (info->sscale * info->rounds) -#define bytes_per_tick(info) (info->channels * bytes_per_sample) -#define src_bytes_per_cycle(info) \ - (src_ticks_per_cycle(info) * bytes_per_tick(info)) -#define dst_bytes_per_cycle(info) \ - (dst_ticks_per_cycle(info) * bytes_per_tick(info)) +static int +sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS) +{ + int err, val; -static uint32_t -gcd(uint32_t x, uint32_t y) + val = feeder_rate_buffersize; + err = sysctl_handle_int(oidp, &val, sizeof(val), req); + /* + * Don't waste too much kernel space + */ + if (val < 2 || val > 65536) + err = EINVAL; + else + feeder_rate_buffersize = val & ~1; + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", ""); + +static void +feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy) { - uint32_t w; + uint32_t w, src = x, dst = y; + while (y != 0) { w = x % y; x = y; y = w; } - return x; + *gx = src / x; + *gy = dst / x; +} + +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; } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; - uint32_t mscale, mroll, l, r, g; - - /* Beat sample rates down by greatest common divisor */ - g = gcd(info->src, info->dst); - info->sscale = info->dst / g; - info->dscale = info->src / g; + int r = 0; + info->pos = 2; + info->bpos = 4; info->alpha = 0; - info->buffer_ticks = 0; - info->buffer_pos = 0; - - /* Pick suitable conversion routine */ - if (info->src > info->dst) { - info->convert = convert_stereo_down; + 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 { - info->convert = convert_stereo_up; - } - - /* - * Determine number of conversion rounds that will fit into - * buffer. NB Must set info->rounds to one before using - * src_ticks_per_cycle here since it used by src_ticks_per_cycle. - */ - info->rounds = 1; - r = (FEEDBUFSZ - bytes_per_tick(info)) / - (src_ticks_per_cycle(info) * bytes_per_tick(info)); - if (r == 0) { - RATE_TRACE("Insufficient buffer space for conversion %d -> %d " - "(%d < %d)\n", info->src, info->dst, FEEDBUFSZ, - src_ticks_per_cycle(info) * bytes_per_tick(info)); - return -1; + 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, + &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) + 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; } - info->rounds = r; - - /* - * Find scale and roll combination that allows us to trade - * costly divide operations in the main loop for multiply-rolls. - */ - for (l = 96; l >= MINGAIN; l -= 3) { - for (mroll = 0; mroll < 16; mroll ++) { - mscale = (1 << mroll) / info->sscale; - - r = (mscale * info->sscale * 100) >> mroll; - if (r > l && r <= 100) { - info->mscale = mscale; - info->mroll = mroll; - RATE_TRACE("Converting %d to %d with " - "mscale = %d and mroll = %d " - "(gain = %d / 100)\n", - info->src, info->dst, - info->mscale, info->mroll, r); - return 0; - } - } - } - - RATE_TRACE("Failed to find a converter within %d%% gain for " - "%d to %d.\n", l, info->src, info->dst); - - return -2; + return 0; } static int feed_rate_set(struct pcm_feeder *f, int what, int value) { struct feed_rate_info *info = f->data; - int rvalue; - - if (value < RATEMIN || value > RATEMAX) { + + if (value < feeder_rate_ratemin || value > feeder_rate_ratemax) return -1; - } - rvalue = (value / ROUNDHZ) * ROUNDHZ; - if (value - rvalue > ROUNDHZ / 2) { - rvalue += ROUNDHZ; - } - - switch(what) { - case FEEDRATE_SRC: - info->src = rvalue; - break; - case FEEDRATE_DST: - info->dst = rvalue; - break; - default: - return -1; + switch (what) { + case FEEDRATE_SRC: + info->rsrc = value; + break; + case FEEDRATE_DST: + info->rdst = value; + break; + default: + return -1; } - return feed_rate_setup(f); } @@ -233,13 +482,16 @@ feed_rate_get(struct pcm_feeder *f, int what) { struct feed_rate_info *info = f->data; - switch(what) { - case FEEDRATE_SRC: - return info->src; - case FEEDRATE_DST: - return info->dst; - default: - return -1; + /* + * Return *real* src/dst rate. + */ + switch (what) { + case FEEDRATE_SRC: + return info->rsrc; + case FEEDRATE_DST: + return info->rdst; + default: + return -1; } return -1; } @@ -252,12 +504,20 @@ feed_rate_init(struct pcm_feeder *f) info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info == NULL) return ENOMEM; - info->src = DSP_DEFAULT_SPEED; - info->dst = DSP_DEFAULT_SPEED; - info->channels = 2; - + /* + * bufsz = sample from last cycle + conversion space + */ + info->bufsz = 2 + feeder_rate_buffersize; + info->buffer = malloc(sizeof(*info->buffer) * info->bufsz, + M_RATEFEEDER, M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_RATEFEEDER); + return ENOMEM; + } + info->rsrc = DSP_DEFAULT_SPEED; + info->rdst = DSP_DEFAULT_SPEED; f->data = info; - return 0; + return feed_rate_setup(f); } static int @@ -266,211 +526,269 @@ feed_rate_free(struct pcm_feeder *f) struct feed_rate_info *info = f->data; if (info) { + if (info->buffer) + free(info->buffer, M_RATEFEEDER); free(info, M_RATEFEEDER); } f->data = NULL; return 0; } -static int -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) +static uint32_t +feed_convert_64(struct feed_rate_info *info, int16_t *dst, uint32_t max) { - uint32_t max_dst_ticks; - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o; + int64_t x, alpha, distance; + uint32_t ret; + int32_t pos, bpos, gx, gy; int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - - src = info->buffer; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; - /* - * For efficiency the main conversion loop should only depend on - * one variable. We use the state to work out the maximum number - * of output samples that are available and eliminate the checking of - * sp from the loop. + * Plain, straight forward 64bit arith. No bit-magic applied here. */ - max_dst_ticks = src_ticks * info->dst / info->src - alpha / dalpha; - if (max_dst_ticks < dst_ticks) { - dst_ticks = max_dst_ticks; + 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; +} - dp = 0; - de = dst_ticks * 2; +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; /* - * Unrolling this loop manually does not help much here because - * of the alpha, malpha comparison. + * 64bit scaling. */ - while (dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - alpha += dalpha; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; + 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; } } - RATE_ASSERT(sp <= se, ("%s: Source overrun\n", __func__)); - - info->buffer_pos = sp / info->channels; - info->alpha = alpha / info->mscale; - - return dp / info->channels; + info->alpha = alpha / info->scale; + info->pos = pos; + return ret; } -static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) +static uint32_t +feed_convert_scale32(struct feed_rate_info *info, int16_t *dst, uint32_t max) { - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o, m, - mdalpha, mstep; + uint32_t ret; + int32_t x, pos, bpos, gx, gy, alpha, roll, distance; int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - + /* + * 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; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; - - dp = 0; - de = dst_ticks * 2; - - m = dalpha / malpha; - mstep = m * 2; - mdalpha = dalpha - m * malpha; + 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; /* - * TODO: eliminate sp or dp from this loop comparison for a few - * extra % performance. + * Plain copy. */ - while (sp < se && dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - - alpha += mdalpha; - sp += mstep; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; + 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->buffer_pos = sp / 2; - info->alpha = alpha / info->mscale; - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: Source overrun\n", __func__)); - - return dp / 2; + info->pos = pos; + info->alpha = alpha; + return ret; } -static int -feed_rate(struct pcm_feeder *f, - struct pcm_channel *c, - uint8_t *b, - uint32_t count, - void *source) +static int32_t +feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { struct feed_rate_info *info = f->data; - - uint32_t done, s_ticks, d_ticks; - done = 0; - - RATE_ASSERT(info->channels == 2, - ("%s: channels (%d) != 2", __func__, info->channels)); - - while (done < count) { - /* Slurp in more data if input buffer is not full */ - while (info->buffer_ticks < src_ticks_per_cycle(info)) { - uint8_t *u8b; - int fetch; - fetch = src_bytes_per_cycle(info) - - info->buffer_ticks * bytes_per_tick(info); - u8b = (uint8_t*)info->buffer + - (info->buffer_ticks + 1) * - bytes_per_tick(info); - fetch = FEEDER_FEED(f->source, c, u8b, fetch, source); - RATE_ASSERT(fetch % bytes_per_tick(info) == 0, - ("%s: fetched unaligned bytes (%d)", - __func__, fetch)); - info->buffer_ticks += fetch / bytes_per_tick(info); - RATE_ASSERT(src_ticks_per_cycle(info) >= - info->buffer_ticks, - ("%s: buffer overfilled (%d > %d).", - __func__, info->buffer_ticks, - src_ticks_per_cycle(info))); - if (fetch == 0) + uint32_t i; + int32_t fetch, slot; + int16_t *dst = (int16_t *)b; + /* + * This loop has been optimized to generalize both up / down + * sampling without causing missing samples or excessive buffer + * feeding. + */ + RATE_ASSERT(count >= 4 && count % 4 == 0, + ("%s: Count size not byte integral\n", __func__)); + count >>= 1; + slot = (((info->gx * (count >> 1)) + info->gy - info->alpha - 1) / info->gy) << 1; + /* + * Optimize buffer feeding aggresively to ensure calculated slot + * can be fitted nicely into available buffer free space, hence + * avoiding multiple feeding. + */ + if (info->pos != 2 && info->bpos - info->pos == 2 && + 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; + } + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", + __func__, slot)); + i = 0; + for (;;) { + for (;;) { + fetch = info->bufsz - info->bpos; + RATE_ASSERT(fetch >= 0, + ("%s: Buffer overrun: %d > %d\n", + __func__, info->bpos, info->bufsz)); + if (slot < fetch) + fetch = slot; + if (fetch > 0) { + RATE_ASSERT(fetch % 2 == 0, + ("%s: Fetch size not sample integral\n", + __func__)); + fetch = FEEDER_FEED(f->source, c, + (uint8_t *)(info->buffer + info->bpos), + fetch << 1, source); + if (fetch == 0) + break; + RATE_ASSERT(fetch % 4 == 0, + ("%s: Fetch size not byte integral\n", + __func__)); + fetch >>= 1; + info->bpos += fetch; + slot -= fetch; + RATE_ASSERT(slot >= 0, + ("%s: Negative Slot: %d\n", __func__ + slot)); + if (slot == 0) + break; + if (info->bpos == info->bufsz) + break; + } else break; } - - /* Find amount of input buffer data that should be processed */ - d_ticks = (count - done) / bytes_per_tick(info); - s_ticks = info->buffer_ticks - info->buffer_pos; - if (info->buffer_ticks != src_ticks_per_cycle(info)) { - if (s_ticks > 8) - s_ticks -= 8; - else - s_ticks = 0; - } - - d_ticks = info->convert(info, s_ticks, d_ticks, - (int16_t*)(b + done)); - if (d_ticks == 0) + if (info->pos == info->bpos) { + RATE_ASSERT(info->pos == 2, + ("%s: EOF while in progress\n", __func__)); break; - done += d_ticks * bytes_per_tick(info); - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: buffer_ticks too big\n", __func__)); - RATE_ASSERT(info->buffer_ticks <= src_ticks_per_cycle(info), - ("too many ticks %d / %d\n", - info->buffer_ticks, src_ticks_per_cycle(info))); - RATE_TRACE("%s: ticks %5d / %d pos %d\n", __func__, - info->buffer_ticks, src_ticks_per_cycle(info), - info->buffer_pos); - - if (src_ticks_per_cycle(info) <= info->buffer_pos) { - /* End of cycle reached, copy last samples to start */ - uint8_t *u8b; - u8b = (uint8_t*)info->buffer; - bcopy(u8b + src_bytes_per_cycle(info), u8b, - bytes_per_tick(info)); - - RATE_ASSERT(info->alpha == 0, - ("%s: completed cycle with " - "alpha non-zero", __func__, info->alpha)); - - info->buffer_pos = 0; - info->buffer_ticks = 0; } + RATE_ASSERT(info->pos <= info->bpos, + ("%s: Buffer overrun: %d > %d\n", __func__, + info->pos, info->bpos)); + RATE_ASSERT(info->pos < info->bpos, + ("%s: Zero buffer!\n", __func__)); + RATE_ASSERT((info->bpos - info->pos) % 2 == 0, + ("%s: Buffer not sample integral\n", __func__)); + i += info->convert(info, dst + i, count - i); + RATE_ASSERT(info->pos <= info->bpos, + ("%s: Buffer overrun: %d > %d\n", + __func__, info->pos, info->bpos)); + if (info->pos == info->bpos) { + /* + * End of buffer cycle. Copy last unit sample + * to beginning of buffer so next cycle can + * interpolate using it. + */ + info->buffer[0] = info->buffer[info->pos - 2]; + info->buffer[1] = info->buffer[info->pos - 1]; + info->bpos = 2; + info->pos = 2; + } + if (i == count) + break; } - - RATE_ASSERT(count >= done, - ("%s: generated too many bytes of data (%d > %d).", - __func__, done, count)); - - if (done != count) { - RATE_TRACE("Only did %d of %d\n", done, count); - } - - return done; + return i << 1; } static struct pcm_feederdesc feeder_rate_desc[] = { @@ -486,4 +804,3 @@ static kobj_method_t feeder_rate_methods[] = { {0, 0} }; FEEDER_DECLARE(feeder_rate, 2, NULL); - diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 56b76f0..facfb1d 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -144,10 +144,13 @@ nomenclature: /* 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) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_8BIT (AFMT_U8 | AFMT_S8) -#define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) -#define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) +#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ + AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) +#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ + AFMT_S16_BE | AFMT_U16_BE) struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index 91a114e..dfc0705 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -30,6 +30,14 @@ SND_DECLARE_FILE("$FreeBSD$"); +/* + * Default speed + */ +#define VCHAN_DEFAULT_SPEED 48000 + +extern int feeder_rate_ratemin; +extern int feeder_rate_ratemax; + struct vchinfo { u_int32_t spd, fmt, blksz, bps, run; struct pcm_channel *channel, *parent; @@ -154,8 +162,12 @@ vchan_setformat(kobj_t obj, void *data, u_int32_t format) ch->fmt = format; ch->bps = 1; ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_16BIT)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_32BIT)? 2 : 0; + if (ch->fmt & AFMT_16BIT) + ch->bps <<= 1; + else if (ch->fmt & AFMT_24BIT) + ch->bps *= 3; + else if (ch->fmt & AFMT_32BIT) + ch->bps <<= 2; CHN_UNLOCK(channel); chn_notify(parent, CHN_N_FORMAT); CHN_LOCK(channel); @@ -243,6 +255,113 @@ static kobj_method_t vchan_methods[] = { }; CHANNEL_DECLARE(vchan); +/* + * On the fly vchan rate settings + */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct snddev_channel *sce; + struct pcm_channel *c, *fake; + struct pcmchan_caps *caps; + int err = 0; + int errcnt = 0; + int found = 0; + int newspd = 0; + int success = 0; + + d = oidp->oid_arg1; + if (pcm_inprog(d, 1) != 1) { + pcm_inprog(d, -1); + return EINPROGRESS; + } + if (d->vchancount < 1) { + pcm_inprog(d, -1); + return EINVAL; + } + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) { + if (req->newptr != NULL && + (c->flags & CHN_F_BUSY)) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EBUSY; + } + } else if (!SLIST_EMPTY(&c->children)) { + if (found++ == 0) + newspd = c->speed; + } + } + CHN_UNLOCK(c); + } + if (found < 1) { + pcm_inprog(d, -1); + return EINVAL; + } + err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); + if (err == 0 && req->newptr != NULL) { + if (newspd < 1) { + pcm_inprog(d, -1); + return EINVAL; + } + SLIST_FOREACH(sce, &d->channels, link) { + c = sce->channel; + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_VIRTUAL) { + if (c->flags & CHN_F_BUSY) { + CHN_UNLOCK(c); + pcm_inprog(d, -1); + return EBUSY; + } + } else if (!SLIST_EMPTY(&c->children)) { + caps = chn_getcaps(c); + if (caps != NULL) { + if (newspd < caps->minspeed || + newspd > caps->maxspeed || + newspd < feeder_rate_ratemin || + newspd > feeder_rate_ratemax) { + errcnt++; + } else { + if (newspd != c->speed) { + err = chn_setspeed(c, newspd); + if (err != 0) + errcnt++; + else + success++; + } else + success++; + } + } else + errcnt++; + } + } + CHN_UNLOCK(c); + } + /* + * Save new value to fake channel. + */ + if (success > 0) { + fake = pcm_getfakechan(d); + if (fake != NULL) { + CHN_LOCK(fake); + fake->speed = newspd; + CHN_UNLOCK(fake); + } + } + } + pcm_inprog(d, -1); + if (errcnt > 0) + err = EINVAL; + return err; +} +#endif + /* virtual channel interface */ int @@ -292,12 +411,74 @@ vchan_create(struct pcm_channel *parent) CHN_LOCK(parent); /* XXX gross ugly hack, murder death kill */ if (first && !err) { + struct pcm_channel *fake; + struct pcmchan_caps *parent_caps; + int speed = 0; + err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); if (err) printf("chn_reset: %d\n", err); - err = chn_setspeed(parent, 44100); + + fake = pcm_getfakechan(d); + if (fake != NULL) { + /* + * Trying to avoid querying kernel hint, previous + * value already saved here. + */ + CHN_LOCK(fake); + speed = fake->speed; + CHN_UNLOCK(fake); + } + /* + * This is very sad. Few soundcards advertised as being + * able to do (insanely) higher/lower speed, but in + * reality, they simply can't. At least, we give user chance + * to set sane value via kernel hints file or sysctl. + */ + if (speed < 1 && resource_int_value(device_get_name(parent->dev), + device_get_unit(parent->dev), + "vchanrate", &speed) != 0) { + speed = VCHAN_DEFAULT_SPEED; + } + + parent_caps = chn_getcaps(parent); + + if (parent_caps != NULL) { + /* + * 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 + * 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; + + err = chn_setspeed(parent, speed); if (err) printf("chn_setspeed: %d\n", err); + else { + if (fake != NULL) { + /* + * Save new value to fake channel. + */ + CHN_LOCK(fake); + fake->speed = speed; + CHN_UNLOCK(fake); + } + } } return err; @@ -338,8 +519,10 @@ gotch: /* remove us from our grandparent's channel list */ err = pcm_chn_remove(d, c); - if (err) + if (err) { + CHN_UNLOCK(parent); return err; + } CHN_UNLOCK(parent); /* destroy ourselves */ @@ -358,6 +541,9 @@ vchan_initsys(device_t dev) 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", ""); #endif return 0; |