/*
* Copyright 2001 Niels Provos <provos@citi.umich.edu>
* 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 Niels Provos.
* 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 <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <event.h>
#include "buffer.h"
#include "voip.h"
struct vbuff* buffer_get(int);
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif /* !MIN */
extern int debug;
static int tailq_init = 0;
static int entries = 0;
#define BUFGET(pn,bid) struct vbuff pn = buffer_get(bid)
TAILQ_HEAD(tailhead, vbuff) qh;
struct vbuff {
int used;
int maxtalks;
int maxsamples;
int maxbuffer;
/* Buffer information */
int *idinuse;
bufid *id;
int32_t *dataseqnr;
u_int16_t *dataid;
int *snap_idinuse;
bufid *snap_id;
int32_t *snap_dataseqnr;
u_int16_t *snap_dataid;
/* Buffered data for each channel */
int16_t **data;
int *dataoff;
/* Reassemble for voice/video playback */
int16_t *buf; //[MAXBUFFER];
int bufentries;
int buf_fd;
struct event bufev; /* XXX */
TAILQ_ENTRY(vbuff) next;
};
/*
* creates a new buffer struct and inserts it into the list
*/
bh
buffer_new(int maxtalks, int maxsamples, int maxbuffer)
{
struct vbuff *vbp;
int i;
if (!tailq_init) {
TAILQ_INIT(&qh);
tailq_init = 1;
}
vbp = (struct vbuff*)malloc(sizeof(struct vbuff));
vbp->used = 1;
vbp->maxtalks = maxtalks;
vbp->maxsamples = maxsamples;
vbp->maxbuffer = maxbuffer;
#define balloc(type, var, num) vbp->var = (type*)malloc(sizeof(type)*num)
balloc(int, idinuse, maxtalks);
balloc(bufid, id, maxtalks);
balloc(int32_t, dataseqnr, maxtalks);
balloc(u_int16_t, dataid, maxtalks);
balloc(int, snap_idinuse, maxtalks);
balloc(bufid, snap_id, maxtalks);
balloc(int32_t, snap_dataseqnr, maxtalks);
balloc(u_int16_t, snap_dataid, maxtalks);
balloc(int, dataoff, maxtalks);
balloc(int16_t, buf, maxbuffer);
#undef balloc
vbp->data = (int16_t**)malloc(sizeof(int16_t*)*maxtalks);
for (i = 0; i < maxtalks; i++)
vbp->data[i] = (int16_t*)malloc(sizeof(int16_t)*maxsamples);
TAILQ_INSERT_TAIL(&qh, vbp, next);
return (bh)entries++;
}
/*
* deletes buffers
*/
void
buffer_delete(void)
{
struct vbuff *vptr;
int i;
for (vptr = TAILQ_FIRST(&qh); vptr; vptr = TAILQ_FIRST(&qh)) {
TAILQ_REMOVE(&qh, vptr, next);
free(vptr->idinuse);
free(vptr->id);
free(vptr->dataseqnr);
free(vptr->dataid);
free(vptr->snap_idinuse);
free(vptr->snap_id);
free(vptr->snap_dataseqnr);
free(vptr->snap_dataid);
free(vptr->dataoff);
free(vptr->buf);
for (i = 0; i < vptr->maxtalks; i++)
free(vptr->data[i]);
free(vptr->data);
free(vptr);
}
}
/*
* deprecates a buffer: unsets used flag
*/
void
buffer_deprecate(bh bhd)
{
BUFGET(*b, bhd);
b->used = 0;
}
/*
* returns a struct vbuff pointer from a list index (handler)
*/
struct vbuff*
buffer_get(bh bhd)
{
int i = 0;
struct vbuff *vptr;
TAILQ_FOREACH(vptr, &qh, next)
if ((i++ == (bh)bhd) && vptr->used) return vptr;
errx(1, "bad buffer handler passed, giving up");
return NULL;
}
void
buffer_cb(int fd, short event, void *arg)
{
bh bhd = *(bh*)arg;
BUFGET(*b, bhd);
buffer_empty(bhd, 0);
if (buffer_output(bhd))
event_add(&b->bufev, NULL);
}
void
buffer_init(bh bhd)
{
BUFGET(*b, bhd);
bh *bhdp;
b->buf_fd = fileno(stdout);
if (fcntl(b->buf_fd, F_SETFL, O_NONBLOCK) == -1)
err(1, "fcntl failed");
bhdp = (bh*)malloc(sizeof(bh));
*bhdp = bhd;
event_set(&b->bufev, b->buf_fd, EV_WRITE, buffer_cb, (void*)bhdp);
buffer_clear(bhd);
}
void
buffer_clear(int bhd)
{
int i;
BUFGET(*b, bhd);
memset(b->idinuse, 0, sizeof(int) * b->maxtalks);
memset(b->dataoff, 0, sizeof(int) * b->maxtalks);
for (i = 0; i < b->maxtalks; i++)
memset(b->data[i], 0, sizeof(int16_t) * b->maxsamples);
b->bufentries = 0;
event_del(&b->bufev);
}
/*
* tries to write bufentries*sizeof(int16_t) bytes to buf_fd. if
* write is failed or misaligned on int16_t, it reports an error. it
* also removes any data succesfully written from the buffer,
* "shifting" other buffer data in to its position. it returns the
* number of remaining buffer entries not outputted
*/
int
buffer_output(bh bhd)
{
BUFGET(*b, bhd);
int clen;
if (!b->bufentries)
return (0);
/* Try to output data */
clen = write(b->buf_fd, b->buf, b->bufentries * sizeof(int16_t));
if (clen == -1) {
fprintf(stderr, "write(%d, %p, %d) -> %d\n",
b->buf_fd, b->buf, b->bufentries * sizeof(int16_t), clen);
if (errno == EAGAIN || errno == EINTR)
return (1);
err(1, "write failed");
}
if (clen % sizeof(int16_t))
errx(1, "non-aligned write");
clen /= sizeof(int16_t);
memmove(b->buf, &b->buf[clen], (b->bufentries - clen) * sizeof(int16_t));
b->bufentries -= clen;
return (b->bufentries);
}
/*
* empty buffer from data to buf for voice playback. up to
* len*sizeof(int16_t) bytes
*/
int
buffer_empty(bh bhd, int len)
{
BUFGET(*b, bhd);
int i, j, max = b->maxsamples, clen;
for (i = 0; i < b->maxtalks; i++) {
clen = b->dataoff[i];
if (clen < len)
clen = len;
if (max > clen)
max = clen;
}
/* Nothing to be emptied */
if (max == 0)
return (0);
if (b->bufentries + max > b->maxbuffer)
errx(1, "buffers full");
memset(&b->buf[b->bufentries], 0, max * sizeof (int16_t));
for (i = 0; i < b->maxtalks; i++) {
clen = MIN(b->dataoff[i], max);
for (j = 0; j < clen; j++)
b->buf[b->bufentries + j] += b->data[i][j];
b->dataoff[i] -= clen;
memmove(b->data[i], b->data[i] + clen, b->dataoff[i] * sizeof(int16_t));
}
b->bufentries += max;
return (1);
}
/*
* creates new space for the bufid in internal arrays if the bufid
* doesn't already exists. enqueues len samples onto buffer. calls
* buffer_empty() if it overflows (followed by a buffer_output
* callback).
*/
int
buffer_enqueue(bh bhd, bufid *sid, u_int16_t seqid, int32_t seqnr,
int16_t *samples, int len)
{
BUFGET(*b, bhd);
int offset, doff;
for (offset = 0; offset < b->maxtalks; offset++) {
if (b->idinuse[offset] &&
memcmp(sid, &b->id[offset], sizeof(bufid)) == 0)
break;
}
if (offset == b->maxtalks) {
for (offset = 0; offset < b->maxtalks; offset++)
if (!b->idinuse[offset])
break;
if (offset == b->maxtalks) {
warnx("could not allocate buffer");
return (-1);
}
b->idinuse[offset] = 1;
b->dataseqnr[offset] = seqnr;
b->dataid[offset] = seqid;
memcpy(&b->id[offset], sid, sizeof(bufid));
}
if (seqnr - b->dataseqnr[offset] > 0) {
if (debug)
fprintf(stderr, "WARNING: lost %u bytes to %u\n",
seqnr - b->dataseqnr[offset], seqnr);
} else if (seqnr - b->dataseqnr[offset] < 0) {
if ((debug >= 2) &&
(seqnr - b->dataseqnr[offset] < -len)
#ifdef HAVE_CENSOR
&& !voip_doinginsert()
#endif
)
fprintf(stderr, "WARNING: reordered: %u < %u\n",
seqnr, b->dataseqnr[offset]);
return (0);
}
b->dataseqnr[offset] = seqnr + len;
b->dataid[offset] = seqid + 1;
if (b->dataoff[offset] + len > b->maxsamples) {
buffer_empty(bhd, len);
event_add(&b->bufev, NULL);
}
doff = b->dataoff[offset];
memcpy(b->data[offset] + doff * sizeof(int16_t),
samples, len * sizeof(int16_t));
b->dataoff[offset] += len;
if (buffer_empty(bhd, 0))
event_add(&b->bufev, NULL);
return (0);
}
/* Take a snapshot of active connection */
void
buffer_snapshot(int bid)
{
BUFGET(*b, bid);
int i, j;
memset(b->snap_idinuse, 0, sizeof (b->snap_idinuse));
for (j = 0, i = 0; i < b->maxtalks; i++) {
if (!b->idinuse[i])
continue;
b->snap_idinuse[j] = 1;
memcpy(&b->snap_id[j], &b->id[i], sizeof(bufid));
b->snap_dataseqnr[j] = b->dataseqnr[i];
b->snap_dataid[j] = b->dataid[i];
j++;
}
}
void
buffer_snap_nextframe(bh bhd, int advance)
{
BUFGET(*b, bhd);
int i;
for (i = 0; i < b->maxtalks; i++) {
if (!b->snap_idinuse[i])
break;
b->snap_dataseqnr[i] += advance * FRAMELEN;
b->snap_dataid[i] += advance;
}
}
int
buffer_snap_info(bh bhd, int which, bufid **id, u_int16_t *dataid,
u_int32_t *seqnr)
{
BUFGET(*b, bhd);
if (which >= b->maxtalks || !b->snap_idinuse[which])
return (-1);
*id = &b->snap_id[which];
*dataid = b->snap_dataid[which];
*seqnr = b->snap_dataseqnr[which];
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1