Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
Re: libsndio: Add an interface to report underruns
To:
Omar Polo <op@omarpolo.com>
Cc:
tech@openbsd.org
Date:
Tue, 30 Dec 2025 10:36:40 +0100

Download raw body.

Thread
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 */