-# $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 <bsd.prog.mk>
-/* $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 <alex@caoua.org>
*
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.
*/
-/* $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 <alex@caoua.org>
*
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) */
-/* $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 <alex@caoua.org>
*
/*
* 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
* 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
* 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 "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.
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);
fa->par = *par;
fa->vol = vol;
fa->name = optarg;
- fa->proc = NULL;
SLIST_INSERT_HEAD(list, fa, entry);
}
* 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;
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;
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) {
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);
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;
}
--- /dev/null
+/* $OpenBSD: dev.c,v 1.1 2008/08/14 09:58:55 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 <unistd.h>
+#include <signal.h>
+#include <err.h>
+
+#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);
+ }
+}
-/* $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 <alex@caoua.org>
*
#ifndef DEV_H
#define DEV_H
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/audioio.h>
-#include <string.h>
+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) */
-/* $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 <alex@caoua.org>
*
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include "conf.h"
#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)
}
/*
- * Convert struct params to sun device parameters.
+ * Convert struct aparams to sun device parameters.
*/
void
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;
return -1;
}
if (ioctl(fd, AUDIO_GETINFO, &aui) < 0) {
- warn("dev_init: getinfo");
+ warn("sun_open: getinfo");
close(fd);
return -1;
}
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;
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
+};
-/* $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 <alex@caoua.org>
*
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#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)
{
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
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)) {
-/* $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 <alex@caoua.org>
*
#include <sys/queue.h>
#include <sys/types.h>
-
#include <poll.h>
+#include "aparams.h"
struct aparams;
struct aproc;
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);
-/* $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.
*
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+
#include <fcntl.h>
+#include <string.h>
#include <unistd.h>
#include <err.h>