diff options
Diffstat (limited to 'usr.bin/aucat')
-rw-r--r-- | usr.bin/aucat/Makefile | 5 | ||||
-rw-r--r-- | usr.bin/aucat/abuf.c | 270 | ||||
-rw-r--r-- | usr.bin/aucat/abuf.h | 86 | ||||
-rw-r--r-- | usr.bin/aucat/aparams.c | 94 | ||||
-rw-r--r-- | usr.bin/aucat/aparams.h | 66 | ||||
-rw-r--r-- | usr.bin/aucat/aproc.c | 850 | ||||
-rw-r--r-- | usr.bin/aucat/aproc.h | 145 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.1 | 338 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.c | 824 | ||||
-rw-r--r-- | usr.bin/aucat/conf.h | 54 | ||||
-rw-r--r-- | usr.bin/aucat/dev.h | 35 | ||||
-rw-r--r-- | usr.bin/aucat/dev_sun.c | 264 | ||||
-rw-r--r-- | usr.bin/aucat/file.c | 302 | ||||
-rw-r--r-- | usr.bin/aucat/file.h | 75 | ||||
-rw-r--r-- | usr.bin/aucat/headers.c | 230 | ||||
-rw-r--r-- | usr.bin/aucat/legacy.c | 155 |
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); +} |