diff options
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) */ |