summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/pcm/vchan.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/sound/pcm/vchan.c')
-rw-r--r--sys/dev/sound/pcm/vchan.c383
1 files changed, 383 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..c96f0f3
--- /dev/null
+++ b/sys/dev/sound/pcm/vchan.c
@@ -0,0 +1,383 @@
+/*
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pcm/vchan.h>
+#include "feeder_if.h"
+
+struct vchinfo {
+ u_int32_t spd, fmt, blksz, run;
+ struct pcm_channel *channel, *parent;
+ struct pcmchan_caps caps;
+};
+
+static u_int32_t vchan_fmt[] = {
+ AFMT_S16_LE,
+ 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;
+ 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_S16_LE, 0},
+ {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;
+ 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;
+
+ ch->blksz = blocksize;
+ chn_notify(parent, CHN_N_BLOCKSIZE);
+ 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;
+
+ if (!(parent->flags & CHN_F_BUSY))
+ return EBUSY;
+
+ 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;
+ }
+
+ first = SLIST_EMPTY(&parent->children);
+ /* add us to our parent channel's children */
+ pce->channel = child;
+ SLIST_INSERT_HEAD(&parent->children, pce, link);
+
+ /* add us to our grandparent's channel list */
+ err = pcm_chn_add(d, child);
+ if (err) {
+ pcm_chn_destroy(child);
+ free(pce, M_DEVBUF);
+ }
+
+ /* XXX gross ugly hack, kill murder death */
+ 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;
+
+ if (!(parent->flags & CHN_F_BUSY))
+ return EBUSY;
+ if (SLIST_EMPTY(&parent->children))
+ return EINVAL;
+
+ /* remove us from our grantparent's channel list */
+ err = pcm_chn_remove(d, c);
+ if (err)
+ return err;
+
+ /* remove us from our parent's children list */
+ SLIST_FOREACH(pce, &parent->children, link) {
+ if (pce->channel == c)
+ goto gotch;
+ }
+ return EINVAL;
+gotch:
+ SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
+ free(pce, M_DEVBUF);
+
+ if (SLIST_EMPTY(&parent->children))
+ parent->flags &= ~CHN_F_BUSY;
+ /* destroy ourselves */
+ err = pcm_chn_destroy(c);
+
+ return err;
+}
+
+#ifdef SND_DYNSYSCTL
+static int
+sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
+{
+ struct snddev_info *d;
+ struct snddev_channel *sce;
+ struct pcm_channel *c;
+ int err, newcnt, cnt;
+
+ d = oidp->oid_arg1;
+
+ cnt = 0;
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL))
+ cnt++;
+ }
+ newcnt = cnt;
+
+ err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
+ if (err == 0 && req->newptr != NULL) {
+ if (newcnt > cnt) {
+ /* add new vchans - find a parent channel first */
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ /* not a candidate if not a play channel */
+ if (c->direction != PCMDIR_PLAY)
+ goto addskip;
+ /* not a candidate if a virtual channel */
+ if (c->flags & CHN_F_VIRTUAL)
+ goto addskip;
+ /* not a candidate if it's in use */
+ if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children)))
+ goto addskip;
+ /*
+ * if we get here we're a nonvirtual play channel, and either
+ * 1) not busy
+ * 2) busy with children, not directly open
+ *
+ * thus we can add children
+ */
+ goto addok;
+addskip:
+ }
+ return EBUSY;
+addok:
+ c->flags |= CHN_F_BUSY;
+ while (err == 0 && newcnt > cnt) {
+ err = vchan_create(c);
+ if (err == 0)
+ cnt++;
+ }
+ } else if (newcnt < cnt) {
+ while (err == 0 && newcnt < cnt) {
+ SLIST_FOREACH(sce, &d->channels, link) {
+ c = sce->channel;
+ if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
+ goto remok;
+ }
+ return EINVAL;
+remok:
+ err = vchan_destroy(c);
+ if (err == 0)
+ cnt--;
+ }
+ }
+ }
+
+ return err;
+}
+#endif
+
+int
+vchan_initsys(struct snddev_info *d)
+{
+#ifdef SND_DYNSYSCTL
+ SYSCTL_ADD_PROC(&d->sysctl_tree, SYSCTL_CHILDREN(d->sysctl_tree_top),
+ OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
+ sysctl_hw_snd_vchans, "I", "")
+#endif
+ return 0;
+}
+
+
OpenPOWER on IntegriCloud