The diff below allows to specifiy "an alternate device" to use in case
the audio device is disconnected. If so, programs continue playing
using the other device.
For instance, if dmesg contains:
audio0 at azalia0
audio1 at uaudio0
and /etc/rc.conf.local contains:
sndiod_flags=-f rsnd/0 -F rsnd/1
then sndiod will try to use the usb device by default. If it's
disconnected, programs continue using the internal one. Note that
there's no way to detect that the usb device is reconnected; the
internal one will keep playing until all programs stop; then sndiod
will retry the usb one again. To force sndiod to retry the usb one,
send SIGHUP.
As we're at it, the same logic is applied to MIDI as well; it makes
possible to swap instuments without restarting programs.
For all this to work, both audio devices must support the same rate
(48kHz by default) and the same block size (now 10ms by default). This
requires the block size calculation to be fixed in the audio(9) layer,
so this diff is needed as well:
https://marc.info/?l=openbsd-tech&m=156610818325864
Please test even if you don't use this feature as this diff changes
the default audio block size (not all USB host controllers support
large transfers).
OK?
Index: dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
retrieving revision 1.58
diff -u -p -u -p -r1.58 dev.c
--- dev.c 29 Aug 2019 07:38:15 -0000 1.58
+++ dev.c 29 Aug 2019 07:52:08 -0000
@@ -968,7 +968,8 @@ dev_new(char *path, struct aparams *par,
return NULL;
}
d = xmalloc(sizeof(struct dev));
- d->path = xstrdup(path);
+ d->path_list = NULL;
+ namelist_add(&d->path_list, path);
d->num = dev_sndnum++;
d->opt_list = NULL;
@@ -1173,6 +1174,94 @@ dev_close(struct dev *d)
dev_close_do(d);
}
+/*
+ * Close the device, but attempt to migrate everything to a new sndio
+ * device.
+ */
+void
+dev_reopen(struct dev *d)
+{
+ struct slot *s;
+ long long pos;
+ unsigned int mode, round, bufsz, rate, pstate;
+ int delta;
+
+ /* not opened */
+ if (d->pstate == DEV_CFG)
+ return;
+
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": reopening device\n");
+ }
+
+ /* save state */
+ mode = d->mode;
+ round = d->round;
+ bufsz = d->bufsz;
+ rate = d->rate;
+ delta = d->delta;
+ pstate = d->pstate;
+
+ /* close device */
+ dev_close_do(d);
+
+ /* open device */
+ if (!dev_open_do(d)) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": found no working alternate device\n");
+ }
+ dev_exitall(d);
+ return;
+ }
+
+ /* check if new parameters are compatible with old ones */
+ if (d->mode != mode ||
+ d->round != round ||
+ d->bufsz != bufsz ||
+ d->rate != rate) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": alternate device not compatible\n");
+ }
+ dev_close(d);
+ return;
+ }
+
+ /*
+ * adjust time positions, make anything go back delta ticks, so
+ * that the new device can start at zero
+ */
+ for (s = d->slot_list; s != NULL; s = s->next) {
+ pos = (long long)(d->round - delta) * s->round + s->delta_rem;
+ s->delta_rem = pos % d->round;
+ s->delta += pos / (int)d->round;
+ s->delta -= s->round;
+ if (log_level >= 2) {
+ slot_log(s);
+ log_puts(": adjusted: delta -> ");
+ log_puti(s->delta);
+ log_puts(", delta_rem -> ");
+ log_puti(s->delta_rem);
+ log_puts("\n");
+ }
+ }
+ if (d->tstate == MMC_RUN) {
+ d->mtc.delta -= delta * MTC_SEC;
+ if (log_level >= 2) {
+ dev_log(d);
+ log_puts(": adjusted mtc: delta ->");
+ log_puti(d->mtc.delta);
+ log_puts("\n");
+ }
+ }
+
+ /* start the device if needed */
+ if (pstate == DEV_RUN)
+ dev_wakeup(d);
+}
+
int
dev_ref(struct dev *d)
{
@@ -1279,7 +1368,7 @@ dev_del(struct dev *d)
}
midi_del(d->midi);
*p = d->next;
- xfree(d->path);
+ namelist_clear(&d->path_list);
xfree(d);
}
Index: dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 dev.h
--- dev.h 12 Jul 2019 06:30:55 -0000 1.21
+++ dev.h 29 Aug 2019 07:52:08 -0000
@@ -159,7 +159,7 @@ struct dev {
#define DEV_INIT 1 /* stopped */
#define DEV_RUN 2 /* playin & recording */
unsigned int pstate; /* one of above */
- char *path; /* sio path */
+ struct name *path_list;
/*
* actual parameters and runtime state (i.e. once opened)
@@ -201,6 +201,7 @@ extern struct dev *dev_list;
void dev_log(struct dev *);
void dev_close(struct dev *);
+void dev_reopen(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: fdpass.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.c,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 fdpass.c
--- fdpass.c 28 Jun 2019 13:35:03 -0000 1.6
+++ fdpass.c 29 Aug 2019 07:52:08 -0000
@@ -300,6 +300,7 @@ fdpass_in_helper(void *arg)
struct fdpass *f = arg;
struct dev *d;
struct port *p;
+ struct name *path;
if (!fdpass_recv(f, &cmd, &num, &mode, &fd))
return;
@@ -314,7 +315,11 @@ fdpass_in_helper(void *arg)
fdpass_close(f);
return;
}
- fd = sio_sun_getfd(d->path, mode, 1);
+ for (path = d->path_list; path != NULL; path = path->next) {
+ fd = sio_sun_getfd(path->str, mode, 1);
+ if (fd != -1)
+ break;
+ }
break;
case FDPASS_OPEN_MIDI:
p = port_bynum(num);
@@ -326,7 +331,11 @@ fdpass_in_helper(void *arg)
fdpass_close(f);
return;
}
- fd = mio_rmidi_getfd(p->path, mode, 1);
+ for (path = p->path_list; path != NULL; path = path->next) {
+ fd = mio_rmidi_getfd(path->str, mode, 1);
+ if (fd != -1)
+ break;
+ }
break;
default:
fdpass_close(f);
Index: midi.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.c,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 midi.c
--- midi.c 29 Aug 2019 07:19:15 -0000 1.21
+++ midi.c 29 Aug 2019 07:52:08 -0000
@@ -438,7 +438,8 @@ port_new(char *path, unsigned int mode,
struct port *c;
c = xmalloc(sizeof(struct port));
- c->path = xstrdup(path);
+ c->path_list = NULL;
+ namelist_add(&c->path_list, path);
c->state = PORT_CFG;
c->hold = hold;
c->midi = midi_new(&port_midiops, c, mode);
@@ -468,7 +469,7 @@ port_del(struct port *c)
#endif
}
*p = c->next;
- xfree(c->path);
+ namelist_clear(&c->path_list);
xfree(c);
}
@@ -592,4 +593,27 @@ port_done(struct port *c)
{
if (c->state == PORT_INIT)
port_drain(c);
+}
+
+void
+port_reopen(struct port *p)
+{
+ if (p->state == PORT_CFG)
+ return;
+
+ if (log_level >= 1) {
+ port_log(p);
+ log_puts(": reopening port\n");
+ }
+
+ port_mio_close(p);
+
+ if (!port_mio_open(p)) {
+ if (log_level >= 1) {
+ port_log(p);
+ log_puts(": found no working alternate port\n");
+ }
+ p->state = PORT_CFG;
+ port_exitall(p);
+ }
}
Index: midi.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 midi.h
--- midi.h 29 Aug 2019 07:11:28 -0000 1.9
+++ midi.h 29 Aug 2019 07:52:08 -0000
@@ -88,7 +88,7 @@ struct port {
#define PORT_DRAIN 2
unsigned int state;
unsigned int num; /* port serial number */
- char *path;
+ struct name *path_list;
int hold; /* hold the port open ? */
struct midi *midi;
};
@@ -121,5 +121,6 @@ int port_init(struct port *);
void port_done(struct port *);
void port_drain(struct port *);
int port_close(struct port *);
+void port_reopen(struct port *);
#endif /* !defined(MIDI_H) */
Index: miofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/miofile.c,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 miofile.c
--- miofile.c 20 Dec 2015 11:38:33 -0000 1.4
+++ miofile.c 29 Aug 2019 07:52:08 -0000
@@ -48,10 +48,13 @@ struct fileops port_mio_ops = {
int
port_mio_open(struct port *p)
{
+ char *path;
+
p->mio.hdl = fdpass_mio_open(p->num, p->midi->mode);
if (p->mio.hdl == NULL)
return 0;
- p->mio.file = file_new(&port_mio_ops, p, p->path, mio_nfds(p->mio.hdl));
+ path = "port";
+ p->mio.file = file_new(&port_mio_ops, p, path, mio_nfds(p->mio.hdl));
return 1;
}
@@ -129,5 +132,5 @@ port_mio_hup(void *arg)
{
struct port *p = arg;
- port_close(p);
+ port_reopen(p);
}
Index: siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
retrieving revision 1.15
diff -u -p -u -p -r1.15 siofile.c
--- siofile.c 29 Aug 2019 07:05:47 -0000 1.15
+++ siofile.c 29 Aug 2019 07:52:08 -0000
@@ -92,6 +92,7 @@ dev_sio_open(struct dev *d)
{
struct sio_par par;
unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
+ char *path;
d->sio.hdl = fdpass_sio_open(d->num, mode);
if (d->sio.hdl == NULL) {
@@ -113,6 +114,7 @@ dev_sio_open(struct dev *d)
log_puts(" mode\n");
}
}
+ path = "dev";
sio_initpar(&par);
par.bits = d->par.bits;
par.bps = d->par.bps;
@@ -212,7 +214,7 @@ dev_sio_open(struct dev *d)
if (!(mode & MODE_REC))
d->mode &= ~MODE_REC;
sio_onmove(d->sio.hdl, dev_sio_onmove, d);
- d->sio.file = file_new(&dev_sio_ops, d, d->path, sio_nfds(d->sio.hdl));
+ d->sio.file = file_new(&dev_sio_ops, d, path, sio_nfds(d->sio.hdl));
timo_set(&d->sio.watchdog, dev_sio_timeout, d);
return 1;
bad_close:
@@ -494,5 +496,5 @@ dev_sio_hup(void *arg)
log_puts(": disconnected\n");
}
#endif
- dev_close(d);
+ dev_reopen(d);
}
Index: sndiod.8
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.8,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 sndiod.8
--- sndiod.8 18 Jan 2016 11:38:07 -0000 1.2
+++ sndiod.8 29 Aug 2019 07:52:08 -0000
@@ -29,10 +29,12 @@
.Op Fl C Ar min : Ns Ar max
.Op Fl c Ar min : Ns Ar max
.Op Fl e Ar enc
+.Op Fl F Ar device
.Op Fl f Ar device
.Op Fl j Ar flag
.Op Fl L Ar addr
.Op Fl m Ar mode
+.Op Fl Q Ar port
.Op Fl q Ar port
.Op Fl r Ar rate
.Op Fl s Ar name
@@ -182,6 +184,18 @@ or
Only the signedness and the precision are mandatory.
Examples:
.Va u8 , s16le , s24le3 , s24le4lsb .
+.It Fl F Ar device
+Specify an alternate device to use.
+If doesn't work, the one given with the last
+.Fl f
+or
+.Fl F
+options will be used.
+For instance, specifying a USB device following a
+PCI 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.
.It Fl f Ar device
Add this
.Xr sndio 7
@@ -245,6 +259,15 @@ but the same sub-device cannot be used f
The default is
.Ar play , Ns Ar rec
(i.e. full-duplex).
+.It Fl Q Ar port
+Specify an alternate MIDI port to use.
+If doesn't work, the one given with the last
+.Fl Q
+or
+.Fl q
+options will be used.
+For instance, this allows to replace a USB MIDI controller without
+the need to restart programs using it.
.It Fl q Ar port
Expose the given MIDI port.
This allows multiple programs to share the port.
@@ -376,11 +399,15 @@ is
If
.Nm
is sent
-.Dv SIGHUP ,
.Dv SIGINT
or
.Dv SIGTERM ,
it terminates.
+If
+.Nm
+is sent
+.Dv SIGHUP ,
+it reopens all audio devices and MIDI ports.
.Pp
By default, when the program cannot accept
recorded data fast enough or cannot provide data to play fast enough,
Index: sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
retrieving revision 1.35
diff -u -p -u -p -r1.35 sndiod.c
--- sndiod.c 29 Jun 2019 21:23:18 -0000 1.35
+++ sndiod.c 29 Aug 2019 07:52:08 -0000
@@ -75,7 +75,7 @@
* block size if neither ``-z'' nor ``-b'' is used
*/
#ifndef DEFAULT_ROUND
-#define DEFAULT_ROUND 960
+#define DEFAULT_ROUND 480
#endif
/*
@@ -93,6 +93,7 @@
#endif
void sigint(int);
+void sighup(int);
void opt_ch(int *, int *);
void opt_enc(struct aparams *);
int opt_mmc(void);
@@ -109,12 +110,13 @@ struct opt *mkopt(char *, struct dev *,
int, int, int, int, int, int, int, int);
unsigned int log_level = 0;
-volatile sig_atomic_t quit_flag = 0;
+volatile sig_atomic_t quit_flag = 0, reopen_flag = 0;
char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
- "[-C min:max] [-c min:max] [-e enc]\n\t"
- "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t"
- "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n";
+ "[-C min:max] [-c min:max]\n\t"
+ "[-e enc] [-F device] [-f device] [-j flag] [-L addr] [-m mode]\n\t"
+ "[-Q port] [-q port] [-r rate] [-s name] [-t mode] [-U unit]\n\t"
+ "[-v volume] [-w flag] [-z nframes]\n";
/*
* SIGINT handler, it raises the quit flag. If the flag is already set,
@@ -129,6 +131,16 @@ sigint(int s)
quit_flag = 1;
}
+/*
+ * SIGHUP handler, it raises the reopen flag, which requests devices
+ * to be reopened.
+ */
+void
+sighup(int s)
+{
+ reopen_flag = 1;
+}
+
void
opt_ch(int *rcmin, int *rcmax)
{
@@ -231,6 +243,7 @@ setsig(void)
struct sigaction sa;
quit_flag = 0;
+ reopen_flag = 0;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigint;
@@ -238,6 +251,7 @@ setsig(void)
err(1, "sigaction(int) failed");
if (sigaction(SIGTERM, &sa, NULL) == -1)
err(1, "sigaction(term) failed");
+ sa.sa_handler = sighup;
if (sigaction(SIGHUP, &sa, NULL) == -1)
err(1, "sigaction(hup) failed");
}
@@ -294,7 +308,8 @@ mkdev(char *path, struct aparams *par,
struct dev *d;
for (d = dev_list; d != NULL; d = d->next) {
- if (strcmp(d->path, path) == 0)
+ if (d->path_list->next == NULL &&
+ strcmp(d->path_list->str, path) == 0)
return d;
}
if (!bufsz && !round) {
@@ -316,7 +331,8 @@ mkport(char *path, int hold)
struct port *c;
for (c = port_list; c != NULL; c = c->next) {
- if (strcmp(c->path, path) == 0)
+ if (c->path_list->next == NULL &&
+ strcmp(c->path_list->str, path) == 0)
return c;
}
c = port_new(path, MODE_MIDIMASK, hold);
@@ -361,6 +377,7 @@ start_helper(int background)
struct dev *d;
struct port *p;
struct passwd *pw;
+ struct name *n;
int s[2];
pid_t pid;
@@ -395,10 +412,14 @@ start_helper(int background)
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
err(1, "cannot drop privileges");
}
- for (d = dev_list; d != NULL; d = d->next)
- dounveil(d->path, "rsnd/", "/dev/audio");
- for (p = port_list; p != NULL; p = p->next)
- dounveil(p->path, "rmidi/", "/dev/rmidi");
+ for (d = dev_list; d != NULL; d = d->next) {
+ for (n = d->path_list; n != NULL; n = n->next)
+ dounveil(n->str, "rsnd/", "/dev/audio");
+ }
+ for (p = port_list; p != NULL; p = p->next) {
+ for (n = p->path_list; n != NULL; n = n->next)
+ dounveil(n->str, "rmidi/", "/dev/rmidi");
+ }
if (pledge("stdio sendfd rpath wpath", NULL) == -1)
err(1, "pledge");
while (file_poll())
@@ -461,7 +482,8 @@ main(int argc, char **argv)
mode = MODE_PLAY | MODE_REC;
tcpaddr_list = NULL;
- while ((c = getopt(argc, argv,
"a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
+ while ((c = getopt(argc, argv,
+ "a:b:c:C:de:F:f:j:L:m:Q:q:r:s:t:U:v:w:x:z:")) != -1) {
switch (c) {
case 'd':
log_level++;
@@ -518,6 +540,11 @@ main(int argc, char **argv)
case 'q':
mkport(optarg, hold);
break;
+ case 'Q':
+ if (port_list == NULL)
+ errx(1, "-Q %s: no ports defined", optarg);
+ namelist_add(&port_list->path_list, optarg);
+ break;
case 'a':
hold = opt_onoff();
break;
@@ -538,6 +565,11 @@ main(int argc, char **argv)
mkdev(optarg, &par, 0, bufsz, round,
rate, hold, autovol);
break;
+ case 'F':
+ if (dev_list == NULL)
+ errx(1, "-F %s: no devices defined", optarg);
+ namelist_add(&dev_list->path_list, optarg);
+ break;
default:
fputs(usagestr, stderr);
return 1;
@@ -617,6 +649,13 @@ main(int argc, char **argv)
for (;;) {
if (quit_flag)
break;
+ if (reopen_flag) {
+ reopen_flag = 0;
+ for (d = dev_list; d != NULL; d = d->next)
+ dev_reopen(d);
+ for (p = port_list; p != NULL; p = p->next)
+ port_reopen(p);
+ }
if (!fdpass_peer)
break;
if (!file_poll())
Index: utils.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/utils.c,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 utils.c
--- utils.c 5 Jul 2019 07:34:40 -0000 1.5
+++ utils.c 29 Aug 2019 07:52:08 -0000
@@ -188,3 +188,30 @@ xstrdup(char *s)
memcpy(p, s, size);
return p;
}
+
+/*
+ * copy and append the given string to the name list
+ */
+void
+namelist_add(struct name **list, char *str)
+{
+ struct name *n;
+ size_t size;
+
+ size = strlen(str) + 1;
+ n = xmalloc(sizeof(struct name) + size);
+ memcpy(n->str, str, size);
+ n->next = *list;
+ *list = n;
+}
+
+void
+namelist_clear(struct name **list)
+{
+ struct name *n;
+
+ while ((n = *list) != NULL) {
+ *list = n->next;
+ xfree(n);
+ }
+}
Index: utils.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/utils.h,v
retrieving revision 1.3
diff -u -p -u -p -r1.3 utils.h
--- utils.h 12 May 2013 04:58:41 -0000 1.3
+++ utils.h 29 Aug 2019 07:52:08 -0000
@@ -20,6 +20,11 @@
#include <stddef.h>
+struct name {
+ struct name *next;
+ char str[];
+};
+
void log_puts(char *);
void log_putx(unsigned long);
void log_putu(unsigned long);
@@ -30,6 +35,9 @@ void log_flush(void);
void *xmalloc(size_t);
char *xstrdup(char *);
void xfree(void *);
+
+void namelist_add(struct name **, char *);
+void namelist_clear(struct name **);
/*
* Log levels: