#ifdef _KERNEL
#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif
#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/pcm.h>
#include <dev/sound/pcm/vchan.h>
#include "feeder_if.h"
#define SND_USE_FXDIV
#include "snd_fxdiv_gen.h"
#endif
struct feed_mixer_info {
uint32_t format;
uint32_t channels;
int bps;
};
__always_inline static void
feed_mixer_apply(uint8_t *src, uint8_t *dst, uint32_t count, const uint32_t fmt)
{
intpcm32_t z;
intpcm_t x, y;
src += count;
dst += count;
do {
src -= AFMT_BPS(fmt);
dst -= AFMT_BPS(fmt);
count -= AFMT_BPS(fmt);
x = pcm_sample_read_calc(src, fmt);
y = pcm_sample_read_calc(dst, fmt);
z = INTPCM_T(x) + y;
x = pcm_clamp_calc(z, fmt);
pcm_sample_write(dst, x, fmt);
} while (count != 0);
}
static int
feed_mixer_init(struct pcm_feeder *f)
{
struct feed_mixer_info *info;
if (f->desc.in != f->desc.out)
return (EINVAL);
info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
if (info == NULL)
return (ENOMEM);
info->format = AFMT_ENCODING(f->desc.in);
info->channels = AFMT_CHANNEL(f->desc.in);
info->bps = AFMT_BPS(f->desc.in);
f->data = info;
return (0);
}
static int
feed_mixer_free(struct pcm_feeder *f)
{
struct feed_mixer_info *info;
info = f->data;
free(info, M_DEVBUF);
f->data = NULL;
return (0);
}
static int
feed_mixer_set(struct pcm_feeder *f, int what, int value)
{
struct feed_mixer_info *info;
info = f->data;
switch (what) {
case FEEDMIXER_CHANNELS:
if (value < SND_CHN_MIN || value > SND_CHN_MAX)
return (EINVAL);
info->channels = (uint32_t)value;
break;
default:
return (EINVAL);
}
return (0);
}
static __inline int
feed_mixer_rec(struct pcm_channel *c)
{
struct pcm_channel *ch;
struct snd_dbuf *b, *bs;
uint32_t cnt, maxfeed;
int rdy;
b = c->bufsoft;
b->rp = 0;
b->rl = 0;
cnt = b->bufsize;
maxfeed = SND_FXROUND(SND_FXDIV_MAX, b->align);
do {
cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf,
min(cnt, maxfeed), c->bufhard);
if (cnt != 0) {
sndbuf_acquire(b, b->tmpbuf, cnt);
cnt = sndbuf_getfree(b);
}
} while (cnt != 0);
if (b->rl < b->align) {
b->rl = 0;
return (0);
}
rdy = b->rl;
CHN_FOREACH(ch, c, children.busy) {
CHN_LOCK(ch);
if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) {
CHN_UNLOCK(ch);
continue;
}
bs = ch->bufsoft;
if (ch->flags & CHN_F_MMAP)
sndbuf_dispose(bs, NULL, sndbuf_getready(bs));
cnt = sndbuf_getfree(bs);
if (cnt < bs->align) {
CHN_UNLOCK(ch);
continue;
}
maxfeed = SND_FXROUND(SND_FXDIV_MAX, bs->align);
do {
cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf,
min(cnt, maxfeed), b);
if (cnt != 0) {
sndbuf_acquire(bs, bs->tmpbuf, cnt);
cnt = sndbuf_getfree(bs);
}
} while (cnt != 0);
if (b->rl != 0)
ch->xruns++;
CHN_UNLOCK(ch);
b->rp = 0;
b->rl = rdy;
}
b->rl = 1;
c->flags &= ~CHN_F_DIRTY;
return (0);
}
static int
feed_mixer_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
uint32_t count, void *source)
{
struct feed_mixer_info *info;
struct snd_dbuf *src = source;
struct pcm_channel *ch;
uint32_t cnt, mcnt, rcnt, sz;
int passthrough;
uint8_t *tmp;
if (c->direction == PCMDIR_REC)
return (feed_mixer_rec(c));
sz = src->bufsize;
if (sz < count)
count = sz;
info = f->data;
sz = info->bps * info->channels;
count = SND_FXROUND(count, sz);
if (count < sz)
return (0);
tmp = src->buf;
rcnt = 0;
mcnt = 0;
passthrough = 0;
CHN_FOREACH(ch, c, children.busy) {
CHN_LOCK(ch);
if (CHN_STOPPED(ch) || (ch->flags & CHN_F_DIRTY)) {
CHN_UNLOCK(ch);
continue;
}
if ((ch->flags & CHN_F_MMAP) && !(ch->flags & CHN_F_CLOSING))
sndbuf_acquire(ch->bufsoft, NULL,
sndbuf_getfree(ch->bufsoft));
if (c->flags & CHN_F_PASSTHROUGH) {
if (passthrough == 0 &&
(ch->format & AFMT_PASSTHROUGH)) {
rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch,
b, count, ch->bufsoft), sz);
passthrough = 1;
} else
FEEDER_FEED(ch->feeder, ch, tmp, count,
ch->bufsoft);
} else if (c->flags & CHN_F_EXCLUSIVE) {
if (passthrough == 0 && (ch->flags & CHN_F_EXCLUSIVE)) {
rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch,
b, count, ch->bufsoft), sz);
passthrough = 1;
} else
FEEDER_FEED(ch->feeder, ch, tmp, count,
ch->bufsoft);
} else {
if (rcnt == 0) {
rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch,
b, count, ch->bufsoft), sz);
mcnt = count - rcnt;
} else {
cnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ch,
tmp, count, ch->bufsoft), sz);
if (cnt != 0) {
if (mcnt != 0) {
memset(b + rcnt,
sndbuf_zerodata(
f->desc.out), mcnt);
mcnt = 0;
}
switch (info->format) {
case AFMT_S16_NE:
feed_mixer_apply(tmp, b, cnt,
AFMT_S16_NE);
break;
case AFMT_S24_NE:
feed_mixer_apply(tmp, b, cnt,
AFMT_S24_NE);
break;
case AFMT_S32_NE:
feed_mixer_apply(tmp, b, cnt,
AFMT_S32_NE);
break;
default:
feed_mixer_apply(tmp, b, cnt,
info->format);
break;
}
if (cnt > rcnt)
rcnt = cnt;
}
}
}
CHN_UNLOCK(ch);
}
if (++c->feedcount == 0)
c->feedcount = 2;
c->flags &= ~CHN_F_DIRTY;
return (rcnt);
}
static kobj_method_t feeder_mixer_methods[] = {
KOBJMETHOD(feeder_init, feed_mixer_init),
KOBJMETHOD(feeder_free, feed_mixer_free),
KOBJMETHOD(feeder_set, feed_mixer_set),
KOBJMETHOD(feeder_feed, feed_mixer_feed),
KOBJMETHOD_END
};
FEEDER_DECLARE(feeder_mixer, FEEDER_MIXER);