git: cf9d2fb18433 - stable/14 - mixer(8): Implement hot-swapping

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Mon, 26 Aug 2024 13:54:46 UTC
The branch stable/14 has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=cf9d2fb184338b73c362b98bb8fadd2de9d94634

commit cf9d2fb184338b73c362b98bb8fadd2de9d94634
Author:     Christos Margiolis <christos@FreeBSD.org>
AuthorDate: 2024-08-24 12:07:35 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2024-08-26 13:52:02 +0000

    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
    
    (cherry picked from commit 9aac27599acaffa21ff69c5be8a2d71d29cc3d6b)
---
 usr.sbin/mixer/mixer.8 | 75 +++++++++++++++++++++++++++++++++++++++++++---
 usr.sbin/mixer/mixer.c | 81 +++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 145 insertions(+), 11 deletions(-)

diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8
index 75c6a81e3a55..819d8ae73ab1 100644
--- a/usr.sbin/mixer/mixer.8
+++ b/usr.sbin/mixer/mixer.8
@@ -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
diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c
index 109d3ad09bc5..468130ddaa88 100644
--- a/usr.sbin/mixer/mixer.c
+++ b/usr.sbin/mixer/mixer.c
@@ -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 @@ next:
 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);
 }