[BACK]Return to abuf.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / aucat

File: [local] / src / usr.bin / aucat / abuf.c (download)

Revision 1.22, Fri Jun 4 06:15:28 2010 UTC (14 years ago) by ratchov
Branch: MAIN
CVS Tags: OPENBSD_5_0_BASE, OPENBSD_5_0, OPENBSD_4_9_BASE, OPENBSD_4_9, OPENBSD_4_8_BASE, OPENBSD_4_8
Changes since 1.21: +13 -1 lines

Allow the audio device to be opened only while it's actually used.
This is necessary for uaudio devices, for instance to start aucat
before the device is plugged. Or to unplug a device whithout
having to restart aucat when another device is plugged.  This is
controlled with the new -a option.

Allow multiple audio devices to be used concurently, i.e.
multiple ``-f devname'' options to be used; -f options must follow
per-device options, which is what we do for other options.

/*	$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);
}