sndio-1.5.0004077500017510001751000000000001332755320600112745ustar00alexalexsndio-1.5.0/.gitignore010066400017510001751000000003061332662053300133340ustar00alexalex# 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 sndio-1.5.0/Makefile.in010066400017510001751000000013021332662053300134060ustar00alexalexall: cd libsndio && ${MAKE} cd sndiod && ${MAKE} cd aucat && ${MAKE} cd midicat && ${MAKE} install: cd libsndio && ${MAKE} install cd sndiod && ${MAKE} install cd aucat && ${MAKE} install cd midicat && ${MAKE} install uninstall: cd midicat && ${MAKE} uninstall cd aucat && ${MAKE} uninstall cd sndiod && ${MAKE} uninstall cd libsndio && ${MAKE} uninstall clean: cd aucat && ${MAKE} clean cd midicat && ${MAKE} clean cd sndiod && ${MAKE} clean cd libsndio && ${MAKE} clean cd examples && ${MAKE} clean distclean: clean rm -f \ Makefile aucat/Makefile midicat/Makefile sndiod/Makefile \ libsndio/Makefile \ examples/Makefile \ contrib/init.d.sndiod \ contrib/sndiod.service sndio-1.5.0/aucat004077500017510001751000000000001332662053300123665ustar00alexalexsndio-1.5.0/aucat/Makefile.in010066400017510001751000000024431332662053300145120ustar00alexalex# extra includes paths (-I options) INCLUDE = -I../libsndio -I../bsd-compat # extra libraries paths (-L options) LIB = -L../libsndio # extra defines (-D options) DEFS = -DDEBUG -DSNDIO_USER=\"@user@\" -DADATA_BITS=@precision@ @defs@ # extra libraries (-l options) LDADD = -lsndio @ldadd@ # variables defined on configure script command line (if any) @vars@ # # binaries, documentation, man pages and examples will be installed in # ${BIN_DIR}, ${MAN1_DIR} # BIN_DIR = @bindir@ MAN1_DIR = @mandir@/man1 # # programs to build # PROG = aucat MAN1 = aucat.1 all: ${PROG} ${MAN1} install: mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR} cp aucat ${DESTDIR}${BIN_DIR} cp aucat.1 ${DESTDIR}${MAN1_DIR} uninstall: cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG} cd ${DESTDIR}${MAN1_DIR} && rm -f ${MAN1} clean: rm -f -- *.o aucat # ---------------------------------------------------------- dependencies --- OBJS = abuf.o afile.o aucat.o dsp.o utils.o aucat: ${OBJS} ${CC} ${LDFLAGS} ${LIB} -o aucat ${OBJS} ${LDADD} .c.o: ${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $< abuf.o: abuf.c abuf.h utils.h afile.o: afile.c afile.h dsp.h defs.h utils.h aucat.o: aucat.c abuf.h afile.h dsp.h defs.h sysex.h utils.h \ ../bsd-compat/bsd-compat.h dsp.o: dsp.c dsp.h defs.h utils.h utils.o: utils.c utils.h sndio-1.5.0/aucat/abuf.c010066400017510001751000000057331332662053300135330ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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. * * 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. */ #include #include "abuf.h" #include "utils.h" #ifdef DEBUG void abuf_log(struct abuf *buf) { log_putu(buf->start); log_puts("+"); log_putu(buf->used); log_puts("/"); log_putu(buf->len); } #endif void abuf_init(struct abuf *buf, unsigned int len) { buf->data = xmalloc(len); buf->len = len; buf->used = 0; buf->start = 0; } void abuf_done(struct abuf *buf) { #ifdef DEBUG if (buf->used > 0) { if (log_level >= 3) { log_puts("deleting non-empty buffer, used = "); log_putu(buf->used); log_puts("\n"); } } #endif xfree(buf->data); buf->data = (void *)0xdeadbeef; } /* * return the reader pointer and the number of bytes available */ unsigned char * abuf_rgetblk(struct abuf *buf, int *rsize) { int count; count = buf->len - buf->start; if (count > buf->used) count = buf->used; *rsize = count; return buf->data + buf->start; } /* * discard "count" bytes at the start postion. */ void abuf_rdiscard(struct abuf *buf, int count) { #ifdef DEBUG if (count < 0 || count > buf->used) { log_puts("abuf_rdiscard: bad count = "); log_putu(count); log_puts("\n"); panic(); } #endif buf->used -= count; buf->start += count; if (buf->start >= buf->len) buf->start -= buf->len; } /* * advance the writer pointer by "count" bytes */ void abuf_wcommit(struct abuf *buf, int count) { #ifdef DEBUG if (count < 0 || count > (buf->len - buf->used)) { log_puts("abuf_wcommit: bad count = "); log_putu(count); log_puts("\n"); panic(); } #endif buf->used += count; } /* * get writer pointer and the number of bytes writable */ unsigned char * abuf_wgetblk(struct abuf *buf, int *rsize) { int end, avail, count; end = buf->start + buf->used; if (end >= buf->len) end -= buf->len; avail = buf->len - buf->used; count = buf->len - end; if (count > avail) count = avail; *rsize = count; return buf->data + end; } sndio-1.5.0/aucat/abuf.h010066400017510001751000000025151332662053300135330ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 struct abuf { int start; /* offset (frames) where stored data starts */ int used; /* frames stored in the buffer */ int len; /* total size of the buffer (frames) */ unsigned char *data; }; void abuf_init(struct abuf *, unsigned int); void abuf_done(struct abuf *); void abuf_log(struct abuf *); unsigned char *abuf_rgetblk(struct abuf *, int *); unsigned char *abuf_wgetblk(struct abuf *, int *); void abuf_rdiscard(struct abuf *, int); void abuf_wcommit(struct abuf *, int); #endif /* !defined(ABUF_H) */ sndio-1.5.0/aucat/afile.c010066400017510001751000000523161332662053300136750ustar00alexalex/* * Copyright (c) 2008-2014 Alexandre Ratchov * * 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 #include #include #include "afile.h" #include "utils.h" typedef struct { unsigned char ld[4]; } le32_t; typedef struct { unsigned char lw[2]; } le16_t; typedef struct { unsigned char bd[4]; } be32_t; typedef struct { unsigned char bw[2]; } be16_t; struct wav_riff { char id[4]; le32_t size; char type[4]; }; struct wav_chunk { char id[4]; le32_t size; }; struct wav_fmt { #define WAV_FMT_PCM 1 #define WAV_FMT_FLOAT 3 #define WAV_FMT_ALAW 6 #define WAV_FMT_ULAW 7 #define WAV_FMT_EXT 0xfffe le16_t fmt; le16_t nch; le32_t rate; le32_t byterate; le16_t blkalign; le16_t bits; #define WAV_FMT_SIZE 16 #define WAV_FMT_EXT_SIZE (16 + 24) le16_t extsize; le16_t valbits; le32_t chanmask; le16_t extfmt; char guid[14]; }; struct wav_hdr { struct wav_riff riff; /* 00..11 */ struct wav_chunk fmt_hdr; /* 12..20 */ struct wav_fmt fmt; struct wav_chunk data_hdr; }; struct aiff_form { char id[4]; be32_t size; char type[4]; }; struct aiff_chunk { char id[4]; be32_t size; }; struct aiff_comm { struct aiff_commbase { be16_t nch; be32_t nfr; be16_t bits; /* rate in 80-bit floating point */ be16_t rate_ex; be32_t rate_hi; be32_t rate_lo; } base; char comp_id[4]; /* followed by stuff we don't care about */ }; struct aiff_data { be32_t offs; be32_t blksz; }; struct aiff_hdr { struct aiff_form form; struct aiff_chunk comm_hdr; struct aiff_commbase comm; struct aiff_chunk data_hdr; struct aiff_data data; }; struct au_hdr { char id[4]; be32_t offs; be32_t size; #define AU_FMT_PCM8 2 #define AU_FMT_PCM16 3 #define AU_FMT_PCM24 4 #define AU_FMT_PCM32 5 #define AU_FMT_FLOAT 6 #define AU_FMT_ALAW 0x1b #define AU_FMT_ULAW 1 be32_t fmt; be32_t rate; be32_t nch; char desc[8]; /* followed by optional desc[] continuation */ }; 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 }; char aiff_id_form[4] = {'F', 'O', 'R', 'M'}; char aiff_id_aiff[4] = {'A', 'I', 'F', 'F'}; char aiff_id_aifc[4] = {'A', 'I', 'F', 'C'}; char aiff_id_data[4] = {'S', 'S', 'N', 'D'}; char aiff_id_comm[4] = {'C', 'O', 'M', 'M'}; char aiff_id_none[4] = {'N', 'O', 'N', 'E'}; char aiff_id_fl32[4] = {'f', 'l', '3', '2'}; char aiff_id_ulaw[4] = {'u', 'l', 'a', 'w'}; char aiff_id_alaw[4] = {'a', 'l', 'a', 'w'}; char au_id[4] = {'.', 's', 'n', 'd'}; static inline unsigned int le16_get(le16_t *p) { return p->lw[0] | p->lw[1] << 8; } static inline void le16_set(le16_t *p, unsigned int v) { p->lw[0] = v; p->lw[1] = v >> 8; } static inline unsigned int le32_get(le32_t *p) { return p->ld[0] | p->ld[1] << 8 | p->ld[2] << 16 | p->ld[3] << 24; } static inline void le32_set(le32_t *p, unsigned int v) { p->ld[0] = v; p->ld[1] = v >> 8; p->ld[2] = v >> 16; p->ld[3] = v >> 24; } static inline unsigned int be16_get(be16_t *p) { return p->bw[1] | p->bw[0] << 8; } static inline void be16_set(be16_t *p, unsigned int v) { p->bw[1] = v; p->bw[0] = v >> 8; } static inline unsigned int be32_get(be32_t *p) { return p->bd[3] | p->bd[2] << 8 | p->bd[1] << 16 | p->bd[0] << 24; } static inline void be32_set(be32_t *p, unsigned int v) { p->bd[3] = v; p->bd[2] = v >> 8; p->bd[1] = v >> 16; p->bd[0] = v >> 24; } static int afile_readhdr(struct afile *f, void *addr, size_t size) { if (lseek(f->fd, 0, SEEK_SET) < 0) { log_puts(f->path); log_puts(": failed to seek to beginning of file\n"); return 0; } if (read(f->fd, addr, size) != size) { log_puts(f->path); log_puts(": failed to read header\n"); return 0; } return 1; } static int afile_writehdr(struct afile *f, void *addr, size_t size) { if (lseek(f->fd, 0, SEEK_SET) < 0) { log_puts(f->path); log_puts(": failed to seek back to header\n"); return 0; } if (write(f->fd, addr, size) != size) { log_puts(f->path); log_puts(": failed to write header\n"); return 0; } f->curpos = f->startpos; return 1; } static int afile_checkpar(struct afile *f) { if (f->nch == 0 || f->nch > NCHAN_MAX) { log_puts(f->path); log_puts(": "); log_putu(f->nch); log_puts(": unsupported number of channels\n"); return 0; } if (f->rate < RATE_MIN || f->rate > RATE_MAX) { log_puts(f->path); log_puts(": "); log_putu(f->rate); log_puts(": unsupported rate\n"); return 0; } if (f->par.bits < BITS_MIN || f->par.bits > BITS_MAX) { log_puts(f->path); log_puts(": "); log_putu(f->par.bits); log_puts(": unsupported bits per sample\n"); return 0; } if (f->par.bits > f->par.bps * 8) { log_puts(f->path); log_puts(": bits larger than bytes-per-sample\n"); return 0; } if (f->fmt == AFILE_FMT_FLOAT && f->par.bits != 32) { log_puts(f->path); log_puts(": only 32-bit floating points are supported\n"); return 0; } return 1; } static int afile_wav_readfmt(struct afile *f, unsigned int csize) { struct wav_fmt fmt; unsigned int wenc; if (csize < WAV_FMT_SIZE) { log_puts(f->path); log_puts(": "); log_putu(csize); log_puts(": bogus format chunk size\n"); return 0; } if (csize > WAV_FMT_EXT_SIZE) csize = WAV_FMT_EXT_SIZE; if (read(f->fd, &fmt, csize) != csize) { log_puts(f->path); log_puts(": failed to read format chunk\n"); return 0; } wenc = le16_get(&fmt.fmt); f->par.bits = le16_get(&fmt.bits); if (wenc == WAV_FMT_EXT) { if (csize != WAV_FMT_EXT_SIZE) { log_puts(f->path); log_puts(": missing extended format chunk\n"); return 0; } if (memcmp(fmt.guid, wav_guid, sizeof(wav_guid)) != 0) { log_puts(f->path); log_puts(": unknown format (GUID)\n"); return 0; } f->par.bps = (f->par.bits + 7) / 8; f->par.bits = le16_get(&fmt.valbits); wenc = le16_get(&fmt.extfmt); } else f->par.bps = (f->par.bits + 7) / 8; f->nch = le16_get(&fmt.nch); f->rate = le32_get(&fmt.rate); f->par.le = 1; f->par.msb = 1; switch (wenc) { case WAV_FMT_PCM: f->fmt = AFILE_FMT_PCM; f->par.sig = (f->par.bits <= 8) ? 0 : 1; break; case WAV_FMT_ALAW: f->fmt = AFILE_FMT_ALAW; f->par.bits = 8; f->par.bps = 1; break; case WAV_FMT_ULAW: f->fmt = AFILE_FMT_ULAW; f->par.bits = 8; f->par.bps = 1; break; case WAV_FMT_FLOAT: f->fmt = AFILE_FMT_FLOAT; break; default: log_putu(wenc); log_puts(": unsupported encoding\n"); return 0; } return afile_checkpar(f); } static int afile_wav_readhdr(struct afile *f) { struct wav_riff riff; struct wav_chunk chunk; unsigned int csize, rsize, pos = 0; int fmt_done = 0; if (!afile_readhdr(f, &riff, sizeof(struct wav_riff))) return 0; if (memcmp(&riff.id, &wav_id_riff, 4) != 0 || memcmp(&riff.type, &wav_id_wave, 4)) { log_puts(f->path); log_puts(": not a .wav file\n"); return 0; } rsize = le32_get(&riff.size); for (;;) { if (pos + sizeof(struct wav_chunk) > rsize) { log_puts(f->path); log_puts(": missing data chunk\n"); return 0; } if (read(f->fd, &chunk, sizeof(chunk)) != sizeof(chunk)) { log_puts(f->path); log_puts(": failed to read chunk header\n"); return 0; } csize = le32_get(&chunk.size); if (memcmp(chunk.id, wav_id_fmt, 4) == 0) { if (!afile_wav_readfmt(f, csize)) return 0; fmt_done = 1; } else if (memcmp(chunk.id, wav_id_data, 4) == 0) { f->startpos = pos + sizeof(riff) + sizeof(chunk); f->endpos = f->startpos + csize; break; } else { #ifdef DEBUG if (log_level >= 2) { log_puts(f->path); log_puts(": skipped unknown chunk\n"); } #endif } /* * next chunk */ pos += sizeof(struct wav_chunk) + csize; if (lseek(f->fd, sizeof(riff) + pos, SEEK_SET) < 0) { log_puts(f->path); log_puts(": filed to seek to chunk\n"); return 0; } } if (!fmt_done) { log_puts(f->path); log_puts(": missing format chunk\n"); return 0; } return 1; } /* * Write header and seek to start position */ static int afile_wav_writehdr(struct afile *f) { struct wav_hdr hdr; memset(&hdr, 0, sizeof(struct wav_hdr)); memcpy(hdr.riff.id, wav_id_riff, 4); memcpy(hdr.riff.type, wav_id_wave, 4); le32_set(&hdr.riff.size, f->endpos - sizeof(hdr.riff)); memcpy(hdr.fmt_hdr.id, wav_id_fmt, 4); le32_set(&hdr.fmt_hdr.size, sizeof(hdr.fmt)); le16_set(&hdr.fmt.fmt, 1); le16_set(&hdr.fmt.nch, f->nch); le32_set(&hdr.fmt.rate, f->rate); le32_set(&hdr.fmt.byterate, f->rate * f->par.bps * f->nch); le16_set(&hdr.fmt.blkalign, f->par.bps * f->nch); le16_set(&hdr.fmt.bits, f->par.bits); memcpy(hdr.data_hdr.id, wav_id_data, 4); le32_set(&hdr.data_hdr.size, f->endpos - f->startpos); return afile_writehdr(f, &hdr, sizeof(struct wav_hdr)); } static int afile_aiff_readcomm(struct afile *f, unsigned int csize, int comp, unsigned int *nfr) { struct aiff_comm comm; unsigned int csize_min; unsigned int e, m; csize_min = comp ? sizeof(struct aiff_comm) : sizeof(struct aiff_commbase); if (csize < csize_min) { log_puts(f->path); log_puts(": "); log_putu(csize); log_puts(": bogus comm chunk size\n"); return 0; } if (read(f->fd, &comm, csize_min) != csize_min) { log_puts(f->path); log_puts(": failed to read comm chunk\n"); return 0; } f->nch = be16_get(&comm.base.nch); e = be16_get(&comm.base.rate_ex); m = be32_get(&comm.base.rate_hi); if (e < 0x3fff || e > 0x3fff + 31) { log_puts(f->path); log_puts(": malformed sample rate\n"); return 0; } f->rate = m >> (0x3fff + 31 - e); if (comp) { if (memcmp(comm.comp_id, aiff_id_none, 4) == 0) { f->fmt = AFILE_FMT_PCM; f->par.bits = be16_get(&comm.base.bits); } else if (memcmp(comm.comp_id, aiff_id_fl32, 4) == 0) { f->fmt = AFILE_FMT_FLOAT; f->par.bits = 32; } else if (memcmp(comm.comp_id, aiff_id_ulaw, 4) == 0) { f->fmt = AFILE_FMT_ULAW; f->par.bits = 8; } else if (memcmp(comm.comp_id, aiff_id_alaw, 4) == 0) { f->fmt = AFILE_FMT_ALAW; f->par.bits = 8; } else { log_puts(f->path); log_puts(": unsupported encoding\n"); return 0; } } else { f->fmt = AFILE_FMT_PCM; f->par.bits = be16_get(&comm.base.bits); } f->par.le = 0; f->par.sig = 1; f->par.msb = 1; f->par.bps = (f->par.bits + 7) / 8; *nfr = be32_get(&comm.base.nfr); return afile_checkpar(f); } static int afile_aiff_readdata(struct afile *f, unsigned int csize, unsigned int *roffs) { struct aiff_data data; if (csize < sizeof(struct aiff_data)) { log_puts(f->path); log_puts(": "); log_putu(csize); log_puts(": bogus data chunk size\n"); return 0; } csize = sizeof(struct aiff_data); if (read(f->fd, &data, csize) != csize) { log_puts(f->path); log_puts(": failed to read data chunk\n"); return 0; } *roffs = csize + be32_get(&data.offs); return 1; } static int afile_aiff_readhdr(struct afile *f) { struct aiff_form form; struct aiff_chunk chunk; unsigned int csize, rsize, nfr = 0, pos = 0, offs; int comm_done = 0, comp; if (!afile_readhdr(f, &form, sizeof(struct aiff_form))) return 0; if (memcmp(&form.id, &aiff_id_form, 4) != 0) { log_puts(f->path); log_puts(": not an aiff file\n"); return 0; } if (memcmp(&form.type, &aiff_id_aiff, 4) == 0) { comp = 0; } else if (memcmp(&form.type, &aiff_id_aifc, 4) == 0) comp = 1; else { log_puts(f->path); log_puts(": unsupported aiff file sub-type\n"); return 0; } rsize = be32_get(&form.size); for (;;) { if (pos + sizeof(struct aiff_chunk) > rsize) { log_puts(f->path); log_puts(": missing data chunk\n"); return 0; } if (read(f->fd, &chunk, sizeof(chunk)) != sizeof(chunk)) { log_puts(f->path); log_puts(": failed to read chunk header\n"); return 0; } csize = be32_get(&chunk.size); if (memcmp(chunk.id, aiff_id_comm, 4) == 0) { if (!afile_aiff_readcomm(f, csize, comp, &nfr)) return 0; comm_done = 1; } else if (memcmp(chunk.id, aiff_id_data, 4) == 0) { if (!afile_aiff_readdata(f, csize, &offs)) return 0; f->startpos = sizeof(form) + pos + sizeof(chunk) + offs; break; } else { #ifdef DEBUG if (log_level >= 2) { log_puts(f->path); log_puts(": skipped unknown chunk\n"); } #endif } /* * The aiff spec says "Each Chunk must contain an even * number of bytes. For those Chunks whose total * contents would yield an odd number of bytes, a zero * pad byte must be added at the end of the Chunk. This * pad byte is not included in ckDataSize, which * indicates the size of the data in the Chunk." */ csize = (csize + 1) & ~1; pos += sizeof(struct aiff_chunk) + csize; if (lseek(f->fd, sizeof(form) + pos, SEEK_SET) < 0) { log_puts(f->path); log_puts(": filed to seek to chunk\n"); return 0; } } if (!comm_done) { log_puts(f->path); log_puts(": missing comm chunk\n"); return 0; } f->endpos = f->startpos + f->par.bps * f->nch * nfr; return 1; } /* * Write header and seek to start position */ static int afile_aiff_writehdr(struct afile *f) { struct aiff_hdr hdr; unsigned int bpf; unsigned int e, m; /* convert rate to 80-bit float (exponent and fraction part) */ m = f->rate; e = 0x3fff + 31; while ((m & 0x80000000) == 0) { e--; m <<= 1; } /* bytes per frame */ bpf = f->nch * f->par.bps; memset(&hdr, 0, sizeof(struct aiff_hdr)); memcpy(hdr.form.id, aiff_id_form, 4); memcpy(hdr.form.type, aiff_id_aiff, 4); be32_set(&hdr.form.size, f->endpos - sizeof(hdr.form)); memcpy(hdr.comm_hdr.id, aiff_id_comm, 4); be32_set(&hdr.comm_hdr.size, sizeof(hdr.comm)); be16_set(&hdr.comm.nch, f->nch); be16_set(&hdr.comm.bits, f->par.bits); be16_set(&hdr.comm.rate_ex, e); be32_set(&hdr.comm.rate_hi, m); be32_set(&hdr.comm.rate_lo, 0); be32_set(&hdr.comm.nfr, (f->endpos - f->startpos) / bpf); memcpy(hdr.data_hdr.id, aiff_id_data, 4); be32_set(&hdr.data_hdr.size, f->endpos - f->startpos); be32_set(&hdr.data.offs, 0); be32_set(&hdr.data.blksz, 0); return afile_writehdr(f, &hdr, sizeof(struct aiff_hdr)); } static int afile_au_readhdr(struct afile *f) { struct au_hdr hdr; unsigned int fmt; if (!afile_readhdr(f, &hdr, sizeof(struct au_hdr))) return 0; if (memcmp(&hdr.id, &au_id, 4) != 0) { log_puts(f->path); log_puts(": not a .au file\n"); return 0; } f->startpos = be32_get(&hdr.offs); f->endpos = f->startpos + be32_get(&hdr.size); fmt = be32_get(&hdr.fmt); switch (fmt) { case AU_FMT_PCM8: f->fmt = AFILE_FMT_PCM; f->par.bits = 8; break; case AU_FMT_PCM16: f->fmt = AFILE_FMT_PCM; f->par.bits = 16; break; case AU_FMT_PCM24: f->fmt = AFILE_FMT_PCM; f->par.bits = 24; break; case AU_FMT_PCM32: f->fmt = AFILE_FMT_PCM; f->par.bits = 32; break; case AU_FMT_ULAW: f->fmt = AFILE_FMT_ULAW; f->par.bits = 8; break; case AU_FMT_ALAW: f->fmt = AFILE_FMT_ALAW; f->par.bits = 8; break; case AU_FMT_FLOAT: f->fmt = AFILE_FMT_FLOAT; f->par.bits = 32; break; default: log_puts(f->path); log_puts(": "); log_putu(fmt); log_puts(": unsupported encoding\n"); return 0; } f->par.le = 0; f->par.sig = 1; f->par.bps = f->par.bits / 8; f->par.msb = 0; f->rate = be32_get(&hdr.rate); f->nch = be32_get(&hdr.nch); if (lseek(f->fd, f->startpos, SEEK_SET) < 0) { log_puts(f->path); log_puts(": "); log_puts("failed to seek to data chunk\n"); return 0; } return afile_checkpar(f); } /* * Write header and seek to start position */ static int afile_au_writehdr(struct afile *f) { struct au_hdr hdr; unsigned int fmt; memset(&hdr, 0, sizeof(struct au_hdr)); memcpy(hdr.id, au_id, 4); be32_set(&hdr.offs, f->startpos); be32_set(&hdr.size, f->endpos - f->startpos); switch (f->par.bits) { case 8: fmt = AU_FMT_PCM8; break; case 16: fmt = AU_FMT_PCM16; break; case 24: fmt = AU_FMT_PCM24; break; case 32: fmt = AU_FMT_PCM32; break; #ifdef DEBUG default: log_puts(f->path); log_puts(": wrong precision\n"); panic(); return 0; #endif } be32_set(&hdr.fmt, fmt); be32_set(&hdr.rate, f->rate); be32_set(&hdr.nch, f->nch); return afile_writehdr(f, &hdr, sizeof(struct au_hdr)); } size_t afile_read(struct afile *f, void *data, size_t count) { off_t maxread; ssize_t n; if (f->endpos >= 0) { maxread = f->endpos - f->curpos; if (maxread == 0) { #ifdef DEBUG if (log_level >= 3) { log_puts(f->path); log_puts(": end reached\n"); } #endif return 0; } if (count > maxread) count = maxread; } n = read(f->fd, data, count); if (n < 0) { log_puts(f->path); log_puts(": couldn't read\n"); return 0; } f->curpos += n; return n; } size_t afile_write(struct afile *f, void *data, size_t count) { off_t maxwrite; int n; if (f->maxpos >= 0) { maxwrite = f->maxpos - f->curpos; if (maxwrite == 0) { #ifdef DEBUG if (log_level >= 3) { log_puts(f->path); log_puts(": max file size reached\n"); } #endif return 0; } if (count > maxwrite) count = maxwrite; } n = write(f->fd, data, count); if (n < 0) { log_puts(f->path); log_puts(": couldn't write\n"); return 0; } f->curpos += n; if (f->endpos < f->curpos) f->endpos = f->curpos; return n; } int afile_seek(struct afile *f, off_t pos) { pos += f->startpos; if (f->endpos >= 0 && pos > f->endpos && !f->par.sig) { log_puts(f->path); log_puts(": attempt to seek outside file boundaries\n"); return 0; } /* * seek only if needed to avoid errors with pipes & sockets */ if (pos != f->curpos) { if (lseek(f->fd, pos, SEEK_SET) < 0) { log_puts(f->path); log_puts(": couldn't seek\n"); return 0; } f->curpos = pos; } return 1; } void afile_close(struct afile *f) { if (f->flags & AFILE_FWRITE) { if (f->hdr == AFILE_HDR_WAV) afile_wav_writehdr(f); else if (f->hdr == AFILE_HDR_AIFF) afile_aiff_writehdr(f); else if (f->hdr == AFILE_HDR_AU) afile_au_writehdr(f); } close(f->fd); } int afile_open(struct afile *f, char *path, int hdr, int flags, struct aparams *par, int rate, int nch) { char *ext; static union { struct wav_hdr wav; struct aiff_hdr aiff; struct au_hdr au; } dummy; f->par = *par; f->rate = rate; f->nch = nch; f->flags = flags; f->hdr = hdr; if (hdr == AFILE_HDR_AUTO) { f->hdr = AFILE_HDR_RAW; ext = strrchr(path, '.'); if (ext != NULL) { ext++; if (strcasecmp(ext, "aif") == 0 || strcasecmp(ext, "aiff") == 0 || strcasecmp(ext, "aifc") == 0) f->hdr = AFILE_HDR_AIFF; else if (strcasecmp(ext, "au") == 0 || strcasecmp(ext, "snd") == 0) f->hdr = AFILE_HDR_AU; else if (strcasecmp(ext, "wav") == 0) f->hdr = AFILE_HDR_WAV; } } if (f->flags == AFILE_FREAD) { if (strcmp(path, "-") == 0) { f->path = "stdin"; f->fd = STDIN_FILENO; } else { f->path = path; f->fd = open(f->path, O_RDONLY, 0); if (f->fd < 0) { log_puts(f->path); log_puts(": failed to open for reading\n"); return 0; } } if (f->hdr == AFILE_HDR_WAV) { if (!afile_wav_readhdr(f)) goto bad_close; } else if (f->hdr == AFILE_HDR_AIFF) { if (!afile_aiff_readhdr(f)) goto bad_close; } else if (f->hdr == AFILE_HDR_AU) { if (!afile_au_readhdr(f)) goto bad_close; } else { f->startpos = 0; f->endpos = -1; /* read until EOF */ f->fmt = AFILE_FMT_PCM; } f->curpos = f->startpos; } else if (flags == AFILE_FWRITE) { if (strcmp(path, "-") == 0) { f->path = "stdout"; f->fd = STDOUT_FILENO; } else { f->path = path; f->fd = open(f->path, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (f->fd < 0) { log_puts(f->path); log_puts(": failed to create file\n"); return 0; } } if (f->hdr == AFILE_HDR_WAV) { f->par.bps = (f->par.bits + 7) >> 3; if (f->par.bits > 8) { f->par.le = 1; f->par.sig = 1; } else f->par.sig = 0; if (f->par.bits & 7) f->par.msb = 1; f->endpos = f->startpos = sizeof(struct wav_hdr); f->maxpos = 0x7fffffff; if (!afile_writehdr(f, &dummy, sizeof(struct wav_hdr))) goto bad_close; } else if (f->hdr == AFILE_HDR_AIFF) { f->par.bps = (f->par.bits + 7) >> 3; if (f->par.bps > 1) f->par.le = 0; f->par.sig = 1; if (f->par.bits & 7) f->par.msb = 1; f->endpos = f->startpos = sizeof(struct aiff_hdr); f->maxpos = 0x7fffffff; if (!afile_writehdr(f, &dummy, sizeof(struct aiff_hdr))) goto bad_close; } else if (f->hdr == AFILE_HDR_AU) { f->par.bits = (f->par.bits + 7) & ~7; f->par.bps = f->par.bits / 8; f->par.le = 0; f->par.sig = 1; f->par.msb = 1; f->endpos = f->startpos = sizeof(struct au_hdr); f->maxpos = 0x7fffffff; if (!afile_writehdr(f, &dummy, sizeof(struct au_hdr))) goto bad_close; } else { f->endpos = f->startpos = 0; f->maxpos = -1; } f->curpos = f->startpos; } else { #ifdef DEBUG log_puts("afile_open: wrong flags\n"); panic(); #endif } return 1; bad_close: close(f->fd); return 0; } sndio-1.5.0/aucat/afile.h010066400017510001751000000042311332662053300136730ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include "dsp.h" struct afile { struct aparams par; /* file params */ #define AFILE_FMT_PCM 0 /* integers (fixed point) */ #define AFILE_FMT_ULAW 1 /* 8-bit mu-law */ #define AFILE_FMT_ALAW 2 /* 8-bit a-law */ #define AFILE_FMT_FLOAT 3 /* IEEE 754 32-bit floats */ int fmt; /* one of above */ int rate; /* file sample rate */ int nch; /* file channel count */ #define AFILE_HDR_AUTO 0 /* guess from file name */ #define AFILE_HDR_RAW 1 /* headerless aka "raw" file */ #define AFILE_HDR_WAV 2 /* microsoft .wav */ #define AFILE_HDR_AIFF 3 /* apple .aiff */ #define AFILE_HDR_AU 4 /* sun/next .au */ int hdr; /* header type */ int fd; /* file descriptor */ #define AFILE_FREAD 1 /* open for reading */ #define AFILE_FWRITE 2 /* open for writing */ int flags; /* bitmap of above */ off_t curpos; /* read/write position (bytes) */ off_t startpos; /* where payload starts */ off_t endpos; /* where payload ends */ off_t maxpos; /* max allowed pos (.wav limitation) */ char *path; /* file name (debug only) */ }; int afile_open(struct afile *, char *, int, int, struct aparams *, int, int); size_t afile_read(struct afile *, void *, size_t); size_t afile_write(struct afile *, void *, size_t); int afile_seek(struct afile *, off_t); void afile_close(struct afile *); #endif /* !defined(WAV_H) */ sndio-1.5.0/aucat/aucat.1010066400017510001751000000173071332662053300136310ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2006 Alexandre Ratchov .\" .\" 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$ .Dt AUCAT 1 .Os .Sh NAME .Nm aucat .Nd audio files manipulation tool .Sh SYNOPSIS .Nm aucat .Op Fl dn .Op Fl b Ar size .Op Fl c Ar min : Ns Ar max .Op Fl e Ar enc .Op Fl f Ar device .Op Fl g Ar position .Op Fl h Ar fmt .Op Fl i Ar file .Op Fl j Ar flag .Op Fl o Ar file .Op Fl p Ar position .Op Fl q Ar port .Op Fl r Ar rate .Op Fl v Ar volume .Sh DESCRIPTION The .Nm utility can play, record, mix, and process audio files on the fly. During playback, .Nm reads audio data concurrently from all played files, mixes it and plays the result on the device. Similarly, it stores audio data recorded from the device into corresponding files. An .Em off-line mode could be used to process audio files without involving audio hardware. Processing includes: .Pp .Bl -bullet -offset indent -compact .It Change the sound encoding. .It Route the sound from one channel to another. .It Control the per-file playback volume. .El .Pp Finally, .Nm can accept MIDI messages usable for: .Pp .Bl -bullet -offset indent -compact .It Volume control. .It Start, stop and relocate playback and recording. .El .Pp The options are as follows: .Bl -tag -width Ds .It Fl b Ar size The buffer size of the audio device in frames. Default is 7680. .It Fl c Ar min : Ns Ar max The range of audio file channel numbers. The default is .Cm 0:1 , i.e. stereo. .It Fl d Increase log verbosity. .It Fl e Ar enc Encoding of the audio file. The default is .Va s16 . Encoding names use the following scheme: signedness .Po .Va s or .Va u .Pc followed by the precision in bits, the byte-order .Po .Va le or .Va be .Pc , the number of bytes per sample, and the alignment .Po .Va msb or .Va lsb .Pc . Only the signedness and the precision are mandatory. Examples: .Va u8 , s16le , s24le3 , s24le4lsb . .It Fl f Ar device Use this .Xr sndio 7 audio device. Device mode and parameters are determined from audio files. Default is .Pa default . .It Fl g Ar position Go to the given time position and start playback or recording there. This option is equivalent to an incoming MMC relocate message with the same position. The position is expressed as the number of samples (at device sample rate). .It Fl h Ar fmt Audio file type. The following file types are supported: .Pp .Bl -tag -width auto -compact .It Cm raw Headerless file. .It Cm wav Microsoft WAV file format. .It Cm aiff Apple's audio interchange file format. .It Cm au Sun/NeXT audio file format. .It Cm auto Try to guess, depending on the file name. This is the default. .El .It Fl i Ar file Play this audio file. If the option argument is .Sq - then standard input will be used. .It Fl j Ar flag Control whether source channels are joined or expanded if they don't match the destination number of channels. If the flag is .Cm off , then each source channel is routed to a single destination channel, possibly discarding channels. If the flag is .Cm on , then a single source may be sent to multiple destinations and multiple sources may be mixed into a single destination. For instance, this feature could be used to convert a stereo file into a mono file mixing left and right channels together. The default is .Cm off . .It Fl n Off-line mode. Read input files and store the result in the output files, processing them on the fly. This mode is useful to mix, demultiplex, resample or re-encode audio files off-line. It requires at least one input .Pq Fl i and one output .Pq Fl o . .It Fl o Ar file Record into this audio file. If the option argument is .Sq - then standard output will be used. .It Fl p Ar position Time offset where the beginning of the file belongs. The first sample of the file will be played or recorded when the device reaches the given position. The position is expressed as the number of samples (at device sample rate). .It Fl q Ar port Control audio device properties through this MIDI port. This includes per-stream volumes and the ability to synchronously start, stop and relocate audio files. .It Fl r Ar rate Sample rate in Hertz of the audio file. The default is .Cm 48000 . .It Fl v Ar volume Software volume attenuation of the file to play. The value must be between 1 and 127, corresponding to \-42dB and \-0dB attenuation in 1/3dB steps. The default is 127, i.e. no attenuation. .El .Pp On the command line, per-file parameters .Pq Fl cehjrv must precede the file definition .Pq Fl io . .Pp If .Nm is sent .Dv SIGHUP , .Dv SIGINT or .Dv SIGTERM , it terminates recording to files. .Sh MIDI CONTROL .Nm can be controlled through MIDI .Pq Fl q as follows: a MIDI channel is assigned to each stream, and the volume is changed using the standard volume controller (number 7). .Pp The master volume can be changed using the standard master volume system exclusive message. .Pp All audio files are controlled by the following MMC messages: .Bl -tag -width relocate -offset indent .It relocate All files are relocated to the requested time position. If it is beyond the end of a file, the file is temporarily disabled until a valid position is requested. .It start Playback and/or recording is started. .It stop Playback and/or recording is stopped and all files are rewound back to the starting position. .El .Pp MIDI control is intended to be used together with .Xr sndiod 8 . For instance, the following command will create two devices: the default .Va snd/0 and a MMC-controlled one .Va snd/0.mmc : .Bd -literal -offset indent $ sndiod -r 48000 -z 480 -s default -t slave -s mmc .Ed .Pp Programs using .Va snd/0 behave normally, while programs using .Va snd/0.mmc wait for the MMC start signal and start synchronously. Then, the following command will play a file on the .Va snd/0.mmc audio device, giving full control to MIDI software or hardware connected to the .Va midithru/0 MIDI port: .Bd -literal -offset indent $ aucat -f snd/0.mmc -q midithru/0 -i file.wav .Ed .Pp At this stage, .Nm will start, stop and relocate automatically following all user actions in the MIDI sequencer, assuming it's configured to transmit MMC on .Va midithru/0 . Furthermore, the MIDI sequencer could be configured to use the .Va snd/0 port as MTC clock source, assured to be synchronous to playback of .Pa file.wav . .Sh EXAMPLES Mix and play two files while recording a third file: .Bd -literal -offset indent $ aucat -i file1.wav -i file2.wav -o file3.wav .Ed .Pp Record channels 2 and 3 into one stereo file and channels 6 and 7 into another stereo file using a 44.1kHz sampling rate for both: .Bd -literal -offset indent $ aucat -r 44100 -c 2:3 -o file1.wav -c 6:7 -o file2.wav .Ed .Pp Split a stereo file into two mono files: .Bd -literal -offset indent $ aucat -n -i stereo.wav -c 0:0 -o left.wav \e -c 1:1 -o right.wav .Ed .Sh SEE ALSO .Xr audioctl 1 , .Xr cdio 1 , .Xr mixerctl 1 , .Xr audio 4 , .Xr sndio 7 , .Xr sndiod 8 .Sh BUGS Resampling is low quality. .Pp There are limitations inherent to the .Ar wav , .Ar aiff , and .Ar au file formats: not all encodings are supported, file sizes are limited to 2GB, and the files must support the .Xr lseek 2 operation (e.g. pipes do not support it). sndio-1.5.0/aucat/aucat.c010066400017510001751000000743351332662053300137170ustar00alexalex/* * Copyright (c) 2008-2014 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include "abuf.h" #include "afile.h" #include "dsp.h" #include "sysex.h" #include "utils.h" #include "bsd-compat.h" /* * masks to extract command and channel of status byte */ #define MIDI_CMDMASK 0xf0 #define MIDI_CHANMASK 0x0f /* * MIDI status bytes of voice messages */ #define MIDI_NOFF 0x80 /* note off */ #define MIDI_NON 0x90 /* note on */ #define MIDI_KAT 0xa0 /* key after touch */ #define MIDI_CTL 0xb0 /* controller */ #define MIDI_PC 0xc0 /* program change */ #define MIDI_CAT 0xd0 /* channel after touch */ #define MIDI_BEND 0xe0 /* pitch bend */ #define MIDI_ACK 0xfe /* active sensing message */ /* * MIDI controller numbers */ #define MIDI_CTL_VOL 7 /* * Max coarse value */ #define MIDI_MAXCTL 127 /* * MIDI status bytes for sysex */ #define MIDI_SX_START 0xf0 #define MIDI_SX_STOP 0xf7 /* * audio device defaults */ #define DEFAULT_RATE 48000 #define DEFAULT_BUFSZ_MS 200 struct slot { struct slot *next; /* next on the play/rec list */ int vol; /* dynamic range */ int volctl; /* volume in the 0..127 range */ struct abuf buf; /* file i/o buffer */ int bpf; /* bytes per frame */ int cmin, cmax; /* file channel range */ struct cmap cmap; /* channel mapper state */ struct resamp resamp; /* resampler state */ struct conv conv; /* format encoder state */ int join; /* channel join factor */ int expand; /* channel expand factor */ void *resampbuf, *convbuf; /* conversion tmp buffers */ int dup; /* mono-to-stereo and alike */ int round; /* slot-side block size */ int mode; /* MODE_{PLAY,REC} */ #define SLOT_CFG 0 /* buffers not allocated yet */ #define SLOT_INIT 1 /* not trying to do anything */ #define SLOT_RUN 2 /* playing/recording */ #define SLOT_STOP 3 /* draining (play only) */ int pstate; /* one of above */ long long skip; /* frames to skip at the beginning */ long long pos; /* start position (at device rate) */ struct afile afile; /* file desc & friends */ }; /* * device properties */ unsigned int dev_mode; /* bitmap of SIO_{PLAY,REC} */ unsigned int dev_bufsz; /* device buffer size */ unsigned int dev_round; /* device block size */ int dev_rate; /* device sample rate (Hz) */ unsigned int dev_pchan, dev_rchan; /* play & rec channels count */ adata_t *dev_pbuf, *dev_rbuf; /* play & rec buffers */ long long dev_pos; /* last MMC position in frames */ #define DEV_STOP 0 /* stopped */ #define DEV_START 1 /* started */ unsigned int dev_pstate; /* one of above */ char *dev_name; /* device sndio(7) name */ char *dev_port; /* control port sndio(7) name */ struct sio_hdl *dev_sh; /* device handle */ struct mio_hdl *dev_mh; /* MIDI control port handle */ unsigned int dev_volctl = MIDI_MAXCTL; /* master volume */ /* * MIDI parser state */ #define MIDI_MSGMAX 32 /* max size of MIDI msg */ unsigned char dev_msg[MIDI_MSGMAX]; /* parsed input message */ unsigned int dev_mst; /* input MIDI running status */ unsigned int dev_mused; /* bytes used in ``msg'' */ unsigned int dev_midx; /* current ``msg'' size */ unsigned int dev_mlen; /* expected ``msg'' length */ unsigned int dev_prime; /* blocks to write to start */ unsigned int log_level = 1; volatile sig_atomic_t quit_flag = 0; struct slot *slot_list = NULL; /* * length of voice and common MIDI messages (status byte included) */ unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; char usagestr[] = "usage: aucat [-dn] [-b size] " "[-c min:max] [-e enc] [-f device] [-g position]\n\t" "[-h fmt] [-i file] [-j flag] [-o file] [-p position] [-q port]\n\t" "[-r rate] [-v volume]\n"; static void slot_log(struct slot *s) { #ifdef DEBUG static char *pstates[] = { "cfg", "ini", "run", "stp" }; #endif log_puts(s->afile.path); #ifdef DEBUG if (log_level >= 3) { log_puts(",pst="); log_puts(pstates[s->pstate]); } #endif } static void slot_flush(struct slot *s) { int count, n; unsigned char *data; for (;;) { data = abuf_rgetblk(&s->buf, &count); if (count == 0) break; n = afile_write(&s->afile, data, count); if (n == 0) { slot_log(s); log_puts(": can't write, disabled\n"); s->pstate = SLOT_INIT; return; } abuf_rdiscard(&s->buf, n); } } static void slot_fill(struct slot *s) { int count, n; unsigned char *data; for (;;) { data = abuf_wgetblk(&s->buf, &count); if (count == 0) break; n = afile_read(&s->afile, data, count); if (n == 0) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": eof reached, stopping\n"); } #endif s->pstate = SLOT_STOP; break; } abuf_wcommit(&s->buf, n); } } static int slot_new(char *path, int mode, struct aparams *par, int hdr, int cmin, int cmax, int rate, int dup, int vol, long long pos) { struct slot *s; s = xmalloc(sizeof(struct slot)); if (!afile_open(&s->afile, path, hdr, mode == SIO_PLAY ? AFILE_FREAD : AFILE_FWRITE, par, rate, cmax - cmin + 1)) { xfree(s); return 0; } s->cmin = cmin; s->cmax = cmin + s->afile.nch - 1; s->dup = dup; s->vol = MIDI_TO_ADATA(vol); s->mode = mode; s->pstate = SLOT_CFG; s->pos = pos; if (log_level >= 2) { slot_log(s); log_puts(": "); log_puts(s->mode == SIO_PLAY ? "play" : "rec"); log_puts(", chan "); log_putu(s->cmin); log_puts(":"); log_putu(s->cmax); log_puts(", "); log_putu(s->afile.rate); log_puts("Hz, "); switch (s->afile.fmt) { case AFILE_FMT_PCM: aparams_log(&s->afile.par); break; case AFILE_FMT_ULAW: log_puts("ulaw"); break; case AFILE_FMT_ALAW: log_puts("alaw"); break; case AFILE_FMT_FLOAT: log_puts("f32le"); break; } if (s->mode == SIO_PLAY && s->afile.endpos >= 0) { log_puts(", bytes "); log_puti(s->afile.startpos); log_puts(".."); log_puti(s->afile.endpos); } if (s->mode == SIO_PLAY) { log_puts(", vol "); log_puti(s->vol); } log_puts("\n"); } s->next = slot_list; slot_list = s; return 1; } static void slot_init(struct slot *s) { unsigned int slot_nch, bufsz; #ifdef DEBUG if (s->pstate != SLOT_CFG) { slot_log(s); log_puts(": slot_init: wrong state\n"); panic(); } #endif s->bpf = s->afile.par.bps * (s->cmax - s->cmin + 1); s->round = (dev_round * s->afile.rate + dev_rate - 1) / dev_rate; bufsz = s->round * (dev_bufsz / dev_round); bufsz -= bufsz % s->round; if (bufsz == 0) bufsz = s->round; abuf_init(&s->buf, bufsz * s->bpf); #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": allocated "); log_putu(bufsz); log_puts(" frame buffer\n"); } #endif slot_nch = s->cmax - s->cmin + 1; s->convbuf = NULL; s->resampbuf = NULL; s->join = 1; s->expand = 1; if (s->mode & SIO_PLAY) { if (s->dup) { if (dev_pchan > slot_nch) s->expand = dev_pchan / slot_nch; else if (dev_pchan < slot_nch) s->join = slot_nch / dev_pchan; } cmap_init(&s->cmap, s->cmin, s->cmax, s->cmin, s->cmax, 0, dev_pchan - 1, 0, dev_pchan - 1); if (s->afile.fmt != AFILE_FMT_PCM || !aparams_native(&s->afile.par)) { dec_init(&s->conv, &s->afile.par, slot_nch); s->convbuf = xmalloc(s->round * slot_nch * sizeof(adata_t)); } if (s->afile.rate != dev_rate) { resamp_init(&s->resamp, s->afile.rate, dev_rate, slot_nch); s->resampbuf = xmalloc(dev_round * slot_nch * sizeof(adata_t)); } } if (s->mode & SIO_REC) { if (s->dup) { if (dev_rchan > slot_nch) s->join = dev_rchan / slot_nch; else if (dev_rchan < slot_nch) s->expand = slot_nch / dev_rchan; } cmap_init(&s->cmap, 0, dev_rchan - 1, 0, dev_rchan - 1, s->cmin, s->cmax, s->cmin, s->cmax); if (s->afile.rate != dev_rate) { resamp_init(&s->resamp, dev_rate, s->afile.rate, slot_nch); s->resampbuf = xmalloc(dev_round * slot_nch * sizeof(adata_t)); } if (!aparams_native(&s->afile.par)) { enc_init(&s->conv, &s->afile.par, slot_nch); s->convbuf = xmalloc(s->round * slot_nch * sizeof(adata_t)); } /* * cmap_copy() doesn't write samples in all channels, * for instance when mono->stereo conversion is * disabled. So we have to prefill cmap_copy() output * with silence. */ if (s->resampbuf) { memset(s->resampbuf, 0, dev_round * slot_nch * sizeof(adata_t)); } else if (s->convbuf) { memset(s->convbuf, 0, s->round * slot_nch * sizeof(adata_t)); } else { memset(s->buf.data, 0, bufsz * slot_nch * sizeof(adata_t)); } } s->pstate = SLOT_INIT; #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": chain initialized\n"); } #endif } static void slot_start(struct slot *s, long long pos) { #ifdef DEBUG if (s->pstate != SLOT_INIT) { slot_log(s); log_puts(": slot_start: wrong state\n"); panic(); } #endif pos -= s->pos; if (pos < 0) { s->skip = -pos; pos = 0; } else s->skip = 0; /* * convert pos to slot sample rate * * At this stage, we could adjust s->resamp.diff to get * sub-frame accuracy. */ pos = pos * s->afile.rate / dev_rate; if (!afile_seek(&s->afile, pos * s->bpf)) { s->pstate = SLOT_INIT; return; } s->pstate = SLOT_RUN; if (s->mode & SIO_PLAY) slot_fill(s); #ifdef DEBUG if (log_level >= 2) { slot_log(s); log_puts(": started\n"); } #endif } static void slot_stop(struct slot *s) { if (s->pstate == SLOT_INIT) return; if (s->mode & SIO_REC) slot_flush(s); if (s->mode & SIO_PLAY) s->buf.used = s->buf.start = 0; s->pstate = SLOT_INIT; #ifdef DEBUG if (log_level >= 2) { slot_log(s); log_puts(": stopped\n"); } #endif } static void slot_del(struct slot *s) { struct slot **ps; if (s->pstate != SLOT_CFG) { slot_stop(s); afile_close(&s->afile); #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": closed\n"); } #endif abuf_done(&s->buf); if (s->resampbuf) xfree(s->resampbuf); if (s->convbuf) xfree(s->convbuf); } for (ps = &slot_list; *ps != s; ps = &(*ps)->next) ; /* nothing */ *ps = s->next; xfree(s); } static void slot_getcnt(struct slot *s, int *icnt, int *ocnt) { int cnt; if (s->resampbuf) resamp_getcnt(&s->resamp, icnt, ocnt); else { cnt = (*icnt < *ocnt) ? *icnt : *ocnt; *icnt = cnt; *ocnt = cnt; } } static void play_filt_resamp(struct slot *s, void *res_in, void *out, int icnt, int ocnt) { int i, offs, vol, nch; void *in; if (s->resampbuf) { resamp_do(&s->resamp, res_in, s->resampbuf, icnt, ocnt); in = s->resampbuf; } else in = res_in; nch = s->cmap.nch; vol = s->vol / s->join; /* XXX */ cmap_add(&s->cmap, in, out, vol, ocnt); offs = 0; for (i = s->join - 1; i > 0; i--) { offs += nch; cmap_add(&s->cmap, (adata_t *)in + offs, out, vol, ocnt); } offs = 0; for (i = s->expand - 1; i > 0; i--) { offs += nch; cmap_add(&s->cmap, in, (adata_t *)out + offs, vol, ocnt); } } static void play_filt_dec(struct slot *s, void *in, void *out, int icnt, int ocnt) { void *tmp; tmp = s->convbuf; if (tmp) { switch (s->afile.fmt) { case AFILE_FMT_PCM: dec_do(&s->conv, in, tmp, icnt); break; case AFILE_FMT_ULAW: dec_do_ulaw(&s->conv, in, tmp, icnt, 0); break; case AFILE_FMT_ALAW: dec_do_ulaw(&s->conv, in, tmp, icnt, 1); break; case AFILE_FMT_FLOAT: dec_do_float(&s->conv, in, tmp, icnt); break; } } else tmp = in; play_filt_resamp(s, tmp, out, icnt, ocnt); } /* * Mix as many as possible frames (but not more than a block) from the * slot buffer to the given location. Return the number of frames mixed * in the output buffer */ static int slot_mix_badd(struct slot *s, adata_t *odata) { adata_t *idata; int len, icnt, ocnt, otodo, odone; odone = 0; otodo = dev_round; if (s->skip > 0) { ocnt = otodo; if (ocnt > s->skip) ocnt = s->skip; s->skip -= ocnt; odata += dev_pchan * ocnt; otodo -= ocnt; odone += ocnt; } while (otodo > 0) { idata = (adata_t *)abuf_rgetblk(&s->buf, &len); icnt = len / s->bpf; if (icnt > s->round) icnt = s->round; ocnt = otodo; slot_getcnt(s, &icnt, &ocnt); if (icnt == 0) break; play_filt_dec(s, idata, odata, icnt, ocnt); abuf_rdiscard(&s->buf, icnt * s->bpf); otodo -= ocnt; odone += ocnt; odata += ocnt * dev_pchan; } return odone; } static void rec_filt_resamp(struct slot *s, void *in, void *res_out, int icnt, int ocnt) { int i, vol, offs, nch; void *out = res_out; out = (s->resampbuf) ? s->resampbuf : res_out; nch = s->cmap.nch; vol = ADATA_UNIT / s->join; cmap_copy(&s->cmap, in, out, vol, icnt); offs = 0; for (i = s->join - 1; i > 0; i--) { offs += nch; cmap_add(&s->cmap, (adata_t *)in + offs, out, vol, icnt); } offs = 0; for (i = s->expand - 1; i > 0; i--) { offs += nch; cmap_copy(&s->cmap, in, (adata_t *)out + offs, vol, icnt); } if (s->resampbuf) resamp_do(&s->resamp, s->resampbuf, res_out, icnt, ocnt); else ocnt = icnt; } static void rec_filt_enc(struct slot *s, void *in, void *out, int icnt, int ocnt) { void *tmp; tmp = s->convbuf; rec_filt_resamp(s, in, tmp ? tmp : out, icnt, ocnt); if (tmp) enc_do(&s->conv, tmp, out, ocnt); } /* * Copy "todo" frames from the given buffer to the slot buffer, * but not more than a block. */ static void slot_sub_bcopy(struct slot *s, adata_t *idata, int itodo) { adata_t *odata; int len, icnt, ocnt; if (s->skip > 0) { icnt = itodo; if (icnt > s->skip) icnt = s->skip; s->skip -= icnt; idata += dev_rchan * icnt; itodo -= icnt; } while (itodo > 0) { odata = (adata_t *)abuf_wgetblk(&s->buf, &len); ocnt = len / s->bpf; if (ocnt > s->round) ocnt = s->round; icnt = itodo; slot_getcnt(s, &icnt, &ocnt); if (ocnt == 0) break; rec_filt_enc(s, idata, odata, icnt, ocnt); abuf_wcommit(&s->buf, ocnt * s->bpf); itodo -= icnt; idata += icnt * dev_rchan; } } static int dev_open(char *dev, int mode, int bufsz, char *port) { int rate, pmax, rmax; struct sio_par par; struct slot *s; if (port) { dev_port = port; dev_mh = mio_open(dev_port, MIO_IN, 0); if (dev_mh == NULL) { log_puts(port); log_puts(": couldn't open midi port\n"); return 0; } } else dev_mh = NULL; dev_name = dev; dev_sh = sio_open(dev, mode, 0); if (dev_sh == NULL) { log_puts(dev_name); log_puts(": couldn't open audio device\n"); return 0; } rate = pmax = rmax = 0; for (s = slot_list; s != NULL; s = s->next) { if (s->afile.rate > rate) rate = s->afile.rate; if (s->mode == SIO_PLAY) { if (s->cmax > pmax) pmax = s->cmax; } if (s->mode == SIO_REC) { if (s->cmax > rmax) rmax = s->cmax; } } sio_initpar(&par); par.bits = ADATA_BITS; par.bps = sizeof(adata_t); par.msb = 0; par.le = SIO_LE_NATIVE; par.rate = rate; if (mode & SIO_PLAY) par.pchan = pmax + 1; if (mode & SIO_REC) par.rchan = rmax + 1; par.appbufsz = bufsz > 0 ? bufsz : rate * DEFAULT_BUFSZ_MS / 1000; if (!sio_setpar(dev_sh, &par) || !sio_getpar(dev_sh, &par)) { log_puts(dev_name); log_puts(": couldn't set audio params\n"); return 0; } if (par.bits != ADATA_BITS || par.bps != sizeof(adata_t) || (par.bps > 1 && par.le != SIO_LE_NATIVE) || (par.bps * 8 > par.bits && par.msb)) { log_puts(dev_name); log_puts(": unsupported audio params\n"); return 0; } dev_mode = mode; dev_rate = par.rate; dev_bufsz = par.bufsz; dev_round = par.round; if (mode & SIO_PLAY) { dev_pchan = par.pchan; dev_pbuf = xmalloc(sizeof(adata_t) * dev_pchan * dev_round); } if (mode & SIO_REC) { dev_rchan = par.rchan; dev_rbuf = xmalloc(sizeof(adata_t) * dev_rchan * dev_round); } dev_pstate = DEV_STOP; if (log_level >= 2) { log_puts(dev_name); log_puts(": "); log_putu(dev_rate); log_puts("Hz"); if (dev_mode & SIO_PLAY) { log_puts(", play 0:"); log_puti(dev_pchan - 1); } if (dev_mode & SIO_REC) { log_puts(", rec 0:"); log_puti(dev_rchan - 1); } log_puts(", "); log_putu(dev_bufsz / dev_round); log_puts(" blocks of "); log_putu(dev_round); log_puts(" frames\n"); } return 1; } static void dev_close(void) { sio_close(dev_sh); if (dev_mh) mio_close(dev_mh); if (dev_mode & SIO_PLAY) xfree(dev_pbuf); if (dev_mode & SIO_REC) xfree(dev_rbuf); } static void dev_master(int val) { struct slot *s; int mastervol, slotvol; mastervol = MIDI_TO_ADATA(dev_volctl); for (s = slot_list; s != NULL; s = s->next) { slotvol = MIDI_TO_ADATA(val); s->vol = ADATA_MUL(mastervol, slotvol); } #ifdef DEBUG if (log_level >= 3) { log_puts("master volume set to "); log_putu(val); log_puts("\n"); } #endif } static void dev_slotvol(int midich, int val) { struct slot *s; int mastervol, slotvol; for (s = slot_list; s != NULL; s = s->next) { if (midich == 0) { mastervol = MIDI_TO_ADATA(dev_volctl); slotvol = MIDI_TO_ADATA(val); s->vol = ADATA_MUL(mastervol, slotvol); #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": volume set to "); log_putu(val); log_puts("\n"); } #endif break; } } } /* * start all slots simultaneously */ static void dev_mmcstart(void) { struct slot *s; if (dev_pstate == DEV_STOP) { dev_pstate = DEV_START; for (s = slot_list; s != NULL; s = s->next) slot_start(s, dev_pos); dev_prime = (dev_mode & SIO_PLAY) ? dev_bufsz / dev_round : 0; sio_start(dev_sh); if (log_level >= 2) log_puts("started\n"); } else { #ifdef DEBUG if (log_level >= 3) log_puts("ignoring mmc start\n"); #endif } } /* * stop all slots simultaneously */ static void dev_mmcstop(void) { struct slot *s; if (dev_pstate == DEV_START) { dev_pstate = DEV_STOP; for (s = slot_list; s != NULL; s = s->next) slot_stop(s); sio_stop(dev_sh); if (log_level >= 2) log_puts("stopped\n"); } else { #ifdef DEBUG if (log_level >= 3) log_puts("ignored mmc stop\n"); #endif } } /* * relocate all slots simultaneously */ static void dev_mmcloc(int hr, int min, int sec, int fr, int cent, int fps) { long long pos; pos = (long long)dev_rate * hr * 3600 + (long long)dev_rate * min * 60 + (long long)dev_rate * sec + (long long)dev_rate * fr / fps + (long long)dev_rate * cent / (100 * fps); if (dev_pos == pos) return; dev_pos = pos; if (log_level >= 2) { log_puts("relocated to "); log_putu(hr); log_puts(":"); log_putu(min); log_puts(":"); log_putu(sec); log_puts("."); log_putu(fr); log_puts("."); log_putu(cent); log_puts(" at "); log_putu(fps); log_puts("fps\n"); } if (dev_pstate == DEV_START) { dev_mmcstop(); dev_mmcstart(); } } static void dev_imsg(unsigned char *msg, unsigned int len) { struct sysex *x; unsigned int fps, chan; if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) { chan = msg[0] & MIDI_CHANMASK; dev_slotvol(chan, msg[2]); return; } x = (struct sysex *)msg; if (x->start != SYSEX_START) return; if (len < SYSEX_SIZE(empty)) return; if (x->type != SYSEX_TYPE_RT) return; if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) { if (len == SYSEX_SIZE(master)) dev_master(x->u.master.coarse); return; } if (x->id0 != SYSEX_MMC) return; switch (x->id1) { case SYSEX_MMC_STOP: if (len != SYSEX_SIZE(stop)) return; dev_mmcstop(); break; case SYSEX_MMC_START: if (len != SYSEX_SIZE(start)) return; dev_mmcstart(); break; case SYSEX_MMC_LOC: if (len != SYSEX_SIZE(loc) || x->u.loc.len != SYSEX_MMC_LOC_LEN || x->u.loc.cmd != SYSEX_MMC_LOC_CMD) return; switch (x->u.loc.hr >> 5) { case MTC_FPS_24: fps = 24; break; case MTC_FPS_25: fps = 25; break; case MTC_FPS_30: fps = 30; break; default: dev_mmcstop(); return; } dev_mmcloc(x->u.loc.hr & 0x1f, x->u.loc.min, x->u.loc.sec, x->u.loc.fr, x->u.loc.cent, fps); break; } } /* * parse the given data chunk and call imsg() for each message */ static void midi_in(unsigned char *idata, int icount) { int i; unsigned char c; for (i = 0; i < icount; i++) { c = *idata++; if (c >= 0xf8) { /* we don't use real-time events */ } else if (c == SYSEX_END) { if (dev_mst == SYSEX_START) { dev_msg[dev_midx++] = c; dev_imsg(dev_msg, dev_midx); } dev_mst = 0; dev_midx = 0; } else if (c >= 0xf0) { dev_msg[0] = c; dev_mlen = common_len[c & 7]; dev_mst = c; dev_midx = 1; } else if (c >= 0x80) { dev_msg[0] = c; dev_mlen = voice_len[(c >> 4) & 7]; dev_mst = c; dev_midx = 1; } else if (dev_mst) { if (dev_midx == 0 && dev_mst != SYSEX_START) dev_msg[dev_midx++] = dev_mst; dev_msg[dev_midx++] = c; if (dev_midx == dev_mlen) { dev_imsg(dev_msg, dev_midx); if (dev_mst >= 0xf0) dev_mst = 0; dev_midx = 0; } else if (dev_midx == MIDI_MSGMAX) { /* sysex too long */ dev_mst = 0; } } } } static int slot_list_mix(unsigned int round, unsigned int pchan, adata_t *pbuf) { unsigned int done, n; struct slot *s; memset(pbuf, 0, pchan * round * sizeof(adata_t)); done = 0; for (s = slot_list; s != NULL; s = s->next) { if (s->pstate == SLOT_INIT || !(s->mode & SIO_PLAY)) continue; if (s->pstate == SLOT_STOP && s->buf.used < s->bpf) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": drained, done\n"); } #endif slot_stop(s); continue; } n = slot_mix_badd(s, dev_pbuf); if (n > done) done = n; } return done; } static int slot_list_copy(unsigned int count, unsigned int rchan, adata_t *rbuf) { unsigned int done; struct slot *s; done = 0; for (s = slot_list; s != NULL; s = s->next) { if (s->pstate == SLOT_INIT || !(s->mode & SIO_REC)) continue; slot_sub_bcopy(s, rbuf, count); done = count; } return done; } static void slot_list_iodo(void) { struct slot *s; for (s = slot_list; s != NULL; s = s->next) { if (s->pstate != SLOT_RUN) continue; if ((s->mode & SIO_PLAY) && (s->buf.used < s->round * s->bpf)) slot_fill(s); if ((s->mode & SIO_REC) && (s->buf.len - s->buf.used < s->round * s->bpf)) slot_flush(s); } } static int offline(void) { unsigned int todo; int rate, cmax; struct slot *s; rate = cmax = 0; for (s = slot_list; s != NULL; s = s->next) { if (s->afile.rate > rate) rate = s->afile.rate; if (s->cmax > cmax) cmax = s->cmax; } dev_sh = NULL; dev_name = "offline"; dev_mode = SIO_PLAY | SIO_REC; dev_rate = rate; dev_bufsz = rate; dev_round = rate; dev_pchan = dev_rchan = cmax + 1; dev_pbuf = dev_rbuf = xmalloc(sizeof(adata_t) * dev_pchan * dev_round); dev_pstate = DEV_STOP; for (s = slot_list; s != NULL; s = s->next) slot_init(s); for (s = slot_list; s != NULL; s = s->next) slot_start(s, 0); for (;;) { todo = slot_list_mix(dev_round, dev_pchan, dev_pbuf); if (todo == 0) break; slot_list_copy(todo, dev_pchan, dev_pbuf); slot_list_iodo(); } xfree(dev_pbuf); while (slot_list) slot_del(slot_list); return 1; } static int playrec_cycle(void) { unsigned int n, todo; unsigned char *p; int pcnt, rcnt; #ifdef DEBUG if (log_level >= 4) { log_puts(dev_name); log_puts(": cycle, prime = "); log_putu(dev_prime); log_puts("\n"); } #endif pcnt = rcnt = 0; if (dev_mode & SIO_REC) { if (dev_prime > 0) dev_prime--; else { todo = dev_round * dev_rchan * sizeof(adata_t); p = (unsigned char *)dev_rbuf; while (todo > 0) { n = sio_read(dev_sh, p, todo); if (n == 0) { log_puts(dev_name); log_puts(": failed to read " "from device\n"); return 0; } p += n; todo -= n; } rcnt = slot_list_copy(dev_round, dev_rchan, dev_rbuf); } } if (dev_mode & SIO_PLAY) { pcnt = slot_list_mix(dev_round, dev_pchan, dev_pbuf); todo = sizeof(adata_t) * dev_pchan * dev_round; n = sio_write(dev_sh, dev_pbuf, todo); if (n == 0) { log_puts(dev_name); log_puts(": failed to write to device\n"); return 0; } } slot_list_iodo(); return pcnt > 0 || rcnt > 0; } static void sigint(int s) { if (quit_flag) _exit(1); quit_flag = 1; } static int playrec(char *dev, int mode, int bufsz, char *port) { #define MIDIBUFSZ 0x100 unsigned char mbuf[MIDIBUFSZ]; struct sigaction sa; struct pollfd *pfds; struct slot *s; int n, ns, nm, ev; if (!dev_open(dev, mode, bufsz, port)) return 0; n = sio_nfds(dev_sh); if (dev_mh) n += mio_nfds(dev_mh); pfds = xmalloc(n * sizeof(struct pollfd)); for (s = slot_list; s != NULL; s = s->next) slot_init(s); if (dev_mh == NULL) dev_mmcstart(); else { if (log_level >= 2) log_puts("ready, waiting for mmc messages\n"); } quit_flag = 0; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = sigint; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGHUP, &sa, NULL); while (!quit_flag) { if (dev_pstate == DEV_START) { ev = 0; if (mode & SIO_PLAY) ev |= POLLOUT; if (mode & SIO_REC) ev |= POLLIN; ns = sio_pollfd(dev_sh, pfds, ev); } else ns = 0; if (dev_mh) nm = mio_pollfd(dev_mh, pfds + ns, POLLIN); else nm = 0; if (poll(pfds, ns + nm, -1) < 0) { if (errno == EINTR) continue; log_puts("poll failed\n"); panic(); } if (dev_pstate == DEV_START) { ev = sio_revents(dev_sh, pfds); if (ev & POLLHUP) { log_puts(dev); log_puts(": audio device gone, stopping\n"); break; } if (ev & (POLLIN | POLLOUT)) { if (!playrec_cycle() && dev_mh == NULL) break; } } if (dev_mh) { ev = mio_revents(dev_mh, pfds + ns); if (ev & POLLHUP) { log_puts(dev_port); log_puts(": midi port gone, stopping\n"); break; } if (ev & POLLIN) { n = mio_read(dev_mh, mbuf, MIDIBUFSZ); midi_in(mbuf, n); } } } sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGHUP, &sa, NULL); if (dev_pstate == DEV_START) dev_mmcstop(); xfree(pfds); dev_close(); while (slot_list) slot_del(slot_list); return 1; } static int opt_onoff(char *s, int *flag) { if (strcmp("off", s) == 0) { *flag = 0; return 1; } if (strcmp("on", s) == 0) { *flag = 1; return 1; } log_puts(s); log_puts(": on/off expected\n"); return 0; } static int opt_enc(char *s, struct aparams *par) { int len; len = aparams_strtoenc(par, s); if (len == 0 || s[len] != '\0') { log_puts(s); log_puts(": bad encoding\n"); return 0; } return 1; } static int opt_hdr(char *s, int *hdr) { if (strcmp("auto", s) == 0) { *hdr = AFILE_HDR_AUTO; return 1; } if (strcmp("raw", s) == 0) { *hdr = AFILE_HDR_RAW; return 1; } if (strcmp("wav", s) == 0) { *hdr = AFILE_HDR_WAV; return 1; } if (strcmp("aiff", s) == 0) { *hdr = AFILE_HDR_AIFF; return 1; } if (strcmp("au", s) == 0) { *hdr = AFILE_HDR_AU; return 1; } log_puts(s); log_puts(": bad header type\n"); return 0; } static int opt_ch(char *s, int *rcmin, int *rcmax) { char *next, *end; long cmin, cmax; errno = 0; cmin = strtol(s, &next, 10); if (next == s || *next != ':') goto failed; cmax = strtol(++next, &end, 10); if (end == next || *end != '\0') goto failed; if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX) goto failed; *rcmin = cmin; *rcmax = cmax; return 1; failed: log_puts(s); log_puts(": channel range expected\n"); return 0; } static int opt_num(char *s, int min, int max, int *num) { const char *errstr; *num = strtonum(s, min, max, &errstr); if (errstr) { log_puts(s); log_puts(": expected integer between "); log_puti(min); log_puts(" and "); log_puti(max); log_puts("\n"); return 0; } return 1; } static int opt_pos(char *s, long long *pos) { const char *errstr; *pos = strtonum(s, 0, LLONG_MAX, &errstr); if (errstr) { log_puts(s); log_puts(": positive number of samples expected\n"); return 0; } return 1; } int main(int argc, char **argv) { int dup, cmin, cmax, rate, vol, bufsz, hdr, mode; char *port, *dev; struct aparams par; int n_flag, c; long long pos; vol = 127; dup = 0; bufsz = 0; rate = DEFAULT_RATE; cmin = 0; cmax = 1; aparams_init(&par); hdr = AFILE_HDR_AUTO; n_flag = 0; port = NULL; dev = NULL; mode = 0; pos = 0; while ((c = getopt(argc, argv, "b:c:de:f:g:h:i:j:no:p:q:r:t:v:")) != -1) { switch (c) { case 'b': if (!opt_num(optarg, 1, RATE_MAX, &bufsz)) return 1; break; case 'c': if (!opt_ch(optarg, &cmin, &cmax)) return 1; break; case 'd': log_level++; break; case 'e': if (!opt_enc(optarg, &par)) return 1; break; case 'f': dev = optarg; break; case 'g': if (!opt_pos(optarg, &dev_pos)) return 1; break; case 'h': if (!opt_hdr(optarg, &hdr)) return 1; break; case 'i': if (!slot_new(optarg, SIO_PLAY, &par, hdr, cmin, cmax, rate, dup, vol, pos)) return 1; mode |= SIO_PLAY; break; case 'j': if (!opt_onoff(optarg, &dup)) return 1; break; case 'n': n_flag = 1; break; case 'o': if (!slot_new(optarg, SIO_REC, &par, hdr, cmin, cmax, rate, dup, 0, pos)) return 1; mode |= SIO_REC; break; case 'p': if (!opt_pos(optarg, &pos)) return 1; break; case 'q': port = optarg; break; case 'r': if (!opt_num(optarg, RATE_MIN, RATE_MAX, &rate)) return 1; break; case 'v': if (!opt_num(optarg, 0, MIDI_MAXCTL, &vol)) return 1; break; default: goto bad_usage; } } argc -= optind; argv += optind; if (argc != 0) { bad_usage: log_puts(usagestr); return 1; } if (n_flag) { if (dev != NULL || port != NULL) { log_puts("-f and -q make no sense in off-line mode\n"); return 1; } if (mode != (SIO_PLAY | SIO_REC)) { log_puts("both -i and -o required\n"); return 1; } if (!offline()) return 1; } else { if (dev == NULL) dev = SIO_DEVANY; if (mode == 0) { log_puts("at least -i or -o required\n"); return 1; } if (!playrec(dev, mode, bufsz, port)) return 1; } return 0; } sndio-1.5.0/aucat/defs.h010066400017510001751000000021521332662053300135340ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 DEFS_H #define DEFS_H /* * limits */ #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 */ #endif /* !defined(DEFS_H) */ sndio-1.5.0/aucat/dsp.c010066400017510001751000000514021332662053300133760ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 "dsp.h" #include "utils.h" 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 }; short dec_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 dec_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 }; /* * 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 = ADATA_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) 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; } /* * Initialise parameters structure with the defaults natively supported * by the machine. */ void aparams_init(struct aparams *par) { par->bps = sizeof(adata_t); par->bits = ADATA_BITS; par->le = ADATA_LE; par->sig = 1; par->msb = 0; } /* * log the given format/channels/encoding */ void aparams_log(struct aparams *par) { char enc[ENCMAX]; aparams_enctostr(par, enc); log_puts(enc); } /* * return true if encoding corresponds to what we store in adata_t */ int aparams_native(struct aparams *par) { return par->bps == sizeof(adata_t) && par->bits == ADATA_BITS && (par->bps == 1 || par->le == ADATA_LE) && (par->bits == par->bps * 8 || !par->msb); } /* * Return the number of input and output frame that would be consumed * by resamp_do(p, *icnt, *ocnt). */ void resamp_getcnt(struct resamp *p, int *icnt, int *ocnt) { long long idiff, odiff; int cdiff; cdiff = p->oblksz - p->diff; idiff = (long long)*icnt * p->oblksz; odiff = (long long)*ocnt * p->iblksz; if (odiff - idiff >= cdiff) *ocnt = (idiff + cdiff + p->iblksz - 1) / p->iblksz; else *icnt = (odiff + p->diff) / p->oblksz; } /* * Resample the given number of frames. The number of output frames * must match the coresponding number of input frames. Either always * use icnt and ocnt such that: * * icnt * oblksz = ocnt * iblksz * * or use resamp_getcnt() to calculate the proper numbers. */ void resamp_do(struct resamp *p, adata_t *in, adata_t *out, int icnt, int ocnt) { unsigned int nch; adata_t *idata; unsigned int oblksz; unsigned int ifr; int s, ds, diff; adata_t *odata; unsigned int iblksz; unsigned int ofr; unsigned int c; adata_t *ctxbuf, *ctx; unsigned int ctx_start; /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = in; odata = out; diff = p->diff; iblksz = p->iblksz; oblksz = p->oblksz; ctxbuf = p->ctx; ctx_start = p->ctx_start; nch = p->nch; ifr = icnt; ofr = ocnt; /* * Start conversion. */ #ifdef DEBUG if (log_level >= 4) { log_puts("resamp: copying "); log_puti(ifr); log_puts(" -> "); log_putu(ofr); log_puts(" frames, diff = "); log_puti(diff); log_puts("\n"); } #endif for (;;) { if (diff >= oblksz) { if (ifr == 0) break; ctx_start ^= 1; ctx = ctxbuf + ctx_start; for (c = nch; c > 0; c--) { *ctx = *idata++; ctx += RESAMP_NCTX; } diff -= oblksz; ifr--; } else { if (ofr == 0) break; ctx = ctxbuf; for (c = nch; c > 0; c--) { s = ctx[ctx_start ^ 1]; ds = ctx[ctx_start] - s; ctx += RESAMP_NCTX; *odata++ = s + ADATA_MULDIV(ds, diff, oblksz); } diff += iblksz; ofr--; } } p->diff = diff; p->ctx_start = ctx_start; #ifdef DEBUG if (ifr != 0) { log_puts("resamp_do: "); log_puti(ifr); log_puts(": too many input frames\n"); panic(); } if (ofr != 0) { log_puts("resamp_do: "); log_puti(ofr); log_puts(": too many output frames\n"); panic(); } #endif } static unsigned int uint_gcd(unsigned int a, unsigned int b) { unsigned int r; while (b > 0) { r = a % b; a = b; b = r; } return a; } /* * initialize resampler with ibufsz/obufsz factor and "nch" channels */ void resamp_init(struct resamp *p, unsigned int iblksz, unsigned int oblksz, int nch) { unsigned int i, g; /* * reduce iblksz/oblksz fraction */ g = uint_gcd(iblksz, oblksz); iblksz /= g; oblksz /= g; /* * ensure weird rates don't cause integer overflow */ while (iblksz > ADATA_UNIT || oblksz > ADATA_UNIT) { iblksz >>= 1; oblksz >>= 1; } p->iblksz = iblksz; p->oblksz = oblksz; p->diff = 0; p->nch = nch; p->ctx_start = 0; for (i = 0; i < NCHAN_MAX * RESAMP_NCTX; i++) p->ctx[i] = 0; #ifdef DEBUG if (log_level >= 3) { log_puts("resamp: "); log_putu(iblksz); log_puts("/"); log_putu(oblksz); log_puts("\n"); } #endif } /* * encode "todo" frames from native to foreign encoding */ void enc_do(struct conv *p, unsigned char *in, unsigned char *out, int todo) { unsigned int f; adata_t *idata; unsigned int s; unsigned int oshift; unsigned int obias; unsigned int obps; unsigned int i; unsigned char *odata; int obnext; int osnext; #ifdef DEBUG if (log_level >= 4) { log_puts("enc: copying "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = (adata_t *)in; odata = out; oshift = p->shift; obias = p->bias; obps = p->bps; obnext = p->bnext; osnext = p->snext; /* * Start conversion. */ odata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { /* convert adata to u32 */ s = (int)*idata++ + ADATA_UNIT; s <<= 32 - ADATA_BITS; /* convert u32 to uN */ s >>= oshift; /* convert uN to sN */ s -= obias; /* packetize sN */ for (i = obps; i > 0; i--) { *odata = (unsigned char)s; s >>= 8; odata += obnext; } odata += osnext; } } /* * store "todo" frames of silence in foreign encoding */ void enc_sil_do(struct conv *p, unsigned char *out, int todo) { unsigned int f; unsigned int s; unsigned int oshift; int obias; unsigned int obps; unsigned int i; unsigned char *odata; int obnext; int osnext; #ifdef DEBUG if (log_level >= 4) { log_puts("enc: silence "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ odata = out; oshift = p->shift; obias = p->bias; obps = p->bps; obnext = p->bnext; osnext = p->snext; /* * Start conversion. */ odata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { s = ((1U << 31) >> oshift) - obias; for (i = obps; i > 0; i--) { *odata = (unsigned char)s; s >>= 8; odata += obnext; } odata += osnext; } } /* * initialize encoder from native to foreign encoding */ void enc_init(struct conv *p, struct aparams *par, int nch) { p->nch = nch; p->bps = par->bps; if (par->msb) { p->shift = 32 - par->bps * 8; } else { p->shift = 32 - par->bits; } if (par->sig) { p->bias = (1U << 31) >> p->shift; } else { p->bias = 0; } if (!par->le) { p->bfirst = par->bps - 1; p->bnext = -1; p->snext = 2 * par->bps; } else { p->bfirst = 0; p->bnext = 1; p->snext = 0; } #ifdef DEBUG if (log_level >= 3) { log_puts("enc: "); aparams_log(par); log_puts(", "); log_puti(p->nch); log_puts(" channels\n"); } #endif } /* * decode "todo" frames from foreign to native encoding */ void dec_do(struct conv *p, unsigned char *in, unsigned char *out, int todo) { unsigned int f; unsigned int ibps; unsigned int i; unsigned int s = 0xdeadbeef; unsigned char *idata; int ibnext; int isnext; unsigned int ibias; unsigned int ishift; adata_t *odata; #ifdef DEBUG if (log_level >= 4) { log_puts("dec: copying "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = in; odata = (adata_t *)out; ibps = p->bps; ibnext = p->bnext; ibias = p->bias; ishift = p->shift; isnext = p->snext; /* * Start conversion. */ idata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { for (i = ibps; i > 0; i--) { s <<= 8; s |= *idata; idata += ibnext; } idata += isnext; s += ibias; s <<= ishift; s >>= 32 - ADATA_BITS; *odata++ = s - ADATA_UNIT; } } /* * convert a 32-bit float to adata_t, clipping to -1:1, boundaries * excluded */ static inline int f32_to_adata(unsigned int x) { unsigned int s, e, m, y; s = (x >> 31); e = (x >> 23) & 0xff; m = (x << 8) | 0x80000000; /* * f32 exponent is (e - 127) and the point is after the 31-th * bit, thus the shift is: * * 31 - (BITS - 1) - (e - 127) * * to ensure output is in the 0..(2^BITS)-1 range, the minimum * shift is 31 - (BITS - 1), and maximum shift is 31 */ if (e < 127 - (ADATA_BITS - 1)) y = 0; else if (e > 127) y = ADATA_UNIT - 1; else y = m >> (127 + (32 - ADATA_BITS) - e); return (y ^ -s) + s; } /* * convert samples from little endian ieee 754 floats to adata_t */ void dec_do_float(struct conv *p, unsigned char *in, unsigned char *out, int todo) { unsigned int f; unsigned int i; unsigned int s = 0xdeadbeef; unsigned char *idata; int ibnext; int isnext; adata_t *odata; #ifdef DEBUG if (log_level >= 4) { log_puts("dec_float: copying "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = in; odata = (adata_t *)out; ibnext = p->bnext; isnext = p->snext; /* * Start conversion. */ idata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { for (i = 4; i > 0; i--) { s <<= 8; s |= *idata; idata += ibnext; } idata += isnext; *odata++ = f32_to_adata(s); } } /* * convert samples from ulaw/alaw to adata_t */ void dec_do_ulaw(struct conv *p, unsigned char *in, unsigned char *out, int todo, int is_alaw) { unsigned int f; unsigned char *idata; adata_t *odata; short *map; #ifdef DEBUG if (log_level >= 4) { log_puts("dec_ulaw: copying "); log_putu(todo); log_puts(" frames\n"); } #endif map = is_alaw ? dec_alawmap : dec_ulawmap; idata = in; odata = (adata_t *)out; for (f = todo * p->nch; f > 0; f--) *odata++ = map[*idata++] << (ADATA_BITS - 16); } /* * initialize decoder from foreign to native encoding */ void dec_init(struct conv *p, struct aparams *par, int nch) { p->bps = par->bps; p->nch = nch; if (par->msb) { p->shift = 32 - par->bps * 8; } else { p->shift = 32 - par->bits; } if (par->sig) { p->bias = (1U << 31) >> p->shift; } else { p->bias = 0; } if (par->le) { p->bfirst = par->bps - 1; p->bnext = -1; p->snext = 2 * par->bps; } else { p->bfirst = 0; p->bnext = 1; p->snext = 0; } #ifdef DEBUG if (log_level >= 3) { log_puts("dec: "); aparams_log(par); log_puts(", "); log_puti(p->nch); log_puts(" channels\n"); } #endif } /* * mix "todo" input frames on the output with the given volume */ void cmap_add(struct cmap *p, void *in, void *out, int vol, int todo) { adata_t *idata, *odata; int i, j, nch, istart, inext, onext, ostart, y, v; #ifdef DEBUG if (log_level >= 4) { log_puts("cmap: adding "); log_puti(todo); log_puts(" frames\n"); } #endif idata = in; odata = out; ostart = p->ostart; onext = p->onext; istart = p->istart; inext = p->inext; nch = p->nch; v = vol; /* * map/mix input on the output */ for (i = todo; i > 0; i--) { odata += ostart; idata += istart; for (j = nch; j > 0; j--) { y = *odata + ADATA_MUL(*idata, v); if (y >= ADATA_UNIT) y = ADATA_UNIT - 1; else if (y < -ADATA_UNIT) y = -ADATA_UNIT; *odata = y; idata++; odata++; } odata += onext; idata += inext; } } /* * overwrite output with "todo" input frames with the given volume */ void cmap_copy(struct cmap *p, void *in, void *out, int vol, int todo) { adata_t *idata, *odata; int i, j, nch, istart, inext, onext, ostart, v; #ifdef DEBUG if (log_level >= 4) { log_puts("cmap: copying "); log_puti(todo); log_puts(" frames\n"); } #endif idata = in; odata = out; ostart = p->ostart; onext = p->onext; istart = p->istart; inext = p->inext; nch = p->nch; v = vol; /* * copy to the output buffer */ for (i = todo; i > 0; i--) { idata += istart; odata += ostart; for (j = nch; j > 0; j--) { *odata = ADATA_MUL(*idata, v); odata++; idata++; } odata += onext; idata += inext; } } /* * initialize channel mapper, to map a subset of input channel range * into a subset of the output channel range */ void cmap_init(struct cmap *p, int imin, int imax, int isubmin, int isubmax, int omin, int omax, int osubmin, int osubmax) { int cmin, cmax; cmin = -NCHAN_MAX; if (osubmin > cmin) cmin = osubmin; if (omin > cmin) cmin = omin; if (isubmin > cmin) cmin = isubmin; if (imin > cmin) cmin = imin; cmax = NCHAN_MAX; if (osubmax < cmax) cmax = osubmax; if (omax < cmax) cmax = omax; if (isubmax < cmax) cmax = isubmax; if (imax < cmax) cmax = imax; p->ostart = cmin - omin; p->onext = omax - cmax; p->istart = cmin - imin; p->inext = imax - cmax; p->nch = cmax - cmin + 1; #ifdef DEBUG if (log_level >= 3) { log_puts("cmap: nch = "); log_puti(p->nch); log_puts(", ostart = "); log_puti(p->ostart); log_puts(", onext = "); log_puti(p->onext); log_puts(", istart = "); log_puti(p->istart); log_puts(", inext = "); log_puti(p->inext); log_puts("\n"); } #endif } sndio-1.5.0/aucat/dsp.h010066400017510001751000000107411332662053300134040ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2012 Alexandre Ratchov * * 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 DSP_H #define DSP_H #include #include "defs.h" /* * Samples are numbers in the interval [-1, 1[, note that 1, the upper * boundary is excluded. We represent them as signed fixed point numbers * of ADATA_BITS. We also assume that 2^(ADATA_BITS - 1) fits in a int. */ #ifndef ADATA_BITS #define ADATA_BITS 16 #endif #define ADATA_LE (BYTE_ORDER == LITTLE_ENDIAN) #define ADATA_UNIT (1 << (ADATA_BITS - 1)) #if ADATA_BITS == 16 #define ADATA_MUL(x,y) (((int)(x) * (int)(y)) >> (ADATA_BITS - 1)) #define ADATA_MULDIV(x,y,z) ((int)(x) * (int)(y) / (int)(z)) typedef short adata_t; #elif ADATA_BITS == 24 #if defined(__i386__) && defined(__GNUC__) static inline int fp24_mul(int x, int a) { int res; asm volatile ( "imull %2\n\t" "shrdl $23, %%edx, %%eax\n\t" : "=a" (res) : "a" (x), "r" (a) : "%edx" ); return res; } static inline int fp24_muldiv(int x, int a, int b) { int res; asm volatile ( "imull %2\n\t" "idivl %3\n\t" : "=a" (res) : "a" (x), "d" (a), "r" (b) ); return res; } #define ADATA_MUL(x,y) fp24_mul(x, y) #define ADATA_MULDIV(x,y,z) fp24_muldiv(x, y, z); #elif defined(__amd64__) || defined(__sparc64__) #define ADATA_MUL(x,y) \ ((int)(((long long)(x) * (long long)(y)) >> (ADATA_BITS - 1))) #define ADATA_MULDIV(x,y,z) \ ((int)((long long)(x) * (long long)(y) / (long long)(z))) #else #error "no 24-bit code for this architecture" #endif typedef int adata_t; #else #error "only 16-bit and 24-bit precisions are supported" #endif /* * Maximum size of the encording string (the longest possible * encoding is ``s24le3msb''). */ #define ENCMAX 10 /* * Default bytes per sample for the given bits per sample. */ #define APARAMS_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4)) struct aparams { unsigned int bps; /* bytes per sample */ unsigned int bits; /* actually used bits */ unsigned int le; /* 1 if little endian, 0 if big endian */ unsigned int sig; /* 1 if signed, 0 if unsigned */ unsigned int msb; /* 1 if msb justified, 0 if lsb justified */ }; struct resamp { #define RESAMP_NCTX 2 unsigned int ctx_start; adata_t ctx[NCHAN_MAX * RESAMP_NCTX]; unsigned int iblksz, oblksz; int diff; int nch; }; struct conv { int bfirst; /* bytes to skip at startup */ unsigned int bps; /* bytes per sample */ unsigned int shift; /* shift to get 32bit MSB */ unsigned int bias; /* bias of unsigned samples */ int bnext; /* to reach the next byte */ int snext; /* to reach the next sample */ int nch; }; struct cmap { int istart; int inext; int onext; int ostart; int nch; }; #define MIDI_TO_ADATA(m) (aparams_ctltovol[m] << (ADATA_BITS - 16)) extern int aparams_ctltovol[128]; void aparams_init(struct aparams *); void aparams_log(struct aparams *); int aparams_strtoenc(struct aparams *, char *); int aparams_enctostr(struct aparams *, char *); int aparams_native(struct aparams *); void resamp_getcnt(struct resamp *, int *, int *); void resamp_do(struct resamp *, adata_t *, adata_t *, int, int); void resamp_init(struct resamp *, unsigned int, unsigned int, int); void enc_do(struct conv *, unsigned char *, unsigned char *, int); void enc_sil_do(struct conv *, unsigned char *, int); void enc_init(struct conv *, struct aparams *, int); void dec_do(struct conv *, unsigned char *, unsigned char *, int); void dec_do_float(struct conv *, unsigned char *, unsigned char *, int); void dec_do_ulaw(struct conv *, unsigned char *, unsigned char *, int, int); void dec_init(struct conv *, struct aparams *, int); void cmap_add(struct cmap *, void *, void *, int, int); void cmap_copy(struct cmap *, void *, void *, int, int); void cmap_init(struct cmap *, int, int, int, int, int, int, int, int); #endif /* !defined(DSP_H) */ sndio-1.5.0/aucat/sysex.h010066400017510001751000000050201332662053300137630ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2011 Alexandre Ratchov * * 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 SYSEX_H #define SYSEX_H #include /* * start and end markers */ #define SYSEX_START 0xf0 #define SYSEX_END 0xf7 /* * type/vendor namespace IDs we use */ #define SYSEX_TYPE_RT 0x7f /* real-time universal */ #define SYSEX_TYPE_EDU 0x7d /* non-comercial */ /* * realtime messages in the "universal real-time" namespace */ #define SYSEX_MTC 0x01 /* mtc messages */ #define SYSEX_MTC_FULL 0x01 /* mtc full frame message */ #define SYSEX_CONTROL 0x04 #define SYSEX_MASTER 0x01 #define SYSEX_MMC 0x06 #define SYSEX_MMC_STOP 0x01 #define SYSEX_MMC_START 0x02 #define SYSEX_MMC_LOC 0x44 #define SYSEX_MMC_LOC_LEN 0x06 #define SYSEX_MMC_LOC_CMD 0x01 /* * sepcial "any" midi device number */ #define SYSEX_DEV_ANY 0x7f /* * minimum size of sysex message we accept */ #define SYSEX_SIZE(m) (5 + sizeof(struct sysex_ ## m)) /* * all possible system exclusive messages we support. */ struct sysex { uint8_t start; uint8_t type; /* type or vendor id */ uint8_t dev; /* device or product id */ uint8_t id0; /* message id */ uint8_t id1; /* sub-id */ union sysex_all { struct sysex_empty { uint8_t end; } empty; struct sysex_master { uint8_t fine; uint8_t coarse; uint8_t end; } master; struct sysex_start { uint8_t end; } start; struct sysex_stop { uint8_t end; } stop; struct sysex_loc { uint8_t len; uint8_t cmd; #define MTC_FPS_24 0 #define MTC_FPS_25 1 #define MTC_FPS_30 3 uint8_t hr; /* MSB contain MTC_FPS */ uint8_t min; uint8_t sec; uint8_t fr; uint8_t cent; uint8_t end; } loc; struct sysex_full { uint8_t hr; uint8_t min; uint8_t sec; uint8_t fr; uint8_t end; } full; } u; }; #endif /* !defined(SYSEX_H) */ sndio-1.5.0/aucat/utils.c010066400017510001751000000066431332662053300137570ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2003-2012 Alexandre Ratchov * * 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. */ /* * log_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 #include #include #include #include #include "utils.h" /* * log buffer size */ #define LOG_BUFSZ 8192 /* * store a character in the log */ #define LOG_PUTC(c) do { \ if (log_used < LOG_BUFSZ) \ log_buf[log_used++] = (c); \ } while (0) char log_buf[LOG_BUFSZ]; /* buffer where traces are stored */ unsigned int log_used = 0; /* bytes used in the buffer */ unsigned int log_sync = 1; /* if true, flush after each '\n' */ /* * write the log buffer on stderr */ void log_flush(void) { if (log_used == 0) return; write(STDERR_FILENO, log_buf, log_used); log_used = 0; } /* * store a string in the log */ void log_puts(char *msg) { char *p = msg; int c; while ((c = *p++) != '\0') { LOG_PUTC(c); if (log_sync && c == '\n') log_flush(); } } /* * store a hex in the log */ void log_putx(unsigned long num) { char dig[sizeof(num) * 2], *p = dig, c; unsigned int 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; LOG_PUTC(c); } } else LOG_PUTC('0'); } /* * store an unsigned decimal in the log */ void log_putu(unsigned long num) { char dig[sizeof(num) * 3], *p = dig; unsigned int ndig; if (num != 0) { for (ndig = 0; num != 0; ndig++) { *p++ = num % 10; num /= 10; } for (; ndig != 0; ndig--) LOG_PUTC(*(--p) + '0'); } else LOG_PUTC('0'); } /* * store a signed decimal in the log */ void log_puti(long num) { if (num < 0) { LOG_PUTC('-'); num = -num; } log_putu(num); } /* * abort program execution after a fatal error */ void panic(void) { log_flush(); (void)kill(getpid(), SIGABRT); _exit(1); } /* * allocate a (small) amount of memory, and abort if it fails */ void * xmalloc(size_t size) { void *p; p = malloc(size); if (p == NULL) { log_puts("failed to allocate "); log_putx(size); log_puts(" bytes\n"); panic(); } return p; } /* * free memory allocated with xmalloc() */ void xfree(void *p) { #ifdef DEBUG if (p == NULL) { log_puts("xfree with NULL arg\n"); panic(); } #endif free(p); } /* * xmalloc-style strdup(3) */ char * xstrdup(char *s) { size_t size; void *p; size = strlen(s) + 1; p = xmalloc(size); memcpy(p, s, size); return p; } sndio-1.5.0/aucat/utils.h010066400017510001751000000026641332662053300137630ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2003-2012 Alexandre Ratchov * * 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 UTILS_H #define UTILS_H #include void log_puts(char *); void log_putx(unsigned long); void log_putu(unsigned long); void log_puti(long); void panic(void); void log_flush(void); void *xmalloc(size_t); char *xstrdup(char *); void xfree(void *); /* * Log 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 (eg. new streams, new parameters ...) * 4 - data blocks and messages */ extern unsigned int log_level; extern unsigned int log_sync; #endif sndio-1.5.0/bsd-compat004077500017510001751000000000001332662053300133225ustar00alexalexsndio-1.5.0/bsd-compat/bsd-compat.h010066400017510001751000000017131332662053300156020ustar00alexalex#ifndef BSD_COMPAT_H #define BSD_COMPAT_H #ifndef HAVE_ISSETUGID #define issetugid _sndio_issetugid int issetugid(void); #endif #ifndef HAVE_STRLCAT #define strlcat _sndio_strlcat size_t strlcat(char *, const char *, size_t); #endif #ifndef HAVE_STRLCPY #define strlcpy _sndio_strlcpy size_t strlcpy(char *, const char *, size_t); #endif #ifndef HAVE_STRTONUM #define strtonum _sndio_strtonum long long strtonum(const char *, long long, long long, const char **); #endif #ifndef HAVE_SOCK_CLOEXEC #define strtonum _sndio_strtonum long long strtonum(const char *, long long, long long, const char **); #endif #ifndef HAVE_CLOCK_GETTIME #define CLOCK_MONOTONIC 0 #define clock_gettime _sndio_clock_gettime struct timespec; int clock_gettime(int, struct timespec *); #endif #ifndef HAVE_SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif #if !defined(CLOCK_UPTIME) && defined(CLOCK_MONOTONIC) #define CLOCK_UPTIME CLOCK_MONOTONIC #endif #endif /* !defined(BSD_COMPAT_H) */ sndio-1.5.0/bsd-compat/clock_gettime.c010066400017510001751000000022651332662053300163600ustar00alexalex/* * Copyright (c) 2017 Alexandre Ratchov * * 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 #include #include #include #include "bsd-compat.h" #ifndef HAVE_CLOCK_GETTIME int _sndio_clock_gettime(int timer, struct timespec *ts) { struct timeval tv; if (timer != CLOCK_MONOTONIC) { errno = EINVAL; return -1; } if (gettimeofday(&tv, NULL) < 0) return -1; ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000; return 0; } #endif sndio-1.5.0/bsd-compat/issetugid.c010066400017510001751000000017361332662053300155510ustar00alexalex/* * Copyright (c) 2010 Jacob Meuser * * 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 #include "bsd-compat.h" #ifndef HAVE_ISSETUGID int issetugid(void) { if (getuid() != geteuid()) return 1; if (getgid() != getegid()) return 1; return 0; } #endif sndio-1.5.0/bsd-compat/strlcat.c010066400017510001751000000033071332662053300152210ustar00alexalex/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * 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 #include #include "bsd-compat.h" #ifndef HAVE_STRLCAT /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } #endif sndio-1.5.0/bsd-compat/strlcpy.c010066400017510001751000000031131332662053300152400ustar00alexalex/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * 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 #include #include "bsd-compat.h" #ifndef HAVE_STRLCPY /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif sndio-1.5.0/bsd-compat/strtonum.c010066400017510001751000000036241332662053300154420ustar00alexalex/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller * All rights reserved. * * 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 #include #include #include "bsd-compat.h" #ifndef LLONG_MIN #define LLONG_MIN (-LLONG_MAX-1) #endif #ifndef LLONG_MAX #define LLONG_MAX __LONG_LONG_MAX__ #endif #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 #ifndef HAVE_STRTONUM long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { long long ll = 0; char *ep; int error = 0; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE }, }; ev[0].err = errno; errno = 0; if (minval > maxval) error = INVALID; else { ll = strtoll(numstr, &ep, 10); if (numstr == ep || *ep != '\0') error = INVALID; else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) error = TOOSMALL; else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ll = 0; return (ll); } #endif sndio-1.5.0/configure010077500017510001751000000150021332662053300132520ustar00alexalex#!/bin/sh # # display help screeen # help() { cat << END Usage: configure [options] --prefix=DIR set install prefix to DIR [$prefix] --bindir=DIR install executables in DIR [\$prefix/bin] --datadir=DIR install read-only data in DIR [\$prefix/share] --includedir=DIR install header files in DIR [\$prefix/include] --libdir=DIR install libraries in DIR [\$prefix/lib] --mandir=DIR install man pages in DIR [\$prefix/man] --precision=NUMBER aucat/sndiod processing precision [$precision] --privsep-user=USER non-privileged user for sndio daemon [$user] --enable-alsa enable alsa audio & midi backends [$alsa] --disable-alsa disable alsa audio & midi backends --enable-sun enable sun audio backend [$sun] --disable-sun disable sun audio backend --enable-rmidi enable character device midi backend [$rmidi] --disable-rmidi disable character device midi backend --enable-umidi enable usb-midi backend [$umidi] --disable-umidi disable usb-midi backend --with-libbsd use the libbsd rather than bsd-compat/* --without-libbsd don't use libbsd END } # # defaults # prefix=/usr/local # where to install sndio so="libsndio.so.\${MAJ}.\${MIN}" # shared libs to build alsa=no # do we want alsa support ? sun=no # do we want sun support ? oss=no # do we want oss support ? rmidi=no # do we want support for raw char dev ? umidi=no # do we want support for umidi ? precision=16 # aucat/sndiod arithmetic precision user=_sndio # non-privileged user for sndio daemon libbsd=no # use libbsd? so_ldflags= # extra linker flags for shared libs unset vars # variables passed as arguments unset bindir # path where to install binaries unset datadir # path where to install doc unset mandir # path where to install man pages unset includedir # path where to install header file unset libdir # path where to install library unset defs # no extra #defines unset ldadd # no extra libraries (-l options) # # guess OS-specific parameters # case `uname` in Linux) alsa=yes ldadd="-lrt" user=sndiod so_ldflags="-Wl,-soname=libsndio.so.\${MAJ}.\${MIN}" so_link="libsndio.so" defs='-D_GNU_SOURCE -DHAVE_SOCK_CLOEXEC -DHAVE_CLOCK_GETTIME' ;; NetBSD) sun=no rmidi=yes user=_sndio so_link="libsndio.so" defs='-DHAVE_ARC4RANDOM -DHAVE_ISSETUGID \\\ -DHAVE_STRLCAT -DHAVE_STRLCPY \\\ -DHAVE_SOCK_CLOEXEC -DHAVE_CLOCK_GETTIME' ;; OpenBSD) sun=yes rmidi=yes user=_sndio defs='-DHAVE_ARC4RANDOM -DHAVE_ISSETUGID \\\ -DHAVE_STRLCAT -DHAVE_STRLCPY -DHAVE_STRTONUM \\\ -DHAVE_SOCK_CLOEXEC -DHAVE_CLOCK_GETTIME' ;; DragonFly|FreeBSD) oss=yes umidi=yes user=_sndio so_ldflags="-Wl,-soname=libsndio.so.\${MAJ}.\${MIN}" so_link="libsndio.so" defs='-DHAVE_ARC4RANDOM -DHAVE_ISSETUGID \\\ -DHAVE_STRLCAT -DHAVE_STRLCPY -DHAVE_STRTONUM \\\ -DHAVE_SOCK_CLOEXEC -DHAVE_CLOCK_GETTIME' ;; Darwin) rmidi=no so="libsndio.\${MAJ}.\${MIN}.dylib" so_link="libsndio.dylib" defs='-DHAVE_ARC4RANDOM -DHAVE_ISSETUGID \\\ -DHAVE_STRLCAT -DHAVE_STRLCPY' esac # shell word separator (none) IFS='' # sed-quoted new-line nl='\ ' for i; do case "$i" in --prefix=*) prefix="${i#--prefix=}" shift;; --bindir=*) bindir="${i#--bindir=}" shift;; --datadir=*) datadir="${i#--datadir=}" shift;; --includedir=*) includedir="${i#--includedir=}" shift;; --libdir=*) libdir="${i#--libdir=}" shift;; --mandir=*) mandir="${i#--mandir=}" shift;; --enable-alsa) alsa=yes shift;; --disable-alsa) alsa=no shift;; --enable-oss) oss=yes shift;; --disable-oss) oss=no shift;; --enable-sun) sun=yes shift;; --disable-sun) sun=no shift;; --enable-rmidi) rmidi=yes shift;; --disable-rmidi) rmidi=no shift;; --enable-umidi) umidi=yes shift;; --disable-umidi) umidi=no shift;; --privsep-user=*) user="${i#--privsep-user=}" shift;; --precision=*) precision="${i#--precision=}" if [ "$precision" != 16 -a "$precision" != 24 ]; then echo "$i: only 16 and 24 are supported" >&2 exit 1 fi shift;; --with-libbsd) libbsd=yes shift;; --without-libbsd) libbsd=no shift;; CC=*|CFLAGS=*|LDFLAGS=*) vars="$vars$i$nl" shift;; *) help exit 1 ;; esac done # # if $xxxdir is not specified, define it to $prefix/xxx # bindir="${bindir:-$prefix/bin}" datadir="${datadir:-$prefix/share}" includedir="${includedir:-$prefix/include}" libdir="${libdir:-$prefix/lib}" mandir="${mandir:-$prefix/share/man}" # # umidi implies rmidi # if [ $umidi = yes ]; then rmidi=yes fi # # if using ALSA, add corresponding parameters # if [ $alsa = yes ]; then defs="$defs -DUSE_ALSA" ldadd="$ldadd -lasound" fi # # if using OSS, add corresponding parameters # if [ $oss = yes ]; then defs="$defs -DUSE_OSS" fi # # if using Sun API, add corresponding parameters # if [ $sun = yes ]; then defs="$defs -DUSE_SUN" fi # # if using raw character devices for midi, add corresponding parameters # if [ $rmidi = yes ]; then defs="$defs -DUSE_RMIDI" fi # # if using usb-midi raw devices for midi, add corresponding parameters # if [ $umidi = yes ]; then defs="$defs -DUSE_UMIDI" fi # # if using libbsd, add corresponding parameters # if [ $libbsd = yes ]; then # no arc4random as libbsd has a questionable implementation defs="$defs -DHAVE_STRLCPY -DHAVE_STRLCAT -DHAVE_STRTONUM" ldadd="$ldadd -lbsd" fi for f in Makefile aucat/Makefile midicat/Makefile sndiod/Makefile \ libsndio/Makefile \ examples/Makefile \ contrib/init.d.sndiod \ contrib/sndiod.service do sed \ -e "s:@bindir@:$bindir:" \ -e "s:@datadir@:$datadir:" \ -e "s:@includedir@:$includedir:" \ -e "s:@libdir@:$libdir:" \ -e "s:@mandir@:$mandir:" \ -e "s:@defs@:$defs:" \ -e "s:@ldadd@:$ldadd:" \ -e "s:@so@:$so:" \ -e "s:@so_link@:$so_link:" \ -e "s:@so_ldflags@:$so_ldflags:" \ -e "s:@vars@:${vars}:" \ -e "s:@precision@:$precision:" \ -e "s:@user@:$user:" \ < $f.in > $f done chmod +x contrib/init.d.sndiod cat <&2 exit 3 ;; esac : sndio-1.5.0/contrib/sndiod.service.in010066400017510001751000000003371332662053300162570ustar00alexalex[Unit] Description=sndio audio and MIDI server After=network.target [Service] Type=forking Restart=on-abort EnvironmentFile=-/etc/default/sndiod ExecStart=@bindir@/sndiod $DAEMON_OPTS [Install] WantedBy=multi-user.target sndio-1.5.0/examples004077500017510001751000000000001332662053300131075ustar00alexalexsndio-1.5.0/examples/Makefile.in010066400017510001751000000016421332662053300152330ustar00alexalex# extra includes paths (-I options) INCLUDE = -I../libsndio # extra libraries paths (-L options) LIB = -L../libsndio # extra defines (-D options) DEFS = -DDEBUG @defs@ # extra libraries (-l options) LDADD = -lsndio @ldadd@ # variables defined on configure script command line (if any) @vars@ PROG = play rec fd vol cap all: ${PROG} play: play.o tools.o ${CC} ${LDFLAGS} ${LIB} -o play play.o tools.o ${LDADD} rec: rec.o tools.o ${CC} ${LDFLAGS} ${LIB} -o rec rec.o tools.o ${LDADD} fd: fd.o tools.o ${CC} ${LDFLAGS} ${LIB} -o fd fd.o tools.o ${LDADD} vol: vol.o tools.o ${CC} ${LDFLAGS} ${LIB} -o vol vol.o tools.o ${LDADD} cap: cap.o tools.o ${CC} ${LDFLAGS} ${LIB} -o cap cap.o tools.o ${LDADD} .c.o: ${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $< tools.o: tools.c play.o: play.c tools.h rec.o: rec.c tools.h fd.o: fd.c tools.h cap.o: cap.c tools.h vol.o: vol.c tools.h clean: rm -f ${PROG} *.o sndio-1.5.0/examples/cap.c010066400017510001751000000034631332662053300141000ustar00alexalex#include #include #include #include #include #include #include #include #include "tools.h" struct sio_par par; struct sio_cap cap; static void print_enc(struct sio_enc *enc) { printf("%s%d", enc->sig ? "s" : "u", enc->bits); if (enc->bps > 1) printf("%s", enc->le ? "le" : "be"); if (enc->bps != SIO_BPS(enc->bits)) printf("%d%s", enc->bps, enc->msb ? "msb" : "lsb"); } static void print_cap(struct sio_cap *cap) { unsigned n, i; for (n = 0; n < cap->nconf; n++) { printf("config %d\n", n); printf("\tenc:"); for (i = 0; i < SIO_NENC; i++) { if (cap->confs[n].enc & (1 << i)) { printf(" "); print_enc(&cap->enc[i]); } } printf("\n\tpchan:"); for (i = 0; i < SIO_NCHAN; i++) { if (cap->confs[n].pchan & (1 << i)) printf(" %d", cap->pchan[i]); } printf("\n\trchan:"); for (i = 0; i < SIO_NCHAN; i++) { if (cap->confs[n].rchan & (1 << i)) printf(" %d", cap->rchan[i]); } printf("\n\trate:"); for (i = 0; i < SIO_NRATE; i++) { if (cap->confs[n].rate & (1 << i)) printf(" %d", cap->rate[i]); } printf("\n"); } } int main(int argc, char **argv) { int ch; unsigned mode = SIO_PLAY | SIO_REC; struct sio_hdl *hdl; while ((ch = getopt(argc, argv, "pr")) != -1) { switch(ch) { case 'p': mode &= ~SIO_REC; break; case 'r': mode &= ~SIO_PLAY; break; default: fprintf(stderr, "usage: cap [-pr]\n"); exit(1); break; } } if (mode == 0) { fprintf(stderr, "-p and -r flags are mutually exclusive\n"); exit(1); } hdl = sio_open(NULL, mode, 0); if (hdl == NULL) { fprintf(stderr, "sio_open() failed\n"); exit(1); } if (!sio_getcap(hdl, &cap)) { fprintf(stderr, "sio_getcap() failed\n"); exit(1); } print_cap(&cap); sio_close(hdl); return 0; } sndio-1.5.0/examples/fd.c010066400017510001751000000152521332662053300137250ustar00alexalex#include #include #include #include #include #include #include #include #include #include "tools.h" struct buf { /* simple circular fifo */ unsigned start; /* first used byte */ unsigned used; /* number of used bytes */ #define BUF_LEN (240 * 0x1000) /* i/o buffer size */ unsigned char data[BUF_LEN]; }; void cb(void *, int); void buf_read(struct buf *, int); void buf_write(struct buf *, int); unsigned buf_rec(struct buf *, struct sio_hdl *); unsigned buf_play(struct buf *, struct sio_hdl *); void usage(void); char *xstr[] = SIO_XSTRINGS; struct sio_par par; struct buf playbuf, recbuf; unsigned long long hwpos, wrpos, rdpos; int tick = 0; int randomize, xruns; void cb(void *addr, int delta) { hwpos += delta; tick = 1; } /* * read buffer contents from a file */ void buf_read(struct buf *buf, int fd) { unsigned count, end, avail; int n; for (;;) { avail = BUF_LEN - buf->used; if (avail == 0) break; end = buf->start + buf->used; if (end >= BUF_LEN) end -= BUF_LEN; count = BUF_LEN - end; if (count > avail) count = avail; n = read(fd, buf->data + end, count); if (n < 0) { perror("buf_read: read"); exit(1); } if (n == 0) { bzero(buf->data + end, count); n = count; } buf->used += n; } } /* * write buffer contents to file */ void buf_write(struct buf *buf, int fd) { unsigned count; int n; while (buf->used) { count = BUF_LEN - buf->start; if (count > buf->used) count = buf->used; n = write(fd, buf->data + buf->start, count); if (n < 0) { perror("buf_write: write"); exit(1); } buf->used -= n; buf->start += n; if (buf->start >= BUF_LEN) buf->start -= BUF_LEN; } } /* * read recorded samples from the device, without blocking */ unsigned buf_rec(struct buf *buf, struct sio_hdl *hdl) { unsigned count, end, avail, done = 0; int n; for (;;) { avail = BUF_LEN - buf->used; if (avail == 0) break; end = buf->start + buf->used; if (end >= BUF_LEN) end -= BUF_LEN; count = BUF_LEN - end; if (count > avail) count = avail; /* try to confuse the server */ if (randomize) count = 1 + (rand() % count); n = sio_read(hdl, buf->data + end, count); if (n == 0) { if (sio_eof(hdl)) { fprintf(stderr, "sio_read() failed\n"); exit(1); } break; } rdpos += n; buf->used += n; done += n; } return done; } /* * write samples to the device, without blocking */ unsigned buf_play(struct buf *buf, struct sio_hdl *hdl) { unsigned count, done = 0; int n; while (buf->used) { count = BUF_LEN - buf->start; if (count > buf->used) count = buf->used; /* try to confuse the server */ if (randomize) count = 1 + (rand() % count); n = sio_write(hdl, buf->data + buf->start, count); if (n == 0) { if (sio_eof(hdl)) { fprintf(stderr, "sio_write() failed\n"); exit(1); } break; } wrpos += n; //write(STDOUT_FILENO, buf->data + buf->start, n); buf->used -= n; buf->start += n; if (buf->start >= BUF_LEN) buf->start -= BUF_LEN; done += n; } return done; } void usage(void) { fprintf(stderr, "usage: fd [-vRX] [-b size] [-c pchan] [-C rchan] [-e enc]\n" " [-i file] [-o file] [-r rate] [-x mode]\n"); } int main(int argc, char **argv) { int ch, recfd, playfd, nfds, events, revents; char *recpath, *playpath; struct sio_hdl *hdl; #define NFDS 16 struct pollfd pfd[NFDS]; unsigned mode; recfd = -1; recpath = NULL; playfd = -1; playpath = NULL; /* * defaults parameters */ sio_initpar(&par); par.sig = 1; par.bits = 16; par.pchan = par.rchan = 2; par.rate = 48000; while ((ch = getopt(argc, argv, "RXr:c:C:e:i:o:b:x:")) != -1) { switch(ch) { case 'r': if (sscanf(optarg, "%u", &par.rate) != 1) { fprintf(stderr, "%s: bad rate\n", optarg); exit(1); } break; case 'c': if (sscanf(optarg, "%u", &par.pchan) != 1) { fprintf(stderr, "%s: bad play chans\n", optarg); exit(1); } break; case 'C': if (sscanf(optarg, "%u", &par.rchan) != 1) { fprintf(stderr, "%s: bad rec chans\n", optarg); exit(1); } break; case 'e': if (!strtoenc(&par, optarg)) { fprintf(stderr, "%s: bad encoding\n", optarg); exit(1); } break; case 'o': recpath = optarg; break; case 'i': playpath = optarg; break; case 'b': if (sscanf(optarg, "%u", &par.appbufsz) != 1) { fprintf(stderr, "%s: bad buf size\n", optarg); exit(1); } break; case 'x': for (par.xrun = 0;; par.xrun++) { if (par.xrun == sizeof(xstr) / sizeof(char *)) { fprintf(stderr, "%s: bad xrun mode\n", optarg); exit(1); } if (strcmp(xstr[par.xrun], optarg) == 0) break; } break; case 'R': randomize = 1; break; case 'X': xruns = 1; break; default: usage(); exit(1); break; } } mode = 0; if (recpath) mode |= SIO_REC; if (playpath) mode |= SIO_PLAY; if (mode == 0) { fprintf(stderr, "-i or -o option required\n"); exit(0); } hdl = sio_open(SIO_DEVANY, mode, 1); if (hdl == NULL) { fprintf(stderr, "sio_open() failed\n"); exit(1); } if (sio_nfds(hdl) > NFDS) { fprintf(stderr, "too many descriptors to poll\n"); exit(1); } sio_onmove(hdl, cb, NULL); if (!sio_setpar(hdl, &par)) { fprintf(stderr, "sio_setpar() failed\n"); exit(1); } if (!sio_getpar(hdl, &par)) { fprintf(stderr, "sio_setpar() failed\n"); exit(1); } fprintf(stderr, "using %u%%%u frame buffer\n", par.bufsz, par.round); if (!sio_start(hdl)) { fprintf(stderr, "sio_start() failed\n"); exit(1); } events = 0; if (recpath) { recfd = open(recpath, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (recfd < 0) { perror(recpath); exit(1); } events |= POLLIN; } if (playpath) { playfd = open(playpath, O_RDONLY, 0); if (playfd < 0) { perror(playpath); exit(1); } events |= POLLOUT; buf_read(&playbuf, playfd); buf_play(&playbuf, hdl); } for (;;) { nfds = sio_pollfd(hdl, pfd, events); while (poll(pfd, nfds, 1000) < 0) { if (errno == EINTR) continue; perror("poll"); exit(1); } revents = sio_revents(hdl, pfd); if (revents & POLLHUP) { fprintf(stderr, "device hangup\n"); exit(0); } if (tick) { fprintf(stderr, "pos = %+7lld, " "plat = %+7lld, rlat = %+7lld\n", hwpos, wrpos - hwpos * par.pchan * par.bps, hwpos * par.rchan * par.bps - rdpos); tick = 0; if (xruns) { if (rand() % 20 == 0) sleep(2); } } if (revents & POLLIN) { buf_rec(&recbuf, hdl); buf_write(&recbuf, recfd); } if (revents & POLLOUT) { buf_play(&playbuf, hdl); buf_read(&playbuf, playfd); } } sio_close(hdl); return 0; } sndio-1.5.0/examples/play.c010066400017510001751000000055211332662053300142770ustar00alexalex#include #include #include #include #include #include #include #include #include "tools.h" void cb(void *, int); void usage(void); unsigned char *buf; struct sio_par par; char *xstr[] = SIO_XSTRINGS; unsigned long long playpos = 0, writepos = 0; int tick = 0; void cb(void *addr, int delta) { unsigned int bytes; bytes = delta * par.bps * par.pchan; /* fprintf(stderr, "advanced by %d\n", bytes); */ playpos += bytes; tick = 1; } void usage(void) { fprintf(stderr, "usage: play [-b size] [-c nchan] [-e enc] [-r rate]\n"); } int main(int argc, char **argv) { int ch; struct sio_hdl *hdl; ssize_t n, len; size_t bufsz; /* * defaults parameters */ sio_initpar(&par); par.sig = 1; par.bits = 16; par.pchan = 2; par.rate = 48000; while ((ch = getopt(argc, argv, "r:c:e:b:x:")) != -1) { switch(ch) { case 'r': if (sscanf(optarg, "%u", &par.rate) != 1) { fprintf(stderr, "%s: bad rate\n", optarg); exit(1); } break; case 'c': if (sscanf(optarg, "%u", &par.pchan) != 1) { fprintf(stderr, "%s: bad channels\n", optarg); exit(1); } break; case 'e': if (!strtoenc(&par, optarg)) { fprintf(stderr, "%s: bad encoding\n", optarg); exit(1); } break; case 'b': if (sscanf(optarg, "%u", &par.appbufsz) != 1) { fprintf(stderr, "%s: bad buf size\n", optarg); exit(1); } break; case 'x': for (par.xrun = 0;; par.xrun++) { if (par.xrun == sizeof(xstr) / sizeof(char *)) { fprintf(stderr, "%s: bad xrun mode\n", optarg); exit(1); } if (strcmp(xstr[par.xrun], optarg) == 0) break; } break; default: usage(); exit(1); break; } } hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0); if (hdl == NULL) { fprintf(stderr, "sio_open() failed\n"); exit(1); } sio_onmove(hdl, cb, NULL); if (!sio_setpar(hdl, &par)) { fprintf(stderr, "sio_setpar() failed\n"); exit(1); } if (!sio_getpar(hdl, &par)) { fprintf(stderr, "sio_getpar() failed\n"); exit(1); } bufsz = par.bps * par.pchan * par.round; buf = malloc(bufsz); if (buf == NULL) { fprintf(stderr, "failed to allocate %zu bytes\n", bufsz); exit(1); } fprintf(stderr, "%zu bytes buffer, max latency %u frames\n", bufsz, par.bufsz); if (!sio_start(hdl)) { fprintf(stderr, "sio_start() failed\n"); exit(1); } for (;;) { len = read(STDIN_FILENO, buf, bufsz); if (len < 0) { perror("stdin"); exit(1); } if (len == 0) { fprintf(stderr, "eof\n"); break; } n = sio_write(hdl, buf, len); if (n == 0) { fprintf(stderr, "sio_write: failed\n"); exit(1); } writepos += n; if (tick) { fprintf(stderr, "playpos = %lld, writepos = %lld, latency = %lld\n", playpos, writepos, writepos - playpos); tick = 0; } } sio_close(hdl); return 0; } sndio-1.5.0/examples/rec.c010066400017510001751000000060241332662053300141020ustar00alexalex#include #include #include #include #include #include #include #include #include "tools.h" void cb(void *, int); void usage(void); unsigned char *buf; struct sio_par par; char *xstr[] = SIO_XSTRINGS; unsigned long long recpos = 0, readpos = 0; int tick = 0; void cb(void *addr, int delta) { unsigned int bytes; bytes = delta * par.bps * par.rchan; // fprintf(stderr, "advanced by %d\n", bytes); recpos += bytes; tick = 1; } void usage(void) { fprintf(stderr, "usage: rec [-b size] [-c nchan] [-e enc] [-n nbytes] " "[-r rate] [-x xrun]\n"); } int main(int argc, char **argv) { int ch; struct sio_hdl *hdl; size_t bufsz; ssize_t n, nbytes; /* * defaults parameters */ sio_initpar(&par); par.sig = 1; par.bits = 16; par.rchan = 2; par.rate = 48000; nbytes = -1; while ((ch = getopt(argc, argv, "b:c:e:n:r:x:")) != -1) { switch (ch) { case 'b': if (sscanf(optarg, "%u", &par.appbufsz) != 1) { fprintf(stderr, "%s: bad buf size\n", optarg); exit(1); } break; case 'c': if (sscanf(optarg, "%u", &par.rchan) != 1) { fprintf(stderr, "%s: bad channels number\n", optarg); exit(1); } break; case 'e': if (!strtoenc(&par, optarg)) { fprintf(stderr, "%s: unknown encoding\n", optarg); exit(1); } break; case 'n': if (sscanf(optarg, "%zu", &nbytes) != 1) { fprintf(stderr, "%s: bad bytes count\n", optarg); exit(1); } break; case 'r': if (sscanf(optarg, "%u", &par.rate) != 1) { fprintf(stderr, "%s: bad rate\n", optarg); exit(1); } break; case 'x': for (par.xrun = 0;; par.xrun++) { if (par.xrun == sizeof(xstr) / sizeof(char *)) { fprintf(stderr, "%s: bad xrun mode\n", optarg); exit(1); } if (strcmp(xstr[par.xrun], optarg) == 0) break; } break; default: usage(); exit(1); break; } } hdl = sio_open(SIO_DEVANY, SIO_REC, 0); if (hdl == NULL) { fprintf(stderr, "sio_open() failed\n"); exit(1); } sio_onmove(hdl, cb, NULL); if (!sio_setpar(hdl, &par)) { fprintf(stderr, "sio_setpar() failed\n"); exit(1); } if (!sio_getpar(hdl, &par)) { fprintf(stderr, "sio_getpar() failed\n"); exit(1); } bufsz = par.bps * par.rchan * par.round; buf = malloc(bufsz); if (buf == NULL) { fprintf(stderr, "failed to allocate %zu bytes\n", bufsz); exit(1); } fprintf(stderr, "%zu bytes buffer, max latency %u frames\n", bufsz, par.bufsz); if (!sio_start(hdl)) { fprintf(stderr, "sio_start() failed\n"); exit(1); } while (nbytes != 0) { n = bufsz; if (nbytes >= 0 && n > nbytes) n = nbytes; n = sio_read(hdl, buf, n); if (n == 0) { fprintf(stderr, "sio_write: failed\n"); exit(1); } nbytes -= n; readpos += n; if (tick) { fprintf(stderr, "recpos = %lld, readpos = %lld, latency = %lld\n", recpos, readpos, recpos - readpos); tick = 0; } if (write(STDOUT_FILENO, buf, n) < 0) { perror("stdout"); exit(1); } } sio_close(hdl); return 0; } sndio-1.5.0/examples/tools.c010066400017510001751000000056261332662053300145000ustar00alexalex/* $OpenBSD: tools.c,v 1.1 2010/11/06 20:25:42 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include "tools.h" /* * Generate a string corresponding to the encoding in par, * return the length of the resulting string */ int 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 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; } sndio-1.5.0/examples/tools.h010066400017510001751000000025561332662053300145040ustar00alexalex/* $OpenBSD: tools.h,v 1.1 2011/04/12 07:12:59 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 TOOLS_H #define TOOLS_H /* * maximum size of the encording string (the longest possible * encoding is ``s24le3msb'') */ #define SIO_ENCMAX 10 /* * 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" #ifdef __cplusplus extern "C" { #endif struct sio_par; int strtoenc(struct sio_par *, char *); int enctostr(struct sio_par *, char *); #ifdef __cplusplus } #endif #endif /* !defined(TOOLS_H) */ sndio-1.5.0/examples/vol.c010066400017510001751000000056701332662053300141370ustar00alexalex#include #include #include #include #include #include #include #include #include #include #include "tools.h" #define BUFSZ 0x100 void usage(void); void onvol(void *, unsigned); unsigned char buf[BUFSZ]; struct sio_par par; unsigned vol = 0xdeadbeef; void usage(void) { fprintf(stderr, "usage: vol [-r rate] [-c nchan] [-e enc]\n"); } void onvol(void *addr, unsigned val) { vol = val; fprintf(stderr, "volume set to %u\n", vol); } int main(int argc, char **argv) { int ch; int tty; struct sio_hdl *hdl; struct termios tio; struct pollfd pfd[2]; char cmd; ssize_t n, len; /* * defaults parameters */ sio_initpar(&par); par.sig = 1; par.bits = 16; par.pchan = 2; par.rate = 48000; if (isatty(STDIN_FILENO)) { fprintf(stderr, "stdin can't be a tty\n"); exit(1); } tty = open("/dev/tty", O_RDWR); if (tty < 0) { perror("/dev/tty"); exit(1); } if (tcgetattr(tty, &tio) < 0) { perror("tcsetattr"); exit(1); } tio.c_lflag &= ~ICANON; tio.c_lflag &= ~ECHO; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; if (tcsetattr(tty, TCSAFLUSH, &tio) < 0) { perror("tcsetattr"); exit(1); } while ((ch = getopt(argc, argv, "r:c:e:b:x:")) != -1) { switch(ch) { case 'r': if (sscanf(optarg, "%u", &par.rate) != 1) { fprintf(stderr, "%s: bad rate\n", optarg); exit(1); } break; case 'c': if (sscanf(optarg, "%u", &par.pchan) != 1) { fprintf(stderr, "%s: bad channels\n", optarg); exit(1); } break; case 'e': if (!strtoenc(&par, optarg)) { fprintf(stderr, "%s: bad encoding\n", optarg); exit(1); } break; default: usage(); exit(1); break; } } hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0); if (hdl == NULL) { fprintf(stderr, "sio_open() failed\n"); exit(1); } if (!sio_setpar(hdl, &par)) { fprintf(stderr, "sio_setpar() failed\n"); exit(1); } if (!sio_onvol(hdl, onvol, NULL)) fprintf(stderr, "warning: no volume knob on this device\n"); fprintf(stderr, "use ``+'' and ``-'' to adjust the volume\n"); if (!sio_start(hdl)) { fprintf(stderr, "sio_start() failed\n"); exit(1); } for (;;) { pfd[0].fd = tty; pfd[0].events = POLLIN; sio_pollfd(hdl, &pfd[1], POLLOUT); if (poll(pfd, 2, -1) < 0) { perror("poll"); exit(1); } if (pfd[0].revents & POLLIN) { if (read(tty, &cmd, 1) < 0) { perror("read(tty)"); exit(1); } switch (cmd) { case '+': if (vol < SIO_MAXVOL) { vol++; sio_setvol(hdl, vol); } break; case '-': if (vol > 0) { vol--; sio_setvol(hdl, vol); } break; } } if (sio_revents(hdl, &pfd[1]) & POLLOUT) { len = read(STDIN_FILENO, buf, BUFSZ); if (len < 0) { perror("stdin"); exit(1); } if (len == 0) break; n = sio_write(hdl, buf, len); if (n == 0) { fprintf(stderr, "sio_write: failed\n"); exit(1); } } } sio_close(hdl); return 0; } sndio-1.5.0/libsndio004077500017510001751000000000001332662053300130745ustar00alexalexsndio-1.5.0/libsndio/Makefile.in010066400017510001751000000115401332662053300152160ustar00alexalex# extra includes paths (-I options) INCLUDE = -I../bsd-compat # extra libraries paths (-L options) LIB = # extra defines (-D options) DEFS = -DDEBUG @defs@ # extra libraries (-l options) LDADD = @ldadd@ # extra compiler flags to produce objects for shared library SO_CFLAGS = -fPIC # extra compiler flags to produce a shared library with the given name SO_LDFLAGS = -shared @so_ldflags@ # variables defined on configure script command line (if any) @vars@ # # headers, libraries and man pages installation paths # INCLUDE_DIR = @includedir@ LIB_DIR = @libdir@ MAN3_DIR = @mandir@/man3 MAN7_DIR = @mandir@/man7 # man3 and man7 pages MAN3 = \ sio_open.3 \ sio_close.3 sio_setpar.3 sio_getpar.3 sio_getcap.3 \ sio_start.3 sio_stop.3 sio_read.3 sio_write.3 sio_onmove.3 \ sio_nfds.3 sio_pollfd.3 sio_revents.3 sio_eof.3 \ sio_setvol.3 sio_onvol.3 sio_initpar.3 \ mio_open.3 \ mio_close.3 mio_read.3 mio_write.3 mio_nfds.3 mio_pollfd.3 \ mio_revents.3 mio_eof.3 MAN7 = sndio.7 # # libraries to build and install # MAJ = 7 MIN = 0 SO = @so@ SO_LINK = @so_link@ all: ${SO} ${SO_LINK} install: mkdir -p ${DESTDIR}${INCLUDE_DIR} mkdir -p ${DESTDIR}${LIB_DIR} mkdir -p ${DESTDIR}${MAN3_DIR} mkdir -p ${DESTDIR}${MAN7_DIR} cp sndio.h ${DESTDIR}${INCLUDE_DIR} cp -R ${SO} ${SO_LINK} ${DESTDIR}${LIB_DIR} cp sio_open.3 ${DESTDIR}${MAN3_DIR} ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_close.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_setpar.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_getpar.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_getcap.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_start.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_stop.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_read.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_write.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_onmove.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_nfds.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_pollfd.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_revents.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_eof.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_setvol.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_onvol.3 ln -sf sio_open.3 ${DESTDIR}${MAN3_DIR}/sio_initpar.3 cp mio_open.3 ${DESTDIR}${MAN3_DIR} ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_close.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_read.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_write.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_nfds.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_pollfd.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_revents.3 ln -sf mio_open.3 ${DESTDIR}${MAN3_DIR}/mio_eof.3 cp sndio.7 ${DESTDIR}${MAN7_DIR} uninstall: rm -f ${DESTDIR}${INCLUDE_DIR}/sndio.h cd ${DESTDIR}${LIB_DIR} && rm -f ${SO} ${SO_LINK} cd ${DESTDIR}${MAN3_DIR} && rm -f ${MAN3} cd ${DESTDIR}${MAN7_DIR} && rm -f ${MAN7} clean: rm -f -- ${SO} ${SO_LINK} *.o # ---------------------------------------------------------- dependencies --- # # object files, sorted following dependencies to allow the # loader to determine dependencies in a single pass # OBJS = debug.o aucat.o \ mio.o mio_rmidi.o mio_alsa.o mio_aucat.o \ sio.o sio_alsa.o sio_aucat.o sio_oss.o sio_sun.o \ issetugid.o strlcat.o strlcpy.o strtonum.o clock_gettime.o .c.o: ${CC} ${CFLAGS} ${SO_CFLAGS} -I. ${INCLUDE} ${DEFS} -o $@ -c $< ${SO}: ${OBJS} ${CC} ${LDFLAGS} ${SO_CFLAGS} ${SO_LDFLAGS} -o ${SO} ${OBJS} ${LDADD} ${SO_LINK}: ln -sf ${SO} ${SO_LINK} issetugid.o: ../bsd-compat/issetugid.c ${CC} ${CFLAGS} ${SO_CFLAGS} ${INCLUDE} ${DEFS} -c -o issetugid.o ../bsd-compat/issetugid.c strlcat.o: ../bsd-compat/strlcat.c ${CC} ${CFLAGS} ${SO_CFLAGS} ${INCLUDE} ${DEFS} -c -o strlcat.o ../bsd-compat/strlcat.c strlcpy.o: ../bsd-compat/strlcpy.c ${CC} ${CFLAGS} ${SO_CFLAGS} ${INCLUDE} ${DEFS} -c -o strlcpy.o ../bsd-compat/strlcpy.c strtonum.o: ../bsd-compat/strtonum.c ${CC} ${CFLAGS} ${SO_CFLAGS} ${INCLUDE} ${DEFS} -c -o strtonum.o ../bsd-compat/strtonum.c clock_gettime.o: ../bsd-compat/clock_gettime.c ${CC} ${CFLAGS} ${SO_CFLAGS} ${INCLUDE} ${DEFS} -c -o clock_gettime.o ../bsd-compat/clock_gettime.c aucat.o: aucat.c aucat.h amsg.h debug.h \ ../bsd-compat/bsd-compat.h debug.o: debug.c debug.h ../bsd-compat/bsd-compat.h mio.o: mio.c debug.h mio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h mio_alsa.o: mio_alsa.c debug.h mio_priv.h sndio.h mio_aucat.o: mio_aucat.c aucat.h amsg.h debug.h mio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h mio_rmidi.o: mio_rmidi.c debug.h mio_priv.h sndio.h sio.o: sio.c debug.h sio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h sio_alsa.o: sio_alsa.c debug.h sio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h sio_aucat.o: sio_aucat.c aucat.h amsg.h debug.h sio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h sio_oss.o: sio_oss.c debug.h sio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h sio_sun.o: sio_sun.c debug.h sio_priv.h sndio.h \ ../bsd-compat/bsd-compat.h sndio-1.5.0/libsndio/amsg.h010066400017510001751000000075001332662053300142520ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 /* * unix-domain socket name is: * * DIR [ '-' UID ] '/' FILE UNIT * * example: "/tmp/sndio-1000/sock0" * */ #define SOCKPATH_DIR "/tmp/sndio" #define SOCKPATH_FILE "sock" #define SOCKPATH_MAX (1 + \ sizeof(SOCKPATH_DIR) - 1 + \ sizeof(char) + \ sizeof(int) * 3 + \ sizeof(char) + \ sizeof(SOCKPATH_FILE) - 1 + \ sizeof(int) * 3) /* * server TCP base port number */ #define AUCAT_PORT 11025 /* * 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_FLOWCTL 6 /* feedback about buffer usage */ #define AMSG_MOVE 7 /* position changed */ #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 */ #define AMSG_AUTH 12 /* send authentication cookie */ uint32_t cmd; uint32_t __pad; union { struct amsg_par { uint8_t legacy_mode; /* compat for old libs */ 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_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 { uint16_t mode; /* bitmap of MODE_XXX */ #define AMSG_VERSION 7 uint8_t version; /* protocol version */ uint8_t devnum; /* device number */ uint32_t _reserved[1]; /* for future use */ #define AMSG_OPTMAX 12 char opt[AMSG_OPTMAX]; /* profile name */ char who[12]; /* hint for leases */ } hello; struct amsg_auth { #define AMSG_COOKIELEN 16 uint8_t cookie[AMSG_COOKIELEN]; } auth; } 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) */ sndio-1.5.0/libsndio/aucat.c010066400017510001751000000321661332662053300144210ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aucat.h" #include "debug.h" #include "bsd-compat.h" #ifndef HAVE_ARC4RANDOM #ifndef DEV_RANDOM #define DEV_RANDOM "/dev/urandom" #endif static int random_bytes(unsigned char *buf, int len) { ssize_t n; int fd; fd = open(DEV_RANDOM, O_RDONLY); if (fd < 0) { DPERROR(DEV_RANDOM); return 0; } while (len > 0) { n = read(fd, buf, len); if (n < 0) { if (errno == EINTR) continue; DPERROR(DEV_RANDOM); close(fd); return 0; } if (n == 0) { DPRINTF("%s: unexpected eof\n", DEV_RANDOM); close(fd); return 0; } buf += n; len -= n; } close(fd); return 1; } #endif /* * read a message, return 0 if not completed */ int _aucat_rmsg(struct aucat *hdl, int *eof) { ssize_t n; unsigned char *data; if (hdl->rstate != RSTATE_MSG) { DPRINTF("_aucat_rmsg: bad state\n"); abort(); } 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) { *eof = 1; DPERROR("_aucat_rmsg: read"); } return 0; } if (n == 0) { DPRINTF("_aucat_rmsg: eof\n"); *eof = 1; return 0; } hdl->rtodo -= n; } if (ntohl(hdl->rmsg.cmd) == AMSG_DATA) { hdl->rtodo = ntohl(hdl->rmsg.u.data.size); hdl->rstate = RSTATE_DATA; } else { hdl->rtodo = sizeof(struct amsg); hdl->rstate = RSTATE_MSG; } return 1; } /* * write a message, return 0 if not completed */ int _aucat_wmsg(struct aucat *hdl, int *eof) { ssize_t n; unsigned char *data; if (hdl->wstate == WSTATE_IDLE) { hdl->wstate = WSTATE_MSG; hdl->wtodo = sizeof(struct amsg); } if (hdl->wstate != WSTATE_MSG) { DPRINTF("_aucat_wmsg: bad state\n"); abort(); } 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) { *eof = 1; DPERROR("_aucat_wmsg: write"); } return 0; } hdl->wtodo -= n; } if (ntohl(hdl->wmsg.cmd) == AMSG_DATA) { hdl->wtodo = ntohl(hdl->wmsg.u.data.size); hdl->wstate = WSTATE_DATA; } else { hdl->wtodo = 0xdeadbeef; hdl->wstate = WSTATE_IDLE; } return 1; } size_t _aucat_rdata(struct aucat *hdl, void *buf, size_t len, int *eof) { ssize_t n; if (hdl->rstate != RSTATE_DATA) { DPRINTF("_aucat_rdata: bad state\n"); abort(); } if (len > hdl->rtodo) len = hdl->rtodo; while ((n = read(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { *eof = 1; DPERROR("_aucat_rdata: read"); } return 0; } if (n == 0) { DPRINTF("_aucat_rdata: eof\n"); *eof = 1; return 0; } hdl->rtodo -= n; if (hdl->rtodo == 0) { hdl->rstate = RSTATE_MSG; hdl->rtodo = sizeof(struct amsg); } DPRINTFN(2, "_aucat_rdata: read: n = %zd\n", n); return n; } size_t _aucat_wdata(struct aucat *hdl, const void *buf, size_t len, unsigned int wbpf, int *eof) { ssize_t n; size_t datasize; switch (hdl->wstate) { case WSTATE_IDLE: datasize = len; if (datasize > AMSG_DATAMAX) datasize = AMSG_DATAMAX; datasize -= datasize % wbpf; if (datasize == 0) datasize = wbpf; hdl->wmsg.cmd = htonl(AMSG_DATA); hdl->wmsg.u.data.size = htonl(datasize); hdl->wtodo = sizeof(struct amsg); hdl->wstate = WSTATE_MSG; /* FALLTHROUGH */ case WSTATE_MSG: if (!_aucat_wmsg(hdl, eof)) return 0; } if (len > hdl->wtodo) len = hdl->wtodo; if (len == 0) { DPRINTF("_aucat_wdata: len == 0\n"); abort(); } while ((n = write(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { *eof = 1; DPERROR("_aucat_wdata: write"); } return 0; } DPRINTFN(2, "_aucat_wdata: write: n = %zd\n", n); hdl->wtodo -= n; if (hdl->wtodo == 0) { hdl->wstate = WSTATE_IDLE; hdl->wtodo = 0xdeadbeef; } return n; } static int aucat_mkcookie(unsigned char *cookie) { #define COOKIE_DIR "/.sndio" #define COOKIE_SUFFIX "/.sndio/cookie" #define TEMPL_SUFFIX ".XXXXXXXX" struct stat sb; char *home, *path = NULL, *tmp = NULL; size_t home_len, path_len; int fd, len; /* please gcc */ path_len = 0xdeadbeef; /* * try to load the cookie */ home = issetugid() ? NULL : getenv("HOME"); if (home == NULL) goto bad_gen; home_len = strlen(home); path = malloc(home_len + sizeof(COOKIE_SUFFIX)); if (path == NULL) goto bad_gen; memcpy(path, home, home_len); memcpy(path + home_len, COOKIE_SUFFIX, sizeof(COOKIE_SUFFIX)); path_len = home_len + sizeof(COOKIE_SUFFIX) - 1; fd = open(path, O_RDONLY); if (fd < 0) { if (errno != ENOENT) DPERROR(path); goto bad_gen; } if (fstat(fd, &sb) < 0) { DPERROR(path); goto bad_close; } if (sb.st_mode & 0077) { DPRINTF("%s has wrong permissions\n", path); goto bad_close; } len = read(fd, cookie, AMSG_COOKIELEN); if (len < 0) { DPERROR(path); goto bad_close; } if (len != AMSG_COOKIELEN) { DPRINTF("%s: short read\n", path); goto bad_close; } close(fd); goto done; bad_close: close(fd); bad_gen: /* * generate a new cookie */ #ifdef HAVE_ARC4RANDOM arc4random_buf(cookie, AMSG_COOKIELEN); #else if (!random_bytes(cookie, AMSG_COOKIELEN)) { if (path) free(path); return 0; } #endif /* * try to save the cookie */ if (home == NULL) goto done; tmp = malloc(path_len + sizeof(TEMPL_SUFFIX)); if (tmp == NULL) goto done; /* create ~/.sndio directory */ memcpy(tmp, home, home_len); memcpy(tmp + home_len, COOKIE_DIR, sizeof(COOKIE_DIR)); if (mkdir(tmp, 0755) < 0 && errno != EEXIST) goto done; /* create cookie file in it */ memcpy(tmp, path, path_len); memcpy(tmp + path_len, TEMPL_SUFFIX, sizeof(TEMPL_SUFFIX)); fd = mkstemp(tmp); if (fd < 0) { DPERROR(tmp); goto done; } if (write(fd, cookie, AMSG_COOKIELEN) < 0) { DPERROR(tmp); unlink(tmp); close(fd); goto done; } close(fd); if (rename(tmp, path) < 0) { DPERROR(tmp); unlink(tmp); } done: free(tmp); free(path); return 1; } static int aucat_connect_tcp(struct aucat *hdl, char *host, unsigned int unit) { int s, error, opt; struct addrinfo *ailist, *ai, aihints; char serv[NI_MAXSERV]; snprintf(serv, sizeof(serv), "%u", unit + AUCAT_PORT); memset(&aihints, 0, sizeof(struct addrinfo)); aihints.ai_socktype = SOCK_STREAM; aihints.ai_protocol = IPPROTO_TCP; error = getaddrinfo(host, serv, &aihints, &ailist); if (error) { DPRINTF("%s: %s\n", host, gai_strerror(error)); return 0; } s = -1; for (ai = ailist; ai != NULL; ai = ai->ai_next) { s = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, ai->ai_protocol); if (s < 0) { DPERROR("socket"); continue; } #ifndef HAVE_SOCK_CLOEXEC if (fcntl(s, F_SETFL, FD_CLOEXEC) < 0) { DPERROR("FD_CLOEXEC"); close(s); s = - 1; continue; } #endif restart: if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) { if (errno == EINTR) goto restart; DPERROR("connect"); close(s); s = -1; continue; } break; } freeaddrinfo(ailist); if (s < 0) return 0; opt = 1; if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int)) < 0) { DPERROR("setsockopt"); close(s); return 0; } hdl->fd = s; return 1; } static int aucat_connect_un(struct aucat *hdl, unsigned int unit) { struct sockaddr_un ca; socklen_t len = sizeof(struct sockaddr_un); uid_t uid; int s; uid = geteuid(); snprintf(ca.sun_path, sizeof(ca.sun_path), SOCKPATH_DIR "-%u/" SOCKPATH_FILE "%u", uid, unit); ca.sun_family = AF_UNIX; s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (s < 0) return 0; #ifndef HAVE_SOCK_CLOEXEC if (fcntl(s, F_SETFL, FD_CLOEXEC) < 0) { DPERROR("FD_CLOEXEC"); close(s); return 0; } #endif while (connect(s, (struct sockaddr *)&ca, len) < 0) { if (errno == EINTR) continue; DPERROR(ca.sun_path); /* try shared server */ snprintf(ca.sun_path, sizeof(ca.sun_path), SOCKPATH_DIR "/" SOCKPATH_FILE "%u", unit); while (connect(s, (struct sockaddr *)&ca, len) < 0) { if (errno == EINTR) continue; DPERROR(ca.sun_path); close(s); return 0; } break; } hdl->fd = s; DPRINTFN(2, "%s: connected\n", ca.sun_path); return 1; } static const char * parsedev(const char *str, unsigned int *rval) { const char *p = str; unsigned int val; for (val = 0; *p >= '0' && *p <= '9'; p++) { val = 10 * val + (*p - '0'); if (val >= 16) { DPRINTF("%s: number too large\n", str); return NULL; } } if (p == str) { DPRINTF("%s: number expected\n", str); return NULL; } *rval = val; return p; } static const char * parsestr(const char *str, char *rstr, unsigned int max) { const char *p = str; while (*p != '\0' && *p != ',' && *p != '/') { if (--max == 0) { DPRINTF("%s: string too long\n", str); return NULL; } *rstr++ = *p++; } if (str == p) { DPRINTF("%s: string expected\n", str); return NULL; } *rstr = '\0'; return p; } int _aucat_open(struct aucat *hdl, const char *str, unsigned int mode) { extern char *__progname; int eof; char host[NI_MAXHOST], opt[AMSG_OPTMAX]; const char *p; unsigned int unit, devnum, type; if ((p = _sndio_parsetype(str, "snd")) != NULL) type = 0; else if ((p = _sndio_parsetype(str, "midithru")) != NULL) type = 1; else if ((p = _sndio_parsetype(str, "midi")) != NULL) type = 2; else { DPRINTF("%s: unsupported device type\n", str); return -1; } if (*p == '@') { p = parsestr(++p, host, NI_MAXHOST); if (p == NULL) return 0; } else *host = '\0'; if (*p == ',') { p = parsedev(++p, &unit); if (p == NULL) return 0; } else unit = 0; if (*p != '/') { DPRINTF("%s: '/' expected\n", str); return 0; } p = parsedev(++p, &devnum); if (p == NULL) return 0; if (*p == '.') { p = parsestr(++p, opt, AMSG_OPTMAX); if (p == NULL) return 0; } else strlcpy(opt, "default", AMSG_OPTMAX); if (*p != '\0') { DPRINTF("%s: junk at end of dev name\n", p); return 0; } devnum += type * 16; /* XXX */ DPRINTFN(2, "_aucat_open: host=%s unit=%u devnum=%u opt=%s\n", host, unit, devnum, opt); if (host[0] != '\0') { if (!aucat_connect_tcp(hdl, host, unit)) return 0; } else { if (!aucat_connect_un(hdl, unit)) return 0; } hdl->rstate = RSTATE_MSG; hdl->rtodo = sizeof(struct amsg); hdl->wstate = WSTATE_IDLE; hdl->wtodo = 0xdeadbeef; hdl->maxwrite = 0; /* * say hello to server */ AMSG_INIT(&hdl->wmsg); hdl->wmsg.cmd = htonl(AMSG_AUTH); if (!aucat_mkcookie(hdl->wmsg.u.auth.cookie)) goto bad_connect; hdl->wtodo = sizeof(struct amsg); if (!_aucat_wmsg(hdl, &eof)) goto bad_connect; AMSG_INIT(&hdl->wmsg); hdl->wmsg.cmd = htonl(AMSG_HELLO); hdl->wmsg.u.hello.version = AMSG_VERSION; hdl->wmsg.u.hello.mode = htons(mode); hdl->wmsg.u.hello.devnum = devnum; 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, &eof)) goto bad_connect; hdl->rtodo = sizeof(struct amsg); if (!_aucat_rmsg(hdl, &eof)) { DPRINTF("aucat_init: mode refused\n"); goto bad_connect; } if (ntohl(hdl->rmsg.cmd) != AMSG_ACK) { DPRINTF("aucat_init: protocol err\n"); goto bad_connect; } return 1; bad_connect: while (close(hdl->fd) < 0 && errno == EINTR) ; /* retry */ return 0; } void _aucat_close(struct aucat *hdl, int eof) { char dummy[1]; if (!eof) { AMSG_INIT(&hdl->wmsg); hdl->wmsg.cmd = htonl(AMSG_BYE); hdl->wtodo = sizeof(struct amsg); if (!_aucat_wmsg(hdl, &eof)) goto bad_close; while (read(hdl->fd, dummy, 1) < 0 && errno == EINTR) ; /* nothing */ } bad_close: while (close(hdl->fd) < 0 && errno == EINTR) ; /* nothing */ } int _aucat_setfl(struct aucat *hdl, int nbio, int *eof) { if (fcntl(hdl->fd, F_SETFL, nbio ? O_NONBLOCK : 0) < 0) { DPERROR("_aucat_setfl: fcntl"); *eof = 1; return 0; } return 1; } int _aucat_pollfd(struct aucat *hdl, struct pollfd *pfd, int events) { if (hdl->rstate == RSTATE_MSG) events |= POLLIN; pfd->fd = hdl->fd; pfd->events = events; return 1; } int _aucat_revents(struct aucat *hdl, struct pollfd *pfd) { int revents = pfd->revents; DPRINTFN(2, "_aucat_revents: revents: %x\n", revents); return revents; } sndio-1.5.0/libsndio/aucat.h010066400017510001751000000020771332662053300144240ustar00alexalex#ifndef AUCAT_H #define AUCAT_H #include "amsg.h" struct aucat { int fd; /* socket */ struct amsg rmsg, wmsg; /* temporary messages */ size_t wtodo, rtodo; /* bytes to complete the packet */ #define RSTATE_MSG 0 /* message being received */ #define RSTATE_DATA 1 /* data being received */ unsigned rstate; /* one of above */ #define WSTATE_IDLE 2 /* nothing to do */ #define WSTATE_MSG 3 /* message being transferred */ #define WSTATE_DATA 4 /* data being transferred */ unsigned wstate; /* one of above */ unsigned maxwrite; /* bytes we're allowed to write */ }; int _aucat_rmsg(struct aucat *, int *); int _aucat_wmsg(struct aucat *, int *); size_t _aucat_rdata(struct aucat *, void *, size_t, int *); size_t _aucat_wdata(struct aucat *, const void *, size_t, unsigned, int *); int _aucat_open(struct aucat *, const char *, unsigned); void _aucat_close(struct aucat *, int); int _aucat_pollfd(struct aucat *, struct pollfd *, int); int _aucat_revents(struct aucat *, struct pollfd *); int _aucat_setfl(struct aucat *, int, int *); #endif /* !defined(AUCAT_H) */ sndio-1.5.0/libsndio/debug.c010066400017510001751000000034201332662053300144010ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2011 Alexandre Ratchov * * 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 #include #include #include "debug.h" #include "bsd-compat.h" #ifdef DEBUG /* * debug level, -1 means uninitialized */ int _sndio_debug = -1; void _sndio_debug_init(void) { char *dbg; if (_sndio_debug < 0) { dbg = issetugid() ? NULL : getenv("SNDIO_DEBUG"); if (!dbg || sscanf(dbg, "%u", &_sndio_debug) != 1) _sndio_debug = 0; } } #endif const char * _sndio_parsetype(const char *str, char *type) { while (*type) { if (*type != *str) return NULL; type++; str++; } if (*str >= 'a' && *str <= 'z') return NULL; return str; } const char * _sndio_parsenum(const char *str, unsigned int *num, unsigned int max) { const char *p = str; unsigned int dig, maxq, maxr, val; val = 0; maxq = max / 10; maxr = max % 10; for (;;) { dig = *p - '0'; if (dig >= 10) break; if (val > maxq || (val == maxq && dig > maxr)) return NULL; val = val * 10 + dig; p++; } if (p == str) return NULL; *num = val; return p; } sndio-1.5.0/libsndio/debug.h010066400017510001751000000027721332662053300144170ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 DEBUG_H #define DEBUG_H #ifdef DEBUG #include #define DPRINTFN(n, ...) \ do { \ if (_sndio_debug >= (n)) \ fprintf(stderr, __VA_ARGS__); \ } while(0) #define DPRINTF(...) \ do { \ if (_sndio_debug > 0) \ fprintf(stderr, __VA_ARGS__); \ } while(0) #define DPERROR(s) \ do { \ if (_sndio_debug > 0) \ perror(s); \ } while(0) void _sndio_debug_init(void); extern int _sndio_debug; #else #define DPRINTF(...) do {} while(0) #define DPRINTFN(...) do {} while(0) #define DPERROR(s) do {} while(0) #endif const char *_sndio_parsetype(const char *, char *); const char *_sndio_parsenum(const char *, unsigned int *, unsigned int); #endif sndio-1.5.0/libsndio/mio.c010066400017510001751000000112221332662053300140760ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include "debug.h" #include "mio_priv.h" #include "bsd-compat.h" struct mio_hdl * mio_open(const char *str, unsigned int mode, int nbio) { static char portany[] = MIO_PORTANY; struct mio_hdl *hdl; #ifdef DEBUG _sndio_debug_init(); #endif if ((mode & (MIO_OUT | MIO_IN)) == 0) return NULL; if (str == NULL) /* backward compat */ str = portany; if (strcmp(str, portany) == 0 && !issetugid()) { str = getenv("MIDIDEVICE"); if (str == NULL) str = portany; } if (strcmp(str, portany) == 0) { hdl = _mio_aucat_open("midithru/0", mode, nbio); if (hdl != NULL) return hdl; #if defined(USE_RMIDI) return _mio_rmidi_open("rmidi/0", mode, nbio); #elif defined(USE_ALSA) return _mio_alsa_open("rmidi/0", mode, nbio); #else return NULL; #endif } if (_sndio_parsetype(str, "snd") || _sndio_parsetype(str, "midithru") || _sndio_parsetype(str, "midi")) return _mio_aucat_open(str, mode, nbio); if (_sndio_parsetype(str, "rmidi")) #if defined(USE_RMIDI) return _mio_rmidi_open(str, mode, nbio); #elif defined(USE_ALSA) return _mio_alsa_open(str, mode, nbio); #else return NULL; #endif DPRINTF("mio_open: %s: unknown device type\n", str); return NULL; } void _mio_create(struct mio_hdl *hdl, struct mio_ops *ops, unsigned int 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); } static int mio_psleep(struct mio_hdl *hdl, int event) { struct pollfd pfd[MIO_MAXNFDS]; int revents; int nfds; nfds = mio_nfds(hdl); if (nfds > MIO_MAXNFDS) { DPRINTF("mio_psleep: %d: too many descriptors\n", nfds); hdl->eof = 1; return 0; } for (;;) { nfds = mio_pollfd(hdl, pfd, event); while (poll(pfd, nfds, -1) < 0) { if (errno == EINTR) continue; DPERROR("mio_psleep: poll"); hdl->eof = 1; return 0; } revents = mio_revents(hdl, pfd); if (revents & POLLHUP) { DPRINTF("mio_psleep: hang-up\n"); return 0; } if (revents & event) break; } return 1; } size_t mio_read(struct mio_hdl *hdl, void *buf, size_t len) { unsigned int n; char *data = buf; size_t todo = 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; } while (todo > 0) { n = hdl->ops->read(hdl, data, todo); if (n == 0 && hdl->eof) break; data += n; todo -= n; if (n > 0 || hdl->nbio) break; if (!mio_psleep(hdl, POLLIN)) break; } return len - todo; } size_t mio_write(struct mio_hdl *hdl, const void *buf, size_t len) { unsigned int n; const unsigned char *data = buf; size_t todo = 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; } if (todo == 0) { DPRINTF("mio_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 (!mio_psleep(hdl, POLLOUT)) break; continue; } data += n; todo -= n; } return len - todo; } int mio_nfds(struct mio_hdl *hdl) { return hdl->ops->nfds(hdl); } 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; } sndio-1.5.0/libsndio/mio_alsa.c010066400017510001751000000132731332662053300151060ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2011 Alexandre Ratchov * * 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. */ #ifdef USE_ALSA #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "mio_priv.h" #define DEVNAME_PREFIX "hw:" #ifdef DEBUG static snd_output_t *output = NULL; #define DALSA(str, err) fprintf(stderr, "%s: %s\n", str, snd_strerror(err)) #else #define DALSA(str, err) do {} while (0) #endif struct mio_alsa_hdl { struct mio_hdl mio; char *devname; snd_rawmidi_t *in, *out; int infds, onfds, nfds, events; }; static void mio_alsa_close(struct mio_hdl *); static size_t mio_alsa_read(struct mio_hdl *, void *, size_t); static size_t mio_alsa_write(struct mio_hdl *, const void *, size_t); static int mio_alsa_nfds(struct mio_hdl *); static int mio_alsa_pollfd(struct mio_hdl *, struct pollfd *, int); static int mio_alsa_revents(struct mio_hdl *, struct pollfd *); static struct mio_ops mio_alsa_ops = { mio_alsa_close, mio_alsa_write, mio_alsa_read, mio_alsa_nfds, mio_alsa_pollfd, mio_alsa_revents }; struct mio_hdl * _mio_alsa_open(const char *_str, unsigned int mode, int nbio) { const char *p; struct mio_alsa_hdl *hdl; size_t len; int rc; p = _sndio_parsetype(_str, "rmidi"); if (p == NULL) { DPRINTF("_mio_alsa_open: %s: \"rsnd\" expected\n", _str); return NULL; } switch (*p) { case '/': p++; break; default: DPRINTF("_mio_alsa_open: %s: '/' expected\n", _str); return NULL; } hdl = malloc(sizeof(struct mio_alsa_hdl)); if (hdl == NULL) return NULL; _mio_create(&hdl->mio, &mio_alsa_ops, mode, nbio); #ifdef DEBUG rc = snd_output_stdio_attach(&output, stderr, 0); if (rc < 0) DALSA("couldn't attach to stderr", rc); #endif len = strlen(p); hdl->devname = malloc(len + sizeof(DEVNAME_PREFIX)); if (hdl->devname == NULL) { free(hdl); return NULL; } memcpy(hdl->devname, DEVNAME_PREFIX, sizeof(DEVNAME_PREFIX) - 1); memcpy(hdl->devname + sizeof(DEVNAME_PREFIX) - 1, p, len + 1); hdl->in = hdl->out = NULL; rc = snd_rawmidi_open(&hdl->in, &hdl->out, hdl->devname, SND_RAWMIDI_NONBLOCK); if (rc) { DALSA("could't open port", rc); free(hdl->devname); free(hdl); return NULL; } hdl->nfds = 0; if (hdl->in) hdl->nfds += snd_rawmidi_poll_descriptors_count(hdl->in); if (hdl->out) hdl->nfds += snd_rawmidi_poll_descriptors_count(hdl->out); return (struct mio_hdl *)hdl; } static void mio_alsa_close(struct mio_hdl *sh) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; if (hdl->in) snd_rawmidi_close(hdl->in); if (hdl->out) { snd_rawmidi_drain(hdl->out); snd_rawmidi_close(hdl->out); } free(hdl->devname); free(hdl); } static size_t mio_alsa_read(struct mio_hdl *sh, void *buf, size_t len) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; ssize_t n; for (;;) { n = snd_rawmidi_read(hdl->in, buf, len); if (n > 0) return n; if (n == -EINTR) continue; if (n == -EAGAIN) return 0; if (n == 0) DPRINTF("mio_alsa_read: eof\n"); if (n < 0) DALSA("mio_alsa_read", n); hdl->mio.eof = 1; return 0; } } static size_t mio_alsa_write(struct mio_hdl *sh, const void *buf, size_t len) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; ssize_t n; for (;;) { n = snd_rawmidi_write(hdl->out, buf, len); if (n > 0) return n; if (n == -EINTR) continue; if (n == -EAGAIN) return 0; if (n == 0) DPRINTF("mio_alsa_write: eof\n"); if (n < 0) DALSA("mio_alsa_write", n); hdl->mio.eof = 1; return 0; } } static int mio_alsa_nfds(struct mio_hdl *sh) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; return hdl->nfds; } static int mio_alsa_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; if (!hdl->in) events &= ~POLLIN; if (!hdl->out) events &= ~POLLOUT; hdl->events = events; if (events & POLLIN) { hdl->infds = snd_rawmidi_poll_descriptors(hdl->in, pfd, hdl->nfds); } else hdl->infds = 0; if (events & POLLOUT) { hdl->onfds += snd_rawmidi_poll_descriptors(hdl->out, pfd + hdl->infds, hdl->nfds - hdl->infds); } else hdl->onfds = 0; return hdl->infds + hdl->onfds; } static int mio_alsa_revents(struct mio_hdl *sh, struct pollfd *pfd) { struct mio_alsa_hdl *hdl = (struct mio_alsa_hdl *)sh; unsigned short r; int revents = 0, rc; if (hdl->events & POLLIN) { rc = snd_rawmidi_poll_descriptors_revents(hdl->in, pfd, hdl->infds, &r); if (rc < 0) { DALSA("snd_rawmidi_poll_descriptors_revents", rc); hdl->mio.eof = 1; return POLLHUP; } revents |= r & (POLLIN | POLLHUP); } if (hdl->events & POLLOUT) { rc = snd_rawmidi_poll_descriptors_revents(hdl->in, pfd + hdl->infds, hdl->onfds, &r); if (rc < 0) { DALSA("snd_rawmidi_poll_descriptors_revents", rc); hdl->mio.eof = 1; return POLLHUP; } revents |= r & (POLLOUT | POLLHUP); } return revents; } #endif /* defined USE_ALSA */ sndio-1.5.0/libsndio/mio_aucat.c010066400017510001751000000106511332662053300152600ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include "aucat.h" #include "debug.h" #include "mio_priv.h" #include "bsd-compat.h" struct mio_aucat_hdl { struct mio_hdl mio; struct aucat aucat; int events; }; static void mio_aucat_close(struct mio_hdl *); static size_t mio_aucat_read(struct mio_hdl *, void *, size_t); static size_t mio_aucat_write(struct mio_hdl *, const void *, size_t); static int mio_aucat_nfds(struct mio_hdl *); static int mio_aucat_pollfd(struct mio_hdl *, struct pollfd *, int); static int mio_aucat_revents(struct mio_hdl *, struct pollfd *); static struct mio_ops mio_aucat_ops = { mio_aucat_close, mio_aucat_write, mio_aucat_read, mio_aucat_nfds, mio_aucat_pollfd, mio_aucat_revents }; /* * execute the next message, return 0 if blocked */ static int mio_aucat_runmsg(struct mio_aucat_hdl *hdl) { int delta; if (!_aucat_rmsg(&hdl->aucat, &hdl->mio.eof)) return 0; switch (ntohl(hdl->aucat.rmsg.cmd)) { case AMSG_DATA: return 1; case AMSG_FLOWCTL: delta = ntohl(hdl->aucat.rmsg.u.ts.delta); hdl->aucat.maxwrite += delta; DPRINTF("aucat: flowctl = %d, maxwrite = %d\n", delta, hdl->aucat.maxwrite); break; default: DPRINTF("mio_aucat_runmsg: unhandled message %u\n", hdl->aucat.rmsg.cmd); hdl->mio.eof = 1; return 0; } hdl->aucat.rstate = RSTATE_MSG; hdl->aucat.rtodo = sizeof(struct amsg); return 1; } struct mio_hdl * _mio_aucat_open(const char *str, unsigned int mode, int nbio) { struct mio_aucat_hdl *hdl; hdl = malloc(sizeof(struct mio_aucat_hdl)); if (hdl == NULL) return NULL; if (!_aucat_open(&hdl->aucat, str, mode)) goto bad; _mio_create(&hdl->mio, &mio_aucat_ops, mode, nbio); if (!_aucat_setfl(&hdl->aucat, 1, &hdl->mio.eof)) goto bad; return (struct mio_hdl *)hdl; bad: free(hdl); return NULL; } static void mio_aucat_close(struct mio_hdl *sh) { struct mio_aucat_hdl *hdl = (struct mio_aucat_hdl *)sh; if (!hdl->mio.eof) _aucat_setfl(&hdl->aucat, 0, &hdl->mio.eof); _aucat_close(&hdl->aucat, hdl->mio.eof); free(hdl); } static size_t mio_aucat_read(struct mio_hdl *sh, void *buf, size_t len) { struct mio_aucat_hdl *hdl = (struct mio_aucat_hdl *)sh; while (hdl->aucat.rstate == RSTATE_MSG) { if (!mio_aucat_runmsg(hdl)) return 0; } return _aucat_rdata(&hdl->aucat, buf, len, &hdl->mio.eof); } static size_t mio_aucat_write(struct mio_hdl *sh, const void *buf, size_t len) { struct mio_aucat_hdl *hdl = (struct mio_aucat_hdl *)sh; size_t n; if (len <= 0 || hdl->aucat.maxwrite <= 0) return 0; if (len > hdl->aucat.maxwrite) len = hdl->aucat.maxwrite; n = _aucat_wdata(&hdl->aucat, buf, len, 1, &hdl->mio.eof); hdl->aucat.maxwrite -= n; return n; } static int mio_aucat_nfds(struct mio_hdl *sh) { return 1; } static int mio_aucat_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events) { struct mio_aucat_hdl *hdl = (struct mio_aucat_hdl *)sh; hdl->events = events; if (hdl->aucat.maxwrite <= 0) events &= ~POLLOUT; return _aucat_pollfd(&hdl->aucat, pfd, events); } static int mio_aucat_revents(struct mio_hdl *sh, struct pollfd *pfd) { struct mio_aucat_hdl *hdl = (struct mio_aucat_hdl *)sh; int revents = pfd->revents; if (revents & POLLIN) { while (hdl->aucat.rstate == RSTATE_MSG) { if (!mio_aucat_runmsg(hdl)) break; } if (hdl->aucat.rstate != RSTATE_DATA) revents &= ~POLLIN; } if (revents & POLLOUT) { if (hdl->aucat.maxwrite <= 0) revents &= ~POLLOUT; } if (hdl->mio.eof) return POLLHUP; return revents & (hdl->events | POLLHUP); } sndio-1.5.0/libsndio/mio_open.3010066400017510001751000000135421332662053300150460ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2007 Alexandre Ratchov .\" .\" 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$ .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 sndio interface to MIDI streams .Sh SYNOPSIS .In sndio.h .Ft "struct mio_hdl *" .Fn mio_open "const char *name" "unsigned int 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 sndiod 8 MIDI thru boxes and control ports 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 .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 MIO_PORTANY 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. 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 prepares the array .Ar pfd of .Va pollfd structures for use with .Xr poll 2 . The optimal size of the .Ar pfd array, which the caller must pre-allocate, is provided by the .Fn mio_nfds function. .Pp .Xr poll 2 will sleep until any of the .Ar events requested with .Fn mio_pollfd have occurred. Events are represented as a bit-mask of .Va POLLIN and .Va POLLOUT constants. The events which woke up .Xr poll 2 can be obtained with the .Fn mio_revents function. 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 requested with .Fn mio_pollfd . .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). .Sh RETURN VALUES The .Fn mio_open function returns the newly created handle on success or NULL on failure. .Pp The .Fn mio_pollfd function returns the number of .Va pollfd structures filled. 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. .Pp The .Fn mio_revents function returns the bit-mask set by .Xr poll 2 in the .Va pfd array of .Va pollfd structures. .Pp The .Fn mio_read and .Fn mio_write functions return the number of bytes transferred. .Pp The .Fn mio_eof function returns 0 if there's no pending error, and a non-zero value if there's an error. .Sh ENVIRONMENT .Bl -tag -width "SNDIO_DEBUGXXX" -compact .It Ev SNDIO_DEBUG The debug level: may be a value between 0 and 2. .El .Sh SEE ALSO .Xr poll 2 , .Xr midi 4 , .Xr sndio 7 , .Xr sndiod 8 .Sh HISTORY These functions first appeared in .Ox 4.7 . .Sh AUTHORS .An Alexandre Ratchov Aq Mt ratchov@openbsd.org sndio-1.5.0/libsndio/mio_priv.h010066400017510001751000000034101332662053300151430ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 "sndio.h" #define MIO_MAXNFDS 16 /* * 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 (*nfds)(struct mio_hdl *); int (*pollfd)(struct mio_hdl *, struct pollfd *, int); int (*revents)(struct mio_hdl *, struct pollfd *); }; struct mio_hdl *_mio_rmidi_open(const char *, unsigned, int); #ifdef USE_ALSA struct mio_hdl *_mio_alsa_open(const char *, unsigned, int); #endif struct mio_hdl *_mio_aucat_open(const char *, unsigned, int); void _mio_create(struct mio_hdl *, struct mio_ops *, unsigned, int); void _mio_destroy(struct mio_hdl *); #endif /* !defined(MIO_PRIV_H) */ sndio-1.5.0/libsndio/mio_rmidi.c010066400017510001751000000124561332662053300152740ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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. */ #ifdef USE_RMIDI #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "mio_priv.h" #ifdef USE_UMIDI #define DEVPATH_PREFIX "/dev/umidi" #define DEVPATH_MAX (1 + \ sizeof(DEVPATH_PREFIX) - 1 + \ sizeof(int) * 3 + \ 1 + \ sizeof(int) * 3) #else #define DEVPATH_PREFIX "/dev/rmidi" #define DEVPATH_MAX (1 + \ sizeof(DEVPATH_PREFIX) - 1 + \ sizeof(int) * 3) #endif struct mio_rmidi_hdl { struct mio_hdl mio; int fd; }; static void mio_rmidi_close(struct mio_hdl *); static size_t mio_rmidi_read(struct mio_hdl *, void *, size_t); static size_t mio_rmidi_write(struct mio_hdl *, const void *, size_t); static int mio_rmidi_nfds(struct mio_hdl *); static int mio_rmidi_pollfd(struct mio_hdl *, struct pollfd *, int); static int mio_rmidi_revents(struct mio_hdl *, struct pollfd *); static struct mio_ops mio_rmidi_ops = { mio_rmidi_close, mio_rmidi_write, mio_rmidi_read, mio_rmidi_nfds, mio_rmidi_pollfd, mio_rmidi_revents }; int mio_rmidi_getfd(const char *str, unsigned int mode, int nbio) { const char *p; char path[DEVPATH_MAX]; unsigned int devnum; #ifdef USE_UMIDI unsigned int subdevnum = 0; #endif int fd, flags; #ifdef DEBUG _sndio_debug_init(); #endif p = _sndio_parsetype(str, "rmidi"); if (p == NULL) { DPRINTF("mio_rmidi_getfd: %s: \"rsnd\" expected\n", str); return -1; } switch (*p) { case '/': p++; break; default: DPRINTF("mio_rmidi_getfd: %s: '/' expected\n", str); return -1; } p = _sndio_parsenum(p, &devnum, 255); if (p == NULL) { DPRINTF("mio_rmidi_getfd: %s: number expected after '/'\n", str); return -1; } #ifdef USE_UMIDI switch (*p) { case '.': p++; p = _sndio_parsenum(p, &subdevnum, 255); if (p == NULL) { DPRINTF("mio_rmidi_getfd: %s: " "number expected after '.'\n", str); return -1; } break; case '\0': break; default: DPRINTF("mio_rmidi_getfd: %s: '.' expected\n", str); return -1; } #endif if (*p != '\0') { DPRINTF("mio_rmidi_getfd: junk at end of string: %s\n", p); return -1; } #ifdef USE_UMIDI snprintf(path, sizeof(path), DEVPATH_PREFIX "%u.%u", devnum, subdevnum); #else snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum); #endif if (mode == (MIO_IN | MIO_OUT)) flags = O_RDWR; else flags = (mode & MIO_OUT) ? O_WRONLY : O_RDONLY; while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) { if (errno == EINTR) continue; DPERROR(path); return -1; } return fd; } struct mio_hdl * mio_rmidi_fdopen(int fd, unsigned int mode, int nbio) { struct mio_rmidi_hdl *hdl; #ifdef DEBUG _sndio_debug_init(); #endif hdl = malloc(sizeof(struct mio_rmidi_hdl)); if (hdl == NULL) return NULL; _mio_create(&hdl->mio, &mio_rmidi_ops, mode, nbio); hdl->fd = fd; return (struct mio_hdl *)hdl; } struct mio_hdl * _mio_rmidi_open(const char *str, unsigned int mode, int nbio) { struct mio_hdl *hdl; int fd; fd = mio_rmidi_getfd(str, mode, nbio); if (fd < 0) return NULL; hdl = mio_rmidi_fdopen(fd, mode, nbio); if (hdl != NULL) return hdl; while (close(fd) < 0 && errno == EINTR) ; /* retry */ return NULL; } static void mio_rmidi_close(struct mio_hdl *sh) { struct mio_rmidi_hdl *hdl = (struct mio_rmidi_hdl *)sh; int rc; do { rc = close(hdl->fd); } while (rc < 0 && errno == EINTR); free(hdl); } static size_t mio_rmidi_read(struct mio_hdl *sh, void *buf, size_t len) { struct mio_rmidi_hdl *hdl = (struct mio_rmidi_hdl *)sh; ssize_t n; while ((n = read(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("mio_rmidi_read: read"); hdl->mio.eof = 1; } return 0; } if (n == 0) { DPRINTF("mio_rmidi_read: eof\n"); hdl->mio.eof = 1; return 0; } return n; } static size_t mio_rmidi_write(struct mio_hdl *sh, const void *buf, size_t len) { struct mio_rmidi_hdl *hdl = (struct mio_rmidi_hdl *)sh; ssize_t n; while ((n = write(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("mio_rmidi_write: write"); hdl->mio.eof = 1; } return 0; } return n; } static int mio_rmidi_nfds(struct mio_hdl *sh) { return 1; } static int mio_rmidi_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events) { struct mio_rmidi_hdl *hdl = (struct mio_rmidi_hdl *)sh; pfd->fd = hdl->fd; pfd->events = events; return 1; } static int mio_rmidi_revents(struct mio_hdl *sh, struct pollfd *pfd) { return pfd->revents; } #endif /* defined USE_RMIDI */ sndio-1.5.0/libsndio/sio.c010066400017510001751000000262341332662053300141150ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include "debug.h" #include "sio_priv.h" #include "bsd-compat.h" #define SIO_PAR_MAGIC 0x83b905a4 void sio_initpar(struct sio_par *par) { memset(par, 0xff, sizeof(struct sio_par)); par->__magic = SIO_PAR_MAGIC; } struct sio_hdl * sio_open(const char *str, unsigned int mode, int nbio) { static char devany[] = SIO_DEVANY; struct sio_hdl *hdl; #ifdef DEBUG _sndio_debug_init(); #endif if ((mode & (SIO_PLAY | SIO_REC)) == 0) return NULL; if (str == NULL) /* backward compat */ str = devany; if (strcmp(str, devany) == 0 && !issetugid()) { str = getenv("AUDIODEVICE"); if (str == NULL) str = devany; } if (strcmp(str, devany) == 0) { hdl = _sio_aucat_open("snd/0", mode, nbio); if (hdl != NULL) return hdl; #if defined(USE_SUN) return _sio_sun_open("rsnd/default", mode, nbio); #elif defined(USE_OSS) return _sio_oss_open("rsnd/default", mode, nbio); #elif defined(USE_ALSA) return _sio_alsa_open("rsnd/default", mode, nbio); #else return NULL; #endif } if (_sndio_parsetype(str, "snd")) return _sio_aucat_open(str, mode, nbio); if (_sndio_parsetype(str, "rsnd")) #if defined(USE_SUN) return _sio_sun_open(str, mode, nbio); #elif defined(USE_OSS) return _sio_oss_open(str, mode, nbio); #elif defined(USE_ALSA) return _sio_alsa_open(str, mode, nbio); #else return NULL; #endif DPRINTF("sio_open: %s: unknown device type\n", str); return NULL; } void _sio_create(struct sio_hdl *hdl, struct sio_ops *ops, unsigned int 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) { #ifdef DEBUG struct timespec ts; #endif if (hdl->eof) { DPRINTF("sio_start: eof\n"); return 0; } if (hdl->started) { DPRINTF("sio_start: already started\n"); hdl->eof = 1; return 0; } hdl->cpos = 0; hdl->rused = hdl->wused = 0; if (!sio_getpar(hdl, &hdl->par)) return 0; #ifdef DEBUG hdl->pollcnt = 0; clock_gettime(CLOCK_MONOTONIC, &ts); hdl->start_nsec = 1000000000LL * ts.tv_sec + ts.tv_nsec; #endif hdl->rdrop = hdl->wsil = 0; 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 DPRINTFN(2, "libsndio: polls: %llu, samples = %llu\n", hdl->pollcnt, hdl->cpos); #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: 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; par->bufsz = ~0U; } 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[SIO_MAXNFDS]; int revents; int nfds; nfds = sio_nfds(hdl); if (nfds > SIO_MAXNFDS) { DPRINTF("sio_psleep: %d: too many descriptors\n", nfds); hdl->eof = 1; return 0; } for (;;) { nfds = sio_pollfd(hdl, pfd, event); while (poll(pfd, nfds, -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; } static int sio_rdrop(struct sio_hdl *hdl) { #define DROP_NMAX 0x1000 static char dummy[DROP_NMAX]; ssize_t n, todo; while (hdl->rdrop > 0) { todo = hdl->rdrop; if (todo > DROP_NMAX) todo = DROP_NMAX; n = hdl->ops->read(hdl, dummy, todo); if (n == 0) return 0; hdl->rdrop -= n; DPRINTF("sio_rdrop: dropped %zu bytes\n", n); } return 1; } static int sio_wsil(struct sio_hdl *hdl) { #define ZERO_NMAX 0x1000 static char zero[ZERO_NMAX]; ssize_t n, todo; while (hdl->wsil > 0) { todo = hdl->wsil; if (todo > ZERO_NMAX) todo = ZERO_NMAX; n = hdl->ops->write(hdl, zero, todo); if (n == 0) return 0; hdl->wsil -= n; DPRINTF("sio_wsil: inserted %zu bytes\n", n); } return 1; } size_t sio_read(struct sio_hdl *hdl, void *buf, size_t len) { unsigned int n; char *data = buf; size_t todo = len, maxread; 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) { if (!sio_rdrop(hdl)) return 0; maxread = hdl->rused; if (maxread > todo) maxread = todo; n = maxread > 0 ? hdl->ops->read(hdl, data, maxread) : 0; if (n == 0) { if (hdl->nbio || hdl->eof || todo < len) break; if (!sio_psleep(hdl, POLLIN)) break; continue; } data += n; todo -= n; hdl->rused -= n; } return len - todo; } size_t sio_write(struct sio_hdl *hdl, const void *buf, size_t len) { unsigned int n; const unsigned char *data = buf; size_t todo = len, maxwrite; 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) { if (!sio_wsil(hdl)) return 0; maxwrite = hdl->par.bufsz * hdl->par.pchan * hdl->par.bps - hdl->wused; if (maxwrite > todo) maxwrite = todo; n = maxwrite > 0 ? hdl->ops->write(hdl, data, maxwrite) : 0; if (n == 0) { if (hdl->nbio || hdl->eof) break; if (!sio_psleep(hdl, POLLOUT)) break; continue; } data += n; todo -= n; hdl->wused += n; } return len - todo; } int sio_nfds(struct sio_hdl *hdl) { return hdl->ops->nfds(hdl); } 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 timespec ts0, ts1; if (_sndio_debug >= 4) clock_gettime(CLOCK_MONOTONIC, &ts0); #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 (_sndio_debug >= 4) { clock_gettime(CLOCK_MONOTONIC, &ts1); DPRINTF("%09lld: sio_revents: revents = 0x%x, took %lldns\n", 1000000000LL * ts0.tv_sec + ts0.tv_nsec - hdl->start_nsec, revents, 1000000000LL * (ts1.tv_sec - ts0.tv_sec) + ts1.tv_nsec - ts0.tv_nsec); } #endif if ((hdl->mode & SIO_PLAY) && !sio_wsil(hdl)) revents &= ~POLLOUT; if ((hdl->mode & SIO_REC) && !sio_rdrop(hdl)) revents &= ~POLLIN; 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; } #ifdef DEBUG void _sio_printpos(struct sio_hdl *hdl) { struct timespec ts; long long rpos, rdiff; long long cpos, cdiff; long long wpos, wdiff; unsigned rbpf, wbpf, rround, wround; clock_gettime(CLOCK_MONOTONIC, &ts); rbpf = (hdl->mode & SIO_REC) ? hdl->par.bps * hdl->par.rchan : 1; wbpf = (hdl->mode & SIO_PLAY) ? hdl->par.bps * hdl->par.pchan : 1; rround = hdl->par.round * rbpf; wround = hdl->par.round * wbpf; rpos = (hdl->mode & SIO_REC) ? hdl->cpos * rbpf - hdl->rused : 0; wpos = (hdl->mode & SIO_PLAY) ? hdl->cpos * wbpf + hdl->wused : 0; cdiff = hdl->cpos % hdl->par.round; cpos = hdl->cpos / hdl->par.round; if (cdiff > hdl->par.round / 2) { cpos++; cdiff = cdiff - hdl->par.round; } rdiff = rpos % rround; rpos = rpos / rround; if (rdiff > rround / 2) { rpos++; rdiff = rdiff - rround; } wdiff = wpos % wround; wpos = wpos / wround; if (wdiff > wround / 2) { wpos++; wdiff = wdiff - wround; } DPRINTF("%011lld: " "clk %+5lld%+5lld, wr %+5lld%+5lld rd: %+5lld%+5lld\n", 1000000000LL * ts.tv_sec + ts.tv_nsec - hdl->start_nsec, cpos, cdiff, wpos, wdiff, rpos, rdiff); } #endif void _sio_onmove_cb(struct sio_hdl *hdl, int delta) { hdl->cpos += delta; if (hdl->mode & SIO_REC) hdl->rused += delta * (hdl->par.bps * hdl->par.rchan); if (hdl->mode & SIO_PLAY) hdl->wused -= delta * (hdl->par.bps * hdl->par.pchan); #ifdef DEBUG if (_sndio_debug >= 3) _sio_printpos(hdl); if ((hdl->mode & SIO_PLAY) && hdl->wused < 0) { DPRINTFN(1, "sndio: h/w failure: negative buffer usage\n"); hdl->eof = 1; return; } #endif if (hdl->move_cb) hdl->move_cb(hdl->move_addr, delta); } int sio_setvol(struct sio_hdl *hdl, unsigned int ctl) { if (hdl->eof) return 0; if (!hdl->ops->setvol) return 1; if (!hdl->ops->setvol(hdl, ctl)) return 0; hdl->ops->getvol(hdl); return 1; } int sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned int), void *addr) { if (hdl->started) { DPRINTF("sio_onvol: already started\n"); hdl->eof = 1; return 0; } if (!hdl->ops->setvol) return 0; hdl->vol_cb = cb; hdl->vol_addr = addr; hdl->ops->getvol(hdl); return 1; } void _sio_onvol_cb(struct sio_hdl *hdl, unsigned int ctl) { if (hdl->vol_cb) hdl->vol_cb(hdl->vol_addr, ctl); } sndio-1.5.0/libsndio/sio_alsa.c010066400017510001751000000675541332662053300151270ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2010 Jacob Meuser * Copyright (c) 2008,2012-2013 Alexandre Ratchov * * 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. */ #ifdef USE_ALSA #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "sio_priv.h" #include "bsd-compat.h" #define DEVNAME_PREFIX "hw:" #ifdef DEBUG static snd_output_t *output = NULL; #define DALSA(str, err) fprintf(stderr, "%s: %s\n", str, snd_strerror(err)) #else #define DALSA(str, err) do {} while (0) #endif struct sio_alsa_hdl { struct sio_hdl sio; struct sio_par par; char *devname; snd_pcm_t *opcm; snd_pcm_t *ipcm; unsigned ibpf, obpf; /* bytes per frame */ int iused, oused; /* frames used in hardware fifos */ int idelta, odelta; /* position reported to client */ int nfds, infds, onfds; int running; int events; int ipartial, opartial; char *itmpbuf, *otmpbuf; }; static void sio_alsa_onmove(struct sio_alsa_hdl *); static int sio_alsa_revents(struct sio_hdl *, struct pollfd *); static void sio_alsa_close(struct sio_hdl *); static int sio_alsa_start(struct sio_hdl *); static int sio_alsa_stop(struct sio_hdl *); static int sio_alsa_setpar(struct sio_hdl *, struct sio_par *); static int sio_alsa_getpar(struct sio_hdl *, struct sio_par *); static int sio_alsa_getcap(struct sio_hdl *, struct sio_cap *); static size_t sio_alsa_read(struct sio_hdl *, void *, size_t); static size_t sio_alsa_write(struct sio_hdl *, const void *, size_t); static int sio_alsa_nfds(struct sio_hdl *); static int sio_alsa_pollfd(struct sio_hdl *, struct pollfd *, int); static int sio_alsa_revents(struct sio_hdl *, struct pollfd *); static struct sio_ops sio_alsa_ops = { sio_alsa_close, sio_alsa_setpar, sio_alsa_getpar, sio_alsa_getcap, sio_alsa_write, sio_alsa_read, sio_alsa_start, sio_alsa_stop, sio_alsa_nfds, sio_alsa_pollfd, sio_alsa_revents, NULL, NULL }; #define CAP_NFMTS (sizeof(cap_fmts) / sizeof(cap_fmts[0])) #define CAP_NCHANS (sizeof(cap_chans) / sizeof(cap_chans[0])) #define CAP_NRATES (sizeof(cap_rates) / sizeof(cap_rates[0])) static unsigned int cap_chans[] = { 1, 2, 4, 6, 8, 10, 12, 16 }; static unsigned int cap_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000 }; static snd_pcm_format_t cap_fmts[] = { /* XXX add s24le3 and s24be3 */ SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_BE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_BE, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, SND_PCM_FORMAT_U8 }; /* * convert ALSA format to sio_par encoding */ static int sio_alsa_fmttopar(struct sio_alsa_hdl *hdl, snd_pcm_format_t fmt, unsigned int *bits, unsigned int *sig, unsigned int *le) { switch (fmt) { case SND_PCM_FORMAT_U8: *bits = 8; *sig = 0; break; case SND_PCM_FORMAT_S8: *bits = 8; *sig = 1; break; case SND_PCM_FORMAT_S16_LE: *bits = 16; *sig = 1; *le = 1; break; case SND_PCM_FORMAT_S16_BE: *bits = 16; *sig = 1; *le = 0; break; case SND_PCM_FORMAT_U16_LE: *bits = 16; *sig = 0; *le = 1; break; case SND_PCM_FORMAT_U16_BE: *bits = 16; *sig = 0; *le = 0; break; case SND_PCM_FORMAT_S24_LE: *bits = 24; *sig = 1; *le = 1; break; case SND_PCM_FORMAT_S24_BE: *bits = 24; *sig = 1; *le = 0; break; case SND_PCM_FORMAT_U24_LE: *bits = 24; *sig = 0; *le = 1; break; case SND_PCM_FORMAT_U24_BE: *bits = 24; *sig = 0; *le = 0; break; case SND_PCM_FORMAT_S32_LE: *bits = 32; *sig = 1; *le = 1; break; case SND_PCM_FORMAT_S32_BE: *bits = 32; *sig = 1; *le = 0; break; case SND_PCM_FORMAT_U32_LE: *bits = 32; *sig = 0; *le = 1; break; case SND_PCM_FORMAT_U32_BE: *bits = 32; *sig = 0; *le = 0; break; default: DPRINTF("sio_alsa_fmttopar: 0x%x: unsupported format\n", fmt); hdl->sio.eof = 1; return 0; } return 1; } /* * convert sio_par encoding to ALSA format */ static void sio_alsa_enctofmt(struct sio_alsa_hdl *hdl, snd_pcm_format_t *rfmt, unsigned int bits, unsigned int sig, unsigned int le) { if (bits == 8) { if (sig == ~0U || !sig) *rfmt = SND_PCM_FORMAT_U8; else *rfmt = SND_PCM_FORMAT_S8; } else if (bits == 16) { if (sig == ~0U || sig) { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE; } else if (le) *rfmt = SND_PCM_FORMAT_S16_LE; else *rfmt = SND_PCM_FORMAT_S16_BE; } else { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_U16_LE : SND_PCM_FORMAT_U16_BE; } else if (le) *rfmt = SND_PCM_FORMAT_U16_LE; else *rfmt = SND_PCM_FORMAT_U16_BE; } } else if (bits == 24) { if (sig == ~0U || sig) { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S24_LE : SND_PCM_FORMAT_S24_BE; } else if (le) *rfmt = SND_PCM_FORMAT_S24_LE; else *rfmt = SND_PCM_FORMAT_S24_BE; } else { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_U24_LE : SND_PCM_FORMAT_U24_BE; } else if (le) *rfmt = SND_PCM_FORMAT_U24_LE; else *rfmt = SND_PCM_FORMAT_U24_BE; } } else if (bits == 32) { if (sig == ~0U || sig) { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_S32_BE; } else if (le) *rfmt = SND_PCM_FORMAT_S32_LE; else *rfmt = SND_PCM_FORMAT_S32_BE; } else { if (le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_U32_LE : SND_PCM_FORMAT_U32_BE; } else if (le) *rfmt = SND_PCM_FORMAT_U32_LE; else *rfmt = SND_PCM_FORMAT_U32_BE; } } else { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE; } } struct sio_hdl * _sio_alsa_open(const char *str, unsigned mode, int nbio) { const char *p; struct sio_alsa_hdl *hdl; struct sio_par par; size_t len; int err; p = _sndio_parsetype(str, "rsnd"); if (p == NULL) { DPRINTF("_sio_alsa_open: %s: \"rsnd\" expected\n", str); return NULL; } switch (*p) { case '/': p++; break; default: DPRINTF("_sio_alsa_open: %s: '/' expected\n", str); return NULL; } hdl = malloc(sizeof(struct sio_alsa_hdl)); if (hdl == NULL) return NULL; _sio_create(&hdl->sio, &sio_alsa_ops, mode, nbio); #ifdef DEBUG err = snd_output_stdio_attach(&output, stderr, 0); if (err < 0) DALSA("couldn't attach to stderr", err); #endif if (strcmp(p, "default") == 0) p = "0"; len = strlen(p); hdl->devname = malloc(len + sizeof(DEVNAME_PREFIX)); if (hdl->devname == NULL) goto bad_free_hdl; memcpy(hdl->devname, DEVNAME_PREFIX, sizeof(DEVNAME_PREFIX) - 1); memcpy(hdl->devname + sizeof(DEVNAME_PREFIX) - 1, p, len + 1); if (mode & SIO_PLAY) { err = snd_pcm_open(&hdl->opcm, hdl->devname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { DALSA("couldn't open play stream", err); goto bad_free; } } if (mode & SIO_REC) { err = snd_pcm_open(&hdl->ipcm, hdl->devname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (err < 0) { DALSA("couldn't open rec stream", err); goto bad_free_opcm; } } /* * snd_pcm_poll_descriptors_count returns a small value * that grows later, after the stream is started */ hdl->nfds = SIO_MAXNFDS; /* * 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.bits = 16; par.le = SIO_LE_NATIVE; par.rate = 48000; if (mode & SIO_PLAY) par.pchan = 2; if (mode & SIO_REC) par.rchan = 2; if (!sio_setpar(&hdl->sio, &par)) goto bad_free_ipcm; return (struct sio_hdl *)hdl; bad_free_ipcm: if (mode & SIO_REC) snd_pcm_close(hdl->ipcm); bad_free_opcm: if (mode & SIO_PLAY) snd_pcm_close(hdl->opcm); bad_free: free(hdl->devname); bad_free_hdl: free(hdl); return NULL; } static void sio_alsa_close(struct sio_hdl *sh) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; if (hdl->sio.mode & SIO_PLAY) snd_pcm_close(hdl->opcm); if (hdl->sio.mode & SIO_REC) snd_pcm_close(hdl->ipcm); free(hdl->devname); free(hdl); } static int sio_alsa_start(struct sio_hdl *sh) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; int err; DPRINTFN(2, "sio_alsa_start:\n"); hdl->ibpf = hdl->par.rchan * hdl->par.bps; hdl->obpf = hdl->par.pchan * hdl->par.bps; hdl->iused = 0; hdl->oused = 0; hdl->idelta = 0; hdl->odelta = 0; hdl->infds = 0; hdl->onfds = 0; hdl->running = 0; if (hdl->sio.mode & SIO_PLAY) { err = snd_pcm_prepare(hdl->opcm); if (err < 0) { DALSA("couldn't prepare play stream", err); hdl->sio.eof = 1; return 0; } hdl->otmpbuf = malloc(hdl->obpf); if (hdl->otmpbuf == NULL) { hdl->sio.eof = 1; return 0; } hdl->opartial = 0; } if (hdl->sio.mode & SIO_REC) { err = snd_pcm_prepare(hdl->ipcm); if (err < 0) { DALSA("couldn't prepare rec stream", err); hdl->sio.eof = 1; return 0; } hdl->itmpbuf = malloc(hdl->ibpf); if (hdl->itmpbuf == NULL) { hdl->sio.eof = 1; return 0; } hdl->ipartial = 0; } if ((hdl->sio.mode & SIO_PLAY) && (hdl->sio.mode & SIO_REC)) { err = snd_pcm_link(hdl->ipcm, hdl->opcm); if (err < 0) { DALSA("couldn't link streams", err); hdl->sio.eof = 1; return 0; } } if (!(hdl->sio.mode & SIO_PLAY)) { err = snd_pcm_start(hdl->ipcm); if (err < 0) { DALSA("couldn't start rec stream", err); hdl->sio.eof = 1; return 0; } } return 1; } static int sio_alsa_stop(struct sio_hdl *sh) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; int err; if (hdl->sio.mode & SIO_PLAY) { err = snd_pcm_drop(hdl->opcm); if (err < 0) { DALSA("couldn't stop play stream", err); hdl->sio.eof = 1; return 0; } free(hdl->otmpbuf); } if (hdl->sio.mode & SIO_REC) { err = snd_pcm_drop(hdl->ipcm); if (err < 0) { DALSA("couldn't stop rec stream", err); hdl->sio.eof = 1; return 0; } free(hdl->itmpbuf); } if ((hdl->sio.mode & SIO_PLAY) && (hdl->sio.mode & SIO_REC)) { err = snd_pcm_unlink(hdl->ipcm); if (err < 0) { DALSA("couldn't unlink streams", err); hdl->sio.eof = 1; return 0; } } DPRINTFN(2, "sio_alsa_stop: stopped\n"); return 1; } static int sio_alsa_xrun(struct sio_alsa_hdl *hdl) { int clk; int wsil, rdrop, cmove; int rbpf, rround; int wbpf; DPRINTFN(2, "sio_alsa_xrun:\n"); if (_sndio_debug >= 2) _sio_printpos(&hdl->sio); /* * we assume rused/wused are zero if rec/play modes are not * selected. This allows us to keep the same formula for all * modes, provided we set rbpf/wbpf to 1 to avoid division by * zero. * * to understand the formula, draw a picture :) */ rbpf = (hdl->sio.mode & SIO_REC) ? hdl->sio.par.bps * hdl->sio.par.rchan : 1; wbpf = (hdl->sio.mode & SIO_PLAY) ? hdl->sio.par.bps * hdl->sio.par.pchan : 1; rround = hdl->sio.par.round * rbpf; clk = hdl->sio.cpos % hdl->sio.par.round; rdrop = (clk * rbpf - hdl->sio.rused) % rround; if (rdrop < 0) rdrop += rround; cmove = (rdrop + hdl->sio.rused) / rbpf; wsil = cmove * wbpf + hdl->sio.wused; DPRINTFN(2, "wsil = %d, cmove = %d, rdrop = %d\n", wsil, cmove, rdrop); if (!sio_alsa_stop(&hdl->sio)) return 0; if (!sio_alsa_start(&hdl->sio)) return 0; if (hdl->sio.mode & SIO_PLAY) { hdl->odelta -= cmove; hdl->sio.wsil = wsil; } if (hdl->sio.mode & SIO_REC) { hdl->idelta -= cmove; hdl->sio.rdrop = rdrop; } DPRINTFN(2, "xrun: corrected\n"); DPRINTFN(2, "wsil = %d, rdrop = %d, odelta = %d, idelta = %d\n", wsil, rdrop, hdl->odelta, hdl->idelta); return 1; } static int sio_alsa_setpar_hw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwp, snd_pcm_format_t *reqfmt, unsigned int *rate, unsigned int *chans, snd_pcm_uframes_t *round, unsigned int *periods) { static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_BE, SND_PCM_FORMAT_U32_LE, SND_PCM_FORMAT_U32_BE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_BE, SND_PCM_FORMAT_U24_LE, SND_PCM_FORMAT_U24_BE, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, SND_PCM_FORMAT_U16_LE, SND_PCM_FORMAT_U16_BE, SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S8 }; int i, err, dir = 0; unsigned req_rate, min_periods = 2; req_rate = *rate; err = snd_pcm_hw_params_any(pcm, hwp); if (err < 0) { DALSA("couldn't init pars", err); return 0; } err = snd_pcm_hw_params_set_access(pcm, hwp, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { DALSA("couldn't set interleaved access", err); return 0; } err = snd_pcm_hw_params_test_format(pcm, hwp, *reqfmt); if (err < 0) { for (i = 0; ; i++) { if (i == sizeof(fmts) / sizeof(snd_pcm_format_t)) { DPRINTF("no known format found\n"); return 0; } err = snd_pcm_hw_params_test_format(pcm, hwp, fmts[i]); if (err) continue; *reqfmt = fmts[i]; break; } } err = snd_pcm_hw_params_set_format(pcm, hwp, *reqfmt); if (err < 0) { DALSA("couldn't set fmt", err); return 0; } err = snd_pcm_hw_params_set_rate_resample(pcm, hwp, 0); if (err < 0) { DALSA("couldn't turn resampling off", err); return 0; } err = snd_pcm_hw_params_set_rate_near(pcm, hwp, rate, 0); if (err < 0) { DALSA("couldn't set rate", err); return 0; } err = snd_pcm_hw_params_set_channels_near(pcm, hwp, chans); if (err < 0) { DALSA("couldn't set channel count", err); return 0; } err = snd_pcm_hw_params_set_periods_integer(pcm, hwp); if (err < 0) { DALSA("couldn't set periods to integer", err); return 0; } err = snd_pcm_hw_params_set_periods_min(pcm, hwp, &min_periods, NULL); if (err < 0) { DALSA("couldn't set minimum periods", err); return 0; } err = snd_pcm_hw_params_set_period_size_integer(pcm, hwp); if (err < 0) { DALSA("couldn't set period to integer", err); return 0; } *round = *round * *rate / req_rate; *round = (*round + 31) & ~31; err = snd_pcm_hw_params_set_period_size_near(pcm, hwp, round, &dir); if (err < 0) { DALSA("couldn't set period size failed", err); return 0; } err = snd_pcm_hw_params_set_periods_near(pcm, hwp, periods, &dir); if (err < 0) { DALSA("couldn't set period count", err); return 0; } err = snd_pcm_hw_params(pcm, hwp); if (err < 0) { DALSA("couldn't commit params", err); return 0; } return 1; } static int sio_alsa_getcap_hw(snd_pcm_t *pcm, int *rates, int *fmts, int *chans) { int i, err; snd_pcm_hw_params_t *hwp; snd_pcm_hw_params_alloca(&hwp); err = snd_pcm_hw_params_any(pcm, hwp); if (err < 0) { DALSA("sio_alsa_trypar: couldn't init pars", err); return 0; } *fmts = 0; for (i = 0; i < CAP_NFMTS; i++) { err = snd_pcm_hw_params_test_format(pcm, hwp, cap_fmts[i]); if (err == 0) { *fmts |= 1 << i; } } *rates = 0; for (i = 0; i < CAP_NRATES; i++) { err = snd_pcm_hw_params_test_rate(pcm, hwp, cap_rates[i], 0); if (err == 0) { *rates |= 1 << i; } } *chans = 0; for (i = 0; i < CAP_NCHANS; i++) { err = snd_pcm_hw_params_test_channels(pcm, hwp, cap_chans[i]); if (err == 0) { *chans |= 1 << i; } } return 1; } /* * guess device capabilities */ static int sio_alsa_getcap(struct sio_hdl *sh, struct sio_cap *cap) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; int irates, orates, ifmts, ofmts, ichans, ochans; int i; irates = orates = ifmts = ofmts = ichans = ochans = 0; if (hdl->sio.mode & SIO_PLAY) { if (!sio_alsa_getcap_hw(hdl->opcm, &orates, &ofmts, &ochans)) { return 0; } } if (hdl->sio.mode & SIO_REC) { if (!sio_alsa_getcap_hw(hdl->ipcm, &irates, &ifmts, &ichans)) { return 0; } } for (i = 0; i < CAP_NFMTS; i++) { sio_alsa_fmttopar(hdl, cap_fmts[i], &cap->enc[i].bits, &cap->enc[i].sig, &cap->enc[i].le); cap->enc[i].bps = SIO_BPS(cap->enc[0].bits); cap->enc[i].msb = 1; } for (i = 0; i < CAP_NRATES; i++) { cap->rate[i] = cap_rates[i]; } for (i = 0; i < CAP_NCHANS; i++) { cap->pchan[i] = cap_chans[i]; cap->rchan[i] = cap_chans[i]; } cap->confs[0].enc = ~0U; cap->confs[0].rate = ~0U; cap->confs[0].pchan = ~0U; cap->confs[0].rchan = ~0U; if (hdl->sio.mode & SIO_PLAY) { cap->confs[0].pchan &= ochans; cap->confs[0].enc &= ofmts; cap->confs[0].rate &= orates; } if (hdl->sio.mode & SIO_REC) { cap->confs[0].rchan &= ichans; cap->confs[0].enc &= ifmts; cap->confs[0].rate &= irates; } cap->nconf = 1; return 1; #undef NCHANS #undef NRATES } static int sio_alsa_setpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; snd_pcm_hw_params_t *ohwp, *ihwp; snd_pcm_sw_params_t *oswp, *iswp; snd_pcm_uframes_t iround, oround; snd_pcm_format_t ifmt, ofmt; unsigned int iperiods, operiods; unsigned irate, orate; int err; snd_pcm_hw_params_alloca(&ohwp); snd_pcm_sw_params_alloca(&oswp); snd_pcm_hw_params_alloca(&ihwp); snd_pcm_sw_params_alloca(&iswp); sio_alsa_enctofmt(hdl, &ifmt, par->bits, par->sig, par->le); irate = (par->rate == ~0U) ? 48000 : par->rate; if (par->appbufsz != ~0U) { iround = (par->round != ~0U) ? par->round : (par->appbufsz + 1) / 2; iperiods = par->appbufsz / iround; if (iperiods < 2) iperiods = 2; } else if (par->round != ~0U) { iround = par->round; iperiods = 2; } else { iperiods = 2; iround = irate / 100; } if (hdl->sio.mode & SIO_REC) { hdl->par.rchan = par->rchan; if (!sio_alsa_setpar_hw(hdl->ipcm, ihwp, &ifmt, &irate, &hdl->par.rchan, &iround, &iperiods)) { hdl->sio.eof = 1; return 0; } } ofmt = ifmt; orate = irate; oround = iround; operiods = iperiods; if (hdl->sio.mode & SIO_PLAY) { hdl->par.pchan = par->pchan; if (!sio_alsa_setpar_hw(hdl->opcm, ohwp, &ofmt, &orate, &hdl->par.pchan, &oround, &operiods)) { hdl->sio.eof = 1; return 0; } if (!(hdl->sio.mode & SIO_REC)) { ifmt = ofmt; irate = orate; iround = oround; iperiods = operiods; } } DPRINTFN(2, "ofmt = %u, orate = %u, oround = %u, operiods = %u\n", ofmt, orate, (unsigned int)oround, operiods); DPRINTFN(2, "ifmt = %u, irate = %u, iround = %u, iperiods = %u\n", ifmt, irate, (unsigned int)iround, iperiods); if (ifmt != ofmt) { DPRINTF("play and rec formats differ\n"); hdl->sio.eof = 1; return 0; } if (irate != orate) { DPRINTF("play and rec rates differ\n"); hdl->sio.eof = 1; return 0; } if (iround != oround) { DPRINTF("play and rec block sizes differ\n"); hdl->sio.eof = 1; return 0; } if (!sio_alsa_fmttopar(hdl, ifmt, &hdl->par.bits, &hdl->par.sig, &hdl->par.le)) return 0; hdl->par.msb = 1; hdl->par.bps = SIO_BPS(hdl->par.bits); hdl->par.rate = irate; hdl->par.round = iround; hdl->par.bufsz = iround * iperiods; hdl->par.appbufsz = hdl->par.bufsz; /* software params */ if (hdl->sio.mode & SIO_REC) { err = snd_pcm_sw_params_current(hdl->ipcm, iswp); if (err < 0) { DALSA("couldn't get current rec params", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_start_threshold(hdl->ipcm, iswp, 0); if (err < 0) { DALSA("couldn't set rec start threshold", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_stop_threshold(hdl->ipcm, iswp, hdl->par.bufsz); if (err < 0) { DALSA("couldn't set rec stop threshold", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_avail_min(hdl->ipcm, iswp, 1); if (err < 0) { DALSA("couldn't set rec avail min", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_period_event(hdl->ipcm, iswp, 1); if (err < 0) { DALSA("couldn't set rec period event", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params(hdl->ipcm, iswp); if (err < 0) { DALSA("couldn't commit rec sw params", err); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_PLAY) { err = snd_pcm_sw_params_current(hdl->opcm, oswp); if (err < 0) { DALSA("couldn't get current play params", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_start_threshold(hdl->opcm, oswp, hdl->par.bufsz); if (err < 0) { DALSA("couldn't set play start threshold", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_stop_threshold(hdl->opcm, oswp, hdl->par.bufsz); if (err < 0) { DALSA("couldn't set play stop threshold", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_avail_min(hdl->opcm, oswp, 1); if (err < 0) { DALSA("couldn't set play avail min", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params_set_period_event(hdl->opcm, oswp, 1); if (err < 0) { DALSA("couldn't set play period event", err); hdl->sio.eof = 1; return 0; } err = snd_pcm_sw_params(hdl->opcm, oswp); if (err < 0) { DALSA("couldn't commit play sw params", err); hdl->sio.eof = 1; return 0; } } #ifdef DEBUG if (_sndio_debug >= 2) { if (hdl->sio.mode & SIO_REC) snd_pcm_dump(hdl->ipcm, output); if (hdl->sio.mode & SIO_PLAY) snd_pcm_dump(hdl->opcm, output); } #endif return 1; } static int sio_alsa_getpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; *par = hdl->par; return 1; } static size_t sio_alsa_read(struct sio_hdl *sh, void *buf, size_t len) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; snd_pcm_sframes_t n; size_t todo; if (hdl->ipartial > 0) { todo = hdl->ipartial; if (todo > len) todo = len; memcpy(buf, hdl->itmpbuf + hdl->ibpf - hdl->ipartial, todo); hdl->ipartial -= todo; return todo; } else { if (len < hdl->ibpf) { buf = hdl->itmpbuf; len = hdl->ibpf; } } todo = len / hdl->ibpf; if (todo == 0) return 0; while ((n = snd_pcm_readi(hdl->ipcm, buf, todo)) < 0) { if (n == -EINTR) continue; if (n == -EPIPE || n == -ESTRPIPE) { sio_alsa_xrun(hdl); return 0; } if (n != -EAGAIN) { DALSA("couldn't read data", n); hdl->sio.eof = 1; } return 0; } if (n == 0) { DPRINTF("sio_alsa_read: eof\n"); hdl->sio.eof = 1; return 0; } hdl->idelta += n; if (buf == hdl->itmpbuf) { hdl->ipartial = hdl->ibpf; return 0; } return n * hdl->ibpf; } static size_t sio_alsa_write(struct sio_hdl *sh, const void *buf, size_t len) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; snd_pcm_sframes_t n; size_t todo; if (len < hdl->obpf || hdl->opartial > 0) { todo = hdl->obpf - hdl->opartial; if (todo > 0) { if (todo > len) todo = len; memcpy(hdl->otmpbuf + hdl->opartial, buf, todo); hdl->opartial += todo; return todo; } len = hdl->obpf; buf = hdl->otmpbuf; } todo = len / hdl->obpf; if (todo == 0) return 0; while ((n = snd_pcm_writei(hdl->opcm, buf, todo)) < 0) { if (n == -EINTR) continue; if (n == -ESTRPIPE || n == -EPIPE) { sio_alsa_xrun(hdl); return 0; } if (n != -EAGAIN) { DALSA("couldn't write data", n); hdl->sio.eof = 1; } return 0; } hdl->odelta += n; if (buf == hdl->otmpbuf) { if (n > 0) hdl->opartial = 0; return 0; } return n * hdl->obpf; } void sio_alsa_onmove(struct sio_alsa_hdl *hdl) { int delta; if (hdl->running) { switch (hdl->sio.mode & (SIO_PLAY | SIO_REC)) { case SIO_PLAY: delta = hdl->odelta; break; case SIO_REC: delta = hdl->idelta; break; default: /* SIO_PLAY | SIO_REC */ delta = hdl->odelta > hdl->idelta ? hdl->odelta : hdl->idelta; } if (delta <= 0) return; } else { delta = 0; hdl->running = 1; } _sio_onmove_cb(&hdl->sio, delta); if (hdl->sio.mode & SIO_PLAY) hdl->odelta -= delta; if (hdl->sio.mode & SIO_REC) hdl->idelta -= delta; } static int sio_alsa_nfds(struct sio_hdl *sh) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; return hdl->nfds; } static int sio_alsa_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; int i; if (hdl->sio.eof) return 0; hdl->events = events & (POLLIN | POLLOUT); if (!(hdl->sio.mode & SIO_PLAY)) hdl->events &= ~POLLOUT; if (!(hdl->sio.mode & SIO_REC)) hdl->events &= ~POLLIN; if (!hdl->sio.started) hdl->events = 0; memset(pfd, 0, sizeof(struct pollfd) * hdl->nfds); hdl->onfds = hdl->infds = 0; if (hdl->events & POLLOUT) { if (!hdl->running && snd_pcm_state(hdl->opcm) == SND_PCM_STATE_RUNNING) sio_alsa_onmove(hdl); hdl->onfds = snd_pcm_poll_descriptors(hdl->opcm, pfd, hdl->nfds); if (hdl->onfds < 0) { DALSA("couldn't poll play descriptors", hdl->onfds); hdl->sio.eof = 1; return 0; } } if (hdl->events & POLLIN) { if (!hdl->running && snd_pcm_state(hdl->ipcm) == SND_PCM_STATE_RUNNING) sio_alsa_onmove(hdl); hdl->infds = snd_pcm_poll_descriptors(hdl->ipcm, pfd + hdl->onfds, hdl->nfds - hdl->onfds); if (hdl->infds < 0) { DALSA("couldn't poll rec descriptors", hdl->infds); hdl->sio.eof = 1; return 0; } } DPRINTFN(4, "sio_alsa_pollfd: events = %x, nfds = %d + %d\n", events, hdl->onfds, hdl->infds); for (i = 0; i < hdl->onfds + hdl->infds; i++) { DPRINTFN(4, "sio_alsa_pollfd: pfds[%d].events = %x\n", i, pfd[i].events); } return hdl->onfds + hdl->infds; } int sio_alsa_revents(struct sio_hdl *sh, struct pollfd *pfd) { struct sio_alsa_hdl *hdl = (struct sio_alsa_hdl *)sh; snd_pcm_sframes_t iused, oavail, oused; snd_pcm_state_t istate, ostate; unsigned short revents, r; int nfds, err, i; if (hdl->sio.eof) return POLLHUP; for (i = 0; i < hdl->onfds + hdl->infds; i++) { DPRINTFN(4, "sio_alsa_revents: pfds[%d].revents = %x\n", i, pfd[i].revents); } revents = nfds = 0; if (hdl->events & POLLOUT) { err = snd_pcm_poll_descriptors_revents(hdl->opcm, pfd, hdl->onfds, &r); if (err < 0) { DALSA("couldn't get play events", err); hdl->sio.eof = 1; return POLLHUP; } revents |= r; nfds += hdl->onfds; } if (hdl->events & POLLIN) { err = snd_pcm_poll_descriptors_revents(hdl->ipcm, pfd + nfds, hdl->infds, &r); if (err < 0) { DALSA("couldn't get rec events", err); hdl->sio.eof = 1; return POLLHUP; } revents |= r; nfds += hdl->infds; } if (hdl->sio.mode & SIO_PLAY) { ostate = snd_pcm_state(hdl->opcm); if (ostate == SND_PCM_STATE_XRUN) { if (!sio_alsa_xrun(hdl)) return POLLHUP; return 0; } if (ostate == SND_PCM_STATE_RUNNING || ostate == SND_PCM_STATE_PREPARED) { oavail = snd_pcm_avail_update(hdl->opcm); if (oavail < 0) { if (oavail == -EPIPE || oavail == -ESTRPIPE) { if (!sio_alsa_xrun(hdl)) return POLLHUP; return 0; } DALSA("couldn't get play buffer ptr", oavail); hdl->sio.eof = 1; return POLLHUP; } oused = hdl->par.bufsz - oavail; hdl->odelta -= oused - hdl->oused; hdl->oused = oused; } } if (hdl->sio.mode & SIO_REC) { istate = snd_pcm_state(hdl->ipcm); if (istate == SND_PCM_STATE_XRUN) { if (!sio_alsa_xrun(hdl)) return POLLHUP; return 0; } if (istate == SND_PCM_STATE_RUNNING || istate == SND_PCM_STATE_PREPARED) { iused = snd_pcm_avail_update(hdl->ipcm); if (iused < 0) { if (iused == -EPIPE || iused == -ESTRPIPE) { if (!sio_alsa_xrun(hdl)) return POLLHUP; return 0; } DALSA("couldn't get rec buffer ptr", iused); hdl->sio.eof = 1; return POLLHUP; } hdl->idelta += iused - hdl->iused; hdl->iused = iused; } } if ((revents & (POLLIN | POLLOUT)) && hdl->running) sio_alsa_onmove(hdl); return revents; } #endif /* defined USE_ALSA */ sndio-1.5.0/libsndio/sio_aucat.c010066400017510001751000000305421332662053300152670ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include "aucat.h" #include "debug.h" #include "sio_priv.h" #include "bsd-compat.h" struct sio_aucat_hdl { struct sio_hdl sio; struct aucat aucat; unsigned int rbpf, wbpf; /* read and write bytes-per-frame */ int events; /* events the user requested */ unsigned int curvol, reqvol; /* current and requested volume */ int delta; /* some of received deltas */ #define PSTATE_INIT 0 #define PSTATE_RUN 1 int pstate; size_t round; /* write block size */ size_t walign; /* align write packets size to this */ }; static void sio_aucat_close(struct sio_hdl *); static int sio_aucat_start(struct sio_hdl *); static int sio_aucat_stop(struct sio_hdl *); static int sio_aucat_setpar(struct sio_hdl *, struct sio_par *); static int sio_aucat_getpar(struct sio_hdl *, struct sio_par *); static int sio_aucat_getcap(struct sio_hdl *, struct sio_cap *); static size_t sio_aucat_read(struct sio_hdl *, void *, size_t); static size_t sio_aucat_write(struct sio_hdl *, const void *, size_t); static int sio_aucat_nfds(struct sio_hdl *); static int sio_aucat_pollfd(struct sio_hdl *, struct pollfd *, int); static int sio_aucat_revents(struct sio_hdl *, struct pollfd *); static int sio_aucat_setvol(struct sio_hdl *, unsigned int); static void sio_aucat_getvol(struct sio_hdl *); static struct sio_ops sio_aucat_ops = { sio_aucat_close, sio_aucat_setpar, sio_aucat_getpar, sio_aucat_getcap, sio_aucat_write, sio_aucat_read, sio_aucat_start, sio_aucat_stop, sio_aucat_nfds, sio_aucat_pollfd, sio_aucat_revents, sio_aucat_setvol, sio_aucat_getvol }; /* * execute the next message, return 0 if blocked */ static int sio_aucat_runmsg(struct sio_aucat_hdl *hdl) { int delta; unsigned int size, ctl; if (!_aucat_rmsg(&hdl->aucat, &hdl->sio.eof)) return 0; switch (ntohl(hdl->aucat.rmsg.cmd)) { case AMSG_DATA: size = ntohl(hdl->aucat.rmsg.u.data.size); if (size == 0 || size % hdl->rbpf) { DPRINTF("sio_aucat_runmsg: bad data message\n"); hdl->sio.eof = 1; return 0; } DPRINTFN(3, "aucat: data(%d)\n", size); return 1; case AMSG_FLOWCTL: delta = ntohl(hdl->aucat.rmsg.u.ts.delta); hdl->aucat.maxwrite += delta * (int)hdl->wbpf; DPRINTFN(3, "aucat: flowctl(%d), maxwrite = %d\n", delta, hdl->aucat.maxwrite); break; case AMSG_MOVE: delta = ntohl(hdl->aucat.rmsg.u.ts.delta); hdl->delta += delta; DPRINTFN(3, "aucat: move(%d), delta = %d, maxwrite = %d\n", delta, hdl->delta, hdl->aucat.maxwrite); if (hdl->delta >= 0) { _sio_onmove_cb(&hdl->sio, hdl->delta); hdl->delta = 0; } break; case AMSG_SETVOL: ctl = ntohl(hdl->aucat.rmsg.u.vol.ctl); hdl->curvol = hdl->reqvol = ctl; DPRINTFN(3, "aucat: setvol(%d)\n", ctl); _sio_onvol_cb(&hdl->sio, ctl); break; case AMSG_STOP: DPRINTFN(3, "aucat: stop()\n"); hdl->pstate = PSTATE_INIT; break; default: DPRINTF("sio_aucat_runmsg: unhandled message %u\n", hdl->aucat.rmsg.cmd); hdl->sio.eof = 1; return 0; } hdl->aucat.rstate = RSTATE_MSG; hdl->aucat.rtodo = sizeof(struct amsg); return 1; } static int sio_aucat_buildmsg(struct sio_aucat_hdl *hdl) { if (hdl->curvol != hdl->reqvol) { hdl->aucat.wstate = WSTATE_MSG; hdl->aucat.wtodo = sizeof(struct amsg); hdl->aucat.wmsg.cmd = htonl(AMSG_SETVOL); hdl->aucat.wmsg.u.vol.ctl = htonl(hdl->reqvol); hdl->curvol = hdl->reqvol; return _aucat_wmsg(&hdl->aucat, &hdl->sio.eof); } return 0; } struct sio_hdl * _sio_aucat_open(const char *str, unsigned int mode, int nbio) { struct sio_aucat_hdl *hdl; hdl = malloc(sizeof(struct sio_aucat_hdl)); if (hdl == NULL) return NULL; if (!_aucat_open(&hdl->aucat, str, mode)) { free(hdl); return NULL; } _sio_create(&hdl->sio, &sio_aucat_ops, mode, nbio); hdl->curvol = SIO_MAXVOL; hdl->reqvol = SIO_MAXVOL; hdl->pstate = PSTATE_INIT; hdl->round = 0xdeadbeef; hdl->walign = 0xdeadbeef; return (struct sio_hdl *)hdl; } static void sio_aucat_close(struct sio_hdl *sh) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; if (!hdl->sio.eof && hdl->sio.started) (void)sio_aucat_stop(&hdl->sio); _aucat_close(&hdl->aucat, hdl->sio.eof); free(hdl); } static int sio_aucat_start(struct sio_hdl *sh) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; hdl->wbpf = hdl->sio.par.bps * hdl->sio.par.pchan; hdl->rbpf = hdl->sio.par.bps * hdl->sio.par.rchan; hdl->aucat.maxwrite = 0; hdl->round = hdl->sio.par.round; hdl->delta = 0; DPRINTFN(2, "aucat: start, maxwrite = %d\n", hdl->aucat.maxwrite); AMSG_INIT(&hdl->aucat.wmsg); hdl->aucat.wmsg.cmd = htonl(AMSG_START); hdl->aucat.wtodo = sizeof(struct amsg); if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof)) return 0; hdl->aucat.rstate = RSTATE_MSG; hdl->aucat.rtodo = sizeof(struct amsg); if (!_aucat_setfl(&hdl->aucat, 1, &hdl->sio.eof)) return 0; hdl->walign = hdl->round * hdl->wbpf; hdl->pstate = PSTATE_RUN; return 1; } static int sio_aucat_stop(struct sio_hdl *sh) { #define ZERO_MAX 0x400 static unsigned char zero[ZERO_MAX]; struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; unsigned int n, count; if (!_aucat_setfl(&hdl->aucat, 0, &hdl->sio.eof)) return 0; /* * complete message or data block in progress */ if (hdl->aucat.wstate == WSTATE_MSG) { if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof)) return 0; } if (hdl->aucat.wstate == WSTATE_DATA) { hdl->aucat.maxwrite = hdl->aucat.wtodo; while (hdl->aucat.wstate != WSTATE_IDLE) { count = hdl->aucat.wtodo; if (count > ZERO_MAX) count = ZERO_MAX; n = sio_aucat_write(&hdl->sio, zero, count); if (n == 0) return 0; } } /* * send stop message */ AMSG_INIT(&hdl->aucat.wmsg); hdl->aucat.wmsg.cmd = htonl(AMSG_STOP); hdl->aucat.wtodo = sizeof(struct amsg); if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof)) return 0; /* * wait for the STOP ACK */ while (hdl->pstate != PSTATE_INIT) { switch (hdl->aucat.rstate) { case RSTATE_MSG: if (!sio_aucat_runmsg(hdl)) return 0; break; case RSTATE_DATA: if (!sio_aucat_read(&hdl->sio, zero, ZERO_MAX)) return 0; break; } } return 1; } static int sio_aucat_setpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; AMSG_INIT(&hdl->aucat.wmsg); hdl->aucat.wmsg.cmd = htonl(AMSG_SETPAR); hdl->aucat.wmsg.u.par.bits = par->bits; hdl->aucat.wmsg.u.par.bps = par->bps; hdl->aucat.wmsg.u.par.sig = par->sig; hdl->aucat.wmsg.u.par.le = par->le; hdl->aucat.wmsg.u.par.msb = par->msb; hdl->aucat.wmsg.u.par.rate = htonl(par->rate); hdl->aucat.wmsg.u.par.appbufsz = htonl(par->appbufsz); hdl->aucat.wmsg.u.par.xrun = par->xrun; if (hdl->sio.mode & SIO_REC) hdl->aucat.wmsg.u.par.rchan = htons(par->rchan); if (hdl->sio.mode & SIO_PLAY) hdl->aucat.wmsg.u.par.pchan = htons(par->pchan); hdl->aucat.wtodo = sizeof(struct amsg); if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof)) return 0; return 1; } static int sio_aucat_getpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; AMSG_INIT(&hdl->aucat.wmsg); hdl->aucat.wmsg.cmd = htonl(AMSG_GETPAR); hdl->aucat.wtodo = sizeof(struct amsg); if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof)) return 0; hdl->aucat.rtodo = sizeof(struct amsg); if (!_aucat_rmsg(&hdl->aucat, &hdl->sio.eof)) return 0; if (ntohl(hdl->aucat.rmsg.cmd) != AMSG_GETPAR) { DPRINTF("sio_aucat_getpar: protocol err\n"); hdl->sio.eof = 1; return 0; } par->bits = hdl->aucat.rmsg.u.par.bits; par->bps = hdl->aucat.rmsg.u.par.bps; par->sig = hdl->aucat.rmsg.u.par.sig; par->le = hdl->aucat.rmsg.u.par.le; par->msb = hdl->aucat.rmsg.u.par.msb; par->rate = ntohl(hdl->aucat.rmsg.u.par.rate); par->bufsz = ntohl(hdl->aucat.rmsg.u.par.bufsz); par->appbufsz = ntohl(hdl->aucat.rmsg.u.par.appbufsz); par->xrun = hdl->aucat.rmsg.u.par.xrun; par->round = ntohl(hdl->aucat.rmsg.u.par.round); if (hdl->sio.mode & SIO_PLAY) par->pchan = ntohs(hdl->aucat.rmsg.u.par.pchan); if (hdl->sio.mode & SIO_REC) par->rchan = ntohs(hdl->aucat.rmsg.u.par.rchan); return 1; } static int sio_aucat_getcap(struct sio_hdl *sh, struct sio_cap *cap) { unsigned int i, bps, le, sig, chan, rindex, rmult; static unsigned int rates[] = { 8000, 11025, 12000 }; 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 int)) { rindex = 0; rmult *= 2; } } cap->nconf = 1; return 1; } static size_t sio_aucat_read(struct sio_hdl *sh, void *buf, size_t len) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; while (hdl->aucat.rstate == RSTATE_MSG) { if (!sio_aucat_runmsg(hdl)) return 0; } return _aucat_rdata(&hdl->aucat, buf, len, &hdl->sio.eof); } static size_t sio_aucat_write(struct sio_hdl *sh, const void *buf, size_t len) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; size_t n; while (hdl->aucat.wstate == WSTATE_IDLE) { if (!sio_aucat_buildmsg(hdl)) break; } if (len <= 0 || hdl->aucat.maxwrite <= 0) return 0; if (len > hdl->aucat.maxwrite) len = hdl->aucat.maxwrite; if (len > hdl->walign) len = hdl->walign; n = _aucat_wdata(&hdl->aucat, buf, len, hdl->wbpf, &hdl->sio.eof); hdl->aucat.maxwrite -= n; hdl->walign -= n; if (hdl->walign == 0) hdl->walign = hdl->round * hdl->wbpf; return n; } static int sio_aucat_nfds(struct sio_hdl *hdl) { return 1; } static int sio_aucat_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; hdl->events = events; if (hdl->aucat.maxwrite <= 0) events &= ~POLLOUT; return _aucat_pollfd(&hdl->aucat, pfd, events); } static int sio_aucat_revents(struct sio_hdl *sh, struct pollfd *pfd) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; int revents = pfd->revents; if (revents & POLLIN) { while (hdl->aucat.rstate == RSTATE_MSG) { if (!sio_aucat_runmsg(hdl)) break; } if (hdl->aucat.rstate != RSTATE_DATA) revents &= ~POLLIN; } if (revents & POLLOUT) { if (hdl->aucat.maxwrite <= 0) revents &= ~POLLOUT; } if (hdl->sio.eof) return POLLHUP; DPRINTFN(3, "sio_aucat_revents: %x\n", revents & hdl->events); return revents & (hdl->events | POLLHUP); } static int sio_aucat_setvol(struct sio_hdl *sh, unsigned int vol) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; hdl->reqvol = vol; return 1; } static void sio_aucat_getvol(struct sio_hdl *sh) { struct sio_aucat_hdl *hdl = (struct sio_aucat_hdl *)sh; _sio_onvol_cb(&hdl->sio, hdl->reqvol); return; } sndio-1.5.0/libsndio/sio_open.3010066400017510001751000000474071332662053300150630ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2007 Alexandre Ratchov .\" .\" 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$ .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 sndio interface to audio devices .Sh SYNOPSIS .In sndio.h .Ft "struct sio_hdl *" .Fn sio_open "const char *name" "unsigned int 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 int vol" .Ft "int" .Fn sio_onvol "struct sio_hdl *hdl" "void (*cb)(void *arg, unsigned int 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 sndiod 8 audio server in a uniform way. .Ss Opening and closing an audio device First the application must call the .Fn sio_open function to obtain a handle to the device; later it will be passed as the .Fa hdl argument of most other functions. The .Fa name parameter gives the device string discussed in .Xr sndio 7 . In most cases it should be set to .Dv SIO_DEVANY to allow the user to select it using the .Ev AUDIODEVICE environment variable. .Pp The following values of the .Fa mode parameter are supported: .Bl -tag -width "SIO_PLAY | SIO_REC" .It Dv SIO_PLAY Play-only mode: data written will be played by the device. .It Dv SIO_REC Record-only mode: samples are recorded by the device and must be read. .It Dv SIO_PLAY | SIO_REC The device 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 .Fa 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 stops the device as if .Fn sio_stop is called and frees the handle. Thus, no samples submitted with .Fn sio_write are discarded. .Ss Negotiating audio parameters Audio samples are interleaved. A frame consists of one sample for each channel. For example, a 16-bit stereo encoding has two samples per frame and, two bytes per sample (thus 4 bytes per frame). .Pp The set of parameters of the device that can be controlled is given by the following structure: .Bd -literal struct sio_par { unsigned int bits; /* bits per sample */ unsigned int bps; /* bytes per sample */ unsigned int sig; /* 1 = signed, 0 = unsigned int */ unsigned int le; /* 1 = LE, 0 = BE byte order */ unsigned int msb; /* 1 = MSB, 0 = LSB aligned */ unsigned int rchan; /* number channels for recording */ unsigned int pchan; /* number channels for playback */ unsigned int rate; /* frames per second */ unsigned int appbufsz; /* minimum buffer size without xruns */ unsigned int bufsz; /* end-to-end buffer size (read-only) */ unsigned int 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 int 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 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 .Dv SIO_REC mode was selected. .It Va pchan The number of played channels; meaningful only if .Dv 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 .Dv SIO_IGNORE , .Dv SIO_SYNC , or .Dv SIO_ERROR constants. .El .Pp The following approach is recommended to negotiate device parameters: .Bl -bullet .It Initialize a .Vt sio_par structure using .Fn sio_initpar and fill it with the desired parameters. Then call .Fn sio_setpar to request the device to use them. Parameters left unset in the .Vt sio_par structure will be set to device-specific defaults. .It Call .Fn sio_getpar to retrieve the actual parameters of the device 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 after .Fn sio_start has been called, .Fn sio_stop must be called before parameters can be changed. .Pp If the device is exposed by the .Xr sndiod 8 server, which is the default configuration, a transparent emulation layer will automatically be set up, and in this case any combination of rate, encoding and numbers of channels is supported. .Pp To ease filling the .Vt sio_par structure, the following macros can be used: .Bl -tag -width "SIO_BPS(bits)" .It Dv SIO_BPS Ns Pq Fa bits Return the smallest value for .Va bps that is a power of two and that is large enough to hold .Fa bits . .It Dv SIO_LE_NATIVE Can be used to set the .Va le parameter when native byte order is required. .El .Ss Getting device capabilities There's no way to get an exhaustive list of all parameter combinations the device supports. Applications that need to have a set of working parameter combinations in advance can use the .Fn sio_getcap function. .Pp The .Vt 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 int bits; unsigned int bps; unsigned int sig; unsigned int le; unsigned int msb; } enc[SIO_NENC]; unsigned int rchan[SIO_NCHAN]; /* allowed rchans */ unsigned int pchan[SIO_NCHAN]; /* allowed pchans */ unsigned int rate[SIO_NRATE]; /* allowed rates */ unsigned int nconf; /* num. of confs[] */ struct sio_conf { unsigned int enc; /* bitmask of enc[] indexes */ unsigned int rchan; /* bitmask of rchan[] indexes */ unsigned int pchan; /* bitmask of pchan[] indexes */ unsigned int rate; /* bitmask of rate[] indexes */ } confs[SIO_NCONF]; }; .Ed .Pp The parameters are as follows: .Bl -tag -width "rchan[SIO_NCHAN]" .It Va enc Ns Bq Dv 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 .Vt sio_par structure. .It Va rchan Ns Bq Dv SIO_NCHAN Array of supported channel numbers for recording usable in the .Vt sio_par structure. .It Va pchan Ns Bq Dv SIO_NCHAN Array of supported channel numbers for playback usable in the .Vt sio_par structure. .It Va rate Ns Bq Dv SIO_NRATE Array of supported sample rates usable in the .Vt 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 Ns Bq Dv 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 .Vt sio_conf structure, then the second element of the .Va rate Ns Bq Dv SIO_NRATE array of the .Vt sio_cap structure is valid for this configuration. .El .Ss Starting and stopping the device The .Fn sio_start function puts the device in a waiting state: the device 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 puts the audio subsystem in the same state as before .Fn sio_start is called. It stops recording, drains the play buffer and then stops playback. If samples to play are queued but playback hasn't started yet then playback is forced immediately; playback will actually stop once the buffer is drained. In no case are samples in the play buffer discarded. .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 .Fa nbytes bytes at the .Fa addr location and return the number of bytes stored. Unless the .Fa 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 .Fa nbio_flag is set, .Fn sio_write will block until the requested amount of data is written. .Ss Non-blocking mode operation If the .Fa 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 The .Xr poll 2 system call can be used to check if data can be read from or written to the device. The .Fn sio_pollfd function fills the array .Fa pfd of .Vt pollfd structures, used by .Xr poll 2 , with .Fa events ; the latter is a bit-mask of .Dv POLLIN and .Dv POLLOUT constants; refer to .Xr poll 2 for more details. The .Fn sio_revents function returns the bit-mask set by .Xr poll 2 in the .Fa pfd array of .Vt pollfd structures. If .Dv POLLIN is set, recorded samples are available in the device buffer and can be read with .Fn sio_read . If .Dv POLLOUT is set, space is available in the device buffer and new samples to play can be submitted with .Fn sio_write . .Dv POLLHUP may be set if an error occurs, even if it is not selected with .Fn sio_pollfd . .Pp The size of the .Ar pfd array, which the caller must pre-allocate, is provided by the .Fn sio_nfds function. .Ss Synchronizing non-audio events to the audio stream in real-time In order to perform actions at precise positions of the audio 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 .Fn cb callback function called at regular time intervals. The .Fa delta argument contains the number of frames the hardware played and/or recorded since the last call of .Fn cb . It is called by .Fn sio_read , .Fn sio_write , and .Fn sio_revents . When the first sample is played and/or recorded, right after the device starts, the callback is invoked with a zero .Fa delta argument. The value of the .Fa 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 .Fa 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. 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 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 .Vt sio_par structure, the audio subsystem will behave as follows: .Bl -tag -width "SIO_IGNORE" .It Dv SIO_IGNORE The devices pauses during overruns and underruns, thus the current position (obtained through .Fn sio_onmove ) stops being incremented. Once the overrun and/or underrun condition is gone, the device resumes; 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 Dv 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 .Fn 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 Dv SIO_ERROR With this mode, on the first play buffer underrun or record buffer overrun, playback and/or recording is terminated and no other function than .Fn sio_close will succeed. .Pp This mode is mostly useful for testing. .El .Ss Controlling the volume The .Fn sio_setvol function can be used to set playback attenuation. The .Fa 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 has returned a non-zero value, the callback has been invoked and thus the current volume is available. If there's no volume setting available, .Fn sio_onvol returns 0 and the callback is never invoked and calls to .Fn sio_setvol are ignored. .Pp The .Fn sio_onvol function can be called with a NULL argument to check whether a volume knob 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 .Fa sio_hdl argument, except .Fn sio_close and .Fn sio_eof , stop working (i.e. always return 0). The .Fn sio_eof function can be used at any stage. .Sh RETURN VALUES The .Fn sio_open function returns the newly created handle on success or NULL on failure. .Pp The .Fn sio_setpar , .Fn sio_getpar , .Fn sio_getcap , .Fn sio_start , .Fn sio_stop , and .Fn sio_setvol functions return 1 on success and 0 on failure. .Pp The .Fn sio_pollfd function returns the number of .Va pollfd structures filled. 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. .Pp The .Fn sio_read and .Fn sio_write functions return the number of bytes transferred. .Pp The .Fn sio_eof function returns 0 if there's no pending error, and a non-zero value if there's an error. .Sh ENVIRONMENT .Bl -tag -width "SNDIO_DEBUGXXX" -compact .It Ev AUDIODEVICE Device to use if .Fn sio_open is called with .Dv SIO_DEVANY as the .Fa name argument. .It Ev SNDIO_DEBUG The debug level: may be a value between 0 and 2. .El .Sh SEE ALSO .Xr audio 4 , .Xr sndio 7 , .Xr sndiod 8 , .Xr audio 9 .Sh BUGS The .Xr audio 4 driver doesn't drain playback buffers, thus if sndio is used to directly access an .Xr audio 4 device, the .Fn sio_stop function will stop playback immediately. .Pp If the application doesn't consume recorded data fast enough then .Dq "control messages" from the .Xr sndiod 8 server are delayed and consequently .Fn 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. sndio-1.5.0/libsndio/sio_oss.c010066400017510001751000000450221332662053300147750ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * Copyright (c) 2016 Tobias Kortkamp * * 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. */ #ifdef USE_OSS #include #include #include #include #include #include #include #include #include #include "debug.h" #include "sio_priv.h" #include "bsd-compat.h" #define DEVPATH_PREFIX "/dev/dsp" #define DEVPATH_MAX (1 + \ sizeof(DEVPATH_PREFIX) - 1 + \ sizeof(int) * 3) struct sio_oss_fmt { int fmt; unsigned int bits; unsigned int bps; unsigned int sig; unsigned int le; unsigned int msb; }; static struct sio_oss_fmt formats[] = { /* See http://manuals.opensound.com/developer/formats.html. * AFMT_{S8,U16}_* are marked as obsolete so are missing here. */ /* le+msb not important */ { AFMT_U8, 8, 1, 0, 0, 0 }, { AFMT_U8, 8, 1, 0, 1, 0 }, { AFMT_U8, 8, 1, 0, 0, 1 }, { AFMT_U8, 8, 1, 0, 1, 1 }, /* msb not important */ { AFMT_S16_BE, 16, 2, 1, 0, 0 }, { AFMT_S16_BE, 16, 2, 1, 0, 1 }, { AFMT_S16_LE, 16, 2, 1, 1, 0 }, { AFMT_S16_LE, 16, 2, 1, 1, 1 }, { AFMT_S24_BE, 24, 3, 1, 0, 0 }, { AFMT_S24_BE, 24, 3, 1, 0, 1 }, { AFMT_S24_LE, 24, 3, 1, 1, 0 }, { AFMT_S24_LE, 24, 3, 1, 1, 1 }, { AFMT_U24_BE, 24, 3, 0, 0, 0 }, { AFMT_U24_BE, 24, 3, 0, 0, 1 }, { AFMT_U24_LE, 24, 3, 0, 1, 0 }, { AFMT_U24_LE, 24, 3, 0, 1, 1 }, { AFMT_S32_BE, 32, 4, 1, 0, 1 }, { AFMT_S32_LE, 32, 4, 1, 1, 1 }, { AFMT_U32_BE, 32, 4, 0, 0, 1 }, { AFMT_U32_LE, 32, 4, 0, 1, 1 }, }; struct sio_oss_hdl { struct sio_hdl sio; int fd; int idelta, odelta; int iused; int oused; int bpf; int fmt; unsigned int rate; unsigned int chan; unsigned int appbufsz; unsigned int round; int filling; }; static struct sio_hdl *sio_oss_fdopen(const char *, int, unsigned int, int); static int sio_oss_getcap(struct sio_hdl *, struct sio_cap *); static int sio_oss_getfd(const char *, unsigned int, int); static int sio_oss_getpar(struct sio_hdl *, struct sio_par *); static int sio_oss_nfds(struct sio_hdl *); static int sio_oss_pollfd(struct sio_hdl *, struct pollfd *, int); static int sio_oss_revents(struct sio_hdl *, struct pollfd *); static int sio_oss_setpar(struct sio_hdl *, struct sio_par *); static int sio_oss_start(struct sio_hdl *); static int sio_oss_stop(struct sio_hdl *); static int sio_oss_xrun(struct sio_oss_hdl *); static size_t sio_oss_read(struct sio_hdl *, void *, size_t); static size_t sio_oss_write(struct sio_hdl *, const void *, size_t); static void sio_oss_close(struct sio_hdl *); static int sio_oss_setvol(struct sio_hdl *, unsigned int); static void sio_oss_getvol(struct sio_hdl *); static struct sio_ops sio_oss_ops = { sio_oss_close, sio_oss_setpar, sio_oss_getpar, sio_oss_getcap, sio_oss_write, sio_oss_read, sio_oss_start, sio_oss_stop, sio_oss_nfds, sio_oss_pollfd, sio_oss_revents, sio_oss_setvol, sio_oss_getvol, }; /* * guess device capabilities */ static int sio_oss_getcap(struct sio_hdl *sh, struct sio_cap *cap) { /* From sound(4): * The FreeBSD multichannel matrix processor supports up to 18 * interleaved channels, but the limit is currently set to 8 * channels (as commonly used for 7.1 surround sound). */ static unsigned int chans[] = { 1, 2, 4, 6, 8 }; static unsigned int rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; static int afmts[] = { AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_S24_LE, AFMT_U24_LE, AFMT_S32_LE, AFMT_U32_LE }; struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; unsigned int nconf = 0; unsigned int enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map; unsigned int i, j, k, conf; int fmts; if (ioctl(hdl->fd, SNDCTL_DSP_GETFMTS, &fmts) < 0) { DPERROR("sio_oss_getcap: GETFMTS"); hdl->sio.eof = 1; return 0; } /* * get a subset of supported encodings */ for (j = 0, i = 0; i < sizeof(afmts) / sizeof(afmts[0]); i++) { if (fmts & afmts[i]) { for (k = 0; k < sizeof(formats) / sizeof(formats[0]); k++) { if (formats[k].fmt == afmts[i]) { cap->enc[j].sig = formats[k].sig; cap->enc[j].bits = formats[k].bits; cap->enc[j].bps = formats[k].bps; cap->enc[j].le = formats[k].le; cap->enc[j].msb = formats[k].msb; enc_map |= 1 << j; j++; break; } } } } /* * fill channels */ if (hdl->sio.mode & SIO_PLAY) { for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) { cap->pchan[i] = chans[i]; pchan_map |= (1 << i); } } if (hdl->sio.mode & SIO_REC) { for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) { cap->rchan[i] = chans[i]; rchan_map |= (1 << i); } } /* * fill rates */ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); j++) { rate_map = 0; if ((enc_map & (1 << j)) == 0) continue; for (i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) { cap->rate[i] = 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; return 1; } static int sio_oss_getfd(const char *str, unsigned int mode, int nbio) { const char *p; char path[DEVPATH_MAX]; unsigned int devnum; int fd, flags, val; audio_buf_info bi; p = _sndio_parsetype(str, "rsnd"); if (p == NULL) { DPRINTF("sio_oss_getfd: %s: \"rsnd\" expected\n", str); return -1; } switch (*p) { case '/': p++; break; default: DPRINTF("sio_oss_getfd: %s: '/' expected\n", str); return -1; } if (strcmp(p, "default") == 0) { strlcpy(path, DEVPATH_PREFIX, sizeof(path)); } else { p = _sndio_parsenum(p, &devnum, 255); if (p == NULL || *p != '\0') { DPRINTF("sio_sun_getfd: %s: number expected after '/'\n", str); return -1; } snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum); } 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 | O_CLOEXEC)) < 0) { if (errno == EINTR) continue; DPERROR(path); return -1; } /* * Check if the device supports playing/recording. * Unfortunately, it's possible for devices to be opened RDWR * even when they don't support playing/recording. */ if (mode & SIO_PLAY && ioctl(fd, SNDCTL_DSP_GETOSPACE, &bi) < 0) { close(fd); return -1; } if (mode & SIO_REC && ioctl(fd, SNDCTL_DSP_GETISPACE, &bi) < 0) { close(fd); return -1; } val = 1; if (ioctl(fd, SNDCTL_DSP_LOW_WATER, &val) < 0) { DPERROR("sio_oss_start: LOW_WATER"); close(fd); return -1; } return fd; } static struct sio_hdl * sio_oss_fdopen(const char *str, int fd, unsigned int mode, int nbio) { struct sio_oss_hdl *hdl; hdl = malloc(sizeof(struct sio_oss_hdl)); if (hdl == NULL) return NULL; _sio_create(&hdl->sio, &sio_oss_ops, mode, nbio); /* Set default device parameters */ hdl->fmt = AFMT_S16_LE; hdl->rate = 48000; hdl->chan = 2; hdl->round = 960; hdl->appbufsz = 8 * 960; hdl->filling = 0; hdl->fd = fd; return (struct sio_hdl *)hdl; } struct sio_hdl * _sio_oss_open(const char *str, unsigned int mode, int nbio) { struct sio_oss_hdl *hdl; int fd; fd = sio_oss_getfd(str, mode, nbio); if (fd < 0) return NULL; hdl = (struct sio_oss_hdl *)sio_oss_fdopen(str, fd, mode, nbio); if (hdl != NULL) return (struct sio_hdl*)hdl; while (close(fd) < 0 && errno == EINTR) ; /* retry */ return NULL; } static void sio_oss_close(struct sio_hdl *sh) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; while (close(hdl->fd) < 0 && errno == EINTR) ; /* retry */ free(hdl); } static int sio_oss_start(struct sio_hdl *sh) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; int trig; hdl->iused = 0; hdl->oused = 0; hdl->idelta = 0; hdl->odelta = 0; if (hdl->sio.mode & SIO_PLAY) { /* * keep the device paused and let sio_oss_pollfd() trigger the * start later, to avoid buffer underruns */ hdl->filling = 1; trig = 0; } else { /* * no play buffers to fill, start now! */ trig = PCM_ENABLE_INPUT; _sio_onmove_cb(&hdl->sio, 0); } if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { DPERROR("sio_oss_start: SETTRIGGER"); hdl->sio.eof = 1; return 0; } return 1; } static int sio_oss_stop(struct sio_hdl *sh) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl*)sh; int trig; if (hdl->filling) { hdl->filling = 0; return 1; } trig = 0; if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { DPERROR("sio_oss_stop: SETTRIGGER"); hdl->sio.eof = 1; return 0; } return 1; } static int sio_oss_setpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; unsigned int i, round, bufsz; int frag_max, frag_shift, frag_count, frag; unsigned int le, sig, msb; le = par->le; sig = par->sig; msb = par->msb; if (le == ~0U) le = 0; if (sig == ~0U) sig = 0; if (msb == ~0U) msb = 0; hdl->fmt = AFMT_S16_LE; for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) { if (formats[i].bits == par->bits && formats[i].le == le && formats[i].sig == sig && formats[i].msb == msb) { hdl->fmt = formats[i].fmt; break; } } if (par->rate != ~0U) hdl->rate = par->rate; if (hdl->rate < 8000) hdl->rate = 8000; if (hdl->rate > 192000) hdl->rate = 192000; if (hdl->sio.mode & SIO_PLAY) hdl->chan = par->pchan; else if (hdl->sio.mode & SIO_REC) hdl->chan = par->rchan; if (ioctl(hdl->fd, SNDCTL_DSP_SETFMT, &hdl->fmt) < 0) { DPERROR("sio_oss_setpar: SETFMT"); hdl->sio.eof = 1; return 0; } for (i = 0; ; i++) { if (i == sizeof(formats) / sizeof(formats[0])) { DPRINTF("sio_oss_setpar: unknown fmt %d\n", hdl->fmt); hdl->sio.eof = 1; return 0; } if (formats[i].fmt == hdl->fmt) break; } if (ioctl(hdl->fd, SNDCTL_DSP_SPEED, &hdl->rate) < 0) { DPERROR("sio_oss_setpar: SPEED"); hdl->sio.eof = 1; return 0; } if (ioctl(hdl->fd, SNDCTL_DSP_CHANNELS, &hdl->chan) < 0) { DPERROR("sio_oss_setpar: CHANNELS"); hdl->sio.eof = 1; return 0; } hdl->bpf = formats[i].bps * hdl->chan; if (par->round != ~0U && par->appbufsz != ~0U) { round = par->round; bufsz = par->appbufsz; } else if (par->round != ~0U) { round = par->round; bufsz = 2 * par->round; } else if (par->appbufsz != ~0U) { round = par->appbufsz / 2; bufsz = par->appbufsz; } else { /* * even if it's not specified, we have to set the * block size to ensure that both play and record * direction get the same block size. Pick an * arbitrary value that would work for most players at * 48kHz, stereo, 16-bit. */ round = 512; bufsz = 1024; } frag_max = round * hdl->chan * formats[i].bps; frag_shift = 8; while (1 << (frag_shift + 1) <= frag_max) frag_shift++; frag_count = bufsz / round; if (frag_count < 2) frag_count = 2; frag = frag_count << 16 | frag_shift; if (ioctl(hdl->fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0) { DPERROR("sio_oss_setpar: SETFRAGMENT"); hdl->sio.eof = 1; return 0; } return 1; } static int sio_oss_getpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; unsigned int i, found = 0; audio_buf_info pbi, rbi; for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) { if (formats[i].fmt == hdl->fmt) { par->sig = formats[i].sig; par->le = formats[i].le; par->bits = formats[i].bits; par->bps = formats[i].bps; par->msb = formats[i].msb; found = 1; break; } } if (!found) { DPRINTF("sio_oss_getpar: unknown format %d\n", hdl->fmt); hdl->sio.eof = 1; return 0; } par->rate = hdl->rate; par->pchan = hdl->chan; par->rchan = hdl->chan; par->xrun = SIO_IGNORE; if (hdl->sio.mode & SIO_PLAY) { if (ioctl(hdl->fd, SNDCTL_DSP_GETOSPACE, &pbi) < 0) { DPERROR("sio_oss_getpar: SNDCTL_DSP_GETOSPACE"); hdl->sio.eof = 1; return 0; } par->round = pbi.fragsize / (par->pchan * par->bps); par->bufsz = pbi.fragstotal * par->round; } if (hdl->sio.mode & SIO_REC) { if (ioctl(hdl->fd, SNDCTL_DSP_GETISPACE, &rbi) < 0) { DPERROR("sio_oss_getpar: SNDCTL_DSP_GETISPACE"); hdl->sio.eof = 1; return 0; } if (!(hdl->sio.mode & SIO_PLAY)) { par->round = rbi.fragsize / (par->rchan * par->bps); par->bufsz = rbi.fragstotal * par->round; } } par->appbufsz = par->bufsz; #ifdef DEBUG if ((hdl->sio.mode & (SIO_REC | SIO_PLAY)) == (SIO_REC | SIO_PLAY)) { if (pbi.fragsize != rbi.fragsize) { DPRINTF("sio_oss_getpar: frag size/count mismatch\n" "play: count = %d, size = %d\n" "rec: count = %d, size = %d\n", pbi.fragstotal, pbi.fragsize, rbi.fragstotal, rbi.fragsize); hdl->sio.eof = 1; return 0; } } #endif return 1; } static size_t sio_oss_read(struct sio_hdl *sh, void *buf, size_t len) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; ssize_t n; while ((n = read(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("sio_oss_read: read"); hdl->sio.eof = 1; } return 0; } if (n == 0) { DPRINTF("sio_oss_read: eof\n"); hdl->sio.eof = 1; return 0; } hdl->idelta += n; return n; } static size_t sio_oss_write(struct sio_hdl *sh, const void *buf, size_t len) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; const unsigned char *data = buf; ssize_t n, todo; todo = len; while ((n = write(hdl->fd, data, todo)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("sio_oss_write: write"); hdl->sio.eof = 1; } return 0; } hdl->odelta += n; return n; } static int sio_oss_nfds(struct sio_hdl *hdl) { return 1; } static int sio_oss_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; int trig; pfd->fd = hdl->fd; pfd->events = events; if (hdl->filling && hdl->sio.wused == hdl->sio.par.bufsz * hdl->sio.par.pchan * hdl->sio.par.bps) { hdl->filling = 0; trig = 0; if (hdl->sio.mode & SIO_PLAY) trig |= PCM_ENABLE_OUTPUT; if (hdl->sio.mode & SIO_REC) trig |= PCM_ENABLE_INPUT; if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { DPERROR("sio_oss_pollfd: SETTRIGGER"); hdl->sio.eof = 1; return 0; } _sio_onmove_cb(&hdl->sio, 0); } return 1; } static int sio_oss_xrun(struct sio_oss_hdl *hdl) { int clk; int wsil, rdrop, cmove; int rbpf, rround; int wbpf; DPRINTFN(2, "sio_oss_xrun:\n"); if (_sndio_debug >= 2) _sio_printpos(&hdl->sio); /* * we assume rused/wused are zero if rec/play modes are not * selected. This allows us to keep the same formula for all * modes, provided we set rbpf/wbpf to 1 to avoid division by * zero. * * to understand the formula, draw a picture :) */ rbpf = (hdl->sio.mode & SIO_REC) ? hdl->sio.par.bps * hdl->sio.par.rchan : 1; wbpf = (hdl->sio.mode & SIO_PLAY) ? hdl->sio.par.bps * hdl->sio.par.pchan : 1; rround = hdl->sio.par.round * rbpf; clk = hdl->sio.cpos % hdl->sio.par.round; rdrop = (clk * rbpf - hdl->sio.rused) % rround; if (rdrop < 0) rdrop += rround; cmove = (rdrop + hdl->sio.rused) / rbpf; wsil = cmove * wbpf + hdl->sio.wused; DPRINTFN(2, "wsil = %d, cmove = %d, rdrop = %d\n", wsil, cmove, rdrop); if (!sio_oss_stop(&hdl->sio)) return 0; if (!sio_oss_start(&hdl->sio)) return 0; if (hdl->sio.mode & SIO_PLAY) { hdl->odelta -= cmove * hdl->bpf; hdl->sio.wsil = wsil; } if (hdl->sio.mode & SIO_REC) { hdl->idelta -= cmove * hdl->bpf; hdl->sio.rdrop = rdrop; } DPRINTFN(2, "xrun: corrected\n"); DPRINTFN(2, "wsil = %d, rdrop = %d, odelta = %d, idelta = %d\n", wsil, rdrop, hdl->odelta, hdl->idelta); return 1; } static int sio_oss_revents(struct sio_hdl *sh, struct pollfd *pfd) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; audio_errinfo ei; int delta, iused, oused; int revents = pfd->revents; oss_count_t optr, iptr; if ((pfd->revents & POLLHUP) || (pfd->revents & (POLLIN | POLLOUT)) == 0) return pfd->revents; /* Hide xruns from clients */ if (ioctl(hdl->fd, SNDCTL_DSP_GETERROR, &ei) < 0) { DPERROR("sio_oss_revents: GETERROR"); hdl->sio.eof = 1; return POLLHUP; } if (ei.play_underruns > 0 || ei.rec_overruns > 0) { if (!sio_oss_xrun(hdl)) return POLLHUP; return 0; } if (hdl->sio.mode & SIO_PLAY) { if (ioctl(hdl->fd, SNDCTL_DSP_CURRENT_OPTR, &optr) < 0) { DPERROR("sio_oss_revents: CURRENT_OPTR"); hdl->sio.eof = 1; return POLLHUP; } oused = optr.fifo_samples * hdl->bpf; hdl->odelta -= oused - hdl->oused; hdl->oused = oused; if (!(hdl->sio.mode & SIO_REC)) { hdl->idelta = hdl->odelta; } } if (hdl->sio.mode & SIO_REC) { if (ioctl(hdl->fd, SNDCTL_DSP_CURRENT_IPTR, &iptr) < 0) { DPERROR("sio_oss_revents: CURRENT_IPTR"); hdl->sio.eof = 1; return POLLHUP; } iused = iptr.fifo_samples * hdl->bpf; hdl->idelta += iused - hdl->iused; hdl->iused = iused; if (!(hdl->sio.mode & SIO_PLAY)) { hdl->odelta = hdl->idelta; } } delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta; if (delta > 0) { _sio_onmove_cb(&hdl->sio, delta / hdl->bpf); hdl->idelta -= delta; hdl->odelta -= delta; } return revents; } static int sio_oss_setvol(struct sio_hdl *sh, unsigned int vol) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; int newvol; /* Scale to 0..100 */ newvol = (100 * vol + SIO_MAXVOL / 2) / SIO_MAXVOL; newvol = newvol | (newvol << 8); if (ioctl(hdl->fd, SNDCTL_DSP_SETPLAYVOL, &newvol) < 0) { DPERROR("sio_oss_setvol"); hdl->sio.eof = 1; return 0; } return 1; } static void sio_oss_getvol(struct sio_hdl *sh) { struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh; int vol; if (ioctl(hdl->fd, SNDCTL_DSP_GETPLAYVOL, &vol) < 0) { DPERROR("sio_oss_getvol"); hdl->sio.eof = 1; return; } /* Use left channel volume and scale to SIO_MAXVOL */ vol = (SIO_MAXVOL * (vol & 0x7f) + 50) / 100; _sio_onvol_cb(&hdl->sio, vol); } #endif /* defined USE_OSS */ sndio-1.5.0/libsndio/sio_priv.h010066400017510001751000000056461332662053300151660ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 "sndio.h" #define SIO_MAXNFDS 16 /* * 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 */ int rdrop; /* recorded bytes to drop */ int wsil; /* silence to play */ int rused; /* bytes used in read buffer */ int wused; /* bytes used in write buffer */ long long cpos; /* clock since start */ struct sio_par par; #ifdef DEBUG unsigned long long pollcnt; /* times sio_revents was called */ long long start_nsec; #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 (*nfds)(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_aucat_open(const char *, unsigned, int); #ifdef USE_SUN struct sio_hdl *_sio_sun_open(const char *, unsigned, int); #endif #ifdef USE_OSS struct sio_hdl *_sio_oss_open(const char *, unsigned, int); #endif #ifdef USE_ALSA struct sio_hdl *_sio_alsa_open(const char *, unsigned, int); #endif 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 void _sio_printpos(struct sio_hdl *); #endif #endif /* !defined(SNDIO_PRIV_H) */ sndio-1.5.0/libsndio/sio_sun.c010066400017510001751000000335551332662053300150060ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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. */ #ifdef USE_SUN #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "sio_priv.h" #include "bsd-compat.h" #define DEVPATH_PREFIX "/dev/audio" #define DEVPATH_MAX (1 + \ sizeof(DEVPATH_PREFIX) - 1 + \ sizeof(int) * 3) struct sio_sun_hdl { struct sio_hdl sio; int fd; int filling; unsigned int ibpf, obpf; /* bytes per frame */ unsigned int ibytes, obytes; /* bytes the hw transferred */ unsigned int ierr, oerr; /* frames the hw dropped */ int idelta, odelta; /* position reported to client */ }; static void sio_sun_close(struct sio_hdl *); static int sio_sun_start(struct sio_hdl *); static int sio_sun_stop(struct sio_hdl *); static int sio_sun_setpar(struct sio_hdl *, struct sio_par *); static int sio_sun_getpar(struct sio_hdl *, struct sio_par *); static int sio_sun_getcap(struct sio_hdl *, struct sio_cap *); static size_t sio_sun_read(struct sio_hdl *, void *, size_t); static size_t sio_sun_write(struct sio_hdl *, const void *, size_t); static int sio_sun_nfds(struct sio_hdl *); static int sio_sun_pollfd(struct sio_hdl *, struct pollfd *, int); static int sio_sun_revents(struct sio_hdl *, struct pollfd *); static struct sio_ops sio_sun_ops = { sio_sun_close, sio_sun_setpar, sio_sun_getpar, sio_sun_getcap, sio_sun_write, sio_sun_read, sio_sun_start, sio_sun_stop, sio_sun_nfds, sio_sun_pollfd, sio_sun_revents, NULL, /* setvol */ NULL, /* getvol */ }; static int sio_sun_adjpar(struct sio_sun_hdl *hdl, struct audio_swpar *ap) { if (hdl->sio.eof) return 0; if (ioctl(hdl->fd, AUDIO_SETPAR, ap)) { DPERROR("AUDIO_SETPAR"); hdl->sio.eof = 1; return 0; } if (ioctl(hdl->fd, AUDIO_GETPAR, ap)) { DPERROR("AUDIO_GETPAR"); hdl->sio.eof = 1; return 0; } return 1; } /* * 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 sio_sun_testpar(struct sio_sun_hdl *hdl, struct sio_enc *enc, unsigned int pchan, unsigned int rchan, unsigned int rate) { struct audio_swpar ap; AUDIO_INITPAR(&ap); if (enc != NULL) { ap.sig = enc->sig; ap.bits = enc->bits; ap.bps = enc->bps; if (ap.bps > 1) ap.le = enc->le; if (ap.bps * 8 > ap.bits) ap.msb = enc->msb; } if (rate) ap.rate = rate; if (pchan && (hdl->sio.mode & SIO_PLAY)) ap.pchan = pchan; if (rchan && (hdl->sio.mode & SIO_REC)) ap.rchan = rchan; if (!sio_sun_adjpar(hdl, &ap)) return 0; if (pchan && ap.pchan != pchan) return 0; if (rchan && ap.rchan != rchan) return 0; if (rate && ap.rate != rate) return 0; if (enc) { if (ap.sig != enc->sig) return 0; if (ap.bits != enc->bits) return 0; if (ap.bps != enc->bps) return 0; if (ap.bps > 1 && ap.le != enc->le) return 0; if (ap.bits < ap.bps * 8 && ap.msb != enc->msb) return 0; } return 1; } /* * guess device capabilities */ static int sio_sun_getcap(struct sio_hdl *sh, struct sio_cap *cap) { static unsigned int chans[] = { 1, 2, 4, 6, 8, 10, 12 }; static unsigned int rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000 }; static unsigned int encs[] = { 8, 16, 24, 32 }; struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; struct audio_swpar savepar, ap; unsigned int nconf = 0; unsigned int enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map; unsigned int i, j, conf; if (ioctl(hdl->fd, AUDIO_GETPAR, &savepar)) { DPERROR("AUDIO_GETPAR"); hdl->sio.eof = 1; return 0; } /* * get a subset of supported encodings */ for (i = 0; i < sizeof(encs) / sizeof(encs[0]); i++) { AUDIO_INITPAR(&ap); ap.bits = encs[i]; ap.sig = (ap.bits > 8) ? 1 : 0; if (!sio_sun_adjpar(hdl, &ap)) return 0; if (ap.bits == encs[i]) { cap->enc[i].sig = ap.sig; cap->enc[i].bits = ap.bits; cap->enc[i].le = ap.le; cap->enc[i].bps = ap.bps; cap->enc[i].msb = ap.msb; enc_map |= 1 << i; } } /* * 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) { for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) { AUDIO_INITPAR(&ap); ap.pchan = chans[i]; if (!sio_sun_adjpar(hdl, &ap)) return 0; if (ap.pchan == chans[i]) { cap->pchan[i] = chans[i]; pchan_map |= (1 << i); } } } if (hdl->sio.mode & SIO_REC) { for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) { AUDIO_INITPAR(&ap); ap.pchan = chans[i]; if (!sio_sun_adjpar(hdl, &ap)) return 0; if (ap.rchan == chans[i]) { cap->rchan[i] = chans[i]; 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 */ for (j = 0; j < sizeof(encs) / sizeof(encs[0]); j++) { rate_map = 0; if ((enc_map & (1 << j)) == 0) continue; for (i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) { if (sio_sun_testpar(hdl, &cap->enc[j], 0, 0, rates[i])) { cap->rate[i] = 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 (ioctl(hdl->fd, AUDIO_SETPAR, &savepar)) { DPERROR("AUDIO_SETPAR"); hdl->sio.eof = 1; return 0; } return 1; } int sio_sun_getfd(const char *str, unsigned int mode, int nbio) { const char *p; char path[DEVPATH_MAX]; unsigned int devnum; int fd, flags; #ifdef DEBUG _sndio_debug_init(); #endif p = _sndio_parsetype(str, "rsnd"); if (p == NULL) { DPRINTF("sio_sun_getfd: %s: \"rsnd\" expected\n", str); return -1; } switch (*p) { case '/': p++; break; default: DPRINTF("sio_sun_getfd: %s: '/' expected\n", str); return -1; } if (strcmp(p, "default") == 0) { devnum = 0; } else { p = _sndio_parsenum(p, &devnum, 255); if (p == NULL || *p != '\0') { DPRINTF("sio_sun_getfd: %s: number expected after '/'\n", str); return -1; } } snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum); 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 | O_CLOEXEC)) < 0) { if (errno == EINTR) continue; DPERROR(path); return -1; } return fd; } struct sio_hdl * sio_sun_fdopen(int fd, unsigned int mode, int nbio) { struct sio_sun_hdl *hdl; #ifdef DEBUG _sndio_debug_init(); #endif hdl = malloc(sizeof(struct sio_sun_hdl)); if (hdl == NULL) return NULL; _sio_create(&hdl->sio, &sio_sun_ops, mode, nbio); /* * pause the device */ if (ioctl(fd, AUDIO_STOP) < 0) { DPERROR("AUDIO_STOP"); free(hdl); return NULL; } hdl->fd = fd; hdl->filling = 0; return (struct sio_hdl *)hdl; } struct sio_hdl * _sio_sun_open(const char *str, unsigned int mode, int nbio) { struct sio_hdl *hdl; int fd; fd = sio_sun_getfd(str, mode, nbio); if (fd < 0) return NULL; hdl = sio_sun_fdopen(fd, mode, nbio); if (hdl != NULL) return hdl; while (close(fd) < 0 && errno == EINTR) ; /* retry */ return NULL; } static void sio_sun_close(struct sio_hdl *sh) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; while (close(hdl->fd) < 0 && errno == EINTR) ; /* retry */ free(hdl); } static int sio_sun_start(struct sio_hdl *sh) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; hdl->obpf = hdl->sio.par.pchan * hdl->sio.par.bps; hdl->ibpf = hdl->sio.par.rchan * hdl->sio.par.bps; hdl->ibytes = 0; hdl->obytes = 0; hdl->ierr = 0; hdl->oerr = 0; hdl->idelta = 0; hdl->odelta = 0; if (hdl->sio.mode & SIO_PLAY) { /* * keep the device paused and let sio_sun_pollfd() trigger the * start later, to avoid buffer underruns */ hdl->filling = 1; } else { /* * no play buffers to fill, start now! */ if (ioctl(hdl->fd, AUDIO_START) < 0) { DPERROR("AUDIO_START"); hdl->sio.eof = 1; return 0; } _sio_onmove_cb(&hdl->sio, 0); } return 1; } static int sio_sun_stop(struct sio_hdl *sh) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; if (hdl->filling) { hdl->filling = 0; return 1; } if (ioctl(hdl->fd, AUDIO_STOP) < 0) { DPERROR("AUDIO_STOP"); hdl->sio.eof = 1; return 0; } return 1; } static int sio_sun_setpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; struct audio_swpar ap; AUDIO_INITPAR(&ap); ap.sig = par->sig; ap.le = par->le; ap.bits = par->bits; ap.bps = par->bps; ap.msb = par->msb; ap.rate = par->rate; if (hdl->sio.mode & SIO_PLAY) ap.pchan = par->pchan; if (hdl->sio.mode & SIO_REC) ap.rchan = par->rchan; if (par->round != ~0U && par->appbufsz != ~0U) { ap.round = par->round; ap.nblks = par->appbufsz / par->round; } else if (par->round != ~0U) { ap.round = par->round; ap.nblks = 2; } else if (par->appbufsz != ~0U) { ap.round = par->appbufsz / 2; ap.nblks = 2; } if (ioctl(hdl->fd, AUDIO_SETPAR, &ap) < 0) { DPERROR("AUDIO_SETPAR"); hdl->sio.eof = 1; return 0; } return 1; } static int sio_sun_getpar(struct sio_hdl *sh, struct sio_par *par) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; struct audio_swpar ap; if (ioctl(hdl->fd, AUDIO_GETPAR, &ap) < 0) { DPERROR("AUDIO_GETPAR"); hdl->sio.eof = 1; return 0; } par->sig = ap.sig; par->le = ap.le; par->bits = ap.bits; par->bps = ap.bps; par->msb = ap.msb; par->rate = ap.rate; par->pchan = ap.pchan; par->rchan = ap.rchan; par->round = ap.round; par->appbufsz = par->bufsz = ap.nblks * ap.round; par->xrun = SIO_IGNORE; return 1; } static size_t sio_sun_read(struct sio_hdl *sh, void *buf, size_t len) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; ssize_t n; while ((n = read(hdl->fd, buf, len)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("sio_sun_read: read"); hdl->sio.eof = 1; } return 0; } if (n == 0) { DPRINTF("sio_sun_read: eof\n"); hdl->sio.eof = 1; return 0; } return n; } static size_t sio_sun_write(struct sio_hdl *sh, const void *buf, size_t len) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; const unsigned char *data = buf; ssize_t n, todo; todo = len; while ((n = write(hdl->fd, data, todo)) < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { DPERROR("sio_sun_write: write"); hdl->sio.eof = 1; } return 0; } return n; } static int sio_sun_nfds(struct sio_hdl *hdl) { return 1; } static int sio_sun_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; pfd->fd = hdl->fd; pfd->events = events; if (hdl->filling && hdl->sio.wused == hdl->sio.par.bufsz * hdl->sio.par.pchan * hdl->sio.par.bps) { hdl->filling = 0; if (ioctl(hdl->fd, AUDIO_START) < 0) { DPERROR("AUDIO_START"); hdl->sio.eof = 1; return 0; } _sio_onmove_cb(&hdl->sio, 0); } return 1; } int sio_sun_revents(struct sio_hdl *sh, struct pollfd *pfd) { struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh; struct audio_pos ap; int dierr = 0, doerr = 0, offset, delta; int revents = pfd->revents; if ((pfd->revents & POLLHUP) || (pfd->revents & (POLLIN | POLLOUT)) == 0) return pfd->revents; if (ioctl(hdl->fd, AUDIO_GETPOS, &ap) < 0) { DPERROR("sio_sun_revents: GETPOS"); hdl->sio.eof = 1; return POLLHUP; } if (hdl->sio.mode & SIO_PLAY) { delta = (ap.play_pos - hdl->obytes) / hdl->obpf; doerr = (ap.play_xrun - hdl->oerr) / hdl->obpf; hdl->obytes = ap.play_pos; hdl->oerr = ap.play_xrun; hdl->odelta += delta; if (!(hdl->sio.mode & SIO_REC)) { hdl->idelta += delta; dierr = doerr; } if (doerr > 0) DPRINTFN(2, "play xrun %d\n", doerr); } if (hdl->sio.mode & SIO_REC) { delta = (ap.rec_pos - hdl->ibytes) / hdl->ibpf; dierr = (ap.rec_xrun - hdl->ierr) / hdl->ibpf; hdl->ibytes = ap.rec_pos; hdl->ierr = ap.rec_xrun; hdl->idelta += delta; if (!(hdl->sio.mode & SIO_PLAY)) { hdl->odelta += delta; doerr = dierr; } if (dierr > 0) DPRINTFN(2, "rec xrun %d\n", dierr); } /* * GETPOS reports positions including xruns, * so we have to substract to get the real position */ hdl->idelta -= dierr; hdl->odelta -= doerr; offset = doerr - dierr; if (offset > 0) { hdl->sio.rdrop += offset * hdl->ibpf; hdl->idelta -= offset; DPRINTFN(2, "will drop %d and pause %d\n", offset, doerr); } else if (offset < 0) { hdl->sio.wsil += -offset * hdl->obpf; hdl->odelta -= -offset; DPRINTFN(2, "will insert %d and pause %d\n", -offset, dierr); } delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta; if (delta > 0) { _sio_onmove_cb(&hdl->sio, delta); hdl->idelta -= delta; hdl->odelta -= delta; } return revents; } #endif /* defined USE_SUN */ sndio-1.5.0/libsndio/sndio.7010066400017510001751000000141401332662053300143540ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2007 Alexandre Ratchov .\" .\" 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$ .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 sndiod 8 , 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 sndiod 8 can be used. It exposes one or more software sub-devices backed by the underlying hardware, while doing all necessary conversions on the fly. It can mix multiple streams or split the hardware into multiple sub-devices, to allow programs to use the hardware concurrently. .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 sndiod 8 . .Pp Additionally, .Xr sndiod 8 exposes a MIDI port 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, and .Xr sndiod 8 service has a name of the form: .Bd -literal -offset center type[@hostname][,unit]/devnum[.option] .Ed .Pp This information is used by audio and MIDI applications to determine how to access the audio device or MIDI port. .Bl -tag -width "hostname" .It Ar type The type of the audio device or MIDI port. Possible values are: .Pp .Bl -tag -width "midithru" -offset 3n -compact .It Cm rsnd Raw .Xr audio 4 device. .It Cm rmidi Raw .Xr midi 4 port. .It Cm snd Audio device exposed by .Xr sndiod 8 . .It Cm midithru MIDI thru box created with .Xr sndiod 8 . .It Cm midi MIDI port exposed by .Xr sndiod 8 . .It Cm default Default audio device or MIDI port (see below). .El .It Ar hostname The hostname or address where the remote .Xr sndiod 8 server to connect to is running. .It Ar unit The number of the .Xr sndiod 8 server to connect to, corresponding to the integer specified using the .Fl U option of .Xr sndiod 8 . Useful only if multiple .Xr sndiod 8 servers are running on the same system. .It Ar devnum Device number. For hardware audio or MIDI ports, this corresponds to the character device minor number. For audio devices or MIDI ports created with .Xr sndiod 8 it corresponds to the number of the corresponding .Fl fq option on the command line. .It Ar option Corresponds to the sub-device string registered using the .Fl s option of .Xr sndiod 8 . .El .Pp For example: .Pp .Bl -tag -width "snd/0.rear" -offset 3n -compact .It Li rsnd/0 First hardware audio device. .It Li rmidi/5 Hardware MIDI port number 5. .It Li snd/0 First audio device exposed by .Xr sndiod 8 . .It Li snd/0.rear Sub-device registered with .Fl s Fa rear . .It Li midithru/0 First MIDI thru box created with .Xr sndiod 8 . .El .Sh DEFAULTS If .Cm default is used as the audio device, the program will use the one specified in the .Ev AUDIODEVICE environment variable. If it is not set, the program first tries to connect to .Li snd/0 . If that fails, it then tries to use .Li rsnd/0 . This allows the .Xr sndiod 8 audio server to be used by default and the bare hardware as fallback; programs don't have to be reconfigured when .Xr sndiod 8 is started or stopped. .Pp If .Cm default is used as the MIDI port, the program will use the one specified in the .Ev MIDIDEVICE environment variable. If it is not set, the program first tries to connect to .Li midithru/0 . If that fails, it then tries to use .Li rmidi/0 . As long as .Xr sndiod 8 is running, this allows programs to exchange MIDI data on machines with no MIDI hardware by default, e.g. a MIDI player could use a software synthesizer with no manual configuration required. .Sh AUTHENTICATION If a shared .Xr sndiod 8 server is running, for privacy reasons only one user may have connections to it at a given time (though the same user could have multiple connections to it). Users are identified by their .Em session cookie , which is automatically generated by audio or MIDI applications upon the first connection to the server. The cookie is stored in .Pa "$HOME/.sndio/cookie" and contains 128 bits of raw random data. .Pp If a session needs to be shared between multiple users, they can connect to the server using the same cookie. .Sh ENVIRONMENT .Bl -tag -width "AUDIODEVICEXXX" -compact .It Ev AUDIODEVICE Audio device that .Xr sio_open 3 uses if the application provides no device chooser. .It Ev MIDIDEVICE MIDI port that .Xr mio_open 3 uses if the application provides no MIDI port chooser. .El .Pp These environment variables are ignored by .Nm if the program has the set-user-ID or set-group-ID bits set. .Sh FILES .Bl -tag -width "/dev/audioNXXX" -compact .It Pa /dev/audioN Audio devices. .It Pa /dev/rmidiN MIDI ports. .El .Sh SEE ALSO .Xr mio_open 3 , .Xr sio_open 3 , .Xr audio 4 , .Xr midi 4 , .Xr sndiod 8 sndio-1.5.0/libsndio/sndio.h010066400017510001751000000113301332662053300144330ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 /* * default audio device and MIDI port */ #define SIO_DEVANY "default" #define MIO_PORTANY "default" /* * private ``handle'' structure */ struct sio_hdl; struct mio_hdl; /* * parameters of a full-duplex stream */ struct sio_par { unsigned int bits; /* bits per sample */ unsigned int bps; /* bytes per sample */ unsigned int sig; /* 1 = signed, 0 = unsigned */ unsigned int le; /* 1 = LE, 0 = BE byte order */ unsigned int msb; /* 1 = MSB, 0 = LSB aligned */ unsigned int rchan; /* number channels for recording direction */ unsigned int pchan; /* number channels for playback direction */ unsigned int rate; /* frames per second */ unsigned int 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 int xrun; /* what to do on overruns/underruns */ unsigned int round; /* optimal bufsz divisor */ unsigned int appbufsz; /* minimum buffer size */ int __pad[3]; /* for future use */ unsigned 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 int bits; unsigned int bps; unsigned int sig; unsigned int le; unsigned int msb; } enc[SIO_NENC]; unsigned int rchan[SIO_NCHAN]; /* allowed values for rchan */ unsigned int pchan[SIO_NCHAN]; /* allowed values for pchan */ unsigned int rate[SIO_NRATE]; /* allowed rates */ int __pad[7]; /* for future use */ unsigned int nconf; /* number of elements in confs[] */ struct sio_conf { unsigned int enc; /* mask of enc[] indexes */ unsigned int rchan; /* mask of chan[] indexes (rec) */ unsigned int pchan; /* mask of chan[] indexes (play) */ unsigned int 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 /* * 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 /* * maximum value of volume, eg. for sio_setvol() */ #define SIO_MAXVOL 127 #ifdef __cplusplus extern "C" { #endif struct pollfd; void sio_initpar(struct sio_par *); struct sio_hdl *sio_open(const char *, unsigned int, 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 int); int sio_onvol(struct sio_hdl *, void (*)(void *, unsigned int), void *); struct mio_hdl *mio_open(const char *, unsigned int, 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 *); int mio_rmidi_getfd(const char *, unsigned int, int); struct mio_hdl *mio_rmidi_fdopen(int, unsigned int, int); int sio_sun_getfd(const char *, unsigned int, int); struct sio_hdl *sio_sun_fdopen(int, unsigned int, int); #ifdef __cplusplus } #endif #endif /* !defined(SNDIO_H) */ sndio-1.5.0/midicat004077500017510001751000000000001332662053300127035ustar00alexalexsndio-1.5.0/midicat/Makefile.in010066400017510001751000000020261332662053300150240ustar00alexalex# extra includes paths (-I options) INCLUDE = -I../libsndio -I../bsd-compat # extra libraries paths (-L options) LIB = -L../libsndio # extra defines (-D options) DEFS = -DDEBUG @defs@ # extra libraries (-l options) LDADD = -lsndio @ldadd@ # variables defined on configure script command line (if any) @vars@ # # binaries, documentation, man pages and examples will be installed in # ${BIN_DIR}, ${MAN1_DIR} # BIN_DIR = @bindir@ MAN1_DIR = @mandir@/man1 # # programs to build # PROG = midicat MAN1 = midicat.1 all: ${PROG} ${MAN1} install: mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR} cp midicat ${DESTDIR}${BIN_DIR} cp midicat.1 ${DESTDIR}${MAN1_DIR} uninstall: cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG} cd ${DESTDIR}${MAN1_DIR} && rm -f ${MAN1} clean: rm -f -- *.o midicat # ---------------------------------------------------------- dependencies --- OBJS = midicat.o midicat: ${OBJS} ${CC} ${LDFLAGS} ${LIB} -o midicat ${OBJS} ${LDADD} .c.o: ${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $< midicat.o: midicat.c sndio-1.5.0/midicat/midicat.1010066400017510001751000000042721332662053300144600ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2015 Alexandre Ratchov .\" .\" 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$ .Dt MIDICAT 1 .Os .Sh NAME .Nm midicat .Nd send to or receive from MIDI ports. .Sh SYNOPSIS .Nm midicat .Bk -words .Op Fl d .Op Fl i Ar file .Op Fl o Ar file .Op Fl q Ar port .Ek .Sh DESCRIPTION The .Nm utility receives MIDI data from the given input MIDI port and/or sends it to the given output MIDI port. The options are as follows: .Bl -tag -width Ds .It Fl d Dump transferred data in hex on stderr. .It Fl i Ar file Read MIDI data from this file instead of receiving it from the MIDI port. If the option argument is .Sq - then standard input will be used. .It Fl o Ar file Write MIDI data to this file instead of sending it to the MIDI port. If the option argument is .Sq - then standard output will be used. .It Fl q Ar port Use this .Xr sndio 7 MIDI port for input/output. If the option is used twice, the first one specifies the input port and the second one the output port. .El .Pp If no files are specified, then .Nm transfers data from the MIDI input port to the MIDI output port. .Sh EXAMPLES Send the given file to .Pa rmidi/0 : .Bd -literal -offset indent $ midicat -i file.syx -q rmidi/0 .Ed .Pp Dump data received from .Pa rmidi/0 to stderr: .Bd -literal -offset indent $ midicat -d -q rmidi/0 -o /dev/null .Ed .Pp Send data from .Pa rmidi/0 to .Pa midithru/0: .Bd -literal -offset indent $ midicat -q rmidi/0 -q midithru/0 .Ed .Sh SEE ALSO .Xr midi 4 , .Xr sndio 7 , .Xr sndiod 8 sndio-1.5.0/midicat/midicat.c010066400017510001751000000105731332662053300145430ustar00alexalex/* * Copyright (c) 2015 Alexandre Ratchov * * 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 #include #include #include #include #include #include "bsd-compat.h" #define MIDI_BUFSZ 1024 char usagestr[] = "usage: midicat [-d] [-i in-file] [-o out-file] " "[-q in-port] [-q out-port]\n"; char *port0, *port1, *ifile, *ofile; struct mio_hdl *ih, *oh; unsigned char buf[MIDI_BUFSZ]; int buf_used = 0; int ifd = -1, ofd = -1; int dump; static int midi_flush(void) { int i, n, sep; if (buf_used == 0) return 1; if (ofile != NULL) { n = write(ofd, buf, buf_used); if (n != buf_used) { fprintf(stderr, "%s: short write\n", ofile); buf_used = 0; return 0; } } else { n = mio_write(oh, buf, buf_used); if (n != buf_used) { fprintf(stderr, "%s: port disconnected\n", ih == oh ? port0 : port1); buf_used = 0; return 0; } } if (dump) { for (i = 0; i < buf_used; i++) { sep = (i % 16 == 15 || i == buf_used - 1) ? '\n' : ' '; fprintf(stderr, "%02x%c", buf[i], sep); } } buf_used = 0; return 1; } int main(int argc, char **argv) { int c, mode; while ((c = getopt(argc, argv, "di:o:q:")) != -1) { switch (c) { case 'd': dump = 1; break; case 'q': if (port0 == NULL) port0 = optarg; else if (port1 == NULL) port1 = optarg; else { fputs("too many -q options\n", stderr); return 1; } break; case 'i': ifile = optarg; break; case 'o': ofile = optarg; break; default: goto bad_usage; } } argc -= optind; argv += optind; if (argc != 0) { bad_usage: fputs(usagestr, stderr); return 1; } /* we don't support more than one data flow */ if (ifile != NULL && ofile != NULL) { fputs("-i and -o are exclusive\n", stderr); return 1; } /* second port makes sense only for port-to-port transfers */ if (port1 != NULL && !(ifile == NULL && ofile == NULL)) { fputs("too many -q options\n", stderr); return 1; } /* if there're neither files nor ports, then we've nothing to do */ if (port0 == NULL && ifile == NULL && ofile == NULL) goto bad_usage; /* if no port specified, use default one */ if (port0 == NULL) port0 = MIO_PORTANY; /* open input or output file (if any) */ if (ifile) { if (strcmp(ifile, "-") == 0) ifd = STDIN_FILENO; else { ifd = open(ifile, O_RDONLY, 0); if (ifd < 0) { perror(ifile); return 1; } } } else if (ofile) { if (strcmp(ofile, "-") == 0) ofd = STDOUT_FILENO; else { ofd = open(ofile, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (ofd < 0) { perror(ofile); return 1; } } } /* open first port for input and output (if output needed) */ if (ofile) mode = MIO_IN; else if (ifile) mode = MIO_OUT; else if (port1 == NULL) mode = MIO_IN | MIO_OUT; else mode = MIO_IN; ih = mio_open(port0, mode, 0); if (ih == NULL) { fprintf(stderr, "%s: couldn't open port\n", port0); return 1; } /* open second port, output only */ if (port1 == NULL) oh = ih; else { oh = mio_open(port1, MIO_OUT, 0); if (oh == NULL) { fprintf(stderr, "%s: couldn't open port\n", port1); exit(1); } } /* transfer until end-of-file or error */ for (;;) { if (ifile != NULL) { buf_used = read(ifd, buf, sizeof(buf)); if (buf_used < 0) { perror("stdin"); break; } if (buf_used == 0) break; if (!midi_flush()) break; } else { buf_used = mio_read(ih, buf, sizeof(buf)); if (buf_used == 0) { fprintf(stderr, "%s: disconnected\n", port0); break; } if (!midi_flush()) break; } } /* clean-up */ if (port0) mio_close(ih); if (port1) mio_close(oh); if (ifile) close(ifd); if (ofile) close(ofd); return 0; } sndio-1.5.0/sndiod004077500017510001751000000000001332662053300125515ustar00alexalexsndio-1.5.0/sndiod/Makefile.in010066400017510001751000000042031332662053300146710ustar00alexalex# extra includes paths (-I options) INCLUDE = -I../libsndio -I../bsd-compat # extra libraries paths (-L options) LIB = -L../libsndio # extra defines (-D options) DEFS = -DDEBUG -DSNDIO_USER=\"@user@\" -DADATA_BITS=@precision@ @defs@ # extra libraries (-l options) LDADD = -lsndio @ldadd@ # variables defined on configure script command line (if any) @vars@ # # binaries, documentation, man pages and examples will be installed in # ${BIN_DIR}, ${MAN8_DIR} # BIN_DIR = @bindir@ MAN8_DIR = @mandir@/man8 # # programs to build # PROG = sndiod MAN8 = sndiod.8 all: ${PROG} install: mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN8_DIR} rm -f ${DESTDIR}${BIN_DIR}/${PROG} ${DESTDIR}${MAN8_DIR}/${MAN8} cp ${PROG} ${DESTDIR}${BIN_DIR} cp ${MAN8} ${DESTDIR}${MAN8_DIR} uninstall: cd ${DESTDIR}${BIN_DIR} && rm -f ${PROG} cd ${DESTDIR}${MAN8_DIR} && rm -f ${MAN8} clean: rm -f -- *.o ${PROG} # ---------------------------------------------------------- dependencies --- OBJS = \ abuf.o utils.o dev.o dsp.o file.o listen.o midi.o miofile.o \ opt.o siofile.o sndiod.o sock.o sndiod: ${OBJS} ${CC} ${LDFLAGS} ${LIB} -o sndiod ${OBJS} ${LDADD} .c.o: ${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $< abuf.o: abuf.c abuf.h utils.h dev.o: dev.c ../bsd-compat/bsd-compat.h abuf.h defs.h dev.h \ dsp.h siofile.h file.h midi.h miofile.h sysex.h utils.h dsp.o: dsp.c dsp.h defs.h utils.h file.o: file.c file.h utils.h listen.o: listen.c listen.h file.h sock.h ../libsndio/amsg.h \ utils.h ../bsd-compat/bsd-compat.h midi.o: midi.c abuf.h defs.h dev.h dsp.h siofile.h file.h midi.h \ miofile.h sysex.h utils.h ../bsd-compat/bsd-compat.h miofile.o: miofile.c defs.h file.h midi.h abuf.h miofile.h utils.h opt.o: opt.c dev.h abuf.h dsp.h defs.h siofile.h file.h opt.h \ utils.h siofile.o: siofile.c abuf.h defs.h dev.h dsp.h siofile.h file.h \ utils.h sndiod.o: sndiod.c ../libsndio/amsg.h defs.h dev.h abuf.h dsp.h \ siofile.h file.h listen.h midi.h miofile.h opt.h sock.h \ utils.h ../bsd-compat/bsd-compat.h sock.o: sock.c abuf.h defs.h dev.h dsp.h siofile.h file.h midi.h \ miofile.h opt.h sock.h ../libsndio/amsg.h utils.h utils.o: utils.c utils.h sndio-1.5.0/sndiod/abuf.c010066400017510001751000000060021332662053300137040ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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. * * 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. */ #include #include #include #include "abuf.h" #include "utils.h" #ifdef DEBUG void abuf_log(struct abuf *buf) { log_putu(buf->start); log_puts("+"); log_putu(buf->used); log_puts("/"); log_putu(buf->len); } #endif void abuf_init(struct abuf *buf, unsigned int len) { buf->data = xmalloc(len); buf->len = len; buf->used = 0; buf->start = 0; } void abuf_done(struct abuf *buf) { #ifdef DEBUG if (buf->used > 0) { if (log_level >= 3) { log_puts("deleting non-empty buffer, used = "); log_putu(buf->used); log_puts("\n"); } } #endif xfree(buf->data); buf->data = (void *)0xdeadbeef; } /* * return the reader pointer and the number of bytes available */ unsigned char * abuf_rgetblk(struct abuf *buf, int *rsize) { int count; count = buf->len - buf->start; if (count > buf->used) count = buf->used; *rsize = count; return buf->data + buf->start; } /* * discard "count" bytes at the start postion. */ void abuf_rdiscard(struct abuf *buf, int count) { #ifdef DEBUG if (count < 0 || count > buf->used) { log_puts("abuf_rdiscard: bad count = "); log_putu(count); log_puts("\n"); panic(); } #endif buf->used -= count; buf->start += count; if (buf->start >= buf->len) buf->start -= buf->len; } /* * advance the writer pointer by "count" bytes */ void abuf_wcommit(struct abuf *buf, int count) { #ifdef DEBUG if (count < 0 || count > (buf->len - buf->used)) { log_puts("abuf_wcommit: bad count = "); log_putu(count); log_puts("\n"); panic(); } #endif buf->used += count; } /* * get writer pointer and the number of bytes writable */ unsigned char * abuf_wgetblk(struct abuf *buf, int *rsize) { int end, avail, count; end = buf->start + buf->used; if (end >= buf->len) end -= buf->len; avail = buf->len - buf->used; count = buf->len - end; if (count > avail) count = avail; *rsize = count; return buf->data + end; } sndio-1.5.0/sndiod/abuf.h010066400017510001751000000025461332662053300137220ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 struct abuf { int start; /* offset (frames) where stored data starts */ int used; /* frames stored in the buffer */ unsigned int len; /* total size of the buffer (frames) */ unsigned char *data; }; void abuf_init(struct abuf *, unsigned int); void abuf_done(struct abuf *); void abuf_log(struct abuf *); unsigned char *abuf_rgetblk(struct abuf *, int *); unsigned char *abuf_wgetblk(struct abuf *, int *); void abuf_rdiscard(struct abuf *, int); void abuf_wcommit(struct abuf *, int); #endif /* !defined(ABUF_H) */ sndio-1.5.0/sndiod/defs.h010066400017510001751000000040051332662053300137160ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 DEFS_H #define DEFS_H /* * 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 */ /* * device or sub-device mode, must be a superset of corresponding SIO_ * and MIO_ constants */ #define MODE_PLAY 0x01 /* allowed to play */ #define MODE_REC 0x02 /* allowed to rec */ #define MODE_MIDIOUT 0x04 /* allowed to read midi */ #define MODE_MIDIIN 0x08 /* allowed to write midi */ #define MODE_MON 0x10 /* allowed to monitor */ #define MODE_RECMASK (MODE_REC | MODE_MON) #define MODE_AUDIOMASK (MODE_PLAY | MODE_REC | MODE_MON) #define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT) /* * underrun/overrun policies, must be the same as SIO_ constants */ #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 */ /* * limits */ #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 */ #endif /* !defined(DEFS_H) */ sndio-1.5.0/sndiod/dev.c010066400017510001751000001150531332662053300135540ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include "bsd-compat.h" #include "abuf.h" #include "defs.h" #include "dev.h" #include "dsp.h" #include "siofile.h" #include "midi.h" #include "opt.h" #include "sysex.h" #include "utils.h" void zomb_onmove(void *); void zomb_onvol(void *); void zomb_fill(void *); void zomb_flush(void *); void zomb_eof(void *); void zomb_exit(void *); void dev_log(struct dev *); void dev_midi_qfr(struct dev *, int); void dev_midi_full(struct dev *); void dev_midi_vol(struct dev *, struct slot *); void dev_midi_master(struct dev *); void dev_midi_slotdesc(struct dev *, struct slot *); void dev_midi_dump(struct dev *); void dev_midi_imsg(void *, unsigned char *, int); void dev_midi_omsg(void *, unsigned char *, int); void dev_midi_fill(void *, int); void dev_midi_exit(void *); void dev_mix_badd(struct dev *, struct slot *); void dev_mix_adjvol(struct dev *); void dev_sub_bcopy(struct dev *, struct slot *); void dev_onmove(struct dev *, int); void dev_master(struct dev *, unsigned int); void dev_cycle(struct dev *); int dev_getpos(struct dev *); struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); void dev_adjpar(struct dev *, int, int, int); int dev_open(struct dev *); void dev_close(struct dev *); int dev_ref(struct dev *); void dev_unref(struct dev *); int dev_init(struct dev *); void dev_done(struct dev *); struct dev *dev_bynum(int); void dev_del(struct dev *); unsigned int dev_roundof(struct dev *, unsigned int); void dev_wakeup(struct dev *); void dev_sync_attach(struct dev *); void dev_mmcstart(struct dev *); void dev_mmcstop(struct dev *); void dev_mmcloc(struct dev *, unsigned int); void slot_log(struct slot *); void slot_del(struct slot *); void slot_setvol(struct slot *, unsigned int); void slot_attach(struct slot *); void slot_ready(struct slot *); void slot_allocbufs(struct slot *); void slot_freebufs(struct slot *); void slot_start(struct slot *); void slot_detach(struct slot *); void slot_stop(struct slot *); void slot_skip_update(struct slot *); void slot_write(struct slot *); void slot_read(struct slot *); int slot_skip(struct slot *); struct midiops dev_midiops = { dev_midi_imsg, dev_midi_omsg, dev_midi_fill, dev_midi_exit }; struct slotops zomb_slotops = { zomb_onmove, zomb_onvol, zomb_fill, zomb_flush, zomb_eof, zomb_exit }; struct dev *dev_list = NULL; unsigned int dev_sndnum = 0; void dev_log(struct dev *d) { #ifdef DEBUG static char *pstates[] = { "cfg", "ini", "run" }; #endif log_puts("snd"); log_putu(d->num); #ifdef DEBUG if (log_level >= 3) { log_puts(" pst="); log_puts(pstates[d->pstate]); } #endif } void slot_log(struct slot *s) { #ifdef DEBUG static char *pstates[] = { "ini", "sta", "rdy", "run", "stp", "mid" }; #endif log_puts(s->name); log_putu(s->unit); #ifdef DEBUG if (log_level >= 3) { log_puts(" vol="); log_putu(s->vol); if (s->ops) { log_puts(",pst="); log_puts(pstates[s->pstate]); } } #endif } void zomb_onmove(void *arg) { } void zomb_onvol(void *arg) { } void zomb_fill(void *arg) { } void zomb_flush(void *arg) { } void zomb_eof(void *arg) { struct slot *s = arg; #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": zomb_eof\n"); } #endif s->ops = NULL; } void zomb_exit(void *arg) { #ifdef DEBUG struct slot *s = arg; if (log_level >= 3) { slot_log(s); log_puts(": zomb_exit\n"); } #endif } /* * send a quarter frame MTC message */ void dev_midi_qfr(struct dev *d, int delta) { unsigned char buf[2]; unsigned int data; int qfrlen; d->mtc.delta += delta * MTC_SEC; qfrlen = d->rate * (MTC_SEC / (4 * d->mtc.fps)); while (d->mtc.delta >= qfrlen) { switch (d->mtc.qfr) { case 0: data = d->mtc.fr & 0xf; break; case 1: data = d->mtc.fr >> 4; break; case 2: data = d->mtc.sec & 0xf; break; case 3: data = d->mtc.sec >> 4; break; case 4: data = d->mtc.min & 0xf; break; case 5: data = d->mtc.min >> 4; break; case 6: data = d->mtc.hr & 0xf; break; case 7: data = (d->mtc.hr >> 4) | (d->mtc.fps_id << 1); /* * tick messages are sent 2 frames ahead */ d->mtc.fr += 2; if (d->mtc.fr < d->mtc.fps) break; d->mtc.fr -= d->mtc.fps; d->mtc.sec++; if (d->mtc.sec < 60) break; d->mtc.sec = 0; d->mtc.min++; if (d->mtc.min < 60) break; d->mtc.min = 0; d->mtc.hr++; if (d->mtc.hr < 24) break; d->mtc.hr = 0; break; default: /* NOTREACHED */ data = 0; } buf[0] = 0xf1; buf[1] = (d->mtc.qfr << 4) | data; d->mtc.qfr++; d->mtc.qfr &= 7; midi_send(d->midi, buf, 2); d->mtc.delta -= qfrlen; } } /* * send a full frame MTC message */ void dev_midi_full(struct dev *d) { struct sysex x; unsigned int fps; d->mtc.delta = MTC_SEC * dev_getpos(d); if (d->rate % (30 * 4 * d->round) == 0) { d->mtc.fps_id = MTC_FPS_30; d->mtc.fps = 30; } else if (d->rate % (25 * 4 * d->round) == 0) { d->mtc.fps_id = MTC_FPS_25; d->mtc.fps = 25; } else { d->mtc.fps_id = MTC_FPS_24; d->mtc.fps = 24; } #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": mtc full frame at "); log_puti(d->mtc.delta); log_puts(", "); log_puti(d->mtc.fps); log_puts(" fps\n"); } #endif fps = d->mtc.fps; d->mtc.hr = (d->mtc.origin / (MTC_SEC * 3600)) % 24; d->mtc.min = (d->mtc.origin / (MTC_SEC * 60)) % 60; d->mtc.sec = (d->mtc.origin / (MTC_SEC)) % 60; d->mtc.fr = (d->mtc.origin / (MTC_SEC / fps)) % fps; x.start = SYSEX_START; x.type = SYSEX_TYPE_RT; x.dev = SYSEX_DEV_ANY; x.id0 = SYSEX_MTC; x.id1 = SYSEX_MTC_FULL; x.u.full.hr = d->mtc.hr | (d->mtc.fps_id << 5); x.u.full.min = d->mtc.min; x.u.full.sec = d->mtc.sec; x.u.full.fr = d->mtc.fr; x.u.full.end = SYSEX_END; d->mtc.qfr = 0; midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(full)); } /* * send a volume change MIDI message */ void dev_midi_vol(struct dev *d, struct slot *s) { unsigned char msg[3]; msg[0] = MIDI_CTL | (s - d->slot); msg[1] = MIDI_CTL_VOL; msg[2] = s->vol; midi_send(d->midi, msg, 3); } /* * send a master volume MIDI message */ void dev_midi_master(struct dev *d) { struct sysex x; memset(&x, 0, sizeof(struct sysex)); x.start = SYSEX_START; x.type = SYSEX_TYPE_RT; x.dev = SYSEX_DEV_ANY; x.id0 = SYSEX_CONTROL; x.id1 = SYSEX_MASTER; x.u.master.fine = 0; x.u.master.coarse = d->master; x.u.master.end = SYSEX_END; midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(master)); } /* * send a sndiod-specific slot description MIDI message */ void dev_midi_slotdesc(struct dev *d, struct slot *s) { struct sysex x; memset(&x, 0, sizeof(struct sysex)); x.start = SYSEX_START; x.type = SYSEX_TYPE_EDU; x.dev = SYSEX_DEV_ANY; x.id0 = SYSEX_AUCAT; x.id1 = SYSEX_AUCAT_SLOTDESC; if (*s->name != '\0') { snprintf((char *)x.u.slotdesc.name, SYSEX_NAMELEN, "%s%u", s->name, s->unit); } x.u.slotdesc.chan = s - d->slot; x.u.slotdesc.end = SYSEX_END; midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(slotdesc)); } void dev_midi_dump(struct dev *d) { struct sysex x; struct slot *s; int i; dev_midi_master(d); for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) { dev_midi_slotdesc(d, s); dev_midi_vol(d, s); } x.start = SYSEX_START; x.type = SYSEX_TYPE_EDU; x.dev = SYSEX_DEV_ANY; x.id0 = SYSEX_AUCAT; x.id1 = SYSEX_AUCAT_DUMPEND; x.u.dumpend.end = SYSEX_END; midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(dumpend)); } void dev_midi_imsg(void *arg, unsigned char *msg, int len) { #ifdef DEBUG struct dev *d = arg; dev_log(d); log_puts(": can't receive midi messages\n"); panic(); #endif } void dev_midi_omsg(void *arg, unsigned char *msg, int len) { struct dev *d = arg; struct sysex *x; unsigned int fps, chan; if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) { chan = msg[0] & MIDI_CHANMASK; if (chan >= DEV_NSLOT) return; slot_setvol(d->slot + chan, msg[2]); return; } x = (struct sysex *)msg; if (x->start != SYSEX_START) return; if (len < SYSEX_SIZE(empty)) return; switch (x->type) { case SYSEX_TYPE_RT: if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) { if (len == SYSEX_SIZE(master)) dev_master(d, x->u.master.coarse); return; } if (x->id0 != SYSEX_MMC) return; switch (x->id1) { case SYSEX_MMC_STOP: if (len != SYSEX_SIZE(stop)) return; if (log_level >= 2) { dev_log(d); log_puts(": mmc stop\n"); } dev_mmcstop(d); break; case SYSEX_MMC_START: if (len != SYSEX_SIZE(start)) return; if (log_level >= 2) { dev_log(d); log_puts(": mmc start\n"); } dev_mmcstart(d); break; case SYSEX_MMC_LOC: if (len != SYSEX_SIZE(loc) || x->u.loc.len != SYSEX_MMC_LOC_LEN || x->u.loc.cmd != SYSEX_MMC_LOC_CMD) return; switch (x->u.loc.hr >> 5) { case MTC_FPS_24: fps = 24; break; case MTC_FPS_25: fps = 25; break; case MTC_FPS_30: fps = 30; break; default: dev_mmcstop(d); return; } dev_mmcloc(d, (x->u.loc.hr & 0x1f) * 3600 * MTC_SEC + x->u.loc.min * 60 * MTC_SEC + x->u.loc.sec * MTC_SEC + x->u.loc.fr * (MTC_SEC / fps) + x->u.loc.cent * (MTC_SEC / 100 / fps)); break; } break; case SYSEX_TYPE_EDU: if (x->id0 != SYSEX_AUCAT || x->id1 != SYSEX_AUCAT_DUMPREQ) return; if (len != SYSEX_SIZE(dumpreq)) return; dev_midi_dump(d); break; } } void dev_midi_fill(void *arg, int count) { /* nothing to do */ } void dev_midi_exit(void *arg) { struct dev *d = arg; if (log_level >= 1) { dev_log(d); log_puts(": midi end point died\n"); } if (d->pstate != DEV_CFG) dev_close(d); } int slot_skip(struct slot *s) { unsigned char *data = (unsigned char *)0xdeadbeef; /* please gcc */ int max, count; max = s->skip; while (s->skip > 0) { if (s->pstate != SLOT_STOP && (s->mode & MODE_RECMASK)) { data = abuf_wgetblk(&s->sub.buf, &count); if (count < s->round * s->sub.bpf) break; } if (s->mode & MODE_PLAY) { if (s->mix.buf.used < s->round * s->mix.bpf) break; } #ifdef DEBUG if (log_level >= 4) { slot_log(s); log_puts(": skipped a cycle\n"); } #endif if (s->pstate != SLOT_STOP && (s->mode & MODE_RECMASK)) { if (s->sub.encbuf) enc_sil_do(&s->sub.enc, data, s->round); else memset(data, 0, s->round * s->sub.bpf); abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf); } if (s->mode & MODE_PLAY) { abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf); } s->skip--; } return max - s->skip; } /* * Mix the slot input block over the output block */ void dev_mix_badd(struct dev *d, struct slot *s) { adata_t *idata, *odata, *in; int icount, i, offs, vol, nch; odata = DEV_PBUF(d); idata = (adata_t *)abuf_rgetblk(&s->mix.buf, &icount); #ifdef DEBUG if (icount < s->round * s->mix.bpf) { slot_log(s); log_puts(": not enough data to mix ("); log_putu(icount); log_puts("bytes)\n"); panic(); } #endif /* * Apply the following processing chain: * * dec -> resamp-> cmap * * where the first two are optional. */ in = idata; if (s->mix.decbuf) { dec_do(&s->mix.dec, (void *)in, s->mix.decbuf, s->round); in = s->mix.decbuf; } if (s->mix.resampbuf) { resamp_do(&s->mix.resamp, in, s->mix.resampbuf, s->round); in = s->mix.resampbuf; } nch = s->mix.cmap.nch; vol = ADATA_MUL(s->mix.weight, s->mix.vol) / s->mix.join; cmap_add(&s->mix.cmap, in, odata, vol, d->round); offs = 0; for (i = s->mix.join - 1; i > 0; i--) { offs += nch; cmap_add(&s->mix.cmap, in + offs, odata, vol, d->round); } offs = 0; for (i = s->mix.expand - 1; i > 0; i--) { offs += nch; cmap_add(&s->mix.cmap, in, odata + offs, vol, d->round); } abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf); } /* * Normalize input levels. */ void dev_mix_adjvol(struct dev *d) { unsigned int n; struct slot *i, *j; int jcmax, icmax, weight; for (i = d->slot_list; i != NULL; i = i->next) { if (!(i->mode & MODE_PLAY)) continue; icmax = i->opt->pmin + i->mix.nch - 1; weight = ADATA_UNIT; if (d->autovol) { /* * count the number of inputs that have * overlapping channel sets */ n = 0; for (j = d->slot_list; j != NULL; j = j->next) { if (!(j->mode & MODE_PLAY)) continue; jcmax = j->opt->pmin + j->mix.nch - 1; if (i->opt->pmin <= jcmax && icmax >= j->opt->pmin) n++; } weight /= n; } if (weight > i->opt->maxweight) weight = i->opt->maxweight; i->mix.weight = ADATA_MUL(weight, MIDI_TO_ADATA(d->master)); #ifdef DEBUG if (log_level >= 3) { slot_log(i); log_puts(": set weight: "); log_puti(i->mix.weight); log_puts("/"); log_puti(i->opt->maxweight); log_puts("\n"); } #endif } } /* * Copy data from slot to device */ void dev_sub_bcopy(struct dev *d, struct slot *s) { adata_t *idata, *enc_out, *resamp_out, *cmap_out; void *odata; int ocount, moffs; int i, vol, offs, nch; if (s->mode & MODE_MON) { moffs = d->poffs + d->round; if (moffs == d->psize) moffs = 0; idata = d->pbuf + moffs * d->pchan; } else idata = d->rbuf; odata = (adata_t *)abuf_wgetblk(&s->sub.buf, &ocount); #ifdef DEBUG if (ocount < s->round * s->sub.bpf) { log_puts("dev_sub_bcopy: not enough space\n"); panic(); } #endif /* * Apply the following processing chain: * * cmap -> resamp -> enc * * where the last two are optional. */ enc_out = odata; resamp_out = s->sub.encbuf ? s->sub.encbuf : enc_out; cmap_out = s->sub.resampbuf ? s->sub.resampbuf : resamp_out; nch = s->sub.cmap.nch; vol = ADATA_UNIT / s->sub.join; cmap_copy(&s->sub.cmap, idata, cmap_out, vol, d->round); offs = 0; for (i = s->sub.join - 1; i > 0; i--) { offs += nch; cmap_add(&s->sub.cmap, idata + offs, cmap_out, vol, d->round); } offs = 0; for (i = s->sub.expand - 1; i > 0; i--) { offs += nch; cmap_copy(&s->sub.cmap, idata, cmap_out + offs, vol, d->round); } if (s->sub.resampbuf) { resamp_do(&s->sub.resamp, s->sub.resampbuf, resamp_out, d->round); } if (s->sub.encbuf) enc_do(&s->sub.enc, s->sub.encbuf, (void *)enc_out, s->round); abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf); } /* * run a one block cycle: consume one recorded block from * rbuf and produce one play block in pbuf */ void dev_cycle(struct dev *d) { struct slot *s, **ps; unsigned char *base; int nsamp; /* * check if the device is actually used. If it isn't, * then close it */ if (d->slot_list == NULL && d->tstate != MMC_RUN) { if (log_level >= 2) { dev_log(d); log_puts(": device stopped\n"); } dev_sio_stop(d); d->pstate = DEV_INIT; if (d->refcnt == 0) dev_close(d); return; } if (d->prime > 0) { #ifdef DEBUG if (log_level >= 4) { dev_log(d); log_puts(": empty cycle, prime = "); log_putu(d->prime); log_puts("\n"); } #endif base = (unsigned char *)DEV_PBUF(d); nsamp = d->round * d->pchan; memset(base, 0, nsamp * sizeof(adata_t)); if (d->encbuf) { enc_do(&d->enc, (unsigned char *)DEV_PBUF(d), d->encbuf, d->round); } d->prime -= d->round; return; } d->delta -= d->round; #ifdef DEBUG if (log_level >= 4) { dev_log(d); log_puts(": full cycle: delta = "); log_puti(d->delta); if (d->mode & MODE_PLAY) { log_puts(", poffs = "); log_puti(d->poffs); } log_puts("\n"); } #endif if (d->mode & MODE_PLAY) { base = (unsigned char *)DEV_PBUF(d); nsamp = d->round * d->pchan; memset(base, 0, nsamp * sizeof(adata_t)); } if ((d->mode & MODE_REC) && d->decbuf) dec_do(&d->dec, d->decbuf, (unsigned char *)d->rbuf, d->round); ps = &d->slot_list; while ((s = *ps) != NULL) { #ifdef DEBUG if (log_level >= 4) { slot_log(s); log_puts(": running"); log_puts(", skip = "); log_puti(s->skip); log_puts("\n"); } #endif /* * skip cycles for XRUN_SYNC correction */ slot_skip(s); if (s->skip < 0) { s->skip++; ps = &s->next; continue; } #ifdef DEBUG if (s->pstate == SLOT_STOP && !(s->mode & MODE_PLAY)) { slot_log(s); log_puts(": rec-only slots can't be drained\n"); panic(); } #endif /* * check if stopped stream finished draining */ if (s->pstate == SLOT_STOP && s->mix.buf.used < s->round * s->mix.bpf) { /* * partial blocks are zero-filled by socket * layer, so s->mix.buf.used == 0 and we can * destroy the buffer */ *ps = s->next; s->pstate = SLOT_INIT; s->ops->eof(s->arg); slot_freebufs(s); dev_mix_adjvol(d); #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": drained\n"); } #endif continue; } /* * check for xruns */ if (((s->mode & MODE_PLAY) && s->mix.buf.used < s->round * s->mix.bpf) || ((s->mode & MODE_RECMASK) && s->sub.buf.len - s->sub.buf.used < s->round * s->sub.bpf)) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": xrun, pause cycle\n"); } #endif if (s->xrun == XRUN_IGNORE) { s->delta -= s->round; ps = &s->next; } else if (s->xrun == XRUN_SYNC) { s->skip++; ps = &s->next; } else if (s->xrun == XRUN_ERROR) { s->ops->exit(s->arg); *ps = s->next; } else { #ifdef DEBUG slot_log(s); log_puts(": bad xrun mode\n"); panic(); #endif } continue; } if ((s->mode & MODE_RECMASK) && !(s->pstate == SLOT_STOP)) { if (s->sub.prime == 0) { dev_sub_bcopy(d, s); s->ops->flush(s->arg); } else { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": prime = "); log_puti(s->sub.prime); log_puts("\n"); } #endif s->sub.prime--; } } if (s->mode & MODE_PLAY) { dev_mix_badd(d, s); if (s->pstate != SLOT_STOP) s->ops->fill(s->arg); } ps = &s->next; } if ((d->mode & MODE_PLAY) && d->encbuf) { enc_do(&d->enc, (unsigned char *)DEV_PBUF(d), d->encbuf, d->round); } } /* * called at every clock tick by the device */ void dev_onmove(struct dev *d, int delta) { long long pos; struct slot *s, *snext; d->delta += delta; for (s = d->slot_list; s != NULL; s = snext) { /* * s->ops->onmove() may remove the slot */ snext = s->next; pos = (long long)delta * s->round + s->delta_rem; s->delta_rem = pos % d->round; s->delta += pos / (int)d->round; if (s->delta >= 0) s->ops->onmove(s->arg); } if (d->tstate == MMC_RUN) dev_midi_qfr(d, delta); } void dev_master(struct dev *d, unsigned int master) { if (log_level >= 2) { dev_log(d); log_puts(": master volume set to "); log_putu(master); log_puts("\n"); } d->master = master; if (d->mode & MODE_PLAY) dev_mix_adjvol(d); } /* * return the latency that a stream would have if it's attached */ int dev_getpos(struct dev *d) { return (d->mode & MODE_PLAY) ? -d->bufsz : 0; } /* * Create a sndio device */ struct dev * dev_new(char *path, struct aparams *par, unsigned int mode, unsigned int bufsz, unsigned int round, unsigned int rate, unsigned int hold, unsigned int autovol) { struct dev *d; unsigned int i; if (dev_sndnum == DEV_NMAX) { if (log_level >= 1) log_puts("too many devices\n"); return NULL; } d = xmalloc(sizeof(struct dev)); d->path = xstrdup(path); d->num = dev_sndnum++; d->opt_list = NULL; /* * XXX: below, we allocate a midi input buffer, since we don't * receive raw midi data, so no need to allocate a input * ibuf. Possibly set imsg & fill callbacks to NULL and * use this to in midi_new() to check if buffers need to be * allocated */ d->midi = midi_new(&dev_midiops, d, MODE_MIDIIN | MODE_MIDIOUT); midi_tag(d->midi, d->num); d->reqpar = *par; d->reqmode = mode; d->reqpchan = d->reqrchan = 0; d->reqbufsz = bufsz; d->reqround = round; d->reqrate = rate; d->hold = hold; d->autovol = autovol; d->refcnt = 0; d->pstate = DEV_CFG; d->serial = 0; for (i = 0; i < DEV_NSLOT; i++) { d->slot[i].unit = i; d->slot[i].ops = NULL; d->slot[i].vol = MIDI_MAXCTL; d->slot[i].serial = d->serial++; strlcpy(d->slot[i].name, "prog", SLOT_NAMEMAX); } d->slot_list = NULL; d->master = MIDI_MAXCTL; d->mtc.origin = 0; d->tstate = MMC_STOP; d->next = dev_list; dev_list = d; return d; } /* * adjust device parameters and mode */ void dev_adjpar(struct dev *d, int mode, int pmax, int rmax) { d->reqmode |= mode & MODE_AUDIOMASK; if (mode & MODE_PLAY) { if (d->reqpchan < pmax + 1) d->reqpchan = pmax + 1; } if (mode & MODE_REC) { if (d->reqrchan < rmax + 1) d->reqrchan = rmax + 1; } } /* * Open the device with the dev_reqxxx capabilities. Setup a mixer, demuxer, * monitor, midi control, and any necessary conversions. */ int dev_open(struct dev *d) { d->mode = d->reqmode; d->round = d->reqround; d->bufsz = d->reqbufsz; d->rate = d->reqrate; d->pchan = d->reqpchan; d->rchan = d->reqrchan; d->par = d->reqpar; if (d->pchan == 0) d->pchan = 2; if (d->rchan == 0) d->rchan = 2; if (!dev_sio_open(d)) { if (log_level >= 1) { dev_log(d); log_puts(": "); log_puts(d->path); log_puts(": failed to open audio device\n"); } return 0; } if (d->mode & MODE_REC) { /* * Create device <-> demuxer buffer */ d->rbuf = xmalloc(d->round * d->rchan * sizeof(adata_t)); /* * Insert a converter, if needed. */ if (!aparams_native(&d->par)) { dec_init(&d->dec, &d->par, d->rchan); d->decbuf = xmalloc(d->round * d->rchan * d->par.bps); } else d->decbuf = NULL; } if (d->mode & MODE_PLAY) { /* * Create device <-> mixer buffer */ d->poffs = 0; d->psize = d->bufsz + d->round; d->pbuf = xmalloc(d->psize * d->pchan * sizeof(adata_t)); d->mode |= MODE_MON; /* * Append a converter, if needed. */ if (!aparams_native(&d->par)) { enc_init(&d->enc, &d->par, d->pchan); d->encbuf = xmalloc(d->round * d->pchan * d->par.bps); } else d->encbuf = NULL; } d->pstate = DEV_INIT; if (log_level >= 2) { dev_log(d); log_puts(": "); log_putu(d->rate); log_puts("Hz, "); aparams_log(&d->par); if (d->mode & MODE_PLAY) { log_puts(", play 0:"); log_puti(d->pchan - 1); } if (d->mode & MODE_REC) { log_puts(", rec 0:"); log_puti(d->rchan - 1); } log_puts(", "); log_putu(d->bufsz / d->round); log_puts(" blocks of "); log_putu(d->round); log_puts(" frames\n"); } return 1; } /* * force the device to go in DEV_CFG state, the caller is supposed to * ensure buffers are drained */ void dev_close(struct dev *d) { int i; struct slot *s; #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": closing\n"); } #endif d->pstate = DEV_CFG; for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) { if (s->ops) s->ops->exit(s->arg); s->ops = NULL; } d->slot_list = NULL; dev_sio_close(d); if (d->mode & MODE_PLAY) { if (d->encbuf != NULL) xfree(d->encbuf); xfree(d->pbuf); } if (d->mode & MODE_REC) { if (d->decbuf != NULL) xfree(d->decbuf); xfree(d->rbuf); } } int dev_ref(struct dev *d) { #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": device requested\n"); } #endif if (d->pstate == DEV_CFG && !dev_open(d)) return 0; d->refcnt++; return 1; } void dev_unref(struct dev *d) { #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": device released\n"); } #endif d->refcnt--; if (d->refcnt == 0 && d->pstate == DEV_INIT) dev_close(d); } /* * initialize the device with the current parameters */ int dev_init(struct dev *d) { if ((d->reqmode & MODE_AUDIOMASK) == 0) { #ifdef DEBUG dev_log(d); log_puts(": has no streams\n"); #endif return 0; } if (d->hold && !dev_ref(d)) return 0; return 1; } /* * Unless the device is already in process of closing, request it to close */ void dev_done(struct dev *d) { #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": draining\n"); } #endif if (d->tstate != MMC_STOP) dev_mmcstop(d); if (d->hold) dev_unref(d); } struct dev * dev_bynum(int num) { struct dev *d; for (d = dev_list; d != NULL; d = d->next) { if (d->num == num) return d; } return NULL; } /* * Free the device */ void dev_del(struct dev *d) { struct dev **p; #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": deleting\n"); } #endif while (d->opt_list != NULL) opt_del(d, d->opt_list); if (d->pstate != DEV_CFG) dev_close(d); for (p = &dev_list; *p != d; p = &(*p)->next) { #ifdef DEBUG if (*p == NULL) { dev_log(d); log_puts(": device to delete not on the list\n"); panic(); } #endif } midi_del(d->midi); *p = d->next; xfree(d->path); xfree(d); } unsigned int dev_roundof(struct dev *d, unsigned int newrate) { return (d->round * newrate + d->rate / 2) / d->rate; } /* * If the device is paused, then resume it. */ void dev_wakeup(struct dev *d) { if (d->pstate == DEV_INIT) { if (log_level >= 2) { dev_log(d); log_puts(": device started\n"); } if (d->mode & MODE_PLAY) { d->prime = d->bufsz; } else { d->prime = 0; } d->poffs = 0; /* * empty cycles don't increment delta, so it's ok to * start at 0 **/ d->delta = 0; d->pstate = DEV_RUN; dev_sio_start(d); } } /* * check that all clients controlled by MMC are ready to start, if so, * attach them all at the same position */ void dev_sync_attach(struct dev *d) { int i; struct slot *s; if (d->tstate != MMC_START) { if (log_level >= 2) { dev_log(d); log_puts(": not started by mmc yet, waiting...\n"); } return; } for (i = 0; i < DEV_NSLOT; i++) { s = d->slot + i; if (!s->ops || !s->opt->mmc) continue; if (s->pstate != SLOT_READY) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": not ready, start delayed\n"); } #endif return; } } if (!dev_ref(d)) return; for (i = 0; i < DEV_NSLOT; i++) { s = d->slot + i; if (!s->ops || !s->opt->mmc) continue; slot_attach(s); s->pstate = SLOT_RUN; } d->tstate = MMC_RUN; dev_midi_full(d); dev_wakeup(d); } /* * start all slots simultaneously */ void dev_mmcstart(struct dev *d) { if (d->tstate == MMC_STOP) { d->tstate = MMC_START; dev_sync_attach(d); #ifdef DEBUG } else { if (log_level >= 3) { dev_log(d); log_puts(": ignoring mmc start\n"); } #endif } } /* * stop all slots simultaneously */ void dev_mmcstop(struct dev *d) { switch (d->tstate) { case MMC_START: d->tstate = MMC_STOP; return; case MMC_RUN: d->tstate = MMC_STOP; dev_unref(d); break; default: #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": ignored mmc stop\n"); } #endif return; } } /* * relocate all slots simultaneously */ void dev_mmcloc(struct dev *d, unsigned int origin) { if (log_level >= 2) { dev_log(d); log_puts(": relocated to "); log_putu(origin); log_puts("\n"); } if (d->tstate == MMC_RUN) dev_mmcstop(d); d->mtc.origin = origin; if (d->tstate == MMC_RUN) dev_mmcstart(d); } /* * allocate buffers & conversion chain */ void slot_allocbufs(struct slot *s) { unsigned int dev_nch; struct dev *d = s->dev; if (s->mode & MODE_PLAY) { s->mix.bpf = s->par.bps * s->mix.nch; abuf_init(&s->mix.buf, s->appbufsz * s->mix.bpf); dev_nch = s->opt->pmax - s->opt->pmin + 1; s->mix.decbuf = NULL; s->mix.resampbuf = NULL; s->mix.join = 1; s->mix.expand = 1; if (s->opt->dup) { if (dev_nch > s->mix.nch) s->mix.expand = dev_nch / s->mix.nch; else if (dev_nch < s->mix.nch) s->mix.join = s->mix.nch / dev_nch; } cmap_init(&s->mix.cmap, s->opt->pmin, s->opt->pmin + s->mix.nch - 1, s->opt->pmin, s->opt->pmin + s->mix.nch - 1, 0, d->pchan - 1, s->opt->pmin, s->opt->pmax); if (!aparams_native(&s->par)) { dec_init(&s->mix.dec, &s->par, s->mix.nch); s->mix.decbuf = xmalloc(s->round * s->mix.nch * sizeof(adata_t)); } if (s->rate != d->rate) { resamp_init(&s->mix.resamp, s->round, d->round, s->mix.nch); s->mix.resampbuf = xmalloc(d->round * s->mix.nch * sizeof(adata_t)); } } if (s->mode & MODE_RECMASK) { s->sub.bpf = s->par.bps * s->sub.nch; abuf_init(&s->sub.buf, s->appbufsz * s->sub.bpf); dev_nch = s->opt->rmax - s->opt->rmin + 1; s->sub.encbuf = NULL; s->sub.resampbuf = NULL; s->sub.join = 1; s->sub.expand = 1; if (s->opt->dup) { if (dev_nch > s->sub.nch) s->sub.join = dev_nch / s->sub.nch; else if (dev_nch < s->sub.nch) s->sub.expand = s->sub.nch / dev_nch; } cmap_init(&s->sub.cmap, 0, ((s->mode & MODE_MON) ? d->pchan : d->rchan) - 1, s->opt->rmin, s->opt->rmax, s->opt->rmin, s->opt->rmin + s->sub.nch - 1, s->opt->rmin, s->opt->rmin + s->sub.nch - 1); if (s->rate != d->rate) { resamp_init(&s->sub.resamp, d->round, s->round, s->sub.nch); s->sub.resampbuf = xmalloc(d->round * s->sub.nch * sizeof(adata_t)); } if (!aparams_native(&s->par)) { enc_init(&s->sub.enc, &s->par, s->sub.nch); s->sub.encbuf = xmalloc(s->round * s->sub.nch * sizeof(adata_t)); } /* * cmap_copy() doesn't write samples in all channels, * for instance when mono->stereo conversion is * disabled. So we have to prefill cmap_copy() output * with silence. */ if (s->sub.resampbuf) { memset(s->sub.resampbuf, 0, d->round * s->sub.nch * sizeof(adata_t)); } else if (s->sub.encbuf) { memset(s->sub.encbuf, 0, s->round * s->sub.nch * sizeof(adata_t)); } else { memset(s->sub.buf.data, 0, s->appbufsz * s->sub.nch * sizeof(adata_t)); } } #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": allocated "); log_putu(s->appbufsz); log_puts("/"); log_putu(SLOT_BUFSZ(s)); log_puts(" fr buffers\n"); } #endif } /* * free buffers & conversion chain */ void slot_freebufs(struct slot *s) { if (s->mode & MODE_RECMASK) { abuf_done(&s->sub.buf); if (s->sub.encbuf) xfree(s->sub.encbuf); if (s->sub.resampbuf) xfree(s->sub.resampbuf); } if (s->mode & MODE_PLAY) { abuf_done(&s->mix.buf); if (s->mix.decbuf) xfree(s->mix.decbuf); if (s->mix.resampbuf) xfree(s->mix.resampbuf); } } /* * allocate a new slot and register the given call-backs */ struct slot * slot_new(struct dev *d, struct opt *opt, char *who, struct slotops *ops, void *arg, int mode) { char *p; char name[SLOT_NAMEMAX]; unsigned int i, unit, umap = 0; unsigned int ser, bestser, bestidx; struct slot *s; /* * create a ``valid'' control name (lowcase, remove [^a-z], truncate) */ for (i = 0, p = who; ; p++) { if (i == SLOT_NAMEMAX - 1 || *p == '\0') { name[i] = '\0'; break; } else if (*p >= 'A' && *p <= 'Z') { name[i++] = *p + 'a' - 'A'; } else if (*p >= 'a' && *p <= 'z') name[i++] = *p; } if (i == 0) strlcpy(name, "noname", SLOT_NAMEMAX); /* * find the first unused "unit" number for this name */ for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) { if (s->ops == NULL) continue; if (strcmp(s->name, name) == 0) umap |= (1 << s->unit); } for (unit = 0; ; unit++) { if ((umap & (1 << unit)) == 0) break; } /* * find a free controller slot with the same name/unit */ for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) { if (s->ops == NULL && strcmp(s->name, name) == 0 && s->unit == unit) { #ifdef DEBUG if (log_level >= 3) { log_puts(name); log_putu(unit); log_puts(": reused\n"); } #endif goto found; } } /* * couldn't find a matching slot, pick oldest free slot * and set its name/unit */ bestser = 0; bestidx = DEV_NSLOT; for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) { if (s->ops != NULL) continue; ser = d->serial - s->serial; if (ser > bestser) { bestser = ser; bestidx = i; } } if (bestidx == DEV_NSLOT) { if (log_level >= 1) { log_puts(name); log_putu(unit); log_puts(": out of sub-device slots\n"); } return NULL; } s = d->slot + bestidx; if (s->name[0] != '\0') s->vol = MIDI_MAXCTL; strlcpy(s->name, name, SLOT_NAMEMAX); s->serial = d->serial++; s->unit = unit; #ifdef DEBUG if (log_level >= 3) { log_puts(name); log_putu(unit); log_puts(": overwritten slot "); log_putu(bestidx); log_puts("\n"); } #endif found: if ((mode & MODE_REC) && (opt->mode & MODE_MON)) { mode |= MODE_MON; mode &= ~MODE_REC; } if ((mode & opt->mode) != mode) { if (log_level >= 1) { slot_log(s); log_puts(": requested mode not allowed\n"); } return 0; } if (!dev_ref(d)) return NULL; if ((mode & d->mode) != mode) { if (log_level >= 1) { slot_log(s); log_puts(": requested mode not supported\n"); } dev_unref(d); return 0; } s->dev = d; s->opt = opt; s->ops = ops; s->arg = arg; s->pstate = SLOT_INIT; s->mode = mode; aparams_init(&s->par); if (s->mode & MODE_PLAY) s->mix.nch = s->opt->pmax - s->opt->pmin + 1; if (s->mode & MODE_RECMASK) s->sub.nch = s->opt->rmax - s->opt->rmin + 1; s->xrun = s->opt->mmc ? XRUN_SYNC : XRUN_IGNORE; s->appbufsz = d->bufsz; s->round = d->round; s->rate = d->rate; dev_midi_slotdesc(d, s); dev_midi_vol(d, s); #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": using "); dev_log(d); log_puts("."); log_puts(opt->name); log_puts(", mode = "); log_putx(mode); log_puts("\n"); } #endif return s; } /* * release the given slot */ void slot_del(struct slot *s) { s->arg = s; s->ops = &zomb_slotops; switch (s->pstate) { case SLOT_INIT: s->ops = NULL; break; case SLOT_START: case SLOT_READY: case SLOT_RUN: slot_stop(s); /* PASSTHROUGH */ case SLOT_STOP: break; } dev_unref(s->dev); s->dev = NULL; } /* * change the slot play volume; called either by the slot or by MIDI */ void slot_setvol(struct slot *s, unsigned int vol) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": setting volume "); log_putu(vol); log_puts("\n"); } #endif s->vol = vol; s->mix.vol = MIDI_TO_ADATA(s->vol); } /* * attach the slot to the device (ie start playing & recording */ void slot_attach(struct slot *s) { struct dev *d = s->dev; long long pos; int startpos; /* * start the device if not started */ dev_wakeup(d); /* * get the current position, the origin is when the first sample * played and/or recorded */ startpos = dev_getpos(d) * (int)s->round / (int)d->round; /* * adjust initial clock */ pos = (long long)d->delta * s->round; s->delta = startpos + pos / (int)d->round; s->delta_rem = pos % d->round; #ifdef DEBUG if (log_level >= 2) { slot_log(s); log_puts(": attached at "); log_puti(startpos); log_puts(", delta = "); log_puti(d->delta); log_puts("\n"); } #endif /* * We dont check whether the device is dying, * because dev_xxx() functions are supposed to * work (i.e., not to crash) */ #ifdef DEBUG if ((s->mode & d->mode) != s->mode) { slot_log(s); log_puts(": mode beyond device mode, not attaching\n"); panic(); } #endif s->next = d->slot_list; d->slot_list = s; if (s->mode & MODE_PLAY) { s->mix.vol = MIDI_TO_ADATA(s->vol); dev_mix_adjvol(d); } } /* * if MMC is enabled, and try to attach all slots synchronously, else * simply attach the slot */ void slot_ready(struct slot *s) { /* * device may be disconnected, and if so we're called from * slot->ops->exit() on a closed device */ if (s->dev->pstate == DEV_CFG) return; if (!s->opt->mmc) { slot_attach(s); s->pstate = SLOT_RUN; } else dev_sync_attach(s->dev); } /* * setup buffers & conversion layers, prepare the slot to receive data * (for playback) or start (recording). */ void slot_start(struct slot *s) { #ifdef DEBUG if (s->pstate != SLOT_INIT) { slot_log(s); log_puts(": slot_start: wrong state\n"); panic(); } if (s->mode & MODE_PLAY) { if (log_level >= 3) { slot_log(s); log_puts(": playing "); aparams_log(&s->par); log_puts(" -> "); aparams_log(&s->dev->par); log_puts("\n"); } } if (s->mode & MODE_RECMASK) { if (log_level >= 3) { slot_log(s); log_puts(": recording "); aparams_log(&s->par); log_puts(" <- "); aparams_log(&s->dev->par); log_puts("\n"); } } #endif slot_allocbufs(s); if (s->mode & MODE_RECMASK) { /* * N-th recorded block is the N-th played block */ s->sub.prime = -dev_getpos(s->dev) / s->dev->round; } s->skip = 0; if (s->mode & MODE_PLAY) { s->pstate = SLOT_START; } else { s->pstate = SLOT_READY; slot_ready(s); } } /* * stop playback and recording, and free conversion layers */ void slot_detach(struct slot *s) { struct slot **ps; #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": detaching\n"); } #endif for (ps = &s->dev->slot_list; *ps != s; ps = &(*ps)->next) { #ifdef DEBUG if (*ps == NULL) { slot_log(s); log_puts(": can't detach, not on list\n"); panic(); } #endif } *ps = s->next; if (s->mode & MODE_PLAY) dev_mix_adjvol(s->dev); } /* * put the slot in stopping state (draining play buffers) or * stop & detach if no data to drain. */ void slot_stop(struct slot *s) { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": stopping\n"); } #endif if (s->pstate == SLOT_START) { /* * If in rec-only mode, we're already in the READY or * RUN states. We're here because the play buffer was * not full enough, try to start so it's drained. */ s->pstate = SLOT_READY; slot_ready(s); } if (s->pstate == SLOT_RUN) { if (s->mode & MODE_PLAY) { /* * Don't detach, dev_cycle() will do it for us * when the buffer is drained. */ s->pstate = SLOT_STOP; return; } slot_detach(s); } else { #ifdef DEBUG if (log_level >= 3) { slot_log(s); log_puts(": not drained (blocked by mmc)\n"); } #endif } s->pstate = SLOT_INIT; s->ops->eof(s->arg); slot_freebufs(s); } void slot_skip_update(struct slot *s) { int skip; skip = slot_skip(s); while (skip > 0) { #ifdef DEBUG if (log_level >= 4) { slot_log(s); log_puts(": catching skipped block\n"); } #endif if (s->mode & MODE_RECMASK) s->ops->flush(s->arg); if (s->mode & MODE_PLAY) s->ops->fill(s->arg); skip--; } } /* * notify the slot that we just wrote in the play buffer, must be called * after each write */ void slot_write(struct slot *s) { if (s->pstate == SLOT_START && s->mix.buf.used == s->mix.buf.len) { #ifdef DEBUG if (log_level >= 4) { slot_log(s); log_puts(": switching to READY state\n"); } #endif s->pstate = SLOT_READY; slot_ready(s); } slot_skip_update(s); } /* * notify the slot that we freed some space in the rec buffer */ void slot_read(struct slot *s) { slot_skip_update(s); } sndio-1.5.0/sndiod/dev.h010066400017510001751000000170431332662053300135610ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 "abuf.h" #include "dsp.h" #include "siofile.h" /* * audio stream state structure */ struct slotops { void (*onmove)(void *); /* clock tick */ void (*onvol)(void *); /* tell client vol changed */ void (*fill)(void *); /* request to fill a play block */ void (*flush)(void *); /* request to flush a rec block */ void (*eof)(void *); /* notify that play drained */ void (*exit)(void *); /* delete client */ }; struct slot { struct slotops *ops; /* client callbacks */ struct slot *next; /* next on the play list */ struct dev *dev; /* device this belongs to */ struct opt *opt; /* config used */ void *arg; /* user data for callbacks */ struct aparams par; /* socket side params */ struct { int weight; /* dynamic range */ unsigned int vol; /* volume within the vol */ struct abuf buf; /* socket side buffer */ int bpf; /* byte per frame */ int nch; /* number of play chans */ struct cmap cmap; /* channel mapper state */ struct resamp resamp; /* resampler state */ struct conv dec; /* format decoder params */ int join; /* channel join factor */ int expand; /* channel expand factor */ void *resampbuf, *decbuf; /* tmp buffers */ } mix; struct { struct abuf buf; /* socket side buffer */ int prime; /* initial cycles to skip */ int bpf; /* byte per frame */ int nch; /* number of rec chans */ struct cmap cmap; /* channel mapper state */ struct resamp resamp; /* buffer for resampling */ struct conv enc; /* buffer for encoding */ int join; /* channel join factor */ int expand; /* channel expand factor */ void *resampbuf, *encbuf; /* tmp buffers */ } sub; int xrun; /* underrun policy */ int skip; /* cycles to skip (for xrun) */ #define SLOT_BUFSZ(s) \ ((s)->appbufsz + (s)->dev->bufsz / (s)->dev->round * (s)->round) int appbufsz; /* slot-side buffer size */ int round; /* slot-side block size */ int rate; /* slot-side sample rate */ int delta; /* pending clock ticks */ int delta_rem; /* remainder for delta */ int mode; /* MODE_{PLAY,REC} */ #define SLOT_INIT 0 /* not trying to do anything */ #define SLOT_START 1 /* buffer allocated */ #define SLOT_READY 2 /* buffer filled enough */ #define SLOT_RUN 3 /* buffer attached to device */ #define SLOT_STOP 4 /* draining */ int pstate; #define SLOT_NAMEMAX 8 char name[SLOT_NAMEMAX]; /* name matching [a-z]+ */ unsigned int unit; /* instance of name */ unsigned int serial; /* global unique number */ unsigned int vol; /* current (midi) volume */ }; struct opt { struct opt *next; #define OPT_NAMEMAX 11 char name[OPT_NAMEMAX + 1]; int maxweight; /* max dynamic range for clients */ int pmin, pmax; /* play channels */ int rmin, rmax; /* recording channels */ int mmc; /* true if MMC control enabled */ int dup; /* true if join/expand enabled */ int mode; /* bitmap of MODE_XXX */ }; /* * audio device with plenty of slots */ struct dev { struct dev *next; struct slot *slot_list; /* audio streams attached */ struct opt *opt_list; struct midi *midi; /* * audio device (while opened) */ struct dev_sio sio; struct aparams par; /* encoding */ int pchan, rchan; /* play & rec channels */ adata_t *rbuf; /* rec buffer */ adata_t *pbuf; /* array of play buffers */ #define DEV_PBUF(d) ((d)->pbuf + (d)->poffs * (d)->pchan) int poffs; /* index of current play buf */ int psize; /* size of play buffer */ struct conv enc; /* native->device format */ struct conv dec; /* device->native format */ unsigned char *encbuf; /* buffer for encoding */ unsigned char *decbuf; /* buffer for decoding */ /* * preallocated audio sub-devices */ #define DEV_NSLOT 8 struct slot slot[DEV_NSLOT]; unsigned int serial; /* for slot allocation */ /* * current position, relative to the current cycle */ int delta; /* * desired parameters */ unsigned int reqmode; /* mode */ struct aparams reqpar; /* parameters */ int reqpchan, reqrchan; /* play & rec chans */ unsigned int reqbufsz; /* buffer size */ unsigned int reqround; /* block size */ unsigned int reqrate; /* sample rate */ unsigned int hold; /* hold the device open ? */ unsigned int autovol; /* auto adjust playvol ? */ unsigned int refcnt; /* number of openers */ #define DEV_NMAX 16 /* max number of devices */ unsigned int num; /* device serial number */ #define DEV_CFG 0 /* closed */ #define DEV_INIT 1 /* stopped */ #define DEV_RUN 2 /* playin & recording */ unsigned int pstate; /* one of above */ char *path; /* sio path */ /* * actual parameters and runtime state (i.e. once opened) */ unsigned int mode; /* bitmap of MODE_xxx */ unsigned int bufsz, round, rate; unsigned int prime; /* * MIDI time code (MTC) */ struct { unsigned int origin; /* MTC start time */ unsigned int fps; /* MTC frames per second */ #define MTC_FPS_24 0 #define MTC_FPS_25 1 #define MTC_FPS_30 3 unsigned int fps_id; /* one of above */ unsigned int hr; /* MTC hours */ unsigned int min; /* MTC minutes */ unsigned int sec; /* MTC seconds */ unsigned int fr; /* MTC frames */ unsigned int qfr; /* MTC quarter frames */ int delta; /* rel. to the last MTC tick */ int refs; } mtc; /* * MIDI machine control (MMC) */ #define MMC_STOP 1 /* stopped, can't start */ #define MMC_START 2 /* attempting to start */ #define MMC_RUN 3 /* started */ unsigned int tstate; /* one of above */ unsigned int master; /* master volume controller */ }; extern struct dev *dev_list; void dev_log(struct dev *); void dev_close(struct dev *); struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); struct dev *dev_bynum(int); void dev_del(struct dev *); void dev_adjpar(struct dev *, int, int, int); int dev_init(struct dev *); void dev_done(struct dev *); int dev_ref(struct dev *); void dev_unref(struct dev *); int dev_getpos(struct dev *); unsigned int dev_roundof(struct dev *, unsigned int); /* * interface to hardware device */ void dev_onmove(struct dev *, int); void dev_cycle(struct dev *); /* * midi & midi call-backs */ void dev_mmcstart(struct dev *); void dev_mmcstop(struct dev *); void dev_mmcloc(struct dev *, unsigned int); void dev_master(struct dev *, unsigned int); void dev_midi_vol(struct dev *, struct slot *); /* * sio_open(3) like interface for clients */ void slot_log(struct slot *); struct slot *slot_new(struct dev *, struct opt *, char *, struct slotops *, void *, int); void slot_del(struct slot *); void slot_setvol(struct slot *, unsigned int); void slot_start(struct slot *); void slot_stop(struct slot *); void slot_read(struct slot *); void slot_write(struct slot *); #endif /* !defined(DEV_H) */ sndio-1.5.0/sndiod/dsp.c010066400017510001751000000321351332662053300135630ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include "dsp.h" #include "utils.h" 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 }; /* * 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 = ADATA_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) 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; } /* * Initialise parameters structure with the defaults natively supported * by the machine. */ void aparams_init(struct aparams *par) { par->bps = sizeof(adata_t); par->bits = ADATA_BITS; par->le = ADATA_LE; par->sig = 1; par->msb = 0; } /* * log the given format/channels/encoding */ void aparams_log(struct aparams *par) { char enc[ENCMAX]; aparams_enctostr(par, enc); log_puts(enc); } /* * return true if encoding corresponds to what we store in adata_t */ int aparams_native(struct aparams *par) { return par->bps == sizeof(adata_t) && par->bits == ADATA_BITS && (par->bps == 1 || par->le == ADATA_LE) && (par->bits == par->bps * 8 || !par->msb); } /* * resample the given number of frames */ void resamp_do(struct resamp *p, adata_t *in, adata_t *out, int todo) { unsigned int nch; adata_t *idata; unsigned int oblksz; int s, ds, diff; adata_t *odata; unsigned int iblksz; unsigned int c; adata_t *ctxbuf, *ctx; unsigned int ctx_start; #ifdef DEBUG if (todo % p->iblksz != 0) { log_puts("resamp_do: partial blocks not supported\n"); panic(); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = in; odata = out; diff = p->oblksz; iblksz = p->iblksz; oblksz = p->oblksz; ctxbuf = p->ctx; ctx_start = p->ctx_start; nch = p->nch; for (;;) { if (diff >= oblksz) { if (todo == 0) break; ctx_start ^= 1; ctx = ctxbuf + ctx_start; for (c = nch; c > 0; c--) { *ctx = *idata++; ctx += RESAMP_NCTX; } diff -= oblksz; todo--; } else { ctx = ctxbuf; for (c = nch; c > 0; c--) { s = ctx[ctx_start ^ 1]; ds = ctx[ctx_start] - s; ctx += RESAMP_NCTX; *odata++ = s + ADATA_MULDIV(ds, diff, oblksz); } diff += iblksz; } } p->ctx_start = ctx_start; } /* * initialize resampler with ibufsz/obufsz factor and "nch" channels */ void resamp_init(struct resamp *p, unsigned int iblksz, unsigned int oblksz, int nch) { unsigned int i; p->iblksz = iblksz; p->oblksz = oblksz; p->nch = nch; p->ctx_start = 0; for (i = 0; i < NCHAN_MAX * RESAMP_NCTX; i++) p->ctx[i] = 0; #ifdef DEBUG if (log_level >= 3) { log_puts("resamp: "); log_putu(iblksz); log_puts("/"); log_putu(oblksz); log_puts("\n"); } #endif } /* * encode "todo" frames from native to foreign encoding */ void enc_do(struct conv *p, unsigned char *in, unsigned char *out, int todo) { unsigned int f; adata_t *idata; unsigned int s; unsigned int oshift; unsigned int obias; unsigned int obps; unsigned int i; unsigned char *odata; int obnext; int osnext; #ifdef DEBUG if (log_level >= 4) { log_puts("enc: copying "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = (adata_t *)in; odata = out; oshift = p->shift; obias = p->bias; obps = p->bps; obnext = p->bnext; osnext = p->snext; /* * Start conversion. */ odata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { /* convert adata to u32 */ s = (int)*idata++ + ADATA_UNIT; s <<= 32 - ADATA_BITS; /* convert u32 to uN */ s >>= oshift; /* convert uN to sN */ s -= obias; /* packetize sN */ for (i = obps; i > 0; i--) { *odata = (unsigned char)s; s >>= 8; odata += obnext; } odata += osnext; } } /* * store "todo" frames of silence in foreign encoding */ void enc_sil_do(struct conv *p, unsigned char *out, int todo) { unsigned int f; unsigned int s; unsigned int oshift; int obias; unsigned int obps; unsigned int i; unsigned char *odata; int obnext; int osnext; #ifdef DEBUG if (log_level >= 4) { log_puts("enc: silence "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ odata = out; oshift = p->shift; obias = p->bias; obps = p->bps; obnext = p->bnext; osnext = p->snext; /* * Start conversion. */ odata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { s = ((1U << 31) >> oshift) - obias; for (i = obps; i > 0; i--) { *odata = (unsigned char)s; s >>= 8; odata += obnext; } odata += osnext; } } /* * initialize encoder from native to foreign encoding */ void enc_init(struct conv *p, struct aparams *par, int nch) { p->nch = nch; p->bps = par->bps; if (par->msb) { p->shift = 32 - par->bps * 8; } else { p->shift = 32 - par->bits; } if (par->sig) { p->bias = (1U << 31) >> p->shift; } else { p->bias = 0; } if (!par->le) { p->bfirst = par->bps - 1; p->bnext = -1; p->snext = 2 * par->bps; } else { p->bfirst = 0; p->bnext = 1; p->snext = 0; } #ifdef DEBUG if (log_level >= 3) { log_puts("enc: "); aparams_log(par); log_puts(", "); log_puti(p->nch); log_puts(" channels\n"); } #endif } /* * decode "todo" frames from foreign to native encoding */ void dec_do(struct conv *p, unsigned char *in, unsigned char *out, int todo) { unsigned int f; unsigned int ibps; unsigned int i; unsigned int s = 0xdeadbeef; unsigned char *idata; int ibnext; int isnext; unsigned int ibias; unsigned int ishift; adata_t *odata; #ifdef DEBUG if (log_level >= 4) { log_puts("dec: copying "); log_putu(todo); log_puts(" frames\n"); } #endif /* * Partially copy structures into local variables, to avoid * unnecessary indirections; this also allows the compiler to * order local variables more "cache-friendly". */ idata = in; odata = (adata_t *)out; ibps = p->bps; ibnext = p->bnext; ibias = p->bias; ishift = p->shift; isnext = p->snext; /* * Start conversion. */ idata += p->bfirst; for (f = todo * p->nch; f > 0; f--) { for (i = ibps; i > 0; i--) { s <<= 8; s |= *idata; idata += ibnext; } idata += isnext; s += ibias; s <<= ishift; s >>= 32 - ADATA_BITS; *odata++ = s - ADATA_UNIT; } } /* * initialize decoder from foreign to native encoding */ void dec_init(struct conv *p, struct aparams *par, int nch) { p->bps = par->bps; p->nch = nch; if (par->msb) { p->shift = 32 - par->bps * 8; } else { p->shift = 32 - par->bits; } if (par->sig) { p->bias = (1U << 31) >> p->shift; } else { p->bias = 0; } if (par->le) { p->bfirst = par->bps - 1; p->bnext = -1; p->snext = 2 * par->bps; } else { p->bfirst = 0; p->bnext = 1; p->snext = 0; } #ifdef DEBUG if (log_level >= 3) { log_puts("dec: "); aparams_log(par); log_puts(", "); log_puti(p->nch); log_puts(" channels\n"); } #endif } /* * mix "todo" input frames on the output with the given volume */ void cmap_add(struct cmap *p, void *in, void *out, int vol, int todo) { adata_t *idata, *odata; int i, j, nch, istart, inext, onext, ostart, y, v; #ifdef DEBUG if (log_level >= 4) { log_puts("cmap: adding "); log_puti(todo); log_puts(" frames\n"); } #endif idata = in; odata = out; ostart = p->ostart; onext = p->onext; istart = p->istart; inext = p->inext; nch = p->nch; v = vol; /* * map/mix input on the output */ for (i = todo; i > 0; i--) { odata += ostart; idata += istart; for (j = nch; j > 0; j--) { y = *odata + ADATA_MUL(*idata, v); if (y >= ADATA_UNIT) y = ADATA_UNIT - 1; else if (y < -ADATA_UNIT) y = -ADATA_UNIT; *odata = y; idata++; odata++; } odata += onext; idata += inext; } } /* * overwrite output with "todo" input frames with the given volume */ void cmap_copy(struct cmap *p, void *in, void *out, int vol, int todo) { adata_t *idata, *odata; int i, j, nch, istart, inext, onext, ostart, v; #ifdef DEBUG if (log_level >= 4) { log_puts("cmap: copying "); log_puti(todo); log_puts(" frames\n"); } #endif idata = in; odata = out; ostart = p->ostart; onext = p->onext; istart = p->istart; inext = p->inext; nch = p->nch; v = vol; /* * copy to the output buffer */ for (i = todo; i > 0; i--) { idata += istart; odata += ostart; for (j = nch; j > 0; j--) { *odata = ADATA_MUL(*idata, v); odata++; idata++; } odata += onext; idata += inext; } } /* * initialize channel mapper, to map a subset of input channel range * into a subset of the output channel range */ void cmap_init(struct cmap *p, int imin, int imax, int isubmin, int isubmax, int omin, int omax, int osubmin, int osubmax) { int cmin, cmax; cmin = -NCHAN_MAX; if (osubmin > cmin) cmin = osubmin; if (omin > cmin) cmin = omin; if (isubmin > cmin) cmin = isubmin; if (imin > cmin) cmin = imin; cmax = NCHAN_MAX; if (osubmax < cmax) cmax = osubmax; if (omax < cmax) cmax = omax; if (isubmax < cmax) cmax = isubmax; if (imax < cmax) cmax = imax; p->ostart = cmin - omin; p->onext = omax - cmax; p->istart = cmin - imin; p->inext = imax - cmax; p->nch = cmax - cmin + 1; #ifdef DEBUG if (log_level >= 3) { log_puts("cmap: nch = "); log_puti(p->nch); log_puts(", ostart = "); log_puti(p->ostart); log_puts(", onext = "); log_puti(p->onext); log_puts(", istart = "); log_puti(p->istart); log_puts(", inext = "); log_puti(p->inext); log_puts("\n"); } #endif } sndio-1.5.0/sndiod/dsp.h010066400017510001751000000103651332662053300135710ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2012 Alexandre Ratchov * * 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 DSP_H #define DSP_H #include #include "defs.h" /* * Samples are numbers in the interval [-1, 1[, note that 1, the upper * boundary is excluded. We represent them as signed fixed point numbers * of ADATA_BITS. We also assume that 2^(ADATA_BITS - 1) fits in a int. */ #ifndef ADATA_BITS #define ADATA_BITS 16 #endif #define ADATA_LE (BYTE_ORDER == LITTLE_ENDIAN) #define ADATA_UNIT (1 << (ADATA_BITS - 1)) #if ADATA_BITS == 16 #define ADATA_MUL(x,y) (((int)(x) * (int)(y)) >> (ADATA_BITS - 1)) #define ADATA_MULDIV(x,y,z) ((int)(x) * (int)(y) / (int)(z)) typedef short adata_t; #elif ADATA_BITS == 24 #if defined(__i386__) && defined(__GNUC__) static inline int fp24_mul(int x, int a) { int res; asm volatile ( "imull %2\n\t" "shrdl $23, %%edx, %%eax\n\t" : "=a" (res) : "a" (x), "r" (a) : "%edx" ); return res; } static inline int fp24_muldiv(int x, int a, int b) { int res; asm volatile ( "imull %2\n\t" "idivl %3\n\t" : "=a" (res) : "a" (x), "d" (a), "r" (b) ); return res; } #define ADATA_MUL(x,y) fp24_mul(x, y) #define ADATA_MULDIV(x,y,z) fp24_muldiv(x, y, z); #elif defined(__amd64__) || defined(__sparc64__) #define ADATA_MUL(x,y) \ ((int)(((long long)(x) * (long long)(y)) >> (ADATA_BITS - 1))) #define ADATA_MULDIV(x,y,z) \ ((int)((long long)(x) * (long long)(y) / (long long)(z))) #else #error "no 24-bit code for this architecture" #endif typedef int adata_t; #else #error "only 16-bit and 24-bit precisions are supported" #endif /* * Maximum size of the encording string (the longest possible * encoding is ``s24le3msb''). */ #define ENCMAX 10 /* * Default bytes per sample for the given bits per sample. */ #define APARAMS_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4)) struct aparams { unsigned int bps; /* bytes per sample */ unsigned int bits; /* actually used bits */ unsigned int le; /* 1 if little endian, else be */ unsigned int sig; /* 1 if signed, 0 if unsigned */ unsigned int msb; /* 1 if msb justified, else lsb */ }; struct resamp { #define RESAMP_NCTX 2 unsigned int ctx_start; adata_t ctx[NCHAN_MAX * RESAMP_NCTX]; unsigned int iblksz, oblksz; int nch; }; struct conv { int bfirst; /* bytes to skip at startup */ unsigned int bps; /* bytes per sample */ unsigned int shift; /* shift to get 32bit MSB */ unsigned int bias; /* bias of unsigned samples */ int bnext; /* to reach the next byte */ int snext; /* to reach the next sample */ int nch; }; struct cmap { int istart; int inext; int onext; int ostart; int nch; }; #define MIDI_TO_ADATA(m) (aparams_ctltovol[m] << (ADATA_BITS - 16)) extern int aparams_ctltovol[128]; void aparams_init(struct aparams *); void aparams_log(struct aparams *); int aparams_strtoenc(struct aparams *, char *); int aparams_enctostr(struct aparams *, char *); int aparams_native(struct aparams *); void resamp_do(struct resamp *, adata_t *, adata_t *, int); void resamp_init(struct resamp *, unsigned int, unsigned int, int); void enc_do(struct conv *, unsigned char *, unsigned char *, int); void enc_sil_do(struct conv *, unsigned char *, int); void enc_init(struct conv *, struct aparams *, int); void dec_do(struct conv *, unsigned char *, unsigned char *, int); void dec_init(struct conv *, struct aparams *, int); void cmap_add(struct cmap *, void *, void *, int, int); void cmap_copy(struct cmap *, void *, void *, int, int); void cmap_init(struct cmap *, int, int, int, int, int, int, int, int); #endif /* !defined(DSP_H) */ sndio-1.5.0/sndiod/file.c010066400017510001751000000223561332662053300137200ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 and dispatch events to sub-modules. * * the module also provides trivial timeout implementation, * derived from: * * anoncvs@moule.caoua.org:/midish * * midish/timo.c rev 1.18 * midish/mdep.c rev 1.71 * * 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 #include #include #include #include #include #include #include #include "bsd-compat.h" #include "file.h" #include "utils.h" #define MAXFDS 100 #define TIMER_MSEC 5 void timo_update(unsigned int); void timo_init(void); void timo_done(void); void file_process(struct file *, struct pollfd *); struct timespec file_ts; struct file *file_list; struct timo *timo_queue; unsigned int timo_abstime; int file_slowaccept = 0, file_nfds; #ifdef DEBUG long long file_wtime, file_utime; #endif /* * 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 int delta) { struct timo **i; unsigned int val; int diff; #ifdef DEBUG if (o->set) { log_puts("timo_add: already set\n"); panic(); } if (delta == 0) { log_puts("timo_add: zero timeout is evil\n"); 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 (log_level >= 4) log_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 int 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) { log_puts("timo_done: timo_queue not empty!\n"); panic(); } #endif timo_queue = (struct timo *)0xdeadbeef; } #ifdef DEBUG void file_log(struct file *f) { static char *states[] = { "ini", "zom" }; log_puts(f->ops->name); if (log_level >= 3) { log_puts("("); log_puts(f->name); log_puts("|"); log_puts(states[f->state]); log_puts(")"); } } #endif struct file * file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds) { struct file *f; if (file_nfds + nfds > MAXFDS) { #ifdef DEBUG if (log_level >= 1) { log_puts(name); log_puts(": too many polled files\n"); } #endif return NULL; } f = xmalloc(sizeof(struct file)); f->max_nfds = nfds; f->ops = ops; f->arg = arg; f->name = name; f->state = FILE_INIT; f->next = file_list; file_list = f; #ifdef DEBUG if (log_level >= 3) { file_log(f); log_puts(": created\n"); } #endif file_nfds += f->max_nfds; return f; } void file_del(struct file *f) { #ifdef DEBUG if (f->state == FILE_ZOMB) { log_puts("bad state in file_del()\n"); panic(); } #endif file_nfds -= f->max_nfds; f->state = FILE_ZOMB; #ifdef DEBUG if (log_level >= 3) { file_log(f); log_puts(": destroyed\n"); } #endif } void file_process(struct file *f, struct pollfd *pfd) { int revents; #ifdef DEBUG struct timespec ts0, ts1; long us; #endif #ifdef DEBUG if (log_level >= 3) clock_gettime(CLOCK_UPTIME, &ts0); #endif revents = (f->state != FILE_ZOMB) ? f->ops->revents(f->arg, pfd) : 0; if ((revents & POLLHUP) && (f->state != FILE_ZOMB)) f->ops->hup(f->arg); if ((revents & POLLIN) && (f->state != FILE_ZOMB)) f->ops->in(f->arg); if ((revents & POLLOUT) && (f->state != FILE_ZOMB)) f->ops->out(f->arg); #ifdef DEBUG if (log_level >= 3) { clock_gettime(CLOCK_UPTIME, &ts1); us = 1000000L * (ts1.tv_sec - ts0.tv_sec); us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; if (log_level >= 4 || us >= 5000) { file_log(f); log_puts(": processed in "); log_putu(us); log_puts("us\n"); } } #endif } int file_poll(void) { struct pollfd pfds[MAXFDS], *pfd; struct file *f, **pf; struct timespec ts; #ifdef DEBUG struct timespec sleepts; int i; #endif long long delta_nsec; int nfds, res, timo; /* * cleanup zombies */ pf = &file_list; while ((f = *pf) != NULL) { if (f->state == FILE_ZOMB) { *pf = f->next; xfree(f); } else pf = &f->next; } if (file_list == NULL && timo_queue == NULL) { #ifdef DEBUG if (log_level >= 3) log_puts("nothing to do...\n"); #endif return 0; } /* * fill pollfd structures */ nfds = 0; for (f = file_list; f != NULL; f = f->next) { f->nfds = f->ops->pollfd(f->arg, pfds + nfds); if (f->nfds == 0) continue; nfds += f->nfds; } #ifdef DEBUG if (log_level >= 4) { log_puts("poll:"); pfd = pfds; for (f = file_list; f != NULL; f = f->next) { log_puts(" "); log_puts(f->ops->name); log_puts(":"); for (i = 0; i < f->nfds; i++) { log_puts(" "); log_putx(pfd->events); pfd++; } } log_puts("\n"); } #endif /* * process files that do not rely on poll */ for (f = file_list; f != NULL; f = f->next) { if (f->nfds > 0) continue; file_process(f, NULL); } /* * Sleep. Calculate the number of milliseconds poll(2) must * wait before the timo_update() needs to be called. If there are * no timeouts scheduled, then call poll(2) with infinite * timeout (i.e -1). */ #ifdef DEBUG clock_gettime(CLOCK_UPTIME, &sleepts); file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec); file_utime += sleepts.tv_nsec - file_ts.tv_nsec; #endif if (timo_queue != NULL) { timo = ((int)timo_queue->val - (int)timo_abstime) / 1000; if (timo < TIMER_MSEC) timo = TIMER_MSEC; } else timo = -1; log_flush(); res = poll(pfds, nfds, timo); if (res < 0) { if (errno != EINTR) { log_puts("poll failed"); panic(); } return 1; } /* * run timeouts */ clock_gettime(CLOCK_UPTIME, &ts); #ifdef DEBUG file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec); file_wtime += ts.tv_nsec - sleepts.tv_nsec; #endif if (timo_queue) { delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec); delta_nsec += ts.tv_nsec - file_ts.tv_nsec; if (delta_nsec >= 0 && delta_nsec < 60000000000LL) timo_update(delta_nsec / 1000); else { if (log_level >= 2) log_puts("out-of-bounds clock delta\n"); } } file_ts = ts; /* * process files that rely on poll */ pfd = pfds; for (f = file_list; f != NULL; f = f->next) { if (f->nfds == 0) continue; file_process(f, pfd); pfd += f->nfds; } return 1; } void filelist_init(void) { sigset_t set; if (clock_gettime(CLOCK_UPTIME, &file_ts) < 0) { log_puts("filelist_init: CLOCK_UPTIME unsupported\n"); panic(); } sigemptyset(&set); sigaddset(&set, SIGPIPE); sigprocmask(SIG_BLOCK, &set, NULL); file_list = NULL; log_sync = 0; timo_init(); } void filelist_done(void) { #ifdef DEBUG struct file *f; if (file_list != NULL) { for (f = file_list; f != NULL; f = f->next) { file_log(f); log_puts(" not closed\n"); } panic(); } log_sync = 1; log_flush(); #endif timo_done(); } sndio-1.5.0/sndiod/file.h010066400017510001751000000047121332662053300137210ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 struct file; struct pollfd; struct timo { struct timo *next; unsigned int val; /* time to wait before the callback */ unsigned int 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; int (*pollfd)(void *, struct pollfd *); int (*revents)(void *, struct pollfd *); /* * we have to handle POLLIN and POLLOUT events * in separate handles, since handling POLLIN can * close the file, and continuing (to handle POLLOUT) * would make use of the free()'ed file structure */ void (*in)(void *); void (*out)(void *); void (*hup)(void *); }; struct file { struct file *next; /* next in file_list */ struct fileops *ops; /* event handlers */ void *arg; /* argument to event handlers */ #define FILE_INIT 0 /* ready */ #define FILE_ZOMB 1 /* closed, but not free()d yet */ unsigned int state; /* one of above */ unsigned int max_nfds; /* max number of descriptors */ unsigned int nfds; /* number of descriptors polled */ char *name; /* for debug purposes */ }; extern struct file *file_list; extern int file_slowaccept; #ifdef DEBUG extern long long file_wtime, file_utime; #endif void timo_set(struct timo *, void (*)(void *), void *); void timo_add(struct timo *, unsigned int); void timo_del(struct timo *); void filelist_init(void); void filelist_done(void); void filelist_unlisten(void); struct file *file_new(struct fileops *, void *, char *, unsigned int); void file_del(struct file *); void file_log(struct file *); int file_poll(void); #endif /* !defined(FILE_H) */ sndio-1.5.0/sndiod/listen.c010066400017510001751000000137661332662053300143040ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "listen.h" #include "file.h" #include "sock.h" #include "utils.h" #include "bsd-compat.h" int listen_pollfd(void *, struct pollfd *); int listen_revents(void *, struct pollfd *); void listen_in(void *); void listen_out(void *); void listen_hup(void *); struct fileops listen_fileops = { "listen", listen_pollfd, listen_revents, listen_in, listen_out, listen_hup }; struct listen *listen_list = NULL; void listen_close(struct listen *f) { struct listen **pf; for (pf = &listen_list; *pf != f; pf = &(*pf)->next) { #ifdef DEBUG if (*pf == NULL) { log_puts("listen_close: not on list\n"); panic(); } #endif } *pf = f->next; if (f->path != NULL) { unlink(f->path); xfree(f->path); } file_del(f->file); close(f->fd); xfree(f); } int listen_new_un(char *path) { int sock, oldumask; struct sockaddr_un sockname; struct listen *f; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { log_puts(path); log_puts(": failed to create socket\n"); return 0; } if (unlink(path) < 0 && errno != ENOENT) { log_puts(path); log_puts(": failed to unlink socket\n"); 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) { log_puts(path); log_puts(": failed to bind socket\n"); goto bad_close; } if (listen(sock, 1) < 0) { log_puts(path); log_puts(": failed to listen\n"); goto bad_close; } umask(oldumask); f = xmalloc(sizeof(struct listen)); f->file = file_new(&listen_fileops, f, path, 1); if (f->file == NULL) goto bad_close; f->path = xstrdup(path); f->fd = sock; f->next = listen_list; listen_list = f; return 1; bad_close: close(sock); return 0; } int listen_new_tcp(char *addr, unsigned int port) { char *host, serv[sizeof(unsigned int) * 3 + 1]; struct addrinfo *ailist, *ai, aihints; struct listen *f; int s, error, opt = 1, n = 0; /* * obtain a list of possible addresses for the host/port */ memset(&aihints, 0, sizeof(struct addrinfo)); snprintf(serv, sizeof(serv), "%u", port); host = strcmp(addr, "-") == 0 ? NULL : addr; aihints.ai_flags |= AI_PASSIVE; aihints.ai_socktype = SOCK_STREAM; aihints.ai_protocol = IPPROTO_TCP; error = getaddrinfo(host, serv, &aihints, &ailist); if (error) { log_puts(addr); log_puts(": failed to resolve address\n"); return 0; } /* * for each address, try create a listening socket bound on * that address */ for (ai = ailist; ai != NULL; ai = ai->ai_next) { s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (s < 0) { log_puts(addr); log_puts(": failed to create socket\n"); continue; } opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) < 0) { log_puts(addr); log_puts(": failed to set SO_REUSEADDR\n"); goto bad_close; } if (ai->ai_family == AF_INET6) { /* * make sure IPv6 sockets are restricted to IPv6 * addresses because we already use a IP socket * for IP addresses */ opt = 1; if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(int)) < 0) { log_puts(addr); log_puts(": failed to set IPV6_V6ONLY\n"); goto bad_close; } } if (bind(s, ai->ai_addr, ai->ai_addrlen) < 0) { log_puts(addr); log_puts(": failed to bind socket\n"); goto bad_close; } if (listen(s, 1) < 0) { log_puts(addr); log_puts(": failed to listen\n"); goto bad_close; } f = xmalloc(sizeof(struct listen)); f->file = file_new(&listen_fileops, f, addr, 1); if (f == NULL) { bad_close: close(s); continue; } f->path = NULL; f->fd = s; f->next = listen_list; listen_list = f; n++; } freeaddrinfo(ailist); return n; } int listen_init(struct listen *f) { return 1; } int listen_pollfd(void *arg, struct pollfd *pfd) { struct listen *f = arg; f->slowaccept = file_slowaccept; if (f->slowaccept) return 0; pfd->fd = f->fd; pfd->events = POLLIN; return 1; } int listen_revents(void *arg, struct pollfd *pfd) { struct listen *f = arg; if (f->slowaccept) return 0; return pfd->revents; } void listen_in(void *arg) { struct listen *f = arg; struct sockaddr caddr; socklen_t caddrlen; int sock, opt; caddrlen = sizeof(caddrlen); while ((sock = accept(f->fd, &caddr, &caddrlen)) < 0) { if (errno == EINTR) continue; if (errno == ENFILE || errno == EMFILE) file_slowaccept = 1; return; } if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { file_log(f->file); log_puts(": failed to set non-blocking mode\n"); goto bad_close; } if (f->path == NULL) { opt = 1; if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int)) < 0) { file_log(f->file); log_puts(": failed to set TCP_NODELAY flag\n"); goto bad_close; } } if (sock_new(sock) == NULL) goto bad_close; return; bad_close: close(sock); } void listen_out(void *arg) { } void listen_hup(void *arg) { struct listen *f = arg; listen_close(f); } sndio-1.5.0/sndiod/listen.h010066400017510001751000000022241332662053300142740ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 struct file; struct listen { struct listen *next; struct file *file; char *path; int fd; int slowaccept; }; extern struct listen *listen_list; int listen_new_un(char *); int listen_new_tcp(char *, unsigned int); int listen_init(struct listen *); void listen_close(struct listen *); #endif /* !defined(LISTEN_H) */ sndio-1.5.0/sndiod/midi.c010066400017510001751000000245371332662053300137260ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include #include "abuf.h" #include "defs.h" #include "dev.h" #include "file.h" #include "midi.h" #include "miofile.h" #include "sysex.h" #include "utils.h" #include "bsd-compat.h" int port_open(struct port *); void port_imsg(void *, unsigned char *, int); void port_omsg(void *, unsigned char *, int); void port_fill(void *, int); void port_exit(void *); struct midiops port_midiops = { port_imsg, port_omsg, port_fill, port_exit }; #define MIDI_NEP 32 struct midi midi_ep[MIDI_NEP]; struct port *port_list = NULL; unsigned int midi_portnum = 0; struct midithru { unsigned int txmask, rxmask; #define MIDITHRU_NMAX 32 } midithru[MIDITHRU_NMAX]; /* * length of voice and common messages (status byte included) */ unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; void midi_log(struct midi *ep) { log_puts("midi"); log_putu(ep - midi_ep); } void midi_init(void) { } void midi_done(void) { } struct midi * midi_new(struct midiops *ops, void *arg, int mode) { int i; struct midi *ep; for (i = 0, ep = midi_ep;; i++, ep++) { if (i == MIDI_NEP) return NULL; if (ep->ops == NULL) break; } ep->ops = ops; ep->arg = arg; ep->used = 0; ep->len = 0; ep->idx = 0; ep->st = 0; ep->txmask = 0; ep->self = 1 << i; ep->tickets = 0; ep->mode = mode; /* * the output buffer is the client input */ if (ep->mode & MODE_MIDIIN) abuf_init(&ep->obuf, MIDI_BUFSZ); midi_tickets(ep); return ep; } void midi_del(struct midi *ep) { int i; struct midi *peer; ep->txmask = 0; for (i = 0; i < MIDI_NEP; i++) { peer = midi_ep + i; if (peer->txmask & ep->self) { peer->txmask &= ~ep->self; midi_tickets(peer); } } for (i = 0; i < MIDITHRU_NMAX; i++) { midithru[i].txmask &= ~ep->self; midithru[i].rxmask &= ~ep->self; } ep->ops = NULL; if (ep->mode & MODE_MIDIIN) { abuf_done(&ep->obuf); } } /* * connect two midi endpoints */ void midi_link(struct midi *ep, struct midi *peer) { if (ep->mode & MODE_MIDIOUT) { ep->txmask |= peer->self; midi_tickets(ep); } if (ep->mode & MODE_MIDIIN) { #ifdef DEBUG if (ep->obuf.used > 0) { midi_log(ep); log_puts(": linked with non-empty buffer\n"); panic(); } #endif /* ep has empty buffer, so no need to call midi_tickets() */ peer->txmask |= ep->self; } } /* * add the midi endpoint in the ``tag'' midi thru box */ void midi_tag(struct midi *ep, unsigned int tag) { struct midi *peer; struct midithru *t = midithru + tag; int i; if (ep->mode & MODE_MIDIOUT) { ep->txmask |= t->txmask; midi_tickets(ep); } if (ep->mode & MODE_MIDIIN) { #ifdef DEBUG if (ep->obuf.used > 0) { midi_log(ep); log_puts(": tagged with non-empty buffer\n"); panic(); } #endif for (i = 0; i < MIDI_NEP; i++) { if (!(t->rxmask & (1 << i))) continue; peer = midi_ep + i; peer->txmask |= ep->self; } } if (ep->mode & MODE_MIDIOUT) t->rxmask |= ep->self; if (ep->mode & MODE_MIDIIN) t->txmask |= ep->self; } /* * broadcast the given message to other endpoints */ void midi_send(struct midi *iep, unsigned char *msg, int size) { struct midi *oep; int i; #ifdef DEBUG if (log_level >= 4) { midi_log(iep); log_puts(": sending:"); for (i = 0; i < size; i++) { log_puts(" "); log_putx(msg[i]); } log_puts("\n"); } #endif for (i = 0; i < MIDI_NEP ; i++) { if ((iep->txmask & (1 << i)) == 0) continue; oep = midi_ep + i; if (msg[0] <= 0x7f) { if (oep->owner != iep) continue; } else if (msg[0] <= 0xf7) oep->owner = iep; #ifdef DEBUG if (log_level >= 4) { midi_log(iep); log_puts(" -> "); midi_log(oep); log_puts("\n"); } #endif oep->ops->omsg(oep->arg, msg, size); } } /* * determine if we have gained more input tickets, and if so call the * fill() call-back to notify the i/o layer that it can send more data */ void midi_tickets(struct midi *iep) { int i, tickets, avail, maxavail; struct midi *oep; maxavail = MIDI_BUFSZ; for (i = 0; i < MIDI_NEP ; i++) { if ((iep->txmask & (1 << i)) == 0) continue; oep = midi_ep + i; avail = oep->obuf.len - oep->obuf.used; if (maxavail > avail) maxavail = avail; } /* * in the worst case output message is twice the * input message (2-byte messages with running status) */ tickets = maxavail / 2 - iep->tickets; if (tickets > 0) { iep->tickets += tickets; iep->ops->fill(iep->arg, tickets); } } /* * recalculate tickets of endpoints sending data to this one */ void midi_fill(struct midi *oep) { int i; struct midi *iep; for (i = 0; i < MIDI_NEP; i++) { iep = midi_ep + i; if (iep->txmask & oep->self) midi_tickets(iep); } } /* * parse then give data chunk, and calling imsg() for each message */ void midi_in(struct midi *iep, unsigned char *idata, int icount) { int i; unsigned char c; for (i = 0; i < icount; i++) { c = *idata++; if (c >= 0xf8) { if (c != MIDI_ACK) iep->ops->imsg(iep->arg, &c, 1); } else if (c == SYSEX_END) { if (iep->st == SYSEX_START) { iep->msg[iep->idx++] = c; iep->ops->imsg(iep->arg, iep->msg, iep->idx); } iep->st = 0; iep->idx = 0; } else if (c >= 0xf0) { iep->msg[0] = c; iep->len = common_len[c & 7]; iep->st = c; iep->idx = 1; } else if (c >= 0x80) { iep->msg[0] = c; iep->len = voice_len[(c >> 4) & 7]; iep->st = c; iep->idx = 1; } else if (iep->st) { if (iep->idx == 0 && iep->st != SYSEX_START) iep->msg[iep->idx++] = iep->st; iep->msg[iep->idx++] = c; if (iep->idx == iep->len) { iep->ops->imsg(iep->arg, iep->msg, iep->idx); if (iep->st >= 0xf0) iep->st = 0; iep->idx = 0; } else if (iep->idx == MIDI_MSGMAX) { /* sysex continued */ iep->ops->imsg(iep->arg, iep->msg, iep->idx); iep->idx = 0; } } } iep->tickets -= icount; if (iep->tickets < 0) iep->tickets = 0; midi_tickets(iep); } /* * store the given message in the output buffer */ void midi_out(struct midi *oep, unsigned char *idata, int icount) { unsigned char *odata; int ocount; #ifdef DEBUG int i; #endif while (icount > 0) { if (oep->obuf.used == oep->obuf.len) { #ifdef DEBUG if (log_level >= 2) { midi_log(oep); log_puts(": too slow, discarding "); log_putu(oep->obuf.used); log_puts(" bytes\n"); } #endif abuf_rdiscard(&oep->obuf, oep->obuf.used); oep->owner = NULL; return; } odata = abuf_wgetblk(&oep->obuf, &ocount); if (ocount > icount) ocount = icount; memcpy(odata, idata, ocount); #ifdef DEBUG if (log_level >= 4) { midi_log(oep); log_puts(": out: "); for (i = 0; i < ocount; i++) { log_puts(" "); log_putx(odata[i]); } log_puts("\n"); } #endif abuf_wcommit(&oep->obuf, ocount); icount -= ocount; idata += ocount; } } void port_log(struct port *p) { midi_log(p->midi); } void port_imsg(void *arg, unsigned char *msg, int size) { struct port *p = arg; midi_send(p->midi, msg, size); } void port_omsg(void *arg, unsigned char *msg, int size) { struct port *p = arg; midi_out(p->midi, msg, size); } void port_fill(void *arg, int count) { /* no flow control */ } void port_exit(void *arg) { #ifdef DEBUG struct port *p = arg; if (log_level >= 3) { port_log(p); log_puts(": port exit\n"); panic(); } #endif } /* * create a new midi port */ struct port * port_new(char *path, unsigned int mode, int hold) { struct port *c; c = xmalloc(sizeof(struct port)); c->path = xstrdup(path); c->state = PORT_CFG; c->hold = hold; c->midi = midi_new(&port_midiops, c, mode); c->num = midi_portnum++; c->next = port_list; port_list = c; return c; } /* * destroy the given midi port */ void port_del(struct port *c) { struct port **p; if (c->state != PORT_CFG) port_close(c); midi_del(c->midi); for (p = &port_list; *p != c; p = &(*p)->next) { #ifdef DEBUG if (*p == NULL) { log_puts("port to delete not on list\n"); panic(); } #endif } *p = c->next; xfree(c->path); xfree(c); } int port_ref(struct port *c) { #ifdef DEBUG if (log_level >= 3) { port_log(c); log_puts(": port requested\n"); } #endif if (c->state == PORT_CFG && !port_open(c)) return 0; return 1; } void port_unref(struct port *c) { int i, rxmask; #ifdef DEBUG if (log_level >= 3) { port_log(c); log_puts(": port released\n"); } #endif for (rxmask = 0, i = 0; i < MIDI_NEP; i++) rxmask |= midi_ep[i].txmask; if ((rxmask & c->midi->self) == 0 && c->midi->txmask == 0 && c->state == PORT_INIT && !c->hold) port_drain(c); } struct port * port_bynum(int num) { struct port *p; for (p = port_list; p != NULL; p = p->next) { if (p->num == num) return p; } return NULL; } int port_open(struct port *c) { if (!port_mio_open(c)) { if (log_level >= 1) { log_puts(c->path); log_puts(": failed to open midi port\n"); } return 0; } c->state = PORT_INIT; return 1; } int port_close(struct port *c) { int i; struct midi *ep; #ifdef DEBUG if (c->state == PORT_CFG) { port_log(c); log_puts(": can't close port (not opened)\n"); panic(); } #endif c->state = PORT_CFG; port_mio_close(c); for (i = 0; i < MIDI_NEP; i++) { ep = midi_ep + i; if ((ep->txmask & c->midi->self) || (c->midi->txmask & ep->self)) ep->ops->exit(ep->arg); } return 1; } void port_drain(struct port *c) { struct midi *ep = c->midi; if (!(ep->mode & MODE_MIDIOUT) || ep->obuf.used == 0) port_close(c); else { c->state = PORT_DRAIN; #ifdef DEBUG if (log_level >= 3) { port_log(c); log_puts(": draining\n"); } #endif } } int port_init(struct port *c) { if (c->hold) return port_open(c); return 1; } void port_done(struct port *c) { if (c->state == PORT_INIT) port_drain(c); } sndio-1.5.0/sndiod/midi.h010066400017510001751000000070141332662053300137220ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include "abuf.h" #include "miofile.h" /* * masks to extract command and channel of status byte */ #define MIDI_CMDMASK 0xf0 #define MIDI_CHANMASK 0x0f /* * MIDI status bytes of voice messages */ #define MIDI_NOFF 0x80 /* note off */ #define MIDI_NON 0x90 /* note on */ #define MIDI_KAT 0xa0 /* key after touch */ #define MIDI_CTL 0xb0 /* controller */ #define MIDI_PC 0xc0 /* program change */ #define MIDI_CAT 0xd0 /* channel after touch */ #define MIDI_BEND 0xe0 /* pitch bend */ #define MIDI_ACK 0xfe /* active sensing message */ /* * MIDI controller numbers */ #define MIDI_CTL_VOL 7 /* volume */ /* * Max coarse value */ #define MIDI_MAXCTL 127 /* * midi stream state structure */ struct midiops { void (*imsg)(void *, unsigned char *, int); void (*omsg)(void *, unsigned char *, int); void (*fill)(void *, int); void (*exit)(void *); }; struct midi { struct midiops *ops; /* port/sock/dev callbacks */ struct midi *owner; /* current writer stream */ unsigned int mode; /* MODE_{MIDIIN,MIDIOUT} */ void *arg; /* user data for callbacks */ #define MIDI_MSGMAX 16 /* max size of MIDI msg */ unsigned char msg[MIDI_MSGMAX]; /* parsed input message */ unsigned int st; /* input MIDI running status */ unsigned int used; /* bytes used in ``msg'' */ unsigned int idx; /* current ``msg'' size */ unsigned int len; /* expected ``msg'' length */ unsigned int txmask; /* list of ep we send to */ unsigned int self; /* equal (1 << index) */ int tickets; /* max bytes we can process */ struct abuf obuf; /* output buffer */ }; /* * midi port */ struct port { struct port *next; struct port_mio mio; #define PORT_CFG 0 #define PORT_INIT 1 #define PORT_DRAIN 2 unsigned int state; unsigned int num; /* port serial number */ char *path; /* hold the port open ? */ int hold; struct midi *midi; }; /* * midi control ports */ extern struct port *port_list; void midi_init(void); void midi_done(void); struct midi *midi_new(struct midiops *, void *, int); void midi_del(struct midi *); void midi_log(struct midi *); void midi_tickets(struct midi *); void midi_in(struct midi *, unsigned char *, int); void midi_out(struct midi *, unsigned char *, int); void midi_send(struct midi *, unsigned char *, int); void midi_fill(struct midi *); void midi_tag(struct midi *, unsigned int); void midi_link(struct midi *, struct midi *); void port_log(struct port *); struct port *port_new(char *, unsigned int, int); struct port *port_bynum(int); void port_del(struct port *); int port_ref(struct port *); void port_unref(struct port *); int port_init(struct port *); void port_done(struct port *); void port_drain(struct port *); int port_close(struct port *); #endif /* !defined(MIDI_H) */ sndio-1.5.0/sndiod/miofile.c010066400017510001751000000053511332662053300144210ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include "defs.h" #include "file.h" #include "midi.h" #include "miofile.h" #include "utils.h" int port_mio_pollfd(void *, struct pollfd *); int port_mio_revents(void *, struct pollfd *); void port_mio_in(void *); void port_mio_out(void *); void port_mio_hup(void *); struct fileops port_mio_ops = { "mio", port_mio_pollfd, port_mio_revents, port_mio_in, port_mio_out, port_mio_hup }; int port_mio_open(struct port *p) { p->mio.hdl = mio_open(p->path, p->midi->mode, 1); if (p->mio.hdl == NULL) return 0; p->mio.file = file_new(&port_mio_ops, p, p->path, mio_nfds(p->mio.hdl)); return 1; } void port_mio_close(struct port *p) { file_del(p->mio.file); mio_close(p->mio.hdl); } int port_mio_pollfd(void *addr, struct pollfd *pfd) { struct port *p = addr; struct midi *ep = p->midi; int events = 0; if (ep->mode & MODE_MIDIIN) events |= POLLIN; if ((ep->mode & MODE_MIDIOUT) && ep->obuf.used > 0) events |= POLLOUT; return mio_pollfd(p->mio.hdl, pfd, events); } int port_mio_revents(void *addr, struct pollfd *pfd) { struct port *p = addr; return mio_revents(p->mio.hdl, pfd); } void port_mio_in(void *arg) { unsigned char data[MIDI_BUFSZ]; struct port *p = arg; struct midi *ep = p->midi; int n; for (;;) { n = mio_read(p->mio.hdl, data, MIDI_BUFSZ); if (n == 0) break; midi_in(ep, data, n); } } void port_mio_out(void *arg) { struct port *p = arg; struct midi *ep = p->midi; unsigned char *data; int n, count; for (;;) { data = abuf_rgetblk(&ep->obuf, &count); if (count == 0) break; n = mio_write(p->mio.hdl, data, count); if (n == 0) break; abuf_rdiscard(&ep->obuf, n); if (n < count) break; } if (p->state == PORT_DRAIN && ep->obuf.used == 0) port_close(p); midi_fill(ep); } void port_mio_hup(void *arg) { struct port *p = arg; port_close(p); } sndio-1.5.0/sndiod/miofile.h010066400017510001751000000020121332662053300144150ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 port; struct port_mio { struct mio_hdl *hdl; struct file *file; }; int port_mio_open(struct port *); void port_mio_close(struct port *); #endif /* !defined(MIOFILE_H) */ sndio-1.5.0/sndiod/opt.c010066400017510001751000000056401332662053300136000ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2011 Alexandre Ratchov * * 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 #include "dev.h" #include "opt.h" #include "utils.h" /* * create a new audio sub-device "configuration" */ struct opt * opt_new(struct dev *d, char *name, int pmin, int pmax, int rmin, int rmax, int maxweight, int mmc, int dup, unsigned int mode) { struct opt *o; unsigned int len; char c; if (opt_byname(d, name)) { dev_log(d); log_puts("."); log_puts(name); log_puts(": already defined\n"); return NULL; } for (len = 0; name[len] != '\0'; len++) { if (len == OPT_NAMEMAX) { log_puts(name); log_puts(": too long\n"); return NULL; } c = name[len]; if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) { log_puts(name); log_puts(": only alphabetic chars allowed\n"); return NULL; } } o = xmalloc(sizeof(struct opt)); if (mode & MODE_PLAY) { o->pmin = pmin; o->pmax = pmax; } if (mode & MODE_RECMASK) { o->rmin = rmin; o->rmax = rmax; } o->maxweight = maxweight; o->mmc = mmc; o->dup = dup; o->mode = mode; memcpy(o->name, name, len + 1); o->next = d->opt_list; d->opt_list = o; if (log_level >= 2) { dev_log(d); log_puts("."); log_puts(o->name); log_puts(":"); if (o->mode & MODE_REC) { log_puts(" rec="); log_putu(o->rmin); log_puts(":"); log_putu(o->rmax); } if (o->mode & MODE_PLAY) { log_puts(" play="); log_putu(o->pmin); log_puts(":"); log_putu(o->pmax); log_puts(" vol="); log_putu(o->maxweight); } if (o->mode & MODE_MON) { log_puts(" mon="); log_putu(o->rmin); log_puts(":"); log_putu(o->rmax); } if (o->mode & (MODE_RECMASK | MODE_PLAY)) { if (o->mmc) log_puts(" mmc"); if (o->dup) log_puts(" dup"); } log_puts("\n"); } return o; } struct opt * opt_byname(struct dev *d, char *name) { struct opt *o; for (o = d->opt_list; o != NULL; o = o->next) { if (strcmp(name, o->name) == 0) return o; } return NULL; } void opt_del(struct dev *d, struct opt *o) { struct opt **po; for (po = &d->opt_list; *po != o; po = &(*po)->next) { #ifdef DEBUG if (*po == NULL) { log_puts("opt_del: not on list\n"); panic(); } #endif } *po = o->next; xfree(o); } sndio-1.5.0/sndiod/opt.h010066400017510001751000000020561332662053300136030ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 struct dev; struct opt *opt_new(struct dev *, char *, int, int, int, int, int, int, int, unsigned int); void opt_del(struct dev *, struct opt *); struct opt *opt_byname(struct dev *, char *); #endif /* !defined(OPT_H) */ sndio-1.5.0/sndiod/siofile.c010066400017510001751000000255271332662053300144360ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include "abuf.h" #include "defs.h" #include "dev.h" #include "dsp.h" #include "file.h" #include "siofile.h" #include "utils.h" #define WATCHDOG_USEC 4000000 /* 4 seconds */ void dev_sio_onmove(void *, int); void dev_sio_timeout(void *); int dev_sio_pollfd(void *, struct pollfd *); int dev_sio_revents(void *, struct pollfd *); void dev_sio_run(void *); void dev_sio_hup(void *); struct fileops dev_sio_ops = { "sio", dev_sio_pollfd, dev_sio_revents, dev_sio_run, dev_sio_run, dev_sio_hup }; void dev_sio_onmove(void *arg, int delta) { struct dev *d = arg; #ifdef DEBUG if (log_level >= 4) { dev_log(d); log_puts(": tick, delta = "); log_puti(delta); log_puts("\n"); } d->sio.sum_utime += file_utime - d->sio.utime; d->sio.sum_wtime += file_wtime - d->sio.wtime; d->sio.wtime = file_wtime; d->sio.utime = file_utime; if (d->mode & MODE_PLAY) d->sio.pused -= delta; if (d->mode & MODE_REC) d->sio.rused += delta; #endif dev_onmove(d, delta); } void dev_sio_timeout(void *arg) { struct dev *d = arg; dev_log(d); log_puts(": watchdog timeout\n"); dev_close(d); } /* * open the device. */ int dev_sio_open(struct dev *d) { struct sio_par par; unsigned int mode = d->mode & (MODE_PLAY | MODE_REC); d->sio.hdl = sio_open(d->path, mode, 1); if (d->sio.hdl == NULL) { if (mode != (SIO_PLAY | SIO_REC)) return 0; d->sio.hdl = sio_open(d->path, SIO_PLAY, 1); if (d->sio.hdl != NULL) mode = SIO_PLAY; else { d->sio.hdl = sio_open(d->path, SIO_REC, 1); if (d->sio.hdl != NULL) mode = SIO_REC; else return 0; } if (log_level >= 1) { log_puts("warning, device opened in "); log_puts(mode == SIO_PLAY ? "play-only" : "rec-only"); log_puts(" mode\n"); } } sio_initpar(&par); par.bits = d->par.bits; par.bps = d->par.bps; par.sig = d->par.sig; par.le = d->par.le; par.msb = d->par.msb; if (mode & SIO_PLAY) par.pchan = d->pchan; if (mode & SIO_REC) par.rchan = d->rchan; if (d->bufsz) par.appbufsz = d->bufsz; if (d->round) par.round = d->round; if (d->rate) par.rate = d->rate; if (!sio_setpar(d->sio.hdl, &par)) goto bad_close; if (!sio_getpar(d->sio.hdl, &par)) goto bad_close; #ifdef DEBUG /* * We support any parameter combination exposed by the kernel, * and we have no other choice than trusting the kernel for * returning correct parameters. But let's check parameters * early and nicely report kernel bugs rather than crashing * later in memset(), malloc() or alike. */ if (par.bits > BITS_MAX) { log_puts(d->path); log_puts(": "); log_putu(par.bits); log_puts(": unsupported number of bits\n"); goto bad_close; } if (par.bps > SIO_BPS(BITS_MAX)) { log_puts(d->path); log_puts(": "); log_putu(par.bps); log_puts(": unsupported sample size\n"); goto bad_close; } if ((mode & SIO_PLAY) && par.pchan > NCHAN_MAX) { log_puts(d->path); log_puts(": "); log_putu(par.pchan); log_puts(": unsupported number of play channels\n"); goto bad_close; } if ((mode & SIO_REC) && par.rchan > NCHAN_MAX) { log_puts(d->path); log_puts(": "); log_putu(par.rchan); log_puts(": unsupported number of rec channels\n"); goto bad_close; } if (par.bufsz == 0 || par.bufsz > RATE_MAX) { log_puts(d->path); log_puts(": "); log_putu(par.bufsz); log_puts(": unsupported buffer size\n"); goto bad_close; } if (par.round == 0 || par.round > par.bufsz || par.bufsz % par.round != 0) { log_puts(d->path); log_puts(": "); log_putu(par.round); log_puts(": unsupported block size\n"); goto bad_close; } if (par.rate == 0 || par.rate > RATE_MAX) { log_puts(d->path); log_puts(": "); log_putu(par.rate); log_puts(": unsupported rate\n"); goto bad_close; } #endif d->par.bits = par.bits; d->par.bps = par.bps; d->par.sig = par.sig; d->par.le = par.le; d->par.msb = par.msb; if (mode & SIO_PLAY) d->pchan = par.pchan; if (mode & SIO_REC) d->rchan = par.rchan; d->bufsz = par.bufsz; d->round = par.round; d->rate = par.rate; if (!(mode & MODE_PLAY)) d->mode &= ~(MODE_PLAY | MODE_MON); if (!(mode & MODE_REC)) d->mode &= ~MODE_REC; sio_onmove(d->sio.hdl, dev_sio_onmove, d); d->sio.file = file_new(&dev_sio_ops, d, d->path, sio_nfds(d->sio.hdl)); timo_set(&d->sio.watchdog, dev_sio_timeout, d); return 1; bad_close: sio_close(d->sio.hdl); return 0; } void dev_sio_close(struct dev *d) { #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": closed\n"); } #endif timo_del(&d->sio.watchdog); file_del(d->sio.file); sio_close(d->sio.hdl); } void dev_sio_start(struct dev *d) { if (!sio_start(d->sio.hdl)) { if (log_level >= 1) { dev_log(d); log_puts(": failed to start device\n"); } return; } if (d->mode & MODE_PLAY) { d->sio.cstate = DEV_SIO_CYCLE; d->sio.todo = 0; } else { d->sio.cstate = DEV_SIO_READ; d->sio.todo = d->round * d->rchan * d->par.bps; } #ifdef DEBUG d->sio.pused = 0; d->sio.rused = 0; d->sio.sum_utime = 0; d->sio.sum_wtime = 0; d->sio.wtime = file_wtime; d->sio.utime = file_utime; if (log_level >= 3) { dev_log(d); log_puts(": started\n"); } #endif timo_add(&d->sio.watchdog, WATCHDOG_USEC); } void dev_sio_stop(struct dev *d) { if (!sio_eof(d->sio.hdl) && !sio_stop(d->sio.hdl)) { if (log_level >= 1) { dev_log(d); log_puts(": failed to stop device\n"); } return; } #ifdef DEBUG if (log_level >= 3) { dev_log(d); log_puts(": stopped, load avg = "); log_puti(d->sio.sum_utime / 1000); log_puts(" / "); log_puti(d->sio.sum_wtime / 1000); log_puts("\n"); } #endif timo_del(&d->sio.watchdog); } int dev_sio_pollfd(void *arg, struct pollfd *pfd) { struct dev *d = arg; int events; events = (d->sio.cstate == DEV_SIO_READ) ? POLLIN : POLLOUT; return sio_pollfd(d->sio.hdl, pfd, events); } int dev_sio_revents(void *arg, struct pollfd *pfd) { struct dev *d = arg; int events; events = sio_revents(d->sio.hdl, pfd); #ifdef DEBUG d->sio.events = events; #endif return events; } void dev_sio_run(void *arg) { struct dev *d = arg; unsigned char *data, *base; unsigned int n; /* * sio_read() and sio_write() would block at the end of the * cycle so we *must* return and restart poll()'ing. Otherwise * we may trigger dev_cycle() which would make all clients * underrun (ex, on a play-only device) */ for (;;) { if (d->pstate != DEV_RUN) return; switch (d->sio.cstate) { case DEV_SIO_READ: #ifdef DEBUG if (!(d->sio.events & POLLIN)) { dev_log(d); log_puts(": recording, but POLLIN not set\n"); panic(); } if (d->sio.todo == 0) { dev_log(d); log_puts(": can't read data\n"); panic(); } if (d->prime > 0) { dev_log(d); log_puts(": unexpected data\n"); panic(); } #endif base = d->decbuf ? d->decbuf : (unsigned char *)d->rbuf; data = base + d->rchan * d->round * d->par.bps - d->sio.todo; n = sio_read(d->sio.hdl, data, d->sio.todo); d->sio.todo -= n; #ifdef DEBUG if (log_level >= 4) { dev_log(d); log_puts(": read "); log_putu(n); log_puts(": bytes, todo "); log_putu(d->sio.todo); log_puts("/"); log_putu(d->round * d->rchan * d->par.bps); log_puts("\n"); } #endif if (d->sio.todo > 0) return; #ifdef DEBUG d->sio.rused -= d->round; if (log_level >= 2) { if (d->sio.rused >= d->round) { dev_log(d); log_puts(": rec hw xrun, rused = "); log_puti(d->sio.rused); log_puts("/"); log_puti(d->bufsz); log_puts("\n"); } if (d->sio.rused < 0 || d->sio.rused >= d->bufsz) { dev_log(d); log_puts(": out of bounds rused = "); log_puti(d->sio.rused); log_puts("/"); log_puti(d->bufsz); log_puts("\n"); } } #endif d->sio.cstate = DEV_SIO_CYCLE; break; case DEV_SIO_CYCLE: timo_del(&d->sio.watchdog); timo_add(&d->sio.watchdog, WATCHDOG_USEC); #ifdef DEBUG /* * check that we're called at cycle boundary: * either after a recorded block, or when POLLOUT is * raised */ if (!((d->mode & MODE_REC) && d->prime == 0) && !(d->sio.events & POLLOUT)) { dev_log(d); log_puts(": cycle not at block boundary\n"); panic(); } #endif dev_cycle(d); if (d->mode & MODE_PLAY) { d->sio.cstate = DEV_SIO_WRITE; d->sio.todo = d->round * d->pchan * d->par.bps; break; } else { d->sio.cstate = DEV_SIO_READ; d->sio.todo = d->round * d->rchan * d->par.bps; return; } case DEV_SIO_WRITE: #ifdef DEBUG if (d->sio.todo == 0) { dev_log(d); log_puts(": can't write data\n"); panic(); } #endif base = d->encbuf ? d->encbuf : (unsigned char *)DEV_PBUF(d); data = base + d->pchan * d->round * d->par.bps - d->sio.todo; n = sio_write(d->sio.hdl, data, d->sio.todo); d->sio.todo -= n; #ifdef DEBUG if (log_level >= 4) { dev_log(d); log_puts(": wrote "); log_putu(n); log_puts(" bytes, todo "); log_putu(d->sio.todo); log_puts("/"); log_putu(d->round * d->pchan * d->par.bps); log_puts("\n"); } #endif if (d->sio.todo > 0) return; #ifdef DEBUG d->sio.pused += d->round; if (log_level >= 2) { if (d->prime == 0 && d->sio.pused <= d->bufsz - d->round) { dev_log(d); log_puts(": play hw xrun, pused = "); log_puti(d->sio.pused); log_puts("/"); log_puti(d->bufsz); log_puts("\n"); } if (d->sio.pused < 0 || d->sio.pused > d->bufsz) { /* device driver or libsndio bug */ dev_log(d); log_puts(": out of bounds pused = "); log_puti(d->sio.pused); log_puts("/"); log_puti(d->bufsz); log_puts("\n"); } } #endif d->poffs += d->round; if (d->poffs == d->psize) d->poffs = 0; if ((d->mode & MODE_REC) && d->prime == 0) { d->sio.cstate = DEV_SIO_READ; d->sio.todo = d->round * d->rchan * d->par.bps; } else d->sio.cstate = DEV_SIO_CYCLE; return; } } } void dev_sio_hup(void *arg) { struct dev *d = arg; #ifdef DEBUG if (log_level >= 2) { dev_log(d); log_puts(": disconnected\n"); } #endif dev_close(d); } sndio-1.5.0/sndiod/siofile.h010066400017510001751000000025411332662053300144320ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include "file.h" struct dev; struct dev_sio { struct sio_hdl *hdl; unsigned int todo; #ifdef DEBUG long long wtime, utime; long long sum_wtime, sum_utime; int pused, rused, events; #endif struct file *file; #define DEV_SIO_READ 0 #define DEV_SIO_CYCLE 1 #define DEV_SIO_WRITE 2 int cstate; struct timo watchdog; }; int dev_sio_open(struct dev *); void dev_sio_close(struct dev *); void dev_sio_log(struct dev *); void dev_sio_start(struct dev *); void dev_sio_stop(struct dev *); #endif /* !defined(SIOFILE_H) */ sndio-1.5.0/sndiod/sndiod.8010066400017510001751000000351461332662053300142070ustar00alexalex.\" $OpenBSD$ .\" .\" Copyright (c) 2006-2012 Alexandre Ratchov .\" .\" 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$ .Dt SNDIOD 8 .Os .Sh NAME .Nm sndiod .Nd audio/MIDI server .Sh SYNOPSIS .Nm sndiod .Bk -words .Op Fl d .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 j Ar flag .Op Fl L Ar addr .Op Fl m Ar mode .Op Fl q Ar port .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 w Ar flag .Op Fl z Ar nframes .Ek .Sh DESCRIPTION The .Nm daemon is an intermediate layer between audio or MIDI programs and the hardware. It performs the necessary audio processing to allow any program to work on any supported hardware. By default, .Nm accepts connections from programs running on the same system only; it initializes only when programs are using its services, allowing .Nm to consume a negligible amount of system resources the rest of the time. Systems with no audio hardware can use .Nm to keep hot-pluggable devices usable by default at virtually no cost. .Pp .Nm operates as follows: it exposes at least one .Em sub-device that any number of audio programs can connect to and use as if it was audio hardware. During playback, .Nm receives audio data concurrently from all programs, mixes it and sends the result to the hardware device. Similarly, during recording it duplicates audio data recorded from the device and sends it to all programs. Since audio data flows through the .Nm process, it has the opportunity to process audio data on the fly: .Pp .Bl -bullet -offset indent -compact .It Change the sound encoding to overcome incompatibilities between software and hardware. .It Route the sound from one channel to another, join stereo or split mono. .It Control the per-application playback volume as well as the master volume. .It Monitor the sound being played, allowing one program to record what other programs play. .El .Pp Processing is configured on a per sub-device basis, meaning that the sound of all programs connected to the same sub-device will be processed according to the same configuration. Multiple sub-devices can be defined, allowing multiple configurations to coexist. The user selects the configuration a given program will use by selecting the sub-device the program uses. .Pp .Nm exposes MIDI thru boxes (hubs), allowing programs to send MIDI messages to each other or to hardware MIDI ports in a uniform way. .Pp Finally, .Nm exposes a control MIDI port usable for: .Pp .Bl -bullet -offset indent -compact .It Volume control. .It Common clock source for audio and MIDI programs. .It Start, stop and relocate groups of audio programs. .El .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Ar flag Control whether .Nm opens the audio device or the MIDI port only when needed or keeps it open all the time. If the flag is .Va on then the audio device or MIDI port 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 audio device, or the device to be disconnected. The default is .Va off . .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. The default is 7680 or twice the block size .Pq Fl z , if the block size is set. .It Xo .Fl C Ar min : Ns Ar max , .Fl c Ar min : Ns Ar max .Xc The range of channel numbers for recording and playback directions, respectively any client is allowed to use. This is a subset of the audio device channels. The default is 0:1, i.e. stereo. .It Fl d Enable debugging to standard error, and do not disassociate from the controlling terminal. Can be specified multiple times to further increase log verbosity. .It Fl e Ar enc Attempt to configure the device to use this encoding. The default is .Va s16 . Encoding names use the following scheme: signedness .Po .Va s or .Va u .Pc followed by the precision in bits, the byte-order .Po .Va le or .Va be .Pc , the number of bytes per sample, and the alignment .Po .Va msb or .Va lsb .Pc . Only the signedness and the precision are mandatory. Examples: .Va u8 , s16le , s24le3 , s24le4lsb . .It Fl f Ar device Add this .Xr sndio 7 audio device to devices used for playing and/or recording. Preceding per-device options .Pq Fl aberwz apply to this device. Sub-devices .Pq Fl s that are applied after will be attached to this device. Device mode and parameters are determined from sub-devices attached to it. .It Fl j Ar flag Control whether program channels are joined or expanded if the number of channels requested by a program is not equal to the device number of channels. If the flag is .Va off then client 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 client channel may be sent on multiple device channels, or multiple client channels may be sent to a single device channel. For instance, this feature could be used for mono to stereo conversions. The default is .Ar on . .It Fl L Ar addr Specify a local network address .Nm should listen on; .Nm will listen on TCP port 11025+n, where n is the unit number specified with .Fl U . Without this option, .Nm listens on the .Ux Ns -domain socket only, and is not reachable from any network. If the option argument is .Sq - then .Nm will accept connections from any address. As the communication is not secure, this option is only suitable for local networks where all hosts and users are trusted. .It Fl m Ar mode Set the sub-device 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 sub-device cannot be used for both recording and monitoring. The default is .Ar play , Ns Ar rec (i.e. full-duplex). .It Fl q Ar port Expose the given MIDI port. This allows multiple programs to share the port. .It Fl r Ar rate Attempt to force the device to use this sample rate in Hertz. The default is 48000. .It Fl s Ar name Add .Ar name to the list of sub-devices to expose. 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 sub-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 clients are controlled by MIDI Machine Control (MMC) messages received by .Nm . If the mode is .Va off (the default), then programs are not affected by MMC messages. If the mode is .Va slave , then programs 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 programs. .It Fl U Ar unit Unit number. Each .Nm server instance has an unique unit number, used in .Xr sndio 7 device names. The default is 0. .It Fl v Ar volume Software volume attenuation of playback. The value must be between 1 and 127, corresponding to \-42dB and \-0dB attenuation in 1/3dB steps. Clients inherit this parameter. Reducing the volume in advance allows a client's volume to stay independent from the number of clients as long as their number is small enough. 18 volume units (i.e. \-6dB attenuation) allows the number of playback programs to be doubled. The default is 118 i.e. \-3dB. .It Fl w Ar flag Control .Nm behaviour when the maximum volume of the hardware is reached and a new program starts playing. This happens only when volumes are not properly set using the .Fl v option. If the flag is .Va on , then the master volume is automatically adjusted to avoid clipping. Using .Va off makes sense in the rare situation where all programs lower their volumes. The default is .Va on . .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 sub-device 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, 100 ticks per second at 48000Hz corresponds to a 480 frame block size. The default is 960 or half of the buffer size .Pq Fl b , if the buffer size is set. .El .Pp On the command line, per-device parameters .Pq Fl aberwz must precede the device definition .Pq Fl f , and per-sub-device parameters .Pq Fl Ccjmtvx must precede the sub-device definition .Pq Fl s . Sub-device definitions .Pq Fl s must follow the definition of the device .Pq Fl f to which they are attached. .Pp If no audio devices .Pq Fl f are specified, settings are applied as if the default device is specified. If no sub-devices .Pq Fl s are specified for a device, a default sub-device is created attached to it. If a device .Pq Fl f is defined twice, both definitions are merged: parameters of the first one are used but sub-devices .Pq Fl s of both definitions are created. The default .Xr sndio 7 device used by .Nm is .Pa rsnd/0 , and the default sub-device exposed by .Nm is .Pa snd/0 . .Pp If .Nm is sent .Dv SIGHUP , .Dv SIGINT or .Dv SIGTERM , it terminates. .Pp By default, when the program cannot accept recorded data fast enough or cannot provide data to play fast enough, the program is paused, i.e. samples that cannot be written are discarded and samples that cannot be read are replaced by silence. If a sub-device is created with the .Fl t option, then recorded samples are discarded, but the same amount of silence will be written once the program 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 program is unblocked. This ensures proper synchronization between programs. .Sh MIDI CONTROL .Nm creates a MIDI port with the same name as the exposed audio sub-device to which MIDI programs can connect. .Nm exposes the audio device clock and allows audio device properties to be controlled through MIDI. .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 The master volume can be changed using the standard master volume system exclusive message. .Pp Streams created with the .Fl t option are controlled by the following MMC messages: .Bl -tag -width relocateXXX -offset indent .It relocate This message is ignored by audio .Nm clients, but 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 .Pq Nm sndiod 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. Client streams 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 .Nm device clock using MTC, allowing non-audio software or hardware to be synchronized to the audio stream. Maximum accuracy is achieved when the number of blocks per second is equal to one of the standard MTC clock rates (96, 100 and 120Hz). The following sample rates .Pq Fl r and block sizes .Pq Fl z are recommended: .Pp .Bl -bullet -offset indent -compact .It 44100Hz, 441 frames (MTC rate is 100Hz) .It 48000Hz, 400 frames (MTC rate is 120Hz) .It 48000Hz, 480 frames (MTC rate is 100Hz) .It 48000Hz, 500 frames (MTC rate is 96Hz) .El .Pp For instance, the following command will create two devices: the default .Va snd/0 and a MIDI-controlled .Va snd/0.mmc : .Bd -literal -offset indent $ sndiod -r 48000 -z 400 -s default -t slave -s mmc .Ed .Pp Streams connected to .Va snd/0 behave normally, while streams connected to .Va snd/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. .Sh EXAMPLES Start server using default parameters, creating an additional sub-device for output to channels 2:3 only (rear speakers on most cards), exposing the .Pa snd/0 and .Pa snd/0.rear devices: .Bd -literal -offset indent $ sndiod -s default -c 2:3 -s rear .Ed .Pp Start server creating the default sub-device with low volume and an additional sub-device for high volume output, exposing the .Pa snd/0 and .Pa snd/0.max devices: .Bd -literal -offset indent $ sndiod -v 65 -s default -v 127 -s max .Ed .Pp Start server 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 $ sndiod -r 48000 -b 480 -z 240 .Ed .Sh SEE ALSO .Xr sndio 7 .Sh BUGS 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. Processing precision can be increased to 24-bit at compilation time though. .Pp If .Fl a Ar off is used, .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 an audio device, creating a deadlock. There's nothing to prevent the user from shooting himself in the foot by creating such a deadlock. sndio-1.5.0/sndiod/sndiod.c010066400017510001751000000261241332662053300142560ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amsg.h" #include "defs.h" #include "dev.h" #include "file.h" #include "listen.h" #include "midi.h" #include "opt.h" #include "sock.h" #include "utils.h" #include "bsd-compat.h" /* * unprivileged user name */ #ifndef SNDIO_USER #define SNDIO_USER "_sndio" #endif /* * priority when run as root */ #ifndef SNDIO_PRIO #define SNDIO_PRIO (-20) #endif /* * sample rate if no ``-r'' is used */ #ifndef DEFAULT_RATE #define DEFAULT_RATE 48000 #endif /* * block size if neither ``-z'' nor ``-b'' is used */ #ifndef DEFAULT_ROUND #define DEFAULT_ROUND 960 #endif /* * buffer size if neither ``-z'' nor ``-b'' is used */ #ifndef DEFAULT_BUFSZ #define DEFAULT_BUFSZ 7680 #endif /* * default device in server mode */ #ifndef DEFAULT_DEV #define DEFAULT_DEV "rsnd/default" #endif void sigint(int); void opt_ch(int *, int *); void opt_enc(struct aparams *); int opt_mmc(void); int opt_onoff(void); int getword(char *, char **); unsigned int opt_mode(void); void getbasepath(char *); void setsig(void); void unsetsig(void); struct dev *mkdev(char *, struct aparams *, int, int, int, int, int, int); struct port *mkport(char *, int); struct opt *mkopt(char *, struct dev *, int, int, int, int, int, int, int, int); unsigned int log_level = 0; volatile sig_atomic_t quit_flag = 0; char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] " "[-C min:max] [-c min:max] [-e enc]\n\t" "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t" "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n"; /* * SIGINT handler, it raises the quit flag. If the flag is already set, * that means that the last SIGINT was not handled, because the process * is blocked somewhere, so exit. */ void sigint(int s) { if (quit_flag) _exit(1); quit_flag = 1; } void opt_ch(int *rcmin, int *rcmax) { char *next, *end; long cmin, cmax; errno = 0; cmin = strtol(optarg, &next, 10); if (next == optarg || *next != ':') goto failed; cmax = strtol(++next, &end, 10); if (end == next || *end != '\0') goto failed; if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX) goto failed; *rcmin = cmin; *rcmax = cmax; return; failed: errx(1, "%s: bad channel range", optarg); } void opt_enc(struct aparams *par) { int len; len = aparams_strtoenc(par, optarg); if (len == 0 || optarg[len] != '\0') errx(1, "%s: bad encoding", optarg); } int opt_mmc(void) { if (strcmp("off", optarg) == 0) return 0; if (strcmp("slave", optarg) == 0) return 1; errx(1, "%s: off/slave expected", optarg); } int opt_onoff(void) { if (strcmp("off", optarg) == 0) return 0; if (strcmp("on", optarg) == 0) return 1; errx(1, "%s: on/off expected", optarg); } int getword(char *word, char **str) { char *p = *str; for (;;) { if (*word == '\0') break; if (*word++ != *p++) return 0; } if (*p == ',' || *p == '\0') { *str = p; return 1; } return 0; } unsigned int opt_mode(void) { unsigned int mode = 0; char *p = optarg; for (;;) { if (getword("play", &p)) { mode |= MODE_PLAY; } else if (getword("rec", &p)) { mode |= MODE_REC; } else if (getword("mon", &p)) { mode |= MODE_MON; } else if (getword("midi", &p)) { mode |= MODE_MIDIMASK; } else errx(1, "%s: bad mode", optarg); if (*p == '\0') break; p++; } if (mode == 0) errx(1, "empty mode"); return mode; } void setsig(void) { struct sigaction sa; quit_flag = 0; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = sigint; if (sigaction(SIGINT, &sa, NULL) < 0) err(1, "sigaction(int) failed"); if (sigaction(SIGTERM, &sa, NULL) < 0) err(1, "sigaction(term) failed"); if (sigaction(SIGHUP, &sa, NULL) < 0) err(1, "sigaction(hup) failed"); } void unsetsig(void) { struct sigaction sa; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; if (sigaction(SIGHUP, &sa, NULL) < 0) err(1, "unsetsig(hup): sigaction failed"); if (sigaction(SIGTERM, &sa, NULL) < 0) err(1, "unsetsig(term): sigaction failed"); if (sigaction(SIGINT, &sa, NULL) < 0) err(1, "unsetsig(int): sigaction failed"); } void getbasepath(char *base) { uid_t uid; struct stat sb; mode_t mask, omask; uid = geteuid(); if (uid == 0) { mask = 022; snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR); } else { mask = 077; snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR "-%u", uid); } omask = umask(mask); if (mkdir(base, 0777) < 0) { if (errno != EEXIST) err(1, "mkdir(\"%s\")", base); } umask(omask); if (stat(base, &sb) < 0) err(1, "stat(\"%s\")", base); if (!S_ISDIR(sb.st_mode)) errx(1, "%s is not a directory", base); if (sb.st_uid != uid || (sb.st_mode & mask) != 0) errx(1, "%s has wrong permissions", base); } struct dev * mkdev(char *path, struct aparams *par, int mode, int bufsz, int round, int rate, int hold, int autovol) { struct dev *d; for (d = dev_list; d != NULL; d = d->next) { if (strcmp(d->path, path) == 0) return d; } if (!bufsz && !round) { round = DEFAULT_ROUND; bufsz = DEFAULT_BUFSZ; } else if (!bufsz) { bufsz = round * 2; } else if (!round) round = bufsz / 2; d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol); if (d == NULL) exit(1); return d; } struct port * mkport(char *path, int hold) { struct port *c; for (c = port_list; c != NULL; c = c->next) { if (strcmp(c->path, path) == 0) return c; } c = port_new(path, MODE_MIDIMASK, hold); if (c == NULL) exit(1); return c; } struct opt * mkopt(char *path, struct dev *d, int pmin, int pmax, int rmin, int rmax, int mode, int vol, int mmc, int dup) { struct opt *o; o = opt_new(d, path, pmin, pmax, rmin, rmax, MIDI_TO_ADATA(vol), mmc, dup, mode); if (o == NULL) return NULL; dev_adjpar(d, o->mode, o->pmax, o->rmax); return o; } int main(int argc, char **argv) { int c, background, unit; int pmin, pmax, rmin, rmax; char base[SOCKPATH_MAX], path[SOCKPATH_MAX]; unsigned int mode, dup, mmc, vol; unsigned int hold, autovol, bufsz, round, rate; const char *str; struct aparams par; struct dev *d; struct port *p; struct listen *l; struct passwd *pw; struct tcpaddr { char *host; struct tcpaddr *next; } *tcpaddr_list, *ta; atexit(log_flush); /* * global options defaults */ vol = 118; dup = 1; mmc = 0; hold = 0; autovol = 1; bufsz = 0; round = 0; rate = DEFAULT_RATE; unit = 0; background = 1; pmin = 0; pmax = 1; rmin = 0; rmax = 1; aparams_init(&par); mode = MODE_PLAY | MODE_REC; tcpaddr_list = NULL; while ((c = getopt(argc, argv, "a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) { switch (c) { case 'd': log_level++; background = 0; break; case 'U': unit = strtonum(optarg, 0, 15, &str); if (str) errx(1, "%s: unit number is %s", optarg, str); break; case 'L': ta = xmalloc(sizeof(struct tcpaddr)); ta->host = optarg; ta->next = tcpaddr_list; tcpaddr_list = ta; break; case 'm': mode = opt_mode(); break; case 'j': dup = opt_onoff(); break; case 't': mmc = opt_mmc(); break; case 'c': opt_ch(&pmin, &pmax); break; case 'C': opt_ch(&rmin, &rmax); break; case 'e': opt_enc(&par); break; case 'r': rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str); if (str) errx(1, "%s: rate is %s", optarg, str); break; case 'v': vol = strtonum(optarg, 0, MIDI_MAXCTL, &str); if (str) errx(1, "%s: volume is %s", optarg, str); break; case 's': if ((d = dev_list) == NULL) { d = mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol); } if (mkopt(optarg, d, pmin, pmax, rmin, rmax, mode, vol, mmc, dup) == NULL) return 1; break; case 'q': mkport(optarg, hold); break; case 'a': hold = opt_onoff(); break; case 'w': autovol = opt_onoff(); break; case 'b': bufsz = strtonum(optarg, 1, RATE_MAX, &str); if (str) errx(1, "%s: buffer size is %s", optarg, str); break; case 'z': round = strtonum(optarg, 1, SHRT_MAX, &str); if (str) errx(1, "%s: block size is %s", optarg, str); break; case 'f': mkdev(optarg, &par, 0, bufsz, round, rate, hold, autovol); break; default: fputs(usagestr, stderr); return 1; } } argc -= optind; argv += optind; if (argc > 0) { fputs(usagestr, stderr); return 1; } if (dev_list == NULL) mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol); for (d = dev_list; d != NULL; d = d->next) { if (opt_byname(d, "default")) continue; if (mkopt("default", d, pmin, pmax, rmin, rmax, mode, vol, mmc, dup) == NULL) return 1; } setsig(); filelist_init(); if (geteuid() == 0) { if ((pw = getpwnam(SNDIO_USER)) == NULL) errx(1, "unknown user %s", SNDIO_USER); } else pw = NULL; getbasepath(base); snprintf(path, SOCKPATH_MAX, "%s/" SOCKPATH_FILE "%u", base, unit); if (!listen_new_un(path)) return 1; for (ta = tcpaddr_list; ta != NULL; ta = ta->next) { if (!listen_new_tcp(ta->host, AUCAT_PORT + unit)) return 1; } for (l = listen_list; l != NULL; l = l->next) { if (!listen_init(l)) return 1; } midi_init(); for (p = port_list; p != NULL; p = p->next) { if (!port_init(p)) return 1; } for (d = dev_list; d != NULL; d = d->next) { if (!dev_init(d)) return 1; } if (background) { log_flush(); log_level = 0; if (daemon(0, 0) < 0) err(1, "daemon"); } if (pw != NULL) { if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) < 0) err(1, "setpriority"); if (setgroups(1, &pw->pw_gid) || setgid(pw->pw_gid) || setuid(pw->pw_uid)) err(1, "cannot drop privileges"); } for (;;) { if (quit_flag) break; if (!file_poll()) break; } while (listen_list != NULL) listen_close(listen_list); while (sock_list != NULL) sock_close(sock_list); for (d = dev_list; d != NULL; d = d->next) dev_done(d); for (p = port_list; p != NULL; p = p->next) port_done(p); while (file_poll()) ; /* nothing */ midi_done(); while (dev_list) dev_del(dev_list); while (port_list) port_del(port_list); while (tcpaddr_list) { ta = tcpaddr_list; tcpaddr_list = ta->next; xfree(ta); } rmdir(base); filelist_done(); unsetsig(); return 0; } sndio-1.5.0/sndiod/sock.c010066400017510001751000000735251332662053300137440ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include "abuf.h" #include "defs.h" #include "dev.h" #include "file.h" #include "midi.h" #include "opt.h" #include "sock.h" #include "utils.h" #include "bsd-compat.h" void sock_log(struct sock *); void sock_close(struct sock *); void sock_slot_fill(void *); void sock_slot_flush(void *); void sock_slot_eof(void *); void sock_slot_onmove(void *); void sock_slot_onvol(void *); void sock_midi_imsg(void *, unsigned char *, int); void sock_midi_omsg(void *, unsigned char *, int); void sock_midi_fill(void *, int); struct sock *sock_new(int); void sock_exit(void *); int sock_fdwrite(struct sock *, void *, int); int sock_fdread(struct sock *, void *, int); int sock_rmsg(struct sock *); int sock_wmsg(struct sock *); int sock_rdata(struct sock *); int sock_wdata(struct sock *); int sock_setpar(struct sock *); int sock_auth(struct sock *); int sock_hello(struct sock *); int sock_execmsg(struct sock *); int sock_buildmsg(struct sock *); int sock_read(struct sock *); int sock_write(struct sock *); int sock_pollfd(void *, struct pollfd *); int sock_revents(void *, struct pollfd *); void sock_in(void *); void sock_out(void *); void sock_hup(void *); struct fileops sock_fileops = { "sock", sock_pollfd, sock_revents, sock_in, sock_out, sock_hup }; struct slotops sock_slotops = { sock_slot_onmove, sock_slot_onvol, sock_slot_fill, sock_slot_flush, sock_slot_eof, sock_exit }; struct midiops sock_midiops = { sock_midi_imsg, sock_midi_omsg, sock_midi_fill, sock_exit }; struct sock *sock_list = NULL; unsigned int sock_sesrefs = 0; /* connections to the session */ uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */ void sock_log(struct sock *f) { #ifdef DEBUG static char *rstates[] = { "ridl", "rmsg", "rdat", "rret" }; static char *wstates[] = { "widl", "wmsg", "wdat" }; #endif if (f->slot) slot_log(f->slot); else if (f->midi) midi_log(f->midi); else log_puts("sock"); #ifdef DEBUG if (log_level >= 3) { log_puts(","); log_puts(rstates[f->rstate]); log_puts(","); log_puts(wstates[f->wstate]); } #endif } void sock_close(struct sock *f) { struct sock **pf; for (pf = &sock_list; *pf != f; pf = &(*pf)->next) { #ifdef DEBUG if (*pf == NULL) { log_puts("sock_close: not on list\n"); panic(); } #endif } *pf = f->next; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": closing\n"); } #endif if (f->pstate > SOCK_AUTH) sock_sesrefs--; if (f->slot) { slot_del(f->slot); f->slot = NULL; } if (f->midi) { midi_del(f->midi); f->midi = NULL; } if (f->port) { port_unref(f->port); f->port = NULL; } file_del(f->file); close(f->fd); file_slowaccept = 0; xfree(f); } void sock_slot_fill(void *arg) { struct sock *f = arg; struct slot *s = f->slot; f->fillpending += s->round; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": fill, rmax -> "); log_puti(f->rmax); log_puts(", pending -> "); log_puti(f->fillpending); log_puts("\n"); } #endif } void sock_slot_flush(void *arg) { struct sock *f = arg; struct slot *s = f->slot; f->wmax += s->round * s->sub.bpf; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": flush, wmax -> "); log_puti(f->wmax); log_puts("\n"); } #endif } void sock_slot_eof(void *arg) { struct sock *f = arg; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": stopped\n"); } #endif f->stoppending = 1; } void sock_slot_onmove(void *arg) { struct sock *f = (struct sock *)arg; struct slot *s = f->slot; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": onmove: delta -> "); log_puti(s->delta); log_puts("\n"); } #endif if (s->pstate != SOCK_START) return; f->tickpending++; } void sock_slot_onvol(void *arg) { struct sock *f = (struct sock *)arg; struct slot *s = f->slot; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": onvol: vol -> "); log_puti(s->vol); log_puts("\n"); } #endif if (s->pstate != SOCK_START) return; } void sock_midi_imsg(void *arg, unsigned char *msg, int size) { struct sock *f = arg; midi_send(f->midi, msg, size); } void sock_midi_omsg(void *arg, unsigned char *msg, int size) { struct sock *f = arg; midi_out(f->midi, msg, size); } void sock_midi_fill(void *arg, int count) { struct sock *f = arg; f->fillpending += count; } struct sock * sock_new(int fd) { struct sock *f; f = xmalloc(sizeof(struct sock)); f->pstate = SOCK_AUTH; f->slot = NULL; f->port = NULL; f->midi = NULL; f->tickpending = 0; f->fillpending = 0; f->stoppending = 0; f->wstate = SOCK_WIDLE; f->wtodo = 0xdeadbeef; f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); f->wmax = f->rmax = 0; f->lastvol = -1; f->file = file_new(&sock_fileops, f, "sock", 1); f->fd = fd; if (f->file == NULL) { xfree(f); return NULL; } f->next = sock_list; sock_list = f; return f; } void sock_exit(void *arg) { struct sock *f = (struct sock *)arg; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": exit\n"); } #endif sock_close(f); } /* * write on the socket fd and handle errors */ int sock_fdwrite(struct sock *f, void *data, int count) { int n; n = write(f->fd, data, count); if (n < 0) { #ifdef DEBUG if (errno == EFAULT) { log_puts("sock_fdwrite: fault\n"); panic(); } #endif if (errno != EAGAIN) { if (log_level >= 1) { sock_log(f); log_puts(": write filed, errno = "); log_puti(errno); log_puts("\n"); } sock_close(f); } else { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": write blocked\n"); } #endif } return 0; } if (n == 0) { sock_close(f); return 0; } return n; } /* * read from the socket fd and handle errors */ int sock_fdread(struct sock *f, void *data, int count) { int n; n = read(f->fd, data, count); if (n < 0) { #ifdef DEBUG if (errno == EFAULT) { log_puts("sock_fdread: fault\n"); panic(); } #endif if (errno != EAGAIN) { if (log_level >= 1) { sock_log(f); log_puts(": read failed, errno = "); log_puti(errno); log_puts("\n"); } sock_close(f); } else { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": read blocked\n"); } #endif } return 0; } if (n == 0) { sock_close(f); return 0; } return n; } /* * read the next message into f->rmsg, return 1 on success */ int sock_rmsg(struct sock *f) { int n; char *data; #ifdef DEBUG if (f->rtodo == 0) { sock_log(f); log_puts(": sock_rmsg: nothing to read\n"); panic(); } #endif data = (char *)&f->rmsg + sizeof(struct amsg) - f->rtodo; n = sock_fdread(f, data, f->rtodo); if (n == 0) return 0; if (n < f->rtodo) { f->rtodo -= n; return 0; } f->rtodo = 0; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": read full message\n"); } #endif return 1; } /* * write the message in f->rmsg, return 1 on success */ int sock_wmsg(struct sock *f) { int n; char *data; #ifdef DEBUG if (f->wtodo == 0) { sock_log(f); log_puts(": sock_wmsg: already written\n"); } #endif data = (char *)&f->wmsg + sizeof(struct amsg) - f->wtodo; n = sock_fdwrite(f, data, f->wtodo); if (n == 0) return 0; if (n < f->wtodo) { f->wtodo -= n; return 0; } f->wtodo = 0; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": wrote full message\n"); } #endif return 1; } /* * read data into the slot/midi ring buffer */ int sock_rdata(struct sock *f) { unsigned char midibuf[MIDI_BUFSZ]; unsigned char *data; int n, count; #ifdef DEBUG if (f->rtodo == 0) { sock_log(f); log_puts(": data block already read\n"); panic(); } #endif while (f->rtodo > 0) { if (f->slot) data = abuf_wgetblk(&f->slot->mix.buf, &count); else { data = midibuf; count = MIDI_BUFSZ; } if (count > f->rtodo) count = f->rtodo; n = sock_fdread(f, data, count); if (n == 0) return 0; f->rtodo -= n; if (f->slot) abuf_wcommit(&f->slot->mix.buf, n); else midi_in(f->midi, midibuf, n); } #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": read complete block\n"); } #endif if (f->slot) slot_write(f->slot); return 1; } /* * write data to the slot/midi ring buffer */ int sock_wdata(struct sock *f) { static unsigned char dummy[AMSG_DATAMAX]; unsigned char *data = NULL; int n, count; #ifdef DEBUG if (f->wtodo == 0) { sock_log(f); log_puts(": attempted to write zero-sized data block\n"); panic(); } #endif if (f->pstate == SOCK_STOP) { while (f->wtodo > 0) { n = sock_fdwrite(f, dummy, f->wtodo); if (n == 0) return 0; f->wtodo -= n; } #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": zero-filled remaining block\n"); } #endif return 1; } while (f->wtodo > 0) { /* * f->slot and f->midi are set by sock_hello(), so * count is always properly initialized */ if (f->slot) data = abuf_rgetblk(&f->slot->sub.buf, &count); else if (f->midi) data = abuf_rgetblk(&f->midi->obuf, &count); if (count > f->wtodo) count = f->wtodo; n = sock_fdwrite(f, data, count); if (n == 0) return 0; f->wtodo -= n; if (f->slot) abuf_rdiscard(&f->slot->sub.buf, n); else if (f->midi) abuf_rdiscard(&f->midi->obuf, n); } if (f->slot) slot_read(f->slot); if (f->midi) midi_fill(f->midi); #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": wrote complete block\n"); } #endif return 1; } int sock_setpar(struct sock *f) { struct slot *s = f->slot; struct dev *d = s->dev; struct amsg_par *p = &f->rmsg.u.par; unsigned int min, max; uint32_t rate, appbufsz; uint16_t pchan, rchan; rchan = ntohs(p->rchan); pchan = ntohs(p->pchan); appbufsz = ntohl(p->appbufsz); rate = ntohl(p->rate); if (AMSG_ISSET(p->bits)) { if (p->bits < BITS_MIN || p->bits > BITS_MAX) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": "); log_putu(p->bits); log_puts(": bits out of bounds\n"); } #endif return 0; } if (AMSG_ISSET(p->bps)) { if (p->bps < ((p->bits + 7) / 8) || p->bps > 4) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": "); log_putu(p->bps); log_puts(": wrong bytes per sample\n"); } #endif return 0; } } else p->bps = APARAMS_BPS(p->bits); s->par.bits = p->bits; s->par.bps = p->bps; } if (AMSG_ISSET(p->sig)) s->par.sig = p->sig ? 1 : 0; if (AMSG_ISSET(p->le)) s->par.le = p->le ? 1 : 0; if (AMSG_ISSET(p->msb)) s->par.msb = p->msb ? 1 : 0; if (AMSG_ISSET(rchan) && (s->mode & MODE_RECMASK)) { if (rchan < 1) rchan = 1; else if (rchan > NCHAN_MAX) rchan = NCHAN_MAX; s->sub.nch = rchan; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": recording channels "); log_putu(s->opt->rmin); log_puts(":"); log_putu(s->opt->rmax); log_puts(" -> "); log_putu(s->opt->rmin); log_puts(":"); log_putu(s->opt->rmin + s->sub.nch - 1); log_puts("\n"); } #endif } if (AMSG_ISSET(pchan) && (s->mode & MODE_PLAY)) { if (pchan < 1) pchan = 1; else if (pchan > NCHAN_MAX) pchan = NCHAN_MAX; s->mix.nch = pchan; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": playback channels "); log_putu(s->opt->pmin); log_puts(":"); log_putu(s->opt->pmin + s->mix.nch - 1); log_puts(" -> "); log_putu(s->opt->pmin); log_puts(":"); log_putu(s->opt->pmax); log_puts("\n"); } #endif } if (AMSG_ISSET(rate)) { if (rate < RATE_MIN) rate = RATE_MIN; else if (rate > RATE_MAX) rate = RATE_MAX; s->round = dev_roundof(d, rate); s->rate = rate; if (!AMSG_ISSET(appbufsz)) { appbufsz = d->bufsz / d->round * s->round; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": "); log_putu(appbufsz); log_puts(" frame buffer\n"); } #endif } #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": "); log_putu(rate); log_puts("Hz sample rate, "); log_putu(s->round); log_puts(" frame blocks\n"); } #endif } if (AMSG_ISSET(p->xrun)) { if (p->xrun != XRUN_IGNORE && p->xrun != XRUN_SYNC && p->xrun != XRUN_ERROR) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": "); log_putx(p->xrun); log_puts(": bad xrun policy\n"); } #endif return 0; } s->xrun = p->xrun; if (s->opt->mmc && s->xrun == XRUN_IGNORE) s->xrun = XRUN_SYNC; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": 0x"); log_putx(s->xrun); log_puts(" xrun policy\n"); } #endif } if (AMSG_ISSET(appbufsz)) { rate = s->rate; min = 1; max = 1 + rate / d->round; min *= s->round; max *= s->round; appbufsz += s->round / 2; appbufsz -= appbufsz % s->round; if (appbufsz < min) appbufsz = min; if (appbufsz > max) appbufsz = max; s->appbufsz = appbufsz; #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": "); log_putu(s->appbufsz); log_puts(" frame buffer\n"); } #endif } return 1; } int sock_auth(struct sock *f) { struct amsg_auth *p = &f->rmsg.u.auth; if (sock_sesrefs == 0) { /* start a new session */ memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN); } else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) { /* another session is active, drop connection */ return 0; } sock_sesrefs++; f->pstate = SOCK_HELLO; return 1; } int sock_hello(struct sock *f) { struct amsg_hello *p = &f->rmsg.u.hello; struct port *c; struct dev *d; struct opt *opt; unsigned int mode; mode = ntohs(p->mode); #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": hello from <"); log_puts(p->who); log_puts(">, mode = "); log_putx(mode); log_puts(", ver "); log_putu(p->version); log_puts("\n"); } #endif if (p->version != AMSG_VERSION) { if (log_level >= 1) { sock_log(f); log_puts(": "); log_putu(p->version); log_puts(": unsupported protocol version\n"); } return 0; } switch (mode) { case MODE_MIDIIN: case MODE_MIDIOUT: case MODE_MIDIOUT | MODE_MIDIIN: case MODE_REC: case MODE_PLAY: case MODE_PLAY | MODE_REC: break; default: #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": "); log_putx(mode); log_puts(": unsupported mode\n"); } #endif return 0; } f->pstate = SOCK_INIT; f->port = NULL; if (mode & MODE_MIDIMASK) { f->slot = NULL; f->midi = midi_new(&sock_midiops, f, mode); if (f->midi == NULL) return 0; /* XXX: add 'devtype' to libsndio */ if (p->devnum < 16) { d = dev_bynum(p->devnum); if (d == NULL) return 0; midi_tag(f->midi, p->devnum); } else if (p->devnum < 32) { midi_tag(f->midi, p->devnum); } else if (p->devnum < 48) { c = port_bynum(p->devnum - 32); if (c == NULL || !port_ref(c)) return 0; f->port = c; midi_link(f->midi, c->midi); } else return 0; return 1; } d = dev_bynum(p->devnum); if (d == NULL) return 0; opt = opt_byname(d, p->opt); if (opt == NULL) return 0; f->slot = slot_new(d, opt, p->who, &sock_slotops, f, mode); if (f->slot == NULL) return 0; f->midi = NULL; return 1; } /* * execute the message in f->rmsg, return 1 on success */ int sock_execmsg(struct sock *f) { struct slot *s = f->slot; struct amsg *m = &f->rmsg; unsigned char *data; int size, ctl; switch (ntohl(m->cmd)) { case AMSG_DATA: #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": DATA message\n"); } #endif if (s != NULL && f->pstate != SOCK_START) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": DATA, wrong state\n"); } #endif sock_close(f); return 0; } if ((f->slot && !(f->slot->mode & MODE_PLAY)) || (f->midi && !(f->midi->mode & MODE_MIDIOUT))) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": DATA, input-only mode\n"); } #endif sock_close(f); return 0; } size = ntohl(m->u.data.size); if (size <= 0) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": zero size payload\n"); } #endif sock_close(f); return 0; } if (s != NULL && size % s->mix.bpf != 0) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": not aligned to frame\n"); } #endif sock_close(f); return 0; } if (s != NULL && size > f->ralign) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": size = "); log_puti(size); log_puts(": ralign = "); log_puti(f->ralign); log_puts(": not aligned to block\n"); } #endif sock_close(f); return 0; } f->rstate = SOCK_RDATA; f->rsize = f->rtodo = size; if (s != NULL) { f->ralign -= size; if (f->ralign == 0) f->ralign = s->round * s->mix.bpf; } if (f->rtodo > f->rmax) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": unexpected data, size = "); log_putu(size); log_puts(", rmax = "); log_putu(f->rmax); log_puts("\n"); } #endif sock_close(f); return 0; } f->rmax -= f->rtodo; if (f->rtodo == 0) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": zero-length data chunk\n"); } #endif sock_close(f); return 0; } break; case AMSG_START: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": START message\n"); } #endif if (f->pstate != SOCK_INIT || s == NULL) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": START, wrong state\n"); } #endif sock_close(f); return 0; } f->tickpending = 0; f->stoppending = 0; slot_start(s); if (s->mode & MODE_PLAY) { f->fillpending = s->appbufsz; f->ralign = s->round * s->mix.bpf; f->rmax = 0; } if (s->mode & MODE_RECMASK) { f->walign = s->round * s->sub.bpf; f->wmax = 0; } f->pstate = SOCK_START; f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); if (log_level >= 2) { slot_log(f->slot); log_puts(": "); log_putu(s->rate); log_puts("Hz, "); aparams_log(&s->par); if (s->mode & MODE_PLAY) { log_puts(", play "); log_puti(s->opt->pmin); log_puts(":"); log_puti(s->opt->pmin + s->mix.nch - 1); } if (s->mode & MODE_RECMASK) { log_puts(", rec "); log_puti(s->opt->rmin); log_puts(":"); log_puti(s->opt->rmin + s->sub.nch - 1); } log_puts(", "); log_putu(s->appbufsz / s->round); log_puts(" blocks of "); log_putu(s->round); log_puts(" frames\n"); } break; case AMSG_STOP: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": STOP message\n"); } #endif if (f->pstate != SOCK_START) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": STOP, wrong state\n"); } #endif sock_close(f); return 0; } f->rmax = 0; if (!(s->mode & MODE_PLAY)) f->stoppending = 1; f->pstate = SOCK_STOP; f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); if (s->mode & MODE_PLAY) { if (f->ralign < s->round * s->mix.bpf) { data = abuf_wgetblk(&s->mix.buf, &size); #ifdef DEBUG if (size < f->ralign) { sock_log(f); log_puts(": unaligned stop, size = "); log_putu(size); log_puts(", ralign = "); log_putu(f->ralign); log_puts("\n"); panic(); } #endif memset(data, 0, f->ralign); abuf_wcommit(&s->mix.buf, f->ralign); f->ralign = s->round * s->mix.bpf; } } slot_stop(s); break; case AMSG_SETPAR: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": SETPAR message\n"); } #endif if (f->pstate != SOCK_INIT || s == NULL) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": SETPAR, wrong state\n"); } #endif sock_close(f); return 0; } if (!sock_setpar(f)) { sock_close(f); return 0; } f->rtodo = sizeof(struct amsg); f->rstate = SOCK_RMSG; break; case AMSG_GETPAR: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": GETPAR message\n"); } #endif if (f->pstate != SOCK_INIT || s == NULL) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": GETPAR, wrong state\n"); } #endif sock_close(f); return 0; } AMSG_INIT(m); m->cmd = htonl(AMSG_GETPAR); m->u.par.legacy_mode = s->mode; m->u.par.xrun = s->xrun; m->u.par.bits = s->par.bits; m->u.par.bps = s->par.bps; m->u.par.sig = s->par.sig; m->u.par.le = s->par.le; m->u.par.msb = s->par.msb; if (s->mode & MODE_PLAY) m->u.par.pchan = htons(s->mix.nch); if (s->mode & MODE_RECMASK) m->u.par.rchan = htons(s->sub.nch); m->u.par.rate = htonl(s->rate); m->u.par.appbufsz = htonl(s->appbufsz); m->u.par.bufsz = htonl(SLOT_BUFSZ(s)); m->u.par.round = htonl(s->round); f->rstate = SOCK_RRET; f->rtodo = sizeof(struct amsg); break; case AMSG_SETVOL: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": SETVOL message\n"); } #endif if (f->pstate < SOCK_INIT || s == NULL) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": SETVOL, wrong state\n"); } #endif sock_close(f); return 0; } ctl = ntohl(m->u.vol.ctl); if (ctl > MIDI_MAXCTL) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": SETVOL, volume out of range\n"); } #endif sock_close(f); return 0; } f->rtodo = sizeof(struct amsg); f->rstate = SOCK_RMSG; f->lastvol = ctl; /* dont trigger feedback message */ slot_setvol(s, ctl); dev_midi_vol(s->dev, s); break; case AMSG_AUTH: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": AUTH message\n"); } #endif if (f->pstate != SOCK_AUTH) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": AUTH, wrong state\n"); } #endif sock_close(f); return 0; } if (!sock_auth(f)) { sock_close(f); return 0; } f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); break; case AMSG_HELLO: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": HELLO message\n"); } #endif if (f->pstate != SOCK_HELLO) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": HELLO, wrong state\n"); } #endif sock_close(f); return 0; } if (!sock_hello(f)) { sock_close(f); return 0; } AMSG_INIT(m); m->cmd = htonl(AMSG_ACK); f->rstate = SOCK_RRET; f->rtodo = sizeof(struct amsg); break; case AMSG_BYE: #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": BYE message\n"); } #endif if (s != NULL && f->pstate != SOCK_INIT) { #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": BYE, wrong state\n"); } #endif } sock_close(f); return 0; default: #ifdef DEBUG if (log_level >= 1) { sock_log(f); log_puts(": unknown command in message\n"); } #endif sock_close(f); return 0; } return 1; } /* * build a message in f->wmsg, return 1 on success and 0 if * there's nothing to do. Assume f->wstate is SOCK_WIDLE */ int sock_buildmsg(struct sock *f) { unsigned int size; /* * If pos changed (or initial tick), build a MOVE message. */ if (f->tickpending) { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": building MOVE message, delta = "); log_puti(f->slot->delta); log_puts("\n"); } #endif AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_MOVE); f->wmsg.u.ts.delta = htonl(f->slot->delta); f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; f->tickpending = 0; /* * XXX: use tickpending as accumulator rather than * slot->delta */ f->slot->delta = 0; return 1; } if (f->fillpending > 0) { AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_FLOWCTL); f->wmsg.u.ts.delta = htonl(f->fillpending); size = f->fillpending; if (f->slot) size *= f->slot->mix.bpf; f->rmax += size; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": building FLOWCTL message, count = "); log_puti(f->fillpending); log_puts(", rmax -> "); log_puti(f->rmax); log_puts("\n"); } #endif f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; f->fillpending = 0; return 1; } /* * if volume changed build a SETVOL message */ if (f->pstate >= SOCK_START && f->slot->vol != f->lastvol) { #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": building SETVOL message, vol = "); log_puti(f->slot->vol); log_puts("\n"); } #endif AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_SETVOL); f->wmsg.u.vol.ctl = htonl(f->slot->vol); f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; f->lastvol = f->slot->vol; return 1; } if (f->midi != NULL && f->midi->obuf.used > 0) { size = f->midi->obuf.used; if (size > AMSG_DATAMAX) size = AMSG_DATAMAX; AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_DATA); f->wmsg.u.data.size = htonl(size); f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; return 1; } /* * If data available, build a DATA message. */ if (f->slot != NULL && f->wmax > 0 && f->slot->sub.buf.used > 0) { size = f->slot->sub.buf.used; if (size > AMSG_DATAMAX) size = AMSG_DATAMAX; if (size > f->walign) size = f->walign; if (size > f->wmax) size = f->wmax; size -= size % f->slot->sub.bpf; #ifdef DEBUG if (size == 0) { sock_log(f); log_puts(": sock_buildmsg size == 0\n"); panic(); } #endif f->walign -= size; f->wmax -= size; if (f->walign == 0) f->walign = f->slot->round * f->slot->sub.bpf; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": building audio DATA message, size = "); log_puti(size); log_puts("\n"); } #endif AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_DATA); f->wmsg.u.data.size = htonl(size); f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; return 1; } if (f->stoppending) { #ifdef DEBUG if (log_level >= 3) { sock_log(f); log_puts(": building STOP message\n"); } #endif f->stoppending = 0; f->pstate = SOCK_INIT; AMSG_INIT(&f->wmsg); f->wmsg.cmd = htonl(AMSG_STOP); f->wtodo = sizeof(struct amsg); f->wstate = SOCK_WMSG; return 1; } #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": no messages to build anymore, idling...\n"); } #endif f->wstate = SOCK_WIDLE; return 0; } /* * iteration of the socket reader loop, return 1 on success */ int sock_read(struct sock *f) { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": reading "); log_putu(f->rtodo); log_puts(" todo\n"); } #endif switch (f->rstate) { case SOCK_RIDLE: return 0; case SOCK_RMSG: if (!sock_rmsg(f)) return 0; if (!sock_execmsg(f)) return 0; break; case SOCK_RDATA: if (!sock_rdata(f)) return 0; f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); break; case SOCK_RRET: if (f->wstate != SOCK_WIDLE) { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": can't reply, write-end blocked\n"); } #endif return 0; } f->wmsg = f->rmsg; f->wstate = SOCK_WMSG; f->wtodo = sizeof(struct amsg); f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": copied RRET message\n"); } #endif } return 1; } /* * iteration of the socket writer loop, return 1 on success */ int sock_write(struct sock *f) { #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": writing"); if (f->wstate != SOCK_WIDLE) { log_puts(" todo = "); log_putu(f->wtodo); } log_puts("\n"); } #endif switch (f->wstate) { case SOCK_WMSG: if (!sock_wmsg(f)) return 0; /* * f->wmsg is either build by sock_buildmsg() or * copied from f->rmsg (in the SOCK_RRET state), so * it's safe. */ if (ntohl(f->wmsg.cmd) != AMSG_DATA) { f->wstate = SOCK_WIDLE; f->wtodo = 0xdeadbeef; break; } f->wstate = SOCK_WDATA; f->wsize = f->wtodo = ntohl(f->wmsg.u.data.size); /* PASSTHROUGH */ case SOCK_WDATA: if (!sock_wdata(f)) return 0; if (f->wtodo > 0) break; f->wstate = SOCK_WIDLE; f->wtodo = 0xdeadbeef; if (f->pstate == SOCK_STOP) { f->pstate = SOCK_INIT; f->wmax = 0; #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": drained, moved to INIT state\n"); } #endif } /* PASSTHROUGH */ case SOCK_WIDLE: if (f->rstate == SOCK_RRET) { f->wmsg = f->rmsg; f->wstate = SOCK_WMSG; f->wtodo = sizeof(struct amsg); f->rstate = SOCK_RMSG; f->rtodo = sizeof(struct amsg); #ifdef DEBUG if (log_level >= 4) { sock_log(f); log_puts(": copied RRET message\n"); } #endif } else { if (!sock_buildmsg(f)) return 0; } break; #ifdef DEBUG default: sock_log(f); log_puts(": bad writing end state\n"); panic(); #endif } return 1; } int sock_pollfd(void *arg, struct pollfd *pfd) { struct sock *f = arg; int events = 0; /* * feedback counters, clock ticks and alike may have changed, * prepare a message to trigger writes * * XXX: doing this at the beginning of the cycle is not optimal, * because state is changed at the end of the read cycle, and * thus counters, ret message and alike are generated then. */ if (f->wstate == SOCK_WIDLE && f->rstate != SOCK_RRET) sock_buildmsg(f); if (f->rstate == SOCK_RMSG || f->rstate == SOCK_RDATA) events |= POLLIN; if (f->rstate == SOCK_RRET || f->wstate == SOCK_WMSG || f->wstate == SOCK_WDATA) events |= POLLOUT; pfd->fd = f->fd; pfd->events = events; return 1; } int sock_revents(void *arg, struct pollfd *pfd) { return pfd->revents; } void sock_in(void *arg) { struct sock *f = arg; while (sock_read(f)) ; } void sock_out(void *arg) { struct sock *f = arg; while (sock_write(f)) ; } void sock_hup(void *arg) { struct sock *f = arg; sock_close(f); } sndio-1.5.0/sndiod/sock.h010066400017510001751000000052071332662053300137410ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * 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" struct file; struct slot; struct midi; struct sock { struct sock *next; int fd; struct file *file; struct amsg rmsg, wmsg; /* messages being sent/received */ unsigned int wmax; /* max bytes we're allowed to write */ unsigned int rmax; /* max bytes we're allowed to read */ unsigned int rsize; /* input bytes to read (DATA msg) */ unsigned int wsize; /* output bytes to write (DATA msg) */ unsigned int rtodo; /* input bytes not read yet */ unsigned int wtodo; /* output bytes not written yet */ #define SOCK_RIDLE 0 /* not expecting messages */ #define SOCK_RMSG 1 /* expecting a message */ #define SOCK_RDATA 2 /* data chunk being read */ #define SOCK_RRET 3 /* reply being returned */ unsigned int 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 int wstate; /* state of the write-end FSM */ #define SOCK_AUTH 0 /* waiting for AUTH message */ #define SOCK_HELLO 1 /* waiting for HELLO message */ #define SOCK_INIT 2 /* parameter negotiation */ #define SOCK_START 3 /* filling play buffers */ #define SOCK_STOP 4 /* draining rec buffers */ unsigned int pstate; /* one of the above */ int tickpending; /* tick waiting to be transmitted */ int fillpending; /* flowctl waiting to be transmitted */ int stoppending; /* last STOP ack to be sent */ unsigned int walign; /* align written data to this */ unsigned int ralign; /* read data is aligned to this */ int lastvol; /* last volume */ struct slot *slot; /* audio device slot number */ struct midi *midi; /* midi endpoint */ struct port *port; /* midi port */ }; struct sock *sock_new(int fd); void sock_close(struct sock *); extern struct sock *sock_list; #endif /* !defined(SOCK_H) */ sndio-1.5.0/sndiod/sysex.h010066400017510001751000000062201332662053300141510ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2011 Alexandre Ratchov * * 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 AUCAT_SYSEX_H #define AUCAT_SYSEX_H #include /* * start and end markers */ #define SYSEX_START 0xf0 #define SYSEX_END 0xf7 /* * type/vendor namespace IDs we use */ #define SYSEX_TYPE_RT 0x7f /* real-time universal */ #define SYSEX_TYPE_EDU 0x7d /* non-comercial */ /* * realtime messages in the "universal real-time" namespace */ #define SYSEX_MTC 0x01 /* mtc messages */ #define SYSEX_MTC_FULL 0x01 /* mtc full frame message */ #define SYSEX_CONTROL 0x04 #define SYSEX_MASTER 0x01 #define SYSEX_MMC 0x06 #define SYSEX_MMC_STOP 0x01 #define SYSEX_MMC_START 0x02 #define SYSEX_MMC_LOC 0x44 #define SYSEX_MMC_LOC_LEN 0x06 #define SYSEX_MMC_LOC_CMD 0x01 /* * sepcial "any" midi device number */ #define SYSEX_DEV_ANY 0x7f /* * aucat-specific messages, in the "edu" namespace */ #define SYSEX_AUCAT 0x23 /* aucat-specific */ #define SYSEX_AUCAT_SLOTDESC 0x01 /* mixer info */ #define SYSEX_AUCAT_DUMPREQ 0x02 /* dump request */ #define SYSEX_AUCAT_DUMPEND 0x03 /* end of dump */ /* * minimum size of sysex message we accept */ #define SYSEX_SIZE(m) (5 + sizeof(struct sysex_ ## m)) /* * all possible system exclusive messages we support. For aucat-specific * messages we use the same header as real-time messages to simplify the * message parser */ struct sysex { uint8_t start; uint8_t type; /* type or vendor id */ uint8_t dev; /* device or product id */ uint8_t id0; /* message id */ uint8_t id1; /* sub-id */ union sysex_all { struct sysex_empty { uint8_t end; } empty; struct sysex_master { uint8_t fine; uint8_t coarse; uint8_t end; } master; struct sysex_start { uint8_t end; } start; struct sysex_stop { uint8_t end; } stop; struct sysex_loc { uint8_t len; uint8_t cmd; uint8_t hr; uint8_t min; uint8_t sec; uint8_t fr; uint8_t cent; uint8_t end; } loc; struct sysex_full { uint8_t hr; uint8_t min; uint8_t sec; uint8_t fr; uint8_t end; } full; struct sysex_slotdesc { uint8_t chan; /* channel */ uint8_t vol; /* current volume */ #define SYSEX_NAMELEN 10 /* \0 included */ uint8_t name[SYSEX_NAMELEN]; /* stream name */ uint8_t end; } slotdesc; struct sysex_dumpreq { uint8_t end; } dumpreq; struct sysex_dumpend { uint8_t end; } dumpend; } u; }; #endif /* !defined(AUCAT_SYSEX_H) */ sndio-1.5.0/sndiod/utils.c010066400017510001751000000066431332662053300141420ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2003-2012 Alexandre Ratchov * * 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. */ /* * log_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 #include #include #include #include #include "utils.h" /* * log buffer size */ #define LOG_BUFSZ 8192 /* * store a character in the log */ #define LOG_PUTC(c) do { \ if (log_used < LOG_BUFSZ) \ log_buf[log_used++] = (c); \ } while (0) char log_buf[LOG_BUFSZ]; /* buffer where traces are stored */ unsigned int log_used = 0; /* bytes used in the buffer */ unsigned int log_sync = 1; /* if true, flush after each '\n' */ /* * write the log buffer on stderr */ void log_flush(void) { if (log_used == 0) return; write(STDERR_FILENO, log_buf, log_used); log_used = 0; } /* * store a string in the log */ void log_puts(char *msg) { char *p = msg; int c; while ((c = *p++) != '\0') { LOG_PUTC(c); if (log_sync && c == '\n') log_flush(); } } /* * store a hex in the log */ void log_putx(unsigned long num) { char dig[sizeof(num) * 2], *p = dig, c; unsigned int 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; LOG_PUTC(c); } } else LOG_PUTC('0'); } /* * store an unsigned decimal in the log */ void log_putu(unsigned long num) { char dig[sizeof(num) * 3], *p = dig; unsigned int ndig; if (num != 0) { for (ndig = 0; num != 0; ndig++) { *p++ = num % 10; num /= 10; } for (; ndig != 0; ndig--) LOG_PUTC(*(--p) + '0'); } else LOG_PUTC('0'); } /* * store a signed decimal in the log */ void log_puti(long num) { if (num < 0) { LOG_PUTC('-'); num = -num; } log_putu(num); } /* * abort program execution after a fatal error */ void panic(void) { log_flush(); (void)kill(getpid(), SIGABRT); _exit(1); } /* * allocate a (small) amount of memory, and abort if it fails */ void * xmalloc(size_t size) { void *p; p = malloc(size); if (p == NULL) { log_puts("failed to allocate "); log_putx(size); log_puts(" bytes\n"); panic(); } return p; } /* * free memory allocated with xmalloc() */ void xfree(void *p) { #ifdef DEBUG if (p == NULL) { log_puts("xfree with NULL arg\n"); panic(); } #endif free(p); } /* * xmalloc-style strdup(3) */ char * xstrdup(char *s) { size_t size; void *p; size = strlen(s) + 1; p = xmalloc(size); memcpy(p, s, size); return p; } sndio-1.5.0/sndiod/utils.h010066400017510001751000000026641332662053300141460ustar00alexalex/* $OpenBSD$ */ /* * Copyright (c) 2003-2012 Alexandre Ratchov * * 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 UTILS_H #define UTILS_H #include void log_puts(char *); void log_putx(unsigned long); void log_putu(unsigned long); void log_puti(long); void panic(void); void log_flush(void); void *xmalloc(size_t); char *xstrdup(char *); void xfree(void *); /* * Log 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 (eg. new streams, new parameters ...) * 4 - data blocks and messages */ extern unsigned int log_level; extern unsigned int log_sync; #endif sndio-1.5.0/version.h010066400017510001751000000000361332755320600132050ustar00alexalex#define VERSION "sndio 1.5.0"