Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
sndiod: enable fall-back audio devices by default
To:
tech@openbsd.org
Date:
Thu, 20 Nov 2025 12:15:54 +0100

Download raw body.

Thread
  • Alexandre Ratchov:

    sndiod: enable fall-back audio devices by default

In short, if the server.device control is used to switch to a new
device (ex. usb headset), then sndiod will always try to use it if
it's present else it will fall back to the previous device.

The diff implements a list of audio devices in priority order. When a
program connects, the first working device of the list is used.

Initially the list contains a single entry: the first device of the
system (or the one specified with -f). This corresponds to the current
sndiod behavior. So far, no behavior change.

Whenever the user switches to a new device with the sndioctl's
server.device control, the new device (ex. usb headset) is inserted at
the head of the list. If the device is unplugged, sndiod switches back
to the last working one, i.e. the next of the list (hda on most
systems) and so on.

This is similar to what the -F hack does but is more flexible (as the
list is dynamic), the code is simpler and requires no
configuration. Existing rc.conf.local settings that use -F still work,
but -F is a dead-end and will be removed.

OK?

Index: dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
diff -u -p -u -p -r1.125 dev.c
--- dev.c	27 Jun 2025 06:41:52 -0000	1.125
+++ dev.c	20 Nov 2025 11:11:19 -0000
@@ -774,7 +774,6 @@ dev_new(char *path, struct aparams *par,
 	d->slot_list = NULL;
 	d->master = MIDI_MAXCTL;
 	d->master_enabled = 0;
-	d->alt_next = d;
 	snprintf(d->name, CTL_NAMEMAX, "%u", d->num);
 	for (pd = &dev_list; *pd != NULL; pd = &(*pd)->next)
 		;
@@ -1113,69 +1112,21 @@ dev_iscompat(struct dev *o, struct dev *
  * Close the device, but attempt to migrate everything to a new sndio
  * device.
  */
-struct dev *
+void
 dev_migrate(struct dev *odev)
 {
-	struct dev *ndev;
 	struct opt *o;
-	struct slot *s;
-	int i;
 
 	/* not opened */
 	if (odev->pstate == DEV_CFG)
-		return odev;
-
-	ndev = odev;
-	while (1) {
-		/* try next one, circulating through the list */
-		ndev = ndev->alt_next;
-		if (ndev == odev) {
-			logx(1, "%s: no fall-back device found", odev->path);
-			return NULL;
-		}
-
-
-		if (!dev_ref(ndev))
-			continue;
-
-		/* check if new parameters are compatible with old ones */
-		if (!dev_iscompat(odev, ndev)) {
-			dev_unref(ndev);
-			continue;
-		}
-
-		/* found it!*/
-		break;
-	}
-
-	logx(1, "%s: switching to %s", odev->path, ndev->path);
-
-	if (mtc_array[0].dev == odev)
-		mtc_setdev(&mtc_array[0], ndev);
+		return;
 
 	/* move opts to new device (also moves clients using the opts) */
 	for (o = opt_list; o != NULL; o = o->next) {
 		if (o->dev != odev)
 			continue;
-		if (strcmp(o->name, o->dev->name) == 0)
-			continue;
-		opt_setdev(o, ndev);
+		opt_migrate(o, odev);
 	}
-
-	/* terminate remaining clients */
-	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
-		if (s->opt == NULL || s->opt->dev != odev)
-			continue;
-		if (s->ops != NULL) {
-			s->ops->exit(s->arg);
-			s->ops = NULL;
-		}
-	}
-
-	/* slots and/or MMC hold refs, drop ours */
-	dev_unref(ndev);
-
-	return ndev;
 }
 
 /*
@@ -2019,8 +1970,10 @@ ctl_setval(struct ctl *c, int val)
 		c->curval = val;
 		return 1;
 	case CTL_OPT_DEV:
-		if (opt_setdev(c->u.opt_dev.opt, c->u.opt_dev.dev))
-			c->u.opt_dev.opt->alt_first = c->u.opt_dev.dev;
+		if (opt_setdev(c->u.opt_dev.opt, c->u.opt_dev.dev)) {
+			/* make this the prefered device */
+			opt_setalt(c->u.opt_dev.opt, c->u.opt_dev.dev);
+		}
 		return 1;
 	default:
 		logx(2, "ctl%u: not writable", c->addr);
Index: dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
diff -u -p -u -p -r1.49 dev.h
--- dev.h	20 Jun 2025 07:14:38 -0000	1.49
+++ dev.h	20 Nov 2025 11:11:19 -0000
@@ -215,11 +215,6 @@ struct dev {
 	char name[CTL_NAMEMAX];
 
 	/*
-	 * next to try if this fails
-	 */
-	struct dev *alt_next;
-
-	/*
 	 * audio device (while opened)
 	 */
 	struct dev_sio sio;
@@ -283,7 +278,7 @@ size_t chans_fmt(char *, size_t, int, in
 int dev_open(struct dev *);
 void dev_close(struct dev *);
 void dev_abort(struct dev *);
-struct dev *dev_migrate(struct dev *);
+void dev_migrate(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);
Index: opt.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.c,v
diff -u -p -u -p -r1.15 opt.c
--- opt.c	20 Jun 2025 07:14:38 -0000	1.15
+++ opt.c	20 Nov 2025 11:11:19 -0000
@@ -15,6 +15,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 #include <string.h>
+#include <stdio.h>
 
 #include "dev.h"
 #include "midi.h"
@@ -317,7 +318,6 @@ 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 dev *a;
 	struct opt *o, **po;
 	char str[64];
 	unsigned int len, num;
@@ -362,17 +362,10 @@ opt_new(struct dev *d, char *name,
 		logx(2, "%s: initial MTC source, controlled by MMC", d->path);
 	}
 
-	if (strcmp(d->name, name) == 0)
-		a = d;
-	else {
-		/* circulate to the first "alternate" device (greatest num) */
-		for (a = d; a->alt_next->num > a->num; a = a->alt_next)
-			;
-	}
-
 	o = xmalloc(sizeof(struct opt));
 	o->num = num;
-	o->alt_first = o->dev = a;
+	o->dev = d;
+	o->alt_list = NULL;
 	o->refcnt = 0;
 
 	/*
@@ -398,6 +391,7 @@ opt_new(struct dev *d, char *name,
 	o->dup = dup;
 	o->mode = mode;
 	memcpy(o->name, name, len + 1);
+	opt_setalt(o, d);
 	o->next = *po;
 	*po = o;
 
@@ -408,6 +402,40 @@ opt_new(struct dev *d, char *name,
 	return o;
 }
 
+/*
+ * Make the given device the first alternate device: if it's on the list
+ * make it the first, else create a new one.
+ */
+void
+opt_setalt(struct opt *o, struct dev *d)
+{
+	struct opt_alt *a, **pa;
+
+	for (pa = &o->alt_list; ; pa = &a->next) {
+		if ((a = *pa) == NULL) {
+			a = xmalloc(sizeof(struct opt_alt));
+			a->dev = d;
+			break;
+		} else if (a->dev == d) {
+			*pa = a->next;
+			break;
+		}
+	}
+	a->next = o->alt_list;
+	o->alt_list = a;
+
+#ifdef DEBUG
+	size_t n = 0;
+	char buf[128];
+
+	for (a = o->alt_list; a != NULL; a = a->next) {
+		n += snprintf(buf + n, n >= sizeof(buf) ? 0 : sizeof(buf) - n,
+		    "%s%s", a->dev->path, (a->next != NULL) ? ", " : "");
+	}
+	logx(2, "%s: alt -> %s", o->name, buf);
+#endif
+}
+
 struct opt *
 opt_byname(char *name)
 {
@@ -562,42 +590,64 @@ opt_setdev(struct opt *o, struct dev *nd
 }
 
 /*
+ * Move the opt structure to a new device
+ */
+void
+opt_migrate(struct opt *o, struct dev *odev)
+{
+	struct opt_alt *a;
+	struct slot *s;
+	int i;
+
+	for (a = o->alt_list; a != NULL; a = a->next) {
+		if (a->dev == odev)
+			continue;
+		if (opt_setdev(o, a->dev))
+			return;
+	}
+	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
+		if (s->opt != o)
+			continue;
+		if (s->ops) {
+			s->ops->exit(s->arg);
+			s->ops = NULL;
+		}
+	}
+}
+
+/*
  * Get a reference to opt's device
  */
 struct dev *
 opt_ref(struct opt *o)
 {
 	struct dev *d;
+	struct opt_alt *a;
 
 	if (o->refcnt == 0) {
-		if (strcmp(o->name, o->dev->name) == 0) {
-			if (!dev_ref(o->dev))
+		/* find first working one */
+		a = o->alt_list;
+		while (1) {
+			if (a == NULL)
 				return NULL;
-		} else {
-			/* find first working one */
-			d = o->alt_first;
-			while (1) {
-				if (dev_ref(d))
-					break;
-				d = d->alt_next;
-				if (d == o->alt_first)
-					return NULL;
-			}
+			if (dev_ref(a->dev))
+				break;
+			a = a->next;
+		}
 
-			/* if device changed, move everything to the new one */
-			if (d != o->dev)
-				opt_setdev(o, d);
-
-			/* create server.device control */
-			for (d = dev_list; d != NULL; d = d->next) {
-				d->refcnt++;
-				if (d->pstate == DEV_CFG)
-					dev_open(d);
-				ctl_new(CTL_OPT_DEV, o, d,
-				    CTL_SEL, dev_getdisplay(d),
-				    o->name, "server", -1, "device",
-				    d->name, -1, 1, o->dev == d);
-			}
+		/* if device changed, move everything to the new one */
+		if (a->dev != o->dev)
+			opt_setdev(o, a->dev);
+
+		/* create server.device control */
+		for (d = dev_list; d != NULL; d = d->next) {
+			d->refcnt++;
+			if (d->pstate == DEV_CFG)
+				dev_open(d);
+			ctl_new(CTL_OPT_DEV, o, d,
+			    CTL_SEL, dev_getdisplay(d),
+			    o->name, "server", -1, "device",
+			    d->name, -1, 1, o->dev == d);
 		}
 	}
 
Index: opt.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/opt.h,v
diff -u -p -u -p -r1.10 opt.h
--- opt.h	20 Jun 2025 07:14:38 -0000	1.10
+++ opt.h	20 Nov 2025 11:11:19 -0000
@@ -29,9 +29,15 @@ struct app {
 	int vol;
 };
 
+struct opt_alt {
+	struct opt_alt *next;
+	struct dev *dev;
+};
+
 struct opt {
 	struct opt *next;
-	struct dev *dev, *alt_first;
+	struct dev *dev;
+	struct opt_alt *alt_list;
 	struct midi *midi;
 	struct mtc *mtc;	/* if set, MMC-controlled MTC source */
 
@@ -59,11 +65,13 @@ void opt_midi_dump(struct opt *o);
 struct opt *opt_new(struct dev *, char *, int, int, int, int,
     int, int, int, unsigned int);
 void opt_del(struct opt *);
+void opt_setalt(struct opt *, struct dev *);
 struct opt *opt_byname(char *);
 struct opt *opt_bynum(int);
 void opt_init(struct opt *);
 void opt_done(struct opt *);
 int opt_setdev(struct opt *, struct dev *);
+void opt_migrate(struct opt *, struct dev *);
 struct dev *opt_ref(struct opt *);
 void opt_unref(struct opt *);
 
Index: sndiod.8
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.8,v
diff -u -p -u -p -r1.18 sndiod.8
--- sndiod.8	19 Jun 2025 20:16:34 -0000	1.18
+++ sndiod.8	20 Nov 2025 11:11:19 -0000
@@ -522,11 +522,9 @@ wait for the MMC start signal and start 
 Regardless of which device a stream is connected to,
 its playback volume knob is exposed.
 .Sh HOT PLUGGING
-If devices specified with
-.Fl F
-are unavailable when needed or unplugged at runtime,
+If the current device is unavailable when needed or unplugged at runtime,
 .Nm
-will attempt to seamlessly fall back to the last device specified.
+will attempt to seamlessly fall back to the last working device.
 .Pp
 .Nm
 will not automatically switch to specified device that is plugged at runtime.
@@ -536,11 +534,7 @@ must be used to change the
 .Va server.device
 control.
 .Pp
-For instance, specifying a USB device with
-.Fl F
-following a PCI device with
-.Fl f
-allows
+For instance, switching from a PCI device to a USB device allows
 .Nm
 to use the USB one preferably when it's connected
 and to fall back to the PCI one when it's disconnected.
Index: sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
diff -u -p -u -p -r1.51 sndiod.c
--- sndiod.c	19 Jun 2025 20:16:34 -0000	1.51
+++ sndiod.c	20 Nov 2025 11:11:20 -0000
@@ -106,7 +106,7 @@ 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 *,
+struct opt *mkopt(char *, struct dev *, struct opt_alt *,
     int, int, int, int, int, int, int, int);
 
 unsigned int log_level = 0;
@@ -262,7 +262,8 @@ static void
 reopen_devs(void)
 {
 	struct opt *o;
-	struct dev *d, *a;
+	struct opt_alt *a;
+	struct dev *d;
 
 	for (o = opt_list; o != NULL; o = o->next) {
 
@@ -270,19 +271,10 @@ reopen_devs(void)
 		if (o->refcnt == 0 || strcmp(o->name, o->dev->name) == 0)
 			continue;
 
-		/* circulate to the device with the highest prio */
-		a = o->alt_first;
-		for (d = a; d->alt_next != a; d = d->alt_next) {
-			if (d->num > o->alt_first->num)
-				o->alt_first = d;
-		}
-
 		/* switch to the first working one, in pririty order */
-		d = o->alt_first;
-		while (d != o->dev) {
-			if (opt_setdev(o, d))
+		for (a = o->alt_list; a->dev != NULL; a = a->next) {
+			if (opt_setdev(o, a->dev))
 				break;
-			d = d->alt_next;
 		}
 	}
 
@@ -440,17 +432,20 @@ mkport(char *path, int hold)
 }
 
 struct opt *
-mkopt(char *path, struct dev *d,
+mkopt(char *path, struct dev *d, struct opt_alt *alt_list,
     int pmin, int pmax, int rmin, int rmax,
     int mode, int vol, int mmc, int dup)
 {
 	struct opt *o;
+	struct opt_alt *a;
 
 	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);
+	for (a = alt_list; a != NULL; a = a->next)
+		opt_setalt(o, a->dev);
 	return o;
 }
 
@@ -547,7 +542,8 @@ main(int argc, char **argv)
 	const char *str;
 	struct aparams par;
 	struct opt *o;
-	struct dev *d, *dev_first, *dev_next;
+	struct dev *d;
+	struct opt_alt *a, **pa, *alt_list;
 	struct port *p, *port_first, *port_next;
 	struct listen *l;
 	struct passwd *pw;
@@ -581,7 +577,7 @@ main(int argc, char **argv)
 	par.sig = 1;
 	par.msb = 0;
 	mode = MODE_PLAY | MODE_REC;
-	dev_first = dev_next = NULL;
+	alt_list = NULL;
 	port_first = port_next = NULL;
 	tcpaddr_list = NULL;
 	d = NULL;
@@ -641,7 +637,7 @@ main(int argc, char **argv)
 				}
 				d = dev_list;
 			}
-			if (mkopt(optarg, d, pmin, pmax, rmin, rmax,
+			if (mkopt(optarg, d, alt_list, pmin, pmax, rmin, rmax,
 				mode, vol, mmc, dup) == NULL)
 				return 1;
 			break;
@@ -678,18 +674,21 @@ main(int argc, char **argv)
 		case 'f':
 			d = mkdev(optarg, &par, 0, bufsz, round,
 			    rate, hold, autovol);
-			/* create new circulate list */
-			dev_first = dev_next = d;
+			while ((a = alt_list) != NULL) {
+				alt_list = a->next;
+				xfree(a);
+			}
 			break;
 		case 'F':
 			if (d == NULL)
 				errx(1, "-F %s: no devices defined", optarg);
-			d = mkdev(optarg, &par, 0, bufsz, round,
+			a = xmalloc(sizeof(struct opt_alt));
+			a->dev = mkdev(optarg, &par, 0, bufsz, round,
 			    rate, hold, autovol);
-			/* add to circulate list */
-			d->alt_next = dev_next;
-			dev_first->alt_next = d;
-			dev_next = d;
+			for (pa = &alt_list; *pa != NULL; pa = &(*pa)->next)
+				;
+			a->next = NULL;
+			*pa = a;
 			break;
 		default:
 			fputs(usagestr, stderr);
@@ -718,7 +717,7 @@ main(int argc, char **argv)
 	 */
 	o = opt_byname("default");
 	if (o == NULL) {
-		o = mkopt("default", dev_list, pmin, pmax, rmin, rmax,
+		o = mkopt("default", dev_list, alt_list, pmin, pmax, rmin, rmax,
 		    mode, vol, 0, dup);
 		if (o == NULL)
 			return 1;
@@ -733,6 +732,11 @@ main(int argc, char **argv)
 			o->maxweight, o->mtc != NULL, o->dup, o->mode) == NULL)
 			return 1;
 		dev_adjpar(d, o->mode, o->pmax, o->rmax);
+	}
+
+	while ((a = alt_list) != NULL) {
+		alt_list = a->next;
+		xfree(a);
 	}
 
 	setsig();