aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/virtual_oss
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/virtual_oss')
-rw-r--r--usr.sbin/virtual_oss/Makefile9
-rw-r--r--usr.sbin/virtual_oss/Makefile.inc3
-rw-r--r--usr.sbin/virtual_oss/virtual_bt_speaker/Makefile11
-rw-r--r--usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c542
-rw-r--r--usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.871
-rw-r--r--usr.sbin/virtual_oss/virtual_equalizer/Makefile11
-rw-r--r--usr.sbin/virtual_oss/virtual_equalizer/equalizer.c431
-rw-r--r--usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8127
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/Makefile24
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/audio_delay.c238
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/backend.h53
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/compressor.c76
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/ctl.c615
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/eq.c226
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/format.c429
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/httpd.c846
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/int.h331
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/main.c2625
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/mul.c175
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/ring.c213
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/utils.h31
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/virtual_oss.8355
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/virtual_oss.c914
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/virtual_oss.h206
-rw-r--r--usr.sbin/virtual_oss/virtual_oss_cmd/Makefile8
-rw-r--r--usr.sbin/virtual_oss/virtual_oss_cmd/command.c113
-rw-r--r--usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8103
27 files changed, 8786 insertions, 0 deletions
diff --git a/usr.sbin/virtual_oss/Makefile b/usr.sbin/virtual_oss/Makefile
new file mode 100644
index 000000000000..6c497ad71d46
--- /dev/null
+++ b/usr.sbin/virtual_oss/Makefile
@@ -0,0 +1,9 @@
+.include <src.opts.mk>
+
+SUBDIR+= virtual_oss_cmd \
+ virtual_oss
+
+SUBDIR.${MK_BLUETOOTH}+= virtual_bt_speaker
+
+.include "Makefile.inc"
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/virtual_oss/Makefile.inc b/usr.sbin/virtual_oss/Makefile.inc
new file mode 100644
index 000000000000..d2b0d74fa8c4
--- /dev/null
+++ b/usr.sbin/virtual_oss/Makefile.inc
@@ -0,0 +1,3 @@
+PACKAGE?= sound
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile b/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile
new file mode 100644
index 000000000000..0f5fb2b4eb99
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile
@@ -0,0 +1,11 @@
+PROG= virtual_bt_speaker
+MAN= ${PROG}.8
+
+SRCS= bt_speaker.c
+
+CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
+ -I${SRCTOP}/lib/virtual_oss/bt
+
+LDFLAGS+= -lm -lbluetooth -lsdp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c
new file mode 100644
index 000000000000..c61eaf1c338d
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c
@@ -0,0 +1,542 @@
+/*-
+ * Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
+ *
+ * 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/types.h>
+#include <sys/rtprio.h>
+#include <sys/soundcard.h>
+
+#include <dlfcn.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <poll.h>
+#include <getopt.h>
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+
+#include "avdtp_signal.h"
+#include "bt.h"
+#include "utils.h"
+
+static int (*bt_receive_f)(struct bt_config *, void *, int, int);
+static int (*avdtpACPHandlePacket_f)(struct bt_config *cfg);
+static void (*avdtpACPFree_f)(struct bt_config *);
+
+static int bt_in_background;
+
+static void
+message(const char *fmt,...)
+{
+ va_list list;
+
+ if (bt_in_background)
+ return;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ va_end(list);
+}
+
+struct bt_audio_receiver {
+ const char *devname;
+ const char *sdp_socket_path;
+ uint16_t l2cap_psm;
+ int fd_listen;
+ void *sdp_session;
+ uint32_t sdp_handle;
+};
+
+static int
+register_sdp(struct bt_audio_receiver *r)
+{
+ struct sdp_audio_sink_profile record = {};
+
+ r->sdp_session = sdp_open_local(r->sdp_socket_path);
+ if (r->sdp_session == NULL || sdp_error(r->sdp_session)) {
+ sdp_close(r->sdp_session);
+ r->sdp_session = NULL;
+ return (0);
+ }
+
+ record.psm = r->l2cap_psm;
+ record.protover = 0x100;
+ record.features = 0x01; /* player only */
+
+ if (sdp_register_service(r->sdp_session, SDP_SERVICE_CLASS_AUDIO_SINK,
+ NG_HCI_BDADDR_ANY, (const uint8_t *)&record, sizeof(record),
+ &r->sdp_handle)) {
+ message("SDP failed to register: %s\n",
+ strerror(sdp_error(r->sdp_session)));
+ sdp_close(r->sdp_session);
+ r->sdp_session = NULL;
+ return (0);
+ }
+ return (1);
+}
+
+static void
+unregister_sdp(struct bt_audio_receiver *r)
+{
+ sdp_unregister_service(r->sdp_session, r->sdp_handle);
+ sdp_close(r->sdp_session);
+ r->sdp_session = NULL;
+}
+
+static int
+start_listen(struct bt_audio_receiver *r)
+{
+ struct sockaddr_l2cap addr = {};
+
+ r->fd_listen = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (r->fd_listen < 0)
+ return (0);
+
+ addr.l2cap_len = sizeof(addr);
+ addr.l2cap_family = AF_BLUETOOTH;
+ addr.l2cap_psm = r->l2cap_psm;
+
+ if (bind(r->fd_listen, (struct sockaddr *)&addr, sizeof(addr)) < 0 ||
+ listen(r->fd_listen, 4) < 0) {
+ close(r->fd_listen);
+ return (0);
+ }
+ return (1);
+}
+
+static void
+stop_listen(struct bt_audio_receiver *r)
+{
+ close(r->fd_listen);
+}
+
+struct bt_audio_connection {
+ struct bt_audio_receiver *r;
+ struct sockaddr_l2cap peer_addr;
+ struct bt_config cfg;
+ int oss_fd;
+};
+
+static void
+close_connection(struct bt_audio_connection *c)
+{
+ avdtpACPFree_f(&c->cfg);
+ if (c->cfg.fd != -1)
+ close(c->cfg.fd);
+ if (c->cfg.hc != -1)
+ close(c->cfg.hc);
+ if (c->oss_fd != -1)
+ close(c->oss_fd);
+ free(c);
+}
+
+static struct bt_audio_connection *
+wait_for_connection(struct bt_audio_receiver *r)
+{
+ struct bt_audio_connection *c =
+ malloc(sizeof(struct bt_audio_connection));
+ socklen_t addrlen;
+
+ memset(c, 0, sizeof(*c));
+
+ c->r = r;
+ c->cfg.fd = -1;
+ c->oss_fd = -1;
+
+ addrlen = sizeof(c->peer_addr);
+ c->cfg.hc = accept(r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen);
+
+ message("Accepted control connection, %d\n", c->cfg.hc);
+ if (c->cfg.hc < 0) {
+ close_connection(c);
+ return NULL;
+ }
+ c->cfg.sep = 0; /* to be set later */
+ c->cfg.media_Type = mediaTypeAudio;
+ c->cfg.chmode = MODE_DUAL;
+ c->cfg.aacMode1 = 0; /* TODO: support AAC */
+ c->cfg.aacMode2 = 0;
+ c->cfg.acceptor_state = acpInitial;
+
+ return (c);
+}
+
+static void
+setup_oss(struct bt_audio_connection *c)
+{
+ c->oss_fd = open(c->r->devname, O_WRONLY);
+
+ if (c->oss_fd < 0)
+ goto err;
+
+ int v;
+
+ switch (c->cfg.chmode) {
+ case MODE_STEREO:
+ case MODE_JOINT:
+ case MODE_DUAL:
+ v = 2;
+ break;
+ case MODE_MONO:
+ v = 1;
+ break;
+ default:
+ message("Wrong chmode\n");
+ goto err;
+ }
+
+ if (ioctl(c->oss_fd, SNDCTL_DSP_CHANNELS, &v) < 0) {
+ message("SNDCTL_DSP_CHANNELS failed\n");
+ goto err;
+ }
+ v = AFMT_S16_NE;
+ if (ioctl(c->oss_fd, SNDCTL_DSP_SETFMT, &v) < 0) {
+ message("SNDCTL_DSP_SETFMT failed\n");
+ goto err;
+ }
+ switch (c->cfg.freq) {
+ case FREQ_16K:
+ v = 16000;
+ break;
+ case FREQ_32K:
+ v = 32000;
+ break;
+ case FREQ_44_1K:
+ v = 44100;
+ break;
+ case FREQ_48K:
+ v = 48000;
+ break;
+ default:
+ message("Wrong freq\n");
+ goto err;
+ }
+
+ if (ioctl(c->oss_fd, SNDCTL_DSP_SPEED, &v) < 0) {
+ message("SNDCTL_DSP_SETFMT failed\n");
+ goto err;
+ }
+ v = (2 << 16) | 15; /* 2 fragments of 32k each */
+ if (ioctl(c->oss_fd, SNDCTL_DSP_SETFRAGMENT, &v) < 0) {
+ message("SNDCTL_DSP_SETFRAGMENT failed\n");
+ goto err;
+ }
+ return;
+
+err:
+ c->oss_fd = -1;
+ message("Cannot open oss device %s\n", c->r->devname);
+}
+
+static void
+process_connection(struct bt_audio_connection *c)
+{
+ struct pollfd pfd[3] = {};
+ time_t oss_attempt = 0;
+
+ while (c->cfg.acceptor_state != acpStreamClosed) {
+ int np;
+
+ pfd[0].fd = c->r->fd_listen;
+ pfd[0].events = POLLIN | POLLRDNORM;
+ pfd[0].revents = 0;
+
+ pfd[1].fd = c->cfg.hc;
+ pfd[1].events = POLLIN | POLLRDNORM;
+ pfd[1].revents = 0;
+
+ pfd[2].fd = c->cfg.fd;
+ pfd[2].events = POLLIN | POLLRDNORM;
+ pfd[2].revents = 0;
+
+ if (c->cfg.fd != -1)
+ np = 3;
+ else
+ np = 2;
+
+ if (poll(pfd, np, INFTIM) < 0)
+ return;
+
+ if (pfd[1].revents != 0) {
+ int retval;
+
+ message("Handling packet: state = %d, ",
+ c->cfg.acceptor_state);
+ retval = avdtpACPHandlePacket_f(&c->cfg);
+ message("retval = %d\n", retval);
+ if (retval < 0)
+ return;
+ }
+ if (pfd[0].revents != 0) {
+ socklen_t addrlen = sizeof(c->peer_addr);
+ int fd = accept4(c->r->fd_listen,
+ (struct sockaddr *)&c->peer_addr, &addrlen,
+ SOCK_NONBLOCK);
+
+ if (fd < 0)
+ return;
+
+ if (c->cfg.fd < 0) {
+ if (c->cfg.acceptor_state == acpStreamOpened) {
+ socklen_t mtusize = sizeof(uint16_t);
+ c->cfg.fd = fd;
+
+ if (getsockopt(c->cfg.fd, SOL_L2CAP, SO_L2CAP_IMTU, &c->cfg.mtu, &mtusize) == -1) {
+ message("Could not get MTU size\n");
+ return;
+ }
+
+ int temp = c->cfg.mtu * 32;
+
+ if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) {
+ message("Could not set send buffer size\n");
+ return;
+ }
+
+ temp = 1;
+ if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) {
+ message("Could not set low water mark\n");
+ return;
+ }
+ message("Accepted data connection, %d\n", c->cfg.fd);
+ }
+ } else {
+ close(fd);
+ }
+ }
+ if (pfd[2].revents != 0) {
+ uint8_t data[65536];
+ int len;
+
+ if ((len = bt_receive_f(&c->cfg, data, sizeof(data), 0)) < 0) {
+ return;
+ }
+ if (c->cfg.acceptor_state != acpStreamSuspended &&
+ c->oss_fd < 0 &&
+ time(NULL) != oss_attempt) {
+ message("Trying to open dsp\n");
+ setup_oss(c);
+ oss_attempt = time(NULL);
+ }
+ if (c->oss_fd > -1) {
+ uint8_t *end = data + len;
+ uint8_t *ptr = data;
+ unsigned delay;
+ unsigned jitter_limit;
+
+ switch (c->cfg.freq) {
+ case FREQ_16K:
+ jitter_limit = (16000 / 20);
+ break;
+ case FREQ_32K:
+ jitter_limit = (32000 / 20);
+ break;
+ case FREQ_44_1K:
+ jitter_limit = (44100 / 20);
+ break;
+ default:
+ jitter_limit = (48000 / 20);
+ break;
+ }
+
+ if (c->cfg.chmode == MODE_MONO) {
+ if (len >= 2 &&
+ ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
+ delay < (jitter_limit * 2)) {
+ uint8_t jitter[jitter_limit * 4] __aligned(4);
+ size_t x;
+
+ /* repeat last sample */
+ for (x = 0; x != sizeof(jitter); x++)
+ jitter[x] = ptr[x % 2];
+
+ write(c->oss_fd, jitter, sizeof(jitter));
+ }
+ } else {
+ if (len >= 4 &&
+ ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
+ delay < (jitter_limit * 4)) {
+ uint8_t jitter[jitter_limit * 8] __aligned(4);
+ size_t x;
+
+ /* repeat last sample */
+ for (x = 0; x != sizeof(jitter); x++)
+ jitter[x] = ptr[x % 4];
+
+ write(c->oss_fd, jitter, sizeof(jitter));
+ }
+ }
+ while (ptr != end) {
+ int written = write(c->oss_fd, ptr, end - ptr);
+
+ if (written < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ break;
+ written = 0;
+ }
+ ptr += written;
+ }
+ if (ptr != end) {
+ message("Not all written, closing dsp\n");
+ close(c->oss_fd);
+ c->oss_fd = -1;
+ oss_attempt = time(NULL);
+ }
+ }
+ }
+
+ if (c->cfg.acceptor_state == acpStreamSuspended &&
+ c->oss_fd > -1) {
+ close(c->oss_fd);
+ c->oss_fd = -1;
+ }
+ }
+}
+
+static struct option bt_speaker_opts[] = {
+ {"device", required_argument, NULL, 'd'},
+ {"sdp_socket_path", required_argument, NULL, 'p'},
+ {"rtprio", required_argument, NULL, 'i'},
+ {"background", no_argument, NULL, 'B'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: virtual_bt_speaker -d /dev/dsp\n"
+ "\t" "-d, --device [device]\n"
+ "\t" "-p, --sdp_socket_path [path]\n"
+ "\t" "-i, --rtprio [priority]\n"
+ "\t" "-B, --background\n"
+ );
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct bt_audio_receiver r = {};
+ struct rtprio rtp = {};
+ void *hdl;
+ int ch;
+
+ r.devname = NULL;
+ r.sdp_socket_path = NULL;
+ r.l2cap_psm = SDP_UUID_PROTOCOL_AVDTP;
+
+ while ((ch = getopt_long(argc, argv, "p:i:d:Bh", bt_speaker_opts, NULL)) != -1) {
+ switch (ch) {
+ case 'd':
+ r.devname = optarg;
+ break;
+ case 'p':
+ r.sdp_socket_path = optarg;
+ break;
+ case 'B':
+ bt_in_background = 1;
+ break;
+ case 'i':
+ rtp.type = RTP_PRIO_REALTIME;
+ rtp.prio = atoi(optarg);
+ if (rtprio(RTP_SET, getpid(), &rtp) != 0) {
+ message("Cannot set realtime priority\n");
+ }
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (r.devname == NULL)
+ errx(EX_USAGE, "No devicename specified");
+
+ if (bt_in_background) {
+ if (daemon(0, 0) != 0)
+ errx(EX_SOFTWARE, "Cannot become daemon");
+ }
+
+ if ((hdl = dlopen("/usr/lib/virtual_oss/voss_bt.so", RTLD_NOW)) == NULL)
+ errx(1, "%s", dlerror());
+ if ((bt_receive_f = dlsym(hdl, "bt_receive")) == NULL)
+ goto err_dlsym;
+ if ((avdtpACPHandlePacket_f = dlsym(hdl, "avdtpACPHandlePacket")) ==
+ NULL)
+ goto err_dlsym;
+ if ((avdtpACPFree_f = dlsym(hdl, "avdtpACPFree")) == NULL)
+ goto err_dlsym;
+
+ while (1) {
+ message("Starting to listen\n");
+ if (!start_listen(&r)) {
+ message("Failed to initialize server socket\n");
+ goto err_listen;
+ }
+ message("Registering service via SDP\n");
+ if (!register_sdp(&r)) {
+ message("Failed to register in SDP\n");
+ goto err_sdp;
+ }
+ while (1) {
+ message("Waiting for connection...\n");
+ struct bt_audio_connection *c = wait_for_connection(&r);
+
+ if (c == NULL) {
+ message("Failed to get connection\n");
+ goto err_conn;
+ }
+ message("Got connection...\n");
+
+ process_connection(c);
+
+ message("Connection finished...\n");
+
+ close_connection(c);
+ }
+err_conn:
+ message("Unregistering service\n");
+ unregister_sdp(&r);
+err_sdp:
+ stop_listen(&r);
+err_listen:
+ sleep(5);
+ }
+ return (0);
+
+err_dlsym:
+ warnx("%s", dlerror());
+ dlclose(hdl);
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8 b/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8
new file mode 100644
index 000000000000..2c6c6ea18bbc
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8
@@ -0,0 +1,71 @@
+.\"
+.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
+.\"
+.\" All rights reserved.
+.\"
+.\" 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 February 12, 2025
+.Dt VIRTUAL_BT_SPEAKER 8
+.Os
+.Sh NAME
+.Nm virtual_bt_speaker
+.Nd virtual bluetooth speaker
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Sh DESCRIPTION
+.Nm
+provides bluetooth speaker functionality.
+It receives connections from bluetooth devices that stream music, and
+forwards the received stream to the given OSS device.
+This utility depends on
+.Xr sdpd 8
+running in the background.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl B
+Run program in background.
+.It Fl d Ar devname
+OSS device to play the received streams.
+.It Fl p Ar socketpath
+Path to SDP control socket.
+Default is to use default location.
+.It Fl i Ar priority
+Set real-time priority.
+.It Fl h
+Show usage.
+.El
+.Sh EXAMPLES
+.Bd -literal -offset indent
+virtual_bt_speaker -d /dev/dspX
+.Ed
+.Sh SEE ALSO
+.Xr sdpd 8
+and
+.Xr virtual_oss 8
+.Sh AUTHORS
+.Nm
+was written by
+.An Richard Kralovic riso@google.com .
diff --git a/usr.sbin/virtual_oss/virtual_equalizer/Makefile b/usr.sbin/virtual_oss/virtual_equalizer/Makefile
new file mode 100644
index 000000000000..e67150eff000
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_equalizer/Makefile
@@ -0,0 +1,11 @@
+PROG= virtual_equalizer
+MAN= ${PROG}.8
+
+SRCS= equalizer.c
+
+CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
+ -I/usr/local/include
+
+LDFLAGS+= -L/usr/local/lib -lm -lfftw3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c
new file mode 100644
index 000000000000..d1682186084d
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c
@@ -0,0 +1,431 @@
+/*-
+ * Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
+ *
+ * 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/ioctl.h>
+#include <sys/socket.h>
+#include <sys/soundcard.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fftw3.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "virtual_oss.h"
+
+struct Equalizer {
+ double rate;
+ int block_size;
+ int do_normalize;
+
+ /* (block_size * 2) elements, time domain */
+ double *fftw_time;
+
+ /* (block_size * 2) elements, half-complex, freq domain */
+ double *fftw_freq;
+
+ fftw_plan forward;
+ fftw_plan inverse;
+};
+
+static int be_silent = 0;
+
+static void
+message(const char *fmt,...)
+{
+ va_list list;
+
+ if (be_silent)
+ return;
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ va_end(list);
+}
+
+/*
+ * Masking window value for -1 < x < 1.
+ *
+ * Window must be symmetric, thus, this function is queried for x >= 0
+ * only. Currently a Hann window.
+ */
+static double
+equalizer_get_window(double x)
+{
+ return (0.5 + 0.5 * cos(M_PI * x));
+}
+
+static int
+equalizer_load_freq_amps(struct Equalizer *e, const char *config)
+{
+ double prev_f = 0.0;
+ double prev_amp = 1.0;
+ double next_f = 0.0;
+ double next_amp = 1.0;
+ int i;
+
+ if (strncasecmp(config, "normalize", 4) == 0) {
+ while (*config != 0) {
+ if (*config == '\n') {
+ config++;
+ break;
+ }
+ config++;
+ }
+ e->do_normalize = 1;
+ } else {
+ e->do_normalize = 0;
+ }
+
+ for (i = 0; i <= (e->block_size / 2); ++i) {
+ const double f = (i * e->rate) / e->block_size;
+
+ while (f >= next_f) {
+ prev_f = next_f;
+ prev_amp = next_amp;
+
+ if (*config == 0) {
+ next_f = e->rate;
+ next_amp = prev_amp;
+ } else {
+ int len;
+
+ if (sscanf(config, "%lf %lf %n", &next_f, &next_amp, &len) == 2) {
+ config += len;
+ if (next_f < prev_f) {
+ message("Parse error: Nonincreasing sequence of frequencies.\n");
+ return (0);
+ }
+ } else {
+ message("Parse error.\n");
+ return (0);
+ }
+ }
+ if (prev_f == 0.0)
+ prev_amp = next_amp;
+ }
+ e->fftw_freq[i] = ((f - prev_f) / (next_f - prev_f)) * (next_amp - prev_amp) + prev_amp;
+ }
+ return (1);
+}
+
+static void
+equalizer_init(struct Equalizer *e, int rate, int block_size)
+{
+ size_t buffer_size;
+
+ e->rate = rate;
+ e->block_size = block_size;
+
+ buffer_size = sizeof(double) * e->block_size;
+
+ e->fftw_time = (double *)malloc(buffer_size);
+ e->fftw_freq = (double *)malloc(buffer_size);
+
+ e->forward = fftw_plan_r2r_1d(block_size, e->fftw_time, e->fftw_freq,
+ FFTW_R2HC, FFTW_MEASURE);
+ e->inverse = fftw_plan_r2r_1d(block_size, e->fftw_freq, e->fftw_time,
+ FFTW_HC2R, FFTW_MEASURE);
+}
+
+static int
+equalizer_load(struct Equalizer *eq, const char *config)
+{
+ int retval = 0;
+ int N = eq->block_size;
+ int buffer_size = sizeof(double) * N;
+ int i;
+
+ memset(eq->fftw_freq, 0, buffer_size);
+
+ message("\n\nReloading amplification specifications:\n%s\n", config);
+
+ if (!equalizer_load_freq_amps(eq, config))
+ goto end;
+
+ double *requested_freq = (double *)malloc(buffer_size);
+
+ memcpy(requested_freq, eq->fftw_freq, buffer_size);
+
+ fftw_execute(eq->inverse);
+
+ /* Multiply by symmetric window and shift */
+ for (i = 0; i < (N / 2); ++i) {
+ double weight = equalizer_get_window(i / (double)(N / 2)) / N;
+
+ eq->fftw_time[N / 2 + i] = eq->fftw_time[i] * weight;
+ }
+ for (i = (N / 2 - 1); i > 0; --i) {
+ eq->fftw_time[i] = eq->fftw_time[N - i];
+ }
+ eq->fftw_time[0] = 0;
+
+ fftw_execute(eq->forward);
+ for (i = 0; i < N; ++i) {
+ eq->fftw_freq[i] /= (double)N;
+ }
+
+ /* Debug output */
+ for (i = 0; i <= (N / 2); ++i) {
+ double f = (eq->rate / N) * i;
+ double a = sqrt(pow(eq->fftw_freq[i], 2.0) +
+ ((i > 0 && i < N / 2) ? pow(eq->fftw_freq[N - i], 2.0) : 0));
+
+ a *= N;
+ double r = requested_freq[i];
+
+ message("%3.1lf Hz: requested %2.2lf, got %2.7lf (log10 = %.2lf), %3.7lfdb\n",
+ f, r, a, log(a) / log(10), (log(a / r) / log(10.0)) * 10.0);
+ }
+
+ /* Normalize FIR filter, if any */
+ if (eq->do_normalize) {
+ double sum = 0;
+
+ for (i = 0; i < N; ++i)
+ sum += fabs(eq->fftw_time[i]);
+ if (sum != 0.0) {
+ for (i = 0; i < N; ++i)
+ eq->fftw_time[i] /= sum;
+ }
+ }
+ for (i = 0; i < N; ++i) {
+ message("%.3lf ms: %.10lf\n", 1000.0 * i / eq->rate, eq->fftw_time[i]);
+ }
+
+ /* End of debug */
+
+ retval = 1;
+
+ free(requested_freq);
+end:
+ return (retval);
+}
+
+static void
+equalizer_done(struct Equalizer *eq)
+{
+
+ fftw_destroy_plan(eq->forward);
+ fftw_destroy_plan(eq->inverse);
+ free(eq->fftw_time);
+ free(eq->fftw_freq);
+}
+
+static struct option equalizer_opts[] = {
+ {"device", required_argument, NULL, 'd'},
+ {"part", required_argument, NULL, 'p'},
+ {"channels", required_argument, NULL, 'c'},
+ {"what", required_argument, NULL, 'w'},
+ {"off", no_argument, NULL, 'o'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"file", no_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+};
+
+static void
+usage(void)
+{
+ message("Usage: virtual_equalizer -d /dev/vdsp.ctl \n"
+ "\t -d, --device [control device]\n"
+ "\t -w, --what [rx_dev,tx_dev,rx_loop,tx_loop, default tx_dev]\n"
+ "\t -p, --part [part number, default 0]\n"
+ "\t -c, --channels [channels, default -1]\n"
+ "\t -f, --file [read input from file, default standard input]\n"
+ "\t -o, --off [disable equalizer]\n"
+ "\t -q, --quiet\n"
+ "\t -h, --help\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct virtual_oss_fir_filter fir = {};
+ struct virtual_oss_io_info info = {};
+
+ struct Equalizer e;
+
+ char buffer[65536];
+ unsigned cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER;
+ unsigned cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER;
+ unsigned cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
+ const char *dsp = NULL;
+ int rate;
+ int channels = -1;
+ int part = 0;
+ int opt;
+ int len;
+ int offset;
+ int disable = 0;
+ int f = STDIN_FILENO;
+
+ while ((opt = getopt_long(argc, argv, "d:c:f:op:w:qh",
+ equalizer_opts, NULL)) != -1) {
+ switch (opt) {
+ case 'd':
+ dsp = optarg;
+ break;
+ case 'c':
+ channels = atoi(optarg);
+ if (channels == 0) {
+ message("Wrong number of channels\n");
+ usage();
+ }
+ break;
+ case 'p':
+ part = atoi(optarg);
+ if (part < 0) {
+ message("Invalid part number\n");
+ usage();
+ }
+ break;
+ case 'w':
+ if (strcmp(optarg, "rx_dev") == 0) {
+ cmd_fir_set = VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER;
+ cmd_fir_get = VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER;
+ cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
+ } else if (strcmp(optarg, "tx_dev") == 0) {
+ cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER;
+ cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER;
+ cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
+ } else if (strcmp(optarg, "rx_loop") == 0) {
+ cmd_fir_set = VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER;
+ cmd_fir_get = VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER;
+ cmd_info = VIRTUAL_OSS_GET_LOOP_INFO;
+ } else if (strcmp(optarg, "tx_loop") == 0) {
+ cmd_fir_set = VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER;
+ cmd_fir_get = VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER;
+ cmd_info = VIRTUAL_OSS_GET_LOOP_INFO;
+ } else {
+ message("Bad -w argument not recognized\n");
+ usage();
+ }
+ break;
+ case 'f':
+ if (f != STDIN_FILENO) {
+ message("Can only specify one file\n");
+ usage();
+ }
+ f = open(optarg, O_RDONLY);
+ if (f < 0) {
+ message("Cannot open specified file\n");
+ usage();
+ }
+ break;
+ case 'o':
+ disable = 1;
+ break;
+ case 'q':
+ be_silent = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ fir.number = part;
+ info.number = part;
+
+ int fd = open(dsp, O_RDWR);
+
+ if (fd < 0) {
+ message("Cannot open DSP device\n");
+ return (EX_SOFTWARE);
+ }
+ if (ioctl(fd, VIRTUAL_OSS_GET_SAMPLE_RATE, &rate) < 0) {
+ message("Cannot get sample rate\n");
+ return (EX_SOFTWARE);
+ }
+ if (ioctl(fd, cmd_fir_get, &fir) < 0) {
+ message("Cannot get current FIR filter\n");
+ return (EX_SOFTWARE);
+ }
+ if (disable) {
+ for (fir.channel = 0; fir.channel != channels; fir.channel++) {
+ if (ioctl(fd, cmd_fir_set, &fir) < 0) {
+ if (fir.channel == 0) {
+ message("Cannot disable FIR filter\n");
+ return (EX_SOFTWARE);
+ }
+ break;
+ }
+ }
+ return (0);
+ }
+ equalizer_init(&e, rate, fir.filter_size);
+ equalizer_load(&e, "");
+
+ if (f == STDIN_FILENO) {
+ if (ioctl(fd, cmd_info, &info) < 0) {
+ message("Cannot read part information\n");
+ return (EX_SOFTWARE);
+ }
+ message("Please enter EQ layout for %s, <freq> <gain>:\n", info.name);
+ }
+ offset = 0;
+ while (1) {
+ if (offset == (int)(sizeof(buffer) - 1)) {
+ message("Too much input data\n");
+ return (EX_SOFTWARE);
+ }
+ len = read(f, buffer + offset, sizeof(buffer) - 1 - offset);
+ if (len <= 0)
+ break;
+ offset += len;
+ }
+ buffer[offset] = 0;
+ close(f);
+
+ if (f == STDIN_FILENO)
+ message("Loading new EQ layout\n");
+
+ if (equalizer_load(&e, buffer) == 0) {
+ message("Invalid equalizer data\n");
+ return (EX_SOFTWARE);
+ }
+ fir.filter_data = e.fftw_time;
+
+ for (fir.channel = 0; fir.channel != channels; fir.channel++) {
+ if (ioctl(fd, cmd_fir_set, &fir) < 0) {
+ if (fir.channel == 0)
+ message("Cannot set FIR filter on channel\n");
+ break;
+ }
+ }
+
+ close(fd);
+ equalizer_done(&e);
+
+ return (0);
+}
diff --git a/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8 b/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8
new file mode 100644
index 000000000000..db47db84305e
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8
@@ -0,0 +1,127 @@
+.\"
+.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
+.\"
+.\" All rights reserved.
+.\"
+.\" 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 February 12, 2025
+.Dt VIRTUAL_EQUALIZER 8
+.Os
+.Sh NAME
+.Nm virtual_equalizer
+.Nd audio equalizer
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Fl o
+.Op Fl q
+.Op Fl d Ar devname
+.Op Fl w Ar what
+.Op Fl p Ar part
+.Op Fl c Ar channels
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+sets the given frequency response for the given
+.Xr virtual_oss 8
+instance via the control character device given by the -d option.
+The design goal of this equalizer is to provide precise equalization
+for arbitrary requested frequency response at the expense of higher
+latency, utilizing a so-called finite impulse response, FIR, filter.
+.Pp
+The requested frequency response is configured via standard input or
+the file specified by the -f option.
+There is one control point in per line.
+Each line consists of two numbers, frequency in Hz and requested
+amplification.
+Amplification between two consecutive control points is a linear
+interpolation of the given control point values.
+.Pp
+To make the filter finite, it is windowed in time domain using a Hann
+window.
+The windowing actually modifies the frequency response - the actual
+response is a convolution of the requested response and spectrum of
+the window.
+This is, however, very close to the requested response.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl q
+Be quiet and don't print anything to standard output.
+.It Fl d Ar device
+The
+.Xr virtual_oss 8
+control character device.
+.It Fl w Ar what
+Select what part the FIR filter should apply to.
+Valid values are: rx_dev, tx_dev, rx_loop and tx_loop.
+The default value is tx_dev.
+.It Fl p Ar part
+Select the index of the part given by the -w option to apply the filter to.
+Default is zero.
+.It Fl c Ar channels
+Select number of channels to apply filter to, starting at channel zero.
+By default all channels of the given part are updated.
+.It Fl f Ar file
+Read filter coefficients from the given file instead of standard input.
+.It Fl o
+Turn equalizer off.
+.It Fl h
+Show usage.
+.El
+.Sh EXAMPLES
+To pass only frequencies between 200Hz and 400Hz:
+.Bd -literal -offset indent
+# Note that the -F and -G options enable FIR filtering.
+virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\
+ -f /dev/dsp -d dsp.virtual -t vdsp.ctl
+
+# For simplex operation use this:
+virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\
+ -R /dev/null -O /dev/dsp -d dsp.virtual -t vdsp.ctl
+
+# Load normalized filter points to avoid sample value overflow
+cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2
+NORMALIZE
+199 0.0
+200 1.0
+400 1.0
+401 0.0
+EOF
+
+# Load FIR filter based on sine frequency points
+cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2
+199 0.0
+200 1.0
+400 1.0
+401 0.0
+EOF
+
+.Ed
+.Sh SEE ALSO
+.Xr virtual_oss 8
+.Sh AUTHORS
+.Nm
+was written by
+.An Richard Kralovic riso@google.com .
diff --git a/usr.sbin/virtual_oss/virtual_oss/Makefile b/usr.sbin/virtual_oss/virtual_oss/Makefile
new file mode 100644
index 000000000000..cdb6bcac3fad
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/Makefile
@@ -0,0 +1,24 @@
+PROG= virtual_oss
+MAN= ${PROG}.8
+
+SRCS= audio_delay.c \
+ compressor.c \
+ ctl.c \
+ eq.c \
+ format.c \
+ httpd.c \
+ main.c \
+ mul.c \
+ ring.c \
+ virtual_oss.c
+
+CFLAGS+= -I${SRCTOP}/contrib/libsamplerate
+# The --export-dynamic-symbol flags below are needed because some backends make
+# use of those symbols.
+LDFLAGS+= -lpthread -lcuse -lnv -lm \
+ -Wl,--export-dynamic-symbol=virtual_oss_wait \
+ -Wl,--export-dynamic-symbol=voss_has_synchronization
+LIBADD= samplerate
+LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/virtual_oss/virtual_oss/audio_delay.c b/usr.sbin/virtual_oss/virtual_oss/audio_delay.c
new file mode 100644
index 000000000000..a69f448354fd
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/audio_delay.c
@@ -0,0 +1,238 @@
+/*-
+ * Copyright (c) 2014 Hans Petter Selasky
+ *
+ * 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/queue.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+#include <math.h>
+#include <sysexits.h>
+
+#include "int.h"
+
+#define REF_FREQ 500 /* HZ */
+
+uint32_t voss_ad_last_delay;
+uint8_t voss_ad_enabled;
+uint8_t voss_ad_output_signal;
+uint8_t voss_ad_input_channel;
+uint8_t voss_ad_output_channel;
+
+static struct voss_ad {
+ double *wave;
+
+ double *sin_a;
+ double *cos_a;
+
+ double *sin_b;
+ double *cos_b;
+
+ double *buf_a;
+ double *buf_b;
+
+ double sum_sin_a;
+ double sum_cos_a;
+
+ double sum_sin_b;
+ double sum_cos_b;
+
+ uint32_t len_a;
+ uint32_t len_b;
+
+ uint32_t offset_a;
+ uint32_t offset_b;
+} voss_ad;
+
+void
+voss_ad_reset(void)
+{
+ uint32_t x;
+
+ for (x = 0; x != voss_ad.len_a; x++)
+ voss_ad.buf_a[x] = 0;
+
+ for (x = 0; x != voss_ad.len_b; x++)
+ voss_ad.buf_b[x] = 0;
+
+ voss_ad.sum_sin_a = 0;
+ voss_ad.sum_cos_a = 0;
+ voss_ad.sum_sin_b = 0;
+ voss_ad.sum_cos_b = 0;
+
+ voss_ad.offset_a = 0;
+ voss_ad.offset_b = 0;
+
+ voss_ad_last_delay = 0;
+}
+
+void
+voss_ad_init(uint32_t rate)
+{
+ double freq;
+ int samples;
+ int len;
+ int x;
+
+ len = sqrt(rate);
+
+ samples = len * len;
+
+ voss_ad.wave = malloc(sizeof(voss_ad.wave[0]) * samples);
+
+ voss_ad.sin_a = malloc(sizeof(voss_ad.sin_a[0]) * len);
+ voss_ad.cos_a = malloc(sizeof(voss_ad.cos_a[0]) * len);
+ voss_ad.buf_a = malloc(sizeof(voss_ad.buf_a[0]) * len);
+ voss_ad.len_a = len;
+
+ voss_ad.sin_b = malloc(sizeof(voss_ad.sin_b[0]) * samples);
+ voss_ad.cos_b = malloc(sizeof(voss_ad.cos_b[0]) * samples);
+ voss_ad.buf_b = malloc(sizeof(voss_ad.buf_b[0]) * samples);
+ voss_ad.len_b = samples;
+
+ if (voss_ad.sin_a == NULL || voss_ad.cos_a == NULL ||
+ voss_ad.sin_b == NULL || voss_ad.cos_b == NULL ||
+ voss_ad.buf_a == NULL || voss_ad.buf_b == NULL)
+ errx(EX_SOFTWARE, "Out of memory");
+
+ freq = 1.0;
+
+ while (1) {
+ double temp = freq * ((double)rate) / ((double)len);
+ if (temp >= REF_FREQ)
+ break;
+ freq += 1.0;
+ }
+
+ for (x = 0; x != len; x++) {
+ voss_ad.sin_a[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len));
+ voss_ad.cos_a[x] = cos(freq * 2.0 * M_PI * ((double)x) / ((double)len));
+ voss_ad.buf_a[x] = 0;
+ }
+
+ for (x = 0; x != samples; x++) {
+
+ voss_ad.wave[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len)) *
+ (1.0 + sin(2.0 * M_PI * ((double)x) / ((double)samples))) / 2.0;
+
+ voss_ad.sin_b[x] = sin(2.0 * M_PI * ((double)x) / ((double)samples));
+ voss_ad.cos_b[x] = cos(2.0 * M_PI * ((double)x) / ((double)samples));
+ voss_ad.buf_b[x] = 0;
+ }
+}
+
+static double
+voss_add_decode_offset(double x /* cos */, double y /* sin */)
+{
+ uint32_t v;
+ double r;
+
+ r = sqrt((x * x) + (y * y));
+
+ if (r == 0.0)
+ return (0);
+
+ x /= r;
+ y /= r;
+
+ v = 0;
+
+ if (y < 0) {
+ v |= 1;
+ y = -y;
+ }
+ if (x < 0) {
+ v |= 2;
+ x = -x;
+ }
+
+ if (y < x) {
+ r = acos(y);
+ } else {
+ r = asin(x);
+ }
+
+ switch (v) {
+ case 0:
+ r = (2.0 * M_PI) - r;
+ break;
+ case 1:
+ r = M_PI + r;
+ break;
+ case 3:
+ r = M_PI - r;
+ break;
+ default:
+ break;
+ }
+ return (r);
+}
+
+double
+voss_ad_getput_sample(double sample)
+{
+ double retval;
+ double phase;
+ uint32_t xa;
+ uint32_t xb;
+
+ xa = voss_ad.offset_a;
+ xb = voss_ad.offset_b;
+ retval = voss_ad.wave[xb];
+
+ sample -= voss_ad.buf_a[xa];
+ voss_ad.sum_sin_a += voss_ad.sin_a[xa] * sample;
+ voss_ad.sum_cos_a += voss_ad.cos_a[xa] * sample;
+ voss_ad.buf_a[xa] += sample;
+
+ sample = sqrt((voss_ad.sum_sin_a * voss_ad.sum_sin_a) +
+ (voss_ad.sum_cos_a * voss_ad.sum_cos_a));
+
+ sample -= voss_ad.buf_b[xb];
+ voss_ad.sum_sin_b += voss_ad.sin_b[xb] * sample;
+ voss_ad.sum_cos_b += voss_ad.cos_b[xb] * sample;
+ voss_ad.buf_b[xb] += sample;
+
+ if (++xa == voss_ad.len_a)
+ xa = 0;
+
+ if (++xb == voss_ad.len_b) {
+ xb = 0;
+
+ phase = voss_add_decode_offset(
+ voss_ad.sum_cos_b, voss_ad.sum_sin_b);
+
+ voss_ad_last_delay = (uint32_t)(phase * (double)(voss_ad.len_b) / (2.0 * M_PI)) - (voss_ad.len_a / 2);
+ if (voss_ad_last_delay > voss_ad.len_b)
+ voss_ad_last_delay = voss_ad.len_b;
+ }
+ voss_ad.offset_a = xa;
+ voss_ad.offset_b = xb;
+
+ return (retval * (1LL << voss_ad_output_signal));
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/backend.h b/usr.sbin/virtual_oss/virtual_oss/backend.h
new file mode 100644
index 000000000000..d7453f3db89e
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/backend.h
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 2015 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifndef _VIRTUAL_BACKEND_H_
+#define _VIRTUAL_BACKEND_H_
+
+struct voss_backend {
+ int (*open)(struct voss_backend *, const char *, int, int, int *, int *);
+ void (*close)(struct voss_backend *);
+ int (*transfer)(struct voss_backend *, void *, int);
+ void (*delay)(struct voss_backend *, int *);
+ void *arg;
+ int fd;
+};
+
+/* Currently selected backends */
+extern struct voss_backend *voss_rx_backend;
+extern struct voss_backend *voss_tx_backend;
+
+/* Available backends */
+/* XXX Get rid somehow? */
+extern struct voss_backend voss_backend_null_rec;
+extern struct voss_backend voss_backend_null_play;
+extern struct voss_backend voss_backend_oss_rec;
+extern struct voss_backend voss_backend_oss_play;
+extern struct voss_backend voss_backend_bt_rec;
+extern struct voss_backend voss_backend_bt_play;
+extern struct voss_backend voss_backend_sndio_rec;
+extern struct voss_backend voss_backend_sndio_play;
+
+#endif /* _VIRTUAL_BACKEND_H_ */
diff --git a/usr.sbin/virtual_oss/virtual_oss/compressor.c b/usr.sbin/virtual_oss/virtual_oss/compressor.c
new file mode 100644
index 000000000000..4a92a38eceaa
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/compressor.c
@@ -0,0 +1,76 @@
+/*-
+ * Copyright (c) 2020 Hans Petter Selasky
+ *
+ * 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/queue.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include "int.h"
+#include "virtual_oss.h"
+
+struct virtual_compressor voss_output_compressor_param = {
+ .knee = 85,
+ .attack = 3,
+ .decay = 20,
+};
+double voss_output_compressor_gain[VMAX_CHAN];
+
+void
+voss_compressor(int64_t *buffer, double *p_ch_gain,
+ const struct virtual_compressor *p_param, const unsigned samples,
+ const unsigned maxchan, const int64_t fmt_max)
+{
+ int64_t knee_amp;
+ int64_t sample;
+ unsigned ch;
+ unsigned i;
+ double amp;
+
+ /* check if compressor is enabled */
+ if (p_param->enabled != 1)
+ return;
+
+ knee_amp = (fmt_max * p_param->knee) / VIRTUAL_OSS_KNEE_MAX;
+
+ for (ch = i = 0; i != samples; i++) {
+ sample = buffer[i];
+ if (sample < 0)
+ sample = -sample;
+
+ amp = p_ch_gain[ch];
+ if (sample > knee_amp) {
+ const double gain = (double)knee_amp / (double)sample;
+ if (gain < amp)
+ amp += (gain - amp) / (1LL << p_param->attack);
+ }
+ buffer[i] *= amp;
+ amp += (1.0 - amp) / (1LL << p_param->decay);
+ p_ch_gain[ch] = amp;
+
+ if (++ch == maxchan)
+ ch = 0;
+ }
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/ctl.c b/usr.sbin/virtual_oss/virtual_oss/ctl.c
new file mode 100644
index 000000000000..4a445a59db59
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/ctl.c
@@ -0,0 +1,615 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * 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/types.h>
+#include <sys/queue.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cuse.h>
+
+#include "int.h"
+#include "virtual_oss.h"
+
+int64_t voss_output_peak[VMAX_CHAN];
+int64_t voss_input_peak[VMAX_CHAN];
+
+static int
+vctl_open(struct cuse_dev *pdev __unused, int fflags __unused)
+{
+ return (0);
+}
+
+static int
+vctl_close(struct cuse_dev *pdev __unused, int fflags __unused)
+{
+ return (0);
+}
+
+static vprofile_t *
+vprofile_by_index(const vprofile_head_t *phead, int index)
+{
+ vprofile_t *pvp;
+
+ TAILQ_FOREACH(pvp, phead, entry) {
+ if (!index--)
+ return (pvp);
+ }
+ return (NULL);
+}
+
+static vmonitor_t *
+vmonitor_by_index(int index, vmonitor_head_t *phead)
+{
+ vmonitor_t *pvm;
+
+ TAILQ_FOREACH(pvm, phead, entry) {
+ if (!index--)
+ return (pvm);
+ }
+ return (NULL);
+}
+
+static int
+vctl_ioctl(struct cuse_dev *pdev __unused, int fflags __unused,
+ unsigned long cmd, void *peer_data)
+{
+ union {
+ int val;
+ struct virtual_oss_io_info io_info;
+ struct virtual_oss_mon_info mon_info;
+ struct virtual_oss_io_peak io_peak;
+ struct virtual_oss_mon_peak mon_peak;
+ struct virtual_oss_compressor out_lim;
+ struct virtual_oss_io_limit io_lim;
+ struct virtual_oss_master_peak master_peak;
+ struct virtual_oss_audio_delay_locator ad_locator;
+ struct virtual_oss_fir_filter fir_filter;
+ struct virtual_oss_system_info sys_info;
+ char options[VIRTUAL_OSS_OPTIONS_MAX];
+ } data;
+
+ vprofile_t *pvp;
+ vmonitor_t *pvm;
+
+ int chan;
+ int len;
+ int error;
+
+ len = IOCPARM_LEN(cmd);
+
+ if (len < 0 || len > (int)sizeof(data))
+ return (CUSE_ERR_INVALID);
+
+ if (cmd & IOC_IN) {
+ error = cuse_copy_in(peer_data, &data, len);
+ if (error)
+ return (error);
+ } else {
+ error = 0;
+ }
+
+ atomic_lock();
+ switch (cmd) {
+ case VIRTUAL_OSS_GET_DEV_INFO:
+ case VIRTUAL_OSS_SET_DEV_INFO:
+ case VIRTUAL_OSS_GET_DEV_PEAK:
+ case VIRTUAL_OSS_SET_DEV_LIMIT:
+ case VIRTUAL_OSS_GET_DEV_LIMIT:
+ case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
+ pvp = vprofile_by_index(&virtual_profile_client_head, data.val);
+ break;
+ case VIRTUAL_OSS_GET_LOOP_INFO:
+ case VIRTUAL_OSS_SET_LOOP_INFO:
+ case VIRTUAL_OSS_GET_LOOP_PEAK:
+ case VIRTUAL_OSS_SET_LOOP_LIMIT:
+ case VIRTUAL_OSS_GET_LOOP_LIMIT:
+ case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
+ case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
+ case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
+ case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
+ pvp = vprofile_by_index(&virtual_profile_loopback_head, data.val);
+ break;
+ default:
+ pvp = NULL;
+ break;
+ }
+
+ switch (cmd) {
+ case VIRTUAL_OSS_GET_VERSION:
+ data.val = VIRTUAL_OSS_VERSION;
+ break;
+ case VIRTUAL_OSS_GET_DEV_INFO:
+ case VIRTUAL_OSS_GET_LOOP_INFO:
+ if (pvp == NULL ||
+ data.io_info.channel < 0 ||
+ data.io_info.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ strlcpy(data.io_info.name, pvp->oss_name, sizeof(data.io_info.name));
+ chan = data.io_info.channel;
+ data.io_info.rx_amp = pvp->rx_shift[chan];
+ data.io_info.tx_amp = pvp->tx_shift[chan];
+ data.io_info.rx_chan = pvp->rx_src[chan];
+ data.io_info.tx_chan = pvp->tx_dst[chan];
+ data.io_info.rx_mute = pvp->rx_mute[chan] ? 1 : 0;
+ data.io_info.tx_mute = pvp->tx_mute[chan] ? 1 : 0;
+ data.io_info.rx_pol = pvp->rx_pol[chan] ? 1 : 0;
+ data.io_info.tx_pol = pvp->tx_pol[chan] ? 1 : 0;
+ data.io_info.bits = pvp->bits;
+ data.io_info.rx_delay = pvp->rec_delay;
+ data.io_info.rx_delay_limit = voss_dsp_sample_rate;
+ break;
+ case VIRTUAL_OSS_SET_DEV_INFO:
+ case VIRTUAL_OSS_SET_LOOP_INFO:
+ if (pvp == NULL ||
+ data.io_info.channel < 0 ||
+ data.io_info.channel >= (int)pvp->channels ||
+ data.io_info.rx_amp < -31 || data.io_info.rx_amp > 31 ||
+ data.io_info.tx_amp < -31 || data.io_info.tx_amp > 31 ||
+ data.io_info.rx_delay < 0 ||
+ data.io_info.rx_delay > (int)voss_dsp_sample_rate) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ chan = data.io_info.channel;
+ pvp->rx_shift[chan] = data.io_info.rx_amp;
+ pvp->tx_shift[chan] = data.io_info.tx_amp;
+ pvp->rx_src[chan] = data.io_info.rx_chan;
+ pvp->tx_dst[chan] = data.io_info.tx_chan;
+ pvp->rx_mute[chan] = data.io_info.rx_mute ? 1 : 0;
+ pvp->tx_mute[chan] = data.io_info.tx_mute ? 1 : 0;
+ pvp->rx_pol[chan] = data.io_info.rx_pol ? 1 : 0;
+ pvp->tx_pol[chan] = data.io_info.tx_pol ? 1 : 0;
+ pvp->rec_delay = data.io_info.rx_delay;
+ break;
+ case VIRTUAL_OSS_GET_INPUT_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_input);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_info.src_chan = pvm->src_chan;
+ data.mon_info.dst_chan = pvm->dst_chan;
+ data.mon_info.pol = pvm->pol;
+ data.mon_info.mute = pvm->mute;
+ data.mon_info.amp = pvm->shift;
+ data.mon_info.bits = voss_dsp_bits;
+ break;
+ case VIRTUAL_OSS_SET_INPUT_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_input);
+ if (pvm == NULL ||
+ data.mon_info.amp < -31 ||
+ data.mon_info.amp > 31) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ pvm->src_chan = data.mon_info.src_chan;
+ pvm->dst_chan = data.mon_info.dst_chan;
+ pvm->pol = data.mon_info.pol ? 1 : 0;
+ pvm->mute = data.mon_info.mute ? 1 : 0;
+ pvm->shift = data.mon_info.amp;
+ break;
+ case VIRTUAL_OSS_GET_OUTPUT_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_output);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_info.src_chan = pvm->src_chan;
+ data.mon_info.dst_chan = pvm->dst_chan;
+ data.mon_info.pol = pvm->pol;
+ data.mon_info.mute = pvm->mute;
+ data.mon_info.amp = pvm->shift;
+ data.mon_info.bits = voss_dsp_bits;
+ break;
+ case VIRTUAL_OSS_SET_OUTPUT_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_output);
+ if (pvm == NULL ||
+ data.mon_info.amp < -31 ||
+ data.mon_info.amp > 31) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ pvm->src_chan = data.mon_info.src_chan;
+ pvm->dst_chan = data.mon_info.dst_chan;
+ pvm->pol = data.mon_info.pol ? 1 : 0;
+ pvm->mute = data.mon_info.mute ? 1 : 0;
+ pvm->shift = data.mon_info.amp;
+ break;
+ case VIRTUAL_OSS_GET_LOCAL_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_local);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_info.src_chan = pvm->src_chan;
+ data.mon_info.dst_chan = pvm->dst_chan;
+ data.mon_info.pol = pvm->pol;
+ data.mon_info.mute = pvm->mute;
+ data.mon_info.amp = pvm->shift;
+ data.mon_info.bits = voss_dsp_bits;
+ break;
+ case VIRTUAL_OSS_SET_LOCAL_MON_INFO:
+ pvm = vmonitor_by_index(data.mon_info.number,
+ &virtual_monitor_local);
+ if (pvm == NULL ||
+ data.mon_info.amp < -31 ||
+ data.mon_info.amp > 31) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ pvm->src_chan = data.mon_info.src_chan;
+ pvm->dst_chan = data.mon_info.dst_chan;
+ pvm->pol = data.mon_info.pol ? 1 : 0;
+ pvm->mute = data.mon_info.mute ? 1 : 0;
+ pvm->shift = data.mon_info.amp;
+ break;
+ case VIRTUAL_OSS_GET_DEV_PEAK:
+ case VIRTUAL_OSS_GET_LOOP_PEAK:
+ if (pvp == NULL ||
+ data.io_peak.channel < 0 ||
+ data.io_peak.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ strlcpy(data.io_peak.name, pvp->oss_name, sizeof(data.io_peak.name));
+ chan = data.io_peak.channel;
+ data.io_peak.rx_peak_value = pvp->rx_peak_value[chan];
+ pvp->rx_peak_value[chan] = 0;
+ data.io_peak.tx_peak_value = pvp->tx_peak_value[chan];
+ pvp->tx_peak_value[chan] = 0;
+ data.io_peak.bits = pvp->bits;
+ break;
+ case VIRTUAL_OSS_GET_INPUT_MON_PEAK:
+ pvm = vmonitor_by_index(data.mon_peak.number,
+ &virtual_monitor_input);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_peak.peak_value = pvm->peak_value;
+ data.mon_peak.bits = voss_dsp_bits;
+ pvm->peak_value = 0;
+ break;
+ case VIRTUAL_OSS_GET_OUTPUT_MON_PEAK:
+ pvm = vmonitor_by_index(data.mon_peak.number,
+ &virtual_monitor_output);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_peak.peak_value = pvm->peak_value;
+ data.mon_peak.bits = voss_dsp_bits;
+ pvm->peak_value = 0;
+ break;
+ case VIRTUAL_OSS_GET_LOCAL_MON_PEAK:
+ pvm = vmonitor_by_index(data.mon_peak.number,
+ &virtual_monitor_local);
+ if (pvm == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.mon_peak.peak_value = pvm->peak_value;
+ data.mon_peak.bits = voss_dsp_bits;
+ pvm->peak_value = 0;
+ break;
+ case VIRTUAL_OSS_ADD_INPUT_MON:
+ pvm = vmonitor_alloc(&data.val,
+ &virtual_monitor_input);
+ if (pvm == NULL)
+ error = CUSE_ERR_INVALID;
+ break;
+ case VIRTUAL_OSS_ADD_OUTPUT_MON:
+ pvm = vmonitor_alloc(&data.val,
+ &virtual_monitor_output);
+ if (pvm == NULL)
+ error = CUSE_ERR_INVALID;
+ break;
+ case VIRTUAL_OSS_ADD_LOCAL_MON:
+ pvm = vmonitor_alloc(&data.val,
+ &virtual_monitor_local);
+ if (pvm == NULL)
+ error = CUSE_ERR_INVALID;
+ break;
+ case VIRTUAL_OSS_SET_OUTPUT_LIMIT:
+ if (data.out_lim.enabled < 0 ||
+ data.out_lim.enabled > 1 ||
+ data.out_lim.knee < VIRTUAL_OSS_KNEE_MIN ||
+ data.out_lim.knee > VIRTUAL_OSS_KNEE_MAX ||
+ data.out_lim.attack < VIRTUAL_OSS_ATTACK_MIN ||
+ data.out_lim.attack > VIRTUAL_OSS_ATTACK_MAX ||
+ data.out_lim.decay < VIRTUAL_OSS_DECAY_MIN ||
+ data.out_lim.decay > VIRTUAL_OSS_DECAY_MAX ||
+ data.out_lim.gain != 0) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ voss_output_compressor_param.enabled = data.out_lim.enabled;
+ voss_output_compressor_param.knee = data.out_lim.knee;
+ voss_output_compressor_param.attack = data.out_lim.attack;
+ voss_output_compressor_param.decay = data.out_lim.decay;
+ break;
+ case VIRTUAL_OSS_GET_OUTPUT_LIMIT:
+ data.out_lim.enabled = voss_output_compressor_param.enabled;
+ data.out_lim.knee = voss_output_compressor_param.knee;
+ data.out_lim.attack = voss_output_compressor_param.attack;
+ data.out_lim.decay = voss_output_compressor_param.decay;
+ data.out_lim.gain = 1000;
+ for (chan = 0; chan != VMAX_CHAN; chan++) {
+ int gain = voss_output_compressor_gain[chan] * 1000.0;
+ if (data.out_lim.gain > gain)
+ data.out_lim.gain = gain;
+ }
+ break;
+ case VIRTUAL_OSS_SET_DEV_LIMIT:
+ case VIRTUAL_OSS_SET_LOOP_LIMIT:
+ if (pvp == NULL ||
+ data.io_lim.param.enabled < 0 ||
+ data.io_lim.param.enabled > 1 ||
+ data.io_lim.param.knee < VIRTUAL_OSS_KNEE_MIN ||
+ data.io_lim.param.knee > VIRTUAL_OSS_KNEE_MAX ||
+ data.io_lim.param.attack < VIRTUAL_OSS_ATTACK_MIN ||
+ data.io_lim.param.attack > VIRTUAL_OSS_ATTACK_MAX ||
+ data.io_lim.param.decay < VIRTUAL_OSS_DECAY_MIN ||
+ data.io_lim.param.decay > VIRTUAL_OSS_DECAY_MAX ||
+ data.io_lim.param.gain != 0) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ pvp->rx_compressor_param.enabled = data.io_lim.param.enabled;
+ pvp->rx_compressor_param.knee = data.io_lim.param.knee;
+ pvp->rx_compressor_param.attack = data.io_lim.param.attack;
+ pvp->rx_compressor_param.decay = data.io_lim.param.decay;
+ break;
+ case VIRTUAL_OSS_GET_DEV_LIMIT:
+ case VIRTUAL_OSS_GET_LOOP_LIMIT:
+ if (pvp == NULL) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.io_lim.param.enabled = pvp->rx_compressor_param.enabled;
+ data.io_lim.param.knee = pvp->rx_compressor_param.knee;
+ data.io_lim.param.attack = pvp->rx_compressor_param.attack;
+ data.io_lim.param.decay = pvp->rx_compressor_param.decay;
+ data.io_lim.param.gain = 1000;
+
+ for (chan = 0; chan != VMAX_CHAN; chan++) {
+ int gain = pvp->rx_compressor_gain[chan] * 1000.0;
+ if (data.io_lim.param.gain > gain)
+ data.io_lim.param.gain = gain;
+ }
+ break;
+ case VIRTUAL_OSS_GET_OUTPUT_PEAK:
+ chan = data.master_peak.channel;
+ if (chan < 0 ||
+ chan >= (int)voss_max_channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.master_peak.bits = voss_dsp_bits;
+ data.master_peak.peak_value = voss_output_peak[chan];
+ voss_output_peak[chan] = 0;
+ break;
+ case VIRTUAL_OSS_GET_INPUT_PEAK:
+ chan = data.master_peak.channel;
+ if (chan < 0 ||
+ chan >= (int)voss_dsp_max_channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ data.master_peak.bits = voss_dsp_bits;
+ data.master_peak.peak_value = voss_input_peak[chan];
+ voss_input_peak[chan] = 0;
+ break;
+
+ case VIRTUAL_OSS_SET_RECORDING:
+ voss_is_recording = data.val ? 1 : 0;
+ break;
+
+ case VIRTUAL_OSS_GET_RECORDING:
+ data.val = voss_is_recording;
+ break;
+
+ case VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR:
+ if (data.ad_locator.channel_output < 0 ||
+ data.ad_locator.channel_output >= (int)voss_mix_channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ if (data.ad_locator.channel_input < 0 ||
+ data.ad_locator.channel_input >= (int)voss_mix_channels) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ if (data.ad_locator.signal_output_level < 0 ||
+ data.ad_locator.signal_output_level >= 64) {
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ voss_ad_enabled = (data.ad_locator.locator_enabled != 0);
+ voss_ad_output_signal = data.ad_locator.signal_output_level;
+ voss_ad_output_channel = data.ad_locator.channel_output;
+ voss_ad_input_channel = data.ad_locator.channel_input;
+ break;
+
+ case VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR:
+ data.ad_locator.locator_enabled = voss_ad_enabled;
+ data.ad_locator.signal_output_level = voss_ad_output_signal;
+ data.ad_locator.channel_output = voss_ad_output_channel;
+ data.ad_locator.channel_input = voss_ad_input_channel;
+ data.ad_locator.channel_last = voss_mix_channels - 1;
+ data.ad_locator.signal_input_delay = voss_ad_last_delay;
+ data.ad_locator.signal_delay_hz = voss_dsp_sample_rate;
+ break;
+
+ case VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR:
+ voss_ad_reset();
+ break;
+
+ case VIRTUAL_OSS_ADD_OPTIONS:
+ data.options[VIRTUAL_OSS_OPTIONS_MAX - 1] = 0;
+ voss_add_options(data.options);
+ break;
+
+ case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
+ if (pvp == NULL ||
+ data.fir_filter.channel < 0 ||
+ data.fir_filter.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ } else if (data.fir_filter.filter_data == NULL) {
+ data.fir_filter.filter_size = pvp->rx_filter_size;
+ } else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
+ error = CUSE_ERR_INVALID;
+ } else if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
+ error = CUSE_ERR_NO_MEMORY; /* filter disabled */
+ } else {
+ error = cuse_copy_out(pvp->rx_filter_data[data.fir_filter.channel],
+ data.fir_filter.filter_data,
+ sizeof(pvp->rx_filter_data[0][0]) *
+ data.fir_filter.filter_size);
+ }
+ break;
+
+ case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
+ if (pvp == NULL ||
+ data.fir_filter.channel < 0 ||
+ data.fir_filter.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ } else if (data.fir_filter.filter_data == NULL) {
+ data.fir_filter.filter_size = pvp->tx_filter_size;
+ } else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
+ error = CUSE_ERR_INVALID;
+ } else if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
+ error = CUSE_ERR_NO_MEMORY; /* filter disabled */
+ } else {
+ error = cuse_copy_out(pvp->tx_filter_data[data.fir_filter.channel],
+ data.fir_filter.filter_data,
+ sizeof(pvp->tx_filter_data[0][0]) *
+ data.fir_filter.filter_size);
+ }
+ break;
+
+ case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
+ if (pvp == NULL ||
+ data.fir_filter.channel < 0 ||
+ data.fir_filter.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ } else if (data.fir_filter.filter_data == NULL) {
+ free(pvp->rx_filter_data[data.fir_filter.channel]);
+ pvp->rx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */
+ } else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
+ error = CUSE_ERR_INVALID;
+ } else if (pvp->rx_filter_size != 0) {
+ size_t size = sizeof(pvp->rx_filter_data[0][0]) * pvp->rx_filter_size;
+ if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
+ pvp->rx_filter_data[data.fir_filter.channel] = malloc(size);
+ if (pvp->rx_filter_data[data.fir_filter.channel] == NULL)
+ error = CUSE_ERR_NO_MEMORY;
+ else
+ memset(pvp->rx_filter_data[data.fir_filter.channel], 0, size);
+ }
+ if (pvp->rx_filter_data[data.fir_filter.channel] != NULL) {
+ error = cuse_copy_in(data.fir_filter.filter_data,
+ pvp->rx_filter_data[data.fir_filter.channel], size);
+ }
+ }
+ break;
+
+ case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
+ case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
+ if (pvp == NULL ||
+ data.fir_filter.channel < 0 ||
+ data.fir_filter.channel >= (int)pvp->channels) {
+ error = CUSE_ERR_INVALID;
+ } else if (data.fir_filter.filter_data == NULL) {
+ free(pvp->tx_filter_data[data.fir_filter.channel]);
+ pvp->tx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */
+ } else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
+ error = CUSE_ERR_INVALID;
+ } else if (pvp->tx_filter_size != 0) {
+ size_t size = sizeof(pvp->tx_filter_data[0][0]) * pvp->tx_filter_size;
+ if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
+ pvp->tx_filter_data[data.fir_filter.channel] = malloc(size);
+ if (pvp->tx_filter_data[data.fir_filter.channel] == NULL)
+ error = CUSE_ERR_NO_MEMORY;
+ else
+ memset(pvp->tx_filter_data[data.fir_filter.channel], 0, size);
+ }
+ if (pvp->tx_filter_data[data.fir_filter.channel] != NULL) {
+ error = cuse_copy_in(data.fir_filter.filter_data,
+ pvp->tx_filter_data[data.fir_filter.channel], size);
+ }
+ }
+ break;
+
+ case VIRTUAL_OSS_GET_SAMPLE_RATE:
+ data.val = voss_dsp_sample_rate;
+ break;
+
+ case VIRTUAL_OSS_GET_SYSTEM_INFO:
+ data.sys_info.tx_jitter_up = voss_jitter_up;
+ data.sys_info.tx_jitter_down = voss_jitter_down;
+ data.sys_info.sample_rate = voss_dsp_sample_rate;
+ data.sys_info.sample_bits = voss_dsp_bits;
+ data.sys_info.sample_channels = voss_mix_channels;
+ strlcpy(data.sys_info.rx_device_name, voss_dsp_rx_device,
+ sizeof(data.sys_info.rx_device_name));
+ strlcpy(data.sys_info.tx_device_name, voss_dsp_tx_device,
+ sizeof(data.sys_info.tx_device_name));
+ break;
+
+ default:
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ atomic_unlock();
+
+ if (error == 0) {
+ if (cmd & IOC_OUT)
+ error = cuse_copy_out(&data, peer_data, len);
+ }
+ return (error);
+}
+
+const struct cuse_methods vctl_methods = {
+ .cm_open = vctl_open,
+ .cm_close = vctl_close,
+ .cm_ioctl = vctl_ioctl,
+};
diff --git a/usr.sbin/virtual_oss/virtual_oss/eq.c b/usr.sbin/virtual_oss/virtual_oss/eq.c
new file mode 100644
index 000000000000..a02b48a9f039
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/eq.c
@@ -0,0 +1,226 @@
+/*-
+ * Copyright (c) 2021 Hans Petter Selasky
+ *
+ * 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/queue.h>
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "int.h"
+
+void
+vclient_tx_equalizer(struct virtual_client *pvc,
+ int64_t *src, size_t total)
+{
+ double *f_data;
+ size_t channels;
+ size_t f_size;
+ size_t x;
+
+ f_size = pvc->profile->tx_filter_size;
+ if (f_size == 0 || total == 0)
+ return;
+
+ channels = pvc->channels;
+ total /= channels;
+
+ while (1) {
+ size_t delta;
+ size_t offset;
+ size_t y;
+
+ offset = pvc->tx_filter_offset;
+ delta = f_size - offset;
+
+ if (delta > total)
+ delta = total;
+
+ for (x = 0; x != channels; x++) {
+ f_data = pvc->profile->tx_filter_data[x];
+ if (f_data == NULL)
+ continue;
+
+ for (y = 0; y != delta; y++) {
+ pvc->tx_filter_in[x][y + offset] = src[x + y * channels];
+ src[x + y * channels] = pvc->tx_filter_out[x][y + offset];
+ }
+ }
+
+ pvc->tx_filter_offset += delta;
+ total -= delta;
+ src += delta * channels;
+
+ /* check if there is enough data for a new transform */
+ if (pvc->tx_filter_offset == f_size) {
+ for (x = 0; x != channels; x++) {
+ f_data = pvc->profile->tx_filter_data[x];
+ if (f_data == NULL)
+ continue;
+
+ /* shift down output */
+ for (y = 0; y != f_size; y++) {
+ pvc->tx_filter_out[x][y] = pvc->tx_filter_out[x][y + f_size];
+ pvc->tx_filter_out[x][y + f_size] = 0;
+ }
+ /* perform transform */
+ voss_x3_multiply_double(pvc->tx_filter_in[x],
+ f_data, pvc->tx_filter_out[x], f_size);
+ }
+ pvc->tx_filter_offset = 0;
+ }
+ if (total == 0)
+ break;
+ }
+}
+
+void
+vclient_rx_equalizer(struct virtual_client *pvc,
+ int64_t *src, size_t total)
+{
+ double *f_data;
+ size_t channels;
+ size_t f_size;
+ size_t x;
+
+ f_size = pvc->profile->rx_filter_size;
+
+ if (f_size == 0 || total == 0)
+ return;
+
+ channels = pvc->channels;
+ total /= channels;
+
+ while (1) {
+ size_t delta;
+ size_t offset;
+ size_t y;
+
+ offset = pvc->rx_filter_offset;
+ delta = f_size - offset;
+
+ if (delta > total)
+ delta = total;
+
+ for (x = 0; x != channels; x++) {
+ f_data = pvc->profile->rx_filter_data[x];
+ if (f_data == NULL)
+ continue;
+
+ for (y = 0; y != delta; y++) {
+ pvc->rx_filter_in[x][y + offset] = src[x + y * channels];
+ src[x + y * channels] = pvc->rx_filter_out[x][y + offset];
+ }
+ }
+
+ pvc->rx_filter_offset += delta;
+ total -= delta;
+ src += delta * channels;
+
+ /* check if there is enough data for a new transform */
+ if (pvc->rx_filter_offset == f_size) {
+ for (x = 0; x != channels; x++) {
+ f_data = pvc->profile->rx_filter_data[x];
+ if (f_data == NULL)
+ continue;
+
+ /* shift output down */
+ for (y = 0; y != f_size; y++) {
+ pvc->rx_filter_out[x][y] = pvc->rx_filter_out[x][y + f_size];
+ pvc->rx_filter_out[x][y + f_size] = 0;
+ }
+ /* perform transform */
+ voss_x3_multiply_double(pvc->rx_filter_in[x],
+ f_data, pvc->rx_filter_out[x], f_size);
+ }
+ pvc->rx_filter_offset = 0;
+ }
+ if (total == 0)
+ break;
+ }
+}
+
+int
+vclient_eq_alloc(struct virtual_client *pvc)
+{
+ uint8_t x;
+
+ pvc->tx_filter_offset = 0;
+ pvc->rx_filter_offset = 0;
+
+ for (x = 0; x != pvc->channels; x++) {
+ uint32_t size;
+
+ size = pvc->profile->tx_filter_size;
+ if (size != 0) {
+ pvc->tx_filter_in[x] =
+ malloc(sizeof(pvc->tx_filter_in[x][0]) * size);
+ pvc->tx_filter_out[x] =
+ calloc(2 * size, sizeof(pvc->tx_filter_out[x][0]));
+ if (pvc->tx_filter_in[x] == NULL ||
+ pvc->tx_filter_out[x] == NULL)
+ goto error;
+ }
+ size = pvc->profile->rx_filter_size;
+ if (size != 0) {
+ pvc->rx_filter_in[x] =
+ malloc(sizeof(pvc->rx_filter_in[x][0]) * size);
+ pvc->rx_filter_out[x] =
+ calloc(2 * size, sizeof(pvc->rx_filter_out[x][0]));
+ if (pvc->rx_filter_in[x] == NULL ||
+ pvc->rx_filter_out[x] == NULL)
+ goto error;
+ }
+ }
+ return (0);
+
+error:
+ vclient_eq_free(pvc);
+ return (ENOMEM);
+}
+
+void
+vclient_eq_free(struct virtual_client *pvc)
+{
+ uint8_t x;
+
+ pvc->tx_filter_offset = 0;
+ pvc->rx_filter_offset = 0;
+
+ for (x = 0; x != VMAX_CHAN; x++) {
+ free(pvc->tx_filter_in[x]);
+ pvc->tx_filter_in[x] = NULL;
+
+ free(pvc->rx_filter_in[x]);
+ pvc->rx_filter_in[x] = NULL;
+
+ free(pvc->tx_filter_out[x]);
+ pvc->tx_filter_out[x] = NULL;
+
+ free(pvc->rx_filter_out[x]);
+ pvc->rx_filter_out[x] = NULL;
+ }
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/format.c b/usr.sbin/virtual_oss/virtual_oss/format.c
new file mode 100644
index 000000000000..d32d0c726510
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/format.c
@@ -0,0 +1,429 @@
+/*-
+ * Copyright (c) 2012-2020 Hans Petter Selasky
+ *
+ * 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/queue.h>
+#include <sys/soundcard.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include "int.h"
+
+void
+format_import(uint32_t fmt, const uint8_t *src, uint32_t len,
+ int64_t *dst)
+{
+ const uint8_t *end = src + len;
+ int64_t val;
+
+ if (fmt & AFMT_16BIT) {
+ while (src != end) {
+ if (fmt & (AFMT_S16_LE | AFMT_U16_LE))
+ val = src[0] | (src[1] << 8);
+ else
+ val = src[1] | (src[0] << 8);
+
+ src += 2;
+
+ if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
+ val = val ^ 0x8000;
+
+ val <<= (64 - 16);
+ val >>= (64 - 16);
+
+ *dst++ = val;
+ }
+
+ } else if (fmt & AFMT_24BIT) {
+ while (src < end) {
+ if (fmt & (AFMT_S24_LE | AFMT_U24_LE))
+ val = src[0] | (src[1] << 8) | (src[2] << 16);
+ else
+ val = src[2] | (src[1] << 8) | (src[0] << 16);
+
+ src += 3;
+
+ if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
+ val = val ^ 0x800000;
+
+ val <<= (64 - 24);
+ val >>= (64 - 24);
+
+ *dst++ = val;
+ }
+ } else if (fmt & AFMT_32BIT) {
+ while (src < end) {
+ int64_t e, m, s;
+
+ if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE))
+ val = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+ else
+ val = src[3] | (src[2] << 8) | (src[1] << 16) | (src[0] << 24);
+
+ src += 4;
+
+ if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
+ val = val ^ 0x80000000LL;
+
+ if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
+ e = (val >> 23) & 0xff;
+ /* NaN, +/- Inf or too small */
+ if (e == 0xff || e < 96) {
+ val = 0;
+ goto skip;
+ }
+ s = val & 0x80000000U;
+ if (e > 126) {
+ val = s == 0 ? format_max(fmt) :
+ -0x80000000LL;
+ goto skip;
+ }
+ m = 0x800000 | (val & 0x7fffff);
+ e += 8 - 127;
+ if (e < 0)
+ m >>= -e;
+ else
+ m <<= e;
+ val = s == 0 ? m : -m;
+ }
+skip:
+ val <<= (64 - 32);
+ val >>= (64 - 32);
+
+ *dst++ = val;
+ }
+
+ } else if (fmt & AFMT_8BIT) {
+ while (src < end) {
+ val = src[0];
+
+ src += 1;
+
+ if (fmt & AFMT_U8)
+ val = val ^ 0x80;
+
+ val <<= (64 - 8);
+ val >>= (64 - 8);
+
+ *dst++ = val;
+ }
+ }
+}
+
+void
+format_export(uint32_t fmt, const int64_t *src, uint8_t *dst, uint32_t len)
+{
+ const uint8_t *end = dst + len;
+ int64_t val;
+
+ if (fmt & AFMT_16BIT) {
+ while (dst != end) {
+
+ val = *src++;
+
+ if (val > 0x7FFF)
+ val = 0x7FFF;
+ else if (val < -0x7FFF)
+ val = -0x7FFF;
+
+ if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
+ val = val ^ 0x8000;
+
+ if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ } else {
+ dst[1] = val;
+ dst[0] = val >> 8;
+ }
+
+ dst += 2;
+ }
+
+ } else if (fmt & AFMT_24BIT) {
+ while (dst != end) {
+
+ val = *src++;
+
+ if (val > 0x7FFFFF)
+ val = 0x7FFFFF;
+ else if (val < -0x7FFFFF)
+ val = -0x7FFFFF;
+
+ if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
+ val = val ^ 0x800000;
+
+ if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ dst[2] = val >> 16;
+ } else {
+ dst[2] = val;
+ dst[1] = val >> 8;
+ dst[0] = val >> 16;
+ }
+
+ dst += 3;
+ }
+ } else if (fmt & AFMT_32BIT) {
+ while (dst != end) {
+ int64_t r, e;
+
+ val = *src++;
+
+ if (val > 0x7FFFFFFFLL)
+ val = 0x7FFFFFFFLL;
+ else if (val < -0x7FFFFFFFLL)
+ val = -0x7FFFFFFFLL;
+
+ if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
+ if (val == 0)
+ r = 0;
+ else if (val == format_max(fmt))
+ r = 0x3f800000;
+ else if (val == -0x80000000LL)
+ r = 0x80000000U | 0x3f800000;
+ else {
+ r = 0;
+ if (val < 0) {
+ r |= 0x80000000U;
+ val = -val;
+ }
+ e = 127 - 8;
+ while ((val & 0x7f000000) != 0) {
+ val >>= 1;
+ e++;
+ }
+ while ((val & 0x7f800000) == 0) {
+ val <<= 1;
+ e--;
+ }
+ r |= (e & 0xff) << 23;
+ r |= val & 0x7fffff;
+ }
+ val = r;
+ }
+
+ if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
+ val = val ^ 0x80000000LL;
+
+ if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ dst[2] = val >> 16;
+ dst[3] = val >> 24;
+ } else {
+ dst[3] = val;
+ dst[2] = val >> 8;
+ dst[1] = val >> 16;
+ dst[0] = val >> 24;
+ }
+
+ dst += 4;
+ }
+
+ } else if (fmt & AFMT_8BIT) {
+ while (dst != end) {
+
+ val = *src++;
+
+ if (val > 0x7F)
+ val = 0x7F;
+ else if (val < -0x7F)
+ val = -0x7F;
+
+ if (fmt & (AFMT_U8))
+ val = val ^ 0x80;
+
+ dst[0] = val;
+
+ dst += 1;
+ }
+ }
+}
+
+int64_t
+format_max(uint32_t fmt)
+{
+ if (fmt & AFMT_16BIT)
+ return (0x7FFF);
+ else if (fmt & AFMT_24BIT)
+ return (0x7FFFFF);
+ else if (fmt & AFMT_32BIT)
+ return (0x7FFFFFFF);
+ else if (fmt & AFMT_8BIT)
+ return (0x7F);
+ return (0);
+}
+
+void
+format_maximum(const int64_t *src, int64_t *dst, uint32_t ch,
+ uint32_t samples, int8_t shift)
+{
+ const int64_t *end = src + (samples * ch);
+ int64_t max[ch];
+ int64_t temp;
+ uint32_t x;
+
+ memset(max, 0, sizeof(max));
+
+ while (src != end) {
+ for (x = 0; x != ch; x++) {
+ temp = *src++;
+ if (temp < 0)
+ temp = -temp;
+ if (temp > max[x])
+ max[x] = temp;
+ }
+ }
+
+ for (x = 0; x != ch; x++) {
+ if (shift < 0)
+ max[x] >>= -shift;
+ else
+ max[x] <<= shift;
+ if (dst[x] < max[x])
+ dst[x] = max[x];
+ }
+}
+
+void
+format_remix(int64_t *buffer_data, uint32_t in_chans,
+ uint32_t out_chans, uint32_t samples)
+{
+ uint32_t x;
+
+ if (out_chans > in_chans) {
+ uint32_t dst = out_chans * (samples - 1);
+ uint32_t src = in_chans * (samples - 1);
+ uint32_t fill = out_chans - in_chans;
+
+ for (x = 0; x != samples; x++) {
+ memset(buffer_data + dst + in_chans, 0, 8 * fill);
+ if (src != dst) {
+ memcpy(buffer_data + dst,
+ buffer_data + src,
+ in_chans * 8);
+ }
+ dst -= out_chans;
+ src -= in_chans;
+ }
+ } else if (out_chans < in_chans) {
+ uint32_t dst = 0;
+ uint32_t src = 0;
+
+ for (x = 0; x != samples; x++) {
+ if (src != dst) {
+ memcpy(buffer_data + dst,
+ buffer_data + src,
+ out_chans * 8);
+ }
+ dst += out_chans;
+ src += in_chans;
+ }
+ }
+}
+
+void
+format_silence(uint32_t fmt, uint8_t *dst, uint32_t len)
+{
+ const uint8_t *end = dst + len;
+
+ if (fmt & AFMT_16BIT) {
+ uint16_t val;
+
+ if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
+ val = 1U << 15;
+ else
+ val = 0;
+
+ while (dst != end) {
+ if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ } else {
+ dst[1] = val;
+ dst[0] = val >> 8;
+ }
+ dst += 2;
+ }
+
+ } else if (fmt & AFMT_24BIT) {
+ uint32_t val;
+
+ if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
+ val = 1U << 23;
+ else
+ val = 0;
+
+ while (dst != end) {
+ if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ dst[2] = val >> 16;
+ } else {
+ dst[2] = val;
+ dst[1] = val >> 8;
+ dst[0] = val >> 16;
+ }
+ dst += 3;
+ }
+ } else if (fmt & AFMT_32BIT) {
+ uint32_t val;
+
+ if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
+ val = 1U << 31;
+ else
+ val = 0;
+
+ while (dst != end) {
+ if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
+ dst[0] = val;
+ dst[1] = val >> 8;
+ dst[2] = val >> 16;
+ dst[3] = val >> 24;
+ } else {
+ dst[3] = val;
+ dst[2] = val >> 8;
+ dst[1] = val >> 16;
+ dst[0] = val >> 24;
+ }
+ dst += 4;
+ }
+
+ } else if (fmt & AFMT_8BIT) {
+ uint8_t val;
+
+ if (fmt & AFMT_U8)
+ val = 1U << 7;
+ else
+ val = 0;
+
+ while (dst != end) {
+ dst[0] = val;
+ dst += 1;
+ }
+ }
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/httpd.c b/usr.sbin/virtual_oss/virtual_oss/httpd.c
new file mode 100644
index 000000000000..dc5d6036f39d
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/httpd.c
@@ -0,0 +1,846 @@
+/*-
+ * Copyright (c) 2020 Hans Petter Selasky
+ *
+ * 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/types.h>
+#include <sys/queue.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/endian.h>
+#include <sys/uio.h>
+#include <sys/soundcard.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <sysexits.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <net/if.h>
+#include <net/if_vlan_var.h>
+#include <net/bpf.h>
+
+#include <arpa/inet.h>
+
+#include <pthread.h>
+
+#include "int.h"
+
+#define VOSS_HTTPD_BIND_MAX 8
+#define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */
+
+struct http_state {
+ int fd;
+ uint64_t ts;
+};
+
+struct rtp_raw_packet {
+ struct {
+ uint32_t padding;
+ uint8_t dhost[6];
+ uint8_t shost[6];
+ uint16_t ether_type;
+ } __packed eth;
+ struct {
+ uint8_t hl_ver;
+ uint8_t tos;
+ uint16_t len;
+ uint16_t ident;
+ uint16_t offset;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t chksum;
+ union {
+ uint32_t sourceip;
+ uint16_t source16[2];
+ };
+ union {
+ uint32_t destip;
+ uint16_t dest16[2];
+ };
+ } __packed ip;
+ struct {
+ uint16_t srcport;
+ uint16_t dstport;
+ uint16_t len;
+ uint16_t chksum;
+ } __packed udp;
+ union {
+ uint8_t header8[12];
+ uint16_t header16[6];
+ uint32_t header32[3];
+ } __packed rtp;
+
+} __packed;
+
+static const char *
+voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd)
+{
+ const char *perr = NULL;
+ struct vlanreq vr = {};
+ struct ifreq ifr = {};
+ int fd;
+
+ fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ perr = "Cannot open raw RTP socket";
+ goto done;
+ }
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (void *)&vr;
+
+ if (ioctl(fd, SIOCGETVLAN, &ifr) == 0)
+ pvc->profile->http.rtp_vlanid = vr.vlr_tag;
+ else
+ pvc->profile->http.rtp_vlanid = 0;
+
+ close(fd);
+
+ ifr.ifr_data = NULL;
+
+ *pfd = fd = open("/dev/bpf", O_RDWR);
+ if (fd < 0) {
+ perr = "Cannot open BPF device";
+ goto done;
+ }
+
+ if (ioctl(fd, BIOCSETIF, &ifr) != 0) {
+ perr = "Cannot bind BPF device to network interface";
+ goto done;
+ }
+done:
+ if (perr != NULL && fd > -1)
+ close(fd);
+ return (perr);
+}
+
+static uint16_t
+voss_ipv4_csum(const void *vptr, size_t count)
+{
+ const uint16_t *ptr = vptr;
+ uint32_t sum = 0;
+
+ while (count--)
+ sum += *ptr++;
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (~sum);
+}
+
+static uint16_t
+voss_udp_csum(uint32_t sum, const void *vhdr, size_t count,
+ const uint16_t *ptr, size_t length)
+{
+ const uint16_t *hdr = vhdr;
+
+ while (count--)
+ sum += *hdr++;
+
+ while (length > 1) {
+ sum += *ptr++;
+ length -= 2;
+ }
+
+ if (length & 1)
+ sum += *__DECONST(uint8_t *, ptr);
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (~sum);
+}
+
+static void
+voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
+{
+ struct rtp_raw_packet pkt = {};
+ struct iovec iov[2];
+ size_t total_ip;
+ uint16_t port = atoi(pvc->profile->http.rtp_port);
+ size_t x;
+
+ /* NOTE: BPF filter will insert VLAN header for us */
+ memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost));
+ memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost));
+ pkt.eth.ether_type = htobe16(0x0800);
+ total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len;
+
+ iov[0].iov_base = pkt.eth.dhost;
+ iov[0].iov_len = 14 + total_ip - len;
+
+ iov[1].iov_base = alloca(len);
+ iov[1].iov_len = len;
+
+ /* byte swap data - WAV files are 16-bit little endian */
+ for (x = 0; x != (len / 2); x++)
+ ((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]);
+
+ pkt.ip.hl_ver = 0x45;
+ pkt.ip.len = htobe16(total_ip);
+ pkt.ip.ttl = 8;
+ pkt.ip.protocol = 17; /* UDP */
+ pkt.ip.sourceip = 0x01010101U;
+ pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0));
+ pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2);
+
+ pkt.udp.srcport = htobe16(port);
+ pkt.udp.dstport = htobe16(port);
+ pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip));
+
+ pkt.rtp.header8[0] = (2 << 6);
+ pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80;
+
+ pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum);
+ pkt.rtp.header32[1] = htobe32(ts);
+ pkt.rtp.header32[2] = htobe32(0);
+
+ pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] +
+ pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len,
+ (void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2,
+ iov[1].iov_base, iov[1].iov_len);
+
+ pvc->profile->http.rtp_seqnum++;
+ pvc->profile->http.rtp_ts += len / (2 * pvc->channels);
+
+ (void)writev(fd, iov, 2);
+}
+
+static void
+voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
+{
+ const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc);
+ const uint32_t max = 1420 - (1420 % mod);
+
+ while (len >= max) {
+ voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts);
+ len -= max;
+ ptr = (uint8_t *)ptr + max;
+ }
+
+ if (len != 0)
+ voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts);
+}
+
+static size_t
+voss_httpd_usage(vclient_t *pvc)
+{
+ size_t usage = 0;
+ size_t x;
+
+ for (x = 0; x < pvc->profile->http.nstate; x++)
+ usage += (pvc->profile->http.state[x].fd != -1);
+ return (usage);
+}
+
+static char *
+voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen)
+{
+ char buffer[2];
+ size_t size = 0;
+
+ if (fread(buffer, 1, 2, io) != 2)
+ return (NULL);
+
+ while (1) {
+ if (buffer[0] == '\r' && buffer[1] == '\n')
+ break;
+ if (size == (linelen - 1))
+ return (NULL);
+ linebuffer[size++] = buffer[0];
+ buffer[0] = buffer[1];
+ if (fread(buffer + 1, 1, 1, io) != 1)
+ return (NULL);
+ }
+ linebuffer[size++] = 0;
+
+ return (linebuffer);
+}
+
+static int
+voss_http_generate_wav_header(vclient_t *pvc, FILE *io,
+ uintmax_t r_start, uintmax_t r_end, bool is_partial)
+{
+ uint8_t buffer[256];
+ uint8_t *ptr;
+ uintmax_t dummy_len;
+ uintmax_t delta;
+ size_t mod;
+ size_t len;
+ size_t buflen;
+
+ ptr = buffer;
+ mod = pvc->channels * vclient_sample_bytes(pvc);
+
+ if (mod == 0 || sizeof(buffer) < (44 + mod - 1))
+ return (-1);
+
+ /* align to next sample */
+ len = 44 + mod - 1;
+ len -= len % mod;
+
+ buflen = len;
+
+ /* clear block */
+ memset(ptr, 0, len);
+
+ /* fill out data header */
+ ptr[len - 8] = 'd';
+ ptr[len - 7] = 'a';
+ ptr[len - 6] = 't';
+ ptr[len - 5] = 'a';
+
+ /* magic for unspecified length */
+ ptr[len - 4] = 0x00;
+ ptr[len - 3] = 0xF0;
+ ptr[len - 2] = 0xFF;
+ ptr[len - 1] = 0x7F;
+
+ /* fill out header */
+ *ptr++ = 'R';
+ *ptr++ = 'I';
+ *ptr++ = 'F';
+ *ptr++ = 'F';
+
+ /* total chunk size - unknown */
+
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+
+ *ptr++ = 'W';
+ *ptr++ = 'A';
+ *ptr++ = 'V';
+ *ptr++ = 'E';
+ *ptr++ = 'f';
+ *ptr++ = 'm';
+ *ptr++ = 't';
+ *ptr++ = ' ';
+
+ /* make sure header fits in PCM block */
+ len -= 28;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* audioformat = PCM */
+
+ *ptr++ = 0x01;
+ *ptr++ = 0x00;
+
+ /* number of channels */
+
+ len = pvc->channels;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* sample rate */
+
+ len = pvc->sample_rate;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* byte rate */
+
+ len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* block align */
+
+ len = pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* bits per sample */
+
+ len = vclient_sample_bytes(pvc) * 8;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* check if alignment is correct */
+ if (r_start >= buflen && (r_start % mod) != 0)
+ return (2);
+
+ dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
+ dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME;
+
+ /* fixup end */
+ if (r_end >= dummy_len)
+ r_end = dummy_len - 1;
+
+ delta = r_end - r_start + 1;
+
+ if (is_partial) {
+ fprintf(io, "HTTP/1.1 206 Partial Content\r\n"
+ "Content-Type: audio/wav\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "Connection: Close\r\n"
+ "Content-Range: bytes %ju-%ju/%ju\r\n"
+ "Content-Length: %ju\r\n"
+ "\r\n", r_start, r_end, dummy_len, delta);
+ } else {
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: audio/wav\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "Connection: Close\r\n"
+ "Content-Length: %ju\r\n"
+ "\r\n", dummy_len);
+ }
+
+ /* check if we should insert a header */
+ if (r_start < buflen) {
+ buflen -= r_start;
+ if (buflen > delta)
+ buflen = delta;
+ /* send data */
+ if (fwrite(buffer + r_start, buflen, 1, io) != 1)
+ return (-1);
+ /* check if all data was read */
+ if (buflen == delta)
+ return (1);
+ }
+ return (0);
+}
+
+static void
+voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa)
+{
+ char linebuffer[2048];
+ uintmax_t r_start = 0;
+ uintmax_t r_end = -1ULL;
+ bool is_partial = false;
+ char *line;
+ FILE *io;
+ size_t x;
+ int page;
+
+ io = fdopen(fd, "r+");
+ if (io == NULL)
+ goto done;
+
+ page = -1;
+
+ /* dump HTTP request header */
+ while (1) {
+ line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer));
+ if (line == NULL)
+ goto done;
+ if (line[0] == 0)
+ break;
+ if (page < 0 && (strstr(line, "GET / ") == line ||
+ strstr(line, "GET /index.html") == line)) {
+ page = 0;
+ } else if (page < 0 && strstr(line, "GET /stream.wav") == line) {
+ page = 1;
+ } else if (page < 0 && strstr(line, "GET /stream.m3u") == line) {
+ page = 2;
+ } else if (strstr(line, "Range: bytes=") == line &&
+ sscanf(line, "Range: bytes=%ju-%ju", &r_start, &r_end) >= 1) {
+ is_partial = true;
+ }
+ }
+
+ switch (page) {
+ case 0:
+ x = voss_httpd_usage(pvc);
+
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "\r\n"
+ "<html><head><title>Welcome to live streaming</title>"
+ "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />"
+ "<meta http-equiv=\"Pragma\" content=\"no-cache\" />"
+ "<meta http-equiv=\"Expires\" content=\"0\" />"
+ "</head>"
+ "<body>"
+ "<h1>Live HD stream</h1>"
+ "<br>"
+ "<br>"
+ "<h2>Alternative 1 (recommended)</h2>"
+ "<ol type=\"1\">"
+ "<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>"
+ "<li>Open VLC and select Network Stream</li>"
+ "<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>"
+ "</ol>"
+ "<br>"
+ "<br>"
+ "<h2>Alternative 2 (on your own)</h2>"
+ "<br>"
+ "<br>"
+ "<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>"
+ "<br>"
+ "<br>",
+ pvc->profile->http.host, pvc->profile->http.port,
+ pvc->profile->http.host, pvc->profile->http.port);
+
+ if (x == pvc->profile->http.nstate)
+ fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x);
+ else
+ fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x);
+
+ fprintf(io, "</body></html>");
+ break;
+ case 1:
+ for (x = 0; x < pvc->profile->http.nstate; x++) {
+ if (pvc->profile->http.state[x].fd >= 0)
+ continue;
+ switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) {
+ static const int enable = 1;
+
+ case 0:
+ fflush(io);
+ fdclose(io, NULL);
+ if (ioctl(fd, FIONBIO, &enable) != 0) {
+ close(fd);
+ return;
+ }
+ pvc->profile->http.state[x].ts =
+ virtual_oss_timestamp() - 1000000000ULL;
+ pvc->profile->http.state[x].fd = fd;
+ return;
+ case 1:
+ fclose(io);
+ return;
+ case 2:
+ fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n");
+ goto done;
+ default:
+ goto done;
+ }
+ }
+ fprintf(io, "HTTP/1.0 503 Out of Resources\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n");
+ break;
+ case 2:
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: audio/mpegurl\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "\r\n");
+ if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) {
+ fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port);
+ } else {
+ fprintf(io, "http://%s:%s/stream.wav\r\n",
+ pvc->profile->http.host, pvc->profile->http.port);
+ }
+ break;
+ default:
+ fprintf(io, "HTTP/1.0 404 Not Found\r\n"
+ "Content-Type: text/html\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n"
+ "<html><head><title>virtual_oss</title></head>"
+ "<body>"
+ "<h1>Invalid page requested! "
+ "<a HREF=\"index.html\">Click here to go back</a>.</h1><br>"
+ "</body>"
+ "</html>");
+ break;
+ }
+done:
+ if (io != NULL)
+ fclose(io);
+ else
+ close(fd);
+}
+
+static int
+voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port,
+ struct pollfd *pfd, int num_sock, int buffer)
+{
+ static const struct timeval timeout = {.tv_sec = 1};
+ struct addrinfo hints = {};
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int error;
+ int flag;
+ int s;
+ int ns = 0;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_PASSIVE;
+
+ if ((error = getaddrinfo(host, port, &hints, &res)))
+ return (-1);
+
+ res0 = res;
+
+ do {
+ if ((s = socket(res0->ai_family, res0->ai_socktype,
+ res0->ai_protocol)) < 0)
+ continue;
+
+ flag = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag));
+ setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer));
+ setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer));
+ setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout));
+ setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout));
+
+ if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) {
+ if (listen(s, pvc->profile->http.nstate) == 0) {
+ if (ns < num_sock) {
+ pfd[ns++].fd = s;
+ continue;
+ }
+ close(s);
+ break;
+ }
+ }
+ close(s);
+ } while ((res0 = res0->ai_next) != NULL);
+
+ freeaddrinfo(res);
+
+ return (ns);
+}
+
+static size_t
+voss_httpd_buflimit(vclient_t *pvc)
+{
+ /* don't buffer more than 250ms */
+ return ((pvc->sample_rate / 4) *
+ pvc->channels * vclient_sample_bytes(pvc));
+};
+
+static void
+voss_httpd_server(vclient_t *pvc)
+{
+ const size_t bufferlimit = voss_httpd_buflimit(pvc);
+ const char *host = pvc->profile->http.host;
+ const char *port = pvc->profile->http.port;
+ struct sockaddr sa = {};
+ struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {};
+ int nfd;
+
+ nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit);
+ if (nfd < 1) {
+ errx(EX_SOFTWARE, "Could not bind to "
+ "'%s' and '%s'", host, port);
+ }
+
+ while (1) {
+ struct sockaddr_in si;
+ int ns = nfd;
+ int c;
+ int f;
+
+ for (c = 0; c != ns; c++) {
+ fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI |
+ POLLERR | POLLHUP | POLLNVAL);
+ fds[c].revents = 0;
+ }
+ if (poll(fds, ns, -1) < 0)
+ errx(EX_SOFTWARE, "Polling failed");
+
+ for (c = 0; c != ns; c++) {
+ socklen_t socklen = sizeof(sa);
+
+ if (fds[c].revents == 0)
+ continue;
+ f = accept(fds[c].fd, &sa, &socklen);
+ if (f < 0)
+ continue;
+ memcpy(&si, &sa, sizeof(sa));
+ voss_httpd_handle_connection(pvc, f, &si);
+ }
+ }
+}
+
+static void
+voss_httpd_streamer(vclient_t *pvc)
+{
+ const size_t bufferlimit = voss_httpd_buflimit(pvc);
+ uint8_t *ptr;
+ size_t len;
+ uint64_t ts;
+ size_t x;
+
+ atomic_lock();
+ while (1) {
+ if (vclient_export_read_locked(pvc) != 0) {
+ atomic_wait();
+ continue;
+ }
+ vring_get_read(&pvc->rx_ring[1], &ptr, &len);
+ if (len == 0) {
+ /* try to avoid ring wraps */
+ vring_reset(&pvc->rx_ring[1]);
+ atomic_wait();
+ continue;
+ }
+ atomic_unlock();
+
+ ts = virtual_oss_timestamp();
+
+ /* check if we should send RTP data, if any */
+ if (pvc->profile->http.rtp_fd > -1) {
+ voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd,
+ ptr, len, pvc->profile->http.rtp_ts);
+ }
+
+ /* send HTTP data, if any */
+ for (x = 0; x < pvc->profile->http.nstate; x++) {
+ int fd = pvc->profile->http.state[x].fd;
+ uint64_t delta = ts - pvc->profile->http.state[x].ts;
+ uint8_t buf[1];
+ int write_len;
+
+ if (fd < 0) {
+ /* do nothing */
+ } else if (delta >= (8ULL * 1000000000ULL)) {
+ /* no data for 8 seconds - terminate */
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if (ioctl(fd, FIONWRITE, &write_len) < 0) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) {
+ /* do nothing */
+ } else if (write(fd, ptr, len) != (ssize_t)len) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else {
+ /* update timestamp */
+ pvc->profile->http.state[x].ts = ts;
+ }
+ }
+
+ atomic_lock();
+ vring_inc_read(&pvc->rx_ring[1], len);
+ }
+}
+
+const char *
+voss_httpd_start(vprofile_t *pvp)
+{
+ vclient_t *pvc;
+ pthread_t td;
+ int error;
+ size_t x;
+
+ if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0)
+ return (NULL);
+
+ pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate);
+ if (pvp->http.state == NULL)
+ return ("Could not allocate HTTP states");
+
+ for (x = 0; x != pvp->http.nstate; x++) {
+ pvp->http.state[x].fd = -1;
+ pvp->http.state[x].ts = 0;
+ }
+
+ pvc = vclient_alloc();
+ if (pvc == NULL)
+ return ("Could not allocate client for HTTP server");
+
+ pvc->profile = pvp;
+
+ if (pvp->http.rtp_ifname != NULL) {
+ const char *perr;
+
+ if (pvc->channels > 2)
+ return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth");
+
+ /* bind to UDP port */
+ perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname,
+ &pvp->http.rtp_fd);
+ if (perr != NULL)
+ return (perr);
+
+ /* setup buffers */
+ error = vclient_setup_buffers(pvc, 0, 0,
+ pvp->channels, AFMT_S16_LE, 44100);
+ } else {
+ pvp->http.rtp_fd = -1;
+
+ /* setup buffers */
+ error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
+ vclient_get_default_fmt(pvp, VTYPE_WAV_HDR),
+ voss_dsp_sample_rate);
+ }
+
+ if (error != 0) {
+ vclient_free(pvc);
+ return ("Could not allocate buffers for HTTP server");
+ }
+
+ /* trigger enabled */
+ pvc->rx_enabled = 1;
+
+ pvc->type = VTYPE_OSS_DAT;
+
+ atomic_lock();
+ TAILQ_INSERT_TAIL(&pvp->head, pvc, entry);
+ atomic_unlock();
+
+ if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc))
+ return ("Could not create HTTP daemon thread");
+ if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc))
+ return ("Could not create HTTP streamer thread");
+
+ return (NULL);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/int.h b/usr.sbin/virtual_oss/virtual_oss/int.h
new file mode 100644
index 000000000000..b3cc573ba8a9
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/int.h
@@ -0,0 +1,331 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifndef _VIRTUAL_INT_H_
+#define _VIRTUAL_INT_H_
+
+#include <signal.h>
+#include <pthread.h>
+
+#include <cuse.h>
+#include <samplerate.h>
+
+extern pthread_mutex_t atomic_mtx;
+extern pthread_cond_t atomic_cv;
+
+#define atomic_lock() pthread_mutex_lock(&atomic_mtx)
+#define atomic_unlock() pthread_mutex_unlock(&atomic_mtx)
+#define atomic_wait() pthread_cond_wait(&atomic_cv, &atomic_mtx)
+#define atomic_wakeup() do { \
+ pthread_cond_broadcast(&atomic_cv); \
+ cuse_poll_wakeup(); \
+} while (0)
+
+#define AFMT_32BIT \
+ (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \
+ AFMT_F32_LE | AFMT_F32_BE)
+#define AFMT_24BIT \
+ (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE)
+#define AFMT_16BIT \
+ (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)
+#define AFMT_8BIT \
+ (AFMT_U8 | AFMT_S8)
+
+#define VMAX_CHAN 64
+/*
+ * XXX 32 - strlen("/dev") to not exceed OSS_DEVNODE_SIZE in soundcard.h. Also
+ * silences GCC warnings.
+ */
+#define VMAX_STRING 27
+
+#define VTYPE_OSS_DAT 0
+#define VTYPE_WAV_HDR 1
+#define VTYPE_WAV_DAT 2
+
+#define VPREFERRED_SNE_AFMT \
+ (AFMT_S8 | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE)
+#define VPREFERRED_UNE_AFMT \
+ (AFMT_U8 | AFMT_U16_NE | AFMT_U24_NE | AFMT_U32_NE)
+#define VPREFERRED_SLE_AFMT \
+ (AFMT_S8 | AFMT_S16_LE | AFMT_S24_LE | AFMT_S32_LE)
+#define VPREFERRED_SBE_AFMT \
+ (AFMT_S8 | AFMT_S16_BE | AFMT_S24_BE | AFMT_S32_BE)
+#define VPREFERRED_ULE_AFMT \
+ (AFMT_U8 | AFMT_U16_LE | AFMT_U24_LE | AFMT_U32_LE)
+#define VPREFERRED_UBE_AFMT \
+ (AFMT_U8 | AFMT_U16_BE | AFMT_U24_BE | AFMT_U32_BE)
+
+#define VSUPPORTED_AFMT \
+ (AFMT_S16_BE | AFMT_S16_LE | AFMT_U16_BE | AFMT_U16_LE | \
+ AFMT_S24_BE | AFMT_S24_LE | AFMT_U24_BE | AFMT_U24_LE | \
+ AFMT_S32_BE | AFMT_S32_LE | AFMT_U32_BE | AFMT_U32_LE | \
+ AFMT_F32_BE | AFMT_F32_LE | \
+ AFMT_U8 | AFMT_S8)
+
+#define VVOLUME_UNIT_SHIFT 7
+
+struct virtual_profile;
+
+typedef TAILQ_ENTRY(virtual_profile) vprofile_entry_t;
+typedef TAILQ_HEAD(, virtual_profile) vprofile_head_t;
+typedef struct virtual_profile vprofile_t;
+
+struct virtual_client;
+
+typedef TAILQ_ENTRY(virtual_client) vclient_entry_t;
+typedef TAILQ_HEAD(, virtual_client) vclient_head_t;
+typedef struct virtual_client vclient_t;
+
+struct virtual_monitor;
+typedef TAILQ_ENTRY(virtual_monitor) vmonitor_entry_t;
+typedef TAILQ_HEAD(, virtual_monitor) vmonitor_head_t;
+typedef struct virtual_monitor vmonitor_t;
+
+struct virtual_resample;
+typedef struct virtual_resample vresample_t;
+
+struct cuse_methods;
+
+struct virtual_compressor {
+ uint8_t enabled; /* 0..1 */
+ uint8_t knee; /* 0..255 */
+ uint8_t attack; /* 0..62 */
+ uint8_t decay; /* 0..62 */
+};
+
+struct virtual_profile {
+ vprofile_entry_t entry;
+ vclient_head_t head;
+ char oss_name[VMAX_STRING];
+ char wav_name[VMAX_STRING];
+ uint32_t rx_filter_size;
+ uint32_t tx_filter_size;
+ double *rx_filter_data[VMAX_CHAN];
+ double *tx_filter_data[VMAX_CHAN];
+ int64_t rx_peak_value[VMAX_CHAN];
+ int64_t tx_peak_value[VMAX_CHAN];
+ int8_t rx_shift[VMAX_CHAN];
+ int8_t tx_shift[VMAX_CHAN];
+ uint8_t rx_src[VMAX_CHAN];
+ uint8_t tx_dst[VMAX_CHAN];
+ uint8_t rx_mute[VMAX_CHAN];
+ uint8_t tx_mute[VMAX_CHAN];
+ uint8_t rx_pol[VMAX_CHAN];
+ uint8_t tx_pol[VMAX_CHAN];
+ uint8_t bits;
+ uint8_t channels;
+ struct virtual_compressor rx_compressor_param;
+ double rx_compressor_gain[VMAX_CHAN];
+ uint8_t synchronized;
+ uint32_t rec_delay;
+ int fd_sta;
+ struct {
+ const char * host;
+ const char * port;
+ const char * rtp_ifname;
+ const char * rtp_port;
+ volatile struct http_state * state;
+ size_t nstate;
+ int rtp_fd;
+ int rtp_vlanid;
+ uint32_t rtp_ts;
+ uint16_t rtp_seqnum;
+ } http;
+};
+
+struct virtual_ring {
+ uint8_t *buf_start;
+ uint32_t pos_read;
+ uint32_t total_size;
+ uint32_t len_write;
+};
+
+struct virtual_resample {
+ SRC_DATA data;
+ SRC_STATE *state;
+ float *data_in;
+ float *data_out;
+};
+
+struct virtual_client {
+ vclient_entry_t entry;
+ uint32_t tx_filter_offset;
+ uint32_t rx_filter_offset;
+ int64_t *tx_filter_in[VMAX_CHAN];
+ int64_t *rx_filter_in[VMAX_CHAN];
+ double *tx_filter_out[VMAX_CHAN];
+ double *rx_filter_out[VMAX_CHAN];
+ struct virtual_ring rx_ring[2];
+ struct virtual_ring tx_ring[2];
+ vresample_t rx_resample;
+ vresample_t tx_resample;
+ struct virtual_profile *profile;
+ uint64_t rx_samples;
+ uint64_t rx_timestamp;
+ uint64_t tx_samples;
+ uint64_t tx_timestamp;
+ uint32_t buffer_frags;
+ uint32_t buffer_size;
+ uint32_t low_water;
+ uint32_t rec_delay;
+ uint32_t rx_noise_rem;
+ uint32_t tx_noise_rem;
+ int rx_busy;
+ int tx_busy;
+ int channels;
+ int format;
+ int rx_enabled;
+ int tx_enabled;
+ int rx_volume;
+ int tx_volume;
+ int type; /* VTYPE_XXX */
+ int sample_rate;
+ uint32_t buffer_size_set:1;
+ uint32_t buffer_frags_set:1;
+ uint32_t sync_busy:1;
+ uint32_t sync_wakeup:1;
+ int padding:28;
+};
+
+struct virtual_monitor {
+ vmonitor_entry_t entry;
+ int64_t peak_value;
+ uint8_t src_chan;
+ uint8_t dst_chan;
+ uint8_t pol;
+ uint8_t mute;
+ int8_t shift;
+};
+
+extern vprofile_head_t virtual_profile_client_head;
+extern vprofile_head_t virtual_profile_loopback_head;
+
+extern vmonitor_head_t virtual_monitor_input;
+extern vmonitor_head_t virtual_monitor_local;
+extern vmonitor_head_t virtual_monitor_output;
+
+extern const struct cuse_methods vctl_methods;
+
+extern struct virtual_compressor voss_output_compressor_param;
+extern double voss_output_compressor_gain[VMAX_CHAN];
+extern int64_t voss_output_peak[VMAX_CHAN];
+extern int64_t voss_input_peak[VMAX_CHAN];
+extern uint32_t voss_jitter_up;
+extern uint32_t voss_jitter_down;
+extern uint32_t voss_max_channels;
+extern uint32_t voss_mix_channels;
+extern uint32_t voss_dsp_samples;
+extern uint32_t voss_dsp_max_channels;
+extern uint32_t voss_dsp_sample_rate;
+extern uint32_t voss_dsp_bits;
+extern uint8_t voss_libsamplerate_enable;
+extern uint8_t voss_libsamplerate_quality;
+extern int voss_is_recording;
+extern int voss_has_synchronization;
+extern char voss_dsp_rx_device[VMAX_STRING];
+extern char voss_dsp_tx_device[VMAX_STRING];
+extern char voss_ctl_device[VMAX_STRING];
+extern volatile sig_atomic_t voss_exit;
+
+extern int vring_alloc(struct virtual_ring *, size_t);
+extern void vring_free(struct virtual_ring *);
+extern void vring_reset(struct virtual_ring *);
+extern void vring_get_read(struct virtual_ring *, uint8_t **, size_t *);
+extern void vring_get_write(struct virtual_ring *, uint8_t **, size_t *);
+extern void vring_inc_read(struct virtual_ring *, size_t);
+extern void vring_inc_write(struct virtual_ring *, size_t);
+extern size_t vring_total_read_len(struct virtual_ring *);
+extern size_t vring_total_write_len(struct virtual_ring *);
+extern size_t vring_write_linear(struct virtual_ring *, const uint8_t *, size_t);
+extern size_t vring_read_linear(struct virtual_ring *, uint8_t *, size_t);
+extern size_t vring_write_zero(struct virtual_ring *, size_t);
+
+extern vclient_t *vclient_alloc(void);
+extern void vclient_free(vclient_t *);
+
+extern int vclient_get_default_fmt(vprofile_t *, int type);
+extern int vclient_setup_buffers(vclient_t *, int size, int frags,
+ int channels, int format, int sample_rate);
+extern int vclient_export_read_locked(vclient_t *);
+extern void vclient_import_write_locked(vclient_t *);
+
+extern uint32_t vclient_sample_bytes(vclient_t *);
+extern uint32_t vclient_bufsize_internal(vclient_t *);
+extern uint32_t vclient_bufsize_scaled(vclient_t *);
+
+extern int64_t vclient_noise(uint32_t *, int64_t, int8_t);
+
+extern vmonitor_t *vmonitor_alloc(int *, vmonitor_head_t *);
+
+extern uint32_t format_best(uint32_t);
+extern void format_import(uint32_t, const uint8_t *, uint32_t, int64_t *);
+extern void format_export(uint32_t, const int64_t *, uint8_t *, uint32_t);
+extern int64_t format_max(uint32_t);
+extern void format_maximum(const int64_t *, int64_t *, uint32_t, uint32_t, int8_t);
+extern void format_remix(int64_t *, uint32_t, uint32_t, uint32_t);
+extern void format_silence(uint32_t, uint8_t *, uint32_t);
+
+extern void *virtual_oss_process(void *);
+
+/* Audio Delay prototypes */
+extern uint32_t voss_ad_last_delay;
+extern uint32_t voss_dsp_rx_refresh;
+extern uint32_t voss_dsp_tx_refresh;
+extern uint8_t voss_ad_enabled;
+extern uint8_t voss_ad_output_signal;
+extern uint8_t voss_ad_input_channel;
+extern uint8_t voss_ad_output_channel;
+extern void voss_ad_reset(void);
+extern void voss_ad_init(uint32_t);
+extern double voss_ad_getput_sample(double);
+
+/* Add audio options prototype */
+extern void voss_add_options(char *);
+
+/* Get current timestamp */
+extern uint64_t virtual_oss_delay_ns(void);
+extern void virtual_oss_wait(void);
+extern uint64_t virtual_oss_timestamp(void);
+
+/* Fast array multiplication */
+extern void voss_x3_multiply_double(const int64_t *, const double *, double *, const size_t);
+
+/* Equalizer support */
+extern void vclient_tx_equalizer(struct virtual_client *, int64_t *, size_t);
+extern void vclient_rx_equalizer(struct virtual_client *, int64_t *, size_t);
+extern int vclient_eq_alloc(struct virtual_client *);
+extern void vclient_eq_free(struct virtual_client *);
+
+/* Internal utilities */
+extern int bt_speaker_main(int argc, char **argv);
+
+/* Internal compressor */
+extern void voss_compressor(int64_t *, double *, const struct virtual_compressor *,
+ const unsigned, const unsigned, const int64_t);
+
+/* HTTP daemon support */
+extern const char *voss_httpd_start(vprofile_t *);
+
+#endif /* _VIRTUAL_INT_H_ */
diff --git a/usr.sbin/virtual_oss/virtual_oss/main.c b/usr.sbin/virtual_oss/virtual_oss/main.c
new file mode 100644
index 000000000000..3f7fb84ce4c6
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/main.c
@@ -0,0 +1,2625 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * 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/queue.h>
+#include <sys/types.h>
+#include <sys/filio.h>
+#include <sys/rtprio.h>
+#include <sys/nv.h>
+#include <sys/sndstat.h>
+#include <sys/soundcard.h>
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <sysexits.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <paths.h>
+
+#include <cuse.h>
+#include <pthread.h>
+
+#include "backend.h"
+#include "int.h"
+#include "utils.h"
+#include "virtual_oss.h"
+
+pthread_mutex_t atomic_mtx;
+pthread_cond_t atomic_cv;
+
+static void
+atomic_init(void)
+{
+ if (pthread_mutex_init(&atomic_mtx, NULL) != 0)
+ err(1, "pthread_mutex_init");
+ if (pthread_cond_init(&atomic_cv, NULL) != 0)
+ err(1, "pthread_cond_init");
+}
+
+uint32_t
+vclient_sample_bytes(vclient_t *pvc)
+{
+ uint32_t fmt = pvc->format;
+
+ if (fmt & AFMT_16BIT)
+ return (2);
+ else if (fmt & AFMT_24BIT)
+ return (3);
+ else if (fmt & AFMT_32BIT)
+ return (4);
+ else if (fmt & AFMT_8BIT)
+ return (1);
+ else
+ return (0);
+ /* TODO AFMT_BPS */
+}
+
+static uint32_t
+vclient_output_delay(vclient_t *pvc)
+{
+ uint64_t size;
+ uint64_t mod;
+
+ if (pvc->tx_busy == 0)
+ vclient_import_write_locked(pvc);
+
+ mod = pvc->channels * vclient_sample_bytes(pvc);
+
+ size = vring_total_read_len(&pvc->tx_ring[0]);
+ size = (size / 8) * vclient_sample_bytes(pvc);
+
+ size = (size * (uint64_t)pvc->sample_rate) /
+ (uint64_t)voss_dsp_sample_rate;
+ size += vring_total_read_len(&pvc->tx_ring[1]);
+ size -= size % mod;
+
+ return (size);
+}
+
+static uint32_t
+vclient_input_delay(vclient_t *pvc)
+{
+ if (pvc->rx_busy == 0)
+ vclient_export_read_locked(pvc);
+ return (vring_total_read_len(&pvc->rx_ring[1]));
+}
+
+uint32_t
+vclient_bufsize_scaled(vclient_t *pvc)
+{
+ uint32_t samples_scaled = ((uint64_t)voss_dsp_samples *
+ (uint64_t)pvc->sample_rate) / (uint64_t)voss_dsp_sample_rate;
+ if (samples_scaled == 0)
+ samples_scaled = 1;
+ return (pvc->channels * samples_scaled * vclient_sample_bytes(pvc));
+}
+
+static uint64_t
+vclient_bufsize_consumed(vclient_t *pvc, uint64_t ts)
+{
+ int64_t delta;
+ int64_t samples_scaled;
+ int64_t retval;
+
+ delta = virtual_oss_timestamp() - ts;
+ if (delta < 0)
+ delta = 0;
+ samples_scaled = (delta * (uint64_t)pvc->sample_rate) / 1000000000ULL;
+ if (samples_scaled < 0)
+ samples_scaled = 0;
+ retval = pvc->channels * samples_scaled * vclient_sample_bytes(pvc);
+ if (retval < 0)
+ retval = 0;
+ return (retval);
+}
+
+/*
+ * VLC and some other audio player use this value for jitter
+ * computations and expect it to be very accurate. VirtualOSS is block
+ * based and does not have sample accuracy. Use the system clock to
+ * update this value as we go along instead:
+ */
+static uint32_t
+vclient_output_delay_adjusted(vclient_t *pvc)
+{
+ int64_t retval = vclient_output_delay(pvc) -
+ vclient_bufsize_consumed(pvc, pvc->tx_timestamp);
+ if (retval < 0)
+ retval = 0;
+ return (retval);
+}
+
+vmonitor_t *
+vmonitor_alloc(int *pid, vmonitor_head_t *phead)
+{
+ int id = 0;
+ vmonitor_t *pvm;
+
+ TAILQ_FOREACH(pvm, phead, entry)
+ id++;
+
+ if (id >= 64) {
+ *pid = 0;
+ return (NULL);
+ }
+ pvm = malloc(sizeof(*pvm));
+ if (pvm == NULL) {
+ *pid = 0;
+ return (NULL);
+ }
+ memset(pvm, 0, sizeof(*pvm));
+
+ pvm->mute = 1;
+
+ TAILQ_INSERT_TAIL(phead, pvm, entry);
+
+ *pid = id;
+ return (pvm);
+}
+
+int64_t
+vclient_noise(uint32_t *pnoise, int64_t volume, int8_t shift)
+{
+ const uint32_t prime = 0xFFFF1DU;
+ int64_t temp;
+
+ /* compute next noise sample */
+ temp = *pnoise;
+ if (temp & 1)
+ temp += prime;
+ temp /= 2;
+ *pnoise = temp;
+
+ /* unsigned to signed conversion */
+ temp ^= 0x800000ULL;
+ if (temp & 0x800000U)
+ temp |= -0x800000ULL;
+
+ /* properly amplify */
+ temp *= volume;
+
+ /* bias shift */
+ shift -= 23 + VVOLUME_UNIT_SHIFT;
+
+ /* range check and shift noise */
+ if (__predict_false(shift < -63 || shift > 63))
+ temp = 0;
+ else if (shift < 0)
+ temp >>= -shift;
+ else
+ temp <<= shift;
+
+ return (temp);
+}
+
+static void
+vresample_free(vresample_t *pvr)
+{
+ if (pvr->state != NULL)
+ src_delete(pvr->state);
+ free(pvr->data_in);
+ free(pvr->data_out);
+ memset(pvr, 0, sizeof(*pvr));
+}
+
+static int
+vresample_setup(vclient_t *pvc, vresample_t *pvr, int samples)
+{
+ int code = 0;
+
+ if (pvr->state != NULL)
+ return (0);
+ pvr->state = src_new(voss_libsamplerate_quality, pvc->channels, &code);
+ if (pvr->state == NULL)
+ goto error;
+ pvr->data_in = malloc(sizeof(float) * samples);
+ if (pvr->data_in == NULL)
+ goto error;
+ pvr->data_out = malloc(sizeof(float) * samples);
+ if (pvr->data_out == NULL)
+ goto error;
+ pvr->data.data_in = pvr->data_in;
+ pvr->data.data_out = pvr->data_out;
+ return (0);
+error:
+ vresample_free(pvr);
+ return (CUSE_ERR_NO_MEMORY);
+}
+
+void
+vclient_free(vclient_t *pvc)
+{
+ vresample_free(&pvc->rx_resample);
+ vresample_free(&pvc->tx_resample);
+
+ /* free equalizer */
+ vclient_eq_free(pvc);
+
+ /* free ring buffers */
+ vring_free(&pvc->rx_ring[0]);
+ vring_free(&pvc->rx_ring[1]);
+ vring_free(&pvc->tx_ring[0]);
+ vring_free(&pvc->tx_ring[1]);
+
+ free(pvc);
+}
+
+vclient_t *
+vclient_alloc(void)
+{
+ vclient_t *pvc;
+
+ pvc = malloc(sizeof(*pvc));
+ if (pvc == NULL)
+ return (NULL);
+
+ memset(pvc, 0, sizeof(*pvc));
+
+ pvc->rx_noise_rem = 1;
+ pvc->tx_noise_rem = 1;
+ pvc->rx_volume = 1 << VVOLUME_UNIT_SHIFT;
+ pvc->tx_volume = 1 << VVOLUME_UNIT_SHIFT;
+
+ return (pvc);
+}
+
+int
+vclient_get_default_fmt(vprofile_t *pvp, int type)
+{
+ int retval;
+
+ if (type == VTYPE_WAV_HDR) {
+ switch (pvp->bits) {
+ case 16:
+ retval = AFMT_S16_LE;
+ break;
+ case 24:
+ retval = AFMT_S24_LE;
+ break;
+ case 32:
+ retval = AFMT_S32_LE;
+ break;
+ default:
+ retval = AFMT_S8;
+ break;
+ }
+ } else {
+ switch (pvp->bits) {
+ case 16:
+ retval = AFMT_S16_NE;
+ break;
+ case 24:
+ retval = AFMT_S24_NE;
+ break;
+ case 32:
+ retval = AFMT_S32_NE;
+ break;
+ default:
+ retval = AFMT_S8;
+ break;
+ }
+ }
+ return (retval);
+}
+
+int
+vclient_setup_buffers(vclient_t *pvc, int size, int frags,
+ int channels, int format, int sample_rate)
+{
+ size_t bufsize_internal;
+ size_t bufsize_min;
+ size_t mod_internal;
+ size_t mod;
+ uint64_t ts;
+ int bufsize;
+
+ /* check we are not busy */
+ if (pvc->rx_busy || pvc->tx_busy)
+ return (CUSE_ERR_BUSY);
+
+ /* free equalizer */
+ vclient_eq_free(pvc);
+
+ /* free existing ring buffers */
+ vring_free(&pvc->rx_ring[0]);
+ vring_free(&pvc->rx_ring[1]);
+ vring_free(&pvc->tx_ring[0]);
+ vring_free(&pvc->tx_ring[1]);
+
+ /* reset resampler */
+ vresample_free(&pvc->rx_resample);
+ vresample_free(&pvc->tx_resample);
+
+ if (sample_rate > 0)
+ pvc->sample_rate = sample_rate;
+ if (format != 0)
+ pvc->format = format;
+ if (channels > 0)
+ pvc->channels = channels;
+
+ mod = pvc->channels * vclient_sample_bytes(pvc);
+ mod_internal = pvc->channels * 8;
+
+ if (size > 0) {
+ size += mod - 1;
+ size -= size % mod;
+
+ pvc->buffer_size = size;
+ pvc->buffer_size_set = 1;
+ } else if (pvc->buffer_size_set == 0)
+ pvc->buffer_size = vclient_bufsize_scaled(pvc);
+
+ pvc->low_water = pvc->buffer_size;
+
+ if (frags > 0) {
+ pvc->buffer_frags = frags;
+ pvc->buffer_frags_set = 1;
+ } else if (pvc->buffer_frags_set == 0)
+ pvc->buffer_frags = 2;
+
+ /* sanity checks */
+ if (frags < 0 || size < 0)
+ return (CUSE_ERR_INVALID);
+ if (pvc->format == 0)
+ return (CUSE_ERR_INVALID);
+ if (pvc->buffer_frags <= 0 || pvc->buffer_frags >= 1024)
+ return (CUSE_ERR_INVALID);
+ if (pvc->buffer_size <= 0 || pvc->buffer_size >= (1024 * 1024))
+ return (CUSE_ERR_INVALID);
+ if ((pvc->buffer_size * pvc->buffer_frags) >= (128 * 1024 * 1024))
+ return (CUSE_ERR_INVALID);
+ if (pvc->channels <= 0 || pvc->channels > pvc->profile->channels)
+ return (CUSE_ERR_INVALID);
+
+ /* get buffer sizes */
+ bufsize = pvc->buffer_frags * pvc->buffer_size;
+ bufsize_internal = ((uint64_t)bufsize * (uint64_t)voss_dsp_sample_rate * 8ULL) /
+ ((uint64_t)pvc->sample_rate * (uint64_t)vclient_sample_bytes(pvc));
+
+ bufsize_min = voss_dsp_samples * pvc->channels * 8;
+
+ /* check for too small buffer size */
+ if (bufsize_internal < bufsize_min)
+ return (CUSE_ERR_INVALID);
+
+ /* allow for jitter */
+ bufsize_internal *= 2ULL;
+
+ /* align buffer size */
+ bufsize_internal += (mod_internal - 1);
+ bufsize_internal -= (bufsize_internal % mod_internal);
+
+ /* allocate new buffers */
+ if (vring_alloc(&pvc->rx_ring[0], bufsize_internal))
+ goto err_0;
+ if (vring_alloc(&pvc->rx_ring[1], bufsize))
+ goto err_1;
+ if (vring_alloc(&pvc->tx_ring[0], bufsize_internal))
+ goto err_2;
+ if (vring_alloc(&pvc->tx_ring[1], bufsize))
+ goto err_3;
+ if (vclient_eq_alloc(pvc))
+ goto err_4;
+
+ ts = virtual_oss_timestamp();
+
+ pvc->rx_samples = 0;
+ pvc->tx_samples = 0;
+ pvc->tx_timestamp = ts;
+ pvc->rx_timestamp = ts;
+
+ return (0);
+
+err_4:
+ vring_free(&pvc->tx_ring[1]);
+err_3:
+ vring_free(&pvc->tx_ring[0]);
+err_2:
+ vring_free(&pvc->rx_ring[1]);
+err_1:
+ vring_free(&pvc->rx_ring[0]);
+err_0:
+ return (CUSE_ERR_NO_MEMORY);
+}
+
+static int
+vclient_open_sub(struct cuse_dev *pdev, int fflags __unused, int type)
+{
+ vclient_t *pvc;
+ vprofile_t *pvp;
+ int error;
+
+ pvp = cuse_dev_get_priv0(pdev);
+
+ pvc = vclient_alloc();
+ if (pvc == NULL)
+ return (CUSE_ERR_NO_MEMORY);
+
+ pvc->profile = pvp;
+
+ /* setup buffers */
+ error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
+ vclient_get_default_fmt(pvp, type), voss_dsp_sample_rate);
+ if (error != 0) {
+ vclient_free(pvc);
+ return (error);
+ }
+
+ pvc->type = type;
+
+ cuse_dev_set_per_file_handle(pdev, pvc);
+
+ atomic_lock();
+ /* only allow one synchronization source at a time */
+ if (pvc->profile->synchronized) {
+ if (voss_has_synchronization != 0)
+ error = CUSE_ERR_BUSY;
+ else
+ voss_has_synchronization++;
+ }
+ if (error == 0)
+ TAILQ_INSERT_TAIL(&pvc->profile->head, pvc, entry);
+ atomic_unlock();
+
+ return (error);
+}
+
+static int
+vclient_open_wav(struct cuse_dev *pdev, int fflags)
+{
+ return (vclient_open_sub(pdev, fflags, VTYPE_WAV_HDR));
+}
+
+static int
+vclient_open_oss(struct cuse_dev *pdev, int fflags)
+{
+ return (vclient_open_sub(pdev, fflags, VTYPE_OSS_DAT));
+}
+
+static int
+vclient_close(struct cuse_dev *pdev, int fflags __unused)
+{
+ vclient_t *pvc;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (CUSE_ERR_INVALID);
+
+ atomic_lock();
+ if (pvc->profile->synchronized) {
+ voss_has_synchronization--;
+
+ /* wait for virtual_oss_process(), if any */
+ while (pvc->sync_busy) {
+ pvc->sync_wakeup = 1;
+ atomic_wakeup();
+ atomic_wait();
+ }
+ }
+ TAILQ_REMOVE(&pvc->profile->head, pvc, entry);
+ atomic_unlock();
+
+ vclient_free(pvc);
+
+ return (0);
+}
+
+static int
+vclient_read_silence_locked(vclient_t *pvc)
+{
+ size_t size;
+ int delta_in;
+
+ delta_in = pvc->profile->rec_delay - pvc->rec_delay;
+ if (delta_in < 1)
+ return (0);
+
+ size = delta_in * pvc->channels * 8;
+ size = vring_write_zero(&pvc->rx_ring[0], size);
+ pvc->rec_delay += size / (pvc->channels * 8);
+
+ delta_in = pvc->profile->rec_delay - pvc->rec_delay;
+ if (delta_in < 1)
+ return (0);
+
+ return (1);
+}
+
+static int
+vclient_generate_wav_header_locked(vclient_t *pvc)
+{
+ uint8_t *ptr;
+ size_t mod;
+ size_t len;
+
+ vring_get_write(&pvc->rx_ring[1], &ptr, &len);
+
+ mod = pvc->channels * vclient_sample_bytes(pvc);
+
+ if (mod == 0 || len < (44 + mod - 1))
+ return (CUSE_ERR_INVALID);
+
+ /* align to next sample */
+ len = 44 + mod - 1;
+ len -= len % mod;
+
+ /* pre-advance write pointer */
+ vring_inc_write(&pvc->rx_ring[1], len);
+
+ /* clear block */
+ memset(ptr, 0, len);
+
+ /* fill out data header */
+ ptr[len - 8] = 'd';
+ ptr[len - 7] = 'a';
+ ptr[len - 6] = 't';
+ ptr[len - 5] = 'a';
+
+ /* magic for unspecified length */
+ ptr[len - 4] = 0x00;
+ ptr[len - 3] = 0xF0;
+ ptr[len - 2] = 0xFF;
+ ptr[len - 1] = 0x7F;
+
+ /* fill out header */
+ *ptr++ = 'R';
+ *ptr++ = 'I';
+ *ptr++ = 'F';
+ *ptr++ = 'F';
+
+ /* total chunk size - unknown */
+
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+
+ *ptr++ = 'W';
+ *ptr++ = 'A';
+ *ptr++ = 'V';
+ *ptr++ = 'E';
+ *ptr++ = 'f';
+ *ptr++ = 'm';
+ *ptr++ = 't';
+ *ptr++ = ' ';
+
+ /* make sure header fits in PCM block */
+ len -= 28;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* audioformat = PCM */
+
+ *ptr++ = 0x01;
+ *ptr++ = 0x00;
+
+ /* number of channels */
+
+ len = pvc->channels;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* sample rate */
+
+ len = pvc->sample_rate;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* byte rate */
+
+ len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* block align */
+
+ len = pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* bits per sample */
+
+ len = vclient_sample_bytes(pvc) * 8;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ return (0);
+}
+
+int
+vclient_export_read_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx)
+{
+ enum { MAX_FRAME = 1024 };
+ size_t dst_mod;
+ size_t src_mod;
+ int error;
+
+ if (pvc->type == VTYPE_WAV_HDR) {
+ error = vclient_generate_wav_header_locked(pvc);
+ if (error != 0)
+ return (error);
+ /* only write header once */
+ pvc->type = VTYPE_WAV_DAT;
+ }
+ error = vclient_read_silence_locked(pvc);
+ if (error != 0)
+ return (0);
+
+ dst_mod = pvc->channels * vclient_sample_bytes(pvc);
+ src_mod = pvc->channels * 8;
+
+ if (pvc->sample_rate == (int)voss_dsp_sample_rate) {
+ while (1) {
+ uint8_t *src_ptr;
+ size_t src_len;
+ uint8_t *dst_ptr;
+ size_t dst_len;
+
+ vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len);
+ vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len);
+
+ src_len /= src_mod;
+ dst_len /= dst_mod;
+
+ /* compare number of samples */
+ if (dst_len > src_len)
+ dst_len = src_len;
+ else
+ src_len = dst_len;
+
+ if (dst_len == 0)
+ break;
+
+ src_len *= src_mod;
+ dst_len *= dst_mod;
+
+ format_export(pvc->format,
+ (const int64_t *)(uintptr_t)src_ptr,
+ dst_ptr, dst_len);
+
+ vring_inc_read(&pvc->rx_ring[0], src_len);
+ vring_inc_write(&pvc->rx_ring[1], dst_len);
+ }
+ } else {
+ vresample_t *pvr = &pvc->rx_resample;
+
+ if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0)
+ return (CUSE_ERR_NO_MEMORY);
+
+ while (1) {
+ uint8_t *src_ptr;
+ size_t src_len;
+ uint8_t *dst_ptr;
+ size_t dst_len;
+ int64_t temp[MAX_FRAME * pvc->channels];
+ size_t samples;
+ size_t y;
+
+ vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len);
+ vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len);
+
+ src_len /= src_mod;
+ dst_len /= dst_mod;
+
+ /* compare number of samples */
+ if (dst_len > src_len)
+ dst_len = src_len;
+ else
+ src_len = dst_len;
+
+ if (dst_len > MAX_FRAME)
+ dst_len = src_len = MAX_FRAME;
+
+ if (dst_len == 0)
+ break;
+
+ src_len *= src_mod;
+ dst_len *= dst_mod;
+
+ for (y = 0; y != src_len; y += 8) {
+ pvr->data_in[y / 8] =
+ *(int64_t *)(uintptr_t)(src_ptr + y);
+ }
+
+ /* setup parameters for transform */
+ pvr->data.input_frames = src_len / src_mod;
+ pvr->data.output_frames = dst_len / dst_mod;
+ pvr->data.src_ratio = (float)pvc->sample_rate / (float)voss_dsp_sample_rate;
+
+ pvc->rx_busy = 1;
+ atomic_unlock();
+ error = src_process(pvr->state, &pvr->data);
+ atomic_lock();
+ pvc->rx_busy = 0;
+
+ if (error != 0)
+ break;
+
+ src_len = pvr->data.input_frames_used * src_mod;
+ dst_len = pvr->data.output_frames_gen * dst_mod;
+
+ samples = pvr->data.output_frames_gen * pvc->channels;
+
+ for (y = 0; y != samples; y++)
+ temp[y] = pvr->data_out[y];
+
+ format_export(pvc->format, temp, dst_ptr, dst_len);
+
+ vring_inc_read(&pvc->rx_ring[0], src_len);
+ vring_inc_write(&pvc->rx_ring[1], dst_len);
+
+ /* check if no data was moved */
+ if (src_len == 0 && dst_len == 0)
+ break;
+ }
+ }
+ if (pvc->sync_busy)
+ atomic_wakeup();
+ return (0);
+}
+
+static int
+vclient_read(struct cuse_dev *pdev, int fflags,
+ void *peer_ptr, int len)
+{
+ vclient_t *pvc;
+
+ int error;
+ int retval;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (CUSE_ERR_INVALID);
+
+ atomic_lock();
+
+ if (pvc->rx_busy) {
+ atomic_unlock();
+ return (CUSE_ERR_BUSY);
+ }
+ pvc->rx_enabled = 1;
+
+ retval = 0;
+
+ while (len > 0) {
+ uint8_t *buf_ptr;
+ size_t buf_len;
+
+ error = vclient_export_read_locked(pvc);
+ if (error != 0) {
+ retval = error;
+ break;
+ }
+
+ vring_get_read(&pvc->rx_ring[1], &buf_ptr, &buf_len);
+
+ if (buf_len == 0) {
+ /* out of data */
+ if (fflags & CUSE_FFLAG_NONBLOCK) {
+ if (retval == 0)
+ retval = CUSE_ERR_WOULDBLOCK;
+ break;
+ }
+ pvc->rx_busy = 1;
+ atomic_wait();
+ pvc->rx_busy = 0;
+ if (cuse_got_peer_signal() == 0) {
+ if (retval == 0)
+ retval = CUSE_ERR_SIGNAL;
+ break;
+ }
+ continue;
+ }
+ if ((int)buf_len > len)
+ buf_len = len;
+
+ pvc->rx_busy = 1;
+ atomic_unlock();
+ error = cuse_copy_out(buf_ptr, peer_ptr, buf_len);
+ atomic_lock();
+ pvc->rx_busy = 0;
+
+ if (error != 0) {
+ retval = error;
+ break;
+ }
+ peer_ptr = ((uint8_t *)peer_ptr) + buf_len;
+ retval += buf_len;
+ len -= buf_len;
+
+ vring_inc_read(&pvc->rx_ring[1], buf_len);
+ }
+ atomic_unlock();
+
+ return (retval);
+}
+
+void
+vclient_import_write_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx)
+{
+ enum { MAX_FRAME = 1024 };
+ size_t dst_mod;
+ size_t src_mod;
+
+ dst_mod = pvc->channels * 8;
+ src_mod = pvc->channels * vclient_sample_bytes(pvc);
+
+ if (pvc->sample_rate == (int)voss_dsp_sample_rate) {
+ while (1) {
+ uint8_t *src_ptr;
+ size_t src_len;
+ uint8_t *dst_ptr;
+ size_t dst_len;
+
+ vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len);
+ vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len);
+
+ src_len /= src_mod;
+ dst_len /= dst_mod;
+
+ /* compare number of samples */
+ if (dst_len > src_len)
+ dst_len = src_len;
+ else
+ src_len = dst_len;
+
+ if (dst_len == 0)
+ break;
+
+ src_len *= src_mod;
+ dst_len *= dst_mod;
+
+ format_import(pvc->format, src_ptr, src_len,
+ (int64_t *)(uintptr_t)dst_ptr);
+
+ vring_inc_read(&pvc->tx_ring[1], src_len);
+ vring_inc_write(&pvc->tx_ring[0], dst_len);
+ }
+ } else {
+ vresample_t *pvr = &pvc->tx_resample;
+
+ if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0)
+ return;
+
+ while (1) {
+ uint8_t *src_ptr;
+ size_t src_len;
+ uint8_t *dst_ptr;
+ size_t dst_len;
+ int64_t temp[MAX_FRAME * pvc->channels];
+ size_t samples;
+ size_t y;
+ int error;
+
+ vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len);
+ vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len);
+
+ src_len /= src_mod;
+ dst_len /= dst_mod;
+
+ /* compare number of samples */
+ if (dst_len > src_len)
+ dst_len = src_len;
+ else
+ src_len = dst_len;
+
+ if (dst_len > MAX_FRAME)
+ dst_len = src_len = MAX_FRAME;
+
+ if (dst_len == 0)
+ break;
+
+ src_len *= src_mod;
+ dst_len *= dst_mod;
+
+ format_import(pvc->format, src_ptr, src_len, temp);
+
+ src_len /= vclient_sample_bytes(pvc);
+
+ for (y = 0; y != src_len; y++)
+ pvr->data_in[y] = temp[y];
+
+ src_len *= vclient_sample_bytes(pvc);
+
+ /* setup parameters for transform */
+ pvr->data.input_frames = src_len / src_mod;
+ pvr->data.output_frames = dst_len / dst_mod;
+ pvr->data.src_ratio = (float)voss_dsp_sample_rate / (float)pvc->sample_rate;
+
+ pvc->tx_busy = 1;
+ atomic_unlock();
+ error = src_process(pvr->state, &pvr->data);
+ atomic_lock();
+ pvc->tx_busy = 0;
+
+ if (error != 0)
+ break;
+
+ src_len = pvr->data.input_frames_used * src_mod;
+ dst_len = pvr->data.output_frames_gen * dst_mod;
+
+ samples = pvr->data.output_frames_gen * pvc->channels;
+
+ for (y = 0; y != samples; y++) {
+ ((int64_t *)(uintptr_t)dst_ptr)[y] =
+ pvr->data_out[y];
+ }
+
+ vring_inc_read(&pvc->tx_ring[1], src_len);
+ vring_inc_write(&pvc->tx_ring[0], dst_len);
+
+ /* check if no data was moved */
+ if (src_len == 0 && dst_len == 0)
+ break;
+ }
+ }
+ if (pvc->sync_busy)
+ atomic_wakeup();
+}
+
+static int
+vclient_write_oss(struct cuse_dev *pdev, int fflags,
+ const void *peer_ptr, int len)
+{
+ vclient_t *pvc;
+
+ int error;
+ int retval;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (CUSE_ERR_INVALID);
+
+ retval = 0;
+
+ atomic_lock();
+
+ if (pvc->tx_busy) {
+ atomic_unlock();
+ return (CUSE_ERR_BUSY);
+ }
+ pvc->tx_enabled = 1;
+
+ while (1) {
+ uint8_t *buf_ptr;
+ size_t buf_len;
+
+ vclient_import_write_locked(pvc);
+
+ if (len < 1)
+ break;
+
+ vring_get_write(&pvc->tx_ring[1], &buf_ptr, &buf_len);
+
+ if (buf_len == 0) {
+ /* out of data */
+ if (fflags & CUSE_FFLAG_NONBLOCK) {
+ if (retval == 0)
+ retval = CUSE_ERR_WOULDBLOCK;
+ break;
+ }
+ pvc->tx_busy = 1;
+ atomic_wait();
+ pvc->tx_busy = 0;
+ if (cuse_got_peer_signal() == 0) {
+ if (retval == 0)
+ retval = CUSE_ERR_SIGNAL;
+ break;
+ }
+ continue;
+ }
+ if ((int)buf_len > len)
+ buf_len = len;
+
+ pvc->tx_busy = 1;
+ atomic_unlock();
+ error = cuse_copy_in(peer_ptr, buf_ptr, buf_len);
+ atomic_lock();
+ pvc->tx_busy = 0;
+
+ if (error != 0) {
+ retval = error;
+ break;
+ }
+ peer_ptr = ((const uint8_t *)peer_ptr) + buf_len;
+ retval += buf_len;
+ len -= buf_len;
+
+ vring_inc_write(&pvc->tx_ring[1], buf_len);
+ }
+ atomic_unlock();
+
+ return (retval);
+}
+
+static int
+vclient_write_wav(struct cuse_dev *pdev __unused, int fflags __unused,
+ const void *peer_ptr __unused, int len __unused)
+{
+ return (CUSE_ERR_INVALID);
+}
+
+static int
+vclient_set_channels(vclient_t *pvc, int channels)
+{
+ if (pvc->channels == channels)
+ return (0);
+ return (vclient_setup_buffers(pvc, 0, 0, channels, 0, 0));
+}
+
+/* greatest common divisor, Euclid equation */
+static uint64_t
+vclient_gcd_64(uint64_t a, uint64_t b)
+{
+ uint64_t an;
+ uint64_t bn;
+
+ while (b != 0) {
+ an = b;
+ bn = a % b;
+ a = an;
+ b = bn;
+ }
+ return (a);
+}
+
+static uint64_t
+vclient_scale(uint64_t value, uint64_t mul, uint64_t div)
+{
+ uint64_t gcd = vclient_gcd_64(mul, div);
+
+ mul /= gcd;
+ div /= gcd;
+
+ return ((value * mul) / div);
+}
+
+static int
+vclient_ioctl_oss(struct cuse_dev *pdev, int fflags __unused,
+ unsigned long cmd, void *peer_data)
+{
+ union {
+ int val;
+ unsigned long long lval;
+ oss_sysinfo sysinfo;
+ oss_card_info card_info;
+ oss_audioinfo audioinfo;
+ audio_buf_info buf_info;
+ oss_count_t oss_count;
+ count_info oss_count_info;
+ audio_errinfo errinfo;
+ oss_label_t label;
+ oss_longname_t longname;
+ } data;
+
+ vclient_t *pvc;
+
+ uint64_t bytes;
+
+ int len;
+ int error;
+ int temp;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (CUSE_ERR_INVALID);
+
+ len = IOCPARM_LEN(cmd);
+
+ if (len < 0 || len > (int)sizeof(data))
+ return (CUSE_ERR_INVALID);
+
+ if (cmd & IOC_IN) {
+ error = cuse_copy_in(peer_data, &data, len);
+ if (error)
+ return (error);
+ } else {
+ error = 0;
+ }
+
+ atomic_lock();
+
+ switch (cmd) {
+ case OSS_GETVERSION:
+ data.val = SOUND_VERSION;
+ break;
+ case SNDCTL_SYSINFO:
+ memset(&data.sysinfo, 0, sizeof(data.sysinfo));
+ strcpy(data.sysinfo.product, "VOSS");
+ strcpy(data.sysinfo.version, "1.0");
+ data.sysinfo.versionnum = SOUND_VERSION;
+ data.sysinfo.numaudios = 1;
+ data.sysinfo.numcards = 1;
+ data.sysinfo.numaudioengines = 1;
+ strcpy(data.sysinfo.license, "BSD");
+ memset(data.sysinfo.filler, -1, sizeof(data.sysinfo.filler));
+ break;
+ case SNDCTL_CARDINFO:
+ memset(&data.card_info, 0, sizeof(data.card_info));
+ strlcpy(data.card_info.shortname, pvc->profile->oss_name,
+ sizeof(data.card_info.shortname));
+ break;
+ case SNDCTL_AUDIOINFO:
+ case SNDCTL_AUDIOINFO_EX:
+ case SNDCTL_ENGINEINFO:
+ memset(&data.audioinfo, 0, sizeof(data.audioinfo));
+ strlcpy(data.audioinfo.name, pvc->profile->oss_name,
+ sizeof(data.audioinfo.name));
+ snprintf(data.audioinfo.devnode, sizeof(data.audioinfo.devnode),
+ "/dev/%s", pvc->profile->oss_name);
+ data.audioinfo.caps = DSP_CAP_INPUT | DSP_CAP_OUTPUT;
+ data.audioinfo.iformats = VSUPPORTED_AFMT;
+ data.audioinfo.oformats = VSUPPORTED_AFMT;
+ data.audioinfo.enabled = 1;
+ data.audioinfo.min_rate = (int)8000;
+ data.audioinfo.max_rate = (int)voss_dsp_sample_rate;
+ data.audioinfo.max_channels = pvc->profile->channels;
+ /* range check */
+ if (voss_libsamplerate_enable == 0 ||
+ data.audioinfo.min_rate > data.audioinfo.max_rate)
+ data.audioinfo.min_rate = data.audioinfo.max_rate;
+ data.audioinfo.nrates = 1;
+ data.audioinfo.rates[0] = (int)voss_dsp_sample_rate;
+ if (voss_libsamplerate_enable != 0 &&
+ 96000 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 96000;
+ if (voss_libsamplerate_enable != 0 &&
+ 48000 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 48000;
+ if (voss_libsamplerate_enable != 0 &&
+ 44100 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 44100;
+ if (voss_libsamplerate_enable != 0 &&
+ 24000 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 24000;
+ if (voss_libsamplerate_enable != 0 &&
+ 16000 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 16000;
+ if (voss_libsamplerate_enable != 0 &&
+ 8000 != voss_dsp_sample_rate)
+ data.audioinfo.rates[data.audioinfo.nrates++] = 8000;
+ data.audioinfo.latency = -1;
+ break;
+ case FIONREAD:
+ data.val = vclient_input_delay(pvc);
+ break;
+ case FIONWRITE:
+ data.val = vring_total_read_len(&pvc->tx_ring[1]);
+ break;
+ case FIOASYNC:
+ case SNDCTL_DSP_NONBLOCK:
+ case FIONBIO:
+ break;
+ case SNDCTL_DSP_SETBLKSIZE:
+ case _IOWR('P', 4, int):
+ error = vclient_setup_buffers(pvc, data.val, 0, 0, 0, 0);
+ /* FALLTHROUGH */
+ case SNDCTL_DSP_GETBLKSIZE:
+ data.val = pvc->buffer_size;
+ break;
+ case SNDCTL_DSP_SETFRAGMENT:
+ if ((data.val & 0xFFFF) < 4) {
+ /* need at least 16 bytes of buffer */
+ data.val &= ~0xFFFF;
+ data.val |= 4;
+ } else if ((data.val & 0xFFFF) > 24) {
+ /* no more than 16MBytes of buffer */
+ data.val &= ~0xFFFF;
+ data.val |= 24;
+ }
+ error = vclient_setup_buffers(pvc,
+ (1 << (data.val & 0xFFFF)), (data.val >> 16), 0, 0, 0);
+ if (error) {
+ /* fallback to defaults */
+ pvc->buffer_size_set = 0;
+ pvc->buffer_frags_set = 0;
+ error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0);
+ if (error)
+ break;
+ /* figure out log2() of actual buffer size */
+ for (data.val = 0;
+ data.val < 24 && (1U << data.val) < pvc->buffer_size;
+ data.val++)
+ ;
+ /* or in the actual number of fragments */
+ data.val |= (pvc->buffer_frags << 16);
+ }
+ break;
+ case SNDCTL_DSP_RESET:
+ error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0);
+ break;
+ case SNDCTL_DSP_SYNC:
+ break;
+ case SNDCTL_DSP_SPEED:
+ if (data.val >= 8000 && data.val <= 96000 &&
+ voss_libsamplerate_enable != 0) {
+ error = vclient_setup_buffers(pvc, 0, 0, 0, 0, data.val);
+ }
+ /* return current speed */
+ data.val = (int)pvc->sample_rate;
+ break;
+ case SOUND_PCM_READ_RATE:
+ data.val = (int)pvc->sample_rate;
+ break;
+ case SNDCTL_DSP_STEREO:
+ if (data.val != 0) {
+ error = vclient_set_channels(pvc, 2);
+ } else {
+ error = vclient_set_channels(pvc, 1);
+ }
+ data.val = (pvc->channels == 2);
+ break;
+ case SOUND_PCM_WRITE_CHANNELS:
+ if (data.val < 0) {
+ data.val = 0;
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ if (data.val == 0) {
+ data.val = pvc->channels;
+ } else {
+ error = vclient_set_channels(pvc, data.val);
+ }
+ break;
+ case SOUND_PCM_READ_CHANNELS:
+ data.val = pvc->channels;
+ break;
+ case AIOGFMT:
+ case SNDCTL_DSP_GETFMTS:
+ data.val = VSUPPORTED_AFMT | AFMT_FULLDUPLEX |
+ (pvc->profile->channels > 1 ? AFMT_STEREO : 0);
+ break;
+ case AIOSFMT:
+ case SNDCTL_DSP_SETFMT:
+ if (data.val != AFMT_QUERY) {
+ temp = data.val & VSUPPORTED_AFMT;
+ if (temp == 0 || (temp & (temp - 1)) != 0) {
+ error = CUSE_ERR_INVALID;
+ } else {
+ error = vclient_setup_buffers(pvc, 0, 0, 0, temp, 0);
+ }
+ } else {
+ data.val = pvc->format;
+ }
+ break;
+ case SNDCTL_DSP_GETISPACE:
+ memset(&data.buf_info, 0, sizeof(data.buf_info));
+ data.buf_info.fragsize = pvc->buffer_size;
+ data.buf_info.fragstotal = pvc->buffer_frags;
+ bytes = (pvc->buffer_size * pvc->buffer_frags);
+ temp = vclient_input_delay(pvc);
+ if (temp < 0 || (uint64_t)temp > bytes)
+ temp = bytes;
+ data.buf_info.fragments = temp / pvc->buffer_size;
+ data.buf_info.bytes = temp;
+ break;
+ case SNDCTL_DSP_GETOSPACE:
+ memset(&data.buf_info, 0, sizeof(data.buf_info));
+ data.buf_info.fragsize = pvc->buffer_size;
+ data.buf_info.fragstotal = pvc->buffer_frags;
+ bytes = (pvc->buffer_size * pvc->buffer_frags);
+ temp = vclient_output_delay(pvc);
+ if (temp < 0 || (uint64_t)temp >= bytes) {
+ /* buffer is full */
+ data.buf_info.fragments = 0;
+ data.buf_info.bytes = 0;
+ } else {
+ /* buffer is not full */
+ bytes -= temp;
+ data.buf_info.fragments = bytes / pvc->buffer_size;
+ data.buf_info.bytes = bytes;
+ }
+ break;
+ case SNDCTL_DSP_GETCAPS:
+ data.val = PCM_CAP_REALTIME | PCM_CAP_DUPLEX |
+ PCM_CAP_INPUT | PCM_CAP_OUTPUT | PCM_CAP_TRIGGER |
+ PCM_CAP_VIRTUAL;
+ break;
+ case SOUND_PCM_READ_BITS:
+ data.val = vclient_sample_bytes(pvc) * 8;
+ break;
+ case SNDCTL_DSP_SETTRIGGER:
+ if (data.val & PCM_ENABLE_INPUT) {
+ pvc->rx_enabled = 1;
+ } else {
+ pvc->rx_enabled = 0;
+ vring_reset(&pvc->rx_ring[1]);
+ }
+
+ if (data.val & PCM_ENABLE_OUTPUT) {
+ pvc->tx_enabled = 1;
+ } else {
+ pvc->tx_enabled = 0;
+ vring_reset(&pvc->tx_ring[1]);
+ }
+ break;
+ case SNDCTL_DSP_GETTRIGGER:
+ data.val = 0;
+ if (pvc->rx_enabled)
+ data.val |= PCM_ENABLE_INPUT;
+ if (pvc->tx_enabled)
+ data.val |= PCM_ENABLE_OUTPUT;
+ break;
+ case SNDCTL_DSP_GETODELAY:
+ data.val = vclient_output_delay_adjusted(pvc);
+ break;
+ case SNDCTL_DSP_POST:
+ break;
+ case SNDCTL_DSP_SETDUPLEX:
+ break;
+ case SNDCTL_DSP_GETRECVOL:
+ temp = (pvc->rx_volume * 100) >> VVOLUME_UNIT_SHIFT;
+ data.val = (temp & 0x00FF) |
+ ((temp << 8) & 0xFF00);
+ break;
+ case SNDCTL_DSP_SETRECVOL:
+ pvc->rx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100;
+ break;
+ case SNDCTL_DSP_GETPLAYVOL:
+ temp = (pvc->tx_volume * 100) >> VVOLUME_UNIT_SHIFT;
+ data.val = (temp & 0x00FF) |
+ ((temp << 8) & 0xFF00);
+ break;
+ case SNDCTL_DSP_SETPLAYVOL:
+ pvc->tx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100;
+ break;
+ case SNDCTL_DSP_CURRENT_IPTR:
+ memset(&data.oss_count, 0, sizeof(data.oss_count));
+ /* compute input samples per channel */
+ data.oss_count.samples =
+ vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate);
+ data.oss_count.samples /= pvc->channels;
+ data.oss_count.fifo_samples =
+ vclient_input_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc));
+ break;
+ case SNDCTL_DSP_CURRENT_OPTR:
+ memset(&data.oss_count, 0, sizeof(data.oss_count));
+ /* compute output samples per channel */
+ data.oss_count.samples =
+ vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate);
+ data.oss_count.samples /= pvc->channels;
+ data.oss_count.fifo_samples =
+ vclient_output_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc));
+ break;
+ case SNDCTL_DSP_GETIPTR:
+ memset(&data.oss_count_info, 0, sizeof(data.oss_count_info));
+ /* compute input bytes */
+ bytes =
+ vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate) *
+ vclient_sample_bytes(pvc);
+ data.oss_count_info.bytes = bytes;
+ data.oss_count_info.blocks = bytes / pvc->buffer_size;
+ data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags);
+ break;
+ case SNDCTL_DSP_GETOPTR:
+ memset(&data.oss_count_info, 0, sizeof(data.oss_count_info));
+ /* compute output bytes */
+ bytes =
+ vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate) *
+ vclient_sample_bytes(pvc);
+ data.oss_count_info.bytes = bytes;
+ data.oss_count_info.blocks = bytes / pvc->buffer_size;
+ data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags);
+ break;
+ case SNDCTL_DSP_HALT_OUTPUT:
+ pvc->tx_enabled = 0;
+ break;
+ case SNDCTL_DSP_HALT_INPUT:
+ pvc->rx_enabled = 0;
+ break;
+ case SNDCTL_DSP_LOW_WATER:
+ if (data.val > 0 && data.val <
+ (int)(pvc->buffer_frags * pvc->buffer_size)) {
+ pvc->low_water = data.val;
+ } else {
+ error = CUSE_ERR_INVALID;
+ }
+ break;
+ case SNDCTL_DSP_GETERROR:
+ memset(&data.errinfo, 0, sizeof(data.errinfo));
+ break;
+ case SNDCTL_DSP_SYNCGROUP:
+ case SNDCTL_DSP_SYNCSTART:
+ break;
+ case SNDCTL_DSP_POLICY:
+ break;
+ case SNDCTL_DSP_COOKEDMODE:
+ break;
+ case SNDCTL_DSP_GET_CHNORDER:
+ data.lval = CHNORDER_NORMAL;
+ break;
+ case SNDCTL_DSP_GETCHANNELMASK:
+ data.val = DSP_BIND_FRONT;
+ break;
+ case SNDCTL_DSP_BIND_CHANNEL:
+ break;
+ case SNDCTL_GETLABEL:
+ memset(&data.label, 0, sizeof(data.label));
+ break;
+ case SNDCTL_SETLABEL:
+ break;
+ case SNDCTL_GETSONG:
+ memset(&data.longname, 0, sizeof(data.longname));
+ break;
+ case SNDCTL_SETSONG:
+ break;
+ case SNDCTL_SETNAME:
+ break;
+ default:
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ atomic_unlock();
+
+ if (error == 0) {
+ if (cmd & IOC_OUT)
+ error = cuse_copy_out(&data, peer_data, len);
+ }
+ return (error);
+}
+
+static int
+vclient_ioctl_wav(struct cuse_dev *pdev, int fflags __unused,
+ unsigned long cmd, void *peer_data)
+{
+ union {
+ int val;
+ } data;
+
+ vclient_t *pvc;
+ int len;
+ int error;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (CUSE_ERR_INVALID);
+
+ len = IOCPARM_LEN(cmd);
+
+ if (len < 0 || len > (int)sizeof(data))
+ return (CUSE_ERR_INVALID);
+
+ if (cmd & IOC_IN) {
+ error = cuse_copy_in(peer_data, &data, len);
+ if (error)
+ return (error);
+ } else {
+ error = 0;
+ }
+
+ atomic_lock();
+ switch (cmd) {
+ case FIONREAD:
+ data.val = vclient_input_delay(pvc);
+ break;
+ case FIOASYNC:
+ case SNDCTL_DSP_NONBLOCK:
+ case FIONBIO:
+ break;
+ default:
+ error = CUSE_ERR_INVALID;
+ break;
+ }
+ atomic_unlock();
+
+ if (error == 0) {
+ if (cmd & IOC_OUT)
+ error = cuse_copy_out(&data, peer_data, len);
+ }
+ return (error);
+}
+
+static int
+vclient_poll(struct cuse_dev *pdev, int fflags, int events)
+{
+ vclient_t *pvc;
+
+ int retval = CUSE_POLL_NONE;
+
+ pvc = cuse_dev_get_per_file_handle(pdev);
+ if (pvc == NULL)
+ return (retval);
+
+ atomic_lock();
+ if ((events & CUSE_POLL_READ) && (fflags & CUSE_FFLAG_READ)) {
+ pvc->rx_enabled = 1;
+ if (vclient_input_delay(pvc) >= pvc->low_water)
+ retval |= CUSE_POLL_READ;
+ }
+ if ((events & CUSE_POLL_WRITE) && (fflags & CUSE_FFLAG_WRITE)) {
+ const uint32_t out_dly = vclient_output_delay(pvc);
+ const uint32_t out_buf = (pvc->buffer_frags * pvc->buffer_size);
+
+ if (out_dly < out_buf && (out_buf - out_dly) >= pvc->low_water)
+ retval |= CUSE_POLL_WRITE;
+ }
+ atomic_unlock();
+
+ return (retval);
+}
+
+static const struct cuse_methods vclient_oss_methods = {
+ .cm_open = vclient_open_oss,
+ .cm_close = vclient_close,
+ .cm_read = vclient_read,
+ .cm_write = vclient_write_oss,
+ .cm_ioctl = vclient_ioctl_oss,
+ .cm_poll = vclient_poll,
+};
+
+static const struct cuse_methods vclient_wav_methods = {
+ .cm_open = vclient_open_wav,
+ .cm_close = vclient_close,
+ .cm_read = vclient_read,
+ .cm_write = vclient_write_wav,
+ .cm_ioctl = vclient_ioctl_wav,
+ .cm_poll = vclient_poll,
+};
+
+vprofile_head_t virtual_profile_client_head;
+vprofile_head_t virtual_profile_loopback_head;
+
+vmonitor_head_t virtual_monitor_input;
+vmonitor_head_t virtual_monitor_output;
+vmonitor_head_t virtual_monitor_local;
+
+uint32_t voss_max_channels;
+uint32_t voss_mix_channels;
+uint32_t voss_dsp_samples;
+uint32_t voss_dsp_max_channels;
+uint32_t voss_dsp_sample_rate;
+uint32_t voss_dsp_bits;
+uint8_t voss_libsamplerate_enable;
+uint8_t voss_libsamplerate_quality = SRC_SINC_FASTEST;
+int voss_is_recording = 1;
+int voss_has_synchronization;
+volatile sig_atomic_t voss_exit = 0;
+
+static int voss_dsp_perm = 0666;
+static int voss_do_background;
+static const char *voss_pid_path;
+
+uint32_t voss_dsp_rx_refresh;
+uint32_t voss_dsp_tx_refresh;
+char voss_dsp_rx_device[VMAX_STRING];
+char voss_dsp_tx_device[VMAX_STRING];
+char voss_ctl_device[VMAX_STRING];
+
+uint32_t voss_jitter_up;
+uint32_t voss_jitter_down;
+
+struct voss_backend *voss_rx_backend;
+struct voss_backend *voss_tx_backend;
+
+static int voss_dups;
+static int voss_ntds;
+static pthread_t *voss_tds;
+
+/* XXX I do not like the prefix argument... */
+static struct voss_backend *
+voss_load_backend(const char *prefix, const char *name, const char *dir)
+{
+ struct voss_backend *backend;
+ void *hdl;
+ char lpath[64], bsym[64];
+
+ snprintf(lpath, sizeof(lpath), "%s/lib/virtual_oss/voss_%s.so",
+ prefix, name);
+ snprintf(bsym, sizeof(bsym), "voss_backend_%s_%s", name, dir);
+
+ if ((hdl = dlopen(lpath, RTLD_NOW)) == NULL)
+ errx(1, "%s", dlerror());
+ if ((backend = dlsym(hdl, bsym)) == NULL) {
+ warnx("%s", dlerror());
+ dlclose(hdl);
+ exit(EXIT_FAILURE);
+ }
+
+ return (backend);
+}
+
+static void
+voss_rx_backend_refresh(void)
+{
+ /* setup RX backend */
+ if (strcmp(voss_dsp_rx_device, "/dev/null") == 0) {
+ voss_rx_backend = voss_load_backend("/usr", "null", "rec");
+ } else if (strstr(voss_dsp_rx_device, "/dev/bluetooth/") == voss_dsp_rx_device) {
+ voss_rx_backend = voss_load_backend("/usr/local", "bt", "rec");
+ } else if (strstr(voss_dsp_rx_device, "/dev/sndio/") == voss_dsp_rx_device) {
+ voss_rx_backend = voss_load_backend("/usr/local", "sndio", "rec");
+ } else {
+ voss_rx_backend = voss_load_backend("/usr", "oss", "rec");
+ }
+}
+
+static void
+voss_tx_backend_refresh(void)
+{
+ /* setup TX backend */
+ if (strcmp(voss_dsp_tx_device, "/dev/null") == 0) {
+ voss_tx_backend = voss_load_backend("/usr", "null", "play");
+ } else if (strstr(voss_dsp_tx_device, "/dev/bluetooth/") == voss_dsp_tx_device) {
+ voss_tx_backend = voss_load_backend("/usr/local", "bt", "play");
+ } else if (strstr(voss_dsp_tx_device, "/dev/sndio/") == voss_dsp_tx_device) {
+ voss_tx_backend = voss_load_backend("/usr/local", "sndio", "play");
+ } else {
+ voss_tx_backend = voss_load_backend("/usr", "oss", "play");
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: virtual_oss [options...] [device] \\\n"
+ "\t" "-C 2 -c 2 -r 48000 -b 16 -s 100.0ms -f /dev/dsp3 \\\n"
+ "\t" "-P /dev/dsp3 -R /dev/dsp1 \\\n"
+ "\t" "-O /dev/dsp3 -R /dev/null \\\n"
+ "\t" "-c 1 -m 0,0 [-w wav.0] -d dsp100.0 \\\n"
+ "\t" "-c 1 -m 0,0 [-w wav.0] -d vdsp.0 \\\n"
+ "\t" "-c 2 -m 0,0,1,1 [-w wav.1] -d vdsp.1 \\\n"
+ "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -l vdsp.loopback \\\n"
+ "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -L vdsp.loopback \\\n"
+ "\t" "-B # run in background \\\n"
+ "\t" "-s <samples> or <milliseconds>ms \\\n"
+ "\t" "-S # enable automatic resampling using libsamplerate \\\n"
+ "\t" "-Q <0,1,2> # quality of resampling 0=best,1=medium,2=fastest (default) \\\n"
+ "\t" "-b <bits> \\\n"
+ "\t" "-r <rate> \\\n"
+ "\t" "-i <rtprio> \\\n"
+ "\t" "-a <amp -63..63> \\\n"
+ "\t" "-a i,<rx_amp -63..63> \\\n"
+ "\t" "-a o,<tx_amp -63..63> \\\n"
+ "\t" "-g <knee,attack,decay> # enable device RX compressor\\\n"
+ "\t" "-x <knee,attack,decay> # enable output compressor\\\n"
+ "\t" "-p <pol 0..1> \\\n"
+ "\t" "-e <rxtx_mute 0..1> \\\n"
+ "\t" "-e <rx_mute 0..1>,<tx_mute 0..1> \\\n"
+ "\t" "-m <mapping> \\\n"
+ "\t" "-m <rx0,tx0,rx1,tx1...rxN,txN> \\\n"
+ "\t" "-C <mixchans>\\\n"
+ "\t" "-c <dspchans> \\\n"
+ "\t" "-M <monitorfilter> \\\n"
+ "\t" "-M i,<src>,<dst>,<pol>,<mute>,<amp> \\\n"
+ "\t" "-M o,<src>,<dst>,<pol>,<mute>,<amp> \\\n"
+ "\t" "-M x,<src>,<dst>,<pol>,<mute>,<amp> \\\n"
+ "\t" "-F <rx_filter_samples> or <milliseconds>ms \\\n"
+ "\t" "-G <tx_filter_samples> or <milliseconds>ms \\\n"
+ "\t" "-E <enable_recording, 0 or 1> \\\n"
+ "\t" "-N <max HTTP connections, default is 1> \\\n"
+ "\t" "-H <bind HTTP server to this host> \\\n"
+ "\t" "-o <bind HTTP server to this port, default is 80> \\\n"
+ "\t" "-J <bind RTP server to this network interface> \\\n"
+ "\t" "-k <bind RTP server to this port, default is 8080> \\\n"
+ "\t" "-t vdsp.ctl \n"
+ "\t" "Left channel = 0\n"
+ "\t" "Right channel = 1\n"
+ "\t" "Max channels = %d\n", VMAX_CHAN);
+
+ exit(EX_USAGE);
+}
+
+static void
+init_compressor(struct virtual_profile *pvp)
+{
+ int x;
+
+ memset(&pvp->rx_compressor_param, 0, sizeof(pvp->rx_compressor_param));
+
+ pvp->rx_compressor_param.knee = 85;
+ pvp->rx_compressor_param.attack = 3;
+ pvp->rx_compressor_param.decay = 20;
+
+ for (x = 0; x != VMAX_CHAN; x++)
+ pvp->rx_compressor_gain[x] = 1.0;
+}
+
+static void
+init_mapping(struct virtual_profile *pvp)
+{
+ int x;
+
+ for (x = 0; x != VMAX_CHAN; x++) {
+ pvp->rx_src[x] = x;
+ pvp->tx_dst[x] = x;
+ }
+}
+
+static void
+init_sndstat(vprofile_t *ptr)
+{
+ int err;
+ nvlist_t *nvl;
+ nvlist_t *di = NULL, *dichild = NULL;
+ struct sndstioc_nv_arg arg;
+ unsigned int min_rate, max_rate;
+
+ nvl = nvlist_create(0);
+ if (nvl == NULL) {
+ warn("Failed to create nvlist");
+ goto done;
+ }
+
+ di = nvlist_create(0);
+ if (di == NULL) {
+ warn("Failed to create nvlist");
+ goto done;
+ }
+
+ dichild = nvlist_create(0);
+ if (dichild == NULL) {
+ warn("Failed to create nvlist");
+ goto done;
+ }
+
+ nvlist_add_string(di, SNDST_DSPS_PROVIDER, "virtual_oss");
+ nvlist_add_string(di, SNDST_DSPS_DESC, "virtual_oss device");
+ nvlist_add_number(di, SNDST_DSPS_PCHAN, 1);
+ nvlist_add_number(di, SNDST_DSPS_RCHAN, 1);
+ min_rate = 8000;
+ max_rate = voss_dsp_sample_rate;
+ if (voss_libsamplerate_enable == 0 ||
+ min_rate > max_rate)
+ min_rate = max_rate;
+ if (voss_libsamplerate_enable != 0 && max_rate < 96000)
+ max_rate = 96000;
+ nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_RATE, min_rate);
+ nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_RATE, max_rate);
+ nvlist_add_number(dichild, SNDST_DSPS_INFO_FORMATS, VSUPPORTED_AFMT);
+ nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_CHN, ptr->channels);
+ nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_CHN, ptr->channels);
+ nvlist_add_nvlist(di, SNDST_DSPS_INFO_PLAY, dichild);
+ nvlist_add_nvlist(di, SNDST_DSPS_INFO_REC, dichild);
+
+ nvlist_add_string(di, SNDST_DSPS_DEVNODE,
+ ptr->oss_name);
+ nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
+
+ if (nvlist_error(nvl)) {
+ warn("Failed building nvlist");
+ goto done;
+ }
+
+ arg.buf = nvlist_pack(nvl, &arg.nbytes);
+ if (arg.buf == NULL) {
+ warn("Failed to pack nvlist");
+ goto done;
+ }
+ err = ioctl(ptr->fd_sta, SNDSTIOC_ADD_USER_DEVS, &arg);
+ free(arg.buf);
+ if (err != 0) {
+ warn("Failed to issue ioctl(SNDSTIOC_ADD_USER_DEVS)");
+ goto done;
+ }
+
+done:
+ nvlist_destroy(di);
+ nvlist_destroy(dichild);
+ nvlist_destroy(nvl);
+}
+
+static const char *
+dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute,
+ int tx_mute, int synchronized, int is_client)
+{
+ vprofile_t *ptr;
+ struct cuse_dev *pdev;
+ int x;
+
+ rx_mute = rx_mute ? 1 : 0;
+ tx_mute = tx_mute ? 1 : 0;
+ pol = pol ? 1 : 0;
+
+ /* Range check amplitude argument. */
+ for (x = 0; x != 2; x++) {
+ if (pamp[x] < -63)
+ pamp[x] = -63;
+ else if (pamp[x] > 63)
+ pamp[x] = 63;
+ }
+
+ ptr = malloc(sizeof(*ptr));
+ if (ptr == NULL)
+ return ("Out of memory");
+
+ memcpy(ptr, pvp, sizeof(*ptr));
+
+ ptr->synchronized = synchronized;
+ ptr->fd_sta = -1;
+ TAILQ_INIT(&ptr->head);
+
+ for (x = 0; x != ptr->channels; x++) {
+ ptr->tx_mute[x] = tx_mute;
+ ptr->rx_mute[x] = rx_mute;
+ ptr->tx_shift[x] = pamp[1];
+ ptr->rx_shift[x] = pamp[0];
+ ptr->tx_pol[x] = pol;
+ ptr->rx_pol[x] = pol;
+ }
+
+ /* create DSP device */
+ if (ptr->oss_name[0] != 0) {
+ /*
+ * Detect /dev/dsp creation and try to disable system
+ * basename cloning automatically:
+ */
+ if (strcmp(ptr->oss_name, "dsp") == 0)
+ system("sysctl hw.snd.basename_clone=0");
+
+ /* create DSP character device */
+ pdev = cuse_dev_create(&vclient_oss_methods, ptr, NULL,
+ 0, 0, voss_dsp_perm, ptr->oss_name);
+ if (pdev == NULL) {
+ free(ptr);
+ return ("Could not create CUSE DSP device");
+ }
+
+ /* register to sndstat */
+ ptr->fd_sta = open("/dev/sndstat", O_WRONLY);
+ if (ptr->fd_sta < 0) {
+ warn("Could not open /dev/sndstat");
+ } else {
+ init_sndstat(ptr);
+ }
+ }
+ /* create WAV device */
+ if (ptr->wav_name[0] != 0) {
+ pdev = cuse_dev_create(&vclient_wav_methods, ptr, NULL,
+ 0, 0, voss_dsp_perm, ptr->wav_name);
+ if (pdev == NULL) {
+ free(ptr);
+ return ("Could not create CUSE WAV device");
+ }
+ }
+
+ atomic_lock();
+ if (is_client)
+ TAILQ_INSERT_TAIL(&virtual_profile_client_head, ptr, entry);
+ else
+ TAILQ_INSERT_TAIL(&virtual_profile_loopback_head, ptr, entry);
+ atomic_unlock();
+
+ voss_dups++;
+
+ /* need new names next time */
+ memset(pvp->oss_name, 0, sizeof(pvp->oss_name));
+ memset(pvp->wav_name, 0, sizeof(pvp->wav_name));
+
+ /* need to set new filter sizes */
+ pvp->rx_filter_size = 0;
+ pvp->tx_filter_size = 0;
+
+ /* need to specify new HTTP parameters next time */
+ pvp->http.host = NULL;
+ pvp->http.port = NULL;
+ pvp->http.nstate = 0;
+ pvp->http.rtp_ifname = NULL;
+ pvp->http.rtp_port = NULL;
+
+ /* need to specify new amplification next time */
+ pamp[0] = 0;
+ pamp[1] = 0;
+
+ /* need to set new compressor parameters next time */
+ init_compressor(pvp);
+
+ return (voss_httpd_start(ptr));
+}
+
+static void
+virtual_pipe(int sig __unused)
+{
+ voss_dsp_tx_refresh = 1;
+ voss_dsp_rx_refresh = 1;
+}
+
+static void
+virtual_cuse_hup(int sig __unused)
+{
+ atomic_wakeup();
+}
+
+static void *
+virtual_cuse_process(void *arg __unused)
+{
+ signal(SIGHUP, &virtual_cuse_hup);
+
+ while (1) {
+ if (cuse_wait_and_process() != 0)
+ break;
+ }
+ return (NULL);
+}
+
+static void
+virtual_cuse_init_profile(struct virtual_profile *pvp)
+{
+ memset(pvp, 0, sizeof(*pvp));
+
+ init_compressor(pvp);
+ init_mapping(pvp);
+}
+
+static void
+virtual_sig_exit(int sig __unused)
+{
+ voss_exit = 1;
+}
+
+static const char *
+parse_options(int narg, char **pparg, int is_main)
+{
+ const char *ptr;
+ int a, b, c;
+ int val;
+ int idx;
+ int type;
+ int opt_mute[2] = {0, 0};
+ int opt_amp[2] = {0, 0};
+ int opt_pol = 0;
+ const char *optstr;
+ struct virtual_profile profile;
+ struct rtprio rtp;
+ float samples_ms;
+
+ if (is_main)
+ optstr = "N:J:k:H:o:F:G:w:e:p:a:C:c:r:b:f:g:x:i:m:M:d:l:L:s:t:h?O:P:Q:R:SBD:E:";
+ else
+ optstr = "F:G:w:e:p:a:c:b:f:m:M:d:l:L:s:O:P:R:E:";
+
+ virtual_cuse_init_profile(&profile);
+
+ /* reset getopt parsing */
+ optreset = 1;
+ optind = 1;
+
+ while ((c = getopt(narg, pparg, optstr)) != -1) {
+ switch (c) {
+ case 'B':
+ voss_do_background = 1;
+ break;
+ case 'D':
+ voss_pid_path = optarg;
+ break;
+ case 'C':
+ if (voss_mix_channels != 0) {
+ return ("The -C argument may only be used once");
+ }
+ voss_mix_channels = atoi(optarg);
+ if (voss_mix_channels >= VMAX_CHAN) {
+ return ("Number of mixing channels is too high");
+ }
+ break;
+ case 'a':
+ switch (optarg[0]) {
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ opt_amp[0] = -(opt_amp[1] = atoi(optarg));
+ break;
+ case 'i':
+ if (optarg[1] != ',')
+ return ("Expected comma after 'i'");
+ opt_amp[0] = atoi(optarg + 2);
+ break;
+ case 'o':
+ if (optarg[1] != ',')
+ return ("Expected comma after 'o'");
+ opt_amp[1] = atoi(optarg + 2);
+ break;
+ default:
+ return ("Invalid syntax for amplitude argument");
+ }
+ break;
+ case 'E':
+ voss_is_recording = (atoi(optarg) != 0);
+ break;
+ case 'e':
+ idx = 0;
+ ptr = optarg;
+ memset(opt_mute, 0, sizeof(opt_mute));
+ while (1) {
+ c = *ptr++;
+ if (c == ',' || c == 0) {
+ idx++;
+ if (c == 0)
+ break;
+ continue;
+ }
+ if (idx < 2 && c >= '0' && c <= '1') {
+ opt_mute[idx] = c - '0';
+ } else {
+ return ("Invalid -e parameter");
+ }
+ }
+ switch (idx) {
+ case 1:
+ opt_mute[1] = opt_mute[0];
+ break;
+ case 2:
+ break;
+ default:
+ return ("Invalid -e parameter");
+ }
+ break;
+ case 'p':
+ opt_pol = atoi(optarg);
+ break;
+ case 'c':
+ profile.channels = atoi(optarg);
+ if (profile.channels == 0)
+ return ("Number of channels is zero");
+ if (profile.channels > VMAX_CHAN)
+ return ("Number of channels is too high");
+ break;
+ case 'r':
+ voss_dsp_sample_rate = atoi(optarg);
+ if (voss_dsp_sample_rate < 8000)
+ return ("Sample rate is too low, 8000 Hz");
+ if (voss_dsp_sample_rate > 0xFFFFFF)
+ return ("Sample rate is too high");
+ break;
+ case 'i':
+ memset(&rtp, 0, sizeof(rtp));
+ rtp.type = RTP_PRIO_REALTIME;
+ rtp.prio = atoi(optarg);
+ if (rtprio(RTP_SET, getpid(), &rtp) != 0)
+ printf("Cannot set realtime priority\n");
+ break;
+ case 'b':
+ profile.bits = atoi(optarg);
+ switch (profile.bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ return ("Invalid number of sample bits");
+ }
+ break;
+ case 'g':
+ if (profile.rx_compressor_param.enabled)
+ return ("Compressor already enabled for this device");
+ if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 ||
+ a < VIRTUAL_OSS_KNEE_MIN ||
+ a > VIRTUAL_OSS_KNEE_MAX ||
+ b < VIRTUAL_OSS_ATTACK_MIN ||
+ b > VIRTUAL_OSS_ATTACK_MAX ||
+ c < VIRTUAL_OSS_DECAY_MIN ||
+ c > VIRTUAL_OSS_DECAY_MAX)
+ return ("Invalid device compressor argument(s)");
+ profile.rx_compressor_param.enabled = 1;
+ profile.rx_compressor_param.knee = a;
+ profile.rx_compressor_param.attack = b;
+ profile.rx_compressor_param.decay = c;
+ break;
+ case 'x':
+ if (voss_output_compressor_param.enabled)
+ return ("Compressor already enabled for output");
+ if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 ||
+ a < VIRTUAL_OSS_KNEE_MIN ||
+ a > VIRTUAL_OSS_KNEE_MAX ||
+ b < VIRTUAL_OSS_ATTACK_MIN ||
+ b > VIRTUAL_OSS_ATTACK_MAX ||
+ c < VIRTUAL_OSS_DECAY_MIN ||
+ c > VIRTUAL_OSS_DECAY_MAX)
+ return ("Invalid output compressor argument(s)");
+ voss_output_compressor_param.enabled = 1;
+ voss_output_compressor_param.knee = a;
+ voss_output_compressor_param.attack = b;
+ voss_output_compressor_param.decay = c;
+ break;
+ case 'f':
+ case 'O':
+ case 'P':
+ case 'R':
+ if (voss_dsp_sample_rate == 0 || voss_dsp_samples == 0)
+ return ("Missing -r or -s parameters");
+ if (voss_dsp_bits == 0) {
+ if (profile.bits == 0)
+ return ("Missing -b parameter");
+ voss_dsp_bits = profile.bits;
+ }
+ if (voss_dsp_max_channels == 0) {
+ if (profile.channels == 0)
+ return ("Missing -c parameter");
+ voss_dsp_max_channels = profile.channels;
+ }
+ if (c == 'f' || c == 'R') {
+ if (strlen(optarg) > VMAX_STRING - 1)
+ return ("Device name too long");
+ strncpy(voss_dsp_rx_device, optarg, sizeof(voss_dsp_rx_device));
+ voss_rx_backend_refresh();
+ voss_dsp_rx_refresh = 1;
+ }
+ if (c == 'f' || c == 'P' || c == 'O') {
+ if (strlen(optarg) > VMAX_STRING - 1)
+ return ("Device name too long");
+ strncpy(voss_dsp_tx_device, optarg, sizeof(voss_dsp_tx_device));
+ voss_tx_backend_refresh();
+ voss_dsp_tx_refresh = 1;
+
+ if (c == 'O' && voss_has_synchronization == 0)
+ voss_has_synchronization++;
+ }
+ break;
+ case 'w':
+ if (strlen(optarg) > VMAX_STRING - 1)
+ return ("Device name too long");
+ strncpy(profile.wav_name, optarg, sizeof(profile.wav_name));
+ break;
+ case 'd':
+ if (strlen(optarg) > VMAX_STRING - 1)
+ return ("Device name too long");
+ strncpy(profile.oss_name, optarg, sizeof(profile.oss_name));
+
+ if (profile.bits == 0 || voss_dsp_sample_rate == 0 ||
+ profile.channels == 0 || voss_dsp_samples == 0)
+ return ("Missing -b, -r, -c or -s parameters");
+
+ val = (voss_dsp_samples *
+ profile.bits * profile.channels) / 8;
+ if (val <= 0 || val >= (1024 * 1024))
+ return ("-s option value is too big");
+
+ ptr = dup_profile(&profile, opt_amp, opt_pol,
+ opt_mute[0], opt_mute[1], 0, 1);
+ if (ptr != NULL)
+ return (ptr);
+ break;
+ case 'L':
+ case 'l':
+ if (strlen(optarg) > VMAX_STRING - 1)
+ return ("Device name too long");
+ strncpy(profile.oss_name, optarg, sizeof(profile.oss_name));
+
+ if (profile.bits == 0 || voss_dsp_sample_rate == 0 ||
+ profile.channels == 0 || voss_dsp_samples == 0)
+ return ("Missing -b, -r, -r or -s parameters");
+
+ val = (voss_dsp_samples *
+ profile.bits * profile.channels) / 8;
+ if (val <= 0 || val >= (1024 * 1024))
+ return ("-s option value is too big");
+
+ ptr = dup_profile(&profile, opt_amp, opt_pol,
+ opt_mute[0], opt_mute[1], c == 'L', 0);
+ if (ptr != NULL)
+ return (ptr);
+ break;
+ case 'S':
+ voss_libsamplerate_enable = 1;
+ break;
+ case 'Q':
+ c = atoi(optarg);
+ switch (c) {
+ case 0:
+ voss_libsamplerate_quality = SRC_SINC_BEST_QUALITY;
+ break;
+ case 1:
+ voss_libsamplerate_quality = SRC_SINC_MEDIUM_QUALITY;
+ break;
+ default:
+ voss_libsamplerate_quality = SRC_SINC_FASTEST;
+ break;
+ }
+ break;
+ case 's':
+ if (voss_dsp_samples != 0)
+ return ("-s option may only be used once");
+ if (profile.bits == 0 || profile.channels == 0)
+ return ("-s option requires -b and -c options");
+ if (strlen(optarg) > 2 &&
+ sscanf(optarg, "%f", &samples_ms) == 1 &&
+ strcmp(optarg + strlen(optarg) - 2, "ms") == 0) {
+ if (voss_dsp_sample_rate == 0)
+ return ("-s <X>ms option requires -r option");
+ if (samples_ms < 0.125 || samples_ms >= 1000.0)
+ return ("-s <X>ms option has invalid value");
+ voss_dsp_samples = voss_dsp_sample_rate * samples_ms / 1000.0;
+ } else {
+ voss_dsp_samples = atoi(optarg);
+ }
+ if (voss_dsp_samples >= (1U << 24))
+ return ("-s option requires a non-zero positive value");
+ break;
+ case 't':
+ if (voss_ctl_device[0])
+ return ("-t parameter may only be used once");
+
+ strlcpy(voss_ctl_device, optarg, sizeof(voss_ctl_device));
+ break;
+ case 'm':
+ ptr = optarg;
+ val = 0;
+ idx = 0;
+ init_mapping(&profile);
+ while (1) {
+ c = *ptr++;
+ if (c == ',' || c == 0) {
+ if (idx >= (2 * VMAX_CHAN))
+ return ("Too many channels in mask");
+ if (idx & 1)
+ profile.tx_dst[idx / 2] = val;
+ else
+ profile.rx_src[idx / 2] = val;
+ if (c == 0)
+ break;
+ val = 0;
+ idx++;
+ continue;
+ }
+ if (c >= '0' && c <= '9') {
+ val *= 10;
+ val += c - '0';
+ }
+ }
+ break;
+ case 'M':
+ ptr = optarg;
+ type = *ptr;
+ if (type == 'i' || type == 'o' || type == 'x') {
+ vmonitor_t *pvm;
+
+ int src = 0;
+ int dst = 0;
+ int pol = 0;
+ int mute = 0;
+ int amp = 0;
+ int neg;
+
+ ptr++;
+ if (*ptr == ',')
+ ptr++;
+ else if (type == 'i')
+ return ("Expected comma after 'i'");
+ else if (type == 'o')
+ return ("Expected comma after 'o'");
+ else
+ return ("Expected comma after 'x'");
+
+ val = 0;
+ neg = 0;
+ idx = 0;
+ while (1) {
+ c = *ptr++;
+ if (c == '-') {
+ neg = 1;
+ continue;
+ }
+ if (c == ',' || c == 0) {
+ switch (idx) {
+ case 0:
+ src = val;
+ break;
+ case 1:
+ dst = val;
+ break;
+ case 2:
+ pol = val ? 1 : 0;
+ break;
+ case 3:
+ mute = val ? 1 : 0;
+ break;
+ case 4:
+ if (val > 31) {
+ return ("Absolute amplitude "
+ "for -M parameter "
+ "cannot exceed 31");
+ }
+ amp = neg ? -val : val;
+ break;
+ default:
+ break;
+ }
+ if (c == 0)
+ break;
+ val = 0;
+ neg = 0;
+ idx++;
+ continue;
+ }
+ if (c >= '0' && c <= '9') {
+ val *= 10;
+ val += c - '0';
+ }
+ }
+ if (idx < 4)
+ return ("Too few parameters for -M");
+
+ pvm = vmonitor_alloc(&idx,
+ (type == 'i') ? &virtual_monitor_input :
+ (type == 'x') ? &virtual_monitor_local :
+ &virtual_monitor_output);
+
+ if (pvm == NULL)
+ return ("Out of memory");
+
+ pvm->src_chan = src;
+ pvm->dst_chan = dst;
+ pvm->pol = pol;
+ pvm->mute = mute;
+ pvm->shift = amp;
+ } else {
+ return ("Invalid -M parameter");
+ }
+ break;
+ case 'F':
+ if (strlen(optarg) > 2 &&
+ sscanf(optarg, "%f", &samples_ms) == 1 &&
+ strcmp(optarg + strlen(optarg) - 2, "ms") == 0) {
+ if (voss_dsp_sample_rate == 0)
+ return ("-F <X>ms option requires -r option");
+ if (samples_ms < 0.125 || samples_ms >= 1000.0)
+ return ("-F <X>ms option has invalid value");
+ profile.rx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0;
+ } else {
+ profile.rx_filter_size = atoi(optarg);
+ }
+ /* make value power of two */
+ while ((profile.rx_filter_size - 1) & profile.rx_filter_size)
+ profile.rx_filter_size += ~(profile.rx_filter_size - 1) & profile.rx_filter_size;
+ /* range check */
+ if (profile.rx_filter_size > VIRTUAL_OSS_FILTER_MAX)
+ return ("Invalid -F parameter is out of range");
+ break;
+ case 'G':
+ if (strlen(optarg) > 2 &&
+ sscanf(optarg, "%f", &samples_ms) == 1 &&
+ strcmp(optarg + strlen(optarg) - 2, "ms") == 0) {
+ if (voss_dsp_sample_rate == 0)
+ return ("-G <X>ms option requires -r option");
+ if (samples_ms < 0.125 || samples_ms >= 1000.0)
+ return ("-G <X>ms option has invalid value");
+ profile.tx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0;
+ } else {
+ profile.tx_filter_size = atoi(optarg);
+ }
+ /* make value power of two */
+ while ((profile.tx_filter_size - 1) & profile.tx_filter_size)
+ profile.tx_filter_size += ~(profile.tx_filter_size - 1) & profile.tx_filter_size;
+ /* range check */
+ if (profile.tx_filter_size > VIRTUAL_OSS_FILTER_MAX)
+ return ("Invalid -F parameter is out of range");
+ break;
+ case 'N':
+ profile.http.nstate = atoi(optarg);
+ break;
+ case 'H':
+ profile.http.host = optarg;
+ if (profile.http.port == NULL)
+ profile.http.port = "80";
+ if (profile.http.nstate == 0)
+ profile.http.nstate = 1;
+ break;
+ case 'o':
+ profile.http.port = optarg;
+ break;
+ case 'J':
+ profile.http.rtp_ifname = optarg;
+ if (profile.http.rtp_port == NULL)
+ profile.http.rtp_port = "8080";
+ break;
+ case 'k':
+ profile.http.rtp_port = optarg;
+ break;
+ default:
+ if (is_main)
+ usage();
+ else
+ return ("Invalid option detected");
+ break;
+ }
+ }
+ return (NULL);
+}
+
+static void
+create_threads(void)
+{
+ int idx;
+
+ /* Give each DSP device 4 threads */
+ voss_ntds = voss_dups * 4;
+ voss_tds = malloc(voss_ntds * sizeof(pthread_t));
+ if (voss_tds == NULL)
+ err(1, "malloc");
+
+ for (idx = 0; idx < voss_ntds; idx++) {
+ if (pthread_create(&voss_tds[idx], NULL, &virtual_cuse_process,
+ NULL) != 0)
+ err(1, "pthread_create");
+ }
+
+ /* Reset until next time called */
+ voss_dups = 0;
+}
+
+static void
+destroy_threads(void)
+{
+ int idx;
+
+ for (idx = 0; idx < voss_ntds; idx++)
+ pthread_cancel(voss_tds[idx]);
+ free(voss_tds);
+}
+
+void
+voss_add_options(char *str)
+{
+ static char name[] = { "virtual_oss" };
+ const char sep[] = "\t ";
+ const char *ptrerr;
+ char *parg[64];
+ char *word;
+ char *brkt;
+ int narg = 0;
+
+ parg[narg++] = name;
+
+ for (word = strtok_r(str, sep, &brkt); word != NULL;
+ word = strtok_r(NULL, sep, &brkt)) {
+ if (narg >= 64) {
+ ptrerr = "Too many arguments";
+ goto done;
+ }
+ parg[narg++] = word;
+ }
+ ptrerr = parse_options(narg, parg, 0);
+done:
+ if (ptrerr != NULL) {
+ strlcpy(str, ptrerr, VIRTUAL_OSS_OPTIONS_MAX);
+ } else {
+ str[0] = 0;
+ create_threads();
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *ptrerr;
+ struct sigaction sa;
+ struct cuse_dev *pdev = NULL;
+
+ TAILQ_INIT(&virtual_profile_client_head);
+ TAILQ_INIT(&virtual_profile_loopback_head);
+
+ TAILQ_INIT(&virtual_monitor_input);
+ TAILQ_INIT(&virtual_monitor_output);
+ TAILQ_INIT(&virtual_monitor_local);
+
+ atomic_init();
+
+ /* automagically load the cuse.ko module, if any */
+ if (feature_present("cuse") == 0) {
+ if (system("kldload cuse") == -1)
+ warn("Failed to kldload cuse");
+ }
+
+ if (cuse_init() != 0)
+ errx(EX_USAGE, "Could not connect to cuse module");
+
+ signal(SIGPIPE, &virtual_pipe);
+
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sa.sa_handler = virtual_sig_exit;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(1, "sigaction(SIGINT)");
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ err(1, "sigaction(SIGTERM)");
+
+ ptrerr = parse_options(argc, argv, 1);
+ if (ptrerr != NULL)
+ errx(EX_USAGE, "%s", ptrerr);
+
+ if (voss_dsp_rx_device[0] == 0 || voss_dsp_tx_device[0] == 0)
+ errx(EX_USAGE, "Missing -f argument");
+
+ /* use DSP channels as default */
+ if (voss_mix_channels == 0)
+ voss_mix_channels = voss_dsp_max_channels;
+
+ if (voss_mix_channels > voss_dsp_max_channels)
+ voss_max_channels = voss_mix_channels;
+ else
+ voss_max_channels = voss_dsp_max_channels;
+
+ if (voss_dsp_samples > (voss_dsp_sample_rate / 4))
+ errx(EX_USAGE, "Too many buffer samples given by -s argument");
+
+ /* check if daemon mode is requested */
+ if (voss_do_background != 0 && daemon(0, 0) != 0)
+ errx(EX_SOFTWARE, "Cannot become daemon");
+
+ if (voss_pid_path != NULL) {
+ int pidfile = open(voss_pid_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ pid_t mypid = getpid();
+ char mypidstr[8];
+ snprintf(mypidstr, sizeof(mypidstr), "%d\n", mypid);
+ if (pidfile < 0)
+ errx(EX_SOFTWARE, "Cannot create PID file '%s'", voss_pid_path);
+ if (write(pidfile, mypidstr, strlen(mypidstr)) !=
+ (ssize_t)strlen(mypidstr))
+ errx(EX_SOFTWARE, "Cannot write PID file");
+ close(pidfile);
+ }
+
+ /* setup audio delay unit */
+ voss_ad_init(voss_dsp_sample_rate);
+
+ /* Create CTL device */
+
+ if (voss_ctl_device[0] != 0) {
+ pdev = cuse_dev_create(&vctl_methods, NULL, NULL,
+ 0, 0, voss_dsp_perm, voss_ctl_device);
+ if (pdev == NULL)
+ errx(EX_USAGE, "Could not create '/dev/%s'", voss_ctl_device);
+
+ voss_dups++;
+ }
+
+ /* Create worker threads */
+ create_threads();
+
+ /* Run DSP threads */
+
+ virtual_oss_process(NULL);
+
+ destroy_threads();
+
+ if (voss_ctl_device[0] != 0)
+ cuse_dev_destroy(pdev);
+
+ return (0);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/mul.c b/usr.sbin/virtual_oss/virtual_oss/mul.c
new file mode 100644
index 000000000000..76cb570eef74
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/mul.c
@@ -0,0 +1,175 @@
+/*-
+ * Copyright (c) 2017 Hans Petter Selasky
+ *
+ * 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/queue.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include "int.h"
+
+#ifndef VOSS_X3_LOG2_COMBA
+#define VOSS_X3_LOG2_COMBA 5
+#endif
+
+#if (VOSS_X3_LOG2_COMBA < 2)
+#error "VOSS_X3_LOG2_COMBA must be greater than 1"
+#endif
+
+struct voss_x3_input_double {
+ double a;
+ double b;
+} __aligned(16);
+
+/*
+ * <input size> = "stride"
+ * <output size> = 2 * "stride"
+ */
+static void
+voss_x3_multiply_sub_double(struct voss_x3_input_double *input, double *ptr_low, double *ptr_high,
+ const size_t stride, const uint8_t toggle)
+{
+ size_t x;
+ size_t y;
+
+ if (stride >= (1UL << VOSS_X3_LOG2_COMBA)) {
+ const size_t strideh = stride >> 1;
+
+ if (toggle) {
+
+ /* inverse step */
+ for (x = 0; x != strideh; x++) {
+ double a, b, c, d;
+
+ a = ptr_low[x];
+ b = ptr_low[x + strideh];
+ c = ptr_high[x];
+ d = ptr_high[x + strideh];
+
+ ptr_low[x + strideh] = a + b;
+ ptr_high[x] = a + b + c + d;
+ }
+
+ voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 1);
+
+ for (x = 0; x != strideh; x++)
+ ptr_low[x + strideh] = -ptr_low[x + strideh];
+
+ voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 1);
+
+ /* forward step */
+ for (x = 0; x != strideh; x++) {
+ double a, b, c, d;
+
+ a = ptr_low[x];
+ b = ptr_low[x + strideh];
+ c = ptr_high[x];
+ d = ptr_high[x + strideh];
+
+ ptr_low[x + strideh] = -a - b;
+ ptr_high[x] = c + b - d;
+
+ input[x + strideh].a += input[x].a;
+ input[x + strideh].b += input[x].b;
+ }
+
+ voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 0);
+ } else {
+ voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 1);
+
+ /* inverse step */
+ for (x = 0; x != strideh; x++) {
+ double a, b, c, d;
+
+ a = ptr_low[x];
+ b = ptr_low[x + strideh];
+ c = ptr_high[x];
+ d = ptr_high[x + strideh];
+
+ ptr_low[x + strideh] = -a - b;
+ ptr_high[x] = a + b + c + d;
+
+ input[x + strideh].a -= input[x].a;
+ input[x + strideh].b -= input[x].b;
+ }
+
+ voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 0);
+
+ for (x = 0; x != strideh; x++)
+ ptr_low[x + strideh] = -ptr_low[x + strideh];
+
+ voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 0);
+
+ /* forward step */
+ for (x = 0; x != strideh; x++) {
+ double a, b, c, d;
+
+ a = ptr_low[x];
+ b = ptr_low[x + strideh];
+ c = ptr_high[x];
+ d = ptr_high[x + strideh];
+
+ ptr_low[x + strideh] = b - a;
+ ptr_high[x] = c - b - d;
+ }
+ }
+ } else {
+ for (x = 0; x != stride; x++) {
+ double value = input[x].a;
+
+ for (y = 0; y != (stride - x); y++) {
+ ptr_low[x + y] += input[y].b * value;
+ }
+
+ for (; y != stride; y++) {
+ ptr_high[x + y - stride] += input[y].b * value;
+ }
+ }
+ }
+}
+
+/*
+ * <input size> = "max"
+ * <output size> = 2 * "max"
+ */
+void
+voss_x3_multiply_double(const int64_t *va, const double *vb, double *pc, const size_t max)
+{
+ struct voss_x3_input_double input[max];
+ size_t x;
+
+ /* check for non-power of two */
+ if (max & (max - 1))
+ return;
+
+ /* setup input vector */
+ for (x = 0; x != max; x++) {
+ input[x].a = va[x];
+ input[x].b = vb[x];
+ }
+
+ /* do multiplication */
+ voss_x3_multiply_sub_double(input, pc, pc + max, max, 1);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/ring.c b/usr.sbin/virtual_oss/virtual_oss/ring.c
new file mode 100644
index 000000000000..3c97bdfc2e84
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/ring.c
@@ -0,0 +1,213 @@
+/*-
+ * Copyright (c) 2018 Hans Petter Selasky
+ *
+ * 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/queue.h>
+#include <sys/types.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include "int.h"
+
+int
+vring_alloc(struct virtual_ring *pvr, size_t size)
+{
+
+ if (pvr->buf_start != NULL)
+ return (EBUSY);
+ pvr->buf_start = malloc(size);
+ if (pvr->buf_start == NULL)
+ return (ENOMEM);
+ pvr->pos_read = 0;
+ pvr->total_size = size;
+ pvr->len_write = 0;
+ return (0);
+}
+
+void
+vring_free(struct virtual_ring *pvr)
+{
+
+ if (pvr->buf_start != NULL) {
+ free(pvr->buf_start);
+ pvr->buf_start = NULL;
+ }
+}
+
+void
+vring_reset(struct virtual_ring *pvr)
+{
+ pvr->pos_read = 0;
+ pvr->len_write = 0;
+}
+
+void
+vring_get_read(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen)
+{
+ uint32_t delta;
+
+ if (pvr->buf_start == NULL) {
+ *pptr = NULL;
+ *plen = 0;
+ return;
+ }
+ delta = pvr->total_size - pvr->pos_read;
+ if (delta > pvr->len_write)
+ delta = pvr->len_write;
+
+ *pptr = pvr->buf_start + pvr->pos_read;
+ *plen = delta;
+}
+
+void
+vring_get_write(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen)
+{
+ uint32_t delta;
+ uint32_t len_read;
+ uint32_t pos_write;
+
+ if (pvr->buf_start == NULL) {
+ *pptr = NULL;
+ *plen = 0;
+ return;
+ }
+ pos_write = pvr->pos_read + pvr->len_write;
+ if (pos_write >= pvr->total_size)
+ pos_write -= pvr->total_size;
+
+ len_read = pvr->total_size - pvr->len_write;
+
+ delta = pvr->total_size - pos_write;
+ if (delta > len_read)
+ delta = len_read;
+
+ *pptr = pvr->buf_start + pos_write;
+ *plen = delta;
+}
+
+void
+vring_inc_read(struct virtual_ring *pvr, size_t len)
+{
+
+ pvr->pos_read += len;
+ pvr->len_write -= len;
+
+ /* check for wrap-around */
+ if (pvr->pos_read == pvr->total_size)
+ pvr->pos_read = 0;
+}
+
+void
+vring_inc_write(struct virtual_ring *pvr, size_t len)
+{
+
+ pvr->len_write += len;
+}
+
+size_t
+vring_total_read_len(struct virtual_ring *pvr)
+{
+
+ return (pvr->len_write);
+}
+
+size_t
+vring_total_write_len(struct virtual_ring *pvr)
+{
+
+ return (pvr->total_size - pvr->len_write);
+}
+
+size_t
+vring_write_linear(struct virtual_ring *pvr, const uint8_t *src, size_t total)
+{
+ uint8_t *buf_ptr;
+ size_t buf_len;
+ size_t sum = 0;
+
+ while (total != 0) {
+ vring_get_write(pvr, &buf_ptr, &buf_len);
+ if (buf_len == 0)
+ break;
+ if (buf_len > total)
+ buf_len = total;
+ memcpy(buf_ptr, src, buf_len);
+ vring_inc_write(pvr, buf_len);
+ src += buf_len;
+ sum += buf_len;
+ total -= buf_len;
+ }
+ return (sum);
+}
+
+size_t
+vring_read_linear(struct virtual_ring *pvr, uint8_t *dst, size_t total)
+{
+ uint8_t *buf_ptr;
+ size_t buf_len;
+ size_t sum = 0;
+
+ if (total > vring_total_read_len(pvr))
+ return (0);
+
+ while (total != 0) {
+ vring_get_read(pvr, &buf_ptr, &buf_len);
+ if (buf_len == 0)
+ break;
+ if (buf_len > total)
+ buf_len = total;
+ memcpy(dst, buf_ptr, buf_len);
+ vring_inc_read(pvr, buf_len);
+ dst += buf_len;
+ sum += buf_len;
+ total -= buf_len;
+ }
+ return (sum);
+}
+
+size_t
+vring_write_zero(struct virtual_ring *pvr, size_t total)
+{
+ uint8_t *buf_ptr;
+ size_t buf_len;
+ size_t sum = 0;
+
+ while (total != 0) {
+ vring_get_write(pvr, &buf_ptr, &buf_len);
+ if (buf_len == 0)
+ break;
+ if (buf_len > total)
+ buf_len = total;
+ memset(buf_ptr, 0, buf_len);
+ vring_inc_write(pvr, buf_len);
+ sum += buf_len;
+ total -= buf_len;
+ }
+ return (sum);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/utils.h b/usr.sbin/virtual_oss/virtual_oss/utils.h
new file mode 100644
index 000000000000..f0998dc75dae
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/utils.h
@@ -0,0 +1,31 @@
+/*-
+ * Copyright (c) 2019 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifndef _VIRTUAL_UTILS_H_
+#define _VIRTUAL_UTILS_H_
+
+int bt_speaker_main(int argc, char **argv);
+
+#endif /* _VIRTUAL_UTILS_H_ */
diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8
new file mode 100644
index 000000000000..6aa9f1289b35
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8
@@ -0,0 +1,355 @@
+.\"
+.\" Copyright (c) 2017-2022 Hans Petter Selasky <hselasky@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.
+.\"
+.\"
+.Dd September 3, 2024
+.Dt VIRTUAL_OSS 8
+.Os
+.Sh NAME
+.Nm virtual_oss
+.Nd daemon to multiplex and demultiplex an OSS device
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Sh DESCRIPTION
+.Nm
+is an audio mixing application that multiplexes and demultiplexes a
+single OSS device into multiple customizable OSS compatible devices
+using character devices from userspace.
+These devices can be used to record played back audio and mix the individual
+channels in multiple ways.
+.Pp
+.Nm
+requires the
+.Xr cuse 3
+kernel module.
+To load the driver as a module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Pp
+ cuse_load="YES"
+.Pp
+All channel numbers start at zero.
+Left channel is zero and right channel is one.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl B
+Run program in background.
+.It Fl S
+Enable automatic DSP rate resampling.
+.It Fl Q Ar quality
+Set resampling quality: 0=best, 1=medium and 2=fastest (default).
+.It Fl b Ar bits
+Set sample depth to
+.Fa bits
+for the subsequent commands.
+Valid values are 8, 16, 24 and 32.
+.It Fl r Ar rate
+Set default sample-rate for the subsequent commands.
+.It Fl s Ar value
+Set default buffer size to
+.Fa value .
+If the argument is suffixed by "ms" it is interpreted as milliseconds.
+Else the argument gives number of samples.
+The buffer size specified is per channel.
+If there are multiple channels, the total buffer size will be larger.
+.It Fl i Ar priority
+Set real-time priority to
+.Fa priority .
+Refer to
+.Xr rtprio 1
+for more information.
+.It Fl a Ar log2_amp
+Set the default DSP output and input device amplification to
+.Fa log2_amp .
+The specified amplification is logarithmic.
+Valid values range from -63 to 63 inclusivly.
+The device input amplification gets set to minus
+.Fa log2_amp
+and the device output amplification gets set to
+.Fa log2_amp .
+.It Fl a Ar i,log2_amp
+Set the default DSP input device amplification to
+.Fa log2_amp .
+The specified amplification is logarithmic.
+Valid values range from -63 to 63 inclusivly.
+.It Fl a Ar o,log2_amp
+Set default DSP output device amplification to
+.Fa log2_amp .
+The specified amplification is logarithmic.
+Valid values range from -63 to 63 inclusivly.
+.It Fl p Ar polarity
+Set default polarity of DSP device.
+A value of zero means normal polarity.
+A value of one means negative polarity.
+.It Fl e Ar rx_mute,tx_mute
+Set default mute state of DSP device.
+A value of zero means unmuted.
+A value of one means muted.
+.It Fl m Ar rx_ch,tx_ch,....
+Set default channel mapping of DSP device, as a comma separated list of
+integers.
+The first integer selects the receive channel, the second value selects the
+transmit channel and then it repeats.
+A value of zero indicates the first receive or transmit channel.
+.It Fl C Ar num
+Set the maximum number of mix channels to
+.Fa num .
+.It Fl c Ar num
+Set mix channels for the subsequent commands.
+.It Fl M Ar type,src_ch,dst_ch,pol,mute,log2_gain
+Add a monitoring filter.
+The filter consists of a list of comma separated arguments.
+The first argument indicates the type of monitoring filter:
+.Bl -tag -width indent
+.It i
+Feedback one mix input channel into another mix output channel, for remote
+feedback.
+.It o
+Add one mix output channel into another mix output channel, for creating a mix
+of multiple output channels.
+.It x
+Feedback one mix output channel into another mix input channel, for local
+feedback.
+.El
+The second argument gives the source mix channel.
+The third argument gives the destination mix channel.
+The fourth argument gives the polarity, default is zero.
+The fifth argument gives the mute state, default is one or muted.
+The sixth argument gives the amplitude, default is zero or no gain.
+.It Fl t Ar devname
+Set control device name.
+.It Fl P Ar devname
+Set playback DSP device only.
+Specifying /dev/null is magic and means no playback device.
+Specifying a
+.Xr sndio 7
+device descriptor prefixed by "/dev/sndio/" is also magic, and will use a sndio
+backend rather than an OSS device.
+.It Fl O Ar devname
+Set playback DSP device only which acts as a master device.
+This option is used in conjunction with -R /dev/null .
+.It Fl R Ar devname
+Set recording DSP device only.
+Specifying /dev/null is magic and means no recording device.
+.It Fl f Ar devname
+Set both playback and recording DSP device
+.It Fl w Ar name
+Create a WAV file format compatible companion device by given name.
+This option should be specified before the -d and -l options.
+.It Fl d Ar name
+Create an OSS device by given name.
+.It Fl l Ar name
+Create a loopback OSS device by given name.
+.It Fl L Ar name
+Create a loopback OSS device which acts as a master device.
+This option is used in conjunction with -f /dev/null .
+.It Fl F Ar size
+Set receive filter size in number of samples or <milliseconds>ms for the next
+device to be created.
+.It Fl G Ar size
+Set transmit filter size in number of samples or <milliseconds>ms for the next
+device to be created.
+.It Fl D Ar file
+Write process ID of virtual_oss to file.
+.It Fl g Ar knee,attack,decay
+Enable device compressor in receive direction.
+See description of -x option.
+.It Fl x Ar knee,attack,decay
+Enable output compressor and set knee, attack and decay.
+Knee is in the range 0..255, while attack and decay are between 0 and 62.
+Samples having an absolute value lower than the knee are transmitted
+unchanged.
+Sample values over the knee are lowered "a little bit".
+You can think about attack and decay as a measure of how fast or slow the
+gain of the compressor will work.
+It is advised that attack is low, so it reacts fast once too high
+sample values appear.
+It is also advised that the decay value is higher than the attack value so
+that the gain reduction is gradually removed.
+The reasoning behind this is that the compressor should react almost
+immediately when high volume signals arrive to protect the hardware,
+but it slowly changes gain when there are no loud signals to avoid
+distorting the signal.
+The default values are 85,3,20 .
+.It Fl E Ar enable_recording
+If the value passed is non-zero, recording is enabled.
+Else recording is disabled.
+This can be used to synchronize multiple recording streams.
+.It Fl h
+Show usage and all available options.
+.El
+.Sh EXAMPLES
+Split a 2-channel OSS compatible sound device into multiple subdevices:
+.Bd -literal -offset indent
+virtual_oss \\
+ -S \\
+ -c 2 -r 48000 -b 16 -s 4ms -f /dev/dspX \\
+ -a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.zyn \\
+ -a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.fld \\
+ -a 0 -b 16 -c 2 -m 0,0,1,1 -d dsp \\
+ -a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.jack.wav -d vdsp.jack \\
+ -a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.rec.wav -l vdsp.rec \\
+ -M i,0,0,0,1,0 \\
+ -M i,0,0,0,1,0 \\
+ -M i,0,0,0,1,0 \\
+ -M i,0,0,0,1,0 \\
+ -t vdsp.ctl
+.Ed
+.Pp
+Split an 8-channel 24-bit OSS compatible sound device into multiple subdevices:
+.Bd -literal -offset indent
+sysctl dev.pcm.X.rec.vchanformat=s24le:7.1
+sysctl dev.pcm.X.rec.vchanrate=48000
+sysctl dev.pcm.X.play.vchanformat=s24le:7.1
+sysctl dev.pcm.X.play.vchanrate=48000
+sysctl dev.pcm.X.bitperfect=1
+
+mixer vol.volume=1 pcm.volume=1
+
+virtual_oss \\
+ -S \\
+ -i 8 \\
+ -x 85,3,20 \\
+ -C 16 -c 8 -r 48000 -b 32 -s 4ms -f /dev/dspX \\
+ -a 12 -b 16 -c 2 -m 0,4,1,5 -d dsp \\
+ -a 12 -b 16 -c 2 -m 8,8,9,9 -d vdsp \\
+ -a 13 -b 16 -c 2 -m 10,10,11,11 -d vdsp.fld \\
+ -a 0 -b 32 -c 4 -m 4,2,5,3,6,4,7,5 -d vdsp.jack \\
+ -a -3 -b 32 -c 2 -m 14,14,15,15 -d vdsp.zyn \\
+ -e 0,1 \\
+ -a 0 -b 32 -c 8 -m 0,8,1,9,2,8,3,9,4,8,5,9,6,8,7,9 -w vdsp.rec.mic.wav -d vdsp.rec.mic \\
+ -a 0 -b 32 -c 2 -m 0,8,1,9 -w vdsp.rec.master.wav -d vdsp.master.mic \\
+ -a 0 -b 32 -c 2 -m 10,10,11,11 -w vdsp.rec.fld.wav -l vdsp.rec.fld \\
+ -a 0 -b 32 -c 2 -m 12,12,13,13 -w vdsp.rec.jack.wav -l vdsp.rec.jack \\
+ -a 0 -b 32 -c 2 -m 14,14,15,15 -w vdsp.rec.zyn.wav -l vdsp.rec.zyn \\
+ -M o,8,0,0,0,0 \\
+ -M o,9,1,0,0,0 \\
+ -M o,10,0,0,0,0 \\
+ -M o,11,1,0,0,0 \\
+ -M o,12,0,0,0,0 \\
+ -M o,13,1,0,0,0 \\
+ -M o,14,0,0,0,0 \\
+ -M o,15,1,0,0,0 \\
+ -M i,14,14,0,1,0 \\
+ -M i,15,15,0,1,0 \\
+ -M x,8,0,0,1,0 \\
+ -M x,8,1,0,1,0 \\
+ -t vdsp.ctl
+
+.Ed
+.Pp
+Create a secondary audio device sending its output audio into both
+input and output channels of the main DSP device.
+.Bd -literal -offset indent
+virtual_oss \\
+ -C 4 -c 2 \\
+ -r 48000 \\
+ -b 24 \\
+ -s 4ms \\
+ -f /dev/dsp3 \\
+ -c 2 \\
+ -d dsp \\
+ -m 2,2,3,3 \\
+ -d dsp.speech \\
+ -M o,2,0,0,0,0 \\
+ -M o,3,1,0,0,0 \\
+ -M x,2,0,0,0,0 \\
+ -M x,3,1,0,0,0
+.Ed
+.Pp
+Connect to a bluetooth audio headset, playback only:
+.Bd -literal -offset indent
+virtual_oss \\
+ -C 2 -c 2 -r 48000 -b 16 -s 4ms \\
+ -R /dev/null -P /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp
+.Ed
+.Pp
+Connect to a bluetooth audio headset, playback and recording:
+.Bd -literal -offset indent
+virtual_oss \\
+ -C 2 -c 2 -r 48000 -b 16 -s 4ms \\
+ -f /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp
+.Ed
+.Pp
+Create recording device which outputs a WAV-formatted file:
+.Bd -literal -offset indent
+virtual_oss \\
+ -C 2 -c 2 -r 48000 -b 16 -s 4ms \\
+ -f /dev/dspX -w dsp.wav -d dsp
+.Ed
+.Pp
+Create a device named dsp.virtual which mix the samples written by all
+clients and outputs the result for further processing into
+dsp.virtual_out:
+.Bd -literal -offset indent
+virtual_oss \\
+ -S -Q 0 -b 16 -c 2 -r 96000 -s 100ms -i 20 \\
+ -f /dev/null -d dsp.virtual -L dsp.virtual_out
+.Ed
+.Pp
+Create a playback-only audio device which sends its output to a remote
+.Xr sndio 7
+server:
+.Bd -literal -offset indent
+virtual_oss \\
+ -b 16 -c 2 -r 44100 -s 50ms \\
+ -R /dev/null -O /dev/sndio/snd@remotehost/0 -d dsp
+.Ed
+.Pp
+Create a full-duplex audio device exchanging audio using the default
+.Xr sndio 7
+server:
+.Bd -literal -offset indent
+virtual_oss -S -b 16 -C 2 -c 2 -r 48000 -s 4ms \\
+ -f /dev/sndio/default -d dsp
+.Ed
+.Pp
+How to set intel based CPUs in performance mode:
+.Bd -literal -offset indent
+sysctl -aN | fgrep dev.hwpstate | fgrep epp | \
+while read OID
+do
+sysctl ${OID}=0
+done
+
+sysctl kern.sched.preempt_thresh=224
+.Ed
+.Sh NOTES
+All character devices are created using the 0666 mode which gives
+everyone in the system access.
+.Sh SEE ALSO
+.Xr cuse 3 ,
+.Xr sound 4 ,
+.Xr loader.conf 5 ,
+.Xr sndio 7 ,
+.Xr mixer 8 ,
+.Xr sysctl 8 ,
+.Xr virtual_bt_speaker 8 ,
+.Xr virtual_equalizer 8 ,
+.Xr virtual_oss_cmd 8
+.Sh AUTHORS
+.Nm
+was written by
+.An Hans Petter Selasky hselasky@freebsd.org .
diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c
new file mode 100644
index 000000000000..891653494f06
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c
@@ -0,0 +1,914 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * 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/queue.h>
+#include <sys/types.h>
+#include <sys/soundcard.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <time.h>
+#include <assert.h>
+
+#include "backend.h"
+#include "int.h"
+
+uint64_t
+virtual_oss_delay_ns(void)
+{
+ uint64_t delay;
+
+ delay = voss_dsp_samples;
+ delay *= 1000000000ULL;
+ delay /= voss_dsp_sample_rate;
+
+ return (delay);
+}
+
+void
+virtual_oss_wait(void)
+{
+ struct timespec ts;
+ uint64_t delay;
+ uint64_t nsec;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+
+ /* TODO use virtual_oss_delay_ns() */
+ delay = voss_dsp_samples;
+ delay *= 1000000000ULL;
+ delay /= voss_dsp_sample_rate;
+
+ usleep((delay - (nsec % delay)) / 1000);
+}
+
+uint64_t
+virtual_oss_timestamp(void)
+{
+ struct timespec ts;
+ uint64_t nsec;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+ return (nsec);
+}
+
+static size_t
+vclient_read_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
+ int64_t *dst, size_t total) __requires_exclusive(atomic_mtx)
+{
+ size_t total_read = 0;
+
+ pvc->sync_busy = 1;
+ while (1) {
+ size_t read = vring_read_linear(pvr, (uint8_t *)dst, 8 * total) / 8;
+
+ total_read += read;
+ dst += read;
+ total -= read;
+
+ if (!pvc->profile->synchronized || pvc->sync_wakeup ||
+ total == 0) {
+ /* fill rest of buffer with silence, if any */
+ if (total_read != 0 && total != 0)
+ memset(dst, 0, 8 * total);
+ break;
+ }
+ atomic_wait();
+ }
+ pvc->sync_busy = 0;
+ if (pvc->sync_wakeup)
+ atomic_wakeup();
+
+ vclient_tx_equalizer(pvc, dst - total_read, total_read);
+
+ return (total_read);
+}
+
+static size_t
+vclient_write_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
+ int64_t *src, size_t total) __requires_exclusive(atomic_mtx)
+{
+ size_t total_written = 0;
+
+ vclient_rx_equalizer(pvc, src, total);
+
+ pvc->sync_busy = 1;
+ while (1) {
+ size_t written = vring_write_linear(pvr, (uint8_t *)src, total * 8) / 8;
+
+ total_written += written;
+ src += written;
+ total -= written;
+
+ if (!pvc->profile->synchronized || pvc->sync_wakeup ||
+ total == 0)
+ break;
+ atomic_wait();
+ }
+ pvc->sync_busy = 0;
+ if (pvc->sync_wakeup)
+ atomic_wakeup();
+
+ return (total_written);
+}
+
+static inline void
+virtual_oss_mixer_core_sub(const int64_t *src, int64_t *dst,
+ uint32_t *pnoise, int src_chan, int dst_chan, int num,
+ int64_t volume, int shift, int shift_orig, bool pol,
+ bool assign)
+{
+ if (pol)
+ volume = -volume;
+
+ if (shift < 0) {
+ shift = -shift;
+ while (num--) {
+ if (assign)
+ *dst = (*src * volume) >> shift;
+ else
+ *dst += (*src * volume) >> shift;
+ if (__predict_true(pnoise != NULL))
+ *dst += vclient_noise(pnoise, volume, shift_orig);
+ src += src_chan;
+ dst += dst_chan;
+ }
+ } else {
+ while (num--) {
+ if (assign)
+ *dst = (*src * volume) << shift;
+ else
+ *dst += (*src * volume) << shift;
+ if (__predict_true(pnoise != NULL))
+ *dst += vclient_noise(pnoise, volume, shift_orig);
+ src += src_chan;
+ dst += dst_chan;
+ }
+ }
+}
+
+static inline void
+virtual_oss_mixer_core(const int64_t *src, int64_t *dst,
+ uint32_t *pnoise, int src_chan, int dst_chan, int num,
+ int64_t volume, int shift, int shift_orig, bool pol,
+ bool assign)
+{
+ const uint8_t selector = (shift_orig > 0) + assign * 2;
+
+ /* optimize some cases */
+ switch (selector) {
+ case 0:
+ virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
+ num, volume, shift, shift_orig, pol, false);
+ break;
+ case 1:
+ virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
+ num, volume, shift, shift_orig, pol, false);
+ break;
+ case 2:
+ virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
+ num, volume, shift, shift_orig, pol, true);
+ break;
+ case 3:
+ virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
+ num, volume, shift, shift_orig, pol, true);
+ break;
+ }
+}
+
+void *
+virtual_oss_process(void *arg __unused)
+{
+ vprofile_t *pvp;
+ vclient_t *pvc;
+ vmonitor_t *pvm;
+ struct voss_backend *rx_be = voss_rx_backend;
+ struct voss_backend *tx_be = voss_tx_backend;
+ int rx_fmt;
+ int tx_fmt;
+ int rx_chn;
+ int tx_chn;
+ int off;
+ int src_chans;
+ int dst_chans;
+ int src;
+ int len;
+ int samples;
+ int shift;
+ int shift_orig;
+ int shift_fmt;
+ int buffer_dsp_max_size;
+ int buffer_dsp_half_size;
+ int buffer_dsp_rx_sample_size;
+ int buffer_dsp_rx_size;
+ int buffer_dsp_tx_size_ref;
+ int buffer_dsp_tx_size;
+ uint64_t nice_timeout = 0;
+ uint64_t last_timestamp;
+ int blocks;
+ int volume;
+ int x_off;
+ int x;
+ int y;
+
+ uint8_t *buffer_dsp;
+ int64_t *buffer_monitor;
+ int64_t *buffer_temp;
+ int64_t *buffer_data;
+ int64_t *buffer_local;
+ int64_t *buffer_orig;
+
+ bool need_delay = false;
+
+ buffer_dsp_max_size = voss_dsp_samples *
+ voss_dsp_max_channels * (voss_dsp_bits / 8);
+ buffer_dsp_half_size = (voss_dsp_samples / 2) *
+ voss_dsp_max_channels * (voss_dsp_bits / 8);
+
+ buffer_dsp = malloc(buffer_dsp_max_size);
+ buffer_temp = malloc(voss_dsp_samples * voss_max_channels * 8);
+ buffer_monitor = malloc(voss_dsp_samples * voss_max_channels * 8);
+ buffer_local = malloc(voss_dsp_samples * voss_max_channels * 8);
+ buffer_data = malloc(voss_dsp_samples * voss_max_channels * 8);
+ buffer_orig = malloc(voss_dsp_samples * voss_max_channels * 8);
+
+ if (buffer_dsp == NULL || buffer_temp == NULL ||
+ buffer_monitor == NULL || buffer_local == NULL ||
+ buffer_data == NULL || buffer_orig == NULL)
+ errx(1, "Cannot allocate buffer memory");
+
+ while (1) {
+ rx_be->close(rx_be);
+ tx_be->close(tx_be);
+
+ if (voss_exit)
+ break;
+ if (need_delay)
+ sleep(2);
+
+ voss_dsp_rx_refresh = 0;
+ voss_dsp_tx_refresh = 0;
+
+ rx_be = voss_rx_backend;
+ tx_be = voss_tx_backend;
+
+ switch (voss_dsp_bits) {
+ case 8:
+ rx_fmt = tx_fmt =
+ AFMT_S8 | AFMT_U8;
+ break;
+ case 16:
+ rx_fmt = tx_fmt =
+ AFMT_S16_BE | AFMT_S16_LE |
+ AFMT_U16_BE | AFMT_U16_LE;
+ break;
+ case 24:
+ rx_fmt = tx_fmt =
+ AFMT_S24_BE | AFMT_S24_LE |
+ AFMT_U24_BE | AFMT_U24_LE;
+ break;
+ case 32:
+ rx_fmt = tx_fmt =
+ AFMT_S32_BE | AFMT_S32_LE |
+ AFMT_U32_BE | AFMT_U32_LE |
+ AFMT_F32_BE | AFMT_F32_LE;
+ break;
+ default:
+ rx_fmt = tx_fmt = 0;
+ break;
+ }
+
+ rx_chn = voss_dsp_max_channels;
+
+ if (rx_be->open(rx_be, voss_dsp_rx_device, voss_dsp_sample_rate,
+ buffer_dsp_half_size, &rx_chn, &rx_fmt) < 0) {
+ need_delay = true;
+ continue;
+ }
+
+ buffer_dsp_rx_sample_size = rx_chn * (voss_dsp_bits / 8);
+ buffer_dsp_rx_size = voss_dsp_samples * buffer_dsp_rx_sample_size;
+
+ tx_chn = voss_dsp_max_channels;
+ if (tx_be->open(tx_be, voss_dsp_tx_device, voss_dsp_sample_rate,
+ buffer_dsp_max_size, &tx_chn, &tx_fmt) < 0) {
+ need_delay = true;
+ continue;
+ }
+
+ buffer_dsp_tx_size_ref = voss_dsp_samples *
+ tx_chn * (voss_dsp_bits / 8);
+
+ /* reset compressor gain */
+ for (x = 0; x != VMAX_CHAN; x++)
+ voss_output_compressor_gain[x] = 1.0;
+
+ /* reset local buffer */
+ memset(buffer_local, 0, 8 * voss_dsp_samples * voss_max_channels);
+
+ while (1) {
+ uint64_t delta_time;
+
+ /* Check if DSP device should be re-opened */
+ if (voss_exit)
+ break;
+ if (voss_dsp_rx_refresh || voss_dsp_tx_refresh) {
+ need_delay = false;
+ break;
+ }
+ delta_time = nice_timeout - virtual_oss_timestamp();
+
+ /* Don't service more than 2x sample rate */
+ nice_timeout = virtual_oss_delay_ns() / 2;
+ if (delta_time >= 1000 && delta_time <= nice_timeout) {
+ /* convert from ns to us */
+ usleep(delta_time / 1000);
+ }
+ /* Compute next timeout */
+ nice_timeout += virtual_oss_timestamp();
+
+ /* Read in samples */
+ len = rx_be->transfer(rx_be, buffer_dsp, buffer_dsp_rx_size);
+ if (len < 0 || (len % buffer_dsp_rx_sample_size) != 0) {
+ need_delay = true;
+ break;
+ }
+ if (len == 0)
+ continue;
+
+ /* Convert to 64-bit samples */
+ format_import(rx_fmt, buffer_dsp, len, buffer_data);
+
+ samples = len / buffer_dsp_rx_sample_size;
+ src_chans = voss_mix_channels;
+
+ /* Compute master input peak values */
+ format_maximum(buffer_data, voss_input_peak, rx_chn, samples, 0);
+
+ /* Remix format */
+ format_remix(buffer_data, rx_chn, src_chans, samples);
+
+ /* Refresh timestamp */
+ last_timestamp = virtual_oss_timestamp();
+
+ atomic_lock();
+
+ if (TAILQ_FIRST(&virtual_monitor_input) != NULL) {
+ /* make a copy of the input data, in case of remote monitoring */
+ memcpy(buffer_monitor, buffer_data, 8 * samples * src_chans);
+ }
+
+ /* (0) Check for local monitoring of output data */
+
+ TAILQ_FOREACH(pvm, &virtual_monitor_local, entry) {
+
+ int64_t val;
+
+ if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
+ pvm->dst_chan >= src_chans)
+ continue;
+
+ src = pvm->src_chan;
+ shift = pvm->shift;
+ x = pvm->dst_chan;
+
+ if (pvm->pol) {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_local[(y * src_chans) + src] >> shift);
+ buffer_data[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_local[(y * src_chans) + src] << shift);
+ buffer_data[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ } else {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = (buffer_local[(y * src_chans) + src] >> shift);
+ buffer_data[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = (buffer_local[(y * src_chans) + src] << shift);
+ buffer_data[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ }
+ }
+
+ /* make a copy of the input data */
+ memcpy(buffer_orig, buffer_data, 8 * samples * src_chans);
+
+ /* (1) Distribute input samples to all clients */
+
+ TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
+
+ if (TAILQ_FIRST(&pvp->head) == NULL)
+ continue;
+
+ /* check if compressor should be applied */
+ voss_compressor(buffer_data, pvp->rx_compressor_gain,
+ &pvp->rx_compressor_param, samples * src_chans,
+ src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
+
+ TAILQ_FOREACH(pvc, &pvp->head, entry) {
+
+ dst_chans = pvc->channels;
+
+ if (dst_chans > (int)voss_max_channels)
+ continue;
+
+ shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
+
+ for (x = 0; x != dst_chans; x++) {
+ src = pvp->rx_src[x];
+ shift_orig = pvp->rx_shift[x] - shift_fmt;
+ shift = shift_orig - VVOLUME_UNIT_SHIFT;
+ volume = pvc->rx_volume;
+
+ if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
+ for (y = 0; y != (samples * dst_chans); y += dst_chans)
+ buffer_temp[y + x] = 0;
+ continue;
+ }
+
+ virtual_oss_mixer_core(buffer_data + src, buffer_temp + x,
+ &pvc->rx_noise_rem, src_chans, dst_chans, samples,
+ volume, shift, shift_orig, pvp->rx_pol[x], true);
+ }
+
+ format_maximum(buffer_temp, pvp->rx_peak_value,
+ dst_chans, samples, shift_fmt);
+
+ /* check if recording is disabled */
+ if (pvc->rx_enabled == 0 ||
+ (voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
+ continue;
+
+ pvc->rx_timestamp = last_timestamp;
+ pvc->rx_samples += samples * dst_chans;
+
+ /* store data into ring buffer */
+ vclient_write_linear(pvc, &pvc->rx_ring[0],
+ buffer_temp, samples * dst_chans);
+ }
+
+ /* restore buffer, if any */
+ if (pvp->rx_compressor_param.enabled)
+ memcpy(buffer_data, buffer_orig, 8 * samples * src_chans);
+ }
+
+ /* fill main output buffer with silence */
+
+ memset(buffer_temp, 0, sizeof(buffer_temp[0]) *
+ samples * src_chans);
+
+ /* (2) Run audio delay locator */
+
+ if (voss_ad_enabled != 0) {
+ y = (samples * voss_mix_channels);
+ for (x = 0; x != y; x += voss_mix_channels) {
+ buffer_temp[x + voss_ad_output_channel] +=
+ voss_ad_getput_sample(buffer_data
+ [x + voss_ad_input_channel]);
+ }
+ }
+
+ /* (3) Load output samples from all clients */
+
+ TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
+ TAILQ_FOREACH(pvc, &pvp->head, entry) {
+
+ if (pvc->tx_enabled == 0)
+ continue;
+
+ dst_chans = pvc->channels;
+
+ if (dst_chans > (int)voss_max_channels)
+ continue;
+
+ /* update counters regardless of data presence */
+ pvc->tx_timestamp = last_timestamp;
+ pvc->tx_samples += samples * dst_chans;
+
+ /* read data from ring buffer */
+ if (vclient_read_linear(pvc, &pvc->tx_ring[0],
+ buffer_data, samples * dst_chans) == 0)
+ continue;
+
+ shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
+
+ format_maximum(buffer_data, pvp->tx_peak_value,
+ dst_chans, samples, shift_fmt);
+
+ for (x = 0; x != pvp->channels; x++) {
+ src = pvp->tx_dst[x];
+ shift_orig = pvp->tx_shift[x] + shift_fmt;
+ shift = shift_orig - VVOLUME_UNIT_SHIFT;
+ volume = pvc->tx_volume;
+
+ if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
+ continue;
+
+ /*
+ * Automagically re-map
+ * channels when the client is
+ * requesting fewer channels
+ * than specified in the
+ * profile. This typically
+ * allows automagic mono to
+ * stereo conversion.
+ */
+ if (__predict_false(x >= dst_chans))
+ x_off = x % dst_chans;
+ else
+ x_off = x;
+
+ virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
+ &pvc->tx_noise_rem, dst_chans, src_chans, samples,
+ volume, shift, shift_orig, pvp->tx_pol[x], false);
+ }
+ }
+ }
+
+ /* (4) Load output samples from all loopbacks */
+
+ TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
+ TAILQ_FOREACH(pvc, &pvp->head, entry) {
+
+ if (pvc->tx_enabled == 0)
+ continue;
+
+ dst_chans = pvc->channels;
+
+ if (dst_chans > (int)voss_max_channels)
+ continue;
+
+ /* read data from ring buffer */
+ if (vclient_read_linear(pvc, &pvc->tx_ring[0],
+ buffer_data, samples * dst_chans) == 0)
+ continue;
+
+ pvc->tx_timestamp = last_timestamp;
+ pvc->tx_samples += samples * dst_chans;
+
+ shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
+
+ format_maximum(buffer_data, pvp->tx_peak_value,
+ dst_chans, samples, shift_fmt);
+
+ for (x = 0; x != pvp->channels; x++) {
+ src = pvp->tx_dst[x];
+ shift_orig = pvp->tx_shift[x] + shift_fmt;
+ shift = shift_orig - VVOLUME_UNIT_SHIFT;
+ volume = pvc->tx_volume;
+
+ if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
+ continue;
+
+ /*
+ * Automagically re-map
+ * channels when the client is
+ * requesting fewer channels
+ * than specified in the
+ * profile. This typically
+ * allows automagic mono to
+ * stereo conversion.
+ */
+ if (__predict_false(x >= dst_chans))
+ x_off = x % dst_chans;
+ else
+ x_off = x;
+
+ virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
+ &pvc->tx_noise_rem, dst_chans, src_chans, samples,
+ volume, shift, shift_orig, pvp->tx_pol[x], false);
+ }
+ }
+ }
+
+ /* (5) Check for input monitoring */
+
+ TAILQ_FOREACH(pvm, &virtual_monitor_input, entry) {
+
+ int64_t val;
+
+ if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
+ pvm->dst_chan >= src_chans)
+ continue;
+
+ src = pvm->src_chan;
+ shift = pvm->shift;
+ x = pvm->dst_chan;
+
+ if (pvm->pol) {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_monitor[(y * src_chans) + src] >> shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_monitor[(y * src_chans) + src] << shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ } else {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = (buffer_monitor[(y * src_chans) + src] >> shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = (buffer_monitor[(y * src_chans) + src] << shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ }
+ }
+
+ if (TAILQ_FIRST(&virtual_monitor_output) != NULL) {
+ memcpy(buffer_monitor, buffer_temp,
+ 8 * samples * src_chans);
+ }
+
+ /* (6) Check for output monitoring */
+
+ TAILQ_FOREACH(pvm, &virtual_monitor_output, entry) {
+
+ int64_t val;
+
+ if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
+ pvm->dst_chan >= src_chans)
+ continue;
+
+ src = pvm->src_chan;
+ shift = pvm->shift;
+ x = pvm->dst_chan;
+
+ if (pvm->pol) {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_monitor[(y * src_chans) + src] >> shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = -(buffer_monitor[(y * src_chans) + src] << shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ } else {
+ if (shift < 0) {
+ shift = -shift;
+ for (y = 0; y != samples; y++) {
+ val = (buffer_monitor[(y * src_chans) + src] >> shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ } else {
+ for (y = 0; y != samples; y++) {
+ val = (buffer_monitor[(y * src_chans) + src] << shift);
+ buffer_temp[(y * src_chans) + x] += val;
+ if (val < 0)
+ val = -val;
+ if (val > pvm->peak_value)
+ pvm->peak_value = val;
+ }
+ }
+ }
+ }
+
+ /* make a copy of the output data */
+ memcpy(buffer_data, buffer_temp, 8 * samples * src_chans);
+
+ /* make a copy for local monitoring, if any */
+ if (TAILQ_FIRST(&virtual_monitor_local) != NULL) {
+ const int end = src_chans * (voss_dsp_samples - samples);
+ const int offs = src_chans * samples;
+
+ assert(end >= 0);
+
+ /* shift down samples */
+ for (int xx = 0; xx != end; xx++)
+ buffer_local[xx] = buffer_local[xx + offs];
+ /* copy in new ones */
+ memcpy(buffer_local + end, buffer_temp, 8 * samples * src_chans);
+ }
+
+ /* (7) Check for output recording */
+
+ TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
+
+ if (TAILQ_FIRST(&pvp->head) == NULL)
+ continue;
+
+ /* check if compressor should be applied */
+ voss_compressor(buffer_temp, pvp->rx_compressor_gain,
+ &pvp->rx_compressor_param, samples,
+ samples * src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
+
+ TAILQ_FOREACH(pvc, &pvp->head, entry) {
+
+ dst_chans = pvc->channels;
+
+ if (dst_chans > (int)voss_max_channels)
+ continue;
+
+ shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
+
+ for (x = 0; x != dst_chans; x++) {
+ src = pvp->rx_src[x];
+ shift_orig = pvp->rx_shift[x] - shift_fmt;
+ shift = shift_orig - VVOLUME_UNIT_SHIFT;
+ volume = pvc->rx_volume;
+
+ if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
+ for (y = 0; y != (samples * dst_chans); y += dst_chans)
+ buffer_monitor[y + x] = 0;
+ continue;
+ }
+
+ virtual_oss_mixer_core(buffer_temp + src, buffer_monitor + x,
+ &pvc->rx_noise_rem, src_chans, dst_chans, samples,
+ volume, shift, shift_orig, pvp->rx_pol[x], true);
+ }
+
+ format_maximum(buffer_monitor, pvp->rx_peak_value,
+ dst_chans, samples, shift_fmt);
+
+ /* check if recording is disabled */
+ if (pvc->rx_enabled == 0 ||
+ (voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
+ continue;
+
+ pvc->rx_timestamp = last_timestamp;
+ pvc->rx_samples += samples * dst_chans;
+
+ /* store data into ring buffer */
+ vclient_write_linear(pvc, &pvc->rx_ring[0],
+ buffer_monitor, samples * dst_chans);
+ }
+
+ /* restore buffer, if any */
+ if (pvp->rx_compressor_param.enabled)
+ memcpy(buffer_temp, buffer_data, 8 * samples * src_chans);
+ }
+
+ atomic_wakeup();
+
+ format_remix(buffer_temp, voss_mix_channels, tx_chn, samples);
+
+ /* Compute master output peak values */
+
+ format_maximum(buffer_temp, voss_output_peak,
+ tx_chn, samples, 0);
+
+ /* Apply compressor, if any */
+
+ voss_compressor(buffer_temp, voss_output_compressor_gain,
+ &voss_output_compressor_param, samples * tx_chn,
+ tx_chn, format_max(tx_fmt));
+
+ /* Recompute buffer DSP transmit size according to received number of samples */
+
+ buffer_dsp_tx_size = samples * tx_chn * (voss_dsp_bits / 8);
+
+ /* Export and transmit resulting audio */
+
+ format_export(tx_fmt, buffer_temp, buffer_dsp,
+ buffer_dsp_tx_size);
+
+ atomic_unlock();
+
+ /* Get output delay in bytes */
+ tx_be->delay(tx_be, &blocks);
+
+ /*
+ * Simple fix for jitter: Repeat data when too
+ * little. Skip data when too much. This
+ * should not happen during normal operation.
+ */
+ if (blocks == 0) {
+ blocks = 2; /* buffer is empty */
+ voss_jitter_up++;
+ } else if (blocks >= (3 * buffer_dsp_tx_size_ref)) {
+ blocks = 0; /* too much data */
+ voss_jitter_down++;
+ } else {
+ blocks = 1; /* normal */
+ }
+
+ len = 0;
+ while (blocks--) {
+ off = 0;
+ while (off < (int)buffer_dsp_tx_size) {
+ len = tx_be->transfer(tx_be, buffer_dsp + off,
+ buffer_dsp_tx_size - off);
+ if (len <= 0)
+ break;
+ off += len;
+ }
+ if (len <= 0)
+ break;
+ }
+
+ /* check for error only */
+ if (len < 0) {
+ need_delay = true;
+ break;
+ }
+ }
+ }
+
+ free(buffer_dsp);
+ free(buffer_temp);
+ free(buffer_monitor);
+ free(buffer_local);
+ free(buffer_data);
+ free(buffer_orig);
+
+ return (NULL);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h
new file mode 100644
index 000000000000..616de2e1abd0
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h
@@ -0,0 +1,206 @@
+/*-
+ * Copyright (c) 2012-2022 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifndef _VIRTUAL_OSS_H_
+#define _VIRTUAL_OSS_H_
+
+#include <sys/ioccom.h>
+
+#define VIRTUAL_OSS_NAME_MAX 32
+#define VIRTUAL_OSS_VERSION 0x00010008
+#define VIRTUAL_OSS_OPTIONS_MAX 1024 /* bytes */
+#define VIRTUAL_OSS_FILTER_MAX 65536 /* samples */
+
+#define VIRTUAL_OSS_GET_VERSION _IOR('O', 0, int)
+
+struct virtual_oss_io_info {
+ int number; /* must be first */
+ int channel;
+ char name[VIRTUAL_OSS_NAME_MAX];
+ int bits;
+ int rx_amp;
+ int tx_amp;
+ int rx_chan;
+ int tx_chan;
+ int rx_mute;
+ int tx_mute;
+ int rx_pol;
+ int tx_pol;
+ int rx_delay; /* in samples */
+ int rx_delay_limit; /* in samples */
+};
+
+#define VIRTUAL_OSS_GET_DEV_INFO _IOWR('O', 1, struct virtual_oss_io_info)
+#define VIRTUAL_OSS_SET_DEV_INFO _IOW('O', 2, struct virtual_oss_io_info)
+
+#define VIRTUAL_OSS_GET_LOOP_INFO _IOWR('O', 3, struct virtual_oss_io_info)
+#define VIRTUAL_OSS_SET_LOOP_INFO _IOW('O', 4, struct virtual_oss_io_info)
+
+struct virtual_oss_mon_info {
+ int number;
+ int bits;
+ int src_chan;
+ int dst_chan;
+ int pol;
+ int mute;
+ int amp;
+};
+
+#define VIRTUAL_OSS_GET_INPUT_MON_INFO _IOWR('O', 5, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_INPUT_MON_INFO _IOW('O', 6, struct virtual_oss_mon_info)
+
+#define VIRTUAL_OSS_GET_OUTPUT_MON_INFO _IOWR('O', 7, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_OUTPUT_MON_INFO _IOW('O', 8, struct virtual_oss_mon_info)
+
+#define VIRTUAL_OSS_GET_LOCAL_MON_INFO _IOWR('O', 43, struct virtual_oss_mon_info)
+#define VIRTUAL_OSS_SET_LOCAL_MON_INFO _IOW('O', 44, struct virtual_oss_mon_info)
+
+struct virtual_oss_io_peak {
+ int number; /* must be first */
+ int channel;
+ char name[VIRTUAL_OSS_NAME_MAX];
+ int bits;
+ long long rx_peak_value;
+ long long tx_peak_value;
+};
+
+#define VIRTUAL_OSS_GET_DEV_PEAK _IOWR('O', 9, struct virtual_oss_io_peak)
+#define VIRTUAL_OSS_GET_LOOP_PEAK _IOWR('O', 10, struct virtual_oss_io_peak)
+
+struct virtual_oss_mon_peak {
+ int number;
+ int bits;
+ long long peak_value;
+};
+
+#define VIRTUAL_OSS_GET_INPUT_MON_PEAK _IOWR('O', 11, struct virtual_oss_mon_peak)
+#define VIRTUAL_OSS_GET_OUTPUT_MON_PEAK _IOWR('O', 12, struct virtual_oss_mon_peak)
+#define VIRTUAL_OSS_GET_LOCAL_MON_PEAK _IOWR('O', 45, struct virtual_oss_mon_peak)
+
+#define VIRTUAL_OSS_ADD_INPUT_MON _IOR('O', 13, int)
+#define VIRTUAL_OSS_ADD_OUTPUT_MON _IOR('O', 14, int)
+#define VIRTUAL_OSS_ADD_LOCAL_MON _IOR('O', 46, int)
+
+struct virtual_oss_compressor {
+ int enabled;
+ int knee;
+#define VIRTUAL_OSS_KNEE_MAX 255 /* inclusive */
+#define VIRTUAL_OSS_KNEE_MIN 0
+ int attack;
+#define VIRTUAL_OSS_ATTACK_MAX 62 /* inclusive */
+#define VIRTUAL_OSS_ATTACK_MIN 0
+ int decay;
+#define VIRTUAL_OSS_DECAY_MAX 62 /* inclusive */
+#define VIRTUAL_OSS_DECAY_MIN 0
+ int gain; /* read only */
+#define VIRTUAL_OSS_GAIN_MAX 1000 /* inclusive */
+#define VIRTUAL_OSS_GAIN_MIN 0
+};
+
+#define VIRTUAL_OSS_SET_OUTPUT_LIMIT _IOW('O', 17, struct virtual_oss_compressor)
+#define VIRTUAL_OSS_GET_OUTPUT_LIMIT _IOWR('O', 18, struct virtual_oss_compressor)
+
+struct virtual_oss_io_limit {
+ int number; /* must be first */
+ struct virtual_oss_compressor param;
+};
+
+#define VIRTUAL_OSS_SET_DEV_LIMIT _IOW('O', 19, struct virtual_oss_io_limit)
+#define VIRTUAL_OSS_GET_DEV_LIMIT _IOWR('O', 20, struct virtual_oss_io_limit)
+
+#define VIRTUAL_OSS_SET_LOOP_LIMIT _IOW('O', 21, struct virtual_oss_io_limit)
+#define VIRTUAL_OSS_GET_LOOP_LIMIT _IOWR('O', 22, struct virtual_oss_io_limit)
+
+struct virtual_oss_master_peak {
+ int channel;
+ int bits;
+ long long peak_value;
+};
+
+#define VIRTUAL_OSS_GET_OUTPUT_PEAK _IOWR('O', 23, struct virtual_oss_master_peak)
+#define VIRTUAL_OSS_GET_INPUT_PEAK _IOWR('O', 24, struct virtual_oss_master_peak)
+
+#define VIRTUAL_OSS_SET_RECORDING _IOW('O', 25, int)
+#define VIRTUAL_OSS_GET_RECORDING _IOR('O', 26, int)
+
+struct virtual_oss_audio_delay_locator {
+ int channel_output;
+ int channel_input;
+ int channel_last;
+ int signal_output_level; /* 2**n */
+ int signal_input_delay; /* in samples, roundtrip */
+ int signal_delay_hz; /* in samples, HZ */
+ int locator_enabled;
+};
+
+#define VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR _IOW('O', 27, struct virtual_oss_audio_delay_locator)
+#define VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR _IOR('O', 28, struct virtual_oss_audio_delay_locator)
+#define VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR _IO('O', 29)
+
+struct virtual_oss_midi_delay_locator {
+ int channel_output;
+ int channel_input;
+ int signal_delay;
+ int signal_delay_hz; /* in samples, HZ */
+ int locator_enabled;
+};
+
+#define VIRTUAL_OSS_SET_MIDI_DELAY_LOCATOR _IOW('O', 30, struct virtual_oss_midi_delay_locator)
+#define VIRTUAL_OSS_GET_MIDI_DELAY_LOCATOR _IOR('O', 31, struct virtual_oss_midi_delay_locator)
+#define VIRTUAL_OSS_RST_MIDI_DELAY_LOCATOR _IO('O', 32)
+
+#define VIRTUAL_OSS_ADD_OPTIONS _IOWR('O', 33, char [VIRTUAL_OSS_OPTIONS_MAX])
+
+struct virtual_oss_fir_filter {
+ int number; /* must be first */
+ int channel;
+ int filter_size;
+ double *filter_data;
+};
+
+#define VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER _IOWR('O', 34, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER _IOWR('O', 35, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER _IOWR('O', 36, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER _IOWR('O', 37, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER _IOWR('O', 38, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER _IOWR('O', 39, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER _IOWR('O', 40, struct virtual_oss_fir_filter)
+#define VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER _IOWR('O', 41, struct virtual_oss_fir_filter)
+
+#define VIRTUAL_OSS_GET_SAMPLE_RATE _IOR('O', 42, int)
+
+struct virtual_oss_system_info {
+ unsigned tx_jitter_up;
+ unsigned tx_jitter_down;
+ unsigned sample_rate;
+ unsigned sample_bits;
+ unsigned sample_channels;
+ char rx_device_name[64];
+ char tx_device_name[64];
+};
+
+#define VIRTUAL_OSS_GET_SYSTEM_INFO _IOR('O', 43, struct virtual_oss_system_info)
+
+#endif /* _VIRTUAL_OSS_H_ */
diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile b/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile
new file mode 100644
index 000000000000..b209d3dca068
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile
@@ -0,0 +1,8 @@
+PROG= virtual_oss_cmd
+MAN= ${PROG}.8
+
+SRCS= command.c
+
+CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/command.c b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c
new file mode 100644
index 000000000000..64781992ddfd
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c
@@ -0,0 +1,113 @@
+/*-
+ * Copyright (c) 2021-2022 Hans Petter Selasky
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <sysexits.h>
+#include <stdarg.h>
+#include <fcntl.h>
+
+#include "virtual_oss.h"
+
+static void
+message(const char *fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ va_end(list);
+}
+
+static void
+usage(void)
+{
+ message("Usage: virtual_oss_cmd /dev/vdsp.ctl [command line arguments to pass to virtual_oss]\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char **argv)
+{
+ char options[VIRTUAL_OSS_OPTIONS_MAX] = {};
+ size_t offset = 0;
+ size_t len = VIRTUAL_OSS_OPTIONS_MAX - 1;
+ int fd;
+
+ /* check if no options */
+ if (argc < 2)
+ usage();
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0)
+ errx(EX_SOFTWARE, "Could not open '%s'", argv[1]);
+
+ for (int x = 2; x != argc; x++) {
+ size_t tmp = strlen(argv[x]) + 1;
+ if (tmp > len)
+ errx(EX_SOFTWARE, "Too many options passed");
+ memcpy(options + offset, argv[x], tmp);
+ options[offset + tmp - 1] = ' ';
+ offset += tmp;
+ len -= tmp;
+ }
+
+ if (options[0] == 0) {
+ struct virtual_oss_system_info info;
+ if (ioctl(fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &info) < 0)
+ errx(EX_SOFTWARE, "Cannot get system information");
+
+ info.rx_device_name[sizeof(info.rx_device_name) - 1] = 0;
+ info.tx_device_name[sizeof(info.tx_device_name) - 1] = 0;
+
+ printf("Sample rate: %u Hz\n"
+ "Sample width: %u bits\n"
+ "Sample channels: %u\n"
+ "Output jitter: %u / %u\n"
+ "Input device name: %s\n"
+ "Output device name: %s\n",
+ info.sample_rate,
+ info.sample_bits,
+ info.sample_channels,
+ info.tx_jitter_down,
+ info.tx_jitter_up,
+ info.rx_device_name,
+ info.tx_device_name);
+ } else {
+ /* execute options */
+ if (ioctl(fd, VIRTUAL_OSS_ADD_OPTIONS, options) < 0)
+ errx(EX_SOFTWARE, "One or more invalid options");
+ /* show error, if any */
+ if (options[0] != '\0')
+ errx(EX_SOFTWARE, "%s", options);
+ }
+
+ close(fd);
+ return (0);
+}
diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8 b/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8
new file mode 100644
index 000000000000..a200d88a4a32
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8
@@ -0,0 +1,103 @@
+.\"
+.\" Copyright (c) 2021-2022 Hans Petter Selasky <hselasky@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.
+.\"
+.\"
+.Dd February 12, 2025
+.Dt VIRTUAL_OSS_CMD 8
+.Os
+.Sh NAME
+.Nm virtual_oss_cmd
+.Nd modify a running
+.Xr virtual_oss 8
+instance's options
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+pass additional command line arguments to a running
+.Xr virtual_oss 8
+instance via its control device.
+Supported command line arguments:
+.Bl -tag -width indent
+.It Fl E Ar xxx
+.It Fl F Ar xxx
+.It Fl G Ar xxx
+.It Fl L Ar xxx
+.It Fl M Ar xxx
+.It Fl O Ar xxx
+.It Fl P Ar xxx
+.It Fl R Ar xxx
+.It Fl a Ar xxx
+.It Fl b Ar xxx
+.It Fl c Ar xxx
+.It Fl d Ar xxx
+.It Fl e Ar xxx
+.It Fl f Ar xxx
+.It Fl l Ar xxx
+.It Fl m Ar xxx
+.It Fl p Ar xxx
+.It Fl s Ar xxx
+.It Fl w Ar xxx
+.El
+.Pp
+Refer to
+.Xr virtual_oss 8
+for a detailed description of the command line arguments.
+.Sh EXAMPLES
+To change the recording device:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl -R /dev/dsp4
+
+.Ed
+To change the playback device:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl -P /dev/dsp4
+
+.Ed
+To enable recording:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl -E 1
+
+.Ed
+To disable recording:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl -E 0
+
+.Ed
+To create a new DSP device on the fly:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl -b 16 -c 2 -d dsp.new
+
+.Ed
+To show system information:
+.Bd -literal -offset indent
+virtual_oss_cmd /dev/vdsp.ctl
+
+.Ed
+.Sh SEE ALSO
+.Xr virtual_oss 8
+.Sh AUTHORS
+.Nm
+was written by
+.An Hans Petter Selasky hselasky@freebsd.org .