Allow switching between devices without disconnecting clients, again.

The new -F option allows alternate device to be specified.  If the
device is disconnected, the one given with the last -f or -F options
will be used instead.

ok mpi@
This commit is contained in:
Alexandre Ratchov 2019-09-21 06:31:41 +02:00
parent 3935da37f6
commit 441100e812
12 changed files with 362 additions and 22 deletions

View File

@ -970,7 +970,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;
@ -1175,6 +1176,74 @@ dev_close(struct dev *d)
dev_freebufs(d);
}
/*
* Close the device, but attempt to migrate everything to a new sndio
* device.
*/
int
dev_reopen(struct dev *d)
{
struct slot *s;
long long pos;
unsigned int pstate;
int delta;
/* not opened */
if (d->pstate == DEV_CFG)
return 1;
/* save state */
delta = d->delta;
pstate = d->pstate;
if (!dev_sio_reopen(d))
return 0;
/* reopen returns a stopped device */
d->pstate = DEV_INIT;
/* reallocate new buffers, with new parameters */
dev_freebufs(d);
dev_allocbufs(d);
/*
* 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)s->delta * d->round + s->delta_rem;
pos -= (long long)delta * s->round;
s->delta_rem = pos % (int)d->round;
s->delta = pos / (int)d->round;
if (log_level >= 3) {
slot_log(s);
log_puts(": adjusted: delta -> ");
log_puti(s->delta);
log_puts(", delta_rem -> ");
log_puti(s->delta_rem);
log_puts("\n");
}
/* reinitilize the format conversion chain */
slot_initconv(s);
}
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);
return 1;
}
int
dev_ref(struct dev *d)
{
@ -1281,7 +1350,7 @@ dev_del(struct dev *d)
}
midi_del(d->midi);
*p = d->next;
xfree(d->path);
namelist_clear(&d->path_list);
xfree(d);
}

View File

@ -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 *);
int 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);

View File

@ -439,7 +439,8 @@ port_new(char *path, unsigned int mode, int hold)
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);
@ -469,7 +470,7 @@ port_del(struct port *c)
#endif
}
*p = c->next;
xfree(c->path);
namelist_clear(&c->path_list);
xfree(c);
}
@ -594,3 +595,15 @@ port_done(struct port *c)
if (c->state == PORT_INIT)
port_drain(c);
}
int
port_reopen(struct port *p)
{
if (p->state == PORT_CFG)
return 1;
if (!port_mio_reopen(p))
return 0;
return 1;
}

View File

@ -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 *);
int port_reopen(struct port *);
#endif /* !defined(MIDI_H) */

View File

@ -44,13 +44,68 @@ struct fileops port_mio_ops = {
port_mio_hup
};
/*
* open the port using one of the provided paths
*/
static struct mio_hdl *
port_mio_openlist(struct port *c, unsigned int mode)
{
struct mio_hdl *hdl;
struct name *n;
n = c->path_list;
while (1) {
if (n == NULL)
break;
hdl = mio_open(n->str, mode, 1);
if (hdl != NULL) {
if (log_level >= 2) {
port_log(c);
log_puts(": using ");
log_puts(n->str);
log_puts("\n");
}
return hdl;
}
n = n->next;
}
return NULL;
}
int
port_mio_open(struct port *p)
{
p->mio.hdl = mio_open(p->path, p->midi->mode, 1);
p->mio.hdl = port_mio_openlist(p, 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));
p->mio.file = file_new(&port_mio_ops, p, "port", mio_nfds(p->mio.hdl));
return 1;
}
/*
* Open an alternate port. Upon success, close the old port
* and continue using the new one.
*/
int
port_mio_reopen(struct port *p)
{
struct mio_hdl *hdl;
hdl = port_mio_openlist(p, p->midi->mode);
if (hdl == NULL) {
if (log_level >= 1) {
port_log(p);
log_puts(": couldn't open an alternate port\n");
}
return 0;
}
/* close unused device */
file_del(p->mio.file);
mio_close(p->mio.hdl);
p->mio.hdl = hdl;
p->mio.file = file_new(&port_mio_ops, p, "port", mio_nfds(hdl));
return 1;
}
@ -128,5 +183,6 @@ port_mio_hup(void *arg)
{
struct port *p = arg;
port_close(p);
if (!port_reopen(p))
port_close(p);
}

View File

@ -25,6 +25,7 @@ struct port_mio {
};
int port_mio_open(struct port *);
int port_mio_reopen(struct port *);
void port_mio_close(struct port *);
#endif /* !defined(MIOFILE_H) */

View File

@ -83,6 +83,34 @@ dev_sio_timeout(void *arg)
dev_close(d);
}
/*
* open the device using one of the provided paths
*/
static struct sio_hdl *
dev_sio_openlist(struct dev *d, unsigned int mode)
{
struct name *n;
struct sio_hdl *hdl;
n = d->path_list;
while (1) {
if (n == NULL)
break;
hdl = sio_open(n->str, mode, 1);
if (hdl != NULL) {
if (log_level >= 2) {
dev_log(d);
log_puts(": using ");
log_puts(n->str);
log_puts("\n");
}
return hdl;
}
n = n->next;
}
return NULL;
}
/*
* open the device.
*/
@ -92,15 +120,15 @@ dev_sio_open(struct dev *d)
struct sio_par par;
unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
d->sio.hdl = sio_open(d->path, mode, 1);
d->sio.hdl = dev_sio_openlist(d, mode);
if (d->sio.hdl == NULL) {
if (mode != (SIO_PLAY | SIO_REC))
return 0;
d->sio.hdl = sio_open(d->path, SIO_PLAY, 1);
d->sio.hdl = dev_sio_openlist(d, SIO_PLAY);
if (d->sio.hdl != NULL)
mode = SIO_PLAY;
else {
d->sio.hdl = sio_open(d->path, SIO_REC, 1);
d->sio.hdl = dev_sio_openlist(d, SIO_REC);
if (d->sio.hdl != NULL)
mode = SIO_REC;
else
@ -211,7 +239,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, "dev", sio_nfds(d->sio.hdl));
timo_set(&d->sio.watchdog, dev_sio_timeout, d);
return 1;
bad_close:
@ -219,6 +247,79 @@ dev_sio_open(struct dev *d)
return 0;
}
/*
* Open an alternate device. Upon success and if the new device is
* compatible with the old one, close the old device and continue
* using the new one. The new device is not started.
*/
int
dev_sio_reopen(struct dev *d)
{
struct sio_par par;
struct sio_hdl *hdl;
hdl = dev_sio_openlist(d, d->mode & (MODE_PLAY | MODE_REC));
if (hdl == NULL) {
if (log_level >= 1) {
dev_log(d);
log_puts(": couldn't open an alternate device\n");
}
return 0;
}
sio_initpar(&par);
par.bits = d->par.bits;
par.bps = d->par.bps;
par.sig = d->par.sig;
par.le = d->par.le;
par.msb = d->par.msb;
if (d->mode & SIO_PLAY)
par.pchan = d->pchan;
if (d->mode & SIO_REC)
par.rchan = d->rchan;
par.appbufsz = d->bufsz;
par.round = d->round;
par.rate = d->rate;
if (!sio_setpar(hdl, &par))
goto bad_close;
if (!sio_getpar(hdl, &par))
goto bad_close;
/* check if new parameters are compatible with old ones */
if (par.round != d->round || par.bufsz != d->bufsz ||
par.rate != d->rate) {
if (log_level >= 1) {
dev_log(d);
log_puts(": alternate device not compatible\n");
}
goto bad_close;
}
/* close unused device */
timo_del(&d->sio.watchdog);
file_del(d->sio.file);
sio_close(d->sio.hdl);
/* update parameters */
d->par.bits = par.bits;
d->par.bps = par.bps;
d->par.sig = par.sig;
d->par.le = par.le;
d->par.msb = par.msb;
if (d->mode & SIO_PLAY)
d->pchan = par.pchan;
if (d->mode & SIO_REC)
d->rchan = par.rchan;
d->sio.hdl = hdl;
d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
sio_onmove(hdl, dev_sio_onmove, d);
return 1;
bad_close:
sio_close(hdl);
return 0;
}
void
dev_sio_close(struct dev *d)
{
@ -493,5 +594,6 @@ dev_sio_hup(void *arg)
log_puts(": disconnected\n");
}
#endif
dev_close(d);
if (!dev_reopen(d))
dev_close(d);
}

View File

@ -38,6 +38,7 @@ struct dev_sio {
};
int dev_sio_open(struct dev *);
int dev_sio_reopen(struct dev *);
void dev_sio_close(struct dev *);
void dev_sio_log(struct dev *);
void dev_sio_start(struct dev *);

View File

@ -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 for both recording and monitoring.
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,

View File

@ -86,6 +86,7 @@
#endif
void sigint(int);
void sighup(int);
void opt_ch(int *, int *);
void opt_enc(struct aparams *);
int opt_mmc(void);
@ -102,12 +103,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,
@ -122,6 +124,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)
{
@ -224,6 +236,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;
@ -231,6 +244,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");
}
@ -287,7 +301,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) {
@ -309,7 +324,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);
@ -375,7 +391,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++;
@ -432,6 +449,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;
@ -452,6 +474,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;
@ -519,6 +546,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 (!file_poll())
break;
}

View File

@ -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);
}
}

View File

@ -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);
@ -31,6 +36,9 @@ void *xmalloc(size_t);
char *xstrdup(char *);
void xfree(void *);
void namelist_add(struct name **, char *);
void namelist_clear(struct name **);
/*
* Log levels:
*