Download raw body.
libsndio: Add an interface to report underruns
On Mon, Dec 29, 2025 at 05:22:02PM +0100, Omar Polo wrote:
> Alexandre Ratchov <alex@caoua.org> wrote:
> > This diff adds the new sio_onxrun(3) function that can be used to
> > register a callback function that will be called on buffer underrun.
> >
> > There are cases where a program may use an external audio clock
> > (ex. an RTP stream) and resample to make the local audio rate
> > match the remote rate to keep the latency constant. To do so,
> > the program must measure continuously the clock drift and calculate
> > the resampling ratio. Upon underrun, such programs must restart
> > the measurements, hence the need for this new interface.
> >
> > The audio/sndiortp port will be the first user of this function,
> > but other programs could benefit from this as well.
> >
> > This function will be necessary to make sndiod combine multiple audio
> > devices into a single one (ex. combine webcam's mic with the internal
> > speaker).
>
> (looking forward to that!)
>
> > OK?
>
> don't feel confident with the internals to give a proper ok, I can just
> say that it reads fine to me.
>
thanks
> noticed one minor omission in the manpage, hence the reply:
>
> > RCS file: /cvs/src/lib/libsndio/sio_open.3,v
> > diff -u -p -u -p -r1.58 sio_open.3
> > --- lib/libsndio/sio_open.3 13 Jun 2025 18:34:00 -0000 1.58
> > +++ lib/libsndio/sio_open.3 27 Dec 2025 17:42:51 -0000
> > @@ -588,6 +588,21 @@ might overrun; in this case recorded dat
> > Similarly if the application cannot provide data to play
> > fast enough, the play buffer underruns and silence is played
> > instead.
>
> it's missing the sio_onxrun() prototype in the SYNOPSIS?
>
Outch! it was also missing in the DESCRIPTION. New diff below:
Index: include/sndio.h
===================================================================
RCS file: /cvs/src/include/sndio.h,v
diff -u -p -u -p -r1.15 sndio.h
--- include/sndio.h 24 May 2024 15:10:26 -0000 1.15
+++ include/sndio.h 30 Dec 2025 09:33:58 -0000
@@ -169,6 +169,7 @@ int sio_setpar(struct sio_hdl *, struct
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 *);
+void sio_onxrun(struct sio_hdl *, void (*)(void *), 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 *);
Index: lib/libsndio/Symbols.map
===================================================================
RCS file: /cvs/src/lib/libsndio/Symbols.map,v
diff -u -p -u -p -r1.3 Symbols.map
--- lib/libsndio/Symbols.map 29 Apr 2022 08:30:48 -0000 1.3
+++ lib/libsndio/Symbols.map 30 Dec 2025 09:33:58 -0000
@@ -18,6 +18,7 @@
sio_eof;
sio_setvol;
sio_onvol;
+ sio_onxrun;
mio_open;
mio_close;
Index: lib/libsndio/amsg.h
===================================================================
RCS file: /cvs/src/lib/libsndio/amsg.h,v
diff -u -p -u -p -r1.16 amsg.h
--- lib/libsndio/amsg.h 24 May 2024 15:16:09 -0000 1.16
+++ lib/libsndio/amsg.h 30 Dec 2025 09:33:58 -0000
@@ -80,6 +80,7 @@ struct amsg {
#define AMSG_CTLSET 14 /* set control value */
#define AMSG_CTLSYNC 15 /* end of controls descriptions */
#define AMSG_CTLSUB 16 /* ondesc/onctl subscription */
+#define AMSG_XRUN 17 /* notification about xruns */
uint32_t cmd;
uint32_t __pad;
union {
@@ -104,6 +105,9 @@ struct amsg {
#define AMSG_DATAMAX 0x1000
uint32_t size;
} data;
+ struct amsg_start {
+ uint8_t xrunnotify;
+ } start;
struct amsg_stop {
uint8_t drain;
} stop;
@@ -113,6 +117,9 @@ struct amsg {
struct amsg_vol {
uint32_t ctl;
} vol;
+ struct amsg_xrun {
+ uint8_t state;
+ } xrun;
struct amsg_hello {
uint16_t mode; /* bitmap of MODE_XXX */
#define AMSG_VERSION 7
Index: lib/libsndio/shlib_version
===================================================================
RCS file: /cvs/src/lib/libsndio/shlib_version,v
diff -u -p -u -p -r1.15 shlib_version
--- lib/libsndio/shlib_version 16 Jul 2025 15:33:05 -0000 1.15
+++ lib/libsndio/shlib_version 30 Dec 2025 09:33:58 -0000
@@ -1,2 +1,2 @@
major=8
-minor=0
+minor=1
Index: lib/libsndio/sio.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sio.c,v
diff -u -p -u -p -r1.27 sio.c
--- lib/libsndio/sio.c 29 Apr 2022 08:30:48 -0000 1.27
+++ lib/libsndio/sio.c 30 Dec 2025 09:33:58 -0000
@@ -85,6 +85,7 @@ _sio_create(struct sio_hdl *hdl, struct
hdl->started = 0;
hdl->eof = 0;
hdl->move_cb = NULL;
+ hdl->xrun_cb = NULL;
hdl->vol_cb = NULL;
}
@@ -123,6 +124,7 @@ sio_start(struct sio_hdl *hdl)
if (!hdl->ops->start(hdl))
return 0;
hdl->started = 1;
+ hdl->xrun = 0;
return 1;
}
@@ -517,6 +519,7 @@ _sio_onmove_cb(struct sio_hdl *hdl, int
#endif
if (hdl->move_cb)
hdl->move_cb(hdl->move_addr, delta);
+ hdl->xrun = 0;
}
int
@@ -553,4 +556,27 @@ _sio_onvol_cb(struct sio_hdl *hdl, unsig
{
if (hdl->vol_cb)
hdl->vol_cb(hdl->vol_addr, ctl);
+}
+
+void
+sio_onxrun(struct sio_hdl *hdl, void (*cb)(void *), void *addr)
+{
+ if (hdl->started) {
+ DPRINTF("sio_onxrun: already started\n");
+ hdl->eof = 1;
+ return;
+ }
+ hdl->xrun_cb = cb;
+ hdl->xrun_addr = addr;
+}
+
+void
+_sio_onxrun_cb(struct sio_hdl *hdl)
+{
+ if (!hdl->xrun) {
+ hdl->xrun = 1;
+ if (hdl->xrun_cb)
+ hdl->xrun_cb(hdl->xrun_addr);
+ }
+ DPRINTFN(1, "sndio: xrun\n");
}
Index: lib/libsndio/sio_aucat.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sio_aucat.c,v
diff -u -p -u -p -r1.21 sio_aucat.c
--- lib/libsndio/sio_aucat.c 29 Apr 2022 08:30:48 -0000 1.21
+++ lib/libsndio/sio_aucat.c 30 Dec 2025 09:33:58 -0000
@@ -115,6 +115,10 @@ sio_aucat_runmsg(struct sio_aucat_hdl *h
hdl->delta = 0;
}
break;
+ case AMSG_XRUN:
+ DPRINTFN(3, "aucat: xrun\n");
+ _sio_onxrun_cb(&hdl->sio);
+ break;
case AMSG_SETVOL:
ctl = ntohl(hdl->aucat.rmsg.u.vol.ctl);
hdl->curvol = hdl->reqvol = ctl;
@@ -196,6 +200,7 @@ sio_aucat_start(struct sio_hdl *sh)
AMSG_INIT(&hdl->aucat.wmsg);
hdl->aucat.wmsg.cmd = htonl(AMSG_START);
+ hdl->aucat.wmsg.u.start.xrunnotify = 1;
hdl->aucat.wtodo = sizeof(struct amsg);
if (!_aucat_wmsg(&hdl->aucat, &hdl->sio.eof))
return 0;
Index: lib/libsndio/sio_open.3
===================================================================
RCS file: /cvs/src/lib/libsndio/sio_open.3,v
diff -u -p -u -p -r1.58 sio_open.3
--- lib/libsndio/sio_open.3 13 Jun 2025 18:34:00 -0000 1.58
+++ lib/libsndio/sio_open.3 30 Dec 2025 09:33:58 -0000
@@ -29,6 +29,7 @@
.Nm sio_read ,
.Nm sio_write ,
.Nm sio_onmove ,
+.Nm sio_onxrun ,
.Nm sio_nfds ,
.Nm sio_pollfd ,
.Nm sio_revents ,
@@ -67,6 +68,12 @@
.Fa "void (*cb)(void *arg, int delta)"
.Fa "void *arg"
.Fc
+.Ft void
+.Fo sio_onxrun
+.Fa "struct sio_hdl *hdl"
+.Fa "void (*cb)(void *arg)"
+.Fa "void *arg"
+.Fc
.Ft int
.Fn sio_nfds "struct sio_hdl *hdl"
.Ft int
@@ -588,6 +595,21 @@ might overrun; in this case recorded dat
Similarly if the application cannot provide data to play
fast enough, the play buffer underruns and silence is played
instead.
+.Pp
+The
+.Fn sio_onxrun
+function can be used to register the
+.Fn cb
+callback function called at buffer underrun or overrun.
+It is called by
+.Fn sio_read ,
+.Fn sio_write ,
+and
+.Fn sio_revents .
+The value of the
+.Fa arg
+pointer is passed to the callback and can contain anything.
+.Pp
Depending on the
.Fa xrun
parameter of the
Index: lib/libsndio/sio_priv.h
===================================================================
RCS file: /cvs/src/lib/libsndio/sio_priv.h,v
diff -u -p -u -p -r1.12 sio_priv.h
--- lib/libsndio/sio_priv.h 21 May 2024 06:07:06 -0000 1.12
+++ lib/libsndio/sio_priv.h 30 Dec 2025 09:33:58 -0000
@@ -30,6 +30,8 @@ struct sio_hdl {
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 */
+ void (*xrun_cb)(void *); /* call-back for xruns */
+ void *xrun_addr; /* user priv. data for xrun_cb */
unsigned mode; /* SIO_PLAY | SIO_REC */
int started; /* true if started */
int nbio; /* true if non-blocking io */
@@ -38,6 +40,7 @@ struct sio_hdl {
int wsil; /* silence to play */
int rused; /* bytes used in read buffer */
int wused; /* bytes used in write buffer */
+ int xrun; /* xrun reported */
long long cpos; /* clock since start */
struct sio_par par;
#ifdef DEBUG
@@ -71,6 +74,7 @@ struct sio_hdl *_sio_sun_open(const char
void _sio_create(struct sio_hdl *, struct sio_ops *, unsigned, int);
void _sio_onmove_cb(struct sio_hdl *, int);
void _sio_onvol_cb(struct sio_hdl *, unsigned);
+void _sio_onxrun_cb(struct sio_hdl *);
#ifdef DEBUG
void _sio_printpos(struct sio_hdl *);
#endif
Index: lib/libsndio/sio_sun.c
===================================================================
RCS file: /cvs/src/lib/libsndio/sio_sun.c,v
diff -u -p -u -p -r1.32 sio_sun.c
--- lib/libsndio/sio_sun.c 11 Nov 2025 11:08:10 -0000 1.32
+++ lib/libsndio/sio_sun.c 30 Dec 2025 09:33:58 -0000
@@ -542,6 +542,9 @@ sio_sun_xrun(struct sio_sun_hdl *hdl)
*/
if (!sio_sun_flush(&hdl->sio))
return 0;
+
+ _sio_onxrun_cb(&hdl->sio);
+
if (!sio_sun_start(&hdl->sio))
return 0;
Index: usr.bin/sndiod/dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
diff -u -p -u -p -r1.126 dev.c
--- usr.bin/sndiod/dev.c 26 Nov 2025 08:40:16 -0000 1.126
+++ usr.bin/sndiod/dev.c 30 Dec 2025 09:33:58 -0000
@@ -28,6 +28,7 @@
#include "utils.h"
void zomb_onmove(void *);
+void zomb_onxrun(void *);
void zomb_onvol(void *);
void zomb_fill(void *);
void zomb_flush(void *);
@@ -64,6 +65,7 @@ int slot_skip(struct slot *);
struct slotops zomb_slotops = {
zomb_onmove,
+ zomb_onxrun,
zomb_onvol,
zomb_fill,
zomb_flush,
@@ -91,6 +93,11 @@ zomb_onmove(void *arg)
}
void
+zomb_onxrun(void *arg)
+{
+}
+
+void
zomb_onvol(void *arg)
{
}
@@ -635,9 +642,13 @@ dev_cycle(struct dev *d)
s->sub.buf.len - s->sub.buf.used <
s->round * s->sub.bpf)) {
+ if (!s->paused) {
#ifdef DEBUG
- logx(3, "slot%zu: xrun, pause cycle", s - slot_array);
+ logx(3, "slot%zu: xrun, paused", s - slot_array);
#endif
+ s->paused = 1;
+ s->ops->onxrun(s->arg);
+ }
if (s->xrun == XRUN_IGNORE) {
s->delta -= s->round;
ps = &s->next;
@@ -654,7 +665,15 @@ dev_cycle(struct dev *d)
#endif
}
continue;
+ } else {
+ if (s->paused) {
+#ifdef DEBUG
+ logx(3, "slot%zu: resumed\n", s - slot_array);
+#endif
+ s->paused = 0;
+ }
}
+
if ((s->mode & MODE_RECMASK) && !(s->pstate == SLOT_STOP)) {
if (s->sub.prime == 0) {
dev_sub_bcopy(d, s);
@@ -1588,6 +1607,7 @@ slot_start(struct slot *s)
s->sub.prime = d->bufsz / d->round;
}
s->skip = 0;
+ s->paused = 0;
/*
* get the current position, the origin is when the first sample
Index: usr.bin/sndiod/dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
diff -u -p -u -p -r1.50 dev.h
--- usr.bin/sndiod/dev.h 26 Nov 2025 08:40:16 -0000 1.50
+++ usr.bin/sndiod/dev.h 30 Dec 2025 09:33:58 -0000
@@ -40,6 +40,7 @@
struct slotops
{
void (*onmove)(void *); /* clock tick */
+ void (*onxrun)(void *); /* xrun */
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 */
@@ -100,6 +101,7 @@ struct slot {
#define SLOT_RUN 3 /* buffer attached to device */
#define SLOT_STOP 4 /* draining */
int pstate;
+ int paused; /* paused because of xrun */
struct app *app;
};
Index: usr.bin/sndiod/siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
diff -u -p -u -p -r1.29 siofile.c
--- usr.bin/sndiod/siofile.c 11 Nov 2025 11:48:03 -0000 1.29
+++ usr.bin/sndiod/siofile.c 30 Dec 2025 09:33:58 -0000
@@ -36,6 +36,7 @@
#define WATCHDOG_USEC 4000000 /* 4 seconds */
void dev_sio_onmove(void *, int);
+void dev_sio_onxrun(void *);
void dev_sio_timeout(void *);
int dev_sio_pollfd(void *, struct pollfd *);
int dev_sio_revents(void *, struct pollfd *);
@@ -74,6 +75,19 @@ dev_sio_onmove(void *arg, int delta)
}
void
+dev_sio_onxrun(void *arg)
+{
+ struct dev *d = arg;
+ struct slot *s;
+
+#ifdef DEBUG
+ logx(1, "%s: xrun", d->path);
+#endif
+ for (s = d->slot_list; s != NULL; s = s->next)
+ s->ops->onxrun(s->arg);
+}
+
+void
dev_sio_timeout(void *arg)
{
struct dev *d = arg;
@@ -213,6 +227,7 @@ dev_sio_open(struct dev *d)
if (d->mode & MODE_PLAY)
d->mode |= MODE_MON;
sio_onmove(d->sio.hdl, dev_sio_onmove, d);
+ sio_onxrun(d->sio.hdl, dev_sio_onxrun, d);
d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
if (d->sioctl.hdl) {
d->sioctl.file = file_new(&dev_sioctl_ops, d, "mix",
Index: usr.bin/sndiod/sock.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.c,v
diff -u -p -u -p -r1.55 sock.c
--- usr.bin/sndiod/sock.c 19 Jun 2025 20:16:34 -0000 1.55
+++ usr.bin/sndiod/sock.c 30 Dec 2025 09:33:58 -0000
@@ -40,6 +40,7 @@ void sock_slot_fill(void *);
void sock_slot_flush(void *);
void sock_slot_eof(void *);
void sock_slot_onmove(void *);
+void sock_slot_onxrun(void *);
void sock_slot_onvol(void *);
void sock_midi_imsg(void *, unsigned char *, int);
void sock_midi_omsg(void *, unsigned char *, int);
@@ -77,6 +78,7 @@ struct fileops sock_fileops = {
struct slotops sock_slotops = {
sock_slot_onmove,
+ sock_slot_onxrun,
sock_slot_onvol,
sock_slot_fill,
sock_slot_flush,
@@ -244,6 +246,21 @@ sock_slot_onmove(void *arg)
}
void
+sock_slot_onxrun(void *arg)
+{
+ struct sock *f = (struct sock *)arg;
+ struct slot *s = f->slot;
+
+#ifdef DEBUG
+ logx(4, "slot%zu: onxrun: notify = %d", s - slot_array, f->xrunnotify);
+#endif
+ if (s->pstate != SOCK_START)
+ return;
+ if (f->xrunnotify)
+ f->xrunpending = 1;
+}
+
+void
sock_slot_onvol(void *arg)
{
struct sock *f = (struct sock *)arg;
@@ -301,6 +318,7 @@ sock_new(int fd)
f->midi = NULL;
f->ctlslot = NULL;
f->tickpending = 0;
+ f->xrunpending = 0;
f->fillpending = 0;
f->stoppending = 0;
f->wstate = SOCK_WIDLE;
@@ -309,6 +327,7 @@ sock_new(int fd)
f->rtodo = sizeof(struct amsg);
f->wmax = f->rmax = 0;
f->lastvol = -1;
+ f->xrunnotify = 0;
f->ctlops = 0;
f->ctlsyncpending = 0;
f->file = file_new(&sock_fileops, f, "sock", 1);
@@ -882,6 +901,9 @@ sock_execmsg(struct sock *f)
return 0;
}
f->tickpending = 0;
+ f->xrunpending = 0;
+ if (AMSG_ISSET(m->u.start.xrunnotify))
+ f->xrunnotify = m->u.start.xrunnotify;
f->stoppending = 0;
slot_start(s);
if (s->mode & MODE_PLAY) {
@@ -1161,6 +1183,18 @@ sock_buildmsg(struct sock *f)
* slot->delta
*/
f->slot->delta = 0;
+ return 1;
+ }
+
+ if (f->xrunpending) {
+#ifdef DEBUG
+ logx(4, "sock %d: building XRUN message", f->fd);
+#endif
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_XRUN);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ f->xrunpending = 0;
return 1;
}
Index: usr.bin/sndiod/sock.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sock.h,v
diff -u -p -u -p -r1.9 sock.h
--- usr.bin/sndiod/sock.h 24 May 2024 15:16:09 -0000 1.9
+++ usr.bin/sndiod/sock.h 30 Dec 2025 09:33:58 -0000
@@ -50,6 +50,8 @@ struct sock {
#define SOCK_STOP 4 /* draining rec buffers */
unsigned int pstate; /* one of the above */
int tickpending; /* tick waiting to be transmitted */
+ int xrunpending; /* xrun waiting to be transmitted */
+ int xrunnotify; /* client subscribed to xrun messages */
int fillpending; /* flowctl waiting to be transmitted */
int stoppending; /* last STOP ack to be sent */
unsigned int walign; /* align written data to this */
libsndio: Add an interface to report underruns