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:
@@ -21,5 +21,7 @@
|
||||
..
|
||||
pkgconfig
|
||||
..
|
||||
virtual_oss
|
||||
..
|
||||
..
|
||||
..
|
||||
|
||||
@@ -103,6 +103,8 @@
|
||||
..
|
||||
ossl-modules
|
||||
..
|
||||
virtual_oss
|
||||
..
|
||||
..
|
||||
libdata
|
||||
ldscripts
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.include <src.opts.mk>
|
||||
|
||||
SHLIBDIR?= ${LIBDIR}/virtual_oss
|
||||
|
||||
SUBDIR+= null \
|
||||
oss
|
||||
|
||||
.include "Makefile.inc"
|
||||
.include <bsd.subdir.mk>
|
||||
@@ -0,0 +1,3 @@
|
||||
.include "../Makefile.inc"
|
||||
|
||||
LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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_ */
|
||||
@@ -0,0 +1,12 @@
|
||||
# $NetBSD$
|
||||
|
||||
WARNS?= 3
|
||||
|
||||
PROG= cosdata
|
||||
SRCS= cosdata.c
|
||||
MAN=
|
||||
|
||||
DPADD+= ${LIBMATH}
|
||||
LDADD+= -lm
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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, },
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
@@ -75,6 +75,7 @@ CONFS= DAEMON \
|
||||
ugidfw \
|
||||
var \
|
||||
var_run \
|
||||
virtual_oss \
|
||||
watchdogd
|
||||
|
||||
CONFGROUPS+= DEVD
|
||||
|
||||
@@ -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 "$@"
|
||||
@@ -99,6 +99,7 @@ SUBDIR= adduser \
|
||||
valectl \
|
||||
vigr \
|
||||
vipw \
|
||||
virtual_oss \
|
||||
wake \
|
||||
watch \
|
||||
watchdogd \
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.include <src.opts.mk>
|
||||
|
||||
SUBDIR+= virtual_bt_speaker \
|
||||
virtual_oss_cmd \
|
||||
virtual_oss
|
||||
|
||||
.include "Makefile.inc"
|
||||
.include <bsd.subdir.mk>
|
||||
@@ -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 .
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 .
|
||||
Reference in New Issue
Block a user