There are 4 audio counters, and we've one ioctl() to get each. So
userland has to call the ioctl() one by one, and must take care to
handle cases when a counters are inconsistent (i.e. don't belong to
the same time). Inconsistencies are extreamly rare produce so the
code is hard to test.
This diff adds the AUDIO_GETPOS ioctl to fetch a snapshot of all
counters in a single call, and adapts libsndio and audioctl to use
it. The logic doesn't change, only the way counters are fetched
from the kernel, so no behaviour change.
OK?
Index: sys/sys/audioio.h
===================================================================
RCS file: /cvs/src/sys/sys/audioio.h,v
retrieving revision 1.22
diff -u -p -u -p -r1.22 audioio.h
--- sys/sys/audioio.h 25 Jun 2015 06:43:46 -0000 1.22
+++ sys/sys/audioio.h 28 Jul 2015 07:32:45 -0000
@@ -92,6 +92,13 @@ typedef struct audio_offset {
u_int unused[2];
} audio_offset_t;
+struct audio_pos {
+ unsigned int play_pos; /* total bytes played */
+ unsigned int play_xrun; /* bytes of silence inserted */
+ unsigned int rec_pos; /* total bytes recorded */
+ unsigned int rec_xrun; /* bytes dropped */
+};
+
/*
* Supported audio encodings
*/
@@ -141,6 +148,7 @@ typedef struct audio_encoding {
#define AUDIO_GETIOFFS _IOR('A', 32, struct audio_offset)
#define AUDIO_GETOOFFS _IOR('A', 33, struct audio_offset)
#define AUDIO_GETPROPS _IOR('A', 34, int)
+#define AUDIO_GETPOS _IOR('A', 35, struct audio_pos)
#define AUDIO_PROP_FULLDUPLEX 0x01
#define AUDIO_PROP_MMAP 0x02
#define AUDIO_PROP_INDEPENDENT 0x04
Index: sys/dev/audio.c
===================================================================
RCS file: /cvs/src/sys/dev/audio.c,v
retrieving revision 1.134
diff -u -p -u -p -r1.134 audio.c
--- sys/dev/audio.c 24 Jul 2015 08:56:45 -0000 1.134
+++ sys/dev/audio.c 28 Jul 2015 07:32:46 -0000
@@ -66,9 +66,9 @@ struct audio_buf {
size_t start; /* first byte used in the FIFO */
size_t used; /* bytes used in the FIFO */
size_t blksz; /* DMA block size */
- unsigned long pos; /* bytes transferred */
- unsigned long xrun; /* bytes lost by xruns */
struct selinfo sel; /* to record & wakeup poll(2) */
+ unsigned int pos; /* bytes transferred */
+ unsigned int xrun; /* bytes lost by xruns */
int blocking; /* read/write blocking */
};
@@ -106,6 +106,7 @@ struct audio_softc {
unsigned char silence[4]; /* a sample of silence */
int pause; /* not trying to start DMA */
int active; /* DMA in process */
+ int offs; /* offset between play & rec dir */
void (*conv_enc)(unsigned char *, int); /* encode to native */
void (*conv_dec)(unsigned char *, int); /* decode to user */
#if NWSKBD > 0
@@ -348,7 +349,7 @@ audio_pintr(void *addr)
struct audio_softc *sc = addr;
unsigned char *ptr;
size_t count;
- int error;
+ int error, nblk, todo;
MUTEX_ASSERT_LOCKED(&audio_lock);
if (!(sc->mode & AUMODE_PLAY) || !sc->active) {
@@ -360,6 +361,23 @@ audio_pintr(void *addr)
return;
}
+ /*
+ * check if record pointer wrapped, see explanation
+ * in audio_rintr()
+ */
+ if (sc->mode & AUMODE_RECORD) {
+ sc->offs--;
+ nblk = sc->rec.len / sc->rec.blksz;
+ todo = -sc->offs;
+ if (todo >= nblk) {
+ todo -= todo % nblk;
+ DPRINTFN(1, "%s: rec ptr wrapped, moving %d blocs\n",
+ DEVNAME(sc), todo);
+ while (todo-- > 0)
+ audio_rintr(sc);
+ }
+ }
+
sc->play.pos += sc->play.blksz;
audio_fill_sil(sc, sc->play.data + sc->play.start, sc->play.blksz);
audio_buf_rdiscard(&sc->play, sc->play.blksz);
@@ -402,7 +420,7 @@ audio_rintr(void *addr)
struct audio_softc *sc = addr;
unsigned char *ptr;
size_t count;
- int error;
+ int error, nblk, todo;
MUTEX_ASSERT_LOCKED(&audio_lock);
if (!(sc->mode & AUMODE_RECORD) || !sc->active) {
@@ -414,6 +432,30 @@ audio_rintr(void *addr)
return;
}
+ /*
+ * Interrupts may be masked by other sub-systems during 320ms
+ * and more. During such a delay the hardware doesn't stop
+ * playing and the play buffer pointers may wrap, this can't be
+ * detected and corrected by low level drivers. This makes the
+ * record stream ahead of the play stream; this is detected as a
+ * hardware anomaly by userland and cause programs to misbehave.
+ *
+ * We fix this by advancing play position by an integer count of
+ * full buffers, so it reaches the record position.
+ */
+ if (sc->mode & AUMODE_PLAY) {
+ sc->offs++;
+ nblk = sc->play.len / sc->play.blksz;
+ todo = sc->offs;
+ if (todo >= nblk) {
+ todo -= todo % nblk;
+ DPRINTFN(1, "%s: play ptr wrapped, moving %d blocs\n",
+ DEVNAME(sc), todo);
+ while (todo-- > 0)
+ audio_pintr(sc);
+ }
+ }
+
sc->rec.pos += sc->rec.blksz;
audio_buf_wcommit(&sc->rec, sc->rec.blksz);
if (sc->rec.used == sc->rec.len) {
@@ -464,6 +506,7 @@ audio_start_do(struct audio_softc *sc)
sc->rec.len, sc->rec.blksz);
error = 0;
+ sc->offs = 0;
if (sc->mode & AUMODE_PLAY) {
if (sc->ops->trigger_output) {
p.encoding = sc->hw_enc;
@@ -1298,7 +1341,7 @@ audio_drain(struct audio_softc *sc)
xrun = sc->play.xrun;
while (sc->play.xrun == xrun) {
- DPRINTF("%s: drain: used = %zu, xrun = %ld\n",
+ DPRINTF("%s: drain: used = %zu, xrun = %d\n",
DEVNAME(sc), sc->play.used, sc->play.xrun);
/*
@@ -1486,6 +1529,7 @@ int
audio_ioctl(struct audio_softc *sc, unsigned long cmd, void *addr)
{
struct audio_offset *ao;
+ struct audio_pos *ap;
int error = 0, fd;
/* block if quiesced */
@@ -1516,6 +1560,15 @@ audio_ioctl(struct audio_softc *sc, unsi
mtx_enter(&audio_lock);
ao = (struct audio_offset *)addr;
ao->samples = sc->rec.pos;
+ mtx_leave(&audio_lock);
+ break;
+ case AUDIO_GETPOS:
+ mtx_enter(&audio_lock);
+ ap = (struct audio_pos *)addr;
+ ap->play_pos = sc->play.pos;
+ ap->play_xrun = sc->play.xrun;
+ ap->rec_pos = sc->rec.pos;
+ ap->rec_xrun = sc->rec.xrun;
mtx_leave(&audio_lock);
break;
case AUDIO_SETINFO:
Index: usr.bin/audioctl/audioctl.c
===================================================================
RCS file: /cvs/src/usr.bin/audioctl/audioctl.c,v
retrieving revision 1.28
diff -u -p -u -p -r1.28 audioctl.c
--- usr.bin/audioctl/audioctl.c 26 May 2015 18:17:12 -0000 1.28
+++ usr.bin/audioctl/audioctl.c 28 Jul 2015 07:32:46 -0000
@@ -59,9 +59,9 @@ audio_info_t info;
char encbuf[1000];
-int properties, fullduplex, perrors, rerrors;
+int properties, fullduplex;
-struct audio_offset poffs, roffs;
+struct audio_pos getpos;
struct field {
const char *name;
@@ -97,8 +97,8 @@ struct field {
{ "play.pause", &info.play.pause, UCHAR, 0 },
{ "play.active", &info.play.active, UCHAR, READONLY },
{ "play.block_size", &info.play.block_size, UINT, 0 },
- { "play.bytes", &poffs.samples, INT, READONLY },
- { "play.errors", &perrors, INT, READONLY },
+ { "play.bytes", &getpos.play_pos, UINT, READONLY },
+ { "play.errors", &getpos.play_xrun, UINT, READONLY },
{ "record.rate", &info.record.sample_rate,UINT, 0 },
{ "record.sample_rate", &info.record.sample_rate,UINT, ALIAS },
{ "record.channels", &info.record.channels, UINT, 0 },
@@ -109,8 +109,8 @@ struct field {
{ "record.pause", &info.record.pause, UCHAR, 0 },
{ "record.active", &info.record.active, UCHAR, READONLY },
{ "record.block_size", &info.record.block_size,UINT, 0 },
- { "record.bytes", &roffs.samples, INT, READONLY },
- { "record.errors", &rerrors, INT, READONLY },
+ { "record.bytes", &getpos.rec_pos, UINT, READONLY },
+ { "record.errors", &getpos.rec_xrun, UINT, READONLY },
{ 0 }
};
@@ -297,16 +297,8 @@ getinfo(int fd)
err(1, "AUDIO_GETFD");
if (ioctl(fd, AUDIO_GETPROPS, &properties) < 0)
err(1, "AUDIO_GETPROPS");
- if (ioctl(fd, AUDIO_PERROR, &perrors) < 0)
- err(1, "AUDIO_PERROR");
- if (ioctl(fd, AUDIO_RERROR, &rerrors) < 0)
- err(1, "AUDIO_RERROR");
- if (ioctl(fd, AUDIO_GETOOFFS, &poffs) < 0)
- err(1, "AUDIO_GETOOFFS");
- if (ioctl(fd, AUDIO_GETIOFFS, &roffs) < 0)
- err(1, "AUDIO_GETOIFFS");
- if (ioctl(fd, AUDIO_GETINFO, &info) < 0)
- err(1, "AUDIO_GETINFO");
+ if (ioctl(fd, AUDIO_GETPOS, &getpos) < 0)
+ err(1, "AUDIO_GETPOS");
}
void
Index: lib/libsndio/sio_sun.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sio_sun.c,v
retrieving revision 1.15
diff -u -p -u -p -r1.15 sio_sun.c
--- lib/libsndio/sio_sun.c 24 Jul 2015 08:50:29 -0000 1.15
+++ lib/libsndio/sio_sun.c 28 Jul 2015 07:32:46 -0000
@@ -804,65 +804,46 @@ int
sio_sun_revents(struct sio_hdl *sh, struct pollfd *pfd)
{
struct sio_sun_hdl *hdl = (struct sio_sun_hdl *)sh;
- struct audio_offset ao;
- int xrun, dierr = 0, doerr = 0, offset, delta;
+ struct audio_pos ap;
+ int dierr = 0, doerr = 0, offset, delta;
int revents = pfd->revents;
if (!hdl->sio.started)
return pfd->revents;
- if (hdl->sio.mode & SIO_PLAY) {
- if (ioctl(hdl->fd, AUDIO_GETOOFFS, &ao) < 0) {
- DPERROR("sio_sun_revents: GETOOFFS");
- hdl->sio.eof = 1;
- return POLLHUP;
- }
- delta = (ao.samples - hdl->obytes) / hdl->obpf;
- hdl->obytes = ao.samples;
- hdl->odelta += delta;
- if (!(hdl->sio.mode & SIO_REC))
- hdl->idelta += delta;
- }
- if (hdl->sio.mode & SIO_REC) {
- if (ioctl(hdl->fd, AUDIO_GETIOFFS, &ao) < 0) {
- DPERROR("sio_sun_revents: GETIOFFS");
- hdl->sio.eof = 1;
- return POLLHUP;
- }
- delta = (ao.samples - hdl->ibytes) / hdl->ibpf;
- hdl->ibytes = ao.samples;
- hdl->idelta += delta;
- if (!(hdl->sio.mode & SIO_PLAY))
- hdl->odelta += delta;
+ 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) {
- if (ioctl(hdl->fd, AUDIO_PERROR, &xrun) < 0) {
- DPERROR("sio_sun_revents: PERROR");
- hdl->sio.eof = 1;
- return POLLHUP;
- }
- doerr = xrun - hdl->oerr;
- hdl->oerr = xrun;
- if (!(hdl->sio.mode & SIO_REC))
+ 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) {
- if (ioctl(hdl->fd, AUDIO_RERROR, &xrun) < 0) {
- DPERROR("sio_sun_revents: RERROR");
- hdl->sio.eof = 1;
- return POLLHUP;
- }
- dierr = xrun - hdl->ierr;
- hdl->ierr = xrun;
- if (!(hdl->sio.mode & SIO_PLAY))
+ 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);
}
/*
- * GET{I,O}OFFS report positions including xruns,
+ * GETPOS reports positions including xruns,
* so we have to substract to get the real position
*/
hdl->idelta -= dierr;