summaryrefslogtreecommitdiff
path: root/usr.bin/aucat
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/aucat')
-rw-r--r--usr.bin/aucat/Makefile5
-rw-r--r--usr.bin/aucat/abuf.c270
-rw-r--r--usr.bin/aucat/abuf.h86
-rw-r--r--usr.bin/aucat/aparams.c94
-rw-r--r--usr.bin/aucat/aparams.h66
-rw-r--r--usr.bin/aucat/aproc.c850
-rw-r--r--usr.bin/aucat/aproc.h145
-rw-r--r--usr.bin/aucat/aucat.1338
-rw-r--r--usr.bin/aucat/aucat.c824
-rw-r--r--usr.bin/aucat/conf.h54
-rw-r--r--usr.bin/aucat/dev.h35
-rw-r--r--usr.bin/aucat/dev_sun.c264
-rw-r--r--usr.bin/aucat/file.c302
-rw-r--r--usr.bin/aucat/file.h75
-rw-r--r--usr.bin/aucat/headers.c230
-rw-r--r--usr.bin/aucat/legacy.c155
16 files changed, 3516 insertions, 277 deletions
diff --git a/usr.bin/aucat/Makefile b/usr.bin/aucat/Makefile
index c798320824a..a9992a57427 100644
--- a/usr.bin/aucat/Makefile
+++ b/usr.bin/aucat/Makefile
@@ -1,5 +1,8 @@
-# $OpenBSD: Makefile,v 1.1 1997/01/02 22:12:26 kstailey Exp $
+# $OpenBSD: Makefile,v 1.2 2008/05/23 07:15:46 ratchov Exp $
PROG= aucat
+SRCS= aucat.c abuf.c aparams.c aproc.c dev_sun.c file.c headers.c \
+ legacy.c
+CFLAGS+= -DDEBUG -Wall -Wstrict-prototypes -Werror -Wundef
.include <bsd.prog.mk>
diff --git a/usr.bin/aucat/abuf.c b/usr.bin/aucat/abuf.c
new file mode 100644
index 00000000000..eb4b97e86b4
--- /dev/null
+++ b/usr.bin/aucat/abuf.c
@@ -0,0 +1,270 @@
+/* $OpenBSD: abuf.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+/*
+ * Simple byte fifo. It has one reader and one writer. The abuf
+ * structure is used to interconnect audio processing units (aproc
+ * structures).
+ *
+ * The abuf data is split in two parts: (1) valid data available to the reader
+ * (2) space available to the writer, which is not necessarily unused. It works
+ * as follows: the write starts filling at offset (start + used), once the data
+ * is ready, the writer adds to used the count of bytes available.
+ *
+ * TODO:
+ *
+ * (easy) create abuf_wcommitblk(), abuf_rdiscardblk() instead of tweeking
+ * the fifo pointers by hand. But first, find shorter function names...
+ *
+ * (easy) dont initialize aproc-specific stuff in abuf_new(), let the
+ * aproc xxx_new() routines do it
+ *
+ * (hard) make abuf_fill() a boolean depending on whether
+ * eof is reached. So the caller can do:
+ *
+ * if (!abuf_fill(buf)) {
+ * ...
+ * }
+ */
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "conf.h"
+#include "aproc.h"
+#include "abuf.h"
+
+struct abuf *
+abuf_new(unsigned nfr, unsigned bpf)
+{
+ struct abuf *buf;
+ unsigned len;
+
+ len = nfr * bpf;
+ buf = malloc(sizeof(struct abuf) + len);
+ if (buf == NULL) {
+ err(1, "abuf_new: malloc");
+ }
+ buf->bpf = bpf;
+
+ /*
+ * fill fifo pointers
+ */
+ buf->len = len;
+ buf->used = 0;
+ buf->start = 0;
+ buf->rproc = NULL;
+ buf->wproc = NULL;
+ return buf;
+}
+
+void
+abuf_del(struct abuf *buf)
+{
+ DPRINTF("abuf_del:\n");
+ if (buf->rproc) {
+ fprintf(stderr, "abuf_del: has rproc: %s\n", buf->rproc->name);
+ abort();
+ }
+ if (buf->wproc) {
+ fprintf(stderr, "abuf_del: has wproc: %s\n", buf->wproc->name);
+ abort();
+ }
+ if (buf->used > 0)
+ fprintf(stderr, "abuf_del: used = %u\n", buf->used);
+ free(buf);
+}
+
+/*
+ * Get a pointer to the readable block at the given offset.
+ */
+unsigned char *
+abuf_rgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
+{
+ unsigned count, start, used;
+
+ start = buf->start + ofs;
+ used = buf->used - ofs;
+ count = buf->len - start;
+ if (count > used)
+ count = used;
+ *rsize = count;
+ return buf->data + start;
+}
+
+/*
+ * Get a pointer to the writable block at offset ofs.
+ */
+unsigned char *
+abuf_wgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
+{
+ unsigned end, avail, count;
+
+
+ end = buf->start + buf->used + ofs;
+ if (end >= buf->len)
+ end -= buf->len;
+#ifdef DEBUG
+ if (end >= buf->len) {
+ fprintf(stderr, "abuf_wgetblk: %s -> %s: bad ofs, "
+ "start = %u, used = %u, len = %u, ofs = %u\n",
+ buf->wproc->name, buf->rproc->name,
+ buf->start, buf->used, buf->len, ofs);
+ abort();
+ }
+#endif
+ avail = buf->len - (buf->used + ofs);
+ count = buf->len - end;
+ if (count > avail)
+ count = avail;
+ *rsize = count;
+ return buf->data + end;
+}
+
+/*
+ * Notify the read end of the buffer that there is input available
+ * and that data can be processed again.
+ */
+void
+abuf_flush(struct abuf *buf)
+{
+ struct aproc *p = buf->rproc;
+
+ for (;;) {
+ if (!ABUF_ROK(buf))
+ break;
+ if (p == NULL || !p->ops->in(p, buf))
+ break;
+ }
+}
+
+/*
+ * Notify the write end of the buffer that there is room and data can be
+ * written again. This routine can only be called from the out()
+ * call-back of the reader.
+ *
+ * NOTE: The abuf writer may reach eof condition and disappear, dont keep
+ * references to abuf->wproc.
+ */
+void
+abuf_fill(struct abuf *buf)
+{
+ struct aproc *p = buf->wproc;
+
+ for (;;) {
+ if (!ABUF_WOK(buf))
+ break;
+ if (p == NULL || !p->ops->out(p, buf))
+ break;
+ }
+}
+
+/*
+ * Run a read/write loop on the buffer until either the reader or the
+ * writer blocks, or until the buffer reaches eofs. We can not get hup hear,
+ * since hup() is only called from terminal nodes, from the main loop.
+ *
+ * NOTE: The buffer may disappear (ie. be free()ed) if eof is reached, so
+ * do not keep references to the buffer or to its writer or reader.
+ */
+void
+abuf_run(struct abuf *buf)
+{
+ struct aproc *p;
+ int canfill = 1, canflush = 1;
+
+ for (;;) {
+ if (ABUF_EOF(buf)) {
+ p = buf->rproc;
+ DPRINTFN(2, "abuf_run: %s: got eof\n", p->name);
+ p->ops->eof(p, buf);
+ buf->rproc = NULL;
+ abuf_del(buf);
+ return;
+ }
+ if (ABUF_WOK(buf) && canfill && buf->wproc) {
+ p = buf->wproc;
+ canfill = p->ops->out(p, buf);
+ } else if (ABUF_ROK(buf) && canflush) {
+ p = buf->rproc;
+ canflush = p->ops->in(p, buf);
+ } else
+ break; /* can neither read nor write */
+ }
+}
+
+/*
+ * Notify the reader that there will be no more input (producer
+ * disappeared). The buffer is flushed and eof() is called only if all
+ * data is flushed.
+ */
+void
+abuf_eof(struct abuf *buf)
+{
+#ifdef DEBUG
+ if (buf->wproc == NULL) {
+ fprintf(stderr, "abuf_eof: no writer\n");
+ abort();
+ }
+#endif
+ DPRINTFN(2, "abuf_eof: requested by %s\n", buf->wproc->name);
+ buf->wproc = NULL;
+ if (buf->rproc != NULL) {
+ abuf_flush(buf);
+ if (ABUF_ROK(buf)) {
+ /*
+ * Could not flush everything, the reader will
+ * have a chance to delete the abuf later.
+ */
+ DPRINTFN(2, "abuf_eof: %s will drain the buf later\n",
+ buf->rproc->name);
+ return;
+ }
+ DPRINTFN(2, "abuf_eof: signaling %s\n", buf->rproc->name);
+ buf->rproc->ops->eof(buf->rproc, buf);
+ buf->rproc = NULL;
+ }
+ abuf_del(buf);
+}
+
+
+/*
+ * Notify the writer that the buffer has no more consumer,
+ * and that no more data will accepted.
+ */
+void
+abuf_hup(struct abuf *buf)
+{
+#ifdef DEBUG
+ if (buf->rproc == NULL) {
+ fprintf(stderr, "abuf_hup: no reader\n");
+ abort();
+ }
+#endif
+ DPRINTFN(2, "abuf_hup: initiated by %s\n", buf->rproc->name);
+ buf->rproc = NULL;
+ if (buf->wproc != NULL) {
+ if (ABUF_ROK(buf)) {
+ warnx("abuf_hup: %s: lost %u bytes",
+ buf->wproc->name, buf->used);
+ buf->used = 0;
+ }
+ DPRINTFN(2, "abuf_hup: signaling %s\n", buf->wproc->name);
+ buf->wproc->ops->hup(buf->wproc, buf);
+ buf->wproc = NULL;
+ }
+ abuf_del(buf);
+}
diff --git a/usr.bin/aucat/abuf.h b/usr.bin/aucat/abuf.h
new file mode 100644
index 00000000000..b28325b93b4
--- /dev/null
+++ b/usr.bin/aucat/abuf.h
@@ -0,0 +1,86 @@
+/* $OpenBSD: abuf.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef ABUF_H
+#define ABUF_H
+
+#include <sys/queue.h>
+
+struct abuf;
+struct aproc;
+
+struct abuf {
+ /*
+ * misc aproc-specific per-buffer parameters
+ */
+ int mixvol; /* input gain */
+ unsigned mixdone; /* input of mixer */
+ unsigned mixtodo; /* output of mixer */
+ unsigned subdone; /* output if sub */
+ LIST_ENTRY(abuf) ient; /* for mix inputs list */
+ LIST_ENTRY(abuf) oent; /* for sub outputs list */
+
+ /*
+ * fifo parameters
+ */
+ unsigned bpf; /* bytes per frame */
+ unsigned start; /* offset where data starts */
+ unsigned used; /* valid data */
+ unsigned len; /* size of the ring */
+ struct aproc *rproc; /* reader */
+ struct aproc *wproc; /* writer */
+ unsigned char data[]; /* actual data */
+};
+
+/*
+ * the buffer contains at least one frame. This macro should
+ * be used to check if the buffer can be flushed
+ */
+#define ABUF_ROK(b) ((b)->used >= (b)->bpf)
+
+/*
+ * there's room for at least one frame
+ */
+#define ABUF_WOK(b) ((b)->len - (b)->used >= (b)->bpf)
+
+/*
+ * the buffer is empty and has no more writer
+ */
+#define ABUF_EOF(b) (!ABUF_ROK(b) && (b)->wproc == NULL)
+
+/*
+ * similar to !ABUF_WOK, but is used for file i/o, where
+ * operation may not involve an integer number of frames
+ */
+#define ABUF_FULL(b) ((b)->used == (b)->len)
+
+/*
+ * same as !ABUF_ROK, but used for files, where
+ * operations are byte orientated, not frame-oriented
+ */
+#define ABUF_EMPTY(b) ((b)->used == 0)
+
+struct abuf *abuf_new(unsigned, unsigned);
+void abuf_del(struct abuf *);
+unsigned char *abuf_rgetblk(struct abuf *, unsigned *, unsigned);
+unsigned char *abuf_wgetblk(struct abuf *, unsigned *, unsigned);
+void abuf_fill(struct abuf *);
+void abuf_flush(struct abuf *);
+void abuf_eof(struct abuf *);
+void abuf_hup(struct abuf *);
+void abuf_run(struct abuf *);
+
+#endif /* !defined(ABUF_H) */
diff --git a/usr.bin/aucat/aparams.c b/usr.bin/aucat/aparams.c
new file mode 100644
index 00000000000..cbecee24ede
--- /dev/null
+++ b/usr.bin/aucat/aparams.c
@@ -0,0 +1,94 @@
+/* $OpenBSD: aparams.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "aparams.h"
+
+/*
+ * Initialise parameters structure with the defaults natively supported
+ * by the machine.
+ */
+void
+aparams_init(struct aparams *par, unsigned cmin, unsigned cmax, unsigned rate)
+{
+ par->bps = 2; /* 2 bytes per sample */
+ par->bits = 16; /* 16 significant bits per sample */
+ par->sig = 1; /* samples are signed */
+ par->le = NATIVE_LE;
+ par->msb = 1; /* msb justified */
+ par->cmin = cmin;
+ par->cmax = cmax;
+ par->rate = rate;
+}
+
+/*
+ * Print the format/channels/encoding on stderr.
+ */
+void
+aparams_print(struct aparams *par)
+{
+ fprintf(stderr, "%c", par->sig ? 's' : 'u');
+ fprintf(stderr, "%u", par->bits);
+ if (par->bps > 1)
+ fprintf(stderr, "%s", par->le ? "le" : "be");
+ if ((par->bits & (par->bits - 1)) != 0 || par->bits != 8 * par->bps) {
+ fprintf(stderr, "/%u", par->bps);
+ fprintf(stderr, "%s", par->msb ? "msb" : "lsb");
+ }
+ fprintf(stderr, ",%u:%u", par->cmin, par->cmax);
+ fprintf(stderr, ",%uHz", par->rate);
+}
+
+void
+aparams_print2(struct aparams *par1, struct aparams *par2)
+{
+ aparams_print(par1);
+ fprintf(stderr, " -> ");
+ aparams_print(par2);
+}
+
+/*
+ * Return true if both parameters are the same.
+ */
+int
+aparams_eq(struct aparams *par1, struct aparams *par2)
+{
+ if (par1->bps != par2->bps ||
+ par1->bits != par2->bits ||
+ par1->sig != par2->sig ||
+ par1->cmin != par2->cmin ||
+ par1->cmax != par2->cmax ||
+ par1->rate != par2->rate)
+ return 0;
+ if ((par1->bits != 8 * par1->bps) && par1->msb != par2->msb)
+ return 0;
+ if (par1->bps > 1 && par1->le != par2->le)
+ return 0;
+ return 1;
+}
+
+/*
+ * Return the number of bytes per frame with the given parameters.
+ */
+unsigned
+aparams_bpf(struct aparams *par)
+{
+ return (par->cmax - par->cmin + 1) * par->bps;
+}
diff --git a/usr.bin/aucat/aparams.h b/usr.bin/aucat/aparams.h
new file mode 100644
index 00000000000..ce1bfa68490
--- /dev/null
+++ b/usr.bin/aucat/aparams.h
@@ -0,0 +1,66 @@
+/* $OpenBSD: aparams.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef APARAMS_H
+#define APARAMS_H
+
+#include <sys/param.h>
+
+#define CHAN_MAX 16 /* max number of channels */
+#define RATE_MIN 1 /* min sample rate */
+#define RATE_MAX (1 << 30) /* max sample rate */
+#define BITS_MAX 32 /* max bits per sample */
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define NATIVE_LE 1
+#elif BYTE_ORDER == BIG_ENDIAN
+#define NATIVE_LE 0
+#else
+/* not defined */
+#endif
+
+/*
+ * encoding specification
+ */
+struct aparams {
+ unsigned bps; /* bytes per sample */
+ unsigned bits; /* actually used bits */
+ unsigned le; /* 1 if little endian, 0 if big endian */
+ unsigned sig; /* 1 if signed, 0 if unsigned */
+ unsigned msb; /* 1 if msb justified, 0 if lsb justified */
+ unsigned cmin, cmax; /* provided/consumed channels */
+ unsigned rate; /* frames per second */
+};
+
+/*
+ * Samples are numbers in the interval [-1, 1[, note that 1, the upper
+ * boundary is excluded. We represent them in 16-bit signed fixed point
+ * numbers, so that we can do all multiplications and divisions in
+ * 32-bit precision without having to deal with overflows.
+ */
+
+#define ADATA_SHIFT (8 * sizeof(short) - 1)
+#define ADATA_UNIT (1 << ADATA_SHIFT)
+#define ADATA_MAX (ADATA_UNIT - 1)
+#define ADATA_MUL(x,y) (((x) * (y)) >> ADATA_SHIFT)
+
+void aparams_init(struct aparams *, unsigned, unsigned, unsigned);
+void aparams_print(struct aparams *);
+void aparams_print2(struct aparams *, struct aparams *);
+int aparams_eq(struct aparams *, struct aparams *);
+unsigned aparams_bpf(struct aparams *);
+
+#endif /* !defined(APARAMS_H) */
diff --git a/usr.bin/aucat/aproc.c b/usr.bin/aucat/aproc.c
new file mode 100644
index 00000000000..12b6aebb6a1
--- /dev/null
+++ b/usr.bin/aucat/aproc.c
@@ -0,0 +1,850 @@
+/* $OpenBSD: aproc.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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 <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (!(f->state & FILE_RFLOW) && ABUF_FULL(obuf))
+ errx(1, "%s: overrun, unimplemented", f->name);
+
+ 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;
+ f->state |= FILE_RFLOW;
+ 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 (!(f->state & FILE_WFLOW) && ABUF_EMPTY(ibuf))
+ errx(1, "%s: underrun, unimplemented", f->name);
+
+ 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;
+ f->state |= FILE_WFLOW;
+ 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 ocount;
+
+ DPRINTFN(4, "mix_in: used = %u, done = %u, zero = %u\n",
+ ibuf->used, ibuf->mixdone, obuf->mixtodo);
+
+ 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(); 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;
+
+ DPRINTFN(4, "mix_out: used = %u, zero = %u\n",
+ obuf->used, obuf->mixtodo);
+
+ /*
+ * XXX: should handle underruns here, currently if one input is
+ * blocked, then the output block can underrun.
+ */
+ mix_bzero(p);
+ ocount = obuf->mixtodo;
+ for (i = LIST_FIRST(&p->ibuflist); i != LIST_END(); i = inext) {
+ inext = LIST_NEXT(i, ient);
+ 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->mixvol = ADATA_UNIT;
+}
+
+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");
+ 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;
+ int again;
+
+ again = 1;
+ done = ibuf->used;
+ for (i = LIST_FIRST(&p->obuflist); i != LIST_END(); i = inext) {
+ inext = LIST_NEXT(i, oent);
+ if (ABUF_WOK(i)) {
+ 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 done;
+
+ 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();
+ 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;
+}
+
+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");
+ 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;
+}
diff --git a/usr.bin/aucat/aproc.h b/usr.bin/aucat/aproc.h
new file mode 100644
index 00000000000..7973418918a
--- /dev/null
+++ b/usr.bin/aucat/aproc.h
@@ -0,0 +1,145 @@
+/* $OpenBSD: aproc.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef APROC_H
+#define APROC_H
+
+#include <sys/queue.h>
+
+#include "aparams.h"
+
+struct abuf;
+struct aproc;
+struct file;
+
+struct aproc_ops {
+ /*
+ * Name of the ops structure, ie type of the unit.
+ */
+ char *name;
+
+ /*
+ * The state of the given input abuf changed (eg. an input block
+ * is ready for processing). This function must get the block
+ * from the input, process it and remove it from the buffer.
+ *
+ * Processing the block will result in a change of the state of
+ * OTHER buffers that are attached to the aproc (eg. the output
+ * buffer was filled), thus this routine MUST notify ALL aproc
+ * structures that are waiting on it; most of the time this
+ * means just calling abuf_flush() on the output buffer.
+ */
+ int (*in)(struct aproc *, struct abuf *);
+
+ /*
+ * The state of the given output abuf changed (eg. space for a
+ * new output block was made available) so processing can
+ * continue. This function must process more input in order to
+ * fill the output block.
+ *
+ * Producing a block will result in the change of the state of
+ * OTHER buffers that are attached to the aproc, thus this
+ * routine MUST notify ALL aproc structures that are waiting on
+ * it; most of the time this means calling abuf_fill() on the
+ * source buffers.
+ *
+ * Before filling input buffers (using abuf_fill()), this
+ * routine must ALWAYS check for eof condition, and if needed,
+ * handle it appropriately and call abuf_hup() to free the input
+ * buffer.
+ */
+ int (*out)(struct aproc *, struct abuf *);
+
+ /*
+ * The input buffer is empty and we can no more receive data
+ * from it. The buffer will be destroyed as soon as this call
+ * returns so the abuf pointer will stop being valid after this
+ * call returns. There's no need to drain the buffer because the
+ * in() call-back was just called before.
+ *
+ * If this call reads and/or writes data on other buffers,
+ * abuf_flush() and abuf_fill() must be called appropriately.
+ */
+ void (*eof)(struct aproc *, struct abuf *);
+
+ /*
+ * The output buffer can no more accept data (it should be
+ * considered as full). After this function returns, it will be
+ * destroyed and the "abuf" pointer will be no more valid.
+ */
+ void (*hup)(struct aproc *, struct abuf *);
+
+ /*
+ * A new input was connected.
+ */
+ void (*newin)(struct aproc *, struct abuf *);
+
+ /*
+ * A new output was connected
+ */
+ void (*newout)(struct aproc *, struct abuf *);
+};
+
+struct aconv {
+ /*
+ * Format of the buffer. This part is used by conversion code.
+ */
+
+ int bfirst; /* bytes to skip at startup */
+ unsigned rate; /* frames per second */
+ unsigned pos; /* current position in the stream */
+ unsigned nch; /* number of channels: nch = cmax - cmin + 1 */
+ unsigned bps; /* bytes per sample (padding included) */
+ unsigned shift; /* shift to get 32bit MSB-justified int */
+ int sigbit; /* sign bits to XOR to unsigned samples */
+ int bnext; /* bytes to skip to reach the next byte */
+ int snext; /* bytes to skip to reach the next sample */
+ unsigned cmin; /* provided/consumed channels */
+ unsigned bpf; /* bytes per frame: bpf = nch * bps */
+ int ctx[CHAN_MAX]; /* current frame (for resampling) */
+};
+
+/*
+ * The aproc structure represents a simple audio processing unit; they are
+ * interconnected by abuf structures and form a kind of "circuit". The circuit
+ * cannot have loops.
+ */
+struct aproc {
+ char *name; /* for debug purposes */
+ struct aproc_ops *ops; /* call-backs */
+ LIST_HEAD(, abuf) ibuflist; /* list of inputs */
+ LIST_HEAD(, abuf) obuflist; /* list of outputs */
+ union { /* follow type-specific data */
+ struct { /* file/device io */
+ struct file *file; /* file to read/write */
+ } io;
+ struct {
+ struct aconv ist, ost;
+ } conv;
+ } u;
+};
+
+void aproc_del(struct aproc *);
+void aproc_setin(struct aproc *, struct abuf *);
+void aproc_setout(struct aproc *, struct abuf *);
+
+struct aproc *rpipe_new(struct file *);
+struct aproc *wpipe_new(struct file *);
+struct aproc *mix_new(void);
+struct aproc *sub_new(void);
+struct aproc *conv_new(char *, struct aparams *, struct aparams *);
+
+#endif /* !defined(FIFO_H) */
diff --git a/usr.bin/aucat/aucat.1 b/usr.bin/aucat/aucat.1
index cb1fb4cd52b..7e9d7a4d93b 100644
--- a/usr.bin/aucat/aucat.1
+++ b/usr.bin/aucat/aucat.1
@@ -1,92 +1,304 @@
-.\" $OpenBSD: aucat.1,v 1.15 2007/05/31 19:20:07 jmc Exp $
+.\" $OpenBSD: aucat.1,v 1.16 2008/05/23 07:15:46 ratchov Exp $
.\"
-.\" Copyright (c) 1997 Kenneth Stailey. All rights reserved.
+.\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org>
.\"
-.\" This code is derived from software contributed to Berkeley by
-.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\" 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.
.\"
-.\" 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.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
-.\" 4. Neither the name of the University nor the names of its contributors
-.\" may be used to endorse or promote products derived from this software
-.\" without specific prior written permission.
+.\" 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.
.\"
-.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
-.\"
-.\"
-.Dd $Mdocdate: May 31 2007 $
+.Dd $Mdocdate: May 23 2008 $
.Dt AUCAT 1
.Os
.Sh NAME
.Nm aucat
-.Nd concatenate and play audio files
+.Nd manipulate linear audio streams
.Sh SYNOPSIS
.Nm aucat
+.Bk -words
+.Op Fl u
+.Op Fl C Ar min : Ns Ar max
+.Op Fl c Ar min : Ns Ar max
+.Op Fl d Ar level
+.Op Fl E Ar enc
+.Op Fl e Ar enc
.Op Fl f Ar device
-.Ar
+.Op Fl H Ar fmt
+.Op Fl h Ar fmt
+.Op Fl i Ar file
+.Op Fl o Ar file
+.Op Fl R Ar rate
+.Op Fl r Ar rate
+.Ek
.Sh DESCRIPTION
The
.Nm
-utility reads files sequentially, writing them to the specified device.
-By default,
+utility can record one input stream
+and store it on multiple destination files,
+doing the necessary conversions on the fly.
+Simultaneously, it can play, convert, and mix multiple input files.
.Nm
-plays audio through the
-.Pa /dev/audio
-device.
-The
-.Ar file
-operands are processed in command line order.
-.Pp
-If a Sun .au header is
-detected it is skipped over and not copied to
-.Pa /dev/audio .
-If a Microsoft .wav header (RIFF) is detected it is interpreted to
-select the right audio encoding for playback and the data chunk of the
-file is copied to
-.Pa /dev/audio ,
-given that the audio driver directly supports the encoding.
-Otherwise, the entire file is copied to
-.Pa /dev/audio .
-.Pp
+also has a legacy mode that works like previous versions of
+.Nm ,
+which does not convert on the fly and supports playback of .au files.
The options are as follows:
-.Bl -tag -width "-f deviceXX"
+.Bl -tag -width "-m mmmmmmmm "
+.It Fl C Ar min : Ns Ar max
+Range of channel numbers on the output stream specified by
+.Fl o
+options that follow (the default is 0:1, i.e. stereo).
+.It Fl c Ar min : Ns Ar max
+Range of channel numbers in the input stream specified by
+.Fl i
+options that follow (the default is 0:1, i.e. stereo).
+.It Fl d Ar level
+The debug level:
+may be a value between 0 and 4.
+.It Fl E Ar enc
+Encoding of the output stream specified by the
+.Fl o
+options that follow (the default is signed, 16-bit, native byte order).
+.It Fl e Ar enc
+Encoding of the input stream specified by
+.Fl i
+options that follow (the default is signed, 16-bit, native byte order).
+.It Fl H Ar fmt
+File format of the output stream specified by the
+.Fl o
+options that follow (the default is auto).
+.It Fl h Ar fmt
+File format of the input stream specified by
+.Fl i
+options that follow (the default is auto).
.It Fl f Ar device
-Specifies an alternate audio device.
+.Xr audio 4
+device to use for playing and/or recording (the default is
+.Pa /dev/audio ) .
+.It Fl i Ar file
+Add this file to the list of files to play.
+The format of the file is specified by the last
+.Fl e ,
+.Fl c ,
+and
+.Fl r
+options.
+If the option argument is
+.Sq -
+then standard input will be used.
+.It Fl o Ar file
+Add this file to the list of files in which to store recorded samples.
+The format of the file is specified by the last
+.Fl E ,
+.Fl C ,
+and
+.Fl R
+options.
+If the option argument is
+.Sq -
+then standard output will be used.
+.It Fl R Ar rate
+Sample rate in Hertz of the output stream specified by the
+.Fl o
+options that follow (the default is 44100Hz).
+.It Fl r Ar rate
+Sample rate in Hertz of the input stream specified by
+.Fl i
+options that follow (the default is 44100Hz).
+.It Fl u
+Don't try to automatically determine the optimal parameters for the
+audio device;
+instead use the parameters specified by the last
+.Fl E ,
+.Fl C ,
+.Fl R ,
+.Fl e ,
+.Fl c ,
+and
+.Fl r
+options.
+As for
+.Fl i
+and
+.Fl o
+options, if the
+.Fl f
+option is used, then parameters must be specified before it.
+.El
+.Pp
+The following file formats are supported:
+.Pp
+.Bl -tag -width s32lexxx -offset -indent
+.It raw
+Headerless file.
+It's recommended to use this format since it has no limitations.
+.It wav
+Microsoft WAVE file format.
+There are limitations inherent to the file format itself:
+not all encodings are supported,
+file sizes are limited to 2GB,
+the file must support the
+.Xr lseek 2
+operation (eg. pipes do not support it).
+.It auto
+Try to guess, depending on the file name.
.El
.Pp
-.Ex -std aucat
+The following encodings are supported:
+.Pp
+.Bl -tag -width s32lexxx -offset -indent -compact
+.It s8
+signed 8-bit
+.It u8
+unsigned 8-bit
+.It s16le
+signed 16-bit, little endian
+.It u16le
+unsigned 16-bit, little endian
+.It s16be
+signed 16-bit, big endian
+.It u16be
+unsigned 16-bit, big endian
+.It s24le
+signed 24-bit, stored in 4 bytes, little endian
+.It u24le
+unsigned 24-bit, stored in 4 bytes, little endian
+.It s24be
+signed 24-bit, stored in 4 bytes, big endian
+.It u24be
+unsigned 24-bit, stored in 4 bytes, big endian
+.It s32le
+signed 32-bit, little endian
+.It u32le
+unsigned 32-bit, little endian
+.It s32be
+signed 32-bit, big endian
+.It u32be
+unsigned 32-bit, big endian
+.It s24le3
+signed 24-bit, packed in 3 bytes, little endian
+.It u24le3
+unsigned 24-bit, packed in 3 bytes, big endian
+.It s24be3
+signed 24-bit, packed in 3 bytes, little endian
+.It u24be3
+unsigned 24-bit, packed in 3 bytes, big endian
+.It s20le3
+signed 20-bit, packed in 3 bytes, little endian
+.It u20le3
+unsigned 20-bit, packed in 3 bytes, big endian
+.It s20be3
+signed 20-bit, packed in 3 bytes, little endian
+.It u20be3
+unsigned 20-bit, packed in 3 bytes, big endian
+.It s18le3
+signed 18-bit, packed in 3 bytes, little endian
+.It u18le3
+unsigned 18-bit, packed in 3 bytes, big endian
+.It s18be3
+signed 18-bit, packed in 3 bytes, little endian
+.It u18be3
+unsigned 18-bit, packed in 3 bytes, big endian
+.El
+.Sh LEGACY MODE
+If neither
+.Fl i
+nor
+.Fl o
+options are specified,
+.Nm
+will run in legacy mode, in which case
+.Nm
+does not convert sample formats or sampling rates.
+In legacy mode, all options except
+.Fl f Ar device
+are ignored and all other arguments are assumed to be names of files.
+In legacy mode
+.Nm
+reads files sequentially, and writes them to the specified device.
+If a Sun .au header is detected it is skipped over and not copied to
+the audio device.
+.Nm
+will attempt to play data from Sun .au files as monaural 8-bit ulaw
+samples with a sampling frequency of 8000 Hz.
+However,
+.Nm
+will not fail if the audio device cannot be configured for these
+parameters.
+If a Microsoft .wav header (RIFF) is detected it is interpreted
+to select the right audio encoding for playback and the data chunk of the
+file is copied to the audio device.
+If the device does not support the encoding,
+.Nm
+will exit with an error.
.Sh ENVIRONMENT
.Bl -tag -width AUDIODEVICE
.It Ev AUDIODEVICE
The audio device to use.
.El
+.Sh EXAMPLES
+The following command will record a stereo s16le stream at
+44100Hz from the default device.
+If necesseary, the stream will be converted and/or resampled
+to match parameters supported by the device:
+.Bd -literal -offset indent
+$ aucat -o file.raw
+.Ed
+.Pp
+The following command will play a stereo s16le stream at
+44100Hz on the default device, doing any necessary conversions:
+.Bd -literal -offset indent
+$ aucat -i file.raw
+.Ed
+.Pp
+The following will mix and play two stereo streams,
+the first at 48kHz and the second at 44.1kHz:
+.Bd -literal -offset indent
+$ aucat -r 48000 -i file1.raw -r 44100 -i file2.raw
+.Ed
+.Pp
+The following will record channels 2 and 3 into one stereo file and
+channels 6 and 7 into another stereo file using a 96kHz sampling rate for
+both:
+.Bd -literal -offset indent
+$ aucat -R 96000 -C 2:3 -o file1.raw -C 6:7 -o file2.raw
+.Ed
+.Pp
+The following will play two s18le mono files, one on each channel:
+.Bd -literal -offset indent
+$ aucat -e s18le -c 0:0 -i f1.raw -c 1:1 -i f2.raw
+.Ed
+.Pp
+The following will mix and play two files and record a third one in
+full-duplex:
+.Bd -literal -offset indent
+$ aucat -i drums.raw -i bass.raw -o guitar.raw
+.Ed
.Sh SEE ALSO
.Xr audioctl 1 ,
-.Xr cdio 1 ,
.Xr mixerctl 1 ,
.Xr audio 4
-.Sh HISTORY
-An
+.Sh BUGS
+The
.Nm
-utility appeared in
-.Ox 2.0 .
+utility assumes non-blocking I/O for input and output streams.
+It will not work reliably on files that may block
+(ordinary files block, pipes don't).
+.Pp
+Resampling is low quality; down-sampling especially should be avoided
+when recording.
+.Pp
+CPU usage is the same for all conversions.
+It should be smaller for simpler ones.
+.Pp
+Buffer overruns and underruns are not handled.
+.Pp
+Processing is done using 16-bit arithmetic,
+thus samples with more than 16 bits are rounded.
+16 bits (ie 97dB dynamic) are largely enough for most applications though.
diff --git a/usr.bin/aucat/aucat.c b/usr.bin/aucat/aucat.c
index 4951cc2a1a1..26a4310908c 100644
--- a/usr.bin/aucat/aucat.c
+++ b/usr.bin/aucat/aucat.c
@@ -1,271 +1,669 @@
-/* $OpenBSD: aucat.c,v 1.14 2008/04/13 22:39:29 jakemsr Exp $ */
+/* $OpenBSD: aucat.c,v 1.15 2008/05/23 07:15:46 ratchov Exp $ */
/*
- * Copyright (c) 1997 Kenneth Stailey. All rights reserved.
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
- * 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.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by Kenneth Stailey.
- * 4. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
+ * 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.
*
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
+ * 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.
+ */
+/*
+ * TODO:
+ *
+ * (not yet)add a silent/quiet/verbose/whatever flag, but be sure
+ * that by default the user is notified when one of the following
+ * (cpu consuming) aproc is created: mix, sub, conv
+ *
+ * (hard) use parsable encoding names instead of the lookup
+ * table. For instance, [s|u]bits[le|be][/bytes{msb|lsb}], example
+ * s8, s16le, s24le/3msb. This would give names that correspond to
+ * what use most linux-centric apps, but for which we have an
+ * algorithm to convert the name to a aparams structure.
+ *
+ * (easy) uses {chmin-chmax} instead of chmin:chmax notation for
+ * channels specification to match the notation used in rmix.
+ *
+ * (easy) use comma-separated parameters syntax, example:
+ * s24le/3msb,{3-6},48000 so we don't have to use three -e, -r, -c
+ * flags, but only one -p flag that specify one or more parameters.
+ *
+ * (hard) dont create mix (sub) if there's only one input (output)
+ *
+ * (hard) if all inputs are over, the mixer terminates and closes
+ * the write end of the device. It should continue writing zeros
+ * until the recording is over (or be able to stop write end of
+ * the device)
+ *
+ * (hard) implement -n flag (no device) to connect all inputs to
+ * the outputs.
+ *
+ * (hard) ignore input files that are not audible (because channels
+ * they provide are not used on the output). Similarly ignore
+ * outputs that are zero filled (because channels they consume are
+ * not provided).
+ *
+ * (easy) do we need -d flag ?
*/
+#include <sys/param.h>
#include <sys/types.h>
-#include <sys/audioio.h>
-#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <err.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <err.h>
+#include <varargs.h>
-#define _PATH_AUDIO "/dev/audio"
+#include "conf.h"
+#include "aparams.h"
+#include "aproc.h"
+#include "abuf.h"
+#include "file.h"
+#include "dev.h"
/*
- * aucat: concatenate and play Sun 8-bit .au files or 8/16-bit
- * uncompressed WAVE RIFF files
+ * Format for file headers.
*/
+#define HDR_AUTO 0 /* guess by looking at the file name */
+#define HDR_RAW 1 /* no headers, ie openbsd native ;-) */
+#define HDR_WAV 2 /* microsoft riff wave */
-int playfile(int, audio_info_t *);
-int readwaveheader(int, audio_info_t *);
-__dead void usage(void);
-
-int afd;
+int debug_level = 0;
+volatile int quit_flag = 0;
+/*
+ * List of allowed encodings and their names.
+ */
+struct enc {
+ char *name;
+ struct aparams par;
+} enc_list[] = {
+ /* name bps, bits, le, sign, msb, unused */
+ { "s8", { 1, 8, 1, 1, 1, 0, 0, 0 } },
+ { "u8", { 1, 8, 1, 0, 1, 0, 0, 0 } },
+ { "s16le", { 2, 16, 1, 1, 1, 0, 0, 0 } },
+ { "u16le", { 2, 16, 1, 0, 1, 0, 0, 0 } },
+ { "s16be", { 2, 16, 0, 1, 1, 0, 0, 0 } },
+ { "u16be", { 2, 16, 0, 0, 1, 0, 0, 0 } },
+ { "s24le", { 4, 24, 1, 1, 1, 0, 0, 0 } },
+ { "u24le", { 4, 24, 1, 0, 1, 0, 0, 0 } },
+ { "s24be", { 4, 24, 0, 1, 1, 0, 0, 0 } },
+ { "u24be", { 4, 24, 0, 0, 1, 0, 0, 0 } },
+ { "s32le", { 4, 32, 1, 1, 1, 0, 0, 0 } },
+ { "u32le", { 4, 32, 1, 0, 1, 0, 0, 0 } },
+ { "s32be", { 4, 32, 0, 1, 1, 0, 0, 0 } },
+ { "u32be", { 4, 32, 0, 0, 1, 0, 0, 0 } },
+ { "s24le3", { 3, 24, 1, 1, 1, 0, 0, 0 } },
+ { "u24le3", { 3, 24, 1, 0, 1, 0, 0, 0 } },
+ { "s24be3", { 3, 24, 0, 1, 1, 0, 0, 0 } },
+ { "u24be3", { 3, 24, 0, 0, 1, 0, 0, 0 } },
+ { "s20le3", { 3, 20, 1, 1, 1, 0, 0, 0 } },
+ { "u20le3", { 3, 20, 1, 0, 1, 0, 0, 0 } },
+ { "s20be3", { 3, 20, 0, 1, 1, 0, 0, 0 } },
+ { "u20be3", { 3, 20, 0, 0, 1, 0, 0, 0 } },
+ { "s18le3", { 3, 18, 1, 1, 1, 0, 0, 0 } },
+ { "u18le3", { 3, 18, 1, 0, 1, 0, 0, 0 } },
+ { "s18be3", { 3, 18, 0, 1, 1, 0, 0, 0 } },
+ { "u18be3", { 3, 18, 0, 0, 1, 0, 0, 0 } },
+ { NULL, { 0, 0, 0, 0, 0, 0, 0, 0 } }
+};
/*
- * function playfile: given a file which is positioned at the beginning
- * of what is assumed to be an .au data stream copy it out to the audio
- * device. Return 0 on success, -1 on failure.
+ * Search an encoding in the above table. On success fill encoding
+ * part of "par" and return 1, otherwise return 0.
*/
-int
-playfile(int fd, audio_info_t *audioinfo)
+unsigned
+enc_lookup(char *name, struct aparams *par)
+{
+ struct enc *e;
+
+ for (e = enc_list; e->name != NULL; e++) {
+ if (strcmp(e->name, name) == 0) {
+ par->bps = e->par.bps;
+ par->bits = e->par.bits;
+ par->sig = e->par.sig;
+ par->le = e->par.le;
+ par->msb = e->par.msb;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+usage(void)
{
- ssize_t rd;
- char buf[5120];
+ extern char *__progname;
- /*
- * If we don't wait here, the AUDIO_SETINFO ioctl interrupts
- * the playback of the previous file.
- */
- if (ioctl(afd, AUDIO_DRAIN, NULL) == -1)
- warn("AUDIO_DRAIN");
+ fprintf(stderr,
+ "usage: %s [-u] [-d level] [-f device]\n"
+ "\t[-C min:max] [-E enc] [-R rate] [-H fmt] [-o file]\n"
+ "\t[-c min:max] [-e enc] [-r rate] [-h fmt] [-i file]\n"
+ "\t-C: range of channel numbers stored in the output file\n"
+ "\t-c: range of channel numbers provided by the input file\n"
+ "\t-d: debug level, between 0 and 4\n"
+ "\t-E: output file encoding (eg. s8, u8, s16le, u32be...)\n"
+ "\t-e: input file encoding (eg. s8, u8, s16le, u32be...)\n"
+ "\t-f: use this device instead of /dev/audio\n"
+ "\t-H: output file header format (eg. auto, wav, raw)\n"
+ "\t-h: input file header format (eg. auto, wav, raw)\n"
+ "\t-i: read samples from file (\"-\" means stdin)\n"
+ "\t-R: sample rate in Hz of the output file\n"
+ "\t-r: sample rate in Hz of the input file\n"
+ "\t-o: write samples to file (\"-\" means stdout)\n"
+ "\t-u: use last -C, -c, -E, -e, -R, -r options "
+ "for device parameters\n",
+ __progname);
+}
- if (ioctl(afd, AUDIO_SETINFO, audioinfo) == -1) {
- warn("AUDIO_SETINFO");
- return -1;
- }
+void
+opt_ch(struct aparams *par)
+{
+ if (sscanf(optarg, "%u:%u", &par->cmin, &par->cmax) != 2 ||
+ par->cmin > CHAN_MAX || par->cmax > CHAN_MAX ||
+ par->cmin > par->cmax)
+ err(1, "%s: bad channel range", optarg);
+}
- while ((rd = read(fd, buf, sizeof(buf))) > 0)
- if (write(afd, buf, rd) != rd)
- warn("write");
- if (rd == -1)
- warn("read");
+void
+opt_rate(struct aparams *par)
+{
+ if (sscanf(optarg, "%u", &par->rate) != 1 ||
+ par->rate < RATE_MIN || par->rate > RATE_MAX)
+ err(1, "%s: bad sample rate", optarg);
+}
- return (0);
+void
+opt_enc(struct aparams *par)
+{
+ if (!enc_lookup(optarg, par))
+ err(1, "%s: bad encoding", optarg);
}
-/*
- * function readwaveheader: given a file which is positioned at four
- * bytes into a RIFF file header, read the rest of the header, check
- * to see if it is a simple WAV file that we can handle, seek to the
- * beginning of the audio data, and set the playback parameters in
- * the audio_info_t structure. Return 0 on success, -1 on failure.
- */
int
-readwaveheader(int fd, audio_info_t *audioinfo)
+opt_hdr(void)
{
- /*
- * The simplest form of a RIFF file...
- */
- struct {
- /* u_int32_t riff_chunkid; -- this is read before in main()! */
- u_int32_t riff_chunksize;
- u_int32_t riff_format;
-
- u_int32_t fmt_subchunkid;
- u_int32_t fmt_subchunksize;
-
- u_int16_t fmt_format; /* 1 = PCM uncompressed */
- u_int16_t fmt_channels; /* 1 = mono, 2 = stereo */
- u_int32_t fmt_samplespersec; /* 8000, 22050, 44100 etc. */
- u_int32_t fmt_byterate; /* total bytes per second */
- u_int16_t fmt_blockalign; /* channels * bitspersample/8 */
- u_int16_t fmt_bitspersample; /* 8 = 8 bits, 16 = 16 bits etc. */
- } header;
- u_int datatag;
- char c;
+ if (strcmp("auto", optarg) == 0)
+ return HDR_AUTO;
+ if (strcmp("raw", optarg) == 0)
+ return HDR_RAW;
+ if (strcmp("wav", optarg) == 0)
+ return HDR_WAV;
+ err(1, "%s: bad header specification", optarg);
+}
- /*
- * Is it an uncompressed wave file?
- */
- if (read(fd, &header, sizeof(header)) != sizeof(header)) {
- warn("read");
- return -1;
- }
- if (strncmp((char *) &header.riff_format, "WAVE", 4) ||
- letoh16(header.fmt_format) != 1 ||
- strncmp((char *) &header.fmt_subchunkid, "fmt ", 4) ||
- (letoh16(header.fmt_bitspersample) != 8 &&
- letoh16(header.fmt_bitspersample) != 16))
- return -1;
+/*
+ * Arguments of -i and -o opations are stored in a list.
+ */
+struct farg {
+ SLIST_ENTRY(farg) entry;
+ struct aparams par; /* last requested format */
+ unsigned vol; /* last requested volume */
+ char *name; /* optarg pointer (no need to copy it */
+ int hdr; /* header format */
+ int fd; /* file descriptor for I/O */
+ struct aproc *proc; /* rpipe_xxx our wpipe_xxx */
+ struct abuf *buf;
+};
+
+SLIST_HEAD(farglist, farg);
- /*
- * Seek to the data chunk.
- */
- for (datatag = 0; datatag < 4; ) {
- if (read(fd, &c, 1) != 1) {
- warn("read");
- return -1;
+/*
+ * Add a farg entry to the given list, corresponding
+ * to the given file name.
+ */
+void
+opt_file(struct farglist *list,
+ struct aparams *par, unsigned vol, int hdr, char *optarg)
+{
+ struct farg *fa;
+ size_t namelen;
+
+ fa = malloc(sizeof(struct farg));
+ if (fa == NULL)
+ err(1, "%s", optarg);
+
+ if (hdr == HDR_AUTO) {
+ namelen = strlen(optarg);
+ if (namelen >= 4 &&
+ strcasecmp(optarg + namelen - 4, ".wav") == 0) {
+ fa->hdr = HDR_WAV;
+ DPRINTF("%s: assuming wav file format\n", optarg);
+ } else {
+ fa->hdr = HDR_RAW;
+ DPRINTF("%s: assuming headerless file\n", optarg);
}
+ } else
+ fa->hdr = hdr;
+ fa->par = *par;
+ fa->vol = vol;
+ fa->name = optarg;
+ fa->proc = NULL;
+ SLIST_INSERT_HEAD(list, fa, entry);
+}
- switch(datatag) {
- case 0:
- if (c == 'd')
- ++datatag;
- break;
- case 1:
- if (c == 'a')
- ++datatag;
- break;
- case 2:
- if (c == 't')
- ++datatag;
- break;
- case 3:
- if (c == 'a')
- ++datatag;
- break;
- default:
- datatag = 0;
- break;
- }
+/*
+ * Open an input file and setup converter if necessary.
+ */
+void
+newinput(struct farg *fa, struct aparams *npar, unsigned nfr)
+{
+ int fd;
+ struct file *f;
+ struct aproc *p, *c;
+ struct abuf *buf, *nbuf;
+
+ if (strcmp(fa->name, "-") == 0) {
+ fd = STDIN_FILENO;
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ warn("stdin");
+ fa->name = "stdin";
+ } else {
+ fd = open(fa->name, O_RDONLY | O_NONBLOCK, 0666);
+ if (fd < 0)
+ err(1, "%s", fa->name);
}
- if (datatag != 4) {
- warnx("no data chunk found in wave file");
- return -1;
+ f = file_new(fd, fa->name);
+ if (fa->hdr == HDR_WAV) {
+ if (!wav_readhdr(fd, &fa->par, &f->rbytes))
+ exit(1);
}
+ buf = abuf_new(nfr, aparams_bpf(&fa->par));
+ p = rpipe_new(f);
+ aproc_setout(p, buf);
+ if (!aparams_eq(&fa->par, npar)) {
+ fprintf(stderr, "%s: ", fa->name);
+ aparams_print2(&fa->par, npar);
+ fprintf(stderr, "\n");
+ nbuf = abuf_new(nfr, aparams_bpf(npar));
+ c = conv_new(fa->name, &fa->par, npar);
+ aproc_setin(c, buf);
+ aproc_setout(c, nbuf);
+ fa->buf = nbuf;
+ } else
+ fa->buf = buf;
+ fa->proc = p;
+ fa->fd = fd;
+}
- /*
- * Ignore the size of the data chunk.
- */
- if (lseek(fd, 4, SEEK_CUR) == -1) {
- warn("lseek");
- return -1;
+/*
+ * Open an output file and setup converter if necessary.
+ */
+void
+newoutput(struct farg *fa, struct aparams *npar, unsigned nfr)
+{
+ int fd;
+ struct file *f;
+ struct aproc *p, *c;
+ struct abuf *buf, *nbuf;
+
+ if (strcmp(fa->name, "-") == 0) {
+ fd = STDOUT_FILENO;
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ warn("stdout");
+ fa->name = "stdout";
+ } else {
+ fd = open(fa->name,
+ O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666);
+ if (fd < 0)
+ err(1, "%s", fa->name);
+ }
+ f = file_new(fd, fa->name);
+ if (fa->hdr == HDR_WAV) {
+ f->wbytes = WAV_DATAMAX;
+ if (!wav_writehdr(fd, &fa->par))
+ exit(1);
}
+ buf = abuf_new(nfr, aparams_bpf(&fa->par));
+ p = wpipe_new(f);
+ aproc_setin(p, buf);
+ if (!aparams_eq(&fa->par, npar)) {
+ fprintf(stderr, "%s: ", fa->name);
+ aparams_print2(npar, &fa->par);
+ fprintf(stderr, "\n");
+ c = conv_new(fa->name, npar, &fa->par);
+ nbuf = abuf_new(nfr, aparams_bpf(npar));
+ aproc_setin(c, nbuf);
+ aproc_setout(c, buf);
+ fa->buf = nbuf;
+ } else
+ fa->buf = buf;
+ fa->proc = p;
+ fa->fd = fd;
+}
- audioinfo->play.sample_rate = letoh32(header.fmt_samplespersec);
- audioinfo->play.channels = letoh16(header.fmt_channels);
- audioinfo->play.precision = letoh16(header.fmt_bitspersample);
- audioinfo->play.encoding = audioinfo->play.precision == 8 ?
- AUDIO_ENCODING_ULINEAR : AUDIO_ENCODING_SLINEAR_LE;
- return 0;
+void
+sighdl(int s)
+{
+ if (quit_flag)
+ _exit(1);
+ quit_flag = 1;
}
int
-main(int argc, char *argv[])
+main(int argc, char **argv)
{
- int fd, ch;
- u_int32_t data;
- char magic[4];
- char *dev;
- audio_info_t ai;
- audio_info_t ai_defaults;
-
- dev = getenv("AUDIODEVICE");
- if (dev == NULL)
- dev = _PATH_AUDIO;
-
- while ((ch = getopt(argc, argv, "f:")) != -1) {
- switch (ch) {
+ struct sigaction sa;
+ int c, u_flag, ohdr, ihdr;
+ struct farg *fa;
+ struct farglist ifiles, ofiles;
+ struct aparams ipar, opar, dipar, dopar, cipar, copar;
+ unsigned ivol, ovol;
+ unsigned dinfr, donfr, cinfr, confr;
+ char *devpath;
+ unsigned n;
+ struct aproc *rec, *play, *mix, *sub, *conv;
+ struct file *dev, *f;
+ struct abuf *buf, *cbuf;
+ int fd;
+
+ aparams_init(&ipar, 0, 1, 44100);
+ aparams_init(&opar, 0, 1, 44100);
+
+ u_flag = 0;
+ devpath = NULL;
+ SLIST_INIT(&ifiles);
+ SLIST_INIT(&ofiles);
+ ihdr = ohdr = HDR_AUTO;
+ ivol = ovol = MIDI_TO_ADATA(127);
+
+ while ((c = getopt(argc, argv, "d:c:C:e:E:r:R:h:H:i:o:f:u")) != -1) {
+ switch (c) {
+ case 'd':
+ if (sscanf(optarg, "%u", &debug_level) != 1 ||
+ debug_level > 4)
+ err(1, "%s: not an integer in the 0..4 range",
+ optarg);
+ break;
+ case 'h':
+ ihdr = opt_hdr();
+ break;
+ case 'H':
+ ohdr = opt_hdr();
+ break;
+ case 'c':
+ opt_ch(&ipar);
+ break;
+ case 'C':
+ opt_ch(&opar);
+ break;
+ case 'e':
+ opt_enc(&ipar);
+ break;
+ case 'E':
+ opt_enc(&opar);
+ break;
+ case 'r':
+ opt_rate(&ipar);
+ break;
+ case 'R':
+ opt_rate(&opar);
+ break;
+ case 'i':
+ opt_file(&ifiles, &ipar, 127, ihdr, optarg);
+ break;
+ case 'o':
+ opt_file(&ofiles, &opar, 127, ohdr, optarg);
+ break;
case 'f':
- dev = optarg;
+ if (devpath)
+ err(1, "only one -f allowed");
+ devpath = optarg;
+ dipar = ipar;
+ dopar = opar;
+ break;
+ case 'u':
+ u_flag = 1;
break;
default:
usage();
- /* NOTREACHED */
+ exit(1);
}
}
argc -= optind;
argv += optind;
- if (argc == 0)
- usage();
+ if (!devpath) {
+ devpath = getenv("AUDIODEVICE");
+ if (devpath == NULL)
+ devpath = DEFAULT_DEVICE;
+ dipar = ipar;
+ dopar = opar;
+ }
- if ((afd = open(dev, O_WRONLY)) < 0)
- err(1, "can't open %s", dev);
-
- if (ioctl(afd, AUDIO_GETINFO, &ai_defaults) == -1)
- err(1, "AUDIO_GETINFO");
-
- while (argc) {
- if ((fd = open(*argv, O_RDONLY)) < 0)
- err(1, "cannot open %s", *argv);
-
- AUDIO_INITINFO(&ai);
-
- ai.play.sample_rate = ai_defaults.play.sample_rate;
- ai.play.channels = ai_defaults.play.channels;
- ai.play.encoding = ai_defaults.play.encoding;
- ai.play.precision = ai_defaults.play.precision;
-
- if (read(fd, magic, sizeof(magic)) != sizeof(magic) ||
- strncmp(magic, ".snd", 4)) {
- /*
- * not an .au file, bad header.
- * Check if it could be a .wav file and set
- * the playback parameters in ai.
- */
- if (strncmp(magic, "RIFF", 4) ||
- readwaveheader(fd, &ai)) {
- /*
- * Assume raw audio data since that's
- * what /dev/audio generates by default.
- */
- if (lseek(fd, 0, SEEK_SET) == -1)
- warn("lseek");
- }
- } else {
- if (read(fd, &data, sizeof(data)) == sizeof(data)) {
- data = ntohl(data);
- if (lseek(fd, (off_t)data, SEEK_SET) == -1)
- warn("lseek");
+ if (SLIST_EMPTY(&ifiles) && SLIST_EMPTY(&ofiles) && argc > 0) {
+ /*
+ * Legacy mode: if no -i or -o options are provided, and
+ * there are arguments then assume the arguments are files
+ * to play.
+ */
+ for (c = 0; c < argc; c++)
+ if (legacy_play(devpath, argv[c]) != 0) {
+ fprintf(stderr, "%s: could not play\n",
+ argv[c]);
+ exit(1);
}
- }
+ exit(0);
+ } else if (argc > 0) {
+ usage();
+ exit(1);
+ }
- if (playfile(fd, &ai) < 0)
- exit(1);
- (void) close(fd);
- argc--;
- argv++;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sighdl;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(1, "sigaction");
+
+ file_start();
+ play = rec = mix = sub = NULL;
+
+ aparams_init(&cipar, CHAN_MAX, 0, RATE_MIN);
+ aparams_init(&copar, CHAN_MAX, 0, RATE_MAX);
+
+ /*
+ * Iterate over all inputs and outputs and find the maximum
+ * sample rate and channel number.
+ */
+ SLIST_FOREACH(fa, &ifiles, entry) {
+ if (cipar.cmin > fa->par.cmin)
+ cipar.cmin = fa->par.cmin;
+ if (cipar.cmax < fa->par.cmax)
+ cipar.cmax = fa->par.cmax;
+ if (cipar.rate < fa->par.rate)
+ cipar.rate = fa->par.rate;
+ }
+ SLIST_FOREACH(fa, &ofiles, entry) {
+ if (copar.cmin > fa->par.cmin)
+ copar.cmin = fa->par.cmin;
+ if (copar.cmax < fa->par.cmax)
+ copar.cmax = fa->par.cmax;
+ if (copar.rate > fa->par.rate)
+ copar.rate = fa->par.rate;
}
- exit(0);
-}
-__dead void
-usage(void)
-{
- extern char *__progname;
+ /*
+ * Open the device and increase the maximum sample rate.
+ * channel number to include those used by the device
+ */
+ if (!u_flag) {
+ dipar = copar;
+ dopar = cipar;
+ }
+ fd = dev_init(devpath,
+ !SLIST_EMPTY(&ofiles) ? &dipar : NULL,
+ !SLIST_EMPTY(&ifiles) ? &dopar : NULL, &dinfr, &donfr);
+ if (fd < 0)
+ exit(1);
+ if (!SLIST_EMPTY(&ofiles)) {
+ fprintf(stderr, "%s: recording ", devpath);
+ aparams_print(&dipar);
+ fprintf(stderr, "\n");
+ if (copar.cmin > dipar.cmin)
+ copar.cmin = dipar.cmin;
+ if (copar.cmax < dipar.cmax)
+ copar.cmax = dipar.cmax;
+ if (copar.rate > dipar.rate)
+ copar.rate = dipar.rate;
+ dinfr *= DEFAULT_NBLK;
+ DPRINTF("%s: using %ums rec buffer\n", devpath,
+ 1000 * dinfr / dipar.rate);
+ }
+ if (!SLIST_EMPTY(&ifiles)) {
+ fprintf(stderr, "%s: playing ", devpath);
+ aparams_print(&dopar);
+ fprintf(stderr, "\n");
+ if (cipar.cmin > dopar.cmin)
+ cipar.cmin = dopar.cmin;
+ if (cipar.cmax < dopar.cmax)
+ cipar.cmax = dopar.cmax;
+ if (cipar.rate < dopar.rate)
+ cipar.rate = dopar.rate;
+ donfr *= DEFAULT_NBLK;
+ DPRINTF("%s: using %ums play buffer\n", devpath,
+ 1000 * donfr / dopar.rate);
+ }
+
+ /*
+ * Create buffers for the device.
+ */
+ dev = file_new(fd, devpath);
+ if (!SLIST_EMPTY(&ofiles)) {
+ rec = rpipe_new(dev);
+ sub = sub_new();
+ }
+ if (!SLIST_EMPTY(&ifiles)) {
+ play = wpipe_new(dev);
+ mix = mix_new();
+ }
+
+ /*
+ * Calculate sizes of buffers using "common" parameters, to
+ * have roughly the same duration as device buffers.
+ */
+ cinfr = donfr * cipar.rate / dopar.rate;
+ confr = dinfr * copar.rate / dipar.rate;
+
+ /*
+ * Create buffers for all input and output pipes.
+ */
+ SLIST_FOREACH(fa, &ifiles, entry) {
+ newinput(fa, &cipar, cinfr);
+ if (mix)
+ aproc_setin(mix, fa->buf);
+ fprintf(stderr, "%s: reading ", fa->name);
+ aparams_print(&fa->par);
+ fprintf(stderr, "\n");
+ }
+ SLIST_FOREACH(fa, &ofiles, entry) {
+ newoutput(fa, &copar, confr);
+ if (sub)
+ aproc_setout(sub, fa->buf);
+ fprintf(stderr, "%s: writing ", fa->name);
+ aparams_print(&fa->par);
+ fprintf(stderr, "\n");
+ }
- fprintf(stderr, "usage: %s [-f device] file ...\n", __progname);
- exit(1);
+ /*
+ * Connect the multiplexer to the device input.
+ */
+ if (sub) {
+ buf = abuf_new(dinfr, aparams_bpf(&dipar));
+ aproc_setout(rec, buf);
+ if (!aparams_eq(&copar, &dipar)) {
+ fprintf(stderr, "%s: ", devpath);
+ aparams_print2(&dipar, &copar);
+ fprintf(stderr, "\n");
+ conv = conv_new("subconv", &dipar, &copar);
+ cbuf = abuf_new(confr, aparams_bpf(&copar));
+ aproc_setin(conv, buf);
+ aproc_setout(conv, cbuf);
+ aproc_setin(sub, cbuf);
+ } else
+ aproc_setin(sub, buf);
+ }
+
+ /*
+ * Normalize input levels and connect the mixer to the device
+ * output.
+ */
+ if (mix) {
+ n = 0;
+ SLIST_FOREACH(fa, &ifiles, entry)
+ n++;
+ SLIST_FOREACH(fa, &ifiles, entry)
+ fa->buf->mixvol /= n;
+ buf = abuf_new(donfr, aparams_bpf(&dopar));
+ aproc_setin(play, buf);
+ if (!aparams_eq(&cipar, &dopar)) {
+ fprintf(stderr, "%s: ", devpath);
+ aparams_print2(&cipar, &dopar);
+ fprintf(stderr, "\n");
+ conv = conv_new("mixconv", &cipar, &dopar);
+ cbuf = abuf_new(cinfr, aparams_bpf(&cipar));
+ aproc_setout(conv, buf);
+ aproc_setin(conv, cbuf);
+ aproc_setout(mix, cbuf);
+ } else
+ aproc_setout(mix, buf);
+ }
+
+ /*
+ * start audio
+ */
+ if (play != NULL) {
+ fprintf(stderr, "filling buffers...\n");
+ while (!quit_flag) {
+ /* no more devices to poll */
+ if (!file_poll())
+ break;
+ /* device is blocked */
+ if (dev->events & POLLOUT)
+ break;
+ /* eof */
+ if (dev->state & FILE_EOF)
+ break;
+ }
+ }
+ fprintf(stderr, "starting device...\n");
+ dev_start(dev->fd);
+ dev->state &= ~(FILE_RFLOW | FILE_WFLOW);
+ while (!quit_flag) {
+ if (!file_poll())
+ break;
+ }
+
+ fprintf(stderr, "draining buffers...\n");
+
+ /*
+ * generate EOF on all files that do input, so
+ * once buffers are drained, everything will be cleaned
+ */
+ LIST_FOREACH(f, &file_list, entry) {
+ if ((f->events) & POLLIN || (f->state & FILE_ROK))
+ file_eof(f);
+ }
+ for (;;) {
+ if (!file_poll())
+ break;
+ }
+ SLIST_FOREACH(fa, &ofiles, entry) {
+ if (fa->hdr == HDR_WAV)
+ wav_writehdr(fa->fd, &fa->par);
+ close(fa->fd);
+ DPRINTF("%s: closed\n", fa->name);
+ }
+ dev_stop(dev->fd);
+ file_stop();
+ return 0;
}
diff --git a/usr.bin/aucat/conf.h b/usr.bin/aucat/conf.h
new file mode 100644
index 00000000000..ac5779d815e
--- /dev/null
+++ b/usr.bin/aucat/conf.h
@@ -0,0 +1,54 @@
+/* $OpenBSD: conf.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef ACONF_H
+#define ACONF_H
+
+/*
+ * debug trace levels:
+ *
+ * 0 - traces are off
+ * 1 - init, free, stuff that's done only once
+ * 2 - rare real-time events: eof / hup, etc...
+ * 3 - poll(), block / unblock state changes
+ * 4 - read()/write()
+ */
+#ifdef DEBUG
+
+/* defined in main.c */
+void debug_printf(int, char *, char *, ...);
+extern int debug_level;
+
+#define DPRINTF(...) DPRINTFN(1, __VA_ARGS__)
+#define DPRINTFN(n, ...) \
+ do { \
+ if (debug_level >= (n)) \
+ fprintf(stderr, __VA_ARGS__); \
+ } while(0)
+#else
+#define DPRINTF(...) do {} while(0)
+#define DPRINTFN(n, ...) do {} while(0)
+#endif
+
+
+#define MIDI_MAXCTL 127
+#define MIDI_TO_ADATA(m) ((ADATA_UNIT * (m) + 64) / 127)
+
+#define DEFAULT_NFR 0x400 /* buf size in frames */
+#define DEFAULT_NBLK 0x8 /* blocks per buffer */
+#define DEFAULT_DEVICE "/dev/audio" /* defaul device */
+
+#endif /* !defined(CONF_H) */
diff --git a/usr.bin/aucat/dev.h b/usr.bin/aucat/dev.h
new file mode 100644
index 00000000000..940e2b52bb4
--- /dev/null
+++ b/usr.bin/aucat/dev.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: dev.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef DEV_H
+#define DEV_H
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <string.h>
+
+int dev_init(char *, struct aparams *, struct aparams *,
+ unsigned *, unsigned *);
+void dev_done(int);
+void dev_start(int);
+void dev_stop(int);
+
+int sun_infotopar(struct audio_prinfo *, struct aparams *);
+void sun_partoinfo(struct audio_prinfo *, struct aparams *);
+
+
+#endif /* !define(DEV_H) */
diff --git a/usr.bin/aucat/dev_sun.c b/usr.bin/aucat/dev_sun.c
new file mode 100644
index 00000000000..47abc6f06af
--- /dev/null
+++ b/usr.bin/aucat/dev_sun.c
@@ -0,0 +1,264 @@
+/* $OpenBSD: dev_sun.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "aparams.h"
+#include "dev.h"
+
+/*
+ * convert sun device parameters to struct params
+ */
+int
+sun_infotopar(struct audio_prinfo *ai, struct aparams *par)
+{
+ par->rate = ai->sample_rate;
+ par->bps = ai->precision / 8;
+ par->bits = ai->precision;
+ par->cmax = par->cmin + ai->channels - 1;
+ if (par->cmax >= CHAN_MAX) {
+ warnx("%u:%u: channel range out of bounds",
+ par->cmin, par->cmax);
+ return 0;
+ }
+ par->msb = 1;
+ switch (ai->encoding) {
+ case AUDIO_ENCODING_SLINEAR_LE:
+ par->le = 1;
+ par->sig = 1;
+ break;
+ case AUDIO_ENCODING_SLINEAR_BE:
+ par->le = 0;
+ par->sig = 1;
+ break;
+ case AUDIO_ENCODING_ULINEAR_LE:
+ par->le = 1;
+ par->sig = 0;
+ break;
+ case AUDIO_ENCODING_ULINEAR_BE:
+ par->le = 0;
+ par->sig = 0;
+ break;
+ case AUDIO_ENCODING_SLINEAR:
+ par->le = NATIVE_LE;
+ par->sig = 1;
+ break;
+ case AUDIO_ENCODING_ULINEAR:
+ par->le = NATIVE_LE;
+ par->sig = 0;
+ break;
+ default:
+ warnx("only linear encodings are supported for audio devices");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Convert struct params to sun device parameters.
+ */
+void
+sun_partoinfo(struct audio_prinfo *ai, struct aparams *par)
+{
+ ai->sample_rate = par->rate;
+ ai->precision = par->bps * 8;
+ ai->channels = par->cmax - par->cmin + 1;
+ if (par->le && par->sig) {
+ ai->encoding = AUDIO_ENCODING_SLINEAR_LE;
+ } else if (!par->le && par->sig) {
+ ai->encoding = AUDIO_ENCODING_SLINEAR_BE;
+ } else if (par->le && !par->sig) {
+ ai->encoding = AUDIO_ENCODING_ULINEAR_LE;
+ } else {
+ ai->encoding = AUDIO_ENCODING_ULINEAR_BE;
+ }
+}
+
+/*
+ * Open the device and pause it, so later play and record
+ * can be started simultaneously.
+ *
+ * int "infr" and "onfd" we return the input and the output
+ * block sizes respectively.
+ */
+int
+dev_init(char *path, struct aparams *ipar, struct aparams *opar,
+ unsigned *infr, unsigned *onfr)
+{
+ int fd;
+ int fullduplex;
+ struct audio_info aui;
+
+ if (!ipar && !opar)
+ errx(1, "%s: must at least play or record", path);
+
+ fd = open(path, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ warn("%s", path);
+ return -1;
+ }
+
+ /*
+ * If both play and record are requested then
+ * set full duplex mode.
+ */
+ if (ipar && opar) {
+ fullduplex = 1;
+ if (ioctl(fd, AUDIO_SETFD, &fullduplex) < 0) {
+ warn("%s: can't set full-duplex", path);
+ close(fd);
+ return -1;
+ }
+ }
+
+ /*
+ * Set parameters and pause the device. When paused, the write(2)
+ * syscall will queue samples but the the kernel will not start playing
+ * them. Setting the 'mode' and pausing the device must be done in a
+ * single ioctl() call, otherwise the sun driver will start the device
+ * and fill the record buffers.
+ */
+ AUDIO_INITINFO(&aui);
+ aui.mode = 0;
+ aui.lowat = UINT_MAX / 2; /* will set lowat = hiwat - 1 */
+ if (opar) {
+ sun_partoinfo(&aui.play, opar);
+ aui.play.pause = 1;
+ aui.mode |= AUMODE_PLAY;
+ }
+ if (ipar) {
+ sun_partoinfo(&aui.record, ipar);
+ aui.record.pause = 1;
+ aui.mode |= AUMODE_RECORD;
+ }
+ if (ioctl(fd, AUDIO_SETINFO, &aui) < 0) {
+ fprintf(stderr, "%s: can't set audio params to ", path);
+ if (ipar)
+ aparams_print(ipar);
+ if (opar) {
+ if (ipar)
+ fprintf(stderr, " and ");
+ aparams_print(opar);
+ }
+ fprintf(stderr, ": %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (ioctl(fd, AUDIO_GETINFO, &aui) < 0) {
+ warn("dev_init: getinfo");
+ close(fd);
+ return -1;
+ }
+ if (opar) {
+ /*
+ * We _must_ ensure that write() will accept at most
+ * one block when it unblocks. Here is our definition
+ * of the block size: the minimum amount of data
+ * write() accepts immediately when it unblocks. If
+ * write() accepts more that 1 block, then this means
+ * that we failed to provide the first block early
+ * enough thus underrun happened.
+ *
+ * If we fail to ensure that lowat = hiwat - 1, then
+ * we will trigger the underrun detection mechanism.
+ * Recording doesn't use the water mark non-sense.
+ */
+ if (aui.lowat != aui.hiwat - 1) {
+ warnx("%s: failed to disable lowat: hiwat = %u, "
+ "lowat = %u", path, aui.hiwat, aui.lowat);
+ close(fd);
+ return -1;
+ }
+ if (!sun_infotopar(&aui.play, opar)) {
+ close(fd);
+ return -1;
+ }
+ *onfr = aui.play.block_size /
+ (aui.play.channels * aui.play.precision / 8);
+ }
+ if (ipar) {
+ if (!sun_infotopar(&aui.record, ipar)) {
+ close(fd);
+ return -1;
+ }
+ *infr = aui.record.block_size /
+ (aui.record.channels * aui.record.precision / 8);
+ }
+ return fd;
+}
+
+void
+dev_done(int fd)
+{
+ close(fd);
+}
+
+/*
+ * Start play/record.
+ */
+void
+dev_start(int fd)
+{
+ audio_info_t aui;
+
+ /*
+ * Just unpause the device. The kernel will start playback and record
+ * simultaneously. There must be samples already written.
+ */
+ AUDIO_INITINFO(&aui);
+ aui.play.pause = aui.record.pause = 0;
+ if (ioctl(fd, AUDIO_SETINFO, &aui) < 0)
+ err(1, "dev_start: setinfo");
+
+ DPRINTF("dev_start: play/rec started\n");
+}
+
+/*
+ * Stop play/record and clear kernel buffers so that dev_start() can be called
+ * again.
+ */
+void
+dev_stop(int fd)
+{
+ audio_info_t aui;
+ unsigned mode;
+
+ if (ioctl(fd, AUDIO_DRAIN) < 0)
+ err(1, "dev_stop: drain");
+
+ /*
+ * The only way to clear kernel buffers and to pause the device
+ * simultaneously is to set the mode again (to the same value).
+ */
+ if (ioctl(fd, AUDIO_GETINFO, &aui) < 0)
+ err(1, "dev_stop: getinfo");
+
+ mode = aui.mode;
+ AUDIO_INITINFO(&aui);
+ aui.mode = mode;
+ aui.play.pause = aui.record.pause = 1;
+ if (ioctl(fd, AUDIO_SETINFO, &aui) < 0)
+ err(1, "dev_stop: setinfo");
+
+ DPRINTF("dev_stop: play/rec stopped\n");
+}
diff --git a/usr.bin/aucat/file.c b/usr.bin/aucat/file.c
new file mode 100644
index 00000000000..bb1d724ea70
--- /dev/null
+++ b/usr.bin/aucat/file.c
@@ -0,0 +1,302 @@
+/* $OpenBSD: file.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+/*
+ * non-blocking file i/o module: each file can be read or written (or
+ * both). To achieve non-blocking io, we simply use the poll() syscall
+ * in an event loop. If a read() or write() syscall return EAGAIN
+ * (operation will block), then the file is marked as "for polling", else
+ * the file is not polled again.
+ *
+ */
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "file.h"
+#include "aproc.h"
+#include "abuf.h"
+
+#define MAXFDS 100
+
+struct filelist file_list;
+
+struct file *
+file_new(int fd, char *name)
+{
+ unsigned i;
+ struct file *f;
+
+ i = 0;
+ LIST_FOREACH(f, &file_list, entry)
+ i++;
+ if (i >= MAXFDS)
+ err(1, "%s: too many polled files", name);
+
+ f = malloc(sizeof(struct file));
+ if (f == NULL)
+ err(1, "%s", name);
+
+ f->fd = fd;
+ f->events = 0;
+ f->rbytes = -1;
+ f->wbytes = -1;
+ f->name = name;
+ f->state = 0;
+ f->rproc = NULL;
+ f->wproc = NULL;
+ LIST_INSERT_HEAD(&file_list, f, entry);
+ DPRINTF("file_new: %s\n", f->name);
+ return f;
+}
+
+void
+file_del(struct file *f)
+{
+ DPRINTF("file_del: %s|%x\n", f->name, f->state);
+}
+
+int
+file_poll(void)
+{
+#ifdef DEBUG
+ int ndead;
+#endif
+ nfds_t nfds;
+ struct pollfd pfds[MAXFDS];
+ struct pollfd *pfd;
+ struct file *f, *fnext;
+
+ nfds = 0;
+#ifdef DEBUG
+ ndead = 0;
+#endif
+ LIST_FOREACH(f, &file_list, entry) {
+ if (!f->events) {
+#ifdef DEBUG
+ if (f->state & (FILE_EOF | FILE_HUP))
+ ndead++;
+#endif
+ f->pfd = NULL;
+ continue;
+ }
+ pfd = &pfds[nfds++];
+ f->pfd = pfd;
+ pfd->fd = f->fd;
+ pfd->events = f->events;
+ }
+
+#ifdef DEBUG
+ if (debug_level >= 4) {
+ fprintf(stderr, "file_poll:");
+ LIST_FOREACH(f, &file_list, entry) {
+ fprintf(stderr, " %s(%x)", f->name, f->events);
+ }
+ fprintf(stderr, "\n");
+ }
+ if (nfds == 0 && ndead == 0 && !LIST_EMPTY(&file_list)) {
+ fprintf(stderr, "file_poll: deadlock\n");
+ abort();
+ }
+#endif
+ if (LIST_EMPTY(&file_list)) {
+ DPRINTF("file_poll: nothing to do...\n");
+ return 0;
+ }
+ if (nfds) {
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno != EINTR)
+ err(1, "file_poll: poll failed");
+ }
+ }
+ LIST_FOREACH(f, &file_list, entry) {
+ pfd = f->pfd;
+ if (pfd == NULL)
+ continue;
+ if ((f->events & POLLIN) && (pfd->revents & POLLIN)) {
+ f->events &= ~POLLIN;
+ f->state |= FILE_ROK;
+ DPRINTFN(3, "file_poll: %s rok\n", f->name);
+ while (f->state & FILE_ROK) {
+ if (!f->rproc->ops->in(f->rproc, NULL))
+ break;
+ }
+ }
+ if ((f->events & POLLOUT) && (pfd->revents & POLLOUT)) {
+ f->events &= ~POLLOUT;
+ f->state |= FILE_WOK;
+ DPRINTFN(3, "file_poll: %s wok\n", f->name);
+ while (f->state & FILE_WOK) {
+ if (!f->wproc->ops->out(f->wproc, NULL))
+ break;
+ }
+ }
+ }
+ LIST_FOREACH(f, &file_list, entry) {
+ if (f->state & FILE_EOF) {
+ DPRINTFN(2, "file_poll: %s: eof\n", f->name);
+ f->rproc->ops->eof(f->rproc, NULL);
+ f->state &= ~FILE_EOF;
+ }
+ if (f->state & FILE_HUP) {
+ DPRINTFN(2, "file_poll: %s hup\n", f->name);
+ f->wproc->ops->hup(f->wproc, NULL);
+ f->state &= ~FILE_HUP;
+ }
+ }
+ for (f = LIST_FIRST(&file_list); f != NULL; f = fnext) {
+ fnext = LIST_NEXT(f, entry);
+ if (f->rproc == NULL && f->wproc == NULL) {
+ LIST_REMOVE(f, entry);
+ DPRINTF("file_poll: %s: deleted\n", f->name);
+ free(f);
+ }
+ }
+ if (LIST_EMPTY(&file_list)) {
+ DPRINTFN(2, "file_poll: terminated\n");
+ return 0;
+ }
+ return 1;
+}
+
+void
+file_start(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ (void)sigaddset(&set, SIGPIPE);
+ if (sigprocmask(SIG_BLOCK, &set, NULL))
+ err(1, "sigprocmask");
+
+ LIST_INIT(&file_list);
+}
+
+void
+file_stop(void)
+{
+ struct file *f;
+
+ if (!LIST_EMPTY(&file_list)) {
+ fprintf(stderr, "file_stop:");
+ LIST_FOREACH(f, &file_list, entry) {
+ fprintf(stderr, " %s(%x)", f->name, f->events);
+ }
+ fprintf(stderr, "\nfile_stop: list not empty\n");
+ exit(1);
+ }
+}
+
+unsigned
+file_read(struct file *file, unsigned char *data, unsigned count)
+{
+ int n;
+
+ if (file->rbytes >= 0 && count > file->rbytes) {
+ count = file->rbytes; /* file->rbytes fits in count */
+ if (count == 0) {
+ DPRINTFN(2, "file_read: %s: complete\n", file->name);
+ file->state &= ~FILE_ROK;
+ file->state |= FILE_EOF;
+ return 0;
+ }
+ }
+ while ((n = read(file->fd, data, count)) < 0) {
+ if (errno == EINTR)
+ continue;
+ file->state &= ~FILE_ROK;
+ if (errno == EAGAIN) {
+ DPRINTFN(3, "file_read: %s: blocking...\n",
+ file->name);
+ file->events |= POLLIN;
+ } else {
+ warn("%s", file->name);
+ file->state |= FILE_EOF;
+ }
+ return 0;
+ }
+ if (n == 0) {
+ DPRINTFN(2, "file_read: %s: eof\n", file->name);
+ file->state &= ~FILE_ROK;
+ file->state |= FILE_EOF;
+ return 0;
+ }
+ if (file->rbytes >= 0)
+ file->rbytes -= n;
+ DPRINTFN(4, "file_read: %s: got %d bytes\n", file->name, n);
+ return n;
+}
+
+
+unsigned
+file_write(struct file *file, unsigned char *data, unsigned count)
+{
+ int n;
+
+ if (file->wbytes >= 0 && count > file->wbytes) {
+ count = file->wbytes; /* file->wbytes fits in count */
+ if (count == 0) {
+ DPRINTFN(2, "file_write: %s: complete\n", file->name);
+ file->state &= ~FILE_WOK;
+ file->state |= FILE_HUP;
+ return 0;
+ }
+ }
+ while ((n = write(file->fd, data, count)) < 0) {
+ if (errno == EINTR)
+ continue;
+ file->state &= ~FILE_WOK;
+ if (errno == EAGAIN) {
+ DPRINTFN(3, "file_write: %s: blocking...\n",
+ file->name);
+ file->events |= POLLOUT;
+ } else {
+ warn("%s", file->name);
+ file->state |= FILE_HUP;
+ }
+ return 0;
+ }
+ if (file->wbytes >= 0)
+ file->wbytes -= n;
+ DPRINTFN(4, "file_write: %s: wrote %d bytes\n", file->name, n);
+ return n;
+}
+
+void
+file_eof(struct file *f)
+{
+ DPRINTFN(2, "file_eof: %s: scheduled for eof\n", f->name);
+ f->events &= ~POLLIN;
+ f->state &= ~FILE_ROK;
+ f->state |= FILE_EOF;
+}
+
+void
+file_hup(struct file *f)
+{
+ DPRINTFN(2, "file_hup: %s: scheduled for hup\n", f->name);
+ f->events &= ~POLLOUT;
+ f->state &= ~FILE_WOK;
+ f->state |= FILE_HUP;
+}
diff --git a/usr.bin/aucat/file.h b/usr.bin/aucat/file.h
new file mode 100644
index 00000000000..f81a729a7ad
--- /dev/null
+++ b/usr.bin/aucat/file.h
@@ -0,0 +1,75 @@
+/* $OpenBSD: file.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+#ifndef FILE_H
+#define FILE_H
+
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <poll.h>
+
+struct aparams;
+struct aproc;
+struct abuf;
+
+struct file {
+ int fd; /* file descriptor */
+ struct pollfd *pfd; /* arg to poll(2) syscall */
+ off_t rbytes; /* bytes to read, -1 if no limit */
+ off_t wbytes; /* bytes to write, -1 if no limit */
+ int events; /* events for poll(2) */
+#define FILE_ROK 0x1 /* file readable */
+#define FILE_WOK 0x2 /* file writable */
+#define FILE_EOF 0x4 /* eof on the read end */
+#define FILE_HUP 0x8 /* eof on the write end */
+#define FILE_RFLOW 0x10 /* has flow control on read() */
+#define FILE_WFLOW 0x20 /* has flow control on write() */
+ int state; /* one of above */
+ char *name; /* for debug purposes */
+ struct aproc *rproc, *wproc; /* reader and/or writer */
+ LIST_ENTRY(file) entry;
+};
+
+LIST_HEAD(filelist,file);
+
+extern struct filelist file_list;
+
+void file_start(void);
+void file_stop(void);
+struct file *file_new(int, char *);
+void file_del(struct file *);
+void file_attach(struct file *, struct aproc *, struct aproc *);
+unsigned file_read(struct file *, unsigned char *, unsigned);
+unsigned file_write(struct file *, unsigned char *, unsigned);
+int file_poll(void);
+void file_eof(struct file *);
+void file_hup(struct file *);
+
+/*
+ * max data of a .wav file. The total file size must be smaller than
+ * 2^31, and we also have to leave some space for the headers (around 40
+ * bytes)
+ */
+#define WAV_DATAMAX (0x7fff0000)
+
+int wav_readhdr(int, struct aparams *, off_t *);
+int wav_writehdr(int, struct aparams *);
+
+/* legacy */
+int legacy_play(char *, char *);
+
+#endif /* !defined(FILE_H) */
diff --git a/usr.bin/aucat/headers.c b/usr.bin/aucat/headers.c
new file mode 100644
index 00000000000..f2960ec5a44
--- /dev/null
+++ b/usr.bin/aucat/headers.c
@@ -0,0 +1,230 @@
+/* $OpenBSD: headers.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "aparams.h"
+
+struct wavriff {
+ char magic[4];
+ uint32_t size;
+ char type[4];
+} __packed;
+
+struct wavchunk {
+ char id[4];
+ uint32_t size;
+} __packed;
+
+struct wavfmt {
+ uint16_t fmt;
+ uint16_t nch;
+ uint32_t rate;
+ uint32_t byterate;
+ uint16_t blkalign;
+ uint16_t bits;
+} __packed;
+
+char wav_id_riff[4] = { 'R', 'I', 'F', 'F' };
+char wav_id_wave[4] = { 'W', 'A', 'V', 'E' };
+char wav_id_data[4] = { 'd', 'a', 't', 'a' };
+char wav_id_fmt[4] = { 'f', 'm', 't', ' ' };
+
+int
+wav_readfmt(int fd, unsigned csize, struct aparams *par)
+{
+ struct wavfmt fmt;
+ unsigned nch, cmax, rate, bits, enc;
+
+ if (csize < sizeof(fmt)) {
+ warnx("bogus format chunk");
+ return 0;
+ }
+ if (read(fd, &fmt, sizeof(fmt)) != sizeof(fmt)) {
+ warn("riff_read: chunk");
+ return 0;
+ }
+ enc = letoh16(fmt.fmt);
+ if (enc != 1) {
+ warnx("%u: only \"pcm\" encoding supported", enc);
+ return 0;
+ }
+ nch = letoh16(fmt.nch);
+ if (nch == 0) {
+ warnx("zero number of channels");
+ return 0;
+ }
+ cmax = par->cmin + nch - 1;
+ if (cmax >= CHAN_MAX) {
+ warnx("%u:%u: bad range", par->cmin, cmax);
+ return 0;
+ }
+ rate = letoh32(fmt.rate);
+ if (rate < RATE_MIN || rate >= RATE_MAX) {
+ warnx("%u: bad sample rate", rate);
+ return 0;
+ }
+ bits = letoh16(fmt.bits);
+ if (bits == 0 || bits >= 32) {
+ warnx("%u: bad number of bits", bits);
+ return 0;
+ }
+ par->bps = (bits + 7) / 8;
+ par->bits = bits;
+ par->le = 1;
+ par->sig = (bits <= 8) ? 0 : 1; /* ask microsoft why... */
+ par->msb = 1;
+ par->cmax = cmax;
+ par->rate = rate;
+ if (debug_level > 0) {
+ fprintf(stderr, "wav_readfmt: using ");
+ aparams_print(par);
+ fprintf(stderr, "\n");
+ }
+ return 1;
+}
+
+int
+wav_readhdr(int fd, struct aparams *par, off_t *datasz)
+{
+ struct wavriff riff;
+ struct wavchunk chunk;
+ unsigned csize, rsize, pos = 0;
+ int fmt_done = 0;
+
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ warn("lseek: 0");
+ return 0;
+ }
+ if (read(fd, &riff, sizeof(riff)) != sizeof(riff)) {
+ warn("riff_read: header");
+ return 0;
+ }
+ if (memcmp(&riff.magic, &wav_id_riff, 4) != 0 ||
+ memcmp(&riff.type, &wav_id_wave, 4)) {
+ warnx("not a wave file");
+ return 0;
+ }
+ rsize = letoh32(riff.size);
+ while (pos + sizeof(struct wavchunk) <= rsize) {
+ if (read(fd, &chunk, sizeof(chunk)) != sizeof(chunk)) {
+ warn("riff_read: chunk");
+ return 0;
+ }
+ csize = letoh32(chunk.size);
+ if (memcmp(chunk.id, wav_id_fmt, 4) == 0) {
+ if (!wav_readfmt(fd, csize, par))
+ return 0;
+ fmt_done = 1;
+ } else if (memcmp(chunk.id, wav_id_data, 4) == 0) {
+ *datasz = csize;
+ break;
+ } else {
+ DPRINTF("unknown chunk: <%.4s>\n", chunk.id);
+ }
+
+ /*
+ * next chunk
+ */
+ pos += sizeof(struct wavchunk) + csize;
+ if (lseek(fd, sizeof(riff) + pos, SEEK_SET) < 0) {
+ warn("lseek");
+ return 0;
+ }
+ }
+ if (!fmt_done) {
+ warnx("missing format chunk");
+ return 0;
+ }
+ return 1;
+}
+
+int
+wav_writehdr(int fd, struct aparams *par)
+{
+ off_t datasz;
+ unsigned nch = par->cmax - par->cmin + 1;
+ struct {
+ struct wavriff riff;
+ struct wavchunk fmt_hdr;
+ struct wavfmt fmt;
+ struct wavchunk data_hdr;
+ } hdr;
+
+ datasz = lseek(fd, 0, SEEK_CUR);
+ if (datasz < 0) {
+ warn("wav_writehdr: lseek(end)");
+ return 0;
+ }
+ if (datasz >= sizeof(hdr))
+ datasz -= sizeof(hdr);
+ else
+ datasz = 0;
+
+ /*
+ * check that encoding is supported by .wav file format
+ */
+ if (par->bits > 8 && !par->le) {
+ warnx("samples must be little endian");
+ return 0;
+ }
+ if (8 * par->bps - par->bits >= 8) {
+ warnx("padding must be less than 8 bits");
+ return 0;
+ }
+ if ((par->bits <= 8 && par->sig) || (par->bits > 8 && !par->sig)) {
+ warnx("samples with more (less) than 8 bits must be signed (unsigned)");
+ return 0;
+ }
+ if (8 * par->bps != par->bits && !par->msb) {
+ warnx("samples must be MSB justified");
+ return 0;
+ }
+
+ memcpy(hdr.riff.magic, wav_id_riff, 4);
+ memcpy(hdr.riff.type, wav_id_wave, 4);
+ hdr.riff.size = htole32(datasz + sizeof(hdr) - sizeof(hdr.riff));
+
+ memcpy(hdr.fmt_hdr.id, wav_id_fmt, 4);
+ hdr.fmt_hdr.size = htole32(sizeof(hdr.fmt));
+ hdr.fmt.fmt = htole16(1);
+ hdr.fmt.nch = htole16(nch);
+ hdr.fmt.rate = htole32(par->rate);
+ hdr.fmt.byterate = htole32(par->rate * par->bps * nch);
+ hdr.fmt.bits = htole16(par->bits);
+ hdr.fmt.blkalign = 0;
+
+ memcpy(hdr.data_hdr.id, wav_id_data, 4);
+ hdr.data_hdr.size = htole32(datasz);
+
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ warn("wav_writehdr: lseek");
+ return 0;
+ }
+ if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ warn("wav_writehdr: write");
+ return 0;
+ }
+ return 1;
+}
diff --git a/usr.bin/aucat/legacy.c b/usr.bin/aucat/legacy.c
new file mode 100644
index 00000000000..56f4be7e854
--- /dev/null
+++ b/usr.bin/aucat/legacy.c
@@ -0,0 +1,155 @@
+/* $OpenBSD: legacy.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/*
+ * Copyright (c) 1997 Kenneth Stailey. 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Kenneth Stailey.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "file.h"
+#include "aparams.h"
+#include "dev.h"
+
+
+/* headerless data files. played at /dev/audio's defaults.
+ */
+#define FMT_RAW 0
+
+/* Sun/NeXT .au files. header is skipped and /dev/audio is configured
+ * for monaural 8-bit ulaw @ 8kHz, the de facto format for .au files,
+ * as well as the historical default configuration for /dev/audio.
+ */
+#define FMT_AU 1
+
+/* RIFF WAV files. header is parsed for format details which are
+ * applied to /dev/audio.
+ */
+#define FMT_WAV 2
+
+
+int
+legacy_play(char *dev, char *aufile)
+{
+ struct audio_prinfo ai;
+ struct audio_info info;
+ struct aparams par;
+ ssize_t rd;
+ off_t datasz;
+ char buf[5120];
+ int afd, fd, fmt = FMT_RAW;
+ u_int32_t pos = 0;
+ char magic[4];
+
+ if ((fd = open(aufile, O_RDONLY)) < 0) {
+ warn("cannot open %s", aufile);
+ return(1);
+ }
+
+ if (read(fd, magic, sizeof(magic)) != sizeof(magic)) {
+ /* read() error, or the file is smaller than sizeof(magic).
+ * treat as a raw file, like previous versions of aucat.
+ */
+ } else if (!strncmp(magic, ".snd", 4)) {
+ fmt = FMT_AU;
+ if (read(fd, &pos, sizeof(pos)) == sizeof(pos))
+ pos = ntohl(pos);
+ } else if (!strncmp(magic, "RIFF", 4) &&
+ wav_readhdr(fd, &par, &datasz)) {
+ fmt = FMT_WAV;
+ }
+
+ /* seek to start of audio data. wav_readhdr already took care
+ * of this for FMT_WAV.
+ */
+ if (fmt == FMT_RAW || fmt == FMT_AU)
+ if (lseek(fd, (off_t)pos, SEEK_SET) == -1)
+ warn("lseek");
+
+ if ((afd = open(dev, O_WRONLY)) < 0) {
+ warn("can't open %s", dev);
+ return(1);
+ }
+
+ AUDIO_INITINFO(&info);
+ ai = info.play;
+
+ switch(fmt) {
+ case FMT_WAV:
+ sun_partoinfo(&ai, &par);
+ break;
+ case FMT_AU:
+ ai.encoding = AUDIO_ENCODING_ULAW;
+ ai.precision = 8;
+ ai.sample_rate = 8000;
+ ai.channels = 1;
+ break;
+ case FMT_RAW:
+ default:
+ break;
+ }
+
+ info.play = ai;
+ if (ioctl(afd, AUDIO_SETINFO, &info) < 0) {
+ warn("%s", dev);
+ /* only WAV could fail in previous aucat versions (unless
+ * the parameters returned by AUDIO_GETINFO would fail,
+ * which is unlikely)
+ */
+ if (fmt == FMT_WAV)
+ return(1);
+ }
+
+ /* parameters may be silently modified. see audio(9)'s
+ * description of set_params. for compatability with previous
+ * aucat versions, continue running if something doesn't match.
+ */
+ (void) ioctl(afd, AUDIO_GETINFO, &info);
+ if (info.play.encoding != ai.encoding ||
+ info.play.precision != ai.precision ||
+ info.play.channels != ai.channels ||
+ /* devices may return a very close rate, such as 44099 when
+ * 44100 was requested. the difference is inaudible. allow
+ * 2% deviation as an example of how to cope.
+ */
+ (info.play.sample_rate > ai.sample_rate * 1.02 ||
+ info.play.sample_rate < ai.sample_rate * 0.98)) {
+ warnx("format not supported by %s", dev);
+ }
+
+ while ((rd = read(fd, buf, sizeof(buf))) > 0)
+ if (write(afd, buf, rd) != rd)
+ warn("write");
+ if (rd == -1)
+ warn("read");
+
+ (void) close(afd);
+ (void) close(fd);
+
+ return(0);
+}