mirror of
https://github.com/ericonr/sndio.git
synced 2024-02-18 04:45:21 -06:00
356 lines
6.7 KiB
C
356 lines
6.7 KiB
C
#include <sys/time.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sndio.h>
|
|
#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;
|
|
}
|