summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Makefile4
-rw-r--r--lib/libsa/Makefile14
-rw-r--r--lib/libsa/aucat.c528
-rw-r--r--lib/libsa/libsa.3706
-rw-r--r--lib/libsa/libsa.c517
-rw-r--r--lib/libsa/libsa.h139
-rw-r--r--lib/libsa/libsa_priv.h65
-rw-r--r--lib/libsa/shlib_version2
-rw-r--r--lib/libsa/sun.c858
-rw-r--r--regress/lib/libsa/Makefile5
-rw-r--r--regress/lib/libsa/sacap/Makefile6
-rw-r--r--regress/lib/libsa/sacap/sacap.c97
-rw-r--r--regress/lib/libsa/safd/Makefile6
-rw-r--r--regress/lib/libsa/safd/safd.c363
-rw-r--r--regress/lib/libsa/saplay/Makefile6
-rw-r--r--regress/lib/libsa/saplay/saplay.c133
-rw-r--r--regress/lib/libsa/sarec/Makefile6
-rw-r--r--regress/lib/libsa/sarec/sarec.c119
-rw-r--r--usr.bin/aucat/Makefile6
-rw-r--r--usr.bin/aucat/abuf.c327
-rw-r--r--usr.bin/aucat/abuf.h17
-rw-r--r--usr.bin/aucat/amsg.h92
-rw-r--r--usr.bin/aucat/aparams.c147
-rw-r--r--usr.bin/aucat/aparams.h23
-rw-r--r--usr.bin/aucat/aproc.c411
-rw-r--r--usr.bin/aucat/aproc.h29
-rw-r--r--usr.bin/aucat/aucat.1104
-rw-r--r--usr.bin/aucat/aucat.c286
-rw-r--r--usr.bin/aucat/conf.h18
-rw-r--r--usr.bin/aucat/dev.c469
-rw-r--r--usr.bin/aucat/dev.h29
-rw-r--r--usr.bin/aucat/dev_sun.c282
-rw-r--r--usr.bin/aucat/file.c292
-rw-r--r--usr.bin/aucat/file.h61
-rw-r--r--usr.bin/aucat/headers.c4
-rw-r--r--usr.bin/aucat/legacy.c74
-rw-r--r--usr.bin/aucat/listen.c133
-rw-r--r--usr.bin/aucat/listen.h37
-rw-r--r--usr.bin/aucat/pipe.c146
-rw-r--r--usr.bin/aucat/pipe.h39
-rw-r--r--usr.bin/aucat/safile.c288
-rw-r--r--usr.bin/aucat/safile.h37
-rw-r--r--usr.bin/aucat/sock.c931
-rw-r--r--usr.bin/aucat/sock.h58
-rw-r--r--usr.bin/aucat/wav.c116
-rw-r--r--usr.bin/aucat/wav.h51
46 files changed, 6880 insertions, 1201 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 1a120c8555a..3f4ce25b7da 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,11 +1,11 @@
-# $OpenBSD: Makefile,v 1.53 2007/10/18 19:21:36 espie Exp $
+# $OpenBSD: Makefile,v 1.54 2008/10/26 08:49:43 ratchov Exp $
# $NetBSD: Makefile,v 1.20.4.1 1996/06/14 17:22:38 cgd Exp $
SUBDIR= csu libarch libc libcompat libcurses \
libdes libedit libevent libexpat \
libform libkeynote libkvm libl libm libmenu \
libocurses libossaudio libpanel libpcap libpthread librpcsvc \
- libskey libsectok libssl libusbhid libutil libwrap liby libz
+ libsa libskey libsectok libssl libusbhid libutil libwrap liby libz
.include <bsd.own.mk>
diff --git a/lib/libsa/Makefile b/lib/libsa/Makefile
new file mode 100644
index 00000000000..7f8ea92b469
--- /dev/null
+++ b/lib/libsa/Makefile
@@ -0,0 +1,14 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+
+LIB= sa
+MAN= libsa.3
+SRCS= aucat.c sun.c libsa.c
+CFLAGS+=-Wall -Wstrict-prototypes -Werror -Wundef -DDEBUG \
+ -I${.CURDIR} -I${.CURDIR}/../../usr.bin/aucat
+
+includes:
+ @cd ${.CURDIR}; cmp -s libsa.h ${DESTDIR}/usr/include/libsa.h || \
+ ${INSTALL} ${INSTALL_COPY} -o $(BINOWN) -g $(BINGRP) \
+ -m 444 libsa.h ${DESTDIR}/usr/include
+
+.include <bsd.lib.mk>
diff --git a/lib/libsa/aucat.c b/lib/libsa/aucat.c
new file mode 100644
index 00000000000..2d3f80ecb70
--- /dev/null
+++ b/lib/libsa/aucat.c
@@ -0,0 +1,528 @@
+/* $OpenBSD: aucat.c,v 1.1 2008/10/26 08:49:44 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/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "amsg.h"
+#include "libsa_priv.h"
+
+struct aucat_hdl {
+ struct sa_hdl sa;
+ int fd; /* socket */
+ struct amsg rmsg, wmsg; /* temporary messages */
+ size_t wtodo, rtodo; /* bytes to complete the packet */
+#define STATE_IDLE 0 /* nothing to do */
+#define STATE_MSG 1 /* message being transferred */
+#define STATE_DATA 2 /* data being transfered */
+ unsigned rstate, wstate; /* one of above */
+ unsigned rbpf, wbpf; /* read and write bytes-per-frame */
+ int maxwrite; /* latency constraint */
+ int events; /* events the user requested */
+};
+
+void aucat_close(struct sa_hdl *);
+int aucat_start(struct sa_hdl *);
+int aucat_stop(struct sa_hdl *);
+int aucat_setpar(struct sa_hdl *, struct sa_par *);
+int aucat_getpar(struct sa_hdl *, struct sa_par *);
+int aucat_getcap(struct sa_hdl *, struct sa_cap *);
+size_t aucat_read(struct sa_hdl *, void *, size_t);
+size_t aucat_write(struct sa_hdl *, void *, size_t);
+int aucat_pollfd(struct sa_hdl *, struct pollfd *, int);
+int aucat_revents(struct sa_hdl *, struct pollfd *);
+
+struct sa_ops aucat_ops = {
+ aucat_close,
+ aucat_setpar,
+ aucat_getpar,
+ aucat_getcap,
+ aucat_write,
+ aucat_read,
+ aucat_start,
+ aucat_stop,
+ aucat_pollfd,
+ aucat_revents
+};
+
+struct sa_hdl *
+sa_open_aucat(char *path, unsigned mode, int nbio)
+{
+ int s;
+ struct aucat_hdl *hdl;
+ struct sockaddr_un ca;
+ socklen_t len = sizeof(struct sockaddr_un);
+
+ if (path == NULL)
+ path = SA_AUCAT_PATH;
+ hdl = malloc(sizeof(struct aucat_hdl));
+ if (hdl == NULL)
+ return NULL;
+ sa_create(&hdl->sa, &aucat_ops, mode, nbio);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ free(hdl);
+ return NULL;
+ }
+ ca.sun_family = AF_UNIX;
+ memcpy(ca.sun_path, path, strlen(path) + 1);
+ while (connect(s, (struct sockaddr *)&ca, len) < 0) {
+ if (errno == EINTR)
+ continue;
+ while (close(s) < 0 && errno == EINTR)
+ ; /* retry */
+ free(hdl);
+ return NULL;
+ }
+ hdl->fd = s;
+ hdl->rstate = STATE_IDLE;
+ hdl->rtodo = 0xdeadbeef;
+ hdl->wstate = STATE_IDLE;
+ hdl->wtodo = 0xdeadbeef;
+ return (struct sa_hdl *)hdl;
+}
+
+/*
+ * read a message, return 0 if blocked
+ */
+int
+aucat_rmsg(struct aucat_hdl *hdl)
+{
+ ssize_t n;
+ unsigned char *data;
+
+ while (hdl->rtodo > 0) {
+ data = (unsigned char *)&hdl->rmsg;
+ data += sizeof(struct amsg) - hdl->rtodo;
+ while ((n = read(hdl->fd, data, hdl->rtodo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ hdl->sa.eof = 1;
+ perror("aucat_rmsg: read");
+ }
+ return 0;
+ }
+ if (n == 0) {
+ fprintf(stderr, "aucat_rmsg: eof\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ hdl->rtodo -= n;
+ }
+ return 1;
+}
+
+/*
+ * write a message, return 0 if blocked
+ */
+int
+aucat_wmsg(struct aucat_hdl *hdl)
+{
+ ssize_t n;
+ unsigned char *data;
+
+ while (hdl->wtodo > 0) {
+ data = (unsigned char *)&hdl->wmsg;
+ data += sizeof(struct amsg) - hdl->wtodo;
+ while ((n = write(hdl->fd, data, hdl->wtodo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ hdl->sa.eof = 1;
+ perror("aucat_wmsg: write");
+ }
+ return 0;
+ }
+ hdl->wtodo -= n;
+ }
+ return 1;
+}
+
+/*
+ * execute the next message, return 0 if blocked
+ */
+int
+aucat_runmsg(struct aucat_hdl *hdl)
+{
+ if (!aucat_rmsg(hdl))
+ return 0;
+ switch (hdl->rmsg.cmd) {
+ case AMSG_DATA:
+ if (hdl->rmsg.u.data.size == 0 ||
+ hdl->rmsg.u.data.size % hdl->rbpf) {
+ fprintf(stderr, "aucat_read: bad data message\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ hdl->rstate = STATE_DATA;
+ hdl->rtodo = hdl->rmsg.u.data.size;
+ break;
+ case AMSG_MOVE:
+ hdl->maxwrite += hdl->rmsg.u.ts.delta * (int)hdl->wbpf;
+ sa_onmove_cb(&hdl->sa, hdl->rmsg.u.ts.delta);
+ hdl->rstate = STATE_MSG;
+ hdl->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_GETPAR:
+ case AMSG_ACK:
+ hdl->rstate = STATE_IDLE;
+ hdl->rtodo = 0xdeadbeef;
+ break;
+ default:
+ fprintf(stderr, "aucat_read: unknown mesg\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return 1;
+}
+
+void
+aucat_close(struct sa_hdl *sh)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+
+ while (close(hdl->fd) < 0 && errno == EINTR)
+ ; /* nothing */
+ free(hdl);
+}
+
+int
+aucat_start(struct sa_hdl *sh)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+ struct sa_par par;
+
+ /*
+ * save bpf
+ */
+ if (!sa_getpar(&hdl->sa, &par))
+ return 0;
+ hdl->wbpf = par.bps * par.pchan;
+ hdl->rbpf = par.bps * par.rchan;
+ hdl->maxwrite = hdl->wbpf * par.bufsz;
+
+ AMSG_INIT(&hdl->wmsg);
+ hdl->wmsg.cmd = AMSG_START;
+ hdl->wtodo = sizeof(struct amsg);
+ if (!aucat_wmsg(hdl))
+ return 0;
+ hdl->rstate = STATE_MSG;
+ hdl->rtodo = sizeof(struct amsg);
+ if (fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) {
+ perror("aucat_start: fcntl(0)");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return 1;
+}
+
+int
+aucat_stop(struct sa_hdl *sh)
+{
+#define ZERO_MAX 0x400
+ static unsigned char zero[ZERO_MAX];
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+ unsigned n, count, todo;
+
+ if (fcntl(hdl->fd, F_SETFL, 0) < 0) {
+ perror("aucat_stop: fcntl(0)");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+
+ /*
+ * complete data block in progress
+ */
+ if (hdl->wstate != STATE_IDLE) {
+ todo = (hdl->wstate == STATE_MSG) ?
+ hdl->wmsg.u.data.size : hdl->wtodo;
+ hdl->maxwrite = todo;
+ memset(zero, 0, ZERO_MAX);
+ while (todo > 0) {
+ count = todo;
+ if (count > ZERO_MAX)
+ count = ZERO_MAX;
+ n = aucat_write(&hdl->sa, zero, count);
+ if (n == 0)
+ return 0;
+ todo -= n;
+ }
+ }
+
+ /*
+ * send stop message
+ */
+ AMSG_INIT(&hdl->wmsg);
+ hdl->wmsg.cmd = AMSG_STOP;
+ hdl->wtodo = sizeof(struct amsg);
+ if (!aucat_wmsg(hdl))
+ return 0;
+
+ /*
+ * wait for the STOP ACK
+ */
+ while (hdl->rstate != STATE_IDLE) {
+ switch (hdl->rstate) {
+ case STATE_MSG:
+ if (!aucat_runmsg(hdl))
+ return 0;
+ break;
+ case STATE_DATA:
+ if (!aucat_read(&hdl->sa, zero, ZERO_MAX))
+ return 0;
+ break;
+ }
+ }
+ return 1;
+}
+
+int
+aucat_setpar(struct sa_hdl *sh, struct sa_par *par)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+
+ AMSG_INIT(&hdl->wmsg);
+ hdl->wmsg.cmd = AMSG_SETPAR;
+ hdl->wmsg.u.par.bits = par->bits;
+ hdl->wmsg.u.par.bps = par->bps;
+ hdl->wmsg.u.par.sig = par->sig;
+ hdl->wmsg.u.par.le = par->le;
+ hdl->wmsg.u.par.msb = par->msb;
+ hdl->wmsg.u.par.rate = par->rate;
+ hdl->wmsg.u.par.bufsz = par->bufsz;
+ hdl->wmsg.u.par.xrun = par->xrun;
+ hdl->wmsg.u.par.mode = hdl->sa.mode;
+ if (hdl->sa.mode & SA_REC)
+ hdl->wmsg.u.par.rchan = par->rchan;
+ if (hdl->sa.mode & SA_PLAY)
+ hdl->wmsg.u.par.pchan = par->pchan;
+ hdl->wtodo = sizeof(struct amsg);
+ if (!aucat_wmsg(hdl))
+ return 0;
+ return 1;
+}
+
+int
+aucat_getpar(struct sa_hdl *sh, struct sa_par *par)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+
+ AMSG_INIT(&hdl->rmsg);
+ hdl->wmsg.cmd = AMSG_GETPAR;
+ hdl->wtodo = sizeof(struct amsg);
+ if (!aucat_wmsg(hdl))
+ return 0;
+ hdl->rtodo = sizeof(struct amsg);
+ if (!aucat_rmsg(hdl))
+ return 0;
+ if (hdl->rmsg.cmd != AMSG_GETPAR) {
+ fprintf(stderr, "aucat_getpar: protocol err\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ par->bits = hdl->rmsg.u.par.bits;
+ par->bps = hdl->rmsg.u.par.bps;
+ par->sig = hdl->rmsg.u.par.sig;
+ par->le = hdl->rmsg.u.par.le;
+ par->msb = hdl->rmsg.u.par.msb;
+ par->rate = hdl->rmsg.u.par.rate;
+ par->bufsz = hdl->rmsg.u.par.bufsz;
+ par->xrun = hdl->rmsg.u.par.xrun;
+ par->round = hdl->rmsg.u.par.round;
+ if (hdl->sa.mode & SA_PLAY)
+ par->pchan = hdl->rmsg.u.par.pchan;
+ if (hdl->sa.mode & SA_REC)
+ par->rchan = hdl->rmsg.u.par.rchan;
+ return 1;
+}
+
+int
+aucat_getcap(struct sa_hdl *sh, struct sa_cap *cap)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+
+ AMSG_INIT(&hdl->rmsg);
+ hdl->wmsg.cmd = AMSG_GETCAP;
+ hdl->wtodo = sizeof(struct amsg);
+ if (!aucat_wmsg(hdl))
+ return 0;
+ hdl->rtodo = sizeof(struct amsg);
+ if (!aucat_rmsg(hdl))
+ return 0;
+ if (hdl->rmsg.cmd != AMSG_GETCAP) {
+ fprintf(stderr, "aucat_getcap: protocol err\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ cap->enc[0].bits = hdl->rmsg.u.cap.bits;
+ cap->enc[0].bps = SA_BPS(hdl->rmsg.u.cap.bits);
+ cap->enc[0].sig = 1;
+ cap->enc[0].le = SA_LE_NATIVE;
+ cap->enc[0].msb = 1;
+ cap->rchan[0] = hdl->rmsg.u.cap.rchan;
+ cap->pchan[0] = hdl->rmsg.u.cap.pchan;
+ cap->rate[0] = hdl->rmsg.u.cap.rate;
+ cap->confs[0].enc = 1;
+ cap->confs[0].pchan = (hdl->sa.mode & SA_PLAY) ? 1 : 0;
+ cap->confs[0].rchan = (hdl->sa.mode & SA_REC) ? 1 : 0;
+ cap->confs[0].rate = 1;
+ cap->nconf = 1;
+ return 1;
+}
+
+size_t
+aucat_read(struct sa_hdl *sh, void *buf, size_t len)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+ ssize_t n;
+
+ while (hdl->rstate != STATE_DATA) {
+ switch (hdl->rstate) {
+ case STATE_MSG:
+ if (!aucat_runmsg(hdl))
+ return 0;
+ break;
+ case STATE_IDLE:
+ fprintf(stderr, "aucat_read: unexpected idle\n");
+ break;
+ }
+ }
+ if (len > hdl->rtodo)
+ len = hdl->rtodo;
+ while ((n = read(hdl->fd, buf, len)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ hdl->sa.eof = 1;
+ perror("aucat_read: read");
+ }
+ return 0;
+ }
+ if (n == 0) {
+ fprintf(stderr, "aucat_read: eof\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ hdl->rtodo -= n;
+ if (hdl->rtodo == 0) {
+ hdl->rstate = STATE_MSG;
+ hdl->rtodo = sizeof(struct amsg);
+ }
+ return n;
+}
+
+size_t
+aucat_write(struct sa_hdl *sh, void *buf, size_t len)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+ unsigned sz;
+ ssize_t n;
+
+ switch (hdl->wstate) {
+ case STATE_IDLE:
+ sz = (len < AMSG_DATAMAX) ? len : AMSG_DATAMAX;
+ sz -= sz % hdl->wbpf;
+ if (sz == 0)
+ sz = hdl->wbpf;
+ hdl->wstate = STATE_MSG;
+ hdl->wtodo = sizeof(struct amsg);
+ hdl->wmsg.cmd = AMSG_DATA;
+ hdl->wmsg.u.data.size = sz;
+ /* PASSTHROUGH */
+ case STATE_MSG:
+ if (!aucat_wmsg(hdl))
+ return 0;
+ hdl->wstate = STATE_DATA;
+ hdl->wtodo = hdl->wmsg.u.data.size;
+ /* PASSTHROUGH */
+ case STATE_DATA:
+ if (hdl->maxwrite <= 0)
+ return 0;
+ if (len > hdl->maxwrite)
+ len = hdl->maxwrite;
+ if (len > hdl->wtodo)
+ len = hdl->wtodo;
+ if (len == 0) {
+ fprintf(stderr, "aucat_write: len == 0\n");
+ abort();
+ }
+ while ((n = write(hdl->fd, buf, len)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ hdl->sa.eof = 1;
+ perror("aucat_read: read");
+ }
+ return 0;
+ }
+ hdl->maxwrite -= n;
+ hdl->wtodo -= n;
+ if (hdl->wtodo == 0) {
+ hdl->wstate = STATE_IDLE;
+ hdl->wtodo = 0xdeadbeef;
+ }
+ return n;
+ default:
+ fprintf(stderr, "aucat_read: bad state\n");
+ abort();
+ }
+}
+
+int
+aucat_pollfd(struct sa_hdl *sh, struct pollfd *pfd, int events)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+
+ hdl->events = events;
+ if (hdl->maxwrite <= 0)
+ events &= ~POLLOUT;
+ if (hdl->rstate != STATE_DATA)
+ events |= POLLIN;
+ pfd->fd = hdl->fd;
+ pfd->events = events;
+ return 1;
+}
+
+int
+aucat_revents(struct sa_hdl *sh, struct pollfd *pfd)
+{
+ struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
+ int revents = pfd->revents;
+
+ if (revents & POLLIN) {
+ while (hdl->rstate == STATE_MSG) {
+ if (!aucat_runmsg(hdl)) {
+ revents &= ~POLLIN;
+ break;
+ }
+ }
+ }
+ if (revents & POLLOUT) {
+ if (hdl->maxwrite <= 0)
+ revents &= ~POLLOUT;
+ }
+ return revents & hdl->events;
+}
diff --git a/lib/libsa/libsa.3 b/lib/libsa/libsa.3
new file mode 100644
index 00000000000..e1b76ac2fea
--- /dev/null
+++ b/lib/libsa/libsa.3
@@ -0,0 +1,706 @@
+.\" $OpenBSD: libsa.3,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+.\"
+.\" Copyright (c) 2007 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.
+.\"
+.Dd $Mdocdate: October 26 2008 $
+.Dt LIBSA 3
+.Os
+.Sh NAME
+.Nm libsa
+.Nd interface to bidirectional audio streams
+.Sh SYNOPSIS
+.Fd #include <libsa.h>
+.Ft "struct sa_hdl *"
+.Fn "sa_open" "char *name" "unsigned mode" "int nbio_flag"
+.Ft "int"
+.Fn "sa_close" "struct sa_hdl *hdl"
+.Ft "int"
+.Fn "sa_setpar" "struct sa_hdl *hdl" "struct sa_par *par"
+.Ft "int"
+.Fn "sa_getpar" "struct sa_hdl *hdl" "struct sa_par *par"
+.Ft "int"
+.Fn "sa_getcap" "struct sa_hdl *hdl" "struct sa_cap *cap"
+.Ft "int"
+.Fn "sa_start" "struct sa_hdl *hdl"
+.Ft "int"
+.Fn "sa_stop" "struct sa_hdl *hdl"
+.Ft "size_t"
+.Fn "sa_read" "struct sa_hdl *hdl" "void *addr" "size_t nbytes"
+.Ft "size_t"
+.Fn "sa_write" "struct sa_hdl *hdl" "void *addr" "size_t nbytes"
+.Ft "void"
+.Fn "sa_onmove" "struct sa_hdl *hdl" "void (*cb)(void *arg, int delta)" "void *arg"
+.Ft "int"
+.Fn "sa_nfds" "struct sa_hdl *hdl"
+.Ft "int"
+.Fn "sa_pollfd" "struct sa_hdl *hdl" "struct pollfd *pfd" "int events"
+.Ft "int"
+.Fn "sa_revents" "struct sa_hdl *hdl" "struct pollfd *pfd"
+.Ft "int"
+.Fn "sa_eof" "struct sa_hdl *hdl"
+.Ft "void"
+.Fn "sa_initpar" "struct sa_par *par"
+.\"Fd #define SA_BPS(bits)
+.\"Fd #define SA_LE_NATIVE
+.Sh DESCRIPTION
+The
+.Nm
+library allows user processes to access
+.Xr audio 4
+hardware and the
+.Xr aucat 1
+audio server in a uniform way.
+It supports full-duplex operation, and when
+used with the
+.Xr aucat 1
+server it supports resampling and format
+conversions on the fly.
+.Ss Opening and closing an audio stream
+First the application must call the
+.Fn sa_open
+routine to obtain a handle representing the newly created stream;
+later it will be passed as the
+.Ar hdl
+argument of most other functions.
+The
+.Fn sa_open
+routine first tries to connect to the
+.Xr aucat 1
+audio server.
+If that fails, it then tries to use the
+.Xr audio 4
+hardware device.
+The
+.Ar name
+parameter gives the path of the
+.Xr aucat 1
+socket or the
+.Xr audio 4
+device.
+In most cases it should be left to NULL to allow
+the user to select it using the
+.Ev AUDIODEVICE
+environment variable.
+.Pp
+The
+.Ar mode
+parameter gives the direction of the stream.
+The following are supported:
+.Bl -tag -width "SA_PLAY | SA_REC"
+.It SA_PLAY
+The stream is play-only; data written to the stream will be played
+by the hardware.
+.It SA_REC
+The stream is record-only; recorded samples by the hardware
+must be read from the stream.
+.It SA_PLAY | SA_REC
+The stream plays and records synchronously; this means that
+the n-th recorded sample was physically sampled exactly when
+the n-th played sample was actually played.
+.El
+.Pp
+If the
+.Ar nbio_flag
+argument is true (i.e. non-zero), then the
+.Fn sa_read
+and
+.Fn sa_write
+routines (see below) will be non-blocking.
+.Pp
+The
+.Fn sa_close
+routine closes the stream and frees all allocated resources
+associated with the
+.Nm
+handle.
+.Ss Negotiating audio parameters
+Audio streams always use linear interleaved encoding.
+The set of parameters of the stream that can be controlled
+is given by the following structure:
+.Bd -literal -offset -indent
+struct sa_par {
+ unsigned bits; /* bits per sample */
+ unsigned bps; /* bytes per sample */
+ unsigned sig; /* 1 = signed, 0 = unsigned */
+ unsigned le; /* 1 = LE, 0 = BE byte order */
+ unsigned msb; /* 1 = MSB, 0 = LSB aligned */
+ unsigned rchan; /* number channels for recording */
+ unsigned pchan; /* number channels for playback */
+ unsigned rate; /* frames per second */
+ unsigned bufsz; /* frames in the stream buffer */
+ unsigned round; /* optimal buffer size divisor */
+#define SA_IGNORE 0 /* pause during xrun */
+#define SA_SYNC 1 /* resync after xrun */
+#define SA_ERROR 2 /* terminate on xrun */
+ unsigned xrun; /* what to do on overrun/underrun */
+};
+.Ed
+.Pp
+The parameters are as follows:
+.Bl -tag -width "round"
+.It Va bits
+Number of bits per sample: must be between 1 and 32.
+.It Va bps
+Bytes per samples; if specified, it must be large enough to hold all bits.
+By default it's set to the smallest power of two large enough to hold
+.Va bits .
+.It Va sig
+If set (i.e. non-zero) then the samples are signed, else unsigned.
+.It Va le
+If set, then the byte order is little endian, else big endian;
+it's meaningful only if
+.Va bps \*(Gt 1 .
+.It Va msb
+If set, then the
+.Va bits
+bits are aligned in the packet to the most significant bit
+(i.e. lower bits are padded),
+else to the least significant bit
+(i.e. higher bits are padded);
+it's meaningful only if
+.Va bits \*(Lt bps * 8 .
+.It Va rchan
+The number of recorded channels; meaningful only if
+.Va SA_REC
+mode was selected.
+.It Va pchan
+The number of played channels; meaningful only if
+.Va SA_PLAY
+mode was selected.
+.It Va rate
+The sampling frequency in Hz.
+.It Va bufsz
+The number of frames that will be buffered for both
+play and record directions.
+.It Va round
+Optimal number of frames that the application buffers
+should be a multiple of, to get best performance;
+it is read-only.
+.It Va xrun
+The action when the client doesn't accept
+recored data or doesn't provide data to play fast enough;
+it can be set to one of the
+.Va SA_IGNORE ,
+.Va SA_SYNC
+or
+.Va SA_ERROR
+constants.
+.El
+.Pp
+There are two approaches to negotiate parameters of the stream:
+.Bl -bullet
+.It
+Advanced applications may use native parameters of
+the audio subsystem.
+This is the best approach from a performance point of view
+since it involves no extra format conversions.
+The
+.Fn sa_getcap ,
+described below,
+can be used to get the list of native parameter sets and then
+.Fn sa_initpar
+and
+.Fn sa_setpar
+can be used to select a working set.
+.It
+Simpler applications that do not have performance constraints may set up
+the audio subsystem to use their own parameters.
+The
+.Va sa_par
+structure must be initialized using the
+.Fn sa_initpar
+routine, filled with the desired parameters and
+the
+.Fn sa_setpar
+routine must be called.
+Finally, the
+.Fn sa_getpar
+routine can be used to ensure that parameters were actually
+accepted.
+.El
+.Pp
+If
+.Nm
+is used to connect to the
+.Xr aucat 1
+server, a transparent emulation layer will
+automatically be set up, and in this case any
+parameters are supported.
+.Pp
+To ease filling the
+.Va sa_par
+structure, the
+following macros can be used:
+.Bl -tag -width "SA_BPS(bits)"
+.It "SA_BPS(bits)"
+Return the smallest value for
+.Va bps
+that is a power of two and that is large enough to
+hold
+.Va bits .
+.It "SA_LE_NATIVE"
+Can be used to set the
+.Va le
+parameter when native byte order is required.
+.El
+.Pp
+Note that (once initialized with the
+.Fn sa_initpar
+routine), not all fields of the
+.Va sa_par
+structure must be filled; it is recommended to fill only
+the required parameters, as other ones will default to
+reasonable values.
+This approach also ensures that if, in the future, newer parameters
+are added, then older unaware applications will continue to
+behave correctly.
+.Ss Getting stream capabilities
+Advanced applications can fetch the native
+parameters of the audio subsystem and then choose parameters
+optimal for both the application and the audio subsystem.
+In this case applications must be able to do
+the necessary format conversions.
+The
+.Va sa_cap
+structure, filled by the
+.Fn sa_getcap
+routine, contains the list of parameter configurations.
+Each configuration contains multiple parameter sets.
+The application must examine all configurations, and
+choose its parameter set from
+.Em one
+of the configurations.
+Parameters of different configurations
+.Em are not
+usable together.
+.Bd -literal
+struct sa_cap {
+ struct sa_enc { /* allowed encodings */
+ unsigned bits;
+ unsigned bps;
+ unsigned sig;
+ unsigned le;
+ unsigned msb;
+ } enc[SA_NENC];
+ unsigned rchan[SA_NCHAN]; /* allowed rchans */
+ unsigned pchan[SA_NCHAN]; /* allowed pchans */
+ unsigned rate[SA_NRATE]; /* allowed rates */
+ unsigned nconf; /* num. of confs[] */
+ struct sa_conf {
+ unsigned enc; /* bitmask of enc[] indexes */
+ unsigned rchan; /* bitmask of rchan[] indexes */
+ unsigned pchan; /* bitmask of pchan[] indexes */
+ unsigned rate; /* bitmask of rate[] indexes */
+ } confs[SA_NCONF];
+};
+.Ed
+.Pp
+The parameters are as follows:
+.Bl -tag -width "rchan[SA_NCHAN]"
+.It Va enc[SA_NENC]
+Array of supported encodings.
+The tuple of
+.Va bits ,
+.Va bps ,
+.Va sig ,
+.Va le
+and
+.Va msb
+parameters are usable in the corresponding parameters
+of the
+.Va sa_par
+structure.
+.It Va rchan[SA_NENC]
+Array of supported channel numbers for recording usable
+in the
+.Va sa_par
+structure.
+.It Va pchan[SA_NENC]
+Array of supported channel numbers for playback usable
+in the
+.Va sa_par
+structure.
+.It Va rate[SA_NRATE]
+Array of supported sample rates usable
+in the
+.Va sa_par
+structure.
+.It Va nconf
+Number of different configurations available, i.e. number
+of filled elements of the
+.Va confs[]
+array.
+.It Va confs[SA_NCONF]
+Array of available configurations.
+Each configuration contains bitmasks indicating which
+elements of the above parameter arrays are valid for the
+given configuration.
+For instance, if the second bit of
+.Va rate
+is set, in the
+.Va sa_conf
+structure, then the second element of the
+.Va rate[SA_NRATE]
+array of the
+.Va sa_cap
+structure is valid for this configuration.
+.El
+.Ss Starting and stopping the stream
+The
+.Fn sa_start
+routine puts the stream in a waiting state:
+the stream will wait for playback data to be provided
+(using the
+.Fn sa_write
+routine).
+Once enough data is queued to ensure that play buffers
+will not underrun, actual playback is started automatically.
+If record mode only is selected, then recording starts
+immediately.
+In full-duplex mode, playback and recording will start
+synchronously as soon as enough data to play is available.
+.Pp
+The
+.Fn sa_stop
+routine stops playback and recording and puts the audio subsystem
+in the same state as after
+.Fn sa_open
+is called.
+Samples in the play buffers are not discarded, and will continue to
+be played after
+.Fn sa_stop
+returns.
+.Ss Playing and recording
+When record mode is selected, the
+.Fn sa_read
+routine must be called to retrieve recorded data; it must be called
+often enough to ensure that internal buffers will not overrun.
+It will store at most
+.Ar nbytes
+bytes at the
+.Ar addr
+location and return the number of bytes stored.
+Unless the
+.Ar nbio_flag
+flag is set, it will block until data becomes available and
+will return zero only on error.
+.Pp
+Similarly, when play mode is selected, the
+.Fn sa_write
+routine must be called to provide data to play.
+Unless the
+.Ar nbio_flag
+is set,
+.Fn sa_write
+will block until the requested amount of data is written.
+.Ss Non-blocking mode operation
+If the
+.Ar nbio_flag
+is set on
+.Fn sa_open ,
+then the
+.Fn sa_read
+and
+.Fn sa_write
+routines will never block; if no data is available, they will
+return zero immediately.
+.Pp
+Note that non-blocking mode must be used on bidirectional streams.
+For instance, if recording is blocked in
+.Fn sa_read
+then, even if samples can be played,
+.Fn sa_write
+cannot be called and the play buffers may underrun.
+.Pp
+To avoid busy loops when non-blocking mode is used, the
+.Xr poll 2
+system call can be used to check if data can be
+read from or written to the stream.
+The
+.Fn sa_pollfd
+routine fills the array
+.Ar pfd
+of
+.Va pollfd
+structures, used by
+.Xr poll 2 ,
+with
+.Ar events ;
+the latter is a bit-mask of
+.Va POLLIN
+and
+.Va POLLOUT
+constants; refer to
+.Xr poll 2
+for more details.
+.Fn sa_pollfd
+returns the number of
+.Va pollfd
+structures filled.
+The
+.Fn sa_revents
+routine returns the bit-mask set by
+.Xr poll 2
+in the
+.Va pfd
+array of
+.Va pollfd
+structures.
+If
+.Va POLLIN
+is set,
+.Fn sa_read
+can be called without blocking.
+If
+.Va POLLOUT
+is set,
+.Fn sa_write
+can be called without blocking.
+.Pp
+The
+.Fn sa_nfds
+routine returns the number of
+.Va pollfd
+structures the caller must preallocate in order to be sure
+that
+.Fn sa_pollfd
+will never overrun.
+.Ss Synchronizing non-audio events to the stream in real-time
+In order to perform actions at precise positions of the stream,
+such as displaying video in sync with the audio stream,
+the application must be notified in real-time of the exact
+position in the stream the hardware is processing.
+.Pp
+The
+.Fn sa_onmove
+routine can be used to register the
+.Va cb
+call-back function that will be called by the
+.Nm
+library at regular time intervals to notify the application
+the position in the stream changed.
+The
+.Va delta
+argument contains the number of frames the hardware moved in the stream
+since the last call of
+.Va cb .
+The value of the
+.Va arg
+pointer is passed to the call-back and can contain anything.
+.Pp
+If desired, the application can maintain the current position by
+starting from zero (when
+.Fn sa_start
+is called) and adding to the current position
+.Va delta
+every time
+.Fn cb
+is called.
+Note that at the beginning the current position might be
+negative indicating that the stream is being buffered,
+but has not reached the hardware yet.
+.Ss Measuring the latency and buffers usage
+The playback latency is the delay it will take for the
+frame just written to become audible, expressed in number of frames.
+The exact playback
+latency can be obtained by subtracting the current position
+from the number of frames written.
+Once playback is actually started (first sample audible)
+the latency will never exceed the
+.Va bufsz
+parameter (see the sections above).
+There's a phase during which
+.Fn sa_write
+only queues data;
+once there's enough data, actual playback starts.
+During this phase the current position is negative and
+the latency might be longer than
+.Va bufsz .
+.Pp
+In any cases, at most
+.Va bufsz
+frames are buffered.
+This value takes into account all buffers,
+including device, kernel and socket buffers.
+During the buffering phase, the number of frames stored
+is equal to the number of frames written.
+Once playback is started, it is equal to the number of frames
+written minus the current position.
+.Pp
+The recording latency is obtained similarly, by subtracting
+the number of frames read from the current position.
+.Pp
+It is strongly discouraged to use the latency and/or the buffer
+usage for anything but monitoring.
+Especially, note that
+.Fn sa_write
+might block even if there is buffer space left;
+using the buffer usage to guess if
+.Fn sa_write
+would block is false and leads to unreliable programs \(en consider using
+.Xr poll 2
+for this.
+.Ss Handling buffer overruns and underruns
+When the application cannot accept recorded data fast enough,
+the record buffer (of size
+.Va bufsz )
+might overrun; in this case recorded data is lost.
+Similarly if the application cannot provide data to play
+fast enough, the play buffer underruns and silence is played
+instead.
+Depending on the
+.Va xrun
+parameter of the
+.Va sa_par
+structure, the audio subsystem will behave as follows:
+.Bl -tag -width "SA_IGNORE"
+.It SA_IGNORE
+The stream is paused during overruns and underruns,
+thus the current position (obtained through
+.Va sa_onmove )
+stops being incremented.
+Once the overrun and/or underrun condition is gone, the stream is unpaused;
+play and record are always kept in sync.
+With this mode, the application cannot notice
+underruns and/or overruns and shouldn't care about them.
+.Pp
+This mode is the default.
+It's suitable for applications,
+like audio players and telephony, where time
+is not important and overruns or underruns are not short.
+.It SA_SYNC
+If the play buffer underruns, then silence is played,
+but in order to reach the right position in time,
+the same amount of written samples will be
+discarded once the application is unblocked.
+Similarly, if the record buffer overruns, then
+samples are discarded, but the same amount of silence will be
+returned later.
+The current position (obtained through
+.Va sa_onmove )
+is still incremented.
+When the play buffer underruns the play latency might become negative;
+when the record buffer overruns, the record latency might become
+larger than
+.Va bufsz .
+.Pp
+This mode is suitable for applications, like music production,
+where time is important and where underruns or overruns are
+short and rare.
+.It SA_ERROR
+With this mode, on the first play buffer underrun or
+record buffer overrun, the stream is terminated and
+no other function than
+.Fn sa_close
+will succeed.
+.Pp
+This mode is mostly useful for testing; portable
+applications shouldn't depend on it, since it's not available
+on other systems.
+.El
+.Ss Error handling
+Errors related to the audio subsystem
+(like hardware errors, dropped connections) and
+programming errors (e.g. call to
+.Fn sa_read
+on a play-only stream) are considered fatal.
+Once an error occurs, all functions taking a
+.Va sa_hdl
+argument, except
+.Fn sa_close
+and
+.Fn sa_eof ,
+stop working (i.e. always return 0).
+.Pp
+The
+.Fn sa_eof
+routine can be used at any stage;
+it returns 0 if there's no pending error, and a non-zero
+value if there's an error.
+.Sh RETURN VALUES
+The
+.Fn sa_open
+function returns the newly created handle on success or NULL
+on failure.
+The
+.Fn sa_setpar ,
+.Fn sa_getpar ,
+.Fn sa_start ,
+and
+.Fn sa_stop ,
+functions return 1 on success and 0 on failure.
+The
+.Fn sa_read
+and
+.Fn sa_write
+functions return the number of bytes transfered.
+.Sh ENVIRONMENT
+.Bl -tag -width "AUDIODEVICEXXX" -compact
+.It Ev AUDIODEVICE
+Path to the
+.Xr aucat 1
+socket to connect to, or the
+.Xr audio 4
+device to use.
+.El
+.Sh FILES
+.Bl -tag -width "/tmp/aucat.sockXXX" -compact
+.It Pa /tmp/aucat.sock
+Default path to
+.Xr aucat 1
+socket to connect to.
+.It Pa /dev/audio
+Default
+.Xr audio 4
+device to use.
+.El
+.\".Sh EXAMPLES
+.\".Bd -literal -offset indent
+.\".Ed
+.Sh SEE ALSO
+.Xr aucat 1 ,
+.Xr audio 4
+.Sh BUGS
+The
+.Xr audio 4
+driver cannot drain playback buffers in the background, thus if
+.Nm
+is used to directly access an
+.Xr audio 4
+device,
+the
+.Fn sa_stop
+routine will stop playback immediately.
+.Pp
+The
+.Xr aucat 1
+server doesn't implement flow control (for performance reasons).
+If the application doesn't consume recorded data fast enough then
+.Dq "control messages"
+are delayed (or lost) and consequently
+overruns and underruns stay unnoticed by the application in the
+.Va SA_SYNC
+mode (overruns and underruns are handled on the server side, so
+synchronization is never lost).
+.Pp
+The
+.Fn sa_open ,
+.Fn sa_setpar ,
+.Fn sa_getpar ,
+.Fn sa_start
+and
+.Fn sa_stop
+routines may block for a very short period of time, thus they should
+not be abused during performance.
diff --git a/lib/libsa/libsa.c b/lib/libsa/libsa.c
new file mode 100644
index 00000000000..19efa3a70fb
--- /dev/null
+++ b/lib/libsa/libsa.c
@@ -0,0 +1,517 @@
+/* $OpenBSD: libsa.c,v 1.1 2008/10/26 08:49:44 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 <sys/types.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "libsa_priv.h"
+
+#define SA_PAR_MAGIC 0x83b905a4
+
+int
+sa_initpar(struct sa_par *par)
+{
+ memset(par, 0xff, sizeof(struct sa_par));
+ par->__magic = SA_PAR_MAGIC;
+ return 1;
+}
+
+/*
+ * Generate a string corresponding to the encoding in par,
+ * return the length of the resulting string
+ */
+int
+sa_enctostr(struct sa_par *par, char *ostr)
+{
+ char *p = ostr;
+
+ *p++ = par->sig ? 's' : 'u';
+ if (par->bits > 9)
+ *p++ = '0' + par->bits / 10;
+ *p++ = '0' + par->bits % 10;
+ if (par->bps > 1) {
+ *p++ = par->le ? 'l' : 'b';
+ *p++ = 'e';
+ if (par->bps != SA_BPS(par->bits) ||
+ par->bits < par->bps * 8) {
+ *p++ = par->bps + '0';
+ if (par->bits < par->bps * 8) {
+ *p++ = par->msb ? 'm' : 'l';
+ *p++ = 's';
+ *p++ = 'b';
+ }
+ }
+ }
+ *p++ = '\0';
+ return p - ostr - 1;
+}
+
+/*
+ * Parse an encoding string, examples: s8, u8, s16, s16le, s24be ...
+ * Retrun the number of bytes consumed
+ */
+int
+sa_strtoenc(struct sa_par *par, char *istr)
+{
+ char *p = istr;
+ int i, sig, bits, le, bps, msb;
+
+#define IS_SEP(c) \
+ (((c) < 'a' || (c) > 'z') && \
+ ((c) < 'A' || (c) > 'Z') && \
+ ((c) < '0' || (c) > '9'))
+
+ /*
+ * get signedness
+ */
+ if (*p == 's') {
+ sig = 1;
+ } else if (*p == 'u') {
+ sig = 0;
+ } else
+ return 0;
+ p++;
+
+ /*
+ * get number of bits per sample
+ */
+ bits = 0;
+ for (i = 0; i < 2; i++) {
+ if (*p < '0' || *p > '9')
+ break;
+ bits = (bits * 10) + *p - '0';
+ p++;
+ }
+ if (bits < 1 || bits > 32)
+ return 0;
+ bps = SA_BPS(bits);
+ le = SA_LE_NATIVE;
+ msb = 1;
+
+ /*
+ * get (optionnal) endianness
+ */
+ if (p[0] == 'l' && p[1] == 'e') {
+ le = 1;
+ p += 2;
+ } else if (p[0] == 'b' && p[1] == 'e') {
+ le = 0;
+ p += 2;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+
+ /*
+ * get (optionnal) number of bytes
+ */
+ if (*p >= '1' && *p <= '4') {
+ bps = *p - '0';
+ if (bps * 8 < bits)
+ return 0;
+ p++;
+
+ /*
+ * get (optionnal) alignement
+ */
+ if (p[0] == 'm' && p[1] == 's' && p[2] == 'b') {
+ msb = 1;
+ p += 3;
+ } else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b') {
+ msb = 0;
+ p += 3;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+ } else if (!IS_SEP(*p))
+ return 0;
+
+done:
+ par->msb = msb;
+ par->sig = sig;
+ par->bits = bits;
+ par->bps = bps;
+ par->le = le;
+ return p - istr;
+}
+
+
+struct sa_hdl *
+sa_open(char *str, unsigned mode, int nbio)
+{
+ struct sa_hdl *hdl;
+
+ if ((mode & (SA_PLAY | SA_REC)) == 0)
+ return NULL;
+ hdl = sa_open_aucat(str, mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ hdl = sa_open_sun(str, mode, nbio);
+ if (hdl != NULL)
+ return hdl;
+ return NULL;
+}
+
+void
+sa_create(struct sa_hdl *hdl, struct sa_ops *ops, unsigned mode, int nbio)
+{
+#ifdef DEBUG
+ char *dbg;
+
+ dbg = getenv("LIBSA_DEBUG");
+ if (!dbg || sscanf(dbg, "%u", &hdl->debug) != 1)
+ hdl->debug = 0;
+#endif
+ hdl->ops = ops;
+ hdl->mode = mode;
+ hdl->nbio = nbio;
+ hdl->started = 0;
+ hdl->eof = 0;
+ hdl->cb_pos = 0;
+}
+
+void
+sa_close(struct sa_hdl *hdl)
+{
+ return hdl->ops->close(hdl);
+}
+
+int
+sa_start(struct sa_hdl *hdl)
+{
+ if (hdl->eof) {
+ fprintf(stderr, "sa_start: eof\n");
+ return 0;
+ }
+ if (hdl->started) {
+ fprintf(stderr, "sa_start: already started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+#ifdef DEBUG
+ if (!sa_getpar(hdl, &hdl->par))
+ return 0;
+ hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->realpos = 0;
+ gettimeofday(&hdl->tv, NULL);
+#endif
+ if (!hdl->ops->start(hdl))
+ return 0;
+ hdl->started = 1;
+ return 1;
+}
+
+int
+sa_stop(struct sa_hdl *hdl)
+{
+ if (hdl->eof) {
+ fprintf(stderr, "sa_stop: eof\n");
+ return 0;
+ }
+ if (!hdl->started) {
+ fprintf(stderr, "sa_stop: not started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (!hdl->ops->stop(hdl))
+ return 0;
+#ifdef DEBUG
+ if (hdl->debug)
+ fprintf(stderr,
+ "libsa: polls: %llu, written = %llu, read: %llu\n",
+ hdl->pollcnt, hdl->wcnt, hdl->rcnt);
+#endif
+ hdl->started = 0;
+ return 1;
+}
+
+int
+sa_setpar(struct sa_hdl *hdl, struct sa_par *par)
+{
+ if (hdl->eof) {
+ fprintf(stderr, "sa_setpar: eof\n");
+ return 0;
+ }
+ if (par->__magic != SA_PAR_MAGIC) {
+ fprintf(stderr,
+ "sa_setpar: use of uninitialized sa_par structure\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (hdl->started) {
+ fprintf(stderr, "sa_setpar: already started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (par->rate != (unsigned)~0 && par->bufsz == (unsigned)~0)
+ par->bufsz = par->rate * 200 / 1000;
+ return hdl->ops->setpar(hdl, par);
+}
+
+int
+sa_getpar(struct sa_hdl *hdl, struct sa_par *par)
+{
+ if (hdl->eof) {
+ fprintf(stderr, "sa_getpar: eof\n");
+ return 0;
+ }
+ if (hdl->started) {
+ fprintf(stderr, "sa_getpar: already started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (!hdl->ops->getpar(hdl, par)) {
+ par->__magic = 0;
+ return 0;
+ }
+ par->__magic = 0;
+ return 1;
+}
+
+int
+sa_getcap(struct sa_hdl *hdl, struct sa_cap *cap)
+{
+ if (hdl->eof) {
+ fprintf(stderr, "sa_getcap: eof\n");
+ return 0;
+ }
+ if (hdl->started) {
+ fprintf(stderr, "sa_getcap: already started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ return hdl->ops->getcap(hdl, cap);
+}
+
+int
+sa_psleep(struct sa_hdl *hdl, int event)
+{
+ struct pollfd pfd;
+ int revents;
+
+ for (;;) {
+ sa_pollfd(hdl, &pfd, event);
+ while (poll(&pfd, 1, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("sa_psleep: poll");
+ hdl->eof = 1;
+ return 0;
+ }
+ revents = sa_revents(hdl, &pfd);
+ if (revents & POLLHUP) {
+ fprintf(stderr, "sa_psleep: hang-up\n");
+ return 0;
+ }
+ if (revents & event)
+ break;
+ }
+ return 1;
+}
+
+size_t
+sa_read(struct sa_hdl *hdl, void *buf, size_t len)
+{
+ unsigned n;
+ char *data = buf;
+ size_t todo = len;
+
+ if (hdl->eof) {
+ fprintf(stderr, "sa_read: eof\n");
+ return 0;
+ }
+ if (!hdl->started || !(hdl->mode & SA_REC)) {
+ fprintf(stderr, "sa_read: recording not stared\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (todo == 0) {
+ fprintf(stderr, "sa_read: zero length read ignored\n");
+ return 0;
+ }
+ while (todo > 0) {
+ n = hdl->ops->read(hdl, data, todo);
+ if (n == 0) {
+ if (hdl->nbio || hdl->eof || todo < len)
+ break;
+ if (!sa_psleep(hdl, POLLIN))
+ break;
+ continue;
+ }
+ data += n;
+ todo -= n;
+ hdl->rcnt += n;
+ }
+ return len - todo;
+}
+
+size_t
+sa_write(struct sa_hdl *hdl, void *buf, size_t len)
+{
+ unsigned n;
+ unsigned char *data = buf;
+ size_t todo = len;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (hdl->debug >= 2)
+ gettimeofday(&tv0, NULL);
+#endif
+
+ if (hdl->eof) {
+ fprintf(stderr, "sa_write: eof\n");
+ return 0;
+ }
+ if (!hdl->started || !(hdl->mode & SA_PLAY)) {
+ fprintf(stderr, "sa_write: playback not started\n");
+ hdl->eof = 1;
+ return 0;
+ }
+ if (todo == 0) {
+ fprintf(stderr, "sa_write: zero length write ignored\n");
+ return 0;
+ }
+ while (todo > 0) {
+ n = hdl->ops->write(hdl, data, todo);
+ if (n == 0) {
+ if (hdl->nbio || hdl->eof)
+ break;
+ if (!sa_psleep(hdl, POLLOUT))
+ break;
+ continue;
+ }
+ data += n;
+ todo -= n;
+ hdl->wcnt += n;
+ }
+#ifdef DEBUG
+ if (hdl->debug >= 2) {
+ gettimeofday(&tv1, NULL);
+ timersub(&tv0, &hdl->tv, &dtv);
+ fprintf(stderr, "%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
+
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ fprintf(stderr,
+ "sa_write: wrote %d bytes of %d in %uus\n",
+ (int)(len - todo), (int)len, us);
+ }
+#endif
+ return len - todo;
+}
+
+int
+sa_nfds(struct sa_hdl *hdl)
+{
+ /*
+ * in the futur we might use larger values
+ */
+ return 1;
+}
+
+int
+sa_pollfd(struct sa_hdl *hdl, struct pollfd *pfd, int events)
+{
+ if (hdl->eof)
+ return 0;
+ return hdl->ops->pollfd(hdl, pfd, events);
+}
+
+int
+sa_revents(struct sa_hdl *hdl, struct pollfd *pfd)
+{
+ int revents;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (hdl->debug >= 2)
+ gettimeofday(&tv0, NULL);
+#endif
+ if (hdl->eof)
+ return POLLHUP;
+ hdl->pollcnt++;
+ revents = hdl->ops->revents(hdl, pfd);
+#ifdef DEBUG
+ if (hdl->debug >= 2) {
+ gettimeofday(&tv1, NULL);
+ timersub(&tv0, &hdl->tv, &dtv);
+ fprintf(stderr, "%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
+
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ fprintf(stderr,
+ "sa_revents: revents = 0x%x, complete in %uus\n",
+ revents, us);
+ }
+#endif
+ return revents;
+}
+
+int
+sa_eof(struct sa_hdl *hdl)
+{
+ return hdl->eof;
+}
+
+void
+sa_onmove(struct sa_hdl *hdl, void (*cb)(void *, int), void *addr)
+{
+ if (hdl->started) {
+ fprintf(stderr, "sa_onmove: already started\n");
+ hdl->eof = 1;
+ return;
+ }
+ hdl->cb_pos = cb;
+ hdl->cb_addr = addr;
+}
+
+void
+sa_onmove_cb(struct sa_hdl *hdl, int delta)
+{
+#ifdef DEBUG
+ struct timeval tv0, dtv;
+ long long playpos;
+
+ if (hdl->debug >= 2) {
+ gettimeofday(&tv0, NULL);
+ timersub(&tv0, &hdl->tv, &dtv);
+ fprintf(stderr, "%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
+ hdl->realpos += delta;
+ playpos = hdl->wcnt / (hdl->par.bps * hdl->par.pchan);
+ fprintf(stderr,
+ "sa_onmove_cb: delta = %+7d, "
+ "plat = %+7lld, "
+ "realpos = %+7lld, "
+ "bufused = %+7lld\n",
+ delta,
+ playpos - hdl->realpos,
+ hdl->realpos,
+ hdl->realpos < 0 ? playpos : playpos - hdl->realpos);
+ }
+#endif
+ if (hdl->cb_pos)
+ hdl->cb_pos(hdl->cb_addr, delta);
+}
diff --git a/lib/libsa/libsa.h b/lib/libsa/libsa.h
new file mode 100644
index 00000000000..fb2b2b5decc
--- /dev/null
+++ b/lib/libsa/libsa.h
@@ -0,0 +1,139 @@
+/* $OpenBSD: libsa.h,v 1.1 2008/10/26 08:49:44 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 LIBSA_H
+#define LIBSA_H
+
+#include <sys/param.h>
+
+/*
+ * private ``handle'' structure
+ */
+struct sa_hdl;
+
+/*
+ * parameters of a full-duplex stream
+ */
+struct sa_par {
+ unsigned bits; /* bits per sample */
+ unsigned bps; /* bytes per sample */
+ unsigned sig; /* 1 = signed, 0 = unsigned */
+ unsigned le; /* 1 = LE, 0 = BE byte order */
+ unsigned msb; /* 1 = MSB, 0 = LSB aligned */
+ unsigned rchan; /* number channels for recording direction */
+ unsigned pchan; /* number channels for playback direction */
+ unsigned rate; /* frames per second */
+ unsigned bufsz; /* minimum buffer size */
+#define SA_IGNORE 0 /* pause during xrun */
+#define SA_SYNC 1 /* resync after xrun */
+#define SA_ERROR 2 /* terminate on xrun */
+ unsigned xrun; /* what to do on overruns/underruns */
+ unsigned round; /* optimal bufsz divisor */
+ int __pad[4]; /* for future use */
+ int __magic; /* for internal/debug purposes only */
+};
+
+/*
+ * capabilities of a stream
+ */
+struct sa_cap {
+#define SA_NENC 8
+#define SA_NCHAN 8
+#define SA_NRATE 16
+#define SA_NCONF 4
+ struct sa_enc { /* allowed sample encodings */
+ unsigned bits;
+ unsigned bps;
+ unsigned sig;
+ unsigned le;
+ unsigned msb;
+ } enc[SA_NENC];
+ unsigned rchan[SA_NCHAN]; /* allowed values for rchan */
+ unsigned pchan[SA_NCHAN]; /* allowed values for pchan */
+ unsigned rate[SA_NRATE]; /* allowed rates */
+ int __pad[7]; /* for future use */
+ unsigned nconf; /* number of elements in confs[] */
+ struct sa_conf {
+ unsigned enc; /* mask of enc[] indexes */
+ unsigned rchan; /* mask of chan[] indexes (rec) */
+ unsigned pchan; /* mask of chan[] indexes (play) */
+ unsigned rate; /* mask of rate[] indexes */
+ } confs[SA_NCONF];
+};
+
+#define SA_XSTRINGS { "ignore", "sync", "error" }
+
+/*
+ * mode bitmap
+ */
+#define SA_PLAY 1
+#define SA_REC 2
+
+/*
+ * maximum size of the encording string (the longest possible
+ * encoding is ``s24le3msb'')
+ */
+#define SA_ENCMAX 10
+
+/*
+ * default bytes per sample for the given bits per sample
+ */
+#define SA_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4))
+
+/*
+ * default value of "sa_par->le" flag
+ */
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define SA_LE_NATIVE 1
+#else
+#define SA_LE_NATIVE 0
+#endif
+
+/*
+ * default device for the sun audio(4) back-end
+ */
+#define SA_SUN_PATH "/dev/audio"
+
+/*
+ * default socket for the aucat(1) back-end
+ */
+#define SA_AUCAT_PATH "/tmp/aucat.sock"
+
+int sa_strtoenc(struct sa_par *, char *);
+int sa_enctostr(struct sa_par *, char *);
+int sa_initpar(struct sa_par *);
+
+struct sa_hdl *sa_open_aucat(char *, unsigned, int);
+struct sa_hdl *sa_open_sun(char *, unsigned, int);
+struct sa_hdl *sa_open_wav(char *, unsigned, int);
+struct sa_hdl *sa_open_raw(char *, unsigned, int);
+struct sa_hdl *sa_open(char *, unsigned, int);
+
+void sa_close(struct sa_hdl *);
+int sa_setpar(struct sa_hdl *, struct sa_par *);
+int sa_getpar(struct sa_hdl *, struct sa_par *);
+int sa_getcap(struct sa_hdl *, struct sa_cap *);
+void sa_onmove(struct sa_hdl *, void (*)(void *, int), void *);
+size_t sa_write(struct sa_hdl *, void *, size_t);
+size_t sa_read(struct sa_hdl *, void *, size_t);
+int sa_start(struct sa_hdl *);
+int sa_stop(struct sa_hdl *);
+int sa_nfds(struct sa_hdl *);
+int sa_pollfd(struct sa_hdl *, struct pollfd *, int);
+int sa_revents(struct sa_hdl *, struct pollfd *);
+int sa_eof(struct sa_hdl *);
+
+#endif /* !defined(LIBSA_H) */
diff --git a/lib/libsa/libsa_priv.h b/lib/libsa/libsa_priv.h
new file mode 100644
index 00000000000..1318a6bf730
--- /dev/null
+++ b/lib/libsa/libsa_priv.h
@@ -0,0 +1,65 @@
+/* $OpenBSD: libsa_priv.h,v 1.1 2008/10/26 08:49:44 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 LIBSA_PRIV_H
+#define LIBSA_PRIV_H
+
+#include <sys/param.h>
+#include "libsa.h"
+
+/*
+ * private ``handle'' structure
+ */
+struct sa_hdl {
+ struct sa_ops *ops;
+ void (*cb_pos)(void *, int); /* call-back for realpos changes */
+ void *cb_addr; /* user priv. data */
+ unsigned mode; /* SA_PLAY | SA_REC */
+ int started; /* true if started */
+ int nbio; /* true if non-blocking io */
+ int eof; /* true if error occured */
+#ifdef DEBUG
+ int debug; /* debug flag */
+ unsigned long long pollcnt; /* times sa_revents was called */
+ unsigned long long wcnt; /* bytes written with sa_write() */
+ unsigned long long rcnt; /* bytes read with sa_read() */
+ long long realpos;
+ struct timeval tv;
+ struct sa_par par;
+#endif
+};
+
+/*
+ * operations every device should support
+ */
+struct sa_ops {
+ void (*close)(struct sa_hdl *);
+ int (*setpar)(struct sa_hdl *, struct sa_par *);
+ int (*getpar)(struct sa_hdl *, struct sa_par *);
+ int (*getcap)(struct sa_hdl *, struct sa_cap *);
+ size_t (*write)(struct sa_hdl *, void *, size_t);
+ size_t (*read)(struct sa_hdl *, void *, size_t);
+ int (*start)(struct sa_hdl *);
+ int (*stop)(struct sa_hdl *);
+ int (*pollfd)(struct sa_hdl *, struct pollfd *, int);
+ int (*revents)(struct sa_hdl *, struct pollfd *);
+};
+
+void sa_create(struct sa_hdl *, struct sa_ops *, unsigned, int);
+void sa_destroy(struct sa_hdl *);
+void sa_onmove_cb(struct sa_hdl *, int);
+
+#endif /* !defined(LIBSA_PRIV_H) */
diff --git a/lib/libsa/shlib_version b/lib/libsa/shlib_version
new file mode 100644
index 00000000000..1edea46de91
--- /dev/null
+++ b/lib/libsa/shlib_version
@@ -0,0 +1,2 @@
+major=1
+minor=0
diff --git a/lib/libsa/sun.c b/lib/libsa/sun.c
new file mode 100644
index 00000000000..d6956f4276c
--- /dev/null
+++ b/lib/libsa/sun.c
@@ -0,0 +1,858 @@
+/* $OpenBSD: sun.c,v 1.1 2008/10/26 08:49:44 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.
+ */
+/*
+ * TODO:
+ *
+ * remove filling code from sun_write() and create sun_fill()
+ *
+ * allow block size to be set
+ *
+ * call hdl->cb_pos() from sun_read() and sun_write(), or better:
+ * implement generic blocking sa_read() and sa_write() with poll(2)
+ * and use non-blocking sa_ops only
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libsa_priv.h"
+
+struct sun_hdl {
+ struct sa_hdl sa;
+ int fd;
+ int filling;
+ unsigned ibpf, obpf; /* bytes per frame */
+ unsigned ibytes, obytes; /* bytes the hw transfered */
+ unsigned ierr, oerr; /* frames the hw dropped */
+ int offset; /* frames play is ahead of record */
+ int idelta, odelta; /* position reported to client */
+};
+
+void sun_close(struct sa_hdl *);
+int sun_start(struct sa_hdl *);
+int sun_stop(struct sa_hdl *);
+int sun_setpar(struct sa_hdl *, struct sa_par *);
+int sun_getpar(struct sa_hdl *, struct sa_par *);
+int sun_getcap(struct sa_hdl *, struct sa_cap *);
+size_t sun_read(struct sa_hdl *, void *, size_t);
+size_t sun_write(struct sa_hdl *, void *, size_t);
+int sun_pollfd(struct sa_hdl *, struct pollfd *, int);
+int sun_revents(struct sa_hdl *, struct pollfd *);
+
+struct sa_ops sun_ops = {
+ sun_close,
+ sun_setpar,
+ sun_getpar,
+ sun_getcap,
+ sun_write,
+ sun_read,
+ sun_start,
+ sun_stop,
+ sun_pollfd,
+ sun_revents
+};
+
+/*
+ * convert sun encoding to sa_par encoding
+ */
+void
+sun_infotoenc(struct audio_prinfo *ai, struct sa_par *par)
+{
+ par->msb = 1;
+ par->bits = ai->precision;
+ par->bps = SA_BPS(par->bits);
+ 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 = SA_LE_NATIVE;
+ par->sig = 1;
+ break;
+ case AUDIO_ENCODING_ULINEAR:
+ par->le = SA_LE_NATIVE;
+ par->sig = 0;
+ break;
+ default:
+ fprintf(stderr, "sun_infotoenc: unsupported encoding\n");
+ exit(1);
+ }
+}
+
+/*
+ * convert sa_par encoding to sun encoding
+ */
+void
+sun_enctoinfo(struct audio_prinfo *ai, struct sa_par *par)
+{
+ 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;
+ }
+ ai->precision = par->bits;
+}
+
+/*
+ * deal with audio(4) driver block size rounding weiredness. Calculate
+ * the smallest number of frames such that both play and record blocks
+ * are multiples of it. That's because, the audio(4) driver uses the same
+ * rounding routine for both play and record buffers, hopefully that
+ * will be changed in the future...
+ */
+unsigned
+sun_round(unsigned ibpf, unsigned obpf)
+{
+ unsigned i, round, p;
+ static unsigned primes[] = {2, 3, 5, 7, 11, 13, 17};
+#define NPRIMES (sizeof(primes) / sizeof(primes[0]))
+
+ round = ibpf * obpf;
+ for (i = 0; i < NPRIMES; i++) {
+ p = primes[i];
+ while ((ibpf % p) == 0 && (obpf % p) == 0) {
+ ibpf /= p;
+ obpf /= p;
+ round /= p;
+ }
+ }
+ return round;
+}
+
+/*
+ * calculate and set the largest possible block size, such that
+ * play and record blocks have the same frames number
+ */
+int
+sun_setnfr(struct sun_hdl *hdl, unsigned bufsz)
+{
+ struct audio_info aui;
+ struct sa_par np;
+ unsigned nfr, infr = 0, onfr = 0, ibpf, obpf;
+ unsigned round;
+ int i;
+
+ if (!sa_getpar(&hdl->sa, &np))
+ return 0;
+ ibpf = (hdl->sa.mode & SA_REC) ? np.rchan * np.bps : 1;
+ obpf = (hdl->sa.mode & SA_PLAY) ? np.pchan * np.bps : 1;
+ round = sun_round(ibpf, obpf);
+
+ /*
+ * if no bufsz is given, use 200ms which is ok in most cases
+ */
+ if (bufsz == 0)
+ bufsz = (np.rate * 200 + 999) / 1000;
+ if (bufsz < 32)
+ bufsz = 32;
+
+ /*
+ * use 12ms block size, unless 12ms is larger than bufsz / 2
+ */
+ nfr = (np.rate * 12 + 999) / 1000;
+ if (2 * nfr > bufsz)
+ nfr = bufsz / 2;
+ nfr += round - 1;
+ nfr -= nfr % round;
+
+ /*
+ * try to set parameters until the device accepts
+ * a common block size for play and record
+ */
+ for (i = 0; i < 5; i++) {
+ AUDIO_INITINFO(&aui);
+ aui.hiwat = (bufsz + nfr - 1) / nfr;
+ aui.lowat = aui.hiwat;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.block_size = (nfr - nfr % round) * ibpf;
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.block_size = (nfr - nfr % round) * obpf;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_setnfr: SETINFO");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
+ perror("sun_setnfr: GETINFO");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ infr = aui.record.block_size / ibpf;
+ onfr = aui.play.block_size / obpf;
+
+ /*
+ * if half-duplex or both block sizes match, it's ok
+ */
+ if (hdl->sa.mode != (SA_REC | SA_PLAY) || infr == onfr)
+ return (hdl->sa.mode & SA_REC) ? infr : onfr;
+
+ /*
+ * retry with the smaller returned value
+ */
+ if ((hdl->sa.mode & SA_REC) && nfr > infr)
+ nfr = infr;
+ if ((hdl->sa.mode & SA_PLAY) && nfr > onfr)
+ nfr = onfr;
+ }
+ fprintf(stderr, "sun_setnfr: couldn't find a working blocksize\n");
+ hdl->sa.eof = 1;
+ return 0;
+}
+
+/*
+ * try to set the device to the given parameters and check that the
+ * device can use them; retrun 1 on success, 0 on failure or error
+ */
+int
+sun_tryinfo(struct sun_hdl *hdl, struct sa_enc *enc,
+ unsigned pchan, unsigned rchan, unsigned rate)
+{
+ struct audio_info aui;
+
+ AUDIO_INITINFO(&aui);
+ if (enc) {
+ if (enc->le && enc->sig) {
+ aui.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
+ aui.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
+ } else if (!enc->le && enc->sig) {
+ aui.play.encoding = AUDIO_ENCODING_SLINEAR_BE;
+ aui.record.encoding = AUDIO_ENCODING_SLINEAR_BE;
+ } else if (enc->le && !enc->sig) {
+ aui.play.encoding = AUDIO_ENCODING_ULINEAR_LE;
+ aui.record.encoding = AUDIO_ENCODING_ULINEAR_LE;
+ } else {
+ aui.play.encoding = AUDIO_ENCODING_ULINEAR_BE;
+ aui.record.encoding = AUDIO_ENCODING_ULINEAR_BE;
+ }
+ aui.play.precision = enc->bits;
+ }
+ if (pchan)
+ aui.play.channels = pchan;
+ if (rchan)
+ aui.record.channels = rchan;
+ if (rate) {
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.sample_rate = rate;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.sample_rate = rate;
+ }
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ if (errno == EINVAL)
+ return 0;
+ perror("sun_tryinfo: setinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
+ perror("sun_tryinfo: getinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (pchan && aui.play.channels != pchan)
+ return 0;
+ if (rchan && aui.record.channels != rchan)
+ return 0;
+ if (rate) {
+ if ((hdl->sa.mode & SA_PLAY) &&
+ (aui.play.sample_rate != rate))
+ return 0;
+ if ((hdl->sa.mode & SA_REC) &&
+ (aui.record.sample_rate != rate))
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * guess device capabilities
+ */
+int
+sun_getcap(struct sa_hdl *sh, struct sa_cap *cap)
+{
+#define NCHANS (sizeof(chans) / sizeof(chans[0]))
+#define NRATES (sizeof(rates) / sizeof(rates[0]))
+ static unsigned chans[] = {
+ 1, 2, 4, 6, 8, 10, 12
+ };
+ static unsigned rates[] = {
+ 8000, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000
+ };
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct sa_par savepar;
+ struct audio_encoding ae;
+ unsigned nenc = 0, nconf = 0;
+ unsigned enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map = 0;
+ unsigned i, j, map;
+
+ if (!sun_getpar(&hdl->sa, &savepar))
+ return 0;
+
+ /*
+ * fill encoding list
+ */
+ for (ae.index = 0; nenc < SA_NENC; ae.index++) {
+ if (ioctl(hdl->fd, AUDIO_GETENC, &ae) < 0) {
+ if (errno == EINVAL)
+ break;
+ perror("sun_getcap: getenc");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED)
+ continue;
+ if (ae.encoding == AUDIO_ENCODING_SLINEAR_LE) {
+ cap->enc[nenc].le = 1;
+ cap->enc[nenc].sig = 1;
+ } else if (ae.encoding == AUDIO_ENCODING_SLINEAR_BE) {
+ cap->enc[nenc].le = 0;
+ cap->enc[nenc].sig = 1;
+ } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_LE) {
+ cap->enc[nenc].le = 1;
+ cap->enc[nenc].sig = 0;
+ } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_BE) {
+ cap->enc[nenc].le = 0;
+ cap->enc[nenc].sig = 0;
+ } else if (ae.encoding == AUDIO_ENCODING_SLINEAR) {
+ cap->enc[nenc].le = SA_LE_NATIVE;
+ cap->enc[nenc].sig = 1;
+ } else if (ae.encoding == AUDIO_ENCODING_ULINEAR) {
+ cap->enc[nenc].le = SA_LE_NATIVE;
+ cap->enc[nenc].sig = 0;
+ } else {
+ /* unsipported encoding */
+ continue;
+ }
+ cap->enc[nenc].bits = ae.precision;
+ cap->enc[nenc].bps = ae.precision / 8;
+ cap->enc[nenc].msb = 0;
+ enc_map |= (1 << nenc);
+ nenc++;
+ }
+
+ /*
+ * fill channels
+ *
+ * for now we're lucky: all kernel devices assume that the
+ * number of channels and the encoding are independent so we can
+ * use the current encoding and try various channels.
+ */
+ if (hdl->sa.mode & SA_PLAY) {
+ memcpy(&cap->pchan, chans, NCHANS * sizeof(unsigned));
+ for (i = 0; i < NCHANS; i++) {
+ if (sun_tryinfo(hdl, NULL, chans[i], 0, 0))
+ pchan_map |= (1 << i);
+ }
+ }
+ if (hdl->sa.mode & SA_REC) {
+ memcpy(&cap->rchan, chans, NCHANS * sizeof(unsigned));
+ for (i = 0; i < NCHANS; i++) {
+ if (sun_tryinfo(hdl, NULL, 0, chans[i], 0))
+ rchan_map |= (1 << i);
+ }
+ }
+
+ /*
+ * fill rates
+ *
+ * rates are not independent from other parameters (eg. on
+ * uaudio devices), so certain rates may not be allowed with
+ * certain encordings. We have to check rates for all encodings
+ */
+ memcpy(&cap->rate, rates, NRATES * sizeof(unsigned));
+ for (j = 0; j < nenc; j++) {
+ if (nconf == SA_NCONF)
+ break;
+ map = 0;
+ for (i = 0; i < NRATES; i++) {
+ if (sun_tryinfo(hdl, NULL, 0, 0, rates[i]))
+ map |= (1 << i);
+ }
+ if (map != rate_map) {
+ rate_map = map;
+ cap->confs[nconf].enc = enc_map;
+ cap->confs[nconf].pchan = pchan_map;
+ cap->confs[nconf].rchan = rchan_map;
+ cap->confs[nconf].rate = rate_map;
+ nconf++;
+ }
+ }
+ cap->nconf = nconf;
+ if (!sun_setpar(&hdl->sa, &savepar))
+ return 0;
+ return 1;
+#undef NCHANS
+#undef NRATES
+}
+
+struct sa_hdl *
+sa_open_sun(char *path, unsigned mode, int nbio)
+{
+ int fd, flags, fullduplex;
+ struct sun_hdl *hdl;
+ struct audio_info aui;
+
+ hdl = malloc(sizeof(struct sun_hdl));
+ if (hdl == NULL)
+ return NULL;
+ sa_create(&hdl->sa, &sun_ops, mode, nbio);
+
+ if (path == NULL) {
+ path = getenv("AUDIODEVICE");
+ if (path == NULL)
+ path = SA_SUN_PATH;
+ }
+ if (mode == (SA_PLAY | SA_REC))
+ flags = O_RDWR;
+ else
+ flags = (mode & SA_PLAY) ? O_WRONLY : O_RDONLY;
+
+ while ((fd = open(path, flags | O_NONBLOCK)) < 0) {
+ if (errno == EINTR)
+ continue;
+ perror(path);
+ goto bad_free;
+ }
+ hdl->fd = fd;
+
+ /*
+ * If both play and record are requested then
+ * set full duplex mode.
+ */
+ if (mode == (SA_PLAY | SA_REC)) {
+ fullduplex = 1;
+ if (ioctl(fd, AUDIO_SETFD, &fullduplex) < 0) {
+ fprintf(stderr, "%s: can't set full-duplex", path);
+ goto bad_close;
+ }
+ }
+ hdl->fd = fd;
+ AUDIO_INITINFO(&aui);
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.encoding = AUDIO_ENCODING_SLINEAR;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.encoding = AUDIO_ENCODING_SLINEAR;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sa_open_sun: setinfo");
+ goto bad_close;
+ }
+ if (!sun_setnfr(hdl, 0))
+ goto bad_close;
+ return (struct sa_hdl *)hdl;
+ bad_close:
+ while (close(hdl->fd) < 0 && errno == EINTR)
+ ; /* retry */
+ bad_free:
+ free(hdl);
+ return NULL;
+}
+
+void
+sun_close(struct sa_hdl *sh)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ int rc;
+ do {
+ rc = close(hdl->fd);
+ } while (rc < 0 && errno == EINTR);
+ free(hdl);
+}
+
+int
+sun_start(struct sa_hdl *sh)
+{
+ struct sa_par par;
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct audio_info aui;
+
+ if (!sa_getpar(&hdl->sa, &par))
+ return 0;
+ hdl->obpf = par.pchan * par.bps;
+ hdl->ibpf = par.rchan * par.bps;
+ hdl->ibytes = 0;
+ hdl->obytes = 0;
+ hdl->ierr = 0;
+ hdl->oerr = 0;
+ hdl->offset = 0;
+ hdl->idelta = 0;
+ hdl->odelta = 0;
+
+ if (hdl->sa.mode & SA_PLAY) {
+ /*
+ * pause the device and let sun_write() trigger the
+ * start later, to avoid buffer underruns
+ */
+ AUDIO_INITINFO(&aui);
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.pause = 1;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.pause = 1;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_start: setinfo2");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ hdl->filling = 1;
+ } else {
+ /*
+ * no play buffers to fill, start now!
+ */
+ AUDIO_INITINFO(&aui);
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.pause = 0;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.pause = 0;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_start: setinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ sa_onmove_cb(&hdl->sa, 0);
+ }
+ return 1;
+}
+
+int
+sun_stop(struct sa_hdl *sh)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct audio_info aui;
+ int mode;
+
+ if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
+ perror("sun_start: setinfo1");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ mode = aui.mode;
+
+ /*
+ * there's no way to drain the device without blocking, so just
+ * stop it until the kernel driver get fixed
+ */
+ AUDIO_INITINFO(&aui);
+ aui.mode = 0;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_stop: setinfo1");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ AUDIO_INITINFO(&aui);
+ aui.mode = mode;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_stop: setinfo2");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return 1;
+}
+
+int
+sun_setpar(struct sa_hdl *sh, struct sa_par *par)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct audio_info aui;
+
+ /*
+ * the only ones supported by Sun API
+ */
+ par->bps = SA_BPS(par->bits);
+ par->msb = 1;
+ par->xrun = SA_IGNORE;
+
+ AUDIO_INITINFO(&aui);
+ if (hdl->sa.mode & SA_PLAY) {
+ aui.play.sample_rate = par->rate;
+ aui.play.channels = par->pchan;
+ sun_enctoinfo(&aui.play, par);
+ }
+ if (hdl->sa.mode & SA_REC) {
+ aui.record.sample_rate = par->rate;
+ aui.record.channels = par->rchan;
+ sun_enctoinfo(&aui.record, par);
+ }
+ aui.lowat = 1;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0 && errno != EINVAL) {
+ perror("sun_setpar: setinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (par->bufsz != (unsigned)~0) {
+ if (!sun_setnfr(hdl, par->bufsz))
+ return 0;
+ }
+ return 1;
+}
+
+int
+sun_getpar(struct sa_hdl *sh, struct sa_par *par)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct audio_info aui;
+
+ if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
+ perror("sun_getpar: setinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (hdl->sa.mode & SA_PLAY) {
+ par->rate = aui.play.sample_rate;
+ sun_infotoenc(&aui.play, par);
+ } else if (hdl->sa.mode & SA_REC) {
+ par->rate = aui.record.sample_rate;
+ sun_infotoenc(&aui.record, par);
+ } else
+ return 0;
+ par->pchan = (hdl->sa.mode & SA_PLAY) ?
+ aui.play.channels : 0;
+ par->rchan = (hdl->sa.mode & SA_REC) ?
+ aui.record.channels : 0;
+ par->round = (hdl->sa.mode & SA_REC) ?
+ aui.record.block_size / (par->bps * par->rchan) :
+ aui.play.block_size / (par->bps * par->pchan);
+ par->bufsz = aui.hiwat * par->round;
+ return 1;
+}
+
+size_t
+sun_read(struct sa_hdl *sh, void *buf, size_t len)
+{
+#define DROP_NMAX 0x1000
+ static char dropbuf[DROP_NMAX];
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ ssize_t n, todo;
+
+ while (hdl->offset > 0) {
+ todo = hdl->offset * hdl->ibpf;
+ if (todo > DROP_NMAX)
+ todo = DROP_NMAX - DROP_NMAX % hdl->ibpf;
+ while ((n = read(hdl->fd, dropbuf, todo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ perror("sun_read: read");
+ hdl->sa.eof = 1;
+ }
+ return 0;
+ }
+ if (n == 0) {
+ fprintf(stderr, "sun_read: eof\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ hdl->offset -= (int)n / (int)hdl->ibpf;
+#ifdef DEBUG
+ if (hdl->sa.debug)
+ fprintf(stderr, "sun_read: dropped %ld/%ld bytes "
+ "to resync\n", n, todo);
+#endif
+ }
+
+ while ((n = read(hdl->fd, buf, len)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ perror("sun_read: read");
+ hdl->sa.eof = 1;
+ }
+ return 0;
+ }
+ if (n == 0) {
+ fprintf(stderr, "sun_read: eof\n");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return n;
+}
+
+size_t
+sun_autostart(struct sun_hdl *hdl)
+{
+ struct audio_info aui;
+ struct pollfd pfd;
+
+ pfd.fd = hdl->fd;
+ pfd.events = POLLOUT;
+ while (poll(&pfd, 1, 0) < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("sun_fill: poll");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ if (!(pfd.revents & POLLOUT)) {
+ hdl->filling = 0;
+ AUDIO_INITINFO(&aui);
+ if (hdl->sa.mode & SA_PLAY)
+ aui.play.pause = 0;
+ if (hdl->sa.mode & SA_REC)
+ aui.record.pause = 0;
+ if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
+ perror("sun_start: setinfo");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ sa_onmove_cb(&hdl->sa, 0);
+ }
+ return 1;
+}
+
+size_t
+sun_write(struct sa_hdl *sh, void *buf, size_t len)
+{
+#define ZERO_NMAX 0x1000
+ static char zero[ZERO_NMAX];
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ unsigned char *data = buf;
+ ssize_t n, todo;
+
+ while (hdl->offset < 0) {
+ todo = (int)-hdl->offset * (int)hdl->obpf;
+ if (todo > ZERO_NMAX)
+ todo = ZERO_NMAX - ZERO_NMAX % hdl->obpf;
+ while ((n = write(hdl->fd, zero, todo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ perror("sun_write: sil");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return 0;
+ }
+ hdl->offset += (int)n / (int)hdl->obpf;
+#ifdef DEBUG
+ if (hdl->sa.debug)
+ fprintf(stderr, "sun_write: inserted %ld/%ld bytes "
+ "of silence to resync\n", n, todo);
+#endif
+ }
+
+ todo = len;
+ while ((n = write(hdl->fd, data, todo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ perror("sun_write: write");
+ hdl->sa.eof = 1;
+ return 0;
+ }
+ return 0;
+ }
+ if (hdl->filling) {
+ if (!sun_autostart(hdl))
+ return 0;
+ }
+ return n;
+}
+
+int
+sun_pollfd(struct sa_hdl *sh, struct pollfd *pfd, int events)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+
+ pfd->fd = hdl->fd;
+ pfd->events = events;
+ return 1;
+}
+
+int
+sun_revents(struct sa_hdl *sh, struct pollfd *pfd)
+{
+ struct sun_hdl *hdl = (struct sun_hdl *)sh;
+ struct audio_offset ao;
+ int xrun, dmove, dierr = 0, doerr = 0, doffset = 0;
+ int revents = pfd->revents;
+
+ if (hdl->sa.mode & SA_PLAY) {
+ if (ioctl(hdl->fd, AUDIO_PERROR, &xrun) < 0) {
+ perror("sun_revents: PERROR");
+ exit(1);
+ }
+ doerr = xrun - hdl->oerr;
+ hdl->oerr = xrun;
+ if (hdl->sa.mode & SA_REC)
+ doffset += doerr;
+ }
+ if (hdl->sa.mode & SA_REC) {
+ if (ioctl(hdl->fd, AUDIO_RERROR, &xrun) < 0) {
+ perror("sun_revents: RERROR");
+ exit(1);
+ }
+ dierr = xrun - hdl->ierr;
+ hdl->ierr = xrun;
+ if (hdl->sa.mode & SA_PLAY)
+ doffset -= dierr;
+ }
+ hdl->offset += doffset;
+ dmove = dierr > doerr ? dierr : doerr;
+ hdl->idelta -= dmove;
+ hdl->odelta -= dmove;
+
+ if ((revents & POLLOUT) && !(hdl->sa.mode & SA_REC)) {
+ if (ioctl(hdl->fd, AUDIO_GETOOFFS, &ao) < 0) {
+ perror("sun_revents: GETOOFFS");
+ exit(1);
+ }
+ hdl->odelta += (ao.samples - hdl->obytes) / hdl->obpf;
+ hdl->obytes = ao.samples;
+ if (hdl->odelta != 0) {
+ sa_onmove_cb(&hdl->sa, hdl->odelta);
+ hdl->odelta = 0;
+ }
+ }
+ if ((revents & POLLIN) && (hdl->sa.mode & SA_REC)) {
+ if (ioctl(hdl->fd, AUDIO_GETIOFFS, &ao) < 0) {
+ perror("sun_revents: GETIOFFS");
+ exit(1);
+ }
+ hdl->idelta += (ao.samples - hdl->ibytes) / hdl->ibpf;
+ hdl->ibytes = ao.samples;
+ if (hdl->idelta != 0) {
+ sa_onmove_cb(&hdl->sa, hdl->idelta);
+ hdl->idelta = 0;
+ }
+ }
+ if (hdl->filling)
+ revents |= POLLOUT;
+ return revents;
+}
diff --git a/regress/lib/libsa/Makefile b/regress/lib/libsa/Makefile
new file mode 100644
index 00000000000..0d90aa8e179
--- /dev/null
+++ b/regress/lib/libsa/Makefile
@@ -0,0 +1,5 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+
+SUBDIR+= saplay sarec safd sacap
+
+.include <bsd.subdir.mk>
diff --git a/regress/lib/libsa/sacap/Makefile b/regress/lib/libsa/sacap/Makefile
new file mode 100644
index 00000000000..5c1ee3cd7e8
--- /dev/null
+++ b/regress/lib/libsa/sacap/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+PROG= sacap
+LDADD= -lsa
+REGRESS_SKIP=
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libsa/sacap/sacap.c b/regress/lib/libsa/sacap/sacap.c
new file mode 100644
index 00000000000..2b46b78a0b0
--- /dev/null
+++ b/regress/lib/libsa/sacap/sacap.c
@@ -0,0 +1,97 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "libsa.h"
+
+struct sa_par par;
+struct sa_cap cap;
+
+void
+pr_enc(struct sa_enc *enc)
+{
+ fprintf(stderr, "%s%d", enc->sig ? "s" : "u", enc->bits);
+ if (enc->bps > 1)
+ fprintf(stderr, "%s", enc->le ? "le" : "be");
+ if (enc->bps != SA_BPS(enc->bits))
+ fprintf(stderr, "%d%s", enc->bps, enc->msb ? "msb" : "lsb");
+}
+
+void
+cap_pr(struct sa_cap *cap)
+{
+ unsigned n, i;
+
+ for (n = 0; n < cap->nconf; n++) {
+ fprintf(stderr, "config %d\n", n);
+ fprintf(stderr, "\tenc:");
+ for (i = 0; i < SA_NENC; i++) {
+ if (cap->confs[n].enc & (1 << i)) {
+ fprintf(stderr, " ");
+ pr_enc(&cap->enc[i]);
+ }
+ }
+ fprintf(stderr, "\n\tpchan:");
+ for (i = 0; i < SA_NCHAN; i++) {
+ if (cap->confs[n].pchan & (1 << i))
+ fprintf(stderr, " %d", cap->pchan[i]);
+ }
+ fprintf(stderr, "\n\trchan:");
+ for (i = 0; i < SA_NCHAN; i++) {
+ if (cap->confs[n].rchan & (1 << i))
+ fprintf(stderr, " %d", cap->rchan[i]);
+ }
+ fprintf(stderr, "\n\trate:");
+ for (i = 0; i < SA_NRATE; i++) {
+ if (cap->confs[n].rate & (1 << i))
+ fprintf(stderr, " %d", cap->rate[i]);
+ }
+ fprintf(stderr, "\n");
+ }
+}
+
+void
+usage(void) {
+ fprintf(stderr, "usage: sacap [-pr]\n");
+}
+
+int
+main(int argc, char **argv) {
+ int ch;
+ unsigned mode = SA_PLAY | SA_REC;
+ struct sa_hdl *hdl;
+
+ while ((ch = getopt(argc, argv, "pr")) != -1) {
+ switch(ch) {
+ case 'p':
+ mode &= ~SA_REC;
+ break;
+ case 'r':
+ mode &= ~SA_PLAY;
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+ if (mode == 0) {
+ fprintf(stderr, "-p and -r flags are mutualy exclusive\n");
+ exit(1);
+ }
+ hdl = sa_open(NULL, mode, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "sa_open() failed\n");
+ exit(1);
+ }
+ if (!sa_getcap(hdl, &cap)) {
+ fprintf(stderr, "sa_setcap() failed\n");
+ exit(1);
+ }
+ cap_pr(&cap);
+ sa_close(hdl);
+ return 0;
+}
diff --git a/regress/lib/libsa/safd/Makefile b/regress/lib/libsa/safd/Makefile
new file mode 100644
index 00000000000..4f6e3144cbb
--- /dev/null
+++ b/regress/lib/libsa/safd/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+PROG= safd
+LDADD= -lsa
+REGRESS_SKIP=
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libsa/safd/safd.c b/regress/lib/libsa/safd/safd.c
new file mode 100644
index 00000000000..8a6e37d27b0
--- /dev/null
+++ b/regress/lib/libsa/safd/safd.c
@@ -0,0 +1,363 @@
+#include <sys/time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "libsa.h"
+
+struct buf { /* simple circular fifo */
+ unsigned start; /* first used byte */
+ unsigned used; /* number of used bytes */
+#define BUF_LEN (240 * 0x1000) /* i/o buffer size */
+ unsigned char data[BUF_LEN];
+};
+
+char *xstr[] = SA_XSTRINGS;
+struct sa_par par;
+struct buf playbuf, recbuf;
+
+long long pos = 0;
+int plat = 0, rlat = 0;
+
+void
+cb(void *addr, int delta)
+{
+ pos += delta;
+ fprintf(stderr, "cb: delta = %+7d, pos = %+7lld, "
+ "plat = %+7d, rlat = %+7d\n",
+ delta, pos, plat, rlat);
+ plat -= delta;
+ rlat += delta;
+}
+
+/*
+ * read buffer contents from a file without blocking
+ */
+void
+buf_read(struct buf *buf, int fd) {
+ unsigned count, end, avail;
+ int n;
+
+ for (;;) {
+ avail = BUF_LEN - buf->used;
+ if (avail == 0)
+ break;
+ end = buf->start + buf->used;
+ if (end >= BUF_LEN)
+ end -= BUF_LEN;
+ count = BUF_LEN - end;
+ if (count > avail)
+ count = avail;
+ n = read(fd, buf->data + end, count);
+ if (n < 0) {
+ perror("buf_read: read");
+ exit(1);
+ }
+ if (n == 0) {
+ bzero(buf->data + end, count);
+ n = count;
+ }
+ buf->used += n;
+ }
+}
+
+/*
+ * write buffer contents to file, without blocking
+ */
+void
+buf_write(struct buf *buf, int fd)
+{
+ unsigned count;
+ int n;
+
+ while (buf->used) {
+ count = BUF_LEN - buf->start;
+ if (count > buf->used)
+ count = buf->used;
+ n = write(fd, buf->data + buf->start, count);
+ if (n < 0) {
+ perror("buf_write: write");
+ exit(1);
+ }
+ buf->used -= n;
+ buf->start += n;
+ if (buf->start >= BUF_LEN)
+ buf->start -= BUF_LEN;
+ }
+}
+
+/*
+ * read buffer contents from a file without blocking
+ */
+unsigned
+buf_rec(struct buf *buf, struct sa_hdl *hdl)
+{
+ unsigned count, end, avail, done = 0;
+ int bpf = par.rchan * par.bps;
+ int n;
+
+ for (;;) {
+ avail = BUF_LEN - buf->used;
+ if (avail == 0)
+ break;
+ end = buf->start + buf->used;
+ if (end >= BUF_LEN)
+ end -= BUF_LEN;
+ count = BUF_LEN - end;
+ if (count > avail)
+ count = avail;
+ n = sa_read(hdl, buf->data + end, count);
+ if (n == 0) {
+ if (sa_eof(hdl)) {
+ fprintf(stderr, "sa_read() failed\n");
+ exit(1);
+ }
+ break;
+ }
+ if (n % bpf) {
+ fprintf(stderr, "rec: bad align: %u bytes\n", n);
+ exit(1);
+ }
+ rlat -= n / bpf;
+ buf->used += n;
+ done += n;
+ }
+ return done;
+}
+
+/*
+ * write buffer contents to file, without blocking
+ */
+unsigned
+buf_play(struct buf *buf, struct sa_hdl *hdl)
+{
+ unsigned count, done = 0;
+ int bpf = par.pchan * par.bps;
+ int n;
+
+ while (buf->used) {
+ count = BUF_LEN - buf->start;
+ if (count > buf->used)
+ count = buf->used;
+ /* try to confuse the server */
+ //count = 1 + (rand() % count);
+ n = sa_write(hdl, buf->data + buf->start, count);
+ if (n == 0) {
+ if (sa_eof(hdl)) {
+ fprintf(stderr, "sa_write() failed\n");
+ exit(1);
+ }
+ break;
+ }
+ if (n % bpf) {
+ fprintf(stderr, "play: bad align: %u bytes\n", n);
+ exit(1);
+ }
+ plat += n / bpf;
+ //write(STDOUT_FILENO, buf->data + buf->start, n);
+ buf->used -= n;
+ buf->start += n;
+ if (buf->start >= BUF_LEN)
+ buf->start -= BUF_LEN;
+ done += n;
+ }
+ return done;
+}
+
+void
+usage(void) {
+ fprintf(stderr,
+ "usage: safd [-v] [-r rate] [-c ichan] [-C ochan] [-e enc] "
+ "[-i file] [-o file]\n");
+}
+
+int
+main(int argc, char **argv) {
+ int ch, recfd, playfd, events, revents;
+ char *recpath, *playpath;
+ struct sa_hdl *hdl;
+ struct pollfd pfd;
+ struct timeval tv, otv, ntv;
+ unsigned mode, done;
+
+ recpath = NULL;
+ playpath = NULL;
+
+ /*
+ * defaults parameters
+ */
+ sa_initpar(&par);
+ par.sig = 1;
+ par.bits = 16;
+ par.pchan = par.rchan = 2;
+ par.rate = 44100;
+
+ while ((ch = getopt(argc, argv, "r:c:C:e:i:o:b:x:")) != -1) {
+ switch(ch) {
+ case 'r':
+ if (sscanf(optarg, "%u", &par.rate) != 1) {
+ fprintf(stderr, "%s: bad rate\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'c':
+ if (sscanf(optarg, "%u", &par.pchan) != 1) {
+ fprintf(stderr, "%s: bad play chans\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'C':
+ if (sscanf(optarg, "%u", &par.rchan) != 1) {
+ fprintf(stderr, "%s: bad rec chans\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'e':
+ if (!sa_strtoenc(&par, optarg)) {
+ fprintf(stderr, "%s: unknown encoding\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'o':
+ recpath = optarg;
+ break;
+ case 'i':
+ playpath = optarg;
+ break;
+ case 'b':
+ if (sscanf(optarg, "%u", &par.bufsz) != 1) {
+ fprintf(stderr, "%s: bad buf size\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'x':
+ for (par.xrun = 0;; par.xrun++) {
+ if (par.xrun == sizeof(xstr) / sizeof(char *)) {
+ fprintf(stderr,
+ "%s: bad xrun mode\n", optarg);
+ exit(1);
+ }
+ if (strcmp(xstr[par.xrun], optarg) == 0)
+ break;
+ }
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+ mode = 0;
+ if (recpath)
+ mode |= SA_REC;
+ if (playpath)
+ mode |= SA_PLAY;
+ if (mode == 0) {
+ fprintf(stderr, "-i or -o option required\n");
+ exit(0);
+ }
+ hdl = sa_open(NULL, mode, 1);
+ if (hdl == NULL) {
+ fprintf(stderr, "sa_open() failed\n");
+ exit(1);
+ }
+ sa_onmove(hdl, cb, NULL);
+ if (!sa_setpar(hdl, &par)) {
+ fprintf(stderr, "sa_setpar() failed\n");
+ exit(1);
+ }
+ if (!sa_getpar(hdl, &par)) {
+ fprintf(stderr, "sa_setpar() failed\n");
+ exit(1);
+ }
+ fprintf(stderr, "using %u%%%u frame buffer\n", par.bufsz, par.round);
+ if (!sa_start(hdl)) {
+ fprintf(stderr, "sa_start() failed\n");
+ exit(1);
+ }
+
+ events = 0;
+ if (recpath > 0) {
+ recfd = open(recpath, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (recfd < 0) {
+ perror(recpath);
+ exit(1);
+ }
+ events |= POLLIN;
+ }
+ if (playpath > 0) {
+ playfd = open(playpath, O_RDONLY, 0);
+ if (playfd < 0) {
+ perror(playpath);
+ exit(1);
+ }
+ events |= POLLOUT;
+ buf_read(&playbuf, playfd);
+ buf_play(&playbuf, hdl);
+ }
+ gettimeofday(&otv, NULL);
+ for (;;) {
+ gettimeofday(&ntv, NULL);
+ timersub(&ntv, &otv, &tv);
+#if 0 /* trigger underrun */
+ if (playpath && (tv.tv_sec % 10) < 7) {
+ events |= POLLOUT;
+ } else
+ events &= ~POLLOUT;
+#endif
+#if 0 /* trigger overrun */
+ if (recpath && (tv.tv_sec % 10) < 7) {
+ events |= POLLIN;
+ } else
+ events &= ~POLLIN;
+#endif
+ //fprintf(stderr, "%ld.%06ld: polling for %d\n",
+ // tv.tv_sec, tv.tv_usec, events);
+ sa_pollfd(hdl, &pfd, events);
+ while (poll(&pfd, 1, 1000) < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("poll");
+ exit(1);
+ }
+ revents = sa_revents(hdl, &pfd);
+ gettimeofday(&ntv, NULL);
+ timersub(&ntv, &otv, &tv);
+ //fprintf(stderr, "%ld.%06ld: got %d\n",
+ // tv.tv_sec, tv.tv_usec, revents);
+ if (revents & POLLHUP) {
+ fprintf(stderr, "device hangup\n");
+ exit(0);
+ }
+ if (revents & POLLIN) {
+ done = buf_rec(&recbuf, hdl);
+ buf_write(&recbuf, recfd);
+ //fprintf(stderr, "%ld.%06ld: recored %u\n",
+ // tv.tv_sec, tv.tv_usec, done);
+ }
+ if (revents & POLLOUT) {
+ done = buf_play(&playbuf, hdl);
+ buf_read(&playbuf, playfd);
+ }
+#if 0
+ if (pos / par.rate > 2) {
+ if (!sa_stop(hdl)) {
+ fprintf(stderr, "sa_stop failed\n");
+ exit(1);
+ }
+ pos = plat = rlat = 0;
+ fprintf(stderr, "pausing...\n");
+ sleep(1);
+ if (!sa_start(hdl)) {
+ fprintf(stderr, "sa_start failed\n");
+ exit(1);
+ }
+ }
+#endif
+ }
+ sa_close(hdl);
+ return 0;
+}
diff --git a/regress/lib/libsa/saplay/Makefile b/regress/lib/libsa/saplay/Makefile
new file mode 100644
index 00000000000..d428ce74d41
--- /dev/null
+++ b/regress/lib/libsa/saplay/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+PROG= saplay
+LDADD= -lsa
+REGRESS_SKIP=
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libsa/saplay/saplay.c b/regress/lib/libsa/saplay/saplay.c
new file mode 100644
index 00000000000..5c9ae2433b2
--- /dev/null
+++ b/regress/lib/libsa/saplay/saplay.c
@@ -0,0 +1,133 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "libsa.h"
+
+#define BUFSZ 0x100
+unsigned char buf[BUFSZ];
+struct sa_par par;
+char *xstr[] = SA_XSTRINGS;
+
+long long realpos = 0, playpos = 0;
+
+void
+cb(void *addr, int delta)
+{
+ int bytes = delta * (int)(par.bps * par.pchan);
+
+ realpos += bytes;
+
+ fprintf(stderr,
+ "cb: bytes = %+7d, latency = %+7lld, "
+ "realpos = %+7lld, bufused = %+7lld\n",
+ bytes, playpos - realpos,
+ realpos, (realpos < 0) ? playpos : playpos - realpos);
+}
+
+void
+usage(void) {
+ fprintf(stderr, "usage: saplay [-r rate] [-c nchan] [-e enc]\n");
+}
+
+int
+main(int argc, char **argv) {
+ int ch;
+ struct sa_hdl *hdl;
+ ssize_t n, len;
+
+ /*
+ * defaults parameters
+ */
+ sa_initpar(&par);
+ par.sig = 1;
+ par.bits = 16;
+ par.pchan = 2;
+ par.rate = 44100;
+
+ while ((ch = getopt(argc, argv, "r:c:e:b:x:")) != -1) {
+ switch(ch) {
+ case 'r':
+ if (sscanf(optarg, "%u", &par.rate) != 1) {
+ fprintf(stderr, "%s: bad rate\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'c':
+ if (sscanf(optarg, "%u", &par.pchan) != 1) {
+ fprintf(stderr, "%s: bad channels\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'e':
+ if (!sa_strtoenc(&par, optarg)) {
+ fprintf(stderr, "%s: bad encoding\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'b':
+ if (sscanf(optarg, "%u", &par.bufsz) != 1) {
+ fprintf(stderr, "%s: bad buf size\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'x':
+ for (par.xrun = 0;; par.xrun++) {
+ if (par.xrun == sizeof(xstr) / sizeof(char *)) {
+ fprintf(stderr,
+ "%s: bad xrun mode\n", optarg);
+ exit(1);
+ }
+ if (strcmp(xstr[par.xrun], optarg) == 0)
+ break;
+ }
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+
+ hdl = sa_open(NULL, SA_PLAY, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "sa_open() failed\n");
+ exit(1);
+ }
+ sa_onmove(hdl, cb, NULL);
+ if (!sa_setpar(hdl, &par)) {
+ fprintf(stderr, "sa_setpar() failed\n");
+ exit(1);
+ }
+ if (!sa_getpar(hdl, &par)) {
+ fprintf(stderr, "sa_getpar() failed\n");
+ exit(1);
+ }
+ if (!sa_start(hdl)) {
+ fprintf(stderr, "sa_start() failed\n");
+ exit(1);
+ }
+ fprintf(stderr, "using %u bytes per buffer, rounding to %u\n",
+ par.bufsz * par.bps * par.pchan,
+ par.round * par.bps * par.pchan);
+ for (;;) {
+ len = read(STDIN_FILENO, buf, BUFSZ);
+ if (len < 0) {
+ perror("stdin");
+ exit(1);
+ }
+ if (len == 0)
+ break;
+ n = sa_write(hdl, buf, len);
+ if (n == 0) {
+ fprintf(stderr, "sa_write: failed\n");
+ exit(1);
+ }
+ playpos += n;
+ }
+ sa_close(hdl);
+ return 0;
+}
diff --git a/regress/lib/libsa/sarec/Makefile b/regress/lib/libsa/sarec/Makefile
new file mode 100644
index 00000000000..bdc207f97c5
--- /dev/null
+++ b/regress/lib/libsa/sarec/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.1 2008/10/26 08:49:44 ratchov Exp $
+PROG= sarec
+LDADD= -lsa
+REGRESS_SKIP=
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libsa/sarec/sarec.c b/regress/lib/libsa/sarec/sarec.c
new file mode 100644
index 00000000000..269c51210f2
--- /dev/null
+++ b/regress/lib/libsa/sarec/sarec.c
@@ -0,0 +1,119 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "libsa.h"
+
+#define BUFSZ 0x1000
+unsigned char buf[BUFSZ];
+struct sa_par par;
+char *xstr[] = SA_XSTRINGS;
+
+long long pos = 0;
+int rlat = 0;
+
+void
+cb(void *addr, int delta)
+{
+ pos += delta;
+ rlat += delta;
+ fprintf(stderr,
+ "cb: delta = %+7d, rlat = %+7d, pos = %+7lld\n",
+ delta, rlat, pos);
+}
+
+void
+usage(void) {
+ fprintf(stderr, "usage: sarec [-r rate] [-c nchan] [-e enc]\n");
+}
+
+int
+main(int argc, char **argv) {
+ int ch;
+ struct sa_hdl *hdl;
+ ssize_t n;
+
+ /*
+ * defaults parameters
+ */
+ sa_initpar(&par);
+ par.sig = 1;
+ par.bits = 16;
+ par.rchan = 2;
+ par.rate = 44100;
+ par.bufsz = 0x10000;
+
+ while ((ch = getopt(argc, argv, "r:c:e:b:x:")) != -1) {
+ switch(ch) {
+ case 'r':
+ if (sscanf(optarg, "%u", &par.rate) != 1) {
+ fprintf(stderr, "%s: bad rate\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'c':
+ if (sscanf(optarg, "%u", &par.rchan) != 1) {
+ fprintf(stderr, "%s: channels number\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'e':
+ if (!sa_strtoenc(&par, optarg)) {
+ fprintf(stderr, "%s: unknown encoding\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'x':
+ for (par.xrun = 0;; par.xrun++) {
+ if (par.xrun == sizeof(xstr) / sizeof(char *)) {
+ fprintf(stderr,
+ "%s: bad xrun mode\n", optarg);
+ exit(1);
+ }
+ if (strcmp(xstr[par.xrun], optarg) == 0)
+ break;
+ }
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+
+ hdl = sa_open(NULL, SA_REC, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "sa_open() failed\n");
+ exit(1);
+ }
+ sa_onmove(hdl, cb, NULL);
+ if (!sa_setpar(hdl, &par)) {
+ fprintf(stderr, "sa_setpar() failed\n");
+ exit(1);
+ }
+ if (!sa_getpar(hdl, &par)) {
+ fprintf(stderr, "sa_getpar() failed\n");
+ exit(1);
+ }
+ if (!sa_start(hdl)) {
+ fprintf(stderr, "sa_start() failed\n");
+ exit(1);
+ }
+ for (;;) {
+ n = sa_read(hdl, buf, BUFSZ);
+ if (n == 0) {
+ fprintf(stderr, "sa_write: failed\n");
+ exit(1);
+ }
+ rlat -= n / (int)(par.bps * par.rchan);
+ if (write(STDOUT_FILENO, buf, n) < 0) {
+ perror("stdout");
+ exit(1);
+ }
+ }
+ sa_close(hdl);
+ return 0;
+}
diff --git a/usr.bin/aucat/Makefile b/usr.bin/aucat/Makefile
index 88c9a50e5b0..e6e600f3907 100644
--- a/usr.bin/aucat/Makefile
+++ b/usr.bin/aucat/Makefile
@@ -1,8 +1,8 @@
-# $OpenBSD: Makefile,v 1.3 2008/08/14 09:58:55 ratchov Exp $
+# $OpenBSD: Makefile,v 1.4 2008/10/26 08:49:43 ratchov Exp $
PROG= aucat
SRCS= aucat.c abuf.c aparams.c aproc.c dev.c file.c headers.c \
- dev_sun.c legacy.c
+ safile.c sock.c pipe.c listen.c wav.c legacy.c
CFLAGS+= -DDEBUG -Wall -Wstrict-prototypes -Werror -Wundef
-
+LDADD+= -lsa
.include <bsd.prog.mk>
diff --git a/usr.bin/aucat/abuf.c b/usr.bin/aucat/abuf.c
index 574b80262b2..61a18a17293 100644
--- a/usr.bin/aucat/abuf.c
+++ b/usr.bin/aucat/abuf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: abuf.c,v 1.6 2008/08/14 10:02:10 ratchov Exp $ */
+/* $OpenBSD: abuf.c,v 1.7 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -23,25 +23,45 @@
* (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
*
- * TODO:
- *
- * (hard) make abuf_fill() a boolean depending on whether
- * eof is reached. So the caller can do:
- *
- * if (!abuf_fill(buf)) {
- * ...
- * }
+ * use blocks instead of frames for WOK and ROK macros. If necessary
+ * (unlikely) define reader block size and writer blocks size to
+ * ease pipe/socket implementation
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <stdarg.h>
#include "conf.h"
#include "aproc.h"
#include "abuf.h"
+#ifdef DEBUG
+void
+abuf_dprn(int n, struct abuf *buf, char *fmt, ...)
+{
+ va_list ap;
+
+ if (debug_level < n)
+ return;
+ fprintf(stderr, "%s->%s: ",
+ buf->wproc ? buf->wproc->name : "none",
+ buf->rproc ? buf->rproc->name : "none");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+}
+#define ABUF_DPRN(n, buf, ...) abuf_dprn((n), (buf), __VA_ARGS__)
+#define ABUF_DPR(buf, ...) abuf_dprn(1, (buf), __VA_ARGS__)
+#else
+#define ABUF_DPRN(n, buf, ...) do {} while (0)
+#define ABUF_DPR(buf, ...) do {} while (0)
+#endif
+
struct abuf *
abuf_new(unsigned nfr, unsigned bpf)
{
@@ -51,9 +71,11 @@ abuf_new(unsigned nfr, unsigned bpf)
len = nfr * bpf;
buf = malloc(sizeof(struct abuf) + len);
if (buf == NULL) {
- err(1, "abuf_new: malloc");
+ fprintf(stderr, "abuf_new: out of mem: %u * %u\n", nfr, bpf);
+ abort();
}
buf->bpf = bpf;
+ buf->inuse = 0;
/*
* fill fifo pointers
@@ -66,6 +88,7 @@ abuf_new(unsigned nfr, unsigned bpf)
buf->drop = 0;
buf->rproc = NULL;
buf->wproc = NULL;
+ buf->duplex = NULL;
buf->data = (unsigned char *)buf + sizeof(*buf);
return buf;
}
@@ -73,17 +96,14 @@ abuf_new(unsigned nfr, unsigned bpf)
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);
+ if (buf->duplex)
+ buf->duplex->duplex = NULL;
+#ifdef DEBUG
+ if (buf->rproc || buf->wproc || ABUF_ROK(buf)) {
+ ABUF_DPRN(0, buf, "abuf_del: used = %u\n", buf->used);
abort();
}
- if (buf->used > 0)
- fprintf(stderr, "abuf_del: used = %u\n", buf->used);
+#endif
free(buf);
}
@@ -99,6 +119,14 @@ abuf_rgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
used = buf->used - ofs;
if (start >= buf->len)
start -= buf->len;
+#ifdef DEBUG
+ if (start >= buf->len || used > buf->used) {
+ ABUF_DPRN(0, buf, "abuf_rgetblk: "
+ "bad ofs: start = %u used = %u/%u, ofs = %u\n",
+ buf->start, buf->used, buf->len, ofs);
+ abort();
+ }
+#endif
count = buf->len - start;
if (count > used)
count = used;
@@ -112,6 +140,12 @@ abuf_rgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
void
abuf_rdiscard(struct abuf *buf, unsigned count)
{
+#ifdef DEBUG
+ if (count > buf->used) {
+ ABUF_DPRN(0, buf, "abuf_rdiscard: bad count %u\n", count);
+ abort();
+ }
+#endif
buf->used -= count;
buf->start += count;
if (buf->start >= buf->len)
@@ -125,6 +159,12 @@ abuf_rdiscard(struct abuf *buf, unsigned count)
void
abuf_wcommit(struct abuf *buf, unsigned count)
{
+#ifdef DEBUG
+ if (count > (buf->len - buf->used)) {
+ ABUF_DPR(buf, "abuf_wcommit: bad count\n");
+ abort();
+ }
+#endif
buf->used += count;
}
@@ -142,7 +182,7 @@ abuf_wgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
end -= buf->len;
#ifdef DEBUG
if (end >= buf->len) {
- fprintf(stderr, "abuf_wgetblk: %s -> %s: bad ofs, "
+ ABUF_DPR(buf, "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);
@@ -171,33 +211,24 @@ abuf_flush_do(struct abuf *buf)
count = buf->drop;
if (count > buf->used)
count = buf->used;
+ if (count == 0) {
+ ABUF_DPR(buf, "abuf_flush_do: no data to drop\n");
+ return 0;
+ }
abuf_rdiscard(buf, count);
buf->drop -= count;
- DPRINTF("abuf_flush_do: drop = %u\n", buf->drop);
+ ABUF_DPR(buf, "abuf_flush_do: drop = %u\n", buf->drop);
+ p = buf->rproc;
} else {
+ ABUF_DPRN(4, buf, "abuf_flush_do: in ready\n");
p = buf->rproc;
- if (p == NULL || !p->ops->in(p, buf))
+ if (!p || !p->ops->in(p, buf))
return 0;
}
return 1;
}
/*
- * 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)
-{
- for (;;) {
- if (!ABUF_ROK(buf))
- break;
- if (!abuf_flush_do(buf))
- break;
- }
-}
-
-/*
* fill the buffer either by generating silence or by calling the aproc
* call-back to provide data. Return 0 if blocked, 1 otherwise
*/
@@ -212,14 +243,93 @@ abuf_fill_do(struct abuf *buf)
data = abuf_wgetblk(buf, &count, 0);
if (count >= buf->silence)
count = buf->silence;
+ if (count == 0) {
+ ABUF_DPR(buf, "abuf_fill_do: no space for silence\n");
+ return 0;
+ }
memset(data, 0, count);
abuf_wcommit(buf, count);
buf->silence -= count;
- DPRINTF("abuf_fill_do: silence = %u\n", buf->silence);
+ ABUF_DPR(buf, "abuf_fill_do: silence = %u\n", buf->silence);
+ p = buf->wproc;
} else {
+ ABUF_DPRN(4, buf, "abuf_fill_do: out avail\n");
p = buf->wproc;
- if (p == NULL || !p->ops->out(p, buf))
+ if (p == NULL || !p->ops->out(p, buf)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*
+ * Notify the reader that there will be no more input (producer
+ * disappeared) and destroy the buffer
+ */
+void
+abuf_eof_do(struct abuf *buf)
+{
+ struct aproc *p;
+
+ p = buf->rproc;
+ if (p) {
+ ABUF_DPRN(2, buf, "abuf_eof_do: signaling reader\n");
+ buf->rproc = NULL;
+ LIST_REMOVE(buf, ient);
+ buf->inuse++;
+ p->ops->eof(p, buf);
+ buf->inuse--;
+ } else
+ ABUF_DPR(buf, "abuf_eof_do: no reader, freeng buf\n");
+ abuf_del(buf);
+}
+
+/*
+ * Notify the writer that the buffer has no more consumer,
+ * and destroy the buffer
+ */
+void
+abuf_hup_do(struct abuf *buf)
+{
+ struct aproc *p;
+
+ if (ABUF_ROK(buf)) {
+ ABUF_DPR(buf, "abuf_hup_do: lost %u bytes\n", buf->used);
+ buf->used = 0;
+ }
+ p = buf->wproc;
+ if (p != NULL) {
+ ABUF_DPRN(2, buf, "abuf_hup_do: signaling writer\n");
+ buf->wproc = NULL;
+ LIST_REMOVE(buf, oent);
+ buf->inuse++;
+ p->ops->hup(p, buf);
+ buf->inuse--;
+ } else
+ ABUF_DPR(buf, "abuf_hup_do: no writer, freeng buf\n");
+ abuf_del(buf);
+}
+
+/*
+ * Notify the read end of the buffer that there is input available
+ * and that data can be processed again.
+ */
+int
+abuf_flush(struct abuf *buf)
+{
+ if (buf->inuse) {
+ ABUF_DPRN(4, buf, "abuf_flush: blocked\n");
+ } else {
+ buf->inuse++;
+ for (;;) {
+ if (!abuf_flush_do(buf))
+ break;
+ }
+ buf->inuse--;
+ if (ABUF_HUP(buf)) {
+ abuf_hup_do(buf);
return 0;
+ }
}
return 1;
}
@@ -229,18 +339,28 @@ abuf_fill_do(struct abuf *buf)
* 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.
+ * Return 1 if the buffer was filled, and 0 if eof condition occured. The
+ * reader must detach the buffer on EOF condition, since it's aproc->eof()
+ * call-back will never be called.
*/
-void
+int
abuf_fill(struct abuf *buf)
{
- for (;;) {
- if (!ABUF_WOK(buf))
- break;
- if (!abuf_fill_do(buf))
- break;
+ if (buf->inuse) {
+ ABUF_DPRN(4, buf, "abuf_fill: blocked\n");
+ } else {
+ buf->inuse++;
+ for (;;) {
+ if (!abuf_fill_do(buf))
+ break;
+ }
+ buf->inuse--;
+ if (ABUF_EOF(buf)) {
+ abuf_eof_do(buf);
+ return 0;
+ }
}
+ return 1;
}
/*
@@ -254,24 +374,35 @@ abuf_fill(struct abuf *buf)
void
abuf_run(struct abuf *buf)
{
- struct aproc *p;
int canfill = 1, canflush = 1;
+ if (buf->inuse) {
+ ABUF_DPRN(4, buf, "abuf_run: blocked\n");
+ return;
+ }
+ buf->inuse++;
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) {
- canfill = abuf_fill_do(buf);
- } else if (ABUF_ROK(buf) && canflush) {
- canflush = abuf_flush_do(buf);
+ if (canfill) {
+ if (!abuf_fill_do(buf))
+ canfill = 0;
+ else
+ canflush = 1;
+ } else if (canflush) {
+ if (!abuf_flush_do(buf))
+ canflush = 0;
+ else
+ canfill = 1;
} else
- break; /* can neither read nor write */
+ break;
+ }
+ buf->inuse--;
+ if (ABUF_EOF(buf)) {
+ abuf_eof_do(buf);
+ return;
+ }
+ if (ABUF_HUP(buf)) {
+ abuf_hup_do(buf);
+ return;
}
}
@@ -285,31 +416,32 @@ abuf_eof(struct abuf *buf)
{
#ifdef DEBUG
if (buf->wproc == NULL) {
- fprintf(stderr, "abuf_eof: no writer\n");
+ ABUF_DPR(buf, "abuf_eof: no writer\n");
abort();
}
#endif
- DPRINTFN(2, "abuf_eof: requested by %s\n", buf->wproc->name);
+ ABUF_DPRN(2, buf, "abuf_eof: requested\n");
+ LIST_REMOVE(buf, oent);
buf->wproc = NULL;
if (buf->rproc != NULL) {
- abuf_flush(buf);
+ if (!abuf_flush(buf))
+ return;
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);
+ ABUF_DPRN(2, buf, "abuf_eof: will drain later\n");
return;
}
- DPRINTFN(2, "abuf_eof: signaling %s\n", buf->rproc->name);
- buf->rproc->ops->eof(buf->rproc, buf);
- buf->rproc = NULL;
}
- abuf_del(buf);
+ if (buf->inuse) {
+ ABUF_DPRN(2, buf, "abuf_eof: signal blocked\n");
+ return;
+ }
+ abuf_eof_do(buf);
}
-
/*
* Notify the writer that the buffer has no more consumer,
* and that no more data will accepted.
@@ -319,21 +451,54 @@ abuf_hup(struct abuf *buf)
{
#ifdef DEBUG
if (buf->rproc == NULL) {
- fprintf(stderr, "abuf_hup: no reader\n");
+ ABUF_DPR(buf, "abuf_hup: no reader\n");
abort();
}
#endif
- DPRINTFN(2, "abuf_hup: initiated by %s\n", buf->rproc->name);
+ ABUF_DPRN(2, buf, "abuf_hup: initiated\n");
+
buf->rproc = NULL;
+ LIST_REMOVE(buf, ient);
if (buf->wproc != NULL) {
- if (ABUF_ROK(buf)) {
- warnx("abuf_hup: %s: lost %u bytes",
- buf->wproc->name, buf->used);
- buf->used = 0;
+ if (buf->inuse) {
+ ABUF_DPRN(2, buf, "abuf_hup: signal blocked\n");
+ return;
}
- DPRINTFN(2, "abuf_hup: signaling %s\n", buf->wproc->name);
- buf->wproc->ops->hup(buf->wproc, buf);
- buf->wproc = NULL;
}
- abuf_del(buf);
+ abuf_hup_do(buf);
+}
+
+/*
+ * Notify the reader of the change of its real-time position
+ */
+void
+abuf_ipos(struct abuf *buf, int delta)
+{
+ struct aproc *p = buf->rproc;
+
+ if (p && p->ops->ipos) {
+ buf->inuse++;
+ p->ops->ipos(p, buf, delta);
+ buf->inuse--;
+ }
+ if (ABUF_HUP(buf))
+ abuf_hup_do(buf);
+}
+
+/*
+ * Notify the writer of the change of its real-time position
+ */
+void
+abuf_opos(struct abuf *buf, int delta)
+{
+ struct aproc *p = buf->wproc;
+
+
+ if (p && p->ops->opos) {
+ buf->inuse++;
+ p->ops->opos(p, buf, delta);
+ buf->inuse--;
+ }
+ if (ABUF_HUP(buf))
+ abuf_hup_do(buf);
}
diff --git a/usr.bin/aucat/abuf.h b/usr.bin/aucat/abuf.h
index e214eb132e7..aefe8698fb8 100644
--- a/usr.bin/aucat/abuf.h
+++ b/usr.bin/aucat/abuf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: abuf.h,v 1.8 2008/08/14 15:25:16 ratchov Exp $ */
+/* $OpenBSD: abuf.h,v 1.9 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -41,7 +41,7 @@ struct abuf {
unsigned xrun; /* common to mix and sub */
LIST_ENTRY(abuf) ient; /* for mix inputs list */
LIST_ENTRY(abuf) oent; /* for sub outputs list */
-
+
/*
* fifo parameters
*/
@@ -54,6 +54,8 @@ struct abuf {
unsigned drop; /* bytes to drop on next read */
struct aproc *rproc; /* reader */
struct aproc *wproc; /* writer */
+ struct abuf *duplex; /* link to buffer of the other direction */
+ unsigned inuse; /* in abuf_{flush,fill,run}() */
unsigned char *data; /* actual data (immediately following) */
};
@@ -74,6 +76,11 @@ struct abuf {
#define ABUF_EOF(b) (!ABUF_ROK(b) && (b)->wproc == NULL)
/*
+ * the buffer is empty and has no more writer
+ */
+#define ABUF_HUP(b) (!ABUF_WOK(b) && (b)->rproc == NULL)
+
+/*
* similar to !ABUF_WOK, but is used for file i/o, where
* operation may not involve an integer number of frames
*/
@@ -91,10 +98,12 @@ unsigned char *abuf_rgetblk(struct abuf *, unsigned *, unsigned);
unsigned char *abuf_wgetblk(struct abuf *, unsigned *, unsigned);
void abuf_rdiscard(struct abuf *, unsigned);
void abuf_wcommit(struct abuf *, unsigned);
-void abuf_fill(struct abuf *);
-void abuf_flush(struct abuf *);
+int abuf_fill(struct abuf *);
+int abuf_flush(struct abuf *);
void abuf_eof(struct abuf *);
void abuf_hup(struct abuf *);
void abuf_run(struct abuf *);
+void abuf_ipos(struct abuf *, int);
+void abuf_opos(struct abuf *, int);
#endif /* !defined(ABUF_H) */
diff --git a/usr.bin/aucat/amsg.h b/usr.bin/aucat/amsg.h
new file mode 100644
index 00000000000..510d45ad1ea
--- /dev/null
+++ b/usr.bin/aucat/amsg.h
@@ -0,0 +1,92 @@
+/* $OpenBSD: amsg.h,v 1.1 2008/10/26 08:49:43 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 SOCKET_H
+#define SOCKET_H
+
+#include <stdint.h>
+
+/*
+ * WARNING: since the protocol may be simultaneously used by static
+ * binaries or by different versions of a shared library, we are not
+ * allowed to change the packet binary representation in a backward
+ * incompatible way.
+ */
+struct amsg {
+#define AMSG_ACK 0 /* ack for START/STOP */
+#define AMSG_GETPAR 1 /* get the current parameters */
+#define AMSG_SETPAR 2 /* set the current parameters */
+#define AMSG_START 3 /* request the server to start the stream */
+#define AMSG_STOP 4 /* request the server to stop the stream */
+#define AMSG_DATA 5 /* data block */
+#define AMSG_MOVE 6 /* position changed */
+#define AMSG_GETCAP 7 /* get capabilities */
+ uint32_t cmd;
+ uint32_t __pad;
+ union {
+ struct amsg_par {
+#define AMSG_PLAY 1 /* will play */
+#define AMSG_REC 2 /* will record */
+ uint8_t mode; /* a bitmap of above */
+#define AMSG_IGNORE 0 /* loose sync */
+#define AMSG_SYNC 1 /* resync after xrun */
+#define AMSG_ERROR 2 /* kill the stream */
+ uint8_t xrun; /* one of above */
+ uint8_t bps; /* bytes per sample */
+ uint8_t bits; /* actually used bits */
+ uint8_t msb; /* 1 if MSB justified */
+ uint8_t le; /* 1 if little endian */
+ uint8_t sig; /* 1 if signed */
+ uint8_t __pad1;
+ uint16_t pchan; /* play channels */
+ uint16_t rchan; /* record channels */
+ uint32_t rate; /* frames per second */
+ uint32_t bufsz; /* buffered frames */
+ uint32_t round;
+ uint32_t _reserved[2]; /* for future use */
+ } par;
+ struct amsg_cap {
+ uint32_t rate; /* native rate */
+ uint32_t rate_div; /* divisor of emul. rates */
+ uint16_t rchan; /* native rec channels */
+ uint16_t pchan; /* native play channels */
+ uint8_t bits; /* native bits per sample */
+ uint8_t bps; /* native ytes per sample */
+ uint8_t _reserved[10]; /* for future use */
+ } cap;
+ struct amsg_data {
+#define AMSG_DATAMAX 0x1000
+ uint32_t size;
+ } data;
+ struct amsg_ts {
+ int32_t delta;
+ } ts;
+ } u;
+};
+
+/*
+ * initialize an amsg structure: fill all fields with 0xff, so the read
+ * can test which fields were set
+ */
+#define AMSG_INIT(m) do { memset((m), 0xff, sizeof(struct amsg)); } while (0)
+
+/*
+ * since the structure is memset to 0xff, the MSB can be used to check
+ * if any filed was set
+ */
+#define AMSG_ISSET(x) (((x) & (1 << (8 * sizeof(x) - 1))) == 0)
+
+#endif /* !defined(SOCKET_H) */
diff --git a/usr.bin/aucat/aparams.c b/usr.bin/aucat/aparams.c
index cbecee24ede..40cc9cd7fb3 100644
--- a/usr.bin/aucat/aparams.c
+++ b/usr.bin/aucat/aparams.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aparams.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/* $OpenBSD: aparams.c,v 1.2 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -22,6 +22,128 @@
#include "aparams.h"
/*
+ * Generate a string corresponding to the encoding in par,
+ * return the length of the resulting string
+ */
+int
+aparams_enctostr(struct aparams *par, char *ostr)
+{
+ char *p = ostr;
+
+ *p++ = par->sig ? 's' : 'u';
+ if (par->bits > 9)
+ *p++ = '0' + par->bits / 10;
+ *p++ = '0' + par->bits % 10;
+ if (par->bps > 1) {
+ *p++ = par->le ? 'l' : 'b';
+ *p++ = 'e';
+ if (par->bps != APARAMS_BPS(par->bits) ||
+ par->bits < par->bps * 8) {
+ *p++ = par->bps + '0';
+ if (par->bits < par->bps * 8) {
+ *p++ = par->msb ? 'm' : 'l';
+ *p++ = 's';
+ *p++ = 'b';
+ }
+ }
+ }
+ *p++ = '\0';
+ return p - ostr - 1;
+}
+
+/*
+ * Parse an encoding string, examples: s8, u8, s16, s16le, s24be ...
+ * set *istr to the char following the encoding. Retrun the number
+ * of bytes consumed
+ */
+int
+aparams_strtoenc(struct aparams *par, char *istr)
+{
+ char *p = istr;
+ int i, sig, bits, le, bps, msb;
+
+#define IS_SEP(c) \
+ (((c) < 'a' || (c) > 'z') && \
+ ((c) < 'A' || (c) > 'Z') && \
+ ((c) < '0' || (c) > '9'))
+
+ /*
+ * get signedness
+ */
+ if (*p == 's') {
+ sig = 1;
+ } else if (*p == 'u') {
+ sig = 0;
+ } else
+ return 0;
+ p++;
+
+ /*
+ * get number of bits per sample
+ */
+ bits = 0;
+ for (i = 0; i < 2; i++) {
+ if (*p < '0' || *p > '9')
+ break;
+ bits = (bits * 10) + *p - '0';
+ p++;
+ }
+ if (bits < BITS_MIN || bits > BITS_MAX)
+ return 0;
+ bps = APARAMS_BPS(bits);
+ msb = 1;
+ le = NATIVE_LE;
+
+ /*
+ * get (optionnal) endianness
+ */
+ if (p[0] == 'l' && p[1] == 'e') {
+ le = 1;
+ p += 2;
+ } else if (p[0] == 'b' && p[1] == 'e') {
+ le = 0;
+ p += 2;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+
+ /*
+ * get (optionnal) number of bytes
+ */
+ if (*p >= '0' && *p <= '9') {
+ bps = *p - '0';
+ if (bps < (bits + 7) / 8 ||
+ bps > (BITS_MAX + 7) / 8)
+ return 0;
+ p++;
+
+ /*
+ * get (optionnal) alignement
+ */
+ if (p[0] == 'm' && p[1] == 's' && p[2] == 'b') {
+ msb = 1;
+ p += 3;
+ } else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b') {
+ msb = 0;
+ p += 3;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+ } else if (!IS_SEP(*p))
+ return 0;
+
+done:
+ par->msb = msb;
+ par->sig = sig;
+ par->bits = bits;
+ par->bps = bps;
+ par->le = le;
+ return p - istr;
+}
+
+/*
* Initialise parameters structure with the defaults natively supported
* by the machine.
*/
@@ -44,14 +166,10 @@ aparams_init(struct aparams *par, unsigned cmin, unsigned cmax, unsigned rate)
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");
- }
+ char enc[ENCMAX];
+
+ aparams_enctostr(par, enc);
+ fprintf(stderr, "%s", enc);
fprintf(stderr, ",%u:%u", par->cmin, par->cmax);
fprintf(stderr, ",%uHz", par->rate);
}
@@ -92,3 +210,14 @@ aparams_bpf(struct aparams *par)
{
return (par->cmax - par->cmin + 1) * par->bps;
}
+
+void
+aparams_copyenc(struct aparams *dst, struct aparams *src)
+{
+ dst->sig = src->sig;
+ dst->le = src->le;
+ dst->msb = src->msb;
+ dst->bits = src->bits;
+ dst->bps = src->bps;
+}
+
diff --git a/usr.bin/aucat/aparams.h b/usr.bin/aucat/aparams.h
index ce1bfa68490..8d394502229 100644
--- a/usr.bin/aucat/aparams.h
+++ b/usr.bin/aucat/aparams.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: aparams.h,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/* $OpenBSD: aparams.h,v 1.2 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -19,11 +19,18 @@
#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 NCHAN_MAX 16 /* max channel in a stream */
+#define RATE_MIN 4000 /* min sample rate */
+#define RATE_MAX 192000 /* max sample rate */
+#define BITS_MIN 1 /* min bits per sample */
#define BITS_MAX 32 /* max bits per sample */
+/*
+ * maximum size of the encording string (the longest possible
+ * encoding is ``s24le3msb'')
+ */
+#define ENCMAX 10
+
#if BYTE_ORDER == LITTLE_ENDIAN
#define NATIVE_LE 1
#elif BYTE_ORDER == BIG_ENDIAN
@@ -33,6 +40,11 @@
#endif
/*
+ * default bytes per sample for the given bits per sample
+ */
+#define APARAMS_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4))
+
+/*
* encoding specification
*/
struct aparams {
@@ -62,5 +74,8 @@ void aparams_print(struct aparams *);
void aparams_print2(struct aparams *, struct aparams *);
int aparams_eq(struct aparams *, struct aparams *);
unsigned aparams_bpf(struct aparams *);
+int aparams_strtoenc(struct aparams *, char *);
+int aparams_enctostr(struct aparams *, char *);
+void aparams_copyenc(struct aparams *, struct aparams *);
#endif /* !defined(APARAMS_H) */
diff --git a/usr.bin/aucat/aproc.c b/usr.bin/aucat/aproc.c
index fdace667490..527a874042d 100644
--- a/usr.bin/aucat/aproc.c
+++ b/usr.bin/aucat/aproc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aproc.c,v 1.11 2008/08/20 14:22:50 ratchov Exp $ */
+/* $OpenBSD: aproc.c,v 1.12 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -39,6 +39,7 @@
*
* (hard) add a lowpass filter for the resampler. Quality is
* not acceptable as is.
+ *
*/
#include <err.h>
#include <limits.h>
@@ -70,9 +71,22 @@ aproc_new(struct aproc_ops *ops, char *name)
void
aproc_del(struct aproc *p)
{
+ struct abuf *i;
+
+ DPRINTF("aproc_del: %s(%s): terminating...\n", p->ops->name, p->name);
+
if (p->ops->done)
p->ops->done(p);
- DPRINTF("aproc_del: %s: %s: deleted\n", p->ops->name, p->name);
+
+ while (!LIST_EMPTY(&p->ibuflist)) {
+ i = LIST_FIRST(&p->ibuflist);
+ abuf_hup(i);
+ }
+ while (!LIST_EMPTY(&p->obuflist)) {
+ i = LIST_FIRST(&p->obuflist);
+ abuf_eof(i);
+ }
+ DPRINTF("aproc_del: %s(%s): freed\n", p->ops->name, p->name);
free(p);
}
@@ -94,6 +108,30 @@ aproc_setout(struct aproc *p, struct abuf *obuf)
p->ops->newout(p, obuf);
}
+void
+aproc_ipos(struct aproc *p, struct abuf *ibuf, int delta)
+{
+ struct abuf *obuf;
+
+ DPRINTFN(3, "aproc_ipos: %s: delta = %d\n", p->name, delta);
+
+ LIST_FOREACH(obuf, &p->obuflist, oent) {
+ abuf_ipos(obuf, delta);
+ }
+}
+
+void
+aproc_opos(struct aproc *p, struct abuf *obuf, int delta)
+{
+ struct abuf *ibuf;
+
+ DPRINTFN(3, "aproc_opos: %s: delta = %d\n", p->name, delta);
+
+ LIST_FOREACH(ibuf, &p->ibuflist, ient) {
+ abuf_opos(ibuf, delta);
+ }
+}
+
int
rpipe_in(struct aproc *p, struct abuf *ibuf_dummy)
{
@@ -104,13 +142,16 @@ rpipe_in(struct aproc *p, struct abuf *ibuf_dummy)
DPRINTFN(3, "rpipe_in: %s\n", p->name);
- if (ABUF_FULL(obuf))
+ if (ABUF_FULL(obuf) || !(f->state & FILE_ROK))
return 0;
data = abuf_wgetblk(obuf, &count, 0);
count = file_read(f, data, count);
+ if (count == 0)
+ return 0;
abuf_wcommit(obuf, count);
- abuf_flush(obuf);
- return !ABUF_FULL(obuf);
+ if (!abuf_flush(obuf))
+ return 0;
+ return 1;
}
int
@@ -120,14 +161,18 @@ rpipe_out(struct aproc *p, struct abuf *obuf)
unsigned char *data;
unsigned count;
+ if (f->refs > 0)
+ return 0;
DPRINTFN(3, "rpipe_out: %s\n", p->name);
-
- if (!(f->state & FILE_ROK))
+
+ if (ABUF_FULL(obuf) || !(f->state & FILE_ROK))
return 0;
data = abuf_wgetblk(obuf, &count, 0);
count = file_read(f, data, count);
+ if (count == 0)
+ return 0;
abuf_wcommit(obuf, count);
- return f->state & FILE_ROK;
+ return 1;
}
void
@@ -136,14 +181,14 @@ rpipe_done(struct aproc *p)
struct file *f = p->u.io.file;
f->rproc = NULL;
- f->events &= ~POLLIN;
+ if (f->wproc == NULL)
+ file_del(f);
}
void
rpipe_eof(struct aproc *p, struct abuf *ibuf_dummy)
{
DPRINTFN(3, "rpipe_eof: %s\n", p->name);
- abuf_eof(LIST_FIRST(&p->obuflist));
aproc_del(p);
}
@@ -155,7 +200,16 @@ rpipe_hup(struct aproc *p, struct abuf *obuf)
}
struct aproc_ops rpipe_ops = {
- "rpipe", rpipe_in, rpipe_out, rpipe_eof, rpipe_hup, NULL, NULL, rpipe_done
+ "rpipe",
+ rpipe_in,
+ rpipe_out,
+ rpipe_eof,
+ rpipe_hup,
+ NULL, /* newin */
+ NULL, /* newout */
+ aproc_ipos,
+ aproc_opos,
+ rpipe_done
};
struct aproc *
@@ -165,8 +219,7 @@ rpipe_new(struct file *f)
p = aproc_new(&rpipe_ops, f->name);
p->u.io.file = f;
- f->rproc = p;
- f->events |= POLLIN;
+ f->rproc = p;
return p;
}
@@ -176,7 +229,8 @@ wpipe_done(struct aproc *p)
struct file *f = p->u.io.file;
f->wproc = NULL;
- f->events &= ~POLLOUT;
+ if (f->rproc == NULL)
+ file_del(f);
}
int
@@ -186,15 +240,18 @@ wpipe_in(struct aproc *p, struct abuf *ibuf)
unsigned char *data;
unsigned count;
+ if (f->refs > 0)
+ return 0;
DPRINTFN(3, "wpipe_in: %s\n", p->name);
- if (!(f->state & FILE_WOK))
+ if (ABUF_EMPTY(ibuf) || !(f->state & FILE_WOK))
return 0;
-
data = abuf_rgetblk(ibuf, &count, 0);
count = file_write(f, data, count);
+ if (count == 0)
+ return 0;
abuf_rdiscard(ibuf, count);
- return f->state & FILE_WOK;
+ return 1;
}
int
@@ -207,17 +264,21 @@ wpipe_out(struct aproc *p, struct abuf *obuf_dummy)
DPRINTFN(3, "wpipe_out: %s\n", p->name);
- if (ABUF_EMPTY(ibuf))
+ if (!abuf_fill(ibuf)) {
+ DPRINTFN(3, "wpipe_out: fill failed\n");
+ return 0;
+ }
+ if (ABUF_EMPTY(ibuf) || !(f->state & FILE_WOK))
return 0;
data = abuf_rgetblk(ibuf, &count, 0);
- count = file_write(f, data, count);
- abuf_rdiscard(ibuf, count);
- if (ABUF_EOF(ibuf)) {
- abuf_hup(ibuf);
- aproc_del(p);
+ if (count == 0) {
+ DPRINTF("wpipe_out: %s: underrun\n", p->name);
return 0;
}
- abuf_fill(ibuf);
+ count = file_write(f, data, count);
+ if (count == 0)
+ return 0;
+ abuf_rdiscard(ibuf, count);
return 1;
}
@@ -232,12 +293,20 @@ void
wpipe_hup(struct aproc *p, struct abuf *obuf_dummy)
{
DPRINTFN(3, "wpipe_hup: %s\n", p->name);
- abuf_hup(LIST_FIRST(&p->ibuflist));
aproc_del(p);
}
struct aproc_ops wpipe_ops = {
- "wpipe", wpipe_in, wpipe_out, wpipe_eof, wpipe_hup, NULL, NULL, wpipe_done
+ "wpipe",
+ wpipe_in,
+ wpipe_out,
+ wpipe_eof,
+ wpipe_hup,
+ NULL, /* newin */
+ NULL, /* newout */
+ aproc_ipos,
+ aproc_opos,
+ wpipe_done
};
struct aproc *
@@ -248,7 +317,6 @@ wpipe_new(struct file *f)
p = aproc_new(&wpipe_ops, f->name);
p->u.io.file = f;
f->wproc = p;
- f->events |= POLLOUT;
return p;
}
@@ -265,6 +333,7 @@ mix_bzero(struct aproc *p)
DPRINTFN(4, "mix_bzero: used = %u, todo = %u\n",
obuf->used, obuf->mixtodo);
odata = (short *)abuf_wgetblk(obuf, &ocount, obuf->mixtodo);
+ ocount -= ocount % obuf->bpf;
if (ocount == 0)
return;
memset(odata, 0, ocount);
@@ -286,10 +355,12 @@ mix_badd(struct abuf *ibuf, struct abuf *obuf)
obuf->mixtodo, ibuf->mixdone);
idata = (short *)abuf_rgetblk(ibuf, &icount, 0);
+ icount -= icount % ibuf->bpf;
if (icount == 0)
return;
odata = (short *)abuf_wgetblk(obuf, &ocount, ibuf->mixdone);
+ ocount -= ocount % obuf->bpf;
if (ocount == 0)
return;
@@ -306,16 +377,6 @@ mix_badd(struct abuf *ibuf, struct abuf *obuf)
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)
{
@@ -325,8 +386,9 @@ mix_in(struct aproc *p, struct abuf *ibuf)
DPRINTFN(4, "mix_in: used = %u, done = %u, todo = %u\n",
ibuf->used, ibuf->mixdone, obuf->mixtodo);
- if (ibuf->mixdone >= obuf->mixtodo)
+ if (!ABUF_ROK(ibuf) || ibuf->mixdone == obuf->mixtodo)
return 0;
+
mix_badd(ibuf, obuf);
ocount = obuf->mixtodo;
LIST_FOREACH(i, &p->ibuflist, ient) {
@@ -337,21 +399,18 @@ mix_in(struct aproc *p, struct abuf *ibuf)
return 0;
abuf_wcommit(obuf, ocount);
+ p->u.mix.lat += ocount / obuf->bpf;
obuf->mixtodo -= ocount;
- abuf_flush(obuf);
+ if (!abuf_flush(obuf))
+ return 0; /* hup */
mix_bzero(p);
- for (i = LIST_FIRST(&p->ibuflist); i != LIST_END(&p->ibuflist); i = inext) {
+ for (i = LIST_FIRST(&p->ibuflist); i != NULL; 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;
- }
+ if (i->mixdone < obuf->mixtodo)
mix_badd(i, obuf);
- abuf_fill(i);
- }
+ if (!abuf_fill(i))
+ continue;
}
return 1;
}
@@ -365,14 +424,18 @@ mix_out(struct aproc *p, struct abuf *obuf)
DPRINTFN(4, "mix_out: used = %u, todo = %u\n",
obuf->used, obuf->mixtodo);
+ if (!ABUF_WOK(obuf))
+ return 0;
+
mix_bzero(p);
ocount = obuf->mixtodo;
- for (i = LIST_FIRST(&p->ibuflist); i != LIST_END(&p->ibuflist); i = inext) {
+ for (i = LIST_FIRST(&p->ibuflist); i != NULL; i = inext) {
inext = LIST_NEXT(i, ient);
+ if (!abuf_fill(i))
+ continue;
if (!ABUF_ROK(i)) {
if ((p->u.mix.flags & MIX_DROP) && i->mixdone == 0) {
if (i->xrun == XRUN_ERROR) {
- mix_rm(p, i);
abuf_hup(i);
continue;
}
@@ -380,28 +443,33 @@ mix_out(struct aproc *p, struct abuf *obuf)
i->mixdone += drop;
if (i->xrun == XRUN_SYNC)
i->drop += drop;
+ else {
+ abuf_opos(i, -(int)(drop / i->bpf));
+ if (i->duplex) {
+ DPRINTF("mix_out: duplex %u\n",
+ drop);
+ i->duplex->drop += drop *
+ i->duplex->bpf / i->bpf;
+ abuf_ipos(i->duplex,
+ -(int)(drop / i->bpf));
+ }
+ }
DPRINTF("mix_out: drop = %u\n", i->drop);
}
} else
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) && (p->u.mix.flags & MIX_AUTOQUIT)) {
DPRINTF("mix_out: nothing more to do...\n");
- obuf->wproc = NULL;
aproc_del(p);
return 0;
}
abuf_wcommit(obuf, ocount);
+ p->u.mix.lat += ocount / obuf->bpf;
obuf->mixtodo -= ocount;
LIST_FOREACH(i, &p->ibuflist, ient) {
i->mixdone -= ocount;
@@ -415,7 +483,8 @@ 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);
+ mix_setmaster(p);
+
/*
* If there's no more inputs, abuf_run() will trigger the eof
* condition and propagate it, so no need to handle it here.
@@ -431,7 +500,6 @@ mix_hup(struct aproc *p, struct abuf *obuf)
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);
@@ -444,6 +512,7 @@ mix_newin(struct aproc *p, struct abuf *ibuf)
ibuf->mixdone = 0;
ibuf->mixvol = ADATA_UNIT;
ibuf->xrun = XRUN_IGNORE;
+ mix_setmaster(p);
}
void
@@ -453,17 +522,36 @@ mix_newout(struct aproc *p, struct abuf *obuf)
mix_bzero(p);
}
+void
+mix_opos(struct aproc *p, struct abuf *obuf, int delta)
+{
+ DPRINTFN(3, "mix_opos: lat = %d/%d\n", p->u.mix.lat, p->u.mix.maxlat);
+ p->u.mix.lat -= delta;
+ aproc_opos(p, obuf, delta);
+}
+
struct aproc_ops mix_ops = {
- "mix", mix_in, mix_out, mix_eof, mix_hup, mix_newin, mix_newout, NULL
+ "mix",
+ mix_in,
+ mix_out,
+ mix_eof,
+ mix_hup,
+ mix_newin,
+ mix_newout,
+ aproc_ipos,
+ mix_opos,
+ NULL
};
struct aproc *
-mix_new(void)
+mix_new(char *name, int maxlat)
{
struct aproc *p;
- p = aproc_new(&mix_ops, "softmix");
+ p = aproc_new(&mix_ops, name);
p->u.mix.flags = 0;
+ p->u.mix.lat = 0;
+ p->u.mix.maxlat = maxlat;
return p;
}
@@ -473,6 +561,7 @@ mix_pushzero(struct aproc *p)
struct abuf *obuf = LIST_FIRST(&p->obuflist);
abuf_wcommit(obuf, obuf->mixtodo);
+ p->u.mix.lat += obuf->mixtodo / obuf->bpf;
obuf->mixtodo = 0;
abuf_run(obuf);
mix_bzero(p);
@@ -504,63 +593,67 @@ sub_bcopy(struct abuf *ibuf, struct abuf *obuf)
unsigned icount, ocount, scount;
idata = abuf_rgetblk(ibuf, &icount, obuf->subdone);
+ icount -= icount % ibuf->bpf;
if (icount == 0)
return;
odata = abuf_wgetblk(obuf, &ocount, 0);
+ ocount -= icount % obuf->bpf;
if (ocount == 0)
return;
scount = (icount < ocount) ? icount : ocount;
memcpy(odata, idata, scount);
- abuf_wcommit(obuf, scount);
+ abuf_wcommit(obuf, scount);
obuf->subdone += 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);
-}
-
int
sub_in(struct aproc *p, struct abuf *ibuf)
{
struct abuf *i, *inext;
unsigned done, drop;
- int again;
-
- again = 1;
+
+ if (!ABUF_ROK(ibuf))
+ return 0;
done = ibuf->used;
- for (i = LIST_FIRST(&p->obuflist); i != LIST_END(&p->obuflist); i = inext) {
+ for (i = LIST_FIRST(&p->obuflist); i != NULL; i = inext) {
inext = LIST_NEXT(i, oent);
if (!ABUF_WOK(i)) {
if ((p->u.sub.flags & SUB_DROP) && i->subdone == 0) {
if (i->xrun == XRUN_ERROR) {
- sub_rm(p, i);
abuf_eof(i);
continue;
}
drop = ibuf->used;
if (i->xrun == XRUN_SYNC)
i->silence += drop;
+ else {
+ abuf_ipos(i, -(int)(drop / i->bpf));
+ if (i->duplex) {
+ DPRINTF("sub_in: duplex %u\n",
+ drop);
+ i->duplex->silence += drop *
+ i->duplex->bpf / i->bpf;
+ abuf_opos(i->duplex,
+ -(int)(drop / i->bpf));
+ }
+ }
i->subdone += drop;
- DPRINTF("sub_in: silence = %u\n", i->silence);
+ DPRINTF("sub_in: silence = %u\n", i->silence);
}
- } else {
+ } else
sub_bcopy(ibuf, i);
- abuf_flush(i);
- }
- if (!ABUF_WOK(i))
- again = 0;
if (done > i->subdone)
done = i->subdone;
+ if (!abuf_flush(i))
+ continue;
}
LIST_FOREACH(i, &p->obuflist, oent) {
i->subdone -= done;
}
abuf_rdiscard(ibuf, done);
- return again;
+ p->u.sub.lat -= done / ibuf->bpf;
+ return 1;
}
int
@@ -570,40 +663,28 @@ sub_out(struct aproc *p, struct abuf *obuf)
struct abuf *i, *inext;
unsigned done;
- if (obuf->subdone >= ibuf->used)
+ if (!ABUF_WOK(obuf))
+ return 0;
+ if (!abuf_fill(ibuf)) {
+ return 0;
+ }
+ 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);
- }
+ for (i = LIST_FIRST(&p->obuflist); i != NULL; i = inext) {
+ inext = LIST_NEXT(i, oent);
+ if (!abuf_flush(i))
+ continue;
+ sub_bcopy(ibuf, i);
if (done > i->subdone)
done = i->subdone;
}
- if (done == 0)
- return 0;
LIST_FOREACH(i, &p->obuflist, oent) {
i->subdone -= done;
}
abuf_rdiscard(ibuf, done);
- if (ABUF_EOF(ibuf)) {
- abuf_hup(ibuf);
- for (i = LIST_FIRST(&p->obuflist);
- i != LIST_END(&p->obuflist);
- i = inext) {
- inext = LIST_NEXT(i, oent);
- if (i != ibuf)
- abuf_eof(i);
- }
- ibuf->wproc = NULL;
- aproc_del(p);
- return 0;
- }
- abuf_fill(ibuf);
+ p->u.sub.lat -= done / ibuf->bpf;
return 1;
}
@@ -614,7 +695,6 @@ sub_eof(struct aproc *p, struct abuf *ibuf)
while (!LIST_EMPTY(&p->obuflist)) {
obuf = LIST_FIRST(&p->obuflist);
- sub_rm(p, obuf);
abuf_eof(obuf);
}
aproc_del(p);
@@ -626,12 +706,7 @@ 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) && (p->u.sub.flags & SUB_AUTOQUIT)) {
- abuf_hup(ibuf);
- aproc_del(p);
- } else
- abuf_run(ibuf);
+ abuf_run(ibuf);
DPRINTF("sub_hup: done\n");
}
@@ -642,21 +717,39 @@ sub_newout(struct aproc *p, struct abuf *obuf)
obuf->xrun = XRUN_IGNORE;
}
+void
+sub_ipos(struct aproc *p, struct abuf *ibuf, int delta)
+{
+ p->u.sub.lat += delta;
+ DPRINTFN(3, "sub_ipos: lat = %d/%d\n", p->u.sub.lat, p->u.sub.maxlat);
+ aproc_ipos(p, ibuf, delta);
+}
+
struct aproc_ops sub_ops = {
- "sub", sub_in, sub_out, sub_eof, sub_hup, NULL, sub_newout, NULL
+ "sub",
+ sub_in,
+ sub_out,
+ sub_eof,
+ sub_hup,
+ NULL,
+ sub_newout,
+ sub_ipos,
+ aproc_opos,
+ NULL
};
struct aproc *
-sub_new(void)
+sub_new(char *name, int maxlat)
{
struct aproc *p;
- p = aproc_new(&sub_ops, "copy");
+ p = aproc_new(&sub_ops, name);
p->u.sub.flags = 0;
+ p->u.sub.lat = 0;
+ p->u.sub.maxlat = maxlat;
return p;
}
-
/*
* Convert one block.
*/
@@ -695,9 +788,11 @@ conv_bcopy(struct aconv *ist, struct aconv *ost,
*/
idata = abuf_rgetblk(ibuf, &icount, 0);
ifr = icount / ibuf->bpf;
+ icount = ifr * ibuf->bpf;
odata = abuf_wgetblk(obuf, &ocount, 0);
ofr = ocount / obuf->bpf;
+ ocount = ofr * obuf->bpf;
/*
* Partially copy structures into local variables, to avoid
@@ -784,11 +879,14 @@ conv_in(struct aproc *p, struct abuf *ibuf)
{
struct abuf *obuf = LIST_FIRST(&p->obuflist);
- if (!ABUF_WOK(obuf))
+ DPRINTFN(4, "conv_in: %s\n", p->name);
+
+ if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf))
return 0;
conv_bcopy(&p->u.conv.ist, &p->u.conv.ost, ibuf, obuf);
- abuf_flush(obuf);
- return ABUF_WOK(obuf);
+ if (!abuf_flush(obuf))
+ return 0;
+ return 1;
}
int
@@ -796,30 +894,29 @@ conv_out(struct aproc *p, struct abuf *obuf)
{
struct abuf *ibuf = LIST_FIRST(&p->ibuflist);
- if (!ABUF_ROK(ibuf))
+ DPRINTFN(4, "conv_out: %s\n", p->name);
+
+ if (!abuf_fill(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);
- aproc_del(p);
+ if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf))
return 0;
- }
- abuf_fill(ibuf);
+ conv_bcopy(&p->u.conv.ist, &p->u.conv.ost, ibuf, obuf);
return 1;
}
void
conv_eof(struct aproc *p, struct abuf *ibuf)
{
- abuf_eof(LIST_FIRST(&p->obuflist));
+ DPRINTFN(4, "conv_eof: %s\n", p->name);
+
aproc_del(p);
}
void
conv_hup(struct aproc *p, struct abuf *obuf)
{
- abuf_hup(LIST_FIRST(&p->ibuflist));
+ DPRINTFN(4, "conv_hup: %s\n", p->name);
+
aproc_del(p);
}
@@ -850,12 +947,55 @@ aconv_init(struct aconv *st, struct aparams *par, int input)
st->rate = par->rate;
st->pos = 0;
- for (i = 0; i < CHAN_MAX; i++)
+ for (i = 0; i < NCHAN_MAX; i++)
st->ctx[i] = 0;
}
+void
+conv_ipos(struct aproc *p, struct abuf *ibuf, int delta)
+{
+ struct abuf *obuf = LIST_FIRST(&p->obuflist);
+ long long ipos;
+ int ifac, ofac;
+
+ DPRINTFN(3, "conv_ipos: %d\n", delta);
+
+ ifac = p->u.conv.ist.rate;
+ ofac = p->u.conv.ost.rate;
+ ipos = p->u.conv.idelta + (long long)delta * ofac;
+ delta = (ipos + ifac - 1) / ifac;
+ p->u.conv.idelta = ipos - (long long)delta * ifac;
+ abuf_ipos(obuf, delta);
+}
+
+void
+conv_opos(struct aproc *p, struct abuf *obuf, int delta)
+{
+ struct abuf *ibuf = LIST_FIRST(&p->ibuflist);
+ long long opos;
+ int ifac, ofac;
+
+ DPRINTFN(3, "conv_opos: %d\n", delta);
+
+ ifac = p->u.conv.ist.rate;
+ ofac = p->u.conv.ost.rate;
+ opos = p->u.conv.odelta + (long long)delta * ifac;
+ delta = (opos + ofac - 1) / ofac;
+ p->u.conv.odelta = opos - (long long)delta * ofac;
+ abuf_opos(ibuf, delta);
+}
+
struct aproc_ops conv_ops = {
- "conv", conv_in, conv_out, conv_eof, conv_hup, NULL, NULL, NULL
+ "conv",
+ conv_in,
+ conv_out,
+ conv_eof,
+ conv_hup,
+ NULL,
+ NULL,
+ conv_ipos,
+ conv_opos,
+ NULL
};
struct aproc *
@@ -866,5 +1006,12 @@ conv_new(char *name, struct aparams *ipar, struct aparams *opar)
p = aproc_new(&conv_ops, name);
aconv_init(&p->u.conv.ist, ipar, 1);
aconv_init(&p->u.conv.ost, opar, 0);
+ p->u.conv.idelta = 0;
+ p->u.conv.odelta = 0;
+ if (debug_level > 0) {
+ DPRINTF("conv_new: %s: ", p->name);
+ aparams_print2(ipar, opar);
+ DPRINTF("\n");
+ }
return p;
}
diff --git a/usr.bin/aucat/aproc.h b/usr.bin/aucat/aproc.h
index c3cb00786cb..cdf97965c3f 100644
--- a/usr.bin/aucat/aproc.h
+++ b/usr.bin/aucat/aproc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: aproc.h,v 1.5 2008/08/14 09:58:55 ratchov Exp $ */
+/* $OpenBSD: aproc.h,v 1.6 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -93,6 +93,18 @@ struct aproc_ops {
void (*newout)(struct aproc *, struct abuf *);
/*
+ * Real-time record position changed (for input buffer),
+ * by the given amount of _frames_
+ */
+ void (*ipos)(struct aproc *, struct abuf *, int);
+
+ /*
+ * Real-time play position changed (for output buffer),
+ * by the given amount of _frames_
+ */
+ void (*opos)(struct aproc *, struct abuf *, int);
+
+ /*
* destroy the aproc, called just before to free the
* aproc structure
*/
@@ -115,7 +127,7 @@ struct aconv {
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) */
+ int ctx[NCHAN_MAX]; /* current frame (for resampling) */
};
/*
@@ -134,16 +146,21 @@ struct aproc {
} io;
struct {
struct aconv ist, ost;
+ int idelta, odelta; /* reminder of conv_[io]pos */
} conv;
struct {
#define MIX_DROP 1
#define MIX_AUTOQUIT 2
- unsigned flags;
+ unsigned flags; /* bit mask of above */
+ int lat; /* current latency */
+ int maxlat; /* max latency allowed*/
} mix;
struct {
#define SUB_DROP 1
#define SUB_AUTOQUIT 2
- unsigned flags;
+ unsigned flags; /* bit mask of above */
+ int lat; /* current latency */
+ int maxlat; /* max latency allowed*/
} sub;
} u;
};
@@ -167,8 +184,8 @@ int wpipe_out(struct aproc *, struct abuf *);
void wpipe_eof(struct aproc *, struct abuf *);
void wpipe_hup(struct aproc *, struct abuf *);
-struct aproc *mix_new(void);
-struct aproc *sub_new(void);
+struct aproc *mix_new(char *, int);
+struct aproc *sub_new(char *, int);
struct aproc *conv_new(char *, struct aparams *, struct aparams *);
void mix_pushzero(struct aproc *);
diff --git a/usr.bin/aucat/aucat.1 b/usr.bin/aucat/aucat.1
index a1439e816db..d016d4479b5 100644
--- a/usr.bin/aucat/aucat.1
+++ b/usr.bin/aucat/aucat.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: aucat.1,v 1.25 2008/06/03 19:27:42 jmc Exp $
+.\" $OpenBSD: aucat.1,v 1.26 2008/10/26 08:49:43 ratchov Exp $
.\"
.\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: June 3 2008 $
+.Dd $Mdocdate: October 26 2008 $
.Dt AUCAT 1
.Os
.Sh NAME
@@ -23,19 +23,16 @@
.Sh SYNOPSIS
.Nm aucat
.Bk -words
-.Op Fl qu
+.Op Fl lqu
+.Op Fl b Ar nsamples
.Op Fl C Ar min : Ns Ar max
.Op Fl c Ar min : Ns Ar max
-.Op Fl E Ar enc
.Op Fl e Ar enc
.Op Fl f Ar device
-.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
-.Op Fl X Ar policy
.Op Fl x Ar policy
.Ek
.Sh DESCRIPTION
@@ -51,15 +48,19 @@ also has a legacy mode that works like previous versions of
which does not convert on the fly and supports playback of .au files.
.Pp
The options are as follows:
-.Bl -tag -width "-m mmmmmmmm "
+.Bl -tag -width Ds
+.It Fl b Ar nsamples
+The buffer size in frames.
+This is the number of samples that will be buffered before being played
+and controls the playback latency.
.It Xo
.Fl C Ar min : Ns Ar max ,
.Fl c Ar min : Ns Ar max
.Xc
-The range of channel numbers on the output or input stream, respectively.
+The range of channel numbers on the record or playback stream, respectively.
The default is 0:1, i.e. stereo.
-.It Fl E Ar enc , Fl e Ar enc
-Encoding of the output or input stream, respectively (see below).
+.It Fl e Ar enc
+Encoding of the playback or recording stream (see below).
The default is signed, 16-bit, native byte order.
.It Fl f Ar device
The
@@ -67,23 +68,34 @@ The
device to use for playing and/or recording.
The default is
.Pa /dev/audio .
-.It Fl H Ar fmt , Fl h Ar fmt
-File format of the output or input stream, respectively (see below).
+.It Fl h Ar fmt
+File format of the playback or record stream (see below).
The default is auto.
.It Fl i Ar file
Add this file to the list of files to play.
If the option argument is
.Sq -
then standard input will be used.
+.It Fl l
+Listen for incoming connections on a Unix domain socket.
+A client might use
+.Nm
+instead of the regular
+.Xr audio 4
+device for audio input and output
+in order to share the physical device with other clients.
+The default socket path is
+.Pa /tmp/aucat.sock
+but it can be changed with the
+.Ev AUCAT_SOCKET
+environment variable.
.It Fl o Ar file
Add this file to the list of files in which to store recorded samples.
If the option argument is
.Sq -
then standard output will be used.
-.It Fl q
-Do not print progress information; run quietly.
-.It Fl R Ar rate , Fl r Ar rate
-Sample rate in Hertz of the output or input stream, respectively.
+.It Fl r Ar rate
+Sample rate in Hertz of the playback or record stream.
The default is 44100Hz.
.It Fl u
Normally
@@ -91,32 +103,23 @@ Normally
tries to automatically determine the optimal parameters for the audio device;
if this option is specified,
it will instead use the parameters specified by the
-.Fl CcEeRr
+.Fl Ccer
options.
-.It Fl X Ar policy
+.It Fl x Ar policy
Action when the output stream cannot accept
-recorded data fast enough.
+recorded data fast enough or the input stream
+cannot provide data to play fast enough.
If the policy
is
.Dq ignore
-(the default) then samples that cannot be written are discarded.
-If the policy is
-.Dq sync
-then samples are discarded, but the same amount of silence will be written
-once the stream is unblocked, in order to reach the right position in time.
-If the policy is
-.Dq error
-then the stream is closed permanently.
-.It Fl x Ar policy
-Action when the input stream cannot provide
-data to play fast enough.
-If the policy is
-.Dq ignore
-(the default) then silence is played.
+(the default) then samples that cannot be written are discarded
+and samples that cannot be read are replaced by silence.
If the policy is
.Dq sync
-then silence is played, but the same amount of samples will be discarded
+then recorded samples are discarded, but the same amount of silence will be written
once the stream is unblocked, in order to reach the right position in time.
+Similarly silence is played, but the same amount of samples will be discarded
+once the stream is unblocked.
If the policy is
.Dq error
then the stream is closed permanently.
@@ -127,10 +130,10 @@ Settings for input
and output
.Pq Fl o
files can be changed using the
-.Fl CcEeHhRrXx
+.Fl Ccehrx
options.
The last
-.Fl CcEeHhRrXx
+.Fl Ccehrx
options specified before an
.Fl i
or
@@ -140,13 +143,13 @@ are applied to
.Pp
Settings for the audio device
can be changed using the
-.Fl CcEeRr
+.Fl Ccer
options.
They apply to the audio device only if the
.Fl u
option is given as well.
The last
-.Fl CcEeRr
+.Fl Ccer
option specified before an
.Fl f
is applied to
@@ -158,7 +161,7 @@ is specified but
.Fl u
is given anyway,
any
-.Fl CcEeRr
+.Fl Ccer
options specified before
.Fl io
options are applied both to
@@ -170,10 +173,8 @@ options,
they will be applied only to the default audio device.
.Pp
File formats are specified using the
-.Fl H
-and
.Fl h
-options.
+option.
The following file formats are supported:
.Bl -tag -width s32lexxx -offset -indent
.It raw
@@ -192,10 +193,8 @@ Try to guess, depending on the file name.
.El
.Pp
Encodings are specified using the
-.Fl E
-and
.Fl e
-options.
+option.
The following encodings are supported:
.Pp
.Bl -tag -width s32lexxx -offset -indent -compact
@@ -282,12 +281,21 @@ If the device does not support the encoding,
.Nm
will exit with an error.
.Sh ENVIRONMENT
-.Bl -tag -width "AUDIODEVICEXXX" -compact
+.Bl -tag -width "AUCAT_SOCKETXXX" -compact
.It Ev AUCAT_DEBUG
The debug level:
may be a value between 0 and 4.
+.It Ev AUCAT_SOCKET
+Path to the Unix domain socket to use.
.It Ev AUDIODEVICE
The audio device to use.
+.El
+.Sh SIGNALS
+.Bl -tag -width "SIGUSR1, SIGUSR2X" -compact
+.It Va SIGINT
+Terminate saving recorded files.
+.It Va SIGUSR1 , Va SIGUSR2
+Increase or decrease debug level respectively.
.El
.Sh EXAMPLES
The following command will record a stereo s16le stream at
@@ -314,7 +322,7 @@ 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
+$ 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:
diff --git a/usr.bin/aucat/aucat.c b/usr.bin/aucat/aucat.c
index 9ad9eec6373..ae74bc87932 100644
--- a/usr.bin/aucat/aucat.c
+++ b/usr.bin/aucat/aucat.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aucat.c,v 1.27 2008/08/25 11:56:12 sobrado Exp $ */
+/* $OpenBSD: aucat.c,v 1.28 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -48,6 +48,7 @@
#include <sys/types.h>
#include <sys/queue.h>
+#include <signal.h>
#include <err.h>
#include <fcntl.h>
#include <signal.h>
@@ -61,73 +62,44 @@
#include "aparams.h"
#include "aproc.h"
#include "abuf.h"
-#include "file.h"
+#include "wav.h"
+#include "listen.h"
#include "dev.h"
-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 *);
+/*
+ * 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;
+}
/*
- * List of allowed encodings and their names.
+ * increase debug level on SIGUSR1
*/
-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 } }
-};
+void
+sigusr1(int s)
+{
+ if (debug_level < 4)
+ debug_level++;
+}
/*
- * Search an encoding in the above table. On success fill encoding
- * part of "par" and return 1, otherwise return 0.
+ * decrease debug level on SIGUSR2
*/
-unsigned
-enc_lookup(char *name, struct aparams *par)
+void
+sigusr2(int s)
{
- 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;
+ if (debug_level > 0)
+ debug_level--;
}
void
@@ -136,10 +108,10 @@ usage(void)
extern char *__progname;
fprintf(stderr,
- "usage: %s [-qu] [-C min:max] [-c min:max] [-E enc] [-e enc] "
- "[-f device]\n"
- "\t[-H fmt] [-h fmt] [-i file] [-o file] [-R rate] [-r rate]\n"
- "\t[-X policy] [-x policy]\n",
+ "usage: %s [-lu] [-C min:max] [-c min:max] [-d level] "
+ "[-e enc]\n"
+ "\t[-f device] [-h fmt] [-i file] [-o file] "
+ "[-r rate] [-x policy]\n",
__progname);
}
@@ -147,8 +119,7 @@ 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)
+ par->cmax < par->cmin || par->cmax > NCHAN_MAX - 1)
err(1, "%s: bad channel range", optarg);
}
@@ -163,8 +134,11 @@ opt_rate(struct aparams *par)
void
opt_enc(struct aparams *par)
{
- if (!enc_lookup(optarg, par))
- err(1, "%s: bad encoding", optarg);
+ int len;
+
+ len = aparams_strtoenc(par, optarg);
+ if (len == 0 || optarg[len] != '\0')
+ errx(1, "%s: bad encoding", optarg);
}
int
@@ -246,7 +220,7 @@ void
newinput(struct farg *fa)
{
int fd;
- struct file *f;
+ struct wav *f;
struct aproc *proc;
struct abuf *buf;
unsigned nfr;
@@ -261,18 +235,16 @@ newinput(struct farg *fa)
if (fd < 0)
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, &f->hpar, &f->rbytes))
- exit(1);
- }
- nfr = dev_onfr * f->hpar.rate / dev_opar.rate;
- buf = abuf_new(nfr, aparams_bpf(&f->hpar));
- proc = rpipe_new(f);
+ /*
+ * XXX : we should round rate, right ?
+ */
+ f = wav_new_in(&wav_ops, fd, fa->name, &fa->par, fa->hdr);
+ nfr = dev_bufsz * fa->par.rate / dev_rate;
+ buf = abuf_new(nfr, aparams_bpf(&fa->par));
+ proc = rpipe_new((struct file *)f);
aproc_setout(proc, buf);
- dev_attach(fa->name, buf, &f->hpar, fa->xrun, NULL, NULL, 0);
+ abuf_fill(buf); /* XXX: move this in dev_attach() ? */
+ dev_attach(fa->name, buf, &fa->par, fa->xrun, NULL, NULL, 0);
}
/*
@@ -282,7 +254,7 @@ void
newoutput(struct farg *fa)
{
int fd;
- struct file *f;
+ struct wav *f;
struct aproc *proc;
struct abuf *buf;
unsigned nfr;
@@ -298,31 +270,31 @@ newoutput(struct farg *fa)
if (fd < 0)
err(1, "%s", fa->name);
}
- f = file_new(fd, fa->name);
- f->hdr = fa->hdr;
- f->hpar = fa->par;
- if (f->hdr == HDR_WAV) {
- f->wbytes = WAV_DATAMAX;
- if (!wav_writehdr(fd, &f->hpar))
- exit(1);
- }
- nfr = dev_infr * f->hpar.rate / dev_ipar.rate;
- proc = wpipe_new(f);
- buf = abuf_new(nfr, aparams_bpf(&f->hpar));
+ /*
+ * XXX : we should round rate, right ?
+ */
+ f = wav_new_out(&wav_ops, fd, fa->name, &fa->par, fa->hdr);
+ nfr = dev_bufsz * fa->par.rate / dev_rate;
+ proc = wpipe_new((struct file *)f);
+ buf = abuf_new(nfr, aparams_bpf(&fa->par));
aproc_setin(proc, buf);
- dev_attach(fa->name, NULL, NULL, 0, buf, &f->hpar, fa->xrun);
+ dev_attach(fa->name, NULL, NULL, 0, buf, &fa->par, fa->xrun);
}
int
main(int argc, char **argv)
{
- int c, u_flag, ohdr, ihdr, ixrun, oxrun;
+ int c, u_flag, l_flag, hdr, xrun;
struct farg *fa;
struct farglist ifiles, ofiles;
struct aparams ipar, opar, dipar, dopar;
- unsigned ivol, ovol;
- char *devpath, *dbgenv;
+ struct sigaction sa;
+ unsigned ivol, ovol, bufsz = 0;
+ char *devpath, *dbgenv, *listenpath;
const char *errstr;
+ extern char *malloc_options;
+
+ malloc_options = "FGJ";
dbgenv = getenv("AUCAT_DEBUG");
if (dbgenv) {
@@ -335,27 +307,22 @@ main(int argc, char **argv)
aparams_init(&opar, 0, 1, 44100);
u_flag = 0;
+ l_flag = 0;
devpath = NULL;
SLIST_INIT(&ifiles);
SLIST_INIT(&ofiles);
- ihdr = ohdr = HDR_AUTO;
- ixrun = oxrun = XRUN_IGNORE;
+ hdr = HDR_AUTO;
+ xrun = XRUN_IGNORE;
ivol = ovol = MIDI_TO_ADATA(127);
- while ((c = getopt(argc, argv, "c:C:e:E:r:R:h:H:x:X:i:o:f:qu"))
+ while ((c = getopt(argc, argv, "b:c:C:e:r:h:x:i:o:f:lqu"))
!= -1) {
switch (c) {
case 'h':
- ihdr = opt_hdr();
- break;
- case 'H':
- ohdr = opt_hdr();
+ hdr = opt_hdr();
break;
case 'x':
- ixrun = opt_xrun();
- break;
- case 'X':
- oxrun = opt_xrun();
+ xrun = opt_xrun();
break;
case 'c':
opt_ch(&ipar);
@@ -365,35 +332,37 @@ main(int argc, char **argv)
break;
case 'e':
opt_enc(&ipar);
- break;
- case 'E':
- opt_enc(&opar);
+ aparams_copyenc(&opar, &ipar);
break;
case 'r':
opt_rate(&ipar);
- break;
- case 'R':
- opt_rate(&opar);
+ opar.rate = ipar.rate;
break;
case 'i':
- opt_file(&ifiles, &ipar, 127, ihdr, ixrun, optarg);
+ opt_file(&ifiles, &ipar, 127, hdr, xrun, optarg);
break;
case 'o':
- opt_file(&ofiles, &opar, 127, ohdr, oxrun, optarg);
+ opt_file(&ofiles, &opar, 127, hdr, xrun, optarg);
break;
case 'f':
if (devpath)
err(1, "only one -f allowed");
devpath = optarg;
- dipar = ipar;
- dopar = opar;
+ dipar = opar;
+ dopar = ipar;
break;
- case 'q':
- quiet_flag = 1;
+ case 'l':
+ l_flag = 1;
break;
case 'u':
u_flag = 1;
break;
+ case 'b':
+ if (sscanf(optarg, "%u", &bufsz) != 1) {
+ fprintf(stderr, "%s: bad buf size\n", optarg);
+ exit(1);
+ }
+ break;
default:
usage();
exit(1);
@@ -403,14 +372,12 @@ main(int argc, char **argv)
argv += optind;
if (!devpath) {
- devpath = getenv("AUDIODEVICE");
- if (devpath == NULL)
- devpath = DEFAULT_DEVICE;
dipar = ipar;
dopar = opar;
}
- if (SLIST_EMPTY(&ifiles) && SLIST_EMPTY(&ofiles) && argc > 0) {
+ if (!l_flag && 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
@@ -426,15 +393,17 @@ main(int argc, char **argv)
exit(1);
}
+ if (l_flag && (!SLIST_EMPTY(&ofiles) || !SLIST_EMPTY(&ifiles)))
+ errx(1, "can't use -l with -o or -i");
- if (!u_flag) {
+ if (!u_flag && !l_flag) {
/*
* 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);
+ aparams_init(&dipar, NCHAN_MAX - 1, 0, RATE_MAX);
+ aparams_init(&dopar, NCHAN_MAX - 1, 0, RATE_MIN);
SLIST_FOREACH(fa, &ifiles, entry) {
if (dopar.cmin > fa->par.cmin)
dopar.cmin = fa->par.cmin;
@@ -452,15 +421,37 @@ main(int argc, char **argv)
dipar.rate = fa->par.rate;
}
}
- file_start();
+
+ quit_flag = 0;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sigint;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ DPRINTF("sigaction(int) failed\n");
+#ifdef DEBUG
+ sa.sa_handler = sigusr1;
+ if (sigaction(SIGUSR1, &sa, NULL) < 0)
+ DPRINTF("sigaction(usr1) failed\n");
+ sa.sa_handler = sigusr2;
+ if (sigaction(SIGUSR2, &sa, NULL) < 0)
+ DPRINTF("sigaction(usr2) failed1n");
+#endif
+ filelist_init();
/*
- * Open the device, dev_init() will return new parameters
- * that must be used by all inputs and outputs.
+ * Open the device.
*/
dev_init(devpath,
- (!SLIST_EMPTY(&ofiles)) ? &dipar : NULL,
- (!SLIST_EMPTY(&ifiles)) ? &dopar : NULL);
+ (l_flag || !SLIST_EMPTY(&ofiles)) ? &dipar : NULL,
+ (l_flag || !SLIST_EMPTY(&ifiles)) ? &dopar : NULL,
+ bufsz);
+
+ if (l_flag) {
+ listenpath = getenv("AUCAT_SOCKET");
+ if (!listenpath)
+ listenpath = DEFAULT_SOCKET;
+ (void)listen_new(&listen_ops, listenpath);
+ }
/*
* Create buffers for all input and output pipes.
@@ -479,24 +470,35 @@ main(int argc, char **argv)
}
/*
- * Normalize input levels
+ * automatically terminate when there no are streams
*/
- if (dev_mix)
- mix_setmaster(dev_mix);
+ if (!l_flag) {
+ if (dev_mix)
+ dev_mix->u.mix.flags |= MIX_AUTOQUIT;
+ if (dev_sub)
+ dev_sub->u.sub.flags |= SUB_AUTOQUIT;
+ }
/*
- * start audio
+ * loop, start audio
*/
- if (!quiet_flag)
- fprintf(stderr, "starting device...\n");
- dev_start();
- if (!quiet_flag)
- fprintf(stderr, "process started...\n");
- dev_run(1);
- if (!quiet_flag)
- fprintf(stderr, "stopping device...\n");
+ for (;;) {
+ if (quit_flag) {
+ if (l_flag)
+ filelist_unlisten();
+ break;
+ }
+ if (!file_poll())
+ break;
+ }
+
dev_done();
+ filelist_done();
- file_stop();
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ DPRINTF("dev_done: sigaction failed\n");
return 0;
}
diff --git a/usr.bin/aucat/conf.h b/usr.bin/aucat/conf.h
index 0938315a2b5..c57a75acad6 100644
--- a/usr.bin/aucat/conf.h
+++ b/usr.bin/aucat/conf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: conf.h,v 1.3 2008/08/14 09:39:16 ratchov Exp $ */
+/* $OpenBSD: conf.h,v 1.4 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -47,8 +47,20 @@ extern int debug_level;
#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 0x2 /* blocks per buffer */
+/*
+ * number of blocks in the device play/record buffers. because Sun API
+ * cannot notify apps of the current positions, we have to use all N
+ * buffers devices blocks plus one extra block, to make write() block,
+ * so that poll() can return the exact postition.
+ */
+#define DEV_NBLK 2
+
+/*
+ * number of blocks in the wav-file i/o buffers
+ */
+#define WAV_NBLK 6
+
#define DEFAULT_DEVICE "/dev/audio" /* defaul device */
+#define DEFAULT_SOCKET "/tmp/aucat.sock"
#endif /* !defined(CONF_H) */
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);
}
}
diff --git a/usr.bin/aucat/dev.h b/usr.bin/aucat/dev.h
index e5b50af53a3..b23fd9e1561 100644
--- a/usr.bin/aucat/dev.h
+++ b/usr.bin/aucat/dev.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: dev.h,v 1.2 2008/08/14 09:58:55 ratchov Exp $ */
+/* $OpenBSD: dev.h,v 1.3 2008/10/26 08:49:43 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -22,37 +22,22 @@ struct aparams;
struct file;
struct abuf;
-extern unsigned dev_infr, dev_onfr;
+extern unsigned dev_bufsz, dev_round, dev_rate;
+extern unsigned dev_rate_div, dev_round_div;
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_roundrate(unsigned *, unsigned *);
+void dev_init(char *, struct aparams *, struct aparams *, unsigned);
void dev_start(void);
void dev_stop(void);
void dev_run(int);
void dev_done(void);
+void dev_sync(struct abuf *, struct abuf *);
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 *);
+extern struct devops *devops, devops_sun, devops_aucat;
#endif /* !define(DEV_H) */
diff --git a/usr.bin/aucat/dev_sun.c b/usr.bin/aucat/dev_sun.c
deleted file mode 100644
index 2e97f6def0e..00000000000
--- a/usr.bin/aucat/dev_sun.c
+++ /dev/null
@@ -1,282 +0,0 @@
-/* $OpenBSD: dev_sun.c,v 1.5 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 <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 "aparams.h"
-#include "dev.h"
-
-/*
- * Convert sun device parameters to struct aparams
- */
-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 aparams 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.
- *
- * in "infr" and "onfd" we return the input and the output
- * block sizes respectively.
- */
-int
-sun_open(char *path, struct aparams *ipar, struct aparams *opar,
- unsigned *infr, unsigned *onfr)
-{
- int fd;
- int fullduplex;
- int flags;
- struct audio_info aui;
- struct audio_bufinfo aubi;
-
- if (!ipar && !opar)
- errx(1, "%s: must at least play or record", path);
-
- if (ipar && opar) {
- flags = O_RDWR;
- } else if (ipar) {
- flags = O_RDONLY;
- } else {
- flags = O_WRONLY;
- }
- fd = open(path, flags | 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;
- 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("sun_open: getinfo");
- close(fd);
- return -1;
- }
- if (opar) {
- if (!sun_infotopar(&aui.play, opar)) {
- close(fd);
- return -1;
- }
- if (ioctl(fd, AUDIO_GETPRINFO, &aubi) < 0) {
- warn("%s: AUDIO_GETPRINFO", path);
- close(fd);
- return -1;
- }
- *onfr = aubi.blksize * aubi.hiwat /
- (aui.play.channels * aui.play.precision / 8);
- }
- if (ipar) {
- if (!sun_infotopar(&aui.record, ipar)) {
- close(fd);
- return -1;
- }
- if (ioctl(fd, AUDIO_GETRRINFO, &aubi) < 0) {
- warn("%s: AUDIO_GETRRINFO", path);
- close(fd);
- return -1;
- }
- *infr = aubi.blksize * aubi.hiwat /
- (aui.record.channels * aui.record.precision / 8);
- }
- return fd;
-}
-
-/*
- * Drain and close the device
- */
-void
-sun_close(int fd)
-{
- close(fd);
- DPRINTF("sun_close: closed\n");
-}
-
-/*
- * Start play/record simultaneously. Play buffers must be filled.
- */
-void
-sun_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, "sun_start: setinfo");
-
- DPRINTF("sun_start: play/rec started\n");
-}
-
-/*
- * Drain play buffers and then stop play/record simultaneously.
- */
-void
-sun_stop(int fd)
-{
- audio_info_t aui;
-
- /*
- * 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
- *
- */
- AUDIO_INITINFO(&aui);
- aui.play.pause = aui.record.pause = 1;
- if (ioctl(fd, AUDIO_SETINFO, &aui) < 0)
- err(1, "sun_stop: setinfo");
-
- 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 70995e64fa2..8ce62b5bbeb 100644
--- a/usr.bin/aucat/file.c
+++ b/usr.bin/aucat/file.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: file.c,v 1.3 2008/08/14 09:58:55 ratchov Exp $ */
+/* $OpenBSD: file.c,v 1.4 2008/10/26 08:49:44 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -31,62 +31,55 @@
#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
+extern struct fileops listen_ops, pipe_ops;
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, ">");
- }
+ if (debug_level < n)
+ return;
+ fprintf(stderr, "%s:%s <", f->ops->name, 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)
+file_new(struct fileops *ops, char *name, unsigned nfds)
{
- unsigned i;
struct file *f;
- i = 0;
LIST_FOREACH(f, &file_list, entry)
- i++;
- if (i >= MAXFDS)
+ nfds += f->ops->nfds(f);
+ if (nfds > MAXFDS)
err(1, "%s: too many polled files", name);
- f = malloc(sizeof(struct file));
+ f = malloc(ops->size);
if (f == NULL)
- err(1, "%s", name);
-
- f->fd = fd;
- f->events = 0;
- f->rbytes = -1;
- f->wbytes = -1;
+ err(1, "file_new: %s", ops->name);
+ f->ops = ops;
f->name = name;
f->state = 0;
f->rproc = NULL;
f->wproc = NULL;
+ f->refs = 0;
LIST_INSERT_HEAD(&file_list, f, entry);
- DPRINTF("file_new: %s\n", f->name);
+ DPRINTF("file_new: %s:%s\n", ops->name, f->name);
return f;
}
@@ -95,54 +88,52 @@ file_del(struct file *f)
{
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);
+ if (f->refs > 0) {
+ DPRINTF(": delayed\n");
+ f->state |= FILE_ZOMB;
+ return;
+ } else {
+ DPRINTF(": immediate\n");
+ LIST_REMOVE(f, entry);
+ f->ops->close(f);
+ free(f);
+ }
}
int
file_poll(void)
{
-#ifdef DEBUG
- int ndead;
-#endif
- nfds_t nfds;
+ nfds_t nfds, n;
+ short events, revents;
struct pollfd pfds[MAXFDS];
- struct pollfd *pfd;
struct file *f, *fnext;
struct aproc *p;
+ /*
+ * fill the pfds[] array with files that are blocked on reading
+ * and/or writing, skipping those that're just waiting
+ */
+ DPRINTFN(4, "file_poll:");
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
+ events = 0;
+ if (f->rproc && !(f->state & FILE_ROK))
+ events |= POLLIN;
+ if (f->wproc && !(f->state & FILE_WOK))
+ events |= POLLOUT;
+ DPRINTFN(4, " %s(%x)", f->name, events);
+ n = f->ops->pollfd(f, pfds + nfds, events);
+ if (n == 0) {
f->pfd = NULL;
continue;
}
- pfd = &pfds[nfds++];
- f->pfd = pfd;
- pfd->fd = f->fd;
- pfd->events = f->events;
+ f->pfd = pfds + nfds;
+ nfds += n;
}
+ DPRINTFN(4, "\n");
#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)) {
+ if (nfds == 0 && !LIST_EMPTY(&file_list)) {
fprintf(stderr, "file_poll: deadlock\n");
abort();
}
@@ -151,60 +142,58 @@ file_poll(void)
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");
- }
+ if (poll(pfds, nfds, -1) < 0) {
+ if (errno == EINTR)
+ return 1;
+ err(1, "file_poll: poll failed");
}
- LIST_FOREACH(f, &file_list, entry) {
- pfd = f->pfd;
- if (pfd == NULL)
+ f = LIST_FIRST(&file_list);
+ while (f != LIST_END(&file_list)) {
+ if (f->pfd == NULL) {
+ f = LIST_NEXT(f, entry);
continue;
- if ((f->events & POLLIN) && (pfd->revents & POLLIN)) {
- f->events &= ~POLLIN;
+ }
+ f->refs++;
+ revents = f->ops->revents(f, f->pfd);
+ if (!(f->state & FILE_ZOMB) && (revents & POLLIN)) {
+ revents &= ~POLLIN;
f->state |= FILE_ROK;
DPRINTFN(3, "file_poll: %s rok\n", f->name);
- while (f->state & FILE_ROK) {
+ for (;;) {
p = f->rproc;
if (!p || !p->ops->in(p, NULL))
break;
}
}
- if ((f->events & POLLOUT) && (pfd->revents & POLLOUT)) {
- f->events &= ~POLLOUT;
+ if (!(f->state & FILE_ZOMB) && (revents & POLLOUT)) {
+ revents &= ~POLLOUT;
f->state |= FILE_WOK;
DPRINTFN(3, "file_poll: %s wok\n", f->name);
- while (f->state & FILE_WOK) {
+ for (;;) {
p = f->wproc;
if (!p || !p->ops->out(p, NULL))
break;
}
}
- }
- LIST_FOREACH(f, &file_list, entry) {
- if (f->state & FILE_EOF) {
+ if (!(f->state & FILE_ZOMB) && (f->state & FILE_EOF)) {
DPRINTFN(2, "file_poll: %s: eof\n", f->name);
p = f->rproc;
if (p)
p->ops->eof(p, NULL);
f->state &= ~FILE_EOF;
}
- if (f->state & FILE_HUP) {
+ if (!(f->state & FILE_ZOMB) && (f->state & FILE_HUP)) {
DPRINTFN(2, "file_poll: %s hup\n", f->name);
p = f->wproc;
if (p)
p->ops->hup(p, NULL);
f->state &= ~FILE_HUP;
}
- }
- for (f = LIST_FIRST(&file_list); f != NULL; f = fnext) {
+ f->refs--;
fnext = LIST_NEXT(f, entry);
- if (f->rproc == NULL && f->wproc == NULL) {
- LIST_REMOVE(f, entry);
- DPRINTF("file_poll: %s: removed\n", f->name);
+ if (f->state & FILE_ZOMB)
file_del(f);
- }
+ f = fnext;
}
if (LIST_EMPTY(&file_list)) {
DPRINTFN(2, "file_poll: terminated\n");
@@ -214,7 +203,7 @@ file_poll(void)
}
void
-file_start(void)
+filelist_init(void)
{
sigset_t set;
@@ -227,109 +216,88 @@ file_start(void)
}
void
-file_stop(void)
+filelist_done(void)
{
struct file *f;
if (!LIST_EMPTY(&file_list)) {
- fprintf(stderr, "file_stop:");
+ fprintf(stderr, "filelist_done: list not empty:\n");
LIST_FOREACH(f, &file_list, entry) {
- fprintf(stderr, " %s(%x)", f->name, f->events);
+ fprintf(stderr, "\t");
+ file_dprint(0, f);
+ fprintf(stderr, "\n");
}
- fprintf(stderr, "\nfile_stop: list not empty\n");
- exit(1);
+ abort();
}
}
-unsigned
-file_read(struct file *file, unsigned char *data, unsigned count)
+/*
+ * close all listening sockets
+ *
+ * XXX: remove this
+ */
+void
+filelist_unlisten(void)
{
- int n;
+ struct file *f, *fnext;
- 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;
+ for (f = LIST_FIRST(&file_list); f != NULL; f = fnext) {
+ fnext = LIST_NEXT(f, entry);
+ if (f->ops == &listen_ops)
+ file_del(f);
}
- if (file->rbytes >= 0)
- file->rbytes -= n;
- DPRINTFN(4, "file_read: %s: got %d bytes\n", file->name, n);
- return n;
}
+unsigned
+file_read(struct file *file, unsigned char *data, unsigned count)
+{
+ return file->ops->read(file, data, count);
+}
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;
+ return file->ops->write(file, data, count);
}
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;
+ struct aproc *p;
+
+ if (f->refs == 0) {
+ DPRINTFN(2, "file_eof: %s: immediate\n", f->name);
+ f->refs++;
+ p = f->rproc;
+ if (p)
+ p->ops->eof(p, NULL);
+ f->refs--;
+ if (f->state & FILE_ZOMB)
+ file_del(f);
+ } else {
+ DPRINTFN(2, "file_eof: %s: delayed\n", f->name);
+ 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;
+ struct aproc *p;
+
+ if (f->refs == 0) {
+ DPRINTFN(2, "file_hup: %s immediate\n", f->name);
+ f->refs++;
+ p = f->wproc;
+ if (p)
+ p->ops->hup(p, NULL);
+ f->refs--;
+ if (f->state & FILE_ZOMB)
+ file_del(f);
+ } else {
+ DPRINTFN(2, "file_hup: %s: delayed\n", f->name);
+ f->state &= ~FILE_WOK;
+ f->state |= FILE_HUP;
+ }
}
diff --git a/usr.bin/aucat/file.h b/usr.bin/aucat/file.h
index 338d7a3c426..b800636a8cb 100644
--- a/usr.bin/aucat/file.h
+++ b/usr.bin/aucat/file.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: file.h,v 1.3 2008/08/14 09:58:55 ratchov Exp $ */
+/* $OpenBSD: file.h,v 1.4 2008/10/26 08:49:44 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -19,46 +19,52 @@
#include <sys/queue.h>
#include <sys/types.h>
-#include <poll.h>
-#include "aparams.h"
+struct file;
struct aparams;
struct aproc;
struct abuf;
+struct pollfd;
+
+struct fileops {
+ char *name;
+ size_t size;
+ void (*close)(struct file *);
+ unsigned (*read)(struct file *, unsigned char *, unsigned);
+ unsigned (*write)(struct file *, unsigned char *, unsigned);
+ void (*start)(struct file *);
+ void (*stop)(struct file *);
+ int (*nfds)(struct file *);
+ int (*pollfd)(struct file *, struct pollfd *, int);
+ int (*revents)(struct file *, struct pollfd *);
+};
struct file {
- int fd; /* file descriptor */
+ struct fileops *ops;
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 */
- int state; /* one of above */
+#define FILE_HUP 0x8 /* hang-up on the write end */
+#define FILE_ZOMB 0x10 /* closed, but struct not freed */
+ unsigned state; /* one of above */
+ unsigned refs; /* reference counter */
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);
extern struct filelist file_list;
-void file_start(void);
-void file_stop(void);
-struct file *file_new(int, char *);
+void filelist_init(void);
+void filelist_done(void);
+void filelist_unlisten(void);
+
+struct file *file_new(struct fileops *, char *, unsigned);
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);
@@ -66,17 +72,4 @@ 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
index f2960ec5a44..4795c47ee7a 100644
--- a/usr.bin/aucat/headers.c
+++ b/usr.bin/aucat/headers.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: headers.c,v 1.1 2008/05/23 07:15:46 ratchov Exp $ */
+/* $OpenBSD: headers.c,v 1.2 2008/10/26 08:49:44 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -76,7 +76,7 @@ wav_readfmt(int fd, unsigned csize, struct aparams *par)
return 0;
}
cmax = par->cmin + nch - 1;
- if (cmax >= CHAN_MAX) {
+ if (cmax >= NCHAN_MAX) {
warnx("%u:%u: bad range", par->cmin, cmax);
return 0;
}
diff --git a/usr.bin/aucat/legacy.c b/usr.bin/aucat/legacy.c
index fcb705120db..70df6ec4750 100644
--- a/usr.bin/aucat/legacy.c
+++ b/usr.bin/aucat/legacy.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: legacy.c,v 1.2 2008/08/14 09:58:55 ratchov Exp $ */
+/* $OpenBSD: legacy.c,v 1.3 2008/10/26 08:49:44 ratchov Exp $ */
/*
* Copyright (c) 1997 Kenneth Stailey. All rights reserved.
*
@@ -37,9 +37,7 @@
#include <unistd.h>
#include <err.h>
-#include "file.h"
-#include "aparams.h"
-#include "dev.h"
+#include "wav.h"
/* headerless data files. played at /dev/audio's defaults.
@@ -58,6 +56,74 @@
#define FMT_WAV 2
+/*
+ * Convert sun device parameters to struct aparams
+ */
+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 > NCHAN_MAX - 1) {
+ 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 aparams 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;
+ }
+}
+
int
legacy_play(char *dev, char *aufile)
{
diff --git a/usr.bin/aucat/listen.c b/usr.bin/aucat/listen.c
new file mode 100644
index 00000000000..1e63c4b4dc3
--- /dev/null
+++ b/usr.bin/aucat/listen.c
@@ -0,0 +1,133 @@
+/* $OpenBSD: listen.c,v 1.1 2008/10/26 08:49:44 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/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "sock.h"
+#include "listen.h"
+
+struct fileops listen_ops = {
+ "listen",
+ sizeof(struct listen),
+ listen_close,
+ NULL, /* read */
+ NULL, /* write */
+ NULL, /* start */
+ NULL, /* stop */
+ listen_nfds,
+ listen_pollfd,
+ listen_revents
+};
+
+struct listen *
+listen_new(struct fileops *ops, char *path)
+{
+ int sock;
+ struct sockaddr_un sockname;
+ struct listen *f;
+
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ if (unlink(path) < 0 && errno != ENOENT) {
+ perror("unlink");
+ exit(1);
+ }
+ sockname.sun_family = AF_UNIX;
+ strlcpy(sockname.sun_path, path, sizeof(sockname.sun_path));
+ if (bind(sock, (struct sockaddr *)&sockname,
+ sizeof(struct sockaddr_un)) < 0) {
+ perror("bind");
+ exit(1);
+ }
+ if (listen(sock, 1) < 0) {
+ perror("listen");
+ exit(1);
+ }
+ f = (struct listen *)file_new(ops, path, 1);
+ f->path = strdup(path);
+ if (f->path == NULL) {
+ perror("strdup");
+ exit(1);
+ }
+ f->fd = sock;
+ return f;
+}
+
+int
+listen_nfds(struct file *f) {
+ return 1;
+}
+
+int
+listen_pollfd(struct file *file, struct pollfd *pfd, int events)
+{
+ struct listen *f = (struct listen *)file;
+
+ pfd->fd = f->fd;
+ pfd->events = POLLIN;
+ return 1;
+}
+
+int
+listen_revents(struct file *file, struct pollfd *pfd)
+{
+ struct listen *f = (struct listen *)file;
+ struct sockaddr caddr;
+ socklen_t caddrlen;
+ int sock;
+
+ if (pfd->revents & POLLIN) {
+ DPRINTF("listen_revents: accepting connection\n");
+ caddrlen = sizeof(caddrlen);
+ sock = accept(f->fd, &caddr, &caddrlen);
+ if (sock < 0) {
+ perror("accept");
+ return 0;
+ }
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+ perror("fcntl(sock, O_NONBLOCK)");
+ close(sock);
+ return 0;
+ }
+ (void)sock_new(&sock_ops, sock, "socket");
+ }
+ return 0;
+}
+
+void
+listen_close(struct file *file)
+{
+ struct listen *f = (struct listen *)file;
+
+ (void)unlink(f->path);
+ free(f->path);
+ (void)close(f->fd);
+}
diff --git a/usr.bin/aucat/listen.h b/usr.bin/aucat/listen.h
new file mode 100644
index 00000000000..042287fcbc2
--- /dev/null
+++ b/usr.bin/aucat/listen.h
@@ -0,0 +1,37 @@
+/* $OpenBSD: listen.h,v 1.1 2008/10/26 08:49:44 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 LISTEN_H
+#define LISTEN_H
+
+#include <sys/types.h>
+
+#include "file.h"
+
+struct listen {
+ struct file file;
+ char *path;
+ int fd;
+};
+
+struct listen *listen_new(struct fileops *, char *);
+int listen_nfds(struct file *);
+int listen_pollfd(struct file *, struct pollfd *, int events);
+int listen_revents(struct file *, struct pollfd *);
+void listen_close(struct file *);
+extern struct fileops listen_ops;
+
+#endif /* !defined(LISTEN_H) */
diff --git a/usr.bin/aucat/pipe.c b/usr.bin/aucat/pipe.c
new file mode 100644
index 00000000000..d7cc750df5d
--- /dev/null
+++ b/usr.bin/aucat/pipe.c
@@ -0,0 +1,146 @@
+#include <sys/time.h>
+#include <sys/types.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "pipe.h"
+
+struct fileops pipe_ops = {
+ "pipe",
+ sizeof(struct pipe),
+ pipe_close,
+ pipe_read,
+ pipe_write,
+ NULL, /* start */
+ NULL, /* stop */
+ pipe_nfds,
+ pipe_pollfd,
+ pipe_revents
+};
+
+struct pipe *
+pipe_new(struct fileops *ops, int fd, char *name)
+{
+ struct pipe *f;
+
+ f = (struct pipe *)file_new(ops, name, 1);
+ f->fd = fd;
+ return f;
+}
+
+unsigned
+pipe_read(struct file *file, unsigned char *data, unsigned count)
+{
+ struct pipe *f = (struct pipe *)file;
+ int n;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (!(f->file.state & FILE_ROK)) {
+ DPRINTF("pipe_read: %s: bad state\n", f->file.name);
+ abort();
+ }
+ gettimeofday(&tv0, NULL);
+#endif
+ while ((n = read(f->fd, data, count)) < 0) {
+ f->file.state &= ~FILE_ROK;
+ if (errno == EAGAIN) {
+ DPRINTFN(3, "pipe_read: %s: blocking...\n",
+ f->file.name);
+ } else {
+ warn("%s", f->file.name);
+ file_eof(&f->file);
+ }
+ return 0;
+ }
+ if (n == 0) {
+ DPRINTFN(2, "pipe_read: %s: eof\n", f->file.name);
+ f->file.state &= ~FILE_ROK;
+ file_eof(&f->file);
+ return 0;
+ }
+#ifdef DEBUG
+ gettimeofday(&tv1, NULL);
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ DPRINTFN(us < 5000 ? 4 : 1,
+ "pipe_read: %s: got %d bytes in %uus\n",
+ f->file.name, n, us);
+#endif
+ return n;
+}
+
+
+unsigned
+pipe_write(struct file *file, unsigned char *data, unsigned count)
+{
+ struct pipe *f = (struct pipe *)file;
+ int n;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (!(f->file.state & FILE_WOK)) {
+ DPRINTF("pipe_write: %s: bad state\n", f->file.name);
+ abort();
+ }
+ gettimeofday(&tv0, NULL);
+#endif
+ while ((n = write(f->fd, data, count)) < 0) {
+ f->file.state &= ~FILE_WOK;
+ if (errno == EAGAIN) {
+ DPRINTFN(3, "pipe_write: %s: blocking...\n",
+ f->file.name);
+ } else {
+ if (errno != EPIPE)
+ warn("%s", f->file.name);
+ file_hup(&f->file);
+ }
+ return 0;
+ }
+#ifdef DEBUG
+ gettimeofday(&tv1, NULL);
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ DPRINTFN(us < 5000 ? 4 : 1,
+ "pipe_write: %s: wrote %d bytes in %uus\n",
+ f->file.name, n, us);
+#endif
+ return n;
+}
+
+int
+pipe_nfds(struct file *file) {
+ return 1;
+}
+
+int
+pipe_pollfd(struct file *file, struct pollfd *pfd, int events)
+{
+ struct pipe *f = (struct pipe *)file;
+
+ pfd->fd = f->fd;
+ pfd->events = events;
+ return (events != 0) ? 1 : 0;
+}
+
+int
+pipe_revents(struct file *f, struct pollfd *pfd)
+{
+ return pfd->revents;
+}
+
+void
+pipe_close(struct file *file)
+{
+ struct pipe *f = (struct pipe *)file;
+
+ close(f->fd);
+}
diff --git a/usr.bin/aucat/pipe.h b/usr.bin/aucat/pipe.h
new file mode 100644
index 00000000000..043dab40c3d
--- /dev/null
+++ b/usr.bin/aucat/pipe.h
@@ -0,0 +1,39 @@
+/* $OpenBSD: pipe.h,v 1.1 2008/10/26 08:49:44 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 PIPE_H
+#define PIPE_H
+
+#include "file.h"
+
+struct aparams;
+
+struct pipe {
+ struct file file;
+ int fd; /* file descriptor */
+};
+
+extern struct fileops pipe_ops;
+
+struct pipe *pipe_new(struct fileops *, int, char *);
+void pipe_close(struct file *);
+unsigned pipe_read(struct file *, unsigned char *, unsigned);
+unsigned pipe_write(struct file *, unsigned char *, unsigned);
+int pipe_nfds(struct file *);
+int pipe_pollfd(struct file *, struct pollfd *, int);
+int pipe_revents(struct file *, struct pollfd *);
+
+#endif /* !defined(FILE_H) */
diff --git a/usr.bin/aucat/safile.c b/usr.bin/aucat/safile.c
new file mode 100644
index 00000000000..2b77bdf596f
--- /dev/null
+++ b/usr.bin/aucat/safile.c
@@ -0,0 +1,288 @@
+/* $OpenBSD: safile.c,v 1.1 2008/10/26 08:49:44 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/types.h>
+#include <sys/time.h>
+
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libsa.h>
+
+#include "conf.h"
+#include "file.h"
+#include "aproc.h"
+#include "aparams.h"
+#include "safile.h"
+#include "dev.h"
+
+struct safile {
+ struct file file;
+ struct sa_hdl *hdl;
+#ifdef DEBUG
+ struct timeval itv, otv;
+#endif
+};
+
+void safile_close(struct file *);
+unsigned safile_read(struct file *, unsigned char *, unsigned);
+unsigned safile_write(struct file *, unsigned char *, unsigned);
+void safile_start(struct file *);
+void safile_stop(struct file *);
+int safile_nfds(struct file *);
+int safile_pollfd(struct file *, struct pollfd *, int);
+int safile_revents(struct file *, struct pollfd *);
+
+struct fileops safile_ops = {
+ "libsa",
+ sizeof(struct safile),
+ safile_close,
+ safile_read,
+ safile_write,
+ safile_start,
+ safile_stop,
+ safile_nfds,
+ safile_pollfd,
+ safile_revents
+};
+
+void
+safile_cb(void *addr, int delta)
+{
+ struct safile *f = (struct safile *)addr;
+ struct aproc *p;
+
+ if (delta != 0) {
+ p = f->file.wproc;
+ if (p && p->ops->opos)
+ p->ops->opos(p, NULL, delta);
+ }
+ if (delta != 0) {
+ p = f->file.rproc;
+ if (p && p->ops->ipos)
+ p->ops->ipos(p, NULL, delta);
+ }
+}
+
+/*
+ * open the device
+ */
+struct safile *
+safile_new(struct fileops *ops, char *path,
+ struct aparams *ipar, struct aparams *opar,
+ unsigned *bufsz, unsigned *round)
+{
+ struct sa_par par;
+ struct sa_hdl *hdl;
+ struct safile *f;
+ int mode;
+
+ mode = 0;
+ if (ipar)
+ mode |= SA_REC;
+ if (opar)
+ mode |= SA_PLAY;
+ if (!mode)
+ fprintf(stderr, "%s: must at least play or record", path);
+ hdl = sa_open(path, mode, 1);
+ if (hdl == NULL) {
+ fprintf(stderr, "safile_new: can't open device\n");
+ return NULL;
+ }
+ sa_initpar(&par);
+ if (ipar) {
+ par.bits = ipar->bits;
+ par.bps = ipar->bps;
+ par.sig = ipar->sig;
+ par.le = ipar->le;
+ par.msb = ipar->msb;
+ par.rate = ipar->rate;
+ par.rchan = ipar->cmax - ipar->cmin + 1;
+ } else {
+ par.bits = opar->bits;
+ par.bps = opar->bps;
+ par.sig = opar->sig;
+ par.le = opar->le;
+ par.msb = opar->msb;
+ par.rate = opar->rate;
+ }
+ if (opar)
+ par.pchan = opar->cmax - opar->cmin + 1;
+ if (*bufsz)
+ par.bufsz = *bufsz;
+ if (!sa_setpar(hdl, &par)) {
+ fprintf(stderr, "safile_new: sa_setpar failed\n");
+ exit(1);
+ }
+ if (!sa_getpar(hdl, &par)) {
+ fprintf(stderr, "safile_new: sa_getpar failed\n");
+ exit(1);
+ }
+ if (ipar) {
+ ipar->bits = par.bits;
+ ipar->bps = par.bps;
+ ipar->sig = par.sig;
+ ipar->le = par.le;
+ ipar->msb = par.msb;
+ ipar->rate = par.rate;
+ ipar->cmax = par.rchan - 1;
+ ipar->cmin = 0;
+ }
+ if (opar) {
+ opar->bits = par.bits;
+ opar->bps = par.bps;
+ opar->sig = par.sig;
+ opar->le = par.le;
+ opar->msb = par.msb;
+ opar->rate = par.rate;
+ opar->cmax = par.pchan - 1;
+ opar->cmin = 0;
+ }
+ *bufsz = par.bufsz;
+ *round = par.round;
+ DPRINTF("safile_open: using %u(%u) fpb\n", *bufsz, *round);
+ f = (struct safile *)file_new(ops, "hdl", sa_nfds(hdl));
+ f->hdl = hdl;
+ sa_onmove(f->hdl, safile_cb, f);
+ return f;
+}
+
+void
+safile_start(struct file *file)
+{
+ struct safile *f = (struct safile *)file;
+
+ if (!sa_start(f->hdl)) {
+ fprintf(stderr, "safile_start: sa_start() failed\n");
+ exit(1);
+ }
+ DPRINTF("safile_start: play/rec started\n");
+}
+
+void
+safile_stop(struct file *file)
+{
+ struct safile *f = (struct safile *)file;
+
+ if (!sa_stop(f->hdl)) {
+ fprintf(stderr, "safile_stop: sa_start() filed\n");
+ exit(1);
+ }
+ DPRINTF("safile_stop: play/rec stopped\n");
+}
+
+unsigned
+safile_read(struct file *file, unsigned char *data, unsigned count)
+{
+ struct safile *f = (struct safile *)file;
+ unsigned n;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (!(f->file.state & FILE_ROK)) {
+ DPRINTF("file_read: %s: bad state\n", f->file.name);
+ abort();
+ }
+ gettimeofday(&tv0, NULL);
+#endif
+ n = sa_read(f->hdl, data, count);
+ if (n == 0) {
+ f->file.state &= ~FILE_ROK;
+ if (sa_eof(f->hdl)) {
+ fprintf(stderr, "safile_read: eof\n");
+ file_eof(&f->file);
+ } else {
+ DPRINTFN(3, "safile_read: %s: blocking...\n",
+ f->file.name);
+ }
+ return 0;
+ }
+#ifdef DEBUG
+ gettimeofday(&tv1, NULL);
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ DPRINTFN(us < 5000 ? 4 : 1,
+ "safile_read: %s: got %d bytes in %uus\n",
+ f->file.name, n, us);
+#endif
+ return n;
+
+}
+
+unsigned
+safile_write(struct file *file, unsigned char *data, unsigned count)
+{
+ struct safile *f = (struct safile *)file;
+ unsigned n;
+#ifdef DEBUG
+ struct timeval tv0, tv1, dtv;
+ unsigned us;
+
+ if (!(f->file.state & FILE_WOK)) {
+ DPRINTF("safile_write: %s: bad state\n", f->file.name);
+ abort();
+ }
+ gettimeofday(&tv0, NULL);
+#endif
+ n = sa_write(f->hdl, data, count);
+ if (n == 0) {
+ f->file.state &= ~FILE_WOK;
+ if (sa_eof(f->hdl)) {
+ fprintf(stderr, "safile_write: %s: hup\n", f->file.name);
+ file_hup(&f->file);
+ } else {
+ DPRINTFN(3, "safile_write: %s: blocking...\n",
+ f->file.name);
+ }
+ return 0;
+ }
+#ifdef DEBUG
+ gettimeofday(&tv1, NULL);
+ timersub(&tv1, &tv0, &dtv);
+ us = dtv.tv_sec * 1000000 + dtv.tv_usec;
+ DPRINTFN(us < 5000 ? 4 : 1,
+ "safile_write: %s: wrote %d bytes in %uus\n",
+ f->file.name, n, us);
+#endif
+ return n;
+}
+
+int
+safile_nfds(struct file *file)
+{
+ return sa_nfds(((struct safile *)file)->hdl);
+}
+
+int
+safile_pollfd(struct file *file, struct pollfd *pfd, int events)
+{
+ return sa_pollfd(((struct safile *)file)->hdl, pfd, events);
+}
+
+int
+safile_revents(struct file *file, struct pollfd *pfd)
+{
+ return sa_revents(((struct safile *)file)->hdl, pfd);
+}
+
+void
+safile_close(struct file *file)
+{
+ return sa_close(((struct safile *)file)->hdl);
+}
diff --git a/usr.bin/aucat/safile.h b/usr.bin/aucat/safile.h
new file mode 100644
index 00000000000..328e9863531
--- /dev/null
+++ b/usr.bin/aucat/safile.h
@@ -0,0 +1,37 @@
+/* $OpenBSD: safile.h,v 1.1 2008/10/26 08:49:44 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 SAFILE_H
+#define SAFILE_H
+
+struct file;
+struct fileops;
+struct safile;
+struct aparams;
+
+struct safile *safile_new(struct fileops *, char *,
+ struct aparams *, struct aparams *, unsigned *, unsigned *);
+
+extern struct fileops safile_ops;
+
+/*
+ * Sun API specific functions
+ */
+struct audio_prinfo;
+int sun_infotopar(struct audio_prinfo *, struct aparams *);
+void sun_partoinfo(struct audio_prinfo *, struct aparams *);
+
+#endif /* !defined(SUN_H) */
diff --git a/usr.bin/aucat/sock.c b/usr.bin/aucat/sock.c
new file mode 100644
index 00000000000..7c9aecce3ea
--- /dev/null
+++ b/usr.bin/aucat/sock.c
@@ -0,0 +1,931 @@
+/* $OpenBSD: sock.c,v 1.1 2008/10/26 08:49:44 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.
+ */
+/*
+ * TODO:
+ *
+ * change f->bufsz to contain only socket-side buffer,
+ * because it's less error prone
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "aproc.h"
+#include "abuf.h"
+#include "sock.h"
+#include "dev.h"
+#include "conf.h"
+
+int sock_attach(struct sock *, int);
+int sock_read(struct sock *);
+int sock_write(struct sock *);
+int sock_execmsg(struct sock *);
+void sock_reset(struct sock *);
+
+struct fileops sock_ops = {
+ "sock",
+ sizeof(struct sock),
+ pipe_close,
+ pipe_read,
+ pipe_write,
+ NULL, /* start */
+ NULL, /* stop */
+ pipe_nfds,
+ pipe_pollfd,
+ pipe_revents
+};
+
+void
+rsock_done(struct aproc *p)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ DPRINTFN(1, "rsock_done: %p\n", f);
+ sock_reset(f);
+ f->pipe.file.rproc = NULL;
+ if (f->pipe.file.wproc) {
+ aproc_del(f->pipe.file.wproc);
+ file_del(&f->pipe.file);
+ }
+}
+
+int
+rsock_in(struct aproc *p, struct abuf *ibuf_dummy)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+ struct abuf *obuf;
+
+ DPRINTFN(4, "rsock_in: %p\n", f);
+
+ if (!sock_read(f))
+ return 0;
+ obuf = LIST_FIRST(&p->obuflist);
+ if (obuf) {
+ if (!abuf_flush(obuf))
+ return 0;
+ }
+ return 1;
+}
+
+int
+rsock_out(struct aproc *p, struct abuf *obuf)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ if (f->pipe.file.refs > 0)
+ return 0;
+
+ DPRINTFN(4, "rsock_out: %p\n", f);
+
+ /*
+ * when calling sock_read(), we may receive a ``STOP'' command,
+ * and detach ``obuf''. In this case, there's no more caller and
+ * we'll stop processing further messages, resulting in a dead lock.
+ * The solution is to iterate over sock_read() in order to
+ * consume all messages().
+ */
+ for (;;) {
+ if (!sock_read(f))
+ return 0;
+ }
+ return 1;
+}
+
+void
+rsock_eof(struct aproc *p, struct abuf *ibuf_dummy)
+{
+ DPRINTFN(3, "rsock_eof: %p\n", p->u.io.file);
+ aproc_del(p);
+}
+
+void
+rsock_hup(struct aproc *p, struct abuf *ibuf)
+{
+ DPRINTFN(3, "rsock_hup: %p\n", p->u.io.file);
+ aproc_del(p);
+}
+
+void
+rsock_opos(struct aproc *p, struct abuf *obuf, int delta)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ f->odelta += delta;
+ DPRINTFN(3, "rsock_opos: %p: delta = %d, odelta = %d\n",
+ f, delta, f->odelta);
+
+ /*
+ * negative deltas are xrun notifications for internal uses
+ * only. Dont generate a packet for this, the client will be
+ * notified later.
+ */
+ if (delta <= 0)
+ return;
+ for (;;) {
+ if (!sock_write(f))
+ break;
+ }
+}
+
+struct aproc_ops rsock_ops = {
+ "rsock",
+ rsock_in,
+ rsock_out,
+ rsock_eof,
+ rsock_hup,
+ NULL, /* newin */
+ NULL, /* newout */
+ NULL, /* ipos */
+ rsock_opos,
+ rsock_done
+};
+
+void
+wsock_done(struct aproc *p)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ DPRINTFN(1, "wsock_done: %p\n", f);
+ sock_reset(f);
+ f->pipe.file.wproc = NULL;
+ if (f->pipe.file.rproc) {
+ aproc_del(f->pipe.file.rproc);
+ file_del(&f->pipe.file);
+ }
+}
+
+int
+wsock_in(struct aproc *p, struct abuf *ibuf)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ if (f->pipe.file.refs > 0)
+ return 0;
+
+ DPRINTFN(4, "wsock_in: %p\n", f);
+
+ /*
+ * see remark in rsock_out()
+ */
+ for (;;) {
+ if (!sock_write(f))
+ return 0;
+ }
+ return 1;
+}
+
+int
+wsock_out(struct aproc *p, struct abuf *obuf_dummy)
+{
+ struct abuf *ibuf = LIST_FIRST(&p->ibuflist);
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ DPRINTFN(3, "wsock_out: %p\n", f);
+
+ if (ibuf) {
+ DPRINTFN(3, "wsock_out: %p, filling ibuf\n", f);
+ if (!abuf_fill(ibuf))
+ return 0;
+ }
+ if (!sock_write(f))
+ return 0;
+ return 1;
+}
+
+void
+wsock_eof(struct aproc *p, struct abuf *obuf)
+{
+ DPRINTFN(3, "wsock_eof: %p\n", p->u.io.file);
+ aproc_del(p);
+}
+
+void
+wsock_hup(struct aproc *p, struct abuf *obuf_dummy)
+{
+ DPRINTFN(3, "wsock_hup: %p\n", p->u.io.file);
+ aproc_del(p);
+}
+
+void
+wsock_ipos(struct aproc *p, struct abuf *obuf, int delta)
+{
+ struct sock *f = (struct sock *)p->u.io.file;
+
+ f->idelta += delta;
+ DPRINTFN(3, "wsock_ipos: %p, delta = %d, odelta = %d\n",
+ f, delta, f->idelta);
+
+ /*
+ * negative deltas are xrun notifications for internal uses
+ * only. Dont generate a packet for this, the client will be
+ * notified later.
+ */
+ if (delta <= 0)
+ return;
+ for (;;) {
+ if (!sock_write(f))
+ break;
+ }
+}
+
+struct aproc_ops wsock_ops = {
+ "wsock",
+ wsock_in,
+ wsock_out,
+ wsock_eof,
+ wsock_hup,
+ NULL, /* newin */
+ NULL, /* newout */
+ wsock_ipos,
+ NULL, /* opos */
+ wsock_done
+};
+
+/*
+ * initialise socket in the SOCK_INIT state with default
+ * parameters
+ */
+struct sock *
+sock_new(struct fileops *ops, int fd, char *name)
+{
+ struct aproc *rproc, *wproc;
+ struct sock *f;
+
+ f = (struct sock *)pipe_new(ops, fd, name);
+ f->pstate = SOCK_INIT;
+ f->mode = 0;
+ if (dev_rec) {
+ f->wpar = dev_ipar;
+ f->mode |= AMSG_REC;
+ }
+ if (dev_play) {
+ f->rpar = dev_opar;
+ f->mode |= AMSG_PLAY;
+ }
+ f->xrun = AMSG_IGNORE;
+ f->bufsz = 2 * dev_bufsz;
+ f->round = dev_round;
+ f->odelta = f->idelta = 0;
+
+ wproc = aproc_new(&wsock_ops, name);
+ wproc->u.io.file = &f->pipe.file;
+ f->pipe.file.wproc = wproc;
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+
+ rproc = aproc_new(&rsock_ops, name);
+ rproc->u.io.file = &f->pipe.file;
+ f->pipe.file.rproc = rproc;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ return f;
+}
+
+/*
+ * free buffers
+ */
+void
+sock_freebuf(struct sock *f)
+{
+ struct abuf *rbuf, *wbuf;
+
+ f->pstate = SOCK_INIT;
+ DPRINTF("sock_freebuf:\n");
+ rbuf = LIST_FIRST(&f->pipe.file.rproc->obuflist);
+ if (rbuf)
+ abuf_eof(rbuf);
+ wbuf = LIST_FIRST(&f->pipe.file.wproc->ibuflist);
+ if (wbuf)
+ abuf_hup(wbuf);
+}
+
+/*
+ * allocate buffers, so client can start filling write-end.
+ */
+void
+sock_allocbuf(struct sock *f)
+{
+ struct abuf *rbuf = NULL, *wbuf = NULL;
+ unsigned nfr = 0;
+
+ if (f->mode & AMSG_PLAY) {
+ nfr = f->bufsz - dev_bufsz * f->rpar.rate / dev_rate;
+ rbuf = abuf_new(nfr, aparams_bpf(&f->rpar));
+ aproc_setout(f->pipe.file.rproc, rbuf);
+ f->odelta = 0;
+ }
+ if (f->mode & AMSG_REC) {
+ nfr = f->bufsz - dev_bufsz * f->wpar.rate / dev_rate;
+ wbuf = abuf_new(nfr, aparams_bpf(&f->wpar));
+ aproc_setin(f->pipe.file.wproc, wbuf);
+ f->idelta = 0;
+ }
+
+ DPRINTF("sock_allocbuf: %p, using %u/%u frames buffer\n",
+ f, nfr, f->bufsz);
+
+ f->pstate = SOCK_START;
+ if (!(f->mode & AMSG_PLAY))
+ (void)sock_attach(f, 0);
+}
+
+/*
+ * attach play and/or record buffers to dev_mix and/or dev_sub
+ */
+int
+sock_attach(struct sock *f, int force)
+{
+ struct abuf *rbuf, *wbuf;
+
+ rbuf = LIST_FIRST(&f->pipe.file.rproc->obuflist);
+ wbuf = LIST_FIRST(&f->pipe.file.wproc->ibuflist);
+
+ /*
+ * if in SOCK_START state, dont attach until
+ * the buffer isn't completely filled
+ */
+ if (!force && rbuf && ABUF_WOK(rbuf))
+ return 0;
+
+ DPRINTF("sock_attach: %p\n", f);
+ f->pstate = SOCK_RUN;
+
+ /*
+ * attach them to the device
+ */
+ dev_attach(f->pipe.file.name,
+ (f->mode & AMSG_PLAY) ? rbuf : NULL, &f->rpar, f->xrun,
+ (f->mode & AMSG_REC) ? wbuf : NULL, &f->wpar, f->xrun);
+
+ /*
+ * send the initial position, if needed
+ */
+ for (;;) {
+ if (!sock_write(f))
+ break;
+ }
+ return 1;
+}
+
+void
+sock_reset(struct sock *f)
+{
+ switch (f->pstate) {
+ case SOCK_START:
+ (void)sock_attach(f, 1);
+ f->pstate = SOCK_RUN;
+ /* PASSTHROUGH */
+ case SOCK_RUN:
+ sock_freebuf(f);
+ f->pstate = SOCK_INIT;
+ /* PASSTHROUGH */
+ case SOCK_INIT:
+ /* nothing yet */
+ break;
+ }
+}
+
+/*
+ * read a message from the file descriptor, return 1 if done, 0
+ * otherwise. The message is stored in f->rmsg
+ */
+int
+sock_rmsg(struct sock *f)
+{
+ unsigned count;
+ unsigned char *data;
+
+ while (f->rtodo > 0) {
+ if (!(f->pipe.file.state & FILE_ROK)) {
+ DPRINTFN(4, "sock_rmsg: blk, rtodo = %u\n", f->rtodo);
+ return 0;
+ }
+ data = (unsigned char *)&f->rmsg;
+ data += sizeof(struct amsg) - f->rtodo;
+ count = file_read(&f->pipe.file, data, f->rtodo);
+ if (count == 0)
+ return 0;
+ f->rtodo -= count;
+ }
+ DPRINTFN(4, "sock_rmsg: %p: done\n", f);
+ return 1;
+}
+
+/*
+ * write a message to the file descriptor, return 1 if done, 0
+ * otherwise. The "m" argument is f->rmsg or f->wmsg, and the "ptodo"
+ * points to the f->rtodo or f->wtodo respectively.
+ */
+int
+sock_wmsg(struct sock *f, struct amsg *m, unsigned *ptodo)
+{
+ unsigned count;
+ unsigned char *data;
+
+ while (*ptodo > 0) {
+ if (!(f->pipe.file.state & FILE_WOK)) {
+ DPRINTFN(4, "sock_wmsg: blk, *ptodo = %u\n", *ptodo);
+ return 0;
+ }
+ data = (unsigned char *)m;
+ data += sizeof(struct amsg) - *ptodo;
+ count = file_write(&f->pipe.file, data, *ptodo);
+ if (count == 0)
+ return 0;
+ *ptodo -= count;
+ }
+ DPRINTFN(4, "sock_wmsg: %p: done\n", f);
+ return 1;
+}
+
+/*
+ * read data chunk from the file descriptor, return 1 if at least one
+ * byte was read, 0 if the file blocked.
+ */
+int
+sock_rdata(struct sock *f)
+{
+ struct aproc *p;
+ struct abuf *obuf;
+ unsigned char *data;
+ unsigned count, n;
+
+#ifdef DEBUG
+ if (f->rtodo == 0) {
+ fprintf(stderr, "sock_rdata: bad call: zero arg\n");
+ abort();
+ }
+#endif
+ p = f->pipe.file.rproc;
+ obuf = LIST_FIRST(&p->obuflist);
+ if (ABUF_FULL(obuf) || !(f->pipe.file.state & FILE_ROK))
+ return 0;
+ data = abuf_wgetblk(obuf, &count, 0);
+ if (count > f->rtodo)
+ count = f->rtodo;
+ n = file_read(&f->pipe.file, data, count);
+ if (n == 0)
+ return 0;
+ abuf_wcommit(obuf, n);
+ f->rtodo -= n;
+ return 1;
+}
+
+/*
+ * write data chunk to the file descriptor, return 1 if at least one
+ * byte was written, 0 if the file blocked.
+ */
+int
+sock_wdata(struct sock *f)
+{
+ struct aproc *p;
+ struct abuf *ibuf;
+ unsigned char *data;
+ unsigned count, n;
+#define ZERO_MAX 0x1000
+ static char zero[ZERO_MAX];
+
+#ifdef DEBUG
+ if (f->wtodo == 0) {
+ fprintf(stderr, "sock_wdata: bad call: zero arg\n");
+ abort();
+ }
+#endif
+ if (!(f->pipe.file.state & FILE_WOK))
+ return 0;
+ p = f->pipe.file.wproc;
+ ibuf = LIST_FIRST(&p->ibuflist);
+ if (ibuf) {
+ if (ABUF_EMPTY(ibuf))
+ return 0;
+ data = abuf_rgetblk(ibuf, &count, 0);
+ if (count > f->wtodo)
+ count = f->wtodo;
+ n = file_write(&f->pipe.file, data, count);
+ if (n == 0)
+ return 0;
+ abuf_rdiscard(ibuf, n);
+ f->wtodo -= n;
+ } else {
+ /*
+ * there's no dev_detach() routine yet,
+ * so now we abruptly destroy the buffer.
+ * Until we implement dev_detach, complete
+ * the packet with zeros...
+ */
+ count = ZERO_MAX;
+ if (count > f->wtodo)
+ count = f->wtodo;
+ n = file_write(&f->pipe.file, zero, count);
+ if (n == 0)
+ return 0;
+ f->wtodo -= n;
+ }
+ return 1;
+}
+
+int
+sock_setpar(struct sock *f)
+{
+ struct amsg_par *p = &f->rmsg.u.par;
+ unsigned minbuf, maxbuf;
+
+ if (AMSG_ISSET(p->mode)) {
+ if ((p->mode & ~(AMSG_PLAY | AMSG_REC)) || p->mode == 0) {
+ DPRINTF("sock_setpar: bad mode %x\n", p->mode);
+ return 0;
+ }
+ f->mode = 0;
+ if ((p->mode & AMSG_PLAY) && dev_mix)
+ f->mode |= AMSG_PLAY;
+ if ((p->mode & AMSG_REC) && dev_sub)
+ f->mode |= AMSG_REC;
+ DPRINTF("sock_setpar: mode -> %x\n", f->mode);
+ }
+ if (AMSG_ISSET(p->bits)) {
+ if (p->bits < BITS_MIN || p->bits > BITS_MAX) {
+ DPRINTF("sock_setpar: bits out of bounds\n");
+ return 0;
+ }
+ if (AMSG_ISSET(p->bps)) {
+ if (p->bps < ((p->bits + 7) / 8) || p->bps > 4) {
+ DPRINTF("sock_setpar: bps out of bounds\n");
+ return 0;
+ }
+ } else
+ p->bps = APARAMS_BPS(p->bits);
+ f->rpar.bits = f->wpar.bits = p->bits;
+ f->rpar.bps = f->wpar.bps = p->bps;
+ DPRINTF("sock_setpar: bits/bps -> %u/%u\n", p->bits, p->bps);
+ }
+ if (AMSG_ISSET(p->sig))
+ f->rpar.sig = f->wpar.sig = p->sig ? 1 : 0;
+ if (AMSG_ISSET(p->le))
+ f->rpar.le = f->wpar.le = p->le ? 1 : 0;
+ if (AMSG_ISSET(p->msb))
+ f->rpar.msb = f->wpar.msb = p->msb ? 1 : 0;
+ if (AMSG_ISSET(p->rchan) && (f->mode & AMSG_REC)) {
+ if (p->rchan < 1)
+ p->rchan = 1;
+ if (p->rchan > NCHAN_MAX - 1)
+ p->rchan = NCHAN_MAX - 1;
+ f->wpar.cmin = 0;
+ f->wpar.cmax = p->rchan - 1;
+ DPRINTF("sock_setpar: rchan -> %u\n", p->rchan);
+ }
+ if (AMSG_ISSET(p->pchan) && (f->mode & AMSG_PLAY)) {
+ if (p->pchan < 1)
+ p->pchan = 1;
+ if (p->pchan > NCHAN_MAX - 1)
+ p->pchan = NCHAN_MAX - 1;
+ f->rpar.cmin = 0;
+ f->rpar.cmax = p->pchan - 1;
+ DPRINTF("sock_setpar: pchan -> %u\n", p->pchan);
+ }
+ if (AMSG_ISSET(p->rate)) {
+ if (p->rate < RATE_MIN)
+ p->rate = RATE_MIN;
+ if (p->rate > RATE_MAX)
+ p->rate = RATE_MAX;
+ dev_roundrate(&p->rate, &f->round);
+ f->rpar.rate = f->wpar.rate = p->rate;
+ if (f->mode & AMSG_PLAY)
+ f->bufsz = 2 * dev_bufsz * f->rpar.rate / dev_rate;
+ else
+ f->bufsz = 2 * dev_bufsz * f->wpar.rate / dev_rate;
+ DPRINTF("sock_setpar: rate -> %u\n", p->rate);
+ }
+ if (AMSG_ISSET(p->xrun)) {
+ if (p->xrun != AMSG_IGNORE &&
+ p->xrun != AMSG_SYNC &&
+ p->xrun != AMSG_ERROR) {
+ DPRINTF("sock_setpar: bad xrun: %u\n", p->xrun);
+ return 0;
+ }
+ f->xrun = p->xrun;
+ DPRINTF("sock_setpar: xrun -> %u\n", f->xrun);
+ }
+ if (AMSG_ISSET(p->bufsz)) {
+ minbuf = 3 * dev_bufsz / 2;
+ minbuf -= minbuf % dev_round;
+ maxbuf = dev_bufsz;
+ if (f->mode & AMSG_PLAY) {
+ minbuf = minbuf * f->rpar.rate / dev_rate;
+ maxbuf = maxbuf * f->rpar.rate / dev_rate;
+ maxbuf += f->rpar.rate;
+ } else {
+ minbuf = minbuf * f->wpar.rate / dev_rate;
+ maxbuf = maxbuf * f->wpar.rate / dev_rate;
+ maxbuf += f->wpar.rate;
+ }
+ if (p->bufsz < minbuf)
+ p->bufsz = minbuf;
+ if (p->bufsz > maxbuf)
+ p->bufsz = maxbuf;
+ f->bufsz = p->bufsz + f->round - 1;
+ f->bufsz -= f->bufsz % f->round;
+ DPRINTF("sock_setpar: bufsz -> %u\n", f->bufsz);
+ }
+ if (debug_level > 0) {
+ fprintf(stderr, "sock_setpar: %p: rpar=", f);
+ aparams_print(&f->rpar);
+ fprintf(stderr, ", wpar=");
+ aparams_print(&f->wpar);
+ fprintf(stderr, ", mode=%u, bufsz=%u\n", f->mode, f->bufsz);
+ }
+ return 1;
+}
+
+/*
+ * execute message in f->rmsg and change the state accordingly; return 1
+ * on success, and 0 on failure, in which case the socket is destroyed.
+ */
+int
+sock_execmsg(struct sock *f)
+{
+ struct amsg *m = &f->rmsg;
+
+ switch (m->cmd) {
+ case AMSG_DATA:
+ DPRINTFN(4, "sock_execmsg: %p: DATA\n", f);
+ if (f->pstate != SOCK_RUN && f->pstate != SOCK_START) {
+ DPRINTF("sock_execmsg: %p: DATA, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ f->rstate = SOCK_RDATA;
+ f->rtodo = m->u.data.size;
+ if (f->rtodo == 0) {
+ DPRINTF("sock_execmsg: zero-length data chunk\n");
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ break;
+ case AMSG_START:
+ DPRINTFN(2, "sock_execmsg: %p: START\n", f);
+ if (f->pstate != SOCK_INIT) {
+ DPRINTF("sock_execmsg: %p: START, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ sock_allocbuf(f);
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_STOP:
+ DPRINTFN(2, "sock_execmsg: %p: STOP\n", f);
+ if (f->pstate != SOCK_RUN && f->pstate != SOCK_START) {
+ DPRINTF("sock_execmsg: %p: STOP, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ if (f->pstate == SOCK_START)
+ (void)sock_attach(f, 1);
+ sock_freebuf(f);
+ AMSG_INIT(m);
+ m->cmd = AMSG_ACK;
+ f->rstate = SOCK_RRET;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_SETPAR:
+ DPRINTFN(2, "sock_execmsg: %p: SETPAR\n", f);
+ if (f->pstate != SOCK_INIT) {
+ DPRINTF("sock_execmsg: %p: SETPAR, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ if (!sock_setpar(f)) {
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ break;
+ case AMSG_GETPAR:
+ DPRINTFN(2, "sock_execmsg: %p: GETPAR\n", f);
+ if (f->pstate != SOCK_INIT) {
+ DPRINTF("sock_execmsg: %p: GETPAR, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ AMSG_INIT(m);
+ m->cmd = AMSG_GETPAR;
+ m->u.par.mode = f->mode;
+ m->u.par.bits = f->rpar.bits;
+ m->u.par.bps = f->rpar.bps;
+ m->u.par.sig = f->rpar.sig;
+ m->u.par.le = f->rpar.le;
+ m->u.par.msb = f->rpar.msb;
+ m->u.par.rate = f->rpar.rate;
+ m->u.par.rchan = f->wpar.cmax - f->wpar.cmin + 1;
+ m->u.par.pchan = f->rpar.cmax - f->rpar.cmin + 1;
+ m->u.par.bufsz = f->bufsz;
+ m->u.par.round = f->round;
+ f->rstate = SOCK_RRET;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_GETCAP:
+ DPRINTFN(2, "sock_execmsg: %p: GETCAP\n", f);
+ if (f->pstate != SOCK_INIT) {
+ DPRINTF("sock_execmsg: %p: GETCAP, bad state\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ AMSG_INIT(m);
+ m->cmd = AMSG_GETCAP;
+ m->u.cap.rate = dev_rate;
+ m->u.cap.rate_div = dev_rate_div;
+ m->u.cap.pchan = dev_opar.cmax - dev_opar.cmin + 1;
+ m->u.cap.rchan = dev_ipar.cmax - dev_ipar.cmin + 1;
+ m->u.cap.bits = sizeof(short) * 8;
+ m->u.cap.bps = sizeof(short);
+ f->rstate = SOCK_RRET;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ default:
+ DPRINTF("sock_execmsg: %p bogus command\n", f);
+ aproc_del(f->pipe.file.rproc);
+ return 0;
+ }
+ if (f->rstate == SOCK_RRET) {
+ if (f->wstate != SOCK_WIDLE ||
+ !sock_wmsg(f, &f->rmsg, &f->rtodo))
+ return 0;
+ DPRINTF("sock_execmsg: %p RRET done\n", f);
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ }
+ return 1;
+}
+
+/*
+ * create a new data/pos message
+ */
+int
+sock_buildmsg(struct sock *f)
+{
+ struct aproc *p;
+ struct abuf *ibuf;
+ int *pdelta;
+
+ /*
+ * if pos changed, build a MOVE message
+ */
+ pdelta = (f->mode & AMSG_REC) ? &f->idelta : &f->odelta;
+ if ((f->pstate == SOCK_RUN && *pdelta > 0) ||
+ (f->pstate == SOCK_START && *pdelta < 0)) {
+ DPRINTFN(4, "sock_buildmsg: %p: POS: %d\n", f, *pdelta);
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = AMSG_MOVE;
+ f->wmsg.u.ts.delta = *pdelta;
+ *pdelta = 0;
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ /*
+ * if data available, build a DATA message
+ */
+ p = f->pipe.file.wproc;
+ ibuf = LIST_FIRST(&p->ibuflist);
+ if (ibuf && ABUF_ROK(ibuf)) {
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = AMSG_DATA;
+ f->wmsg.u.data.size = ibuf->used - (ibuf->used % ibuf->bpf);
+ if (f->wmsg.u.data.size > AMSG_DATAMAX)
+ f->wmsg.u.data.size =
+ AMSG_DATAMAX - (AMSG_DATAMAX % ibuf->bpf);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ DPRINTFN(4, "sock_buildmsg: %p: idling...\n", f);
+ f->wstate = SOCK_WIDLE;
+ return 0;
+}
+
+/*
+ * read from the socket file descriptor, fill input buffer and update
+ * the state. Return 1 if at least one message or 1 data byte was
+ * processed, 0 if something blocked.
+ */
+int
+sock_read(struct sock *f)
+{
+ DPRINTFN(4, "sock_read: %p; rstate = %u, rtodo = %u\n",
+ f, f->rstate, f->rtodo);
+
+ switch (f->rstate) {
+ case SOCK_RMSG:
+ if (!sock_rmsg(f))
+ return 0;
+ if (!sock_execmsg(f))
+ return 0;
+ break;
+ case SOCK_RDATA:
+ if (!sock_rdata(f))
+ return 0;
+ if (f->rtodo == 0) {
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ }
+ if (f->pstate == SOCK_START)
+ (void)sock_attach(f, 0);
+ break;
+ case SOCK_RRET:
+ DPRINTF("sock_read: %p: blocked in RRET\n", f);
+ return 0;
+ }
+ DPRINTFN(4, "sock_read: %p: done, rstate = %u\n", f, f->rstate);
+ return 1;
+}
+
+/*
+ * process messages to return
+ */
+int
+sock_return(struct sock *f)
+{
+ struct aproc *rp;
+
+ while (f->rstate == SOCK_RRET) {
+ if (!sock_wmsg(f, &f->rmsg, &f->rtodo))
+ return 0;
+ DPRINTF("sock_return: %p: done\n", f);
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ for (;;) {
+ /*
+ * in() may trigger rsock_done and destroy the
+ * wsock
+ */
+ rp = f->pipe.file.rproc;
+ if (!rp || !rp->ops->in(rp, NULL))
+ break;
+ }
+ if (f->pipe.file.wproc == NULL)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * write messages and data on the socket file descriptor. Return 1 if
+ * at least one message or one data byte was processed, 0 if something
+ * blocked.
+ */
+int
+sock_write(struct sock *f)
+{
+ DPRINTFN(4, "sock_write: %p: wstate = %u, wtodo = %u\n",
+ f, f->wstate, f->wtodo);
+
+ switch (f->wstate) {
+ case SOCK_WMSG:
+ if (!sock_wmsg(f, &f->wmsg, &f->wtodo))
+ return 0;
+ if (f->wmsg.cmd != AMSG_DATA) {
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+ break;
+ }
+ f->wstate = SOCK_WDATA;
+ f->wtodo = f->wmsg.u.data.size;
+ /* PASSTHROUGH */
+ case SOCK_WDATA:
+ if (!sock_wdata(f))
+ return 0;
+ if (f->wtodo > 0)
+ break;
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+ /* PASSTHROUGH */
+ case SOCK_WIDLE:
+ if (!sock_return(f))
+ return 0;
+ if (!sock_buildmsg(f))
+ return 0;
+ break;
+ default:
+ fprintf(stderr, "sock_write: unknown state\n");
+ abort();
+ }
+ return 1;
+}
+
diff --git a/usr.bin/aucat/sock.h b/usr.bin/aucat/sock.h
new file mode 100644
index 00000000000..45d96b177e8
--- /dev/null
+++ b/usr.bin/aucat/sock.h
@@ -0,0 +1,58 @@
+/* $OpenBSD: sock.h,v 1.1 2008/10/26 08:49:44 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 SOCK_H
+#define SOCK_H
+
+#include "pipe.h"
+#include "aparams.h"
+#include "amsg.h"
+
+struct sock {
+ struct pipe pipe;
+ /*
+ * socket and protocol specific stuff, mainly used
+ * to decode/encode messages in the stream.
+ */
+ struct amsg rmsg, wmsg; /* messages being sent/received */
+ unsigned rtodo; /* input bytes not read yet */
+ unsigned wtodo; /* output bytes not written yet */
+#define SOCK_RDATA 0 /* data chunk being read */
+#define SOCK_RMSG 1 /* amsg query being processed */
+#define SOCK_RRET 2 /* amsg reply being returned */
+ unsigned rstate; /* state of the read-end FSM */
+#define SOCK_WIDLE 0 /* nothing to do */
+#define SOCK_WMSG 1 /* amsg being written */
+#define SOCK_WDATA 2 /* data chunk being written */
+ unsigned wstate; /* state of the write-end FSM */
+#define SOCK_INIT 0 /* parameter negotiation */
+#define SOCK_START 1 /* filling play buffers */
+#define SOCK_RUN 2 /* attached to the mix / sub */
+ unsigned pstate; /* one of the above */
+ unsigned mode; /* a set of AMSG_PLAY, AMSG_REC */
+ struct aparams rpar; /* read (ie play) parameters */
+ struct aparams wpar; /* write (ie rec) parameters */
+ int idelta; /* input (rec) pos. change to send */
+ int odelta; /* output (play) pos. change to send */
+ unsigned bufsz; /* total buffer size */
+ unsigned round; /* block size */
+ unsigned xrun; /* one of AMSG_IGNORE, ... */
+};
+
+struct sock *sock_new(struct fileops *, int fd, char *);
+extern struct fileops sock_ops;
+
+#endif /* !defined(SOCK_H) */
diff --git a/usr.bin/aucat/wav.c b/usr.bin/aucat/wav.c
new file mode 100644
index 00000000000..ae85e809f13
--- /dev/null
+++ b/usr.bin/aucat/wav.c
@@ -0,0 +1,116 @@
+#include <sys/types.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "wav.h"
+
+/*
+ * 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)
+
+struct fileops wav_ops = {
+ "wav",
+ sizeof(struct wav),
+ wav_close,
+ wav_read,
+ wav_write,
+ NULL, /* start */
+ NULL, /* stop */
+ pipe_nfds,
+ pipe_pollfd,
+ pipe_revents
+};
+
+struct wav *
+wav_new_in(struct fileops *ops, int fd, char *name,
+ struct aparams *par, unsigned hdr)
+{
+ struct wav *f;
+
+ f = (struct wav *)pipe_new(ops, fd, name);
+ if (hdr == HDR_WAV) {
+ if (!wav_readhdr(f->pipe.fd, par, &f->rbytes))
+ exit(1);
+ f->hpar = *par;
+ } else
+ f->rbytes = -1;
+ f->hdr = 0;
+ return f;
+}
+
+struct wav *
+wav_new_out(struct fileops *ops, int fd, char *name,
+ struct aparams *par, unsigned hdr)
+{
+ struct wav *f;
+
+ f = (struct wav *)pipe_new(ops, fd, name);
+ if (hdr == HDR_WAV) {
+ if (!wav_writehdr(f->pipe.fd, par))
+ exit(1);
+ f->hpar = *par;
+ f->wbytes = WAV_DATAMAX;
+ } else
+ f->wbytes = -1;
+ f->hdr = hdr;
+ return f;
+}
+
+unsigned
+wav_read(struct file *file, unsigned char *data, unsigned count)
+{
+ struct wav *f = (struct wav *)file;
+ unsigned n;
+
+ if (f->rbytes >= 0 && count > f->rbytes) {
+ count = f->rbytes; /* file->rbytes fits in count */
+ if (count == 0) {
+ DPRINTFN(2, "wav_read: %s: complete\n", f->pipe.file.name);
+ file_eof(&f->pipe.file);
+ return 0;
+ }
+ }
+ n = pipe_read(file, data, count);
+ if (f->rbytes >= 0)
+ f->rbytes -= n;
+ return n;
+}
+
+
+unsigned
+wav_write(struct file *file, unsigned char *data, unsigned count)
+{
+ struct wav *f = (struct wav *)file;
+ unsigned n;
+
+ if (f->wbytes >= 0 && count > f->wbytes) {
+ count = f->wbytes; /* wbytes fits in count */
+ if (count == 0) {
+ DPRINTFN(2, "wav_write: %s: complete\n",
+ f->pipe.file.name);
+ file_hup(&f->pipe.file);
+ return 0;
+ }
+ }
+ n = pipe_write(file, data, count);
+ if (f->wbytes >= 0)
+ f->wbytes -= n;
+ return n;
+}
+
+void
+wav_close(struct file *file)
+{
+ struct wav *f = (struct wav *)file;
+
+ if (f->hdr == HDR_WAV)
+ wav_writehdr(f->pipe.fd, &f->hpar);
+ pipe_close(file);
+}
diff --git a/usr.bin/aucat/wav.h b/usr.bin/aucat/wav.h
new file mode 100644
index 00000000000..dea4971d869
--- /dev/null
+++ b/usr.bin/aucat/wav.h
@@ -0,0 +1,51 @@
+/* $OpenBSD: wav.h,v 1.1 2008/10/26 08:49:44 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 WAV_H
+#define WAV_H
+
+#include <sys/types.h>
+
+#include "pipe.h"
+#include "aparams.h"
+
+struct wav {
+ struct pipe pipe;
+#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 */
+ off_t rbytes; /* bytes to read, -1 if no limit */
+ off_t wbytes; /* bytes to write, -1 if no limit */
+};
+
+extern struct fileops wav_ops;
+
+struct wav *wav_new_in(struct fileops *, int, char *,
+ struct aparams *, unsigned);
+struct wav *wav_new_out(struct fileops *, int, char *,
+ struct aparams *, unsigned);
+unsigned wav_read(struct file *, unsigned char *, unsigned);
+unsigned wav_write(struct file *, unsigned char *, unsigned);
+void wav_close(struct file *);
+int wav_readhdr(int, struct aparams *, off_t *);
+int wav_writehdr(int, struct aparams *);
+
+/* legacy */
+int legacy_play(char *, char *);
+
+#endif /* !defined(WAV_H) */