diff options
Diffstat (limited to 'usr.sbin/virtual_oss')
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 . |