mixer(8): Implement hot-swapping

Introduce a -V option, which can be used alongside -d (default unit
change), in order to hot-swap devices (i.e switch to them on the fly
without needing to restart the track), in case virtual_oss(8) exists and
is running.

Sponsored by:	The FreeBSD Foundation
MFC after:	2 days
Reviewed by:	dev_submerge.ch
Differential Revision:	https://reviews.freebsd.org/D46253
This commit is contained in:
Christos Margiolis
2024-08-24 15:07:35 +03:00
parent 2668e76d6e
commit 9aac27599a
2 changed files with 145 additions and 11 deletions
+71 -4
View File
@@ -19,7 +19,7 @@
.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
.\" THE SOFTWARE.
.\"
.Dd February 8, 2024
.Dd August 14, 2024
.Dt MIXER 8
.Os
.Sh NAME
@@ -28,7 +28,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl f Ar device
.Op Fl d Ar pcmN | N
.Op Fl d Ar pcmN | N Op Fl V Ar voss_device:mode
.Op Fl os
.Op Ar dev Ns Op Cm \&. Ns Ar control Ns Op Cm \&= Ns Ar value
.Ar ...
@@ -43,7 +43,7 @@ The
utility is used to set and display soundcard mixer device controls.
.Pp
The options are as follows:
.Bl -tag -width "-d pcmN | N"
.Bl -tag -width "-V voss_device:mode"
.It Fl a
Print the values for all mixer devices available in the system
.Pq see Sx FILES .
@@ -54,6 +54,30 @@ where N is the unit number (e.g for pcm0, the unit number is 0).
See
.Sx EXAMPLES
on how to list all available audio devices in the system.
.Pp
There is also the possibility of hot-swapping to the new default device if
.Xr virtual_oss 8
exists in the system and is running, in which case the
.Fl V
option needs to be specified as well.
.Pp
Hot-swapping generally cannot happen with plain
.Xr sound 4 ,
so the user has to restart the track in order to get sound coming out of the
new default device.
This is because applications usually open a device at the start of the track
and do not check for default device changes, in order to open the new device
mid-track.
.Xr virtual_oss 8 ,
on the other hand, can do hot-swapping, because it creates a virtual device for
applications to open, and then does all the necessary routing and conversions
to the appropriate device(s).
.Pp
Note that hot-swapping will work only for applications that are using
.Xr virtual_oss 8
devices, and not plain
.Xr sound 4
ones.
.It Fl f Ar device
Open
.Ar device
@@ -66,6 +90,33 @@ Print mixer values in a format suitable for use inside scripts.
The mixer's header (name, audio card name, ...) will not be printed.
.It Fl s
Print only the recording source(s) of the mixer device.
.It Fl V Ar voss_device:mode
Specify a
.Xr virtual_oss 8
control device, as well as a mode (see below), in order to hot-swap devices.
This option is meant to only be used in combination with the
.Fl d
option.
.Pp
The available modes are as follows:
.Bl -column play
.It Sy Mode Ta Sy Action
.It all Ta Playback and recording
.It play Ta Playback
.It rec Ta Recording
.El
.Pp
The
.Pa mode
part is needed, so that
.Nm
will not accidentally hot-swap both the recording and playback device in
.Xr virtual_oss 8 ,
if only one direction is to be hot-swapped.
.Pp
See
.Sx EXAMPLES
on how to use this option.
.El
.Pp
The list of mixer devices that may be modified are:
@@ -273,10 +324,26 @@ $ mixer -f /dev/mixer0 -o > info
\&...
$ mixer -f /dev/mixer0 `cat info`
.Ed
.Pp
Suppose
.Xr virtual_oss 8
is running with
.Pa /dev/vdsp.ctl
as its control device, and
.Pa pcm0
as the playback device.
Change the default device to
.Pa pcm1 ,
and hot-swap to it for both recording and playback in
.Xr virtual_oss 8 :
.Bd -literal -offset indent
$ mixer -d pcm1 -V /dev/vdsp.ctl:all
.Ed
.Sh SEE ALSO
.Xr mixer 3 ,
.Xr sound 4 ,
.Xr sysctl 8
.Xr sysctl 8 ,
.Xr virtual_oss 8
.Sh HISTORY
The
.Nm
+74 -7
View File
@@ -20,6 +20,9 @@
* THE SOFTWARE.
*/
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <mixer.h>
@@ -40,7 +43,7 @@ static void printall(struct mixer *, int);
static void printminfo(struct mixer *, int);
static void printdev(struct mixer *, int);
static void printrecsrc(struct mixer *, int); /* XXX: change name */
static int set_dunit(struct mixer *, int);
static int set_dunit(struct mixer *, int, char *);
/* Control handlers */
static int mod_volume(struct mix_dev *, void *);
static int mod_mute(struct mix_dev *, void *);
@@ -54,13 +57,13 @@ main(int argc, char *argv[])
{
struct mixer *m;
mix_ctl_t *cp;
char *name = NULL, buf[NAME_MAX];
char *name = NULL, buf[NAME_MAX], *vctl = NULL;
char *p, *q, *devstr, *ctlstr, *valstr = NULL;
int dunit, i, n, pall = 1, shorthand;
int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
int ch;
while ((ch = getopt(argc, argv, "ad:f:hos")) != -1) {
while ((ch = getopt(argc, argv, "ad:f:hosV:")) != -1) {
switch (ch) {
case 'a':
aflag = 1;
@@ -83,6 +86,9 @@ main(int argc, char *argv[])
case 's':
sflag = 1;
break;
case 'V':
vctl = optarg;
break;
case 'h': /* FALLTHROUGH */
case '?':
default:
@@ -119,7 +125,7 @@ main(int argc, char *argv[])
initctls(m);
if (dflag) {
if (set_dunit(m, dunit) < 0)
if (set_dunit(m, dunit, vctl) < 0)
goto parse;
else {
/*
@@ -209,7 +215,8 @@ main(int argc, char *argv[])
static void __dead2
usage(void)
{
fprintf(stderr, "usage: %1$s [-f device] [-d pcmN | N] [-os] [dev[.control[=value]]] ...\n"
fprintf(stderr, "usage: %1$s [-f device] [-d pcmN | N "
"[-V voss_device:mode]] [-os] [dev[.control[=value]]] ...\n"
" %1$s [-os] -a\n"
" %1$s -h\n", getprogname());
exit(1);
@@ -322,9 +329,32 @@ printrecsrc(struct mixer *m, int oflag)
}
static int
set_dunit(struct mixer *m, int dunit)
set_dunit(struct mixer *m, int dunit, char *vctl)
{
int n;
const char *opt;
char *dev, *mode;
char buf[32];
size_t size;
int n, rc;
/*
* Issue warning in case of hw.snd.basename_clone being unset. Omit the
* check and warning if the -V flag is used, since the user is most
* likely to be aware of this, and the warning might be confusing.
*/
if (vctl == NULL) {
size = sizeof(int);
if (sysctlbyname("hw.snd.basename_clone", &n, &size,
NULL, 0) < 0) {
warn("hw.snd.basename_clone failed");
return (-1);
}
if (n == 0) {
warnx("warning: hw.snd.basename_clone not set. "
"/dev/dsp is managed externally and does not "
"change with the default unit change here.");
}
}
if ((n = mixer_get_dunit()) < 0) {
warn("cannot get default unit");
@@ -336,6 +366,43 @@ set_dunit(struct mixer *m, int dunit)
}
printf("default_unit: %d -> %d\n", n, dunit);
/* Hot-swap in case virtual_oss exists and is running. */
if (vctl != NULL) {
dev = strsep(&vctl, ":");
mode = vctl;
if (dev == NULL || mode == NULL) {
warnx("voss_device:mode tuple incomplete");
return (-1);
}
if (strcmp(mode, "all") == 0)
opt = "-f";
else if (strcmp(mode, "play") == 0)
opt = "-P";
else if (strcmp(mode, "rec") == 0)
opt = "-R";
else {
warnx("please use one of the following modes: "
"all, play, rec");
return (-1);
}
snprintf(buf, sizeof(buf), "/dev/dsp%d", dunit);
switch (fork()) {
case -1:
warn("fork");
break;
case 0:
rc = execl("/usr/local/sbin/virtual_oss_cmd",
"virtual_oss_cmd", dev, opt, buf, NULL);
if (rc < 0)
warn("virtual_oss_cmd");
_exit(0);
default:
if (wait(NULL) < 0)
warn("wait");
break;
}
}
return (0);
}