/* $OpenBSD: aproc.c,v 1.5 2008/06/02 17:06:36 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * aproc structures are simple audio processing units. They are * interconnected by abuf structures and form a kind of circuit. aproc * structure have call-backs that do the actual processing. * * This module implements the following processing units: * * - rpipe: read end of an unix file (pipe, socket, device...) * * - wpipe: write end of an unix file (pipe, socket, device...) * * - mix: mix N inputs -> 1 output * * - sub: from 1 input -> extract/copy N outputs * * - conv: converts/resamples/remaps a single stream * * TODO * * (easy) split the "conv" into 2 converters: one for input (that * convers anything to 16bit signed) and one for the output (that * converts 16bit signed to anything) * * (hard) handle underruns in rpipe and mix * * (hard) handle overruns in wpipe and sub * * (hard) add a lowpass filter for the resampler. Quality is * not acceptable as is. */ #include #include #include #include #include #include "conf.h" #include "aparams.h" #include "abuf.h" #include "aproc.h" #include "file.h" struct aproc * aproc_new(struct aproc_ops *ops, char *name) { struct aproc *p; p = malloc(sizeof(struct aproc)); if (p == NULL) err(1, name); LIST_INIT(&p->ibuflist); LIST_INIT(&p->obuflist); p->name = name; p->ops = ops; return p; } void aproc_del(struct aproc *p) { DPRINTF("aproc_del: %s: %s: deleted\n", p->ops->name, p->name); free(p); } void aproc_setin(struct aproc *p, struct abuf *ibuf) { LIST_INSERT_HEAD(&p->ibuflist, ibuf, ient); ibuf->rproc = p; if (p->ops->newin) p->ops->newin(p, ibuf); } void aproc_setout(struct aproc *p, struct abuf *obuf) { LIST_INSERT_HEAD(&p->obuflist, obuf, oent); obuf->wproc = p; if (p->ops->newout) p->ops->newout(p, obuf); } int rpipe_in(struct aproc *p, struct abuf *ibuf_dummy) { struct abuf *obuf = LIST_FIRST(&p->obuflist); struct file *f = p->u.io.file; unsigned char *data; unsigned count; if (ABUF_FULL(obuf)) return 0; data = abuf_wgetblk(obuf, &count, 0); obuf->used += file_read(f, data, count); abuf_flush(obuf); return !ABUF_FULL(obuf); } int rpipe_out(struct aproc *p, struct abuf *obuf) { struct file *f = p->u.io.file; unsigned char *data; unsigned count; if (!(f->state & FILE_ROK)) return 0; data = abuf_wgetblk(obuf, &count, 0); obuf->used += file_read(f, data, count); return f->state & FILE_ROK; } void rpipe_del(struct aproc *p) { struct file *f = p->u.io.file; f->rproc = NULL; f->events &= ~POLLIN; aproc_del(p); } void rpipe_eof(struct aproc *p, struct abuf *ibuf_dummy) { DPRINTFN(3, "rpipe_eof: %s\n", p->name); abuf_eof(LIST_FIRST(&p->obuflist)); rpipe_del(p); } void rpipe_hup(struct aproc *p, struct abuf *obuf) { DPRINTFN(3, "rpipe_hup: %s\n", p->name); rpipe_del(p); } struct aproc_ops rpipe_ops = { "rpipe", rpipe_in, rpipe_out, rpipe_eof, rpipe_hup, NULL, NULL }; struct aproc * rpipe_new(struct file *f) { struct aproc *p; p = aproc_new(&rpipe_ops, f->name); p->u.io.file = f; f->rproc = p; f->events |= POLLIN; return p; } void wpipe_del(struct aproc *p) { struct file *f = p->u.io.file; f->wproc = NULL; f->events &= ~POLLOUT; aproc_del(p); } int wpipe_in(struct aproc *p, struct abuf *ibuf) { struct file *f = p->u.io.file; unsigned char *data; unsigned count; if (!(f->state & FILE_WOK)) return 0; data = abuf_rgetblk(ibuf, &count, 0); count = file_write(f, data, count); ibuf->used -= count; ibuf->start += count; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; return f->state & FILE_WOK; } int wpipe_out(struct aproc *p, struct abuf *obuf_dummy) { struct abuf *ibuf = LIST_FIRST(&p->ibuflist); struct file *f = p->u.io.file; unsigned char *data; unsigned count; if (ABUF_EMPTY(ibuf)) return 0; data = abuf_rgetblk(ibuf, &count, 0); count = file_write(f, data, count); ibuf->used -= count; ibuf->start += count; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; if (ABUF_EOF(ibuf)) { abuf_hup(ibuf); wpipe_del(p); return 0; } abuf_fill(ibuf); return 1; } void wpipe_eof(struct aproc *p, struct abuf *ibuf) { DPRINTFN(3, "wpipe_eof: %s\n", p->name); wpipe_del(p); } void wpipe_hup(struct aproc *p, struct abuf *obuf_dummy) { DPRINTFN(3, "wpipe_hup: %s\n", p->name); abuf_hup(LIST_FIRST(&p->ibuflist)); wpipe_del(p); } struct aproc_ops wpipe_ops = { "wpipe", wpipe_in, wpipe_out, wpipe_eof, wpipe_hup, NULL, NULL }; struct aproc * wpipe_new(struct file *f) { struct aproc *p; p = aproc_new(&wpipe_ops, f->name); p->u.io.file = f; f->wproc = p; f->events |= POLLOUT; return p; } /* * Fill an output block with silence. */ void mix_bzero(struct aproc *p) { struct abuf *obuf = LIST_FIRST(&p->obuflist); short *odata; unsigned ocount; DPRINTFN(4, "mix_bzero: used = %u, zero = %u\n", obuf->used, obuf->mixtodo); odata = (short *)abuf_wgetblk(obuf, &ocount, obuf->mixtodo); if (ocount == 0) return; memset(odata, 0, ocount); obuf->mixtodo += ocount; DPRINTFN(4, "mix_bzero: ocount %u, todo %u\n", ocount, obuf->mixtodo); } /* * Mix an input block over an output block. */ void mix_badd(struct abuf *ibuf, struct abuf *obuf) { short *idata, *odata; unsigned i, scount, icount, ocount; int vol = ibuf->mixvol; DPRINTFN(4, "mix_badd: zero = %u, done = %u\n", obuf->mixtodo, ibuf->mixdone); idata = (short *)abuf_rgetblk(ibuf, &icount, 0); if (icount == 0) return; odata = (short *)abuf_wgetblk(obuf, &ocount, ibuf->mixdone); if (ocount == 0) return; scount = (icount < ocount) ? icount : ocount; for (i = scount / sizeof(short); i > 0; i--) { *odata += (*idata * vol) >> ADATA_SHIFT; idata++; odata++; } ibuf->used -= scount; ibuf->mixdone += scount; ibuf->start += scount; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; DPRINTFN(4, "mix_badd: added %u, done = %u, zero = %u\n", scount, ibuf->mixdone, obuf->mixtodo); } /* * Remove an input stream from the mixer. */ void mix_rm(struct aproc *p, struct abuf *ibuf) { LIST_REMOVE(ibuf, ient); DPRINTF("mix_rm: %s\n", p->name); } int mix_in(struct aproc *p, struct abuf *ibuf) { struct abuf *i, *inext, *obuf = LIST_FIRST(&p->obuflist); unsigned icount, ocount; DPRINTFN(4, "mix_in: used = %u, done = %u, zero = %u\n", ibuf->used, ibuf->mixdone, obuf->mixtodo); /* * discard data already sent as silence */ if (ibuf->mixdrop > 0) { icount = ibuf->mixdrop; if (icount > ibuf->used) icount = ibuf->used; ibuf->used -= icount; ibuf->start += icount; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; ibuf->mixdrop -= icount; DPRINTF("mix_in: catched xruns, drop = %u\n", ibuf->mixdrop); } if (ibuf->mixdone >= obuf->mixtodo) return 0; mix_badd(ibuf, obuf); ocount = obuf->mixtodo; LIST_FOREACH(i, &p->ibuflist, ient) { if (ocount > i->mixdone) ocount = i->mixdone; } if (ocount == 0) return 0; obuf->used += ocount; obuf->mixtodo -= ocount; abuf_flush(obuf); mix_bzero(p); for (i = LIST_FIRST(&p->ibuflist); i != LIST_END(&p->ibuflist); i = inext) { inext = LIST_NEXT(i, ient); i->mixdone -= ocount; if (i != ibuf && i->mixdone < obuf->mixtodo) { if (ABUF_EOF(i)) { mix_rm(p, i); abuf_hup(i); continue; } mix_badd(i, obuf); abuf_fill(i); } } return 1; } int mix_out(struct aproc *p, struct abuf *obuf) { struct abuf *i, *inext; unsigned ocount, drop; DPRINTFN(4, "mix_out: used = %u, zero = %u\n", obuf->used, obuf->mixtodo); mix_bzero(p); ocount = obuf->mixtodo; for (i = LIST_FIRST(&p->ibuflist); i != LIST_END(&p->ibuflist); i = inext) { inext = LIST_NEXT(i, ient); if (!ABUF_ROK(i)) { if ((p->u.mix.flags & MIX_DROP) && i->mixdone == 0) { if (i->xrun == XRUN_ERROR) { mix_rm(p, i); abuf_hup(i); continue; } drop = obuf->mixtodo; i->mixdone += drop; if (i->xrun == XRUN_SYNC) i->mixdrop += drop; DPRINTF("mix_out: xrun, drop = %u\n", i->mixdrop); } } else mix_badd(i, obuf); if (ocount > i->mixdone) ocount = i->mixdone; if (ABUF_EOF(i)) { mix_rm(p, i); abuf_hup(i); continue; } abuf_fill(i); } if (ocount == 0) return 0; if (LIST_EMPTY(&p->ibuflist)) { DPRINTF("mix_out: nothing more to do...\n"); obuf->wproc = NULL; aproc_del(p); return 0; } obuf->used += ocount; obuf->mixtodo -= ocount; LIST_FOREACH(i, &p->ibuflist, ient) { i->mixdone -= ocount; } return 1; } void mix_eof(struct aproc *p, struct abuf *ibuf) { struct abuf *obuf = LIST_FIRST(&p->obuflist); DPRINTF("mix_eof: %s: detached\n", p->name); mix_rm(p, ibuf); /* * If there's no more inputs, abuf_run() will trigger the eof * condition and propagate it, so no need to handle it here. */ abuf_run(obuf); DPRINTF("mix_eof: done\n"); } void mix_hup(struct aproc *p, struct abuf *obuf) { struct abuf *ibuf; while (!LIST_EMPTY(&p->ibuflist)) { ibuf = LIST_FIRST(&p->ibuflist); mix_rm(p, ibuf); abuf_hup(ibuf); } DPRINTF("mix_hup: %s: done\n", p->name); aproc_del(p); } void mix_newin(struct aproc *p, struct abuf *ibuf) { ibuf->mixdone = 0; ibuf->mixdrop = 0; ibuf->mixvol = ADATA_UNIT; ibuf->xrun = XRUN_IGNORE; } void mix_newout(struct aproc *p, struct abuf *obuf) { obuf->mixtodo = 0; mix_bzero(p); } struct aproc_ops mix_ops = { "mix", mix_in, mix_out, mix_eof, mix_hup, mix_newin, mix_newout }; struct aproc * mix_new(void) { struct aproc *p; p = aproc_new(&mix_ops, "softmix"); p->u.mix.flags = 0; return p; } /* * Copy data from ibuf to obuf. */ void sub_bcopy(struct abuf *ibuf, struct abuf *obuf) { unsigned char *idata, *odata; unsigned icount, ocount, scount; idata = abuf_rgetblk(ibuf, &icount, obuf->subdone); if (icount == 0) return; odata = abuf_wgetblk(obuf, &ocount, 0); if (ocount == 0) return; scount = (icount < ocount) ? icount : ocount; memcpy(odata, idata, scount); obuf->subdone += scount; obuf->used += scount; DPRINTFN(4, "sub_bcopy: %u bytes\n", scount); } void sub_rm(struct aproc *p, struct abuf *obuf) { LIST_REMOVE(obuf, oent); DPRINTF("sub_rm: %s\n", p->name); } void sub_del(struct aproc *p) { aproc_del(p); } int sub_in(struct aproc *p, struct abuf *ibuf) { struct abuf *i, *inext; unsigned done, drop; int again; again = 1; done = ibuf->used; for (i = LIST_FIRST(&p->obuflist); i != LIST_END(&p->obuflist); i = inext) { inext = LIST_NEXT(i, oent); if (!ABUF_WOK(i)) { if ((p->u.sub.flags & SUB_DROP) && i->subdone == 0) { if (i->xrun == XRUN_ERROR) { sub_rm(p, i); abuf_eof(i); continue; } drop = ibuf->used; if (i->xrun == XRUN_SYNC) i->subdrop += drop; i->subdone += drop; DPRINTF("sub_in: xrun, drop = %u\n", i->subdrop); } } else { sub_bcopy(ibuf, i); abuf_flush(i); } if (!ABUF_WOK(i)) again = 0; if (done > i->subdone) done = i->subdone; } LIST_FOREACH(i, &p->obuflist, oent) { i->subdone -= done; } ibuf->used -= done; ibuf->start += done; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; return again; } int sub_out(struct aproc *p, struct abuf *obuf) { struct abuf *ibuf = LIST_FIRST(&p->ibuflist); struct abuf *i, *inext; unsigned char *odata; unsigned ocount; unsigned done; /* * generate silence for dropped samples */ while (obuf->subdrop > 0) { odata = abuf_wgetblk(obuf, &ocount, 0); if (ocount >= obuf->subdrop) ocount = obuf->subdrop; if (ocount == 0) break; memset(odata, 0, ocount); obuf->used += ocount; obuf->subdrop -= ocount; DPRINTF("sub_out: catch, drop = %u\n", obuf->subdrop); } if (obuf->subdone >= ibuf->used) return 0; sub_bcopy(ibuf, obuf); done = ibuf->used; LIST_FOREACH(i, &p->obuflist, oent) { if (i != obuf && ABUF_WOK(i)) { sub_bcopy(ibuf, i); abuf_flush(i); } if (done > i->subdone) done = i->subdone; } if (done == 0) return 0; LIST_FOREACH(i, &p->obuflist, oent) { i->subdone -= done; } ibuf->used -= done; ibuf->start += done; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; if (ABUF_EOF(ibuf)) { abuf_hup(ibuf); for (i = LIST_FIRST(&p->obuflist); i != LIST_END(&p->obuflist); i = inext) { inext = LIST_NEXT(i, oent); if (i != ibuf) abuf_eof(i); } ibuf->wproc = NULL; sub_del(p); return 0; } abuf_fill(ibuf); return 1; } void sub_eof(struct aproc *p, struct abuf *ibuf) { struct abuf *obuf; while (!LIST_EMPTY(&p->obuflist)) { obuf = LIST_FIRST(&p->obuflist); sub_rm(p, obuf); abuf_eof(obuf); } sub_del(p); } void sub_hup(struct aproc *p, struct abuf *obuf) { struct abuf *ibuf = LIST_FIRST(&p->ibuflist); DPRINTF("sub_hup: %s: detached\n", p->name); sub_rm(p, obuf); if (LIST_EMPTY(&p->obuflist)) { abuf_hup(ibuf); sub_del(p); } else abuf_run(ibuf); DPRINTF("sub_hup: done\n"); } void sub_newout(struct aproc *p, struct abuf *obuf) { obuf->subdone = 0; obuf->subdrop = 0; obuf->xrun = XRUN_IGNORE; } struct aproc_ops sub_ops = { "sub", sub_in, sub_out, sub_eof, sub_hup, NULL, sub_newout }; struct aproc * sub_new(void) { struct aproc *p; p = aproc_new(&sub_ops, "copy"); p->u.sub.flags = 0; return p; } /* * Convert one block. */ void conv_bcopy(struct aconv *ist, struct aconv *ost, struct abuf *ibuf, struct abuf *obuf) { int *ictx; unsigned inch, ibps; unsigned char *idata; int ibnext, isigbit; unsigned ishift; int isnext; unsigned ipos, orate; unsigned ifr; int *octx; unsigned onch, oshift; int osigbit; unsigned obps; unsigned char *odata; int obnext, osnext; unsigned opos, irate; unsigned ofr; unsigned c, i; int s, *ctx; unsigned icount, ocount; /* * It's ok to have s uninitialized, but we dont want the compiler to * complain about it. */ s = (int)0xdeadbeef; /* * Calculate max frames readable at once from the input buffer. */ idata = abuf_rgetblk(ibuf, &icount, 0); ifr = icount / ibuf->bpf; odata = abuf_wgetblk(obuf, &ocount, 0); ofr = ocount / obuf->bpf; /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ ictx = ist->ctx + ist->cmin; octx = ist->ctx + ost->cmin; inch = ist->nch; ibps = ist->bps; ibnext = ist->bnext; isigbit = ist->sigbit; ishift = ist->shift; isnext = ist->snext; ipos = ist->pos; irate = ist->rate; onch = ost->nch; oshift = ost->shift; osigbit = ost->sigbit; obps = ost->bps; obnext = ost->bnext; osnext = ost->snext; opos = ost->pos; orate = ost->rate; /* * Start conversion. */ idata += ist->bfirst; odata += ost->bfirst; DPRINTFN(4, "conv_bcopy: ifr=%d ofr=%d\n", ifr, ofr); for (;;) { if ((int)(ipos - opos) > 0) { if (ofr == 0) break; ctx = octx; for (c = onch; c > 0; c--) { s = *ctx++ << 16; s >>= oshift; s ^= osigbit; for (i = obps; i > 0; i--) { *odata = (unsigned char)s; s >>= 8; odata += obnext; } odata += osnext; } opos += irate; ofr--; } else { if (ifr == 0) break; ctx = ictx; for (c = inch; c > 0; c--) { for (i = ibps; i > 0; i--) { s <<= 8; s |= *idata; idata += ibnext; } s ^= isigbit; s <<= ishift; *ctx++ = (short)(s >> 16); idata += isnext; } ipos += orate; ifr--; } } ist->pos = ipos; ost->pos = opos; DPRINTFN(4, "conv_bcopy: done, ifr=%d ofr=%d\n", ifr, ofr); /* * Update FIFO pointers. */ icount -= ifr * ist->bpf; ibuf->used -= icount; ibuf->start += icount; if (ibuf->start >= ibuf->len) ibuf->start -= ibuf->len; ocount -= ofr * ost->bpf; obuf->used += ocount; } void conv_del(struct aproc *p) { aproc_del(p); } int conv_in(struct aproc *p, struct abuf *ibuf) { struct abuf *obuf = LIST_FIRST(&p->obuflist); if (!ABUF_WOK(obuf)) return 0; conv_bcopy(&p->u.conv.ist, &p->u.conv.ost, ibuf, obuf); abuf_flush(obuf); return ABUF_WOK(obuf); } int conv_out(struct aproc *p, struct abuf *obuf) { struct abuf *ibuf = LIST_FIRST(&p->ibuflist); if (!ABUF_ROK(ibuf)) return 0; conv_bcopy(&p->u.conv.ist, &p->u.conv.ost, ibuf, obuf); if (ABUF_EOF(ibuf)) { obuf->wproc = NULL; abuf_hup(ibuf); conv_del(p); return 0; } abuf_fill(ibuf); return 1; } void conv_eof(struct aproc *p, struct abuf *ibuf) { abuf_eof(LIST_FIRST(&p->obuflist)); conv_del(p); } void conv_hup(struct aproc *p, struct abuf *obuf) { abuf_hup(LIST_FIRST(&p->ibuflist)); conv_del(p); } void aconv_init(struct aconv *st, struct aparams *par, int input) { unsigned i; st->bps = par->bps; st->sigbit = par->sig ? 0 : 1 << (par->bits - 1); if (par->msb) { st->shift = 32 - par->bps * 8; } else { st->shift = 32 - par->bits; } if ((par->le && input) || (!par->le && !input)) { st->bfirst = st->bps - 1; st->bnext = -1; st->snext = 2 * st->bps; } else { st->bfirst = 0; st->bnext = 1; st->snext = 0; } st->cmin = par->cmin; st->nch = par->cmax - par->cmin + 1; st->bpf = st->nch * st->bps; st->rate = par->rate; st->pos = 0; for (i = 0; i < CHAN_MAX; i++) st->ctx[i] = 0; } struct aproc_ops conv_ops = { "conv", conv_in, conv_out, conv_eof, conv_hup, NULL, NULL }; struct aproc * conv_new(char *name, struct aparams *ipar, struct aparams *opar) { struct aproc *p; p = aproc_new(&conv_ops, name); aconv_init(&p->u.conv.ist, ipar, 1); aconv_init(&p->u.conv.ost, opar, 0); return p; }