mirror of https://github.com/ericonr/sndio.git
import from OpenBSD
This commit is contained in:
commit
f268454989
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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) */
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
|
@ -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) */
|
File diff suppressed because it is too large
Load Diff
|
@ -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) */
|
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
|
@ -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) */
|
|
@ -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
|
|
@ -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 */
|
File diff suppressed because it is too large
Load Diff
|
@ -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) */
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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) */
|
File diff suppressed because it is too large
Load Diff
|
@ -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) */
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
|
@ -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) */
|
|
@ -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);
|
||||
}
|
|
@ -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) */
|
File diff suppressed because it is too large
Load Diff
|
@ -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) */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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) */
|
|
@ -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) */
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue