From 7061d687e5cfe865da42e09d645e3389fb80b225 Mon Sep 17 00:00:00 2001 From: Alexandre Ratchov Date: Thu, 14 Aug 2008 09:58:56 +0000 Subject: move all device related stuff from aucat.c to a new dev.c file. The new dev_xxx() routines expose a "high level" self-contained interface to the device. At initialization, the device is opened and two chains of aproc structures are created: * a playback chain that exposes a (initially) empty mix aproc to which the rest of the code can attach new streams to be played * record chain that exposes a (initially) empty sub aproc to which the rest of the code can attach new stream to to record The rest of the code, has just to use dev_attach() routine to attach streams. While we're at it, add a ``devops'' structure containing pointers to the device-specific routines. This will allow later to add support for other type of device than the Sun API. Also, write the .wav headers in file_del(), so put all header related data in the file strucuture. This allows to close() the file, as soon as wpipe_xxx() aproc terminates. This will be useful for the server, because it will need to close() descripts of closed connections immediately. add mix_pushzero() routine to fill the mixer with silence. It will be used to avoid the mixer to underrun when there are no input streams. Since we always have at least one input stream there's no behaviour change. ok jakemsr --- usr.bin/aucat/Makefile | 6 +- usr.bin/aucat/aproc.c | 29 ++- usr.bin/aucat/aproc.h | 5 +- usr.bin/aucat/aucat.c | 374 ++++++++----------------------------- usr.bin/aucat/dev.c | 485 ++++++++++++++++++++++++++++++++++++++++++++++++ usr.bin/aucat/dev.h | 45 +++-- usr.bin/aucat/dev_sun.c | 74 +++++--- usr.bin/aucat/file.c | 34 +++- usr.bin/aucat/file.h | 13 +- usr.bin/aucat/legacy.c | 7 +- 10 files changed, 727 insertions(+), 345 deletions(-) create mode 100644 usr.bin/aucat/dev.c (limited to 'usr.bin') diff --git a/usr.bin/aucat/Makefile b/usr.bin/aucat/Makefile index a9992a57427..88c9a50e5b0 100644 --- a/usr.bin/aucat/Makefile +++ b/usr.bin/aucat/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.2 2008/05/23 07:15:46 ratchov Exp $ +# $OpenBSD: Makefile,v 1.3 2008/08/14 09:58:55 ratchov Exp $ PROG= aucat -SRCS= aucat.c abuf.c aparams.c aproc.c dev_sun.c file.c headers.c \ - legacy.c +SRCS= aucat.c abuf.c aparams.c aproc.c dev.c file.c headers.c \ + dev_sun.c legacy.c CFLAGS+= -DDEBUG -Wall -Wstrict-prototypes -Werror -Wundef .include diff --git a/usr.bin/aucat/aproc.c b/usr.bin/aucat/aproc.c index aa358a71052..8fa5c7adfb4 100644 --- a/usr.bin/aucat/aproc.c +++ b/usr.bin/aucat/aproc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aproc.c,v 1.9 2008/08/14 09:47:51 ratchov Exp $ */ +/* $OpenBSD: aproc.c,v 1.10 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -467,6 +467,33 @@ mix_new(void) return p; } +void +mix_pushzero(struct aproc *p) +{ + struct abuf *obuf = LIST_FIRST(&p->obuflist); + + abuf_wcommit(obuf, obuf->mixtodo); + obuf->mixtodo = 0; + abuf_flush(obuf); + mix_bzero(p); +} + +/* + * Normalize input levels + */ +void +mix_setmaster(struct aproc *p) +{ + unsigned n; + struct abuf *buf; + + n = 0; + LIST_FOREACH(buf, &p->ibuflist, ient) + n++; + LIST_FOREACH(buf, &p->ibuflist, ient) + buf->mixvol = ADATA_UNIT / n; +} + /* * Copy data from ibuf to obuf. */ diff --git a/usr.bin/aucat/aproc.h b/usr.bin/aucat/aproc.h index 26cd5a77136..c3cb00786cb 100644 --- a/usr.bin/aucat/aproc.h +++ b/usr.bin/aucat/aproc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: aproc.h,v 1.4 2008/08/14 09:47:51 ratchov Exp $ */ +/* $OpenBSD: aproc.h,v 1.5 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -171,4 +171,7 @@ struct aproc *mix_new(void); struct aproc *sub_new(void); struct aproc *conv_new(char *, struct aparams *, struct aparams *); +void mix_pushzero(struct aproc *); +void mix_setmaster(struct aproc *); + #endif /* !defined(FIFO_H) */ diff --git a/usr.bin/aucat/aucat.c b/usr.bin/aucat/aucat.c index 1128439b939..17c8d87500b 100644 --- a/usr.bin/aucat/aucat.c +++ b/usr.bin/aucat/aucat.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aucat.c,v 1.25 2008/06/02 17:09:51 ratchov Exp $ */ +/* $OpenBSD: aucat.c,v 1.26 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -17,10 +17,6 @@ /* * 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 @@ -34,8 +30,6 @@ * 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 @@ -48,8 +42,6 @@ * 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 @@ -72,15 +64,12 @@ #include "file.h" #include "dev.h" -/* - * 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 debug_level = 0, quiet_flag = 0; +volatile int quit_flag = 0, pause_flag = 0; -int debug_level = 0; -volatile int quit_flag = 0; +void suspend(struct file *); +void fill(struct file *); +void flush(struct file *); /* * List of allowed encodings and their names. @@ -212,9 +201,6 @@ struct farg { char *name; /* optarg pointer (no need to copy it */ int hdr; /* header format */ int xrun; /* overrun/underrun policy */ - int fd; /* file descriptor for I/O */ - struct aproc *proc; /* rpipe_xxx our wpipe_xxx */ - struct abuf *buf; }; SLIST_HEAD(farglist, farg); @@ -250,7 +236,6 @@ opt_file(struct farglist *list, fa->par = *par; fa->vol = vol; fa->name = optarg; - fa->proc = NULL; SLIST_INSERT_HEAD(list, fa, entry); } @@ -258,12 +243,13 @@ opt_file(struct farglist *list, * Open an input file and setup converter if necessary. */ void -newinput(struct farg *fa, struct aparams *npar, unsigned nfr, int quiet_flag) +newinput(struct farg *fa) { int fd; struct file *f; - struct aproc *p, *c; - struct abuf *buf, *nbuf; + struct aproc *proc; + struct abuf *buf; + unsigned nfr; if (strcmp(fa->name, "-") == 0) { fd = STDIN_FILENO; @@ -276,40 +262,30 @@ newinput(struct farg *fa, struct aparams *npar, unsigned nfr, int quiet_flag) err(1, "%s", fa->name); } f = file_new(fd, fa->name); + f->hdr = 0; + f->hpar = fa->par; if (fa->hdr == HDR_WAV) { - if (!wav_readhdr(fd, &fa->par, &f->rbytes)) + if (!wav_readhdr(fd, &f->hpar, &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)) { - if (!quiet_flag) { - 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; + nfr = dev_onfr * f->hpar.rate / dev_opar.rate; + buf = abuf_new(nfr, aparams_bpf(&f->hpar)); + proc = rpipe_new(f); + aproc_setout(proc, buf); + dev_attach(fa->name, buf, &f->hpar, fa->xrun, NULL, NULL, 0); } /* * Open an output file and setup converter if necessary. */ void -newoutput(struct farg *fa, struct aparams *npar, unsigned nfr, int quiet_flag) +newoutput(struct farg *fa) { int fd; struct file *f; - struct aproc *p, *c; - struct abuf *buf, *nbuf; + struct aproc *proc; + struct abuf *buf; + unsigned nfr; if (strcmp(fa->name, "-") == 0) { fd = STDOUT_FILENO; @@ -323,57 +299,30 @@ newoutput(struct farg *fa, struct aparams *npar, unsigned nfr, int quiet_flag) err(1, "%s", fa->name); } f = file_new(fd, fa->name); - if (fa->hdr == HDR_WAV) { + f->hdr = fa->hdr; + f->hpar = fa->par; + if (f->hdr == HDR_WAV) { f->wbytes = WAV_DATAMAX; - if (!wav_writehdr(fd, &fa->par)) + if (!wav_writehdr(fd, &f->hpar)) exit(1); } - buf = abuf_new(nfr, aparams_bpf(&fa->par)); - p = wpipe_new(f); - aproc_setin(p, buf); - if (!aparams_eq(&fa->par, npar)) { - if (!quiet_flag) { - 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; -} - -void -sighdl(int s) -{ - if (quit_flag) - _exit(1); - quit_flag = 1; + nfr = dev_infr * f->hpar.rate / dev_ipar.rate; + proc = wpipe_new(f); + buf = abuf_new(nfr, aparams_bpf(&f->hpar)); + aproc_setin(proc, buf); + dev_attach(fa->name, NULL, NULL, 0, buf, &f->hpar, fa->xrun); } int main(int argc, char **argv) { - sigset_t sigset; - struct sigaction sa; - int c, u_flag, quiet_flag, ohdr, ihdr, ixrun, oxrun; + int c, u_flag, ohdr, ihdr, ixrun, oxrun; struct farg *fa; struct farglist ifiles, ofiles; - struct aparams ipar, opar, dipar, dopar, cipar, copar; + struct aparams ipar, opar, dipar, dopar; unsigned ivol, ovol; - unsigned dinfr, donfr, cinfr, confr; char *devpath, *dbgenv; - unsigned n; - struct aproc *rec, *play, *mix, *sub, *conv; - struct file *dev, *f; - struct abuf *buf, *cbuf; const char *errstr; - int fd; dbgenv = getenv("AUCAT_DEBUG"); if (dbgenv) { @@ -385,7 +334,7 @@ main(int argc, char **argv) aparams_init(&ipar, 0, 1, 44100); aparams_init(&opar, 0, 1, 44100); - u_flag = quiet_flag = 0; + u_flag = 0; devpath = NULL; SLIST_INIT(&ifiles); SLIST_INIT(&ofiles); @@ -477,240 +426,77 @@ main(int argc, char **argv) exit(1); } - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sa.sa_handler = sighdl; - if (sigaction(SIGINT, &sa, NULL) < 0) - err(1, "sigaction"); - - sigemptyset(&sigset); - (void)sigaddset(&sigset, SIGTSTP); - (void)sigaddset(&sigset, SIGCONT); - if (sigprocmask(SIG_BLOCK, &sigset, NULL)) - err(1, "sigprocmask"); - - 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; - } - /* - * 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)) { - if (!quiet_flag) { - fprintf(stderr, "%s: recording ", devpath); - aparams_print(&dipar); - fprintf(stderr, "\n"); + /* + * Calculate "best" device parameters. Iterate over all + * inputs and outputs and find the maximum sample rate + * and channel number. + */ + aparams_init(&dipar, CHAN_MAX, 0, RATE_MAX); + aparams_init(&dopar, CHAN_MAX, 0, RATE_MIN); + SLIST_FOREACH(fa, &ifiles, entry) { + if (dopar.cmin > fa->par.cmin) + dopar.cmin = fa->par.cmin; + if (dopar.cmax < fa->par.cmax) + dopar.cmax = fa->par.cmax; + if (dopar.rate < fa->par.rate) + dopar.rate = fa->par.rate; } - 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)) { - if (!quiet_flag) { - fprintf(stderr, "%s: playing ", devpath); - aparams_print(&dopar); - fprintf(stderr, "\n"); + SLIST_FOREACH(fa, &ofiles, entry) { + if (dipar.cmin > fa->par.cmin) + dipar.cmin = fa->par.cmin; + if (dipar.cmax < fa->par.cmax) + dipar.cmax = fa->par.cmax; + if (dipar.rate > fa->par.rate) + dipar.rate = fa->par.rate; } - 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(); } + file_start(); /* - * Calculate sizes of buffers using "common" parameters, to - * have roughly the same duration as device buffers. + * Open the device, dev_init() will return new parameters + * that must be used by all inputs and outputs. */ - cinfr = donfr * cipar.rate / dopar.rate; - confr = dinfr * copar.rate / dipar.rate; + dev_init(devpath, + (!SLIST_EMPTY(&ofiles)) ? &dipar : NULL, + (!SLIST_EMPTY(&ifiles)) ? &dopar : NULL); /* * Create buffers for all input and output pipes. */ - SLIST_FOREACH(fa, &ifiles, entry) { - newinput(fa, &cipar, cinfr, quiet_flag); - if (mix) { - aproc_setin(mix, fa->buf); - fa->buf->xrun = fa->xrun; - } - if (!quiet_flag) { - fprintf(stderr, "%s: reading ", fa->name); - aparams_print(&fa->par); - fprintf(stderr, "\n"); - } + while (!SLIST_EMPTY(&ifiles)) { + fa = SLIST_FIRST(&ifiles); + SLIST_REMOVE_HEAD(&ifiles, entry); + newinput(fa); + free(fa); } - SLIST_FOREACH(fa, &ofiles, entry) { - newoutput(fa, &copar, confr, quiet_flag); - if (sub) { - aproc_setout(sub, fa->buf); - fa->buf->xrun = fa->xrun; - } - if (!quiet_flag) { - fprintf(stderr, "%s: writing ", fa->name); - aparams_print(&fa->par); - fprintf(stderr, "\n"); - } + while (!SLIST_EMPTY(&ofiles)) { + fa = SLIST_FIRST(&ofiles); + SLIST_REMOVE_HEAD(&ofiles, entry); + newoutput(fa); + free(fa); } /* - * Connect the multiplexer to the device input. + * Normalize input levels */ - if (sub) { - buf = abuf_new(dinfr, aparams_bpf(&dipar)); - aproc_setout(rec, buf); - if (!aparams_eq(&copar, &dipar)) { - if (!quiet_flag) { - 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)) { - if (!quiet_flag) { - 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); - } + if (dev_mix) + mix_setmaster(dev_mix); /* * start audio */ - if (play != NULL) { - if (!quiet_flag) - fprintf(stderr, "filling buffers...\n"); - buf = LIST_FIRST(&play->ibuflist); - while (!quit_flag) { - /* no more devices to poll */ - if (!file_poll()) - break; - /* eof */ - if (dev->state & FILE_EOF) - break; - /* device is blocked and play buffer is full */ - if ((dev->events & POLLOUT) && !ABUF_WOK(buf)) - break; - } - } if (!quiet_flag) fprintf(stderr, "starting device...\n"); - dev_start(dev->fd); - if (mix) - mix->u.mix.flags |= MIX_DROP; - if (sub) - sub->u.sub.flags |= SUB_DROP; - while (!quit_flag) { - if (!file_poll()) - break; - } - + dev_start(); if (!quiet_flag) - fprintf(stderr, "draining buffers...\n"); + fprintf(stderr, "process started...\n"); + dev_run(1); + if (!quiet_flag) + fprintf(stderr, "stopping device...\n"); + dev_done(); - /* - * 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/dev.c b/usr.bin/aucat/dev.c new file mode 100644 index 00000000000..808450aa0c7 --- /dev/null +++ b/usr.bin/aucat/dev.c @@ -0,0 +1,485 @@ +/* $OpenBSD: dev.c,v 1.1 2008/08/14 09:58:55 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "dev.h" +#include "abuf.h" +#include "aproc.h" +#include "file.h" +#include "conf.h" + +int quit_flag, pause_flag; +unsigned dev_infr, dev_onfr; +struct aparams dev_ipar, dev_opar; +struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play; +struct file *dev_file; +struct devops *devops = &devops_sun; + +/* + * SIGINT handler, it raises the quit flag. If the flag is already set, + * that means that the last SIGINT was not handled, because the process + * is blocked somewhere, so exit + */ +void +sigint(int s) +{ + if (quit_flag) + _exit(1); + quit_flag = 1; +} + +/* + * called when the user hits ctrl-z + */ +void +sigtstp(int s) +{ + pause_flag = 1; +} + +/* + * SIGCONT is send when resumed after SIGTSTP or SIGSTOP. If the pause + * flag is not set, that means that the process was not suspended by + * dev_suspend(), which means that we lost the sync; since we cannot + * resync, just exit + */ +void +sigcont(int s) +{ + static char msg[] = "can't resume afer SIGSTOP, terminating...\n"; + + if (!pause_flag) { + write(STDERR_FILENO, msg, sizeof(msg) - 1); + _exit(1); + } +} + +/* + * suicide with SIGTSTP (tty stop) as if the user had hit ctrl-z + */ +void +dev_suspend(void) +{ + struct sigaction sa; + + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + if (sigaction(SIGTSTP, &sa, NULL) < 0) + err(1, "sigaction"); + DPRINTF("suspended by tty\n"); + kill(getpid(), SIGTSTP); + pause_flag = 0; + sa.sa_handler = sigtstp; + if (sigaction(SIGTSTP, &sa, NULL) < 0) + err(1, "sigaction"); + DPRINTF("resumed after suspend\n"); +} + +/* + * fill playback buffer, so when device is started there + * are samples to play + */ +void +dev_fill(void) +{ + struct abuf *buf; + + + /* + * if there are no inputs, zero fill the mixer + */ + if (dev_mix && LIST_EMPTY(&dev_mix->ibuflist)) + mix_pushzero(dev_mix); + DPRINTF("filling play buffers...\n"); + for (;;) { + if (!dev_file->wproc) { + DPRINTF("fill: no writer\n"); + break; + } + if (dev_file->events & POLLOUT) { + /* + * kernel buffers are full, but continue + * until the play buffer is full too. + */ + buf = LIST_FIRST(&dev_file->wproc->ibuflist); + if (!ABUF_WOK(buf)) + break; /* buffer full */ + if (!buf->wproc) + break; /* will never be filled */ + } + if (!file_poll()) + break; + if (pause_flag) + dev_suspend(); + } +} + +/* + * flush recorded samples once the device is stopped so + * they aren't lost + */ +void +dev_flush(void) +{ + struct abuf *buf; + + DPRINTF("flushing record buffers...\n"); + for (;;) { + if (!dev_file->rproc) { + DPRINTF("flush: no more reader\n"); + break; + } + if (dev_file->events & POLLIN) { + /* + * we drained kernel buffers, but continue + * until the record buffer is empty. + */ + buf = LIST_FIRST(&dev_file->rproc->obuflist); + if (!ABUF_ROK(buf)) + break; /* buffer empty */ + if (!buf->rproc) + break; /* will never be drained */ + } + if (!file_poll()) + break; + if (pause_flag) + dev_suspend(); + } +} + + +/* + * open the device with the given hardware parameters and create a mixer + * and a multiplexer connected to it with all necessary conversions + * setup + */ +void +dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar) +{ + int fd; + struct sigaction sa; + unsigned infr, onfr; + struct aparams ipar, opar; + struct aproc *conv; + struct abuf *buf; + + quit_flag = 0; + pause_flag = 0; + + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = sigint; + if (sigaction(SIGINT, &sa, NULL) < 0) + err(1, "sigaction"); + sa.sa_handler = sigtstp; + if (sigaction(SIGTSTP, &sa, NULL) < 0) + err(1, "sigaction"); + sa.sa_handler = sigcont; + if (sigaction(SIGCONT, &sa, NULL) < 0) + err(1, "sigaction"); + + fd = devops->open(devpath, dipar, dopar, &infr, &onfr); + if (fd < 0) + exit(1); + dev_file = file_new(fd, devpath); + + /* + * create record chain + */ + if (dipar) { + aparams_init(&ipar, dipar->cmin, dipar->cmax, dipar->rate); + infr *= DEFAULT_NBLK; + + /* + * create the read end + */ + dev_rec = rpipe_new(dev_file); + buf = abuf_new(infr, aparams_bpf(dipar)); + aproc_setout(dev_rec, buf); + + /* + * append a converter, if needed + */ + if (!aparams_eq(dipar, &ipar)) { + if (debug_level > 0) { + fprintf(stderr, "%s: ", devpath); + aparams_print2(dipar, &ipar); + fprintf(stderr, "\n"); + } + conv = conv_new("subconv", dipar, &ipar); + aproc_setin(conv, buf); + buf = abuf_new(infr, aparams_bpf(&ipar)); + aproc_setout(conv, buf); + } + dev_ipar = ipar; + dev_infr = infr; + + /* + * append a "sub" to which clients will connect + */ + dev_sub = sub_new(); + aproc_setin(dev_sub, buf); + } else { + dev_rec = NULL; + dev_sub = NULL; + } + + /* + * create play chain + */ + if (dopar) { + aparams_init(&opar, dopar->cmin, dopar->cmax, dopar->rate); + onfr *= DEFAULT_NBLK; + + /* + * create the write end + */ + dev_play = wpipe_new(dev_file); + buf = abuf_new(onfr, aparams_bpf(dopar)); + aproc_setin(dev_play, buf); + + /* + * append a converter, if needed + */ + if (!aparams_eq(&opar, dopar)) { + if (debug_level > 0) { + fprintf(stderr, "%s: ", devpath); + aparams_print2(&opar, dopar); + fprintf(stderr, "\n"); + } + conv = conv_new("mixconv", &opar, dopar); + aproc_setout(conv, buf); + buf = abuf_new(onfr, aparams_bpf(&opar)); + aproc_setin(conv, buf); + *dopar = opar; + } + dev_opar = opar; + dev_onfr = onfr; + + /* + * append a "mix" to which clients will connect + */ + dev_mix = mix_new(); + aproc_setout(dev_mix, buf); + } else { + dev_play = NULL; + dev_mix = NULL; + } +} + +/* + * cleanly stop and drain everything and close the device + * once both play chain and record chain are gone + */ +void +dev_done(void) +{ + struct sigaction sa; + struct file *f; + + /* + * generate EOF on all inputs (including device), so once + * buffers are drained, everything will be cleaned + */ + LIST_FOREACH(f, &file_list, entry) { + if (f->rproc) + file_eof(f); + } + /* + * destroy automatically mixe instead + * of generating silence + */ + if (dev_mix) + dev_mix->u.mix.flags |= MIX_AUTOQUIT; + if (dev_sub) + dev_sub->u.sub.flags |= SUB_AUTOQUIT; + /* + * drain buffers of terminated inputs. + */ + for (;;) { + if (!file_poll()) + break; + } + devops->close(dev_file->fd); + + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + if (sigaction(SIGINT, &sa, NULL) < 0) + err(1, "sigaction"); + if (sigaction(SIGTSTP, &sa, NULL) < 0) + err(1, "sigaction"); + if (sigaction(SIGCONT, &sa, NULL) < 0) + err(1, "sigaction"); +} + +/* + * start the (paused) device. By default it's paused + */ +void +dev_start(void) +{ + dev_fill(); + if (dev_mix) + dev_mix->u.mix.flags |= MIX_DROP; + if (dev_sub) + dev_sub->u.sub.flags |= SUB_DROP; + devops->start(dev_file->fd); +} + +/* + * pause the device + */ +void +dev_stop(void) +{ + devops->stop(dev_file->fd); + if (dev_mix) + dev_mix->u.mix.flags &= ~MIX_DROP; + if (dev_sub) + dev_sub->u.sub.flags &= ~SUB_DROP; + dev_flush(); +} + +/* + * loop until there's either input or output to process + */ +void +dev_run(int autoquit) +{ + while (!quit_flag) { + if ((!dev_mix || LIST_EMPTY(&dev_mix->ibuflist)) && + (!dev_sub || LIST_EMPTY(&dev_sub->obuflist)) && autoquit) + break; + if (!file_poll()) + break; + if (pause_flag) { + devops->stop(dev_file->fd); + dev_flush(); + dev_suspend(); + dev_fill(); + devops->start(dev_file->fd); + } + } +} + +/* + * attach the given input and output buffers to the mixer and the + * multiplexer respectively. The operation is done synchronously, so + * both buffers enter in sync. If buffers do not match play + * and rec + */ +void +dev_attach(char *name, + struct abuf *ibuf, struct aparams *ipar, unsigned underrun, + struct abuf *obuf, struct aparams *opar, unsigned overrun) +{ + int delta; + struct abuf *pbuf = NULL, *rbuf = NULL; + struct aproc *conv; + + if (ibuf) { + pbuf = LIST_FIRST(&dev_mix->obuflist); + if (!aparams_eq(ipar, &dev_opar)) { + if (debug_level > 1) { + fprintf(stderr, "dev_attach: %s: ", name); + aparams_print2(ipar, &dev_opar); + fprintf(stderr, "\n"); + } + conv = conv_new(name, ipar, &dev_opar); + aproc_setin(conv, ibuf); + ibuf = abuf_new(dev_onfr, aparams_bpf(&dev_opar)); + aproc_setout(conv, ibuf); + } + aproc_setin(dev_mix, ibuf); + ibuf->xrun = underrun; + mix_setmaster(dev_mix); + } + if (obuf) { + rbuf = LIST_FIRST(&dev_sub->ibuflist); + if (!aparams_eq(opar, &dev_ipar)) { + if (debug_level > 1) { + fprintf(stderr, "dev_attach: %s: ", name); + aparams_print2(&dev_ipar, opar); + fprintf(stderr, "\n"); + } + conv = conv_new(name, &dev_ipar, opar); + aproc_setout(conv, obuf); + obuf = abuf_new(dev_infr, aparams_bpf(&dev_ipar)); + aproc_setin(conv, obuf); + } + aproc_setout(dev_sub, obuf); + obuf->xrun = overrun; + } + + /* + * calculate delta, the number of frames the play chain is ahead + * of the record chain. It's necessary to schedule silences (or + * drops) in order to start playback and record in sync. + */ + if (ibuf && obuf) { + delta = + rbuf->bpf * (pbuf->abspos + pbuf->used) - + pbuf->bpf * rbuf->abspos; + delta /= pbuf->bpf * rbuf->bpf; + DPRINTF("dev_attach: ppos = %u, pused = %u, rpos = %u\n", + pbuf->abspos, pbuf->used, rbuf->abspos); + } else + delta = 0; + DPRINTF("dev_attach: delta = %u\n", delta); + + if (delta > 0) { + /* + * if the play chain is ahead (most cases) drop some of + * the recorded input, to get both in sync + */ + obuf->drop += delta * obuf->bpf; + } else if (delta < 0) { + /* + * if record chain is ahead (should never happen, + * right?) then insert silence to play + */ + ibuf->silence += -delta * ibuf->bpf; + } + if (ibuf && (dev_mix->u.mix.flags & MIX_DROP)) { + DPRINTF("lmkqsjdlkqsjklqsd\n"); + /* + * fill the play buffer with silence to avoid underruns, + * drop samples on the input to keep play/record in sync + * after the silence insertion + */ + ibuf->silence += dev_onfr * ibuf->bpf; + if (obuf) + obuf->drop += dev_onfr * obuf->bpf; + /* + * force data to propagate + */ + abuf_run(ibuf); + DPRINTF("dev_attach: ibuf: used = %u, silence = %u\n", + ibuf->used, ibuf->silence); + } + if (obuf && (dev_sub->u.mix.flags & SUB_DROP)) { + abuf_run(obuf); + DPRINTF("dev_attach: ibuf: used = %u, drop = %u\n", + obuf->used, obuf->drop); + } +} diff --git a/usr.bin/aucat/dev.h b/usr.bin/aucat/dev.h index 940e2b52bb4..e5b50af53a3 100644 --- a/usr.bin/aucat/dev.h +++ b/usr.bin/aucat/dev.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dev.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */ +/* $OpenBSD: dev.h,v 1.2 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -17,19 +17,42 @@ #ifndef DEV_H #define DEV_H -#include -#include -#include -#include +struct aproc; +struct aparams; +struct file; +struct abuf; -int dev_init(char *, struct aparams *, struct aparams *, - unsigned *, unsigned *); -void dev_done(int); -void dev_start(int); -void dev_stop(int); +extern unsigned dev_infr, dev_onfr; +extern struct aparams dev_ipar, dev_opar; +extern struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play; +extern struct file *dev_file; +void dev_fill(void); +void dev_flush(void); +void dev_init(char *, struct aparams *, struct aparams *); +void dev_start(void); +void dev_stop(void); +void dev_run(int); +void dev_done(void); +void dev_attach(char *, + struct abuf *, struct aparams *, unsigned, + struct abuf *, struct aparams *, unsigned); + +struct devops { + int (*open)(char *, struct aparams *, struct aparams *, + unsigned *, unsigned *); + void (*close)(int); + void (*start)(int); + void (*stop)(int); +}; + +extern struct devops *devops, devops_sun; + +/* + * Sun API specific functions + */ +struct audio_prinfo; 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 index e0e36b07d0e..2e97f6def0e 100644 --- a/usr.bin/aucat/dev_sun.c +++ b/usr.bin/aucat/dev_sun.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dev_sun.c,v 1.4 2008/06/03 14:36:20 drahn Exp $ */ +/* $OpenBSD: dev_sun.c,v 1.5 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -15,11 +15,16 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include +#include + #include #include #include #include #include +#include #include #include "conf.h" @@ -27,7 +32,7 @@ #include "dev.h" /* - * convert sun device parameters to struct params + * Convert sun device parameters to struct aparams */ int sun_infotopar(struct audio_prinfo *ai, struct aparams *par) @@ -75,7 +80,7 @@ sun_infotopar(struct audio_prinfo *ai, struct aparams *par) } /* - * Convert struct params to sun device parameters. + * Convert struct aparams to sun device parameters. */ void sun_partoinfo(struct audio_prinfo *ai, struct aparams *par) @@ -98,11 +103,11 @@ sun_partoinfo(struct audio_prinfo *ai, struct aparams *par) * 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 + * in "infr" and "onfd" we return the input and the output * block sizes respectively. */ int -dev_init(char *path, struct aparams *ipar, struct aparams *opar, +sun_open(char *path, struct aparams *ipar, struct aparams *opar, unsigned *infr, unsigned *onfr) { int fd; @@ -173,7 +178,7 @@ dev_init(char *path, struct aparams *ipar, struct aparams *opar, return -1; } if (ioctl(fd, AUDIO_GETINFO, &aui) < 0) { - warn("dev_init: getinfo"); + warn("sun_open: getinfo"); close(fd); return -1; } @@ -206,18 +211,21 @@ dev_init(char *path, struct aparams *ipar, struct aparams *opar, return fd; } +/* + * Drain and close the device + */ void -dev_done(int fd) +sun_close(int fd) { close(fd); - DPRINTF("dev_done: closed\n"); + DPRINTF("sun_close: closed\n"); } /* - * Start play/record. + * Start play/record simultaneously. Play buffers must be filled. */ void -dev_start(int fd) +sun_start(int fd) { audio_info_t aui; @@ -228,37 +236,47 @@ dev_start(int fd) AUDIO_INITINFO(&aui); aui.play.pause = aui.record.pause = 0; if (ioctl(fd, AUDIO_SETINFO, &aui) < 0) - err(1, "dev_start: setinfo"); + err(1, "sun_start: setinfo"); - DPRINTF("dev_start: play/rec started\n"); + DPRINTF("sun_start: play/rec started\n"); } /* - * Stop play/record and clear kernel buffers so that dev_start() can be called - * again. + * Drain play buffers and then stop play/record simultaneously. */ void -dev_stop(int fd) +sun_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). + * Sun API doesn't not allows us to drain and stop without + * loosing the sync between playback and record. So, for now we + * just pause the device until this problem is worked around. + * + * there are three possible workarounds: + * + * 1) stop depending on this, ie. make the rest of the code + * able to resynchronize playback to record. Then just + * close/reset the device to stop it. + * + * 2) send "hiwat" blocks of silence and schedule the + * very same amount of silence to drop. + * + * 3) modify the AUDIO_DRAIN ioctl(2) not to loose sync + * */ - 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"); + err(1, "sun_stop: setinfo"); - DPRINTF("dev_stop: play/rec stopped\n"); + DPRINTF("sun_stop: play/rec stopped\n"); } + +struct devops devops_sun = { + sun_open, + sun_close, + sun_start, + sun_stop +}; diff --git a/usr.bin/aucat/file.c b/usr.bin/aucat/file.c index 7aae942925d..70995e64fa2 100644 --- a/usr.bin/aucat/file.c +++ b/usr.bin/aucat/file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: file.c,v 1.2 2008/08/14 09:48:50 ratchov Exp $ */ +/* $OpenBSD: file.c,v 1.3 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -31,17 +31,36 @@ #include #include #include +#include #include #include "conf.h" #include "file.h" #include "aproc.h" #include "abuf.h" +#include "dev.h" #define MAXFDS 100 struct filelist file_list; +void +file_dprint(int n, struct file *f) +{ + if (debug_level >= n) { + fprintf(stderr, "%s <", f->name); + if (f->state & FILE_ROK) + fprintf(stderr, "ROK"); + if (f->state & FILE_WOK) + fprintf(stderr, "WOK"); + if (f->state & FILE_EOF) + fprintf(stderr, "EOF"); + if (f->state & FILE_HUP) + fprintf(stderr, "HUP"); + fprintf(stderr, ">"); + } +} + struct file * file_new(int fd, char *name) { @@ -74,7 +93,14 @@ file_new(int fd, char *name) void file_del(struct file *f) { - DPRINTF("file_del: %s|%x\n", f->name, f->state); + DPRINTF("file_del: "); + file_dprint(1, f); + DPRINTF("\n"); + + if (f->hdr == HDR_WAV) + wav_writehdr(f->fd, &f->hpar); + close(f->fd); + free(f); } int @@ -176,8 +202,8 @@ file_poll(void) 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); + DPRINTF("file_poll: %s: removed\n", f->name); + file_del(f); } } if (LIST_EMPTY(&file_list)) { diff --git a/usr.bin/aucat/file.h b/usr.bin/aucat/file.h index 9e3adcfff25..338d7a3c426 100644 --- a/usr.bin/aucat/file.h +++ b/usr.bin/aucat/file.h @@ -1,4 +1,4 @@ -/* $OpenBSD: file.h,v 1.2 2008/06/02 17:05:12 ratchov Exp $ */ +/* $OpenBSD: file.h,v 1.3 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * @@ -19,8 +19,8 @@ #include #include - #include +#include "aparams.h" struct aparams; struct aproc; @@ -40,6 +40,15 @@ struct file { char *name; /* for debug purposes */ struct aproc *rproc, *wproc; /* reader and/or writer */ LIST_ENTRY(file) entry; + + /* + * disk-file specific stuff + */ +#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 */ + unsigned hdr; /* HDR_RAW or HDR_WAV */ + struct aparams hpar; /* parameters to write on the header */ }; LIST_HEAD(filelist,file); diff --git a/usr.bin/aucat/legacy.c b/usr.bin/aucat/legacy.c index 56f4be7e854..fcb705120db 100644 --- a/usr.bin/aucat/legacy.c +++ b/usr.bin/aucat/legacy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: legacy.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */ +/* $OpenBSD: legacy.c,v 1.2 2008/08/14 09:58:55 ratchov Exp $ */ /* * Copyright (c) 1997 Kenneth Stailey. All rights reserved. * @@ -28,7 +28,12 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include + #include +#include #include #include -- cgit v1.2.3