/* * Copyright 2001 Niels Provos * 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 #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #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); }