diff options
-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; |