diff options
author | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2008-10-26 08:49:45 +0000 |
---|---|---|
committer | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2008-10-26 08:49:45 +0000 |
commit | 13e276c156d9b9f3a5064700b447d8e90d89bebf (patch) | |
tree | 7c47aad8fcd6da2de5156ec12853b26ee468547e /usr.bin | |
parent | c1f6af90f771854093903e82e7de930b96a15d25 (diff) |
add minimal server capability to aucat(1). When started in server
mode, it listens on an unix socket and mixes/demultiplexes any number
of full-duplex streams, doing necessary format conversions and
resampling on the fly.
programs can use the new libsa(3) library to play and record audio.
The library provides a very simple API to connect to the audio server;
if aucat(1) isn't running, it uses the audio(4) driver transparently
instead.
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/aucat/Makefile | 6 | ||||
-rw-r--r-- | usr.bin/aucat/abuf.c | 327 | ||||
-rw-r--r-- | usr.bin/aucat/abuf.h | 17 | ||||
-rw-r--r-- | usr.bin/aucat/amsg.h | 92 | ||||
-rw-r--r-- | usr.bin/aucat/aparams.c | 147 | ||||
-rw-r--r-- | usr.bin/aucat/aparams.h | 23 | ||||
-rw-r--r-- | usr.bin/aucat/aproc.c | 411 | ||||
-rw-r--r-- | usr.bin/aucat/aproc.h | 29 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.1 | 104 | ||||
-rw-r--r-- | usr.bin/aucat/aucat.c | 286 | ||||
-rw-r--r-- | usr.bin/aucat/conf.h | 18 | ||||
-rw-r--r-- | usr.bin/aucat/dev.c | 469 | ||||
-rw-r--r-- | usr.bin/aucat/dev.h | 29 | ||||
-rw-r--r-- | usr.bin/aucat/dev_sun.c | 282 | ||||
-rw-r--r-- | usr.bin/aucat/file.c | 292 | ||||
-rw-r--r-- | usr.bin/aucat/file.h | 61 | ||||
-rw-r--r-- | usr.bin/aucat/headers.c | 4 | ||||
-rw-r--r-- | usr.bin/aucat/legacy.c | 74 | ||||
-rw-r--r-- | usr.bin/aucat/listen.c | 133 | ||||
-rw-r--r-- | usr.bin/aucat/listen.h | 37 | ||||
-rw-r--r-- | usr.bin/aucat/pipe.c | 146 | ||||
-rw-r--r-- | usr.bin/aucat/pipe.h | 39 | ||||
-rw-r--r-- | usr.bin/aucat/safile.c | 288 | ||||
-rw-r--r-- | usr.bin/aucat/safile.h | 37 | ||||
-rw-r--r-- | usr.bin/aucat/sock.c | 931 | ||||
-rw-r--r-- | usr.bin/aucat/sock.h | 58 | ||||
-rw-r--r-- | usr.bin/aucat/wav.c | 116 | ||||
-rw-r--r-- | usr.bin/aucat/wav.h | 51 |
28 files changed, 3308 insertions, 1199 deletions
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) */ |