diff options
Diffstat (limited to 'sys/dev/sound/pcm/vchan.c')
-rw-r--r-- | sys/dev/sound/pcm/vchan.c | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c new file mode 100644 index 0000000..eca4ac4 --- /dev/null +++ b/sys/dev/sound/pcm/vchan.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2001 Cameron Grant <gandalf@vilnya.demon.co.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/vchan.h> +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +struct vchinfo { + u_int32_t spd, fmt, blksz, bps, run; + struct pcm_channel *channel, *parent; + struct pcmchan_caps caps; +}; + +static u_int32_t vchan_fmt[] = { + AFMT_STEREO | AFMT_S16_LE, + 0 +}; + +static int +vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +{ + /* + * to is the output buffer, tmp is the input buffer + * count is the number of 16bit samples to mix + */ + int i; + int x; + + for(i = 0; i < count; i++) { + x = to[i]; + x += tmp[i]; + if (x < -32768) { + /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ + x = -32768; + } + if (x > 32767) { + /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ + x = 32767; + } + to[i] = x & 0x0000ffff; + } + return 0; +} + +static int +feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +{ + /* we're going to abuse things a bit */ + struct snd_dbuf *src = source; + struct pcmchan_children *cce; + struct pcm_channel *ch; + int16_t *tmp, *dst; + unsigned int cnt; + + KASSERT(sndbuf_getsize(src) >= count, ("bad bufsize")); + count &= ~1; + bzero(b, count); + + /* + * we are going to use our source as a temporary buffer since it's + * got no other purpose. we obtain our data by traversing the channel + * list of children and calling vchan_mix_* to mix count bytes from each + * into our destination buffer, b + */ + dst = (int16_t *)b; + tmp = (int16_t *)sndbuf_getbuf(src); + bzero(tmp, count); + SLIST_FOREACH(cce, &c->children, link) { + ch = cce->channel; + if (ch->flags & CHN_F_TRIGGERED) { + if (ch->flags & CHN_F_MAPPED) + sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); + cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); + vchan_mix_s16(dst, tmp, cnt / 2); + } + } + + return count; +} + +static struct pcm_feederdesc feeder_vchan_s16_desc[] = { + {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0}, +}; +static kobj_method_t feeder_vchan_s16_methods[] = { + KOBJMETHOD(feeder_feed, feed_vchan_s16), + { 0, 0 } +}; +FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); + +/************************************************************/ + +static void * +vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct vchinfo *ch; + struct pcm_channel *parent = devinfo; + + KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->parent = parent; + ch->channel = c; + ch->fmt = AFMT_U8; + ch->spd = DSP_DEFAULT_SPEED; + ch->blksz = 2048; + + c->flags |= CHN_F_VIRTUAL; + + return ch; +} + +static int +vchan_free(kobj_t obj, void *data) +{ + return 0; +} + +static int +vchan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + 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; + chn_notify(parent, CHN_N_FORMAT); + return 0; +} + +static int +vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + ch->spd = speed; + chn_notify(parent, CHN_N_RATE); + return speed; +} + +static int +vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + int prate, crate; + + ch->blksz = blocksize; + chn_notify(parent, CHN_N_BLOCKSIZE); + + crate = ch->spd * ch->bps; + prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard); + blocksize = sndbuf_getblksz(parent->bufhard); + blocksize *= prate; + blocksize /= crate; + + return blocksize; +} + +static int +vchan_trigger(kobj_t obj, void *data, int go) +{ + struct vchinfo *ch = data; + struct pcm_channel *parent = ch->parent; + + if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + return 0; + + ch->run = (go == PCMTRIG_START)? 1 : 0; + chn_notify(parent, CHN_N_TRIGGER); + + return 0; +} + +static struct pcmchan_caps * +vchan_getcaps(kobj_t obj, void *data) +{ + struct vchinfo *ch = data; + + ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard); + ch->caps.maxspeed = ch->caps.minspeed; + ch->caps.fmtlist = vchan_fmt; + ch->caps.caps = 0; + + return &ch->caps; +} + +static kobj_method_t vchan_methods[] = { + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_setblocksize, vchan_setblocksize), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(vchan); + +/* virtual channel interface */ + +int +vchan_create(struct pcm_channel *parent) +{ + struct snddev_info *d = parent->parentsnddev; + struct pcmchan_children *pce; + struct pcm_channel *child; + int err, first; + + pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); + if (!pce) { + return ENOMEM; + } + + /* create a new playback channel */ + child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); + if (!child) { + free(pce, M_DEVBUF); + return ENODEV; + } + + CHN_LOCK(parent); + if (!(parent->flags & CHN_F_BUSY)) { + CHN_UNLOCK(parent); + return EBUSY; + } + + first = SLIST_EMPTY(&parent->children); + /* add us to our parent channel's children */ + pce->channel = child; + SLIST_INSERT_HEAD(&parent->children, pce, link); + CHN_UNLOCK(parent); + + /* add us to our grandparent's channel list */ + err = pcm_chn_add(d, child, !first); + if (err) { + pcm_chn_destroy(child); + free(pce, M_DEVBUF); + } + + /* XXX gross ugly hack, murder death kill */ + if (first && !err) { + err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); + if (err) + printf("chn_reset: %d\n", err); + err = chn_setspeed(parent, 44100); + if (err) + printf("chn_setspeed: %d\n", err); + } + + return err; +} + +int +vchan_destroy(struct pcm_channel *c) +{ + struct pcm_channel *parent = c->parentchannel; + struct snddev_info *d = parent->parentsnddev; + struct pcmchan_children *pce; + int err, last; + + CHN_LOCK(parent); + if (!(parent->flags & CHN_F_BUSY)) { + CHN_UNLOCK(parent); + return EBUSY; + } + if (SLIST_EMPTY(&parent->children)) { + CHN_UNLOCK(parent); + return EINVAL; + } + + /* remove us from our parent's children list */ + SLIST_FOREACH(pce, &parent->children, link) { + if (pce->channel == c) + goto gotch; + } + CHN_UNLOCK(parent); + return EINVAL; +gotch: + SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); + free(pce, M_DEVBUF); + + last = SLIST_EMPTY(&parent->children); + if (last) + parent->flags &= ~CHN_F_BUSY; + + /* remove us from our grantparent's channel list */ + err = pcm_chn_remove(d, c, !last); + if (err) + return err; + + CHN_UNLOCK(parent); + /* destroy ourselves */ + err = pcm_chn_destroy(c); + + return err; +} + +int +vchan_initsys(device_t dev) +{ +#ifdef SND_DYNSYSCTL + struct snddev_info *d; + + d = device_get_softc(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", ""); +#endif + + return 0; +} + + |