Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 5 May 2025 11:39:44 GMT
From:      Christos Margiolis <christos@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 44e5a0150835 - main - audio(8): Initial revision
Message-ID:  <202505051139.545Bdi3s045374@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by christos:

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

commit 44e5a01508353cfa52e185a531cfb5a6b39b28f6
Author:     Christos Margiolis <christos@FreeBSD.org>
AuthorDate: 2025-05-05 11:38:59 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2025-05-05 11:39:35 +0000

    audio(8): Initial revision
    
    audio(8) is a new program that lists and modifies audio device
    properties, using a control-driven interface, in similar fashion to
    mixer(8).
    
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D46227
---
 usr.sbin/Makefile       |   1 +
 usr.sbin/audio/Makefile |   8 +
 usr.sbin/audio/audio.8  | 187 +++++++++
 usr.sbin/audio/audio.c  | 987 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1183 insertions(+)

diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index a35c34ee23fc..a891e290bafe 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -2,6 +2,7 @@
 
 SUBDIR=	adduser \
 	arp \
+	audio \
 	binmiscctl \
 	boottrace \
 	bsdconfig \
diff --git a/usr.sbin/audio/Makefile b/usr.sbin/audio/Makefile
new file mode 100644
index 000000000000..6a5d45f4b898
--- /dev/null
+++ b/usr.sbin/audio/Makefile
@@ -0,0 +1,8 @@
+.include <src.opts.mk>
+
+PROG=		audio
+SRCS=		${PROG}.c
+MAN=		${PROG}.8
+LDFLAGS+=	-lnv -lmixer
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/audio/audio.8 b/usr.sbin/audio/audio.8
new file mode 100644
index 000000000000..82bc170cef82
--- /dev/null
+++ b/usr.sbin/audio/audio.8
@@ -0,0 +1,187 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024-2025 The FreeBSD Foundation
+.\"
+.\" Portions of this software were developed by Christos Margiolis
+.\" <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd May 5, 2025
+.Dt AUDIO 8
+.Os
+.Sh NAME
+.Nm audio
+.Nd list and modify soundcard properties
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar device
+.Op Fl hov
+.Op Ar control Ns Oo = Ns Ar value Oc Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to set and display sound card properties, using a
+control-driven interface, in order to filter and/or set specific properties.
+.Pp
+The options are as follows:
+.Bl -tag -width "-f device"
+.It Fl f Ar device
+Choose a specific audio device
+.Pq see Sx FILES .
+Userland devices (e.g those registered by
+.Xr virtual_oss 8
+can also be selected.
+.It Fl h
+Print a help message.
+.It Fl o
+Print values in a format suitable for use inside scripts.
+.It Fl v
+Run in verbose mode.
+This option will print all of the device's channel properties.
+.El
+.Pp
+The device controls are as follows:
+.Bl -column xxxxxxxxxxxxxxx xxxxx xxxxxxxx xxxxxxxxxxxxxxxxxxx -offset indent
+.It Sy Name Ta Sy Type Ta Sy Read/Write Ta Sy Action
+.It name Ta String Ta Read Ta Device name
+.It desc Ta String Ta Read Ta Device description
+.It status Ta String Ta Read Ta Device status
+.It devnode Ta String Ta Read Ta Device node
+.It from_user Ta Boolean Ta Read Ta Userland device
+.It unit Ta Number Ta Read Ta Device unit
+.It caps Ta String Ta Read Ta Device OSS capabitilies
+.It bitperfect Ta Boolean Ta Read/Write Ta Bit-perfect mode enabled
+.It autoconv Ta Boolean Ta Read/Write Ta Auto-conversions enabled
+.It realtime Ta Boolean Ta Read/Write Ta Real-time mode enabled
+.It play Ta Group Ta Read Ta Playback properties
+.It play.format Ta String Ta Read/Write Ta Playback format
+.It play.rate Ta Number Ta Read/Write Ta Playback sample rate
+.It play.vchans Ta Boolean Ta Read/Write Ta Playback VCHANs (virtual channels) enabled
+.It play.min_rate Ta Number Ta Read Ta Minimum playback sample rate
+.It play.max_rate Ta Number Ta Read Ta Maximum playback sample rate
+.It play.min_chans Ta Number Ta Read Ta Natively supported minimum playback sample channels
+.It play.max_chans Ta Number Ta Read Ta Natively supported maximum playback sample channels
+.It play.formats Ta String Ta Read Ta Natively supported playback formats
+.It rec Ta Group Ta Read Ta Recording properties
+.It rec.format Ta String Ta Read/Write Ta Recording format
+.It rec.rate Ta Number Ta Read/Write Ta Recording sample rate
+.It rec.vchans Ta Boolean Ta Read/Write Ta Recording VCHANs (virtual channels) enabled
+.It rec.min_rate Ta Number Ta Read Ta Minimum recording sample rate
+.It rec.max_rate Ta Number Ta Read Ta Maximum recording sample rate
+.It rec.min_chans Ta Number Ta Read Ta Natively supported minimum recording sample channels
+.It rec.max_chans Ta Number Ta Read Ta Natively supported maximum recording sample channels
+.It rec.formats Ta String Ta Read Ta Natively supported recording formats
+.El
+.Pp
+The
+.Pa play.format ,
+.Pa play.rate ,
+.Pa rec.format and
+.Pa rec.rate
+controls will be read-only if VCHANs are disabled.
+.Pp
+The device channel controls are as follows:
+.Bl -column xxxxxxxxxxxxxxx xxxxx xxxxxxxx xxxxxxxxxxxxxxxxxxx -offset indent
+.It Sy Name Ta Sy Type Ta Sy Read/Write Ta Sy Action
+.It name Ta String Ta Read Ta Channel name
+.It parentchan Ta String Ta Read Ta Parent (primary) channel name
+.It unit Ta Number Ta Read Ta Channel unit
+.It caps Ta String Ta Read Ta Channel OSS capabilities
+.It latency Ta Number Ta Read Ta Channel latency
+.It format Ta String Ta Read Ta Channel format
+.It rate Ta Number Ta Read Ta Channel sample rate
+.It pid Ta Number Ta Read Ta PID of process consuming channel
+.It proc Ta String Ta Read Ta Name of process consuming channel
+.It interrupts Ta Number Ta Read Ta Number of interrupts since channel was opened
+.It xruns Ta Number Ta Read Ta Number of playback underruns/recoring overruns
+.It feedcount Ta Number Ta Read Ta Number of bytes fed to channel
+.It volume Ta Volume Ta Read Ta Channel left-right volume in normalized form (0.00 to 1.00).
+.It hwbuf Ta Group Ta Read Ta Hardware buffer properties
+.It hwbuf.format Ta String Ta Read Ta Hardware buffer format
+.It hwbuf.rate Ta String Ta Read Ta Hardware buffer sample rate
+.It hwbuf.size_bytes Ta Number Ta Read Ta Hardware buffer size in bytes
+.It hwbuf.size_frames Ta Number Ta Read Ta Hardware buffer size in frames
+.It hwbuf.blksz Ta Number Ta Read Ta Hardware buffer block size
+.It hwbuf.blkcnt Ta Number Ta Read Ta Hardware buffer block count
+.It hwbuf.free Ta Number Ta Read Ta Hardware buffer free space in bytes
+.It hwbuf.ready Ta Number Ta Read Ta Hardware buffer ready space in bytes
+.It swbuf Ta Group Ta Read Ta Software buffer properties
+.It swbuf.format Ta String Ta Read Ta Software buffer format
+.It swbuf.rate Ta String Ta Read Ta Software buffer sample rate
+.It swbuf.size_bytes Ta Number Ta Read Ta Software buffer size in bytes
+.It swbuf.size_frames Ta Number Ta Read Ta Software buffer size in frames
+.It swbuf.blksz Ta Number Ta Read Ta Software buffer block size
+.It swbuf.blkcnt Ta Number Ta Read Ta Software buffer block count
+.It swbuf.free Ta Number Ta Read Ta Software buffer free space in bytes
+.It swbuf.ready Ta Number Ta Read Ta Software buffer ready space in bytes
+.It feederchain Ta String Ta Read Ta Channel feeder chain
+.El
+.Sh FILES
+.Bl -tag -width /dev/dspX -compact
+.It Pa /dev/dsp
+The default audio device.
+.It Pa /dev/dspX
+The audio device file, where X is the unit of the device, for example
+.Ar /dev/dsp0 .
+.El
+.Sh EXAMPLES
+Disable auto-conversions and enable realtime mode to get as low latencies as
+possible:
+.Bd -literal -offset indent
+$ audio autoconv=0 realtime=1
+.Ed
+.Pp
+Set the playback sample format to 2-channel signed 24-bit low endian, and sample
+rate to 48000 Hz:
+.Bd -literal -offset indent
+$ audio play.format=s24le:2.0 play.rate=48000
+.Ed
+.Pp
+List the PIDs and process names of all channels for
+.Pa /dev/dsp1 :
+.Bd -literal -offset indent
+$ audio -f /dev/dsp1 pid proc
+.Ed
+.Pp
+Dump
+.Pa /dev/dsp0
+information to a file and retrieve back later:
+.Bd -literal -offset indent
+$ audio -f /dev/dsp0 -o > info
+\&...
+$ audio -f /dev/dsp0 `cat info`
+.Ed
+.Sh SEE ALSO
+.Xr sndstat 4 ,
+.Xr sound 4 ,
+.Xr mixer 8 ,
+.Xr sysctl 8
+.Sh AUTHORS
+The
+.Nm
+utility was implemented by
+.An Christos Margiolis Aq Mt christos@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
diff --git a/usr.sbin/audio/audio.c b/usr.sbin/audio/audio.c
new file mode 100644
index 000000000000..f7c4aa5a7f04
--- /dev/null
+++ b/usr.sbin/audio/audio.c
@@ -0,0 +1,987 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024-2025 The FreeBSD Foundation
+ *
+ * This software was developed by Christos Margiolis <christos@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/nv.h>
+#include <sys/queue.h>
+#include <sys/sndstat.h>
+#include <sys/soundcard.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <mixer.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Taken from sys/dev/sound/pcm/ */
+#define STATUS_LEN	64
+#define FMTSTR_LEN	16
+
+struct audio_chan {
+	char name[NAME_MAX];
+	char parentchan[NAME_MAX];
+	int unit;
+#define INPUT	0
+#define OUTPUT	1
+	int direction;
+	char caps[BUFSIZ];
+	int latency;
+	int rate;
+	char format[FMTSTR_LEN];
+	int pid;
+	char proc[NAME_MAX];
+	int interrupts;
+	int xruns;
+	int feedcount;
+	int volume;
+	struct {
+		char format[FMTSTR_LEN];
+		int rate;
+		int size_bytes;
+		int size_frames;
+		int blksz;
+		int blkcnt;
+		int free;
+		int ready;
+	} hwbuf, swbuf;
+	char feederchain[BUFSIZ];
+	struct audio_dev *dev;
+	TAILQ_ENTRY(audio_chan) next;
+};
+
+struct audio_dev {
+	char name[NAME_MAX];
+	char desc[NAME_MAX];
+	char status[BUFSIZ];
+	char devnode[NAME_MAX];
+	int from_user;
+	int unit;
+	char caps[BUFSIZ];
+	int bitperfect;
+	int realtime;
+	int autoconv;
+	struct {
+		char format[FMTSTR_LEN];
+		int rate;
+		int pchans;
+		int vchans;
+		int min_rate;
+		int max_rate;
+		int min_chans;
+		int max_chans;
+		char formats[BUFSIZ];
+	} play, rec;
+	TAILQ_HEAD(, audio_chan) chans;
+};
+
+struct audio_ctl {
+	const char *name;
+	size_t off;
+#define STR	0
+#define NUM	1
+#define VOL	2
+#define GRP	3
+	int type;
+	int (*mod)(struct audio_dev *, void *);
+};
+
+struct map {
+	int val;
+	const char *str;
+};
+
+static int mod_bitperfect(struct audio_dev *, void *);
+static int mod_autoconv(struct audio_dev *, void *);
+static int mod_realtime(struct audio_dev *, void *);
+static int mod_play_vchans(struct audio_dev *, void *);
+static int mod_play_rate(struct audio_dev *, void *);
+static int mod_play_format(struct audio_dev *, void *);
+static int mod_rec_vchans(struct audio_dev *, void *);
+static int mod_rec_rate(struct audio_dev *, void *);
+static int mod_rec_format(struct audio_dev *, void *);
+
+static struct audio_ctl dev_ctls[] = {
+#define F(member)	offsetof(struct audio_dev, member)
+	{ "name",		F(name),		STR,	NULL },
+	{ "desc",		F(desc),		STR,	NULL },
+	{ "status",		F(status),		STR,	NULL },
+	{ "devnode",		F(devnode),		STR,	NULL },
+	{ "from_user",		F(from_user),		NUM,	NULL },
+	{ "unit",		F(unit),		NUM,	NULL },
+	{ "caps",		F(caps),		STR,	NULL },
+	{ "bitperfect",		F(bitperfect),		NUM,	mod_bitperfect },
+	{ "autoconv",		F(autoconv),		NUM,	mod_autoconv },
+	{ "realtime",		F(realtime),		NUM,	mod_realtime },
+	{ "play",		F(play),		GRP,	NULL },
+	{ "play.format",	F(play.format),		STR,	mod_play_format },
+	{ "play.rate",		F(play.rate),		NUM,	mod_play_rate },
+	/*{ "play.pchans",	F(play.pchans),		NUM,	NULL },*/
+	{ "play.vchans",	F(play.vchans),		NUM,	mod_play_vchans },
+	{ "play.min_rate",	F(play.min_rate),	NUM,	NULL },
+	{ "play.max_rate",	F(play.max_rate),	NUM,	NULL },
+	{ "play.min_chans",	F(play.min_chans),	NUM,	NULL },
+	{ "play.max_chans",	F(play.max_chans),	NUM,	NULL },
+	{ "play.formats",	F(play.formats),	STR,	NULL },
+	{ "rec",		F(rec),			GRP,	NULL },
+	{ "rec.rate",		F(rec.rate),		NUM,	mod_rec_rate },
+	{ "rec.format",		F(rec.format),		STR,	mod_rec_format },
+	/*{ "rec.pchans",		F(rec.pchans),		NUM,	NULL },*/
+	{ "rec.vchans",		F(rec.vchans),		NUM,	mod_rec_vchans },
+	{ "rec.min_rate",	F(rec.min_rate),	NUM,	NULL },
+	{ "rec.max_rate",	F(rec.max_rate),	NUM,	NULL },
+	{ "rec.min_chans",	F(rec.min_chans),	NUM,	NULL },
+	{ "rec.max_chans",	F(rec.max_chans),	NUM,	NULL },
+	{ "rec.formats",	F(rec.formats),		STR,	NULL },
+	{ NULL,			0,			0,	NULL }
+#undef F
+};
+
+static struct audio_ctl chan_ctls[] = {
+#define F(member)	offsetof(struct audio_chan, member)
+	/*{ "name",		F(name),		STR,	NULL },*/
+	{ "parentchan",		F(parentchan),		STR,	NULL },
+	{ "unit",		F(unit),		NUM,	NULL },
+	{ "caps",		F(caps),		STR,	NULL },
+	{ "latency",		F(latency),		NUM,	NULL },
+	{ "rate",		F(rate),		NUM,	NULL },
+	{ "format",		F(format),		STR,	NULL },
+	{ "pid",		F(pid),			NUM,	NULL },
+	{ "proc",		F(proc),		STR,	NULL },
+	{ "interrupts",		F(interrupts),		NUM,	NULL },
+	{ "xruns",		F(xruns),		NUM,	NULL },
+	{ "feedcount",		F(feedcount),		NUM,	NULL },
+	{ "volume",		F(volume),		VOL,	NULL },
+	{ "hwbuf",		F(hwbuf),		GRP,	NULL },
+	{ "hwbuf.format",	F(hwbuf.format),	STR,	NULL },
+	{ "hwbuf.rate",		F(hwbuf.rate),		NUM,	NULL },
+	{ "hwbuf.size_bytes",	F(hwbuf.size_bytes),	NUM,	NULL },
+	{ "hwbuf.size_frames",	F(hwbuf.size_frames),	NUM,	NULL },
+	{ "hwbuf.blksz",	F(hwbuf.blksz),		NUM,	NULL },
+	{ "hwbuf.blkcnt",	F(hwbuf.blkcnt),	NUM,	NULL },
+	{ "hwbuf.free",		F(hwbuf.free),		NUM,	NULL },
+	{ "hwbuf.ready",	F(hwbuf.ready),		NUM,	NULL },
+	{ "swbuf",		F(swbuf),		GRP,	NULL },
+	{ "swbuf.format",	F(swbuf.format),	STR,	NULL },
+	{ "swbuf.rate",		F(swbuf.rate),		NUM,	NULL },
+	{ "swbuf.size_bytes",	F(swbuf.size_bytes),	NUM,	NULL },
+	{ "swbuf.size_frames",	F(swbuf.size_frames),	NUM,	NULL },
+	{ "swbuf.blksz",	F(swbuf.blksz),		NUM,	NULL },
+	{ "swbuf.blkcnt",	F(swbuf.blkcnt),	NUM,	NULL },
+	{ "swbuf.free",		F(swbuf.free),		NUM,	NULL },
+	{ "swbuf.ready",	F(swbuf.ready),		NUM,	NULL },
+	{ "feederchain",	F(feederchain),		STR,	NULL },
+	{ NULL,			0,			0,	NULL }
+#undef F
+};
+
+/*
+ * Taken from the OSSv4 manual. Not all of them are supported on FreeBSD
+ * however, and some of them are obsolete.
+ */
+static struct map capmap[] = {
+	{ PCM_CAP_ANALOGIN,	"ANALOGIN" },
+	{ PCM_CAP_ANALOGOUT,	"ANALOGOUT" },
+	{ PCM_CAP_BATCH,	"BATCH" },
+	{ PCM_CAP_BIND,		"BIND" },
+	{ PCM_CAP_COPROC,	"COPROC" },
+	{ PCM_CAP_DEFAULT,	"DEFAULT" },
+	{ PCM_CAP_DIGITALIN,	"DIGITALIN" },
+	{ PCM_CAP_DIGITALOUT,	"DIGITALOUT" },
+	{ PCM_CAP_DUPLEX,	"DUPLEX" },
+	{ PCM_CAP_FREERATE,	"FREERATE" },
+	{ PCM_CAP_HIDDEN,	"HIDDEN" },
+	{ PCM_CAP_INPUT,	"INPUT" },
+	{ PCM_CAP_MMAP,		"MMAP" },
+	{ PCM_CAP_MODEM,	"MODEM" },
+	{ PCM_CAP_MULTI,	"MULTI" },
+	{ PCM_CAP_OUTPUT,	"OUTPUT" },
+	{ PCM_CAP_REALTIME,	"REALTIME" },
+	{ PCM_CAP_REVISION,	"REVISION" },
+	{ PCM_CAP_SHADOW,	"SHADOW" },
+	{ PCM_CAP_SPECIAL,	"SPECIAL" },
+	{ PCM_CAP_TRIGGER,	"TRIGGER" },
+	{ PCM_CAP_VIRTUAL,	"VIRTUAL" },
+	{ 0,			NULL }
+};
+
+static struct map fmtmap[] = {
+	{ AFMT_A_LAW,		"alaw" },
+	{ AFMT_MU_LAW,		"mulaw" },
+	{ AFMT_S8,		"s8" },
+	{ AFMT_U8,		"u8" },
+	{ AFMT_AC3,		"ac3" },
+	{ AFMT_S16_LE,		"s16le" },
+	{ AFMT_S16_BE,		"s16be" },
+	{ AFMT_U16_LE,		"u16le" },
+	{ AFMT_U16_BE,		"u16be" },
+	{ AFMT_S24_LE,		"s24le" },
+	{ AFMT_S24_BE,		"s24be" },
+	{ AFMT_U24_LE,		"u24le" },
+	{ AFMT_U24_BE,		"u24be" },
+	{ AFMT_S32_LE,		"s32le" },
+	{ AFMT_S32_BE,		"s32be" },
+	{ AFMT_U32_LE,		"u32le" },
+	{ AFMT_U32_BE,		"u32be" },
+	{ AFMT_F32_LE,		"f32le" },
+	{ AFMT_F32_BE,		"f32be" },
+	{ 0,			NULL }
+};
+
+static bool oflag = false;
+static bool vflag = false;
+
+static void
+cap2str(char *buf, size_t size, int caps)
+{
+	struct map *p;
+
+	for (p = capmap; p->str != NULL; p++) {
+		if ((p->val & caps) == 0)
+			continue;
+		strlcat(buf, p->str, size);
+		strlcat(buf, ",", size);
+	}
+	if (*buf == '\0')
+		strlcpy(buf, "UNKNOWN", size);
+	else
+		buf[strlen(buf) - 1] = '\0';
+}
+
+static void
+fmt2str(char *buf, size_t size, int fmt)
+{
+	struct map *p;
+	int enc, ch, ext;
+
+	enc = fmt & 0xf00fffff;
+	ch = (fmt & 0x07f00000) >> 20;
+	ext = (fmt & 0x08000000) >> 27;
+
+	for (p = fmtmap; p->str != NULL; p++) {
+		if ((p->val & enc) == 0)
+			continue;
+		strlcat(buf, p->str, size);
+		if (ch) {
+			snprintf(buf + strlen(buf), size,
+			    ":%d.%d", ch - ext, ext);
+		}
+		strlcat(buf, ",", size);
+	}
+	if (*buf == '\0')
+		strlcpy(buf, "UNKNOWN", size);
+	else
+		buf[strlen(buf) - 1] = '\0';
+}
+
+static int
+bytes2frames(int bytes, int fmt)
+{
+	int enc, ch, samplesz;
+
+	enc = fmt & 0xf00fffff;
+	ch = (fmt & 0x07f00000) >> 20;
+	/* Add the channel extension if present (e.g 2.1). */
+	ch += (fmt & 0x08000000) >> 27;
+
+	if (enc & (AFMT_S8 | AFMT_U8 | AFMT_MU_LAW | AFMT_A_LAW))
+		samplesz = 1;
+	else if (enc & (AFMT_S16_NE | AFMT_U16_NE))
+		samplesz = 2;
+	else if (enc & (AFMT_S24_NE | AFMT_U24_NE))
+		samplesz = 3;
+	else if (enc & (AFMT_S32_NE | AFMT_U32_NE | AFMT_F32_NE))
+		samplesz = 4;
+	else
+		samplesz = 0;
+
+	if (!samplesz || !ch)
+		return (-1);
+
+	return (bytes / (samplesz * ch));
+}
+
+static struct audio_dev *
+read_dev(char *path)
+{
+	nvlist_t *nvl;
+	const nvlist_t * const *di;
+	const nvlist_t * const *cdi;
+	struct sndstioc_nv_arg arg;
+	struct audio_dev *dp = NULL;
+	struct audio_chan *ch;
+	size_t nitems, nchans, i, j;
+	int fd, caps, unit;
+
+	if ((fd = open("/dev/sndstat", O_RDONLY)) < 0)
+		err(1, "open(/dev/sndstat)");
+
+	if (ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL) < 0)
+		err(1, "ioctl(SNDSTIOC_REFRESH_DEVS)");
+
+	arg.nbytes = 0;
+	arg.buf = NULL;
+	if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
+		err(1, "ioctl(SNDSTIOC_GET_DEVS#1)");
+
+	if ((arg.buf = malloc(arg.nbytes)) == NULL)
+		err(1, "malloc");
+
+	if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
+		err(1, "ioctl(SNDSTIOC_GET_DEVS#2)");
+
+	if ((nvl = nvlist_unpack(arg.buf, arg.nbytes, 0)) == NULL)
+		err(1, "nvlist_unpack");
+
+	if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS))
+		errx(1, "no soundcards attached");
+
+	if (path == NULL || (path != NULL && strcmp(basename(path), "dsp") == 0))
+		unit = mixer_get_dunit();
+	else
+		unit = -1;
+
+	/* Find whether the requested device exists */
+	di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems);
+	for (i = 0; i < nitems; i++) {
+		if (unit == -1 && strcmp(basename(path),
+		    nvlist_get_string(di[i], SNDST_DSPS_DEVNODE)) == 0)
+			break;
+		else if (nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO) &&
+		    (int)nvlist_get_number(nvlist_get_nvlist(di[i],
+		    SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_UNIT) == unit)
+			break;;
+	}
+	if (i == nitems)
+		errx(1, "device not found");
+
+#define NV(type, item)	\
+	nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item)
+	if ((dp = calloc(1, sizeof(struct audio_dev))) == NULL)
+		err(1, "calloc");
+
+	dp->unit = -1;
+	strlcpy(dp->name, NV(string, NAMEUNIT), sizeof(dp->name));
+	strlcpy(dp->desc, NV(string, DESC), sizeof(dp->desc));
+	strlcpy(dp->devnode, NV(string, DEVNODE), sizeof(dp->devnode));
+	dp->from_user = NV(bool, FROM_USER);
+	dp->play.pchans = NV(number, PCHAN);
+	dp->rec.pchans = NV(number, RCHAN);
+#undef NV
+
+	if (dp->play.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY))
+		errx(1, "%s: playback channel list empty", dp->name);
+	if (dp->rec.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC))
+		errx(1, "%s: recording channel list empty", dp->name);
+
+#define NV(type, mode, item)						\
+	nvlist_get_ ## type (nvlist_get_nvlist(di[i],			\
+	    SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item)
+	if (dp->play.pchans) {
+		dp->play.min_rate = NV(number, PLAY, MIN_RATE);
+		dp->play.max_rate = NV(number, PLAY, MAX_RATE);
+		dp->play.min_chans = NV(number, PLAY, MIN_CHN);
+		dp->play.max_chans = NV(number, PLAY, MAX_CHN);
+		fmt2str(dp->play.formats, sizeof(dp->play.formats),
+		    NV(number, PLAY, FORMATS));
+	}
+	if (dp->rec.pchans) {
+		dp->rec.min_rate = NV(number, REC, MIN_RATE);
+		dp->rec.max_rate = NV(number, REC, MAX_RATE);
+		dp->rec.min_chans = NV(number, REC, MIN_CHN);
+		dp->rec.max_chans = NV(number, REC, MAX_CHN);
+		fmt2str(dp->rec.formats, sizeof(dp->rec.formats),
+		    NV(number, REC, FORMATS));
+	}
+#undef NV
+
+	/*
+	 * Skip further parsing if the provider is not sound(4), as the
+	 * following code is sound(4)-specific.
+	 */
+	if (strcmp(nvlist_get_string(di[i], SNDST_DSPS_PROVIDER),
+	    SNDST_DSPS_SOUND4_PROVIDER) != 0)
+		goto done;
+
+	if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO))
+		errx(1, "%s: provider_info list empty", dp->name);
+
+#define NV(type, item)							\
+	nvlist_get_ ## type (nvlist_get_nvlist(di[i],			\
+	    SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item)
+	strlcpy(dp->status, NV(string, STATUS), sizeof(dp->status));
+	dp->unit = NV(number, UNIT);
+	dp->bitperfect = NV(bool, BITPERFECT);
+	dp->play.vchans = NV(bool, PVCHAN);
+	dp->play.rate = NV(number, PVCHANRATE);
+	fmt2str(dp->play.format, sizeof(dp->play.format),
+	    NV(number, PVCHANFORMAT));
+	dp->rec.vchans = NV(bool, RVCHAN);
+	dp->rec.rate = NV(number, RVCHANRATE);
+	fmt2str(dp->rec.format, sizeof(dp->rec.format),
+	    NV(number, RVCHANFORMAT));
+#undef NV
+
+	dp->autoconv = (dp->play.vchans || dp->rec.vchans) && !dp->bitperfect;
+
+	if (!nvlist_exists(nvlist_get_nvlist(di[i],
+	    SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO))
+		errx(1, "%s: channel info list empty", dp->name);
+
+	cdi = nvlist_get_nvlist_array(
+	    nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO),
+	    SNDST_DSPS_SOUND4_CHAN_INFO, &nchans);
+
+	TAILQ_INIT(&dp->chans);
+	caps = 0;
+	for (j = 0; j < nchans; j++) {
+#define NV(type, item)	\
+	nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item)
+		if ((ch = calloc(1, sizeof(struct audio_chan))) == NULL)
+			err(1, "calloc");
+
+		strlcpy(ch->name, NV(string, NAME), sizeof(ch->name));
+		strlcpy(ch->parentchan, NV(string, PARENTCHAN),
+		    sizeof(ch->parentchan));
+		ch->unit = NV(number, UNIT);
+		ch->direction = (NV(number, CAPS) & PCM_CAP_INPUT) ?
+		    INPUT : OUTPUT;
+		cap2str(ch->caps, sizeof(ch->caps), NV(number, CAPS));
+		ch->latency = NV(number, LATENCY);
+		ch->rate = NV(number, RATE);
+		fmt2str(ch->format, sizeof(ch->format), NV(number, FORMAT));
+		ch->pid = NV(number, PID);
+		strlcpy(ch->proc, NV(string, COMM), sizeof(ch->proc));
+		ch->interrupts = NV(number, INTR);
+		ch->xruns = NV(number, XRUNS);
+		ch->feedcount = NV(number, FEEDCNT);
+		ch->volume = NV(number, LEFTVOL) |
+		    NV(number, RIGHTVOL) << 8;
+		fmt2str(ch->hwbuf.format, sizeof(ch->hwbuf.format),
+		    NV(number, HWBUF_FORMAT));
+		ch->hwbuf.rate = NV(number, HWBUF_RATE);
+		ch->hwbuf.size_bytes = NV(number, HWBUF_SIZE);
+		ch->hwbuf.size_frames =
+		    bytes2frames(ch->hwbuf.size_bytes, NV(number, HWBUF_FORMAT));
+		ch->hwbuf.blksz = NV(number, HWBUF_BLKSZ);
+		ch->hwbuf.blkcnt = NV(number, HWBUF_BLKCNT);
+		ch->hwbuf.free = NV(number, HWBUF_FREE);
+		ch->hwbuf.ready = NV(number, HWBUF_READY);
+		fmt2str(ch->swbuf.format, sizeof(ch->swbuf.format),
+		    NV(number, SWBUF_FORMAT));
+		ch->swbuf.rate = NV(number, SWBUF_RATE);
+		ch->swbuf.size_bytes = NV(number, SWBUF_SIZE);
+		ch->swbuf.size_frames =
+		    bytes2frames(ch->swbuf.size_bytes, NV(number, SWBUF_FORMAT));
+		ch->swbuf.blksz = NV(number, SWBUF_BLKSZ);
+		ch->swbuf.blkcnt = NV(number, SWBUF_BLKCNT);
+		ch->swbuf.free = NV(number, SWBUF_FREE);
+		ch->swbuf.ready = NV(number, SWBUF_READY);
+		strlcpy(ch->feederchain, NV(string, FEEDERCHAIN),
+		    sizeof(ch->feederchain));
+		ch->dev = dp;
+
+		caps |= NV(number, CAPS);
+		TAILQ_INSERT_TAIL(&dp->chans, ch, next);
+
+		if (!dp->rec.vchans && ch->direction == INPUT) {
+			strlcpy(dp->rec.format, ch->hwbuf.format,
+			    sizeof(dp->rec.format));
+			dp->rec.rate = ch->hwbuf.rate;
+		} else if (!dp->play.vchans && ch->direction == OUTPUT) {
+			strlcpy(dp->play.format, ch->hwbuf.format,
+			    sizeof(dp->play.format));
+			dp->play.rate = ch->hwbuf.rate;
+		}
+#undef NV
+	}
+	cap2str(dp->caps, sizeof(dp->caps), caps);
+
+done:
+	free(arg.buf);
+	nvlist_destroy(nvl);
+	close(fd);
+
+	return (dp);
+}
+
+static void
+free_dev(struct audio_dev *dp)
+{
+	struct audio_chan *ch;
+
+	while (!TAILQ_EMPTY(&dp->chans)) {
+		ch = TAILQ_FIRST(&dp->chans);
+		TAILQ_REMOVE(&dp->chans, ch, next);
+		free(ch);
+	}
+	free(dp);
+}
+
+static void
+print_dev_ctl(struct audio_dev *dp, struct audio_ctl *ctl, bool simple,
+    bool showgrp)
+{
+	struct audio_ctl *cp;
+	size_t len;
+
+	if (ctl->type != GRP) {
+		if (simple)
+			printf("%s=", ctl->name);
+		else
+			printf("    %-20s= ", ctl->name);
+	}
+
+	switch (ctl->type) {
+	case STR:
+		printf("%s\n", (char *)dp + ctl->off);
+		break;
+	case NUM:
+		printf("%d\n", *(int *)((intptr_t)dp + ctl->off));
+		break;
+	case VOL:
+		break;
+	case GRP:
+		if (!simple || !showgrp)
+			break;
+		for (cp = dev_ctls; cp->name != NULL; cp++) {
+			len = strlen(ctl->name);
+			if (strncmp(ctl->name, cp->name, len) == 0 &&
+			    cp->name[len] == '.' && cp->type != GRP)
+				print_dev_ctl(dp, cp, simple, showgrp);
+		}
+		break;
+	}
+}
+
+static void
+print_chan_ctl(struct audio_chan *ch, struct audio_ctl *ctl, bool simple,
+    bool showgrp)
+{
+	struct audio_ctl *cp;
+	size_t len;
+	int v;
+
+	if (ctl->type != GRP) {
+		if (simple)
+			printf("%s.%s=", ch->name, ctl->name);
+		else
+			printf("        %-20s= ", ctl->name);
+	}
+
+	switch (ctl->type) {
+	case STR:
+		printf("%s\n", (char *)ch + ctl->off);
+		break;
+	case NUM:
+		printf("%d\n", *(int *)((intptr_t)ch + ctl->off));
+		break;
+	case VOL:
+		v = *(int *)((intptr_t)ch + ctl->off);
+		printf("%.2f:%.2f\n",
+		    MIX_VOLNORM(v & 0x00ff), MIX_VOLNORM((v >> 8) & 0x00ff));
+		break;
+	case GRP:
+		if (!simple || !showgrp)
+			break;
+		for (cp = chan_ctls; cp->name != NULL; cp++) {
+			len = strlen(ctl->name);
+			if (strncmp(ctl->name, cp->name, len) == 0 &&
+			    cp->name[len] == '.' && cp->type != GRP)
+				print_chan_ctl(ch, cp, simple, showgrp);
+		}
+		break;
+	}
+}
+
+static void
+print_dev(struct audio_dev *dp)
+{
+	struct audio_chan *ch;
+	struct audio_ctl *ctl;
+
+	if (!oflag) {
+		printf("%s: <%s> %s", dp->name, dp->desc, dp->status);
+
+		printf(" (");
+		if (dp->play.pchans)
+			printf("play");
+		if (dp->play.pchans && dp->rec.pchans)
+			printf("/");
+		if (dp->rec.pchans)
+			printf("rec");
+		printf(")\n");
+	}
+
+	for (ctl = dev_ctls; ctl->name != NULL; ctl++)
+		print_dev_ctl(dp, ctl, oflag, false);
+
+	if (vflag) {
+		TAILQ_FOREACH(ch, &dp->chans, next) {
+			if (!oflag)
+				printf("    %s\n", ch->name);
+			for (ctl = chan_ctls; ctl->name != NULL; ctl++)
+				print_chan_ctl(ch, ctl, oflag, false);
+		}
+	}
+}
+
+static int
+sysctl_int(const char *buf, const char *arg, int *var)
+{
+	size_t size;
+	const char *val = arg;
+	int n, prev;
+
+	n = strtol(val, NULL, 10);
+	if (errno == EINVAL || errno == ERANGE) {
+		warn("strtol(%s)", val);
+		return (-1);
+	}
+
+	size = sizeof(int);
+	/* Read current value. */
+	if (sysctlbyname(buf, &prev, &size, NULL, 0) < 0) {
+		warn("sysctlbyname(%s)", buf);
+		return (-1);
+	}
+	/* Apply new value. */
+	if (sysctlbyname(buf, NULL, 0, &n, size) < 0) {
+		warn("sysctlbyname(%s, %d)", buf, n);
+		return (-1);
+	}
+	/* Read back applied value for good measure. */
+	if (sysctlbyname(buf, &n, &size, NULL, 0) < 0) {
+		warn("sysctlbyname(%s)", buf);
+		return (-1);
+	}
+
+	printf("%s: %d -> %d\n", buf, prev, n);
+	if (var != NULL)
+		*var = n;
+
+	return (0);
+}
+
+static int
+sysctl_str(const char *buf, const char *arg, char *var, size_t varsz)
+{
+	size_t size;
+	const char *val = arg;
+	char prev[BUFSIZ];
+	char *tmp;
+
+	/* Read current value. */
+	size = sizeof(prev);
+	if (sysctlbyname(buf, prev, &size, NULL, 0) < 0) {
+		warn("sysctlbyname(%s)", buf);
+		return (-1);
+	}
+
+	size = strlen(val);
+	/* Apply new value. */
+	if (sysctlbyname(buf, NULL, 0, val, size) < 0) {
+		warn("sysctlbyname(%s, %s)", buf, val);
+		return (-1);
+	}
+	/* Get size of new string. */
+	if (sysctlbyname(buf, NULL, &size, NULL, 0) < 0) {
+		warn("sysctlbyname(%s)", buf);
+		return (-1);
+	}
+	if ((tmp = calloc(1, size)) == NULL)
+		err(1, "calloc");
+	/* Read back applied value for good measure. */
+	if (sysctlbyname(buf, tmp, &size, NULL, 0) < 0) {
+		warn("sysctlbyname(%s)", buf);
+		free(tmp);
+		return (-1);
+	}
+
+	printf("%s: %s -> %s\n", buf, prev, tmp);
+	if (var != NULL)
+		strlcpy(var, tmp, varsz);
*** 252 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202505051139.545Bdi3s045374>