diff --git a/lib/Makefile b/lib/Makefile --- a/lib/Makefile +++ b/lib/Makefile @@ -115,7 +115,8 @@ libz \ libzstd \ ncurses \ - nss_tacplus + nss_tacplus \ + virtual_oss # Inter-library dependencies. When the makefile for a library contains LDADD # libraries, those libraries should be listed as build order dependencies here. diff --git a/lib/virtual_oss/Makefile b/lib/virtual_oss/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/Makefile @@ -0,0 +1,14 @@ +.include + +.if !defined(COMPAT_libcompat) +SHLIBDIR=/lib/virtual_oss +.else +SHLIBDIR=/usr/lib${COMPAT_libcompat}/virtual_oss +.endif + +SUBDIR+= bt \ + null \ + oss + +.include "Makefile.inc" +.include diff --git a/lib/virtual_oss/Makefile.inc b/lib/virtual_oss/Makefile.inc new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/lib/virtual_oss/bt/Makefile b/lib/virtual_oss/bt/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/Makefile @@ -0,0 +1,17 @@ +SHLIB_NAME= voss_bt.so + +SRCS= bt.c \ + avdtp.c \ + sbc_encode.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I${SRCTOP}/contrib/libsamplerate +LDFLAGS+= -lbluetooth -lsdp +LIBADD= samplerate + +.if defined(HAVE_FFMPEG) +CFLAGS+= -DHAVE_FFMPEG +LDFLAGS+= -lavdevice -lavutil -lavcodec -lavresample -lavformat +.endif + +.include diff --git a/lib/virtual_oss/bt/avdtp.c b/lib/virtual_oss/bt/avdtp.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/avdtp.c @@ -0,0 +1,720 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015-2016 Nathanial Sloss + * Copyright (c) 2016-2019 Hans Petter Selasky + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 + +#include +#include +#include +#include +#include + +#include "avdtp_signal.h" +#include "bt.h" + +#define DPRINTF(...) printf("backend_bt: " __VA_ARGS__) + +struct avdtpGetPacketInfo { + uint8_t buffer_data[512]; + uint16_t buffer_len; + uint8_t trans; + uint8_t signalID; +}; + +static int avdtpAutoConfig(struct bt_config *); + +/* Return received message type if success, < 0 if failure. */ +static int +avdtpGetPacket(int fd, struct avdtpGetPacketInfo *info) +{ + uint8_t *pos = info->buffer_data; + uint8_t *end = info->buffer_data + sizeof(info->buffer_data); + uint8_t message_type; + int len; + + memset(info, 0, sizeof(*info)); + + /* Handle fragmented packets */ + for (int remaining = 1; remaining > 0; --remaining) { + len = read(fd, pos, end - pos); + + if (len < AVDTP_LEN_SUCCESS) + return (-1); + if (len == (int)(end - pos)) + return (-1); /* buffer too small */ + + uint8_t trans = (pos[0] & TRANSACTIONLABEL) >> TRANSACTIONLABEL_S; + uint8_t packet_type = (pos[0] & PACKETTYPE) >> PACKETTYPE_S; + uint8_t current_message_type = (info->buffer_data[0] & MESSAGETYPE); + uint8_t shift; + if (pos == info->buffer_data) { + info->trans = trans; + message_type = current_message_type; + if (packet_type == singlePacket) { + info->signalID = (pos[1] & SIGNALID_MASK); + shift = 2; + } else { + if (packet_type != startPacket) + return (-1); + remaining = pos[1]; + info->signalID = (pos[2] & SIGNALID_MASK); + shift = 3; + } + } else { + if (info->trans != trans || + message_type != current_message_type || + (remaining == 1 && packet_type != endPacket) || + (remaining > 1 && packet_type != continuePacket)) { + return (-1); + } + shift = 1; + } + memmove(pos, pos + shift, len); + pos += len; + } + info->buffer_len = pos - info->buffer_data; + return (message_type); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpSendPacket(int fd, uint8_t command, uint8_t trans, uint8_t type, + uint8_t * data0, int datasize0, uint8_t * data1, + int datasize1) +{ + struct iovec iov[3]; + uint8_t header[2]; + int retval; + + /* fill out command header */ + header[0] = (trans << 4) | (type & 3); + if (command != 0) + header[1] = command & 0x3f; + else + header[1] = 3; + + iov[0].iov_base = header; + iov[0].iov_len = 2; + iov[1].iov_base = data0; + iov[1].iov_len = datasize0; + iov[2].iov_base = data1; + iov[2].iov_len = datasize1; + + retval = writev(fd, iov, 3); + if (retval != (2 + datasize0 + datasize1)) + return (-EINVAL); + else + return (0); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpSendSyncCommand(int fd, struct avdtpGetPacketInfo *info, + uint8_t command, uint8_t type, uint8_t * data0, + int datasize0, uint8_t * data1, int datasize1) +{ + static uint8_t transLabel; + uint8_t trans; + int retval; + + alarm(8); /* set timeout */ + + trans = (transLabel++) & 0xF; + + retval = avdtpSendPacket(fd, command, trans, type, + data0, datasize0, data1, datasize1); + if (retval) + goto done; +retry: + switch (avdtpGetPacket(fd, info)) { + case RESPONSEACCEPT: + if (info->trans != trans) + goto retry; + retval = 0; + break; + case RESPONSEREJECT: + if (info->trans != trans) + goto retry; + retval = -EINVAL; + break; + case COMMAND: + retval = avdtpSendReject(fd, info->trans, info->signalID); + if (retval == 0) + goto retry; + break; + default: + retval = -ENXIO; + break; + } +done: + alarm(0); /* clear timeout */ + + return (retval); +} + +/* + * Variant for acceptor role: We support any frequency, blocks, bands, and + * allocation. Returns 0 on success, < 0 on failure. + */ +static int +avdtpSendCapabilitiesResponseSBCForACP(int fd, int trans) +{ + uint8_t data[10]; + + data[0] = mediaTransport; + data[1] = 0; + data[2] = mediaCodec; + data[3] = 0x6; + data[4] = mediaTypeAudio; + data[5] = SBC_CODEC_ID; + data[6] = + (1 << (3 - MODE_STEREO)) | + (1 << (3 - MODE_JOINT)) | + (1 << (3 - MODE_DUAL)) | + (1 << (3 - MODE_MONO)) | + (1 << (7 - FREQ_44_1K)) | + (1 << (7 - FREQ_48K)) | + (1 << (7 - FREQ_32K)) | + (1 << (7 - FREQ_16K)); + data[7] = + (1 << (7 - BLOCKS_4)) | + (1 << (7 - BLOCKS_8)) | + (1 << (7 - BLOCKS_12)) | + (1 << (7 - BLOCKS_16)) | + (1 << (3 - BANDS_4)) | + (1 << (3 - BANDS_8)) | (1 << ALLOC_LOUDNESS) | (1 << ALLOC_SNR); + data[8] = MIN_BITPOOL; + data[9] = DEFAULT_MAXBPOOL; + + return (avdtpSendPacket(fd, AVDTP_GET_CAPABILITIES, trans, + RESPONSEACCEPT, data, sizeof(data), NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendAccept(int fd, uint8_t trans, uint8_t myCommand) +{ + return (avdtpSendPacket(fd, myCommand, trans, RESPONSEACCEPT, + NULL, 0, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendReject(int fd, uint8_t trans, uint8_t myCommand) +{ + uint8_t value = 0; + + return (avdtpSendPacket(fd, myCommand, trans, RESPONSEREJECT, + &value, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendDiscResponseAudio(int fd, uint8_t trans, + uint8_t mySep, uint8_t is_sink) +{ + uint8_t data[2]; + + data[0] = mySep << 2; + data[1] = mediaTypeAudio << 4 | (is_sink ? (1 << 3) : 0); + + return (avdtpSendPacket(fd, AVDTP_DISCOVER, trans, RESPONSEACCEPT, + data, 2, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpDiscoverAndConfig(struct bt_config *cfg, bool isSink) +{ + struct avdtpGetPacketInfo info; + uint16_t offset; + uint8_t chmode = cfg->chmode; + uint8_t aacMode1 = cfg->aacMode1; + uint8_t aacMode2 = cfg->aacMode2; + int retval; + + retval = avdtpSendSyncCommand(cfg->hc, &info, AVDTP_DISCOVER, 0, + NULL, 0, NULL, 0); + if (retval) + return (retval); + + retval = -EBUSY; + for (offset = 0; offset + 2 <= info.buffer_len; offset += 2) { + cfg->sep = info.buffer_data[offset] >> 2; + cfg->media_Type = info.buffer_data[offset + 1] >> 4; + cfg->chmode = chmode; + cfg->aacMode1 = aacMode1; + cfg->aacMode2 = aacMode2; + if (info.buffer_data[offset] & DISCOVER_SEP_IN_USE) + continue; + if (info.buffer_data[offset + 1] & DISCOVER_IS_SINK) { + if (!isSink) + continue; + } else { + if (isSink) + continue; + } + /* try to configure SBC */ + retval = avdtpAutoConfig(cfg); + if (retval == 0) + return (0); + } + return (retval); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpGetCapabilities(int fd, uint8_t sep, struct avdtpGetPacketInfo *info) +{ + uint8_t address = (sep << 2); + + return (avdtpSendSyncCommand(fd, info, + AVDTP_GET_CAPABILITIES, 0, &address, 1, + NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSetConfiguration(int fd, uint8_t sep, uint8_t * data, int datasize) +{ + struct avdtpGetPacketInfo info; + uint8_t configAddresses[2]; + + configAddresses[0] = sep << 2; + configAddresses[1] = INTSEP << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_SET_CONFIGURATION, 0, + configAddresses, 2, data, datasize)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpOpen(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_OPEN, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpStart(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_START, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpClose(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_CLOSE, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSuspend(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_SUSPEND, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpAbort(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_ABORT, 0, + &address, 1, NULL, 0)); +} + +static int +avdtpAutoConfig(struct bt_config *cfg) +{ + struct avdtpGetPacketInfo info; + uint8_t freqmode; + uint8_t blk_len_sb_alloc; + uint8_t availFreqMode = 0; + uint8_t availConfig = 0; + uint8_t supBitpoolMin = 0; + uint8_t supBitpoolMax = 0; + uint8_t aacMode1 = 0; + uint8_t aacMode2 = 0; +#ifdef HAVE_FFMPEG + uint8_t aacBitrate3 = 0; + uint8_t aacBitrate4 = 0; + uint8_t aacBitrate5 = 0; +#endif + int retval; + int i; + + retval = avdtpGetCapabilities(cfg->hc, cfg->sep, &info); + if (retval) { + DPRINTF("Cannot get capabilities\n"); + return (retval); + } +retry: + for (i = 0; (i + 1) < info.buffer_len;) { +#if 0 + DPRINTF("0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", + info.buffer_data[i + 0], + info.buffer_data[i + 1], + info.buffer_data[i + 2], + info.buffer_data[i + 3], + info.buffer_data[i + 4], info.buffer_data[i + 5]); +#endif + if (i + 2 + info.buffer_data[i + 1] > info.buffer_len) + break; + switch (info.buffer_data[i]) { + case mediaTransport: + break; + case mediaCodec: + if (info.buffer_data[i + 1] < 2) + break; + /* check codec */ + switch (info.buffer_data[i + 3]) { + case 0: /* SBC */ + if (info.buffer_data[i + 1] < 6) + break; + availFreqMode = info.buffer_data[i + 4]; + availConfig = info.buffer_data[i + 5]; + supBitpoolMin = info.buffer_data[i + 6]; + supBitpoolMax = info.buffer_data[i + 7]; + break; + case 2: /* MPEG2/4 AAC */ + if (info.buffer_data[i + 1] < 8) + break; + aacMode1 = info.buffer_data[i + 5]; + aacMode2 = info.buffer_data[i + 6]; +#ifdef HAVE_FFMPEG + aacBitrate3 = info.buffer_data[i + 7]; + aacBitrate4 = info.buffer_data[i + 8]; + aacBitrate5 = info.buffer_data[i + 9]; +#endif + break; + default: + break; + } + } + /* jump to next information element */ + i += 2 + info.buffer_data[i + 1]; + } + aacMode1 &= cfg->aacMode1; + aacMode2 &= cfg->aacMode2; + + /* Try AAC first */ + if (aacMode1 == cfg->aacMode1 && aacMode2 == cfg->aacMode2) { +#ifdef HAVE_FFMPEG + uint8_t config[12] = { mediaTransport, 0x0, mediaCodec, + 0x8, 0x0, 0x02, 0x80, aacMode1, aacMode2, aacBitrate3, + aacBitrate4, aacBitrate5 + }; + + if (avdtpSetConfiguration + (cfg->hc, cfg->sep, config, sizeof(config)) == 0) { + cfg->codec = CODEC_AAC; + return (0); + } +#endif + } + /* Try SBC second */ + if (cfg->freq == FREQ_UNDEFINED) + goto auto_config_failed; + + freqmode = (1 << (3 - cfg->freq + 4)) | (1 << (3 - cfg->chmode)); + + if ((availFreqMode & freqmode) != freqmode) { + DPRINTF("No frequency and mode match\n"); + goto auto_config_failed; + } + for (i = 0; i != 4; i++) { + blk_len_sb_alloc = (1 << (i + 4)) | + (1 << (1 - cfg->bands + 2)) | (1 << cfg->allocm); + + if ((availConfig & blk_len_sb_alloc) == blk_len_sb_alloc) + break; + } + if (i == 4) { + DPRINTF("No bands available\n"); + goto auto_config_failed; + } + cfg->blocks = (3 - i); + + if (cfg->allocm == ALLOC_SNR) + supBitpoolMax &= ~1; + + if (cfg->chmode == MODE_DUAL || cfg->chmode == MODE_MONO) + supBitpoolMax /= 2; + + if (cfg->bands == BANDS_4) + supBitpoolMax /= 2; + + if (supBitpoolMax > cfg->bitpool) + supBitpoolMax = cfg->bitpool; + else + cfg->bitpool = supBitpoolMax; + + do { + uint8_t config[10] = { mediaTransport, 0x0, mediaCodec, 0x6, + 0x0, 0x0, freqmode, blk_len_sb_alloc, supBitpoolMin, + supBitpoolMax + }; + + if (avdtpSetConfiguration + (cfg->hc, cfg->sep, config, sizeof(config)) == 0) { + cfg->codec = CODEC_SBC; + return (0); + } + } while (0); + +auto_config_failed: + if (cfg->chmode == MODE_STEREO) { + cfg->chmode = MODE_MONO; + cfg->aacMode2 ^= 0x0C; + goto retry; + } + return (-EINVAL); +} + +void +avdtpACPFree(struct bt_config *cfg) +{ + if (cfg->handle.sbc_enc) { + free(cfg->handle.sbc_enc); + cfg->handle.sbc_enc = NULL; + } +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpParseSBCConfig(uint8_t * data, struct bt_config *cfg) +{ + if (data[0] & (1 << (7 - FREQ_48K))) { + cfg->freq = FREQ_48K; + } else if (data[0] & (1 << (7 - FREQ_44_1K))) { + cfg->freq = FREQ_44_1K; + } else if (data[0] & (1 << (7 - FREQ_32K))) { + cfg->freq = FREQ_32K; + } else if (data[0] & (1 << (7 - FREQ_16K))) { + cfg->freq = FREQ_16K; + } else { + return -EINVAL; + } + + if (data[0] & (1 << (3 - MODE_STEREO))) { + cfg->chmode = MODE_STEREO; + } else if (data[0] & (1 << (3 - MODE_JOINT))) { + cfg->chmode = MODE_JOINT; + } else if (data[0] & (1 << (3 - MODE_DUAL))) { + cfg->chmode = MODE_DUAL; + } else if (data[0] & (1 << (3 - MODE_MONO))) { + cfg->chmode = MODE_MONO; + } else { + return -EINVAL; + } + + if (data[1] & (1 << (7 - BLOCKS_16))) { + cfg->blocks = BLOCKS_16; + } else if (data[1] & (1 << (7 - BLOCKS_12))) { + cfg->blocks = BLOCKS_12; + } else if (data[1] & (1 << (7 - BLOCKS_8))) { + cfg->blocks = BLOCKS_8; + } else if (data[1] & (1 << (7 - BLOCKS_4))) { + cfg->blocks = BLOCKS_4; + } else { + return -EINVAL; + } + + if (data[1] & (1 << (3 - BANDS_8))) { + cfg->bands = BANDS_8; + } else if (data[1] & (1 << (3 - BANDS_4))) { + cfg->bands = BANDS_4; + } else { + return -EINVAL; + } + + if (data[1] & (1 << ALLOC_LOUDNESS)) { + cfg->allocm = ALLOC_LOUDNESS; + } else if (data[1] & (1 << ALLOC_SNR)) { + cfg->allocm = ALLOC_SNR; + } else { + return -EINVAL; + } + cfg->bitpool = data[3]; + return 0; +} + +int +avdtpACPHandlePacket(struct bt_config *cfg) +{ + struct avdtpGetPacketInfo info; + int retval; + + if (avdtpGetPacket(cfg->hc, &info) != COMMAND) + return (-ENXIO); + + switch (info.signalID) { + case AVDTP_DISCOVER: + retval = + avdtpSendDiscResponseAudio(cfg->hc, info.trans, ACPSEP, 1); + if (!retval) + retval = AVDTP_DISCOVER; + break; + case AVDTP_GET_CAPABILITIES: + retval = + avdtpSendCapabilitiesResponseSBCForACP(cfg->hc, info.trans); + if (!retval) + retval = AVDTP_GET_CAPABILITIES; + break; + case AVDTP_SET_CONFIGURATION: + if (cfg->acceptor_state != acpInitial) + goto err; + cfg->sep = info.buffer_data[1] >> 2; + int is_configured = 0; + for (int i = 2; (i + 1) < info.buffer_len;) { + if (i + 2 + info.buffer_data[i + 1] > info.buffer_len) + break; + switch (info.buffer_data[i]) { + case mediaTransport: + break; + case mediaCodec: + if (info.buffer_data[i + 1] < 2) + break; + /* check codec */ + switch (info.buffer_data[i + 3]) { + case 0: /* SBC */ + if (info.buffer_data[i + 1] < 6) + break; + retval = + avdtpParseSBCConfig(info.buffer_data + i + 4, cfg); + if (retval) + return retval; + is_configured = 1; + break; + case 2: /* MPEG2/4 AAC */ + /* TODO: Add support */ + default: + break; + } + } + /* jump to next information element */ + i += 2 + info.buffer_data[i + 1]; + } + if (!is_configured) + goto err; + + retval = + avdtpSendAccept(cfg->hc, info.trans, AVDTP_SET_CONFIGURATION); + if (retval) + return (retval); + + /* TODO: Handle other codecs */ + if (cfg->handle.sbc_enc == NULL) { + cfg->handle.sbc_enc = malloc(sizeof(*cfg->handle.sbc_enc)); + if (cfg->handle.sbc_enc == NULL) + return (-ENOMEM); + } + memset(cfg->handle.sbc_enc, 0, sizeof(*cfg->handle.sbc_enc)); + + retval = AVDTP_SET_CONFIGURATION; + cfg->acceptor_state = acpConfigurationSet; + break; + case AVDTP_OPEN: + if (cfg->acceptor_state != acpConfigurationSet) + goto err; + retval = avdtpSendAccept(cfg->hc, info.trans, info.signalID); + if (retval) + return (retval); + retval = info.signalID; + cfg->acceptor_state = acpStreamOpened; + break; + case AVDTP_START: + if (cfg->acceptor_state != acpStreamOpened && + cfg->acceptor_state != acpStreamSuspended) { + goto err; + } + retval = avdtpSendAccept(cfg->hc, info.trans, info.signalID); + if (retval) + return retval; + retval = info.signalID; + cfg->acceptor_state = acpStreamStarted; + break; + case AVDTP_CLOSE: + if (cfg->acceptor_state != acpStreamOpened && + cfg->acceptor_state != acpStreamStarted && + cfg->acceptor_state != acpStreamSuspended) { + goto err; + } + retval = avdtpSendAccept(cfg->hc, info.trans, info.signalID); + if (retval) + return (retval); + retval = info.signalID; + cfg->acceptor_state = acpStreamClosed; + break; + case AVDTP_SUSPEND: + if (cfg->acceptor_state != acpStreamOpened && + cfg->acceptor_state != acpStreamStarted) { + goto err; + } + retval = avdtpSendAccept(cfg->hc, info.trans, info.signalID); + if (retval) + return (retval); + retval = info.signalID; + cfg->acceptor_state = acpStreamSuspended; + break; + case AVDTP_GET_CONFIGURATION: + case AVDTP_RECONFIGURE: + case AVDTP_ABORT: + /* TODO: Implement this. */ + default: +err: + avdtpSendReject(cfg->hc, info.trans, info.signalID); + return (-ENXIO); + } + return (retval); +} diff --git a/lib/virtual_oss/bt/avdtp_signal.h b/lib/virtual_oss/bt/avdtp_signal.h new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/avdtp_signal.h @@ -0,0 +1,139 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 _AVDTP_SIGNAL_H_ +#define _AVDTP_SIGNAL_H_ + +#include +#include + +/* Our endpoint. */ +#define INTSEP 8 +#define ACPSEP 8 + +/* AVDTP signals. */ + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0a +#define AVDTP_SECUURITY_CONTROL 0x0b + +/* Signal Command & Response Header Masks. */ + +#define TRANSACTIONLABEL 0xf0 +#define TRANSACTIONLABEL_S 4 +#define SIGNALID_MASK 0x3f +#define PACKETTYPE 0x0c +#define PACKETTYPE_S 0x02 +#define MESSAGETYPE 0x03 +#define SIGNALIDENTIFIER 0x3f +#define DISCOVER_SEP_IN_USE 0x02 +#define DISCOVER_IS_SINK 0x08 + +/* Packet Types */ +#define singlePacket 0x0 +#define startPacket 0x1 +#define continuePacket 0x2 +#define endPacket 0x3 + +/* Message Types */ +#define COMMAND 0x0 +#define RESPONSEACCEPT 0x2 +#define RESPONSEREJECT 0x3 + +/* Response general error/success lengths */ +#define AVDTP_LEN_SUCCESS 2 +#define AVDTP_LEN_ERROR 3 + +/* Error codes */ +#define BAD_HEADER_FORMAT 0x01 +#define BAD_LENGTH 0x11 +#define BAD_ACP_SEID 0x12 +#define SEP_IN_USE 0x13 +#define SEP_NOT_IN_USE 0x14 +#define BAD_SERV_CATAGORY 0x17 +#define BAD_PAYLOAD_FORMAT 0x18 +#define NOT_SUPPORTED_COMMAND 0x19 +#define INVALID_CAPABILITIES 0x1a + +#define BAD_RECOVERY_TYPE 0x22 +#define BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define BAD_RECOVERY_FORMAT 0x25 +#define BAD_ROHC_FORMAT 0x26 +#define BAD_CP_FORMAT 0x27 +#define BAD_MULTIPLEXING_FORMAT 0x28 +#define UNSUPPORTED_CONFIGURATION 0x29 +#define BAD_STATE 0x31 + +/* Service Capabilities Field. */ +#define mediaTransport 0x1 +#define reporting 0x2 +#define recovery 0x3 +#define contentProtection 0x4 +#define headerCompression 0x5 +#define multiplexing 0x6 +#define mediaCodec 0x7 + +/* Media Codec Capabilities */ +#define mediaCodecSbc 0x00 +#define mediaCodecMpeg1 0x01 +#define mediaCodecMpeg2 0x02 + +#define SBC_CODEC_ID 0x0 +#define mediaTypeAudio 0x0 + +struct bt_config; + +int avdtpSendAccept(int, uint8_t, uint8_t); +int avdtpSendReject(int, uint8_t, uint8_t); +int avdtpSendDiscResponseAudio(int, uint8_t, uint8_t, uint8_t); +int avdtpDiscoverAndConfig(struct bt_config *, bool); +int avdtpSetConfiguration(int, uint8_t, uint8_t *, int); +int avdtpOpen(int, uint8_t); +int avdtpStart(int, uint8_t); +int avdtpClose(int, uint8_t); +int avdtpSuspend(int, uint8_t); +int avdtpAbort(int, uint8_t); + +/* Return < 0 if error, processed signal otherwise. */ +int avdtpACPHandlePacket(struct bt_config *cfg); +/* Free state allocated in avdtpACPHandlePacket(), if any. */ +void avdtpACPFree(struct bt_config *cfg); + +#endif /* _AVDTP_SIGNAL_H_ */ diff --git a/lib/virtual_oss/bt/bt.h b/lib/virtual_oss/bt/bt.h new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/bt.h @@ -0,0 +1,116 @@ +/*- + * 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 _BACKEND_BT_H_ +#define _BACKEND_BT_H_ + +#ifdef HAVE_FFMPEG +#include +#include +#include +#endif + +#include "sbc_encode.h" + +struct bt_config { + uint8_t sep; /* SEID of the peer */ + uint8_t media_Type; + uint8_t chmode; +#define MODE_STEREO 2 +#define MODE_JOINT 3 +#define MODE_DUAL 1 +#define MODE_MONO 0 + uint8_t allocm; +#define ALLOC_LOUDNESS 0 +#define ALLOC_SNR 1 + uint8_t bitpool; + uint8_t bands; +#define BANDS_4 0 +#define BANDS_8 1 + uint8_t blocks; +#define BLOCKS_4 0 +#define BLOCKS_8 1 +#define BLOCKS_12 2 +#define BLOCKS_16 3 + uint8_t freq; +#define FREQ_UNDEFINED 255 +#define FREQ_16K 0 +#define FREQ_32K 1 +#define FREQ_44_1K 2 +#define FREQ_48K 3 + uint16_t mtu; + uint8_t codec; +#define CODEC_SBC 0x00 +#define CODEC_AAC 0x02 + uint8_t aacMode1; + uint8_t aacMode2; + + /* transcoding handle(s) */ + union { +#ifdef HAVE_FFMPEG + struct { + AVCodec *codec; + AVCodecContext *context; + AVFormatContext *format; + AVFrame *frame; + AVStream *stream; + } av; +#endif + struct sbc_encode *sbc_enc; + } handle; + + /* audio input buffer */ + uint32_t rem_in_len; + uint32_t rem_in_size; + uint8_t *rem_in_data; + + /* data transport */ + uint32_t mtu_seqnumber; + uint32_t mtu_timestamp; + uint32_t mtu_offset; + + /* bluetooth file handles */ + int fd; + int hc; + + /* scratch buffer */ + uint8_t mtu_data[65536]; + + /* acceptor state */ + int8_t acceptor_state; +#define acpInitial 1 +#define acpConfigurationSet 2 +#define acpStreamOpened 3 +#define acpStreamStarted 4 +#define acpStreamSuspended 5 +#define acpStreamClosed 6 +}; + +size_t sbc_encode_frame(struct bt_config *); +size_t sbc_decode_frame(struct bt_config *, int); + +int bt_receive(struct bt_config *cfg, void *ptr, int len, int use_delay); + +#endif /* _BACKEND_BT_H_ */ diff --git a/lib/virtual_oss/bt/bt.c b/lib/virtual_oss/bt/bt.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/bt.c @@ -0,0 +1,1074 @@ +/*- + * Copyright (c) 2015-2019 Hans Petter Selasky + * Copyright (c) 2015 Nathanial Sloss + * Copyright (c) 2006 Itronix Inc + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#define L2CAP_SOCKET_CHECKED +#include +#include + +#include "backend.h" +#include "int.h" + +#include "avdtp_signal.h" +#include "bt.h" + +#define DPRINTF(...) printf("backend_bt: " __VA_ARGS__) + +struct l2cap_info { + bdaddr_t laddr; + bdaddr_t raddr; +}; + +static struct bt_config bt_play_cfg; +static struct bt_config bt_rec_cfg; + +int +bt_receive(struct bt_config *cfg, void *ptr, int len, int use_delay) +{ + struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data; + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t *tmp = ptr; + int old_len = len; + int delta; + int err; + + /* wait for service interval, if any */ + if (use_delay) + virtual_oss_wait(); + + switch (cfg->blocks) { + case BLOCKS_4: + sbc->blocks = 4; + break; + case BLOCKS_8: + sbc->blocks = 8; + break; + case BLOCKS_12: + sbc->blocks = 12; + break; + default: + sbc->blocks = 16; + break; + } + + switch (cfg->bands) { + case BANDS_4: + sbc->bands = 4; + break; + default: + sbc->bands = 8; + break; + } + + if (cfg->chmode != MODE_MONO) { + sbc->channels = 2; + } else { + sbc->channels = 1; + } + + while (1) { + delta = len & ~1; + if (delta > (int)(2 * sbc->rem_len)) + delta = (2 * sbc->rem_len); + + /* copy out samples, if any */ + memcpy(tmp, (char *)sbc->music_data + sbc->rem_off, delta); + tmp += delta; + len -= delta; + sbc->rem_off += delta / 2; + sbc->rem_len -= delta / 2; + if (len == 0) + break; + + if (sbc->rem_len == 0 && + sbc->rem_data_frames != 0) { + err = sbc_decode_frame(cfg, sbc->rem_data_len * 8); + sbc->rem_data_frames--; + sbc->rem_data_ptr += err; + sbc->rem_data_len -= err; + continue; + } + /* TODO: Support fragmented SBC frames */ + err = read(cfg->fd, cfg->mtu_data, cfg->mtu); + + if (err == 0) { + break; + } else if (err < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + else + return (-1); /* disconnected */ + } + + /* verify RTP header */ + if (err < (int)sizeof(*phdr) || phdr->id != 0x80) + continue; + + sbc->rem_data_frames = phdr->numFrames; + sbc->rem_data_ptr = (uint8_t *)(phdr + 1); + sbc->rem_data_len = err - sizeof(*phdr); + } + return (old_len - len); +} + +static int +bt_set_format(int *format) +{ + int value; + + value = *format & AFMT_S16_NE; + if (value != 0) { + *format = value; + return (0); + } + return (-1); +} + +static void +bt_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + if (cfg->hc > 0) { + avdtpAbort(cfg->hc, cfg->sep); + avdtpClose(cfg->hc, cfg->sep); + close(cfg->hc); + cfg->hc = -1; + } + if (cfg->fd > 0) { + close(cfg->fd); + cfg->fd = -1; + } +} + +static void +bt_play_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + if (cfg->handle.sbc_enc == NULL) + break; + free(cfg->handle.sbc_enc); + cfg->handle.sbc_enc = NULL; + break; +#ifdef HAVE_FFMPEG + case CODEC_AAC: + if (cfg->handle.av.context == NULL) + break; + av_free(cfg->rem_in_data); + av_frame_free(&cfg->handle.av.frame); + avcodec_close(cfg->handle.av.context); + avformat_free_context(cfg->handle.av.format); + cfg->handle.av.context = NULL; + break; +#endif + default: + break; + } + return (bt_close(pbe)); +} + +static void +bt_rec_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + break; +#ifdef HAVE_FFMPEG + case CODEC_AAC: + break; +#endif + + default: + break; + } + return (bt_close(pbe)); +} + +static const uint32_t bt_attrs[] = { + SDP_ATTR_RANGE(SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), +}; + +#define BT_NUM_VALUES 32 +#define BT_BUF_SIZE 32 + +static int +bt_find_psm(const uint8_t *start, const uint8_t *end) +{ + uint32_t type; + uint32_t len; + int protover = 0; + int psm = -1; + + if ((end - start) < 2) + return (-1); + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + return (-1); + } + + while (start < end) { + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + return (-1); + } + /* check range */ + if (len > (uint32_t)(end - start)) + break; + + if (len >= 6) { + const uint8_t *ptr = start; + + SDP_GET8(type, ptr); + if (type == SDP_DATA_UUID16) { + uint16_t temp; + + SDP_GET16(temp, ptr); + switch (temp) { + case SDP_UUID_PROTOCOL_L2CAP: + SDP_GET8(type, ptr); + SDP_GET16(psm, ptr); + break; + case SDP_UUID_PROTOCOL_AVDTP: + SDP_GET8(type, ptr); + SDP_GET16(protover, ptr); + break; + default: + break; + } + } + } + start += len; + + if (protover >= 0x0100 && psm > -1) + return (htole16(psm)); + } + return (-1); +} + +static int +bt_query(struct l2cap_info *info, uint16_t service_class) +{ + sdp_attr_t values[BT_NUM_VALUES]; + uint8_t buffer[BT_NUM_VALUES][BT_BUF_SIZE]; + void *ss; + int psm = -1; + int n; + + memset(buffer, 0, sizeof(buffer)); + memset(values, 0, sizeof(values)); + + ss = sdp_open(&info->laddr, &info->raddr); + if (ss == NULL || sdp_error(ss) != 0) { + DPRINTF("Could not open SDP\n"); + sdp_close(ss); + return (psm); + } + /* Initialize attribute values array */ + for (n = 0; n != BT_NUM_VALUES; n++) { + values[n].flags = SDP_ATTR_INVALID; + values[n].vlen = BT_BUF_SIZE; + values[n].value = buffer[n]; + } + + /* Do SDP Service Search Attribute Request */ + n = sdp_search(ss, 1, &service_class, 1, bt_attrs, BT_NUM_VALUES, values); + if (n != 0) { + DPRINTF("SDP search failed\n"); + goto done; + } + /* Print attributes values */ + for (n = 0; n != BT_NUM_VALUES; n++) { + if (values[n].flags != SDP_ATTR_OK) + break; + if (values[n].attr != SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST) + continue; + psm = bt_find_psm(values[n].value, values[n].value + values[n].vlen); + if (psm > -1) + break; + } +done: + sdp_close(ss); + return (psm); +} + +static int +bt_open(struct voss_backend *pbe __unused, const char *devname, int samplerate, + int bufsize __unused, int *pchannels, int *pformat, struct bt_config *cfg, + int service_class, int isSink) +{ + struct sockaddr_l2cap addr; + struct l2cap_info info; + socklen_t mtusize = sizeof(uint16_t); + int tmpbitpool; + int l2cap_psm; + int temp; + + memset(&info, 0, sizeof(info)); + + if (strstr(devname, "/dev/bluetooth/") != devname) { + printf("Invalid device name '%s'", devname); + goto error; + } + /* skip prefix */ + devname += sizeof("/dev/bluetooth/") - 1; + + if (!bt_aton(devname, &info.raddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(devname)) == NULL) { + DPRINTF("Could not get host by name\n"); + goto error; + } + bdaddr_copy(&info.raddr, (bdaddr_t *)he->h_addr); + } + switch (samplerate) { + case 8000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x80; + cfg->aacMode2 = 0x0C; + break; + case 11025: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x40; + cfg->aacMode2 = 0x0C; + break; + case 12000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x20; + cfg->aacMode2 = 0x0C; + break; + case 16000: + cfg->freq = FREQ_16K; + cfg->aacMode1 = 0x10; + cfg->aacMode2 = 0x0C; + break; + case 22050: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x08; + cfg->aacMode2 = 0x0C; + break; + case 24000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x04; + cfg->aacMode2 = 0x0C; + break; + case 32000: + cfg->freq = FREQ_32K; + cfg->aacMode1 = 0x02; + cfg->aacMode2 = 0x0C; + break; + case 44100: + cfg->freq = FREQ_44_1K; + cfg->aacMode1 = 0x01; + cfg->aacMode2 = 0x0C; + break; + case 48000: + cfg->freq = FREQ_48K; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x8C; + break; + case 64000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x4C; + break; + case 88200: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x2C; + break; + case 96000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x1C; + break; + default: + DPRINTF("Invalid samplerate %d", samplerate); + goto error; + } + cfg->bands = BANDS_8; + cfg->bitpool = 0; + + switch (*pchannels) { + case 1: + cfg->aacMode2 &= 0xF8; + cfg->chmode = MODE_MONO; + break; + default: + cfg->aacMode2 &= 0xF4; + cfg->chmode = MODE_STEREO; + break; + } + + cfg->allocm = ALLOC_LOUDNESS; + + if (cfg->chmode == MODE_MONO || cfg->chmode == MODE_DUAL) + tmpbitpool = 16; + else + tmpbitpool = 32; + + if (cfg->bands == BANDS_8) + tmpbitpool *= 8; + else + tmpbitpool *= 4; + + if (tmpbitpool > DEFAULT_MAXBPOOL) + tmpbitpool = DEFAULT_MAXBPOOL; + + cfg->bitpool = tmpbitpool; + + if (bt_set_format(pformat)) { + DPRINTF("Unsupported sample format\n"); + goto error; + } + l2cap_psm = bt_query(&info, service_class); + DPRINTF("PSM=0x%02x\n", l2cap_psm); + if (l2cap_psm < 0) { + DPRINTF("PSM not found\n"); + goto error; + } + cfg->hc = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (cfg->hc < 0) { + DPRINTF("Could not create BT socket\n"); + goto error; + } + memset(&addr, 0, sizeof(addr)); + addr.l2cap_len = sizeof(addr); + addr.l2cap_family = AF_BLUETOOTH; + bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr); + + if (bind(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not bind to HC\n"); + goto error; + } + bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr); + addr.l2cap_psm = l2cap_psm; + if (connect(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not connect to HC: %d\n", errno); + goto error; + } + if (avdtpDiscoverAndConfig(cfg, isSink)) { + DPRINTF("DISCOVER FAILED\n"); + goto error; + } + if (avdtpOpen(cfg->hc, cfg->sep)) { + DPRINTF("OPEN FAILED\n"); + goto error; + } + cfg->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (cfg->fd < 0) { + DPRINTF("Could not create BT socket\n"); + goto error; + } + memset(&addr, 0, sizeof(addr)); + + addr.l2cap_len = sizeof(addr); + addr.l2cap_family = AF_BLUETOOTH; + bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr); + + if (bind(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not bind\n"); + goto error; + } + bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr); + addr.l2cap_psm = l2cap_psm; + if (connect(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not connect: %d\n", errno); + goto error; + } + if (isSink) { + if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_OMTU, &cfg->mtu, &mtusize) == -1) { + DPRINTF("Could not get MTU\n"); + goto error; + } + temp = cfg->mtu * 16; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDBUF, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set send buffer size\n"); + goto error; + } + temp = cfg->mtu; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDLOWAT, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set low water mark\n"); + goto error; + } + } else { + if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_IMTU, &cfg->mtu, &mtusize) == -1) { + DPRINTF("Could not get MTU\n"); + goto error; + } + temp = cfg->mtu * 16; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set receive buffer size\n"); + goto error; + } + temp = 1; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set low water mark\n"); + goto error; + } + temp = 1; + if (ioctl(cfg->fd, FIONBIO, &temp) == -1) { + DPRINTF("Could not set non-blocking I/O for receive direction\n"); + goto error; + } + } + + if (avdtpStart(cfg->hc, cfg->sep)) { + DPRINTF("START FAILED\n"); + goto error; + } + switch (cfg->chmode) { + case MODE_MONO: + *pchannels = 1; + break; + default: + *pchannels = 2; + break; + } + return (0); + +error: + if (cfg->hc > 0) { + close(cfg->hc); + cfg->hc = -1; + } + if (cfg->fd > 0) { + close(cfg->fd); + cfg->fd = -1; + } + return (-1); +} + +static void +bt_init_cfg(struct bt_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); +} + +static int +bt_rec_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + struct bt_config *cfg = pbe->arg; + int retval; + + bt_init_cfg(cfg); + + retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat, + cfg, SDP_SERVICE_CLASS_AUDIO_SOURCE, 0); + if (retval != 0) + return (retval); + return (0); +} + +static int +bt_play_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + struct bt_config *cfg = pbe->arg; + int retval; + + bt_init_cfg(cfg); + + retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat, + cfg, SDP_SERVICE_CLASS_AUDIO_SINK, 1); + if (retval != 0) + return (retval); + + /* setup codec */ + switch (cfg->codec) { + case CODEC_SBC: + cfg->handle.sbc_enc = + malloc(sizeof(*cfg->handle.sbc_enc)); + if (cfg->handle.sbc_enc == NULL) + return (-1); + memset(cfg->handle.sbc_enc, 0, sizeof(*cfg->handle.sbc_enc)); + break; +#ifdef HAVE_FFMPEG + case CODEC_AAC: + av_register_all(); + + cfg->handle.av.codec = avcodec_find_encoder_by_name("aac"); + if (cfg->handle.av.codec == NULL) { + DPRINTF("Codec AAC encoder not found\n"); + goto av_error_0; + } + cfg->handle.av.format = avformat_alloc_context(); + if (cfg->handle.av.format == NULL) { + DPRINTF("Could not allocate format context\n"); + goto av_error_0; + } + cfg->handle.av.format->oformat = + av_guess_format("latm", NULL, NULL); + if (cfg->handle.av.format->oformat == NULL) { + DPRINTF("Could not guess output format\n"); + goto av_error_1; + } + cfg->handle.av.stream = avformat_new_stream( + cfg->handle.av.format, cfg->handle.av.codec); + + if (cfg->handle.av.stream == NULL) { + DPRINTF("Could not create new stream\n"); + goto av_error_1; + } + cfg->handle.av.context = cfg->handle.av.stream->codec; + if (cfg->handle.av.context == NULL) { + DPRINTF("Could not allocate audio context\n"); + goto av_error_1; + } + avcodec_get_context_defaults3(cfg->handle.av.context, + cfg->handle.av.codec); + + cfg->handle.av.context->bit_rate = 128000; + cfg->handle.av.context->sample_fmt = AV_SAMPLE_FMT_FLTP; + cfg->handle.av.context->sample_rate = samplerate; + switch (*pchannels) { + case 1: + cfg->handle.av.context->channel_layout = AV_CH_LAYOUT_MONO; + cfg->handle.av.context->channels = 1; + break; + default: + cfg->handle.av.context->channel_layout = AV_CH_LAYOUT_STEREO; + cfg->handle.av.context->channels = 2; + break; + } + + cfg->handle.av.context->profile = FF_PROFILE_AAC_LOW; + if (1) { + AVDictionary *opts = NULL; + + av_dict_set(&opts, "strict", "-2", 0); + av_dict_set_int(&opts, "latm", 1, 0); + + if (avcodec_open2(cfg->handle.av.context, + cfg->handle.av.codec, &opts) < 0) { + av_dict_free(&opts); + + DPRINTF("Could not open codec\n"); + goto av_error_1; + } + av_dict_free(&opts); + } + cfg->handle.av.frame = av_frame_alloc(); + if (cfg->handle.av.frame == NULL) { + DPRINTF("Could not allocate audio frame\n"); + goto av_error_2; + } + cfg->handle.av.frame->nb_samples = cfg->handle.av.context->frame_size; + cfg->handle.av.frame->format = cfg->handle.av.context->sample_fmt; + cfg->handle.av.frame->channel_layout = cfg->handle.av.context->channel_layout; + cfg->rem_in_size = av_samples_get_buffer_size(NULL, + cfg->handle.av.context->channels, + cfg->handle.av.context->frame_size, + cfg->handle.av.context->sample_fmt, 0); + + cfg->rem_in_data = av_malloc(cfg->rem_in_size); + if (cfg->rem_in_data == NULL) { + DPRINTF("Could not allocate %u bytes sample buffer\n", + (unsigned)cfg->rem_in_size); + goto av_error_3; + } + retval = avcodec_fill_audio_frame(cfg->handle.av.frame, + cfg->handle.av.context->channels, + cfg->handle.av.context->sample_fmt, + cfg->rem_in_data, cfg->rem_in_size, 0); + if (retval < 0) { + DPRINTF("Could not setup audio frame\n"); + goto av_error_4; + } + break; +av_error_4: + av_free(cfg->rem_in_data); +av_error_3: + av_frame_free(&cfg->handle.av.frame); +av_error_2: + avcodec_close(cfg->handle.av.context); +av_error_1: + avformat_free_context(cfg->handle.av.format); + cfg->handle.av.context = NULL; +av_error_0: + bt_close(pbe); + return (-1); +#endif + default: + bt_close(pbe); + return (-1); + } + return (0); +} + +static int +bt_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (bt_receive(pbe->arg, ptr, len, 1)); +} + +static int +bt_play_sbc_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + struct sbc_encode *sbc = cfg->handle.sbc_enc; + int rem_size = 1; + int old_len = len; + int err = 0; + + switch (cfg->blocks) { + case BLOCKS_4: + sbc->blocks = 4; + rem_size *= 4; + break; + case BLOCKS_8: + sbc->blocks = 8; + rem_size *= 8; + break; + case BLOCKS_12: + sbc->blocks = 12; + rem_size *= 12; + break; + default: + sbc->blocks = 16; + rem_size *= 16; + break; + } + + switch (cfg->bands) { + case BANDS_4: + rem_size *= 4; + sbc->bands = 4; + break; + default: + rem_size *= 8; + sbc->bands = 8; + break; + } + + /* store number of samples per frame */ + sbc->framesamples = rem_size; + + if (cfg->chmode != MODE_MONO) { + rem_size *= 2; + sbc->channels = 2; + } else { + sbc->channels = 1; + } + + rem_size *= 2; /* 16-bit samples */ + + while (len > 0) { + int delta = len; + + if (delta > (int)(rem_size - sbc->rem_len)) + delta = (int)(rem_size - sbc->rem_len); + + /* copy in samples */ + memcpy((char *)sbc->music_data + sbc->rem_len, ptr, delta); + + ptr = (char *)ptr + delta; + len -= delta; + sbc->rem_len += delta; + + /* check if buffer is full */ + if ((int)sbc->rem_len == rem_size) { + struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data; + uint32_t pkt_len; + uint32_t rem; + + if (cfg->chmode == MODE_MONO) + sbc->channels = 1; + else + sbc->channels = 2; + + pkt_len = sbc_encode_frame(cfg); + + retry: + if (cfg->mtu_offset == 0) { + phdr->id = 0x80; /* RTP v2 */ + phdr->id2 = 0x60; /* payload type 96. */ + phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8); + phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber); + phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24); + phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16); + phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8); + phdr->ts0 = (uint8_t)(cfg->mtu_timestamp); + phdr->reserved0 = 0x01; + phdr->numFrames = 0; + + cfg->mtu_seqnumber++; + cfg->mtu_offset += sizeof(*phdr); + } + /* compute bytes left */ + rem = cfg->mtu - cfg->mtu_offset; + + if (phdr->numFrames == 255 || rem < pkt_len) { + int xlen; + + if (phdr->numFrames == 0) + return (-1); + do { + xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset); + } while (xlen < 0 && errno == EAGAIN); + + if (xlen < 0) + return (-1); + + cfg->mtu_offset = 0; + goto retry; + } + memcpy(cfg->mtu_data + cfg->mtu_offset, sbc->data, pkt_len); + memset(sbc->data, 0, pkt_len); + cfg->mtu_offset += pkt_len; + cfg->mtu_timestamp += sbc->framesamples; + phdr->numFrames++; + + sbc->rem_len = 0; + } + } + if (err == 0) + return (old_len); + return (err); +} + +#ifdef HAVE_FFMPEG +static int +bt_play_aac_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + struct aac_header { + uint8_t id; + uint8_t id2; + uint8_t seqnumMSB; + uint8_t seqnumLSB; + uint8_t ts3; + uint8_t ts2; + uint8_t ts1; + uint8_t ts0; + uint8_t sync3; + uint8_t sync2; + uint8_t sync1; + uint8_t sync0; + uint8_t fixed[8]; + }; + int old_len = len; + int err = 0; + + while (len > 0) { + int delta = len; + int rem; + int i; + + if (delta > (int)(cfg->rem_in_size - cfg->rem_in_len)) + delta = (int)(cfg->rem_in_size - cfg->rem_in_len); + + memcpy(cfg->rem_in_data + cfg->rem_in_len, ptr, delta); + + ptr = (char *)ptr + delta; + len -= delta; + cfg->rem_in_len += delta; + + /* check if buffer is full */ + if (cfg->rem_in_len == cfg->rem_in_size) { + struct aac_header *phdr = (struct aac_header *)cfg->mtu_data; + AVPacket pkt; + uint8_t *pkt_buf; + int pkt_len; + int got_output; + int i; + + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + err = avcodec_encode_audio2(cfg->handle.av.context, + &pkt, cfg->handle.av.frame, &got_output); + if (err < 0) { + DPRINTF("Error encoding audio frame\n"); + return (-1); + } + if (got_output == 0) { + /* reset remaining length */ + cfg->rem_in_len = 0; + continue; + } + phdr->id = 0x80;/* RTP v2 */ + phdr->id2 = 0x60; /* payload type 96. */ + phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8); + phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber); + phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24); + phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16); + phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8); + phdr->ts0 = (uint8_t)(cfg->mtu_timestamp); + phdr->sync3 = 0; + phdr->sync2 = 0; + phdr->sync1 = 0; + phdr->sync0 = 0; + phdr->fixed[0] = 0xfc; + phdr->fixed[1] = 0x00; + phdr->fixed[2] = 0x00; + phdr->fixed[3] = 0xb0; + phdr->fixed[4] = 0x90; + phdr->fixed[5] = 0x80; + phdr->fixed[6] = 0x03; + phdr->fixed[7] = 0x00; + + cfg->mtu_seqnumber++; + cfg->mtu_offset = sizeof(*phdr); + + /* compute bytes left */ + rem = cfg->mtu - cfg->mtu_offset; + + if (avio_open_dyn_buf(&cfg->handle.av.format->pb) == 0) { + static int once = 0; + + if (!once++) + avformat_write_header(cfg->handle.av.format, NULL); + av_write_frame(cfg->handle.av.format, &pkt); + av_free_packet(&pkt); + pkt_len = avio_close_dyn_buf(cfg->handle.av.format->pb, &pkt_buf); + if (rem < pkt_len) + DPRINTF("Out of buffer space\n"); + if (pkt_len >= 3 && rem >= pkt_len) { + int xlen; + + memcpy(cfg->mtu_data + cfg->mtu_offset, pkt_buf + 3, pkt_len - 3); + + av_free(pkt_buf); + + cfg->mtu_offset += pkt_len - 3; + if (cfg->chmode != MODE_MONO) + cfg->mtu_timestamp += cfg->rem_in_size / 4; + else + cfg->mtu_timestamp += cfg->rem_in_size / 2; + do { + xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset); + } while (xlen < 0 && errno == EAGAIN); + + if (xlen < 0) + return (-1); + } else { + av_free(pkt_buf); + } + } else { + av_free_packet(&pkt); + } + /* reset remaining length */ + cfg->rem_in_len = 0; + } + } + if (err == 0) + return (old_len); + return (err); +} + +#endif + +static int +bt_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + return (bt_play_sbc_transfer(pbe, ptr, len)); +#ifdef HAVE_FFMPEG + case CODEC_AAC: + return (bt_play_aac_transfer(pbe, ptr, len)); +#endif + default: + return (-1); + } +} + +static void +bt_rec_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +static void +bt_play_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + /* TODO */ + *pdelay = -1; +} + +struct voss_backend voss_backend_bt_rec = { + .open = bt_rec_open, + .close = bt_rec_close, + .transfer = bt_rec_transfer, + .delay = bt_rec_delay, + .arg = &bt_rec_cfg, +}; + +struct voss_backend voss_backend_bt_play = { + .open = bt_play_open, + .close = bt_play_close, + .transfer = bt_play_transfer, + .delay = bt_play_delay, + .arg = &bt_play_cfg, +}; diff --git a/lib/virtual_oss/bt/cosdata-gen/Makefile b/lib/virtual_oss/bt/cosdata-gen/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/cosdata-gen/Makefile @@ -0,0 +1,12 @@ +# $NetBSD$ + +WARNS?= 3 + +PROG= cosdata +SRCS= cosdata.c +MAN= + +DPADD+= ${LIBMATH} +LDADD+= -lm + +.include diff --git a/lib/virtual_oss/bt/cosdata-gen/cosdata.c b/lib/virtual_oss/bt/cosdata-gen/cosdata.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/cosdata-gen/cosdata.c @@ -0,0 +1,177 @@ +/*- + * Copyright (c) 2015 - 2016 Nathanial Sloss + * All rights reserved. + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 +#include +#include + +static const double sbc8_coeffs[] = { + 0.00000000e+00, 1.56575398e-04, 3.43256425e-04, 5.54620202e-04, + 8.23919506e-04, 1.13992507e-03, 1.47640169e-03, 1.78371725e-03, + 2.01182542e-03, 2.10371989e-03, 1.99454554e-03, 1.61656283e-03, + 9.02154502e-04, -1.78805361e-04, -1.64973098e-03, -3.49717454e-03, + 5.65949473e-03, 8.02941163e-03, 1.04584443e-02, 1.27472335e-02, + 1.46525263e-02, 1.59045603e-02, 1.62208471e-02, 1.53184106e-02, + 1.29371806e-02, 8.85757540e-03, 2.92408442e-03, -4.91578024e-03, + -1.46404076e-02, -2.61098752e-02, -3.90751381e-02, -5.31873032e-02, + 6.79989431e-02, 8.29847578e-02, 9.75753918e-02, 1.11196689e-01, + 1.23264548e-01, 1.33264415e-01, 1.40753505e-01, 1.45389847e-01, + 1.46955068e-01, 1.45389847e-01, 1.40753505e-01, 1.33264415e-01, + 1.23264548e-01, 1.11196689e-01, 9.75753918e-02, 8.29847578e-02, + -6.79989431e-02, -5.31873032e-02, -3.90751381e-02, -2.61098752e-02, + -1.46404076e-02, -4.91578024e-03, 2.92408442e-03, 8.85757540e-03, + 1.29371806e-02, 1.53184106e-02, 1.62208471e-02, 1.59045603e-02, + 1.46525263e-02, 1.27472335e-02, 1.04584443e-02, 8.02941163e-03, + -5.65949473e-03, -3.49717454e-03, -1.64973098e-03, -1.78805361e-04, + 9.02154502e-04, 1.61656283e-03, 1.99454554e-03, 2.10371989e-03, + 2.01182542e-03, 1.78371725e-03, 1.47640169e-03, 1.13992507e-03, + 8.23919506e-04, 5.54620202e-04, 3.43256425e-04, 1.56575398e-04, +}; + +static const double sbc4_coeffs[] = { + 0.00000000e+00, 5.36548976e-04, 1.49188357e-03, 2.73370904e-03, + 3.83720193e-03, 3.89205149e-03, 1.86581691e-03, -3.06012286e-03, + 1.09137620e-02, 2.04385087e-02, 2.88757392e-02, 3.21939290e-02, + 2.58767811e-02, 6.13245186e-03, -2.88217274e-02, -7.76463494e-02, + 1.35593274e-01, 1.94987841e-01, 2.46636662e-01, 2.81828203e-01, + 2.94315332e-01, 2.81828203e-01, 2.46636662e-01, 1.94987841e-01, + -1.35593274e-01, -7.76463494e-02, -2.88217274e-02, 6.13245186e-03, + 2.58767811e-02, 3.21939290e-02, 2.88757392e-02, 2.04385087e-02, + -1.09137620e-02, -3.06012286e-03, 1.86581691e-03, 3.89205149e-03, + 3.83720193e-03, 2.73370904e-03, 1.49188357e-03, 5.36548976e-04, +}; + +#define AC(x) (int)(sizeof(x) / sizeof((x)[0])) + +int +main(int argc, char **argv) +{ + float S[8][16]; + int i; + int k; + int count = 0; + + printf("/* sbc_coeffs.h - Automatically generated by cosdata.c. */\n" + "\n"); + + printf("static const float sbc_coeffs8[] = {\n "); + for (k = 0; k < AC(sbc8_coeffs); k++) { + if ((count % 8) == 0 && count != 0) + printf("\n "); + printf("%0.12ff, ", (float)sbc8_coeffs[k]); + count++; + } + printf("\n};\n"); + + count = 0; + printf("static const float sbc_coeffs4[] = {\n "); + for (k = 0; k < AC(sbc4_coeffs); k++) { + if ((count % 8) == 0 && count != 0) + printf("\n "); + printf("%0.12ff, ", (float)sbc4_coeffs[k]); + count++; + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdata8[8][16] = {\n "); + for (i = 0; i < 8; i++) { + for (k = 0; k < 16; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k - 4) * (M_PI / 8.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 15) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdata4[4][8] = {\n "); + for (i = 0; i < 4; i++) { + for (k = 0; k < 8; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k - 2) * (M_PI / 4.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 7) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdecdata8[8][16] = {\n "); + for (i = 0; i < 8; i++) { + for (k = 0; k < 16; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k + 4) * (M_PI / 8.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 15) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdecdata4[4][8] = {\n "); + for (i = 0; i < 4; i++) { + for (k = 0; k < 8; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k + 2) * (M_PI / 4.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 7) + printf("},"); + count++; + } + } + printf("\n};\n"); + + return (0); +} diff --git a/lib/virtual_oss/bt/sbc_coeffs.h b/lib/virtual_oss/bt/sbc_coeffs.h new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/sbc_coeffs.h @@ -0,0 +1,69 @@ +/* sbc_coeffs.h - Automatically generated by cosdata.c. */ + +static const float sbc_coeffs8[] = { + 0.000000000000f, 0.000156575392f, 0.000343256426f, 0.000554620230f, 0.000823919487f, 0.001139925094f, 0.001476401696f, 0.001783717307f, + 0.002011825331f, 0.002103719860f, 0.001994545572f, 0.001616562833f, 0.000902154483f, -0.000178805363f, -0.001649730955f, -0.003497174475f, + 0.005659494549f, 0.008029411547f, 0.010458444245f, 0.012747233734f, 0.014652526006f, 0.015904560685f, 0.016220847145f, 0.015318410471f, + 0.012937180698f, 0.008857575245f, 0.002924084431f, -0.004915780388f, -0.014640407637f, -0.026109876111f, -0.039075139910f, -0.053187303245f, + 0.067998945713f, 0.082984760404f, 0.097575388849f, 0.111196689308f, 0.123264551163f, 0.133264422417f, 0.140753507614f, 0.145389840007f, + 0.146955072880f, 0.145389840007f, 0.140753507614f, 0.133264422417f, 0.123264551163f, 0.111196689308f, 0.097575388849f, 0.082984760404f, + -0.067998945713f, -0.053187303245f, -0.039075139910f, -0.026109876111f, -0.014640407637f, -0.004915780388f, 0.002924084431f, 0.008857575245f, + 0.012937180698f, 0.015318410471f, 0.016220847145f, 0.015904560685f, 0.014652526006f, 0.012747233734f, 0.010458444245f, 0.008029411547f, + -0.005659494549f, -0.003497174475f, -0.001649730955f, -0.000178805363f, 0.000902154483f, 0.001616562833f, 0.001994545572f, 0.002103719860f, + 0.002011825331f, 0.001783717307f, 0.001476401696f, 0.001139925094f, 0.000823919487f, 0.000554620230f, 0.000343256426f, 0.000156575392f, +}; +static const float sbc_coeffs4[] = { + 0.000000000000f, 0.000536548963f, 0.001491883537f, 0.002733709058f, 0.003837201977f, 0.003892051522f, 0.001865816885f, -0.003060122952f, + 0.010913762264f, 0.020438509062f, 0.028875738382f, 0.032193928957f, 0.025876780972f, 0.006132451817f, -0.028821727261f, -0.077646352351f, + 0.135593280196f, 0.194987848401f, 0.246636658907f, 0.281828194857f, 0.294315338135f, 0.281828194857f, 0.246636658907f, 0.194987848401f, + -0.135593280196f, -0.077646352351f, -0.028821727261f, 0.006132451817f, 0.025876780972f, 0.032193928957f, 0.028875738382f, 0.020438509062f, + -0.010913762264f, -0.003060122952f, 0.001865816885f, 0.003892051522f, 0.003837201977f, 0.002733709058f, 0.001491883537f, 0.000536548963f, +}; +static const float cosdata8[8][16] = { + { 0.707106769085f, 0.831469595432f, 0.923879504204f, 0.980785250664f, 1.000000000000f, 0.980785250664f, 0.923879504204f, 0.831469595432f, + 0.707106769085f, 0.555570244789f, 0.382683426142f, 0.195090353489f, -0.000000043711f, -0.195090323687f, -0.382683396339f, -0.555570185184f, }, + { -0.707106769085f, -0.195090323687f, 0.382683426142f, 0.831469595432f, 1.000000000000f, 0.831469595432f, 0.382683426142f, -0.195090323687f, + -0.707106769085f, -0.980785310268f, -0.923879504204f, -0.555570423603f, 0.000000011925f, 0.555570065975f, 0.923879563808f, 0.980785310268f, }, + { -0.707106828690f, -0.980785310268f, -0.382683396339f, 0.555570244789f, 1.000000000000f, 0.555570244789f, -0.382683396339f, -0.980785310268f, + -0.707106828690f, 0.195090413094f, 0.923879563808f, 0.831469655037f, 0.000000139071f, -0.831469774246f, -0.923879444599f, -0.195090219378f, }, + { 0.707106649876f, -0.555570423603f, -0.923879504204f, 0.195090353489f, 1.000000000000f, 0.195090353489f, -0.923879504204f, -0.555570423603f, + 0.707106649876f, 0.831469655037f, -0.382683008909f, -0.980785369873f, -0.000000290067f, 0.980785250664f, 0.382683545351f, -0.831469595432f, }, + { 0.707106769085f, 0.555570065975f, -0.923879504204f, -0.195090323687f, 1.000000000000f, -0.195090323687f, -0.923879504204f, 0.555570065975f, + 0.707106769085f, -0.831469774246f, -0.382683843374f, 0.980785250664f, -0.000000035775f, -0.980785250664f, 0.382683902979f, 0.831469714642f, }, + { -0.707106590271f, 0.980785310268f, -0.382683575153f, -0.555570185184f, 1.000000000000f, -0.555570185184f, -0.382683575153f, 0.980785310268f, + -0.707106590271f, -0.195090219378f, 0.923879683018f, -0.831469595432f, -0.000000592058f, 0.831469714642f, -0.923879623413f, 0.195090919733f, }, + { -0.707106530666f, 0.195090532303f, 0.382683604956f, -0.831469655037f, 1.000000000000f, -0.831469655037f, 0.382683604956f, 0.195090532303f, + -0.707106530666f, 0.980785310268f, -0.923879384995f, 0.555569529533f, -0.000000687457f, -0.555570006371f, 0.923879563808f, -0.980785191059f, }, + { 0.707106828690f, -0.831469774246f, 0.923879563808f, -0.980785310268f, 1.000000000000f, -0.980785310268f, 0.923879563808f, -0.831469774246f, + 0.707106828690f, -0.555570065975f, 0.382683902979f, -0.195089668036f, 0.000000059624f, 0.195089548826f, -0.382683813572f, 0.555569946766f, }, +}; +static const float cosdata4[4][8] = { + { 0.707106769085f, 0.923879504204f, 1.000000000000f, 0.923879504204f, 0.707106769085f, 0.382683426142f, -0.000000043711f, -0.382683396339f, }, + { -0.707106769085f, 0.382683426142f, 1.000000000000f, 0.382683426142f, -0.707106769085f, -0.923879504204f, 0.000000011925f, 0.923879563808f, }, + { -0.707106828690f, -0.382683396339f, 1.000000000000f, -0.382683396339f, -0.707106828690f, 0.923879563808f, 0.000000139071f, -0.923879444599f, }, + { 0.707106649876f, -0.923879504204f, 1.000000000000f, -0.923879504204f, 0.707106649876f, -0.382683008909f, -0.000000290067f, 0.382683545351f, }, +}; +static const float cosdecdata8[8][16] = { + { 0.707106769085f, 0.555570244789f, 0.382683426142f, 0.195090353489f, -0.000000043711f, -0.195090323687f, -0.382683396339f, -0.555570185184f, + -0.707106769085f, -0.831469655037f, -0.923879504204f, -0.980785310268f, -1.000000000000f, -0.980785310268f, -0.923879504204f, -0.831469535828f, }, + { -0.707106769085f, -0.980785310268f, -0.923879504204f, -0.555570423603f, 0.000000011925f, 0.555570065975f, 0.923879563808f, 0.980785310268f, + 0.707106769085f, 0.195090532303f, -0.382683008909f, -0.831469774246f, -1.000000000000f, -0.831469714642f, -0.382683843374f, 0.195090577006f, }, + { -0.707106828690f, 0.195090413094f, 0.923879563808f, 0.831469655037f, 0.000000139071f, -0.831469774246f, -0.923879444599f, -0.195090219378f, + 0.707106828690f, 0.980785310268f, 0.382683545351f, -0.555570065975f, -1.000000000000f, -0.555570542812f, 0.382683902979f, 0.980785191059f, }, + { 0.707106649876f, 0.831469655037f, -0.382683008909f, -0.980785369873f, -0.000000290067f, 0.980785250664f, 0.382683545351f, -0.831469595432f, + -0.707107424736f, 0.555569529533f, 0.923879802227f, -0.195089668036f, -1.000000000000f, -0.195090815425f, 0.923879384995f, 0.555570483208f, }, + { 0.707106769085f, -0.831469774246f, -0.382683843374f, 0.980785250664f, -0.000000035775f, -0.980785250664f, 0.382683902979f, 0.831469714642f, + -0.707106173038f, -0.555570006371f, 0.923879384995f, 0.195089548826f, -1.000000000000f, 0.195089697838f, 0.923879325390f, -0.555570125580f, }, + { -0.707106590271f, -0.195090219378f, 0.923879683018f, -0.831469595432f, -0.000000592058f, 0.831469714642f, -0.923879623413f, 0.195090919733f, + 0.707107424736f, -0.980785191059f, 0.382683366537f, 0.555569946766f, -1.000000000000f, 0.555571198463f, 0.382683783770f, -0.980785667896f, }, + { -0.707106530666f, 0.980785310268f, -0.923879384995f, 0.555569529533f, -0.000000687457f, -0.555570006371f, 0.923879563808f, -0.980785191059f, + 0.707106173038f, -0.195089071989f, -0.382684975863f, 0.831468641758f, -1.000000000000f, 0.831470131874f, -0.382683992386f, -0.195090129972f, }, + { 0.707106828690f, -0.555570065975f, 0.382683902979f, -0.195089668036f, 0.000000059624f, 0.195089548826f, -0.382683813572f, 0.555569946766f, + -0.707106053829f, 0.831468641758f, -0.923880040646f, 0.980785369873f, -1.000000000000f, 0.980785429478f, -0.923880159855f, 0.831468760967f, }, +}; +static const float cosdecdata4[4][8] = { + { 0.707106769085f, 0.382683426142f, -0.000000043711f, -0.382683396339f, -0.707106769085f, -0.923879504204f, -1.000000000000f, -0.923879504204f, }, + { -0.707106769085f, -0.923879504204f, 0.000000011925f, 0.923879563808f, 0.707106769085f, -0.382683008909f, -1.000000000000f, -0.382683843374f, }, + { -0.707106828690f, 0.923879563808f, 0.000000139071f, -0.923879444599f, 0.707106828690f, 0.382683545351f, -1.000000000000f, 0.382683902979f, }, + { 0.707106649876f, -0.382683008909f, -0.000000290067f, 0.382683545351f, -0.707107424736f, 0.923879802227f, -1.000000000000f, 0.923879384995f, }, +}; diff --git a/lib/virtual_oss/bt/sbc_encode.h b/lib/virtual_oss/bt/sbc_encode.h new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/sbc_encode.h @@ -0,0 +1,82 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 _SBC_ENCODE_H_ +#define _SBC_ENCODE_H_ + +#define MIN_BITPOOL 2 +#define DEFAULT_MAXBPOOL 250 + +/* + * SBC header format + */ +struct sbc_header { + uint8_t id; + uint8_t id2; + uint8_t seqnumMSB; + uint8_t seqnumLSB; + uint8_t ts3; + uint8_t ts2; + uint8_t ts1; + uint8_t ts0; + uint8_t reserved3; + uint8_t reserved2; + uint8_t reserved1; + uint8_t reserved0; + uint8_t numFrames; +}; + +struct sbc_encode { + int16_t music_data[256]; + uint8_t data[1024]; + uint8_t *rem_data_ptr; + int rem_data_len; + int rem_data_frames; + int bits[2][8]; + float output[256]; + float left[160]; + float right[160]; + float samples[16][2][8]; + uint32_t rem_len; + uint32_t rem_off; + uint32_t bitoffset; + uint32_t maxoffset; + uint32_t crc; + uint16_t framesamples; + uint8_t scalefactor[2][8]; + uint8_t channels; + uint8_t bands; + uint8_t blocks; + uint8_t join; +}; + +#endif /* _SBC_ENCODE_H_ */ diff --git a/lib/virtual_oss/bt/sbc_encode.c b/lib/virtual_oss/bt/sbc_encode.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/bt/sbc_encode.c @@ -0,0 +1,701 @@ +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sbc_coeffs.h" +#include "bt.h" + +#define SYNCWORD 0x9c +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#define BIT30 (1U << 30) +#define BM(x) ((1LL << (x)) - 1LL) + +/* Loudness offset allocations. */ +static const int loudnessoffset8[4][8] = { + {-2, 0, 0, 0, 0, 0, 0, 1}, + {-3, 0, 0, 0, 0, 0, 1, 2}, + {-4, 0, 0, 0, 0, 0, 1, 2}, + {-4, 0, 0, 0, 0, 0, 1, 2}, +}; + +static const int loudnessoffset4[4][4] = { + {-1, 0, 0, 0}, + {-2, 0, 0, 1}, + {-2, 0, 0, 1}, + {-2, 0, 0, 1}, +}; + +static uint8_t +calc_scalefactors_joint(struct sbc_encode *sbc) +{ + float sb_j[16][2]; + uint32_t x; + uint32_t y; + uint8_t block; + uint8_t joint; + uint8_t sb; + uint8_t lz; + + joint = 0; + for (sb = 0; sb != sbc->bands - 1; sb++) { + for (block = 0; block < sbc->blocks; block++) { + sb_j[block][0] = (sbc->samples[block][0][sb] + + sbc->samples[block][1][sb]) / 2.0f; + sb_j[block][1] = (sbc->samples[block][0][sb] - + sbc->samples[block][1][sb]) / 2.0f; + } + + x = 1 << 15; + y = 1 << 15; + for (block = 0; block < sbc->blocks; block++) { + x |= (uint32_t)ABS(sb_j[block][0]); + y |= (uint32_t)ABS(sb_j[block][1]); + } + + lz = 1; + while (!(x & BIT30)) { + lz++; + x <<= 1; + } + x = 16 - lz; + + lz = 1; + while (!(y & BIT30)) { + lz++; + y <<= 1; + } + y = 16 - lz; + + if ((sbc->scalefactor[0][sb] + sbc->scalefactor[1][sb]) > x + y) { + joint |= 1 << (sbc->bands - sb - 1); + sbc->scalefactor[0][sb] = x; + sbc->scalefactor[1][sb] = y; + for (block = 0; block < sbc->blocks; block++) { + sbc->samples[block][0][sb] = sb_j[block][0]; + sbc->samples[block][1][sb] = sb_j[block][1]; + } + } + } + return (joint); +} + +static void +calc_scalefactors(struct sbc_encode *sbc) +{ + uint8_t block; + uint8_t ch; + uint8_t sb; + + for (ch = 0; ch != sbc->channels; ch++) { + for (sb = 0; sb != sbc->bands; sb++) { + uint32_t x = 1 << 15; + uint8_t lx = 1; + + for (block = 0; block != sbc->blocks; block++) + x |= (uint32_t)ABS(sbc->samples[block][ch][sb]); + + while (!(x & BIT30)) { + lx++; + x <<= 1; + } + sbc->scalefactor[ch][sb] = 16 - lx; + } + } +} + +static void +calc_bitneed(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + int32_t bitneed[2][8]; + int32_t max_bitneed, bitcount; + int32_t slicecount, bitslice; + int32_t loudness; + int ch, sb, start_chan = 0; + + if (cfg->chmode == MODE_DUAL) + sbc->channels = 1; + +next_chan: + max_bitneed = 0; + bitcount = 0; + slicecount = 0; + + if (cfg->allocm == ALLOC_SNR) { + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + bitneed[ch][sb] = sbc->scalefactor[ch][sb]; + + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } else { + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->scalefactor[ch][sb] == 0) { + bitneed[ch][sb] = -5; + } else { + if (sbc->bands == 8) { + loudness = sbc->scalefactor[ch][sb] - + loudnessoffset8[cfg->freq][sb]; + } else { + loudness = sbc->scalefactor[ch][sb] - + loudnessoffset4[cfg->freq][sb]; + } + if (loudness > 0) + bitneed[ch][sb] = loudness / 2; + else + bitneed[ch][sb] = loudness; + } + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } + + slicecount = bitcount = 0; + bitslice = max_bitneed + 1; + do { + bitslice--; + bitcount += slicecount; + slicecount = 0; + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if ((bitneed[ch][sb] > bitslice + 1) && + (bitneed[ch][sb] < bitslice + 16)) + slicecount++; + else if (bitneed[ch][sb] == bitslice + 1) + slicecount += 2; + } + } + } while (bitcount + slicecount < cfg->bitpool); + + /* check if exactly one more fits */ + if (bitcount + slicecount == cfg->bitpool) { + bitcount += slicecount; + bitslice--; + } + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (bitneed[ch][sb] < bitslice + 2) { + sbc->bits[ch][sb] = 0; + } else { + sbc->bits[ch][sb] = bitneed[ch][sb] - bitslice; + if (sbc->bits[ch][sb] > 16) + sbc->bits[ch][sb] = 16; + } + } + } + + if (cfg->chmode == MODE_DUAL) + ch = start_chan; + else + ch = 0; + sb = 0; + while (bitcount < cfg->bitpool && sb < sbc->bands) { + if ((sbc->bits[ch][sb] >= 2) && (sbc->bits[ch][sb] < 16)) { + sbc->bits[ch][sb]++; + bitcount++; + } else if ((bitneed[ch][sb] == bitslice + 1) && + (cfg->bitpool > bitcount + 1)) { + sbc->bits[ch][sb] = 2; + bitcount += 2; + } + if (sbc->channels == 1 || start_chan == 1) + sb++; + else if (ch == 1) { + ch = 0; + sb++; + } else + ch = 1; + } + + if (cfg->chmode == MODE_DUAL) + ch = start_chan; + else + ch = 0; + sb = 0; + while (bitcount < cfg->bitpool && sb < sbc->bands) { + if (sbc->bits[ch][sb] < 16) { + sbc->bits[ch][sb]++; + bitcount++; + } + if (sbc->channels == 1 || start_chan == 1) + sb++; + else if (ch == 1) { + ch = 0; + sb++; + } else + ch = 1; + } + + if (cfg->chmode == MODE_DUAL && start_chan == 0) { + start_chan = 1; + sbc->channels = 2; + goto next_chan; + } +} + +static void +sbc_store_bits_crc(struct sbc_encode *sbc, uint32_t numbits, uint32_t value) +{ + uint32_t off = sbc->bitoffset; + + while (numbits-- && off != sbc->maxoffset) { + if (value & (1 << numbits)) { + sbc->data[off / 8] |= 1 << ((7 - off) & 7); + sbc->crc ^= 0x80; + } + sbc->crc *= 2; + if (sbc->crc & 0x100) + sbc->crc ^= 0x11d; /* CRC-8 polynomial */ + + off++; + } + sbc->bitoffset = off; +} + +static int +sbc_encode(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + const int16_t *input = sbc->music_data; + float delta[2][8]; + float levels[2][8]; + float mask[2][8]; + float S; + float *X; + float Z[80]; + float Y[80]; + float audioout; + int16_t left[8]; + int16_t right[8]; + int16_t *data; + int numsamples; + int i; + int k; + int block; + int chan; + int sb; + + for (block = 0; block < sbc->blocks; block++) { + + for (i = 0; i < sbc->bands; i++) { + left[i] = *input++; + if (sbc->channels == 2) + right[i] = *input++; + } + + for (chan = 0; chan < sbc->channels; chan++) { + + /* select right or left channel */ + if (chan == 0) { + X = sbc->left; + data = left; + } else { + X = sbc->right; + data = right; + } + + /* shift up old data */ + for (i = (sbc->bands * 10) - 1; i > sbc->bands - 1; i--) + X[i] = X[i - sbc->bands]; + k = 0; + for (i = sbc->bands - 1; i >= 0; i--) + X[i] = data[k++]; + for (i = 0; i < sbc->bands * 10; i++) { + if (sbc->bands == 8) + Z[i] = sbc_coeffs8[i] * X[i]; + else + Z[i] = sbc_coeffs4[i] * X[i]; + } + for (i = 0; i < sbc->bands * 2; i++) { + Y[i] = 0; + for (k = 0; k < 5; k++) + Y[i] += Z[i + k * sbc->bands * 2]; + } + for (i = 0; i < sbc->bands; i++) { + S = 0; + for (k = 0; k < sbc->bands * 2; k++) { + if (sbc->bands == 8) { + S += cosdata8[i][k] * Y[k]; + } else { + S += cosdata4[i][k] * Y[k]; + } + } + sbc->samples[block][chan][i] = S * (1 << 15); + } + } + } + + calc_scalefactors(sbc); + + if (cfg->chmode == MODE_JOINT) + sbc->join = calc_scalefactors_joint(sbc); + else + sbc->join = 0; + + calc_bitneed(cfg); + + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + mask[chan][sb] = BM(sbc->bits[chan][sb]); + levels[chan][sb] = mask[chan][sb] * + (1LL << (15 - sbc->scalefactor[chan][sb])); + delta[chan][sb] = + (1LL << (sbc->scalefactor[chan][sb] + 16)); + } + } + + numsamples = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + audioout = (levels[chan][sb] * + (delta[chan][sb] + sbc->samples[block][chan][sb])); + audioout /= (1LL << 32); + + audioout = roundf(audioout); + + /* range check */ + if (audioout > mask[chan][sb]) + audioout = mask[chan][sb]; + + sbc->output[numsamples++] = audioout; + } + } + } + return (numsamples); +} + +static void +sbc_decode(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + float delta[2][8]; + float levels[2][8]; + float audioout; + float *X; + float *V; + float left[160]; + float right[160]; + float U[160]; + float W[160]; + float S[8]; + int position; + int block; + int chan; + int sb; + int i; + int k; + + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + levels[chan][sb] = (1 << sbc->bits[chan][sb]) - 1; + delta[chan][sb] = (1 << sbc->scalefactor[chan][sb]); + } + } + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) { + audioout = 0; + } else { + audioout = + ((((sbc->output[i] * 2.0f) + 1.0f) * delta[chan][sb]) / + levels[chan][sb]) - delta[chan][sb]; + } + sbc->output[i++] = audioout; + } + } + } + + if (cfg->chmode == MODE_JOINT) { + i = 0; + while (i < (sbc->blocks * sbc->bands * sbc->channels)) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->join & (1 << (sbc->bands - sb - 1))) { + audioout = sbc->output[i]; + sbc->output[i] = (2.0f * sbc->output[i]) + + (2.0f * sbc->output[i + sbc->bands]); + sbc->output[i + sbc->bands] = + (2.0f * audioout) - + (2.0f * sbc->output[i + sbc->bands]); + sbc->output[i] /= 2.0f; + sbc->output[i + sbc->bands] /= 2.0f; + } + i++; + } + i += sbc->bands; + } + } + position = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + /* select right or left channel */ + if (chan == 0) { + X = left; + V = sbc->left; + } else { + X = right; + V = sbc->right; + } + for (i = 0; i < sbc->bands; i++) + S[i] = sbc->output[position++]; + + for (i = (sbc->bands * 20) - 1; i >= (sbc->bands * 2); i--) + V[i] = V[i - (sbc->bands * 2)]; + for (k = 0; k < sbc->bands * 2; k++) { + float vk = 0; + for (i = 0; i < sbc->bands; i++) { + if (sbc->bands == 8) { + vk += cosdecdata8[i][k] * S[i]; + } else { + vk += cosdecdata4[i][k] * S[i]; + } + } + V[k] = vk; + } + for (i = 0; i <= 4; i++) { + for (k = 0; k < sbc->bands; k++) { + U[(i * sbc->bands * 2) + k] = + V[(i * sbc->bands * 4) + k]; + U[(i * sbc->bands + * 2) + sbc->bands + k] = + V[(i * sbc->bands * 4) + + (sbc->bands * 3) + k]; + } + } + for (i = 0; i < sbc->bands * 10; i++) { + if (sbc->bands == 4) { + W[i] = U[i] * (sbc_coeffs4[i] * -4.0f); + } else if (sbc->bands == 8) { + W[i] = U[i] * (sbc_coeffs8[i] * -8.0f); + } else { + W[i] = 0; + } + } + + for (k = 0; k < sbc->bands; k++) { + unsigned int offset = k + (block * sbc->bands); + + X[offset] = 0; + for (i = 0; i < 10; i++) { + X[offset] += W[k + (i * sbc->bands)]; + } + + if (X[offset] > 32767.0) + X[offset] = 32767.0; + else if (X[offset] < -32767.0) + X[offset] = -32767.0; + } + } + } + + for (i = 0, k = 0; k != (sbc->blocks * sbc->bands); k++) { + sbc->music_data[i++] = left[k]; + if (sbc->channels == 2) + sbc->music_data[i++] = right[k]; + } +} + +size_t +sbc_encode_frame(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t config; + uint8_t block; + uint8_t chan; + uint8_t sb; + uint8_t j; + uint8_t i; + + config = (cfg->freq << 6) | (cfg->blocks << 4) | + (cfg->chmode << 2) | (cfg->allocm << 1) | cfg->bands; + + sbc_encode(cfg); + + /* set initial CRC */ + sbc->crc = 0x5e; + + /* reset data position and size */ + sbc->bitoffset = 0; + sbc->maxoffset = sizeof(sbc->data) * 8; + + sbc_store_bits_crc(sbc, 8, SYNCWORD); + sbc_store_bits_crc(sbc, 8, config); + sbc_store_bits_crc(sbc, 8, cfg->bitpool); + + /* skip 8-bit CRC */ + sbc->bitoffset += 8; + + if (cfg->chmode == MODE_JOINT) { + if (sbc->bands == 8) + sbc_store_bits_crc(sbc, 8, sbc->join); + else if (sbc->bands == 4) + sbc_store_bits_crc(sbc, 4, sbc->join); + } + for (i = 0; i < sbc->channels; i++) { + for (j = 0; j < sbc->bands; j++) + sbc_store_bits_crc(sbc, 4, sbc->scalefactor[i][j]); + } + + /* store 8-bit CRC */ + sbc->data[3] = (sbc->crc & 0xFF); + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + + sbc_store_bits_crc(sbc, sbc->bits[chan][sb], sbc->output[i++]); + } + } + } + return ((sbc->bitoffset + 7) / 8); +} + +static uint32_t +sbc_load_bits_crc(struct sbc_encode *sbc, uint32_t numbits) +{ + uint32_t off = sbc->bitoffset; + uint32_t value = 0; + + while (numbits-- && off != sbc->maxoffset) { + if (sbc->rem_data_ptr[off / 8] & (1 << ((7 - off) & 7))) { + value |= (1 << numbits); + sbc->crc ^= 0x80; + } + sbc->crc *= 2; + if (sbc->crc & 0x100) + sbc->crc ^= 0x11d; /* CRC-8 polynomial */ + + off++; + } + sbc->bitoffset = off; + return (value); +} + +size_t +sbc_decode_frame(struct bt_config *cfg, int bits) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t config; + uint8_t block; + uint8_t chan; + uint8_t sb; + uint8_t j; + uint8_t i; + + sbc->rem_off = 0; + sbc->rem_len = 0; + + config = (cfg->freq << 6) | (cfg->blocks << 4) | + (cfg->chmode << 2) | (cfg->allocm << 1) | cfg->bands; + + /* set initial CRC */ + sbc->crc = 0x5e; + + /* reset data position and size */ + sbc->bitoffset = 0; + sbc->maxoffset = bits; + + /* verify SBC header */ + if (sbc->maxoffset < (8 * 4)) + return (0); + if (sbc_load_bits_crc(sbc, 8) != SYNCWORD) + return (0); + if (sbc_load_bits_crc(sbc, 8) != config) + return (0); + cfg->bitpool = sbc_load_bits_crc(sbc, 8); + + (void)sbc_load_bits_crc(sbc, 8);/* CRC */ + + if (cfg->chmode == MODE_JOINT) { + if (sbc->bands == 8) + sbc->join = sbc_load_bits_crc(sbc, 8); + else if (sbc->bands == 4) + sbc->join = sbc_load_bits_crc(sbc, 4); + else + sbc->join = 0; + } else { + sbc->join = 0; + } + + for (i = 0; i < sbc->channels; i++) { + for (j = 0; j < sbc->bands; j++) + sbc->scalefactor[i][j] = sbc_load_bits_crc(sbc, 4); + } + + calc_bitneed(cfg); + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) { + i++; + continue; + } + sbc->output[i++] = + sbc_load_bits_crc(sbc, sbc->bits[chan][sb]); + } + } + } + + sbc_decode(cfg); + + sbc->rem_off = 0; + sbc->rem_len = sbc->blocks * sbc->channels * sbc->bands; + + return ((sbc->bitoffset + 7) / 8); +} diff --git a/lib/virtual_oss/null/Makefile b/lib/virtual_oss/null/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/null/Makefile @@ -0,0 +1,9 @@ +SHLIB_NAME= voss_null.so + +SRCS= null.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I${SRCTOP}/contrib/libsamplerate +LIBADD= samplerate + +.include diff --git a/lib/virtual_oss/null/null.c b/lib/virtual_oss/null/null.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/null/null.c @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 2015-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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "backend.h" +#include "int.h" + +static void +null_close(struct voss_backend *pbe __unused) +{ +} + +static int +null_open(struct voss_backend *pbe __unused, const char *devname __unused, + int samplerate __unused, int bufsize __unused, int *pchannels __unused, + int *pformat) +{ + int value[3]; + int i; + + value[0] = *pformat & VPREFERRED_SNE_AFMT; + value[1] = *pformat & VPREFERRED_SLE_AFMT; + value[2] = *pformat & VPREFERRED_SBE_AFMT; + + for (i = 0; i != 3; i++) { + if (value[i] == 0) + continue; + *pformat = value[i]; + return (0); + } + return (-1); +} + +static int +null_rec_transfer(struct voss_backend *pbe __unused, void *ptr, int len) +{ + + if (voss_has_synchronization == 0) + virtual_oss_wait(); + memset(ptr, 0, len); + return (len); +} + +static int +null_play_transfer(struct voss_backend *pbe __unused, void *ptr __unused, + int len) +{ + return (len); +} + +static void +null_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +struct voss_backend voss_backend_null_rec = { + .open = null_open, + .close = null_close, + .transfer = null_rec_transfer, + .delay = null_delay, +}; + +struct voss_backend voss_backend_null_play = { + .open = null_open, + .close = null_close, + .transfer = null_play_transfer, + .delay = null_delay, +}; diff --git a/lib/virtual_oss/oss/Makefile b/lib/virtual_oss/oss/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/oss/Makefile @@ -0,0 +1,9 @@ +SHLIB_NAME= voss_oss.so + +SRCS= oss.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I${SRCTOP}/contrib/libsamplerate +LIBADD= samplerate + +.include diff --git a/lib/virtual_oss/oss/oss.c b/lib/virtual_oss/oss/oss.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/oss/oss.c @@ -0,0 +1,197 @@ +/*- + * Copyright (c) 2015-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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "backend.h" +#include "int.h" + +static int +oss_set_format(int fd, int *format) +{ + int value[6]; + int error; + int fmt; + int i; + + value[0] = *format & VPREFERRED_SNE_AFMT; + value[1] = *format & VPREFERRED_UNE_AFMT; + value[2] = *format & VPREFERRED_SLE_AFMT; + value[3] = *format & VPREFERRED_SBE_AFMT; + value[4] = *format & VPREFERRED_ULE_AFMT; + value[5] = *format & VPREFERRED_UBE_AFMT; + + for (i = 0; i != 6; i++) { + fmt = value[i]; + if (fmt == 0) + continue; + error = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); + /* make sure we got the format we asked for */ + if (error == 0 && fmt == value[i]) { + *format = fmt; + return (0); + } + } + return (-1); +} + +static void +oss_close(struct voss_backend *pbe) +{ + if (pbe->fd > -1) { + close(pbe->fd); + pbe->fd = -1; + } +} + +static int +oss_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat, int attr, int fionbio) +{ + int temp; + int err; + + pbe->fd = open(devname, attr); + if (pbe->fd < 0) { + warn("Could not open DSP device '%s'", devname); + return (-1); + } + err = ioctl(pbe->fd, FIONBIO, &fionbio); + if (err < 0) { + warn("Could not set blocking mode on DSP"); + goto error; + } + err = oss_set_format(pbe->fd, pformat); + if (err < 0) { + warn("Could not set sample format 0x%08x", *pformat); + goto error; + } + temp = *pchannels; + bufsize /= temp; /* get buffer size per channel */ + do { + err = ioctl(pbe->fd, SOUND_PCM_WRITE_CHANNELS, &temp); + } while (err < 0 && --temp > 0); + + err = ioctl(pbe->fd, SOUND_PCM_READ_CHANNELS, &temp); + if (err < 0 || temp <= 0 || temp > *pchannels) { + warn("Could not set DSP channels: %d / %d", temp, *pchannels); + goto error; + } + *pchannels = temp; + + temp = samplerate; + err = ioctl(pbe->fd, SNDCTL_DSP_SPEED, &temp); + if (err < 0 || temp != samplerate) { + warn("Could not set sample rate to %d / %d Hz", temp, samplerate); + goto error; + } + + temp = bufsize * (*pchannels); + err = ioctl(pbe->fd, SNDCTL_DSP_SETBLKSIZE, &temp); + if (err < 0) { + warn("Could not set block size to %d", temp); + goto error; + } + return (0); +error: + close(pbe->fd); + pbe->fd = -1; + return (-1); +} + +static int +oss_rec_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + return (oss_open(pbe, devname, samplerate, bufsize, pchannels, pformat, O_RDONLY, 0)); +} + +static int +oss_play_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + bufsize *= 4; /* XXX allow extra space for jitter */ + return (oss_open(pbe, devname, samplerate, bufsize, pchannels, pformat, O_WRONLY, 0)); +} + +static int +oss_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct pollfd fds = { .fd = pbe->fd, .events = POLLIN | POLLRDNORM }; + int err; + + /* wait at maximum 2 seconds for data, else something is wrong */ + err = poll(&fds, 1, 2000); + if (err < 1) + return (-1); + return (read(pbe->fd, ptr, len)); +} + +static int +oss_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (write(pbe->fd, ptr, len)); +} + +static void +oss_rec_delay(struct voss_backend *pbe, int *pdelay) +{ + if (ioctl(pbe->fd, FIONREAD, pdelay) != 0) + *pdelay = -1; +} + +static void +oss_play_delay(struct voss_backend *pbe, int *pdelay) +{ + if (voss_has_synchronization != 0 || + ioctl(pbe->fd, SNDCTL_DSP_GETODELAY, pdelay) != 0) + *pdelay = -1; +} + +struct voss_backend voss_backend_oss_rec = { + .open = oss_rec_open, + .close = oss_close, + .transfer = oss_rec_transfer, + .delay = oss_rec_delay, + .fd = -1, +}; + +struct voss_backend voss_backend_oss_play = { + .open = oss_play_open, + .close = oss_close, + .transfer = oss_play_transfer, + .delay = oss_play_delay, + .fd = -1, +}; diff --git a/lib/virtual_oss/sndio/Makefile b/lib/virtual_oss/sndio/Makefile new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/sndio/Makefile @@ -0,0 +1,9 @@ +SHLIB_NAME= voss_sndio.so + +SRCS= sndio.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I/usr/local/include +LDFLAGS+= -L/usr/local/lib -lsndio + +.include diff --git a/lib/virtual_oss/sndio/sndio.c b/lib/virtual_oss/sndio/sndio.c new file mode 100644 --- /dev/null +++ b/lib/virtual_oss/sndio/sndio.c @@ -0,0 +1,203 @@ +/*- + * Copyright (c) 2021 Tim Creech + * + * 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 +#include + +#include +#include +#include + +#include + +#include "backend.h" +#include "int.h" + +static struct sio_hdl * +get_sio_hdl(struct voss_backend *pbe) +{ + if (pbe) + return (pbe->arg); + + return (NULL); +} + +static void +sndio_close(struct voss_backend *pbe) +{ + if (!pbe) + return; + + if (get_sio_hdl(pbe)) + sio_close(get_sio_hdl(pbe)); +} + +static int +sndio_get_signedness(int *fmt) +{ + int s_fmt = *fmt & (VPREFERRED_SLE_AFMT | VPREFERRED_SBE_AFMT); + + if (s_fmt) { + *fmt = s_fmt; + return (1); + } + *fmt = *fmt & (VPREFERRED_ULE_AFMT | VPREFERRED_UBE_AFMT); + return (0); +} + +static int +sndio_get_endianness_is_le(int *fmt) +{ + int le_fmt = *fmt & (VPREFERRED_SLE_AFMT | VPREFERRED_ULE_AFMT); + + if (le_fmt) { + *fmt = le_fmt; + return (1); + } + *fmt = *fmt & (VPREFERRED_SBE_AFMT | VPREFERRED_UBE_AFMT); + return (0); +} + +static int +sndio_get_bits(int *fmt) +{ + if (*fmt & AFMT_16BIT) + return (16); + if (*fmt & AFMT_24BIT) + return (24); + if (*fmt & AFMT_32BIT) + return (32); + if (*fmt & AFMT_8BIT) + return (8); + return (-1); + /* TODO AFMT_BIT */ +} + +static int +sndio_open(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat, int direction) +{ + const char *sndio_name = devname + strlen("/dev/sndio/"); + + int sig = sndio_get_signedness(pformat); + int le = sndio_get_endianness_is_le(pformat); + int bits = sndio_get_bits(pformat); + + if (bits == -1) { + warn("unsupported format precision"); + return (-1); + } + + struct sio_hdl *hdl = sio_open(sndio_name, direction, 0); + + if (hdl == 0) { + warn("sndio: failed to open device"); + return (-1); + } + + struct sio_par par; + + sio_initpar(&par); + par.pchan = *pchannels; + par.sig = sig; + par.bits = bits; + par.bps = SIO_BPS(bits); + par.le = le; + par.rate = samplerate; + par.appbufsz = bufsize; + par.xrun = SIO_SYNC; + if (!sio_setpar(hdl, &par)) + errx(1, "internal error, sio_setpar() failed"); + if (!sio_getpar(hdl, &par)) + errx(1, "internal error, sio_getpar() failed"); + if ((int)par.pchan != *pchannels) + errx(1, "couldn't set number of channels"); + if ((int)par.sig != sig || (int)par.bits != bits || (int)par.le != le) + errx(1, "couldn't set format"); + if ((int)par.bits != bits) + errx(1, "couldn't set precision"); + if ((int)par.rate < samplerate * 995 / 1000 || + (int)par.rate > samplerate * 1005 / 1000) + errx(1, "couldn't set rate"); + if (par.xrun != SIO_SYNC) + errx(1, "couldn't set xun policy"); + + /* Save the device handle with the backend */ + pbe->arg = hdl; + + /* Start the device. */ + if (!sio_start(hdl)) + errx(1, "couldn't start device"); + + return (0); +} + +static int +sndio_open_play(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat) +{ + return (sndio_open(pbe, devname, samplerate, bufsize, pchannels, pformat, SIO_PLAY)); +} + +static int +sndio_open_rec(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat) +{ + return (sndio_open(pbe, devname, samplerate, bufsize, pchannels, pformat, SIO_REC)); +} + +static int +sndio_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (sio_write(get_sio_hdl(pbe), ptr, len)); +} + +static int +sndio_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (sio_read(get_sio_hdl(pbe), ptr, len)); +} + +static void +sndio_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +struct voss_backend voss_backend_sndio_rec = { + .open = sndio_open_rec, + .close = sndio_close, + .transfer = sndio_rec_transfer, + .delay = sndio_delay, + .fd = -1, +}; + +struct voss_backend voss_backend_sndio_play = { + .open = sndio_open_play, + .close = sndio_close, + .transfer = sndio_play_transfer, + .delay = sndio_delay, + .fd = -1, +}; diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile --- a/libexec/rc/rc.d/Makefile +++ b/libexec/rc/rc.d/Makefile @@ -77,6 +77,7 @@ ugidfw \ var \ var_run \ + virtual_oss \ watchdogd CONFGROUPS+= DEVD diff --git a/libexec/rc/rc.d/virtual_oss b/libexec/rc/rc.d/virtual_oss new file mode 100644 --- /dev/null +++ b/libexec/rc/rc.d/virtual_oss @@ -0,0 +1,129 @@ +#!/bin/sh + +# PROVIDE: virtual_oss +# REQUIRE: kld ldconfig +# BEFORE: LOGIN sndiod +# KEYWORD: shutdown + +. /etc/rc.subr + +name="virtual_oss" +desc="Virtual OSS device manager" +rcvar="${name}_enable" + +command="/usr/local/sbin/${name}" +command_args="-B" + +load_rc_config $name +start_precmd="${name}_precmd" +start_cmd="${name}_start" +stop_cmd="${name}_stop" +status_cmd="${name}_status" + +configs= +pidpath="/var/run/${name}" +virtual_oss_default_args="\ + -S \ + -C 2 \ + -c 2 \ + -r 48000 \ + -b 24 \ + -s 8ms \ + -i 8 \ + -f /dev/dsp \ + -d dsp \ + -t vdsp.ctl" + +# Set to NO by default. Set it to "YES" to enable virtual_oss. +: ${virtual_oss_enable:="NO"} + +# List of configurations to use. Default is "dsp". +: ${virtual_oss_configs:="dsp"} + +# Default (dsp) virtual_oss config. +: ${virtual_oss_dsp:="$virtual_oss_default_args"} + +: ${virtual_oss_delay:=1} + +virtual_oss() +{ + # When running early we need to pre-load some libraries + env LD_PRELOAD=%%LIBFFTW3%% %%PREFIX%%/sbin/virtual_oss $* +} + +virtual_oss_pids() +{ + pids=$(pgrep -d ' ' $name) + pids=${pids% } + printf "${pids}" +} + +virtual_oss_precmd() +{ + /usr/bin/install -d -m 0755 -o root ${pidpath} + load_kld cuse +} + +start_instance() +{ + config=$* + instance_args=$(eval "echo \$virtual_oss_${config}") + if [ -z "${instance_args}" ]; then + echo "No such config ${config}" + else + echo -n "Starting Virtual OSS config ${config} ..." + ${command} \ + ${command_args} \ + -D ${pidpath}/${config}.pid \ + ${instance_args} + echo " done" + fi +} + +stop_instance() +{ + config=$* + instance_args=`eval "echo \$virtual_oss_${config}"` + if [ -z "${instance_args}" ]; then + echo "No such config ${config}" + else + echo -n "Stopping Virtual OSS config ${config} ... " + kill $(cat "${pidpath}/${config}.pid") + rm -f ${pidpath}/${config}.pid + echo "done" + fi +} + +virtual_oss_start() +{ + configs=$* + [ -z "${configs}" ] && configs="${virtual_oss_configs}" + for config in ${configs}; do + start_instance $config + sleep ${virtual_oss_delay} + done +} + +virtual_oss_stop() +{ + configs=$* + [ -z "${configs}" ] && configs="${virtual_oss_configs}" + for config in ${configs}; do + stop_instance ${config} + sleep ${virtual_oss_delay} + done +} + +virtual_oss_status() +{ + pids=$(virtual_oss_pids) + + if [ "${pids}" ]; then + echo "${name} is running as pid ${pids}." + else + echo "${name} is not running." + return 1 + fi +} + +run_rc_command $* diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -99,6 +99,7 @@ valectl \ vigr \ vipw \ + virtual_oss \ wake \ watch \ watchdogd \ diff --git a/usr.sbin/virtual_oss/Makefile b/usr.sbin/virtual_oss/Makefile new file mode 100644 --- /dev/null +++ b/usr.sbin/virtual_oss/Makefile @@ -0,0 +1,8 @@ +.include + +SUBDIR+= virtual_bt_speaker \ + virtual_oss_cmd \ + virtual_oss + +.include "Makefile.inc" +.include diff --git a/usr.sbin/virtual_oss/Makefile.inc b/usr.sbin/virtual_oss/Makefile.inc new file mode 100644 --- /dev/null +++ b/usr.sbin/virtual_oss/Makefile.inc @@ -0,0 +1 @@ +.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 --- /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+= -L${LIBDIR} -lm -lbluetooth -lsdp + +.include 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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c @@ -0,0 +1,543 @@ +/*- + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define L2CAP_SOCKET_CHECKED +#include +#include + +#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("libvoss_bt.so.0", 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 --- /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 +.\" +.\" 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 --- /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 diff --git a/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c new file mode 100644 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c @@ -0,0 +1,431 @@ +/*- + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, :\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 --- /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 +.\" +.\" 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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/Makefile @@ -0,0 +1,19 @@ +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 +LDFLAGS+= -lpthread -lcuse -lnv -lm +LIBADD= samplerate + +.include 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 --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 --- /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 --- /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 + +#include +#include + +#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 --- /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 +#include + +#include +#include +#include + +#include + +#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 --- /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 + +#include +#include +#include +#include + +#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 --- /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 +#include + +#include +#include + +#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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/httpd.c @@ -0,0 +1,844 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#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 uint16_t *ptr, size_t count) +{ + 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 uint16_t *hdr, size_t count, + const uint16_t *ptr, size_t length) +{ + 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); + + if (writev(fd, iov, 2) < 0) + ; +} + +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=%zu-%zu", &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" + "Welcome to live streaming" + "" + "" + "" + "" + "" + "

Live HD stream

" + "
" + "
" + "

Alternative 1 (recommended)

" + "
    " + "
  1. Install VideoLanClient (VLC), from App- or Play-store free of charge
  2. " + "
  3. Open VLC and select Network Stream
  4. " + "
  5. Enter, copy or share this network address to VLC: http://%s:%s/stream.m3u
  6. " + "
" + "
" + "
" + "

Alternative 2 (on your own)

" + "
" + "
" + "" + "
" + "
", + pvc->profile->http.host, pvc->profile->http.port, + pvc->profile->http.host, pvc->profile->http.port); + + if (x == pvc->profile->http.nstate) + fprintf(io, "

There are currently no free slots (%zu active). Try again later!

", x); + else + fprintf(io, "

There are %zu free slots (%zu active)

", pvc->profile->http.nstate - x, x); + + fprintf(io, ""); + 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" + "Virtual OSS" + "" + "

Invalid page requested! " + "Click here to go back.


" + "" + ""); + 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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/int.h @@ -0,0 +1,327 @@ +/*- + * 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 +#include + +#include +#include + +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 +#define VMAX_STRING 64 /* characters */ + +#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 | AFMT_F32_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 | AFMT_F32_LE) +#define VPREFERRED_SBE_AFMT \ + (AFMT_S8 | AFMT_S16_BE | AFMT_S24_BE | AFMT_S32_BE | AFMT_F32_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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/main.c @@ -0,0 +1,2623 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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), + _PATH_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; + +static struct voss_backend * +voss_load_backend(const char *name, const char *dir) +{ + struct voss_backend *backend; + void *hdl; + char lpath[64], bsym[64]; + + snprintf(lpath, sizeof(lpath), "libvoss_%s.so.0", 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("null", "rec"); + } else if (strstr(voss_dsp_rx_device, "/dev/bluetooth/") == voss_dsp_rx_device) { + voss_rx_backend = voss_load_backend("bt", "rec"); + } else if (strstr(voss_dsp_rx_device, "/dev/sndio/") == voss_dsp_rx_device) { + voss_rx_backend = voss_load_backend("sndio", "rec"); + } else { + voss_rx_backend = voss_load_backend("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("null", "play"); + } else if (strstr(voss_dsp_tx_device, "/dev/bluetooth/") == voss_dsp_tx_device) { + voss_tx_backend = voss_load_backend("bt", "play"); + } else if (strstr(voss_dsp_tx_device, "/dev/sndio/") == voss_dsp_tx_device) { + voss_tx_backend = voss_load_backend("sndio", "play"); + } else { + voss_tx_backend = voss_load_backend("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 or 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 \\\n" + "\t" "-r \\\n" + "\t" "-i \\\n" + "\t" "-a \\\n" + "\t" "-a i, \\\n" + "\t" "-a o, \\\n" + "\t" "-g # enable device RX compressor\\\n" + "\t" "-x # enable output compressor\\\n" + "\t" "-p \\\n" + "\t" "-e \\\n" + "\t" "-e , \\\n" + "\t" "-m \\\n" + "\t" "-m \\\n" + "\t" "-C \\\n" + "\t" "-c \\\n" + "\t" "-M \\\n" + "\t" "-M i,,,,, \\\n" + "\t" "-M o,,,,, \\\n" + "\t" "-M x,,,,, \\\n" + "\t" "-F or ms \\\n" + "\t" "-G or ms \\\n" + "\t" "-E \\\n" + "\t" "-N \\\n" + "\t" "-H \\\n" + "\t" "-o \\\n" + "\t" "-J \\\n" + "\t" "-k \\\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"); + 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 ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-s 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"); + + strncpy(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 ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-F 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 ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-G 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; + + 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 --- /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 + +#include +#include + +#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); + +/* + * = "stride" + * = 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; + } + } + } +} + +/* + * = "max" + * = 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 --- /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 +#include + +#include +#include +#include +#include +#include +#include + +#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 --- /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.h b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h new file mode 100644 --- /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 + +#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/virtual_oss.8 b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 new file mode 100644 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 @@ -0,0 +1,355 @@ +.\" +.\" Copyright (c) 2017-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. +.\" +.\" +.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 ms for the next +device to be created. +.It Fl G Ar size +Set transmit filter size in number of samples or 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 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c @@ -0,0 +1,913 @@ +/*- + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + 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_cmd/Makefile b/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile new file mode 100644 --- /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 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 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 --- /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 +.\" +.\" 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 .