summaryrefslogtreecommitdiff
path: root/usr.bin/aucat/dev.c
diff options
context:
space:
mode:
authorAlexandre Ratchov <ratchov@cvs.openbsd.org>2008-10-26 08:49:45 +0000
committerAlexandre Ratchov <ratchov@cvs.openbsd.org>2008-10-26 08:49:45 +0000
commit13e276c156d9b9f3a5064700b447d8e90d89bebf (patch)
tree7c47aad8fcd6da2de5156ec12853b26ee468547e /usr.bin/aucat/dev.c
parentc1f6af90f771854093903e82e7de930b96a15d25 (diff)
add minimal server capability to aucat(1). When started in server
mode, it listens on an unix socket and mixes/demultiplexes any number of full-duplex streams, doing necessary format conversions and resampling on the fly. programs can use the new libsa(3) library to play and record audio. The library provides a very simple API to connect to the audio server; if aucat(1) isn't running, it uses the audio(4) driver transparently instead.
Diffstat (limited to 'usr.bin/aucat/dev.c')
-rw-r--r--usr.bin/aucat/dev.c469
1 files changed, 208 insertions, 261 deletions
diff --git a/usr.bin/aucat/dev.c b/usr.bin/aucat/dev.c
index 225ed6ed377..0f418a63fc8 100644
--- a/usr.bin/aucat/dev.c
+++ b/usr.bin/aucat/dev.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: dev.c,v 1.2 2008/08/14 15:25:16 ratchov Exp $ */
+/* $OpenBSD: dev.c,v 1.3 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -17,204 +17,130 @@
#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 "pipe.h"
#include "conf.h"
+#include "safile.h"
-int quit_flag, pause_flag;
-unsigned dev_infr, dev_onfr;
+unsigned dev_bufsz, dev_round, dev_rate;
+unsigned dev_rate_div, dev_round_div;
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;
+struct file *dev_file;
/*
- * 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
+ * supported rates
*/
-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);
- }
-}
+#define NRATES (sizeof(dev_rates) / sizeof(dev_rates[0]))
+unsigned dev_rates[] = {
+ 6400, 7200, 8000, 9600, 11025, 12000,
+ 12800, 14400, 16000, 19200, 22050, 24000,
+ 25600, 28800, 32000, 38400, 44100, 48000,
+ 51200, 57600, 64000, 76800, 88200, 96000,
+ 102400, 115200, 128000, 153600, 176400, 192000
+};
/*
- * suicide with SIGTSTP (tty stop) as if the user had hit ctrl-z
+ * factors of supported rates
*/
-void
-dev_suspend(void)
-{
- struct sigaction sa;
+#define NPRIMES (sizeof(dev_primes) / sizeof(dev_primes[0]))
+unsigned dev_primes[] = {2, 3, 5, 7};
- 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)
+int
+dev_setrate(unsigned rate)
{
- struct abuf *buf;
-
+ unsigned i, r, p;
- /*
- * 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");
+ r = 1000 * rate;
+ for (i = 0; i < NRATES; i++) {
+ if (i == NRATES) {
+ fprintf(stderr, "dev_setrate: %u, unsupported\n", rate);
+ return 0;
+ }
+ if (r > 996 * dev_rates[i] &&
+ r < 1004 * dev_rates[i]) {
+ dev_rate = dev_rates[i];
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 */
+ }
+
+ dev_rate_div = dev_rate;
+ dev_round_div = dev_round;
+ for (i = 0; i < NPRIMES; i++) {
+ p = dev_primes[i];
+ while (dev_rate_div % p == 0 && dev_round_div % p == 0) {
+ dev_rate_div /= p;
+ dev_round_div /= p;
}
- if (!file_poll())
- break;
- if (pause_flag)
- dev_suspend();
}
+ return 1;
}
-/*
- * flush recorded samples once the device is stopped so
- * they aren't lost
- */
void
-dev_flush(void)
+dev_roundrate(unsigned *newrate, unsigned *newround)
{
- 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();
- }
+ *newrate += dev_rate_div - 1;
+ *newrate -= *newrate % dev_rate_div;
+ *newround = *newrate * dev_round_div / dev_rate_div;
}
-
/*
* 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)
+dev_init(char *devpath,
+ struct aparams *dipar, struct aparams *dopar, unsigned bufsz)
{
- int fd;
- struct sigaction sa;
- unsigned infr, onfr;
struct aparams ipar, opar;
struct aproc *conv;
struct abuf *buf;
+ unsigned nfr, ibufsz, obufsz;
- 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)
+ /*
+ * use 1/4 of the total buffer for the device
+ */
+ dev_bufsz = (bufsz + 3) / 4;
+ dev_file = (struct file *)safile_new(&safile_ops, devpath,
+ dipar, dopar, &dev_bufsz, &dev_round);
+ if (!dev_file)
+ exit(1);
+ if (!dev_setrate(dipar ? dipar->rate : dopar->rate))
exit(1);
- dev_file = file_new(fd, devpath);
+ if (dipar) {
+ dipar->rate = dev_rate;
+ if (debug_level > 0) {
+ DPRINTF("dev_init: dipar: ");
+ aparams_print(dipar);
+ DPRINTF("\n");
+ }
+ }
+ if (dopar) {
+ dopar->rate = dev_rate;
+ if (debug_level > 0) {
+ DPRINTF("dev_init: dopar: ");
+ aparams_print(dopar);
+ DPRINTF("\n");
+ }
+ }
+ nfr = ibufsz = obufsz = dev_bufsz;
/*
- * create record chain
+ * create record chain: use 1/4 for the file i/o buffers
*/
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));
+ buf = abuf_new(nfr, aparams_bpf(dipar));
aproc_setout(dev_rec, buf);
+ ibufsz += nfr;
/*
* append a converter, if needed
@@ -227,16 +153,16 @@ dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar)
}
conv = conv_new("subconv", dipar, &ipar);
aproc_setin(conv, buf);
- buf = abuf_new(infr, aparams_bpf(&ipar));
+ buf = abuf_new(nfr, aparams_bpf(&ipar));
aproc_setout(conv, buf);
+ ibufsz += nfr;
}
dev_ipar = ipar;
- dev_infr = infr;
/*
* append a "sub" to which clients will connect
*/
- dev_sub = sub_new();
+ dev_sub = sub_new("sub", nfr);
aproc_setin(dev_sub, buf);
} else {
dev_rec = NULL;
@@ -248,15 +174,14 @@ dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar)
*/
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));
+ buf = abuf_new(nfr, aparams_bpf(dopar));
aproc_setin(dev_play, buf);
-
+ obufsz += nfr;
+
/*
* append a converter, if needed
*/
@@ -268,22 +193,24 @@ dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar)
}
conv = conv_new("mixconv", &opar, dopar);
aproc_setout(conv, buf);
- buf = abuf_new(onfr, aparams_bpf(&opar));
+ buf = abuf_new(nfr, aparams_bpf(&opar));
aproc_setin(conv, buf);
- *dopar = opar;
+ obufsz += nfr;
}
dev_opar = opar;
- dev_onfr = onfr;
/*
* append a "mix" to which clients will connect
*/
- dev_mix = mix_new();
+ dev_mix = mix_new("mix", nfr);
aproc_setout(dev_mix, buf);
} else {
dev_play = NULL;
dev_mix = NULL;
}
+ dev_bufsz = (dopar) ? obufsz : ibufsz;
+ DPRINTF("dev_init: using %u fpb\n", dev_bufsz);
+ dev_start();
}
/*
@@ -293,43 +220,56 @@ dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar)
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);
+ if (dev_mix) {
+ /*
+ * generate EOF on all inputs (but not the device), and
+ * put the mixer in ``autoquit'' state, so once buffers
+ * are drained the mixer will terminate and shutdown the
+ * write-end of the device
+ *
+ * NOTE: since file_eof() can destroy the file and
+ * reorder the file_list, we have to restart the loop
+ * after each call to file_eof()
+ */
+ restart:
+ LIST_FOREACH(f, &file_list, entry) {
+ if (f != dev_file && f->rproc) {
+ file_eof(f);
+ goto restart;
+ }
+ }
+ if (dev_mix)
+ dev_mix->u.mix.flags |= MIX_AUTOQUIT;
+
+ /*
+ * wait play chain to terminate
+ */
+ while (dev_file->wproc != NULL) {
+ if (!file_poll())
+ break;
+ }
+ dev_mix = 0;
}
- /*
- * 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;
+ if (dev_sub) {
+ /*
+ * same as above, but for the record chain: generate eof
+ * on the read-end of the device and wait record buffers
+ * to desappear. We must stop the device first, because
+ * play-end will underrun (and xrun correction code will
+ * insert silence on the record-end of the device)
+ */
+ dev_stop();
+ file_eof(dev_file);
+ if (dev_sub)
+ dev_sub->u.sub.flags |= SUB_AUTOQUIT;
+ for (;;) {
+ if (!file_poll())
+ break;
+ }
+ dev_sub = NULL;
}
- 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");
}
/*
@@ -338,12 +278,11 @@ dev_done(void)
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);
+ dev_file->ops->start(dev_file);
}
/*
@@ -352,34 +291,78 @@ dev_start(void)
void
dev_stop(void)
{
- devops->stop(dev_file->fd);
+ dev_file->ops->stop(dev_file);
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
+ * sync play buffer to rec buffer (for instance when one of
+ * them underruns/overruns)
*/
void
-dev_run(int autoquit)
+dev_sync(struct abuf *ibuf, struct abuf *obuf)
{
- while (!quit_flag) {
- if ((!dev_mix || LIST_EMPTY(&dev_mix->ibuflist)) &&
- (!dev_sub || LIST_EMPTY(&dev_sub->obuflist)) && autoquit)
- break;
- if (!file_poll())
+ struct abuf *pbuf, *rbuf;
+ int delta;
+
+ if (!dev_mix || !dev_sub)
+ return;
+ pbuf = LIST_FIRST(&dev_mix->obuflist);
+ if (!pbuf)
+ return;
+ rbuf = LIST_FIRST(&dev_sub->ibuflist);
+ if (!rbuf)
+ return;
+ for (;;) {
+ if (!ibuf || !ibuf->rproc) {
+ DPRINTF("dev_sync: reader desappeared\n");
+ return;
+ }
+ if (ibuf->rproc == dev_mix)
break;
- if (pause_flag) {
- devops->stop(dev_file->fd);
- dev_flush();
- dev_suspend();
- dev_fill();
- devops->start(dev_file->fd);
+ ibuf = LIST_FIRST(&ibuf->rproc->obuflist);
+ }
+ for (;;) {
+ if (!obuf || !obuf->wproc) {
+ DPRINTF("dev_sync: writer desappeared\n");
+ return;
}
+ if (obuf->wproc == dev_sub)
+ break;
+ obuf = LIST_FIRST(&obuf->wproc->ibuflist);
}
+
+ /*
+ * 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.
+ */
+ delta =
+ rbuf->bpf * (pbuf->abspos + pbuf->used) -
+ pbuf->bpf * rbuf->abspos;
+ delta /= pbuf->bpf * rbuf->bpf;
+ DPRINTF("dev_sync: delta = %d, ppos = %u, pused = %u, rpos = %u\n",
+ delta, pbuf->abspos, pbuf->used, rbuf->abspos);
+
+ 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;
+ abuf_ipos(obuf, -delta);
+ } else if (delta < 0) {
+ /*
+ * if record chain is ahead (should never happen,
+ * right?) then insert silence to play
+ */
+ ibuf->silence += -delta * ibuf->bpf;
+ abuf_opos(ibuf, delta);
+ } else
+ DPRINTF("dev_sync: nothing to do\n");
}
/*
@@ -393,9 +376,9 @@ 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;
+ unsigned nfr;
if (ibuf) {
pbuf = LIST_FIRST(&dev_mix->obuflist);
@@ -405,14 +388,17 @@ dev_attach(char *name,
aparams_print2(ipar, &dev_opar);
fprintf(stderr, "\n");
}
+ nfr = (dev_bufsz + 3) / 4 + dev_round - 1;
+ nfr -= nfr % dev_round;
conv = conv_new(name, ipar, &dev_opar);
aproc_setin(conv, ibuf);
- ibuf = abuf_new(dev_onfr, aparams_bpf(&dev_opar));
+ ibuf = abuf_new(nfr, aparams_bpf(&dev_opar));
aproc_setout(conv, ibuf);
+ /* XXX: call abuf_fill() here ? */
}
aproc_setin(dev_mix, ibuf);
+ abuf_opos(ibuf, -dev_mix->u.mix.lat);
ibuf->xrun = underrun;
- mix_setmaster(dev_mix);
}
if (obuf) {
rbuf = LIST_FIRST(&dev_sub->ibuflist);
@@ -422,63 +408,24 @@ dev_attach(char *name,
aparams_print2(&dev_ipar, opar);
fprintf(stderr, "\n");
}
+ nfr = (dev_bufsz + 3) / 4 + dev_round - 1;
+ nfr -= nfr % dev_round;
conv = conv_new(name, &dev_ipar, opar);
aproc_setout(conv, obuf);
- obuf = abuf_new(dev_infr, aparams_bpf(&dev_ipar));
+ obuf = abuf_new(nfr, aparams_bpf(&dev_ipar));
aproc_setin(conv, obuf);
}
aproc_setout(dev_sub, obuf);
+ abuf_ipos(obuf, -dev_sub->u.sub.lat);
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.
+ * sync play to record
*/
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)) {
- /*
- * 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);
+ ibuf->duplex = obuf;
+ obuf->duplex = ibuf;
+ dev_sync(ibuf, obuf);
}
}