diff --git a/lib/Makefile b/lib/Makefile index 1e375bb456e6..038763bfcba8 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,236 +1,237 @@ # @(#)Makefile 8.1 (Berkeley) 6/4/93 # $FreeBSD$ .include # The SUBDIR_BOOTSTRAP list is a small set of libraries which are used by many # of the other libraries. These are built first with a .WAIT between them # and the main list to avoid needing a SUBDIR_DEPEND line on every library # naming just these few items. SUBDIR_BOOTSTRAP= \ csu \ .WAIT \ libc \ libc_nonshared \ libcompiler_rt \ ${_libclang_rt} \ ${_libcplusplus} \ ${_libcxxrt} \ libelf \ libssp \ libssp_nonshared \ msun # The main list; please keep these sorted alphabetically. # The only exception is sqlite3: we place it at the start of the list since it # takes a long time to build and starting it first improves parallelism. SUBDIR= ${SUBDIR_BOOTSTRAP} \ .WAIT \ libsqlite3 \ geom \ lib9p \ libalias \ libarchive \ libauditd \ libbegemot \ libblocksruntime \ libbsdstat \ libbsm \ libbz2 \ libcalendar \ libcam \ libcapsicum \ libcasper \ libcompat \ libcrypt \ libdevctl \ libdevinfo \ libdevstat \ libdl \ libdwarf \ libedit \ libelftc \ libevent1 \ libexecinfo \ libexpat \ libfetch \ libfigpar \ libgcc_eh \ libgcc_s \ libgeom \ libifconfig \ libipsec \ libjail \ libkiconv \ libkvm \ liblua \ liblzma \ libmemstat \ libmd \ + libmixer \ libmt \ lib80211 \ libnetbsd \ libnetmap \ libnv \ libopenbsd \ libopie \ libpam \ libpathconv \ libpcap \ libpjdlog \ libproc \ libprocstat \ libregex \ librpcsvc \ librss \ librt \ librtld_db \ libsbuf \ libsmb \ libstdbuf \ libstdthreads \ libsysdecode \ libtacplus \ libthr \ libthread_db \ libucl \ libufs \ libugidfw \ libulog \ libutil \ ${_libvgl} \ libwrap \ libxo \ liby \ libz \ libzstd \ ncurses # Inter-library dependencies. When the makefile for a library contains LDADD # libraries, those libraries should be listed as build order dependencies here. SUBDIR_DEPEND_geom= libufs SUBDIR_DEPEND_googletest= libregex SUBDIR_DEPEND_libarchive= libz libbz2 libexpat liblzma libmd libzstd SUBDIR_DEPEND_libauditdm= libbsm SUBDIR_DEPEND_libbsnmp= ${_libnetgraph} SUBDIR_DEPEND_libc++:= libcxxrt # libssp_nonshared doesn't need to be linked into libc on every arch, but it is # small enough to build that this bit of serialization is likely insignificant. SUBDIR_DEPEND_libc= libcompiler_rt libssp_nonshared SUBDIR_DEPEND_libcam= libsbuf SUBDIR_DEPEND_libcasper= libnv SUBDIR_DEPEND_libdevstat= libkvm SUBDIR_DEPEND_libdpv= libfigpar ncurses libutil SUBDIR_DEPEND_libedit= ncurses SUBDIR_DEPEND_libgeom= libexpat libsbuf SUBDIR_DEPEND_librpcsec_gss= libgssapi SUBDIR_DEPEND_libmagic= libz SUBDIR_DEPEND_libmemstat= libkvm SUBDIR_DEPEND_libopie= libmd SUBDIR_DEPEND_libpam= libcrypt libopie ${_libradius} librpcsvc libtacplus libutil ${_libypclnt} ${_libcom_err} SUBDIR_DEPEND_libpjdlog= libutil SUBDIR_DEPEND_libprocstat= libkvm libutil SUBDIR_DEPEND_libradius= libmd SUBDIR_DEPEND_libsmb= libkiconv SUBDIR_DEPEND_libtacplus= libmd SUBDIR_DEPEND_libulog= libmd SUBDIR_DEPEND_libunbound= ${_libldns} SUBDIR_DEPEND_liblzma= libthr .if ${MK_OFED} != "no" SUBDIR_DEPEND_libpcap= ofed .endif .if !defined(COMPAT_32BIT) SUBDIR+= flua SUBDIR_DEPEND_flua= libjail .endif # NB: keep these sorted by MK_* knobs SUBDIR.${MK_ATM}+= libngatm SUBDIR.${MK_BEARSSL}+= libbearssl libsecureboot SUBDIR.${MK_BLACKLIST}+=libblacklist SUBDIR.${MK_BLUETOOTH}+=libbluetooth libsdp SUBDIR.${MK_BSNMP}+= libbsnmp .if !defined(COMPAT_32BIT) && !defined(COMPAT_SOFTFP) .if ${MK_CLANG} != "no" || ${MK_LLD} != "no" || \ ${MK_LLDB} != "no" || ${MK_LLVM_BINUTILS} != "no" SUBDIR+= clang .endif .endif SUBDIR.${MK_CUSE}+= libcuse SUBDIR.${MK_CXX}+= libdevdctl SUBDIR.${MK_TOOLCHAIN}+=libpe SUBDIR.${MK_DIALOG}+= libdpv SUBDIR.${MK_FILE}+= libmagic SUBDIR.${MK_GPIO}+= libgpio SUBDIR.${MK_GSSAPI}+= libgssapi librpcsec_gss SUBDIR.${MK_ICONV}+= libiconv_modules SUBDIR.${MK_KERBEROS_SUPPORT}+= libcom_err SUBDIR.${MK_LDNS}+= libldns SUBDIR.${MK_STATS}+= libstats # The libraries under libclang_rt can only be built by clang, and only make # sense to build when clang is enabled at all. Furthermore, they can only be # built for certain architectures. .if ${COMPILER_TYPE} == "clang" && \ (${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \ ${MACHINE_CPUARCH} == "arm" || ${MACHINE_CPUARCH} == "i386" || \ ${MACHINE_CPUARCH} == "powerpc") _libclang_rt= libclang_rt .elif ${MK_ASAN} != "no" || ${MK_UBSAN} != "no" .error "Requested build with sanitizers but cannot build runtime libraries!" .endif .if ${MK_CXX} != "no" _libcxxrt= libcxxrt _libcplusplus= libc++ _libcplusplus+= libc++experimental .endif SUBDIR.${MK_EFI}+= libefivar SUBDIR.${MK_GOOGLETEST}+= googletest SUBDIR.${MK_NETGRAPH}+= libnetgraph SUBDIR.${MK_NIS}+= libypclnt .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" _libvgl= libvgl .endif .if ${MACHINE_CPUARCH} == "aarch64" SUBDIR.${MK_PMC}+= libopencsd .endif .if ${MACHINE_CPUARCH} == "amd64" SUBDIR.${MK_PMC}+= libipt SUBDIR.${MK_BHYVE}+= libvmmapi .endif .if ${MACHINE_ARCH} != "powerpc" SUBDIR.${MK_OPENMP}+= libomp .endif SUBDIR.${MK_OPENSSL}+= libmp SUBDIR.${MK_PF}+= libpfctl SUBDIR.${MK_PMC}+= libpmc libpmcstat SUBDIR.${MK_RADIUS_SUPPORT}+= libradius SUBDIR.${MK_SENDMAIL}+= libmilter libsm libsmdb libsmutil SUBDIR.${MK_TELNET}+= libtelnet SUBDIR.${MK_TESTS_SUPPORT}+= atf SUBDIR.${MK_TESTS_SUPPORT}.${MK_CXX}+= liblutok SUBDIR.${MK_TESTS}+= tests SUBDIR.${MK_UNBOUND}+= libunbound SUBDIR.${MK_USB}+= libusbhid libusb SUBDIR.${MK_OFED}+= ofed SUBDIR.${MK_VERIEXEC}+= libveriexec SUBDIR.${MK_ZFS}+= libbe .if !make(install) SUBDIR_PARALLEL= .endif .include diff --git a/lib/libmixer/Makefile b/lib/libmixer/Makefile new file mode 100644 index 000000000000..12081ee3835b --- /dev/null +++ b/lib/libmixer/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +LIB= mixer +SRCS= ${LIB}.c +INCS= ${LIB}.h +MAN= ${LIB}.3 + +.include diff --git a/lib/libmixer/mixer.3 b/lib/libmixer/mixer.3 new file mode 100644 index 000000000000..f97a88bb79a0 --- /dev/null +++ b/lib/libmixer/mixer.3 @@ -0,0 +1,540 @@ +.\"- +.\" Copyright (c) 2021 Christos Margiolis +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining a copy +.\" of this software and associated documentation files (the "Software"), to deal +.\" in the Software without restriction, including without limitation the rights +.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.\" copies of the Software, and to permit persons to whom the Software is +.\" furnished to do so, subject to the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included in +.\" all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.\" THE SOFTWARE. +.\" +.\" $FreeBSD$ +.\" + +.Dd June 30, 2021 +.Dt mixer 3 +.Os +.Sh NAME +.Nm mixer_open , +.Nm mixer_close , +.Nm mixer_get_dev , +.Nm mixer_get_dev_byname , +.Nm mixer_add_ctl , +.Nm mixer_add_ctl_s , +.Nm mixer_remove_ctl , +.Nm mixer_get_ctl , +.Nm mixer_get_ctl_byname , +.Nm mixer_set_vol , +.Nm mixer_set_mute , +.Nm mixer_mod_recsrc , +.Nm mixer_get_dunit , +.Nm mixer_set_dunit , +.Nm mixer_get_mode, +.Nm mixer_get_nmixers , +.Nm MIX_ISDEV , +.Nm MIX_ISMUTE , +.Nm MIX_ISREC , +.Nm MIX_ISRECSRC , +.Nm MIX_VOLNORM , +.Nm MIX_VOLDENORM +.Nd interface to OSS mixers +.Sh LIBRARY +Mixer library (libmixer, -lmixer) +.Sh SYNOPSIS +.In mixer.h +.Ft struct mixer * +.Fn mixer_open "const char *name" +.Ft int +.Fn mixer_close "struct mixer *m" +.Ft struct mix_dev * +.Fn mixer_get_dev "struct mixer *m" "int devno" +.Ft struct mix_dev * +.Fn mixer_get_dev_byname "struct mixer *m" "name" +.Ft int +.Fn mixer_add_ctl "struct mix_dev *parent" "int id" "const char *name" \ + "int (*mod)(struct mix_dev *d, void *p)" \ + "int (*print)(struct mix_dev *d, void *p) +.Ft int +.Fn mixer_add_ctl_s "mix_ctl_t *ctl" +.Ft int +.Fn mixer_remove_ctl "mix_ctl_t *ctl" +.Ft mix_ctl_t * +.Fn mixer_get_ctl "struct mix_dev *d" "int id" +.Ft mix_ctl_t * +.Fn mixer_get_ctl_byname "struct mix_dev *d" "const char *name" +.Ft int +.Fn mixer_set_vol "struct mixer *m" "mix_volume_t vol" +.Ft int +.Fn mixer_set_mute "struct mixer *m" "int opt" +.Ft int +.Fn mixer_mod_recsrc "struct mixer *m" "int opt" +.Ft int +.Fn mixer_get_dunit "void" +.Ft int +.Fn mixer_set_dunit "struct mixer *m" "int unit" +.Ft int +.Fn mixer_get_mode "int unit" +.Ft int +.Fn mixer_get_nmixers "void" +.Ft int +.Fn MIX_ISDEV "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISMUTE "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISREC "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISRECSRC "struct mixer *m" "int devno" +.Ft float +.Fn MIX_VOLNORM "int v" +.Ft int +.Fn MIX_VOLDENORM "float v" +.Sh DESCRIPTION +The +.Nm mixer +library allows userspace programs to access and manipulate OSS sound mixers in +a simple way. +.Ss Mixer +.Pp +A mixer is described by the following structure: +.Bd -literal +struct mixer { + TAILQ_HEAD(, mix_dev) devs; /* device list */ + struct mix_dev *dev; /* selected device */ + oss_mixerinfo mi; /* mixer info */ + oss_card_info ci; /* audio card info */ + char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */ + int fd; /* file descriptor */ + int unit; /* audio card unit */ + int ndev; /* number of devices */ + int devmask; /* supported devices */ +#define MIX_MUTE 0x01 +#define MIX_UNMUTE 0x02 +#define MIX_TOGGLEMUTE 0x04 + int mutemask; /* muted devices */ + int recmask; /* recording devices */ +#define MIX_ADDRECSRC 0x01 +#define MIX_REMOVERECSRC 0x02 +#define MIX_SETRECSRC 0x04 +#define MIX_TOGGLERECSRC 0x08 + int recsrc; /* recording sources */ +#define MIX_MODE_MIXER 0x01 +#define MIX_MODE_PLAY 0x02 +#define MIX_MODE_REC 0x04 + int mode; /* dev.pcm.X.mode sysctl */ + int f_default; /* default mixer flag */ +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "f_default" +.It Fa devs +A tail queue structure containing all supported mixer devices. +.It Fa dev +A pointer to the currently selected device. The device is one of the elements in +.Ar devs . +.It Fa mi +OSS information about the mixer. Look at the definition of the +.Ft oss_mixerinfo +structure in +.In sys/soundcard.h +to see its fields. +.It Fa ci +OSS audio card information. This structure is also defined in +.In sys/soundcard.h . +.It Fa name +Path to the mixer (e.g /dev/mixer0). +.It Fa fd +File descriptor returned when the mixer is opened in +.Fn mixer_open . +.It Fa unit +Audio card unit. Since each mixer device maps to a pcmX device, +.Ar unit +is always equal to the number of that pcmX device. For example, if the audio +device's number is 0 (i.e pcm0), then +.Ar unit +is 0 as well. This number is useful when checking if the mixer's audio +card is the default one. +.It Fa ndev +Number of devices in +.Ar devs . +.It Fa devmask +Bit mask containing all supported devices for the mixer. For example +if device 10 is supported, then the 10th bit in the mask will be set. By default, +.Fn mixer_open +stores only the supported devices in devs, so it's very unlikely this mask will +be needed. +.It Fa mutemask +Bit mask containing all muted devices. The logic is the same as with +.Ar devmask . +.It Fa recmask +Bit mask containing all recording devices. Again, same logic as with the +other masks. +.It Fa recsrc +Bit mask containing all recording sources. Yes, same logic again. +.It Fa mode +Bit mask containing the supported modes for this audio device. It holds the value +of the +.Ar dev.pcm.X.mode +sysctl. +.It Fa f_default +Flag which tells whether the mixer's audio card is the default one. +.El +.Ss Mixer device +.Pp +Each mixer device stored in a mixer is described as follows: +.Bd -literal +struct mix_dev { + struct mixer *parent_mixer; /* parent mixer */ + char name[NAME_MAX]; /* device name (e.g "vol") */ + int devno; /* device number */ + struct mix_volume { +#define MIX_VOLMIN 0.0f +#define MIX_VOLMAX 1.0f +#define MIX_VOLNORM(v) ((v) / 100.0f) +#define MIX_VOLDENORM(v) ((int)((v) * 100.0f + 0.5f)) + float left; /* left volume */ + float right; /* right volume */ + } vol; + int nctl; /* number of controls */ + TAILQ_HEAD(, mix_ctl) ctls; /* control list */ + TAILQ_ENTRY(mix_dev) devs; +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "parent_mixer" +.It Fa parent_mixer +Pointer to the mixer the device is attached to. +.It Fa name +Device name given by the OSS API. Devices can have one of the following names: +.Bd -ragged +vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix, +pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3, +phin, phout, video, radio, and monitor. +.Ed +.It Fa devno +Device's index in the SOUND_MIXER_NRDEVICES macro defined in +.In sys/soundcard.h . +This number is used to check against the masks defined in the +.Ar mixer +structure. +.It Fa left, right +Left and right-ear volumes. Although the OSS API stores volumes in integers from +0-100, we normalize them to 32-bit floating point numbers. However, the volumes +can be denormalized using the +.Ar MIX_VOLDENORM +macro if needed. +.It Fa nctl +Number of user-defined mixer controls associated with the device. +.It Fa ctls +A tail queue containing user-defined mixer controls. +.El +.Ss User-defined mixer controls +.Pp +Each mixer device can have user-defined controls. The control structure +is defined as follows: +.Bd -literal +struct mix_ctl { + struct mix_dev *parent_dev; /* parent device */ + int id; /* control id */ + char name[NAME_MAX]; /* control name */ + int (*mod)(struct mix_dev *, void *); /* modify control values */ + int (*print)(struct mix_dev *, void *); /* print control */ + TAILQ_ENTRY(mix_ctl) ctls; +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "parent_dev" +.It Fa parent_dev +Pointer to the device the control is attached to. +.It Fa id +Control ID assigned by the caller. Even though the library will +report it, care has to be taken to not give a control the same ID in case +the caller has to choose controls using their ID. +.It Fa name +Control name. As with +.Ar id , +the caller has to make sure the same name is not used more than once. +.It Fa mod +Function pointer to a control modification function. As in +.Xr mixer 8 , +each mixer control's values can be modified. For example, if we have a +volume control, the +.Ar mod +function will be responsible for handling volume changes. +.It Fa print +Function pointer to a control print function. +.El +.Ss Opening and closing the mixer +.Pp +The application must first call the +.Fn mixer_open +function to obtain a handle to the device, which is used as an argument +in most other functions and macros. The parameter +.Ar name +specifies the path to the mixer. OSS mixers are stored under +.Ar /dev/mixerN +where +.Ar N +is the number of the mixer device. Each device maps to an actual +.Ar pcm +audio card, so +.Ar /dev/mixer0 +is the mixer for +.Ar pcm0 , +and so on. If +.Ar name +is +.Ar NULL +or +.Ar /dev/mixer , +.Fn mixer_open +opens the default mixer (hw.snd.defaul_unit). +.Pp +The +.Fn mixer_close +function frees resources and closes the mixer device. It's a good practice to +always call it when the application is done using the mixer. +.Ss Manipulating the mixer +.Pp +The +.Fn mixer_get_dev +and +.Fn mixer_get_dev_byname +functions select a mixer device, either by its number or by its name +respectively. The mixer structure keeps a list of all the devices, but only +one can be manipulated at a time. Each time a new device is to be manipulated, +one of the two functions has to be called. +.Pp +The +.Fn mixer_set_vol +function changes the volume of the selected mixer device. The +.Ar vol +parameter is a structure that stores the left and right volumes of a given +device. The allowed volume values are between MIX_VOLMIN (0.0) and +MIX_VOLMAX (1.0). +.Pp +The +.Fn mixer_set_mute +function modifies the mute of a selected device. The +.Ar opt +parameter has to be one of the following options: +.Bl -tag -width MIX_TOGGLEMUTE -offset indent +.It Dv MIX_MUTE +Mute the device. +.It Dv MIX_UNMUTE +Unmute the device. +.It Dv MIX_TOGGLEMUTE +Toggle the device's mute (e.g mute if unmuted and unmute if muted). +.El +.Pp +The +.Fn mixer_mod_recsrc +function modifies a recording device. The selected device has to be +a recording device, otherwise the function will fail. The +.Ar opt +parameter has to be one of the following options: +.Bl -tag -width MIX_REMOVERECSRC -offset indent +.It Dv MIX_ADDRECSRC +Add device to the recording sources. +.It Dv MIX_REMOVERECSRC +Remove device from the recording sources. +.It Dv MIX_SETRECSRC +Set device as the only recording source. +.It Dv MIX_TOGGLERECSRC +Toggle device from the recording sources. +.El +.Pp +The +.Fn mixer_get_dunit +and +.Fn mixer_set_dunit +functions get and set the default audio card in the system. Although this is +not really a mixer feature, it's useful to have instead of having to use +the +.Xr sysctl 3 +controls. +.Pp +The +.Fn mixer_get_mode +function returns the playback/recording mode of the audio device the mixer +belongs to. The available values are the following: +.Bl -tag -width "MIX_STATUS_PLAY | MIX_STATUS_REC" -offset indent +.It Dv MIX_STATUS_NONE +Neither playback nor recording. +.It Dv MIX_STATUS_PLAY +Playback. +.It Dv MIX_STATUS_REC +Recording. +.It Dv MIX_STATUS_PLAY | MIX_STATUS_REC +Playback and recording. +.El +.Pp +The +.Fn mixer_get_nmixers +function returns the total number of mixer devices in the system. +.Pp +The +.Fn MIX_ISDEV +macro checks if a device is actually a valid device for a given mixer. It's very +unlikely that this macro will ever be needed since the library stores only +valid devices by default. +.Pp +The +.Fn MIX_ISMUTE +macro checks if a device is muted. +.Pp +The +.Fn MIX_ISREC +macro checks if a device is a recording device. +.Pp +The +.Fn MIX_ISRECSRC +macro checks if a device is a recording source. +.Pp +The +.Fn MIX_VOLNORM +macro normalizes a value to 32-bit floating point number. It's used +to normalize the volumes read from the OSS API. +.Pp +The +.Fn MIX_VOLDENORM +macro denormalizes the left and right volumes stores in the +.Ft mix_dev +structure. +.Ss Defining and using mixer controls +.Pp +The +.Fn mix_add_ctl +function creates a control and attaches it to the device specified in the +.Ar parent +argument. +.Pp +The +.Fn mix_add_ctl_s +function does the same thing as with +.Fn mix_add_ctl +but the caller passes a +.Ft mix_ctl_t * +structure instead of each field as a seperate argument. +.Pp +The +.Fn mixer_remove_ctl +functions removes a control from the device its attached to. +.Pp +The +.Fn mixer_get_ctl +function searches for a control in the device specified in the +.Ar d +argument and returns a pointer to it. The search is done using the control's ID. +.Pp +The +.Fn mixer_get_ctl_byname +function is the same as with +.Fn mixer_get_ctl +but the search is done using the control's name. +.Sh RETURN VALUES +.Pp +The +.Fn mixer_open +function returns the newly created handle on success and NULL on failure. +.Pp +The +.Fn mixer_close , +.Fn mixer_set_vol , +.Fn mixer_set_mute , +.Fn mixer_mod_recsrc , +.Fn mixer_get_dunut , +.Fn mixer_set_dunit +and +.Fn mixer_get_nmixers +functions return 0 or positive values on success and -1 on failure. +.Pp +The +.Fn mixer_get_dev +and +.Fn mixer_get_dev_byname +functions return the selected device on success and NULL on failure. +.Pp +All functions set the value of +.Ar errno +on failure. +.Sh EXAMPLES +.Ss Change the volume of a device +.Bd -literal +struct mixer *m; +mix_volume_t vol; +char *mix_name, *dev_name; + +mix_name = ...; +if ((m = mixer_open(mix_name)) == NULL) + err(1, "mixer_open: %s", mix_name); + +dev_name = ...; +if ((m->dev = mixer_get_dev_byname(m, dev_name)) < 0) + err(1, "unknown device: %s", dev_name); + +vol.left = ...; +vol.right = ....; +if (mixer_set_vol(m, vol) < 0) + warn("cannot change volume"); + +(void)mixer_close(m); +.Ed +.Ss Mute all unmuted devices +.Bd -literal +struct mixer *m; +struct mix_dev *dp; + +if ((m = mixer_open(NULL)) == NULL) /* Open the default mixer. */ + err(1, "mixer_open"); +TAILQ_FOREACH(dp, &m->devs, devs) { + m->dev = dp; /* Select device. */ + if (M_ISMUTE(m, dp->devno)) + continue; + if (mixer_set_mute(m, MIX_MUTE) < 0) + warn("cannot mute device: %s", dp->name); +} + +(void)mixer_close(m); +.Ed +.Ss Print all recording sources' names and volumes +.Bd -literal +struct mixer *m; +struct mix_dev *dp; + +char *mix_name, *dev_name; + +mix_name = ...; +if ((m = mixer_open(mix_name)) == NULL) + err(1, "mixer_open: %s", mix_name); + +TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISRECSRC(m, dp->devno)) + printf("%s\\t%.2f:%.2f\\n", + dp->name, dp->vol.left, dp->vol.right); +} + +(void)mixer_close(m); +.Ed +.Sh SEE ALSO +.Xr mixer 8 , +.Xr sound 4 , +.Xr sysctl 3 , +.Xr queue 3 +and +.Xr errno 2 +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/lib/libmixer/mixer.c b/lib/libmixer/mixer.c new file mode 100644 index 000000000000..b10d5c6607cf --- /dev/null +++ b/lib/libmixer/mixer.c @@ -0,0 +1,493 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * $FreeBSD$ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mixer.h" + +#define BASEPATH "/dev/mixer" + +static int _mixer_readvol(struct mixer *, struct mix_dev *); + +/* + * Fetch volume from the device. + */ +static int +_mixer_readvol(struct mixer *m, struct mix_dev *dev) +{ + int v; + + if (ioctl(m->fd, MIXER_READ(dev->devno), &v) < 0) + return (-1); + dev->vol.left = MIX_VOLNORM(v & 0x00ff); + dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff); + + return (0); +} + +/* + * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer. + * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the + * mixer for pcm0, and so on. + * + * @param name path to mixer device. NULL or "/dev/mixer" for the + * the default mixer (i.e `hw.snd.default_unit`). + */ +struct mixer * +mixer_open(const char *name) +{ + struct mixer *m = NULL; + struct mix_dev *dp; + const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + int i; + + if ((m = calloc(1, sizeof(struct mixer))) == NULL) + goto fail; + + if (name != NULL) { + /* `name` does not start with "/dev/mixer". */ + if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) { + errno = EINVAL; + goto fail; + } + /* `name` is "/dev/mixer" so, we'll use the default unit. */ + if (strncmp(name, BASEPATH, strlen(name)) == 0) + goto dunit; + m->unit = strtol(name + strlen(BASEPATH), NULL, 10); + (void)strlcpy(m->name, name, sizeof(m->name)); + } else { +dunit: + if ((m->unit = mixer_get_dunit()) < 0) + goto fail; + (void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit); + } + + if ((m->fd = open(m->name, O_RDWR)) < 0) + goto fail; + + m->devmask = m->recmask = m->recsrc = 0; + m->f_default = m->unit == mixer_get_dunit(); + m->mode = mixer_get_mode(m->unit); + /* The unit number _must_ be set before the ioctl. */ + m->mi.dev = m->unit; + m->ci.card = m->unit; + if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0 || + ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0 || + ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 || + ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 || + ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 || + ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) + goto fail; + + TAILQ_INIT(&m->devs); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!MIX_ISDEV(m, i)) + continue; + if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL) + goto fail; + dp->parent_mixer = m; + dp->devno = i; + dp->nctl = 0; + if (_mixer_readvol(m, dp) < 0) + goto fail; + (void)strlcpy(dp->name, names[i], sizeof(dp->name)); + TAILQ_INIT(&dp->ctls); + TAILQ_INSERT_TAIL(&m->devs, dp, devs); + m->ndev++; + } + + /* The default device is always "vol". */ + m->dev = TAILQ_FIRST(&m->devs); + + return (m); +fail: + if (m != NULL) + (void)mixer_close(m); + + return (NULL); +} + +/* + * Free resources and close the mixer. + */ +int +mixer_close(struct mixer *m) +{ + struct mix_dev *dp; + int r; + + r = close(m->fd); + while (!TAILQ_EMPTY(&m->devs)) { + dp = TAILQ_FIRST(&m->devs); + TAILQ_REMOVE(&m->devs, dp, devs); + while (!TAILQ_EMPTY(&dp->ctls)) + (void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls)); + free(dp); + } + free(m); + + return (r); +} + +/* + * Select a mixer device. The mixer structure keeps a list of all the devices + * the mixer has, but only one can be manipulated at a time -- this is what + * the `dev` in the mixer structure field is for. Each time a device is to be + * manipulated, `dev` has to point to it first. + * + * The caller must manually assign the return value to `m->dev`. + */ +struct mix_dev * +mixer_get_dev(struct mixer *m, int dev) +{ + struct mix_dev *dp; + + if (dev < 0 || dev >= m->ndev) { + errno = ERANGE; + return (NULL); + } + TAILQ_FOREACH(dp, &m->devs, devs) { + if (dp->devno == dev) + return (dp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Select a device by name. + * + * @param name device name (e.g vol, pcm, ...) + */ +struct mix_dev * +mixer_get_dev_byname(struct mixer *m, const char *name) +{ + struct mix_dev *dp; + + TAILQ_FOREACH(dp, &m->devs, devs) { + if (!strncmp(dp->name, name, sizeof(dp->name))) + return (dp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Add a mixer control to a device. + */ +int +mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name, + int (*mod)(struct mix_dev *, void *), + int (*print)(struct mix_dev *, void *)) +{ + struct mix_dev *dp; + mix_ctl_t *ctl, *cp; + + /* XXX: should we accept NULL name? */ + if (parent_dev == NULL) { + errno = EINVAL; + return (-1); + } + if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL) + return (-1); + ctl->parent_dev = parent_dev; + ctl->id = id; + if (name != NULL) + (void)strlcpy(ctl->name, name, sizeof(ctl->name)); + ctl->mod = mod; + ctl->print = print; + dp = ctl->parent_dev; + /* Make sure the same ID or name doesn't exist already. */ + TAILQ_FOREACH(cp, &dp->ctls, ctls) { + if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) { + errno = EINVAL; + return (-1); + } + } + TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls); + dp->nctl++; + + return (0); +} + +/* + * Same as `mixer_add_ctl`. + */ +int +mixer_add_ctl_s(mix_ctl_t *ctl) +{ + if (ctl == NULL) + return (-1); + + return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name, + ctl->mod, ctl->print)); +} + +/* + * Remove a mixer control from a device. + */ +int +mixer_remove_ctl(mix_ctl_t *ctl) +{ + struct mix_dev *p; + + if (ctl == NULL) { + errno = EINVAL; + return (-1); + } + p = ctl->parent_dev; + if (!TAILQ_EMPTY(&p->ctls)) { + TAILQ_REMOVE(&p->ctls, ctl, ctls); + free(ctl); + } + + return (0); +} + +/* + * Get a mixer control by id. + */ +mix_ctl_t * +mixer_get_ctl(struct mix_dev *d, int id) +{ + mix_ctl_t *cp; + + TAILQ_FOREACH(cp, &d->ctls, ctls) { + if (cp->id == id) + return (cp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Get a mixer control by name. + */ +mix_ctl_t * +mixer_get_ctl_byname(struct mix_dev *d, const char *name) +{ + mix_ctl_t *cp; + + TAILQ_FOREACH(cp, &d->ctls, ctls) { + if (!strncmp(cp->name, name, sizeof(cp->name))) + return (cp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Change the mixer's left and right volume. The allowed volume values are + * between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires + * an integer value between 0 and 100 stored as `lvol | rvol << 8` -- for + * that reason, we de-normalize the 32-bit float volume value, before + * we pass it to the `ioctl`. + * + * Volume clumping should be done by the caller. + */ +int +mixer_set_vol(struct mixer *m, mix_volume_t vol) +{ + int v; + + if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX || + vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) { + errno = ERANGE; + return (-1); + } + v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8; + if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0) + return (-1); + if (_mixer_readvol(m, m->dev) < 0) + return (-1); + + return (0); +} + +/* + * Manipulate a device's mute. + * + * @param opt MIX_MUTE mute device + * MIX_UNMUTE unmute device + * MIX_TOGGLEMUTE toggle device's mute + */ +int +mixer_set_mute(struct mixer *m, int opt) +{ + switch (opt) { + case MIX_MUTE: + m->mutemask |= (1 << m->dev->devno); + break; + case MIX_UNMUTE: + m->mutemask &= ~(1 << m->dev->devno); + break; + case MIX_TOGGLEMUTE: + m->mutemask ^= (1 << m->dev->devno); + break; + default: + errno = EINVAL; + return (-1); + } + if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0) + return (-1); + if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0) + return (-1); + + return 0; +} + +/* + * Modify a recording device. The selected device has to be a recording device, + * otherwise the function will fail. + * + * @param opt MIX_ADDRECSRC add device to recording sources + * MIX_REMOVERECSRC remove device from recording sources + * MIX_SETRECSRC set device as the only recording source + * MIX_TOGGLERECSRC toggle device from recording sources + */ +int +mixer_mod_recsrc(struct mixer *m, int opt) +{ + if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) { + errno = ENODEV; + return (-1); + } + switch (opt) { + case MIX_ADDRECSRC: + m->recsrc |= (1 << m->dev->devno); + break; + case MIX_REMOVERECSRC: + m->recsrc &= ~(1 << m->dev->devno); + break; + case MIX_SETRECSRC: + m->recsrc = (1 << m->dev->devno); + break; + case MIX_TOGGLERECSRC: + m->recsrc ^= (1 << m->dev->devno); + break; + default: + errno = EINVAL; + return (-1); + } + if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0) + return (-1); + if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) + return (-1); + + return (0); +} + +/* + * Get default audio card's number. This is used to open the default mixer + * and set the mixer structure's `f_default` flag. + */ +int +mixer_get_dunit(void) +{ + size_t size; + int unit; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0) + return (-1); + + return (unit); +} + +/* + * Change the default audio card. This is normally _not_ a mixer feature, but + * it's useful to have, so the caller can avoid having to manually use + * the sysctl API. + * + * @param unit the audio card number (e.g pcm0, pcm1, ...). + */ +int +mixer_set_dunit(struct mixer *m, int unit) +{ + size_t size; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0) + return (-1); + /* XXX: how will other mixers get updated? */ + m->f_default = m->unit == unit; + + return (0); +} + +/* + * Get sound device mode (none, play, rec, play+rec). Userland programs can + * use the MIX_STATUS_* flags to determine the mode of the device. + */ +int +mixer_get_mode(int unit) +{ + char buf[64]; + size_t size; + unsigned int mode; + + (void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit); + size = sizeof(unsigned int); + if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0) + return (-1); + + return (mode); +} + +/* + * Get the total number of mixers in the system. + */ +int +mixer_get_nmixers(void) +{ + struct mixer *m; + oss_sysinfo si; + + /* + * Open a dummy mixer because we need the `fd` field for the + * `ioctl` to work. + */ + if ((m = mixer_open(NULL)) == NULL) + return (-1); + if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) { + (void)mixer_close(m); + return (-1); + } + (void)mixer_close(m); + + return (si.nummixers); +} diff --git a/lib/libmixer/mixer.h b/lib/libmixer/mixer.h new file mode 100644 index 000000000000..6e77fdec421f --- /dev/null +++ b/lib/libmixer/mixer.h @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * $FreeBSD$ + */ + +#ifndef _MIXER_H_ +#define _MIXER_H_ + +#include +#include +#include + +#include + +#define MIX_ISSET(n,f) (((1U << (n)) & (f)) ? 1 : 0) +#define MIX_ISDEV(m,n) MIX_ISSET(n, (m)->devmask) +#define MIX_ISMUTE(m,n) MIX_ISSET(n, (m)->mutemask) +#define MIX_ISREC(m,n) MIX_ISSET(n, (m)->recmask) +#define MIX_ISRECSRC(m,n) MIX_ISSET(n, (m)->recsrc) + +/* Forward declarations */ +struct mixer; +struct mix_dev; + +typedef struct mix_ctl mix_ctl_t; +typedef struct mix_volume mix_volume_t; + +/* User-defined controls */ +struct mix_ctl { + struct mix_dev *parent_dev; /* parent device */ + int id; /* control id */ + char name[NAME_MAX]; /* control name */ + int (*mod)(struct mix_dev *, void *); /* modify control values */ + int (*print)(struct mix_dev *, void *); /* print control */ + TAILQ_ENTRY(mix_ctl) ctls; +}; + +struct mix_dev { + struct mixer *parent_mixer; /* parent mixer */ + char name[NAME_MAX]; /* device name (e.g "vol") */ + int devno; /* device number */ + struct mix_volume { +#define MIX_VOLMIN 0.0f +#define MIX_VOLMAX 1.0f +#define MIX_VOLNORM(v) ((v) / 100.0f) +#define MIX_VOLDENORM(v) ((int)((v) * 100.0f + 0.5f)) + float left; /* left volume */ + float right; /* right volume */ + } vol; + int nctl; /* number of controls */ + TAILQ_HEAD(, mix_ctl) ctls; /* control list */ + TAILQ_ENTRY(mix_dev) devs; +}; + +struct mixer { + TAILQ_HEAD(, mix_dev) devs; /* device list */ + struct mix_dev *dev; /* selected device */ + oss_mixerinfo mi; /* mixer info */ + oss_card_info ci; /* audio card info */ + char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */ + int fd; /* file descriptor */ + int unit; /* audio card unit */ + int ndev; /* number of devices */ + int devmask; /* supported devices */ +#define MIX_MUTE 0x01 +#define MIX_UNMUTE 0x02 +#define MIX_TOGGLEMUTE 0x04 + int mutemask; /* muted devices */ + int recmask; /* recording devices */ +#define MIX_ADDRECSRC 0x01 +#define MIX_REMOVERECSRC 0x02 +#define MIX_SETRECSRC 0x04 +#define MIX_TOGGLERECSRC 0x08 + int recsrc; /* recording sources */ +#define MIX_MODE_MIXER 0x01 +#define MIX_MODE_PLAY 0x02 +#define MIX_MODE_REC 0x04 + int mode; /* dev.pcm.X.mode sysctl */ + int f_default; /* default mixer flag */ +}; + +__BEGIN_DECLS + +struct mixer *mixer_open(const char *); +int mixer_close(struct mixer *); +struct mix_dev *mixer_get_dev(struct mixer *, int); +struct mix_dev *mixer_get_dev_byname(struct mixer *, const char *); +int mixer_add_ctl(struct mix_dev *, int, const char *, + int (*)(struct mix_dev *, void *), int (*)(struct mix_dev *, void *)); +int mixer_add_ctl_s(mix_ctl_t *); +int mixer_remove_ctl(mix_ctl_t *); +mix_ctl_t *mixer_get_ctl(struct mix_dev *, int); +mix_ctl_t *mixer_get_ctl_byname(struct mix_dev *, const char *); +int mixer_set_vol(struct mixer *, mix_volume_t); +int mixer_set_mute(struct mixer *, int); +int mixer_mod_recsrc(struct mixer *, int); +int mixer_get_dunit(void); +int mixer_set_dunit(struct mixer *, int); +int mixer_get_mode(int); +int mixer_get_nmixers(void); + +__END_DECLS + +#endif /* _MIXER_H_ */ diff --git a/usr.sbin/mixer/Makefile b/usr.sbin/mixer/Makefile index dce135bc7891..6bbc6f07c7e1 100644 --- a/usr.sbin/mixer/Makefile +++ b/usr.sbin/mixer/Makefile @@ -1,11 +1,10 @@ # $FreeBSD$ .include -PROG= mixer -MAN= mixer.8 - -HAS_TESTS= -SUBDIR.${MK_TESTS}+= tests +PROG= mixer +SRCS= ${PROG}.c +MAN= ${PROG}.8 +LDFLAGS+= -lmixer .include diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8 index 6e569cc8cfd9..39b30b53d529 100644 --- a/usr.sbin/mixer/mixer.8 +++ b/usr.sbin/mixer/mixer.8 @@ -1,181 +1,245 @@ -.\" Copyright (c) 1997 -.\" Mike Pritchard . All rights reserved. +.\"- +.\" Copyright (c) 2021 Christos Margiolis .\" -.\" 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. -.\" 3. Neither the name of the author nor the names of its contributors -.\" may be used to endorse or promote products derived from this software -.\" without specific prior written permission. +.\" Permission is hereby granted, free of charge, to any person obtaining a copy +.\" of this software and associated documentation files (the "Software"), to deal +.\" in the Software without restriction, including without limitation the rights +.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.\" copies of the Software, and to permit persons to whom the Software is +.\" furnished to do so, subject to the following conditions: .\" -.\" THIS SOFTWARE IS PROVIDED BY MIKE PRITCHARD 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. +.\" The above copyright notice and this permission notice shall be included in +.\" all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.\" THE SOFTWARE. .\" .\" $FreeBSD$ .\" -.Dd June 2, 2014 -.Dt MIXER 8 + +.Dd June 30, 2021 +.Dt mixer 8 .Os .Sh NAME .Nm mixer -.Nd set/display soundcard mixer values +.Nd manipulate soundcard mixer controls .Sh SYNOPSIS .Nm .Op Fl f Ar device -.Op Fl s | S -.Oo -.Ar dev -.Sm off -.Oo -.Op Cm + | - -.Ar lvol -.Op : Oo Cm + | - Oc Ar rvol -.Oc -.Oc -.Sm on -.Ar ... -.Nm -.Op Fl f Ar device -.Op Fl s | S -.Cm recsrc +.Op Fl d Ar unit +.Op Fl os +.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value .Ar ... .Nm -.Op Fl f Ar device -.Op Fl s | S -.Sm off -.Bro -.Cm ^ | + | - | = -.Brc -.Cm rec -.Sm on -.Ar rdev ... +.Op Fl d Ar unit +.Op Fl os +.Fl a .Sh DESCRIPTION The .Nm -utility is used to set and display soundcard mixer device levels. -It may -also be used to start and stop recording from the soundcard. -The list -of mixer devices that may be modified are: +utility is used to set and display soundcard mixer device controls. +.Pp +The options are as follows: +.Bl -tag -width "-f device" +.It Fl a +Print the values for all mixer devices available in the system (see FILES). +.It Fl d Ar unit +Change the default audio card to +.Ar unit . +The unit has to be an integer value. To see what unit values are available, look +at the number each mixer device has by running +.Nm . +.It Fl f Ar device +Open +.Ar device +as the mixer device (see FILES). +.It Fl o +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. +.El +.Pp +The list of mixer devices that may be modified are: .Bd -ragged -offset indent vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix, pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3, phin, phout, video, radio, and monitor. .Ed .Pp Not all mixer devices are available. .Pp Without any arguments, .Nm -displays the current settings for all supported devices, followed by information -about the current recording input devices. +displays all information for each one of the mixer's supported devices to +.Ar stdout . If the .Ar dev argument is specified, .Nm -displays only the value for that +displays only the values for .Ar dev . +More than one device may be specified. +.Pp +Commands use the following format: +.Pp +.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent +.It Sy "Name Action" +.It "dev Display all controls" +.It "dev.control Display only the specified control" +.It "dev.control=value Set control value" +.El +.Pp +The available controls are as follows (replace +.Ar dev +with one of the available devices): +.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent +.It Sy "Name Value" +.It "dev.volume [[+|-]lvol[:[+|-]rvol]]" +.It "dev.mute {0|1|^}" +.It "dev.recsrc {+|-|^|=}" +.El .Pp -To modify the mixer value -.Ar dev , -the optional left and right channel settings of -.Ar lvol Ns Op : Ns Ar rvol -may be specified. The +.Ar dev.volume +control modifies a device's volume. The optional .Ar lvol -and +and/or .Ar rvol -arguments may be from 0 - 100. -Omitting -.Ar dev -and including only the channel settings will change the main volume level. -.Pp -If the left or right channel settings are prefixed with +values have to be specified. The values have to be normalized 32-bit floats, +from 0.0 to 1.0 inclusivly. If no "." character is present, the value is treated +like a percentage, for backwards compatibility. +If the the left or right volume values are prefixed with .Cm + or .Cm - , the value following will be used as a relative adjustment, modifying the current settings by the amount specified. .Pp -If the -.Fl s -flag is used, the current mixer values will be displayed in a format suitable -for use as the command-line arguments to a future invocation of -.Nm -(as above). -.Pp The -.Fl S -flag provides the above output without mixing field separators. +.Ar dev.mute +control (un)mutes a device. The following values are available: +.Bl -tag -width = -offset indent +.It Cm 0 +unmutes +.Ar dev . +.It Cm 1 +mutes +.Ar dev . +.It Cm ^ +toggles the mute of +.Ar dev . +.El .Pp -To change the recording device you use one of: -.Bl -tag -width =rec -offset indent -.It Cm ^rec +The +.Ar dev.recsrc +control modifies the recording sources of a mixer. +.Nm +marks devices which can be used as a recording source with +.Ar rec . +Recording sources are marked with +.Ar src . +To modify the recording source you can use one of the following modifiers +on a +.Ar rec +device: +.Bl -tag -width = -offset indent +.It Cm ^ toggles -.Ar rdev +.Ar dev of possible recording devices -.It Cm +rec +.It Cm + adds -.Ar rdev +.Ar dev to possible recording devices -.It Cm -rec +.It Cm - removes -.Ar rdev +.Ar dev from possible recording devices -.It Cm =rec +.It Cm = sets the recording device to -.Ar rdev +.Ar dev .El +.Sh FILES +.Bl -tag -width /dev/mixerN -compact +.It Pa /dev/mixerN +The mixer device, where +.Ar N +is the number of that device, for example +.Ar /dev/mixer0 . +PCM cards and mixers have a 1:1 relationship, which means that +.Ar mixer0 +is the mixer for +.Ar pcm0 +and so on. By default, +.Nm +prints both the audio card's number and the mixer associated with it +in the form of +.Ar pcmN:mixer . +The +.Ar /dev/mixer +file, although it doesn't exist in the filesystem, points to the default +mixer device and is the file +.Nm +opens when the +.Fl f Ar device +option has not been specified. +.El +.Sh EXAMPLES .Pp -The above commands work on an internal mask. -After all the options -have been parsed, it will set then read the mask from the sound card. -This will let you see EXACTLY what the soundcard is using for the -recording device(s). +Change the volume for the +.Ar vol +device of the +.Ar /dev/mixer0 +mixer device to 0.65: +.Bl -tag -width Ds -offset indent +.It $ mixer -f /dev/mixer0 vol.volume=0.65 +.El .Pp -The option recsrc will display the current recording devices. +Increase the +.Ar mic +device's left volume by 0.10 and decrease the right +volume by 0.05: +.Bl -tag -width Ds -offset indent +.It $ mixer mic.volume=+0.10:-0.05 +.El .Pp -The option -.Fl f -.Ar device -will open -.Ar device -as the mixer device. -.Sh FILES -.Bl -tag -width /dev/mixer -compact -.It Pa /dev/mixer -the default mixer device +Toggle the mute for +.Ar vol : +.Bl -tag -width Ds -offset indent +.It $ mixer vol.mute=^ +.El +.Pp +Set +.Ar mic +and toggle +.Ar line +recording sources: +.Bl -tag -width Ds -offset indent +.It $ mixer mic.recsrc=+ line.recsrc=^ +.El +.Pp +Dump +.Ar /dev/mixer0 +information to a file and retrieve back later +.Bl -tag -width Ds -offset indent +.It $ mixer -f /dev/mixer0 -o > info +.It ... +.It $ mixer -f /dev/mixer0 `cat info` .El .Sh SEE ALSO -.Xr cdcontrol 1 , -.Xr sound 4 +.Xr mixer 3 , +.Xr sound 4 , +.Xr sysctl 8 .Sh HISTORY The .Nm -utility first appeared in -.Fx 2.0.5 . +utility first appeared in FreeBSD 2.0.5 and was rewritten completely in +FreeBSD 12.2. \" FIXME: replace 12.2 with proper version. .Sh AUTHORS -.An -nosplit -Original source by -.An Craig Metz Aq Mt cmetz@thor.tjhsst.edu -and -.An Hannu Savolainen . -Mostly rewritten by -.An John-Mark Gurney Aq Mt jmg@FreeBSD.org . -This -manual page was written by -.An Mike Pritchard Aq Mt mpp@FreeBSD.org . +.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c index 23193f22324b..3bbb8eebf93f 100644 --- a/usr.sbin/mixer/mixer.c +++ b/usr.sbin/mixer/mixer.c @@ -1,341 +1,484 @@ -/* - * This is an example of a mixer program for Linux +/*- + * Copyright (c) 2021 Christos Margiolis * - * updated 1/1/93 to add stereo, level query, broken - * devmask kludge - cmetz@thor.tjhsst.edu + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * (C) Craig Metz and Hannu Savolainen 1993. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * You may do anything you wish with this program. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * - * ditto for my modifications (John-Mark Gurney, 1997) + * $FreeBSD$ */ -#include -__FBSDID("$FreeBSD$"); - #include -#include -#include -#include +#include #include -#include #include +#include #include -#include - -static const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; -static void usage(int devmask, int recmask) __dead2; -static int res_name(const char *name, int mask); -static void print_recsrc(int recsrc, int recmask, int sflag); +#include + +static void usage(void) __dead2; +static void initctls(struct mixer *); +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 */ +/* Control handlers */ +static int mod_dunit(struct mix_dev *, void *); +static int mod_volume(struct mix_dev *, void *); +static int mod_mute(struct mix_dev *, void *); +static int mod_recsrc(struct mix_dev *, void *); +static int print_volume(struct mix_dev *, void *); +static int print_mute(struct mix_dev *, void *); +static int print_recsrc(struct mix_dev *, void *); + +static const mix_ctl_t ctl_dunit = { + .parent_dev = NULL, + .id = -1, + .name = "default_unit", + .mod = mod_dunit, + .print = NULL +}; -static void __dead2 -usage(int devmask, int recmask) +int +main(int argc, char *argv[]) { - int i, n; - - printf("usage: mixer [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n" - " mixer [-f device] [-s | -S] recsrc ...\n" - " mixer [-f device] [-s | -S] {^|+|-|=}rec rdev ...\n"); - if (devmask != 0) { - printf(" devices: "); - for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++) - if ((1 << i) & devmask) { - if (n) - printf(", "); - printf("%s", names[i]); - n++; - } + struct mixer *m; + mix_ctl_t *cp; + char *name = NULL, buf[NAME_MAX]; + char *p, *bufp, *devstr, *ctlstr, *valstr = NULL; + int dunit, i, n, pall = 1; + int aflag = 0, dflag = 0, oflag = 0, sflag = 0; + char ch; + + while ((ch = getopt(argc, argv, "ad:f:os")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + break; + case 'd': + dunit = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) + err(1, "strtol"); + dflag = 1; + break; + case 'f': + name = optarg; + break; + case 'o': + oflag = 1; + break; + case 's': + sflag = 1; + break; + case '?': + default: + usage(); + } } - if (recmask != 0) { - printf("\n rec devices: "); - for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++) - if ((1 << i) & recmask) { - if (n) - printf(", "); - printf("%s", names[i]); - n++; + argc -= optind; + argv += optind; + + /* Print all mixers and exit. */ + if (aflag) { + if ((n = mixer_get_nmixers()) < 0) + err(1, "mixer_get_nmixers"); + for (i = 0; i < n; i++) { + (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i); + if ((m = mixer_open(buf)) == NULL) + err(1, "mixer_open: %s", buf); + initctls(m); + if (sflag) + printrecsrc(m, oflag); + else { + printall(m, oflag); + if (oflag) + printf("\n"); } + (void)mixer_close(m); + } + return (0); } - printf("\n"); + + if ((m = mixer_open(name)) == NULL) + err(1, "mixer_open: %s", name); + + initctls(m); + + if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0) + goto parse; + if (sflag) { + printrecsrc(m, oflag); + (void)mixer_close(m); + return (0); + } + +parse: + while (argc > 0) { + if ((p = bufp = strdup(*argv)) == NULL) + err(1, "strdup(%s)", *argv); + /* Split the string into device, control and value. */ + devstr = strsep(&p, "."); + if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { + warnx("%s: no such device", devstr); + goto next; + } + /* Input: `dev`. */ + if (p == NULL) { + printdev(m, 1); + pall = 0; + goto next; + } + ctlstr = strsep(&p, "="); + if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { + warnx("%s.%s: no such control", devstr, ctlstr); + goto next; + } + + /* Input: `dev.control`. */ + if (p == NULL) { + (void)cp->print(cp->parent_dev, cp->name); + pall = 0; + goto next; + } + valstr = p; + /* Input: `dev.control=val`. */ + cp->mod(cp->parent_dev, valstr); +next: + free(p); + argc--; + argv++; + } + + if (pall) + printall(m, oflag); + (void)mixer_close(m); + + return (0); +} + +static void __dead2 +usage(void) +{ + printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n" + " %1$s [-d unit] [-os] -a\n", + getprogname()); exit(1); } -static int -res_name(const char *name, int mask) +static void +initctls(struct mixer *m) { - int foo; + struct mix_dev *dp; + int rc = 0; + +#define C_VOL 0 +#define C_MUT 1 +#define C_SRC 2 + TAILQ_FOREACH(dp, &m->devs, devs) { + rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); + rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); + rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); + } + if (rc) { + (void)mixer_close(m); + err(1, "cannot make controls"); + } +} - for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++) - if ((1 << foo) & mask && strcmp(names[foo], name) == 0) - break; +static void +printall(struct mixer *m, int oflag) +{ + struct mix_dev *dp; - return (foo == SOUND_MIXER_NRDEVICES ? -1 : foo); + printminfo(m, oflag); + TAILQ_FOREACH(dp, &m->devs, devs) { + m->dev = dp; + printdev(m, oflag); + } } static void -print_recsrc(int recsrc, int recmask, int sflag) +printminfo(struct mixer *m, int oflag) { - int i, n; + int playrec = MIX_MODE_PLAY | MIX_MODE_REC; - if (recmask == 0) + if (oflag) return; + printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info); + printf(" ("); + if (m->mode & MIX_MODE_PLAY) + printf("play"); + if ((m->mode & playrec) == playrec) + printf("/"); + if (m->mode & MIX_MODE_REC) + printf("rec"); + printf(")"); + if (m->f_default) + printf(" (default)"); + printf("\n"); +} - if (!sflag) - printf("Recording source: "); +static void +printdev(struct mixer *m, int oflag) +{ + struct mix_dev *d = m->dev; + mix_ctl_t *cp; + + if (!oflag) { + char buffer[32]; + (void)snprintf(buffer, sizeof(buffer), + "%s.%s", d->name, "volume"); + + printf(" %-16s= %.2f:%.2f\t", + buffer, d->vol.left, d->vol.right); + if (!MIX_ISREC(m, d->devno)) + printf(" pbk"); + if (MIX_ISREC(m, d->devno)) + printf(" rec"); + if (MIX_ISRECSRC(m, d->devno)) + printf(" src"); + if (MIX_ISMUTE(m, d->devno)) + printf(" mute"); + printf("\n"); + } else { + TAILQ_FOREACH(cp, &d->ctls, ctls) { + (void)cp->print(cp->parent_dev, cp->name); + } + } +} - for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++) - if ((1 << i) & recsrc) { - if (sflag) - printf("%srec ", n ? " +" : "="); - else if (n) +static void +printrecsrc(struct mixer *m, int oflag) +{ + struct mix_dev *dp; + int n = 0; + + if (!m->recmask) + return; + if (!oflag) + printf("%s: ", m->mi.name); + TAILQ_FOREACH(dp, &m->devs, devs) { + if (MIX_ISRECSRC(m, dp->devno)) { + if (n++ && !oflag) printf(", "); - printf("%s", names[i]); - n++; + printf("%s", dp->name); + if (oflag) + printf(".%s=+%s", + mixer_get_ctl(dp, C_SRC)->name, + n ? " " : ""); } - if (!sflag) - printf("\n"); + } + printf("\n"); } -int -main(int argc, char *argv[]) +static int +mod_dunit(struct mix_dev *d, void *p) { - char mixer[PATH_MAX] = "/dev/mixer"; - char lstr[8], rstr[8]; - char *name, *eptr; - int devmask = 0, recmask = 0, recsrc = 0, orecsrc; - int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0; - int l, r, lrel, rrel; - int ch, foo, bar, baz, dev, m, n, t; - - if ((name = strdup(basename(argv[0]))) == NULL) - err(1, "strdup()"); - if (strncmp(name, "mixer", 5) == 0 && name[5] != '\0') { - n = strtol(name + 5, &eptr, 10) - 1; - if (n > 0 && *eptr == '\0') - snprintf(mixer, PATH_MAX - 1, "/dev/mixer%d", n); - } - free(name); - name = mixer; + int dunit = *((int *)p); + int n; - n = 1; - for (;;) { - if (n >= argc || *argv[n] != '-') - break; - if (strlen(argv[n]) != 2) { - if (strcmp(argv[n] + 1, "rec") != 0) - dusage = 1; - break; - } - ch = *(argv[n] + 1); - if (ch == 'f' && n < argc - 1) { - name = argv[n + 1]; - n += 2; - } else if (ch == 's') { - sflag = 1; - n++; - } else if (ch == 'S') { - Sflag = 1; - n++; - } else { - dusage = 1; - break; - } + if ((n = mixer_get_dunit()) < 0) { + warn("cannot get default unit"); + return (-1); } - if (sflag && Sflag) - dusage = 1; - - argc -= n - 1; - argv += n - 1; - - if ((baz = open(name, O_RDWR)) < 0) - err(1, "%s", name); - if (ioctl(baz, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) - err(1, "SOUND_MIXER_READ_DEVMASK"); - if (ioctl(baz, SOUND_MIXER_READ_RECMASK, &recmask) == -1) - err(1, "SOUND_MIXER_READ_RECMASK"); - if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) - err(1, "SOUND_MIXER_READ_RECSRC"); - orecsrc = recsrc; - - if (argc == 1 && dusage == 0) { - for (foo = 0, n = 0; foo < SOUND_MIXER_NRDEVICES; foo++) { - if (!((1 << foo) & devmask)) - continue; - if (ioctl(baz, MIXER_READ(foo),&bar) == -1) { - warn("MIXER_READ"); - continue; - } - if (Sflag || sflag) { - printf("%s%s%c%d:%d", n ? " " : "", - names[foo], Sflag ? ':' : ' ', - bar & 0x7f, (bar >> 8) & 0x7f); - n++; - } else - printf("Mixer %-8s is currently set to " - "%3d:%d\n", names[foo], bar & 0x7f, - (bar >> 8) & 0x7f); - } - if (n && recmask) - printf(" "); - print_recsrc(recsrc, recmask, Sflag || sflag); - return (0); + if (mixer_set_dunit(d->parent_mixer, dunit) < 0) { + warn("cannot set default unit to: %d", dunit); + return (-1); } + printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit); - argc--; - argv++; - - n = 0; - while (argc > 0 && dusage == 0) { - if (strcmp("recsrc", *argv) == 0) { - drecsrc = 1; - argc--; - argv++; - continue; - } else if (strcmp("rec", *argv + 1) == 0) { - if (**argv != '+' && **argv != '-' && - **argv != '=' && **argv != '^') { - warnx("unknown modifier: %c", **argv); - dusage = 1; - break; - } - if (argc <= 1) { - warnx("no recording device specified"); - dusage = 1; - break; - } - if ((dev = res_name(argv[1], recmask)) == -1) { - warnx("unknown recording device: %s", argv[1]); - dusage = 1; - break; - } - switch (**argv) { - case '+': - recsrc |= (1 << dev); - break; - case '-': - recsrc &= ~(1 << dev); - break; - case '=': - recsrc = (1 << dev); - break; - case '^': - recsrc ^= (1 << dev); - break; - } - drecsrc = 1; - argc -= 2; - argv += 2; - continue; - } - - if ((t = sscanf(*argv, "%d:%d", &l, &r)) > 0) - dev = 0; - else if ((dev = res_name(*argv, devmask)) == -1) { - warnx("unknown device: %s", *argv); - dusage = 1; - break; - } + return (0); +} - lrel = rrel = 0; - if (argc > 1) { - m = sscanf(argv[1], "%7[^:]:%7s", lstr, rstr); - if (m == EOF) { - warnx("invalid value: %s", argv[1]); - dusage = 1; - break; - } - if (m > 0) { - if (*lstr == '+' || *lstr == '-') - lrel = rrel = 1; - l = strtol(lstr, NULL, 10); - } - if (m > 1) { - if (*rstr == '+' || *rstr == '-') - rrel = 1; - r = strtol(rstr, NULL, 10); - } - } +static int +mod_volume(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + mix_volume_t v; + const char *val; + char lstr[8], rstr[8]; + float lprev, rprev, lrel, rrel; + int n; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_VOL); + val = p; + n = sscanf(val, "%7[^:]:%7s", lstr, rstr); + if (n == EOF) { + warnx("invalid volume value: %s", val); + return (-1); + } + lrel = rrel = 0; + if (n > 0) { + if (*lstr == '+' || *lstr == '-') + lrel = rrel = 1; + v.left = strtof(lstr, NULL); + + /* be backwards compatible */ + if (strstr(lstr, ".") == NULL) + v.left /= 100.0f; + } + if (n > 1) { + if (*rstr == '+' || *rstr == '-') + rrel = 1; + v.right = strtof(rstr, NULL); + + /* be backwards compatible */ + if (strstr(rstr, ".") == NULL) + v.right /= 100.0f; + } + switch (n) { + case 1: + v.right = v.left; /* FALLTHROUGH */ + case 2: + if (lrel) + v.left += m->dev->vol.left; + if (rrel) + v.right += m->dev->vol.right; + + if (v.left < MIX_VOLMIN) + v.left = MIX_VOLMIN; + else if (v.left > MIX_VOLMAX) + v.left = MIX_VOLMAX; + if (v.right < MIX_VOLMIN) + v.right = MIX_VOLMIN; + else if (v.right > MIX_VOLMAX) + v.right = MIX_VOLMAX; + + lprev = m->dev->vol.left; + rprev = m->dev->vol.right; + if (mixer_set_vol(m, v) < 0) + warn("%s.%s=%.2f:%.2f", + m->dev->name, cp->name, v.left, v.right); + else + printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", + m->dev->name, cp->name, lprev, rprev, v.left, v.right); + } - switch (argc > 1 ? m : t) { - case 0: - if (ioctl(baz, MIXER_READ(dev), &bar) == -1) { - warn("MIXER_READ"); - argc--; - argv++; - continue; - } - if (Sflag || sflag) { - printf("%s%s%c%d:%d", n ? " " : "", - names[dev], Sflag ? ':' : ' ', - bar & 0x7f, (bar >> 8) & 0x7f); - n++; - } else - printf("Mixer %-8s is currently set to " - "%3d:%d\n", names[dev], bar & 0x7f, - (bar >> 8) & 0x7f); - - argc--; - argv++; - break; - case 1: - r = l; - /* FALLTHROUGH */ - case 2: - if (ioctl(baz, MIXER_READ(dev), &bar) == -1) { - warn("MIXER_READ"); - argc--; - argv++; - continue; - } + return (0); +} - if (lrel) - l = (bar & 0x7f) + l; - if (rrel) - r = ((bar >> 8) & 0x7f) + r; - - if (l < 0) - l = 0; - else if (l > 100) - l = 100; - if (r < 0) - r = 0; - else if (r > 100) - r = 100; - - if (!Sflag) - printf("Setting the mixer %s from %d:%d to " - "%d:%d.\n", names[dev], bar & 0x7f, - (bar >> 8) & 0x7f, l, r); - - l |= r << 8; - if (ioctl(baz, MIXER_WRITE(dev), &l) == -1) - warn("WRITE_MIXER"); - - argc -= 2; - argv += 2; - break; - } +static int +mod_mute(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_MUT); + val = p; + switch (*val) { + case '0': + opt = MIX_UNMUTE; + break; + case '1': + opt = MIX_MUTE; + break; + case '^': + opt = MIX_TOGGLEMUTE; + break; + default: + warnx("%c: no such modifier", *val); + return (-1); } + n = MIX_ISMUTE(m, m->dev->devno); + if (mixer_set_mute(m, opt) < 0) + warn("%s.%s=%c", m->dev->name, cp->name, *val); + else + printf("%s.%s: %d -> %d\n", + m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno)); - if (dusage) { - close(baz); - usage(devmask, recmask); - /* NOTREACHED */ - } + return (0); +} - if (orecsrc != recsrc) { - if (ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1) - err(1, "SOUND_MIXER_WRITE_RECSRC"); - if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) - err(1, "SOUND_MIXER_READ_RECSRC"); +static int +mod_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_SRC); + val = p; + switch (*val) { + case '+': + opt = MIX_ADDRECSRC; + break; + case '-': + opt = MIX_REMOVERECSRC; + break; + case '=': + opt = MIX_SETRECSRC; + break; + case '^': + opt = MIX_TOGGLERECSRC; + break; + default: + warnx("%c: no such modifier", *val); + return (-1); } + n = MIX_ISRECSRC(m, m->dev->devno); + if (mixer_mod_recsrc(m, opt) < 0) + warn("%s.%s=%c", m->dev->name, cp->name, *val); + else + printf("%s.%s: %d -> %d\n", + m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno)); + + return (0); +} - if (drecsrc) - print_recsrc(recsrc, recmask, Sflag || sflag); +static int +print_volume(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%.2f:%.2f\n", + m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right); + + return (0); +} + +static int +print_mute(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno)); + + return (0); +} + +static int +print_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; - close(baz); + if (!MIX_ISRECSRC(m, m->dev->devno)) + return (-1); + printf("%s.%s=+\n", m->dev->name, ctl_name); return (0); } diff --git a/usr.sbin/mixer/tests/Makefile b/usr.sbin/mixer/tests/Makefile deleted file mode 100644 index 032cab369f7e..000000000000 --- a/usr.sbin/mixer/tests/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# $FreeBSD$ - -ATF_TESTS_SH+= mixer_test - -.include diff --git a/usr.sbin/mixer/tests/mixer_test.sh b/usr.sbin/mixer/tests/mixer_test.sh deleted file mode 100755 index 1a1266553b3f..000000000000 --- a/usr.sbin/mixer/tests/mixer_test.sh +++ /dev/null @@ -1,123 +0,0 @@ -# -# SPDX-License-Identifier: BSD-2-Clause-FreeBSD -# -# Copyright (c) 2019 Mateusz Piotrowski <0mp@FreeBSD.org> -# -# 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. - -# $FreeBSD$ - -mixer_unavailable() -{ - ! { mixer && mixer vol; } >/dev/null 2>&1 -} - -save_mixer_vol() -{ - atf_check -o match:'^[0-9]{1,3}:[0-9]{1,3}$' -o save:saved_vol \ - -x "mixer vol | awk '{print \$7}'" -} - -set_mixer_vol() -{ - atf_check \ - -o match:'^Setting the mixer vol from [0-9]{1,3}:[0-9]{1,3} to 0:0\.$' \ - mixer vol 0 -} - -restore_mixer_vol() -{ - if [ -r "saved_vol" ]; then - mixer vol "$(cat saved_vol)" - fi -} - -atf_test_case s_flag cleanup -s_flag_head() -{ - atf_set "descr" "Verify that the output of the -s flag could be " \ - "reused as command-line arguments to the mixer command" -} -s_flag_body() -{ - if mixer_unavailable; then - atf_skip "This test requires mixer support" - fi - save_mixer_vol - set_mixer_vol - atf_check -o inline:"vol 0:0" -o save:values mixer -s vol - atf_check -o inline:"Setting the mixer vol from 0:0 to 0:0.\n" \ - mixer $(cat values) -} -s_flag_cleanup() -{ - restore_mixer_vol -} - -atf_test_case S_flag cleanup -S_flag_head() -{ - atf_set "descr" "Verify that the output of the -S flag is " \ - "matching the documented behavior" -} -S_flag_body() -{ - if mixer_unavailable; then - atf_skip "This test requires mixer support" - fi - save_mixer_vol - set_mixer_vol - atf_check -o inline:"vol:0:0" mixer -S vol -} -S_flag_cleanup() -{ - restore_mixer_vol -} - -atf_test_case set_empty_value cleanup -set_empty_value_head() -{ - atf_set "descr" "Verify that mixer returns when the provided " \ - "value to set is an empty string instead of a number" - atf_set "timeout" "1" -} -set_empty_value_body() -{ - if mixer_unavailable; then - atf_skip "This test requires mixer support" - fi - save_mixer_vol - atf_check -s exit:1 -e inline:"mixer: invalid value: \n" \ - -o match:"^usage:" mixer vol "" -} -set_empty_value_cleanup() -{ - restore_mixer_vol -} - - -atf_init_test_cases() -{ - atf_add_test_case s_flag - atf_add_test_case S_flag - atf_add_test_case set_empty_value -}