diff --git a/sndiod/dev.c b/sndiod/dev.c index ee11441..01a5388 100644 --- a/sndiod/dev.c +++ b/sndiod/dev.c @@ -58,7 +58,10 @@ int dev_getpos(struct dev *); struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); void dev_adjpar(struct dev *, int, int, int); +int dev_open_do(struct dev *); int dev_open(struct dev *); +void dev_exitall(struct dev *); +void dev_close_do(struct dev *); void dev_close(struct dev *); int dev_ref(struct dev *); void dev_unref(struct dev *); @@ -973,7 +976,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; @@ -1043,27 +1047,14 @@ dev_adjpar(struct dev *d, int mode, * monitor, midi control, and any necessary conversions. */ int -dev_open(struct dev *d) +dev_open_do(struct dev *d) { int i, gunit; struct ctl *c; - d->mode = d->reqmode; - d->round = d->reqround; - d->bufsz = d->reqbufsz; - d->rate = d->reqrate; - d->pchan = d->reqpchan; - d->rchan = d->reqrchan; - d->par = d->reqpar; - if (d->pchan == 0) - d->pchan = 2; - if (d->rchan == 0) - d->rchan = 2; if (!dev_sio_open(d)) { if (log_level >= 1) { dev_log(d); - log_puts(": "); - log_puts(d->path); log_puts(": failed to open audio device\n"); } return 0; @@ -1155,16 +1146,52 @@ dev_open(struct dev *d) } /* - * force the device to go in DEV_CFG state, the caller is supposed to - * ensure buffers are drained + * Reset parameters and open the device. + */ +int +dev_open(struct dev *d) +{ + d->mode = d->reqmode; + d->round = d->reqround; + d->bufsz = d->reqbufsz; + d->rate = d->reqrate; + d->pchan = d->reqpchan; + d->rchan = d->reqrchan; + d->par = d->reqpar; + if (d->pchan == 0) + d->pchan = 2; + if (d->rchan == 0) + d->rchan = 2; + if (!dev_open_do(d)) + return 0; + return 1; +} + +/* + * Force all slots to exit */ void -dev_close(struct dev *d) +dev_exitall(struct dev *d) { int i; struct slot *s; struct ctl *c; + for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) { + if (s->ops) + s->ops->exit(s->arg); + s->ops = NULL; + } + d->slot_list = NULL; +} + +/* + * force the device to go in DEV_CFG state, the caller is supposed to + * ensure buffers are drained + */ +void +dev_close_do(struct dev *d) +{ #ifdef DEBUG if (log_level >= 3) { dev_log(d); @@ -1176,12 +1203,6 @@ dev_close(struct dev *d) xfree(c); } d->pstate = DEV_CFG; - for (s = d->slot, i = DEV_NSLOT; i > 0; i--, s++) { - if (s->ops) - s->ops->exit(s->arg); - s->ops = NULL; - } - d->slot_list = NULL; dev_siomix_close(d); dev_sio_close(d); if (d->mode & MODE_PLAY) { @@ -1196,6 +1217,104 @@ dev_close(struct dev *d) } } +/* + * Close the device and exit all slots + */ +void +dev_close(struct dev *d) +{ + dev_exitall(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) { @@ -1302,7 +1421,7 @@ dev_del(struct dev *d) } midi_del(d->midi); *p = d->next; - xfree(d->path); + namelist_clear(&d->path_list); xfree(d); } diff --git a/sndiod/dev.h b/sndiod/dev.h index 19303e8..961be85 100644 --- a/sndiod/dev.h +++ b/sndiod/dev.h @@ -196,7 +196,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) @@ -246,6 +246,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); diff --git a/sndiod/midi.c b/sndiod/midi.c index b5cc4e8..00f941c 100644 --- a/sndiod/midi.c +++ b/sndiod/midi.c @@ -33,6 +33,7 @@ void port_imsg(void *, unsigned char *, int); void port_omsg(void *, unsigned char *, int); void port_fill(void *, int); void port_exit(void *); +void port_exitall(struct port *); struct midiops port_midiops = { port_imsg, @@ -438,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); @@ -468,7 +470,7 @@ port_del(struct port *c) #endif } *p = c->next; - xfree(c->path); + namelist_clear(&c->path_list); xfree(c); } @@ -521,7 +523,7 @@ port_open(struct port *c) { if (!port_mio_open(c)) { if (log_level >= 1) { - log_puts(c->path); + port_log(c); log_puts(": failed to open midi port\n"); } return 0; @@ -530,11 +532,23 @@ port_open(struct port *c) return 1; } -int -port_close(struct port *c) +void +port_exitall(struct port *c) { int i; struct midi *ep; + + for (i = 0; i < MIDI_NEP; i++) { + ep = midi_ep + i; + if ((ep->txmask & c->midi->self) || + (c->midi->txmask & ep->self)) + ep->ops->exit(ep->arg); + } +} + +int +port_close(struct port *c) +{ #ifdef DEBUG if (c->state == PORT_CFG) { port_log(c); @@ -545,12 +559,7 @@ port_close(struct port *c) c->state = PORT_CFG; port_mio_close(c); - for (i = 0; i < MIDI_NEP; i++) { - ep = midi_ep + i; - if ((ep->txmask & c->midi->self) || - (c->midi->txmask & ep->self)) - ep->ops->exit(ep->arg); - } + port_exitall(c); return 1; } @@ -586,3 +595,26 @@ 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); + } +} diff --git a/sndiod/midi.h b/sndiod/midi.h index 565cf21..26f6e86 100644 --- a/sndiod/midi.h +++ b/sndiod/midi.h @@ -88,8 +88,8 @@ struct port { #define PORT_DRAIN 2 unsigned int state; unsigned int num; /* port serial number */ - char *path; /* hold the port open ? */ - int hold; + 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) */ diff --git a/sndiod/miofile.c b/sndiod/miofile.c index 5b037f0..7669468 100644 --- a/sndiod/miofile.c +++ b/sndiod/miofile.c @@ -44,13 +44,35 @@ struct fileops port_mio_ops = { port_mio_hup }; +/* + * open the port using one of the provided paths + */ +static char * +port_mio_openlist(struct port *c, unsigned int mode) +{ + struct name *n; + + n = c->path_list; + while (1) { + if (n == NULL) + break; + c->mio.hdl = mio_open(n->str, mode, 1); + if (c->mio.hdl != NULL) + return n->str; + n = n->next; + } + return NULL; +} + int port_mio_open(struct port *p) { - p->mio.hdl = mio_open(p->path, p->midi->mode, 1); + char *path; + + path = 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, path, mio_nfds(p->mio.hdl)); return 1; } @@ -128,5 +150,5 @@ port_mio_hup(void *arg) { struct port *p = arg; - port_close(p); + port_reopen(p); } diff --git a/sndiod/siofile.c b/sndiod/siofile.c index d2f4ef4..60e4a28 100644 --- a/sndiod/siofile.c +++ b/sndiod/siofile.c @@ -84,6 +84,26 @@ dev_sio_timeout(void *arg) dev_close(d); } +/* + * open the device using one of the provided paths + */ +static char * +dev_sio_openlist(struct dev *d, unsigned int mode) +{ + struct name *n; + + n = d->path_list; + while (1) { + if (n == NULL) + break; + d->sio.hdl = sio_open(n->str, mode, 1); + if (d->sio.hdl != NULL) + return n->str; + n = n->next; + } + return NULL; +} + /* * open the device. */ @@ -92,17 +112,18 @@ dev_sio_open(struct dev *d) { struct sio_par par; unsigned int mode = d->mode & (MODE_PLAY | MODE_REC); + char *path; - d->sio.hdl = sio_open(d->path, mode, 1); - if (d->sio.hdl == NULL) { + path = dev_sio_openlist(d, mode); + if (path == NULL) { if (mode != (SIO_PLAY | SIO_REC)) return 0; - d->sio.hdl = sio_open(d->path, SIO_PLAY, 1); - if (d->sio.hdl != NULL) + path = dev_sio_openlist(d, SIO_PLAY); + if (path != NULL) mode = SIO_PLAY; else { - d->sio.hdl = sio_open(d->path, SIO_REC, 1); - if (d->sio.hdl != NULL) + path = dev_sio_openlist(d, SIO_REC); + if (path != NULL) mode = SIO_REC; else return 0; @@ -144,35 +165,35 @@ dev_sio_open(struct dev *d) */ if (par.bits > BITS_MAX) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.bits); log_puts(": unsupported number of bits\n"); goto bad_close; } if (par.bps > SIO_BPS(BITS_MAX)) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.bps); log_puts(": unsupported sample size\n"); goto bad_close; } if ((mode & SIO_PLAY) && par.pchan > NCHAN_MAX) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.pchan); log_puts(": unsupported number of play channels\n"); goto bad_close; } if ((mode & SIO_REC) && par.rchan > NCHAN_MAX) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.rchan); log_puts(": unsupported number of rec channels\n"); goto bad_close; } if (par.bufsz == 0 || par.bufsz > RATE_MAX) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.bufsz); log_puts(": unsupported buffer size\n"); @@ -180,14 +201,14 @@ dev_sio_open(struct dev *d) } if (par.round == 0 || par.round > par.bufsz || par.bufsz % par.round != 0) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.round); log_puts(": unsupported block size\n"); goto bad_close; } if (par.rate == 0 || par.rate > RATE_MAX) { - log_puts(d->path); + dev_log(d); log_puts(": "); log_putu(par.rate); log_puts(": unsupported rate\n"); @@ -212,8 +233,14 @@ 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); + if (log_level >= 1) { + dev_log(d); + log_puts(": using "); + log_puts(path); + log_puts("\n"); + } return 1; bad_close: sio_close(d->sio.hdl); @@ -494,5 +521,5 @@ dev_sio_hup(void *arg) log_puts(": disconnected\n"); } #endif - dev_close(d); + dev_reopen(d); } diff --git a/sndiod/sndiod.8 b/sndiod/sndiod.8 index 7c26b0b..58b29bf 100644 --- a/sndiod/sndiod.8 +++ b/sndiod/sndiod.8 @@ -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, diff --git a/sndiod/sndiod.c b/sndiod/sndiod.c index 5d3bd73..56cde52 100644 --- a/sndiod/sndiod.c +++ b/sndiod/sndiod.c @@ -68,7 +68,7 @@ * block size if neither ``-z'' nor ``-b'' is used */ #ifndef DEFAULT_ROUND -#define DEFAULT_ROUND 960 +#define DEFAULT_ROUND 480 #endif /* @@ -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; } diff --git a/sndiod/utils.c b/sndiod/utils.c index ebb9fb5..bea290b 100644 --- a/sndiod/utils.c +++ b/sndiod/utils.c @@ -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); + } +} diff --git a/sndiod/utils.h b/sndiod/utils.h index f11b675..ca3e089 100644 --- a/sndiod/utils.h +++ b/sndiod/utils.h @@ -20,6 +20,11 @@ #include +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: *