import from OpenBSD

This commit is contained in:
Alexandre Ratchov 2010-08-19 22:38:45 +02:00
commit f268454989
48 changed files with 18359 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# CVS default ignores begin
tags
TAGS
.make.state
.nse_depinfo
*~
#*
.#*
,*
_$*
*$
*.old
*.bak
*.BAK
*.orig
*.rej
.del-*
*.a
*.olb
*.o
*.obj
*.so
*.exe
*.Z
*.elc
*.ln
core
# CVS default ignores end

619
aucat/abuf.c Normal file
View File

@ -0,0 +1,619 @@
/* $OpenBSD: abuf.c,v 1.22 2010/06/04 06:15:28 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.
*/
/*
* Simple byte fifo. It has one reader and one writer. The abuf
* structure is used to interconnect audio processing units (aproc
* structures).
*
* The abuf data is split in two parts: (1) valid data available to the reader
* (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
*
* 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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "abuf.h"
#include "aparams.h"
#include "aproc.h"
#include "conf.h"
#ifdef DEBUG
#include "dbg.h"
#endif
#ifdef DEBUG
void
abuf_dbg(struct abuf *buf)
{
if (buf->wproc) {
aproc_dbg(buf->wproc);
} else {
dbg_puts("none");
}
dbg_puts(buf->inuse ? "=>" : "->");
if (buf->rproc) {
aproc_dbg(buf->rproc);
} else {
dbg_puts("none");
}
}
void
abuf_dump(struct abuf *buf)
{
abuf_dbg(buf);
dbg_puts(": used = ");
dbg_putu(buf->used);
dbg_puts("/");
dbg_putu(buf->len);
dbg_puts(" start = ");
dbg_putu(buf->start);
dbg_puts("\n");
}
#endif
struct abuf *
abuf_new(unsigned nfr, struct aparams *par)
{
struct abuf *buf;
unsigned len, bpf;
bpf = aparams_bpf(par);
len = nfr * bpf;
buf = malloc(sizeof(struct abuf) + len);
if (buf == NULL) {
#ifdef DEBUG
dbg_puts("couldn't allocate abuf of ");
dbg_putu(nfr);
dbg_puts("fr * ");
dbg_putu(bpf);
dbg_puts("bpf\n");
dbg_panic();
#else
err(1, "malloc");
#endif
}
buf->bpf = bpf;
buf->cmin = par->cmin;
buf->cmax = par->cmax;
buf->inuse = 0;
/*
* fill fifo pointers
*/
buf->len = nfr;
buf->used = 0;
buf->start = 0;
buf->rproc = NULL;
buf->wproc = NULL;
buf->duplex = NULL;
return buf;
}
void
abuf_del(struct abuf *buf)
{
if (buf->duplex)
buf->duplex->duplex = NULL;
#ifdef DEBUG
if (buf->rproc || buf->wproc) {
abuf_dbg(buf);
dbg_puts(": can't delete referenced buffer\n");
dbg_panic();
}
if (ABUF_ROK(buf)) {
/*
* XXX : we should call abort(), here.
* However, poll() doesn't seem to return POLLHUP,
* so the reader is never destroyed; instead it appears
* as blocked. Fix file_poll(), if fixable, and add
* a call to abord() here.
*/
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": deleting non-empty buffer, used = ");
dbg_putu(buf->used);
dbg_puts("\n");
}
}
#endif
free(buf);
}
/*
* Clear buffer contents.
*/
void
abuf_clear(struct abuf *buf)
{
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": cleared\n");
}
#endif
buf->used = 0;
buf->start = 0;
}
/*
* Get a pointer to the readable block at the given offset.
*/
unsigned char *
abuf_rgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
{
unsigned count, start, used;
start = buf->start + ofs;
used = buf->used - ofs;
if (start >= buf->len)
start -= buf->len;
#ifdef DEBUG
if (start >= buf->len || used > buf->used) {
abuf_dump(buf);
dbg_puts(": rgetblk: bad ofs = ");
dbg_putu(ofs);
dbg_puts("\n");
dbg_panic();
}
#endif
count = buf->len - start;
if (count > used)
count = used;
*rsize = count;
return (unsigned char *)buf + sizeof(struct abuf) + start * buf->bpf;
}
/*
* Discard the block at the start postion.
*/
void
abuf_rdiscard(struct abuf *buf, unsigned count)
{
#ifdef DEBUG
if (count > buf->used) {
abuf_dump(buf);
dbg_puts(": rdiscard: bad count = ");
dbg_putu(count);
dbg_puts("\n");
dbg_panic();
}
if (debug_level >= 4) {
abuf_dbg(buf);
dbg_puts(": discard(");
dbg_putu(count);
dbg_puts(")\n");
}
#endif
buf->used -= count;
buf->start += count;
if (buf->start >= buf->len)
buf->start -= buf->len;
}
/*
* Commit the data written at the end postion.
*/
void
abuf_wcommit(struct abuf *buf, unsigned count)
{
#ifdef DEBUG
if (count > (buf->len - buf->used)) {
abuf_dump(buf);
dbg_puts(": rdiscard: bad count = ");
dbg_putu(count);
dbg_puts("\n");
dbg_panic();
}
if (debug_level >= 4) {
abuf_dbg(buf);
dbg_puts(": commit(");
dbg_putu(count);
dbg_puts(")\n");
}
#endif
buf->used += count;
}
/*
* Get a pointer to the writable block at offset ofs.
*/
unsigned char *
abuf_wgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs)
{
unsigned end, avail, count;
end = buf->start + buf->used + ofs;
if (end >= buf->len)
end -= buf->len;
#ifdef DEBUG
if (end >= buf->len) {
abuf_dump(buf);
dbg_puts(": wgetblk: bad ofs = ");
dbg_putu(ofs);
dbg_puts("\n");
dbg_panic();
}
#endif
avail = buf->len - (buf->used + ofs);
count = buf->len - end;
if (count > avail)
count = avail;
*rsize = count;
return (unsigned char *)buf + sizeof(struct abuf) + end * buf->bpf;
}
/*
* Flush buffer either by dropping samples or by calling the aproc
* call-back to consume data. Return 0 if blocked, 1 otherwise.
*/
int
abuf_flush_do(struct abuf *buf)
{
struct aproc *p;
p = buf->rproc;
if (!p)
return 0;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": in\n");
}
#endif
return p->ops->in(p, buf);
}
/*
* Fill the buffer either by generating silence or by calling the aproc
* call-back to provide data. Return 0 if blocked, 1 otherwise.
*/
int
abuf_fill_do(struct abuf *buf)
{
struct aproc *p;
p = buf->wproc;
if (!p)
return 0;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": out\n");
}
#endif
return p->ops->out(p, buf);
}
/*
* 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) {
buf->rproc = NULL;
LIST_REMOVE(buf, ient);
buf->inuse++;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": eof\n");
}
#endif
p->ops->eof(p, buf);
buf->inuse--;
}
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)) {
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": hup: lost ");
dbg_putu(buf->used);
dbg_puts(" bytes\n");
}
#endif
buf->used = 0;
}
p = buf->wproc;
if (p != NULL) {
buf->wproc = NULL;
LIST_REMOVE(buf, oent);
buf->inuse++;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": hup\n");
}
#endif
p->ops->hup(p, buf);
buf->inuse--;
}
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) {
#ifdef DEBUG
if (debug_level >= 4) {
abuf_dbg(buf);
dbg_puts(": flush blocked (inuse)\n");
}
#endif
} else {
buf->inuse++;
for (;;) {
if (!abuf_flush_do(buf))
break;
}
buf->inuse--;
if (ABUF_HUP(buf)) {
abuf_hup_do(buf);
return 0;
}
}
return 1;
}
/*
* Notify the write end of the buffer that there is room and data can be
* written again. This routine can only be called from the out()
* call-back of the reader.
*
* Return 1 if the buffer was filled, and 0 if eof condition occured. The
* reader must detach the buffer on EOF condition, since its aproc->eof()
* call-back will never be called.
*/
int
abuf_fill(struct abuf *buf)
{
if (buf->inuse) {
#ifdef DEBUG
if (debug_level >= 4) {
abuf_dbg(buf);
dbg_puts(": fill blocked (inuse)\n");
}
#endif
} else {
buf->inuse++;
for (;;) {
if (!abuf_fill_do(buf))
break;
}
buf->inuse--;
if (ABUF_EOF(buf)) {
abuf_eof_do(buf);
return 0;
}
}
return 1;
}
/*
* Run a read/write loop on the buffer until either the reader or the
* writer blocks, or until the buffer reaches eofs. We can not get hup here,
* since hup() is only called from terminal nodes, from the main loop.
*
* NOTE: The buffer may disappear (ie. be free()ed) if eof is reached, so
* do not keep references to the buffer or to its writer or reader.
*/
void
abuf_run(struct abuf *buf)
{
int canfill = 1, canflush = 1;
if (buf->inuse) {
#ifdef DEBUG
if (debug_level >= 4) {
abuf_dbg(buf);
dbg_puts(": run blocked (inuse)\n");
}
#endif
return;
}
buf->inuse++;
for (;;) {
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;
}
buf->inuse--;
if (ABUF_EOF(buf)) {
abuf_eof_do(buf);
return;
}
if (ABUF_HUP(buf)) {
abuf_hup_do(buf);
return;
}
}
/*
* Notify the reader that there will be no more input (producer
* disappeared). The buffer is flushed and eof() is called only if all
* data is flushed.
*/
void
abuf_eof(struct abuf *buf)
{
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": eof requested\n");
}
if (buf->wproc == NULL) {
abuf_dbg(buf);
dbg_puts(": eof, no writer\n");
dbg_panic();
}
#endif
LIST_REMOVE(buf, oent);
buf->wproc = NULL;
if (buf->rproc != NULL) {
if (!abuf_flush(buf))
return;
if (ABUF_ROK(buf)) {
/*
* Could not flush everything, the reader will
* have a chance to delete the abuf later.
*/
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": eof, blocked (drain)\n");
}
#endif
return;
}
}
if (buf->inuse) {
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": eof, blocked (inuse)\n");
}
#endif
return;
}
abuf_eof_do(buf);
}
/*
* Notify the writer that the buffer has no more consumer,
* and that no more data will accepted.
*/
void
abuf_hup(struct abuf *buf)
{
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": hup requested\n");
}
if (buf->rproc == NULL) {
abuf_dbg(buf);
dbg_puts(": hup, no reader\n");
dbg_panic();
}
#endif
buf->rproc = NULL;
LIST_REMOVE(buf, ient);
if (buf->wproc != NULL) {
if (buf->inuse) {
#ifdef DEBUG
if (debug_level >= 3) {
abuf_dbg(buf);
dbg_puts(": eof, blocked (inuse)\n");
}
#endif
return;
}
}
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++;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": ipos delta = ");
dbg_puti(delta);
dbg_puts("\n");
}
#endif
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++;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": opos delta = ");
dbg_puti(delta);
dbg_puts("\n");
}
#endif
p->ops->opos(p, buf, delta);
buf->inuse--;
}
if (ABUF_HUP(buf))
abuf_hup_do(buf);
}

123
aucat/abuf.h Normal file
View File

@ -0,0 +1,123 @@
/* $OpenBSD: abuf.h,v 1.22 2010/04/06 20:07:01 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 ABUF_H
#define ABUF_H
#include <sys/queue.h>
#define XRUN_IGNORE 0 /* on xrun silently insert/discard samples */
#define XRUN_SYNC 1 /* catchup to sync to the mix/sub */
#define XRUN_ERROR 2 /* xruns are errors, eof/hup buffer */
#define MIDI_MSGMAX 16 /* max size of MIDI messaage */
struct aproc;
struct aparams;
struct abuf {
LIST_ENTRY(abuf) ient; /* reader's list of inputs entry */
LIST_ENTRY(abuf) oent; /* writer's list of outputs entry */
/*
* fifo parameters
*/
unsigned bpf; /* bytes per frame */
unsigned cmin, cmax; /* channel range of this buf */
unsigned start; /* offset where data starts */
unsigned used; /* valid data */
unsigned len; /* size of the ring */
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 tickets; /* max data to (if throttling) */
/*
* Misc reader aproc-specific per-buffer parameters.
*/
union {
struct {
int weight; /* dynamic range */
int maxweight; /* max dynamic range allowed */
unsigned vol; /* volume within the dynamic range */
unsigned done; /* frames ready */
unsigned xrun; /* underrun policy */
int drop; /* frames to drop on next read */
} mix;
struct {
unsigned st; /* MIDI running status */
unsigned used; /* bytes used from ``msg'' */
unsigned idx; /* actual MIDI message size */
unsigned len; /* MIDI message length */
unsigned char msg[MIDI_MSGMAX];
} midi;
} r;
/*
* Misc reader aproc-specific per-buffer parameters.
*/
union {
struct {
unsigned todo; /* frames to process */
} mix;
struct {
unsigned done; /* frames copied */
unsigned xrun; /* overrun policy */
int silence; /* silence to add on next write */
} sub;
} w;
};
/*
* the buffer contains at least one frame. This macro should
* be used to check if the buffer can be flushed
*/
#define ABUF_ROK(b) ((b)->used > 0)
/*
* there's room for at least one frame
*/
#define ABUF_WOK(b) ((b)->len - (b)->used > 0)
/*
* the buffer is empty and has no writer anymore
*/
#define ABUF_EOF(b) (!ABUF_ROK(b) && (b)->wproc == NULL)
/*
* the buffer has no reader anymore, note that it's not
* enough the buffer to be disconnected, because it can
* be not yet connected buffer (eg. socket play buffer)
*/
#define ABUF_HUP(b) (!ABUF_WOK(b) && (b)->rproc == NULL)
struct abuf *abuf_new(unsigned, struct aparams *);
void abuf_del(struct abuf *);
void abuf_dbg(struct abuf *);
void abuf_clear(struct abuf *);
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);
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) */

115
aucat/amsg.h Normal file
View File

@ -0,0 +1,115 @@
/* $OpenBSD: amsg.h,v 1.17 2010/06/05 12:45:48 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 AMSG_H
#define AMSG_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.
*
* Especially, make sure the amsg_xxx structures are not larger
* than 32 bytes.
*/
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_POS 6 /* initial position */
#define AMSG_MOVE 7 /* position changed */
#define AMSG_GETCAP 8 /* get capabilities */
#define AMSG_SETVOL 9 /* set volume */
#define AMSG_HELLO 10 /* say hello, check versions and so ... */
#define AMSG_BYE 11 /* ask server to drop connection */
uint32_t cmd;
uint32_t __pad;
union {
struct amsg_par {
uint8_t legacy_mode; /* compat for old libs */
#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; /* total buffered frames */
uint32_t round;
uint32_t appbufsz; /* client side bufsz */
uint32_t _reserved[1]; /* for future use */
} par;
struct amsg_cap {
uint32_t rate; /* native rate */
uint32_t _reserved2[1]; /* for future use */
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;
struct amsg_vol {
uint32_t ctl;
} vol;
struct amsg_hello {
#define AMSG_PLAY 0x1 /* audio playback */
#define AMSG_REC 0x2 /* audio recording */
#define AMSG_MIDIIN 0x4 /* MIDI thru input */
#define AMSG_MIDIOUT 0x8 /* MIDI thru output */
#define AMSG_MON 0x10 /* audio monitoring */
#define AMSG_RECMASK (AMSG_REC | AMSG_MON) /* can record ? */
uint16_t proto; /* protocol type */
#define AMSG_VERSION 3
uint8_t version; /* protocol version */
uint8_t reserved1[5]; /* for future use */
char opt[12]; /* profile name */
char who[12]; /* hint for leases */
} hello;
} 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 field was set.
*/
#define AMSG_ISSET(x) (((x) & (1 << (8 * sizeof(x) - 1))) == 0)
#endif /* !defined(AMSG_H) */

290
aucat/aparams.c Normal file
View File

@ -0,0 +1,290 @@
/* $OpenBSD: aparams.c,v 1.10 2010/01/10 21:47:41 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 "aparams.h"
#ifdef DEBUG
#include "dbg.h"
#endif
int aparams_ctltovol[128] = {
0,
256, 266, 276, 287, 299, 310, 323, 335,
348, 362, 376, 391, 406, 422, 439, 456,
474, 493, 512, 532, 553, 575, 597, 621,
645, 670, 697, 724, 753, 782, 813, 845,
878, 912, 948, 985, 1024, 1064, 1106, 1149,
1195, 1241, 1290, 1341, 1393, 1448, 1505, 1564,
1625, 1689, 1756, 1825, 1896, 1971, 2048, 2128,
2212, 2299, 2389, 2483, 2580, 2682, 2787, 2896,
3010, 3128, 3251, 3379, 3511, 3649, 3792, 3941,
4096, 4257, 4424, 4598, 4778, 4966, 5161, 5363,
5574, 5793, 6020, 6256, 6502, 6757, 7023, 7298,
7585, 7883, 8192, 8514, 8848, 9195, 9556, 9931,
10321, 10726, 11148, 11585, 12040, 12513, 13004, 13515,
14045, 14596, 15170, 15765, 16384, 17027, 17696, 18390,
19112, 19863, 20643, 21453, 22295, 23170, 24080, 25025,
26008, 27029, 28090, 29193, 30339, 31530, 32768
};
/*
* Fake parameters for byte-streams
*/
struct aparams aparams_none = { 1, 0, 0, 0, 0, 0, 0, 0 };
/*
* 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. Return 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 (optional) 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 (optional) 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 (optional) 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.
*/
void
aparams_init(struct aparams *par, unsigned cmin, unsigned cmax, unsigned rate)
{
par->bps = 2; /* 2 bytes per sample */
par->bits = 16; /* 16 significant bits per sample */
par->sig = 1; /* samples are signed */
par->le = NATIVE_LE;
par->msb = 1; /* msb justified */
par->cmin = cmin;
par->cmax = cmax;
par->rate = rate;
}
#ifdef DEBUG
/*
* Print the format/channels/encoding on stderr.
*/
void
aparams_dbg(struct aparams *par)
{
char enc[ENCMAX];
aparams_enctostr(par, enc);
dbg_puts(enc);
dbg_puts(",");
dbg_putu(par->cmin);
dbg_puts(":");
dbg_putu(par->cmax);
dbg_puts(",");
dbg_putu(par->rate);
}
#endif
/*
* Return true if both encodings are the same.
*/
int
aparams_eqenc(struct aparams *par1, struct aparams *par2)
{
if (par1->bps != par2->bps ||
par1->bits != par2->bits ||
par1->sig != par2->sig)
return 0;
if ((par1->bits != 8 * par1->bps) && par1->msb != par2->msb)
return 0;
if (par1->bps > 1 && par1->le != par2->le)
return 0;
return 1;
}
/*
* Return true if both parameters are the same.
*/
int
aparams_eq(struct aparams *par1, struct aparams *par2)
{
if (!aparams_eqenc(par1, par2) ||
par1->cmin != par2->cmin ||
par1->cmax != par2->cmax ||
par1->rate != par2->rate)
return 0;
return 1;
}
/*
* Return true if first channel range includes second range.
*/
int
aparams_subset(struct aparams *subset, struct aparams *set)
{
return subset->cmin >= set->cmin && subset->cmax <= set->cmax;
}
/*
* Grow channels range and sample rate of ``set'' in order ``subset'' to
* become an actual subset of it.
*/
void
aparams_grow(struct aparams *set, struct aparams *subset)
{
if (set->cmin > subset->cmin)
set->cmin = subset->cmin;
if (set->cmax < subset->cmax)
set->cmax = subset->cmax;
if (set->rate < subset->rate)
set->rate = subset->rate;
}
/*
* Return true if rates are the same.
*/
int
aparams_eqrate(struct aparams *p1, struct aparams *p2)
{
/* XXX: allow 1/9 halftone of difference */
return p1->rate == p2->rate;
}
/*
* Return the number of bytes per frame with the given parameters.
*/
unsigned
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;
}

90
aucat/aparams.h Normal file
View File

@ -0,0 +1,90 @@
/* $OpenBSD: aparams.h,v 1.8 2009/09/27 11:51:20 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 APARAMS_H
#define APARAMS_H
#include <sys/param.h>
#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
#define NATIVE_LE 0
#else
/* not defined */
#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 {
unsigned bps; /* bytes per sample */
unsigned bits; /* actually used bits */
unsigned le; /* 1 if little endian, 0 if big endian */
unsigned sig; /* 1 if signed, 0 if unsigned */
unsigned msb; /* 1 if msb justified, 0 if lsb justified */
unsigned cmin, cmax; /* provided/consumed channels */
unsigned rate; /* frames per second */
};
/*
* Samples are numbers in the interval [-1, 1[, note that 1, the upper
* boundary is excluded. We represent them in 16-bit signed fixed point
* numbers, so that we can do all multiplications and divisions in
* 32-bit precision without having to deal with overflows.
*/
#define ADATA_SHIFT (8 * sizeof(short) - 1)
#define ADATA_UNIT (1 << ADATA_SHIFT)
#define ADATA_MAX (ADATA_UNIT - 1)
#define ADATA_MUL(x,y) (((x) * (y)) >> ADATA_SHIFT)
#define MIDI_MAXCTL 127
#define MIDI_TO_ADATA(m) (aparams_ctltovol[m])
extern int aparams_ctltovol[128];
extern struct aparams aparams_none;
void aparams_init(struct aparams *, unsigned, unsigned, unsigned);
void aparams_dbg(struct aparams *);
int aparams_eqrate(struct aparams *, struct aparams *);
int aparams_eqenc(struct aparams *, struct aparams *);
int aparams_eq(struct aparams *, struct aparams *);
int aparams_subset(struct aparams *, struct aparams *);
void aparams_grow(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) */

2329
aucat/aproc.c Normal file

File diff suppressed because it is too large Load Diff

274
aucat/aproc.h Normal file
View File

@ -0,0 +1,274 @@
/* $OpenBSD: aproc.h,v 1.37 2010/06/04 06:15:28 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 APROC_H
#define APROC_H
#include <sys/queue.h>
#include "aparams.h"
#include "file.h"
struct abuf;
struct aproc;
struct file;
struct aproc_ops {
/*
* Name of the ops structure, ie type of the unit.
*/
char *name;
/*
* The state of the given input abuf changed (eg. an input block
* is ready for processing). This function must get the block
* from the input, process it and remove it from the buffer.
*
* Processing the block will result in a change of the state of
* OTHER buffers that are attached to the aproc (eg. the output
* buffer was filled), thus this routine MUST notify ALL aproc
* structures that are waiting on it; most of the time this
* means just calling abuf_flush() on the output buffer.
*/
int (*in)(struct aproc *, struct abuf *);
/*
* The state of the given output abuf changed (eg. space for a
* new output block was made available) so processing can
* continue. This function must process more input in order to
* fill the output block.
*
* Producing a block will result in the change of the state of
* OTHER buffers that are attached to the aproc, thus this
* routine MUST notify ALL aproc structures that are waiting on
* it; most of the time this means calling abuf_fill() on the
* source buffers.
*
* Before filling input buffers (using abuf_fill()), this
* routine must ALWAYS check for eof condition, and if needed,
* handle it appropriately and call abuf_hup() to free the input
* buffer.
*/
int (*out)(struct aproc *, struct abuf *);
/*
* The input buffer is empty and we can no more receive data
* from it. The buffer will be destroyed as soon as this call
* returns so the abuf pointer will stop being valid after this
* call returns. There's no need to drain the buffer because the
* in() call-back was just called before.
*
* If this call reads and/or writes data on other buffers,
* abuf_flush() and abuf_fill() must be called appropriately.
*/
void (*eof)(struct aproc *, struct abuf *);
/*
* The output buffer can no more accept data (it should be
* considered as full). After this function returns, it will be
* destroyed and the "abuf" pointer will be no more valid.
*/
void (*hup)(struct aproc *, struct abuf *);
/*
* A new input was connected.
*/
void (*newin)(struct aproc *, struct abuf *);
/*
* A new output was connected
*/
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.
*/
void (*done)(struct aproc *);
};
/*
* The aproc structure represents a simple audio processing unit; they are
* interconnected by abuf structures and form a kind of "circuit". The circuit
* cannot have loops.
*/
struct aproc {
char *name; /* for debug purposes */
struct aproc_ops *ops; /* call-backs */
LIST_HEAD(, abuf) ins; /* list of inputs */
LIST_HEAD(, abuf) outs; /* list of outputs */
unsigned refs; /* extern references */
#define APROC_ZOMB 1 /* destroyed but not freed */
#define APROC_QUIT 2 /* try to terminate if unused */
#define APROC_DROP 4 /* xrun if capable */
unsigned flags;
union { /* follow type-specific data */
struct { /* file/device io */
struct file *file; /* file to read/write */
unsigned partial; /* bytes of partial frame */
} io;
struct {
unsigned idle; /* frames since idleing */
unsigned round; /* block size, for xruns */
int lat; /* current latency */
int maxlat; /* max latency allowed */
unsigned abspos; /* frames produced */
struct aproc *ctl; /* MIDI control/sync */
struct aproc *mon; /* snoop output */
} mix;
struct {
unsigned idle; /* frames since idleing */
unsigned round; /* block size, for xruns */
int lat; /* current latency */
int maxlat; /* max latency allowed */
unsigned abspos; /* frames consumed */
struct aproc *ctl;
} sub;
struct {
int delta; /* time position */
unsigned bufsz; /* buffer size (latency) */
unsigned pending; /* uncommited samples */
} mon;
struct {
#define RESAMP_NCTX 2
unsigned ctx_start;
short ctx[NCHAN_MAX * RESAMP_NCTX];
unsigned iblksz, oblksz;
int diff;
int idelta, odelta; /* remainder of resamp_xpos */
} resamp;
struct {
int bfirst; /* bytes to skip at startup */
unsigned bps; /* bytes per sample */
unsigned shift; /* shift to get 32bit MSB */
int sigbit; /* sign bits to XOR */
int bnext; /* to reach the next byte */
int snext; /* to reach the next sample */
} conv;
struct {
struct abuf *owner; /* current input stream */
struct timo timo; /* timout for throtteling */
} thru;
struct {
struct dev *dev; /* controlled device */
#define CTL_NSLOT 8
#define CTL_NAMEMAX 8
unsigned serial;
#define CTL_OFF 0 /* ignore MMC messages */
#define CTL_STOP 1 /* stopped, can't start */
#define CTL_START 2 /* attempting to start */
#define CTL_RUN 3 /* started */
unsigned tstate;
unsigned origin; /* MTC start time */
unsigned fps; /* MTC frames per second */
#define MTC_FPS_24 0
#define MTC_FPS_25 1
#define MTC_FPS_30 3
unsigned fps_id; /* one of above */
unsigned hr; /* MTC hours */
unsigned min; /* MTC minutes */
unsigned sec; /* MTC seconds */
unsigned fr; /* MTC frames */
unsigned qfr; /* MTC quarter frames */
int delta; /* rel. to the last MTC tick */
struct ctl_slot {
struct ctl_ops {
void (*vol)(void *, unsigned);
void (*start)(void *);
void (*stop)(void *);
void (*loc)(void *, unsigned);
void (*quit)(void *);
} *ops;
void *arg;
unsigned unit;
char name[CTL_NAMEMAX];
unsigned serial;
unsigned vol;
unsigned tstate;
} slot[CTL_NSLOT];
} ctl;
} u;
};
/*
* Check if the given pointer is a valid aproc structure.
*
* aproc structures are not free()'d immediately, because
* there may be pointers to them, instead the APROC_ZOMB flag
* is set which means that they should not be used. When
* aprocs reference counter reaches zero, they are actually
* freed
*/
#define APROC_OK(p) ((p) && !((p)->flags & APROC_ZOMB))
struct aproc *aproc_new(struct aproc_ops *, char *);
void aproc_del(struct aproc *);
void aproc_dbg(struct aproc *);
void aproc_setin(struct aproc *, struct abuf *);
void aproc_setout(struct aproc *, struct abuf *);
int aproc_depend(struct aproc *, struct aproc *);
void aproc_ipos(struct aproc *, struct abuf *, int);
void aproc_opos(struct aproc *, struct abuf *, int);
struct aproc *rfile_new(struct file *);
struct aproc *wfile_new(struct file *);
struct aproc *mix_new(char *, int, unsigned);
struct aproc *sub_new(char *, int, unsigned);
struct aproc *resamp_new(char *, unsigned, unsigned);
struct aproc *enc_new(char *, struct aparams *);
struct aproc *dec_new(char *, struct aparams *);
struct aproc *join_new(char *);
struct aproc *mon_new(char *, unsigned);
int rfile_in(struct aproc *, struct abuf *);
int rfile_out(struct aproc *, struct abuf *);
void rfile_eof(struct aproc *, struct abuf *);
void rfile_hup(struct aproc *, struct abuf *);
void rfile_done(struct aproc *);
int rfile_do(struct aproc *, unsigned, unsigned *);
int wfile_in(struct aproc *, struct abuf *);
int wfile_out(struct aproc *, struct abuf *);
void wfile_eof(struct aproc *, struct abuf *);
void wfile_hup(struct aproc *, struct abuf *);
void wfile_done(struct aproc *);
int wfile_do(struct aproc *, unsigned, unsigned *);
void mix_setmaster(struct aproc *);
void mix_clear(struct aproc *);
void mix_prime(struct aproc *);
void mix_quit(struct aproc *);
void mix_drop(struct abuf *, int);
void sub_silence(struct abuf *, int);
void sub_clear(struct aproc *);
void mon_snoop(struct aproc *, struct abuf *, unsigned, unsigned);
void mon_clear(struct aproc *);
#endif /* !defined(APROC_H) */

633
aucat/aucat.1 Normal file
View File

@ -0,0 +1,633 @@
.\" $OpenBSD: aucat.1,v 1.73 2010/07/31 08:48:01 ratchov Exp $
.\"
.\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: July 31 2010 $
.Dt AUCAT 1
.Os
.Sh NAME
.Nm aucat
.Nd audio server and stream manipulation tool
.Sh SYNOPSIS
.Nm aucat
.Bk -words
.Op Fl dlnu
.Op Fl a Ar flag
.Op Fl b Ar nframes
.Op Fl C Ar min : Ns Ar max
.Op Fl c Ar min : Ns Ar max
.Op Fl e Ar enc
.Op Fl f Ar device
.Op Fl h Ar fmt
.Op Fl i Ar file
.Op Fl j Ar flag
.Op Fl m Ar mode
.Op Fl o Ar file
.Op Fl q Ar device
.Op Fl r Ar rate
.Op Fl s Ar name
.Op Fl t Ar mode
.Op Fl U Ar unit
.Op Fl v Ar volume
.Op Fl x Ar policy
.Op Fl z Ar nframes
.Ek
.Sh DESCRIPTION
.Nm
is an audio utility which can simultaneously play and record
any number of audio
.Em streams
on any number of audio devices,
possibly controlled through MIDI.
It can also act as an audio server, in which case streams
correspond to client connections rather than plain files.
.Pp
Audio devices are independent.
A list of streams is attached to each audio device,
as well as an optional list of MIDI ports to control the device.
A typical invocation of
.Nm
consists in providing streams to play and record,
and possibly the audio device to use, if the default is not desired.
.Pp
This also applies to server mode, except that streams are created
dynamically when clients connect to the server.
Thus, instead of actual streams (paths to plain files),
templates for client streams (sub-device names) must be provided.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl a Ar flag
Control whether
.Nm
opens the audio device only when needed or keeps it open all the time.
If the flag is
.Va on
then the device is kept open all the time, ensuring no other program can
steal it.
If the flag is
.Va off ,
then it's automatically closed, allowing other programs to have direct
access to the device, or the device to be disconnected.
The default is
.Va on .
.It Fl b Ar nframes
The buffer size of the audio device in frames.
A frame consists of one sample for each channel in the stream.
This is the number of frames that will be buffered before being played
and thus controls the playback latency.
.It Xo
.Fl C Ar min : Ns Ar max ,
.Fl c Ar min : Ns Ar max
.Xc
The range of stream channel numbers for recording and playback directions,
respectively.
The default is 0:1, i.e. stereo.
.It Fl d
Increase log verbosity.
.Nm
logs on stderr until it daemonizes.
.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
Add this
.Xr sndio 7
audio device to devices used for playing and/or recording.
Preceding streams
.Pq Fl ios ,
control MIDI ports
.Pq Fl q ,
and per-device options
.Pq Fl abz
apply to this device.
Device mode and parameters are determined from streams
attached to it.
.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 streams to play.
If the option argument is
.Sq -
then standard input will be used.
.It Fl j Ar flag
Control whether stream channels are joined or expanded if
the stream number of channels is not equal to the device number of channels.
If the flag is
.Va off
then stream channels are routed to the corresponding
device channel, possibly discarding channels not present in the device.
If the flag is
.Va on ,
then a single stream channel may be sent on multiple device channels,
or multiple stream channels may be sent to a single device channel.
For instance, this feature could be used to request mono streams to
be sent on multiple outputs or to record a stereo input into a mono stream.
The default is
.Ar on .
.It Fl l
Detach and become a daemon.
.It Fl m Ar mode
Set the stream mode.
Valid modes are
.Ar play ,
.Ar rec ,
and
.Ar mon ,
corresponding to playback, recording and monitoring.
A monitoring stream is a fake recording stream corresponding to
the mix of all playback streams.
Multiple modes can be specified, separated by commas,
but the same stream cannot be used for both recording and monitoring.
The default is
.Ar play , Ns Ar rec
(i.e. full-duplex).
.It Fl n
Loopback mode.
Instead of using audio devices, send input streams
to the output, processing them on the fly.
This mode is useful to mix, demultiplex, resample or reencode
audio files offline.
.It Fl o Ar file
Add this file to the list of recording streams.
If the option argument is
.Sq -
then standard output will be used.
.It Fl q Ar device
Expose the audio device clock on this
.Xr sndio 7
MIDI port and allow audio device properties to be controlled
through MIDI.
This includes per-stream volumes and the ability to
synchronously start, stop and relocate streams created in
MIDI Machine
Control (MMC) slave mode
.Pq Fl t .
.It Fl r Ar rate
Sample rate in Hertz of the stream.
The default is 44100Hz.
.It Fl s Ar name
Add
.Ar name
to the list of sub-devices to expose in server mode.
This allows clients to use
.Nm
instead of the physical audio device for audio input and output
in order to share the physical device with other clients.
Defining multiple sub-devices allows splitting a physical audio device
into logical devices having different properties (e.g. channel ranges).
The given
.Ar name
corresponds to the
.Dq option
part of the
.Xr sndio 7
device name string.
.It Fl t Ar mode
Select the way streams are controlled by MIDI Machine Control (MMC)
messages.
If the mode is
.Va off
(the default), then streams are not affected by MMC messages.
If the mode is
.Va slave ,
then streams are started synchronously by MMC start messages;
additionally, the server clock is exposed as MIDI Time Code (MTC)
messages allowing MTC-capable software or hardware to be synchronized
to audio streams.
.It Fl U Ar unit
Unit number to use when running in server mode.
Each
.Nm
server instance has an unique unit number,
used in
.Xr sndio 7
device names.
The default is 0.
.It Fl u
Normally
.Nm
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 Ccer
options.
.It Fl v Ar volume
Software volume attenuation of the playback stream.
The value must be between 1 and 127,
corresponding to \-42dB and \-0dB attenuation.
In server mode, clients inherit this parameter.
Reducing the volume in advance reduces a client's dynamic range,
but allows client volume to stay independent from the number
of clients as long as their number is small enough.
A good compromise is to use \-4dB attenuation (12 volume units)
for each additional client expected
(115 if 2 clients are expected, 103 for 3 clients, and so on).
.It Fl x Ar policy
Action when the output stream cannot accept
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
and samples that cannot be read are replaced by silence.
If the policy is
.Dq sync
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.
.Pp
If a stream is created with the
.Fl t
option,
the
.Dq ignore
action is disabled for any stream connected to it
to ensure proper synchronization.
.It Fl z Ar nframes
The audio device block size in frames.
This is the number of frames between audio clock ticks,
i.e. the clock resolution.
If a stream is created with the
.Fl t
option,
and MTC is used for synchronization, the clock
resolution must be 96, 100 or 120 ticks per second for maximum
accuracy.
For instance, 120 ticks per second at 48000Hz corresponds
to a 400 frame block size.
.El
.Pp
On the command line,
per-device parameters
.Pq Fl abz
must precede the device definition
.Pq Fl f ,
and per-stream parameters
.Pq Fl Ccehjmrtvx
must precede the stream definition
.Pq Fl ios .
MIDI ports
.Pq Fl q
and streams definitions
.Pq Fl ios
must precede the definition of the device
.Pq Fl f
to which they are attached.
Global parameters
.Pq Fl dlnu
are position-independent.
.Pp
If no audio devices
.Pq Fl f
are specified,
settings are applied as if
the default device is specified as the last argument.
If no streams
.Pq Fl ios
are specified for a device, a default server sub-device is
created attached to it, meaning that
.Nm
behaves as an audio server.
The default
.Xr sndio 7
device is
.Pa aucat:0
.Pq also known as Pa aucat:0.default
.Pp
If
.Nm
is sent
.Dv SIGHUP ,
.Dv SIGINT
or
.Dv SIGTERM ,
it terminates recording to files.
.Pp
File formats are specified using the
.Fl h
option.
The following file formats are supported:
.Bl -tag -width s32lexxx -offset indent
.It raw
Headerless file.
This format is recommended since it has no limitations.
.It wav
Microsoft WAVE file format.
There are limitations inherent to the file format itself:
not all encodings are supported,
file sizes are limited to 2GB,
and the file must support the
.Xr lseek 2
operation (e.g. pipes do not support it).
.It auto
Try to guess, depending on the file name.
.El
.Pp
Encodings are specified using the
.Fl e
option.
The following encodings are supported:
.Pp
.Bl -tag -width s32lexxx -offset indent -compact
.It s8
signed 8-bit
.It u8
unsigned 8-bit
.It s16le
signed 16-bit, little endian
.It u16le
unsigned 16-bit, little endian
.It s16be
signed 16-bit, big endian
.It u16be
unsigned 16-bit, big endian
.It s24le
signed 24-bit, stored in 4 bytes, little endian
.It u24le
unsigned 24-bit, stored in 4 bytes, little endian
.It s24be
signed 24-bit, stored in 4 bytes, big endian
.It u24be
unsigned 24-bit, stored in 4 bytes, big endian
.It s32le
signed 32-bit, little endian
.It u32le
unsigned 32-bit, little endian
.It s32be
signed 32-bit, big endian
.It u32be
unsigned 32-bit, big endian
.It s24le3
signed 24-bit, packed in 3 bytes, little endian
.It u24le3
unsigned 24-bit, packed in 3 bytes, big endian
.It s24be3
signed 24-bit, packed in 3 bytes, little endian
.It u24be3
unsigned 24-bit, packed in 3 bytes, big endian
.It s20le3
signed 20-bit, packed in 3 bytes, little endian
.It u20le3
unsigned 20-bit, packed in 3 bytes, big endian
.It s20be3
signed 20-bit, packed in 3 bytes, little endian
.It u20be3
unsigned 20-bit, packed in 3 bytes, big endian
.It s18le3
signed 18-bit, packed in 3 bytes, little endian
.It u18le3
unsigned 18-bit, packed in 3 bytes, big endian
.It s18be3
signed 18-bit, packed in 3 bytes, little endian
.It u18be3
unsigned 18-bit, packed in 3 bytes, big endian
.El
.Sh SERVER MODE
If at least one sub-device
.Pq Fl s
is exposed by
.Nm ,
including the case when no stream options are given,
then
.Nm
can be used as a server
to overcome hardware limitations and allow applications
to run on fixed sample rate devices or on devices
supporting only unusual encodings.
.Pp
Certain applications, such as synthesis software,
require a low latency audio setup.
To reduce the probability of buffer underruns or overruns, especially
on busy machines, the server can be started by the super-user, in which
case it will run with higher priority.
Any user will still be able to connect to it,
but for privacy reasons only one user may have
connections to it at a given time.
.Sh MIDI CONTROL
.Nm
can expose the audio device clock on registered
MIDI ports
.Pq Fl q
and allows audio device properties to be controlled
through MIDI.
If running in server mode
.Nm
creates a MIDI port with the same name as the default audio
device to which MIDI programs can connect.
.Pp
A MIDI channel is assigned to each stream, and the volume
is changed using the standard volume controller (number 7).
Similarly, when the audio client changes its volume,
the same MIDI controller message is sent out; it can be used
for instance for monitoring or as feedback for motorized
faders.
.Pp
Streams created with the
.Fl t
option are controlled by the following MMC messages:
.Bl -tag -width relocateXXX -offset indent
.It relocate
Streams are relocated to the requested time postion
relative to the beginning of the stream, at which playback
and recording must start.
If the requested position is beyond the end of file,
the stream is temporarly disabled until a valid postion is requested.
This message is ignored by client streams (server mode).
The given time position is sent to MIDI ports as an MTC
.Dq "full frame"
message forcing all MTC-slaves to relocate to the given
position (see below).
.It start
Put all streams in starting mode.
In this mode,
.Nm
waits for all streams to become ready
to start, and then starts them synchronously.
Once started, new streams can be created (server mode)
but they will be blocked
until the next stop-to-start transition.
.It stop
Put all streams in stopped mode (the default).
In this mode, any stream attempting to start playback or recording
is paused.
Files are stopped and rewound back to the starting position,
while client streams (server mode) that are already
started are not affected until they stop and try to start again.
.El
.Pp
Streams created with the
.Fl t
option export the server clock using MTC, allowing non-audio
software or hardware to be synchronized to the audio stream.
The following sample rates
.Pq Fl r
and block sizes
.Pq Fl z
are recommended for maximum accuracy:
.Pp
.Bl -bullet -offset indent -compact
.It
44100Hz, 441 frames
.It
48000Hz, 400 frames
.It
48000Hz, 480 frames
.It
48000Hz, 500 frames
.El
.Pp
For instance, the following command will create two devices:
the default
.Va aucat:0
and a MIDI-controlled
.Va aucat:0.mmc :
.Bd -literal -offset indent
$ aucat -l -r 48000 -z 400 -s default -t slave -s mmc
.Ed
.Pp
Streams connected to
.Va aucat:0
behave normally, while streams connected to
.Va aucat:0.mmc
wait for the MMC start signal and start synchronously.
Regardless of which device a stream is connected to,
its playback volume knob is exposed.
.Pp
For instance, the following command will play a file on the
.Va aucat:0.mmc
audio device, and give full control to MIDI software or hardware
connected to the
.Va midithru:0
MIDI device:
.Bd -literal -offset indent
$ aucat -t slave -q midithru:0 -i file.wav -f aucat:0.mmc
.Ed
.Pp
At this stage,
.Nm
will start, stop and relocate automatically following all user
actions in the MIDI sequencer.
Note that the sequencer must use
.Va aucat:0
as the MTC source, i.e. the audio server, not the audio player.
.Sh ENVIRONMENT
.Bl -tag -width "AUDIODEVICE" -compact
.It Ev AUDIODEVICE
.Xr sndio 7
audio device to use if the
.Fl f
option is not specified.
.El
.Sh EXAMPLES
The following will mix and play two stereo streams,
the first at 48kHz and the second at 44.1kHz:
.Bd -literal -offset indent
$ aucat -r 48000 -i file1.raw -r 44100 -i file2.raw
.Ed
.Pp
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
.Ed
.Pp
The following will split a stereo file into two mono files:
.Bd -literal -offset indent
$ aucat -n -i stereo.wav -C 0:0 -o left.wav -C 1:1 -o right.wav
.Ed
.Pp
The following will start
.Nm
in server mode using default parameters, but will create an
additional sub-device for output to channels 2:3 only (rear speakers
on most cards), exposing the
.Pa aucat:0
and
.Pa aucat:0.rear
devices:
.Bd -literal -offset indent
$ aucat -l -s default -c 2:3 -s rear
.Ed
.Pp
The following will start
.Nm
in server mode creating the default sub-device with low volume and
an additional sub-device for high volume output, exposing the
.Pa aucat:0
and
.Pa aucat:0.max
devices:
.Bd -literal -offset indent
$ aucat -l -v 65 -s default -v 127 -s max
.Ed
.Pp
The following will start
.Nm
in server mode configuring the audio device to use
a 48kHz sample frequency, 240-frame block size,
and 2-block buffers.
The corresponding latency is 10ms, which is
the time it takes the sound to propagate 3.5 meters.
.Bd -literal -offset indent
$ aucat -l -r 48000 -b 480 -z 240
.Ed
.Sh SEE ALSO
.Xr audioctl 1 ,
.Xr cdio 1 ,
.Xr mixerctl 1 ,
.Xr audio 4 ,
.Xr sndio 7
.Sh BUGS
The
.Nm
utility assumes non-blocking I/O for input and output streams.
It will not work reliably on files that may block
(ordinary files block, pipes don't).
To avoid audio underruns/overruns or MIDI jitter caused by file I/O,
it's recommended to use two
.Nm
processes: a server handling audio and MIDI I/O and a client handling
disk I/O.
.Pp
Resampling is low quality; down-sampling especially should be avoided
when recording.
.Pp
Processing is done using 16-bit arithmetic,
thus samples with more than 16 bits are rounded.
16 bits (i.e. 97dB dynamic) are largely enough for most applications though.
.Pp
If
.Fl a Ar off
is used in server mode,
.Nm
creates sub-devices to expose first
and then opens the audio hardware on demand.
Technically, this allows
.Nm
to attempt to use one of the sub-devices it exposes as audio device,
creating a deadlock.
To avoid this,
.Fl a Ar off
is disabled for the default audio device, but nothing prevents the user
from shooting himself in the foot by creating a similar deadlock.

1100
aucat/aucat.c Normal file

File diff suppressed because it is too large Load Diff

50
aucat/conf.h Normal file
View File

@ -0,0 +1,50 @@
/* $OpenBSD: conf.h,v 1.15 2010/04/06 20:07:01 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 CONF_H
#define CONF_H
#ifdef DEBUG
/*
* Debug trace levels:
*
* 0 - fatal errors: bugs, asserts, internal errors.
* 1 - warnings: bugs in clients, failed allocations, non-fatal errors.
* 2 - misc information (hardware parameters, incoming clients)
* 3 - structural changes (new aproc structures and files stream params changes)
* 4 - data blocks and messages
*/
extern int debug_level;
#endif
/*
* socket and option names
*/
#define DEFAULT_MIDITHRU "midithru"
#define DEFAULT_SOFTAUDIO "softaudio"
#define DEFAULT_OPT "default"
/*
* MIDI buffer size
*/
#define MIDI_BUFSZ 3125 /* 1 second at 31.25kbit/s */
/*
* units used for MTC clock.
*/
#define MTC_SEC 2400 /* 1 second is 2400 ticks */
#endif /* !defined(CONF_H) */

153
aucat/dbg.c Normal file
View File

@ -0,0 +1,153 @@
#ifdef DEBUG
/*
* Copyright (c) 2003-2007 Alexandre Ratchov <alex@caoua.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* dbg_xxx() routines are used to quickly store traces into a trace buffer.
* This allows trances to be collected during time sensitive operations without
* disturbing them. The buffer can be flushed on standard error later, when
* slow syscalls are no longer disruptive, e.g. at the end of the poll() loop.
*/
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include "dbg.h"
/*
* size of the buffer where traces are stored
*/
#define DBG_BUFSZ 8192
/*
* store a character in the trace buffer
*/
#define DBG_PUTC(c) do { \
if (dbg_used < DBG_BUFSZ) \
dbg_buf[dbg_used++] = (c); \
} while (0)
char dbg_buf[DBG_BUFSZ]; /* buffer where traces are stored */
unsigned dbg_used = 0; /* bytes used in the buffer */
unsigned dbg_sync = 1; /* if true, flush after each '\n' */
/*
* write debug info buffer on stderr
*/
void
dbg_flush(void)
{
if (dbg_used == 0)
return;
write(STDERR_FILENO, dbg_buf, dbg_used);
dbg_used = 0;
}
/*
* store a string in the debug buffer
*/
void
dbg_puts(char *msg)
{
char *p = msg;
int c;
while ((c = *p++) != '\0') {
DBG_PUTC(c);
if (dbg_sync && c == '\n')
dbg_flush();
}
}
/*
* store a hex in the debug buffer
*/
void
dbg_putx(unsigned long num)
{
char dig[sizeof(num) * 2], *p = dig, c;
unsigned ndig;
if (num != 0) {
for (ndig = 0; num != 0; ndig++) {
*p++ = num & 0xf;
num >>= 4;
}
for (; ndig != 0; ndig--) {
c = *(--p);
c += (c < 10) ? '0' : 'a' - 10;
DBG_PUTC(c);
}
} else
DBG_PUTC('0');
}
/*
* store a decimal in the debug buffer
*/
void
dbg_putu(unsigned long num)
{
char dig[sizeof(num) * 3], *p = dig;
unsigned ndig;
if (num != 0) {
for (ndig = 0; num != 0; ndig++) {
*p++ = num % 10;
num /= 10;
}
for (; ndig != 0; ndig--)
DBG_PUTC(*(--p) + '0');
} else
DBG_PUTC('0');
}
/*
* store a signed integer in the trace buffer
*/
void
dbg_puti(long num)
{
if (num < 0) {
DBG_PUTC('-');
num = -num;
}
dbg_putu(num);
}
/*
* abort program execution after a fatal error, we should
* put code here to backup user data
*/
void
dbg_panic(void)
{
dbg_flush();
abort();
}
#endif

45
aucat/dbg.h Normal file
View File

@ -0,0 +1,45 @@
#ifdef DEBUG
/*
* Copyright (c) 2003-2007 Alexandre Ratchov <alex@caoua.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MIDISH_DBG_H
#define MIDISH_DBG_H
void dbg_puts(char *);
void dbg_putx(unsigned long);
void dbg_putu(unsigned long);
void dbg_puti(long);
void dbg_panic(void);
void dbg_flush(void);
extern unsigned dbg_sync;
#endif /* MIDISH_DBG_H */
#endif /* DEBUG */

1168
aucat/dev.c Normal file

File diff suppressed because it is too large Load Diff

78
aucat/dev.h Normal file
View File

@ -0,0 +1,78 @@
/* $OpenBSD: dev.h,v 1.27 2010/07/06 01:12:45 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 DEV_H
#define DEV_H
#include "aparams.h"
struct aproc;
struct abuf;
struct dev {
struct dev *next;
/*
* desired parameters
*/
unsigned reqmode; /* mode */
struct aparams reqipar, reqopar; /* parameters */
unsigned reqbufsz; /* buffer size */
unsigned reqround; /* block size */
unsigned reqrate; /* sample rate */
unsigned hold; /* hold the device open ? */
unsigned refcnt; /* number of openers */
#define DEV_CLOSED 0 /* closed */
#define DEV_INIT 1 /* stopped */
#define DEV_START 2 /* ready to start */
#define DEV_RUN 3 /* started */
unsigned pstate; /* on of DEV_xxx */
char *path; /* sio path */
/*
* actual parameters and runtime state (i.e. once opened)
*/
unsigned mode; /* bitmap of MODE_xxx */
unsigned bufsz, round, rate;
struct aparams ipar, opar;
struct aproc *mix, *sub, *submon;
struct aproc *rec, *play, *mon;
struct aproc *midi;
};
extern struct dev *dev_list;
int dev_run(struct dev *);
int dev_ref(struct dev *);
void dev_unref(struct dev *);
void dev_del(struct dev *);
void dev_wakeup(struct dev *);
void dev_drain(struct dev *);
struct dev *dev_new_thru(void);
struct dev *dev_new_loop(struct aparams *, struct aparams *, unsigned);
struct dev *dev_new_sio(char *, unsigned,
struct aparams *, struct aparams *, unsigned, unsigned, unsigned);
int dev_thruadd(struct dev *, char *, int, int);
void dev_midiattach(struct dev *, struct abuf *, struct abuf *);
unsigned dev_roundof(struct dev *, unsigned);
int dev_getpos(struct dev *);
void dev_attach(struct dev *, char *, unsigned,
struct abuf *, struct aparams *, unsigned,
struct abuf *, struct aparams *, unsigned,
unsigned, int);
void dev_setvol(struct dev *, struct abuf *, int);
#endif /* !define(DEV_H) */

743
aucat/file.c Normal file
View File

@ -0,0 +1,743 @@
/* $OpenBSD: file.c,v 1.21 2010/07/10 12:32:45 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.
*/
/*
* non-blocking file i/o module: each file can be read or written (or
* both). To achieve non-blocking io, we simply use the poll() syscall
* in an event loop. If a read() or write() syscall return EAGAIN
* (operation will block), then the file is marked as "for polling", else
* the file is not polled again.
*
* the module also provides trivial timeout implementation,
* derived from:
*
* anoncvs@moule.caoua.org:/cvs
*
* midish/timo.c rev 1.16
* midish/mdep.c rev 1.69
*
* A timeout is used to schedule the call of a routine (the callback)
* there is a global list of timeouts that is processed inside the
* event loop. Timeouts work as follows:
*
* first the timo structure must be initialized with timo_set()
*
* then the timeout is scheduled (only once) with timo_add()
*
* if the timeout expires, the call-back is called; then it can
* be scheduled again if needed. It's OK to reschedule it again
* from the callback
*
* the timeout can be aborted with timo_del(), it is OK to try to
* abort a timout that has expired
*
*/
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include "abuf.h"
#include "aproc.h"
#include "conf.h"
#include "file.h"
#ifdef DEBUG
#include "dbg.h"
#endif
#define MAXFDS 100
#define TIMER_USEC 10000
struct timespec file_ts;
struct filelist file_list;
struct timo *timo_queue;
unsigned timo_abstime;
/*
* initialise a timeout structure, arguments are callback and argument
* that will be passed to the callback
*/
void
timo_set(struct timo *o, void (*cb)(void *), void *arg)
{
o->cb = cb;
o->arg = arg;
o->set = 0;
}
/*
* schedule the callback in 'delta' 24-th of microseconds. The timeout
* must not be already scheduled
*/
void
timo_add(struct timo *o, unsigned delta)
{
struct timo **i;
unsigned val;
int diff;
#ifdef DEBUG
if (o->set) {
dbg_puts("timo_add: already set\n");
dbg_panic();
}
if (delta == 0) {
dbg_puts("timo_add: zero timeout is evil\n");
dbg_panic();
}
#endif
val = timo_abstime + delta;
for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
diff = (*i)->val - val;
if (diff > 0) {
break;
}
}
o->set = 1;
o->val = val;
o->next = *i;
*i = o;
}
/*
* abort a scheduled timeout
*/
void
timo_del(struct timo *o)
{
struct timo **i;
for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
if (*i == o) {
*i = o->next;
o->set = 0;
return;
}
}
#ifdef DEBUG
if (debug_level >= 4)
dbg_puts("timo_del: not found\n");
#endif
}
/*
* routine to be called by the timer when 'delta' 24-th of microsecond
* elapsed. This routine updates time referece used by timeouts and
* calls expired timeouts
*/
void
timo_update(unsigned delta)
{
struct timo *to;
int diff;
/*
* update time reference
*/
timo_abstime += delta;
/*
* remove from the queue and run expired timeouts
*/
while (timo_queue != NULL) {
/*
* there is no overflow here because + and - are
* modulo 2^32, they are the same for both signed and
* unsigned integers
*/
diff = timo_queue->val - timo_abstime;
if (diff > 0)
break;
to = timo_queue;
timo_queue = to->next;
to->set = 0;
to->cb(to->arg);
}
}
/*
* initialize timeout queue
*/
void
timo_init(void)
{
timo_queue = NULL;
timo_abstime = 0;
}
/*
* destroy timeout queue
*/
void
timo_done(void)
{
#ifdef DEBUG
if (timo_queue != NULL) {
dbg_puts("timo_done: timo_queue not empty!\n");
dbg_panic();
}
#endif
timo_queue = (struct timo *)0xdeadbeef;
}
#ifdef DEBUG
void
file_dbg(struct file *f)
{
dbg_puts(f->ops->name);
dbg_puts("(");
dbg_puts(f->name);
dbg_puts("|");
if (f->state & FILE_ROK)
dbg_puts("r");
if (f->state & FILE_RINUSE)
dbg_puts("R");
if (f->state & FILE_WOK)
dbg_puts("w");
if (f->state & FILE_WINUSE)
dbg_puts("W");
if (f->state & FILE_EOF)
dbg_puts("e");
if (f->state & FILE_HUP)
dbg_puts("h");
if (f->state & FILE_ZOMB)
dbg_puts("Z");
dbg_puts(")");
}
#endif
struct file *
file_new(struct fileops *ops, char *name, unsigned nfds)
{
struct file *f;
LIST_FOREACH(f, &file_list, entry)
nfds += f->ops->nfds(f);
if (nfds > MAXFDS) {
#ifdef DEBUG
if (debug_level >= 1) {
dbg_puts(name);
dbg_puts(": too many polled files\n");
}
#endif
return NULL;
}
f = malloc(ops->size);
if (f == NULL)
err(1, "file_new: %s", ops->name);
f->ops = ops;
f->name = name;
f->state = 0;
f->cycles = 0;
f->rproc = NULL;
f->wproc = NULL;
LIST_INSERT_HEAD(&file_list, f, entry);
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": created\n");
}
#endif
return f;
}
void
file_del(struct file *f)
{
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": terminating...\n");
}
#endif
if (f->state & (FILE_RINUSE | FILE_WINUSE)) {
f->state |= FILE_ZOMB;
} else {
LIST_REMOVE(f, entry);
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": destroyed\n");
}
#endif
f->ops->close(f);
free(f);
}
}
int
file_poll(void)
{
nfds_t nfds, n;
short events, revents;
struct pollfd pfds[MAXFDS];
struct file *f, *fnext;
struct aproc *p;
struct timespec ts;
long delta_nsec;
if (LIST_EMPTY(&file_list)) {
#ifdef DEBUG
if (debug_level >= 3)
dbg_puts("nothing to do...\n");
#endif
return 0;
}
/*
* Fill the pfds[] array with files that are blocked on reading
* and/or writing, skipping those that are just waiting.
*/
#ifdef DEBUG
dbg_flush();
if (debug_level >= 4)
dbg_puts("poll:");
#endif
nfds = 0;
LIST_FOREACH(f, &file_list, entry) {
events = 0;
if (f->rproc && !(f->state & FILE_ROK))
events |= POLLIN;
if (f->wproc && !(f->state & FILE_WOK))
events |= POLLOUT;
#ifdef DEBUG
if (debug_level >= 4) {
dbg_puts(" ");
file_dbg(f);
}
#endif
n = f->ops->pollfd(f, pfds + nfds, events);
if (n == 0) {
f->pfd = NULL;
continue;
}
f->pfd = pfds + nfds;
nfds += n;
}
#ifdef DEBUG
if (debug_level >= 4) {
dbg_puts("\npfds[] =");
for (n = 0; n < nfds; n++) {
dbg_puts(" ");
dbg_putx(pfds[n].events);
}
dbg_puts("\n");
}
#endif
if (nfds > 0) {
if (poll(pfds, nfds, -1) < 0) {
if (errno == EINTR)
return 1;
err(1, "file_poll: poll failed");
}
clock_gettime(CLOCK_MONOTONIC, &ts);
delta_nsec = 1000000000L * (ts.tv_sec - file_ts.tv_sec);
delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
if (delta_nsec > 0) {
file_ts = ts;
timo_update(delta_nsec / 1000);
}
}
f = LIST_FIRST(&file_list);
while (f != LIST_END(&file_list)) {
if (f->pfd == NULL) {
f = LIST_NEXT(f, entry);
continue;
}
revents = f->ops->revents(f, f->pfd);
#ifdef DEBUG
if (revents) {
f->cycles++;
if (f->cycles > FILE_MAXCYCLES) {
file_dbg(f);
dbg_puts(": busy loop, disconnecting\n");
revents = POLLHUP;
}
}
#endif
if (!(f->state & FILE_ZOMB) && (revents & POLLIN)) {
revents &= ~POLLIN;
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(f);
dbg_puts(": rok\n");
}
#endif
f->state |= FILE_ROK;
f->state |= FILE_RINUSE;
for (;;) {
p = f->rproc;
if (!p)
break;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": in\n");
}
#endif
if (!p->ops->in(p, NULL))
break;
}
f->state &= ~FILE_RINUSE;
}
if (!(f->state & FILE_ZOMB) && (revents & POLLOUT)) {
revents &= ~POLLOUT;
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(f);
dbg_puts(": wok\n");
}
#endif
f->state |= FILE_WOK;
f->state |= FILE_WINUSE;
for (;;) {
p = f->wproc;
if (!p)
break;
#ifdef DEBUG
if (debug_level >= 4) {
aproc_dbg(p);
dbg_puts(": out\n");
}
#endif
if (!p->ops->out(p, NULL))
break;
}
f->state &= ~FILE_WINUSE;
}
if (!(f->state & FILE_ZOMB) && (revents & POLLHUP)) {
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": disconnected\n");
}
#endif
f->state |= (FILE_EOF | FILE_HUP);
}
if (!(f->state & FILE_ZOMB) && (f->state & FILE_EOF)) {
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": eof\n");
}
#endif
p = f->rproc;
if (p) {
f->state |= FILE_RINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": eof\n");
}
#endif
p->ops->eof(p, NULL);
f->state &= ~FILE_RINUSE;
}
f->state &= ~FILE_EOF;
}
if (!(f->state & FILE_ZOMB) && (f->state & FILE_HUP)) {
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": hup\n");
}
#endif
p = f->wproc;
if (p) {
f->state |= FILE_WINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": hup\n");
}
#endif
p->ops->hup(p, NULL);
f->state &= ~FILE_WINUSE;
}
f->state &= ~FILE_HUP;
}
fnext = LIST_NEXT(f, entry);
if (f->state & FILE_ZOMB)
file_del(f);
f = fnext;
}
if (LIST_EMPTY(&file_list)) {
#ifdef DEBUG
if (debug_level >= 3)
dbg_puts("no files anymore...\n");
#endif
return 0;
}
return 1;
}
/*
* handler for SIGALRM, invoked periodically
*/
void
file_sigalrm(int i)
{
/* nothing to do, we only want poll() to return EINTR */
}
void
filelist_init(void)
{
static struct sigaction sa;
struct itimerval it;
sigset_t set;
sigemptyset(&set);
(void)sigaddset(&set, SIGPIPE);
if (sigprocmask(SIG_BLOCK, &set, NULL))
err(1, "sigprocmask");
LIST_INIT(&file_list);
if (clock_gettime(CLOCK_MONOTONIC, &file_ts) < 0) {
perror("clock_gettime");
exit(1);
}
sa.sa_flags = SA_RESTART;
sa.sa_handler = file_sigalrm;
sigfillset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0) {
perror("sigaction");
exit(1);
}
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = TIMER_USEC;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = TIMER_USEC;
if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
perror("setitimer");
exit(1);
}
timo_init();
#ifdef DEBUG
dbg_sync = 0;
#endif
}
void
filelist_done(void)
{
struct itimerval it;
#ifdef DEBUG
struct file *f;
if (!LIST_EMPTY(&file_list)) {
LIST_FOREACH(f, &file_list, entry) {
file_dbg(f);
dbg_puts(" not closed\n");
}
dbg_panic();
}
dbg_sync = 1;
dbg_flush();
#endif
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
perror("setitimer");
exit(1);
}
timo_done();
}
unsigned
file_read(struct file *f, unsigned char *data, unsigned count)
{
unsigned n;
#ifdef DEBUG
struct timespec ts0, ts1;
long us;
if (!(f->state & FILE_ROK)) {
file_dbg(f);
dbg_puts(": read: bad state\n");
dbg_panic();
}
clock_gettime(CLOCK_MONOTONIC, &ts0);
#endif
n = f->ops->read(f, data, count);
#ifdef DEBUG
if (n > 0)
f->cycles = 0;
clock_gettime(CLOCK_MONOTONIC, &ts1);
us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
if (debug_level >= 4 || (debug_level >= 2 && us >= 5000)) {
dbg_puts(f->name);
dbg_puts(": read ");
dbg_putu(n);
dbg_puts(" bytes in ");
dbg_putu(us);
dbg_puts("us\n");
}
#endif
return n;
}
unsigned
file_write(struct file *f, unsigned char *data, unsigned count)
{
unsigned n;
#ifdef DEBUG
struct timespec ts0, ts1;
long us;
if (!(f->state & FILE_WOK)) {
file_dbg(f);
dbg_puts(": write: bad state\n");
dbg_panic();
}
clock_gettime(CLOCK_MONOTONIC, &ts0);
#endif
n = f->ops->write(f, data, count);
#ifdef DEBUG
if (n > 0)
f->cycles = 0;
clock_gettime(CLOCK_MONOTONIC, &ts1);
us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
if (debug_level >= 4 || (debug_level >= 2 && us >= 5000)) {
dbg_puts(f->name);
dbg_puts(": wrote ");
dbg_putu(n);
dbg_puts(" bytes in ");
dbg_putu(us);
dbg_puts("us\n");
}
#endif
return n;
}
void
file_eof(struct file *f)
{
struct aproc *p;
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": eof requested\n");
}
#endif
if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) {
p = f->rproc;
if (p) {
f->state |= FILE_RINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": eof\n");
}
#endif
p->ops->eof(p, NULL);
f->state &= ~FILE_RINUSE;
}
if (f->state & FILE_ZOMB)
file_del(f);
} else {
f->state &= ~FILE_ROK;
f->state |= FILE_EOF;
}
}
void
file_hup(struct file *f)
{
struct aproc *p;
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": hup requested\n");
}
#endif
if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) {
p = f->wproc;
if (p) {
f->state |= FILE_WINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": hup\n");
}
#endif
p->ops->hup(p, NULL);
f->state &= ~FILE_WINUSE;
}
if (f->state & FILE_ZOMB)
file_del(f);
} else {
f->state &= ~FILE_WOK;
f->state |= FILE_HUP;
}
}
void
file_close(struct file *f)
{
struct aproc *p;
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(f);
dbg_puts(": closing\n");
}
#endif
if (f->wproc == NULL && f->rproc == NULL)
f->state |= FILE_ZOMB;
if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) {
p = f->rproc;
if (p) {
f->state |= FILE_RINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": eof\n");
}
#endif
p->ops->eof(p, NULL);
f->state &= ~FILE_RINUSE;
}
p = f->wproc;
if (p) {
f->state |= FILE_WINUSE;
#ifdef DEBUG
if (debug_level >= 3) {
aproc_dbg(p);
dbg_puts(": hup\n");
}
#endif
p->ops->hup(p, NULL);
f->state &= ~FILE_WINUSE;
}
if (f->state & FILE_ZOMB)
file_del(f);
} else {
f->state &= ~(FILE_ROK | FILE_WOK);
f->state |= (FILE_EOF | FILE_HUP);
}
}

92
aucat/file.h Normal file
View File

@ -0,0 +1,92 @@
/* $OpenBSD: file.h,v 1.10 2010/07/06 20:06:35 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 FILE_H
#define FILE_H
#include <sys/queue.h>
#include <sys/types.h>
struct file;
struct aproc;
struct pollfd;
struct timo {
struct timo *next;
unsigned val; /* time to wait before the callback */
unsigned set; /* true if the timeout is set */
void (*cb)(void *arg); /* routine to call on expiration */
void *arg; /* argument to give to 'cb' */
};
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 {
struct fileops *ops;
struct pollfd *pfd; /* arg to poll(2) syscall */
#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 /* hang-up on the write end */
#define FILE_ZOMB 0x10 /* closed, but struct not freed */
#define FILE_RINUSE 0x20 /* inside rproc->ops->in() */
#define FILE_WINUSE 0x40 /* inside wproc->ops->out() */
unsigned state; /* one of above */
#ifdef DEBUG
#define FILE_MAXCYCLES 20
unsigned cycles; /* number of POLLIN/POLLOUT events */
#endif
char *name; /* for debug purposes */
struct aproc *rproc, *wproc; /* reader and/or writer */
LIST_ENTRY(file) entry;
};
LIST_HEAD(filelist,file);
extern struct filelist file_list;
void timo_set(struct timo *, void (*)(void *), void *);
void timo_add(struct timo *, unsigned);
void timo_del(struct timo *);
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_dbg(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);
int file_poll(void);
void file_eof(struct file *);
void file_hup(struct file *);
void file_close(struct file *);
#endif /* !defined(FILE_H) */

291
aucat/headers.c Normal file
View File

@ -0,0 +1,291 @@
/* $OpenBSD: headers.c,v 1.18 2010/06/05 16:54:19 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "aparams.h"
#include "conf.h"
#include "wav.h"
/*
* Encoding IDs used in .wav headers.
*/
#define WAV_ENC_PCM 1
#define WAV_ENC_ALAW 6
#define WAV_ENC_ULAW 7
#define WAV_ENC_EXT 0xfffe
struct wavriff {
char magic[4];
uint32_t size;
char type[4];
} __packed;
struct wavchunk {
char id[4];
uint32_t size;
} __packed;
struct wavfmt {
uint16_t fmt;
uint16_t nch;
uint32_t rate;
uint32_t byterate;
uint16_t blkalign;
uint16_t bits;
#define WAV_FMT_SIZE 16
#define WAV_FMT_SIZE2 (16 + 2)
#define WAV_FMT_EXT_SIZE (16 + 24)
uint16_t extsize;
uint16_t valbits;
uint32_t chanmask;
uint16_t extfmt;
char guid[14];
} __packed;
char wav_id_riff[4] = { 'R', 'I', 'F', 'F' };
char wav_id_wave[4] = { 'W', 'A', 'V', 'E' };
char wav_id_data[4] = { 'd', 'a', 't', 'a' };
char wav_id_fmt[4] = { 'f', 'm', 't', ' ' };
char wav_guid[14] = {
0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x80, 0x00,
0x00, 0xAA, 0x00, 0x38,
0x9B, 0x71
};
int
wav_readfmt(int fd, unsigned csize, struct aparams *par, short **map)
{
struct wavfmt fmt;
unsigned nch, cmax, rate, bits, bps, enc;
if (csize < WAV_FMT_SIZE) {
warnx("%u: bugus format chunk size", csize);
return 0;
}
if (csize > WAV_FMT_EXT_SIZE)
csize = WAV_FMT_EXT_SIZE;
if (read(fd, &fmt, csize) != csize) {
warn("riff_read: chunk");
return 0;
}
enc = letoh16(fmt.fmt);
bits = letoh16(fmt.bits);
if (enc == WAV_ENC_EXT) {
if (csize != WAV_FMT_EXT_SIZE) {
warnx("missing extended format chunk in .wav file");
return 0;
}
if (memcmp(fmt.guid, wav_guid, sizeof(wav_guid)) != 0) {
warnx("unknown format (GUID) in .wav file");
return 0;
}
bps = (bits + 7) / 8;
bits = letoh16(fmt.valbits);
enc = letoh16(fmt.extfmt);
} else
bps = (bits + 7) / 8;
switch (enc) {
case WAV_ENC_PCM:
*map = NULL;
break;
case WAV_ENC_ALAW:
*map = wav_alawmap;
break;
case WAV_ENC_ULAW:
*map = wav_ulawmap;
break;
default:
errx(1, "%u: unsupported encoding in .wav file", enc);
}
nch = letoh16(fmt.nch);
if (nch == 0) {
warnx("zero number of channels");
return 0;
}
cmax = par->cmin + nch - 1;
if (cmax >= NCHAN_MAX) {
warnx("%u:%u: bad range", par->cmin, cmax);
return 0;
}
rate = letoh32(fmt.rate);
if (rate < RATE_MIN || rate > RATE_MAX) {
warnx("%u: bad sample rate", rate);
return 0;
}
if (bits == 0 || bits > 32) {
warnx("%u: bad number of bits", bits);
return 0;
}
if (bits > bps * 8) {
warnx("%u: bits larger than bytes-per-sample", bps);
return 0;
}
if (enc == WAV_ENC_PCM) {
par->bps = bps;
par->bits = bits;
par->le = 1;
par->sig = (bits <= 8) ? 0 : 1; /* ask microsoft why... */
} else {
if (bits != 8) {
warnx("%u: mulaw/alaw encoding not 8-bit", bits);
return 0;
}
par->bits = 8 * sizeof(short);
par->bps = sizeof(short);
par->le = NATIVE_LE;
par->sig = 1;
}
par->msb = 1;
par->cmax = cmax;
par->rate = rate;
return 1;
}
int
wav_readhdr(int fd, struct aparams *par, off_t *startpos, off_t *datasz, short **map)
{
struct wavriff riff;
struct wavchunk chunk;
unsigned csize, rsize, pos = 0;
int fmt_done = 0;
if (lseek(fd, 0, SEEK_SET) < 0) {
warn("lseek: 0");
return 0;
}
if (read(fd, &riff, sizeof(riff)) != sizeof(riff)) {
warn("wav_readhdr: header");
return 0;
}
if (memcmp(&riff.magic, &wav_id_riff, 4) != 0 ||
memcmp(&riff.type, &wav_id_wave, 4)) {
warnx("not a wave file");
return 0;
}
rsize = letoh32(riff.size);
for (;;) {
if (pos + sizeof(struct wavchunk) > rsize) {
warnx("missing data chunk");
return 0;
}
if (read(fd, &chunk, sizeof(chunk)) != sizeof(chunk)) {
warn("wav_readhdr: chunk");
return 0;
}
csize = letoh32(chunk.size);
if (memcmp(chunk.id, wav_id_fmt, 4) == 0) {
if (!wav_readfmt(fd, csize, par, map))
return 0;
fmt_done = 1;
} else if (memcmp(chunk.id, wav_id_data, 4) == 0) {
*startpos = pos + sizeof(riff) + sizeof(chunk);
*datasz = csize;
break;
} else {
#ifdef DEBUG
if (debug_level >= 2)
warnx("ignoring chuck <%.4s>\n", chunk.id);
#endif
}
/*
* next chunk
*/
pos += sizeof(struct wavchunk) + csize;
if (lseek(fd, sizeof(riff) + pos, SEEK_SET) < 0) {
warn("lseek");
return 0;
}
}
if (!fmt_done) {
warnx("missing format chunk");
return 0;
}
return 1;
}
/*
* Write header and seek to start position
*/
int
wav_writehdr(int fd, struct aparams *par, off_t *startpos, off_t datasz)
{
unsigned nch = par->cmax - par->cmin + 1;
struct {
struct wavriff riff;
struct wavchunk fmt_hdr;
struct wavfmt fmt;
struct wavchunk data_hdr;
} hdr;
/*
* Check that encoding is supported by .wav file format.
*/
if (par->bits > 8 && !par->le) {
warnx("samples must be little endian");
return 0;
}
if (8 * par->bps - par->bits >= 8) {
warnx("padding must be less than 8 bits");
return 0;
}
if ((par->bits <= 8 && par->sig) || (par->bits > 8 && !par->sig)) {
warnx("samples with more (less) than 8 bits must be signed "
"(unsigned)");
return 0;
}
if (8 * par->bps != par->bits && !par->msb) {
warnx("samples must be MSB justified");
return 0;
}
memcpy(hdr.riff.magic, wav_id_riff, 4);
memcpy(hdr.riff.type, wav_id_wave, 4);
hdr.riff.size = htole32(datasz + sizeof(hdr) - sizeof(hdr.riff));
memcpy(hdr.fmt_hdr.id, wav_id_fmt, 4);
hdr.fmt_hdr.size = htole32(sizeof(hdr.fmt));
hdr.fmt.fmt = htole16(1);
hdr.fmt.nch = htole16(nch);
hdr.fmt.rate = htole32(par->rate);
hdr.fmt.byterate = htole32(par->rate * par->bps * nch);
hdr.fmt.bits = htole16(par->bits);
hdr.fmt.blkalign = par->bps * nch;
memcpy(hdr.data_hdr.id, wav_id_data, 4);
hdr.data_hdr.size = htole32(datasz);
if (lseek(fd, 0, SEEK_SET) < 0) {
warn("wav_writehdr: lseek");
return 0;
}
if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
warn("wav_writehdr: write");
return 0;
}
*startpos = sizeof(hdr);
return 1;
}

192
aucat/legacy.c Normal file
View File

@ -0,0 +1,192 @@
/* $OpenBSD: legacy.c,v 1.12 2010/04/06 20:07:01 ratchov Exp $ */
/*
* Copyright (c) 1997 Kenneth Stailey. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Kenneth Stailey.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sndio.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "wav.h"
/*
* Headerless data files. Played at /dev/audio's defaults.
*/
#define FMT_RAW 0
/*
* Sun/NeXT .au files. Header is skipped and /dev/audio is configured
* for monaural 8-bit ulaw @ 8kHz, the de facto format for .au files,
* as well as the historical default configuration for /dev/audio.
*/
#define FMT_AU 1
/*
* RIFF WAV files. Header is parsed for format details which are
* applied to /dev/audio.
*/
#define FMT_WAV 2
int
legacy_play(char *dev, char *aufile)
{
struct sio_hdl *hdl;
struct sio_par spar, par;
struct aparams apar;
ssize_t rd;
off_t datasz, dummy;
char buf[5120];
size_t readsz;
int fd, fmt = FMT_RAW;
u_int32_t pos = 0, snd_fmt = 1, rate = 8000, chan = 1;
char magic[4];
short *map;
if ((fd = open(aufile, O_RDONLY)) < 0) {
warn("cannot open %s", aufile);
return(1);
}
if (read(fd, magic, sizeof(magic)) != sizeof(magic)) {
/*
* read() error, or the file is smaller than sizeof(magic).
* treat as a raw file, like previous versions of aucat.
*/
} else if (!strncmp(magic, ".snd", 4)) {
fmt = FMT_AU;
if (read(fd, &pos, sizeof(pos)) == sizeof(pos))
pos = ntohl(pos);
/* data size */
if (lseek(fd, 4, SEEK_CUR) == -1)
warn("lseek hdr");
if (read(fd, &snd_fmt, sizeof(snd_fmt)) == sizeof(snd_fmt))
snd_fmt = ntohl(snd_fmt);
if (read(fd, &rate, sizeof(rate)) == sizeof(rate))
rate = ntohl(rate);
if (read(fd, &chan, sizeof(chan)) == sizeof(chan))
chan = ntohl(chan);
} else if (!strncmp(magic, "RIFF", 4) &&
wav_readhdr(fd, &apar, &dummy, &datasz, &map)) {
fmt = FMT_WAV;
}
/*
* Seek to start of audio data. wav_readhdr already took care
* of this for FMT_WAV.
*/
if (fmt == FMT_RAW || fmt == FMT_AU)
if (lseek(fd, (off_t)pos, SEEK_SET) == -1)
warn("lseek");
if ((hdl = sio_open(dev, SIO_PLAY, 0)) == NULL) {
warnx("can't get sndio handle");
return(1);
}
sio_initpar(&par);
switch(fmt) {
case FMT_WAV:
par.rate = apar.rate;
par.pchan = apar.cmax - apar.cmin + 1;
par.sig = apar.sig;
par.bits = apar.bits;
par.le = apar.le;
break;
case FMT_AU:
par.rate = rate;
par.pchan = chan;
par.sig = 1;
par.bits = 16;
par.le = SIO_LE_NATIVE;
map = wav_ulawmap;
if (snd_fmt == 27)
map = wav_alawmap;
break;
case FMT_RAW:
default:
break;
}
spar = par;
if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) {
warnx("can't set audio parameters");
/*
* Only WAV could fail in previous aucat versions (unless
* the parameters returned by AUDIO_GETINFO would fail,
* which is unlikely).
*/
if (fmt == FMT_WAV)
return(1);
}
/*
* Parameters may be silently modified. See audio(9)'s
* description of set_params. For compatability with previous
* aucat versions, continue running if something doesn't match.
*/
if (par.bits != spar.bits ||
par.sig != par.sig ||
par.le != spar.le ||
par.pchan != spar.pchan ||
/*
* Devices may return a very close rate, such as 44099 when
* 44100 was requested. The difference is inaudible. Allow
* 2% deviation as an example of how to cope.
*/
(par.rate > spar.rate * 1.02 || par.rate < spar.rate * 0.98)) {
warnx("format not supported");
}
if (!sio_start(hdl)) {
warnx("could not start sndio");
exit(1);
}
readsz = sizeof(buf);
if (map)
readsz /= 2;
while ((rd = read(fd, buf, readsz)) > 0) {
if (map) {
wav_conv(buf, rd, map);
rd *= 2;
}
if (sio_write(hdl, buf, rd) != rd)
warnx("sio_write: short write");
}
if (rd == -1)
warn("read");
sio_close(hdl);
close(fd);
return(0);
}

143
aucat/listen.c Normal file
View File

@ -0,0 +1,143 @@
/* $OpenBSD: listen.c,v 1.11 2009/09/27 11:51:20 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/stat.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 "listen.h"
#include "sock.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, oldumask;
struct sockaddr_un sockname;
struct listen *f;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return NULL;
}
if (unlink(path) < 0 && errno != ENOENT) {
perror("unlink");
goto bad_close;
}
sockname.sun_family = AF_UNIX;
strlcpy(sockname.sun_path, path, sizeof(sockname.sun_path));
oldumask = umask(0111);
if (bind(sock, (struct sockaddr *)&sockname,
sizeof(struct sockaddr_un)) < 0) {
perror("bind");
goto bad_close;
}
umask(oldumask);
if (listen(sock, 1) < 0) {
perror("listen");
goto bad_close;
}
f = (struct listen *)file_new(ops, path, 1);
if (f == NULL)
goto bad_close;
f->path = strdup(path);
if (f->path == NULL) {
perror("strdup");
exit(1);
}
f->fd = sock;
return f;
bad_close:
close(sock);
return NULL;
}
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) {
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;
}
if (sock_new(&sock_ops, sock) == NULL) {
close(sock);
return 0;
}
}
return 0;
}
void
listen_close(struct file *file)
{
struct listen *f = (struct listen *)file;
unlink(f->path);
free(f->path);
close(f->fd);
}

38
aucat/listen.h Normal file
View File

@ -0,0 +1,38 @@
/* $OpenBSD: listen.h,v 1.5 2009/07/25 10:52:19 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 "aparams.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);
int listen_revents(struct file *, struct pollfd *);
void listen_close(struct file *);
extern struct fileops listen_ops;
#endif /* !defined(LISTEN_H) */

1200
aucat/midi.c Normal file

File diff suppressed because it is too large Load Diff

36
aucat/midi.h Normal file
View File

@ -0,0 +1,36 @@
/* $OpenBSD: midi.h,v 1.9 2010/06/04 06:15:28 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 MIDI_H
#define MIDI_H
struct dev;
struct aproc *thru_new(char *);
struct aproc *ctl_new(char *, struct dev *);
int ctl_slotnew(struct aproc *, char *, struct ctl_ops *, void *, int);
void ctl_slotdel(struct aproc *, int);
void ctl_slotvol(struct aproc *, int, unsigned);
int ctl_slotstart(struct aproc *, int);
void ctl_slotstop(struct aproc *, int);
void ctl_ontick(struct aproc *, int);
void ctl_stop(struct aproc *);
void ctl_start(struct aproc *);
int ctl_idle(struct aproc *);
#endif /* !defined(MIDI_H) */

166
aucat/midicat.1 Normal file
View File

@ -0,0 +1,166 @@
.\" $OpenBSD: midicat.1,v 1.13 2010/07/06 10:45:01 jmc Exp $
.\"
.\" Copyright (c) 2006 Alexandre Ratchov <alex@caoua.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: July 6 2010 $
.Dt MIDICAT 1
.Os
.Sh NAME
.Nm midicat
.Nd MIDI server and manipulation tool
.Sh SYNOPSIS
.Nm midicat
.Op Fl dl
.Op Fl i Ar file
.Op Fl o Ar file
.Op Fl q Ar port
.Op Fl s Ar name
.Op Fl U Ar unit
.Sh DESCRIPTION
The
.Nm
utility can merge any number of MIDI inputs and broadcast the result
to any number of MIDI outputs, similarly to a hardware MIDI thru box.
MIDI streams are typically MIDI ports or plain files containing raw MIDI
data.
.Pp
The
.Nm
utility can also act as a MIDI server in which case MIDI streams
correspond to client connections.
The server capability
allows any MIDI-capable application to send MIDI messages to
MIDI hardware or to another application in a uniform way.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl d
Increase log verbosity.
.Nm
logs on stderr until it daemonizes.
.It Fl i Ar file
Read data to send from this file.
If the option argument is
.Sq -
then standard input will be used.
.It Fl l
Detach and become a daemon.
.It Fl o Ar file
Write received data into this file.
If the option argument is
.Sq -
then standard output will be used.
.It Fl q Ar port
Send and receive data from this
.Xr sndio 7
MIDI port.
.It Fl s Ar name
Expose a MIDI thru box to which MIDI programs
can connect.
Preceding streams
.Pq Fl ioq
are subscribed to this thru box.
The given
.Ar name
corresponds to the
.Dq option
part of the
.Xr sndio 7
device name string.
.It Fl U Ar unit
Unit number to use when running in server mode.
Each
.Nm
server instance has an unique unit number,
used in
.Xr sndio 7
device names.
The default is 0.
.El
.Pp
If files
.Pq Fl io
are specified but no ports
.Pq Fl q
are specified, the default
.Xr sndio 7
port is used.
If no streams
.Pq Fl ioq
are specified, server mode is assumed and a thru box is created
as if
.Fl s Ar default
was used as the last argument.
.Pp
Generally MIDI applications are real-time.
To reduce jitter, especially on busy machines,
the server can be started by the super-user,
in which case it will run with higher priority.
Any user will still be able to connect to it,
but for privacy reasons only one user may have connections to
it at a given time.
.Pp
If
.Nm
is sent
.Dv SIGHUP ,
.Dv SIGINT
or
.Dv SIGTERM ,
then processing terminates.
.Sh EXAMPLES
The following dumps MIDI data received from the default port:
.Bd -literal -offset indent
$ midicat -o - | hexdump -e '1/1 "%x"'
.Ed
.Pp
The following sends raw MIDI data to the
.Pa rmidi:5
port:
.Bd -literal -offset indent
$ midicat -i sysexfile -q rmidi:5
.Ed
.Pp
The following connects
.Pa rmidi:5
and
.Pa rmidi:6
ports:
.Bd -literal -offset indent
$ midicat -q rmidi:5 -q rmidi:6
.Ed
.Pp
The following creates a MIDI thru box and daemonizes,
allowing MIDI programs to send data to each other instead of
using hardware MIDI ports:
.Bd -literal -offset indent
$ midicat -l
.Ed
.Pp
The following creates a MIDI thru box and subscribes the
.Pa rmidi:5
port, allowing multiple MIDI programs to use the port
simultaneously:
.Bd -literal -offset indent
$ midicat -q rmidi:5 -s default
.Ed
.Sh SEE ALSO
.Xr aucat 1 ,
.Xr midi 4 ,
.Xr sndio 7
.Sh BUGS
The ability to merge multiple inputs is provided to allow multiple
applications producing MIDI data to keep their connection open while
idling; it does not replace a fully featured MIDI merger.

169
aucat/miofile.c Normal file
View File

@ -0,0 +1,169 @@
/* $OpenBSD: miofile.c,v 1.5 2010/06/04 06:15:28 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 <sndio.h>
#include "conf.h"
#include "file.h"
#include "miofile.h"
#ifdef DEBUG
#include "dbg.h"
#endif
struct miofile {
struct file file;
struct mio_hdl *hdl;
};
void miofile_close(struct file *);
unsigned miofile_read(struct file *, unsigned char *, unsigned);
unsigned miofile_write(struct file *, unsigned char *, unsigned);
void miofile_start(struct file *);
void miofile_stop(struct file *);
int miofile_nfds(struct file *);
int miofile_pollfd(struct file *, struct pollfd *, int);
int miofile_revents(struct file *, struct pollfd *);
struct fileops miofile_ops = {
"mio",
sizeof(struct miofile),
miofile_close,
miofile_read,
miofile_write,
NULL, /* start */
NULL, /* stop */
miofile_nfds,
miofile_pollfd,
miofile_revents
};
/*
* open the device
*/
struct miofile *
miofile_new(struct fileops *ops, char *path, int input, int output)
{
char *siopath;
struct mio_hdl *hdl;
struct miofile *f;
int mode = 0;
siopath = (strcmp(path, "default") == 0) ? NULL : path;
if (input)
mode |= MIO_IN;
if (output)
mode |= MIO_OUT;
hdl = mio_open(siopath, mode, 1);
if (hdl == NULL)
return NULL;
f = (struct miofile *)file_new(ops, path, mio_nfds(hdl));
if (f == NULL)
goto bad_close;
f->hdl = hdl;
return f;
bad_close:
mio_close(hdl);
return NULL;
}
unsigned
miofile_read(struct file *file, unsigned char *data, unsigned count)
{
struct miofile *f = (struct miofile *)file;
unsigned n;
n = mio_read(f->hdl, data, count);
if (n == 0) {
f->file.state &= ~FILE_ROK;
if (mio_eof(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to read from device\n");
#endif
file_eof(&f->file);
} else {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": reading blocked\n");
}
#endif
}
return 0;
}
return n;
}
unsigned
miofile_write(struct file *file, unsigned char *data, unsigned count)
{
struct miofile *f = (struct miofile *)file;
unsigned n;
n = mio_write(f->hdl, data, count);
if (n == 0) {
f->file.state &= ~FILE_WOK;
if (mio_eof(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to write on device\n");
#endif
file_hup(&f->file);
} else {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": writing blocked\n");
}
#endif
}
return 0;
}
return n;
}
int
miofile_nfds(struct file *file)
{
return mio_nfds(((struct miofile *)file)->hdl);
}
int
miofile_pollfd(struct file *file, struct pollfd *pfd, int events)
{
return mio_pollfd(((struct miofile *)file)->hdl, pfd, events);
}
int
miofile_revents(struct file *file, struct pollfd *pfd)
{
return mio_revents(((struct miofile *)file)->hdl, pfd);
}
void
miofile_close(struct file *file)
{
return mio_close(((struct miofile *)file)->hdl);
}

28
aucat/miofile.h Normal file
View File

@ -0,0 +1,28 @@
/* $OpenBSD: miofile.h,v 1.1 2009/07/25 08:44:27 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 MIOFILE_H
#define MIOFILE_H
struct file;
struct fileops;
struct miofile;
struct miofile *miofile_new(struct fileops *, char *, int, int);
extern struct fileops miofile_ops;
#endif /* !defined(MIOFILE_H) */

131
aucat/opt.c Normal file
View File

@ -0,0 +1,131 @@
/* $OpenBSD: opt.c,v 1.10 2010/07/06 01:12:45 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dev.h"
#include "conf.h"
#include "opt.h"
#ifdef DEBUG
#include "dbg.h"
#endif
struct optlist opt_list = SLIST_HEAD_INITIALIZER(&opt_list);
void
opt_new(char *name, struct dev *d, struct aparams *wpar, struct aparams *rpar,
int maxweight, int mmc, int join, unsigned mode)
{
struct opt *o;
unsigned len;
char c;
for (len = 0; name[len] != '\0'; len++) {
if (len == OPT_NAMEMAX) {
fprintf(stderr, "%s: name too long\n", name);
exit(1);
}
c = name[len];
if (c < 'a' && c > 'z' &&
c < 'A' && c > 'Z' &&
c < '0' && c > '9' &&
c != '_') {
fprintf(stderr, "%s: '%c' not allowed\n", name, c);
exit(1);
}
}
SLIST_FOREACH(o, &opt_list, entry) {
if (strcmp(name, o->name) == 0) {
fprintf(stderr, "%s: already defined\n", name);
exit(1);
}
}
o = malloc(sizeof(struct opt));
if (o == NULL) {
perror("opt_new: malloc");
exit(1);
}
memcpy(o->name, name, len + 1);
if (mode & MODE_RECMASK)
o->wpar = (mode & MODE_MON) ? *rpar : *wpar;
if (mode & MODE_PLAY)
o->rpar = *rpar;
o->maxweight = maxweight;
o->mmc = mmc;
o->join = join;
o->mode = mode;
o->dev = d;
#ifdef DEBUG
if (debug_level >= 2) {
dbg_puts(o->name);
dbg_puts("@");
dbg_puts(o->dev->path);
dbg_puts(":");
if (mode & MODE_REC) {
dbg_puts(" rec=");
dbg_putu(o->wpar.cmin);
dbg_puts(":");
dbg_putu(o->wpar.cmax);
}
if (mode & MODE_PLAY) {
dbg_puts(" play=");
dbg_putu(o->rpar.cmin);
dbg_puts(":");
dbg_putu(o->rpar.cmax);
dbg_puts(" vol=");
dbg_putu(o->maxweight);
}
if (mode & MODE_MON) {
dbg_puts(" mon=");
dbg_putu(o->wpar.cmin);
dbg_puts(":");
dbg_putu(o->wpar.cmax);
}
if (o->mmc)
dbg_puts(" mmc");
dbg_puts("\n");
}
#endif
SLIST_INSERT_HEAD(&opt_list, o, entry);
}
struct opt *
opt_byname(char *name)
{
struct opt *o;
SLIST_FOREACH(o, &opt_list, entry) {
if (strcmp(name, o->name) == 0) {
#ifdef DEBUG
if (debug_level >= 3) {
dbg_puts(o->name);
dbg_puts(": option found\n");
}
#endif
return o;
}
}
#ifdef DEBUG
if (debug_level >= 3) {
dbg_puts(name);
dbg_puts(": option not found\n");
}
#endif
return NULL;
}

53
aucat/opt.h Normal file
View File

@ -0,0 +1,53 @@
/* $OpenBSD: opt.h,v 1.8 2010/06/04 06:15:28 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 OPT_H
#define OPT_H
#include <sys/queue.h>
#include "aparams.h"
struct dev;
struct opt {
SLIST_ENTRY(opt) entry;
#define OPT_NAMEMAX 11
char name[OPT_NAMEMAX + 1];
int maxweight; /* max dynamic range for clients */
struct aparams wpar; /* template for clients write params */
struct aparams rpar; /* template for clients read params */
int mmc; /* true if MMC control enabled */
int join; /* true if join/expand enabled */
#define MODE_PLAY 0x1 /* allowed to play */
#define MODE_REC 0x2 /* allowed to rec */
#define MODE_MIDIIN 0x4 /* allowed to read midi */
#define MODE_MIDIOUT 0x8 /* allowed to write midi */
#define MODE_MON 0x10 /* allowed to monitor */
#define MODE_LOOP 0x20 /* deviceless mode */
#define MODE_RECMASK (MODE_REC | MODE_MON)
#define MODE_AUDIOMASK (MODE_REC | MODE_MON | MODE_PLAY)
#define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT)
unsigned mode; /* bitmap of above */
struct dev *dev; /* device to which we're attached */
};
SLIST_HEAD(optlist,opt);
void opt_new(char *, struct dev *, struct aparams *, struct aparams *,
int, int, int, unsigned);
struct opt *opt_byname(char *);
#endif /* !defined(OPT_H) */

192
aucat/pipe.c Normal file
View File

@ -0,0 +1,192 @@
/*
* 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/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"
#ifdef DEBUG
#include "dbg.h"
#endif
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);
if (f == NULL)
return NULL;
f->fd = fd;
return f;
}
unsigned
pipe_read(struct file *file, unsigned char *data, unsigned count)
{
struct pipe *f = (struct pipe *)file;
int n;
while ((n = read(f->fd, data, count)) < 0) {
f->file.state &= ~FILE_ROK;
if (errno == EAGAIN) {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": reading blocked\n");
}
#endif
} else {
warn("%s", f->file.name);
file_eof(&f->file);
}
return 0;
}
if (n == 0) {
f->file.state &= ~FILE_ROK; /* XXX: already cleared in file_eof */
file_eof(&f->file);
return 0;
}
return n;
}
unsigned
pipe_write(struct file *file, unsigned char *data, unsigned count)
{
struct pipe *f = (struct pipe *)file;
int n;
while ((n = write(f->fd, data, count)) < 0) {
f->file.state &= ~FILE_WOK;
if (errno == EAGAIN) {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": writing blocked\n");
}
#endif
} else {
if (errno != EPIPE)
warn("%s", f->file.name);
file_hup(&f->file);
}
return 0;
}
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);
}
off_t
pipe_endpos(struct file *file)
{
struct pipe *f = (struct pipe *)file;
off_t pos;
pos = lseek(f->fd, 0, SEEK_END);
if (pos < 0) {
#ifdef DEBUG
file_dbg(&f->file);
dbg_puts(": couldn't get file size\n");
#endif
return 0;
}
return pos;
}
int
pipe_seek(struct file *file, off_t pos)
{
struct pipe *f = (struct pipe *)file;
off_t newpos;
newpos = lseek(f->fd, pos, SEEK_SET);
if (newpos < 0) {
#ifdef DEBUG
file_dbg(&f->file);
dbg_puts(": couldn't seek\n");
#endif
/* XXX: call eof() */
return 0;
}
return 1;
}
int
pipe_trunc(struct file *file, off_t pos)
{
struct pipe *f = (struct pipe *)file;
if (ftruncate(f->fd, pos) < 0) {
#ifdef DEBUG
file_dbg(&f->file);
dbg_puts(": couldn't truncate file\n");
#endif
/* XXX: call hup() */
return 0;
}
return 1;
}

40
aucat/pipe.h Normal file
View File

@ -0,0 +1,40 @@
/* $OpenBSD: pipe.h,v 1.5 2010/04/06 20:07:01 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 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 *);
int pipe_seek(struct file *, off_t);
int pipe_trunc(struct file *, off_t);
off_t pipe_endpos(struct file *);
#endif /* !defined(PIPE_H) */

465
aucat/siofile.c Normal file
View File

@ -0,0 +1,465 @@
/* $OpenBSD: siofile.c,v 1.6 2010/06/04 06:15:28 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/time.h>
#include <sys/types.h>
#include <poll.h>
#include <sndio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aparams.h"
#include "aproc.h"
#include "abuf.h"
#include "conf.h"
#include "dev.h"
#include "file.h"
#include "siofile.h"
#ifdef DEBUG
#include "dbg.h"
#endif
struct siofile {
struct file file;
struct sio_hdl *hdl;
unsigned wtickets, wbpf;
unsigned rtickets, rbpf;
unsigned bufsz;
int started;
};
void siofile_close(struct file *);
unsigned siofile_read(struct file *, unsigned char *, unsigned);
unsigned siofile_write(struct file *, unsigned char *, unsigned);
void siofile_start(struct file *);
void siofile_stop(struct file *);
int siofile_nfds(struct file *);
int siofile_pollfd(struct file *, struct pollfd *, int);
int siofile_revents(struct file *, struct pollfd *);
struct fileops siofile_ops = {
"sio",
sizeof(struct siofile),
siofile_close,
siofile_read,
siofile_write,
siofile_start,
siofile_stop,
siofile_nfds,
siofile_pollfd,
siofile_revents
};
int wsio_out(struct aproc *, struct abuf *);
int rsio_in(struct aproc *, struct abuf *);
struct aproc_ops rsio_ops = {
"rsio",
rsio_in,
rfile_out,
rfile_eof,
rfile_hup,
NULL, /* newin */
NULL, /* newout */
aproc_ipos,
aproc_opos,
rfile_done
};
struct aproc_ops wsio_ops = {
"wsio",
wfile_in,
wsio_out,
wfile_eof,
wfile_hup,
NULL, /* newin */
NULL, /* newout */
aproc_ipos,
aproc_opos,
wfile_done
};
struct aproc *
rsio_new(struct file *f)
{
struct aproc *p;
p = aproc_new(&rsio_ops, f->name);
p->u.io.file = f;
p->u.io.partial = 0;
f->rproc = p;
return p;
}
struct aproc *
wsio_new(struct file *f)
{
struct aproc *p;
p = aproc_new(&wsio_ops, f->name);
p->u.io.file = f;
p->u.io.partial = 0;
f->wproc = p;
return p;
}
int
wsio_out(struct aproc *p, struct abuf *obuf)
{
struct siofile *f = (struct siofile *)p->u.io.file;
if (f->wtickets == 0) {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": no more write tickets\n");
}
#endif
f->file.state &= ~FILE_WOK;
return 0;
}
return wfile_out(p, obuf);
}
int
rsio_in(struct aproc *p, struct abuf *ibuf)
{
struct siofile *f = (struct siofile *)p->u.io.file;
if (f->rtickets == 0) {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": no more read tickets\n");
}
#endif
f->file.state &= ~FILE_ROK;
return 0;
}
return rfile_in(p, ibuf);
}
void
siofile_cb(void *addr, int delta)
{
struct siofile *f = (struct siofile *)addr;
struct aproc *p;
#ifdef DEBUG
if (delta < 0 || delta > (60 * RATE_MAX)) {
file_dbg(&f->file);
dbg_puts(": ");
dbg_puti(delta);
dbg_puts(": bogus sndio delta");
dbg_panic();
}
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": tick, delta = ");
dbg_puti(delta);
dbg_puts("\n");
}
#endif
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);
}
f->wtickets += delta * f->wbpf;
f->rtickets += delta * f->rbpf;
}
/*
* Open the device.
*/
struct siofile *
siofile_new(struct fileops *ops, char *path, unsigned *rmode,
struct aparams *ipar, struct aparams *opar,
unsigned *bufsz, unsigned *round)
{
char *siopath;
struct sio_par par;
struct sio_hdl *hdl;
struct siofile *f;
unsigned mode = *rmode;
siopath = (strcmp(path, "default") == 0) ? NULL : path;
hdl = sio_open(siopath, mode, 1);
if (hdl == NULL) {
if (mode != (SIO_PLAY | SIO_REC))
return NULL;
hdl = sio_open(siopath, SIO_PLAY, 1);
if (hdl != NULL)
mode = SIO_PLAY;
else {
hdl = sio_open(siopath, SIO_REC, 1);
if (hdl != NULL)
mode = SIO_REC;
else
return NULL;
}
#ifdef DEBUG
if (debug_level >= 1) {
dbg_puts("warning, device opened in ");
dbg_puts(mode == SIO_PLAY ? "play-only" : "rec-only");
dbg_puts(" mode\n");
}
#endif
}
sio_initpar(&par);
if (mode & SIO_REC) {
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 (mode & SIO_PLAY)
par.pchan = opar->cmax - opar->cmin + 1;
par.appbufsz = *bufsz;
par.round = *round;
if (!sio_setpar(hdl, &par))
goto bad_close;
if (!sio_getpar(hdl, &par))
goto bad_close;
if (mode & SIO_REC) {
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 = ipar->cmin + par.rchan - 1;
}
if (mode & SIO_PLAY) {
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 = opar->cmin + par.pchan - 1;
}
*rmode = mode;
*bufsz = par.bufsz;
*round = par.round;
f = (struct siofile *)file_new(ops, path, sio_nfds(hdl));
if (f == NULL)
goto bad_close;
f->hdl = hdl;
f->started = 0;
f->wtickets = 0;
f->rtickets = 0;
f->wbpf = par.pchan * par.bps;
f->rbpf = par.rchan * par.bps;
f->bufsz = par.bufsz;
sio_onmove(f->hdl, siofile_cb, f);
return f;
bad_close:
sio_close(hdl);
return NULL;
}
void
siofile_start(struct file *file)
{
struct siofile *f = (struct siofile *)file;
if (!sio_start(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to start device\n");
#endif
file_close(file);
return;
}
f->started = 1;
f->wtickets = f->bufsz * f->wbpf;
f->rtickets = 0;
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(&f->file);
dbg_puts(": started\n");
}
#endif
}
void
siofile_stop(struct file *file)
{
struct siofile *f = (struct siofile *)file;
f->started = 0;
if (!sio_eof(f->hdl) && !sio_stop(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to stop device\n");
#endif
file_close(file);
return;
}
#ifdef DEBUG
if (debug_level >= 3) {
file_dbg(&f->file);
dbg_puts(": stopped\n");
}
#endif
}
unsigned
siofile_read(struct file *file, unsigned char *data, unsigned count)
{
struct siofile *f = (struct siofile *)file;
unsigned n;
#ifdef DEBUG
if (f->rtickets == 0) {
file_dbg(&f->file);
dbg_puts(": called with no read tickets\n");
}
#endif
if (count > f->rtickets)
count = f->rtickets;
n = f->started ? sio_read(f->hdl, data, count) : 0;
if (n == 0) {
f->file.state &= ~FILE_ROK;
if (sio_eof(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to read from device\n");
#endif
file_eof(&f->file);
} else {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": reading blocked\n");
}
#endif
}
return 0;
} else {
f->rtickets -= n;
if (f->rtickets == 0) {
f->file.state &= ~FILE_ROK;
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": read tickets exhausted\n");
}
#endif
}
}
return n;
}
unsigned
siofile_write(struct file *file, unsigned char *data, unsigned count)
{
struct siofile *f = (struct siofile *)file;
unsigned n;
#ifdef DEBUG
if (f->wtickets == 0) {
file_dbg(&f->file);
dbg_puts(": called with no write tickets\n");
}
#endif
if (count > f->wtickets)
count = f->wtickets;
n = f->started ? sio_write(f->hdl, data, count) : 0;
if (n == 0) {
f->file.state &= ~FILE_WOK;
if (sio_eof(f->hdl)) {
#ifdef DEBUG
dbg_puts(f->file.name);
dbg_puts(": failed to write on device\n");
#endif
file_hup(&f->file);
} else {
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": writing blocked\n");
}
#endif
}
return 0;
} else {
f->wtickets -= n;
if (f->wtickets == 0) {
f->file.state &= ~FILE_WOK;
#ifdef DEBUG
if (debug_level >= 4) {
file_dbg(&f->file);
dbg_puts(": write tickets exhausted\n");
}
#endif
}
}
return n;
}
int
siofile_nfds(struct file *file)
{
return sio_nfds(((struct siofile *)file)->hdl);
}
int
siofile_pollfd(struct file *file, struct pollfd *pfd, int events)
{
struct siofile *f = (struct siofile *)file;
if (!f->started)
events &= ~(POLLIN | POLLOUT);
return sio_pollfd(((struct siofile *)file)->hdl, pfd, events);
}
int
siofile_revents(struct file *file, struct pollfd *pfd)
{
return sio_revents(((struct siofile *)file)->hdl, pfd);
}
void
siofile_close(struct file *file)
{
struct siofile *f = (struct siofile *)file;
if (f->started)
siofile_stop(&f->file);
return sio_close(((struct siofile *)file)->hdl);
}

32
aucat/siofile.h Normal file
View File

@ -0,0 +1,32 @@
/* $OpenBSD: siofile.h,v 1.5 2010/05/02 11:54:26 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 SIOFILE_H
#define SIOFILE_H
struct fileops;
struct siofile;
struct aparams;
struct aproc;
struct siofile *siofile_new(struct fileops *, char *, unsigned *,
struct aparams *, struct aparams *, unsigned *, unsigned *);
struct aproc *rsio_new(struct file *f);
struct aproc *wsio_new(struct file *f);
extern struct fileops siofile_ops;
#endif /* !defined(SIOFILE_H) */

1745
aucat/sock.c Normal file

File diff suppressed because it is too large Load Diff

75
aucat/sock.h Normal file
View File

@ -0,0 +1,75 @@
/* $OpenBSD: sock.h,v 1.17 2010/06/05 12:45:48 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 "amsg.h"
#include "aparams.h"
#include "pipe.h"
struct opt;
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 wmax; /* max frames we're allowed to write */
unsigned rmax; /* max frames we're allowed to read */
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_HELLO 0 /* waiting for HELLO message */
#define SOCK_INIT 1 /* parameter negotiation */
#define SOCK_START 2 /* filling play buffers */
#define SOCK_READY 3 /* play buffers full */
#define SOCK_RUN 4 /* attached to the mix / sub */
#define SOCK_STOP 5 /* draining rec buffers */
#define SOCK_MIDI 6 /* raw byte stream (midi) */
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 delta; /* pos. change to send */
int startpos; /* initial pos. to send */
int tickpending; /* delta waiting to be transmitted */
int startpending; /* initial delta waiting to be transmitted */
unsigned walign; /* align data packets to this */
unsigned bufsz; /* total buffer size */
unsigned round; /* block size */
unsigned xrun; /* one of AMSG_IGNORE, ... */
int vol; /* requested volume */
int lastvol; /* last volume */
int slot; /* mixer ctl slot number */
struct opt *opt; /* "subdevice" definition */
struct dev *dev; /* actual hardware device */
char who[12]; /* label, mostly for debugging */
};
struct sock *sock_new(struct fileops *, int fd);
extern struct fileops sock_ops;
#endif /* !defined(SOCK_H) */

930
aucat/wav.c Normal file
View File

@ -0,0 +1,930 @@
/*
* 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 <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "abuf.h"
#include "aproc.h"
#include "conf.h"
#include "dev.h"
#include "midi.h"
#include "wav.h"
#include "opt.h"
#ifdef DEBUG
#include "dbg.h"
#endif
short wav_ulawmap[256] = {
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0
};
short wav_alawmap[256] = {
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848
};
/*
* 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
};
int rwav_in(struct aproc *, struct abuf *);
int rwav_out(struct aproc *, struct abuf *);
void rwav_eof(struct aproc *, struct abuf *);
void rwav_hup(struct aproc *, struct abuf *);
void rwav_done(struct aproc *);
struct aproc *rwav_new(struct file *);
int wwav_in(struct aproc *, struct abuf *);
int wwav_out(struct aproc *, struct abuf *);
void wwav_eof(struct aproc *, struct abuf *);
void wwav_hup(struct aproc *, struct abuf *);
void wwav_done(struct aproc *);
struct aproc *wwav_new(struct file *);
void wav_setvol(void *, unsigned);
void wav_startreq(void *);
void wav_stopreq(void *);
void wav_locreq(void *, unsigned);
void wav_quitreq(void *);
struct ctl_ops ctl_wavops = {
wav_setvol,
wav_startreq,
wav_stopreq,
wav_locreq,
wav_quitreq
};
struct aproc_ops rwav_ops = {
"rwav",
rwav_in,
rwav_out,
rfile_eof,
rfile_hup,
NULL, /* newin */
NULL, /* newout */
NULL, /* ipos */
NULL, /* opos */
rwav_done
};
struct aproc_ops wwav_ops = {
"wwav",
wwav_in,
wwav_out,
wfile_eof,
wfile_hup,
NULL, /* newin */
NULL, /* newout */
NULL, /* ipos */
NULL, /* opos */
wwav_done
};
#ifdef DEBUG
/*
* print the given wav structure
*/
void
wav_dbg(struct wav *f)
{
static char *pstates[] = { "ini", "sta", "rdy", "run", "fai" };
struct aproc *midi = f->dev ? f->dev->midi : NULL;
dbg_puts("wav(");
if (f->slot >= 0 && APROC_OK(midi)) {
dbg_puts(midi->u.ctl.slot[f->slot].name);
dbg_putu(midi->u.ctl.slot[f->slot].unit);
} else
dbg_puts(f->pipe.file.name);
dbg_puts(")/");
dbg_puts(pstates[f->pstate]);
}
#endif
/*
* convert ``count'' samples using the given char->short map
*/
void
wav_conv(unsigned char *data, unsigned count, short *map)
{
unsigned i;
unsigned char *iptr;
short *optr;
iptr = data + count;
optr = (short *)data + count;
for (i = count; i > 0; i--) {
--optr;
--iptr;
*optr = map[*iptr];
}
}
/*
* read method of the file structure
*/
unsigned
wav_read(struct file *file, unsigned char *data, unsigned count)
{
struct wav *f = (struct wav *)file;
unsigned n;
if (f->map)
count /= sizeof(short);
if (f->rbytes >= 0 && count > f->rbytes) {
count = f->rbytes; /* file->rbytes fits in count */
if (count == 0) {
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": read complete\n");
}
#endif
if (!f->mmc)
file_eof(&f->pipe.file);
return 0;
}
}
n = pipe_read(file, data, count);
if (n == 0)
return 0;
if (f->rbytes >= 0)
f->rbytes -= n;
if (f->map) {
wav_conv(data, n, f->map);
n *= sizeof(short);
}
return n;
}
/*
* write method of the file structure
*/
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) {
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": write complete\n");
}
#endif
file_hup(&f->pipe.file);
return 0;
}
}
n = pipe_write(file, data, count);
if (f->wbytes >= 0)
f->wbytes -= n;
f->endpos += n;
return n;
}
/*
* close method of the file structure
*/
void
wav_close(struct file *file)
{
struct wav *f = (struct wav *)file;
if (f->mode & MODE_RECMASK) {
pipe_trunc(&f->pipe.file, f->endpos);
if (f->hdr == HDR_WAV) {
wav_writehdr(f->pipe.fd,
&f->hpar,
&f->startpos,
f->endpos - f->startpos);
}
}
pipe_close(file);
if (f->dev) {
dev_unref(f->dev);
f->dev = NULL;
}
}
/*
* attach play (rec) abuf structure to the device and
* switch to the ``RUN'' state; the play abug must not be empty
*/
int
wav_attach(struct wav *f, int force)
{
struct abuf *rbuf = NULL, *wbuf = NULL;
struct dev *d = f->dev;
if (f->mode & MODE_PLAY)
rbuf = LIST_FIRST(&f->pipe.file.rproc->outs);
if (f->mode & MODE_RECMASK)
wbuf = LIST_FIRST(&f->pipe.file.wproc->ins);
f->pstate = WAV_RUN;
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": attaching\n");
}
#endif
/*
* start the device (dev_getpos() and dev_attach() must
* be called on a started device
*/
dev_wakeup(d);
dev_attach(d, f->pipe.file.name, f->mode,
rbuf, &f->hpar, f->join ? d->opar.cmax - d->opar.cmin + 1 : 0,
wbuf, &f->hpar, f->join ? d->ipar.cmax - d->ipar.cmin + 1 : 0,
f->xrun, f->maxweight);
if (f->mode & MODE_PLAY)
dev_setvol(d, rbuf, MIDI_TO_ADATA(f->vol));
return 1;
}
/*
* allocate the play (rec) abuf structure; if this is a
* file to record, then attach it to the device
*
* XXX: buffer size should be larger than dev_bufsz, because
* in non-server mode we don't prime play buffers with
* silence
*/
void
wav_allocbuf(struct wav *f)
{
struct abuf *buf;
struct dev *d = f->dev;
unsigned nfr;
f->pstate = WAV_START;
if (f->mode & MODE_PLAY) {
nfr = 2 * d->bufsz * f->hpar.rate / d->rate;
buf = abuf_new(nfr, &f->hpar);
aproc_setout(f->pipe.file.rproc, buf);
abuf_fill(buf);
if (!ABUF_WOK(buf) || (f->pipe.file.state & FILE_EOF))
f->pstate = WAV_READY;
}
if (f->mode & MODE_RECMASK) {
nfr = 2 * d->bufsz * f->hpar.rate / d->rate;
buf = abuf_new(nfr, &f->hpar);
aproc_setin(f->pipe.file.wproc, buf);
f->pstate = WAV_READY;
}
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": allocating buffers\n");
}
#endif
if (f->pstate == WAV_READY && ctl_slotstart(d->midi, f->slot))
(void)wav_attach(f, 0);
}
/*
* free abuf structure and switch to the ``INIT'' state
*/
void
wav_freebuf(struct wav *f)
{
struct abuf *rbuf = NULL, *wbuf = NULL;
if (f->mode & MODE_PLAY)
rbuf = LIST_FIRST(&f->pipe.file.rproc->outs);
if (f->mode & MODE_RECMASK)
wbuf = LIST_FIRST(&f->pipe.file.wproc->ins);
f->pstate = WAV_INIT;
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": freeing buffers\n");
}
#endif
if (rbuf || wbuf)
ctl_slotstop(f->dev->midi, f->slot);
if (rbuf)
abuf_eof(rbuf);
if (wbuf)
abuf_hup(wbuf);
}
/*
* switch to the ``INIT'' state performing
* necessary actions to reach it
*/
void
wav_reset(struct wav *f)
{
switch (f->pstate) {
case WAV_START:
case WAV_READY:
if (ctl_slotstart(f->dev->midi, f->slot))
(void)wav_attach(f, 1);
/* PASSTHROUGH */
case WAV_RUN:
wav_freebuf(f);
f->pstate = WAV_INIT;
/* PASSTHROUGH */
case WAV_INIT:
case WAV_FAILED:
/* nothing yet */
break;
}
}
/*
* terminate the wav reader/writer
*/
void
wav_exit(struct wav *f)
{
/* XXX: call file_close() ? */
if (f->mode & MODE_PLAY) {
aproc_del(f->pipe.file.rproc);
} else if (f->mode & MODE_RECMASK) {
aproc_del(f->pipe.file.wproc);
}
}
/*
* seek to f->mmcpos and prepare to start, close
* the file on error.
*/
int
wav_seekmmc(struct wav *f)
{
/*
* don't go beyond the end-of-file, if so
* put it in INIT state so it dosn't start
*/
if (f->mmcpos > f->endpos) {
wav_reset(f);
f->pstate = WAV_FAILED;
/*
* don't make other stream wait for us
*/
if (f->slot >= 0)
ctl_slotstart(f->dev->midi, f->slot);
return 0;
}
if (!pipe_seek(&f->pipe.file, f->mmcpos)) {
wav_exit(f);
return 0;
}
if (f->hdr == HDR_WAV)
f->wbytes = WAV_DATAMAX - f->mmcpos;
f->rbytes = f->endpos - f->mmcpos;
wav_reset(f);
wav_allocbuf(f);
return 1;
}
/*
* read samples from the file and possibly start it
*/
int
wav_rdata(struct wav *f)
{
struct aproc *p;
struct abuf *obuf;
p = f->pipe.file.rproc;
obuf = LIST_FIRST(&p->outs);
if (obuf == NULL)
return 0;
if (!ABUF_WOK(obuf) || !(f->pipe.file.state & FILE_ROK))
return 0;
if (!rfile_do(p, obuf->len, NULL))
return 0;
switch (f->pstate) {
case WAV_START:
if (!ABUF_WOK(obuf) || (f->pipe.file.state & FILE_EOF))
f->pstate = WAV_READY;
/* PASSTHROUGH */
case WAV_READY:
if (ctl_slotstart(f->dev->midi, f->slot))
(void)wav_attach(f, 0);
break;
#ifdef DEBUG
case WAV_RUN:
break;
default:
wav_dbg(f);
dbg_puts(": bad state\n");
dbg_panic();
#endif
}
if (f->rbytes == 0 && f->mmc) {
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": trying to restart\n");
}
#endif
if (!wav_seekmmc(f))
return 0;
}
return 1;
}
int
wav_wdata(struct wav *f)
{
struct aproc *p;
struct abuf *ibuf;
if (!(f->pipe.file.state & FILE_WOK))
return 0;
p = f->pipe.file.wproc;
ibuf = LIST_FIRST(&p->ins);
if (ibuf == NULL)
return 0;
if (!ABUF_ROK(ibuf))
return 0;
if (!wfile_do(p, ibuf->len, NULL))
return 0;
return 1;
}
/*
* callback to set the volume, invoked by the MIDI control code
*/
void
wav_setvol(void *arg, unsigned vol)
{
struct wav *f = (struct wav *)arg;
struct abuf *rbuf;
f->vol = vol;
if ((f->mode & MODE_PLAY) && f->pstate == WAV_RUN) {
rbuf = LIST_FIRST(&f->pipe.file.rproc->outs);
dev_setvol(f->dev, rbuf, MIDI_TO_ADATA(vol));
}
}
/*
* callback to start the stream, invoked by the MIDI control code
*/
void
wav_startreq(void *arg)
{
struct wav *f = (struct wav *)arg;
switch (f->pstate) {
case WAV_FAILED:
#ifdef DEBUG
if (debug_level >= 2) {
wav_dbg(f);
dbg_puts(": skipped (failed to seek)\n");
}
#endif
return;
case WAV_READY:
if (f->mode & MODE_RECMASK)
f->endpos = f->startpos;
(void)wav_attach(f, 0);
break;
#ifdef DEBUG
default:
wav_dbg(f);
dbg_puts(": not in READY state\n");
dbg_panic();
break;
#endif
}
}
/*
* callback to stop the stream, invoked by the MIDI control code
*/
void
wav_stopreq(void *arg)
{
struct wav *f = (struct wav *)arg;
#ifdef DEBUG
if (debug_level >= 2) {
wav_dbg(f);
dbg_puts(": stopping");
if (f->pstate != WAV_FAILED && (f->mode & MODE_RECMASK)) {
dbg_puts(", ");
dbg_putu(f->endpos);
dbg_puts(" bytes recorded");
}
dbg_puts("\n");
}
#endif
if (!f->mmc) {
wav_exit(f);
return;
}
(void)wav_seekmmc(f);
}
/*
* callback to relocate the stream, invoked by the MIDI control code
* on a stopped stream
*/
void
wav_locreq(void *arg, unsigned mmc)
{
struct wav *f = (struct wav *)arg;
#ifdef DEBUG
if (f->pstate == WAV_RUN) {
wav_dbg(f);
dbg_puts(": in RUN state\n");
dbg_panic();
}
#endif
f->mmcpos = f->startpos +
((off_t)mmc * f->hpar.rate / MTC_SEC) * aparams_bpf(&f->hpar);
(void)wav_seekmmc(f);
}
/*
* Callback invoked when slot is gone
*/
void
wav_quitreq(void *arg)
{
struct wav *f = (struct wav *)arg;
#ifdef DEBUG
if (debug_level >= 3) {
wav_dbg(f);
dbg_puts(": slot gone\n");
}
#endif
if (f->pstate != WAV_RUN)
wav_exit(f);
}
/*
* create a file reader in the ``INIT'' state
*/
struct wav *
wav_new_in(struct fileops *ops,
struct dev *dev, unsigned mode, char *name, unsigned hdr,
struct aparams *par, unsigned xrun, unsigned volctl, int tr, int join)
{
int fd;
struct wav *f;
if (name != NULL) {
fd = open(name, O_RDONLY | O_NONBLOCK, 0666);
if (fd < 0) {
perror(name);
return NULL;
}
} else {
name = "stdin";
fd = STDIN_FILENO;
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
perror(name);
}
f = (struct wav *)pipe_new(ops, fd, name);
if (f == NULL) {
close(fd);
return NULL;
}
if (!dev_ref(dev)) {
close(fd);
return NULL;
}
if (!(dev->mode & MODE_PLAY)) {
#ifdef DEBUG
dbg_puts(name);
dbg_puts(": device can't play\n");
#endif
close(fd);
dev_unref(dev);
}
f->dev = dev;
if (hdr == HDR_WAV) {
if (!wav_readhdr(f->pipe.fd, par, &f->startpos, &f->rbytes, &f->map)) {
file_del((struct file *)f);
return NULL;
}
f->endpos = f->startpos + f->rbytes;
} else {
f->startpos = 0;
f->endpos = pipe_endpos(&f->pipe.file);
if (f->endpos > 0) {
if (!pipe_seek(&f->pipe.file, 0)) {
file_del((struct file *)f);
return NULL;
}
f->rbytes = f->endpos;
} else
f->rbytes = -1;
f->map = NULL;
}
f->mmc = tr;
f->join = join;
f->mode = mode;
f->hpar = *par;
f->hdr = 0;
f->xrun = xrun;
f->maxweight = MIDI_TO_ADATA(volctl);
f->slot = ctl_slotnew(f->dev->midi, "play", &ctl_wavops, f, 1);
rwav_new((struct file *)f);
wav_allocbuf(f);
#ifdef DEBUG
if (debug_level >= 2) {
dbg_puts(name);
dbg_puts(": playing ");
dbg_putu(f->startpos);
dbg_puts("..");
dbg_putu(f->endpos);
dbg_puts(": playing ");
aparams_dbg(par);
if (f->mmc)
dbg_puts(", mmc");
dbg_puts("\n");
}
#endif
return f;
}
/*
* create a file writer in the ``INIT'' state
*/
struct wav *
wav_new_out(struct fileops *ops,
struct dev *dev, unsigned mode, char *name, unsigned hdr,
struct aparams *par, unsigned xrun, int tr, int join)
{
int fd;
struct wav *f;
if (name == NULL) {
name = "stdout";
fd = STDOUT_FILENO;
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
perror(name);
} else {
fd = open(name,
O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666);
if (fd < 0) {
perror(name);
return NULL;
}
}
f = (struct wav *)pipe_new(ops, fd, name);
if (f == NULL) {
close(fd);
return NULL;
}
if (!dev_ref(dev)) {
close(fd);
return NULL;
}
if (!(dev->mode & MODE_RECMASK)) {
#ifdef DEBUG
dbg_puts(name);
dbg_puts(": device can't record\n");
#endif
close(fd);
dev_unref(dev);
}
f->dev = dev;
if (hdr == HDR_WAV) {
par->le = 1;
par->sig = (par->bits <= 8) ? 0 : 1;
par->bps = (par->bits + 7) / 8;
if (!wav_writehdr(f->pipe.fd, par, &f->startpos, 0)) {
file_del((struct file *)f);
return NULL;
}
f->wbytes = WAV_DATAMAX;
f->endpos = f->startpos;
} else {
f->wbytes = -1;
f->startpos = f->endpos = 0;
}
f->mmc = tr;
f->join = join;
f->mode = mode;
f->hpar = *par;
f->hdr = hdr;
f->xrun = xrun;
f->slot = ctl_slotnew(f->dev->midi, "rec", &ctl_wavops, f, 1);
wwav_new((struct file *)f);
wav_allocbuf(f);
#ifdef DEBUG
if (debug_level >= 2) {
dbg_puts(name);
dbg_puts(": recording ");
aparams_dbg(par);
dbg_puts("\n");
}
#endif
return f;
}
void
rwav_done(struct aproc *p)
{
struct wav *f = (struct wav *)p->u.io.file;
if (f->slot >= 0)
ctl_slotdel(f->dev->midi, f->slot);
f->slot = -1;
rfile_done(p);
}
int
rwav_in(struct aproc *p, struct abuf *ibuf_dummy)
{
struct wav *f = (struct wav *)p->u.io.file;
struct abuf *obuf;
if (!wav_rdata(f))
return 0;
obuf = LIST_FIRST(&p->outs);
if (obuf && f->pstate >= WAV_RUN) {
if (!abuf_flush(obuf))
return 0;
}
return 1;
}
int
rwav_out(struct aproc *p, struct abuf *obuf)
{
struct wav *f = (struct wav *)p->u.io.file;
if (f->pipe.file.state & FILE_RINUSE)
return 0;
for (;;) {
if (!wav_rdata(f))
return 0;
}
return 1;
}
struct aproc *
rwav_new(struct file *f)
{
struct aproc *p;
p = aproc_new(&rwav_ops, f->name);
p->u.io.file = f;
p->u.io.partial = 0;;
f->rproc = p;
return p;
}
void
wwav_done(struct aproc *p)
{
struct wav *f = (struct wav *)p->u.io.file;
if (f->slot >= 0)
ctl_slotdel(f->dev->midi, f->slot);
f->slot = -1;
wfile_done(p);
}
int
wwav_in(struct aproc *p, struct abuf *ibuf)
{
struct wav *f = (struct wav *)p->u.io.file;
if (f->pipe.file.state & FILE_WINUSE)
return 0;
for (;;) {
if (!wav_wdata(f))
return 0;
}
return 1;
}
int
wwav_out(struct aproc *p, struct abuf *obuf_dummy)
{
struct abuf *ibuf = LIST_FIRST(&p->ins);
struct wav *f = (struct wav *)p->u.io.file;
if (ibuf && f->pstate == WAV_RUN) {
if (!abuf_fill(ibuf))
return 0;
}
if (!wav_wdata(f))
return 0;
return 1;
}
struct aproc *
wwav_new(struct file *f)
{
struct aproc *p;
p = aproc_new(&wwav_ops, f->name);
p->u.io.file = f;
p->u.io.partial = 0;;
f->wproc = p;
return p;
}

70
aucat/wav.h Normal file
View File

@ -0,0 +1,70 @@
/* $OpenBSD: wav.h,v 1.11 2010/07/31 08:48:01 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 "aparams.h"
#include "pipe.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 */
unsigned xrun; /* xrun policy */
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 */
off_t startpos; /* beginning of the data chunk */
off_t endpos; /* end of the data chunk */
off_t mmcpos; /* play/rec start point set by MMC */
short *map; /* mulaw/alaw -> s16 conversion table */
int slot; /* mixer ctl slot number */
int mmc; /* use MMC control */
int join; /* join/expand channels */
unsigned vol; /* current volume */
unsigned maxweight; /* dynamic range when vol == 127 */
#define WAV_INIT 0 /* not trying to do anything */
#define WAV_START 1 /* buffer allocated */
#define WAV_READY 2 /* buffer filled enough */
#define WAV_RUN 3 /* buffer attached to device */
#define WAV_FAILED 4 /* failed to seek */
unsigned pstate; /* one of above */
unsigned mode; /* bitmap of MODE_* */
struct dev *dev; /* device playing or recording */
};
extern struct fileops wav_ops;
struct wav *wav_new_in(struct fileops *, struct dev *,
unsigned, char *, unsigned, struct aparams *, unsigned, unsigned, int, int);
struct wav *wav_new_out(struct fileops *, struct dev *,
unsigned, char *, unsigned, struct aparams *, unsigned, int, int);
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 *, off_t *, short **);
int wav_writehdr(int, struct aparams *, off_t *, off_t);
void wav_conv(unsigned char *, unsigned, short *);
extern short wav_ulawmap[256];
extern short wav_alawmap[256];
#endif /* !defined(WAV_H) */

750
libsndio/aucat.c Normal file
View File

@ -0,0 +1,750 @@
/* $OpenBSD: aucat.c,v 1.41 2010/06/05 16:00:52 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "amsg.h"
#include "sndio_priv.h"
struct aucat_hdl {
struct sio_hdl sio;
int fd; /* socket */
struct amsg rmsg, wmsg; /* temporary messages */
size_t wtodo, rtodo; /* bytes to complete the packet */
#define STATE_IDLE 0 /* nothing to do */
#define STATE_MSG 1 /* message being transferred */
#define STATE_DATA 2 /* data being transferred */
unsigned rstate, wstate; /* one of above */
unsigned rbpf, wbpf; /* read and write bytes-per-frame */
int maxwrite; /* latency constraint */
int events; /* events the user requested */
unsigned curvol, reqvol; /* current and requested volume */
int delta; /* some of received deltas */
};
static void aucat_close(struct sio_hdl *);
static int aucat_start(struct sio_hdl *);
static int aucat_stop(struct sio_hdl *);
static int aucat_setpar(struct sio_hdl *, struct sio_par *);
static int aucat_getpar(struct sio_hdl *, struct sio_par *);
static int aucat_getcap(struct sio_hdl *, struct sio_cap *);
static size_t aucat_read(struct sio_hdl *, void *, size_t);
static size_t aucat_write(struct sio_hdl *, const void *, size_t);
static int aucat_pollfd(struct sio_hdl *, struct pollfd *, int);
static int aucat_revents(struct sio_hdl *, struct pollfd *);
static int aucat_setvol(struct sio_hdl *, unsigned);
static void aucat_getvol(struct sio_hdl *);
static struct sio_ops aucat_ops = {
aucat_close,
aucat_setpar,
aucat_getpar,
aucat_getcap,
aucat_write,
aucat_read,
aucat_start,
aucat_stop,
aucat_pollfd,
aucat_revents,
aucat_setvol,
aucat_getvol
};
/*
* read a message, return 0 if blocked
*/
static int
aucat_rmsg(struct aucat_hdl *hdl)
{
ssize_t n;
unsigned char *data;
while (hdl->rtodo > 0) {
data = (unsigned char *)&hdl->rmsg;
data += sizeof(struct amsg) - hdl->rtodo;
while ((n = read(hdl->fd, data, hdl->rtodo)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
hdl->sio.eof = 1;
DPERROR("aucat_rmsg: read");
}
return 0;
}
if (n == 0) {
DPRINTF("aucat_rmsg: eof\n");
hdl->sio.eof = 1;
return 0;
}
hdl->rtodo -= n;
}
return 1;
}
/*
* write a message, return 0 if blocked
*/
static int
aucat_wmsg(struct aucat_hdl *hdl)
{
ssize_t n;
unsigned char *data;
while (hdl->wtodo > 0) {
data = (unsigned char *)&hdl->wmsg;
data += sizeof(struct amsg) - hdl->wtodo;
while ((n = write(hdl->fd, data, hdl->wtodo)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
hdl->sio.eof = 1;
DPERROR("aucat_wmsg: write");
}
return 0;
}
hdl->wtodo -= n;
}
return 1;
}
/*
* execute the next message, return 0 if blocked
*/
static int
aucat_runmsg(struct aucat_hdl *hdl)
{
if (!aucat_rmsg(hdl))
return 0;
switch (hdl->rmsg.cmd) {
case AMSG_DATA:
if (hdl->rmsg.u.data.size == 0 ||
hdl->rmsg.u.data.size % hdl->rbpf) {
DPRINTF("aucat_runmsg: bad data message\n");
hdl->sio.eof = 1;
return 0;
}
hdl->rstate = STATE_DATA;
hdl->rtodo = hdl->rmsg.u.data.size;
break;
case AMSG_POS:
hdl->maxwrite += hdl->rmsg.u.ts.delta * (int)hdl->wbpf;
DPRINTF("aucat: pos = %d, maxwrite = %d\n",
hdl->rmsg.u.ts.delta, hdl->maxwrite);
hdl->delta = hdl->rmsg.u.ts.delta;
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
break;
case AMSG_MOVE:
hdl->maxwrite += hdl->rmsg.u.ts.delta * hdl->wbpf;
hdl->delta += hdl->rmsg.u.ts.delta;
DPRINTF("aucat: move = %d, delta = %d, maxwrite = %d\n",
hdl->rmsg.u.ts.delta, hdl->delta, hdl->maxwrite);
if (hdl->delta >= 0) {
sio_onmove_cb(&hdl->sio, hdl->delta);
hdl->delta = 0;
}
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
break;
case AMSG_SETVOL:
hdl->curvol = hdl->reqvol = hdl->rmsg.u.vol.ctl;
sio_onvol_cb(&hdl->sio, hdl->curvol);
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
break;
case AMSG_GETPAR:
case AMSG_ACK:
hdl->rstate = STATE_IDLE;
hdl->rtodo = 0xdeadbeef;
break;
default:
DPRINTF("aucat_runmsg: unknown message\n");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
struct sio_hdl *
sio_open_aucat(const char *str, unsigned mode, int nbio)
{
extern char *__progname;
int s;
char unit[4], *sep, *opt;
struct aucat_hdl *hdl;
struct sockaddr_un ca;
socklen_t len = sizeof(struct sockaddr_un);
uid_t uid;
sep = strchr(str, '.');
if (sep == NULL) {
opt = "default";
strlcpy(unit, str, sizeof(unit));
} else {
opt = sep + 1;
if (sep - str >= sizeof(unit)) {
DPRINTF("sio_open_aucat: %s: too long\n", str);
return NULL;
}
strlcpy(unit, str, opt - str);
}
DPRINTF("sio_open_aucat: trying %s -> %s.%s\n", str, unit, opt);
uid = geteuid();
if (strchr(str, '/') != NULL)
return NULL;
snprintf(ca.sun_path, sizeof(ca.sun_path),
"/tmp/aucat-%u/softaudio%s", uid, unit);
ca.sun_family = AF_UNIX;
hdl = malloc(sizeof(struct aucat_hdl));
if (hdl == NULL)
return NULL;
sio_create(&hdl->sio, &aucat_ops, mode, nbio);
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0)
goto bad_free;
while (connect(s, (struct sockaddr *)&ca, len) < 0) {
if (errno == EINTR)
continue;
DPERROR("sio_open_aucat: connect");
/* try shared server */
snprintf(ca.sun_path, sizeof(ca.sun_path),
"/tmp/aucat/softaudio%s", unit);
while (connect(s, (struct sockaddr *)&ca, len) < 0) {
if (errno == EINTR)
continue;
DPERROR("sio_open_aucat: connect");
goto bad_connect;
}
break;
}
if (fcntl(s, F_SETFD, FD_CLOEXEC) < 0) {
DPERROR("FD_CLOEXEC");
goto bad_connect;
}
hdl->fd = s;
hdl->rstate = STATE_IDLE;
hdl->rtodo = 0xdeadbeef;
hdl->wstate = STATE_IDLE;
hdl->wtodo = 0xdeadbeef;
hdl->curvol = SIO_MAXVOL;
hdl->reqvol = SIO_MAXVOL;
/*
* say hello to server
*/
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_HELLO;
hdl->wmsg.u.hello.version = AMSG_VERSION;
hdl->wmsg.u.hello.proto = 0;
if (mode & SIO_PLAY)
hdl->wmsg.u.hello.proto |= AMSG_PLAY;
if (mode & SIO_REC)
hdl->wmsg.u.hello.proto |= AMSG_REC;
strlcpy(hdl->wmsg.u.hello.who, __progname,
sizeof(hdl->wmsg.u.hello.who));
strlcpy(hdl->wmsg.u.hello.opt, opt,
sizeof(hdl->wmsg.u.hello.opt));
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
goto bad_connect;
hdl->rtodo = sizeof(struct amsg);
if (!aucat_rmsg(hdl)) {
DPRINTF("sio_open_aucat: mode refused\n");
goto bad_connect;
}
if (hdl->rmsg.cmd != AMSG_ACK) {
DPRINTF("sio_open_aucat: protocol err\n");
goto bad_connect;
}
return (struct sio_hdl *)hdl;
bad_connect:
while (close(s) < 0 && errno == EINTR)
; /* retry */
bad_free:
free(hdl);
return NULL;
}
static void
aucat_close(struct sio_hdl *sh)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
char dummy[1];
if (!hdl->sio.eof && hdl->sio.started)
(void)aucat_stop(&hdl->sio);
if (!hdl->sio.eof) {
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_BYE;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
goto bad_close;
while (read(hdl->fd, dummy, 1) < 0 && errno == EINTR)
; /* nothing */
}
bad_close:
while (close(hdl->fd) < 0 && errno == EINTR)
; /* nothing */
free(hdl);
}
static int
aucat_start(struct sio_hdl *sh)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
struct sio_par par;
/*
* save bpf
*/
if (!sio_getpar(&hdl->sio, &par))
return 0;
hdl->wbpf = par.bps * par.pchan;
hdl->rbpf = par.bps * par.rchan;
hdl->maxwrite = hdl->wbpf * par.bufsz;
hdl->delta = 0;
DPRINTF("aucat: start, maxwrite = %d\n", hdl->maxwrite);
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_START;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
return 0;
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
if (fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) {
DPERROR("aucat_start: fcntl(0)");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static int
aucat_stop(struct sio_hdl *sh)
{
#define ZERO_MAX 0x400
static unsigned char zero[ZERO_MAX];
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
unsigned n, count;
if (fcntl(hdl->fd, F_SETFL, 0) < 0) {
DPERROR("aucat_stop: fcntl(0)");
hdl->sio.eof = 1;
return 0;
}
/*
* complete message or data block in progress
*/
if (hdl->wstate == STATE_MSG) {
if (!aucat_wmsg(hdl))
return 0;
if (hdl->wmsg.cmd == AMSG_DATA) {
hdl->wstate = STATE_DATA;
hdl->wtodo = hdl->wmsg.u.data.size;
} else
hdl->wstate = STATE_IDLE;
}
if (hdl->wstate == STATE_DATA) {
hdl->maxwrite = hdl->wtodo;
while (hdl->wstate != STATE_IDLE) {
count = hdl->wtodo;
if (count > ZERO_MAX)
count = ZERO_MAX;
n = aucat_write(&hdl->sio, zero, count);
if (n == 0)
return 0;
}
}
/*
* send stop message
*/
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_STOP;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
return 0;
if (hdl->rstate == STATE_IDLE) {
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
}
/*
* wait for the STOP ACK
*/
while (hdl->rstate != STATE_IDLE) {
switch (hdl->rstate) {
case STATE_MSG:
if (!aucat_runmsg(hdl))
return 0;
break;
case STATE_DATA:
if (!aucat_read(&hdl->sio, zero, ZERO_MAX))
return 0;
break;
}
}
return 1;
}
static int
aucat_setpar(struct sio_hdl *sh, struct sio_par *par)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_SETPAR;
hdl->wmsg.u.par.bits = par->bits;
hdl->wmsg.u.par.bps = par->bps;
hdl->wmsg.u.par.sig = par->sig;
hdl->wmsg.u.par.le = par->le;
hdl->wmsg.u.par.msb = par->msb;
hdl->wmsg.u.par.rate = par->rate;
hdl->wmsg.u.par.appbufsz = par->appbufsz;
hdl->wmsg.u.par.xrun = par->xrun;
if (hdl->sio.mode & SIO_REC)
hdl->wmsg.u.par.rchan = par->rchan;
if (hdl->sio.mode & SIO_PLAY)
hdl->wmsg.u.par.pchan = par->pchan;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
return 0;
return 1;
}
static int
aucat_getpar(struct sio_hdl *sh, struct sio_par *par)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_GETPAR;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
return 0;
hdl->rtodo = sizeof(struct amsg);
if (!aucat_rmsg(hdl))
return 0;
if (hdl->rmsg.cmd != AMSG_GETPAR) {
DPRINTF("aucat_getpar: protocol err\n");
hdl->sio.eof = 1;
return 0;
}
par->bits = hdl->rmsg.u.par.bits;
par->bps = hdl->rmsg.u.par.bps;
par->sig = hdl->rmsg.u.par.sig;
par->le = hdl->rmsg.u.par.le;
par->msb = hdl->rmsg.u.par.msb;
par->rate = hdl->rmsg.u.par.rate;
par->bufsz = hdl->rmsg.u.par.bufsz;
par->appbufsz = hdl->rmsg.u.par.appbufsz;
par->xrun = hdl->rmsg.u.par.xrun;
par->round = hdl->rmsg.u.par.round;
if (hdl->sio.mode & SIO_PLAY)
par->pchan = hdl->rmsg.u.par.pchan;
if (hdl->sio.mode & SIO_REC)
par->rchan = hdl->rmsg.u.par.rchan;
return 1;
}
static int
aucat_getcap(struct sio_hdl *sh, struct sio_cap *cap)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
unsigned i, bps, le, sig, chan, rindex, rmult;
static unsigned rates[] = { 8000, 11025, 12000 };
AMSG_INIT(&hdl->wmsg);
hdl->wmsg.cmd = AMSG_GETCAP;
hdl->wtodo = sizeof(struct amsg);
if (!aucat_wmsg(hdl))
return 0;
hdl->rtodo = sizeof(struct amsg);
if (!aucat_rmsg(hdl))
return 0;
if (hdl->rmsg.cmd != AMSG_GETCAP) {
DPRINTF("aucat_getcap: protocol err\n");
hdl->sio.eof = 1;
return 0;
}
bps = 1;
sig = le = 0;
cap->confs[0].enc = 0;
for (i = 0; i < SIO_NENC; i++) {
if (bps > 4)
break;
cap->confs[0].enc |= 1 << i;
cap->enc[i].bits = bps == 4 ? 24 : bps * 8;
cap->enc[i].bps = bps;
cap->enc[i].sig = sig ^ 1;
cap->enc[i].le = bps > 1 ? le : SIO_LE_NATIVE;
cap->enc[i].msb = 1;
le++;
if (le > 1 || bps == 1) {
le = 0;
sig++;
}
if (sig > 1 || (le == 0 && bps > 1)) {
sig = 0;
bps++;
}
}
chan = 1;
cap->confs[0].rchan = 0;
for (i = 0; i < SIO_NCHAN; i++) {
if (chan > 16)
break;
cap->confs[0].rchan |= 1 << i;
cap->rchan[i] = chan;
if (chan >= 12) {
chan += 4;
} else if (chan >= 2) {
chan += 2;
} else
chan++;
}
chan = 1;
cap->confs[0].pchan = 0;
for (i = 0; i < SIO_NCHAN; i++) {
if (chan > 16)
break;
cap->confs[0].pchan |= 1 << i;
cap->pchan[i] = chan;
if (chan >= 12) {
chan += 4;
} else if (chan >= 2) {
chan += 2;
} else
chan++;
}
rindex = 0;
rmult = 1;
cap->confs[0].rate = 0;
for (i = 0; i < SIO_NRATE; i++) {
if (rmult >= 32)
break;
cap->rate[i] = rates[rindex] * rmult;
cap->confs[0].rate |= 1 << i;
rindex++;
if (rindex == sizeof(rates) / sizeof(unsigned)) {
rindex = 0;
rmult *= 2;
}
}
cap->nconf = 1;
return 1;
}
static size_t
aucat_read(struct sio_hdl *sh, void *buf, size_t len)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
ssize_t n;
while (hdl->rstate != STATE_DATA) {
switch (hdl->rstate) {
case STATE_MSG:
if (!aucat_runmsg(hdl))
return 0;
break;
case STATE_IDLE:
DPRINTF("aucat_read: unexpected idle state\n");
hdl->sio.eof = 1;
return 0;
}
}
if (len > hdl->rtodo)
len = hdl->rtodo;
while ((n = read(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
hdl->sio.eof = 1;
DPERROR("aucat_read: read");
}
return 0;
}
if (n == 0) {
DPRINTF("aucat_read: eof\n");
hdl->sio.eof = 1;
return 0;
}
hdl->rtodo -= n;
if (hdl->rtodo == 0) {
hdl->rstate = STATE_MSG;
hdl->rtodo = sizeof(struct amsg);
}
DPRINTF("aucat: read: n = %zd\n", n);
return n;
}
static int
aucat_buildmsg(struct aucat_hdl *hdl, size_t len)
{
unsigned sz;
if (hdl->curvol != hdl->reqvol) {
hdl->wstate = STATE_MSG;
hdl->wtodo = sizeof(struct amsg);
hdl->wmsg.cmd = AMSG_SETVOL;
hdl->wmsg.u.vol.ctl = hdl->reqvol;
hdl->curvol = hdl->reqvol;
return 1;
} else if (len > 0 && hdl->maxwrite > 0) {
sz = len;
if (sz > AMSG_DATAMAX)
sz = AMSG_DATAMAX;
if (sz > hdl->maxwrite)
sz = hdl->maxwrite;
sz -= sz % hdl->wbpf;
if (sz == 0)
sz = hdl->wbpf;
hdl->wstate = STATE_MSG;
hdl->wtodo = sizeof(struct amsg);
hdl->wmsg.cmd = AMSG_DATA;
hdl->wmsg.u.data.size = sz;
return 1;
}
return 0;
}
static size_t
aucat_write(struct sio_hdl *sh, const void *buf, size_t len)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
ssize_t n;
while (hdl->wstate != STATE_DATA) {
switch (hdl->wstate) {
case STATE_IDLE:
if (!aucat_buildmsg(hdl, len))
return 0;
/* PASSTHROUGH */
case STATE_MSG:
if (!aucat_wmsg(hdl))
return 0;
if (hdl->wmsg.cmd == AMSG_DATA) {
hdl->wstate = STATE_DATA;
hdl->wtodo = hdl->wmsg.u.data.size;
} else
hdl->wstate = STATE_IDLE;
break;
default:
DPRINTF("aucat_write: bad state\n");
abort();
}
}
if (hdl->maxwrite <= 0)
return 0;
if (len > hdl->maxwrite)
len = hdl->maxwrite;
if (len > hdl->wtodo)
len = hdl->wtodo;
if (len == 0) {
DPRINTF("aucat_write: len == 0\n");
abort();
}
while ((n = write(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
hdl->sio.eof = 1;
DPERROR("aucat_write: write");
}
return 0;
}
hdl->maxwrite -= n;
DPRINTF("aucat: write: n = %zd, maxwrite = %d\n", n, hdl->maxwrite);
hdl->wtodo -= n;
if (hdl->wtodo == 0) {
hdl->wstate = STATE_IDLE;
hdl->wtodo = 0xdeadbeef;
}
return n;
}
static int
aucat_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
hdl->events = events;
if (hdl->maxwrite <= 0)
events &= ~POLLOUT;
if (hdl->rstate == STATE_MSG)
events |= POLLIN;
pfd->fd = hdl->fd;
pfd->events = events;
DPRINTF("aucat: pollfd: %x -> %x\n", hdl->events, pfd->events);
return 1;
}
static int
aucat_revents(struct sio_hdl *sh, struct pollfd *pfd)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
int revents = pfd->revents;
if (revents & POLLIN) {
while (hdl->rstate == STATE_MSG) {
if (!aucat_runmsg(hdl)) {
revents &= ~POLLIN;
break;
}
}
}
if (revents & POLLOUT) {
if (hdl->maxwrite <= 0)
revents &= ~POLLOUT;
}
if (hdl->sio.eof)
return POLLHUP;
DPRINTF("aucat: revents: %x\n", revents & hdl->events);
return revents & (hdl->events | POLLHUP);
}
static int
aucat_setvol(struct sio_hdl *sh, unsigned vol)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
hdl->reqvol = vol;
return 1;
}
static void
aucat_getvol(struct sio_hdl *sh)
{
struct aucat_hdl *hdl = (struct aucat_hdl *)sh;
sio_onvol_cb(&hdl->sio, hdl->reqvol);
return;
}

175
libsndio/mio.c Normal file
View File

@ -0,0 +1,175 @@
/* $OpenBSD: mio.c,v 1.8 2010/04/24 06:15:54 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mio_priv.h"
#ifdef DEBUG
/*
* debug level, -1 means uninitialized
*/
int mio_debug = -1;
#endif
struct mio_hdl *
mio_open(const char *str, unsigned mode, int nbio)
{
static char prefix_midithru[] = "midithru";
static char prefix_rmidi[] = "rmidi";
static char prefix_aucat[] = "aucat";
struct mio_hdl *hdl;
struct stat sb;
char *sep, buf[4];
int len;
#ifdef DEBUG
char *dbg;
if (mio_debug < 0) {
dbg = issetugid() ? NULL : getenv("MIO_DEBUG");
if (!dbg || sscanf(dbg, "%u", &mio_debug) != 1)
mio_debug = 0;
}
#endif
if ((mode & (MIO_OUT | MIO_IN)) == 0)
return NULL;
if (str == NULL && !issetugid())
str = getenv("MIDIDEVICE");
if (str == NULL) {
hdl = mio_open_thru("0", mode, nbio);
if (hdl != NULL)
return hdl;
return mio_open_rmidi("0", mode, nbio);
}
sep = strchr(str, ':');
if (sep == NULL) {
/*
* try legacy "/dev/rmidioxxx" device name
*/
if (stat(str, &sb) < 0 || !S_ISCHR(sb.st_mode)) {
DPRINTF("mio_open: %s: missing ':' separator\n", str);
return NULL;
}
snprintf(buf, sizeof(buf), "%u", minor(sb.st_rdev));
return mio_open_rmidi(buf, mode, nbio);
}
len = sep - str;
if (len == (sizeof(prefix_midithru) - 1) &&
memcmp(str, prefix_midithru, len) == 0)
return mio_open_thru(sep + 1, mode, nbio);
if (len == (sizeof(prefix_aucat) - 1) &&
memcmp(str, prefix_aucat, len) == 0)
return mio_open_aucat(sep + 1, mode, nbio);
if (len == (sizeof(prefix_rmidi) - 1) &&
memcmp(str, prefix_rmidi, len) == 0)
return mio_open_rmidi(sep + 1, mode, nbio);
DPRINTF("mio_open: %s: unknown device type\n", str);
return NULL;
}
void
mio_create(struct mio_hdl *hdl, struct mio_ops *ops, unsigned mode, int nbio)
{
hdl->ops = ops;
hdl->mode = mode;
hdl->nbio = nbio;
hdl->eof = 0;
}
void
mio_close(struct mio_hdl *hdl)
{
hdl->ops->close(hdl);
}
size_t
mio_read(struct mio_hdl *hdl, void *buf, size_t len)
{
if (hdl->eof) {
DPRINTF("mio_read: eof\n");
return 0;
}
if (!(hdl->mode & MIO_IN)) {
DPRINTF("mio_read: not input device\n");
hdl->eof = 1;
return 0;
}
if (len == 0) {
DPRINTF("mio_read: zero length read ignored\n");
return 0;
}
return hdl->ops->read(hdl, buf, len);
}
size_t
mio_write(struct mio_hdl *hdl, const void *buf, size_t len)
{
if (hdl->eof) {
DPRINTF("mio_write: eof\n");
return 0;
}
if (!(hdl->mode & MIO_OUT)) {
DPRINTF("mio_write: not output device\n");
hdl->eof = 1;
return 0;
}
if (len == 0) {
DPRINTF("mio_write: zero length write ignored\n");
return 0;
}
return hdl->ops->write(hdl, buf, len);
}
int
mio_nfds(struct mio_hdl *hdl)
{
return 1;
}
int
mio_pollfd(struct mio_hdl *hdl, struct pollfd *pfd, int events)
{
if (hdl->eof)
return 0;
return hdl->ops->pollfd(hdl, pfd, events);
}
int
mio_revents(struct mio_hdl *hdl, struct pollfd *pfd)
{
if (hdl->eof)
return POLLHUP;
return hdl->ops->revents(hdl, pfd);
}
int
mio_eof(struct mio_hdl *hdl)
{
return hdl->eof;
}

253
libsndio/mio_open.3 Normal file
View File

@ -0,0 +1,253 @@
.\" $OpenBSD: mio_open.3,v 1.3 2009/07/26 12:42:48 ratchov Exp $
.\"
.\" Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: July 26 2009 $
.Dt MIO_OPEN 3
.Os
.Sh NAME
.Nm mio_open ,
.Nm mio_close ,
.Nm mio_read ,
.Nm mio_write ,
.Nm mio_nfds ,
.Nm mio_pollfd ,
.Nm mio_revents ,
.Nm mio_eof
.Nd interface to MIDI streams
.Sh SYNOPSIS
.Fd #include <sndio.h>
.Ft "struct mio_hdl *"
.Fn "mio_open" "const char *name" "unsigned mode" "int nbio_flag"
.Ft "void"
.Fn "mio_close" "struct mio_hdl *hdl"
.Ft "size_t"
.Fn "mio_read" "struct mio_hdl *hdl" "void *addr" "size_t nbytes"
.Ft "size_t"
.Fn "mio_write" "struct mio_hdl *hdl" "const void *addr" "size_t nbytes"
.Ft "int"
.Fn "mio_nfds" "struct mio_hdl *hdl"
.Ft "int"
.Fn "mio_pollfd" "struct mio_hdl *hdl" "struct pollfd *pfd" "int events"
.Ft "int"
.Fn "mio_revents" "struct mio_hdl *hdl" "struct pollfd *pfd"
.Ft "int"
.Fn "mio_eof" "struct mio_hdl *hdl"
.Sh DESCRIPTION
The
.Nm sndio
library allows user processes to access
.Xr midi 4
hardware and
.Xr midicat 1
MIDI thru boxes in a uniform way.
.Ss Opening and closing an MIDI stream
First the application must call the
.Fn mio_open
function to obtain a handle representing the newly created stream;
later it will be passed as the
.Ar hdl
argument of most other functions.
The
.Fn mio_open
function tries to connect to the
.Xr midicat 1
software MIDI thru box or to use the
.Xr midi 4
hardware device.
The
.Ar name
parameter gives the device string discussed in
.Xr sndio 7 .
If the program is using a single device and is providing no device chooser,
it should be set to NULL to allow the user to select it using the
.Ev MIDIDEVICE
environment variable.
.Pp
The
.Ar mode
parameter gives the direction of the stream.
The following are supported:
.Bl -tag -width "MIO_OUT | MIO_IN"
.It MIO_OUT
The stream is output-only; data written to the stream will be sent
to the hardware or other programs.
.It MIO_IN
The stream is input-only; received data from the hardware or
other programs must be read from the stream.
.It MIO_IN | MIO_OUT
The stream sends and receives data.
This mode should be used rather than calling
.Fn mio_open
twice.
.El
.Pp
If the
.Ar nbio_flag
argument is true (i.e. non-zero), then the
.Fn mio_read
and
.Fn mio_write
functions (see below) will be non-blocking.
.Pp
The
.Fn mio_close
function closes the stream and frees all allocated resources
associated with the
.Nm libsndio
handle.
.Ss Sending and receiving data
When input mode is selected, the
.Fn mio_read
function must be called to retrieve received data; it must be called
often enough to ensure that internal buffers will not overrun.
It will store at most
.Ar nbytes
bytes at the
.Ar addr
location and return the number of bytes stored.
Unless the
.Ar nbio_flag
flag is set, it will block until data becomes available and
will return zero only on error.
.Pp
When output mode is selected, the
.Fn mio_write
function can be called to provide data to transmit.
Unless the
.Ar nbio_flag
is set,
.Fn mio_write
will block until the requested amount of data is written.
.Ss Non-blocking mode operation
If the
.Ar nbio_flag
is set on
.Fn mio_open ,
then the
.Fn mio_read
and
.Fn mio_write
functions will never block; if no data is available, they will
return zero immediately.
.Pp
To avoid busy loops when non-blocking mode is used, the
.Xr poll 2
system call can be used to check if data can be
read from or written to the stream.
The
.Fn mio_pollfd
function fills the array
.Ar pfd
of
.Va pollfd
structures, used by
.Xr poll 2 ,
with
.Ar events ;
the latter is a bit-mask of
.Va POLLIN
and
.Va POLLOUT
constants; refer to
.Xr poll 2
for more details.
.Fn mio_pollfd
returns the number of
.Va pollfd
structures filled.
The
.Fn mio_revents
function returns the bit-mask set by
.Xr poll 2
in the
.Va pfd
array of
.Va pollfd
structures.
If
.Va POLLIN
is set,
.Fn mio_read
can be called without blocking.
If
.Va POLLOUT
is set,
.Fn mio_write
can be called without blocking.
POLLHUP may be set if an error occurs, even if
it is not selected with
.Fn mio_pollfd .
.Pp
The
.Fn mio_nfds
function returns the number of
.Va pollfd
structures the caller must preallocate in order to be sure
that
.Fn mio_pollfd
will never overrun.
.Ss Error handling
Errors related to the MIDI subsystem
(like hardware errors or dropped connections) and
programming errors (such as a call to
.Fn mio_read
on a play-only stream) are considered fatal.
Once an error occurs, all functions which take a
.Va mio_hdl
argument, except
.Fn mio_close
and
.Fn mio_eof ,
stop working (i.e. always return 0).
.Pp
The
.Fn mio_eof
function can be used at any stage;
it returns 0 if there's no pending error, and a non-zero
value if there's an error.
.Sh RETURN VALUES
The
.Fn mio_open
function returns the newly created handle on success or NULL
on failure.
The
.Fn mio_pollfd
function returns 1 on success and 0 on failure.
The
.Fn mio_read
and
.Fn mio_write
functions return the number of bytes transferred.
.Sh ENVIRONMENT
.Bl -tag -width "MIO_DEBUGXXX" -compact
.It Ev MIO_DEBUG
The debug level:
may be a value between 0 and 2.
.El
.Sh FILES
.Bl -tag -width "/tmp/aucat-<uid>/midithru0" -compact
.It Pa /tmp/aucat-<uid>/midithru0
Default path to
.Xr midicat 1
socket to connect to.
.It Pa /dev/rmidiX
.Xr midi 4
devices.
.El
.Sh SEE ALSO
.Xr midicat 1 ,
.Xr midi 4 ,
.Xr sndio 7

70
libsndio/mio_priv.h Normal file
View File

@ -0,0 +1,70 @@
/* $OpenBSD: mio_priv.h,v 1.4 2009/08/21 16:48:03 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 MIO_PRIV_H
#define MIO_PRIV_H
#include <sys/param.h>
#include "sndio.h"
#ifdef DEBUG
#define DPRINTF(...) \
do { \
if (mio_debug > 0) \
fprintf(stderr, __VA_ARGS__); \
} while(0)
#define DPERROR(s) \
do { \
if (mio_debug > 0) \
perror(s); \
} while(0)
#else
#define DPRINTF(...) do {} while(0)
#define DPERROR(s) do {} while(0)
#endif
/*
* private ``handle'' structure
*/
struct mio_hdl {
struct mio_ops *ops;
unsigned mode; /* MIO_IN | MIO_OUT */
int nbio; /* true if non-blocking io */
int eof; /* true if error occured */
};
/*
* operations every device should support
*/
struct mio_ops {
void (*close)(struct mio_hdl *);
size_t (*write)(struct mio_hdl *, const void *, size_t);
size_t (*read)(struct mio_hdl *, void *, size_t);
int (*pollfd)(struct mio_hdl *, struct pollfd *, int);
int (*revents)(struct mio_hdl *, struct pollfd *);
};
struct mio_hdl *mio_open_rmidi(const char *, unsigned, int);
struct mio_hdl *mio_open_thru(const char *, unsigned, int);
struct mio_hdl *mio_open_aucat(const char *, unsigned, int);
void mio_create(struct mio_hdl *, struct mio_ops *, unsigned, int);
void mio_destroy(struct mio_hdl *);
#ifdef DEBUG
extern int mio_debug;
#endif
#endif /* !defined(MIO_PRIV_H) */

159
libsndio/mio_rmidi.c Normal file
View File

@ -0,0 +1,159 @@
/* $OpenBSD: mio_rmidi.c,v 1.7 2010/07/21 23:00:16 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/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mio_priv.h"
#define RMIDI_PATH "/dev/rmidi0"
struct rmidi_hdl {
struct mio_hdl mio;
int fd;
};
static void rmidi_close(struct mio_hdl *);
static size_t rmidi_read(struct mio_hdl *, void *, size_t);
static size_t rmidi_write(struct mio_hdl *, const void *, size_t);
static int rmidi_pollfd(struct mio_hdl *, struct pollfd *, int);
static int rmidi_revents(struct mio_hdl *, struct pollfd *);
static struct mio_ops rmidi_ops = {
rmidi_close,
rmidi_write,
rmidi_read,
rmidi_pollfd,
rmidi_revents,
};
struct mio_hdl *
mio_open_rmidi(const char *str, unsigned mode, int nbio)
{
int fd, flags;
struct rmidi_hdl *hdl;
char path[PATH_MAX];
hdl = malloc(sizeof(struct rmidi_hdl));
if (hdl == NULL)
return NULL;
mio_create(&hdl->mio, &rmidi_ops, mode, nbio);
snprintf(path, sizeof(path), "/dev/rmidi%s", str);
if (mode == (MIO_OUT | MIO_IN))
flags = O_RDWR;
else
flags = (mode & MIO_OUT) ? O_WRONLY : O_RDONLY;
if (nbio)
flags |= O_NONBLOCK;
while ((fd = open(path, flags)) < 0) {
if (errno == EINTR)
continue;
DPERROR(path);
goto bad_free;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
DPERROR("FD_CLOEXEC");
goto bad_close;
}
hdl->fd = fd;
return (struct mio_hdl *)hdl;
bad_close:
while (close(hdl->fd) < 0 && errno == EINTR)
; /* retry */
bad_free:
free(hdl);
return NULL;
}
static void
rmidi_close(struct mio_hdl *sh)
{
struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh;
int rc;
do {
rc = close(hdl->fd);
} while (rc < 0 && errno == EINTR);
free(hdl);
}
static size_t
rmidi_read(struct mio_hdl *sh, void *buf, size_t len)
{
struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh;
ssize_t n;
while ((n = read(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("rmidi_read: read");
hdl->mio.eof = 1;
}
return 0;
}
if (n == 0) {
DPRINTF("rmidi_read: eof\n");
hdl->mio.eof = 1;
return 0;
}
return n;
}
static size_t
rmidi_write(struct mio_hdl *sh, const void *buf, size_t len)
{
struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh;
ssize_t n;
while ((n = write(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("rmidi_write: write");
hdl->mio.eof = 1;
}
return 0;
}
return n;
}
static int
rmidi_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events)
{
struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh;
pfd->fd = hdl->fd;
pfd->events = events;
return 1;
}
static int
rmidi_revents(struct mio_hdl *sh, struct pollfd *pfd)
{
return pfd->revents;
}

249
libsndio/mio_thru.c Normal file
View File

@ -0,0 +1,249 @@
/* $OpenBSD: mio_thru.c,v 1.10 2010/07/21 23:00:16 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "amsg.h"
#include "mio_priv.h"
#define THRU_SOCKET "midithru"
struct thru_hdl {
struct mio_hdl mio;
int fd;
};
static void thru_close(struct mio_hdl *);
static size_t thru_read(struct mio_hdl *, void *, size_t);
static size_t thru_write(struct mio_hdl *, const void *, size_t);
static int thru_pollfd(struct mio_hdl *, struct pollfd *, int);
static int thru_revents(struct mio_hdl *, struct pollfd *);
static struct mio_ops thru_ops = {
thru_close,
thru_write,
thru_read,
thru_pollfd,
thru_revents,
};
struct mio_hdl *
thru_open(const char *str, char *sock, unsigned mode, int nbio)
{
extern char *__progname;
char unit[4], *sep, *opt;
struct amsg msg;
int s, n, todo;
unsigned char *data;
struct thru_hdl *hdl;
struct sockaddr_un ca;
socklen_t len = sizeof(struct sockaddr_un);
uid_t uid;
sep = strchr(str, '.');
if (sep == NULL) {
opt = "default";
strlcpy(unit, str, sizeof(unit));
} else {
opt = sep + 1;
if (sep - str >= sizeof(unit)) {
DPRINTF("thru_open: %s: too long\n", str);
return NULL;
}
strlcpy(unit, str, opt - str);
}
DPRINTF("thru_open: trying %s -> %s.%s\n", str, unit, opt);
uid = geteuid();
if (strchr(str, '/') != NULL)
return NULL;
snprintf(ca.sun_path, sizeof(ca.sun_path),
"/tmp/aucat-%u/%s%s", uid, sock, unit);
ca.sun_family = AF_UNIX;
hdl = malloc(sizeof(struct thru_hdl));
if (hdl == NULL)
return NULL;
mio_create(&hdl->mio, &thru_ops, mode, nbio);
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0)
goto bad_free;
while (connect(s, (struct sockaddr *)&ca, len) < 0) {
if (errno == EINTR)
continue;
DPERROR("thru_open: connect");
/* try shared server */
snprintf(ca.sun_path, sizeof(ca.sun_path),
"/tmp/aucat/%s%s", sock, unit);
while (connect(s, (struct sockaddr *)&ca, len) < 0) {
if (errno == EINTR)
continue;
DPERROR("thru_open: connect");
goto bad_connect;
}
break;
}
if (fcntl(s, F_SETFD, FD_CLOEXEC) < 0) {
DPERROR("FD_CLOEXEC");
goto bad_connect;
}
hdl->fd = s;
/*
* say hello to server
*/
AMSG_INIT(&msg);
msg.cmd = AMSG_HELLO;
msg.u.hello.version = AMSG_VERSION;
msg.u.hello.proto = 0;
if (mode & MIO_IN)
msg.u.hello.proto |= AMSG_MIDIIN;
if (mode & MIO_OUT)
msg.u.hello.proto |= AMSG_MIDIOUT;
strlcpy(msg.u.hello.opt, opt, sizeof(msg.u.hello.opt));
strlcpy(msg.u.hello.who, __progname, sizeof(msg.u.hello.who));
n = write(s, &msg, sizeof(struct amsg));
if (n < 0) {
DPERROR("thru_open");
goto bad_connect;
}
if (n != sizeof(struct amsg)) {
DPRINTF("thru_open: short write\n");
goto bad_connect;
}
todo = sizeof(struct amsg);
data = (unsigned char *)&msg;
while (todo > 0) {
n = read(s, data, todo);
if (n < 0) {
DPERROR("thru_open");
goto bad_connect;
}
if (n == 0) {
DPRINTF("thru_open: eof\n");
goto bad_connect;
}
todo -= n;
data += n;
}
if (msg.cmd != AMSG_ACK) {
DPRINTF("thru_open: proto error\n");
goto bad_connect;
}
if (nbio && fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) {
DPERROR("thru_open: fcntl(NONBLOCK)");
goto bad_connect;
}
return (struct mio_hdl *)hdl;
bad_connect:
while (close(s) < 0 && errno == EINTR)
; /* retry */
bad_free:
free(hdl);
return NULL;
}
struct mio_hdl *
mio_open_thru(const char *str, unsigned mode, int nbio)
{
return thru_open(str, "midithru", mode, nbio);
}
struct mio_hdl *
mio_open_aucat(const char *str, unsigned mode, int nbio)
{
return thru_open(str, "softaudio", mode, nbio);
}
static void
thru_close(struct mio_hdl *sh)
{
struct thru_hdl *hdl = (struct thru_hdl *)sh;
int rc;
do {
rc = close(hdl->fd);
} while (rc < 0 && errno == EINTR);
free(hdl);
}
static size_t
thru_read(struct mio_hdl *sh, void *buf, size_t len)
{
struct thru_hdl *hdl = (struct thru_hdl *)sh;
ssize_t n;
while ((n = read(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("thru_read: read");
hdl->mio.eof = 1;
}
return 0;
}
if (n == 0) {
DPRINTF("thru_read: eof\n");
hdl->mio.eof = 1;
return 0;
}
return n;
}
static size_t
thru_write(struct mio_hdl *sh, const void *buf, size_t len)
{
struct thru_hdl *hdl = (struct thru_hdl *)sh;
ssize_t n;
while ((n = write(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("thru_write: write");
hdl->mio.eof = 1;
}
return 0;
}
return n;
}
static int
thru_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events)
{
struct thru_hdl *hdl = (struct thru_hdl *)sh;
pfd->fd = hdl->fd;
pfd->events = events;
return 1;
}
static int
thru_revents(struct mio_hdl *sh, struct pollfd *pfd)
{
return pfd->revents;
}

774
libsndio/sio_open.3 Normal file
View File

@ -0,0 +1,774 @@
.\" $OpenBSD: sio_open.3,v 1.24 2010/04/26 07:11:10 jakemsr Exp $
.\"
.\" Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: April 26 2010 $
.Dt SIO_OPEN 3
.Os
.Sh NAME
.Nm sio_open ,
.Nm sio_close ,
.Nm sio_setpar ,
.Nm sio_getpar ,
.Nm sio_getcap ,
.Nm sio_start ,
.Nm sio_stop ,
.Nm sio_read ,
.Nm sio_write ,
.Nm sio_onmove ,
.Nm sio_nfds ,
.Nm sio_pollfd ,
.Nm sio_revents ,
.Nm sio_eof ,
.Nm sio_setvol ,
.Nm sio_onvol ,
.Nm sio_initpar
.Nd interface to bidirectional audio streams
.Sh SYNOPSIS
.Fd #include <sndio.h>
.Ft "struct sio_hdl *"
.Fn "sio_open" "const char *name" "unsigned mode" "int nbio_flag"
.Ft "void"
.Fn "sio_close" "struct sio_hdl *hdl"
.Ft "int"
.Fn "sio_setpar" "struct sio_hdl *hdl" "struct sio_par *par"
.Ft "int"
.Fn "sio_getpar" "struct sio_hdl *hdl" "struct sio_par *par"
.Ft "int"
.Fn "sio_getcap" "struct sio_hdl *hdl" "struct sio_cap *cap"
.Ft "int"
.Fn "sio_start" "struct sio_hdl *hdl"
.Ft "int"
.Fn "sio_stop" "struct sio_hdl *hdl"
.Ft "size_t"
.Fn "sio_read" "struct sio_hdl *hdl" "void *addr" "size_t nbytes"
.Ft "size_t"
.Fn "sio_write" "struct sio_hdl *hdl" "const void *addr" "size_t nbytes"
.Ft "void"
.Fn "sio_onmove" "struct sio_hdl *hdl" "void (*cb)(void *arg, int delta)" "void *arg"
.Ft "int"
.Fn "sio_nfds" "struct sio_hdl *hdl"
.Ft "int"
.Fn "sio_pollfd" "struct sio_hdl *hdl" "struct pollfd *pfd" "int events"
.Ft "int"
.Fn "sio_revents" "struct sio_hdl *hdl" "struct pollfd *pfd"
.Ft "int"
.Fn "sio_eof" "struct sio_hdl *hdl"
.Ft "int"
.Fn "sio_setvol" "struct sio_hdl *hdl" "unsigned vol"
.Ft "void"
.Fn "sio_onvol" "struct sio_hdl *hdl" "void (*cb)(void *arg, unsigned vol)" "void *arg"
.Ft "void"
.Fn "sio_initpar" "struct sio_par *par"
.\"Fd #define SIO_BPS(bits)
.\"Fd #define SIO_LE_NATIVE
.Sh DESCRIPTION
The
.Nm sndio
library allows user processes to access
.Xr audio 4
hardware and the
.Xr aucat 1
audio server in a uniform way.
It supports full-duplex operation, and when
used with the
.Xr aucat 1
server it supports resampling and format
conversions on the fly.
.Ss Opening and closing an audio stream
First the application must call the
.Fn sio_open
function to obtain a handle representing the newly created stream;
later it will be passed as the
.Ar hdl
argument of most other functions.
The
.Fn sio_open
function first tries to connect to the
.Xr aucat 1
audio server.
If that fails, it then tries to use the
.Xr audio 4
hardware device.
The
.Ar name
parameter gives the device string discussed in
.Xr sndio 7 .
In most cases it should be set to NULL to allow
the user to select it using the
.Ev AUDIODEVICE
environment variable.
.Pp
The
.Ar mode
parameter gives the direction of the stream.
The following are supported:
.Bl -tag -width "SIO_PLAY | SIO_REC"
.It SIO_PLAY
The stream is play-only; data written to the stream will be played
by the hardware.
.It SIO_REC
The stream is record-only; recorded samples by the hardware
must be read from the stream.
.It SIO_PLAY | SIO_REC
The stream plays and records synchronously; this means that
the n-th recorded sample was physically sampled exactly when
the n-th played sample was actually played.
.El
.Pp
If the
.Ar nbio_flag
argument is true (i.e. non-zero), then the
.Fn sio_read
and
.Fn sio_write
functions (see below) will be non-blocking.
.Pp
The
.Fn sio_close
function closes the stream and frees all allocated resources
associated with the
.Nm libsndio
handle.
If the stream is not stopped it will be stopped first as if
.Fn sio_stop
is called.
.Ss Negotiating audio parameters
Audio streams always use linear interleaved encoding.
A frame consists of one sample for each channel in the stream.
For example, a 16-bit stereo stream has two samples per frame
and, typically, two bytes per sample (thus 4 bytes per frame).
.Pp
The set of parameters of the stream that can be controlled
is given by the following structure:
.Bd -literal
struct sio_par {
unsigned bits; /* bits per sample */
unsigned bps; /* bytes per sample */
unsigned sig; /* 1 = signed, 0 = unsigned */
unsigned le; /* 1 = LE, 0 = BE byte order */
unsigned msb; /* 1 = MSB, 0 = LSB aligned */
unsigned rchan; /* number channels for recording */
unsigned pchan; /* number channels for playback */
unsigned rate; /* frames per second */
unsigned appbufsz; /* minimum buffer size without xruns */
unsigned bufsz; /* end-to-end buffer size (read-only) */
unsigned round; /* optimal buffer size divisor */
#define SIO_IGNORE 0 /* pause during xrun */
#define SIO_SYNC 1 /* resync after xrun */
#define SIO_ERROR 2 /* terminate on xrun */
unsigned xrun; /* what to do on overrun/underrun */
};
.Ed
.Pp
The parameters are as follows:
.Bl -tag -width "appbufsz"
.It Va bits
Number of bits per sample: must be between 1 and 32.
.It Va bps
Bytes per samples; if specified, it must be large enough to hold all bits.
By default it's set to the smallest power of two large enough to hold
.Va bits .
.It Va sig
If set (i.e. non-zero) then the samples are signed, else unsigned.
.It Va le
If set, then the byte order is little endian, else big endian;
it's meaningful only if
.Va bps
\*(Gt 1.
.It Va msb
If set, then the
.Va bits
bits are aligned in the packet to the most significant bit
(i.e. lower bits are padded),
else to the least significant bit
(i.e. higher bits are padded);
it's meaningful only if
.Va bits
\*(Lt
.Va bps
* 8.
.It Va rchan
The number of recorded channels; meaningful only if
.Va SIO_REC
mode was selected.
.It Va pchan
The number of played channels; meaningful only if
.Va SIO_PLAY
mode was selected.
.It Va rate
The sampling frequency in Hz.
.It Va bufsz
The maximum number of frames that may be buffered.
This parameter takes into account any buffers, and
can be used for latency calculations.
It is read-only.
.It Va appbufsz
Size of the buffer in frames the application must maintain non empty
(on the play end) or non full (on the record end) by calling
.Fn sio_write
or
.Fn sio_read
fast enough to avoid overrun or underrun conditions.
The audio subsystem may use additional buffering, thus this
parameter cannot be used for latency calculations
.It Va round
Optimal number of frames that the application buffers
should be a multiple of, to get best performance.
Applications can use this parameter to round their block size.
.It Va xrun
The action when the client doesn't accept
recorded data or doesn't provide data to play fast enough;
it can be set to one of the
.Va SIO_IGNORE ,
.Va SIO_SYNC
or
.Va SIO_ERROR
constants.
.El
.Pp
The following approach is recommended to negotiate parameters of the stream:
.Bl -bullet
.It
Initialize a
.Va sio_par
structure using
.Fn sio_initpar
and fill it with
the desired parameters.
If the application supports any value for a given parameter,
then the corresponding parameter should be left unset.
Then call
.Fn sio_setpar
to request the stream to use them.
.It
Call
.Fn sio_getpar
to retrieve the actual parameters of the stream
and check that they are usable.
If they are not, then fail or set up a conversion layer.
Sometimes the rate set can be slightly different to what was requested.
A difference of about 0.5% is not audible and should be ignored.
.El
.Pp
Parameters cannot be changed while the stream is in a waiting state;
if
.Fn sio_start
has been called,
.Fn sio_stop
must be called before parameters can be changed.
.Pp
If
.Nm libsndio
is used to connect to the
.Xr aucat 1
server, a transparent emulation layer will
automatically be set up, and in this case any
parameters are supported.
.Pp
To ease filling the
.Va sio_par
structure, the
following macros can be used:
.Bl -tag -width "SIO_BPS(bits)"
.It "SIO_BPS(bits)"
Return the smallest value for
.Va bps
that is a power of two and that is large enough to
hold
.Va bits .
.It "SIO_LE_NATIVE"
Can be used to set the
.Va le
parameter when native byte order is required.
.El
.Ss Getting stream capabilities
There's no way to get an exhaustive list of all parameter
combinations the stream supports.
Applications that need to have a set of working
parameter combinations in advance can use the
.Fn sio_getcap
function.
.Pp
The
.Va sio_cap
structure contains the list of parameter configurations.
Each configuration contains multiple parameter sets.
The application must examine all configurations, and
choose its parameter set from
.Em one
of the configurations.
Parameters of different configurations
.Em are not
usable together.
.Bd -literal
struct sio_cap {
struct sio_enc { /* allowed encodings */
unsigned bits;
unsigned bps;
unsigned sig;
unsigned le;
unsigned msb;
} enc[SIO_NENC];
unsigned rchan[SIO_NCHAN]; /* allowed rchans */
unsigned pchan[SIO_NCHAN]; /* allowed pchans */
unsigned rate[SIO_NRATE]; /* allowed rates */
unsigned nconf; /* num. of confs[] */
struct sio_conf {
unsigned enc; /* bitmask of enc[] indexes */
unsigned rchan; /* bitmask of rchan[] indexes */
unsigned pchan; /* bitmask of pchan[] indexes */
unsigned rate; /* bitmask of rate[] indexes */
} confs[SIO_NCONF];
};
.Ed
.Pp
The parameters are as follows:
.Bl -tag -width "rchan[SIO_NCHAN]"
.It Va enc[SIO_NENC]
Array of supported encodings.
The tuple of
.Va bits ,
.Va bps ,
.Va sig ,
.Va le
and
.Va msb
parameters are usable in the corresponding parameters
of the
.Va sio_par
structure.
.It Va rchan[SIO_NCHAN]
Array of supported channel numbers for recording usable
in the
.Va sio_par
structure.
.It Va pchan[SIO_NCHAN]
Array of supported channel numbers for playback usable
in the
.Va sio_par
structure.
.It Va rate[SIO_NRATE]
Array of supported sample rates usable
in the
.Va sio_par
structure.
.It Va nconf
Number of different configurations available, i.e. number
of filled elements of the
.Va confs[]
array.
.It Va confs[SIO_NCONF]
Array of available configurations.
Each configuration contains bitmasks indicating which
elements of the above parameter arrays are valid for the
given configuration.
For instance, if the second bit of
.Va rate
is set, in the
.Va sio_conf
structure, then the second element of the
.Va rate[SIO_NRATE]
array of the
.Va sio_cap
structure is valid for this configuration.
.El
.Ss Starting and stopping the stream
The
.Fn sio_start
function puts the stream in a waiting state:
the stream will wait for playback data to be provided
(using the
.Fn sio_write
function).
Once enough data is queued to ensure that play buffers
will not underrun, actual playback is started automatically.
If record mode only is selected, then recording starts
immediately.
In full-duplex mode, playback and recording will start
synchronously as soon as enough data to play is available.
.Pp
The
.Fn sio_stop
function stops playback and recording and puts the audio subsystem
in the same state as after
.Fn sio_open
is called.
Samples in the play buffers are not discarded, and will continue to
be played after
.Fn sio_stop
returns.
If samples to play are queued but playback hasn't started yet
then playback is forced immediately; the stream will actually stop
once the buffer is drained.
.Ss Playing and recording
When record mode is selected, the
.Fn sio_read
function must be called to retrieve recorded data; it must be called
often enough to ensure that internal buffers will not overrun.
It will store at most
.Ar nbytes
bytes at the
.Ar addr
location and return the number of bytes stored.
Unless the
.Ar nbio_flag
flag is set, it will block until data becomes available and
will return zero only on error.
.Pp
Similarly, when play mode is selected, the
.Fn sio_write
function must be called to provide data to play.
Unless the
.Ar nbio_flag
is set,
.Fn sio_write
will block until the requested amount of data is written.
.Ss Non-blocking mode operation
If the
.Ar nbio_flag
is set on
.Fn sio_open ,
then the
.Fn sio_read
and
.Fn sio_write
functions will never block; if no data is available, they will
return zero immediately.
.Pp
Note that non-blocking mode must be used on bidirectional streams.
For instance, if recording is blocked in
.Fn sio_read
then, even if samples can be played,
.Fn sio_write
cannot be called and the play buffers may underrun.
.Pp
To avoid busy loops when non-blocking mode is used, the
.Xr poll 2
system call can be used to check if data can be
read from or written to the stream.
The
.Fn sio_pollfd
function fills the array
.Ar pfd
of
.Va pollfd
structures, used by
.Xr poll 2 ,
with
.Ar events ;
the latter is a bit-mask of
.Va POLLIN
and
.Va POLLOUT
constants; refer to
.Xr poll 2
for more details.
.Fn sio_pollfd
returns the number of
.Va pollfd
structures filled.
The
.Fn sio_revents
function returns the bit-mask set by
.Xr poll 2
in the
.Va pfd
array of
.Va pollfd
structures.
If
.Va POLLIN
is set,
.Fn sio_read
can be called without blocking.
If
.Va POLLOUT
is set,
.Fn sio_write
can be called without blocking.
POLLHUP may be set if an error occurs, even if
it is not selected with
.Fn sio_pollfd .
.Pp
The
.Fn sio_nfds
function returns the number of
.Va pollfd
structures the caller must preallocate in order to be sure
that
.Fn sio_pollfd
will never overrun.
.Ss Synchronizing non-audio events to the stream in real-time
In order to perform actions at precise positions of the stream,
such as displaying video in sync with the audio stream,
the application must be notified in real-time of the exact
position in the stream the hardware is processing.
.Pp
The
.Fn sio_onmove
function can be used to register the
.Va cb
callback function that will be called by the
.Nm sndio
library at regular time intervals to notify the application
the position in the stream changed.
The
.Va delta
argument contains the number of frames the hardware moved in the stream
since the last call of
.Va cb .
When the stream starts, the callback is invoked with a zero
.Va delta
argument.
The value of the
.Va arg
pointer is passed to the callback and can contain anything.
.Pp
If desired, the application can maintain the current position by
starting from zero (when
.Fn sio_start
is called) and adding to the current position
.Va delta
every time
.Fn cb
is called.
.Ss Measuring the latency and buffers usage
The playback latency is the delay it will take for the
frame just written to become audible, expressed in number of frames.
The exact playback
latency can be obtained by subtracting the current position
from the number of frames written.
Once playback is actually started (first sample audible)
the latency will never exceed the
.Va bufsz
parameter (see the sections above).
There's a phase during which
.Fn sio_write
only queues data;
once there's enough data, actual playback starts.
During this phase talking about latency is meaningless.
.Pp
In any cases, at most
.Va bufsz
frames are buffered.
This value takes into account all buffers,
including device, kernel and socket buffers.
The number of frames stored is equal to the number of frames
written minus the current position.
.Pp
The recording latency is obtained similarly, by subtracting
the number of frames read from the current position.
.Pp
It is strongly discouraged to use the latency and/or the buffer
usage for anything but monitoring.
Especially, note that
.Fn sio_write
might block even if there is buffer space left;
using the buffer usage to guess if
.Fn sio_write
would block is false and leads to unreliable programs \(en consider using
.Xr poll 2
for this.
.Ss Handling buffer overruns and underruns
When the application cannot accept recorded data fast enough,
the record buffer (of size
.Va appbufsz )
might overrun; in this case recorded data is lost.
Similarly if the application cannot provide data to play
fast enough, the play buffer underruns and silence is played
instead.
Depending on the
.Va xrun
parameter of the
.Va sio_par
structure, the audio subsystem will behave as follows:
.Bl -tag -width "SIO_IGNORE"
.It SIO_IGNORE
The stream is paused during overruns and underruns,
thus the current position (obtained through
.Va sio_onmove )
stops being incremented.
Once the overrun and/or underrun condition is gone, the stream is unpaused;
play and record are always kept in sync.
With this mode, the application cannot notice
underruns and/or overruns and shouldn't care about them.
.Pp
This mode is the default.
It's suitable for applications,
like audio players and telephony, where time
is not important and overruns or underruns are not short.
.It SIO_SYNC
If the play buffer underruns, then silence is played,
but in order to reach the right position in time,
the same amount of written samples will be
discarded once the application is unblocked.
Similarly, if the record buffer overruns, then
samples are discarded, but the same amount of silence will be
returned later.
The current position (obtained through
.Va sio_onmove )
is still incremented.
When the play buffer underruns the play latency might become negative;
when the record buffer overruns, the record latency might become
larger than
.Va bufsz .
.Pp
This mode is suitable for applications, like music production,
where time is important and where underruns or overruns are
short and rare.
.It SIO_ERROR
With this mode, on the first play buffer underrun or
record buffer overrun, the stream is terminated and
no other function than
.Fn sio_close
will succeed.
.Pp
This mode is mostly useful for testing; portable
applications shouldn't depend on it, since it's not available
on other systems.
.El
.Ss Controlling the volume
The
.Fn sio_setvol
function can be used to set playback attenuation.
The
.Va vol
parameter takes a value between 0 (maximum attenuation)
and
.Dv SIO_MAXVOL
(no attenuation).
It specifies the weight the audio subsystem will
give to this stream.
It is not meant to control hardware parameters like
speaker gain; the
.Xr mixerctl 1
interface should be used for that purpose instead.
.Pp
An application can use the
.Fn sio_onvol
function to register a callback function that
will be called each time the volume is changed,
including when
.Fn sio_setvol
is used.
The callback is always invoked when
.Fn sio_onvol
is called in order to provide the initial volume.
An application can safely assume that once
.Fn sio_onvol
returns, the callback has already been invoked and thus
the current volume is available.
.Ss Error handling
Errors related to the audio subsystem
(like hardware errors, dropped connections) and
programming errors (e.g. call to
.Fn sio_read
on a play-only stream) are considered fatal.
Once an error occurs, all functions taking a
.Va sio_hdl
argument, except
.Fn sio_close
and
.Fn sio_eof ,
stop working (i.e. always return 0).
.Pp
The
.Fn sio_eof
function can be used at any stage;
it returns 0 if there's no pending error, and a non-zero
value if there's an error.
.Sh RETURN VALUES
The
.Fn sio_open
function returns the newly created handle on success or NULL
on failure.
The
.Fn sio_setpar ,
.Fn sio_getpar ,
.Fn sio_getcap ,
.Fn sio_start ,
.Fn sio_stop ,
.Fn sio_pollfd
and
.Fn sio_setvol
functions return 1 on success and 0 on failure.
The
.Fn sio_read
and
.Fn sio_write
functions return the number of bytes transferred.
.Sh ENVIRONMENT
.Bl -tag -width "AUDIODEVICEXXX" -compact
.It Ev AUDIODEVICE
Device to use if
.Fn sio_open
is called with a NULL
.Va name
argument.
.It Ev SIO_DEBUG
The debug level:
may be a value between 0 and 2.
.El
.Sh FILES
.Bl -tag -width "/tmp/aucat-<uid>/softaudio0" -compact
.It Pa /tmp/aucat-<uid>/softaudio0
Default path to
.Xr aucat 1
socket to connect to.
.It Pa /dev/audio
Default
.Xr audio 4
device to use.
.El
.\".Sh EXAMPLES
.\".Bd -literal -offset indent
.\".Ed
.Sh SEE ALSO
.Xr aucat 1 ,
.Xr audio 4 ,
.Xr sndio 7 ,
.Xr audio 9
.Sh BUGS
The
.Xr audio 4
driver cannot drain playback buffers in the background, thus if
.Nm libsndio
is used to directly access an
.Xr audio 4
device,
the
.Fn sio_stop
function will stop playback immediately.
.Pp
The
.Xr aucat 1
server doesn't implement flow control (for performance reasons).
If the application doesn't consume recorded data fast enough then
.Dq "control messages"
are delayed and consequently
.Va sio_onmove
callback or volume changes may be delayed.
.Pp
The
.Fn sio_open ,
.Fn sio_setpar ,
.Fn sio_getpar ,
.Fn sio_getcap ,
.Fn sio_start
and
.Fn sio_stop
functions may block for a very short period of time, thus they should
be avoided in code sections where blocking is not desirable.

180
libsndio/sndio.7 Normal file
View File

@ -0,0 +1,180 @@
.\" $OpenBSD: sndio.7,v 1.2 2009/08/21 16:48:03 ratchov Exp $
.\"
.\" Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: August 21 2009 $
.Dt SNDIO 7
.Os
.Sh NAME
.Nm sndio
.Nd interface to audio and MIDI
.Sh DESCRIPTION
The
.Nm sndio
audio and MIDI system provides access to audio and MIDI hardware and
to services provided by
.Xr aucat 1
and
.Xr midicat 1 ,
summarized below.
.Pp
Hardware
.Xr audio 4
devices correspond to peripherals.
Only one application may use any device at a given time.
Generally a limited number of encodings, sample rates and channel numbers are
supported by the hardware, which may not meet the requirements of
audio programs.
.Pp
To overcome hardware limitations and to allow multiple applications
to share the hardware,
.Xr aucat 1
can be used.
It exposes one or more software subdevices backed by the underlying hardware,
while doing all necessary conversions on the fly.
It can mix multiple streams or split the hardware into
multiple subdevices, to allow programs to use the hardware
concurently.
.Pp
Hardware MIDI ports correspond to serial connectors provided by the
.Xr midi 4
driver.
They are typically used to access MIDI hardware (synthesizers, keyboards,
control surfaces, etc.), but they do not allow applications to exchange
information using the MIDI protocol.
.Pp
Software MIDI thru boxes allow one application to send MIDI data to other
applications connected to the thru box (for instance a software sequencer
can send events to multiple software synthesizers).
There's no hardware involved: thru boxes are created by
.Xr midicat 1 .
.Pp
Additionally,
.Xr aucat 1
exposes a MIDI device used to control and monitor audio streams
in real time using MIDI.
.Sh DEVICE NAMES
From the user's perspective every audio interface, MIDI port,
.Xr aucat 1
or
.Xr midicat 1
service has a name of the form:
.Bd -literal -offset center
type:unit[.option]
.Ed
.Pp
This information is used by audio and MIDI applications to determine
how to access the audio or MIDI device or service.
.Bl -tag -width "option"
.It Pa type
The type of the audio or MIDI device.
Possible values for audio devices are
.Pa aucat
and
.Pa sun ,
corresponding to
.Xr aucat 1
sockets and hardware
.Xr audio 4
devices.
Possible values for MIDI devices are
.Pa midithru ,
.Pa rmidi ,
and
.Pa aucat
corresponding to
.Xr midicat 1
software MIDI thru boxes, hardware
.Xr midi 4
ports and
.Xr aucat 1
control through MIDI respectively.
.It Pa unit
For hardware audio or MIDI devices, this corresponds to
the character device minor number.
For audio or MIDI devices created with
.Xr aucat 1
or
.Xr midicat 1
it corresponds to the server
.Em unit
number, typically 0.
.It Pa option
Corresponds to the profile string registered using the
.Fl s
option of
.Xr aucat 1 .
Only meaningful for
.Pa aucat
device types.
.El
.Pp
For example:
.Pp
.Bl -tag -width "aucat:0.rear" -offset 3n -compact
.It Pa sun:0
First hardware audio device.
.It Pa aucat:0
Default audio device of the first
.Xr aucat 1
audio server.
.It Pa aucat:0.rear
First
.Xr aucat 1
server;
device registered with
.Fl s Fa rear .
.It Pa rmidi:5
Hardware MIDI port number 5.
.It Pa midithru:0
First software MIDI thru box created with
.Xr midicat 1 .
.It Pa aucat:0
MIDI port controlling the first
.Xr aucat 1
audio server.
.El
.Sh ENVIRONMENT
.Bl -tag -width "AUDIODEVICEXXX" -compact
.It Ev AUDIODEVICE
Audio device to use if the application provides
no device chooser.
.It Ev MIDIDEVICE
MIDI port to use if the application provides
no MIDI port chooser.
.El
.Pp
Environment variables are ignored by programs
with the set-user-ID or set-group-ID bits set.
.Sh FILES
.Bl -tag -width "/tmp/aucat-xxx/softaudioNXXX" -compact
.It Pa /dev/audioN
Audio devices.
.It Pa /dev/rmidiN
MIDI ports.
.It Pa /tmp/aucat-xxx/softaudioN
Audio devices provided by
.Xr aucat 1 .
.It Pa /tmp/aucat-xxx/midithruN
MIDI thru boxes provided by
.Xr midicat 1 .
.El
.Sh SEE ALSO
.Xr aucat 1 ,
.Xr midicat 1 ,
.Xr mio_open 3 ,
.Xr sio_open 3 ,
.Xr audio 4 ,
.Xr midi 4

599
libsndio/sndio.c Normal file
View File

@ -0,0 +1,599 @@
/* $OpenBSD: sndio.c,v 1.25 2010/04/24 06:15:54 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sndio_priv.h"
#define SIO_PAR_MAGIC 0x83b905a4
#ifdef DEBUG
/*
* debug level, -1 means uninitialized
*/
int sio_debug = -1;
#endif
void
sio_initpar(struct sio_par *par)
{
memset(par, 0xff, sizeof(struct sio_par));
par->__magic = SIO_PAR_MAGIC;
}
/*
* Generate a string corresponding to the encoding in par,
* return the length of the resulting string
*/
int
sio_enctostr(struct sio_par *par, char *ostr)
{
char *p = ostr;
*p++ = par->sig ? 's' : 'u';
if (par->bits > 9)
*p++ = '0' + par->bits / 10;
*p++ = '0' + par->bits % 10;
if (par->bps > 1) {
*p++ = par->le ? 'l' : 'b';
*p++ = 'e';
if (par->bps != SIO_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 ...
* Return the number of bytes consumed
*/
int
sio_strtoenc(struct sio_par *par, char *istr)
{
char *p = istr;
int i, sig, bits, le, bps, msb;
#define IS_SEP(c) \
(((c) < 'a' || (c) > 'z') && \
((c) < 'A' || (c) > 'Z') && \
((c) < '0' || (c) > '9'))
/*
* get signedness
*/
if (*p == 's') {
sig = 1;
} else if (*p == 'u') {
sig = 0;
} else
return 0;
p++;
/*
* get number of bits per sample
*/
bits = 0;
for (i = 0; i < 2; i++) {
if (*p < '0' || *p > '9')
break;
bits = (bits * 10) + *p - '0';
p++;
}
if (bits < 1 || bits > 32)
return 0;
bps = SIO_BPS(bits);
le = SIO_LE_NATIVE;
msb = 1;
/*
* get (optional) 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 (optional) number of bytes
*/
if (*p >= '1' && *p <= '4') {
bps = *p - '0';
if (bps * 8 < bits)
return 0;
p++;
/*
* get (optional) alignment
*/
if (p[0] == 'm' && p[1] == 's' && p[2] == 'b') {
msb = 1;
p += 3;
} else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b') {
msb = 0;
p += 3;
} else if (IS_SEP(*p)) {
goto done;
} else
return 0;
} else if (!IS_SEP(*p))
return 0;
done:
par->msb = msb;
par->sig = sig;
par->bits = bits;
par->bps = bps;
par->le = le;
return p - istr;
}
struct sio_hdl *
sio_open(const char *str, unsigned mode, int nbio)
{
static char prefix_aucat[] = "aucat";
static char prefix_sun[] = "sun";
struct sio_hdl *hdl;
struct stat sb;
char *sep, buf[NAME_MAX];
int len;
#ifdef DEBUG
char *dbg;
if (sio_debug < 0) {
dbg = issetugid() ? NULL : getenv("SIO_DEBUG");
if (!dbg || sscanf(dbg, "%u", &sio_debug) != 1)
sio_debug = 0;
}
#endif
if ((mode & (SIO_PLAY | SIO_REC)) == 0)
return NULL;
if (str == NULL && !issetugid())
str = getenv("AUDIODEVICE");
if (str == NULL) {
hdl = sio_open_aucat("0", mode, nbio);
if (hdl != NULL)
return hdl;
if (stat("/dev/audio", &sb) == 0 && S_ISCHR(sb.st_mode)) {
snprintf(buf, sizeof(buf), "%u",
minor(sb.st_rdev) & 0xf);
} else
strlcpy(buf, "0", sizeof(buf));
return sio_open_sun(buf, mode, nbio);
}
sep = strchr(str, ':');
if (sep == NULL) {
/*
* try legacy "/dev/audioxxx" or ``socket'' device name
*/
if (stat(str, &sb) < 0 || !S_ISCHR(sb.st_mode)) {
snprintf(buf, sizeof(buf), "0.%s", str);
return sio_open_aucat(buf, mode, nbio);
}
snprintf(buf, sizeof(buf), "%u", minor(sb.st_rdev) & 0xf);
return sio_open_sun(buf, mode, nbio);
}
len = sep - str;
if (len == (sizeof(prefix_aucat) - 1) &&
memcmp(str, prefix_aucat, len) == 0)
return sio_open_aucat(sep + 1, mode, nbio);
if (len == (sizeof(prefix_sun) - 1) &&
memcmp(str, prefix_sun, len) == 0)
return sio_open_sun(sep + 1, mode, nbio);
DPRINTF("sio_open: %s: unknown device type\n", str);
return NULL;
}
void
sio_create(struct sio_hdl *hdl, struct sio_ops *ops, unsigned mode, int nbio)
{
hdl->ops = ops;
hdl->mode = mode;
hdl->nbio = nbio;
hdl->started = 0;
hdl->eof = 0;
hdl->move_cb = NULL;
hdl->vol_cb = NULL;
}
void
sio_close(struct sio_hdl *hdl)
{
hdl->ops->close(hdl);
}
int
sio_start(struct sio_hdl *hdl)
{
if (hdl->eof) {
DPRINTF("sio_start: eof\n");
return 0;
}
if (hdl->started) {
DPRINTF("sio_start: already started\n");
hdl->eof = 1;
return 0;
}
#ifdef DEBUG
if (!sio_getpar(hdl, &hdl->par))
return 0;
hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->realpos = 0;
gettimeofday(&hdl->tv, NULL);
#endif
if (!hdl->ops->start(hdl))
return 0;
hdl->started = 1;
return 1;
}
int
sio_stop(struct sio_hdl *hdl)
{
if (hdl->eof) {
DPRINTF("sio_stop: eof\n");
return 0;
}
if (!hdl->started) {
DPRINTF("sio_stop: not started\n");
hdl->eof = 1;
return 0;
}
if (!hdl->ops->stop(hdl))
return 0;
#ifdef DEBUG
DPRINTF("libsndio: polls: %llu, written = %llu, read: %llu\n",
hdl->pollcnt, hdl->wcnt, hdl->rcnt);
#endif
hdl->started = 0;
return 1;
}
int
sio_setpar(struct sio_hdl *hdl, struct sio_par *par)
{
if (hdl->eof) {
DPRINTF("sio_setpar: eof\n");
return 0;
}
if (par->__magic != SIO_PAR_MAGIC) {
DPRINTF("sio_setpar: use of uninitialized sio_par structure\n");
hdl->eof = 1;
return 0;
}
if (hdl->started) {
DPRINTF("sio_setpar: already started\n");
hdl->eof = 1;
return 0;
}
if (par->bufsz != ~0U) {
DPRINTF("sio_setpar: setting bufsz is deprecated\n");
par->appbufsz = par->bufsz;
}
if (par->rate != ~0U && par->appbufsz == ~0U)
par->appbufsz = par->rate * 200 / 1000;
return hdl->ops->setpar(hdl, par);
}
int
sio_getpar(struct sio_hdl *hdl, struct sio_par *par)
{
if (hdl->eof) {
DPRINTF("sio_getpar: eof\n");
return 0;
}
if (hdl->started) {
DPRINTF("sio_getpar: already started\n");
hdl->eof = 1;
return 0;
}
if (!hdl->ops->getpar(hdl, par)) {
par->__magic = 0;
return 0;
}
par->__magic = 0;
return 1;
}
int
sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap)
{
if (hdl->eof) {
DPRINTF("sio_getcap: eof\n");
return 0;
}
if (hdl->started) {
DPRINTF("sio_getcap: already started\n");
hdl->eof = 1;
return 0;
}
return hdl->ops->getcap(hdl, cap);
}
static int
sio_psleep(struct sio_hdl *hdl, int event)
{
struct pollfd pfd;
int revents;
for (;;) {
sio_pollfd(hdl, &pfd, event);
while (poll(&pfd, 1, -1) < 0) {
if (errno == EINTR)
continue;
DPERROR("sio_psleep: poll");
hdl->eof = 1;
return 0;
}
revents = sio_revents(hdl, &pfd);
if (revents & POLLHUP) {
DPRINTF("sio_psleep: hang-up\n");
return 0;
}
if (revents & event)
break;
}
return 1;
}
size_t
sio_read(struct sio_hdl *hdl, void *buf, size_t len)
{
unsigned n;
char *data = buf;
size_t todo = len;
if (hdl->eof) {
DPRINTF("sio_read: eof\n");
return 0;
}
if (!hdl->started || !(hdl->mode & SIO_REC)) {
DPRINTF("sio_read: recording not started\n");
hdl->eof = 1;
return 0;
}
if (todo == 0) {
DPRINTF("sio_read: zero length read ignored\n");
return 0;
}
while (todo > 0) {
n = hdl->ops->read(hdl, data, todo);
if (n == 0) {
if (hdl->nbio || hdl->eof || todo < len)
break;
if (!sio_psleep(hdl, POLLIN))
break;
continue;
}
data += n;
todo -= n;
#ifdef DEBUG
hdl->rcnt += n;
#endif
}
return len - todo;
}
size_t
sio_write(struct sio_hdl *hdl, const void *buf, size_t len)
{
unsigned n;
const unsigned char *data = buf;
size_t todo = len;
#ifdef DEBUG
struct timeval tv0, tv1, dtv;
unsigned us;
if (sio_debug >= 2)
gettimeofday(&tv0, NULL);
#endif
if (hdl->eof) {
DPRINTF("sio_write: eof\n");
return 0;
}
if (!hdl->started || !(hdl->mode & SIO_PLAY)) {
DPRINTF("sio_write: playback not started\n");
hdl->eof = 1;
return 0;
}
if (todo == 0) {
DPRINTF("sio_write: zero length write ignored\n");
return 0;
}
while (todo > 0) {
n = hdl->ops->write(hdl, data, todo);
if (n == 0) {
if (hdl->nbio || hdl->eof)
break;
if (!sio_psleep(hdl, POLLOUT))
break;
continue;
}
data += n;
todo -= n;
#ifdef DEBUG
hdl->wcnt += n;
#endif
}
#ifdef DEBUG
if (sio_debug >= 2) {
gettimeofday(&tv1, NULL);
timersub(&tv0, &hdl->tv, &dtv);
DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
timersub(&tv1, &tv0, &dtv);
us = dtv.tv_sec * 1000000 + dtv.tv_usec;
DPRINTF(
"sio_write: wrote %d bytes of %d in %uus\n",
(int)(len - todo), (int)len, us);
}
#endif
return len - todo;
}
int
sio_nfds(struct sio_hdl *hdl)
{
/*
* In the future we might use larger values
*/
return 1;
}
int
sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events)
{
if (hdl->eof)
return 0;
if (!hdl->started)
events = 0;
return hdl->ops->pollfd(hdl, pfd, events);
}
int
sio_revents(struct sio_hdl *hdl, struct pollfd *pfd)
{
int revents;
#ifdef DEBUG
struct timeval tv0, tv1, dtv;
unsigned us;
if (sio_debug >= 2)
gettimeofday(&tv0, NULL);
#endif
if (hdl->eof)
return POLLHUP;
#ifdef DEBUG
hdl->pollcnt++;
#endif
revents = hdl->ops->revents(hdl, pfd);
if (!hdl->started)
return revents & POLLHUP;
#ifdef DEBUG
if (sio_debug >= 2) {
gettimeofday(&tv1, NULL);
timersub(&tv0, &hdl->tv, &dtv);
DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
timersub(&tv1, &tv0, &dtv);
us = dtv.tv_sec * 1000000 + dtv.tv_usec;
DPRINTF("sio_revents: revents = 0x%x, complete in %uus\n",
revents, us);
}
#endif
return revents;
}
int
sio_eof(struct sio_hdl *hdl)
{
return hdl->eof;
}
void
sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr)
{
if (hdl->started) {
DPRINTF("sio_onmove: already started\n");
hdl->eof = 1;
return;
}
hdl->move_cb = cb;
hdl->move_addr = addr;
}
void
sio_onmove_cb(struct sio_hdl *hdl, int delta)
{
#ifdef DEBUG
struct timeval tv0, dtv;
long long playpos;
if (sio_debug >= 3 && (hdl->mode & SIO_PLAY)) {
gettimeofday(&tv0, NULL);
timersub(&tv0, &hdl->tv, &dtv);
DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec);
hdl->realpos += delta;
playpos = hdl->wcnt / (hdl->par.bps * hdl->par.pchan);
DPRINTF("sio_onmove_cb: delta = %+7d, "
"plat = %+7lld, "
"realpos = %+7lld, "
"bufused = %+7lld\n",
delta,
playpos - hdl->realpos,
hdl->realpos,
hdl->realpos < 0 ? playpos : playpos - hdl->realpos);
}
#endif
if (hdl->move_cb)
hdl->move_cb(hdl->move_addr, delta);
}
int
sio_setvol(struct sio_hdl *hdl, unsigned ctl)
{
if (hdl->eof)
return 0;
if (!hdl->ops->setvol(hdl, ctl))
return 0;
hdl->ops->getvol(hdl);
return 1;
}
void
sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned), void *addr)
{
if (hdl->started) {
DPRINTF("sio_onvol: already started\n");
hdl->eof = 1;
return;
}
hdl->vol_cb = cb;
hdl->vol_addr = addr;
hdl->ops->getvol(hdl);
}
void
sio_onvol_cb(struct sio_hdl *hdl, unsigned ctl)
{
if (hdl->vol_cb)
hdl->vol_cb(hdl->vol_addr, ctl);
}

164
libsndio/sndio.h Normal file
View File

@ -0,0 +1,164 @@
/* $OpenBSD: sndio.h,v 1.3 2009/07/25 11:27:14 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 SNDIO_H
#define SNDIO_H
#include <sys/param.h>
/*
* private ``handle'' structure
*/
struct sio_hdl;
struct mio_hdl;
/*
* parameters of a full-duplex stream
*/
struct sio_par {
unsigned bits; /* bits per sample */
unsigned bps; /* bytes per sample */
unsigned sig; /* 1 = signed, 0 = unsigned */
unsigned le; /* 1 = LE, 0 = BE byte order */
unsigned msb; /* 1 = MSB, 0 = LSB aligned */
unsigned rchan; /* number channels for recording direction */
unsigned pchan; /* number channels for playback direction */
unsigned rate; /* frames per second */
unsigned bufsz; /* end-to-end buffer size */
#define SIO_IGNORE 0 /* pause during xrun */
#define SIO_SYNC 1 /* resync after xrun */
#define SIO_ERROR 2 /* terminate on xrun */
unsigned xrun; /* what to do on overruns/underruns */
unsigned round; /* optimal bufsz divisor */
unsigned appbufsz; /* minimum buffer size */
int __pad[3]; /* for future use */
int __magic; /* for internal/debug purposes only */
};
/*
* capabilities of a stream
*/
struct sio_cap {
#define SIO_NENC 8
#define SIO_NCHAN 8
#define SIO_NRATE 16
#define SIO_NCONF 4
struct sio_enc { /* allowed sample encodings */
unsigned bits;
unsigned bps;
unsigned sig;
unsigned le;
unsigned msb;
} enc[SIO_NENC];
unsigned rchan[SIO_NCHAN]; /* allowed values for rchan */
unsigned pchan[SIO_NCHAN]; /* allowed values for pchan */
unsigned rate[SIO_NRATE]; /* allowed rates */
int __pad[7]; /* for future use */
unsigned nconf; /* number of elements in confs[] */
struct sio_conf {
unsigned enc; /* mask of enc[] indexes */
unsigned rchan; /* mask of chan[] indexes (rec) */
unsigned pchan; /* mask of chan[] indexes (play) */
unsigned rate; /* mask of rate[] indexes */
} confs[SIO_NCONF];
};
#define SIO_XSTRINGS { "ignore", "sync", "error" }
/*
* mode bitmap
*/
#define SIO_PLAY 1
#define SIO_REC 2
#define MIO_OUT 4
#define MIO_IN 8
/*
* maximum size of the encording string (the longest possible
* encoding is ``s24le3msb'')
*/
#define SIO_ENCMAX 10
/*
* default bytes per sample for the given bits per sample
*/
#define SIO_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4))
/*
* default value of "sio_par->le" flag
*/
#if BYTE_ORDER == LITTLE_ENDIAN
#define SIO_LE_NATIVE 1
#else
#define SIO_LE_NATIVE 0
#endif
/*
* default device for the sun audio(4) back-end
*/
#define SIO_SUN_PATH "/dev/audio"
/*
* default socket name for the aucat(1) back-end
*/
#define SIO_AUCAT_PATH "default"
/*
* maximum value of volume, eg. for sio_setvol()
*/
#define SIO_MAXVOL 127
#ifdef __cplusplus
extern "C" {
#endif
struct pollfd;
int sio_strtoenc(struct sio_par *, char *);
int sio_enctostr(struct sio_par *, char *);
void sio_initpar(struct sio_par *);
struct sio_hdl *sio_open(const char *, unsigned, int);
void sio_close(struct sio_hdl *);
int sio_setpar(struct sio_hdl *, struct sio_par *);
int sio_getpar(struct sio_hdl *, struct sio_par *);
int sio_getcap(struct sio_hdl *, struct sio_cap *);
void sio_onmove(struct sio_hdl *, void (*)(void *, int), void *);
size_t sio_write(struct sio_hdl *, const void *, size_t);
size_t sio_read(struct sio_hdl *, void *, size_t);
int sio_start(struct sio_hdl *);
int sio_stop(struct sio_hdl *);
int sio_nfds(struct sio_hdl *);
int sio_pollfd(struct sio_hdl *, struct pollfd *, int);
int sio_revents(struct sio_hdl *, struct pollfd *);
int sio_eof(struct sio_hdl *);
int sio_setvol(struct sio_hdl *, unsigned);
void sio_onvol(struct sio_hdl *, void (*)(void *, unsigned), void *);
struct mio_hdl *mio_open(const char *, unsigned, int);
void mio_close(struct mio_hdl *);
size_t mio_write(struct mio_hdl *, const void *, size_t);
size_t mio_read(struct mio_hdl *, void *, size_t);
int mio_nfds(struct mio_hdl *);
int mio_pollfd(struct mio_hdl *, struct pollfd *, int);
int mio_revents(struct mio_hdl *, struct pollfd *);
int mio_eof(struct mio_hdl *);
#ifdef __cplusplus
}
#endif
#endif /* !defined(SNDIO_H) */

91
libsndio/sndio_priv.h Normal file
View File

@ -0,0 +1,91 @@
/* $OpenBSD: sndio_priv.h,v 1.8 2009/07/25 11:15:56 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 SNDIO_PRIV_H
#define SNDIO_PRIV_H
#include <sys/param.h>
#include "sndio.h"
#ifdef DEBUG
#define DPRINTF(...) \
do { \
if (sio_debug > 0) \
fprintf(stderr, __VA_ARGS__); \
} while(0)
#define DPERROR(s) \
do { \
if (sio_debug > 0) \
perror(s); \
} while(0)
#else
#define DPRINTF(...) do {} while(0)
#define DPERROR(s) do {} while(0)
#endif
/*
* private ``handle'' structure
*/
struct sio_hdl {
struct sio_ops *ops;
void (*move_cb)(void *, int); /* call-back for realpos changes */
void *move_addr; /* user priv. data for move_cb */
void (*vol_cb)(void *, unsigned); /* call-back for volume changes */
void *vol_addr; /* user priv. data for vol_cb */
unsigned mode; /* SIO_PLAY | SIO_REC */
int started; /* true if started */
int nbio; /* true if non-blocking io */
int eof; /* true if error occured */
#ifdef DEBUG
unsigned long long pollcnt; /* times sio_revents was called */
unsigned long long wcnt; /* bytes written with sio_write() */
unsigned long long rcnt; /* bytes read with sio_read() */
long long realpos;
struct timeval tv;
struct sio_par par;
#endif
};
/*
* operations every device should support
*/
struct sio_ops {
void (*close)(struct sio_hdl *);
int (*setpar)(struct sio_hdl *, struct sio_par *);
int (*getpar)(struct sio_hdl *, struct sio_par *);
int (*getcap)(struct sio_hdl *, struct sio_cap *);
size_t (*write)(struct sio_hdl *, const void *, size_t);
size_t (*read)(struct sio_hdl *, void *, size_t);
int (*start)(struct sio_hdl *);
int (*stop)(struct sio_hdl *);
int (*pollfd)(struct sio_hdl *, struct pollfd *, int);
int (*revents)(struct sio_hdl *, struct pollfd *);
int (*setvol)(struct sio_hdl *, unsigned);
void (*getvol)(struct sio_hdl *);
};
struct sio_hdl *sio_open_aucat(const char *, unsigned, int);
struct sio_hdl *sio_open_sun(const char *, unsigned, int);
void sio_create(struct sio_hdl *, struct sio_ops *, unsigned, int);
void sio_destroy(struct sio_hdl *);
void sio_onmove_cb(struct sio_hdl *, int);
void sio_onvol_cb(struct sio_hdl *, unsigned);
#ifdef DEBUG
extern int sio_debug;
#endif
#endif /* !defined(SNDIO_PRIV_H) */

969
libsndio/sun.c Normal file
View File

@ -0,0 +1,969 @@
/* $OpenBSD: sun.c,v 1.40 2010/08/06 06:52:17 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* TODO:
*
* remove filling code from sun_write() and create sun_fill()
*
* allow block size to be set
*
* call hdl->cb_pos() from sun_read() and sun_write(), or better:
* implement generic blocking sio_read() and sio_write() with poll(2)
* and use non-blocking sio_ops only
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sndio_priv.h"
struct sun_hdl {
struct sio_hdl sio;
int fd;
int filling;
unsigned ibpf, obpf; /* bytes per frame */
unsigned ibytes, obytes; /* bytes the hw transfered */
unsigned ierr, oerr; /* frames the hw dropped */
int offset; /* frames play is ahead of record */
int idelta, odelta; /* position reported to client */
int mix_fd, mix_index; /* /dev/mixerN stuff */
};
static void sun_close(struct sio_hdl *);
static int sun_start(struct sio_hdl *);
static int sun_stop(struct sio_hdl *);
static int sun_setpar(struct sio_hdl *, struct sio_par *);
static int sun_getpar(struct sio_hdl *, struct sio_par *);
static int sun_getcap(struct sio_hdl *, struct sio_cap *);
static size_t sun_read(struct sio_hdl *, void *, size_t);
static size_t sun_write(struct sio_hdl *, const void *, size_t);
static int sun_pollfd(struct sio_hdl *, struct pollfd *, int);
static int sun_revents(struct sio_hdl *, struct pollfd *);
static int sun_setvol(struct sio_hdl *, unsigned);
static void sun_getvol(struct sio_hdl *);
static struct sio_ops sun_ops = {
sun_close,
sun_setpar,
sun_getpar,
sun_getcap,
sun_write,
sun_read,
sun_start,
sun_stop,
sun_pollfd,
sun_revents,
sun_setvol,
sun_getvol
};
/*
* convert sun encoding to sio_par encoding
*/
static int
sun_infotoenc(struct sun_hdl *hdl, struct audio_prinfo *ai, struct sio_par *par)
{
par->msb = ai->msb;
par->bits = ai->precision;
par->bps = ai->bps;
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 = SIO_LE_NATIVE;
par->sig = 1;
break;
case AUDIO_ENCODING_ULINEAR:
par->le = SIO_LE_NATIVE;
par->sig = 0;
break;
default:
DPRINTF("sun_infotoenc: unsupported encoding\n");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
/*
* convert sio_par encoding to sun encoding
*/
static void
sun_enctoinfo(struct sun_hdl *hdl, unsigned *renc, struct sio_par *par)
{
if (par->le == ~0U && par->sig == ~0U) {
*renc = ~0U;
} else if (par->le == ~0U || par->sig == ~0U) {
*renc = AUDIO_ENCODING_SLINEAR;
} else if (par->le && par->sig) {
*renc = AUDIO_ENCODING_SLINEAR_LE;
} else if (!par->le && par->sig) {
*renc = AUDIO_ENCODING_SLINEAR_BE;
} else if (par->le && !par->sig) {
*renc = AUDIO_ENCODING_ULINEAR_LE;
} else {
*renc = AUDIO_ENCODING_ULINEAR_BE;
}
}
/*
* try to set the device to the given parameters and check that the
* device can use them; return 1 on success, 0 on failure or error
*/
static int
sun_tryinfo(struct sun_hdl *hdl, struct sio_enc *enc,
unsigned pchan, unsigned rchan, unsigned rate)
{
struct audio_info aui;
struct audio_prinfo *pr;
pr = (hdl->sio.mode & SIO_PLAY) ? &aui.play : &aui.record;
AUDIO_INITINFO(&aui);
if (enc) {
if (enc->le && enc->sig) {
pr->encoding = AUDIO_ENCODING_SLINEAR_LE;
} else if (!enc->le && enc->sig) {
pr->encoding = AUDIO_ENCODING_SLINEAR_BE;
} else if (enc->le && !enc->sig) {
pr->encoding = AUDIO_ENCODING_ULINEAR_LE;
} else {
pr->encoding = AUDIO_ENCODING_ULINEAR_BE;
}
pr->precision = enc->bits;
}
if (rate)
pr->sample_rate = rate;
if ((hdl->sio.mode & (SIO_PLAY | SIO_REC)) == (SIO_PLAY | SIO_REC))
aui.record = aui.play;
if (pchan && (hdl->sio.mode & SIO_PLAY))
aui.play.channels = pchan;
if (rchan && (hdl->sio.mode & SIO_REC))
aui.record.channels = rchan;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
if (errno == EINVAL)
return 0;
DPERROR("sun_tryinfo: setinfo");
hdl->sio.eof = 1;
return 0;
}
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_tryinfo: getinfo");
hdl->sio.eof = 1;
return 0;
}
if (pchan && aui.play.channels != pchan)
return 0;
if (rchan && aui.record.channels != rchan)
return 0;
if (rate) {
if ((hdl->sio.mode & SIO_PLAY) &&
(aui.play.sample_rate != rate))
return 0;
if ((hdl->sio.mode & SIO_REC) &&
(aui.record.sample_rate != rate))
return 0;
}
return 1;
}
/*
* guess device capabilities
*/
static int
sun_getcap(struct sio_hdl *sh, struct sio_cap *cap)
{
#define NCHANS (sizeof(chans) / sizeof(chans[0]))
#define NRATES (sizeof(rates) / sizeof(rates[0]))
static unsigned chans[] = {
1, 2, 4, 6, 8, 10, 12
};
static unsigned rates[] = {
8000, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 64000, 88200, 96000
};
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct sio_par savepar;
struct audio_encoding ae;
unsigned nenc = 0, nconf = 0;
unsigned enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map;
unsigned i, j, conf;
if (!sun_getpar(&hdl->sio, &savepar))
return 0;
/*
* fill encoding list
*/
for (ae.index = 0; nenc < SIO_NENC; ae.index++) {
if (ioctl(hdl->fd, AUDIO_GETENC, &ae) < 0) {
if (errno == EINVAL)
break;
DPERROR("sun_getcap: getenc");
hdl->sio.eof = 1;
return 0;
}
if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED)
continue;
if (ae.encoding == AUDIO_ENCODING_SLINEAR_LE) {
cap->enc[nenc].le = 1;
cap->enc[nenc].sig = 1;
} else if (ae.encoding == AUDIO_ENCODING_SLINEAR_BE) {
cap->enc[nenc].le = 0;
cap->enc[nenc].sig = 1;
} else if (ae.encoding == AUDIO_ENCODING_ULINEAR_LE) {
cap->enc[nenc].le = 1;
cap->enc[nenc].sig = 0;
} else if (ae.encoding == AUDIO_ENCODING_ULINEAR_BE) {
cap->enc[nenc].le = 0;
cap->enc[nenc].sig = 0;
} else if (ae.encoding == AUDIO_ENCODING_SLINEAR) {
cap->enc[nenc].le = SIO_LE_NATIVE;
cap->enc[nenc].sig = 1;
} else if (ae.encoding == AUDIO_ENCODING_ULINEAR) {
cap->enc[nenc].le = SIO_LE_NATIVE;
cap->enc[nenc].sig = 0;
} else {
/* unsipported encoding */
continue;
}
cap->enc[nenc].bits = ae.precision;
cap->enc[nenc].bps = ae.bps;
cap->enc[nenc].msb = ae.msb;
enc_map |= (1 << nenc);
nenc++;
}
/*
* fill channels
*
* for now we're lucky: all kernel devices assume that the
* number of channels and the encoding are independent so we can
* use the current encoding and try various channels.
*/
if (hdl->sio.mode & SIO_PLAY) {
memcpy(&cap->pchan, chans, NCHANS * sizeof(unsigned));
for (i = 0; i < NCHANS; i++) {
if (sun_tryinfo(hdl, NULL, chans[i], 0, 0))
pchan_map |= (1 << i);
}
}
if (hdl->sio.mode & SIO_REC) {
memcpy(&cap->rchan, chans, NCHANS * sizeof(unsigned));
for (i = 0; i < NCHANS; i++) {
if (sun_tryinfo(hdl, NULL, 0, chans[i], 0))
rchan_map |= (1 << i);
}
}
/*
* fill rates
*
* rates are not independent from other parameters (eg. on
* uaudio devices), so certain rates may not be allowed with
* certain encodings. We have to check rates for all encodings
*/
memcpy(&cap->rate, rates, NRATES * sizeof(unsigned));
for (j = 0; j < nenc; j++) {
rate_map = 0;
for (i = 0; i < NRATES; i++) {
if (sun_tryinfo(hdl, &cap->enc[j], 0, 0, rates[i]))
rate_map |= (1 << i);
}
for (conf = 0; conf < nconf; conf++) {
if (cap->confs[conf].rate == rate_map) {
cap->confs[conf].enc |= (1 << j);
break;
}
}
if (conf == nconf) {
if (nconf == SIO_NCONF)
break;
cap->confs[nconf].enc = (1 << j);
cap->confs[nconf].pchan = pchan_map;
cap->confs[nconf].rchan = rchan_map;
cap->confs[nconf].rate = rate_map;
nconf++;
}
}
cap->nconf = nconf;
if (!sun_setpar(&hdl->sio, &savepar))
return 0;
return 1;
#undef NCHANS
#undef NRATES
}
static void
sun_getvol(struct sio_hdl *sh)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
sio_onvol_cb(&hdl->sio, SIO_MAXVOL);
}
int
sun_setvol(struct sio_hdl *sh, unsigned vol)
{
return 1;
}
struct sio_hdl *
sio_open_sun(const char *str, unsigned mode, int nbio)
{
int fd, flags, fullduplex;
struct audio_info aui;
struct sun_hdl *hdl;
struct sio_par par;
char path[PATH_MAX];
hdl = malloc(sizeof(struct sun_hdl));
if (hdl == NULL)
return NULL;
sio_create(&hdl->sio, &sun_ops, mode, nbio);
snprintf(path, sizeof(path), "/dev/audio%s", str);
if (mode == (SIO_PLAY | SIO_REC))
flags = O_RDWR;
else
flags = (mode & SIO_PLAY) ? O_WRONLY : O_RDONLY;
while ((fd = open(path, flags | O_NONBLOCK)) < 0) {
if (errno == EINTR)
continue;
DPERROR(path);
goto bad_free;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
DPERROR("FD_CLOEXEC");
goto bad_close;
}
/*
* pause the device
*/
AUDIO_INITINFO(&aui);
if (mode & SIO_PLAY)
aui.play.pause = 1;
if (mode & SIO_REC)
aui.record.pause = 1;
if (ioctl(fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sio_open_sun: setinfo");
goto bad_close;
}
/*
* If both play and record are requested then
* set full duplex mode.
*/
if (mode == (SIO_PLAY | SIO_REC)) {
fullduplex = 1;
if (ioctl(fd, AUDIO_SETFD, &fullduplex) < 0) {
DPRINTF("sio_open_sun: %s: can't set full-duplex\n", path);
goto bad_close;
}
}
hdl->fd = fd;
/*
* Default parameters may not be compatible with libsndio (eg. mulaw
* encodings, different playback and recording parameters, etc...), so
* set parameters to a random value. If the requested parameters are
* not supported by the device, then sio_setpar() will pick supported
* ones.
*/
sio_initpar(&par);
par.rate = 48000;
par.le = SIO_LE_NATIVE;
par.sig = 1;
par.bits = 16;
par.appbufsz = 1200;
if (!sio_setpar(&hdl->sio, &par))
goto bad_close;
return (struct sio_hdl *)hdl;
bad_close:
while (close(fd) < 0 && errno == EINTR)
; /* retry */
bad_free:
free(hdl);
return NULL;
}
static void
sun_close(struct sio_hdl *sh)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
while (close(hdl->fd) < 0 && errno == EINTR)
; /* retry */
free(hdl);
}
static int
sun_start(struct sio_hdl *sh)
{
struct sio_par par;
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct audio_info aui;
if (!sio_getpar(&hdl->sio, &par))
return 0;
hdl->obpf = par.pchan * par.bps;
hdl->ibpf = par.rchan * par.bps;
hdl->ibytes = 0;
hdl->obytes = 0;
hdl->ierr = 0;
hdl->oerr = 0;
hdl->offset = 0;
hdl->idelta = 0;
hdl->odelta = 0;
if (hdl->sio.mode & SIO_PLAY) {
/*
* keep the device paused and let sun_write() trigger the
* start later, to avoid buffer underruns
*/
hdl->filling = 1;
} else {
/*
* no play buffers to fill, start now!
*/
AUDIO_INITINFO(&aui);
if (hdl->sio.mode & SIO_REC)
aui.record.pause = 0;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sun_start: setinfo");
hdl->sio.eof = 1;
return 0;
}
hdl->filling = 0;
sio_onmove_cb(&hdl->sio, 0);
}
return 1;
}
static int
sun_stop(struct sio_hdl *sh)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct audio_info aui;
int mode;
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_stop: getinfo");
hdl->sio.eof = 1;
return 0;
}
mode = aui.mode;
/*
* there's no way to drain the device without blocking, so just
* stop it until the kernel driver get fixed
*/
AUDIO_INITINFO(&aui);
aui.mode = 0;
if (hdl->sio.mode & SIO_PLAY)
aui.play.pause = 1;
if (hdl->sio.mode & SIO_REC)
aui.record.pause = 1;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sun_stop: setinfo1");
hdl->sio.eof = 1;
return 0;
}
AUDIO_INITINFO(&aui);
aui.mode = mode;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sun_stop: setinfo2");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static int
sun_setpar(struct sio_hdl *sh, struct sio_par *par)
{
#define NRETRIES 8
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct audio_info aui;
unsigned i, infr, ibpf, onfr, obpf;
unsigned bufsz, round;
unsigned rate, req_rate, prec, enc;
/*
* try to set parameters until the device accepts
* a common encoding and rate for play and record
*/
rate = par->rate;
prec = par->bits;
sun_enctoinfo(hdl, &enc, par);
for (i = 0;; i++) {
if (i == NRETRIES) {
DPRINTF("sun_setpar: couldn't set parameters\n");
hdl->sio.eof = 1;
return 0;
}
AUDIO_INITINFO(&aui);
if (hdl->sio.mode & SIO_PLAY) {
aui.play.sample_rate = rate;
aui.play.precision = prec;
aui.play.encoding = enc;
aui.play.channels = par->pchan;
}
if (hdl->sio.mode & SIO_REC) {
aui.record.sample_rate = rate;
aui.record.precision = prec;
aui.record.encoding = enc;
aui.record.channels = par->rchan;
}
DPRINTF("sun_setpar: %i: trying pars = %u/%u/%u\n",
i, rate, prec, enc);
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0 && errno != EINVAL) {
DPERROR("sun_setpar: setinfo(pars)");
hdl->sio.eof = 1;
return 0;
}
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_setpar: getinfo(pars)");
hdl->sio.eof = 1;
return 0;
}
enc = (hdl->sio.mode & SIO_REC) ?
aui.record.encoding : aui.play.encoding;
switch (enc) {
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_ULINEAR_BE:
case AUDIO_ENCODING_SLINEAR:
case AUDIO_ENCODING_ULINEAR:
break;
default:
DPRINTF("sun_setpar: couldn't set linear encoding\n");
hdl->sio.eof = 1;
return 0;
}
if (hdl->sio.mode != (SIO_REC | SIO_PLAY))
break;
if (aui.play.sample_rate == aui.record.sample_rate &&
aui.play.precision == aui.record.precision &&
aui.play.encoding == aui.record.encoding)
break;
if (i < NRETRIES / 2) {
rate = aui.play.sample_rate;
prec = aui.play.precision;
enc = aui.play.encoding;
} else {
rate = aui.record.sample_rate;
prec = aui.record.precision;
enc = aui.record.encoding;
}
}
/*
* If the rate that the hardware is using is different than
* the requested rate, scale buffer sizes so they will be the
* same time duration as what was requested. This just gets
* the rates to use for scaling, that actual scaling is done
* later.
*/
rate = (hdl->sio.mode & SIO_REC) ? aui.record.sample_rate :
aui.play.sample_rate;
req_rate = rate;
if (par->rate && par->rate != ~0U)
req_rate = par->rate;
/*
* if block size and buffer size are not both set then
* set the blocksize to half the buffer size
*/
bufsz = par->appbufsz;
round = par->round;
if (bufsz != ~0U) {
bufsz = bufsz * rate / req_rate;
if (round == ~0U)
round = (bufsz + 1) / 2;
else
round = round * rate / req_rate;
} else if (round != ~0U) {
round = round * rate / req_rate;
bufsz = round * 2;
} else
return 1;
/*
* get the play/record frame size in bytes
*/
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_setpar: GETINFO");
hdl->sio.eof = 1;
return 0;
}
ibpf = (hdl->sio.mode & SIO_REC) ?
aui.record.channels * aui.record.bps : 1;
obpf = (hdl->sio.mode & SIO_PLAY) ?
aui.play.channels * aui.play.bps : 1;
DPRINTF("sun_setpar: bpf = (%u, %u)\n", ibpf, obpf);
/*
* try to set parameters until the device accepts
* a common block size for play and record
*/
for (i = 0; i < NRETRIES; i++) {
AUDIO_INITINFO(&aui);
aui.hiwat = (bufsz + round - 1) / round;
aui.lowat = aui.hiwat;
if (hdl->sio.mode & SIO_REC)
aui.record.block_size = round * ibpf;
if (hdl->sio.mode & SIO_PLAY)
aui.play.block_size = round * obpf;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sun_setpar2: SETINFO");
hdl->sio.eof = 1;
return 0;
}
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_setpar2: GETINFO");
hdl->sio.eof = 1;
return 0;
}
infr = aui.record.block_size / ibpf;
onfr = aui.play.block_size / obpf;
DPRINTF("sun_setpar: %i: trying round = %u -> (%u, %u)\n",
i, round, infr, onfr);
/*
* if half-duplex or both block sizes match, we're done
*/
if (hdl->sio.mode != (SIO_REC | SIO_PLAY) || infr == onfr) {
DPRINTF("sun_setpar: blocksize ok\n");
return 1;
}
/*
* half of the retries, retry with the smaller value,
* then with the larger returned value
*/
if (i < NRETRIES / 2)
round = infr < onfr ? infr : onfr;
else
round = infr < onfr ? onfr : infr;
}
DPRINTF("sun_setpar: couldn't find a working blocksize\n");
hdl->sio.eof = 1;
return 0;
#undef NRETRIES
}
static int
sun_getpar(struct sio_hdl *sh, struct sio_par *par)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct audio_info aui;
if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) {
DPERROR("sun_getpar: getinfo");
hdl->sio.eof = 1;
return 0;
}
if (hdl->sio.mode & SIO_PLAY) {
par->rate = aui.play.sample_rate;
if (!sun_infotoenc(hdl, &aui.play, par))
return 0;
} else if (hdl->sio.mode & SIO_REC) {
par->rate = aui.record.sample_rate;
if (!sun_infotoenc(hdl, &aui.record, par))
return 0;
} else
return 0;
par->pchan = (hdl->sio.mode & SIO_PLAY) ?
aui.play.channels : 0;
par->rchan = (hdl->sio.mode & SIO_REC) ?
aui.record.channels : 0;
par->round = (hdl->sio.mode & SIO_REC) ?
aui.record.block_size / (par->bps * par->rchan) :
aui.play.block_size / (par->bps * par->pchan);
par->appbufsz = aui.hiwat * par->round;
par->bufsz = par->appbufsz;
return 1;
}
/*
* drop recorded samples to compensate xruns
*/
static int
sun_rdrop(struct sun_hdl *hdl)
{
#define DROP_NMAX 0x1000
static char dropbuf[DROP_NMAX];
ssize_t n, todo;
while (hdl->offset > 0) {
todo = hdl->offset * hdl->ibpf;
if (todo > DROP_NMAX)
todo = DROP_NMAX - DROP_NMAX % hdl->ibpf;
while ((n = read(hdl->fd, dropbuf, todo)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sun_rdrop: read");
hdl->sio.eof = 1;
}
return 0;
}
if (n == 0) {
DPRINTF("sun_rdrop: eof\n");
hdl->sio.eof = 1;
return 0;
}
hdl->offset -= (int)n / (int)hdl->ibpf;
DPRINTF("sun_rdrop: dropped %ld/%ld bytes\n", n, todo);
}
return 1;
}
static size_t
sun_read(struct sio_hdl *sh, void *buf, size_t len)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
ssize_t n;
if (!sun_rdrop(hdl))
return 0;
while ((n = read(hdl->fd, buf, len)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sun_read: read");
hdl->sio.eof = 1;
}
return 0;
}
if (n == 0) {
DPRINTF("sun_read: eof\n");
hdl->sio.eof = 1;
return 0;
}
return n;
}
static size_t
sun_autostart(struct sun_hdl *hdl)
{
struct audio_info aui;
struct pollfd pfd;
pfd.fd = hdl->fd;
pfd.events = POLLOUT;
while (poll(&pfd, 1, 0) < 0) {
if (errno == EINTR)
continue;
DPERROR("sun_autostart: poll");
hdl->sio.eof = 1;
return 0;
}
if (!(pfd.revents & POLLOUT)) {
hdl->filling = 0;
AUDIO_INITINFO(&aui);
if (hdl->sio.mode & SIO_PLAY)
aui.play.pause = 0;
if (hdl->sio.mode & SIO_REC)
aui.record.pause = 0;
if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) {
DPERROR("sun_autostart: setinfo");
hdl->sio.eof = 1;
return 0;
}
sio_onmove_cb(&hdl->sio, 0);
}
return 1;
}
/*
* insert silence to play to compensate xruns
*/
static int
sun_wsil(struct sun_hdl *hdl)
{
#define ZERO_NMAX 0x1000
static char zero[ZERO_NMAX];
ssize_t n, todo;
while (hdl->offset < 0) {
todo = (int)-hdl->offset * (int)hdl->obpf;
if (todo > ZERO_NMAX)
todo = ZERO_NMAX - ZERO_NMAX % hdl->obpf;
while ((n = write(hdl->fd, zero, todo)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sun_wsil: write");
hdl->sio.eof = 1;
return 0;
}
return 0;
}
hdl->offset += (int)n / (int)hdl->obpf;
DPRINTF("sun_wsil: inserted %ld/%ld bytes\n", n, todo);
}
return 1;
}
static size_t
sun_write(struct sio_hdl *sh, const void *buf, size_t len)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
const unsigned char *data = buf;
ssize_t n, todo;
if (!sun_wsil(hdl))
return 0;
todo = len;
while ((n = write(hdl->fd, data, todo)) < 0) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sun_write: write");
hdl->sio.eof = 1;
}
return 0;
}
if (hdl->filling) {
if (!sun_autostart(hdl))
return 0;
}
return n;
}
static int
sun_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
pfd->fd = hdl->fd;
pfd->events = events;
return 1;
}
int
sun_revents(struct sio_hdl *sh, struct pollfd *pfd)
{
struct sun_hdl *hdl = (struct sun_hdl *)sh;
struct audio_offset ao;
int xrun, dmove, dierr = 0, doerr = 0, delta;
int revents = pfd->revents;
if (hdl->sio.mode & SIO_PLAY) {
if (ioctl(hdl->fd, AUDIO_PERROR, &xrun) < 0) {
DPERROR("sun_revents: PERROR");
hdl->sio.eof = 1;
return POLLHUP;
}
doerr = xrun - hdl->oerr;
hdl->oerr = xrun;
if (!(hdl->sio.mode & SIO_REC))
dierr = doerr;
}
if (hdl->sio.mode & SIO_REC) {
if (ioctl(hdl->fd, AUDIO_RERROR, &xrun) < 0) {
DPERROR("sun_revents: RERROR");
hdl->sio.eof = 1;
return POLLHUP;
}
dierr = xrun - hdl->ierr;
hdl->ierr = xrun;
if (!(hdl->sio.mode & SIO_PLAY))
doerr = dierr;
}
hdl->offset += doerr - dierr;
dmove = dierr > doerr ? dierr : doerr;
hdl->idelta -= dmove;
hdl->odelta -= dmove;
if ((revents & POLLOUT) && (hdl->sio.mode & SIO_PLAY)) {
if (ioctl(hdl->fd, AUDIO_GETOOFFS, &ao) < 0) {
DPERROR("sun_revents: GETOOFFS");
hdl->sio.eof = 1;
return POLLHUP;
}
delta = (ao.samples - hdl->obytes) / hdl->obpf;
hdl->obytes = ao.samples;
hdl->odelta += delta;
if (!(hdl->sio.mode & SIO_REC))
hdl->idelta += delta;
}
if ((revents & POLLIN) && (hdl->sio.mode & SIO_REC)) {
if (ioctl(hdl->fd, AUDIO_GETIOFFS, &ao) < 0) {
DPERROR("sun_revents: GETIOFFS");
hdl->sio.eof = 1;
return POLLHUP;
}
delta = (ao.samples - hdl->ibytes) / hdl->ibpf;
hdl->ibytes = ao.samples;
hdl->idelta += delta;
if (!(hdl->sio.mode & SIO_PLAY))
hdl->odelta += delta;
}
delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta;
if (delta > 0) {
sio_onmove_cb(&hdl->sio, delta);
hdl->idelta -= delta;
hdl->odelta -= delta;
}
/*
* drop recorded samples or insert silence to play
* right now to adjust revents, and avoid busy loops
* programs
*/
if (hdl->sio.started) {
if (hdl->filling)
revents |= POLLOUT;
if ((hdl->sio.mode & SIO_PLAY) && !sun_wsil(hdl))
revents &= ~POLLOUT;
if ((hdl->sio.mode & SIO_REC) && !sun_rdrop(hdl))
revents &= ~POLLIN;
}
return revents;
}