virtual_oss: Port to base

This patch diverges quite a bit from the current upstream [1] in a few
ways:

1. virtual_oss(8), virtual_bt_speaker(8) and virtual_oss_cmd(8) are
   actually separate programs.
2. Backends (lib/virtual_oss) are built as separate shared libraries and
   we dlopen() them in virtual_oss(8) and virtual_bt_speaker(8) on
   demand.
3. virtual_equalizer(8) and the sndio and bluetooth backends are built
   as ports, because they depend on third-party libraries.
4. Use newer libav API in bluetooth backend (see HAVE_LIBAV ifdefs) to
   address compiler errors.

[1] https://github.com/freebsd/virtual_oss

Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Reviewed by:	emaste
Differential Revision:	https://reviews.freebsd.org/D52308
This commit is contained in:
Christos Margiolis
2025-09-28 11:56:52 +02:00
parent f34e1c76ad
commit 9cab9fde5e
51 changed files with 12547 additions and 1 deletions
+2
View File
@@ -21,5 +21,7 @@
..
pkgconfig
..
virtual_oss
..
..
..
+2
View File
@@ -103,6 +103,8 @@
..
ossl-modules
..
virtual_oss
..
..
libdata
ldscripts
+3 -1
View File
@@ -115,7 +115,8 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
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.
@@ -157,6 +158,7 @@ SUBDIR_DEPEND_liblzma= libthr
SUBDIR_DEPEND_libpcap= ofed
.endif
SUBDIR_DEPEND_nss_tacplus= libtacplus
SUBDIR_DEPEND_virtual_oss= libsamplerate
# NB: keep these sorted by MK_* knobs
+9
View File
@@ -0,0 +1,9 @@
.include <src.opts.mk>
SHLIBDIR?= ${LIBDIR}/virtual_oss
SUBDIR+= null \
oss
.include "Makefile.inc"
.include <bsd.subdir.mk>
+3
View File
@@ -0,0 +1,3 @@
.include "../Makefile.inc"
LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate
+19
View File
@@ -0,0 +1,19 @@
SHLIB_NAME= voss_bt.so
SHLIBDIR= ${LIBDIR}/virtual_oss
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_LIBAV)
CFLAGS+= -I${LOCALBASE:U/usr/local}/include -DHAVE_LIBAV
LDFLAGS+= -L${LOCALBASE:U/usr/local}/lib \
-lavdevice -lavutil -lavcodec -lavformat
.endif
.include <bsd.lib.mk>
+720
View File
@@ -0,0 +1,720 @@
/* $NetBSD$ */
/*-
* Copyright (c) 2015-2016 Nathanial Sloss <nathanialsloss@yahoo.com.au>
* Copyright (c) 2016-2019 Hans Petter Selasky <hps@selasky.org>
* Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
*
* 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 <sys/uio.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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_LIBAV
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_LIBAV
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_LIBAV
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);
}
+139
View File
@@ -0,0 +1,139 @@
/* $NetBSD$ */
/*-
* Copyright (c) 2015 Nathanial Sloss <nathanialsloss@yahoo.com.au>
*
* 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 <stdint.h>
#include <stdbool.h>
/* 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_ */
File diff suppressed because it is too large Load Diff
+116
View File
@@ -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_LIBAV
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#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_LIBAV
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_ */
+12
View File
@@ -0,0 +1,12 @@
# $NetBSD$
WARNS?= 3
PROG= cosdata
SRCS= cosdata.c
MAN=
DPADD+= ${LIBMATH}
LDADD+= -lm
.include <bsd.prog.mk>
+177
View File
@@ -0,0 +1,177 @@
/*-
* Copyright (c) 2015 - 2016 Nathanial Sloss <nathanialsloss@yahoo.com.au>
* 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 <math.h>
#include <stdio.h>
#include <stdint.h>
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);
}
+69
View File
@@ -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, },
};
+701
View File
@@ -0,0 +1,701 @@
/*-
* Copyright (c) 2015 Nathanial Sloss <nathanialsloss@yahoo.com.au>
*
* 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 <sys/cdefs.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/endian.h>
#include <sys/uio.h>
#include <stdio.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#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);
}
+82
View File
@@ -0,0 +1,82 @@
/* $NetBSD$ */
/*-
* Copyright (c) 2015 Nathanial Sloss <nathanialsloss@yahoo.com.au>
*
* 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_ */
+10
View File
@@ -0,0 +1,10 @@
SHLIB_NAME= voss_null.so
SHLIBDIR= ${LIBDIR}/virtual_oss
SRCS= null.c
CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
-I${SRCTOP}/contrib/libsamplerate
LIBADD= samplerate
.include <bsd.lib.mk>
+102
View File
@@ -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 <sys/queue.h>
#include <sys/filio.h>
#include <sys/soundcard.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <time.h>
#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,
};
+10
View File
@@ -0,0 +1,10 @@
SHLIB_NAME= voss_oss.so
SHLIBDIR= ${LIBDIR}/virtual_oss
SRCS= oss.c
CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
-I${SRCTOP}/contrib/libsamplerate
LIBADD= samplerate
.include <bsd.lib.mk>
+197
View File
@@ -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 <sys/queue.h>
#include <sys/filio.h>
#include <sys/soundcard.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <poll.h>
#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,
};
+12
View File
@@ -0,0 +1,12 @@
SHLIB_NAME= voss_sndio.so
SHLIBDIR= ${LIBDIR}/virtual_oss
SRCS= sndio.c
CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
-I${SRCTOP}/contrib/libsamplerate \
-I${LOCALBASE:U/usr/local}/include
LDFLAGS+= -L${LOCALBASE:U/usr/local}/lib -lsndio
LIBADD= samplerate
.include <bsd.lib.mk>
+203
View File
@@ -0,0 +1,203 @@
/*-
* Copyright (c) 2021 Tim Creech <tcreech@tcreech.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/soundcard.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
#include <sndio.h>
#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,
};
+1
View File
@@ -75,6 +75,7 @@ CONFS= DAEMON \
ugidfw \
var \
var_run \
virtual_oss \
watchdogd
CONFGROUPS+= DEVD
+119
View File
@@ -0,0 +1,119 @@
#!/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/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_pids()
{
pids=$(pgrep -d ' ' ${name})
pids=${pids% }
printf '%s\n' "${pids}"
}
virtual_oss_precmd()
{
/usr/bin/install -d -m 0755 -o root "${pidpath}"
load_kld cuse
}
start_instance()
{
config="$1"
instance_args=$(eval "echo \$virtual_oss_${config}")
if [ -z "${instance_args}" ]; then
warn "no such config: ${config}"
else
startmsg -n "Starting virtual_oss config: ${config}: "
${command} \
${command_args} \
-D "${pidpath}/${config}.pid" \
${instance_args}
startmsg "done"
fi
}
stop_instance()
{
config="$1"
instance_args=$(eval "echo \$virtual_oss_${config}")
if [ -z "${instance_args}" ]; then
warn "no such config: ${config}"
else
startmsg -n "Stopping virtual_oss config: ${config}: "
kill "$(cat "${pidpath}/${config}.pid")"
rm -f "${pidpath}/${config}.pid"
startmsg "done"
fi
}
virtual_oss_start()
{
configs="$1"
[ -z "${configs}" ] && configs="${virtual_oss_configs}"
for config in ${configs}; do
start_instance "${config}"
done
}
virtual_oss_stop()
{
configs="$1"
[ -z "${configs}" ] && configs="${virtual_oss_configs}"
for config in ${configs}; do
stop_instance "${config}"
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 "$@"
+1
View File
@@ -99,6 +99,7 @@ SUBDIR= adduser \
valectl \
vigr \
vipw \
virtual_oss \
wake \
watch \
watchdogd \
+8
View File
@@ -0,0 +1,8 @@
.include <src.opts.mk>
SUBDIR+= virtual_bt_speaker \
virtual_oss_cmd \
virtual_oss
.include "Makefile.inc"
.include <bsd.subdir.mk>
+1
View File
@@ -0,0 +1 @@
.include "../Makefile.inc"
@@ -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 <bsd.prog.mk>
@@ -0,0 +1,542 @@
/*-
* Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/rtprio.h>
#include <sys/soundcard.h>
#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include <poll.h>
#include <getopt.h>
#define L2CAP_SOCKET_CHECKED
#include <bluetooth.h>
#include <sdp.h>
#include "avdtp_signal.h"
#include "bt.h"
#include "utils.h"
static int (*bt_receive_f)(struct bt_config *, void *, int, int);
static int (*avdtpACPHandlePacket_f)(struct bt_config *cfg);
static void (*avdtpACPFree_f)(struct bt_config *);
static int bt_in_background;
static void
message(const char *fmt,...)
{
va_list list;
if (bt_in_background)
return;
va_start(list, fmt);
vfprintf(stderr, fmt, list);
va_end(list);
}
struct bt_audio_receiver {
const char *devname;
const char *sdp_socket_path;
uint16_t l2cap_psm;
int fd_listen;
void *sdp_session;
uint32_t sdp_handle;
};
static int
register_sdp(struct bt_audio_receiver *r)
{
struct sdp_audio_sink_profile record = {};
r->sdp_session = sdp_open_local(r->sdp_socket_path);
if (r->sdp_session == NULL || sdp_error(r->sdp_session)) {
sdp_close(r->sdp_session);
r->sdp_session = NULL;
return (0);
}
record.psm = r->l2cap_psm;
record.protover = 0x100;
record.features = 0x01; /* player only */
if (sdp_register_service(r->sdp_session, SDP_SERVICE_CLASS_AUDIO_SINK,
NG_HCI_BDADDR_ANY, (const uint8_t *)&record, sizeof(record),
&r->sdp_handle)) {
message("SDP failed to register: %s\n",
strerror(sdp_error(r->sdp_session)));
sdp_close(r->sdp_session);
r->sdp_session = NULL;
return (0);
}
return (1);
}
static void
unregister_sdp(struct bt_audio_receiver *r)
{
sdp_unregister_service(r->sdp_session, r->sdp_handle);
sdp_close(r->sdp_session);
r->sdp_session = NULL;
}
static int
start_listen(struct bt_audio_receiver *r)
{
struct sockaddr_l2cap addr = {};
r->fd_listen = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
if (r->fd_listen < 0)
return (0);
addr.l2cap_len = sizeof(addr);
addr.l2cap_family = AF_BLUETOOTH;
addr.l2cap_psm = r->l2cap_psm;
if (bind(r->fd_listen, (struct sockaddr *)&addr, sizeof(addr)) < 0 ||
listen(r->fd_listen, 4) < 0) {
close(r->fd_listen);
return (0);
}
return (1);
}
static void
stop_listen(struct bt_audio_receiver *r)
{
close(r->fd_listen);
}
struct bt_audio_connection {
struct bt_audio_receiver *r;
struct sockaddr_l2cap peer_addr;
struct bt_config cfg;
int oss_fd;
};
static void
close_connection(struct bt_audio_connection *c)
{
avdtpACPFree_f(&c->cfg);
if (c->cfg.fd != -1)
close(c->cfg.fd);
if (c->cfg.hc != -1)
close(c->cfg.hc);
if (c->oss_fd != -1)
close(c->oss_fd);
free(c);
}
static struct bt_audio_connection *
wait_for_connection(struct bt_audio_receiver *r)
{
struct bt_audio_connection *c =
malloc(sizeof(struct bt_audio_connection));
socklen_t addrlen;
memset(c, 0, sizeof(*c));
c->r = r;
c->cfg.fd = -1;
c->oss_fd = -1;
addrlen = sizeof(c->peer_addr);
c->cfg.hc = accept(r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen);
message("Accepted control connection, %d\n", c->cfg.hc);
if (c->cfg.hc < 0) {
close_connection(c);
return NULL;
}
c->cfg.sep = 0; /* to be set later */
c->cfg.media_Type = mediaTypeAudio;
c->cfg.chmode = MODE_DUAL;
c->cfg.aacMode1 = 0; /* TODO: support AAC */
c->cfg.aacMode2 = 0;
c->cfg.acceptor_state = acpInitial;
return (c);
}
static void
setup_oss(struct bt_audio_connection *c)
{
c->oss_fd = open(c->r->devname, O_WRONLY);
if (c->oss_fd < 0)
goto err;
int v;
switch (c->cfg.chmode) {
case MODE_STEREO:
case MODE_JOINT:
case MODE_DUAL:
v = 2;
break;
case MODE_MONO:
v = 1;
break;
default:
message("Wrong chmode\n");
goto err;
}
if (ioctl(c->oss_fd, SNDCTL_DSP_CHANNELS, &v) < 0) {
message("SNDCTL_DSP_CHANNELS failed\n");
goto err;
}
v = AFMT_S16_NE;
if (ioctl(c->oss_fd, SNDCTL_DSP_SETFMT, &v) < 0) {
message("SNDCTL_DSP_SETFMT failed\n");
goto err;
}
switch (c->cfg.freq) {
case FREQ_16K:
v = 16000;
break;
case FREQ_32K:
v = 32000;
break;
case FREQ_44_1K:
v = 44100;
break;
case FREQ_48K:
v = 48000;
break;
default:
message("Wrong freq\n");
goto err;
}
if (ioctl(c->oss_fd, SNDCTL_DSP_SPEED, &v) < 0) {
message("SNDCTL_DSP_SETFMT failed\n");
goto err;
}
v = (2 << 16) | 15; /* 2 fragments of 32k each */
if (ioctl(c->oss_fd, SNDCTL_DSP_SETFRAGMENT, &v) < 0) {
message("SNDCTL_DSP_SETFRAGMENT failed\n");
goto err;
}
return;
err:
c->oss_fd = -1;
message("Cannot open oss device %s\n", c->r->devname);
}
static void
process_connection(struct bt_audio_connection *c)
{
struct pollfd pfd[3] = {};
time_t oss_attempt = 0;
while (c->cfg.acceptor_state != acpStreamClosed) {
int np;
pfd[0].fd = c->r->fd_listen;
pfd[0].events = POLLIN | POLLRDNORM;
pfd[0].revents = 0;
pfd[1].fd = c->cfg.hc;
pfd[1].events = POLLIN | POLLRDNORM;
pfd[1].revents = 0;
pfd[2].fd = c->cfg.fd;
pfd[2].events = POLLIN | POLLRDNORM;
pfd[2].revents = 0;
if (c->cfg.fd != -1)
np = 3;
else
np = 2;
if (poll(pfd, np, INFTIM) < 0)
return;
if (pfd[1].revents != 0) {
int retval;
message("Handling packet: state = %d, ",
c->cfg.acceptor_state);
retval = avdtpACPHandlePacket_f(&c->cfg);
message("retval = %d\n", retval);
if (retval < 0)
return;
}
if (pfd[0].revents != 0) {
socklen_t addrlen = sizeof(c->peer_addr);
int fd = accept4(c->r->fd_listen,
(struct sockaddr *)&c->peer_addr, &addrlen,
SOCK_NONBLOCK);
if (fd < 0)
return;
if (c->cfg.fd < 0) {
if (c->cfg.acceptor_state == acpStreamOpened) {
socklen_t mtusize = sizeof(uint16_t);
c->cfg.fd = fd;
if (getsockopt(c->cfg.fd, SOL_L2CAP, SO_L2CAP_IMTU, &c->cfg.mtu, &mtusize) == -1) {
message("Could not get MTU size\n");
return;
}
int temp = c->cfg.mtu * 32;
if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) {
message("Could not set send buffer size\n");
return;
}
temp = 1;
if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) {
message("Could not set low water mark\n");
return;
}
message("Accepted data connection, %d\n", c->cfg.fd);
}
} else {
close(fd);
}
}
if (pfd[2].revents != 0) {
uint8_t data[65536];
int len;
if ((len = bt_receive_f(&c->cfg, data, sizeof(data), 0)) < 0) {
return;
}
if (c->cfg.acceptor_state != acpStreamSuspended &&
c->oss_fd < 0 &&
time(NULL) != oss_attempt) {
message("Trying to open dsp\n");
setup_oss(c);
oss_attempt = time(NULL);
}
if (c->oss_fd > -1) {
uint8_t *end = data + len;
uint8_t *ptr = data;
unsigned delay;
unsigned jitter_limit;
switch (c->cfg.freq) {
case FREQ_16K:
jitter_limit = (16000 / 20);
break;
case FREQ_32K:
jitter_limit = (32000 / 20);
break;
case FREQ_44_1K:
jitter_limit = (44100 / 20);
break;
default:
jitter_limit = (48000 / 20);
break;
}
if (c->cfg.chmode == MODE_MONO) {
if (len >= 2 &&
ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
delay < (jitter_limit * 2)) {
uint8_t jitter[jitter_limit * 4] __aligned(4);
size_t x;
/* repeat last sample */
for (x = 0; x != sizeof(jitter); x++)
jitter[x] = ptr[x % 2];
write(c->oss_fd, jitter, sizeof(jitter));
}
} else {
if (len >= 4 &&
ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
delay < (jitter_limit * 4)) {
uint8_t jitter[jitter_limit * 8] __aligned(4);
size_t x;
/* repeat last sample */
for (x = 0; x != sizeof(jitter); x++)
jitter[x] = ptr[x % 4];
write(c->oss_fd, jitter, sizeof(jitter));
}
}
while (ptr != end) {
int written = write(c->oss_fd, ptr, end - ptr);
if (written < 0) {
if (errno != EINTR && errno != EAGAIN)
break;
written = 0;
}
ptr += written;
}
if (ptr != end) {
message("Not all written, closing dsp\n");
close(c->oss_fd);
c->oss_fd = -1;
oss_attempt = time(NULL);
}
}
}
if (c->cfg.acceptor_state == acpStreamSuspended &&
c->oss_fd > -1) {
close(c->oss_fd);
c->oss_fd = -1;
}
}
}
static struct option bt_speaker_opts[] = {
{"device", required_argument, NULL, 'd'},
{"sdp_socket_path", required_argument, NULL, 'p'},
{"rtprio", required_argument, NULL, 'i'},
{"background", no_argument, NULL, 'B'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
static void
usage(void)
{
fprintf(stderr, "Usage: virtual_bt_speaker -d /dev/dsp\n"
"\t" "-d, --device [device]\n"
"\t" "-p, --sdp_socket_path [path]\n"
"\t" "-i, --rtprio [priority]\n"
"\t" "-B, --background\n"
);
exit(EX_USAGE);
}
int
main(int argc, char **argv)
{
struct bt_audio_receiver r = {};
struct rtprio rtp = {};
void *hdl;
int ch;
r.devname = NULL;
r.sdp_socket_path = NULL;
r.l2cap_psm = SDP_UUID_PROTOCOL_AVDTP;
while ((ch = getopt_long(argc, argv, "p:i:d:Bh", bt_speaker_opts, NULL)) != -1) {
switch (ch) {
case 'd':
r.devname = optarg;
break;
case 'p':
r.sdp_socket_path = optarg;
break;
case 'B':
bt_in_background = 1;
break;
case 'i':
rtp.type = RTP_PRIO_REALTIME;
rtp.prio = atoi(optarg);
if (rtprio(RTP_SET, getpid(), &rtp) != 0) {
message("Cannot set realtime priority\n");
}
break;
default:
usage();
break;
}
}
if (r.devname == NULL)
errx(EX_USAGE, "No devicename specified");
if (bt_in_background) {
if (daemon(0, 0) != 0)
errx(EX_SOFTWARE, "Cannot become daemon");
}
if ((hdl = dlopen("/usr/lib/virtual_oss/voss_bt.so", RTLD_NOW)) == NULL)
errx(1, "%s", dlerror());
if ((bt_receive_f = dlsym(hdl, "bt_receive")) == NULL)
goto err_dlsym;
if ((avdtpACPHandlePacket_f = dlsym(hdl, "avdtpACPHandlePacket")) ==
NULL)
goto err_dlsym;
if ((avdtpACPFree_f = dlsym(hdl, "avdtpACPFree")) == NULL)
goto err_dlsym;
while (1) {
message("Starting to listen\n");
if (!start_listen(&r)) {
message("Failed to initialize server socket\n");
goto err_listen;
}
message("Registering service via SDP\n");
if (!register_sdp(&r)) {
message("Failed to register in SDP\n");
goto err_sdp;
}
while (1) {
message("Waiting for connection...\n");
struct bt_audio_connection *c = wait_for_connection(&r);
if (c == NULL) {
message("Failed to get connection\n");
goto err_conn;
}
message("Got connection...\n");
process_connection(c);
message("Connection finished...\n");
close_connection(c);
}
err_conn:
message("Unregistering service\n");
unregister_sdp(&r);
err_sdp:
stop_listen(&r);
err_listen:
sleep(5);
}
return (0);
err_dlsym:
warnx("%s", dlerror());
dlclose(hdl);
exit(EXIT_FAILURE);
}
@@ -0,0 +1,71 @@
.\"
.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
.\"
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\"
.Dd February 12, 2025
.Dt VIRTUAL_BT_SPEAKER 8
.Os
.Sh NAME
.Nm virtual_bt_speaker
.Nd virtual bluetooth speaker
.Sh SYNOPSIS
.Nm
.Op Fl h
.Sh DESCRIPTION
.Nm
provides bluetooth speaker functionality.
It receives connections from bluetooth devices that stream music, and
forwards the received stream to the given OSS device.
This utility depends on
.Xr sdpd 8
running in the background.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl B
Run program in background.
.It Fl d Ar devname
OSS device to play the received streams.
.It Fl p Ar socketpath
Path to SDP control socket.
Default is to use default location.
.It Fl i Ar priority
Set real-time priority.
.It Fl h
Show usage.
.El
.Sh EXAMPLES
.Bd -literal -offset indent
virtual_bt_speaker -d /dev/dspX
.Ed
.Sh SEE ALSO
.Xr sdpd 8
and
.Xr virtual_oss 8
.Sh AUTHORS
.Nm
was written by
.An Richard Kralovic riso@google.com .
@@ -0,0 +1,11 @@
PROG= virtual_equalizer
MAN= ${PROG}.8
SRCS= equalizer.c
CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \
-I/usr/local/include
LDFLAGS+= -L/usr/local/lib -lm -lfftw3
.include <bsd.prog.mk>
@@ -0,0 +1,431 @@
/*-
* Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/soundcard.h>
#include <sys/types.h>
#include <sys/un.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fftw3.h>
#include <getopt.h>
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include "virtual_oss.h"
struct Equalizer {
double rate;
int block_size;
int do_normalize;
/* (block_size * 2) elements, time domain */
double *fftw_time;
/* (block_size * 2) elements, half-complex, freq domain */
double *fftw_freq;
fftw_plan forward;
fftw_plan inverse;
};
static int be_silent = 0;
static void
message(const char *fmt,...)
{
va_list list;
if (be_silent)
return;
va_start(list, fmt);
vfprintf(stderr, fmt, list);
va_end(list);
}
/*
* Masking window value for -1 < x < 1.
*
* Window must be symmetric, thus, this function is queried for x >= 0
* only. Currently a Hann window.
*/
static double
equalizer_get_window(double x)
{
return (0.5 + 0.5 * cos(M_PI * x));
}
static int
equalizer_load_freq_amps(struct Equalizer *e, const char *config)
{
double prev_f = 0.0;
double prev_amp = 1.0;
double next_f = 0.0;
double next_amp = 1.0;
int i;
if (strncasecmp(config, "normalize", 4) == 0) {
while (*config != 0) {
if (*config == '\n') {
config++;
break;
}
config++;
}
e->do_normalize = 1;
} else {
e->do_normalize = 0;
}
for (i = 0; i <= (e->block_size / 2); ++i) {
const double f = (i * e->rate) / e->block_size;
while (f >= next_f) {
prev_f = next_f;
prev_amp = next_amp;
if (*config == 0) {
next_f = e->rate;
next_amp = prev_amp;
} else {
int len;
if (sscanf(config, "%lf %lf %n", &next_f, &next_amp, &len) == 2) {
config += len;
if (next_f < prev_f) {
message("Parse error: Nonincreasing sequence of frequencies.\n");
return (0);
}
} else {
message("Parse error.\n");
return (0);
}
}
if (prev_f == 0.0)
prev_amp = next_amp;
}
e->fftw_freq[i] = ((f - prev_f) / (next_f - prev_f)) * (next_amp - prev_amp) + prev_amp;
}
return (1);
}
static void
equalizer_init(struct Equalizer *e, int rate, int block_size)
{
size_t buffer_size;
e->rate = rate;
e->block_size = block_size;
buffer_size = sizeof(double) * e->block_size;
e->fftw_time = (double *)malloc(buffer_size);
e->fftw_freq = (double *)malloc(buffer_size);
e->forward = fftw_plan_r2r_1d(block_size, e->fftw_time, e->fftw_freq,
FFTW_R2HC, FFTW_MEASURE);
e->inverse = fftw_plan_r2r_1d(block_size, e->fftw_freq, e->fftw_time,
FFTW_HC2R, FFTW_MEASURE);
}
static int
equalizer_load(struct Equalizer *eq, const char *config)
{
int retval = 0;
int N = eq->block_size;
int buffer_size = sizeof(double) * N;
int i;
memset(eq->fftw_freq, 0, buffer_size);
message("\n\nReloading amplification specifications:\n%s\n", config);
if (!equalizer_load_freq_amps(eq, config))
goto end;
double *requested_freq = (double *)malloc(buffer_size);
memcpy(requested_freq, eq->fftw_freq, buffer_size);
fftw_execute(eq->inverse);
/* Multiply by symmetric window and shift */
for (i = 0; i < (N / 2); ++i) {
double weight = equalizer_get_window(i / (double)(N / 2)) / N;
eq->fftw_time[N / 2 + i] = eq->fftw_time[i] * weight;
}
for (i = (N / 2 - 1); i > 0; --i) {
eq->fftw_time[i] = eq->fftw_time[N - i];
}
eq->fftw_time[0] = 0;
fftw_execute(eq->forward);
for (i = 0; i < N; ++i) {
eq->fftw_freq[i] /= (double)N;
}
/* Debug output */
for (i = 0; i <= (N / 2); ++i) {
double f = (eq->rate / N) * i;
double a = sqrt(pow(eq->fftw_freq[i], 2.0) +
((i > 0 && i < N / 2) ? pow(eq->fftw_freq[N - i], 2.0) : 0));
a *= N;
double r = requested_freq[i];
message("%3.1lf Hz: requested %2.2lf, got %2.7lf (log10 = %.2lf), %3.7lfdb\n",
f, r, a, log(a) / log(10), (log(a / r) / log(10.0)) * 10.0);
}
/* Normalize FIR filter, if any */
if (eq->do_normalize) {
double sum = 0;
for (i = 0; i < N; ++i)
sum += fabs(eq->fftw_time[i]);
if (sum != 0.0) {
for (i = 0; i < N; ++i)
eq->fftw_time[i] /= sum;
}
}
for (i = 0; i < N; ++i) {
message("%.3lf ms: %.10lf\n", 1000.0 * i / eq->rate, eq->fftw_time[i]);
}
/* End of debug */
retval = 1;
free(requested_freq);
end:
return (retval);
}
static void
equalizer_done(struct Equalizer *eq)
{
fftw_destroy_plan(eq->forward);
fftw_destroy_plan(eq->inverse);
free(eq->fftw_time);
free(eq->fftw_freq);
}
static struct option equalizer_opts[] = {
{"device", required_argument, NULL, 'd'},
{"part", required_argument, NULL, 'p'},
{"channels", required_argument, NULL, 'c'},
{"what", required_argument, NULL, 'w'},
{"off", no_argument, NULL, 'o'},
{"quiet", no_argument, NULL, 'q'},
{"file", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
};
static void
usage(void)
{
message("Usage: virtual_equalizer -d /dev/vdsp.ctl \n"
"\t -d, --device [control device]\n"
"\t -w, --what [rx_dev,tx_dev,rx_loop,tx_loop, default tx_dev]\n"
"\t -p, --part [part number, default 0]\n"
"\t -c, --channels [channels, default -1]\n"
"\t -f, --file [read input from file, default standard input]\n"
"\t -o, --off [disable equalizer]\n"
"\t -q, --quiet\n"
"\t -h, --help\n");
exit(EX_USAGE);
}
int
main(int argc, char **argv)
{
struct virtual_oss_fir_filter fir = {};
struct virtual_oss_io_info info = {};
struct Equalizer e;
char buffer[65536];
unsigned cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER;
unsigned cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER;
unsigned cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
const char *dsp = NULL;
int rate;
int channels = -1;
int part = 0;
int opt;
int len;
int offset;
int disable = 0;
int f = STDIN_FILENO;
while ((opt = getopt_long(argc, argv, "d:c:f:op:w:qh",
equalizer_opts, NULL)) != -1) {
switch (opt) {
case 'd':
dsp = optarg;
break;
case 'c':
channels = atoi(optarg);
if (channels == 0) {
message("Wrong number of channels\n");
usage();
}
break;
case 'p':
part = atoi(optarg);
if (part < 0) {
message("Invalid part number\n");
usage();
}
break;
case 'w':
if (strcmp(optarg, "rx_dev") == 0) {
cmd_fir_set = VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER;
cmd_fir_get = VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER;
cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
} else if (strcmp(optarg, "tx_dev") == 0) {
cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER;
cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER;
cmd_info = VIRTUAL_OSS_GET_DEV_INFO;
} else if (strcmp(optarg, "rx_loop") == 0) {
cmd_fir_set = VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER;
cmd_fir_get = VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER;
cmd_info = VIRTUAL_OSS_GET_LOOP_INFO;
} else if (strcmp(optarg, "tx_loop") == 0) {
cmd_fir_set = VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER;
cmd_fir_get = VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER;
cmd_info = VIRTUAL_OSS_GET_LOOP_INFO;
} else {
message("Bad -w argument not recognized\n");
usage();
}
break;
case 'f':
if (f != STDIN_FILENO) {
message("Can only specify one file\n");
usage();
}
f = open(optarg, O_RDONLY);
if (f < 0) {
message("Cannot open specified file\n");
usage();
}
break;
case 'o':
disable = 1;
break;
case 'q':
be_silent = 1;
break;
default:
usage();
}
}
fir.number = part;
info.number = part;
int fd = open(dsp, O_RDWR);
if (fd < 0) {
message("Cannot open DSP device\n");
return (EX_SOFTWARE);
}
if (ioctl(fd, VIRTUAL_OSS_GET_SAMPLE_RATE, &rate) < 0) {
message("Cannot get sample rate\n");
return (EX_SOFTWARE);
}
if (ioctl(fd, cmd_fir_get, &fir) < 0) {
message("Cannot get current FIR filter\n");
return (EX_SOFTWARE);
}
if (disable) {
for (fir.channel = 0; fir.channel != channels; fir.channel++) {
if (ioctl(fd, cmd_fir_set, &fir) < 0) {
if (fir.channel == 0) {
message("Cannot disable FIR filter\n");
return (EX_SOFTWARE);
}
break;
}
}
return (0);
}
equalizer_init(&e, rate, fir.filter_size);
equalizer_load(&e, "");
if (f == STDIN_FILENO) {
if (ioctl(fd, cmd_info, &info) < 0) {
message("Cannot read part information\n");
return (EX_SOFTWARE);
}
message("Please enter EQ layout for %s, <freq> <gain>:\n", info.name);
}
offset = 0;
while (1) {
if (offset == (int)(sizeof(buffer) - 1)) {
message("Too much input data\n");
return (EX_SOFTWARE);
}
len = read(f, buffer + offset, sizeof(buffer) - 1 - offset);
if (len <= 0)
break;
offset += len;
}
buffer[offset] = 0;
close(f);
if (f == STDIN_FILENO)
message("Loading new EQ layout\n");
if (equalizer_load(&e, buffer) == 0) {
message("Invalid equalizer data\n");
return (EX_SOFTWARE);
}
fir.filter_data = e.fftw_time;
for (fir.channel = 0; fir.channel != channels; fir.channel++) {
if (ioctl(fd, cmd_fir_set, &fir) < 0) {
if (fir.channel == 0)
message("Cannot set FIR filter on channel\n");
break;
}
}
close(fd);
equalizer_done(&e);
return (0);
}
@@ -0,0 +1,127 @@
.\"
.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
.\"
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\"
.Dd February 12, 2025
.Dt VIRTUAL_EQUALIZER 8
.Os
.Sh NAME
.Nm virtual_equalizer
.Nd audio equalizer
.Sh SYNOPSIS
.Nm
.Op Fl h
.Op Fl o
.Op Fl q
.Op Fl d Ar devname
.Op Fl w Ar what
.Op Fl p Ar part
.Op Fl c Ar channels
.Op Fl f Ar file
.Sh DESCRIPTION
.Nm
sets the given frequency response for the given
.Xr virtual_oss 8
instance via the control character device given by the -d option.
The design goal of this equalizer is to provide precise equalization
for arbitrary requested frequency response at the expense of higher
latency, utilizing a so-called finite impulse response, FIR, filter.
.Pp
The requested frequency response is configured via standard input or
the file specified by the -f option.
There is one control point in per line.
Each line consists of two numbers, frequency in Hz and requested
amplification.
Amplification between two consecutive control points is a linear
interpolation of the given control point values.
.Pp
To make the filter finite, it is windowed in time domain using a Hann
window.
The windowing actually modifies the frequency response - the actual
response is a convolution of the requested response and spectrum of
the window.
This is, however, very close to the requested response.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl q
Be quiet and don't print anything to standard output.
.It Fl d Ar device
The
.Xr virtual_oss 8
control character device.
.It Fl w Ar what
Select what part the FIR filter should apply to.
Valid values are: rx_dev, tx_dev, rx_loop and tx_loop.
The default value is tx_dev.
.It Fl p Ar part
Select the index of the part given by the -w option to apply the filter to.
Default is zero.
.It Fl c Ar channels
Select number of channels to apply filter to, starting at channel zero.
By default all channels of the given part are updated.
.It Fl f Ar file
Read filter coefficients from the given file instead of standard input.
.It Fl o
Turn equalizer off.
.It Fl h
Show usage.
.El
.Sh EXAMPLES
To pass only frequencies between 200Hz and 400Hz:
.Bd -literal -offset indent
# Note that the -F and -G options enable FIR filtering.
virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\
-f /dev/dsp -d dsp.virtual -t vdsp.ctl
# For simplex operation use this:
virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\
-R /dev/null -O /dev/dsp -d dsp.virtual -t vdsp.ctl
# Load normalized filter points to avoid sample value overflow
cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2
NORMALIZE
199 0.0
200 1.0
400 1.0
401 0.0
EOF
# Load FIR filter based on sine frequency points
cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2
199 0.0
200 1.0
400 1.0
401 0.0
EOF
.Ed
.Sh SEE ALSO
.Xr virtual_oss 8
.Sh AUTHORS
.Nm
was written by
.An Richard Kralovic riso@google.com .
+24
View File
@@ -0,0 +1,24 @@
PROG= virtual_oss
MAN= ${PROG}.8
SRCS= audio_delay.c \
compressor.c \
ctl.c \
eq.c \
format.c \
httpd.c \
main.c \
mul.c \
ring.c \
virtual_oss.c
CFLAGS+= -I${SRCTOP}/contrib/libsamplerate
# The --export-dynamic-symbol flags below are needed because some backends make
# use of those symbols.
LDFLAGS+= -lpthread -lcuse -lnv -lm \
-Wl,--export-dynamic-symbol=virtual_oss_wait \
-Wl,--export-dynamic-symbol=voss_has_synchronization
LIBADD= samplerate
LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate
.include <bsd.prog.mk>
@@ -0,0 +1,238 @@
/*-
* Copyright (c) 2014 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <math.h>
#include <sysexits.h>
#include "int.h"
#define REF_FREQ 500 /* HZ */
uint32_t voss_ad_last_delay;
uint8_t voss_ad_enabled;
uint8_t voss_ad_output_signal;
uint8_t voss_ad_input_channel;
uint8_t voss_ad_output_channel;
static struct voss_ad {
double *wave;
double *sin_a;
double *cos_a;
double *sin_b;
double *cos_b;
double *buf_a;
double *buf_b;
double sum_sin_a;
double sum_cos_a;
double sum_sin_b;
double sum_cos_b;
uint32_t len_a;
uint32_t len_b;
uint32_t offset_a;
uint32_t offset_b;
} voss_ad;
void
voss_ad_reset(void)
{
uint32_t x;
for (x = 0; x != voss_ad.len_a; x++)
voss_ad.buf_a[x] = 0;
for (x = 0; x != voss_ad.len_b; x++)
voss_ad.buf_b[x] = 0;
voss_ad.sum_sin_a = 0;
voss_ad.sum_cos_a = 0;
voss_ad.sum_sin_b = 0;
voss_ad.sum_cos_b = 0;
voss_ad.offset_a = 0;
voss_ad.offset_b = 0;
voss_ad_last_delay = 0;
}
void
voss_ad_init(uint32_t rate)
{
double freq;
int samples;
int len;
int x;
len = sqrt(rate);
samples = len * len;
voss_ad.wave = malloc(sizeof(voss_ad.wave[0]) * samples);
voss_ad.sin_a = malloc(sizeof(voss_ad.sin_a[0]) * len);
voss_ad.cos_a = malloc(sizeof(voss_ad.cos_a[0]) * len);
voss_ad.buf_a = malloc(sizeof(voss_ad.buf_a[0]) * len);
voss_ad.len_a = len;
voss_ad.sin_b = malloc(sizeof(voss_ad.sin_b[0]) * samples);
voss_ad.cos_b = malloc(sizeof(voss_ad.cos_b[0]) * samples);
voss_ad.buf_b = malloc(sizeof(voss_ad.buf_b[0]) * samples);
voss_ad.len_b = samples;
if (voss_ad.sin_a == NULL || voss_ad.cos_a == NULL ||
voss_ad.sin_b == NULL || voss_ad.cos_b == NULL ||
voss_ad.buf_a == NULL || voss_ad.buf_b == NULL)
errx(EX_SOFTWARE, "Out of memory");
freq = 1.0;
while (1) {
double temp = freq * ((double)rate) / ((double)len);
if (temp >= REF_FREQ)
break;
freq += 1.0;
}
for (x = 0; x != len; x++) {
voss_ad.sin_a[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len));
voss_ad.cos_a[x] = cos(freq * 2.0 * M_PI * ((double)x) / ((double)len));
voss_ad.buf_a[x] = 0;
}
for (x = 0; x != samples; x++) {
voss_ad.wave[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len)) *
(1.0 + sin(2.0 * M_PI * ((double)x) / ((double)samples))) / 2.0;
voss_ad.sin_b[x] = sin(2.0 * M_PI * ((double)x) / ((double)samples));
voss_ad.cos_b[x] = cos(2.0 * M_PI * ((double)x) / ((double)samples));
voss_ad.buf_b[x] = 0;
}
}
static double
voss_add_decode_offset(double x /* cos */, double y /* sin */)
{
uint32_t v;
double r;
r = sqrt((x * x) + (y * y));
if (r == 0.0)
return (0);
x /= r;
y /= r;
v = 0;
if (y < 0) {
v |= 1;
y = -y;
}
if (x < 0) {
v |= 2;
x = -x;
}
if (y < x) {
r = acos(y);
} else {
r = asin(x);
}
switch (v) {
case 0:
r = (2.0 * M_PI) - r;
break;
case 1:
r = M_PI + r;
break;
case 3:
r = M_PI - r;
break;
default:
break;
}
return (r);
}
double
voss_ad_getput_sample(double sample)
{
double retval;
double phase;
uint32_t xa;
uint32_t xb;
xa = voss_ad.offset_a;
xb = voss_ad.offset_b;
retval = voss_ad.wave[xb];
sample -= voss_ad.buf_a[xa];
voss_ad.sum_sin_a += voss_ad.sin_a[xa] * sample;
voss_ad.sum_cos_a += voss_ad.cos_a[xa] * sample;
voss_ad.buf_a[xa] += sample;
sample = sqrt((voss_ad.sum_sin_a * voss_ad.sum_sin_a) +
(voss_ad.sum_cos_a * voss_ad.sum_cos_a));
sample -= voss_ad.buf_b[xb];
voss_ad.sum_sin_b += voss_ad.sin_b[xb] * sample;
voss_ad.sum_cos_b += voss_ad.cos_b[xb] * sample;
voss_ad.buf_b[xb] += sample;
if (++xa == voss_ad.len_a)
xa = 0;
if (++xb == voss_ad.len_b) {
xb = 0;
phase = voss_add_decode_offset(
voss_ad.sum_cos_b, voss_ad.sum_sin_b);
voss_ad_last_delay = (uint32_t)(phase * (double)(voss_ad.len_b) / (2.0 * M_PI)) - (voss_ad.len_a / 2);
if (voss_ad_last_delay > voss_ad.len_b)
voss_ad_last_delay = voss_ad.len_b;
}
voss_ad.offset_a = xa;
voss_ad.offset_b = xb;
return (retval * (1LL << voss_ad_output_signal));
}
@@ -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_ */
@@ -0,0 +1,76 @@
/*-
* Copyright (c) 2020 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <stdint.h>
#include <string.h>
#include "int.h"
#include "virtual_oss.h"
struct virtual_compressor voss_output_compressor_param = {
.knee = 85,
.attack = 3,
.decay = 20,
};
double voss_output_compressor_gain[VMAX_CHAN];
void
voss_compressor(int64_t *buffer, double *p_ch_gain,
const struct virtual_compressor *p_param, const unsigned samples,
const unsigned maxchan, const int64_t fmt_max)
{
int64_t knee_amp;
int64_t sample;
unsigned ch;
unsigned i;
double amp;
/* check if compressor is enabled */
if (p_param->enabled != 1)
return;
knee_amp = (fmt_max * p_param->knee) / VIRTUAL_OSS_KNEE_MAX;
for (ch = i = 0; i != samples; i++) {
sample = buffer[i];
if (sample < 0)
sample = -sample;
amp = p_ch_gain[ch];
if (sample > knee_amp) {
const double gain = (double)knee_amp / (double)sample;
if (gain < amp)
amp += (gain - amp) / (1LL << p_param->attack);
}
buffer[i] *= amp;
amp += (1.0 - amp) / (1LL << p_param->decay);
p_ch_gain[ch] = amp;
if (++ch == maxchan)
ch = 0;
}
}
+615
View File
@@ -0,0 +1,615 @@
/*-
* Copyright (c) 2012-2022 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/queue.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <cuse.h>
#include "int.h"
#include "virtual_oss.h"
int64_t voss_output_peak[VMAX_CHAN];
int64_t voss_input_peak[VMAX_CHAN];
static int
vctl_open(struct cuse_dev *pdev __unused, int fflags __unused)
{
return (0);
}
static int
vctl_close(struct cuse_dev *pdev __unused, int fflags __unused)
{
return (0);
}
static vprofile_t *
vprofile_by_index(const vprofile_head_t *phead, int index)
{
vprofile_t *pvp;
TAILQ_FOREACH(pvp, phead, entry) {
if (!index--)
return (pvp);
}
return (NULL);
}
static vmonitor_t *
vmonitor_by_index(int index, vmonitor_head_t *phead)
{
vmonitor_t *pvm;
TAILQ_FOREACH(pvm, phead, entry) {
if (!index--)
return (pvm);
}
return (NULL);
}
static int
vctl_ioctl(struct cuse_dev *pdev __unused, int fflags __unused,
unsigned long cmd, void *peer_data)
{
union {
int val;
struct virtual_oss_io_info io_info;
struct virtual_oss_mon_info mon_info;
struct virtual_oss_io_peak io_peak;
struct virtual_oss_mon_peak mon_peak;
struct virtual_oss_compressor out_lim;
struct virtual_oss_io_limit io_lim;
struct virtual_oss_master_peak master_peak;
struct virtual_oss_audio_delay_locator ad_locator;
struct virtual_oss_fir_filter fir_filter;
struct virtual_oss_system_info sys_info;
char options[VIRTUAL_OSS_OPTIONS_MAX];
} data;
vprofile_t *pvp;
vmonitor_t *pvm;
int chan;
int len;
int error;
len = IOCPARM_LEN(cmd);
if (len < 0 || len > (int)sizeof(data))
return (CUSE_ERR_INVALID);
if (cmd & IOC_IN) {
error = cuse_copy_in(peer_data, &data, len);
if (error)
return (error);
} else {
error = 0;
}
atomic_lock();
switch (cmd) {
case VIRTUAL_OSS_GET_DEV_INFO:
case VIRTUAL_OSS_SET_DEV_INFO:
case VIRTUAL_OSS_GET_DEV_PEAK:
case VIRTUAL_OSS_SET_DEV_LIMIT:
case VIRTUAL_OSS_GET_DEV_LIMIT:
case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
pvp = vprofile_by_index(&virtual_profile_client_head, data.val);
break;
case VIRTUAL_OSS_GET_LOOP_INFO:
case VIRTUAL_OSS_SET_LOOP_INFO:
case VIRTUAL_OSS_GET_LOOP_PEAK:
case VIRTUAL_OSS_SET_LOOP_LIMIT:
case VIRTUAL_OSS_GET_LOOP_LIMIT:
case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
pvp = vprofile_by_index(&virtual_profile_loopback_head, data.val);
break;
default:
pvp = NULL;
break;
}
switch (cmd) {
case VIRTUAL_OSS_GET_VERSION:
data.val = VIRTUAL_OSS_VERSION;
break;
case VIRTUAL_OSS_GET_DEV_INFO:
case VIRTUAL_OSS_GET_LOOP_INFO:
if (pvp == NULL ||
data.io_info.channel < 0 ||
data.io_info.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
break;
}
strlcpy(data.io_info.name, pvp->oss_name, sizeof(data.io_info.name));
chan = data.io_info.channel;
data.io_info.rx_amp = pvp->rx_shift[chan];
data.io_info.tx_amp = pvp->tx_shift[chan];
data.io_info.rx_chan = pvp->rx_src[chan];
data.io_info.tx_chan = pvp->tx_dst[chan];
data.io_info.rx_mute = pvp->rx_mute[chan] ? 1 : 0;
data.io_info.tx_mute = pvp->tx_mute[chan] ? 1 : 0;
data.io_info.rx_pol = pvp->rx_pol[chan] ? 1 : 0;
data.io_info.tx_pol = pvp->tx_pol[chan] ? 1 : 0;
data.io_info.bits = pvp->bits;
data.io_info.rx_delay = pvp->rec_delay;
data.io_info.rx_delay_limit = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_SET_DEV_INFO:
case VIRTUAL_OSS_SET_LOOP_INFO:
if (pvp == NULL ||
data.io_info.channel < 0 ||
data.io_info.channel >= (int)pvp->channels ||
data.io_info.rx_amp < -31 || data.io_info.rx_amp > 31 ||
data.io_info.tx_amp < -31 || data.io_info.tx_amp > 31 ||
data.io_info.rx_delay < 0 ||
data.io_info.rx_delay > (int)voss_dsp_sample_rate) {
error = CUSE_ERR_INVALID;
break;
}
chan = data.io_info.channel;
pvp->rx_shift[chan] = data.io_info.rx_amp;
pvp->tx_shift[chan] = data.io_info.tx_amp;
pvp->rx_src[chan] = data.io_info.rx_chan;
pvp->tx_dst[chan] = data.io_info.tx_chan;
pvp->rx_mute[chan] = data.io_info.rx_mute ? 1 : 0;
pvp->tx_mute[chan] = data.io_info.tx_mute ? 1 : 0;
pvp->rx_pol[chan] = data.io_info.rx_pol ? 1 : 0;
pvp->tx_pol[chan] = data.io_info.tx_pol ? 1 : 0;
pvp->rec_delay = data.io_info.rx_delay;
break;
case VIRTUAL_OSS_GET_INPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_input);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_INPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_input);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_OUTPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_output);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_OUTPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_output);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_LOCAL_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_local);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_LOCAL_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_local);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_DEV_PEAK:
case VIRTUAL_OSS_GET_LOOP_PEAK:
if (pvp == NULL ||
data.io_peak.channel < 0 ||
data.io_peak.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
break;
}
strlcpy(data.io_peak.name, pvp->oss_name, sizeof(data.io_peak.name));
chan = data.io_peak.channel;
data.io_peak.rx_peak_value = pvp->rx_peak_value[chan];
pvp->rx_peak_value[chan] = 0;
data.io_peak.tx_peak_value = pvp->tx_peak_value[chan];
pvp->tx_peak_value[chan] = 0;
data.io_peak.bits = pvp->bits;
break;
case VIRTUAL_OSS_GET_INPUT_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_input);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_GET_OUTPUT_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_output);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_GET_LOCAL_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_local);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_ADD_INPUT_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_input);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_ADD_OUTPUT_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_output);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_ADD_LOCAL_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_local);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_SET_OUTPUT_LIMIT:
if (data.out_lim.enabled < 0 ||
data.out_lim.enabled > 1 ||
data.out_lim.knee < VIRTUAL_OSS_KNEE_MIN ||
data.out_lim.knee > VIRTUAL_OSS_KNEE_MAX ||
data.out_lim.attack < VIRTUAL_OSS_ATTACK_MIN ||
data.out_lim.attack > VIRTUAL_OSS_ATTACK_MAX ||
data.out_lim.decay < VIRTUAL_OSS_DECAY_MIN ||
data.out_lim.decay > VIRTUAL_OSS_DECAY_MAX ||
data.out_lim.gain != 0) {
error = CUSE_ERR_INVALID;
break;
}
voss_output_compressor_param.enabled = data.out_lim.enabled;
voss_output_compressor_param.knee = data.out_lim.knee;
voss_output_compressor_param.attack = data.out_lim.attack;
voss_output_compressor_param.decay = data.out_lim.decay;
break;
case VIRTUAL_OSS_GET_OUTPUT_LIMIT:
data.out_lim.enabled = voss_output_compressor_param.enabled;
data.out_lim.knee = voss_output_compressor_param.knee;
data.out_lim.attack = voss_output_compressor_param.attack;
data.out_lim.decay = voss_output_compressor_param.decay;
data.out_lim.gain = 1000;
for (chan = 0; chan != VMAX_CHAN; chan++) {
int gain = voss_output_compressor_gain[chan] * 1000.0;
if (data.out_lim.gain > gain)
data.out_lim.gain = gain;
}
break;
case VIRTUAL_OSS_SET_DEV_LIMIT:
case VIRTUAL_OSS_SET_LOOP_LIMIT:
if (pvp == NULL ||
data.io_lim.param.enabled < 0 ||
data.io_lim.param.enabled > 1 ||
data.io_lim.param.knee < VIRTUAL_OSS_KNEE_MIN ||
data.io_lim.param.knee > VIRTUAL_OSS_KNEE_MAX ||
data.io_lim.param.attack < VIRTUAL_OSS_ATTACK_MIN ||
data.io_lim.param.attack > VIRTUAL_OSS_ATTACK_MAX ||
data.io_lim.param.decay < VIRTUAL_OSS_DECAY_MIN ||
data.io_lim.param.decay > VIRTUAL_OSS_DECAY_MAX ||
data.io_lim.param.gain != 0) {
error = CUSE_ERR_INVALID;
break;
}
pvp->rx_compressor_param.enabled = data.io_lim.param.enabled;
pvp->rx_compressor_param.knee = data.io_lim.param.knee;
pvp->rx_compressor_param.attack = data.io_lim.param.attack;
pvp->rx_compressor_param.decay = data.io_lim.param.decay;
break;
case VIRTUAL_OSS_GET_DEV_LIMIT:
case VIRTUAL_OSS_GET_LOOP_LIMIT:
if (pvp == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.io_lim.param.enabled = pvp->rx_compressor_param.enabled;
data.io_lim.param.knee = pvp->rx_compressor_param.knee;
data.io_lim.param.attack = pvp->rx_compressor_param.attack;
data.io_lim.param.decay = pvp->rx_compressor_param.decay;
data.io_lim.param.gain = 1000;
for (chan = 0; chan != VMAX_CHAN; chan++) {
int gain = pvp->rx_compressor_gain[chan] * 1000.0;
if (data.io_lim.param.gain > gain)
data.io_lim.param.gain = gain;
}
break;
case VIRTUAL_OSS_GET_OUTPUT_PEAK:
chan = data.master_peak.channel;
if (chan < 0 ||
chan >= (int)voss_max_channels) {
error = CUSE_ERR_INVALID;
break;
}
data.master_peak.bits = voss_dsp_bits;
data.master_peak.peak_value = voss_output_peak[chan];
voss_output_peak[chan] = 0;
break;
case VIRTUAL_OSS_GET_INPUT_PEAK:
chan = data.master_peak.channel;
if (chan < 0 ||
chan >= (int)voss_dsp_max_channels) {
error = CUSE_ERR_INVALID;
break;
}
data.master_peak.bits = voss_dsp_bits;
data.master_peak.peak_value = voss_input_peak[chan];
voss_input_peak[chan] = 0;
break;
case VIRTUAL_OSS_SET_RECORDING:
voss_is_recording = data.val ? 1 : 0;
break;
case VIRTUAL_OSS_GET_RECORDING:
data.val = voss_is_recording;
break;
case VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR:
if (data.ad_locator.channel_output < 0 ||
data.ad_locator.channel_output >= (int)voss_mix_channels) {
error = CUSE_ERR_INVALID;
break;
}
if (data.ad_locator.channel_input < 0 ||
data.ad_locator.channel_input >= (int)voss_mix_channels) {
error = CUSE_ERR_INVALID;
break;
}
if (data.ad_locator.signal_output_level < 0 ||
data.ad_locator.signal_output_level >= 64) {
error = CUSE_ERR_INVALID;
break;
}
voss_ad_enabled = (data.ad_locator.locator_enabled != 0);
voss_ad_output_signal = data.ad_locator.signal_output_level;
voss_ad_output_channel = data.ad_locator.channel_output;
voss_ad_input_channel = data.ad_locator.channel_input;
break;
case VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR:
data.ad_locator.locator_enabled = voss_ad_enabled;
data.ad_locator.signal_output_level = voss_ad_output_signal;
data.ad_locator.channel_output = voss_ad_output_channel;
data.ad_locator.channel_input = voss_ad_input_channel;
data.ad_locator.channel_last = voss_mix_channels - 1;
data.ad_locator.signal_input_delay = voss_ad_last_delay;
data.ad_locator.signal_delay_hz = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR:
voss_ad_reset();
break;
case VIRTUAL_OSS_ADD_OPTIONS:
data.options[VIRTUAL_OSS_OPTIONS_MAX - 1] = 0;
voss_add_options(data.options);
break;
case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
data.fir_filter.filter_size = pvp->rx_filter_size;
} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
error = CUSE_ERR_NO_MEMORY; /* filter disabled */
} else {
error = cuse_copy_out(pvp->rx_filter_data[data.fir_filter.channel],
data.fir_filter.filter_data,
sizeof(pvp->rx_filter_data[0][0]) *
data.fir_filter.filter_size);
}
break;
case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
data.fir_filter.filter_size = pvp->tx_filter_size;
} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
error = CUSE_ERR_NO_MEMORY; /* filter disabled */
} else {
error = cuse_copy_out(pvp->tx_filter_data[data.fir_filter.channel],
data.fir_filter.filter_data,
sizeof(pvp->tx_filter_data[0][0]) *
data.fir_filter.filter_size);
}
break;
case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
free(pvp->rx_filter_data[data.fir_filter.channel]);
pvp->rx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */
} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->rx_filter_size != 0) {
size_t size = sizeof(pvp->rx_filter_data[0][0]) * pvp->rx_filter_size;
if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
pvp->rx_filter_data[data.fir_filter.channel] = malloc(size);
if (pvp->rx_filter_data[data.fir_filter.channel] == NULL)
error = CUSE_ERR_NO_MEMORY;
else
memset(pvp->rx_filter_data[data.fir_filter.channel], 0, size);
}
if (pvp->rx_filter_data[data.fir_filter.channel] != NULL) {
error = cuse_copy_in(data.fir_filter.filter_data,
pvp->rx_filter_data[data.fir_filter.channel], size);
}
}
break;
case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
free(pvp->tx_filter_data[data.fir_filter.channel]);
pvp->tx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */
} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->tx_filter_size != 0) {
size_t size = sizeof(pvp->tx_filter_data[0][0]) * pvp->tx_filter_size;
if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
pvp->tx_filter_data[data.fir_filter.channel] = malloc(size);
if (pvp->tx_filter_data[data.fir_filter.channel] == NULL)
error = CUSE_ERR_NO_MEMORY;
else
memset(pvp->tx_filter_data[data.fir_filter.channel], 0, size);
}
if (pvp->tx_filter_data[data.fir_filter.channel] != NULL) {
error = cuse_copy_in(data.fir_filter.filter_data,
pvp->tx_filter_data[data.fir_filter.channel], size);
}
}
break;
case VIRTUAL_OSS_GET_SAMPLE_RATE:
data.val = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_GET_SYSTEM_INFO:
data.sys_info.tx_jitter_up = voss_jitter_up;
data.sys_info.tx_jitter_down = voss_jitter_down;
data.sys_info.sample_rate = voss_dsp_sample_rate;
data.sys_info.sample_bits = voss_dsp_bits;
data.sys_info.sample_channels = voss_mix_channels;
strlcpy(data.sys_info.rx_device_name, voss_dsp_rx_device,
sizeof(data.sys_info.rx_device_name));
strlcpy(data.sys_info.tx_device_name, voss_dsp_tx_device,
sizeof(data.sys_info.tx_device_name));
break;
default:
error = CUSE_ERR_INVALID;
break;
}
atomic_unlock();
if (error == 0) {
if (cmd & IOC_OUT)
error = cuse_copy_out(&data, peer_data, len);
}
return (error);
}
const struct cuse_methods vctl_methods = {
.cm_open = vctl_open,
.cm_close = vctl_close,
.cm_ioctl = vctl_ioctl,
};
+226
View File
@@ -0,0 +1,226 @@
/*-
* Copyright (c) 2021 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "int.h"
void
vclient_tx_equalizer(struct virtual_client *pvc,
int64_t *src, size_t total)
{
double *f_data;
size_t channels;
size_t f_size;
size_t x;
f_size = pvc->profile->tx_filter_size;
if (f_size == 0 || total == 0)
return;
channels = pvc->channels;
total /= channels;
while (1) {
size_t delta;
size_t offset;
size_t y;
offset = pvc->tx_filter_offset;
delta = f_size - offset;
if (delta > total)
delta = total;
for (x = 0; x != channels; x++) {
f_data = pvc->profile->tx_filter_data[x];
if (f_data == NULL)
continue;
for (y = 0; y != delta; y++) {
pvc->tx_filter_in[x][y + offset] = src[x + y * channels];
src[x + y * channels] = pvc->tx_filter_out[x][y + offset];
}
}
pvc->tx_filter_offset += delta;
total -= delta;
src += delta * channels;
/* check if there is enough data for a new transform */
if (pvc->tx_filter_offset == f_size) {
for (x = 0; x != channels; x++) {
f_data = pvc->profile->tx_filter_data[x];
if (f_data == NULL)
continue;
/* shift down output */
for (y = 0; y != f_size; y++) {
pvc->tx_filter_out[x][y] = pvc->tx_filter_out[x][y + f_size];
pvc->tx_filter_out[x][y + f_size] = 0;
}
/* perform transform */
voss_x3_multiply_double(pvc->tx_filter_in[x],
f_data, pvc->tx_filter_out[x], f_size);
}
pvc->tx_filter_offset = 0;
}
if (total == 0)
break;
}
}
void
vclient_rx_equalizer(struct virtual_client *pvc,
int64_t *src, size_t total)
{
double *f_data;
size_t channels;
size_t f_size;
size_t x;
f_size = pvc->profile->rx_filter_size;
if (f_size == 0 || total == 0)
return;
channels = pvc->channels;
total /= channels;
while (1) {
size_t delta;
size_t offset;
size_t y;
offset = pvc->rx_filter_offset;
delta = f_size - offset;
if (delta > total)
delta = total;
for (x = 0; x != channels; x++) {
f_data = pvc->profile->rx_filter_data[x];
if (f_data == NULL)
continue;
for (y = 0; y != delta; y++) {
pvc->rx_filter_in[x][y + offset] = src[x + y * channels];
src[x + y * channels] = pvc->rx_filter_out[x][y + offset];
}
}
pvc->rx_filter_offset += delta;
total -= delta;
src += delta * channels;
/* check if there is enough data for a new transform */
if (pvc->rx_filter_offset == f_size) {
for (x = 0; x != channels; x++) {
f_data = pvc->profile->rx_filter_data[x];
if (f_data == NULL)
continue;
/* shift output down */
for (y = 0; y != f_size; y++) {
pvc->rx_filter_out[x][y] = pvc->rx_filter_out[x][y + f_size];
pvc->rx_filter_out[x][y + f_size] = 0;
}
/* perform transform */
voss_x3_multiply_double(pvc->rx_filter_in[x],
f_data, pvc->rx_filter_out[x], f_size);
}
pvc->rx_filter_offset = 0;
}
if (total == 0)
break;
}
}
int
vclient_eq_alloc(struct virtual_client *pvc)
{
uint8_t x;
pvc->tx_filter_offset = 0;
pvc->rx_filter_offset = 0;
for (x = 0; x != pvc->channels; x++) {
uint32_t size;
size = pvc->profile->tx_filter_size;
if (size != 0) {
pvc->tx_filter_in[x] =
malloc(sizeof(pvc->tx_filter_in[x][0]) * size);
pvc->tx_filter_out[x] =
calloc(2 * size, sizeof(pvc->tx_filter_out[x][0]));
if (pvc->tx_filter_in[x] == NULL ||
pvc->tx_filter_out[x] == NULL)
goto error;
}
size = pvc->profile->rx_filter_size;
if (size != 0) {
pvc->rx_filter_in[x] =
malloc(sizeof(pvc->rx_filter_in[x][0]) * size);
pvc->rx_filter_out[x] =
calloc(2 * size, sizeof(pvc->rx_filter_out[x][0]));
if (pvc->rx_filter_in[x] == NULL ||
pvc->rx_filter_out[x] == NULL)
goto error;
}
}
return (0);
error:
vclient_eq_free(pvc);
return (ENOMEM);
}
void
vclient_eq_free(struct virtual_client *pvc)
{
uint8_t x;
pvc->tx_filter_offset = 0;
pvc->rx_filter_offset = 0;
for (x = 0; x != VMAX_CHAN; x++) {
free(pvc->tx_filter_in[x]);
pvc->tx_filter_in[x] = NULL;
free(pvc->rx_filter_in[x]);
pvc->rx_filter_in[x] = NULL;
free(pvc->tx_filter_out[x]);
pvc->tx_filter_out[x] = NULL;
free(pvc->rx_filter_out[x]);
pvc->rx_filter_out[x] = NULL;
}
}
+429
View File
@@ -0,0 +1,429 @@
/*-
* Copyright (c) 2012-2020 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/soundcard.h>
#include <stdint.h>
#include <string.h>
#include "int.h"
void
format_import(uint32_t fmt, const uint8_t *src, uint32_t len,
int64_t *dst)
{
const uint8_t *end = src + len;
int64_t val;
if (fmt & AFMT_16BIT) {
while (src != end) {
if (fmt & (AFMT_S16_LE | AFMT_U16_LE))
val = src[0] | (src[1] << 8);
else
val = src[1] | (src[0] << 8);
src += 2;
if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
val = val ^ 0x8000;
val <<= (64 - 16);
val >>= (64 - 16);
*dst++ = val;
}
} else if (fmt & AFMT_24BIT) {
while (src < end) {
if (fmt & (AFMT_S24_LE | AFMT_U24_LE))
val = src[0] | (src[1] << 8) | (src[2] << 16);
else
val = src[2] | (src[1] << 8) | (src[0] << 16);
src += 3;
if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
val = val ^ 0x800000;
val <<= (64 - 24);
val >>= (64 - 24);
*dst++ = val;
}
} else if (fmt & AFMT_32BIT) {
while (src < end) {
int64_t e, m, s;
if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE))
val = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
else
val = src[3] | (src[2] << 8) | (src[1] << 16) | (src[0] << 24);
src += 4;
if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
val = val ^ 0x80000000LL;
if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
e = (val >> 23) & 0xff;
/* NaN, +/- Inf or too small */
if (e == 0xff || e < 96) {
val = 0;
goto skip;
}
s = val & 0x80000000U;
if (e > 126) {
val = s == 0 ? format_max(fmt) :
-0x80000000LL;
goto skip;
}
m = 0x800000 | (val & 0x7fffff);
e += 8 - 127;
if (e < 0)
m >>= -e;
else
m <<= e;
val = s == 0 ? m : -m;
}
skip:
val <<= (64 - 32);
val >>= (64 - 32);
*dst++ = val;
}
} else if (fmt & AFMT_8BIT) {
while (src < end) {
val = src[0];
src += 1;
if (fmt & AFMT_U8)
val = val ^ 0x80;
val <<= (64 - 8);
val >>= (64 - 8);
*dst++ = val;
}
}
}
void
format_export(uint32_t fmt, const int64_t *src, uint8_t *dst, uint32_t len)
{
const uint8_t *end = dst + len;
int64_t val;
if (fmt & AFMT_16BIT) {
while (dst != end) {
val = *src++;
if (val > 0x7FFF)
val = 0x7FFF;
else if (val < -0x7FFF)
val = -0x7FFF;
if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
val = val ^ 0x8000;
if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
dst[0] = val;
dst[1] = val >> 8;
} else {
dst[1] = val;
dst[0] = val >> 8;
}
dst += 2;
}
} else if (fmt & AFMT_24BIT) {
while (dst != end) {
val = *src++;
if (val > 0x7FFFFF)
val = 0x7FFFFF;
else if (val < -0x7FFFFF)
val = -0x7FFFFF;
if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
val = val ^ 0x800000;
if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
dst[0] = val;
dst[1] = val >> 8;
dst[2] = val >> 16;
} else {
dst[2] = val;
dst[1] = val >> 8;
dst[0] = val >> 16;
}
dst += 3;
}
} else if (fmt & AFMT_32BIT) {
while (dst != end) {
int64_t r, e;
val = *src++;
if (val > 0x7FFFFFFFLL)
val = 0x7FFFFFFFLL;
else if (val < -0x7FFFFFFFLL)
val = -0x7FFFFFFFLL;
if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
if (val == 0)
r = 0;
else if (val == format_max(fmt))
r = 0x3f800000;
else if (val == -0x80000000LL)
r = 0x80000000U | 0x3f800000;
else {
r = 0;
if (val < 0) {
r |= 0x80000000U;
val = -val;
}
e = 127 - 8;
while ((val & 0x7f000000) != 0) {
val >>= 1;
e++;
}
while ((val & 0x7f800000) == 0) {
val <<= 1;
e--;
}
r |= (e & 0xff) << 23;
r |= val & 0x7fffff;
}
val = r;
}
if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
val = val ^ 0x80000000LL;
if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
dst[0] = val;
dst[1] = val >> 8;
dst[2] = val >> 16;
dst[3] = val >> 24;
} else {
dst[3] = val;
dst[2] = val >> 8;
dst[1] = val >> 16;
dst[0] = val >> 24;
}
dst += 4;
}
} else if (fmt & AFMT_8BIT) {
while (dst != end) {
val = *src++;
if (val > 0x7F)
val = 0x7F;
else if (val < -0x7F)
val = -0x7F;
if (fmt & (AFMT_U8))
val = val ^ 0x80;
dst[0] = val;
dst += 1;
}
}
}
int64_t
format_max(uint32_t fmt)
{
if (fmt & AFMT_16BIT)
return (0x7FFF);
else if (fmt & AFMT_24BIT)
return (0x7FFFFF);
else if (fmt & AFMT_32BIT)
return (0x7FFFFFFF);
else if (fmt & AFMT_8BIT)
return (0x7F);
return (0);
}
void
format_maximum(const int64_t *src, int64_t *dst, uint32_t ch,
uint32_t samples, int8_t shift)
{
const int64_t *end = src + (samples * ch);
int64_t max[ch];
int64_t temp;
uint32_t x;
memset(max, 0, sizeof(max));
while (src != end) {
for (x = 0; x != ch; x++) {
temp = *src++;
if (temp < 0)
temp = -temp;
if (temp > max[x])
max[x] = temp;
}
}
for (x = 0; x != ch; x++) {
if (shift < 0)
max[x] >>= -shift;
else
max[x] <<= shift;
if (dst[x] < max[x])
dst[x] = max[x];
}
}
void
format_remix(int64_t *buffer_data, uint32_t in_chans,
uint32_t out_chans, uint32_t samples)
{
uint32_t x;
if (out_chans > in_chans) {
uint32_t dst = out_chans * (samples - 1);
uint32_t src = in_chans * (samples - 1);
uint32_t fill = out_chans - in_chans;
for (x = 0; x != samples; x++) {
memset(buffer_data + dst + in_chans, 0, 8 * fill);
if (src != dst) {
memcpy(buffer_data + dst,
buffer_data + src,
in_chans * 8);
}
dst -= out_chans;
src -= in_chans;
}
} else if (out_chans < in_chans) {
uint32_t dst = 0;
uint32_t src = 0;
for (x = 0; x != samples; x++) {
if (src != dst) {
memcpy(buffer_data + dst,
buffer_data + src,
out_chans * 8);
}
dst += out_chans;
src += in_chans;
}
}
}
void
format_silence(uint32_t fmt, uint8_t *dst, uint32_t len)
{
const uint8_t *end = dst + len;
if (fmt & AFMT_16BIT) {
uint16_t val;
if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
val = 1U << 15;
else
val = 0;
while (dst != end) {
if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
dst[0] = val;
dst[1] = val >> 8;
} else {
dst[1] = val;
dst[0] = val >> 8;
}
dst += 2;
}
} else if (fmt & AFMT_24BIT) {
uint32_t val;
if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
val = 1U << 23;
else
val = 0;
while (dst != end) {
if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
dst[0] = val;
dst[1] = val >> 8;
dst[2] = val >> 16;
} else {
dst[2] = val;
dst[1] = val >> 8;
dst[0] = val >> 16;
}
dst += 3;
}
} else if (fmt & AFMT_32BIT) {
uint32_t val;
if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
val = 1U << 31;
else
val = 0;
while (dst != end) {
if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
dst[0] = val;
dst[1] = val >> 8;
dst[2] = val >> 16;
dst[3] = val >> 24;
} else {
dst[3] = val;
dst[2] = val >> 8;
dst[1] = val >> 16;
dst[0] = val >> 24;
}
dst += 4;
}
} else if (fmt & AFMT_8BIT) {
uint8_t val;
if (fmt & AFMT_U8)
val = 1U << 7;
else
val = 0;
while (dst != end) {
dst[0] = val;
dst += 1;
}
}
}
+844
View File
@@ -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 <sys/types.h>
#include <sys/queue.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/endian.h>
#include <sys/uio.h>
#include <sys/soundcard.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <poll.h>
#include <sysexits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <net/if_vlan_var.h>
#include <net/bpf.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "int.h"
#define VOSS_HTTPD_BIND_MAX 8
#define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */
struct http_state {
int fd;
uint64_t ts;
};
struct rtp_raw_packet {
struct {
uint32_t padding;
uint8_t dhost[6];
uint8_t shost[6];
uint16_t ether_type;
} __packed eth;
struct {
uint8_t hl_ver;
uint8_t tos;
uint16_t len;
uint16_t ident;
uint16_t offset;
uint8_t ttl;
uint8_t protocol;
uint16_t chksum;
union {
uint32_t sourceip;
uint16_t source16[2];
};
union {
uint32_t destip;
uint16_t dest16[2];
};
} __packed ip;
struct {
uint16_t srcport;
uint16_t dstport;
uint16_t len;
uint16_t chksum;
} __packed udp;
union {
uint8_t header8[12];
uint16_t header16[6];
uint32_t header32[3];
} __packed rtp;
} __packed;
static const char *
voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd)
{
const char *perr = NULL;
struct vlanreq vr = {};
struct ifreq ifr = {};
int fd;
fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (fd < 0) {
perr = "Cannot open raw RTP socket";
goto done;
}
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
ifr.ifr_data = (void *)&vr;
if (ioctl(fd, SIOCGETVLAN, &ifr) == 0)
pvc->profile->http.rtp_vlanid = vr.vlr_tag;
else
pvc->profile->http.rtp_vlanid = 0;
close(fd);
ifr.ifr_data = NULL;
*pfd = fd = open("/dev/bpf", O_RDWR);
if (fd < 0) {
perr = "Cannot open BPF device";
goto done;
}
if (ioctl(fd, BIOCSETIF, &ifr) != 0) {
perr = "Cannot bind BPF device to network interface";
goto done;
}
done:
if (perr != NULL && fd > -1)
close(fd);
return (perr);
}
static uint16_t
voss_ipv4_csum(const 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"
"<html><head><title>Welcome to live streaming</title>"
"<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />"
"<meta http-equiv=\"Pragma\" content=\"no-cache\" />"
"<meta http-equiv=\"Expires\" content=\"0\" />"
"</head>"
"<body>"
"<h1>Live HD stream</h1>"
"<br>"
"<br>"
"<h2>Alternative 1 (recommended)</h2>"
"<ol type=\"1\">"
"<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>"
"<li>Open VLC and select Network Stream</li>"
"<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>"
"</ol>"
"<br>"
"<br>"
"<h2>Alternative 2 (on your own)</h2>"
"<br>"
"<br>"
"<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>"
"<br>"
"<br>",
pvc->profile->http.host, pvc->profile->http.port,
pvc->profile->http.host, pvc->profile->http.port);
if (x == pvc->profile->http.nstate)
fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x);
else
fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x);
fprintf(io, "</body></html>");
break;
case 1:
for (x = 0; x < pvc->profile->http.nstate; x++) {
if (pvc->profile->http.state[x].fd >= 0)
continue;
switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) {
static const int enable = 1;
case 0:
fflush(io);
fdclose(io, NULL);
if (ioctl(fd, FIONBIO, &enable) != 0) {
close(fd);
return;
}
pvc->profile->http.state[x].ts =
virtual_oss_timestamp() - 1000000000ULL;
pvc->profile->http.state[x].fd = fd;
return;
case 1:
fclose(io);
return;
case 2:
fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n");
goto done;
default:
goto done;
}
}
fprintf(io, "HTTP/1.0 503 Out of Resources\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n");
break;
case 2:
fprintf(io, "HTTP/1.0 200 OK\r\n"
"Content-Type: audio/mpegurl\r\n"
"Server: virtual_oss/1.0\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"\r\n");
if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) {
fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port);
} else {
fprintf(io, "http://%s:%s/stream.wav\r\n",
pvc->profile->http.host, pvc->profile->http.port);
}
break;
default:
fprintf(io, "HTTP/1.0 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Server: virtual_oss/1.0\r\n"
"\r\n"
"<html><head><title>virtual_oss</title></head>"
"<body>"
"<h1>Invalid page requested! "
"<a HREF=\"index.html\">Click here to go back</a>.</h1><br>"
"</body>"
"</html>");
break;
}
done:
if (io != NULL)
fclose(io);
else
close(fd);
}
static int
voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port,
struct pollfd *pfd, int num_sock, int buffer)
{
static const struct timeval timeout = {.tv_sec = 1};
struct addrinfo hints = {};
struct addrinfo *res;
struct addrinfo *res0;
int error;
int flag;
int s;
int ns = 0;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
if ((error = getaddrinfo(host, port, &hints, &res)))
return (-1);
res0 = res;
do {
if ((s = socket(res0->ai_family, res0->ai_socktype,
res0->ai_protocol)) < 0)
continue;
flag = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag));
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer));
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer));
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout));
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout));
if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) {
if (listen(s, pvc->profile->http.nstate) == 0) {
if (ns < num_sock) {
pfd[ns++].fd = s;
continue;
}
close(s);
break;
}
}
close(s);
} while ((res0 = res0->ai_next) != NULL);
freeaddrinfo(res);
return (ns);
}
static size_t
voss_httpd_buflimit(vclient_t *pvc)
{
/* don't buffer more than 250ms */
return ((pvc->sample_rate / 4) *
pvc->channels * vclient_sample_bytes(pvc));
};
static void
voss_httpd_server(vclient_t *pvc)
{
const size_t bufferlimit = voss_httpd_buflimit(pvc);
const char *host = pvc->profile->http.host;
const char *port = pvc->profile->http.port;
struct sockaddr sa = {};
struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {};
int nfd;
nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit);
if (nfd < 1) {
errx(EX_SOFTWARE, "Could not bind to "
"'%s' and '%s'", host, port);
}
while (1) {
struct sockaddr_in si;
int ns = nfd;
int c;
int f;
for (c = 0; c != ns; c++) {
fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI |
POLLERR | POLLHUP | POLLNVAL);
fds[c].revents = 0;
}
if (poll(fds, ns, -1) < 0)
errx(EX_SOFTWARE, "Polling failed");
for (c = 0; c != ns; c++) {
socklen_t socklen = sizeof(sa);
if (fds[c].revents == 0)
continue;
f = accept(fds[c].fd, &sa, &socklen);
if (f < 0)
continue;
memcpy(&si, &sa, sizeof(sa));
voss_httpd_handle_connection(pvc, f, &si);
}
}
}
static void
voss_httpd_streamer(vclient_t *pvc)
{
const size_t bufferlimit = voss_httpd_buflimit(pvc);
uint8_t *ptr;
size_t len;
uint64_t ts;
size_t x;
atomic_lock();
while (1) {
if (vclient_export_read_locked(pvc) != 0) {
atomic_wait();
continue;
}
vring_get_read(&pvc->rx_ring[1], &ptr, &len);
if (len == 0) {
/* try to avoid ring wraps */
vring_reset(&pvc->rx_ring[1]);
atomic_wait();
continue;
}
atomic_unlock();
ts = virtual_oss_timestamp();
/* check if we should send RTP data, if any */
if (pvc->profile->http.rtp_fd > -1) {
voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd,
ptr, len, pvc->profile->http.rtp_ts);
}
/* send HTTP data, if any */
for (x = 0; x < pvc->profile->http.nstate; x++) {
int fd = pvc->profile->http.state[x].fd;
uint64_t delta = ts - pvc->profile->http.state[x].ts;
uint8_t buf[1];
int write_len;
if (fd < 0) {
/* do nothing */
} else if (delta >= (8ULL * 1000000000ULL)) {
/* no data for 8 seconds - terminate */
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if (ioctl(fd, FIONWRITE, &write_len) < 0) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) {
/* do nothing */
} else if (write(fd, ptr, len) != (ssize_t)len) {
pvc->profile->http.state[x].fd = -1;
close(fd);
} else {
/* update timestamp */
pvc->profile->http.state[x].ts = ts;
}
}
atomic_lock();
vring_inc_read(&pvc->rx_ring[1], len);
}
}
const char *
voss_httpd_start(vprofile_t *pvp)
{
vclient_t *pvc;
pthread_t td;
int error;
size_t x;
if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0)
return (NULL);
pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate);
if (pvp->http.state == NULL)
return ("Could not allocate HTTP states");
for (x = 0; x != pvp->http.nstate; x++) {
pvp->http.state[x].fd = -1;
pvp->http.state[x].ts = 0;
}
pvc = vclient_alloc();
if (pvc == NULL)
return ("Could not allocate client for HTTP server");
pvc->profile = pvp;
if (pvp->http.rtp_ifname != NULL) {
const char *perr;
if (pvc->channels > 2)
return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth");
/* bind to UDP port */
perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname,
&pvp->http.rtp_fd);
if (perr != NULL)
return (perr);
/* setup buffers */
error = vclient_setup_buffers(pvc, 0, 0,
pvp->channels, AFMT_S16_LE, 44100);
} else {
pvp->http.rtp_fd = -1;
/* setup buffers */
error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
vclient_get_default_fmt(pvp, VTYPE_WAV_HDR),
voss_dsp_sample_rate);
}
if (error != 0) {
vclient_free(pvc);
return ("Could not allocate buffers for HTTP server");
}
/* trigger enabled */
pvc->rx_enabled = 1;
pvc->type = VTYPE_OSS_DAT;
atomic_lock();
TAILQ_INSERT_TAIL(&pvp->head, pvc, entry);
atomic_unlock();
if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc))
return ("Could not create HTTP daemon thread");
if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc))
return ("Could not create HTTP streamer thread");
return (NULL);
}
+327
View File
@@ -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 <signal.h>
#include <pthread.h>
#include <cuse.h>
#include <samplerate.h>
extern pthread_mutex_t atomic_mtx;
extern pthread_cond_t atomic_cv;
#define atomic_lock() pthread_mutex_lock(&atomic_mtx)
#define atomic_unlock() pthread_mutex_unlock(&atomic_mtx)
#define atomic_wait() pthread_cond_wait(&atomic_cv, &atomic_mtx)
#define atomic_wakeup() do { \
pthread_cond_broadcast(&atomic_cv); \
cuse_poll_wakeup(); \
} while (0)
#define AFMT_32BIT \
(AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \
AFMT_F32_LE | AFMT_F32_BE)
#define AFMT_24BIT \
(AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE)
#define AFMT_16BIT \
(AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)
#define AFMT_8BIT \
(AFMT_U8 | AFMT_S8)
#define VMAX_CHAN 64
#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_ */
File diff suppressed because it is too large Load Diff
+175
View File
@@ -0,0 +1,175 @@
/*-
* Copyright (c) 2017 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <stdint.h>
#include <string.h>
#include "int.h"
#ifndef VOSS_X3_LOG2_COMBA
#define VOSS_X3_LOG2_COMBA 5
#endif
#if (VOSS_X3_LOG2_COMBA < 2)
#error "VOSS_X3_LOG2_COMBA must be greater than 1"
#endif
struct voss_x3_input_double {
double a;
double b;
} __aligned(16);
/*
* <input size> = "stride"
* <output size> = 2 * "stride"
*/
static void
voss_x3_multiply_sub_double(struct voss_x3_input_double *input, double *ptr_low, double *ptr_high,
const size_t stride, const uint8_t toggle)
{
size_t x;
size_t y;
if (stride >= (1UL << VOSS_X3_LOG2_COMBA)) {
const size_t strideh = stride >> 1;
if (toggle) {
/* inverse step */
for (x = 0; x != strideh; x++) {
double a, b, c, d;
a = ptr_low[x];
b = ptr_low[x + strideh];
c = ptr_high[x];
d = ptr_high[x + strideh];
ptr_low[x + strideh] = a + b;
ptr_high[x] = a + b + c + d;
}
voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 1);
for (x = 0; x != strideh; x++)
ptr_low[x + strideh] = -ptr_low[x + strideh];
voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 1);
/* forward step */
for (x = 0; x != strideh; x++) {
double a, b, c, d;
a = ptr_low[x];
b = ptr_low[x + strideh];
c = ptr_high[x];
d = ptr_high[x + strideh];
ptr_low[x + strideh] = -a - b;
ptr_high[x] = c + b - d;
input[x + strideh].a += input[x].a;
input[x + strideh].b += input[x].b;
}
voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 0);
} else {
voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 1);
/* inverse step */
for (x = 0; x != strideh; x++) {
double a, b, c, d;
a = ptr_low[x];
b = ptr_low[x + strideh];
c = ptr_high[x];
d = ptr_high[x + strideh];
ptr_low[x + strideh] = -a - b;
ptr_high[x] = a + b + c + d;
input[x + strideh].a -= input[x].a;
input[x + strideh].b -= input[x].b;
}
voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 0);
for (x = 0; x != strideh; x++)
ptr_low[x + strideh] = -ptr_low[x + strideh];
voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 0);
/* forward step */
for (x = 0; x != strideh; x++) {
double a, b, c, d;
a = ptr_low[x];
b = ptr_low[x + strideh];
c = ptr_high[x];
d = ptr_high[x + strideh];
ptr_low[x + strideh] = b - a;
ptr_high[x] = c - b - d;
}
}
} else {
for (x = 0; x != stride; x++) {
double value = input[x].a;
for (y = 0; y != (stride - x); y++) {
ptr_low[x + y] += input[y].b * value;
}
for (; y != stride; y++) {
ptr_high[x + y - stride] += input[y].b * value;
}
}
}
}
/*
* <input size> = "max"
* <output size> = 2 * "max"
*/
void
voss_x3_multiply_double(const int64_t *va, const double *vb, double *pc, const size_t max)
{
struct voss_x3_input_double input[max];
size_t x;
/* check for non-power of two */
if (max & (max - 1))
return;
/* setup input vector */
for (x = 0; x != max; x++) {
input[x].a = va[x];
input[x].b = vb[x];
}
/* do multiplication */
voss_x3_multiply_sub_double(input, pc, pc + max, max, 1);
}
+213
View File
@@ -0,0 +1,213 @@
/*-
* Copyright (c) 2018 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include "int.h"
int
vring_alloc(struct virtual_ring *pvr, size_t size)
{
if (pvr->buf_start != NULL)
return (EBUSY);
pvr->buf_start = malloc(size);
if (pvr->buf_start == NULL)
return (ENOMEM);
pvr->pos_read = 0;
pvr->total_size = size;
pvr->len_write = 0;
return (0);
}
void
vring_free(struct virtual_ring *pvr)
{
if (pvr->buf_start != NULL) {
free(pvr->buf_start);
pvr->buf_start = NULL;
}
}
void
vring_reset(struct virtual_ring *pvr)
{
pvr->pos_read = 0;
pvr->len_write = 0;
}
void
vring_get_read(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen)
{
uint32_t delta;
if (pvr->buf_start == NULL) {
*pptr = NULL;
*plen = 0;
return;
}
delta = pvr->total_size - pvr->pos_read;
if (delta > pvr->len_write)
delta = pvr->len_write;
*pptr = pvr->buf_start + pvr->pos_read;
*plen = delta;
}
void
vring_get_write(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen)
{
uint32_t delta;
uint32_t len_read;
uint32_t pos_write;
if (pvr->buf_start == NULL) {
*pptr = NULL;
*plen = 0;
return;
}
pos_write = pvr->pos_read + pvr->len_write;
if (pos_write >= pvr->total_size)
pos_write -= pvr->total_size;
len_read = pvr->total_size - pvr->len_write;
delta = pvr->total_size - pos_write;
if (delta > len_read)
delta = len_read;
*pptr = pvr->buf_start + pos_write;
*plen = delta;
}
void
vring_inc_read(struct virtual_ring *pvr, size_t len)
{
pvr->pos_read += len;
pvr->len_write -= len;
/* check for wrap-around */
if (pvr->pos_read == pvr->total_size)
pvr->pos_read = 0;
}
void
vring_inc_write(struct virtual_ring *pvr, size_t len)
{
pvr->len_write += len;
}
size_t
vring_total_read_len(struct virtual_ring *pvr)
{
return (pvr->len_write);
}
size_t
vring_total_write_len(struct virtual_ring *pvr)
{
return (pvr->total_size - pvr->len_write);
}
size_t
vring_write_linear(struct virtual_ring *pvr, const uint8_t *src, size_t total)
{
uint8_t *buf_ptr;
size_t buf_len;
size_t sum = 0;
while (total != 0) {
vring_get_write(pvr, &buf_ptr, &buf_len);
if (buf_len == 0)
break;
if (buf_len > total)
buf_len = total;
memcpy(buf_ptr, src, buf_len);
vring_inc_write(pvr, buf_len);
src += buf_len;
sum += buf_len;
total -= buf_len;
}
return (sum);
}
size_t
vring_read_linear(struct virtual_ring *pvr, uint8_t *dst, size_t total)
{
uint8_t *buf_ptr;
size_t buf_len;
size_t sum = 0;
if (total > vring_total_read_len(pvr))
return (0);
while (total != 0) {
vring_get_read(pvr, &buf_ptr, &buf_len);
if (buf_len == 0)
break;
if (buf_len > total)
buf_len = total;
memcpy(dst, buf_ptr, buf_len);
vring_inc_read(pvr, buf_len);
dst += buf_len;
sum += buf_len;
total -= buf_len;
}
return (sum);
}
size_t
vring_write_zero(struct virtual_ring *pvr, size_t total)
{
uint8_t *buf_ptr;
size_t buf_len;
size_t sum = 0;
while (total != 0) {
vring_get_write(pvr, &buf_ptr, &buf_len);
if (buf_len == 0)
break;
if (buf_len > total)
buf_len = total;
memset(buf_ptr, 0, buf_len);
vring_inc_write(pvr, buf_len);
sum += buf_len;
total -= buf_len;
}
return (sum);
}
+31
View File
@@ -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_ */
@@ -0,0 +1,355 @@
.\"
.\" Copyright (c) 2017-2022 Hans Petter Selasky <hselasky@freebsd.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\"
.Dd September 3, 2024
.Dt VIRTUAL_OSS 8
.Os
.Sh NAME
.Nm virtual_oss
.Nd daemon to multiplex and demultiplex an OSS device
.Sh SYNOPSIS
.Nm
.Op Fl h
.Sh DESCRIPTION
.Nm
is an audio mixing application that multiplexes and demultiplexes a
single OSS device into multiple customizable OSS compatible devices
using character devices from userspace.
These devices can be used to record played back audio and mix the individual
channels in multiple ways.
.Pp
.Nm
requires the
.Xr cuse 3
kernel module.
To load the driver as a module at boot time, place the following line in
.Xr loader.conf 5 :
.Pp
cuse_load="YES"
.Pp
All channel numbers start at zero.
Left channel is zero and right channel is one.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl B
Run program in background.
.It Fl S
Enable automatic DSP rate resampling.
.It Fl Q Ar quality
Set resampling quality: 0=best, 1=medium and 2=fastest (default).
.It Fl b Ar bits
Set sample depth to
.Fa bits
for the subsequent commands.
Valid values are 8, 16, 24 and 32.
.It Fl r Ar rate
Set default sample-rate for the subsequent commands.
.It Fl s Ar value
Set default buffer size to
.Fa value .
If the argument is suffixed by "ms" it is interpreted as milliseconds.
Else the argument gives number of samples.
The buffer size specified is per channel.
If there are multiple channels, the total buffer size will be larger.
.It Fl i Ar priority
Set real-time priority to
.Fa priority .
Refer to
.Xr rtprio 1
for more information.
.It Fl a Ar log2_amp
Set the default DSP output and input device amplification to
.Fa log2_amp .
The specified amplification is logarithmic.
Valid values range from -63 to 63 inclusivly.
The device input amplification gets set to minus
.Fa log2_amp
and the device output amplification gets set to
.Fa log2_amp .
.It Fl a Ar i,log2_amp
Set the default DSP input device amplification to
.Fa log2_amp .
The specified amplification is logarithmic.
Valid values range from -63 to 63 inclusivly.
.It Fl a Ar o,log2_amp
Set default DSP output device amplification to
.Fa log2_amp .
The specified amplification is logarithmic.
Valid values range from -63 to 63 inclusivly.
.It Fl p Ar polarity
Set default polarity of DSP device.
A value of zero means normal polarity.
A value of one means negative polarity.
.It Fl e Ar rx_mute,tx_mute
Set default mute state of DSP device.
A value of zero means unmuted.
A value of one means muted.
.It Fl m Ar rx_ch,tx_ch,....
Set default channel mapping of DSP device, as a comma separated list of
integers.
The first integer selects the receive channel, the second value selects the
transmit channel and then it repeats.
A value of zero indicates the first receive or transmit channel.
.It Fl C Ar num
Set the maximum number of mix channels to
.Fa num .
.It Fl c Ar num
Set mix channels for the subsequent commands.
.It Fl M Ar type,src_ch,dst_ch,pol,mute,log2_gain
Add a monitoring filter.
The filter consists of a list of comma separated arguments.
The first argument indicates the type of monitoring filter:
.Bl -tag -width indent
.It i
Feedback one mix input channel into another mix output channel, for remote
feedback.
.It o
Add one mix output channel into another mix output channel, for creating a mix
of multiple output channels.
.It x
Feedback one mix output channel into another mix input channel, for local
feedback.
.El
The second argument gives the source mix channel.
The third argument gives the destination mix channel.
The fourth argument gives the polarity, default is zero.
The fifth argument gives the mute state, default is one or muted.
The sixth argument gives the amplitude, default is zero or no gain.
.It Fl t Ar devname
Set control device name.
.It Fl P Ar devname
Set playback DSP device only.
Specifying /dev/null is magic and means no playback device.
Specifying a
.Xr sndio 7
device descriptor prefixed by "/dev/sndio/" is also magic, and will use a sndio
backend rather than an OSS device.
.It Fl O Ar devname
Set playback DSP device only which acts as a master device.
This option is used in conjunction with -R /dev/null .
.It Fl R Ar devname
Set recording DSP device only.
Specifying /dev/null is magic and means no recording device.
.It Fl f Ar devname
Set both playback and recording DSP device
.It Fl w Ar name
Create a WAV file format compatible companion device by given name.
This option should be specified before the -d and -l options.
.It Fl d Ar name
Create an OSS device by given name.
.It Fl l Ar name
Create a loopback OSS device by given name.
.It Fl L Ar name
Create a loopback OSS device which acts as a master device.
This option is used in conjunction with -f /dev/null .
.It Fl F Ar size
Set receive filter size in number of samples or <milliseconds>ms for the next
device to be created.
.It Fl G Ar size
Set transmit filter size in number of samples or <milliseconds>ms for the next
device to be created.
.It Fl D Ar file
Write process ID of virtual_oss to file.
.It Fl g Ar knee,attack,decay
Enable device compressor in receive direction.
See description of -x option.
.It Fl x Ar knee,attack,decay
Enable output compressor and set knee, attack and decay.
Knee is in the range 0..255, while attack and decay are between 0 and 62.
Samples having an absolute value lower than the knee are transmitted
unchanged.
Sample values over the knee are lowered "a little bit".
You can think about attack and decay as a measure of how fast or slow the
gain of the compressor will work.
It is advised that attack is low, so it reacts fast once too high
sample values appear.
It is also advised that the decay value is higher than the attack value so
that the gain reduction is gradually removed.
The reasoning behind this is that the compressor should react almost
immediately when high volume signals arrive to protect the hardware,
but it slowly changes gain when there are no loud signals to avoid
distorting the signal.
The default values are 85,3,20 .
.It Fl E Ar enable_recording
If the value passed is non-zero, recording is enabled.
Else recording is disabled.
This can be used to synchronize multiple recording streams.
.It Fl h
Show usage and all available options.
.El
.Sh EXAMPLES
Split a 2-channel OSS compatible sound device into multiple subdevices:
.Bd -literal -offset indent
virtual_oss \\
-S \\
-c 2 -r 48000 -b 16 -s 4ms -f /dev/dspX \\
-a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.zyn \\
-a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.fld \\
-a 0 -b 16 -c 2 -m 0,0,1,1 -d dsp \\
-a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.jack.wav -d vdsp.jack \\
-a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.rec.wav -l vdsp.rec \\
-M i,0,0,0,1,0 \\
-M i,0,0,0,1,0 \\
-M i,0,0,0,1,0 \\
-M i,0,0,0,1,0 \\
-t vdsp.ctl
.Ed
.Pp
Split an 8-channel 24-bit OSS compatible sound device into multiple subdevices:
.Bd -literal -offset indent
sysctl dev.pcm.X.rec.vchanformat=s24le:7.1
sysctl dev.pcm.X.rec.vchanrate=48000
sysctl dev.pcm.X.play.vchanformat=s24le:7.1
sysctl dev.pcm.X.play.vchanrate=48000
sysctl dev.pcm.X.bitperfect=1
mixer vol.volume=1 pcm.volume=1
virtual_oss \\
-S \\
-i 8 \\
-x 85,3,20 \\
-C 16 -c 8 -r 48000 -b 32 -s 4ms -f /dev/dspX \\
-a 12 -b 16 -c 2 -m 0,4,1,5 -d dsp \\
-a 12 -b 16 -c 2 -m 8,8,9,9 -d vdsp \\
-a 13 -b 16 -c 2 -m 10,10,11,11 -d vdsp.fld \\
-a 0 -b 32 -c 4 -m 4,2,5,3,6,4,7,5 -d vdsp.jack \\
-a -3 -b 32 -c 2 -m 14,14,15,15 -d vdsp.zyn \\
-e 0,1 \\
-a 0 -b 32 -c 8 -m 0,8,1,9,2,8,3,9,4,8,5,9,6,8,7,9 -w vdsp.rec.mic.wav -d vdsp.rec.mic \\
-a 0 -b 32 -c 2 -m 0,8,1,9 -w vdsp.rec.master.wav -d vdsp.master.mic \\
-a 0 -b 32 -c 2 -m 10,10,11,11 -w vdsp.rec.fld.wav -l vdsp.rec.fld \\
-a 0 -b 32 -c 2 -m 12,12,13,13 -w vdsp.rec.jack.wav -l vdsp.rec.jack \\
-a 0 -b 32 -c 2 -m 14,14,15,15 -w vdsp.rec.zyn.wav -l vdsp.rec.zyn \\
-M o,8,0,0,0,0 \\
-M o,9,1,0,0,0 \\
-M o,10,0,0,0,0 \\
-M o,11,1,0,0,0 \\
-M o,12,0,0,0,0 \\
-M o,13,1,0,0,0 \\
-M o,14,0,0,0,0 \\
-M o,15,1,0,0,0 \\
-M i,14,14,0,1,0 \\
-M i,15,15,0,1,0 \\
-M x,8,0,0,1,0 \\
-M x,8,1,0,1,0 \\
-t vdsp.ctl
.Ed
.Pp
Create a secondary audio device sending its output audio into both
input and output channels of the main DSP device.
.Bd -literal -offset indent
virtual_oss \\
-C 4 -c 2 \\
-r 48000 \\
-b 24 \\
-s 4ms \\
-f /dev/dsp3 \\
-c 2 \\
-d dsp \\
-m 2,2,3,3 \\
-d dsp.speech \\
-M o,2,0,0,0,0 \\
-M o,3,1,0,0,0 \\
-M x,2,0,0,0,0 \\
-M x,3,1,0,0,0
.Ed
.Pp
Connect to a bluetooth audio headset, playback only:
.Bd -literal -offset indent
virtual_oss \\
-C 2 -c 2 -r 48000 -b 16 -s 4ms \\
-R /dev/null -P /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp
.Ed
.Pp
Connect to a bluetooth audio headset, playback and recording:
.Bd -literal -offset indent
virtual_oss \\
-C 2 -c 2 -r 48000 -b 16 -s 4ms \\
-f /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp
.Ed
.Pp
Create recording device which outputs a WAV-formatted file:
.Bd -literal -offset indent
virtual_oss \\
-C 2 -c 2 -r 48000 -b 16 -s 4ms \\
-f /dev/dspX -w dsp.wav -d dsp
.Ed
.Pp
Create a device named dsp.virtual which mix the samples written by all
clients and outputs the result for further processing into
dsp.virtual_out:
.Bd -literal -offset indent
virtual_oss \\
-S -Q 0 -b 16 -c 2 -r 96000 -s 100ms -i 20 \\
-f /dev/null -d dsp.virtual -L dsp.virtual_out
.Ed
.Pp
Create a playback-only audio device which sends its output to a remote
.Xr sndio 7
server:
.Bd -literal -offset indent
virtual_oss \\
-b 16 -c 2 -r 44100 -s 50ms \\
-R /dev/null -O /dev/sndio/snd@remotehost/0 -d dsp
.Ed
.Pp
Create a full-duplex audio device exchanging audio using the default
.Xr sndio 7
server:
.Bd -literal -offset indent
virtual_oss -S -b 16 -C 2 -c 2 -r 48000 -s 4ms \\
-f /dev/sndio/default -d dsp
.Ed
.Pp
How to set intel based CPUs in performance mode:
.Bd -literal -offset indent
sysctl -aN | fgrep dev.hwpstate | fgrep epp | \
while read OID
do
sysctl ${OID}=0
done
sysctl kern.sched.preempt_thresh=224
.Ed
.Sh NOTES
All character devices are created using the 0666 mode which gives
everyone in the system access.
.Sh SEE ALSO
.Xr cuse 3 ,
.Xr sound 4 ,
.Xr loader.conf 5 ,
.Xr sndio 7 ,
.Xr mixer 8 ,
.Xr sysctl 8 ,
.Xr virtual_bt_speaker 8 ,
.Xr virtual_equalizer 8 ,
.Xr virtual_oss_cmd 8
.Sh AUTHORS
.Nm
was written by
.An Hans Petter Selasky hselasky@freebsd.org .
@@ -0,0 +1,914 @@
/*-
* Copyright (c) 2012-2022 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/soundcard.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <time.h>
#include <assert.h>
#include "backend.h"
#include "int.h"
uint64_t
virtual_oss_delay_ns(void)
{
uint64_t delay;
delay = voss_dsp_samples;
delay *= 1000000000ULL;
delay /= voss_dsp_sample_rate;
return (delay);
}
void
virtual_oss_wait(void)
{
struct timespec ts;
uint64_t delay;
uint64_t nsec;
clock_gettime(CLOCK_MONOTONIC, &ts);
nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
/* TODO use virtual_oss_delay_ns() */
delay = voss_dsp_samples;
delay *= 1000000000ULL;
delay /= voss_dsp_sample_rate;
usleep((delay - (nsec % delay)) / 1000);
}
uint64_t
virtual_oss_timestamp(void)
{
struct timespec ts;
uint64_t nsec;
clock_gettime(CLOCK_MONOTONIC, &ts);
nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
return (nsec);
}
static size_t
vclient_read_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
int64_t *dst, size_t total) __requires_exclusive(atomic_mtx)
{
size_t total_read = 0;
pvc->sync_busy = 1;
while (1) {
size_t read = vring_read_linear(pvr, (uint8_t *)dst, 8 * total) / 8;
total_read += read;
dst += read;
total -= read;
if (!pvc->profile->synchronized || pvc->sync_wakeup ||
total == 0) {
/* fill rest of buffer with silence, if any */
if (total_read != 0 && total != 0)
memset(dst, 0, 8 * total);
break;
}
atomic_wait();
}
pvc->sync_busy = 0;
if (pvc->sync_wakeup)
atomic_wakeup();
vclient_tx_equalizer(pvc, dst - total_read, total_read);
return (total_read);
}
static size_t
vclient_write_linear(struct virtual_client *pvc, struct virtual_ring *pvr,
int64_t *src, size_t total) __requires_exclusive(atomic_mtx)
{
size_t total_written = 0;
vclient_rx_equalizer(pvc, src, total);
pvc->sync_busy = 1;
while (1) {
size_t written = vring_write_linear(pvr, (uint8_t *)src, total * 8) / 8;
total_written += written;
src += written;
total -= written;
if (!pvc->profile->synchronized || pvc->sync_wakeup ||
total == 0)
break;
atomic_wait();
}
pvc->sync_busy = 0;
if (pvc->sync_wakeup)
atomic_wakeup();
return (total_written);
}
static inline void
virtual_oss_mixer_core_sub(const int64_t *src, int64_t *dst,
uint32_t *pnoise, int src_chan, int dst_chan, int num,
int64_t volume, int shift, int shift_orig, bool pol,
bool assign)
{
if (pol)
volume = -volume;
if (shift < 0) {
shift = -shift;
while (num--) {
if (assign)
*dst = (*src * volume) >> shift;
else
*dst += (*src * volume) >> shift;
if (__predict_true(pnoise != NULL))
*dst += vclient_noise(pnoise, volume, shift_orig);
src += src_chan;
dst += dst_chan;
}
} else {
while (num--) {
if (assign)
*dst = (*src * volume) << shift;
else
*dst += (*src * volume) << shift;
if (__predict_true(pnoise != NULL))
*dst += vclient_noise(pnoise, volume, shift_orig);
src += src_chan;
dst += dst_chan;
}
}
}
static inline void
virtual_oss_mixer_core(const int64_t *src, int64_t *dst,
uint32_t *pnoise, int src_chan, int dst_chan, int num,
int64_t volume, int shift, int shift_orig, bool pol,
bool assign)
{
const uint8_t selector = (shift_orig > 0) + assign * 2;
/* optimize some cases */
switch (selector) {
case 0:
virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, false);
break;
case 1:
virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, false);
break;
case 2:
virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, true);
break;
case 3:
virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan,
num, volume, shift, shift_orig, pol, true);
break;
}
}
void *
virtual_oss_process(void *arg __unused)
{
vprofile_t *pvp;
vclient_t *pvc;
vmonitor_t *pvm;
struct voss_backend *rx_be = voss_rx_backend;
struct voss_backend *tx_be = voss_tx_backend;
int rx_fmt;
int tx_fmt;
int rx_chn;
int tx_chn;
int off;
int src_chans;
int dst_chans;
int src;
int len;
int samples;
int shift;
int shift_orig;
int shift_fmt;
int buffer_dsp_max_size;
int buffer_dsp_half_size;
int buffer_dsp_rx_sample_size;
int buffer_dsp_rx_size;
int buffer_dsp_tx_size_ref;
int buffer_dsp_tx_size;
uint64_t nice_timeout = 0;
uint64_t last_timestamp;
int blocks;
int volume;
int x_off;
int x;
int y;
uint8_t *buffer_dsp;
int64_t *buffer_monitor;
int64_t *buffer_temp;
int64_t *buffer_data;
int64_t *buffer_local;
int64_t *buffer_orig;
bool need_delay = false;
buffer_dsp_max_size = voss_dsp_samples *
voss_dsp_max_channels * (voss_dsp_bits / 8);
buffer_dsp_half_size = (voss_dsp_samples / 2) *
voss_dsp_max_channels * (voss_dsp_bits / 8);
buffer_dsp = malloc(buffer_dsp_max_size);
buffer_temp = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_monitor = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_local = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_data = malloc(voss_dsp_samples * voss_max_channels * 8);
buffer_orig = malloc(voss_dsp_samples * voss_max_channels * 8);
if (buffer_dsp == NULL || buffer_temp == NULL ||
buffer_monitor == NULL || buffer_local == NULL ||
buffer_data == NULL || buffer_orig == NULL)
errx(1, "Cannot allocate buffer memory");
while (1) {
rx_be->close(rx_be);
tx_be->close(tx_be);
if (voss_exit)
break;
if (need_delay)
sleep(2);
voss_dsp_rx_refresh = 0;
voss_dsp_tx_refresh = 0;
rx_be = voss_rx_backend;
tx_be = voss_tx_backend;
switch (voss_dsp_bits) {
case 8:
rx_fmt = tx_fmt =
AFMT_S8 | AFMT_U8;
break;
case 16:
rx_fmt = tx_fmt =
AFMT_S16_BE | AFMT_S16_LE |
AFMT_U16_BE | AFMT_U16_LE;
break;
case 24:
rx_fmt = tx_fmt =
AFMT_S24_BE | AFMT_S24_LE |
AFMT_U24_BE | AFMT_U24_LE;
break;
case 32:
rx_fmt = tx_fmt =
AFMT_S32_BE | AFMT_S32_LE |
AFMT_U32_BE | AFMT_U32_LE |
AFMT_F32_BE | AFMT_F32_LE;
break;
default:
rx_fmt = tx_fmt = 0;
break;
}
rx_chn = voss_dsp_max_channels;
if (rx_be->open(rx_be, voss_dsp_rx_device, voss_dsp_sample_rate,
buffer_dsp_half_size, &rx_chn, &rx_fmt) < 0) {
need_delay = true;
continue;
}
buffer_dsp_rx_sample_size = rx_chn * (voss_dsp_bits / 8);
buffer_dsp_rx_size = voss_dsp_samples * buffer_dsp_rx_sample_size;
tx_chn = voss_dsp_max_channels;
if (tx_be->open(tx_be, voss_dsp_tx_device, voss_dsp_sample_rate,
buffer_dsp_max_size, &tx_chn, &tx_fmt) < 0) {
need_delay = true;
continue;
}
buffer_dsp_tx_size_ref = voss_dsp_samples *
tx_chn * (voss_dsp_bits / 8);
/* reset compressor gain */
for (x = 0; x != VMAX_CHAN; x++)
voss_output_compressor_gain[x] = 1.0;
/* reset local buffer */
memset(buffer_local, 0, 8 * voss_dsp_samples * voss_max_channels);
while (1) {
uint64_t delta_time;
/* Check if DSP device should be re-opened */
if (voss_exit)
break;
if (voss_dsp_rx_refresh || voss_dsp_tx_refresh) {
need_delay = false;
break;
}
delta_time = nice_timeout - virtual_oss_timestamp();
/* Don't service more than 2x sample rate */
nice_timeout = virtual_oss_delay_ns() / 2;
if (delta_time >= 1000 && delta_time <= nice_timeout) {
/* convert from ns to us */
usleep(delta_time / 1000);
}
/* Compute next timeout */
nice_timeout += virtual_oss_timestamp();
/* Read in samples */
len = rx_be->transfer(rx_be, buffer_dsp, buffer_dsp_rx_size);
if (len < 0 || (len % buffer_dsp_rx_sample_size) != 0) {
need_delay = true;
break;
}
if (len == 0)
continue;
/* Convert to 64-bit samples */
format_import(rx_fmt, buffer_dsp, len, buffer_data);
samples = len / buffer_dsp_rx_sample_size;
src_chans = voss_mix_channels;
/* Compute master input peak values */
format_maximum(buffer_data, voss_input_peak, rx_chn, samples, 0);
/* Remix format */
format_remix(buffer_data, rx_chn, src_chans, samples);
/* Refresh timestamp */
last_timestamp = virtual_oss_timestamp();
atomic_lock();
if (TAILQ_FIRST(&virtual_monitor_input) != NULL) {
/* make a copy of the input data, in case of remote monitoring */
memcpy(buffer_monitor, buffer_data, 8 * samples * src_chans);
}
/* (0) Check for local monitoring of output data */
TAILQ_FOREACH(pvm, &virtual_monitor_local, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_local[(y * src_chans) + src] >> shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_local[(y * src_chans) + src] << shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_local[(y * src_chans) + src] >> shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_local[(y * src_chans) + src] << shift);
buffer_data[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
/* make a copy of the input data */
memcpy(buffer_orig, buffer_data, 8 * samples * src_chans);
/* (1) Distribute input samples to all clients */
TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
if (TAILQ_FIRST(&pvp->head) == NULL)
continue;
/* check if compressor should be applied */
voss_compressor(buffer_data, pvp->rx_compressor_gain,
&pvp->rx_compressor_param, samples * src_chans,
src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
TAILQ_FOREACH(pvc, &pvp->head, entry) {
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
for (x = 0; x != dst_chans; x++) {
src = pvp->rx_src[x];
shift_orig = pvp->rx_shift[x] - shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->rx_volume;
if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
for (y = 0; y != (samples * dst_chans); y += dst_chans)
buffer_temp[y + x] = 0;
continue;
}
virtual_oss_mixer_core(buffer_data + src, buffer_temp + x,
&pvc->rx_noise_rem, src_chans, dst_chans, samples,
volume, shift, shift_orig, pvp->rx_pol[x], true);
}
format_maximum(buffer_temp, pvp->rx_peak_value,
dst_chans, samples, shift_fmt);
/* check if recording is disabled */
if (pvc->rx_enabled == 0 ||
(voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
continue;
pvc->rx_timestamp = last_timestamp;
pvc->rx_samples += samples * dst_chans;
/* store data into ring buffer */
vclient_write_linear(pvc, &pvc->rx_ring[0],
buffer_temp, samples * dst_chans);
}
/* restore buffer, if any */
if (pvp->rx_compressor_param.enabled)
memcpy(buffer_data, buffer_orig, 8 * samples * src_chans);
}
/* fill main output buffer with silence */
memset(buffer_temp, 0, sizeof(buffer_temp[0]) *
samples * src_chans);
/* (2) Run audio delay locator */
if (voss_ad_enabled != 0) {
y = (samples * voss_mix_channels);
for (x = 0; x != y; x += voss_mix_channels) {
buffer_temp[x + voss_ad_output_channel] +=
voss_ad_getput_sample(buffer_data
[x + voss_ad_input_channel]);
}
}
/* (3) Load output samples from all clients */
TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
TAILQ_FOREACH(pvc, &pvp->head, entry) {
if (pvc->tx_enabled == 0)
continue;
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
/* update counters regardless of data presence */
pvc->tx_timestamp = last_timestamp;
pvc->tx_samples += samples * dst_chans;
/* read data from ring buffer */
if (vclient_read_linear(pvc, &pvc->tx_ring[0],
buffer_data, samples * dst_chans) == 0)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
format_maximum(buffer_data, pvp->tx_peak_value,
dst_chans, samples, shift_fmt);
for (x = 0; x != pvp->channels; x++) {
src = pvp->tx_dst[x];
shift_orig = pvp->tx_shift[x] + shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->tx_volume;
if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
continue;
/*
* Automagically re-map
* channels when the client is
* requesting fewer channels
* than specified in the
* profile. This typically
* allows automagic mono to
* stereo conversion.
*/
if (__predict_false(x >= dst_chans))
x_off = x % dst_chans;
else
x_off = x;
virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
&pvc->tx_noise_rem, dst_chans, src_chans, samples,
volume, shift, shift_orig, pvp->tx_pol[x], false);
}
}
}
/* (4) Load output samples from all loopbacks */
TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
TAILQ_FOREACH(pvc, &pvp->head, entry) {
if (pvc->tx_enabled == 0)
continue;
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
/* read data from ring buffer */
if (vclient_read_linear(pvc, &pvc->tx_ring[0],
buffer_data, samples * dst_chans) == 0)
continue;
pvc->tx_timestamp = last_timestamp;
pvc->tx_samples += samples * dst_chans;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
format_maximum(buffer_data, pvp->tx_peak_value,
dst_chans, samples, shift_fmt);
for (x = 0; x != pvp->channels; x++) {
src = pvp->tx_dst[x];
shift_orig = pvp->tx_shift[x] + shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->tx_volume;
if (pvp->tx_mute[x] || src >= src_chans || volume == 0)
continue;
/*
* Automagically re-map
* channels when the client is
* requesting fewer channels
* than specified in the
* profile. This typically
* allows automagic mono to
* stereo conversion.
*/
if (__predict_false(x >= dst_chans))
x_off = x % dst_chans;
else
x_off = x;
virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src,
&pvc->tx_noise_rem, dst_chans, src_chans, samples,
volume, shift, shift_orig, pvp->tx_pol[x], false);
}
}
}
/* (5) Check for input monitoring */
TAILQ_FOREACH(pvm, &virtual_monitor_input, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
if (TAILQ_FIRST(&virtual_monitor_output) != NULL) {
memcpy(buffer_monitor, buffer_temp,
8 * samples * src_chans);
}
/* (6) Check for output monitoring */
TAILQ_FOREACH(pvm, &virtual_monitor_output, entry) {
int64_t val;
if (pvm->mute != 0 || pvm->src_chan >= src_chans ||
pvm->dst_chan >= src_chans)
continue;
src = pvm->src_chan;
shift = pvm->shift;
x = pvm->dst_chan;
if (pvm->pol) {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = -(buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
} else {
if (shift < 0) {
shift = -shift;
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] >> shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
} else {
for (y = 0; y != samples; y++) {
val = (buffer_monitor[(y * src_chans) + src] << shift);
buffer_temp[(y * src_chans) + x] += val;
if (val < 0)
val = -val;
if (val > pvm->peak_value)
pvm->peak_value = val;
}
}
}
}
/* make a copy of the output data */
memcpy(buffer_data, buffer_temp, 8 * samples * src_chans);
/* make a copy for local monitoring, if any */
if (TAILQ_FIRST(&virtual_monitor_local) != NULL) {
const int end = src_chans * (voss_dsp_samples - samples);
const int offs = src_chans * samples;
assert(end >= 0);
/* shift down samples */
for (int xx = 0; xx != end; xx++)
buffer_local[xx] = buffer_local[xx + offs];
/* copy in new ones */
memcpy(buffer_local + end, buffer_temp, 8 * samples * src_chans);
}
/* (7) Check for output recording */
TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
if (TAILQ_FIRST(&pvp->head) == NULL)
continue;
/* check if compressor should be applied */
voss_compressor(buffer_temp, pvp->rx_compressor_gain,
&pvp->rx_compressor_param, samples,
samples * src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
TAILQ_FOREACH(pvc, &pvp->head, entry) {
dst_chans = pvc->channels;
if (dst_chans > (int)voss_max_channels)
continue;
shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8);
for (x = 0; x != dst_chans; x++) {
src = pvp->rx_src[x];
shift_orig = pvp->rx_shift[x] - shift_fmt;
shift = shift_orig - VVOLUME_UNIT_SHIFT;
volume = pvc->rx_volume;
if (pvp->rx_mute[x] || src >= src_chans || volume == 0) {
for (y = 0; y != (samples * dst_chans); y += dst_chans)
buffer_monitor[y + x] = 0;
continue;
}
virtual_oss_mixer_core(buffer_temp + src, buffer_monitor + x,
&pvc->rx_noise_rem, src_chans, dst_chans, samples,
volume, shift, shift_orig, pvp->rx_pol[x], true);
}
format_maximum(buffer_monitor, pvp->rx_peak_value,
dst_chans, samples, shift_fmt);
/* check if recording is disabled */
if (pvc->rx_enabled == 0 ||
(voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT))
continue;
pvc->rx_timestamp = last_timestamp;
pvc->rx_samples += samples * dst_chans;
/* store data into ring buffer */
vclient_write_linear(pvc, &pvc->rx_ring[0],
buffer_monitor, samples * dst_chans);
}
/* restore buffer, if any */
if (pvp->rx_compressor_param.enabled)
memcpy(buffer_temp, buffer_data, 8 * samples * src_chans);
}
atomic_wakeup();
format_remix(buffer_temp, voss_mix_channels, tx_chn, samples);
/* Compute master output peak values */
format_maximum(buffer_temp, voss_output_peak,
tx_chn, samples, 0);
/* Apply compressor, if any */
voss_compressor(buffer_temp, voss_output_compressor_gain,
&voss_output_compressor_param, samples * tx_chn,
tx_chn, format_max(tx_fmt));
/* Recompute buffer DSP transmit size according to received number of samples */
buffer_dsp_tx_size = samples * tx_chn * (voss_dsp_bits / 8);
/* Export and transmit resulting audio */
format_export(tx_fmt, buffer_temp, buffer_dsp,
buffer_dsp_tx_size);
atomic_unlock();
/* Get output delay in bytes */
tx_be->delay(tx_be, &blocks);
/*
* Simple fix for jitter: Repeat data when too
* little. Skip data when too much. This
* should not happen during normal operation.
*/
if (blocks == 0) {
blocks = 2; /* buffer is empty */
voss_jitter_up++;
} else if (blocks >= (3 * buffer_dsp_tx_size_ref)) {
blocks = 0; /* too much data */
voss_jitter_down++;
} else {
blocks = 1; /* normal */
}
len = 0;
while (blocks--) {
off = 0;
while (off < (int)buffer_dsp_tx_size) {
len = tx_be->transfer(tx_be, buffer_dsp + off,
buffer_dsp_tx_size - off);
if (len <= 0)
break;
off += len;
}
if (len <= 0)
break;
}
/* check for error only */
if (len < 0) {
need_delay = true;
break;
}
}
}
free(buffer_dsp);
free(buffer_temp);
free(buffer_monitor);
free(buffer_local);
free(buffer_data);
free(buffer_orig);
return (NULL);
}
@@ -0,0 +1,206 @@
/*-
* Copyright (c) 2012-2022 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _VIRTUAL_OSS_H_
#define _VIRTUAL_OSS_H_
#include <sys/ioccom.h>
#define VIRTUAL_OSS_NAME_MAX 32
#define VIRTUAL_OSS_VERSION 0x00010008
#define VIRTUAL_OSS_OPTIONS_MAX 1024 /* bytes */
#define VIRTUAL_OSS_FILTER_MAX 65536 /* samples */
#define VIRTUAL_OSS_GET_VERSION _IOR('O', 0, int)
struct virtual_oss_io_info {
int number; /* must be first */
int channel;
char name[VIRTUAL_OSS_NAME_MAX];
int bits;
int rx_amp;
int tx_amp;
int rx_chan;
int tx_chan;
int rx_mute;
int tx_mute;
int rx_pol;
int tx_pol;
int rx_delay; /* in samples */
int rx_delay_limit; /* in samples */
};
#define VIRTUAL_OSS_GET_DEV_INFO _IOWR('O', 1, struct virtual_oss_io_info)
#define VIRTUAL_OSS_SET_DEV_INFO _IOW('O', 2, struct virtual_oss_io_info)
#define VIRTUAL_OSS_GET_LOOP_INFO _IOWR('O', 3, struct virtual_oss_io_info)
#define VIRTUAL_OSS_SET_LOOP_INFO _IOW('O', 4, struct virtual_oss_io_info)
struct virtual_oss_mon_info {
int number;
int bits;
int src_chan;
int dst_chan;
int pol;
int mute;
int amp;
};
#define VIRTUAL_OSS_GET_INPUT_MON_INFO _IOWR('O', 5, struct virtual_oss_mon_info)
#define VIRTUAL_OSS_SET_INPUT_MON_INFO _IOW('O', 6, struct virtual_oss_mon_info)
#define VIRTUAL_OSS_GET_OUTPUT_MON_INFO _IOWR('O', 7, struct virtual_oss_mon_info)
#define VIRTUAL_OSS_SET_OUTPUT_MON_INFO _IOW('O', 8, struct virtual_oss_mon_info)
#define VIRTUAL_OSS_GET_LOCAL_MON_INFO _IOWR('O', 43, struct virtual_oss_mon_info)
#define VIRTUAL_OSS_SET_LOCAL_MON_INFO _IOW('O', 44, struct virtual_oss_mon_info)
struct virtual_oss_io_peak {
int number; /* must be first */
int channel;
char name[VIRTUAL_OSS_NAME_MAX];
int bits;
long long rx_peak_value;
long long tx_peak_value;
};
#define VIRTUAL_OSS_GET_DEV_PEAK _IOWR('O', 9, struct virtual_oss_io_peak)
#define VIRTUAL_OSS_GET_LOOP_PEAK _IOWR('O', 10, struct virtual_oss_io_peak)
struct virtual_oss_mon_peak {
int number;
int bits;
long long peak_value;
};
#define VIRTUAL_OSS_GET_INPUT_MON_PEAK _IOWR('O', 11, struct virtual_oss_mon_peak)
#define VIRTUAL_OSS_GET_OUTPUT_MON_PEAK _IOWR('O', 12, struct virtual_oss_mon_peak)
#define VIRTUAL_OSS_GET_LOCAL_MON_PEAK _IOWR('O', 45, struct virtual_oss_mon_peak)
#define VIRTUAL_OSS_ADD_INPUT_MON _IOR('O', 13, int)
#define VIRTUAL_OSS_ADD_OUTPUT_MON _IOR('O', 14, int)
#define VIRTUAL_OSS_ADD_LOCAL_MON _IOR('O', 46, int)
struct virtual_oss_compressor {
int enabled;
int knee;
#define VIRTUAL_OSS_KNEE_MAX 255 /* inclusive */
#define VIRTUAL_OSS_KNEE_MIN 0
int attack;
#define VIRTUAL_OSS_ATTACK_MAX 62 /* inclusive */
#define VIRTUAL_OSS_ATTACK_MIN 0
int decay;
#define VIRTUAL_OSS_DECAY_MAX 62 /* inclusive */
#define VIRTUAL_OSS_DECAY_MIN 0
int gain; /* read only */
#define VIRTUAL_OSS_GAIN_MAX 1000 /* inclusive */
#define VIRTUAL_OSS_GAIN_MIN 0
};
#define VIRTUAL_OSS_SET_OUTPUT_LIMIT _IOW('O', 17, struct virtual_oss_compressor)
#define VIRTUAL_OSS_GET_OUTPUT_LIMIT _IOWR('O', 18, struct virtual_oss_compressor)
struct virtual_oss_io_limit {
int number; /* must be first */
struct virtual_oss_compressor param;
};
#define VIRTUAL_OSS_SET_DEV_LIMIT _IOW('O', 19, struct virtual_oss_io_limit)
#define VIRTUAL_OSS_GET_DEV_LIMIT _IOWR('O', 20, struct virtual_oss_io_limit)
#define VIRTUAL_OSS_SET_LOOP_LIMIT _IOW('O', 21, struct virtual_oss_io_limit)
#define VIRTUAL_OSS_GET_LOOP_LIMIT _IOWR('O', 22, struct virtual_oss_io_limit)
struct virtual_oss_master_peak {
int channel;
int bits;
long long peak_value;
};
#define VIRTUAL_OSS_GET_OUTPUT_PEAK _IOWR('O', 23, struct virtual_oss_master_peak)
#define VIRTUAL_OSS_GET_INPUT_PEAK _IOWR('O', 24, struct virtual_oss_master_peak)
#define VIRTUAL_OSS_SET_RECORDING _IOW('O', 25, int)
#define VIRTUAL_OSS_GET_RECORDING _IOR('O', 26, int)
struct virtual_oss_audio_delay_locator {
int channel_output;
int channel_input;
int channel_last;
int signal_output_level; /* 2**n */
int signal_input_delay; /* in samples, roundtrip */
int signal_delay_hz; /* in samples, HZ */
int locator_enabled;
};
#define VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR _IOW('O', 27, struct virtual_oss_audio_delay_locator)
#define VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR _IOR('O', 28, struct virtual_oss_audio_delay_locator)
#define VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR _IO('O', 29)
struct virtual_oss_midi_delay_locator {
int channel_output;
int channel_input;
int signal_delay;
int signal_delay_hz; /* in samples, HZ */
int locator_enabled;
};
#define VIRTUAL_OSS_SET_MIDI_DELAY_LOCATOR _IOW('O', 30, struct virtual_oss_midi_delay_locator)
#define VIRTUAL_OSS_GET_MIDI_DELAY_LOCATOR _IOR('O', 31, struct virtual_oss_midi_delay_locator)
#define VIRTUAL_OSS_RST_MIDI_DELAY_LOCATOR _IO('O', 32)
#define VIRTUAL_OSS_ADD_OPTIONS _IOWR('O', 33, char [VIRTUAL_OSS_OPTIONS_MAX])
struct virtual_oss_fir_filter {
int number; /* must be first */
int channel;
int filter_size;
double *filter_data;
};
#define VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER _IOWR('O', 34, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER _IOWR('O', 35, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER _IOWR('O', 36, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER _IOWR('O', 37, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER _IOWR('O', 38, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER _IOWR('O', 39, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER _IOWR('O', 40, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER _IOWR('O', 41, struct virtual_oss_fir_filter)
#define VIRTUAL_OSS_GET_SAMPLE_RATE _IOR('O', 42, int)
struct virtual_oss_system_info {
unsigned tx_jitter_up;
unsigned tx_jitter_down;
unsigned sample_rate;
unsigned sample_bits;
unsigned sample_channels;
char rx_device_name[64];
char tx_device_name[64];
};
#define VIRTUAL_OSS_GET_SYSTEM_INFO _IOR('O', 43, struct virtual_oss_system_info)
#endif /* _VIRTUAL_OSS_H_ */
@@ -0,0 +1,8 @@
PROG= virtual_oss_cmd
MAN= ${PROG}.8
SRCS= command.c
CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss
.include <bsd.prog.mk>
@@ -0,0 +1,113 @@
/*-
* Copyright (c) 2021-2022 Hans Petter Selasky
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <sysexits.h>
#include <stdarg.h>
#include <fcntl.h>
#include "virtual_oss.h"
static void
message(const char *fmt, ...)
{
va_list list;
va_start(list, fmt);
vfprintf(stderr, fmt, list);
va_end(list);
}
static void
usage(void)
{
message("Usage: virtual_oss_cmd /dev/vdsp.ctl [command line arguments to pass to virtual_oss]\n");
exit(EX_USAGE);
}
int
main(int argc, char **argv)
{
char options[VIRTUAL_OSS_OPTIONS_MAX] = {};
size_t offset = 0;
size_t len = VIRTUAL_OSS_OPTIONS_MAX - 1;
int fd;
/* check if no options */
if (argc < 2)
usage();
fd = open(argv[1], O_RDWR);
if (fd < 0)
errx(EX_SOFTWARE, "Could not open '%s'", argv[1]);
for (int x = 2; x != argc; x++) {
size_t tmp = strlen(argv[x]) + 1;
if (tmp > len)
errx(EX_SOFTWARE, "Too many options passed");
memcpy(options + offset, argv[x], tmp);
options[offset + tmp - 1] = ' ';
offset += tmp;
len -= tmp;
}
if (options[0] == 0) {
struct virtual_oss_system_info info;
if (ioctl(fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &info) < 0)
errx(EX_SOFTWARE, "Cannot get system information");
info.rx_device_name[sizeof(info.rx_device_name) - 1] = 0;
info.tx_device_name[sizeof(info.tx_device_name) - 1] = 0;
printf("Sample rate: %u Hz\n"
"Sample width: %u bits\n"
"Sample channels: %u\n"
"Output jitter: %u / %u\n"
"Input device name: %s\n"
"Output device name: %s\n",
info.sample_rate,
info.sample_bits,
info.sample_channels,
info.tx_jitter_down,
info.tx_jitter_up,
info.rx_device_name,
info.tx_device_name);
} else {
/* execute options */
if (ioctl(fd, VIRTUAL_OSS_ADD_OPTIONS, options) < 0)
errx(EX_SOFTWARE, "One or more invalid options");
/* show error, if any */
if (options[0] != '\0')
errx(EX_SOFTWARE, "%s", options);
}
close(fd);
return (0);
}
@@ -0,0 +1,103 @@
.\"
.\" Copyright (c) 2021-2022 Hans Petter Selasky <hselasky@freebsd.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\"
.Dd February 12, 2025
.Dt VIRTUAL_OSS_CMD 8
.Os
.Sh NAME
.Nm virtual_oss_cmd
.Nd modify a running
.Xr virtual_oss 8
instance's options
.Sh SYNOPSIS
.Nm
.Sh DESCRIPTION
.Nm
pass additional command line arguments to a running
.Xr virtual_oss 8
instance via its control device.
Supported command line arguments:
.Bl -tag -width indent
.It Fl E Ar xxx
.It Fl F Ar xxx
.It Fl G Ar xxx
.It Fl L Ar xxx
.It Fl M Ar xxx
.It Fl O Ar xxx
.It Fl P Ar xxx
.It Fl R Ar xxx
.It Fl a Ar xxx
.It Fl b Ar xxx
.It Fl c Ar xxx
.It Fl d Ar xxx
.It Fl e Ar xxx
.It Fl f Ar xxx
.It Fl l Ar xxx
.It Fl m Ar xxx
.It Fl p Ar xxx
.It Fl s Ar xxx
.It Fl w Ar xxx
.El
.Pp
Refer to
.Xr virtual_oss 8
for a detailed description of the command line arguments.
.Sh EXAMPLES
To change the recording device:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl -R /dev/dsp4
.Ed
To change the playback device:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl -P /dev/dsp4
.Ed
To enable recording:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl -E 1
.Ed
To disable recording:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl -E 0
.Ed
To create a new DSP device on the fly:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl -b 16 -c 2 -d dsp.new
.Ed
To show system information:
.Bd -literal -offset indent
virtual_oss_cmd /dev/vdsp.ctl
.Ed
.Sh SEE ALSO
.Xr virtual_oss 8
.Sh AUTHORS
.Nm
was written by
.An Hans Petter Selasky hselasky@freebsd.org .