diff --git a/etc/mtree/BSD.lib32.dist b/etc/mtree/BSD.lib32.dist index a736a7d58b66..6520b7b95116 100644 --- a/etc/mtree/BSD.lib32.dist +++ b/etc/mtree/BSD.lib32.dist @@ -1,25 +1,27 @@ # # Please see the file src/etc/mtree/README before making changes to this file. # /set type=dir uname=root gname=wheel mode=0755 . lib32 dtrace .. engines-3 .. geom .. i18n .. libxo encoder .. .. ossl-modules .. pkgconfig .. + virtual_oss + .. .. .. diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist index 19da845e962f..1945c26ebc5f 100644 --- a/etc/mtree/BSD.usr.dist +++ b/etc/mtree/BSD.usr.dist @@ -1,921 +1,923 @@ # # Please see the file src/etc/mtree/README before making changes to this file. # /set type=dir uname=root gname=wheel mode=0755 . bin .. include private bsddialog .. bsdstat .. event1 .. gmock internal custom .. .. .. gtest internal custom .. .. .. samplerate .. sqlite3 .. ucl .. yaml .. zstd .. .. .. lib clang 19 include cuda_wrappers bits .. .. fuzzer .. hlsl .. openmp_wrappers .. orc_rt .. ppc_wrappers .. profile .. sanitizer .. xray .. .. lib freebsd .. .. share .. .. .. compat .. dtrace .. engines-3 .. flua freebsd sys .. .. .. i18n .. krb5 kdb .. plugins kdb .. preauth .. tls .. .. .. libxo encoder .. .. ossl-modules .. + virtual_oss + .. .. libdata ldscripts .. pkgconfig .. .. libexec bsdconfig 020.docsinstall include .. .. 030.packages include .. .. 040.password include .. .. 050.diskmgmt include .. .. 070.usermgmt include .. .. 080.console include .. .. 090.timezone include .. .. 110.mouse include .. .. 120.networking include .. .. 130.security include .. .. 140.startup include .. .. 150.ttys include .. .. dot include .. .. include .. includes include .. .. .. bsdinstall .. dwatch .. fwget .. hyperv .. kgdb .. lpr ru .. .. sendmail .. sm.bin .. zfs .. .. local .. obj nochange .. sbin .. share atf tags=package=tests .. bhyve gdb .. kbdlayout .. .. bsdconfig media .. networking .. packages .. password .. startup .. timezone .. usermgmt .. .. calendar .. certs trusted tags=package=caroot .. untrusted tags=package=caroot .. .. dict .. doc IPv6 .. atf tags=package=tests .. kyua tags=package=tests .. legal .. llvm clang .. .. ncurses .. ntp drivers icons .. scripts .. .. hints .. icons .. pic .. scripts .. .. pjdfstest .. .. dtrace .. et .. examples BSD_daemon .. FreeBSD_version .. bhyve .. bootforth .. bsdconfig .. csh .. diskless .. dma .. dwatch .. etc defaults .. .. find_interface .. flua .. hast .. hostapd .. indent .. inotify .. ipfilter .. ipfw .. jails .. kld cdev module .. test .. .. dyn_sysctl .. firmware fwconsumer .. fwimage .. .. khelp .. syscall module .. test .. .. .. kyua tags=package=tests .. libusb20 .. libvgl .. mdoc .. netgraph .. perfmon .. pf .. ppi .. ppp .. printing .. scsi_target .. ses getencstat .. sesd .. setencstat .. setobjstat .. srcs .. .. smbfs print .. .. sound .. sunrpc dir .. msg .. sort .. .. tcsh .. uefisign .. ypldap .. .. firmware .. flua .. games fortune .. .. i18n csmapper APPLE .. AST .. BIG5 .. CNS .. CP .. EBCDIC .. GB .. GEORGIAN .. ISO-8859 .. ISO646 .. JIS .. KAZAKH .. KOI .. KS .. MISC .. TCVN .. .. esdb APPLE .. AST .. BIG5 .. CP .. DEC .. EBCDIC .. EUC .. GB .. GEORGIAN .. ISO-2022 .. ISO-8859 .. ISO646 .. KAZAKH .. KOI .. MISC .. TCVN .. UTF .. .. .. keys pkg revoked tags=package=runtime .. trusted tags=package=runtime .. .. .. kyua tags=package=tests misc tags=package=tests .. store tags=package=tests .. .. locale .. man man1 .. man2 .. man3 .. man3lua .. man4 aarch64 .. amd64 .. arm .. i386 .. powerpc .. .. man5 .. man6 .. man7 .. man8 amd64 .. i386 .. powerpc .. .. man9 .. .. misc fonts .. .. mk .. nls C .. af_ZA.ISO8859-1 .. af_ZA.ISO8859-15 .. af_ZA.UTF-8 .. am_ET.UTF-8 .. be_BY.CP1131 .. be_BY.CP1251 .. be_BY.ISO8859-5 .. be_BY.UTF-8 .. bg_BG.CP1251 .. bg_BG.UTF-8 .. ca_ES.ISO8859-1 .. ca_ES.ISO8859-15 .. ca_ES.UTF-8 .. cs_CZ.ISO8859-2 .. cs_CZ.UTF-8 .. da_DK.ISO8859-1 .. da_DK.ISO8859-15 .. da_DK.UTF-8 .. de_AT.ISO8859-1 .. de_AT.ISO8859-15 .. de_AT.UTF-8 .. de_CH.ISO8859-1 .. de_CH.ISO8859-15 .. de_CH.UTF-8 .. de_DE.ISO8859-1 .. de_DE.ISO8859-15 .. de_DE.UTF-8 .. el_GR.ISO8859-7 .. el_GR.UTF-8 .. en_AU.ISO8859-1 .. en_AU.ISO8859-15 .. en_AU.US-ASCII .. en_AU.UTF-8 .. en_CA.ISO8859-1 .. en_CA.ISO8859-15 .. en_CA.US-ASCII .. en_CA.UTF-8 .. en_GB.ISO8859-1 .. en_GB.ISO8859-15 .. en_GB.US-ASCII .. en_GB.UTF-8 .. en_IE.UTF-8 .. en_NZ.ISO8859-1 .. en_NZ.ISO8859-15 .. en_NZ.US-ASCII .. en_NZ.UTF-8 .. en_US.ISO8859-1 .. en_US.ISO8859-15 .. en_US.UTF-8 .. es_ES.ISO8859-1 .. es_ES.ISO8859-15 .. es_ES.UTF-8 .. et_EE.ISO8859-15 .. et_EE.UTF-8 .. fi_FI.ISO8859-1 .. fi_FI.ISO8859-15 .. fi_FI.UTF-8 .. fr_BE.ISO8859-1 .. fr_BE.ISO8859-15 .. fr_BE.UTF-8 .. fr_CA.ISO8859-1 .. fr_CA.ISO8859-15 .. fr_CA.UTF-8 .. fr_CH.ISO8859-1 .. fr_CH.ISO8859-15 .. fr_CH.UTF-8 .. fr_FR.ISO8859-1 .. fr_FR.ISO8859-15 .. fr_FR.UTF-8 .. gl_ES.ISO8859-1 .. he_IL.UTF-8 .. hi_IN.ISCII-DEV .. hr_HR.ISO8859-2 .. hr_HR.UTF-8 .. hu_HU.ISO8859-2 .. hu_HU.UTF-8 .. hy_AM.ARMSCII-8 .. hy_AM.UTF-8 .. is_IS.ISO8859-1 .. is_IS.ISO8859-15 .. is_IS.UTF-8 .. it_CH.ISO8859-1 .. it_CH.ISO8859-15 .. it_CH.UTF-8 .. it_IT.ISO8859-1 .. it_IT.ISO8859-15 .. it_IT.UTF-8 .. ja_JP.SJIS .. ja_JP.UTF-8 .. ja_JP.eucJP .. kk_KZ.PT154 .. kk_KZ.UTF-8 .. ko_KR.CP949 .. ko_KR.UTF-8 .. ko_KR.eucKR .. lt_LT.ISO8859-13 .. lt_LT.UTF-8 .. lv_LV.ISO8859-13 .. lv_LV.UTF-8 .. mn_MN.UTF-8 .. nl_BE.ISO8859-1 .. nl_BE.ISO8859-15 .. nl_BE.UTF-8 .. nl_NL.ISO8859-1 .. nl_NL.ISO8859-15 .. nl_NL.UTF-8 .. no_NO.ISO8859-1 .. no_NO.ISO8859-15 .. no_NO.UTF-8 .. pl_PL.ISO8859-2 .. pl_PL.UTF-8 .. pt_BR.ISO8859-1 .. pt_BR.UTF-8 .. pt_PT.ISO8859-1 .. pt_PT.ISO8859-15 .. pt_PT.UTF-8 .. ro_RO.ISO8859-2 .. ro_RO.UTF-8 .. ru_RU.CP1251 .. ru_RU.CP866 .. ru_RU.ISO8859-5 .. ru_RU.KOI8-R .. ru_RU.UTF-8 .. sk_SK.ISO8859-2 .. sk_SK.UTF-8 .. sl_SI.ISO8859-2 .. sl_SI.UTF-8 .. sr_YU.ISO8859-2 .. sr_YU.ISO8859-5 .. sr_YU.UTF-8 .. sv_SE.ISO8859-1 .. sv_SE.ISO8859-15 .. sv_SE.UTF-8 .. tr_TR.ISO8859-9 .. tr_TR.UTF-8 .. uk_UA.ISO8859-5 .. uk_UA.KOI8-U .. uk_UA.UTF-8 .. zh_CN.GB18030 .. zh_CN.GB2312 .. zh_CN.GBK .. zh_CN.UTF-8 .. zh_CN.eucCN .. zh_HK.UTF-8 .. zh_TW.UTF-8 .. .. openssl man man1 .. man3 .. man5 .. man7 .. .. .. security .. sendmail .. skel .. snmp defs .. mibs .. .. syscons fonts .. keymaps .. scrnmaps .. .. tabset .. vi catalog .. .. vt fonts .. keymaps .. .. zfs compatibility.d .. .. zoneinfo Africa .. America Argentina .. Indiana .. Kentucky .. North_Dakota .. .. Antarctica .. Arctic .. Asia .. Atlantic .. Australia .. Brazil .. Canada .. Chile .. Etc .. Europe .. Indian .. Mexico .. Pacific .. US .. .. .. src nochange .. .. diff --git a/lib/Makefile b/lib/Makefile index 2b7cf2fdcb7d..bf38a489911d 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,250 +1,252 @@ .include # The SUBDIR_BOOTSTRAP list is a small set of libraries which are used by many # of the other libraries. These are built first with a .WAIT between them # and the main list to avoid needing a SUBDIR_DEPEND line on every library # naming just these few items. SUBDIR_BOOTSTRAP= \ csu \ .WAIT \ libc \ libc_nonshared \ libcompiler_rt \ ${_libclang_rt} \ libc++ \ libc++experimental \ libcxxrt \ libder \ libdiff \ libelf \ libssp \ libssp_nonshared \ libsys \ msun # The main list; please keep these sorted alphabetically. # The only exception is sqlite3: we place it at the start of the list since it # takes a long time to build and starting it first improves parallelism. SUBDIR= ${SUBDIR_BOOTSTRAP} \ .WAIT \ libsqlite3 \ geom \ lib9p \ libalias \ libarchive \ libauditd \ libbegemot \ libblocksruntime \ libbsddialog \ libbsdstat \ libbsm \ libbz2 \ libcalendar \ libcam \ libcapsicum \ libcasper \ libcompat \ libcrypt \ libdevctl \ libdevdctl \ libdevinfo \ libdevstat \ libdl \ libdwarf \ libedit \ libelftc \ libevent1 \ libexecinfo \ libexpat \ libfetch \ libgcc_eh \ libgcc_s \ libgeom \ libifconfig \ libipsec \ libiscsiutil \ libjail \ libkiconv \ libkldelf \ libkvm \ liblua \ liblzma \ libmemstat \ libmd \ libmixer \ libmt \ lib80211 \ libnetbsd \ libnetmap \ libnv \ libnvmf \ libopenbsd \ libpam \ libpathconv \ libpcap \ libpjdlog \ libproc \ libprocstat \ libregex \ librpcsvc \ librss \ librt \ librtld_db \ libsamplerate \ libsbuf \ libsmb \ libstdbuf \ libstdthreads \ libsysdecode \ libtacplus \ libthr \ libthread_db \ libucl \ libufs \ libugidfw \ libulog \ libutil \ libutil++ \ ${_libvgl} \ libwrap \ libxo \ liby \ libyaml \ 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. SUBDIR_DEPEND_geom= libufs SUBDIR_DEPEND_googletest= libregex SUBDIR_DEPEND_libarchive= libz libbz2 libexpat liblzma libmd libzstd SUBDIR_DEPEND_libauditdm= libbsm SUBDIR_DEPEND_libbsddialog= ncurses SUBDIR_DEPEND_libbsnmp= ${_libnetgraph} SUBDIR_DEPEND_libc++:= libcxxrt # libssp_nonshared doesn't need to be linked into libc on every arch, but it is # small enough to build that this bit of serialization is likely insignificant. SUBDIR_DEPEND_libc= libsys libcompiler_rt libssp_nonshared SUBDIR_DEPEND_libcam= libsbuf SUBDIR_DEPEND_libcasper= libnv SUBDIR_DEPEND_libcrypt= libmd SUBDIR_DEPEND_libdevstat= libkvm SUBDIR_DEPEND_libdpv= libfigpar ncurses libutil SUBDIR_DEPEND_libedit= ncurses SUBDIR_DEPEND_libgeom= libexpat libsbuf .if ${MK_MITKRB5} == "no" SUBDIR_DEPEND_librpcsec_gss= libgssapi .endif SUBDIR_DEPEND_libmagic= libz SUBDIR_DEPEND_libmemstat= libkvm SUBDIR_DEPEND_libpam= libcrypt ${_libradius} librpcsvc libtacplus libutil ${_libypclnt} ${_libcom_err} SUBDIR_DEPEND_libpjdlog= libutil SUBDIR_DEPEND_libprocstat= libkvm libutil SUBDIR_DEPEND_libradius= libmd SUBDIR_DEPEND_libsmb= libkiconv # See comment above about libssp_nonshared SUBDIR_DEPEND_libsys= libcompiler_rt libssp_nonshared SUBDIR_DEPEND_libtacplus= libmd SUBDIR_DEPEND_libulog= libmd SUBDIR_DEPEND_libunbound= ${_libldns} SUBDIR_DEPEND_liblzma= libthr .if ${MK_OFED} != "no" SUBDIR_DEPEND_libpcap= ofed .endif SUBDIR_DEPEND_nss_tacplus= libtacplus +SUBDIR_DEPEND_virtual_oss= libsamplerate # NB: keep these sorted by MK_* knobs SUBDIR.${MK_BEARSSL}+= libbearssl libsecureboot SUBDIR.${MK_BLACKLIST}+=libblacklist SUBDIR.${MK_BLUETOOTH}+=libbluetooth libsdp SUBDIR.${MK_BSNMP}+= libbsnmp .if !defined(COMPAT_LIBCOMPAT) .if ${MK_CLANG} != "no" || ${MK_LLD} != "no" || \ ${MK_LLDB} != "no" || ${MK_LLVM_BINUTILS} != "no" SUBDIR+= clang .endif .endif SUBDIR.${MK_CUSE}+= libcuse SUBDIR.${MK_TOOLCHAIN}+=libpe SUBDIR.${MK_DIALOG}+= libdpv libfigpar SUBDIR.${MK_FDT}+= libfdt SUBDIR.${MK_FILE}+= libmagic SUBDIR.${MK_GPIO}+= libgpio .if ${MK_MITKRB5} == "no" SUBDIR.${MK_KERBEROS}+= libgssapi .endif SUBDIR.${MK_KERBEROS}+= librpcsec_gss SUBDIR.${MK_ICONV}+= libiconv_modules .if ${MK_MITKRB5} == "no" SUBDIR.${MK_KERBEROS}+= libcom_err .endif SUBDIR.${MK_LDNS}+= libldns SUBDIR.${MK_STATS}+= libstats # The libraries under libclang_rt can only be built by clang. .if (${COMPILER_TYPE} == "clang" || make(clean) || make(cleandir)) && \ ${MK_CLANG} != "no" _libclang_rt= libclang_rt .elif (${MK_ASAN} != "no" || ${MK_UBSAN} != "no") && make(all) .error Requested build with sanitizers but cannot build runtime libraries! .endif # This construct disables libefivar for 32-bit build. .if ${MACHINE_CPUARCH} != "i386" SUBDIR.${MK_EFI}+= libefivar .endif SUBDIR.${MK_GOOGLETEST}+= googletest SUBDIR.${MK_NETGRAPH}+= libnetgraph SUBDIR.${MK_NIS}+= libypclnt .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" _libvgl= libvgl .endif .if ${MACHINE_CPUARCH} == "aarch64" SUBDIR.${MK_PMC}+= libopencsd .endif .if ${MACHINE_CPUARCH} == "amd64" SUBDIR.${MK_PMC}+= libipt .endif .if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "aarch64" || \ ${MACHINE_CPUARCH} == "riscv" SUBDIR.${MK_BHYVE}+= libvmmapi .endif .if ${MACHINE_ARCH} != "powerpc" && ${MACHINE_CPUARCH} != "arm" SUBDIR.${MK_OPENMP}+= libomp .endif .if ${MK_USB} != "no" SUBDIR.${MK_OPENSSH}+= libcbor libfido2 .endif SUBDIR.${MK_OPENSSL}+= libmp SUBDIR.${MK_PF}+= libpfctl SUBDIR.${MK_PMC}+= libpmc libpmcstat SUBDIR.${MK_RADIUS_SUPPORT}+= libradius SUBDIR.${MK_SENDMAIL}+= libmilter libsm libsmdb libsmutil SUBDIR.${MK_TELNET}+= libtelnet SUBDIR.${MK_TESTS_SUPPORT}+= atf SUBDIR.${MK_TESTS_SUPPORT}+= liblutok SUBDIR.${MK_TESTS}+= tests SUBDIR.${MK_UNBOUND}+= libunbound SUBDIR.${MK_USB}+= libusbhid libusb SUBDIR.${MK_OFED}+= ofed SUBDIR.${MK_VERIEXEC}+= libveriexec SUBDIR.${MK_ZFS}+= libbe .if !make(install) SUBDIR_PARALLEL= .endif .include diff --git a/lib/virtual_oss/Makefile b/lib/virtual_oss/Makefile new file mode 100644 index 000000000000..dc83edd4b980 --- /dev/null +++ b/lib/virtual_oss/Makefile @@ -0,0 +1,9 @@ +.include + +SHLIBDIR?= ${LIBDIR}/virtual_oss + +SUBDIR+= null \ + oss + +.include "Makefile.inc" +.include diff --git a/lib/virtual_oss/Makefile.inc b/lib/virtual_oss/Makefile.inc new file mode 100644 index 000000000000..45c8e0b1fdfc --- /dev/null +++ b/lib/virtual_oss/Makefile.inc @@ -0,0 +1,3 @@ +.include "../Makefile.inc" + +LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate diff --git a/lib/virtual_oss/bt/Makefile b/lib/virtual_oss/bt/Makefile new file mode 100644 index 000000000000..15413b7a1f1e --- /dev/null +++ b/lib/virtual_oss/bt/Makefile @@ -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 diff --git a/lib/virtual_oss/bt/avdtp.c b/lib/virtual_oss/bt/avdtp.c new file mode 100644 index 000000000000..82ed0fb942b6 --- /dev/null +++ b/lib/virtual_oss/bt/avdtp.c @@ -0,0 +1,720 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015-2016 Nathanial Sloss + * Copyright (c) 2016-2019 Hans Petter Selasky + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "avdtp_signal.h" +#include "bt.h" + +#define DPRINTF(...) printf("backend_bt: " __VA_ARGS__) + +struct avdtpGetPacketInfo { + uint8_t buffer_data[512]; + uint16_t buffer_len; + uint8_t trans; + uint8_t signalID; +}; + +static int avdtpAutoConfig(struct bt_config *); + +/* Return received message type if success, < 0 if failure. */ +static int +avdtpGetPacket(int fd, struct avdtpGetPacketInfo *info) +{ + uint8_t *pos = info->buffer_data; + uint8_t *end = info->buffer_data + sizeof(info->buffer_data); + uint8_t message_type; + int len; + + memset(info, 0, sizeof(*info)); + + /* Handle fragmented packets */ + for (int remaining = 1; remaining > 0; --remaining) { + len = read(fd, pos, end - pos); + + if (len < AVDTP_LEN_SUCCESS) + return (-1); + if (len == (int)(end - pos)) + return (-1); /* buffer too small */ + + uint8_t trans = (pos[0] & TRANSACTIONLABEL) >> TRANSACTIONLABEL_S; + uint8_t packet_type = (pos[0] & PACKETTYPE) >> PACKETTYPE_S; + uint8_t current_message_type = (info->buffer_data[0] & MESSAGETYPE); + uint8_t shift; + if (pos == info->buffer_data) { + info->trans = trans; + message_type = current_message_type; + if (packet_type == singlePacket) { + info->signalID = (pos[1] & SIGNALID_MASK); + shift = 2; + } else { + if (packet_type != startPacket) + return (-1); + remaining = pos[1]; + info->signalID = (pos[2] & SIGNALID_MASK); + shift = 3; + } + } else { + if (info->trans != trans || + message_type != current_message_type || + (remaining == 1 && packet_type != endPacket) || + (remaining > 1 && packet_type != continuePacket)) { + return (-1); + } + shift = 1; + } + memmove(pos, pos + shift, len); + pos += len; + } + info->buffer_len = pos - info->buffer_data; + return (message_type); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpSendPacket(int fd, uint8_t command, uint8_t trans, uint8_t type, + uint8_t * data0, int datasize0, uint8_t * data1, + int datasize1) +{ + struct iovec iov[3]; + uint8_t header[2]; + int retval; + + /* fill out command header */ + header[0] = (trans << 4) | (type & 3); + if (command != 0) + header[1] = command & 0x3f; + else + header[1] = 3; + + iov[0].iov_base = header; + iov[0].iov_len = 2; + iov[1].iov_base = data0; + iov[1].iov_len = datasize0; + iov[2].iov_base = data1; + iov[2].iov_len = datasize1; + + retval = writev(fd, iov, 3); + if (retval != (2 + datasize0 + datasize1)) + return (-EINVAL); + else + return (0); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpSendSyncCommand(int fd, struct avdtpGetPacketInfo *info, + uint8_t command, uint8_t type, uint8_t * data0, + int datasize0, uint8_t * data1, int datasize1) +{ + static uint8_t transLabel; + uint8_t trans; + int retval; + + alarm(8); /* set timeout */ + + trans = (transLabel++) & 0xF; + + retval = avdtpSendPacket(fd, command, trans, type, + data0, datasize0, data1, datasize1); + if (retval) + goto done; +retry: + switch (avdtpGetPacket(fd, info)) { + case RESPONSEACCEPT: + if (info->trans != trans) + goto retry; + retval = 0; + break; + case RESPONSEREJECT: + if (info->trans != trans) + goto retry; + retval = -EINVAL; + break; + case COMMAND: + retval = avdtpSendReject(fd, info->trans, info->signalID); + if (retval == 0) + goto retry; + break; + default: + retval = -ENXIO; + break; + } +done: + alarm(0); /* clear timeout */ + + return (retval); +} + +/* + * Variant for acceptor role: We support any frequency, blocks, bands, and + * allocation. Returns 0 on success, < 0 on failure. + */ +static int +avdtpSendCapabilitiesResponseSBCForACP(int fd, int trans) +{ + uint8_t data[10]; + + data[0] = mediaTransport; + data[1] = 0; + data[2] = mediaCodec; + data[3] = 0x6; + data[4] = mediaTypeAudio; + data[5] = SBC_CODEC_ID; + data[6] = + (1 << (3 - MODE_STEREO)) | + (1 << (3 - MODE_JOINT)) | + (1 << (3 - MODE_DUAL)) | + (1 << (3 - MODE_MONO)) | + (1 << (7 - FREQ_44_1K)) | + (1 << (7 - FREQ_48K)) | + (1 << (7 - FREQ_32K)) | + (1 << (7 - FREQ_16K)); + data[7] = + (1 << (7 - BLOCKS_4)) | + (1 << (7 - BLOCKS_8)) | + (1 << (7 - BLOCKS_12)) | + (1 << (7 - BLOCKS_16)) | + (1 << (3 - BANDS_4)) | + (1 << (3 - BANDS_8)) | (1 << ALLOC_LOUDNESS) | (1 << ALLOC_SNR); + data[8] = MIN_BITPOOL; + data[9] = DEFAULT_MAXBPOOL; + + return (avdtpSendPacket(fd, AVDTP_GET_CAPABILITIES, trans, + RESPONSEACCEPT, data, sizeof(data), NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendAccept(int fd, uint8_t trans, uint8_t myCommand) +{ + return (avdtpSendPacket(fd, myCommand, trans, RESPONSEACCEPT, + NULL, 0, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendReject(int fd, uint8_t trans, uint8_t myCommand) +{ + uint8_t value = 0; + + return (avdtpSendPacket(fd, myCommand, trans, RESPONSEREJECT, + &value, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSendDiscResponseAudio(int fd, uint8_t trans, + uint8_t mySep, uint8_t is_sink) +{ + uint8_t data[2]; + + data[0] = mySep << 2; + data[1] = mediaTypeAudio << 4 | (is_sink ? (1 << 3) : 0); + + return (avdtpSendPacket(fd, AVDTP_DISCOVER, trans, RESPONSEACCEPT, + data, 2, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpDiscoverAndConfig(struct bt_config *cfg, bool isSink) +{ + struct avdtpGetPacketInfo info; + uint16_t offset; + uint8_t chmode = cfg->chmode; + uint8_t aacMode1 = cfg->aacMode1; + uint8_t aacMode2 = cfg->aacMode2; + int retval; + + retval = avdtpSendSyncCommand(cfg->hc, &info, AVDTP_DISCOVER, 0, + NULL, 0, NULL, 0); + if (retval) + return (retval); + + retval = -EBUSY; + for (offset = 0; offset + 2 <= info.buffer_len; offset += 2) { + cfg->sep = info.buffer_data[offset] >> 2; + cfg->media_Type = info.buffer_data[offset + 1] >> 4; + cfg->chmode = chmode; + cfg->aacMode1 = aacMode1; + cfg->aacMode2 = aacMode2; + if (info.buffer_data[offset] & DISCOVER_SEP_IN_USE) + continue; + if (info.buffer_data[offset + 1] & DISCOVER_IS_SINK) { + if (!isSink) + continue; + } else { + if (isSink) + continue; + } + /* try to configure SBC */ + retval = avdtpAutoConfig(cfg); + if (retval == 0) + return (0); + } + return (retval); +} + +/* Returns 0 on success, < 0 on failure. */ +static int +avdtpGetCapabilities(int fd, uint8_t sep, struct avdtpGetPacketInfo *info) +{ + uint8_t address = (sep << 2); + + return (avdtpSendSyncCommand(fd, info, + AVDTP_GET_CAPABILITIES, 0, &address, 1, + NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSetConfiguration(int fd, uint8_t sep, uint8_t * data, int datasize) +{ + struct avdtpGetPacketInfo info; + uint8_t configAddresses[2]; + + configAddresses[0] = sep << 2; + configAddresses[1] = INTSEP << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_SET_CONFIGURATION, 0, + configAddresses, 2, data, datasize)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpOpen(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_OPEN, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpStart(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_START, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpClose(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_CLOSE, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpSuspend(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_SUSPEND, 0, + &address, 1, NULL, 0)); +} + +/* Returns 0 on success, < 0 on failure. */ +int +avdtpAbort(int fd, uint8_t sep) +{ + struct avdtpGetPacketInfo info; + uint8_t address = sep << 2; + + return (avdtpSendSyncCommand(fd, &info, AVDTP_ABORT, 0, + &address, 1, NULL, 0)); +} + +static int +avdtpAutoConfig(struct bt_config *cfg) +{ + struct avdtpGetPacketInfo info; + uint8_t freqmode; + uint8_t blk_len_sb_alloc; + uint8_t availFreqMode = 0; + uint8_t availConfig = 0; + uint8_t supBitpoolMin = 0; + uint8_t supBitpoolMax = 0; + uint8_t aacMode1 = 0; + uint8_t aacMode2 = 0; +#ifdef HAVE_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); +} diff --git a/lib/virtual_oss/bt/avdtp_signal.h b/lib/virtual_oss/bt/avdtp_signal.h new file mode 100644 index 000000000000..a46cc6dd9dcf --- /dev/null +++ b/lib/virtual_oss/bt/avdtp_signal.h @@ -0,0 +1,139 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _AVDTP_SIGNAL_H_ +#define _AVDTP_SIGNAL_H_ + +#include +#include + +/* Our endpoint. */ +#define INTSEP 8 +#define ACPSEP 8 + +/* AVDTP signals. */ + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0a +#define AVDTP_SECUURITY_CONTROL 0x0b + +/* Signal Command & Response Header Masks. */ + +#define TRANSACTIONLABEL 0xf0 +#define TRANSACTIONLABEL_S 4 +#define SIGNALID_MASK 0x3f +#define PACKETTYPE 0x0c +#define PACKETTYPE_S 0x02 +#define MESSAGETYPE 0x03 +#define SIGNALIDENTIFIER 0x3f +#define DISCOVER_SEP_IN_USE 0x02 +#define DISCOVER_IS_SINK 0x08 + +/* Packet Types */ +#define singlePacket 0x0 +#define startPacket 0x1 +#define continuePacket 0x2 +#define endPacket 0x3 + +/* Message Types */ +#define COMMAND 0x0 +#define RESPONSEACCEPT 0x2 +#define RESPONSEREJECT 0x3 + +/* Response general error/success lengths */ +#define AVDTP_LEN_SUCCESS 2 +#define AVDTP_LEN_ERROR 3 + +/* Error codes */ +#define BAD_HEADER_FORMAT 0x01 +#define BAD_LENGTH 0x11 +#define BAD_ACP_SEID 0x12 +#define SEP_IN_USE 0x13 +#define SEP_NOT_IN_USE 0x14 +#define BAD_SERV_CATAGORY 0x17 +#define BAD_PAYLOAD_FORMAT 0x18 +#define NOT_SUPPORTED_COMMAND 0x19 +#define INVALID_CAPABILITIES 0x1a + +#define BAD_RECOVERY_TYPE 0x22 +#define BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define BAD_RECOVERY_FORMAT 0x25 +#define BAD_ROHC_FORMAT 0x26 +#define BAD_CP_FORMAT 0x27 +#define BAD_MULTIPLEXING_FORMAT 0x28 +#define UNSUPPORTED_CONFIGURATION 0x29 +#define BAD_STATE 0x31 + +/* Service Capabilities Field. */ +#define mediaTransport 0x1 +#define reporting 0x2 +#define recovery 0x3 +#define contentProtection 0x4 +#define headerCompression 0x5 +#define multiplexing 0x6 +#define mediaCodec 0x7 + +/* Media Codec Capabilities */ +#define mediaCodecSbc 0x00 +#define mediaCodecMpeg1 0x01 +#define mediaCodecMpeg2 0x02 + +#define SBC_CODEC_ID 0x0 +#define mediaTypeAudio 0x0 + +struct bt_config; + +int avdtpSendAccept(int, uint8_t, uint8_t); +int avdtpSendReject(int, uint8_t, uint8_t); +int avdtpSendDiscResponseAudio(int, uint8_t, uint8_t, uint8_t); +int avdtpDiscoverAndConfig(struct bt_config *, bool); +int avdtpSetConfiguration(int, uint8_t, uint8_t *, int); +int avdtpOpen(int, uint8_t); +int avdtpStart(int, uint8_t); +int avdtpClose(int, uint8_t); +int avdtpSuspend(int, uint8_t); +int avdtpAbort(int, uint8_t); + +/* Return < 0 if error, processed signal otherwise. */ +int avdtpACPHandlePacket(struct bt_config *cfg); +/* Free state allocated in avdtpACPHandlePacket(), if any. */ +void avdtpACPFree(struct bt_config *cfg); + +#endif /* _AVDTP_SIGNAL_H_ */ diff --git a/lib/virtual_oss/bt/bt.c b/lib/virtual_oss/bt/bt.c new file mode 100644 index 000000000000..57f000b067d5 --- /dev/null +++ b/lib/virtual_oss/bt/bt.c @@ -0,0 +1,1061 @@ +/*- + * Copyright (c) 2015-2019 Hans Petter Selasky + * Copyright (c) 2015 Nathanial Sloss + * Copyright (c) 2006 Itronix Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#define L2CAP_SOCKET_CHECKED +#include +#include + +#include "backend.h" +#include "int.h" + +#include "avdtp_signal.h" +#include "bt.h" + +#define DPRINTF(...) printf("backend_bt: " __VA_ARGS__) + +struct l2cap_info { + bdaddr_t laddr; + bdaddr_t raddr; +}; + +static struct bt_config bt_play_cfg; +static struct bt_config bt_rec_cfg; + +int +bt_receive(struct bt_config *cfg, void *ptr, int len, int use_delay) +{ + struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data; + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t *tmp = ptr; + int old_len = len; + int delta; + int err; + + /* wait for service interval, if any */ + if (use_delay) + virtual_oss_wait(); + + switch (cfg->blocks) { + case BLOCKS_4: + sbc->blocks = 4; + break; + case BLOCKS_8: + sbc->blocks = 8; + break; + case BLOCKS_12: + sbc->blocks = 12; + break; + default: + sbc->blocks = 16; + break; + } + + switch (cfg->bands) { + case BANDS_4: + sbc->bands = 4; + break; + default: + sbc->bands = 8; + break; + } + + if (cfg->chmode != MODE_MONO) { + sbc->channels = 2; + } else { + sbc->channels = 1; + } + + while (1) { + delta = len & ~1; + if (delta > (int)(2 * sbc->rem_len)) + delta = (2 * sbc->rem_len); + + /* copy out samples, if any */ + memcpy(tmp, (char *)sbc->music_data + sbc->rem_off, delta); + tmp += delta; + len -= delta; + sbc->rem_off += delta / 2; + sbc->rem_len -= delta / 2; + if (len == 0) + break; + + if (sbc->rem_len == 0 && + sbc->rem_data_frames != 0) { + err = sbc_decode_frame(cfg, sbc->rem_data_len * 8); + sbc->rem_data_frames--; + sbc->rem_data_ptr += err; + sbc->rem_data_len -= err; + continue; + } + /* TODO: Support fragmented SBC frames */ + err = read(cfg->fd, cfg->mtu_data, cfg->mtu); + + if (err == 0) { + break; + } else if (err < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + else + return (-1); /* disconnected */ + } + + /* verify RTP header */ + if (err < (int)sizeof(*phdr) || phdr->id != 0x80) + continue; + + sbc->rem_data_frames = phdr->numFrames; + sbc->rem_data_ptr = (uint8_t *)(phdr + 1); + sbc->rem_data_len = err - sizeof(*phdr); + } + return (old_len - len); +} + +static int +bt_set_format(int *format) +{ + int value; + + value = *format & AFMT_S16_NE; + if (value != 0) { + *format = value; + return (0); + } + return (-1); +} + +static void +bt_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + if (cfg->hc > 0) { + avdtpAbort(cfg->hc, cfg->sep); + avdtpClose(cfg->hc, cfg->sep); + close(cfg->hc); + cfg->hc = -1; + } + if (cfg->fd > 0) { + close(cfg->fd); + cfg->fd = -1; + } +} + +static void +bt_play_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + if (cfg->handle.sbc_enc == NULL) + break; + free(cfg->handle.sbc_enc); + cfg->handle.sbc_enc = NULL; + break; +#ifdef HAVE_LIBAV + case CODEC_AAC: + if (cfg->handle.av.context == NULL) + break; + av_free(cfg->rem_in_data); + av_frame_free(&cfg->handle.av.frame); + avcodec_close(cfg->handle.av.context); + avformat_free_context(cfg->handle.av.format); + cfg->handle.av.context = NULL; + break; +#endif + default: + break; + } + return (bt_close(pbe)); +} + +static void +bt_rec_close(struct voss_backend *pbe) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + break; +#ifdef HAVE_LIBAV + case CODEC_AAC: + break; +#endif + + default: + break; + } + return (bt_close(pbe)); +} + +static const uint32_t bt_attrs[] = { + SDP_ATTR_RANGE(SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), +}; + +#define BT_NUM_VALUES 32 +#define BT_BUF_SIZE 32 + +static int +bt_find_psm(const uint8_t *start, const uint8_t *end) +{ + uint32_t type; + uint32_t len; + int protover = 0; + int psm = -1; + + if ((end - start) < 2) + return (-1); + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + return (-1); + } + + while (start < end) { + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + return (-1); + } + /* check range */ + if (len > (uint32_t)(end - start)) + break; + + if (len >= 6) { + const uint8_t *ptr = start; + + SDP_GET8(type, ptr); + if (type == SDP_DATA_UUID16) { + uint16_t temp; + + SDP_GET16(temp, ptr); + switch (temp) { + case SDP_UUID_PROTOCOL_L2CAP: + SDP_GET8(type, ptr); + SDP_GET16(psm, ptr); + break; + case SDP_UUID_PROTOCOL_AVDTP: + SDP_GET8(type, ptr); + SDP_GET16(protover, ptr); + break; + default: + break; + } + } + } + start += len; + + if (protover >= 0x0100 && psm > -1) + return (htole16(psm)); + } + return (-1); +} + +static int +bt_query(struct l2cap_info *info, uint16_t service_class) +{ + sdp_attr_t values[BT_NUM_VALUES]; + uint8_t buffer[BT_NUM_VALUES][BT_BUF_SIZE]; + void *ss; + int psm = -1; + int n; + + memset(buffer, 0, sizeof(buffer)); + memset(values, 0, sizeof(values)); + + ss = sdp_open(&info->laddr, &info->raddr); + if (ss == NULL || sdp_error(ss) != 0) { + DPRINTF("Could not open SDP\n"); + sdp_close(ss); + return (psm); + } + /* Initialize attribute values array */ + for (n = 0; n != BT_NUM_VALUES; n++) { + values[n].flags = SDP_ATTR_INVALID; + values[n].vlen = BT_BUF_SIZE; + values[n].value = buffer[n]; + } + + /* Do SDP Service Search Attribute Request */ + n = sdp_search(ss, 1, &service_class, 1, bt_attrs, BT_NUM_VALUES, values); + if (n != 0) { + DPRINTF("SDP search failed\n"); + goto done; + } + /* Print attributes values */ + for (n = 0; n != BT_NUM_VALUES; n++) { + if (values[n].flags != SDP_ATTR_OK) + break; + if (values[n].attr != SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST) + continue; + psm = bt_find_psm(values[n].value, values[n].value + values[n].vlen); + if (psm > -1) + break; + } +done: + sdp_close(ss); + return (psm); +} + +static int +bt_open(struct voss_backend *pbe __unused, const char *devname, int samplerate, + int bufsize __unused, int *pchannels, int *pformat, struct bt_config *cfg, + int service_class, int isSink) +{ + struct sockaddr_l2cap addr; + struct l2cap_info info; + socklen_t mtusize = sizeof(uint16_t); + int tmpbitpool; + int l2cap_psm; + int temp; + + memset(&info, 0, sizeof(info)); + + if (strstr(devname, "/dev/bluetooth/") != devname) { + printf("Invalid device name '%s'", devname); + goto error; + } + /* skip prefix */ + devname += sizeof("/dev/bluetooth/") - 1; + + if (!bt_aton(devname, &info.raddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(devname)) == NULL) { + DPRINTF("Could not get host by name\n"); + goto error; + } + bdaddr_copy(&info.raddr, (bdaddr_t *)he->h_addr); + } + switch (samplerate) { + case 8000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x80; + cfg->aacMode2 = 0x0C; + break; + case 11025: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x40; + cfg->aacMode2 = 0x0C; + break; + case 12000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x20; + cfg->aacMode2 = 0x0C; + break; + case 16000: + cfg->freq = FREQ_16K; + cfg->aacMode1 = 0x10; + cfg->aacMode2 = 0x0C; + break; + case 22050: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x08; + cfg->aacMode2 = 0x0C; + break; + case 24000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0x04; + cfg->aacMode2 = 0x0C; + break; + case 32000: + cfg->freq = FREQ_32K; + cfg->aacMode1 = 0x02; + cfg->aacMode2 = 0x0C; + break; + case 44100: + cfg->freq = FREQ_44_1K; + cfg->aacMode1 = 0x01; + cfg->aacMode2 = 0x0C; + break; + case 48000: + cfg->freq = FREQ_48K; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x8C; + break; + case 64000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x4C; + break; + case 88200: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x2C; + break; + case 96000: + cfg->freq = FREQ_UNDEFINED; + cfg->aacMode1 = 0; + cfg->aacMode2 = 0x1C; + break; + default: + DPRINTF("Invalid samplerate %d", samplerate); + goto error; + } + cfg->bands = BANDS_8; + cfg->bitpool = 0; + + switch (*pchannels) { + case 1: + cfg->aacMode2 &= 0xF8; + cfg->chmode = MODE_MONO; + break; + default: + cfg->aacMode2 &= 0xF4; + cfg->chmode = MODE_STEREO; + break; + } + + cfg->allocm = ALLOC_LOUDNESS; + + if (cfg->chmode == MODE_MONO || cfg->chmode == MODE_DUAL) + tmpbitpool = 16; + else + tmpbitpool = 32; + + if (cfg->bands == BANDS_8) + tmpbitpool *= 8; + else + tmpbitpool *= 4; + + if (tmpbitpool > DEFAULT_MAXBPOOL) + tmpbitpool = DEFAULT_MAXBPOOL; + + cfg->bitpool = tmpbitpool; + + if (bt_set_format(pformat)) { + DPRINTF("Unsupported sample format\n"); + goto error; + } + l2cap_psm = bt_query(&info, service_class); + DPRINTF("PSM=0x%02x\n", l2cap_psm); + if (l2cap_psm < 0) { + DPRINTF("PSM not found\n"); + goto error; + } + cfg->hc = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (cfg->hc < 0) { + DPRINTF("Could not create BT socket\n"); + goto error; + } + memset(&addr, 0, sizeof(addr)); + addr.l2cap_len = sizeof(addr); + addr.l2cap_family = AF_BLUETOOTH; + bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr); + + if (bind(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not bind to HC\n"); + goto error; + } + bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr); + addr.l2cap_psm = l2cap_psm; + if (connect(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not connect to HC: %d\n", errno); + goto error; + } + if (avdtpDiscoverAndConfig(cfg, isSink)) { + DPRINTF("DISCOVER FAILED\n"); + goto error; + } + if (avdtpOpen(cfg->hc, cfg->sep)) { + DPRINTF("OPEN FAILED\n"); + goto error; + } + cfg->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (cfg->fd < 0) { + DPRINTF("Could not create BT socket\n"); + goto error; + } + memset(&addr, 0, sizeof(addr)); + + addr.l2cap_len = sizeof(addr); + addr.l2cap_family = AF_BLUETOOTH; + bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr); + + if (bind(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not bind\n"); + goto error; + } + bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr); + addr.l2cap_psm = l2cap_psm; + if (connect(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + DPRINTF("Could not connect: %d\n", errno); + goto error; + } + if (isSink) { + if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_OMTU, &cfg->mtu, &mtusize) == -1) { + DPRINTF("Could not get MTU\n"); + goto error; + } + temp = cfg->mtu * 16; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDBUF, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set send buffer size\n"); + goto error; + } + temp = cfg->mtu; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDLOWAT, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set low water mark\n"); + goto error; + } + } else { + if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_IMTU, &cfg->mtu, &mtusize) == -1) { + DPRINTF("Could not get MTU\n"); + goto error; + } + temp = cfg->mtu * 16; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set receive buffer size\n"); + goto error; + } + temp = 1; + if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) { + DPRINTF("Could not set low water mark\n"); + goto error; + } + temp = 1; + if (ioctl(cfg->fd, FIONBIO, &temp) == -1) { + DPRINTF("Could not set non-blocking I/O for receive direction\n"); + goto error; + } + } + + if (avdtpStart(cfg->hc, cfg->sep)) { + DPRINTF("START FAILED\n"); + goto error; + } + switch (cfg->chmode) { + case MODE_MONO: + *pchannels = 1; + break; + default: + *pchannels = 2; + break; + } + return (0); + +error: + if (cfg->hc > 0) { + close(cfg->hc); + cfg->hc = -1; + } + if (cfg->fd > 0) { + close(cfg->fd); + cfg->fd = -1; + } + return (-1); +} + +static void +bt_init_cfg(struct bt_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); +} + +static int +bt_rec_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + struct bt_config *cfg = pbe->arg; + int retval; + + bt_init_cfg(cfg); + + retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat, + cfg, SDP_SERVICE_CLASS_AUDIO_SOURCE, 0); + if (retval != 0) + return (retval); + return (0); +} + +static int +bt_play_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + struct bt_config *cfg = pbe->arg; + int retval; + + bt_init_cfg(cfg); + + retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat, + cfg, SDP_SERVICE_CLASS_AUDIO_SINK, 1); + if (retval != 0) + return (retval); + + /* setup codec */ + switch (cfg->codec) { + case CODEC_SBC: + cfg->handle.sbc_enc = + malloc(sizeof(*cfg->handle.sbc_enc)); + if (cfg->handle.sbc_enc == NULL) + return (-1); + memset(cfg->handle.sbc_enc, 0, sizeof(*cfg->handle.sbc_enc)); + break; +#ifdef HAVE_LIBAV + case CODEC_AAC: + cfg->handle.av.codec = __DECONST(AVCodec *, + avcodec_find_encoder_by_name("aac")); + if (cfg->handle.av.codec == NULL) { + DPRINTF("Codec AAC encoder not found\n"); + goto av_error_0; + } + cfg->handle.av.format = avformat_alloc_context(); + if (cfg->handle.av.format == NULL) { + DPRINTF("Could not allocate format context\n"); + goto av_error_0; + } + cfg->handle.av.format->oformat = + av_guess_format("latm", NULL, NULL); + if (cfg->handle.av.format->oformat == NULL) { + DPRINTF("Could not guess output format\n"); + goto av_error_1; + } + cfg->handle.av.stream = avformat_new_stream( + cfg->handle.av.format, cfg->handle.av.codec); + + if (cfg->handle.av.stream == NULL) { + DPRINTF("Could not create new stream\n"); + goto av_error_1; + } + cfg->handle.av.context = avcodec_alloc_context3(cfg->handle.av.codec); + if (cfg->handle.av.context == NULL) { + DPRINTF("Could not allocate audio context\n"); + goto av_error_1; + } + /*avcodec_get_context_defaults3(cfg->handle.av.context,*/ + /*cfg->handle.av.codec);*/ + + cfg->handle.av.context->bit_rate = 128000; + cfg->handle.av.context->sample_fmt = AV_SAMPLE_FMT_FLTP; + cfg->handle.av.context->sample_rate = samplerate; + switch (*pchannels) { + case 1: + cfg->handle.av.context->ch_layout = *(AVChannelLayout *)AV_CH_LAYOUT_MONO; + break; + default: + cfg->handle.av.context->ch_layout = *(AVChannelLayout *)AV_CH_LAYOUT_STEREO; + break; + } + + cfg->handle.av.context->profile = FF_PROFILE_AAC_LOW; + if (1) { + AVDictionary *opts = NULL; + + av_dict_set(&opts, "strict", "-2", 0); + av_dict_set_int(&opts, "latm", 1, 0); + + if (avcodec_open2(cfg->handle.av.context, + cfg->handle.av.codec, &opts) < 0) { + av_dict_free(&opts); + + DPRINTF("Could not open codec\n"); + goto av_error_1; + } + av_dict_free(&opts); + } + cfg->handle.av.frame = av_frame_alloc(); + if (cfg->handle.av.frame == NULL) { + DPRINTF("Could not allocate audio frame\n"); + goto av_error_2; + } + cfg->handle.av.frame->nb_samples = cfg->handle.av.context->frame_size; + cfg->handle.av.frame->format = cfg->handle.av.context->sample_fmt; + cfg->handle.av.frame->ch_layout = cfg->handle.av.context->ch_layout; + cfg->rem_in_size = av_samples_get_buffer_size(NULL, + cfg->handle.av.context->ch_layout.nb_channels, + cfg->handle.av.context->frame_size, + cfg->handle.av.context->sample_fmt, 0); + + cfg->rem_in_data = av_malloc(cfg->rem_in_size); + if (cfg->rem_in_data == NULL) { + DPRINTF("Could not allocate %u bytes sample buffer\n", + (unsigned)cfg->rem_in_size); + goto av_error_3; + } + retval = avcodec_fill_audio_frame(cfg->handle.av.frame, + cfg->handle.av.context->ch_layout.nb_channels, + cfg->handle.av.context->sample_fmt, + cfg->rem_in_data, cfg->rem_in_size, 0); + if (retval < 0) { + DPRINTF("Could not setup audio frame\n"); + goto av_error_4; + } + break; +av_error_4: + av_free(cfg->rem_in_data); +av_error_3: + av_frame_free(&cfg->handle.av.frame); +av_error_2: + avcodec_close(cfg->handle.av.context); +av_error_1: + avformat_free_context(cfg->handle.av.format); + cfg->handle.av.context = NULL; +av_error_0: + bt_close(pbe); + return (-1); +#endif + default: + bt_close(pbe); + return (-1); + } + return (0); +} + +static int +bt_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (bt_receive(pbe->arg, ptr, len, 1)); +} + +static int +bt_play_sbc_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + struct sbc_encode *sbc = cfg->handle.sbc_enc; + int rem_size = 1; + int old_len = len; + int err = 0; + + switch (cfg->blocks) { + case BLOCKS_4: + sbc->blocks = 4; + rem_size *= 4; + break; + case BLOCKS_8: + sbc->blocks = 8; + rem_size *= 8; + break; + case BLOCKS_12: + sbc->blocks = 12; + rem_size *= 12; + break; + default: + sbc->blocks = 16; + rem_size *= 16; + break; + } + + switch (cfg->bands) { + case BANDS_4: + rem_size *= 4; + sbc->bands = 4; + break; + default: + rem_size *= 8; + sbc->bands = 8; + break; + } + + /* store number of samples per frame */ + sbc->framesamples = rem_size; + + if (cfg->chmode != MODE_MONO) { + rem_size *= 2; + sbc->channels = 2; + } else { + sbc->channels = 1; + } + + rem_size *= 2; /* 16-bit samples */ + + while (len > 0) { + int delta = len; + + if (delta > (int)(rem_size - sbc->rem_len)) + delta = (int)(rem_size - sbc->rem_len); + + /* copy in samples */ + memcpy((char *)sbc->music_data + sbc->rem_len, ptr, delta); + + ptr = (char *)ptr + delta; + len -= delta; + sbc->rem_len += delta; + + /* check if buffer is full */ + if ((int)sbc->rem_len == rem_size) { + struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data; + uint32_t pkt_len; + uint32_t rem; + + if (cfg->chmode == MODE_MONO) + sbc->channels = 1; + else + sbc->channels = 2; + + pkt_len = sbc_encode_frame(cfg); + + retry: + if (cfg->mtu_offset == 0) { + phdr->id = 0x80; /* RTP v2 */ + phdr->id2 = 0x60; /* payload type 96. */ + phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8); + phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber); + phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24); + phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16); + phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8); + phdr->ts0 = (uint8_t)(cfg->mtu_timestamp); + phdr->reserved0 = 0x01; + phdr->numFrames = 0; + + cfg->mtu_seqnumber++; + cfg->mtu_offset += sizeof(*phdr); + } + /* compute bytes left */ + rem = cfg->mtu - cfg->mtu_offset; + + if (phdr->numFrames == 255 || rem < pkt_len) { + int xlen; + + if (phdr->numFrames == 0) + return (-1); + do { + xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset); + } while (xlen < 0 && errno == EAGAIN); + + if (xlen < 0) + return (-1); + + cfg->mtu_offset = 0; + goto retry; + } + memcpy(cfg->mtu_data + cfg->mtu_offset, sbc->data, pkt_len); + memset(sbc->data, 0, pkt_len); + cfg->mtu_offset += pkt_len; + cfg->mtu_timestamp += sbc->framesamples; + phdr->numFrames++; + + sbc->rem_len = 0; + } + } + if (err == 0) + return (old_len); + return (err); +} + +#ifdef HAVE_LIBAV +static int +bt_play_aac_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + struct aac_header { + uint8_t id; + uint8_t id2; + uint8_t seqnumMSB; + uint8_t seqnumLSB; + uint8_t ts3; + uint8_t ts2; + uint8_t ts1; + uint8_t ts0; + uint8_t sync3; + uint8_t sync2; + uint8_t sync1; + uint8_t sync0; + uint8_t fixed[8]; + }; + int old_len = len; + int err = 0; + + while (len > 0) { + int delta = len; + int rem; + + if (delta > (int)(cfg->rem_in_size - cfg->rem_in_len)) + delta = (int)(cfg->rem_in_size - cfg->rem_in_len); + + memcpy(cfg->rem_in_data + cfg->rem_in_len, ptr, delta); + + ptr = (char *)ptr + delta; + len -= delta; + cfg->rem_in_len += delta; + + /* check if buffer is full */ + if (cfg->rem_in_len == cfg->rem_in_size) { + struct aac_header *phdr = (struct aac_header *)cfg->mtu_data; + AVPacket *pkt; + uint8_t *pkt_buf; + int pkt_len; + + pkt = av_packet_alloc(); + err = avcodec_send_frame(cfg->handle.av.context, + cfg->handle.av.frame); + if (err < 0) { + DPRINTF("Error encoding audio frame\n"); + return (-1); + } + phdr->id = 0x80;/* RTP v2 */ + phdr->id2 = 0x60; /* payload type 96. */ + phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8); + phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber); + phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24); + phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16); + phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8); + phdr->ts0 = (uint8_t)(cfg->mtu_timestamp); + phdr->sync3 = 0; + phdr->sync2 = 0; + phdr->sync1 = 0; + phdr->sync0 = 0; + phdr->fixed[0] = 0xfc; + phdr->fixed[1] = 0x00; + phdr->fixed[2] = 0x00; + phdr->fixed[3] = 0xb0; + phdr->fixed[4] = 0x90; + phdr->fixed[5] = 0x80; + phdr->fixed[6] = 0x03; + phdr->fixed[7] = 0x00; + + cfg->mtu_seqnumber++; + cfg->mtu_offset = sizeof(*phdr); + + /* compute bytes left */ + rem = cfg->mtu - cfg->mtu_offset; + + if (avio_open_dyn_buf(&cfg->handle.av.format->pb) == 0) { + static int once = 0; + + if (!once++) + (void)avformat_write_header(cfg->handle.av.format, NULL); + av_write_frame(cfg->handle.av.format, pkt); + av_packet_unref(pkt); + pkt_len = avio_close_dyn_buf(cfg->handle.av.format->pb, &pkt_buf); + if (rem < pkt_len) + DPRINTF("Out of buffer space\n"); + if (pkt_len >= 3 && rem >= pkt_len) { + int xlen; + + memcpy(cfg->mtu_data + cfg->mtu_offset, pkt_buf + 3, pkt_len - 3); + + av_free(pkt_buf); + + cfg->mtu_offset += pkt_len - 3; + if (cfg->chmode != MODE_MONO) + cfg->mtu_timestamp += cfg->rem_in_size / 4; + else + cfg->mtu_timestamp += cfg->rem_in_size / 2; + do { + xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset); + } while (xlen < 0 && errno == EAGAIN); + + if (xlen < 0) + return (-1); + } else { + av_free(pkt_buf); + } + } else { + av_packet_unref(pkt); + } + /* reset remaining length */ + cfg->rem_in_len = 0; + } + } + if (err == 0) + return (old_len); + return (err); +} + +#endif + +static int +bt_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct bt_config *cfg = pbe->arg; + + switch (cfg->codec) { + case CODEC_SBC: + return (bt_play_sbc_transfer(pbe, ptr, len)); +#ifdef HAVE_LIBAV + case CODEC_AAC: + return (bt_play_aac_transfer(pbe, ptr, len)); +#endif + default: + return (-1); + } +} + +static void +bt_rec_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +static void +bt_play_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + /* TODO */ + *pdelay = -1; +} + +struct voss_backend voss_backend_bt_rec = { + .open = bt_rec_open, + .close = bt_rec_close, + .transfer = bt_rec_transfer, + .delay = bt_rec_delay, + .arg = &bt_rec_cfg, +}; + +struct voss_backend voss_backend_bt_play = { + .open = bt_play_open, + .close = bt_play_close, + .transfer = bt_play_transfer, + .delay = bt_play_delay, + .arg = &bt_play_cfg, +}; diff --git a/lib/virtual_oss/bt/bt.h b/lib/virtual_oss/bt/bt.h new file mode 100644 index 000000000000..2abdb9eb021a --- /dev/null +++ b/lib/virtual_oss/bt/bt.h @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 2015 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _BACKEND_BT_H_ +#define _BACKEND_BT_H_ + +#ifdef HAVE_LIBAV +#include +#include +#include +#endif + +#include "sbc_encode.h" + +struct bt_config { + uint8_t sep; /* SEID of the peer */ + uint8_t media_Type; + uint8_t chmode; +#define MODE_STEREO 2 +#define MODE_JOINT 3 +#define MODE_DUAL 1 +#define MODE_MONO 0 + uint8_t allocm; +#define ALLOC_LOUDNESS 0 +#define ALLOC_SNR 1 + uint8_t bitpool; + uint8_t bands; +#define BANDS_4 0 +#define BANDS_8 1 + uint8_t blocks; +#define BLOCKS_4 0 +#define BLOCKS_8 1 +#define BLOCKS_12 2 +#define BLOCKS_16 3 + uint8_t freq; +#define FREQ_UNDEFINED 255 +#define FREQ_16K 0 +#define FREQ_32K 1 +#define FREQ_44_1K 2 +#define FREQ_48K 3 + uint16_t mtu; + uint8_t codec; +#define CODEC_SBC 0x00 +#define CODEC_AAC 0x02 + uint8_t aacMode1; + uint8_t aacMode2; + + /* transcoding handle(s) */ + union { +#ifdef HAVE_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_ */ diff --git a/lib/virtual_oss/bt/cosdata-gen/Makefile b/lib/virtual_oss/bt/cosdata-gen/Makefile new file mode 100644 index 000000000000..d08e263f32b5 --- /dev/null +++ b/lib/virtual_oss/bt/cosdata-gen/Makefile @@ -0,0 +1,12 @@ +# $NetBSD$ + +WARNS?= 3 + +PROG= cosdata +SRCS= cosdata.c +MAN= + +DPADD+= ${LIBMATH} +LDADD+= -lm + +.include diff --git a/lib/virtual_oss/bt/cosdata-gen/cosdata.c b/lib/virtual_oss/bt/cosdata-gen/cosdata.c new file mode 100644 index 000000000000..b8409cbd0216 --- /dev/null +++ b/lib/virtual_oss/bt/cosdata-gen/cosdata.c @@ -0,0 +1,177 @@ +/*- + * Copyright (c) 2015 - 2016 Nathanial Sloss + * All rights reserved. + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +static const double sbc8_coeffs[] = { + 0.00000000e+00, 1.56575398e-04, 3.43256425e-04, 5.54620202e-04, + 8.23919506e-04, 1.13992507e-03, 1.47640169e-03, 1.78371725e-03, + 2.01182542e-03, 2.10371989e-03, 1.99454554e-03, 1.61656283e-03, + 9.02154502e-04, -1.78805361e-04, -1.64973098e-03, -3.49717454e-03, + 5.65949473e-03, 8.02941163e-03, 1.04584443e-02, 1.27472335e-02, + 1.46525263e-02, 1.59045603e-02, 1.62208471e-02, 1.53184106e-02, + 1.29371806e-02, 8.85757540e-03, 2.92408442e-03, -4.91578024e-03, + -1.46404076e-02, -2.61098752e-02, -3.90751381e-02, -5.31873032e-02, + 6.79989431e-02, 8.29847578e-02, 9.75753918e-02, 1.11196689e-01, + 1.23264548e-01, 1.33264415e-01, 1.40753505e-01, 1.45389847e-01, + 1.46955068e-01, 1.45389847e-01, 1.40753505e-01, 1.33264415e-01, + 1.23264548e-01, 1.11196689e-01, 9.75753918e-02, 8.29847578e-02, + -6.79989431e-02, -5.31873032e-02, -3.90751381e-02, -2.61098752e-02, + -1.46404076e-02, -4.91578024e-03, 2.92408442e-03, 8.85757540e-03, + 1.29371806e-02, 1.53184106e-02, 1.62208471e-02, 1.59045603e-02, + 1.46525263e-02, 1.27472335e-02, 1.04584443e-02, 8.02941163e-03, + -5.65949473e-03, -3.49717454e-03, -1.64973098e-03, -1.78805361e-04, + 9.02154502e-04, 1.61656283e-03, 1.99454554e-03, 2.10371989e-03, + 2.01182542e-03, 1.78371725e-03, 1.47640169e-03, 1.13992507e-03, + 8.23919506e-04, 5.54620202e-04, 3.43256425e-04, 1.56575398e-04, +}; + +static const double sbc4_coeffs[] = { + 0.00000000e+00, 5.36548976e-04, 1.49188357e-03, 2.73370904e-03, + 3.83720193e-03, 3.89205149e-03, 1.86581691e-03, -3.06012286e-03, + 1.09137620e-02, 2.04385087e-02, 2.88757392e-02, 3.21939290e-02, + 2.58767811e-02, 6.13245186e-03, -2.88217274e-02, -7.76463494e-02, + 1.35593274e-01, 1.94987841e-01, 2.46636662e-01, 2.81828203e-01, + 2.94315332e-01, 2.81828203e-01, 2.46636662e-01, 1.94987841e-01, + -1.35593274e-01, -7.76463494e-02, -2.88217274e-02, 6.13245186e-03, + 2.58767811e-02, 3.21939290e-02, 2.88757392e-02, 2.04385087e-02, + -1.09137620e-02, -3.06012286e-03, 1.86581691e-03, 3.89205149e-03, + 3.83720193e-03, 2.73370904e-03, 1.49188357e-03, 5.36548976e-04, +}; + +#define AC(x) (int)(sizeof(x) / sizeof((x)[0])) + +int +main(int argc, char **argv) +{ + float S[8][16]; + int i; + int k; + int count = 0; + + printf("/* sbc_coeffs.h - Automatically generated by cosdata.c. */\n" + "\n"); + + printf("static const float sbc_coeffs8[] = {\n "); + for (k = 0; k < AC(sbc8_coeffs); k++) { + if ((count % 8) == 0 && count != 0) + printf("\n "); + printf("%0.12ff, ", (float)sbc8_coeffs[k]); + count++; + } + printf("\n};\n"); + + count = 0; + printf("static const float sbc_coeffs4[] = {\n "); + for (k = 0; k < AC(sbc4_coeffs); k++) { + if ((count % 8) == 0 && count != 0) + printf("\n "); + printf("%0.12ff, ", (float)sbc4_coeffs[k]); + count++; + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdata8[8][16] = {\n "); + for (i = 0; i < 8; i++) { + for (k = 0; k < 16; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k - 4) * (M_PI / 8.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 15) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdata4[4][8] = {\n "); + for (i = 0; i < 4; i++) { + for (k = 0; k < 8; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k - 2) * (M_PI / 4.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 7) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdecdata8[8][16] = {\n "); + for (i = 0; i < 8; i++) { + for (k = 0; k < 16; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k + 4) * (M_PI / 8.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 15) + printf("},"); + count++; + } + } + printf("\n};\n"); + + count = 0; + printf("static const float cosdecdata4[4][8] = {\n "); + for (i = 0; i < 4; i++) { + for (k = 0; k < 8; k++) { + S[i][k] = cosf((float)((i + 0.5) * (k + 2) * (M_PI / 4.0))); + + if ((count % 8) == 0 && count != 0) + printf("\n "); + if (k == 0) + printf("{ "); + printf("%0.12ff, ", S[i][k]); + if (k == 7) + printf("},"); + count++; + } + } + printf("\n};\n"); + + return (0); +} diff --git a/lib/virtual_oss/bt/sbc_coeffs.h b/lib/virtual_oss/bt/sbc_coeffs.h new file mode 100644 index 000000000000..b9428033d50b --- /dev/null +++ b/lib/virtual_oss/bt/sbc_coeffs.h @@ -0,0 +1,69 @@ +/* sbc_coeffs.h - Automatically generated by cosdata.c. */ + +static const float sbc_coeffs8[] = { + 0.000000000000f, 0.000156575392f, 0.000343256426f, 0.000554620230f, 0.000823919487f, 0.001139925094f, 0.001476401696f, 0.001783717307f, + 0.002011825331f, 0.002103719860f, 0.001994545572f, 0.001616562833f, 0.000902154483f, -0.000178805363f, -0.001649730955f, -0.003497174475f, + 0.005659494549f, 0.008029411547f, 0.010458444245f, 0.012747233734f, 0.014652526006f, 0.015904560685f, 0.016220847145f, 0.015318410471f, + 0.012937180698f, 0.008857575245f, 0.002924084431f, -0.004915780388f, -0.014640407637f, -0.026109876111f, -0.039075139910f, -0.053187303245f, + 0.067998945713f, 0.082984760404f, 0.097575388849f, 0.111196689308f, 0.123264551163f, 0.133264422417f, 0.140753507614f, 0.145389840007f, + 0.146955072880f, 0.145389840007f, 0.140753507614f, 0.133264422417f, 0.123264551163f, 0.111196689308f, 0.097575388849f, 0.082984760404f, + -0.067998945713f, -0.053187303245f, -0.039075139910f, -0.026109876111f, -0.014640407637f, -0.004915780388f, 0.002924084431f, 0.008857575245f, + 0.012937180698f, 0.015318410471f, 0.016220847145f, 0.015904560685f, 0.014652526006f, 0.012747233734f, 0.010458444245f, 0.008029411547f, + -0.005659494549f, -0.003497174475f, -0.001649730955f, -0.000178805363f, 0.000902154483f, 0.001616562833f, 0.001994545572f, 0.002103719860f, + 0.002011825331f, 0.001783717307f, 0.001476401696f, 0.001139925094f, 0.000823919487f, 0.000554620230f, 0.000343256426f, 0.000156575392f, +}; +static const float sbc_coeffs4[] = { + 0.000000000000f, 0.000536548963f, 0.001491883537f, 0.002733709058f, 0.003837201977f, 0.003892051522f, 0.001865816885f, -0.003060122952f, + 0.010913762264f, 0.020438509062f, 0.028875738382f, 0.032193928957f, 0.025876780972f, 0.006132451817f, -0.028821727261f, -0.077646352351f, + 0.135593280196f, 0.194987848401f, 0.246636658907f, 0.281828194857f, 0.294315338135f, 0.281828194857f, 0.246636658907f, 0.194987848401f, + -0.135593280196f, -0.077646352351f, -0.028821727261f, 0.006132451817f, 0.025876780972f, 0.032193928957f, 0.028875738382f, 0.020438509062f, + -0.010913762264f, -0.003060122952f, 0.001865816885f, 0.003892051522f, 0.003837201977f, 0.002733709058f, 0.001491883537f, 0.000536548963f, +}; +static const float cosdata8[8][16] = { + { 0.707106769085f, 0.831469595432f, 0.923879504204f, 0.980785250664f, 1.000000000000f, 0.980785250664f, 0.923879504204f, 0.831469595432f, + 0.707106769085f, 0.555570244789f, 0.382683426142f, 0.195090353489f, -0.000000043711f, -0.195090323687f, -0.382683396339f, -0.555570185184f, }, + { -0.707106769085f, -0.195090323687f, 0.382683426142f, 0.831469595432f, 1.000000000000f, 0.831469595432f, 0.382683426142f, -0.195090323687f, + -0.707106769085f, -0.980785310268f, -0.923879504204f, -0.555570423603f, 0.000000011925f, 0.555570065975f, 0.923879563808f, 0.980785310268f, }, + { -0.707106828690f, -0.980785310268f, -0.382683396339f, 0.555570244789f, 1.000000000000f, 0.555570244789f, -0.382683396339f, -0.980785310268f, + -0.707106828690f, 0.195090413094f, 0.923879563808f, 0.831469655037f, 0.000000139071f, -0.831469774246f, -0.923879444599f, -0.195090219378f, }, + { 0.707106649876f, -0.555570423603f, -0.923879504204f, 0.195090353489f, 1.000000000000f, 0.195090353489f, -0.923879504204f, -0.555570423603f, + 0.707106649876f, 0.831469655037f, -0.382683008909f, -0.980785369873f, -0.000000290067f, 0.980785250664f, 0.382683545351f, -0.831469595432f, }, + { 0.707106769085f, 0.555570065975f, -0.923879504204f, -0.195090323687f, 1.000000000000f, -0.195090323687f, -0.923879504204f, 0.555570065975f, + 0.707106769085f, -0.831469774246f, -0.382683843374f, 0.980785250664f, -0.000000035775f, -0.980785250664f, 0.382683902979f, 0.831469714642f, }, + { -0.707106590271f, 0.980785310268f, -0.382683575153f, -0.555570185184f, 1.000000000000f, -0.555570185184f, -0.382683575153f, 0.980785310268f, + -0.707106590271f, -0.195090219378f, 0.923879683018f, -0.831469595432f, -0.000000592058f, 0.831469714642f, -0.923879623413f, 0.195090919733f, }, + { -0.707106530666f, 0.195090532303f, 0.382683604956f, -0.831469655037f, 1.000000000000f, -0.831469655037f, 0.382683604956f, 0.195090532303f, + -0.707106530666f, 0.980785310268f, -0.923879384995f, 0.555569529533f, -0.000000687457f, -0.555570006371f, 0.923879563808f, -0.980785191059f, }, + { 0.707106828690f, -0.831469774246f, 0.923879563808f, -0.980785310268f, 1.000000000000f, -0.980785310268f, 0.923879563808f, -0.831469774246f, + 0.707106828690f, -0.555570065975f, 0.382683902979f, -0.195089668036f, 0.000000059624f, 0.195089548826f, -0.382683813572f, 0.555569946766f, }, +}; +static const float cosdata4[4][8] = { + { 0.707106769085f, 0.923879504204f, 1.000000000000f, 0.923879504204f, 0.707106769085f, 0.382683426142f, -0.000000043711f, -0.382683396339f, }, + { -0.707106769085f, 0.382683426142f, 1.000000000000f, 0.382683426142f, -0.707106769085f, -0.923879504204f, 0.000000011925f, 0.923879563808f, }, + { -0.707106828690f, -0.382683396339f, 1.000000000000f, -0.382683396339f, -0.707106828690f, 0.923879563808f, 0.000000139071f, -0.923879444599f, }, + { 0.707106649876f, -0.923879504204f, 1.000000000000f, -0.923879504204f, 0.707106649876f, -0.382683008909f, -0.000000290067f, 0.382683545351f, }, +}; +static const float cosdecdata8[8][16] = { + { 0.707106769085f, 0.555570244789f, 0.382683426142f, 0.195090353489f, -0.000000043711f, -0.195090323687f, -0.382683396339f, -0.555570185184f, + -0.707106769085f, -0.831469655037f, -0.923879504204f, -0.980785310268f, -1.000000000000f, -0.980785310268f, -0.923879504204f, -0.831469535828f, }, + { -0.707106769085f, -0.980785310268f, -0.923879504204f, -0.555570423603f, 0.000000011925f, 0.555570065975f, 0.923879563808f, 0.980785310268f, + 0.707106769085f, 0.195090532303f, -0.382683008909f, -0.831469774246f, -1.000000000000f, -0.831469714642f, -0.382683843374f, 0.195090577006f, }, + { -0.707106828690f, 0.195090413094f, 0.923879563808f, 0.831469655037f, 0.000000139071f, -0.831469774246f, -0.923879444599f, -0.195090219378f, + 0.707106828690f, 0.980785310268f, 0.382683545351f, -0.555570065975f, -1.000000000000f, -0.555570542812f, 0.382683902979f, 0.980785191059f, }, + { 0.707106649876f, 0.831469655037f, -0.382683008909f, -0.980785369873f, -0.000000290067f, 0.980785250664f, 0.382683545351f, -0.831469595432f, + -0.707107424736f, 0.555569529533f, 0.923879802227f, -0.195089668036f, -1.000000000000f, -0.195090815425f, 0.923879384995f, 0.555570483208f, }, + { 0.707106769085f, -0.831469774246f, -0.382683843374f, 0.980785250664f, -0.000000035775f, -0.980785250664f, 0.382683902979f, 0.831469714642f, + -0.707106173038f, -0.555570006371f, 0.923879384995f, 0.195089548826f, -1.000000000000f, 0.195089697838f, 0.923879325390f, -0.555570125580f, }, + { -0.707106590271f, -0.195090219378f, 0.923879683018f, -0.831469595432f, -0.000000592058f, 0.831469714642f, -0.923879623413f, 0.195090919733f, + 0.707107424736f, -0.980785191059f, 0.382683366537f, 0.555569946766f, -1.000000000000f, 0.555571198463f, 0.382683783770f, -0.980785667896f, }, + { -0.707106530666f, 0.980785310268f, -0.923879384995f, 0.555569529533f, -0.000000687457f, -0.555570006371f, 0.923879563808f, -0.980785191059f, + 0.707106173038f, -0.195089071989f, -0.382684975863f, 0.831468641758f, -1.000000000000f, 0.831470131874f, -0.382683992386f, -0.195090129972f, }, + { 0.707106828690f, -0.555570065975f, 0.382683902979f, -0.195089668036f, 0.000000059624f, 0.195089548826f, -0.382683813572f, 0.555569946766f, + -0.707106053829f, 0.831468641758f, -0.923880040646f, 0.980785369873f, -1.000000000000f, 0.980785429478f, -0.923880159855f, 0.831468760967f, }, +}; +static const float cosdecdata4[4][8] = { + { 0.707106769085f, 0.382683426142f, -0.000000043711f, -0.382683396339f, -0.707106769085f, -0.923879504204f, -1.000000000000f, -0.923879504204f, }, + { -0.707106769085f, -0.923879504204f, 0.000000011925f, 0.923879563808f, 0.707106769085f, -0.382683008909f, -1.000000000000f, -0.382683843374f, }, + { -0.707106828690f, 0.923879563808f, 0.000000139071f, -0.923879444599f, 0.707106828690f, 0.382683545351f, -1.000000000000f, 0.382683902979f, }, + { 0.707106649876f, -0.382683008909f, -0.000000290067f, 0.382683545351f, -0.707107424736f, 0.923879802227f, -1.000000000000f, 0.923879384995f, }, +}; diff --git a/lib/virtual_oss/bt/sbc_encode.c b/lib/virtual_oss/bt/sbc_encode.c new file mode 100644 index 000000000000..153f2e69e76e --- /dev/null +++ b/lib/virtual_oss/bt/sbc_encode.c @@ -0,0 +1,701 @@ +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sbc_coeffs.h" +#include "bt.h" + +#define SYNCWORD 0x9c +#define ABS(x) (((x) < 0) ? -(x) : (x)) +#define BIT30 (1U << 30) +#define BM(x) ((1LL << (x)) - 1LL) + +/* Loudness offset allocations. */ +static const int loudnessoffset8[4][8] = { + {-2, 0, 0, 0, 0, 0, 0, 1}, + {-3, 0, 0, 0, 0, 0, 1, 2}, + {-4, 0, 0, 0, 0, 0, 1, 2}, + {-4, 0, 0, 0, 0, 0, 1, 2}, +}; + +static const int loudnessoffset4[4][4] = { + {-1, 0, 0, 0}, + {-2, 0, 0, 1}, + {-2, 0, 0, 1}, + {-2, 0, 0, 1}, +}; + +static uint8_t +calc_scalefactors_joint(struct sbc_encode *sbc) +{ + float sb_j[16][2]; + uint32_t x; + uint32_t y; + uint8_t block; + uint8_t joint; + uint8_t sb; + uint8_t lz; + + joint = 0; + for (sb = 0; sb != sbc->bands - 1; sb++) { + for (block = 0; block < sbc->blocks; block++) { + sb_j[block][0] = (sbc->samples[block][0][sb] + + sbc->samples[block][1][sb]) / 2.0f; + sb_j[block][1] = (sbc->samples[block][0][sb] - + sbc->samples[block][1][sb]) / 2.0f; + } + + x = 1 << 15; + y = 1 << 15; + for (block = 0; block < sbc->blocks; block++) { + x |= (uint32_t)ABS(sb_j[block][0]); + y |= (uint32_t)ABS(sb_j[block][1]); + } + + lz = 1; + while (!(x & BIT30)) { + lz++; + x <<= 1; + } + x = 16 - lz; + + lz = 1; + while (!(y & BIT30)) { + lz++; + y <<= 1; + } + y = 16 - lz; + + if ((sbc->scalefactor[0][sb] + sbc->scalefactor[1][sb]) > x + y) { + joint |= 1 << (sbc->bands - sb - 1); + sbc->scalefactor[0][sb] = x; + sbc->scalefactor[1][sb] = y; + for (block = 0; block < sbc->blocks; block++) { + sbc->samples[block][0][sb] = sb_j[block][0]; + sbc->samples[block][1][sb] = sb_j[block][1]; + } + } + } + return (joint); +} + +static void +calc_scalefactors(struct sbc_encode *sbc) +{ + uint8_t block; + uint8_t ch; + uint8_t sb; + + for (ch = 0; ch != sbc->channels; ch++) { + for (sb = 0; sb != sbc->bands; sb++) { + uint32_t x = 1 << 15; + uint8_t lx = 1; + + for (block = 0; block != sbc->blocks; block++) + x |= (uint32_t)ABS(sbc->samples[block][ch][sb]); + + while (!(x & BIT30)) { + lx++; + x <<= 1; + } + sbc->scalefactor[ch][sb] = 16 - lx; + } + } +} + +static void +calc_bitneed(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + int32_t bitneed[2][8]; + int32_t max_bitneed, bitcount; + int32_t slicecount, bitslice; + int32_t loudness; + int ch, sb, start_chan = 0; + + if (cfg->chmode == MODE_DUAL) + sbc->channels = 1; + +next_chan: + max_bitneed = 0; + bitcount = 0; + slicecount = 0; + + if (cfg->allocm == ALLOC_SNR) { + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + bitneed[ch][sb] = sbc->scalefactor[ch][sb]; + + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } else { + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->scalefactor[ch][sb] == 0) { + bitneed[ch][sb] = -5; + } else { + if (sbc->bands == 8) { + loudness = sbc->scalefactor[ch][sb] - + loudnessoffset8[cfg->freq][sb]; + } else { + loudness = sbc->scalefactor[ch][sb] - + loudnessoffset4[cfg->freq][sb]; + } + if (loudness > 0) + bitneed[ch][sb] = loudness / 2; + else + bitneed[ch][sb] = loudness; + } + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } + + slicecount = bitcount = 0; + bitslice = max_bitneed + 1; + do { + bitslice--; + bitcount += slicecount; + slicecount = 0; + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if ((bitneed[ch][sb] > bitslice + 1) && + (bitneed[ch][sb] < bitslice + 16)) + slicecount++; + else if (bitneed[ch][sb] == bitslice + 1) + slicecount += 2; + } + } + } while (bitcount + slicecount < cfg->bitpool); + + /* check if exactly one more fits */ + if (bitcount + slicecount == cfg->bitpool) { + bitcount += slicecount; + bitslice--; + } + for (ch = start_chan; ch < sbc->channels; ch++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (bitneed[ch][sb] < bitslice + 2) { + sbc->bits[ch][sb] = 0; + } else { + sbc->bits[ch][sb] = bitneed[ch][sb] - bitslice; + if (sbc->bits[ch][sb] > 16) + sbc->bits[ch][sb] = 16; + } + } + } + + if (cfg->chmode == MODE_DUAL) + ch = start_chan; + else + ch = 0; + sb = 0; + while (bitcount < cfg->bitpool && sb < sbc->bands) { + if ((sbc->bits[ch][sb] >= 2) && (sbc->bits[ch][sb] < 16)) { + sbc->bits[ch][sb]++; + bitcount++; + } else if ((bitneed[ch][sb] == bitslice + 1) && + (cfg->bitpool > bitcount + 1)) { + sbc->bits[ch][sb] = 2; + bitcount += 2; + } + if (sbc->channels == 1 || start_chan == 1) + sb++; + else if (ch == 1) { + ch = 0; + sb++; + } else + ch = 1; + } + + if (cfg->chmode == MODE_DUAL) + ch = start_chan; + else + ch = 0; + sb = 0; + while (bitcount < cfg->bitpool && sb < sbc->bands) { + if (sbc->bits[ch][sb] < 16) { + sbc->bits[ch][sb]++; + bitcount++; + } + if (sbc->channels == 1 || start_chan == 1) + sb++; + else if (ch == 1) { + ch = 0; + sb++; + } else + ch = 1; + } + + if (cfg->chmode == MODE_DUAL && start_chan == 0) { + start_chan = 1; + sbc->channels = 2; + goto next_chan; + } +} + +static void +sbc_store_bits_crc(struct sbc_encode *sbc, uint32_t numbits, uint32_t value) +{ + uint32_t off = sbc->bitoffset; + + while (numbits-- && off != sbc->maxoffset) { + if (value & (1 << numbits)) { + sbc->data[off / 8] |= 1 << ((7 - off) & 7); + sbc->crc ^= 0x80; + } + sbc->crc *= 2; + if (sbc->crc & 0x100) + sbc->crc ^= 0x11d; /* CRC-8 polynomial */ + + off++; + } + sbc->bitoffset = off; +} + +static int +sbc_encode(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + const int16_t *input = sbc->music_data; + float delta[2][8]; + float levels[2][8]; + float mask[2][8]; + float S; + float *X; + float Z[80]; + float Y[80]; + float audioout; + int16_t left[8]; + int16_t right[8]; + int16_t *data; + int numsamples; + int i; + int k; + int block; + int chan; + int sb; + + for (block = 0; block < sbc->blocks; block++) { + + for (i = 0; i < sbc->bands; i++) { + left[i] = *input++; + if (sbc->channels == 2) + right[i] = *input++; + } + + for (chan = 0; chan < sbc->channels; chan++) { + + /* select right or left channel */ + if (chan == 0) { + X = sbc->left; + data = left; + } else { + X = sbc->right; + data = right; + } + + /* shift up old data */ + for (i = (sbc->bands * 10) - 1; i > sbc->bands - 1; i--) + X[i] = X[i - sbc->bands]; + k = 0; + for (i = sbc->bands - 1; i >= 0; i--) + X[i] = data[k++]; + for (i = 0; i < sbc->bands * 10; i++) { + if (sbc->bands == 8) + Z[i] = sbc_coeffs8[i] * X[i]; + else + Z[i] = sbc_coeffs4[i] * X[i]; + } + for (i = 0; i < sbc->bands * 2; i++) { + Y[i] = 0; + for (k = 0; k < 5; k++) + Y[i] += Z[i + k * sbc->bands * 2]; + } + for (i = 0; i < sbc->bands; i++) { + S = 0; + for (k = 0; k < sbc->bands * 2; k++) { + if (sbc->bands == 8) { + S += cosdata8[i][k] * Y[k]; + } else { + S += cosdata4[i][k] * Y[k]; + } + } + sbc->samples[block][chan][i] = S * (1 << 15); + } + } + } + + calc_scalefactors(sbc); + + if (cfg->chmode == MODE_JOINT) + sbc->join = calc_scalefactors_joint(sbc); + else + sbc->join = 0; + + calc_bitneed(cfg); + + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + mask[chan][sb] = BM(sbc->bits[chan][sb]); + levels[chan][sb] = mask[chan][sb] * + (1LL << (15 - sbc->scalefactor[chan][sb])); + delta[chan][sb] = + (1LL << (sbc->scalefactor[chan][sb] + 16)); + } + } + + numsamples = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + audioout = (levels[chan][sb] * + (delta[chan][sb] + sbc->samples[block][chan][sb])); + audioout /= (1LL << 32); + + audioout = roundf(audioout); + + /* range check */ + if (audioout > mask[chan][sb]) + audioout = mask[chan][sb]; + + sbc->output[numsamples++] = audioout; + } + } + } + return (numsamples); +} + +static void +sbc_decode(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + float delta[2][8]; + float levels[2][8]; + float audioout; + float *X; + float *V; + float left[160]; + float right[160]; + float U[160]; + float W[160]; + float S[8]; + int position; + int block; + int chan; + int sb; + int i; + int k; + + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + levels[chan][sb] = (1 << sbc->bits[chan][sb]) - 1; + delta[chan][sb] = (1 << sbc->scalefactor[chan][sb]); + } + } + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) { + audioout = 0; + } else { + audioout = + ((((sbc->output[i] * 2.0f) + 1.0f) * delta[chan][sb]) / + levels[chan][sb]) - delta[chan][sb]; + } + sbc->output[i++] = audioout; + } + } + } + + if (cfg->chmode == MODE_JOINT) { + i = 0; + while (i < (sbc->blocks * sbc->bands * sbc->channels)) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->join & (1 << (sbc->bands - sb - 1))) { + audioout = sbc->output[i]; + sbc->output[i] = (2.0f * sbc->output[i]) + + (2.0f * sbc->output[i + sbc->bands]); + sbc->output[i + sbc->bands] = + (2.0f * audioout) - + (2.0f * sbc->output[i + sbc->bands]); + sbc->output[i] /= 2.0f; + sbc->output[i + sbc->bands] /= 2.0f; + } + i++; + } + i += sbc->bands; + } + } + position = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + /* select right or left channel */ + if (chan == 0) { + X = left; + V = sbc->left; + } else { + X = right; + V = sbc->right; + } + for (i = 0; i < sbc->bands; i++) + S[i] = sbc->output[position++]; + + for (i = (sbc->bands * 20) - 1; i >= (sbc->bands * 2); i--) + V[i] = V[i - (sbc->bands * 2)]; + for (k = 0; k < sbc->bands * 2; k++) { + float vk = 0; + for (i = 0; i < sbc->bands; i++) { + if (sbc->bands == 8) { + vk += cosdecdata8[i][k] * S[i]; + } else { + vk += cosdecdata4[i][k] * S[i]; + } + } + V[k] = vk; + } + for (i = 0; i <= 4; i++) { + for (k = 0; k < sbc->bands; k++) { + U[(i * sbc->bands * 2) + k] = + V[(i * sbc->bands * 4) + k]; + U[(i * sbc->bands + * 2) + sbc->bands + k] = + V[(i * sbc->bands * 4) + + (sbc->bands * 3) + k]; + } + } + for (i = 0; i < sbc->bands * 10; i++) { + if (sbc->bands == 4) { + W[i] = U[i] * (sbc_coeffs4[i] * -4.0f); + } else if (sbc->bands == 8) { + W[i] = U[i] * (sbc_coeffs8[i] * -8.0f); + } else { + W[i] = 0; + } + } + + for (k = 0; k < sbc->bands; k++) { + unsigned int offset = k + (block * sbc->bands); + + X[offset] = 0; + for (i = 0; i < 10; i++) { + X[offset] += W[k + (i * sbc->bands)]; + } + + if (X[offset] > 32767.0) + X[offset] = 32767.0; + else if (X[offset] < -32767.0) + X[offset] = -32767.0; + } + } + } + + for (i = 0, k = 0; k != (sbc->blocks * sbc->bands); k++) { + sbc->music_data[i++] = left[k]; + if (sbc->channels == 2) + sbc->music_data[i++] = right[k]; + } +} + +size_t +sbc_encode_frame(struct bt_config *cfg) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t config; + uint8_t block; + uint8_t chan; + uint8_t sb; + uint8_t j; + uint8_t i; + + config = (cfg->freq << 6) | (cfg->blocks << 4) | + (cfg->chmode << 2) | (cfg->allocm << 1) | cfg->bands; + + sbc_encode(cfg); + + /* set initial CRC */ + sbc->crc = 0x5e; + + /* reset data position and size */ + sbc->bitoffset = 0; + sbc->maxoffset = sizeof(sbc->data) * 8; + + sbc_store_bits_crc(sbc, 8, SYNCWORD); + sbc_store_bits_crc(sbc, 8, config); + sbc_store_bits_crc(sbc, 8, cfg->bitpool); + + /* skip 8-bit CRC */ + sbc->bitoffset += 8; + + if (cfg->chmode == MODE_JOINT) { + if (sbc->bands == 8) + sbc_store_bits_crc(sbc, 8, sbc->join); + else if (sbc->bands == 4) + sbc_store_bits_crc(sbc, 4, sbc->join); + } + for (i = 0; i < sbc->channels; i++) { + for (j = 0; j < sbc->bands; j++) + sbc_store_bits_crc(sbc, 4, sbc->scalefactor[i][j]); + } + + /* store 8-bit CRC */ + sbc->data[3] = (sbc->crc & 0xFF); + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) + continue; + + sbc_store_bits_crc(sbc, sbc->bits[chan][sb], sbc->output[i++]); + } + } + } + return ((sbc->bitoffset + 7) / 8); +} + +static uint32_t +sbc_load_bits_crc(struct sbc_encode *sbc, uint32_t numbits) +{ + uint32_t off = sbc->bitoffset; + uint32_t value = 0; + + while (numbits-- && off != sbc->maxoffset) { + if (sbc->rem_data_ptr[off / 8] & (1 << ((7 - off) & 7))) { + value |= (1 << numbits); + sbc->crc ^= 0x80; + } + sbc->crc *= 2; + if (sbc->crc & 0x100) + sbc->crc ^= 0x11d; /* CRC-8 polynomial */ + + off++; + } + sbc->bitoffset = off; + return (value); +} + +size_t +sbc_decode_frame(struct bt_config *cfg, int bits) +{ + struct sbc_encode *sbc = cfg->handle.sbc_enc; + uint8_t config; + uint8_t block; + uint8_t chan; + uint8_t sb; + uint8_t j; + uint8_t i; + + sbc->rem_off = 0; + sbc->rem_len = 0; + + config = (cfg->freq << 6) | (cfg->blocks << 4) | + (cfg->chmode << 2) | (cfg->allocm << 1) | cfg->bands; + + /* set initial CRC */ + sbc->crc = 0x5e; + + /* reset data position and size */ + sbc->bitoffset = 0; + sbc->maxoffset = bits; + + /* verify SBC header */ + if (sbc->maxoffset < (8 * 4)) + return (0); + if (sbc_load_bits_crc(sbc, 8) != SYNCWORD) + return (0); + if (sbc_load_bits_crc(sbc, 8) != config) + return (0); + cfg->bitpool = sbc_load_bits_crc(sbc, 8); + + (void)sbc_load_bits_crc(sbc, 8);/* CRC */ + + if (cfg->chmode == MODE_JOINT) { + if (sbc->bands == 8) + sbc->join = sbc_load_bits_crc(sbc, 8); + else if (sbc->bands == 4) + sbc->join = sbc_load_bits_crc(sbc, 4); + else + sbc->join = 0; + } else { + sbc->join = 0; + } + + for (i = 0; i < sbc->channels; i++) { + for (j = 0; j < sbc->bands; j++) + sbc->scalefactor[i][j] = sbc_load_bits_crc(sbc, 4); + } + + calc_bitneed(cfg); + + i = 0; + for (block = 0; block < sbc->blocks; block++) { + for (chan = 0; chan < sbc->channels; chan++) { + for (sb = 0; sb < sbc->bands; sb++) { + if (sbc->bits[chan][sb] == 0) { + i++; + continue; + } + sbc->output[i++] = + sbc_load_bits_crc(sbc, sbc->bits[chan][sb]); + } + } + } + + sbc_decode(cfg); + + sbc->rem_off = 0; + sbc->rem_len = sbc->blocks * sbc->channels * sbc->bands; + + return ((sbc->bitoffset + 7) / 8); +} diff --git a/lib/virtual_oss/bt/sbc_encode.h b/lib/virtual_oss/bt/sbc_encode.h new file mode 100644 index 000000000000..71f8460a242e --- /dev/null +++ b/lib/virtual_oss/bt/sbc_encode.h @@ -0,0 +1,82 @@ +/* $NetBSD$ */ + +/*- + * Copyright (c) 2015 Nathanial Sloss + * + * This software is dedicated to the memory of - + * Baron James Anlezark (Barry) - 1 Jan 1949 - 13 May 2012. + * + * Barry was a man who loved his music. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SBC_ENCODE_H_ +#define _SBC_ENCODE_H_ + +#define MIN_BITPOOL 2 +#define DEFAULT_MAXBPOOL 250 + +/* + * SBC header format + */ +struct sbc_header { + uint8_t id; + uint8_t id2; + uint8_t seqnumMSB; + uint8_t seqnumLSB; + uint8_t ts3; + uint8_t ts2; + uint8_t ts1; + uint8_t ts0; + uint8_t reserved3; + uint8_t reserved2; + uint8_t reserved1; + uint8_t reserved0; + uint8_t numFrames; +}; + +struct sbc_encode { + int16_t music_data[256]; + uint8_t data[1024]; + uint8_t *rem_data_ptr; + int rem_data_len; + int rem_data_frames; + int bits[2][8]; + float output[256]; + float left[160]; + float right[160]; + float samples[16][2][8]; + uint32_t rem_len; + uint32_t rem_off; + uint32_t bitoffset; + uint32_t maxoffset; + uint32_t crc; + uint16_t framesamples; + uint8_t scalefactor[2][8]; + uint8_t channels; + uint8_t bands; + uint8_t blocks; + uint8_t join; +}; + +#endif /* _SBC_ENCODE_H_ */ diff --git a/lib/virtual_oss/null/Makefile b/lib/virtual_oss/null/Makefile new file mode 100644 index 000000000000..ec5c2d40f665 --- /dev/null +++ b/lib/virtual_oss/null/Makefile @@ -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 diff --git a/lib/virtual_oss/null/null.c b/lib/virtual_oss/null/null.c new file mode 100644 index 000000000000..149c6e3e8c6a --- /dev/null +++ b/lib/virtual_oss/null/null.c @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 2015-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "backend.h" +#include "int.h" + +static void +null_close(struct voss_backend *pbe __unused) +{ +} + +static int +null_open(struct voss_backend *pbe __unused, const char *devname __unused, + int samplerate __unused, int bufsize __unused, int *pchannels __unused, + int *pformat) +{ + int value[3]; + int i; + + value[0] = *pformat & VPREFERRED_SNE_AFMT; + value[1] = *pformat & VPREFERRED_SLE_AFMT; + value[2] = *pformat & VPREFERRED_SBE_AFMT; + + for (i = 0; i != 3; i++) { + if (value[i] == 0) + continue; + *pformat = value[i]; + return (0); + } + return (-1); +} + +static int +null_rec_transfer(struct voss_backend *pbe __unused, void *ptr, int len) +{ + + if (voss_has_synchronization == 0) + virtual_oss_wait(); + memset(ptr, 0, len); + return (len); +} + +static int +null_play_transfer(struct voss_backend *pbe __unused, void *ptr __unused, + int len) +{ + return (len); +} + +static void +null_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +struct voss_backend voss_backend_null_rec = { + .open = null_open, + .close = null_close, + .transfer = null_rec_transfer, + .delay = null_delay, +}; + +struct voss_backend voss_backend_null_play = { + .open = null_open, + .close = null_close, + .transfer = null_play_transfer, + .delay = null_delay, +}; diff --git a/lib/virtual_oss/oss/Makefile b/lib/virtual_oss/oss/Makefile new file mode 100644 index 000000000000..257d7f0c0bae --- /dev/null +++ b/lib/virtual_oss/oss/Makefile @@ -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 diff --git a/lib/virtual_oss/oss/oss.c b/lib/virtual_oss/oss/oss.c new file mode 100644 index 000000000000..b4779f9108d9 --- /dev/null +++ b/lib/virtual_oss/oss/oss.c @@ -0,0 +1,197 @@ +/*- + * Copyright (c) 2015-2019 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "backend.h" +#include "int.h" + +static int +oss_set_format(int fd, int *format) +{ + int value[6]; + int error; + int fmt; + int i; + + value[0] = *format & VPREFERRED_SNE_AFMT; + value[1] = *format & VPREFERRED_UNE_AFMT; + value[2] = *format & VPREFERRED_SLE_AFMT; + value[3] = *format & VPREFERRED_SBE_AFMT; + value[4] = *format & VPREFERRED_ULE_AFMT; + value[5] = *format & VPREFERRED_UBE_AFMT; + + for (i = 0; i != 6; i++) { + fmt = value[i]; + if (fmt == 0) + continue; + error = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); + /* make sure we got the format we asked for */ + if (error == 0 && fmt == value[i]) { + *format = fmt; + return (0); + } + } + return (-1); +} + +static void +oss_close(struct voss_backend *pbe) +{ + if (pbe->fd > -1) { + close(pbe->fd); + pbe->fd = -1; + } +} + +static int +oss_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat, int attr, int fionbio) +{ + int temp; + int err; + + pbe->fd = open(devname, attr); + if (pbe->fd < 0) { + warn("Could not open DSP device '%s'", devname); + return (-1); + } + err = ioctl(pbe->fd, FIONBIO, &fionbio); + if (err < 0) { + warn("Could not set blocking mode on DSP"); + goto error; + } + err = oss_set_format(pbe->fd, pformat); + if (err < 0) { + warn("Could not set sample format 0x%08x", *pformat); + goto error; + } + temp = *pchannels; + bufsize /= temp; /* get buffer size per channel */ + do { + err = ioctl(pbe->fd, SOUND_PCM_WRITE_CHANNELS, &temp); + } while (err < 0 && --temp > 0); + + err = ioctl(pbe->fd, SOUND_PCM_READ_CHANNELS, &temp); + if (err < 0 || temp <= 0 || temp > *pchannels) { + warn("Could not set DSP channels: %d / %d", temp, *pchannels); + goto error; + } + *pchannels = temp; + + temp = samplerate; + err = ioctl(pbe->fd, SNDCTL_DSP_SPEED, &temp); + if (err < 0 || temp != samplerate) { + warn("Could not set sample rate to %d / %d Hz", temp, samplerate); + goto error; + } + + temp = bufsize * (*pchannels); + err = ioctl(pbe->fd, SNDCTL_DSP_SETBLKSIZE, &temp); + if (err < 0) { + warn("Could not set block size to %d", temp); + goto error; + } + return (0); +error: + close(pbe->fd); + pbe->fd = -1; + return (-1); +} + +static int +oss_rec_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + return (oss_open(pbe, devname, samplerate, bufsize, pchannels, pformat, O_RDONLY, 0)); +} + +static int +oss_play_open(struct voss_backend *pbe, const char *devname, int samplerate, + int bufsize, int *pchannels, int *pformat) +{ + bufsize *= 4; /* XXX allow extra space for jitter */ + return (oss_open(pbe, devname, samplerate, bufsize, pchannels, pformat, O_WRONLY, 0)); +} + +static int +oss_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + struct pollfd fds = { .fd = pbe->fd, .events = POLLIN | POLLRDNORM }; + int err; + + /* wait at maximum 2 seconds for data, else something is wrong */ + err = poll(&fds, 1, 2000); + if (err < 1) + return (-1); + return (read(pbe->fd, ptr, len)); +} + +static int +oss_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (write(pbe->fd, ptr, len)); +} + +static void +oss_rec_delay(struct voss_backend *pbe, int *pdelay) +{ + if (ioctl(pbe->fd, FIONREAD, pdelay) != 0) + *pdelay = -1; +} + +static void +oss_play_delay(struct voss_backend *pbe, int *pdelay) +{ + if (voss_has_synchronization != 0 || + ioctl(pbe->fd, SNDCTL_DSP_GETODELAY, pdelay) != 0) + *pdelay = -1; +} + +struct voss_backend voss_backend_oss_rec = { + .open = oss_rec_open, + .close = oss_close, + .transfer = oss_rec_transfer, + .delay = oss_rec_delay, + .fd = -1, +}; + +struct voss_backend voss_backend_oss_play = { + .open = oss_play_open, + .close = oss_close, + .transfer = oss_play_transfer, + .delay = oss_play_delay, + .fd = -1, +}; diff --git a/lib/virtual_oss/sndio/Makefile b/lib/virtual_oss/sndio/Makefile new file mode 100644 index 000000000000..9b5af63a3246 --- /dev/null +++ b/lib/virtual_oss/sndio/Makefile @@ -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 diff --git a/lib/virtual_oss/sndio/sndio.c b/lib/virtual_oss/sndio/sndio.c new file mode 100644 index 000000000000..2d1a3411049b --- /dev/null +++ b/lib/virtual_oss/sndio/sndio.c @@ -0,0 +1,203 @@ +/*- + * Copyright (c) 2021 Tim Creech + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include + +#include "backend.h" +#include "int.h" + +static struct sio_hdl * +get_sio_hdl(struct voss_backend *pbe) +{ + if (pbe) + return (pbe->arg); + + return (NULL); +} + +static void +sndio_close(struct voss_backend *pbe) +{ + if (!pbe) + return; + + if (get_sio_hdl(pbe)) + sio_close(get_sio_hdl(pbe)); +} + +static int +sndio_get_signedness(int *fmt) +{ + int s_fmt = *fmt & (VPREFERRED_SLE_AFMT | VPREFERRED_SBE_AFMT); + + if (s_fmt) { + *fmt = s_fmt; + return (1); + } + *fmt = *fmt & (VPREFERRED_ULE_AFMT | VPREFERRED_UBE_AFMT); + return (0); +} + +static int +sndio_get_endianness_is_le(int *fmt) +{ + int le_fmt = *fmt & (VPREFERRED_SLE_AFMT | VPREFERRED_ULE_AFMT); + + if (le_fmt) { + *fmt = le_fmt; + return (1); + } + *fmt = *fmt & (VPREFERRED_SBE_AFMT | VPREFERRED_UBE_AFMT); + return (0); +} + +static int +sndio_get_bits(int *fmt) +{ + if (*fmt & AFMT_16BIT) + return (16); + if (*fmt & AFMT_24BIT) + return (24); + if (*fmt & AFMT_32BIT) + return (32); + if (*fmt & AFMT_8BIT) + return (8); + return (-1); + /* TODO AFMT_BIT */ +} + +static int +sndio_open(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat, int direction) +{ + const char *sndio_name = devname + strlen("/dev/sndio/"); + + int sig = sndio_get_signedness(pformat); + int le = sndio_get_endianness_is_le(pformat); + int bits = sndio_get_bits(pformat); + + if (bits == -1) { + warn("unsupported format precision"); + return (-1); + } + + struct sio_hdl *hdl = sio_open(sndio_name, direction, 0); + + if (hdl == 0) { + warn("sndio: failed to open device"); + return (-1); + } + + struct sio_par par; + + sio_initpar(&par); + par.pchan = *pchannels; + par.sig = sig; + par.bits = bits; + par.bps = SIO_BPS(bits); + par.le = le; + par.rate = samplerate; + par.appbufsz = bufsize; + par.xrun = SIO_SYNC; + if (!sio_setpar(hdl, &par)) + errx(1, "internal error, sio_setpar() failed"); + if (!sio_getpar(hdl, &par)) + errx(1, "internal error, sio_getpar() failed"); + if ((int)par.pchan != *pchannels) + errx(1, "couldn't set number of channels"); + if ((int)par.sig != sig || (int)par.bits != bits || (int)par.le != le) + errx(1, "couldn't set format"); + if ((int)par.bits != bits) + errx(1, "couldn't set precision"); + if ((int)par.rate < samplerate * 995 / 1000 || + (int)par.rate > samplerate * 1005 / 1000) + errx(1, "couldn't set rate"); + if (par.xrun != SIO_SYNC) + errx(1, "couldn't set xun policy"); + + /* Save the device handle with the backend */ + pbe->arg = hdl; + + /* Start the device. */ + if (!sio_start(hdl)) + errx(1, "couldn't start device"); + + return (0); +} + +static int +sndio_open_play(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat) +{ + return (sndio_open(pbe, devname, samplerate, bufsize, pchannels, pformat, SIO_PLAY)); +} + +static int +sndio_open_rec(struct voss_backend *pbe, const char *devname, + int samplerate, int bufsize, int *pchannels, int *pformat) +{ + return (sndio_open(pbe, devname, samplerate, bufsize, pchannels, pformat, SIO_REC)); +} + +static int +sndio_play_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (sio_write(get_sio_hdl(pbe), ptr, len)); +} + +static int +sndio_rec_transfer(struct voss_backend *pbe, void *ptr, int len) +{ + return (sio_read(get_sio_hdl(pbe), ptr, len)); +} + +static void +sndio_delay(struct voss_backend *pbe __unused, int *pdelay) +{ + *pdelay = -1; +} + +struct voss_backend voss_backend_sndio_rec = { + .open = sndio_open_rec, + .close = sndio_close, + .transfer = sndio_rec_transfer, + .delay = sndio_delay, + .fd = -1, +}; + +struct voss_backend voss_backend_sndio_play = { + .open = sndio_open_play, + .close = sndio_close, + .transfer = sndio_play_transfer, + .delay = sndio_delay, + .fd = -1, +}; diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile index 896d5bd8ae09..370254285efb 100644 --- a/libexec/rc/rc.d/Makefile +++ b/libexec/rc/rc.d/Makefile @@ -1,440 +1,441 @@ .include CONFDIR= /etc/rc.d CONFGROUPS= CONFS CONFSPACKAGE= rc CONFS= DAEMON \ FILESYSTEMS \ LOGIN \ NETWORKING \ SERVERS \ adjkerntz \ bgfsck \ bridge \ cfumass \ cleanvar \ cleartmp \ ddb \ defaultroute \ devfs \ dmesg \ dumpon \ fsck \ growfs \ growfs_fstab \ hostid \ hostid_save \ hostname \ iovctl \ ip6addrctl \ ipsec \ kld \ kldxref \ ldconfig \ linux \ local \ localpkg \ mixer \ motd \ mountcritlocal \ mountcritremote \ mountlate \ mdconfig \ mdconfig2 \ msgs \ netif \ netoptions \ netwait \ noshutdown \ ${_nscd} \ ${_opensm} \ os-release \ pwcheck \ quota \ random \ rarpd \ rctl \ root \ routing \ rpcbind \ rtadvd \ rtsold \ savecore \ securelevel \ serial \ static_arp \ static_ndp \ stf \ swap \ swaplate \ sysctl \ sysctl_lastload \ sysvipc \ tmp \ ugidfw \ var \ var_run \ + virtual_oss \ watchdogd CONFGROUPS+= DEVD DEVD= devd DEVDPACKAGE= devd CONFGROUPS+= DEVMATCH DEVMATCH= devmatch DEVMATCHPACKAGE= devmatch CONFGROUPS+= DHCLIENT DHCLIENT= dhclient DHCLIENTPACKAGE= dhclient CONFGROUPS+= GEOM GEOM= geli \ geli2 \ gptboot GEOMPACKAGE= geom CONFGROUPS+= GGATED GGATED= ggated GGATEDPACKAGE= ggate CONFGROUPS+= RESOLVCONF RESOLVCONF= resolv RESOLVCONFPACKAGE= resolvconf CONFGROUPS+= CRON CRON+= cron CRONPACKAGE= cron CONFGROUPS+= CTL CTL= ctld CTLPACKAGE= ctl CONFGROUPS+= NFS NFS= lockd \ mountd \ nfscbd \ nfsclient \ nfsd \ nfsuserd \ statd NFSPACKAGE= nfs CONFGROUPS+= NEWSYSLOG NEWSYSLOG= newsyslog NEWSYSLOGPACKAGE= newsyslog CONFGROUPS+= POWERD POWERD= powerd POWERDPACKAGE= powerd CONFGROUPS+= PPPOED PPPOED= pppoed PPPOEDPACKAGE= ppp CONFGROUPS+= SYSLOGD SYSLOGD= syslogd SYSLOGDPACKAGE= syslogd CONFGROUPS+= RCMDS RCMDS= rwho RCMDSPACKAGE= rcmds .if ${MK_ACCT} != "no" || ${MK_UTMPX} != "no" CONFGROUPS+= ACCT ACCTPACKAGE= acct .if ${MK_ACCT} != "no" ACCT+= accounting .endif .if ${MK_UTMPX} != "no" ACCT+= utx .endif .endif .if ${MK_ACPI} != "no" CONFGROUPS+= ACPI ACPI= power_profile ACPIPACKAGE= acpi .endif .if ${MK_APM} != "no" CONFGROUPS+= APM APM+= apm .if ${MACHINE} == "i386" APM+= apmd .endif APMPACKAGE= apm .endif .if ${MK_AUDIT} != "no" CONFGROUPS+= AUDIT AUDIT+= auditd AUDIT+= auditdistd AUDITPACKAGE= audit .endif .if ${MK_AUTOFS} != "no" CONFGROUPS+= AUTOFS AUTOFS= automount \ automountd \ autounmountd AUTOFSPACKAGE= autofs .endif .if ${MK_BLACKLIST} != "no" CONFGROUPS+= BLOCKLIST BLOCKLIST= blacklistd BLOCKLISTPACKAGE=blocklist .endif .if ${MK_BLUETOOTH} != "no" CONFGROUPS+= BLUETOOTH BLUETOOTH+= bluetooth \ bthidd \ hcsecd \ rfcomm_pppd_server \ sdpd \ ubthidhci BLUETOOTHPACKAGE= bluetooth .endif .if ${MK_BOOTPARAMD} != "no" CONFS+= bootparams .endif .if ${MK_BSNMP} != "no" CONFGROUPS+= BSNMP BSNMP+= bsnmpd BSNMPPACKAGE= bsnmp .endif .if ${MK_CCD} != "no" CONFGROUPS+= CCD CCD= ccd CCDPACKAGE= ccdconfig .endif .if ${MK_FTP} != "no" CONFGROUPS+= FTPD FTPD= ftpd FTPDPACKAGE= ftpd .endif .if ${MK_KERBEROS_SUPPORT} != "no" CONFGROUPS+= GSSD GSSD= gssd GSSDPACKAGE= gssd .endif .if ${MK_HAST} != "no" CONFGROUPS+= HAST HAST= hastd HASTPACKAGE= hast .endif .if ${MK_INETD} != "no" CONFGROUPS+= INETD INETD= inetd INETDPACKAGE= inetd .endif .if ${MK_IPFILTER} != "no" CONFGROUPS+= IPF IPF= ipfilter \ ipfs \ ipmon \ ipnat \ ippool IPFPACKAGE= ipf .endif .if ${MK_IPFW} != "no" CONFGROUPS+= IPFW IPFW= ipfw dnctl .if ${MK_NETGRAPH} != "no" IPFW+= ipfw_netflow .endif IPFWPACKAGE= ipfw # natd is only built when ipfw is built CONFGROUPS+= NATD NATD+= natd NATDPACKAGE= natd .endif .if ${MK_ISCSI} != "no" CONFGROUPS+= ISCSI ISCSI= iscsictl \ iscsid ISCSIPACKAGE= iscsi .endif .if ${MK_JAIL} != "no" CONFGROUPS+= JAIL JAIL+= jail JAILPACKAGE= jail .endif .if ${MK_LEGACY_CONSOLE} != "no" CONFGROUPS+= CONSOLE CONSOLE+= moused CONSOLE+= msconvd CONSOLE+= syscons CONSOLEPACKAGE= console-tools .endif .if ${MK_LPR} != "no" CONFGROUPS+= LP LP+= lpd LPPACKAGE= lp .endif .if ${MK_KERBEROS} != "no" .if ${MK_MITKRB5} == "no" # Heimdal rc scripts CONFGROUPS+= HEIMDAL HEIMDAL= ipropd_master \ ipropd_slave \ kadmind \ kdc \ kfd \ kpasswdd HEIMDALPACKAGE= kerberos DIRS+= VAR_HEMIDAL VAR_HEMIDAL= /var/heimdal VAR_HEMIDAL_MODE= 700 .else # ${MK_MITKRB5} != "no" # MIT KRB5 rc scripts CONFGROUPS+= KRB5 KRB5= kadmind \ kdc KRB5PACKAGE= kerberos-kdc .endif # ${MK_MITKRB5} .endif # ${MK_KERBEROS} .if ${MK_NIS} != "no" CONFGROUPS+= YP YP= ypbind \ ypldap \ yppasswdd \ ypserv \ ypset \ ypupdated \ ypxfrd \ nisdomain YPPACKAGE= yp .endif .if ${MK_NS_CACHING} != "no" _nscd= nscd .endif .if ${MK_NTP} != "no" CONFGROUPS+= NTP NTP+= ntpd \ ntpdate NTPPACKAGE= ntp .endif .if ${MK_OFED_EXTRA} != "no" _opensm= opensm .endif .if ${MK_OPENSSL} != "no" && ${MK_OPENSSL_KTLS} != "no" CONFS+= tlsclntd \ tlsservd .endif .if ${MK_OPENSSH} != "no" CONFGROUPS+= SSH SSH= sshd SSHPACKAGE= ssh .endif .if ${MK_PF} != "no" CONFGROUPS+= PF PF= pf \ pflog \ pfsync \ ftp-proxy PFPACKAGE= pf .endif .if ${MK_PPP} != "no" CONFGROUPS+= PPP PPP= ppp PPPPACKAGE= ppp .endif .if ${MK_INET6} != "no" || ${MK_ROUTED} != "no" CONFGROUPS+= RIP RIPPACKAGE= rip .if ${MK_INET6} != "no" RIP+= route6d .endif .if ${MK_ROUTED} != "no" RIP+= routed .endif .endif .if ${MK_SENDMAIL} != "no" CONFGROUPS+= SMRCD SMRCD= sendmail SMRCDPACKAGE= sendmail .endif .if ${MK_NUAGEINIT} != "no" CONFGROUPS+= NIUAGEINIT NIUAGEINIT= nuageinit \ nuageinit_post_net \ nuageinit_user_data_script NIUAGEINITPACKAGE= nuageinit .endif .if ${MK_UNBOUND} != "no" CONFGROUPS+= UNBOUND UNBOUND+= local_unbound UNBOUNDPACKAGE= unbound .endif .if ${MK_VI} != "no" CONFGROUPS+= VI VI+= virecover VIPACKAGE= vi .endif .if ${MK_WIRELESS} != "no" CONFGROUPS+= HOSTAPD HOSTAPD= hostapd HOSTAPDPACKAGE= hostapd CONFGROUPS+= WPA WPA= wpa_supplicant WPAPACKAGE= wpa .endif .if ${MK_ZFS} != "no" CONFGROUPS+= ZFS ZFS+= zfs ZFS+= zfsbe ZFS+= zfsd ZFS+= zfskeys ZFS+= zpool ZFS+= zpoolreguid ZFS+= zpoolupgrade ZFS+= zvol ZFSPACKAGE= zfs .endif .for fg in ${CONFGROUPS} ${fg}MODE?= ${BINMODE} .endfor .include diff --git a/libexec/rc/rc.d/virtual_oss b/libexec/rc/rc.d/virtual_oss new file mode 100644 index 000000000000..4f5c34ce03f3 --- /dev/null +++ b/libexec/rc/rc.d/virtual_oss @@ -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 "$@" diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index b97c22ffeb08..90b360ac55e6 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -1,228 +1,229 @@ .include SUBDIR= adduser \ arp \ binmiscctl \ boottrace \ bsdconfig \ camdd \ cdcontrol \ chkgrp \ chown \ chroot \ ckdist \ clear_locks \ crashinfo \ cron \ ctld \ ctladm \ daemon \ dconschat \ devctl \ devinfo \ diskinfo \ dumpcis \ etcupdate \ extattr \ extattrctl \ fifolog \ fstyp \ fwcontrol \ fwget \ getfmac \ getpmac \ gstat \ i2c \ ifmcstat \ iostat \ iovctl \ kldxref \ mailwrapper \ makefs \ memcontrol \ mfiutil \ mixer \ mlxcontrol \ mountd \ mount_smbfs \ mpsutil \ mptutil \ mtest \ newsyslog \ nfscbd \ nfsd \ nfsdumpstate \ nfsrevoke \ nfsuserd \ nmtree \ nologin \ pciconf \ periodic \ pnfsdscopymr \ pnfsdsfile \ pnfsdskill \ powerd \ prometheus_sysctl_exporter \ pstat \ pw \ pwd_mkdb \ pwm \ quot \ rarpd \ rmt \ rpcbind \ rpc.lockd \ rpc.statd \ rpc.umntall \ rtprio \ rwhod \ service \ services_mkdb \ sesutil \ setfib \ setfmac \ setpmac \ smbmsg \ snapinfo \ sndctl \ spi \ spray \ syslogd \ sysrc \ tcpdrop \ tcpdump \ tcpsso \ traceroute \ trim \ tzsetup \ ugidfw \ valectl \ vigr \ vipw \ + virtual_oss \ wake \ watch \ watchdogd \ zdump \ zic \ zonectl # NB: keep these sorted by MK_* knobs SUBDIR.${MK_ACCT}+= accton SUBDIR.${MK_ACCT}+= sa SUBDIR.${MK_AUDIT}+= audit SUBDIR.${MK_AUDIT}+= auditd .if ${MK_OPENSSL} != "no" SUBDIR.${MK_AUDIT}+= auditdistd .endif SUBDIR.${MK_AUDIT}+= auditreduce SUBDIR.${MK_AUDIT}+= praudit SUBDIR.${MK_AUTHPF}+= authpf SUBDIR.${MK_AUTOFS}+= autofs SUBDIR.${MK_BLACKLIST}+= blacklistctl SUBDIR.${MK_BLACKLIST}+= blacklistd SUBDIR.${MK_BLUETOOTH}+= bluetooth SUBDIR.${MK_BOOTPARAMD}+= bootparamd SUBDIR.${MK_BSDINSTALL}+= bsdinstall SUBDIR.${MK_BSNMP}+= bsnmpd .if ${MK_CAROOT} != "no" SUBDIR.${MK_OPENSSL}+= certctl .endif SUBDIR.${MK_CXGBETOOL}+= cxgbetool SUBDIR.${MK_EFI}+= efivar efidp efibootmgr efitable efiwake .if ${MK_OPENSSL} != "no" SUBDIR.${MK_EFI}+= uefisign .endif SUBDIR.${MK_FDT}+= ofwdump SUBDIR.${MK_FLOPPY}+= fdcontrol SUBDIR.${MK_FLOPPY}+= fdformat SUBDIR.${MK_FLOPPY}+= fdread SUBDIR.${MK_FLOPPY}+= fdwrite SUBDIR.${MK_FREEBSD_UPDATE}+= freebsd-update SUBDIR.${MK_KERBEROS_SUPPORT}+= gssd SUBDIR.${MK_GPIO}+= gpioctl SUBDIR.${MK_HYPERV}+= hyperv SUBDIR.${MK_INET6}+= ip6addrctl SUBDIR.${MK_INET6}+= mld6query SUBDIR.${MK_INET6}+= ndp SUBDIR.${MK_INET6}+= rip6query SUBDIR.${MK_INET6}+= route6d SUBDIR.${MK_INET6}+= rrenumd SUBDIR.${MK_INET6}+= rtadvctl SUBDIR.${MK_INET6}+= rtadvd SUBDIR.${MK_INET6}+= rtsold SUBDIR.${MK_INET6}+= traceroute6 SUBDIR.${MK_INETD}+= inetd SUBDIR.${MK_IPFW}+= ipfwpcap SUBDIR.${MK_ISCSI}+= iscsid SUBDIR.${MK_JAIL}+= jail SUBDIR.${MK_JAIL}+= jexec SUBDIR.${MK_JAIL}+= jls # XXX MK_SYSCONS SUBDIR.${MK_LEGACY_CONSOLE}+= kbdcontrol SUBDIR.${MK_LEGACY_CONSOLE}+= kbdmap SUBDIR.${MK_LEGACY_CONSOLE}+= moused SUBDIR.${MK_LEGACY_CONSOLE}+= vidcontrol SUBDIR.${MK_PPP}+= pppctl SUBDIR.${MK_NS_CACHING}+= nscd SUBDIR.${MK_LPR}+= lpr SUBDIR.${MK_MAN_UTILS}+= manctl SUBDIR.${MK_MLX5TOOL}+= mlx5tool SUBDIR.${MK_NETGRAPH}+= flowctl SUBDIR.${MK_NETGRAPH}+= ngctl SUBDIR.${MK_NETGRAPH}+= nghook SUBDIR.${MK_NIS}+= rpc.yppasswdd SUBDIR.${MK_NIS}+= rpc.ypupdated SUBDIR.${MK_NIS}+= rpc.ypxfrd SUBDIR.${MK_NIS}+= ypbind SUBDIR.${MK_NIS}+= ypldap SUBDIR.${MK_NIS}+= yp_mkdb SUBDIR.${MK_NIS}+= yppoll SUBDIR.${MK_NIS}+= yppush SUBDIR.${MK_NIS}+= ypserv SUBDIR.${MK_NIS}+= ypset SUBDIR.${MK_NTP}+= ntp SUBDIR.${MK_OPENSSL_KTLS}+= rpc.tlsclntd SUBDIR.${MK_OPENSSL_KTLS}+= rpc.tlsservd SUBDIR.${MK_PF}+= ftp-proxy SUBDIR.${MK_PKGBOOTSTRAP}+= pkg SUBDIR.${MK_PMC}+= pmc pmcannotate pmccontrol pmcstat pmcstudy SUBDIR.${MK_PPP}+= ppp SUBDIR.${MK_QUOTAS}+= edquota SUBDIR.${MK_QUOTAS}+= quotaon SUBDIR.${MK_QUOTAS}+= repquota SUBDIR.${MK_SENDMAIL}+= editmap SUBDIR.${MK_SENDMAIL}+= mailstats SUBDIR.${MK_SENDMAIL}+= makemap SUBDIR.${MK_SENDMAIL}+= praliases SUBDIR.${MK_SENDMAIL}+= sendmail SUBDIR.${MK_TCP_WRAPPERS}+= tcpdchk SUBDIR.${MK_TCP_WRAPPERS}+= tcpdmatch SUBDIR.${MK_TOOLCHAIN}+= config SUBDIR.${MK_TOOLCHAIN}+= crunch SUBDIR.${MK_UNBOUND}+= unbound SUBDIR.${MK_USB}+= uathload SUBDIR.${MK_USB}+= uhsoctl SUBDIR.${MK_USB}+= usbconfig SUBDIR.${MK_USB}+= usbdump SUBDIR.${MK_UTMPX}+= ac SUBDIR.${MK_UTMPX}+= lastlogin SUBDIR.${MK_UTMPX}+= utx SUBDIR.${MK_WIRELESS}+= wlandebug SUBDIR.${MK_WIRELESS}+= wlanstats SUBDIR.${MK_WIRELESS}+= wpa SUBDIR.${MK_TESTS}+= tests .include SUBDIR_PARALLEL= # Add architecture-specific manpages # to be included anyway MAN= apmd/apmd.8 \ nvram/nvram.8 .include .include diff --git a/usr.sbin/virtual_oss/Makefile b/usr.sbin/virtual_oss/Makefile new file mode 100644 index 000000000000..bf73041377b3 --- /dev/null +++ b/usr.sbin/virtual_oss/Makefile @@ -0,0 +1,8 @@ +.include + +SUBDIR+= virtual_bt_speaker \ + virtual_oss_cmd \ + virtual_oss + +.include "Makefile.inc" +.include diff --git a/usr.sbin/virtual_oss/Makefile.inc b/usr.sbin/virtual_oss/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/virtual_oss/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile b/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile new file mode 100644 index 000000000000..5e4553e7a298 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_bt_speaker/Makefile @@ -0,0 +1,11 @@ +PROG= virtual_bt_speaker +MAN= ${PROG}.8 + +SRCS= bt_speaker.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I${SRCTOP}/lib/virtual_oss/bt + +LDFLAGS+= -L${LIBDIR} -lm -lbluetooth -lsdp + +.include diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c new file mode 100644 index 000000000000..c61eaf1c338d --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c @@ -0,0 +1,542 @@ +/*- + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define L2CAP_SOCKET_CHECKED +#include +#include + +#include "avdtp_signal.h" +#include "bt.h" +#include "utils.h" + +static int (*bt_receive_f)(struct bt_config *, void *, int, int); +static int (*avdtpACPHandlePacket_f)(struct bt_config *cfg); +static void (*avdtpACPFree_f)(struct bt_config *); + +static int bt_in_background; + +static void +message(const char *fmt,...) +{ + va_list list; + + if (bt_in_background) + return; + + va_start(list, fmt); + vfprintf(stderr, fmt, list); + va_end(list); +} + +struct bt_audio_receiver { + const char *devname; + const char *sdp_socket_path; + uint16_t l2cap_psm; + int fd_listen; + void *sdp_session; + uint32_t sdp_handle; +}; + +static int +register_sdp(struct bt_audio_receiver *r) +{ + struct sdp_audio_sink_profile record = {}; + + r->sdp_session = sdp_open_local(r->sdp_socket_path); + if (r->sdp_session == NULL || sdp_error(r->sdp_session)) { + sdp_close(r->sdp_session); + r->sdp_session = NULL; + return (0); + } + + record.psm = r->l2cap_psm; + record.protover = 0x100; + record.features = 0x01; /* player only */ + + if (sdp_register_service(r->sdp_session, SDP_SERVICE_CLASS_AUDIO_SINK, + NG_HCI_BDADDR_ANY, (const uint8_t *)&record, sizeof(record), + &r->sdp_handle)) { + message("SDP failed to register: %s\n", + strerror(sdp_error(r->sdp_session))); + sdp_close(r->sdp_session); + r->sdp_session = NULL; + return (0); + } + return (1); +} + +static void +unregister_sdp(struct bt_audio_receiver *r) +{ + sdp_unregister_service(r->sdp_session, r->sdp_handle); + sdp_close(r->sdp_session); + r->sdp_session = NULL; +} + +static int +start_listen(struct bt_audio_receiver *r) +{ + struct sockaddr_l2cap addr = {}; + + r->fd_listen = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (r->fd_listen < 0) + return (0); + + addr.l2cap_len = sizeof(addr); + addr.l2cap_family = AF_BLUETOOTH; + addr.l2cap_psm = r->l2cap_psm; + + if (bind(r->fd_listen, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(r->fd_listen, 4) < 0) { + close(r->fd_listen); + return (0); + } + return (1); +} + +static void +stop_listen(struct bt_audio_receiver *r) +{ + close(r->fd_listen); +} + +struct bt_audio_connection { + struct bt_audio_receiver *r; + struct sockaddr_l2cap peer_addr; + struct bt_config cfg; + int oss_fd; +}; + +static void +close_connection(struct bt_audio_connection *c) +{ + avdtpACPFree_f(&c->cfg); + if (c->cfg.fd != -1) + close(c->cfg.fd); + if (c->cfg.hc != -1) + close(c->cfg.hc); + if (c->oss_fd != -1) + close(c->oss_fd); + free(c); +} + +static struct bt_audio_connection * +wait_for_connection(struct bt_audio_receiver *r) +{ + struct bt_audio_connection *c = + malloc(sizeof(struct bt_audio_connection)); + socklen_t addrlen; + + memset(c, 0, sizeof(*c)); + + c->r = r; + c->cfg.fd = -1; + c->oss_fd = -1; + + addrlen = sizeof(c->peer_addr); + c->cfg.hc = accept(r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen); + + message("Accepted control connection, %d\n", c->cfg.hc); + if (c->cfg.hc < 0) { + close_connection(c); + return NULL; + } + c->cfg.sep = 0; /* to be set later */ + c->cfg.media_Type = mediaTypeAudio; + c->cfg.chmode = MODE_DUAL; + c->cfg.aacMode1 = 0; /* TODO: support AAC */ + c->cfg.aacMode2 = 0; + c->cfg.acceptor_state = acpInitial; + + return (c); +} + +static void +setup_oss(struct bt_audio_connection *c) +{ + c->oss_fd = open(c->r->devname, O_WRONLY); + + if (c->oss_fd < 0) + goto err; + + int v; + + switch (c->cfg.chmode) { + case MODE_STEREO: + case MODE_JOINT: + case MODE_DUAL: + v = 2; + break; + case MODE_MONO: + v = 1; + break; + default: + message("Wrong chmode\n"); + goto err; + } + + if (ioctl(c->oss_fd, SNDCTL_DSP_CHANNELS, &v) < 0) { + message("SNDCTL_DSP_CHANNELS failed\n"); + goto err; + } + v = AFMT_S16_NE; + if (ioctl(c->oss_fd, SNDCTL_DSP_SETFMT, &v) < 0) { + message("SNDCTL_DSP_SETFMT failed\n"); + goto err; + } + switch (c->cfg.freq) { + case FREQ_16K: + v = 16000; + break; + case FREQ_32K: + v = 32000; + break; + case FREQ_44_1K: + v = 44100; + break; + case FREQ_48K: + v = 48000; + break; + default: + message("Wrong freq\n"); + goto err; + } + + if (ioctl(c->oss_fd, SNDCTL_DSP_SPEED, &v) < 0) { + message("SNDCTL_DSP_SETFMT failed\n"); + goto err; + } + v = (2 << 16) | 15; /* 2 fragments of 32k each */ + if (ioctl(c->oss_fd, SNDCTL_DSP_SETFRAGMENT, &v) < 0) { + message("SNDCTL_DSP_SETFRAGMENT failed\n"); + goto err; + } + return; + +err: + c->oss_fd = -1; + message("Cannot open oss device %s\n", c->r->devname); +} + +static void +process_connection(struct bt_audio_connection *c) +{ + struct pollfd pfd[3] = {}; + time_t oss_attempt = 0; + + while (c->cfg.acceptor_state != acpStreamClosed) { + int np; + + pfd[0].fd = c->r->fd_listen; + pfd[0].events = POLLIN | POLLRDNORM; + pfd[0].revents = 0; + + pfd[1].fd = c->cfg.hc; + pfd[1].events = POLLIN | POLLRDNORM; + pfd[1].revents = 0; + + pfd[2].fd = c->cfg.fd; + pfd[2].events = POLLIN | POLLRDNORM; + pfd[2].revents = 0; + + if (c->cfg.fd != -1) + np = 3; + else + np = 2; + + if (poll(pfd, np, INFTIM) < 0) + return; + + if (pfd[1].revents != 0) { + int retval; + + message("Handling packet: state = %d, ", + c->cfg.acceptor_state); + retval = avdtpACPHandlePacket_f(&c->cfg); + message("retval = %d\n", retval); + if (retval < 0) + return; + } + if (pfd[0].revents != 0) { + socklen_t addrlen = sizeof(c->peer_addr); + int fd = accept4(c->r->fd_listen, + (struct sockaddr *)&c->peer_addr, &addrlen, + SOCK_NONBLOCK); + + if (fd < 0) + return; + + if (c->cfg.fd < 0) { + if (c->cfg.acceptor_state == acpStreamOpened) { + socklen_t mtusize = sizeof(uint16_t); + c->cfg.fd = fd; + + if (getsockopt(c->cfg.fd, SOL_L2CAP, SO_L2CAP_IMTU, &c->cfg.mtu, &mtusize) == -1) { + message("Could not get MTU size\n"); + return; + } + + int temp = c->cfg.mtu * 32; + + if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) { + message("Could not set send buffer size\n"); + return; + } + + temp = 1; + if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) { + message("Could not set low water mark\n"); + return; + } + message("Accepted data connection, %d\n", c->cfg.fd); + } + } else { + close(fd); + } + } + if (pfd[2].revents != 0) { + uint8_t data[65536]; + int len; + + if ((len = bt_receive_f(&c->cfg, data, sizeof(data), 0)) < 0) { + return; + } + if (c->cfg.acceptor_state != acpStreamSuspended && + c->oss_fd < 0 && + time(NULL) != oss_attempt) { + message("Trying to open dsp\n"); + setup_oss(c); + oss_attempt = time(NULL); + } + if (c->oss_fd > -1) { + uint8_t *end = data + len; + uint8_t *ptr = data; + unsigned delay; + unsigned jitter_limit; + + switch (c->cfg.freq) { + case FREQ_16K: + jitter_limit = (16000 / 20); + break; + case FREQ_32K: + jitter_limit = (32000 / 20); + break; + case FREQ_44_1K: + jitter_limit = (44100 / 20); + break; + default: + jitter_limit = (48000 / 20); + break; + } + + if (c->cfg.chmode == MODE_MONO) { + if (len >= 2 && + ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 && + delay < (jitter_limit * 2)) { + uint8_t jitter[jitter_limit * 4] __aligned(4); + size_t x; + + /* repeat last sample */ + for (x = 0; x != sizeof(jitter); x++) + jitter[x] = ptr[x % 2]; + + write(c->oss_fd, jitter, sizeof(jitter)); + } + } else { + if (len >= 4 && + ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 && + delay < (jitter_limit * 4)) { + uint8_t jitter[jitter_limit * 8] __aligned(4); + size_t x; + + /* repeat last sample */ + for (x = 0; x != sizeof(jitter); x++) + jitter[x] = ptr[x % 4]; + + write(c->oss_fd, jitter, sizeof(jitter)); + } + } + while (ptr != end) { + int written = write(c->oss_fd, ptr, end - ptr); + + if (written < 0) { + if (errno != EINTR && errno != EAGAIN) + break; + written = 0; + } + ptr += written; + } + if (ptr != end) { + message("Not all written, closing dsp\n"); + close(c->oss_fd); + c->oss_fd = -1; + oss_attempt = time(NULL); + } + } + } + + if (c->cfg.acceptor_state == acpStreamSuspended && + c->oss_fd > -1) { + close(c->oss_fd); + c->oss_fd = -1; + } + } +} + +static struct option bt_speaker_opts[] = { + {"device", required_argument, NULL, 'd'}, + {"sdp_socket_path", required_argument, NULL, 'p'}, + {"rtprio", required_argument, NULL, 'i'}, + {"background", no_argument, NULL, 'B'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} +}; + +static void +usage(void) +{ + fprintf(stderr, "Usage: virtual_bt_speaker -d /dev/dsp\n" + "\t" "-d, --device [device]\n" + "\t" "-p, --sdp_socket_path [path]\n" + "\t" "-i, --rtprio [priority]\n" + "\t" "-B, --background\n" + ); + exit(EX_USAGE); +} + +int +main(int argc, char **argv) +{ + struct bt_audio_receiver r = {}; + struct rtprio rtp = {}; + void *hdl; + int ch; + + r.devname = NULL; + r.sdp_socket_path = NULL; + r.l2cap_psm = SDP_UUID_PROTOCOL_AVDTP; + + while ((ch = getopt_long(argc, argv, "p:i:d:Bh", bt_speaker_opts, NULL)) != -1) { + switch (ch) { + case 'd': + r.devname = optarg; + break; + case 'p': + r.sdp_socket_path = optarg; + break; + case 'B': + bt_in_background = 1; + break; + case 'i': + rtp.type = RTP_PRIO_REALTIME; + rtp.prio = atoi(optarg); + if (rtprio(RTP_SET, getpid(), &rtp) != 0) { + message("Cannot set realtime priority\n"); + } + break; + default: + usage(); + break; + } + } + + if (r.devname == NULL) + errx(EX_USAGE, "No devicename specified"); + + if (bt_in_background) { + if (daemon(0, 0) != 0) + errx(EX_SOFTWARE, "Cannot become daemon"); + } + + if ((hdl = dlopen("/usr/lib/virtual_oss/voss_bt.so", RTLD_NOW)) == NULL) + errx(1, "%s", dlerror()); + if ((bt_receive_f = dlsym(hdl, "bt_receive")) == NULL) + goto err_dlsym; + if ((avdtpACPHandlePacket_f = dlsym(hdl, "avdtpACPHandlePacket")) == + NULL) + goto err_dlsym; + if ((avdtpACPFree_f = dlsym(hdl, "avdtpACPFree")) == NULL) + goto err_dlsym; + + while (1) { + message("Starting to listen\n"); + if (!start_listen(&r)) { + message("Failed to initialize server socket\n"); + goto err_listen; + } + message("Registering service via SDP\n"); + if (!register_sdp(&r)) { + message("Failed to register in SDP\n"); + goto err_sdp; + } + while (1) { + message("Waiting for connection...\n"); + struct bt_audio_connection *c = wait_for_connection(&r); + + if (c == NULL) { + message("Failed to get connection\n"); + goto err_conn; + } + message("Got connection...\n"); + + process_connection(c); + + message("Connection finished...\n"); + + close_connection(c); + } +err_conn: + message("Unregistering service\n"); + unregister_sdp(&r); +err_sdp: + stop_listen(&r); +err_listen: + sleep(5); + } + return (0); + +err_dlsym: + warnx("%s", dlerror()); + dlclose(hdl); + exit(EXIT_FAILURE); +} diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8 b/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8 new file mode 100644 index 000000000000..2c6c6ea18bbc --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_bt_speaker/virtual_bt_speaker.8 @@ -0,0 +1,71 @@ +.\" +.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic +.\" +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd February 12, 2025 +.Dt VIRTUAL_BT_SPEAKER 8 +.Os +.Sh NAME +.Nm virtual_bt_speaker +.Nd virtual bluetooth speaker +.Sh SYNOPSIS +.Nm +.Op Fl h +.Sh DESCRIPTION +.Nm +provides bluetooth speaker functionality. +It receives connections from bluetooth devices that stream music, and +forwards the received stream to the given OSS device. +This utility depends on +.Xr sdpd 8 +running in the background. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl B +Run program in background. +.It Fl d Ar devname +OSS device to play the received streams. +.It Fl p Ar socketpath +Path to SDP control socket. +Default is to use default location. +.It Fl i Ar priority +Set real-time priority. +.It Fl h +Show usage. +.El +.Sh EXAMPLES +.Bd -literal -offset indent +virtual_bt_speaker -d /dev/dspX +.Ed +.Sh SEE ALSO +.Xr sdpd 8 +and +.Xr virtual_oss 8 +.Sh AUTHORS +.Nm +was written by +.An Richard Kralovic riso@google.com . diff --git a/usr.sbin/virtual_oss/virtual_equalizer/Makefile b/usr.sbin/virtual_oss/virtual_equalizer/Makefile new file mode 100644 index 000000000000..e67150eff000 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_equalizer/Makefile @@ -0,0 +1,11 @@ +PROG= virtual_equalizer +MAN= ${PROG}.8 + +SRCS= equalizer.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss \ + -I/usr/local/include + +LDFLAGS+= -L/usr/local/lib -lm -lfftw3 + +.include diff --git a/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c new file mode 100644 index 000000000000..d1682186084d --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_equalizer/equalizer.c @@ -0,0 +1,431 @@ +/*- + * Copyright (c) 2019 Google LLC, written by Richard Kralovic + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virtual_oss.h" + +struct Equalizer { + double rate; + int block_size; + int do_normalize; + + /* (block_size * 2) elements, time domain */ + double *fftw_time; + + /* (block_size * 2) elements, half-complex, freq domain */ + double *fftw_freq; + + fftw_plan forward; + fftw_plan inverse; +}; + +static int be_silent = 0; + +static void +message(const char *fmt,...) +{ + va_list list; + + if (be_silent) + return; + va_start(list, fmt); + vfprintf(stderr, fmt, list); + va_end(list); +} + +/* + * Masking window value for -1 < x < 1. + * + * Window must be symmetric, thus, this function is queried for x >= 0 + * only. Currently a Hann window. + */ +static double +equalizer_get_window(double x) +{ + return (0.5 + 0.5 * cos(M_PI * x)); +} + +static int +equalizer_load_freq_amps(struct Equalizer *e, const char *config) +{ + double prev_f = 0.0; + double prev_amp = 1.0; + double next_f = 0.0; + double next_amp = 1.0; + int i; + + if (strncasecmp(config, "normalize", 4) == 0) { + while (*config != 0) { + if (*config == '\n') { + config++; + break; + } + config++; + } + e->do_normalize = 1; + } else { + e->do_normalize = 0; + } + + for (i = 0; i <= (e->block_size / 2); ++i) { + const double f = (i * e->rate) / e->block_size; + + while (f >= next_f) { + prev_f = next_f; + prev_amp = next_amp; + + if (*config == 0) { + next_f = e->rate; + next_amp = prev_amp; + } else { + int len; + + if (sscanf(config, "%lf %lf %n", &next_f, &next_amp, &len) == 2) { + config += len; + if (next_f < prev_f) { + message("Parse error: Nonincreasing sequence of frequencies.\n"); + return (0); + } + } else { + message("Parse error.\n"); + return (0); + } + } + if (prev_f == 0.0) + prev_amp = next_amp; + } + e->fftw_freq[i] = ((f - prev_f) / (next_f - prev_f)) * (next_amp - prev_amp) + prev_amp; + } + return (1); +} + +static void +equalizer_init(struct Equalizer *e, int rate, int block_size) +{ + size_t buffer_size; + + e->rate = rate; + e->block_size = block_size; + + buffer_size = sizeof(double) * e->block_size; + + e->fftw_time = (double *)malloc(buffer_size); + e->fftw_freq = (double *)malloc(buffer_size); + + e->forward = fftw_plan_r2r_1d(block_size, e->fftw_time, e->fftw_freq, + FFTW_R2HC, FFTW_MEASURE); + e->inverse = fftw_plan_r2r_1d(block_size, e->fftw_freq, e->fftw_time, + FFTW_HC2R, FFTW_MEASURE); +} + +static int +equalizer_load(struct Equalizer *eq, const char *config) +{ + int retval = 0; + int N = eq->block_size; + int buffer_size = sizeof(double) * N; + int i; + + memset(eq->fftw_freq, 0, buffer_size); + + message("\n\nReloading amplification specifications:\n%s\n", config); + + if (!equalizer_load_freq_amps(eq, config)) + goto end; + + double *requested_freq = (double *)malloc(buffer_size); + + memcpy(requested_freq, eq->fftw_freq, buffer_size); + + fftw_execute(eq->inverse); + + /* Multiply by symmetric window and shift */ + for (i = 0; i < (N / 2); ++i) { + double weight = equalizer_get_window(i / (double)(N / 2)) / N; + + eq->fftw_time[N / 2 + i] = eq->fftw_time[i] * weight; + } + for (i = (N / 2 - 1); i > 0; --i) { + eq->fftw_time[i] = eq->fftw_time[N - i]; + } + eq->fftw_time[0] = 0; + + fftw_execute(eq->forward); + for (i = 0; i < N; ++i) { + eq->fftw_freq[i] /= (double)N; + } + + /* Debug output */ + for (i = 0; i <= (N / 2); ++i) { + double f = (eq->rate / N) * i; + double a = sqrt(pow(eq->fftw_freq[i], 2.0) + + ((i > 0 && i < N / 2) ? pow(eq->fftw_freq[N - i], 2.0) : 0)); + + a *= N; + double r = requested_freq[i]; + + message("%3.1lf Hz: requested %2.2lf, got %2.7lf (log10 = %.2lf), %3.7lfdb\n", + f, r, a, log(a) / log(10), (log(a / r) / log(10.0)) * 10.0); + } + + /* Normalize FIR filter, if any */ + if (eq->do_normalize) { + double sum = 0; + + for (i = 0; i < N; ++i) + sum += fabs(eq->fftw_time[i]); + if (sum != 0.0) { + for (i = 0; i < N; ++i) + eq->fftw_time[i] /= sum; + } + } + for (i = 0; i < N; ++i) { + message("%.3lf ms: %.10lf\n", 1000.0 * i / eq->rate, eq->fftw_time[i]); + } + + /* End of debug */ + + retval = 1; + + free(requested_freq); +end: + return (retval); +} + +static void +equalizer_done(struct Equalizer *eq) +{ + + fftw_destroy_plan(eq->forward); + fftw_destroy_plan(eq->inverse); + free(eq->fftw_time); + free(eq->fftw_freq); +} + +static struct option equalizer_opts[] = { + {"device", required_argument, NULL, 'd'}, + {"part", required_argument, NULL, 'p'}, + {"channels", required_argument, NULL, 'c'}, + {"what", required_argument, NULL, 'w'}, + {"off", no_argument, NULL, 'o'}, + {"quiet", no_argument, NULL, 'q'}, + {"file", no_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, +}; + +static void +usage(void) +{ + message("Usage: virtual_equalizer -d /dev/vdsp.ctl \n" + "\t -d, --device [control device]\n" + "\t -w, --what [rx_dev,tx_dev,rx_loop,tx_loop, default tx_dev]\n" + "\t -p, --part [part number, default 0]\n" + "\t -c, --channels [channels, default -1]\n" + "\t -f, --file [read input from file, default standard input]\n" + "\t -o, --off [disable equalizer]\n" + "\t -q, --quiet\n" + "\t -h, --help\n"); + exit(EX_USAGE); +} + +int +main(int argc, char **argv) +{ + struct virtual_oss_fir_filter fir = {}; + struct virtual_oss_io_info info = {}; + + struct Equalizer e; + + char buffer[65536]; + unsigned cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER; + unsigned cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER; + unsigned cmd_info = VIRTUAL_OSS_GET_DEV_INFO; + const char *dsp = NULL; + int rate; + int channels = -1; + int part = 0; + int opt; + int len; + int offset; + int disable = 0; + int f = STDIN_FILENO; + + while ((opt = getopt_long(argc, argv, "d:c:f:op:w:qh", + equalizer_opts, NULL)) != -1) { + switch (opt) { + case 'd': + dsp = optarg; + break; + case 'c': + channels = atoi(optarg); + if (channels == 0) { + message("Wrong number of channels\n"); + usage(); + } + break; + case 'p': + part = atoi(optarg); + if (part < 0) { + message("Invalid part number\n"); + usage(); + } + break; + case 'w': + if (strcmp(optarg, "rx_dev") == 0) { + cmd_fir_set = VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER; + cmd_fir_get = VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER; + cmd_info = VIRTUAL_OSS_GET_DEV_INFO; + } else if (strcmp(optarg, "tx_dev") == 0) { + cmd_fir_set = VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER; + cmd_fir_get = VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER; + cmd_info = VIRTUAL_OSS_GET_DEV_INFO; + } else if (strcmp(optarg, "rx_loop") == 0) { + cmd_fir_set = VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER; + cmd_fir_get = VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER; + cmd_info = VIRTUAL_OSS_GET_LOOP_INFO; + } else if (strcmp(optarg, "tx_loop") == 0) { + cmd_fir_set = VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER; + cmd_fir_get = VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER; + cmd_info = VIRTUAL_OSS_GET_LOOP_INFO; + } else { + message("Bad -w argument not recognized\n"); + usage(); + } + break; + case 'f': + if (f != STDIN_FILENO) { + message("Can only specify one file\n"); + usage(); + } + f = open(optarg, O_RDONLY); + if (f < 0) { + message("Cannot open specified file\n"); + usage(); + } + break; + case 'o': + disable = 1; + break; + case 'q': + be_silent = 1; + break; + default: + usage(); + } + } + + fir.number = part; + info.number = part; + + int fd = open(dsp, O_RDWR); + + if (fd < 0) { + message("Cannot open DSP device\n"); + return (EX_SOFTWARE); + } + if (ioctl(fd, VIRTUAL_OSS_GET_SAMPLE_RATE, &rate) < 0) { + message("Cannot get sample rate\n"); + return (EX_SOFTWARE); + } + if (ioctl(fd, cmd_fir_get, &fir) < 0) { + message("Cannot get current FIR filter\n"); + return (EX_SOFTWARE); + } + if (disable) { + for (fir.channel = 0; fir.channel != channels; fir.channel++) { + if (ioctl(fd, cmd_fir_set, &fir) < 0) { + if (fir.channel == 0) { + message("Cannot disable FIR filter\n"); + return (EX_SOFTWARE); + } + break; + } + } + return (0); + } + equalizer_init(&e, rate, fir.filter_size); + equalizer_load(&e, ""); + + if (f == STDIN_FILENO) { + if (ioctl(fd, cmd_info, &info) < 0) { + message("Cannot read part information\n"); + return (EX_SOFTWARE); + } + message("Please enter EQ layout for %s, :\n", info.name); + } + offset = 0; + while (1) { + if (offset == (int)(sizeof(buffer) - 1)) { + message("Too much input data\n"); + return (EX_SOFTWARE); + } + len = read(f, buffer + offset, sizeof(buffer) - 1 - offset); + if (len <= 0) + break; + offset += len; + } + buffer[offset] = 0; + close(f); + + if (f == STDIN_FILENO) + message("Loading new EQ layout\n"); + + if (equalizer_load(&e, buffer) == 0) { + message("Invalid equalizer data\n"); + return (EX_SOFTWARE); + } + fir.filter_data = e.fftw_time; + + for (fir.channel = 0; fir.channel != channels; fir.channel++) { + if (ioctl(fd, cmd_fir_set, &fir) < 0) { + if (fir.channel == 0) + message("Cannot set FIR filter on channel\n"); + break; + } + } + + close(fd); + equalizer_done(&e); + + return (0); +} diff --git a/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8 b/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8 new file mode 100644 index 000000000000..db47db84305e --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_equalizer/virtual_equalizer.8 @@ -0,0 +1,127 @@ +.\" +.\" Copyright (c) 2019 Google LLC, written by Richard Kralovic +.\" +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd February 12, 2025 +.Dt VIRTUAL_EQUALIZER 8 +.Os +.Sh NAME +.Nm virtual_equalizer +.Nd audio equalizer +.Sh SYNOPSIS +.Nm +.Op Fl h +.Op Fl o +.Op Fl q +.Op Fl d Ar devname +.Op Fl w Ar what +.Op Fl p Ar part +.Op Fl c Ar channels +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +sets the given frequency response for the given +.Xr virtual_oss 8 +instance via the control character device given by the -d option. +The design goal of this equalizer is to provide precise equalization +for arbitrary requested frequency response at the expense of higher +latency, utilizing a so-called finite impulse response, FIR, filter. +.Pp +The requested frequency response is configured via standard input or +the file specified by the -f option. +There is one control point in per line. +Each line consists of two numbers, frequency in Hz and requested +amplification. +Amplification between two consecutive control points is a linear +interpolation of the given control point values. +.Pp +To make the filter finite, it is windowed in time domain using a Hann +window. +The windowing actually modifies the frequency response - the actual +response is a convolution of the requested response and spectrum of +the window. +This is, however, very close to the requested response. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl q +Be quiet and don't print anything to standard output. +.It Fl d Ar device +The +.Xr virtual_oss 8 +control character device. +.It Fl w Ar what +Select what part the FIR filter should apply to. +Valid values are: rx_dev, tx_dev, rx_loop and tx_loop. +The default value is tx_dev. +.It Fl p Ar part +Select the index of the part given by the -w option to apply the filter to. +Default is zero. +.It Fl c Ar channels +Select number of channels to apply filter to, starting at channel zero. +By default all channels of the given part are updated. +.It Fl f Ar file +Read filter coefficients from the given file instead of standard input. +.It Fl o +Turn equalizer off. +.It Fl h +Show usage. +.El +.Sh EXAMPLES +To pass only frequencies between 200Hz and 400Hz: +.Bd -literal -offset indent +# Note that the -F and -G options enable FIR filtering. +virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\ + -f /dev/dsp -d dsp.virtual -t vdsp.ctl + +# For simplex operation use this: +virtual_oss -B -C 2 -c 2 -S -Q 0 -b 32 -r 48000 -s 8ms -F 80ms -G 80ms \\ + -R /dev/null -O /dev/dsp -d dsp.virtual -t vdsp.ctl + +# Load normalized filter points to avoid sample value overflow +cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2 +NORMALIZE +199 0.0 +200 1.0 +400 1.0 +401 0.0 +EOF + +# Load FIR filter based on sine frequency points +cat << EOF | virtual_equalizer -d /dev/vdsp.ctl -w tx_dev -p 0 -c 2 +199 0.0 +200 1.0 +400 1.0 +401 0.0 +EOF + +.Ed +.Sh SEE ALSO +.Xr virtual_oss 8 +.Sh AUTHORS +.Nm +was written by +.An Richard Kralovic riso@google.com . diff --git a/usr.sbin/virtual_oss/virtual_oss/Makefile b/usr.sbin/virtual_oss/virtual_oss/Makefile new file mode 100644 index 000000000000..cdb6bcac3fad --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/Makefile @@ -0,0 +1,24 @@ +PROG= virtual_oss +MAN= ${PROG}.8 + +SRCS= audio_delay.c \ + compressor.c \ + ctl.c \ + eq.c \ + format.c \ + httpd.c \ + main.c \ + mul.c \ + ring.c \ + virtual_oss.c + +CFLAGS+= -I${SRCTOP}/contrib/libsamplerate +# The --export-dynamic-symbol flags below are needed because some backends make +# use of those symbols. +LDFLAGS+= -lpthread -lcuse -lnv -lm \ + -Wl,--export-dynamic-symbol=virtual_oss_wait \ + -Wl,--export-dynamic-symbol=voss_has_synchronization +LIBADD= samplerate +LDFLAGS+= -L${.OBJDIR:H:H}/libsamplerate + +.include diff --git a/usr.sbin/virtual_oss/virtual_oss/audio_delay.c b/usr.sbin/virtual_oss/virtual_oss/audio_delay.c new file mode 100644 index 000000000000..a69f448354fd --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/audio_delay.c @@ -0,0 +1,238 @@ +/*- + * Copyright (c) 2014 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "int.h" + +#define REF_FREQ 500 /* HZ */ + +uint32_t voss_ad_last_delay; +uint8_t voss_ad_enabled; +uint8_t voss_ad_output_signal; +uint8_t voss_ad_input_channel; +uint8_t voss_ad_output_channel; + +static struct voss_ad { + double *wave; + + double *sin_a; + double *cos_a; + + double *sin_b; + double *cos_b; + + double *buf_a; + double *buf_b; + + double sum_sin_a; + double sum_cos_a; + + double sum_sin_b; + double sum_cos_b; + + uint32_t len_a; + uint32_t len_b; + + uint32_t offset_a; + uint32_t offset_b; +} voss_ad; + +void +voss_ad_reset(void) +{ + uint32_t x; + + for (x = 0; x != voss_ad.len_a; x++) + voss_ad.buf_a[x] = 0; + + for (x = 0; x != voss_ad.len_b; x++) + voss_ad.buf_b[x] = 0; + + voss_ad.sum_sin_a = 0; + voss_ad.sum_cos_a = 0; + voss_ad.sum_sin_b = 0; + voss_ad.sum_cos_b = 0; + + voss_ad.offset_a = 0; + voss_ad.offset_b = 0; + + voss_ad_last_delay = 0; +} + +void +voss_ad_init(uint32_t rate) +{ + double freq; + int samples; + int len; + int x; + + len = sqrt(rate); + + samples = len * len; + + voss_ad.wave = malloc(sizeof(voss_ad.wave[0]) * samples); + + voss_ad.sin_a = malloc(sizeof(voss_ad.sin_a[0]) * len); + voss_ad.cos_a = malloc(sizeof(voss_ad.cos_a[0]) * len); + voss_ad.buf_a = malloc(sizeof(voss_ad.buf_a[0]) * len); + voss_ad.len_a = len; + + voss_ad.sin_b = malloc(sizeof(voss_ad.sin_b[0]) * samples); + voss_ad.cos_b = malloc(sizeof(voss_ad.cos_b[0]) * samples); + voss_ad.buf_b = malloc(sizeof(voss_ad.buf_b[0]) * samples); + voss_ad.len_b = samples; + + if (voss_ad.sin_a == NULL || voss_ad.cos_a == NULL || + voss_ad.sin_b == NULL || voss_ad.cos_b == NULL || + voss_ad.buf_a == NULL || voss_ad.buf_b == NULL) + errx(EX_SOFTWARE, "Out of memory"); + + freq = 1.0; + + while (1) { + double temp = freq * ((double)rate) / ((double)len); + if (temp >= REF_FREQ) + break; + freq += 1.0; + } + + for (x = 0; x != len; x++) { + voss_ad.sin_a[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len)); + voss_ad.cos_a[x] = cos(freq * 2.0 * M_PI * ((double)x) / ((double)len)); + voss_ad.buf_a[x] = 0; + } + + for (x = 0; x != samples; x++) { + + voss_ad.wave[x] = sin(freq * 2.0 * M_PI * ((double)x) / ((double)len)) * + (1.0 + sin(2.0 * M_PI * ((double)x) / ((double)samples))) / 2.0; + + voss_ad.sin_b[x] = sin(2.0 * M_PI * ((double)x) / ((double)samples)); + voss_ad.cos_b[x] = cos(2.0 * M_PI * ((double)x) / ((double)samples)); + voss_ad.buf_b[x] = 0; + } +} + +static double +voss_add_decode_offset(double x /* cos */, double y /* sin */) +{ + uint32_t v; + double r; + + r = sqrt((x * x) + (y * y)); + + if (r == 0.0) + return (0); + + x /= r; + y /= r; + + v = 0; + + if (y < 0) { + v |= 1; + y = -y; + } + if (x < 0) { + v |= 2; + x = -x; + } + + if (y < x) { + r = acos(y); + } else { + r = asin(x); + } + + switch (v) { + case 0: + r = (2.0 * M_PI) - r; + break; + case 1: + r = M_PI + r; + break; + case 3: + r = M_PI - r; + break; + default: + break; + } + return (r); +} + +double +voss_ad_getput_sample(double sample) +{ + double retval; + double phase; + uint32_t xa; + uint32_t xb; + + xa = voss_ad.offset_a; + xb = voss_ad.offset_b; + retval = voss_ad.wave[xb]; + + sample -= voss_ad.buf_a[xa]; + voss_ad.sum_sin_a += voss_ad.sin_a[xa] * sample; + voss_ad.sum_cos_a += voss_ad.cos_a[xa] * sample; + voss_ad.buf_a[xa] += sample; + + sample = sqrt((voss_ad.sum_sin_a * voss_ad.sum_sin_a) + + (voss_ad.sum_cos_a * voss_ad.sum_cos_a)); + + sample -= voss_ad.buf_b[xb]; + voss_ad.sum_sin_b += voss_ad.sin_b[xb] * sample; + voss_ad.sum_cos_b += voss_ad.cos_b[xb] * sample; + voss_ad.buf_b[xb] += sample; + + if (++xa == voss_ad.len_a) + xa = 0; + + if (++xb == voss_ad.len_b) { + xb = 0; + + phase = voss_add_decode_offset( + voss_ad.sum_cos_b, voss_ad.sum_sin_b); + + voss_ad_last_delay = (uint32_t)(phase * (double)(voss_ad.len_b) / (2.0 * M_PI)) - (voss_ad.len_a / 2); + if (voss_ad_last_delay > voss_ad.len_b) + voss_ad_last_delay = voss_ad.len_b; + } + voss_ad.offset_a = xa; + voss_ad.offset_b = xb; + + return (retval * (1LL << voss_ad_output_signal)); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/backend.h b/usr.sbin/virtual_oss/virtual_oss/backend.h new file mode 100644 index 000000000000..d7453f3db89e --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/backend.h @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2015 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _VIRTUAL_BACKEND_H_ +#define _VIRTUAL_BACKEND_H_ + +struct voss_backend { + int (*open)(struct voss_backend *, const char *, int, int, int *, int *); + void (*close)(struct voss_backend *); + int (*transfer)(struct voss_backend *, void *, int); + void (*delay)(struct voss_backend *, int *); + void *arg; + int fd; +}; + +/* Currently selected backends */ +extern struct voss_backend *voss_rx_backend; +extern struct voss_backend *voss_tx_backend; + +/* Available backends */ +/* XXX Get rid somehow? */ +extern struct voss_backend voss_backend_null_rec; +extern struct voss_backend voss_backend_null_play; +extern struct voss_backend voss_backend_oss_rec; +extern struct voss_backend voss_backend_oss_play; +extern struct voss_backend voss_backend_bt_rec; +extern struct voss_backend voss_backend_bt_play; +extern struct voss_backend voss_backend_sndio_rec; +extern struct voss_backend voss_backend_sndio_play; + +#endif /* _VIRTUAL_BACKEND_H_ */ diff --git a/usr.sbin/virtual_oss/virtual_oss/compressor.c b/usr.sbin/virtual_oss/virtual_oss/compressor.c new file mode 100644 index 000000000000..4a92a38eceaa --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/compressor.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2020 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include + +#include "int.h" +#include "virtual_oss.h" + +struct virtual_compressor voss_output_compressor_param = { + .knee = 85, + .attack = 3, + .decay = 20, +}; +double voss_output_compressor_gain[VMAX_CHAN]; + +void +voss_compressor(int64_t *buffer, double *p_ch_gain, + const struct virtual_compressor *p_param, const unsigned samples, + const unsigned maxchan, const int64_t fmt_max) +{ + int64_t knee_amp; + int64_t sample; + unsigned ch; + unsigned i; + double amp; + + /* check if compressor is enabled */ + if (p_param->enabled != 1) + return; + + knee_amp = (fmt_max * p_param->knee) / VIRTUAL_OSS_KNEE_MAX; + + for (ch = i = 0; i != samples; i++) { + sample = buffer[i]; + if (sample < 0) + sample = -sample; + + amp = p_ch_gain[ch]; + if (sample > knee_amp) { + const double gain = (double)knee_amp / (double)sample; + if (gain < amp) + amp += (gain - amp) / (1LL << p_param->attack); + } + buffer[i] *= amp; + amp += (1.0 - amp) / (1LL << p_param->decay); + p_ch_gain[ch] = amp; + + if (++ch == maxchan) + ch = 0; + } +} diff --git a/usr.sbin/virtual_oss/virtual_oss/ctl.c b/usr.sbin/virtual_oss/virtual_oss/ctl.c new file mode 100644 index 000000000000..4a445a59db59 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/ctl.c @@ -0,0 +1,615 @@ +/*- + * Copyright (c) 2012-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#include + +#include "int.h" +#include "virtual_oss.h" + +int64_t voss_output_peak[VMAX_CHAN]; +int64_t voss_input_peak[VMAX_CHAN]; + +static int +vctl_open(struct cuse_dev *pdev __unused, int fflags __unused) +{ + return (0); +} + +static int +vctl_close(struct cuse_dev *pdev __unused, int fflags __unused) +{ + return (0); +} + +static vprofile_t * +vprofile_by_index(const vprofile_head_t *phead, int index) +{ + vprofile_t *pvp; + + TAILQ_FOREACH(pvp, phead, entry) { + if (!index--) + return (pvp); + } + return (NULL); +} + +static vmonitor_t * +vmonitor_by_index(int index, vmonitor_head_t *phead) +{ + vmonitor_t *pvm; + + TAILQ_FOREACH(pvm, phead, entry) { + if (!index--) + return (pvm); + } + return (NULL); +} + +static int +vctl_ioctl(struct cuse_dev *pdev __unused, int fflags __unused, + unsigned long cmd, void *peer_data) +{ + union { + int val; + struct virtual_oss_io_info io_info; + struct virtual_oss_mon_info mon_info; + struct virtual_oss_io_peak io_peak; + struct virtual_oss_mon_peak mon_peak; + struct virtual_oss_compressor out_lim; + struct virtual_oss_io_limit io_lim; + struct virtual_oss_master_peak master_peak; + struct virtual_oss_audio_delay_locator ad_locator; + struct virtual_oss_fir_filter fir_filter; + struct virtual_oss_system_info sys_info; + char options[VIRTUAL_OSS_OPTIONS_MAX]; + } data; + + vprofile_t *pvp; + vmonitor_t *pvm; + + int chan; + int len; + int error; + + len = IOCPARM_LEN(cmd); + + if (len < 0 || len > (int)sizeof(data)) + return (CUSE_ERR_INVALID); + + if (cmd & IOC_IN) { + error = cuse_copy_in(peer_data, &data, len); + if (error) + return (error); + } else { + error = 0; + } + + atomic_lock(); + switch (cmd) { + case VIRTUAL_OSS_GET_DEV_INFO: + case VIRTUAL_OSS_SET_DEV_INFO: + case VIRTUAL_OSS_GET_DEV_PEAK: + case VIRTUAL_OSS_SET_DEV_LIMIT: + case VIRTUAL_OSS_GET_DEV_LIMIT: + case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER: + case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER: + case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER: + case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER: + pvp = vprofile_by_index(&virtual_profile_client_head, data.val); + break; + case VIRTUAL_OSS_GET_LOOP_INFO: + case VIRTUAL_OSS_SET_LOOP_INFO: + case VIRTUAL_OSS_GET_LOOP_PEAK: + case VIRTUAL_OSS_SET_LOOP_LIMIT: + case VIRTUAL_OSS_GET_LOOP_LIMIT: + case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER: + case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER: + case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER: + case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER: + pvp = vprofile_by_index(&virtual_profile_loopback_head, data.val); + break; + default: + pvp = NULL; + break; + } + + switch (cmd) { + case VIRTUAL_OSS_GET_VERSION: + data.val = VIRTUAL_OSS_VERSION; + break; + case VIRTUAL_OSS_GET_DEV_INFO: + case VIRTUAL_OSS_GET_LOOP_INFO: + if (pvp == NULL || + data.io_info.channel < 0 || + data.io_info.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + break; + } + strlcpy(data.io_info.name, pvp->oss_name, sizeof(data.io_info.name)); + chan = data.io_info.channel; + data.io_info.rx_amp = pvp->rx_shift[chan]; + data.io_info.tx_amp = pvp->tx_shift[chan]; + data.io_info.rx_chan = pvp->rx_src[chan]; + data.io_info.tx_chan = pvp->tx_dst[chan]; + data.io_info.rx_mute = pvp->rx_mute[chan] ? 1 : 0; + data.io_info.tx_mute = pvp->tx_mute[chan] ? 1 : 0; + data.io_info.rx_pol = pvp->rx_pol[chan] ? 1 : 0; + data.io_info.tx_pol = pvp->tx_pol[chan] ? 1 : 0; + data.io_info.bits = pvp->bits; + data.io_info.rx_delay = pvp->rec_delay; + data.io_info.rx_delay_limit = voss_dsp_sample_rate; + break; + case VIRTUAL_OSS_SET_DEV_INFO: + case VIRTUAL_OSS_SET_LOOP_INFO: + if (pvp == NULL || + data.io_info.channel < 0 || + data.io_info.channel >= (int)pvp->channels || + data.io_info.rx_amp < -31 || data.io_info.rx_amp > 31 || + data.io_info.tx_amp < -31 || data.io_info.tx_amp > 31 || + data.io_info.rx_delay < 0 || + data.io_info.rx_delay > (int)voss_dsp_sample_rate) { + error = CUSE_ERR_INVALID; + break; + } + chan = data.io_info.channel; + pvp->rx_shift[chan] = data.io_info.rx_amp; + pvp->tx_shift[chan] = data.io_info.tx_amp; + pvp->rx_src[chan] = data.io_info.rx_chan; + pvp->tx_dst[chan] = data.io_info.tx_chan; + pvp->rx_mute[chan] = data.io_info.rx_mute ? 1 : 0; + pvp->tx_mute[chan] = data.io_info.tx_mute ? 1 : 0; + pvp->rx_pol[chan] = data.io_info.rx_pol ? 1 : 0; + pvp->tx_pol[chan] = data.io_info.tx_pol ? 1 : 0; + pvp->rec_delay = data.io_info.rx_delay; + break; + case VIRTUAL_OSS_GET_INPUT_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_input); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_info.src_chan = pvm->src_chan; + data.mon_info.dst_chan = pvm->dst_chan; + data.mon_info.pol = pvm->pol; + data.mon_info.mute = pvm->mute; + data.mon_info.amp = pvm->shift; + data.mon_info.bits = voss_dsp_bits; + break; + case VIRTUAL_OSS_SET_INPUT_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_input); + if (pvm == NULL || + data.mon_info.amp < -31 || + data.mon_info.amp > 31) { + error = CUSE_ERR_INVALID; + break; + } + pvm->src_chan = data.mon_info.src_chan; + pvm->dst_chan = data.mon_info.dst_chan; + pvm->pol = data.mon_info.pol ? 1 : 0; + pvm->mute = data.mon_info.mute ? 1 : 0; + pvm->shift = data.mon_info.amp; + break; + case VIRTUAL_OSS_GET_OUTPUT_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_output); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_info.src_chan = pvm->src_chan; + data.mon_info.dst_chan = pvm->dst_chan; + data.mon_info.pol = pvm->pol; + data.mon_info.mute = pvm->mute; + data.mon_info.amp = pvm->shift; + data.mon_info.bits = voss_dsp_bits; + break; + case VIRTUAL_OSS_SET_OUTPUT_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_output); + if (pvm == NULL || + data.mon_info.amp < -31 || + data.mon_info.amp > 31) { + error = CUSE_ERR_INVALID; + break; + } + pvm->src_chan = data.mon_info.src_chan; + pvm->dst_chan = data.mon_info.dst_chan; + pvm->pol = data.mon_info.pol ? 1 : 0; + pvm->mute = data.mon_info.mute ? 1 : 0; + pvm->shift = data.mon_info.amp; + break; + case VIRTUAL_OSS_GET_LOCAL_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_local); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_info.src_chan = pvm->src_chan; + data.mon_info.dst_chan = pvm->dst_chan; + data.mon_info.pol = pvm->pol; + data.mon_info.mute = pvm->mute; + data.mon_info.amp = pvm->shift; + data.mon_info.bits = voss_dsp_bits; + break; + case VIRTUAL_OSS_SET_LOCAL_MON_INFO: + pvm = vmonitor_by_index(data.mon_info.number, + &virtual_monitor_local); + if (pvm == NULL || + data.mon_info.amp < -31 || + data.mon_info.amp > 31) { + error = CUSE_ERR_INVALID; + break; + } + pvm->src_chan = data.mon_info.src_chan; + pvm->dst_chan = data.mon_info.dst_chan; + pvm->pol = data.mon_info.pol ? 1 : 0; + pvm->mute = data.mon_info.mute ? 1 : 0; + pvm->shift = data.mon_info.amp; + break; + case VIRTUAL_OSS_GET_DEV_PEAK: + case VIRTUAL_OSS_GET_LOOP_PEAK: + if (pvp == NULL || + data.io_peak.channel < 0 || + data.io_peak.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + break; + } + strlcpy(data.io_peak.name, pvp->oss_name, sizeof(data.io_peak.name)); + chan = data.io_peak.channel; + data.io_peak.rx_peak_value = pvp->rx_peak_value[chan]; + pvp->rx_peak_value[chan] = 0; + data.io_peak.tx_peak_value = pvp->tx_peak_value[chan]; + pvp->tx_peak_value[chan] = 0; + data.io_peak.bits = pvp->bits; + break; + case VIRTUAL_OSS_GET_INPUT_MON_PEAK: + pvm = vmonitor_by_index(data.mon_peak.number, + &virtual_monitor_input); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_peak.peak_value = pvm->peak_value; + data.mon_peak.bits = voss_dsp_bits; + pvm->peak_value = 0; + break; + case VIRTUAL_OSS_GET_OUTPUT_MON_PEAK: + pvm = vmonitor_by_index(data.mon_peak.number, + &virtual_monitor_output); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_peak.peak_value = pvm->peak_value; + data.mon_peak.bits = voss_dsp_bits; + pvm->peak_value = 0; + break; + case VIRTUAL_OSS_GET_LOCAL_MON_PEAK: + pvm = vmonitor_by_index(data.mon_peak.number, + &virtual_monitor_local); + if (pvm == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.mon_peak.peak_value = pvm->peak_value; + data.mon_peak.bits = voss_dsp_bits; + pvm->peak_value = 0; + break; + case VIRTUAL_OSS_ADD_INPUT_MON: + pvm = vmonitor_alloc(&data.val, + &virtual_monitor_input); + if (pvm == NULL) + error = CUSE_ERR_INVALID; + break; + case VIRTUAL_OSS_ADD_OUTPUT_MON: + pvm = vmonitor_alloc(&data.val, + &virtual_monitor_output); + if (pvm == NULL) + error = CUSE_ERR_INVALID; + break; + case VIRTUAL_OSS_ADD_LOCAL_MON: + pvm = vmonitor_alloc(&data.val, + &virtual_monitor_local); + if (pvm == NULL) + error = CUSE_ERR_INVALID; + break; + case VIRTUAL_OSS_SET_OUTPUT_LIMIT: + if (data.out_lim.enabled < 0 || + data.out_lim.enabled > 1 || + data.out_lim.knee < VIRTUAL_OSS_KNEE_MIN || + data.out_lim.knee > VIRTUAL_OSS_KNEE_MAX || + data.out_lim.attack < VIRTUAL_OSS_ATTACK_MIN || + data.out_lim.attack > VIRTUAL_OSS_ATTACK_MAX || + data.out_lim.decay < VIRTUAL_OSS_DECAY_MIN || + data.out_lim.decay > VIRTUAL_OSS_DECAY_MAX || + data.out_lim.gain != 0) { + error = CUSE_ERR_INVALID; + break; + } + voss_output_compressor_param.enabled = data.out_lim.enabled; + voss_output_compressor_param.knee = data.out_lim.knee; + voss_output_compressor_param.attack = data.out_lim.attack; + voss_output_compressor_param.decay = data.out_lim.decay; + break; + case VIRTUAL_OSS_GET_OUTPUT_LIMIT: + data.out_lim.enabled = voss_output_compressor_param.enabled; + data.out_lim.knee = voss_output_compressor_param.knee; + data.out_lim.attack = voss_output_compressor_param.attack; + data.out_lim.decay = voss_output_compressor_param.decay; + data.out_lim.gain = 1000; + for (chan = 0; chan != VMAX_CHAN; chan++) { + int gain = voss_output_compressor_gain[chan] * 1000.0; + if (data.out_lim.gain > gain) + data.out_lim.gain = gain; + } + break; + case VIRTUAL_OSS_SET_DEV_LIMIT: + case VIRTUAL_OSS_SET_LOOP_LIMIT: + if (pvp == NULL || + data.io_lim.param.enabled < 0 || + data.io_lim.param.enabled > 1 || + data.io_lim.param.knee < VIRTUAL_OSS_KNEE_MIN || + data.io_lim.param.knee > VIRTUAL_OSS_KNEE_MAX || + data.io_lim.param.attack < VIRTUAL_OSS_ATTACK_MIN || + data.io_lim.param.attack > VIRTUAL_OSS_ATTACK_MAX || + data.io_lim.param.decay < VIRTUAL_OSS_DECAY_MIN || + data.io_lim.param.decay > VIRTUAL_OSS_DECAY_MAX || + data.io_lim.param.gain != 0) { + error = CUSE_ERR_INVALID; + break; + } + pvp->rx_compressor_param.enabled = data.io_lim.param.enabled; + pvp->rx_compressor_param.knee = data.io_lim.param.knee; + pvp->rx_compressor_param.attack = data.io_lim.param.attack; + pvp->rx_compressor_param.decay = data.io_lim.param.decay; + break; + case VIRTUAL_OSS_GET_DEV_LIMIT: + case VIRTUAL_OSS_GET_LOOP_LIMIT: + if (pvp == NULL) { + error = CUSE_ERR_INVALID; + break; + } + data.io_lim.param.enabled = pvp->rx_compressor_param.enabled; + data.io_lim.param.knee = pvp->rx_compressor_param.knee; + data.io_lim.param.attack = pvp->rx_compressor_param.attack; + data.io_lim.param.decay = pvp->rx_compressor_param.decay; + data.io_lim.param.gain = 1000; + + for (chan = 0; chan != VMAX_CHAN; chan++) { + int gain = pvp->rx_compressor_gain[chan] * 1000.0; + if (data.io_lim.param.gain > gain) + data.io_lim.param.gain = gain; + } + break; + case VIRTUAL_OSS_GET_OUTPUT_PEAK: + chan = data.master_peak.channel; + if (chan < 0 || + chan >= (int)voss_max_channels) { + error = CUSE_ERR_INVALID; + break; + } + data.master_peak.bits = voss_dsp_bits; + data.master_peak.peak_value = voss_output_peak[chan]; + voss_output_peak[chan] = 0; + break; + case VIRTUAL_OSS_GET_INPUT_PEAK: + chan = data.master_peak.channel; + if (chan < 0 || + chan >= (int)voss_dsp_max_channels) { + error = CUSE_ERR_INVALID; + break; + } + data.master_peak.bits = voss_dsp_bits; + data.master_peak.peak_value = voss_input_peak[chan]; + voss_input_peak[chan] = 0; + break; + + case VIRTUAL_OSS_SET_RECORDING: + voss_is_recording = data.val ? 1 : 0; + break; + + case VIRTUAL_OSS_GET_RECORDING: + data.val = voss_is_recording; + break; + + case VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR: + if (data.ad_locator.channel_output < 0 || + data.ad_locator.channel_output >= (int)voss_mix_channels) { + error = CUSE_ERR_INVALID; + break; + } + if (data.ad_locator.channel_input < 0 || + data.ad_locator.channel_input >= (int)voss_mix_channels) { + error = CUSE_ERR_INVALID; + break; + } + if (data.ad_locator.signal_output_level < 0 || + data.ad_locator.signal_output_level >= 64) { + error = CUSE_ERR_INVALID; + break; + } + voss_ad_enabled = (data.ad_locator.locator_enabled != 0); + voss_ad_output_signal = data.ad_locator.signal_output_level; + voss_ad_output_channel = data.ad_locator.channel_output; + voss_ad_input_channel = data.ad_locator.channel_input; + break; + + case VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR: + data.ad_locator.locator_enabled = voss_ad_enabled; + data.ad_locator.signal_output_level = voss_ad_output_signal; + data.ad_locator.channel_output = voss_ad_output_channel; + data.ad_locator.channel_input = voss_ad_input_channel; + data.ad_locator.channel_last = voss_mix_channels - 1; + data.ad_locator.signal_input_delay = voss_ad_last_delay; + data.ad_locator.signal_delay_hz = voss_dsp_sample_rate; + break; + + case VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR: + voss_ad_reset(); + break; + + case VIRTUAL_OSS_ADD_OPTIONS: + data.options[VIRTUAL_OSS_OPTIONS_MAX - 1] = 0; + voss_add_options(data.options); + break; + + case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER: + case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER: + if (pvp == NULL || + data.fir_filter.channel < 0 || + data.fir_filter.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + } else if (data.fir_filter.filter_data == NULL) { + data.fir_filter.filter_size = pvp->rx_filter_size; + } else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) { + error = CUSE_ERR_INVALID; + } else if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) { + error = CUSE_ERR_NO_MEMORY; /* filter disabled */ + } else { + error = cuse_copy_out(pvp->rx_filter_data[data.fir_filter.channel], + data.fir_filter.filter_data, + sizeof(pvp->rx_filter_data[0][0]) * + data.fir_filter.filter_size); + } + break; + + case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER: + case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER: + if (pvp == NULL || + data.fir_filter.channel < 0 || + data.fir_filter.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + } else if (data.fir_filter.filter_data == NULL) { + data.fir_filter.filter_size = pvp->tx_filter_size; + } else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) { + error = CUSE_ERR_INVALID; + } else if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) { + error = CUSE_ERR_NO_MEMORY; /* filter disabled */ + } else { + error = cuse_copy_out(pvp->tx_filter_data[data.fir_filter.channel], + data.fir_filter.filter_data, + sizeof(pvp->tx_filter_data[0][0]) * + data.fir_filter.filter_size); + } + break; + + case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER: + case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER: + if (pvp == NULL || + data.fir_filter.channel < 0 || + data.fir_filter.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + } else if (data.fir_filter.filter_data == NULL) { + free(pvp->rx_filter_data[data.fir_filter.channel]); + pvp->rx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */ + } else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) { + error = CUSE_ERR_INVALID; + } else if (pvp->rx_filter_size != 0) { + size_t size = sizeof(pvp->rx_filter_data[0][0]) * pvp->rx_filter_size; + if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) { + pvp->rx_filter_data[data.fir_filter.channel] = malloc(size); + if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) + error = CUSE_ERR_NO_MEMORY; + else + memset(pvp->rx_filter_data[data.fir_filter.channel], 0, size); + } + if (pvp->rx_filter_data[data.fir_filter.channel] != NULL) { + error = cuse_copy_in(data.fir_filter.filter_data, + pvp->rx_filter_data[data.fir_filter.channel], size); + } + } + break; + + case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER: + case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER: + if (pvp == NULL || + data.fir_filter.channel < 0 || + data.fir_filter.channel >= (int)pvp->channels) { + error = CUSE_ERR_INVALID; + } else if (data.fir_filter.filter_data == NULL) { + free(pvp->tx_filter_data[data.fir_filter.channel]); + pvp->tx_filter_data[data.fir_filter.channel] = NULL; /* disable filter */ + } else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) { + error = CUSE_ERR_INVALID; + } else if (pvp->tx_filter_size != 0) { + size_t size = sizeof(pvp->tx_filter_data[0][0]) * pvp->tx_filter_size; + if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) { + pvp->tx_filter_data[data.fir_filter.channel] = malloc(size); + if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) + error = CUSE_ERR_NO_MEMORY; + else + memset(pvp->tx_filter_data[data.fir_filter.channel], 0, size); + } + if (pvp->tx_filter_data[data.fir_filter.channel] != NULL) { + error = cuse_copy_in(data.fir_filter.filter_data, + pvp->tx_filter_data[data.fir_filter.channel], size); + } + } + break; + + case VIRTUAL_OSS_GET_SAMPLE_RATE: + data.val = voss_dsp_sample_rate; + break; + + case VIRTUAL_OSS_GET_SYSTEM_INFO: + data.sys_info.tx_jitter_up = voss_jitter_up; + data.sys_info.tx_jitter_down = voss_jitter_down; + data.sys_info.sample_rate = voss_dsp_sample_rate; + data.sys_info.sample_bits = voss_dsp_bits; + data.sys_info.sample_channels = voss_mix_channels; + strlcpy(data.sys_info.rx_device_name, voss_dsp_rx_device, + sizeof(data.sys_info.rx_device_name)); + strlcpy(data.sys_info.tx_device_name, voss_dsp_tx_device, + sizeof(data.sys_info.tx_device_name)); + break; + + default: + error = CUSE_ERR_INVALID; + break; + } + atomic_unlock(); + + if (error == 0) { + if (cmd & IOC_OUT) + error = cuse_copy_out(&data, peer_data, len); + } + return (error); +} + +const struct cuse_methods vctl_methods = { + .cm_open = vctl_open, + .cm_close = vctl_close, + .cm_ioctl = vctl_ioctl, +}; diff --git a/usr.sbin/virtual_oss/virtual_oss/eq.c b/usr.sbin/virtual_oss/virtual_oss/eq.c new file mode 100644 index 000000000000..a02b48a9f039 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/eq.c @@ -0,0 +1,226 @@ +/*- + * Copyright (c) 2021 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include + +#include "int.h" + +void +vclient_tx_equalizer(struct virtual_client *pvc, + int64_t *src, size_t total) +{ + double *f_data; + size_t channels; + size_t f_size; + size_t x; + + f_size = pvc->profile->tx_filter_size; + if (f_size == 0 || total == 0) + return; + + channels = pvc->channels; + total /= channels; + + while (1) { + size_t delta; + size_t offset; + size_t y; + + offset = pvc->tx_filter_offset; + delta = f_size - offset; + + if (delta > total) + delta = total; + + for (x = 0; x != channels; x++) { + f_data = pvc->profile->tx_filter_data[x]; + if (f_data == NULL) + continue; + + for (y = 0; y != delta; y++) { + pvc->tx_filter_in[x][y + offset] = src[x + y * channels]; + src[x + y * channels] = pvc->tx_filter_out[x][y + offset]; + } + } + + pvc->tx_filter_offset += delta; + total -= delta; + src += delta * channels; + + /* check if there is enough data for a new transform */ + if (pvc->tx_filter_offset == f_size) { + for (x = 0; x != channels; x++) { + f_data = pvc->profile->tx_filter_data[x]; + if (f_data == NULL) + continue; + + /* shift down output */ + for (y = 0; y != f_size; y++) { + pvc->tx_filter_out[x][y] = pvc->tx_filter_out[x][y + f_size]; + pvc->tx_filter_out[x][y + f_size] = 0; + } + /* perform transform */ + voss_x3_multiply_double(pvc->tx_filter_in[x], + f_data, pvc->tx_filter_out[x], f_size); + } + pvc->tx_filter_offset = 0; + } + if (total == 0) + break; + } +} + +void +vclient_rx_equalizer(struct virtual_client *pvc, + int64_t *src, size_t total) +{ + double *f_data; + size_t channels; + size_t f_size; + size_t x; + + f_size = pvc->profile->rx_filter_size; + + if (f_size == 0 || total == 0) + return; + + channels = pvc->channels; + total /= channels; + + while (1) { + size_t delta; + size_t offset; + size_t y; + + offset = pvc->rx_filter_offset; + delta = f_size - offset; + + if (delta > total) + delta = total; + + for (x = 0; x != channels; x++) { + f_data = pvc->profile->rx_filter_data[x]; + if (f_data == NULL) + continue; + + for (y = 0; y != delta; y++) { + pvc->rx_filter_in[x][y + offset] = src[x + y * channels]; + src[x + y * channels] = pvc->rx_filter_out[x][y + offset]; + } + } + + pvc->rx_filter_offset += delta; + total -= delta; + src += delta * channels; + + /* check if there is enough data for a new transform */ + if (pvc->rx_filter_offset == f_size) { + for (x = 0; x != channels; x++) { + f_data = pvc->profile->rx_filter_data[x]; + if (f_data == NULL) + continue; + + /* shift output down */ + for (y = 0; y != f_size; y++) { + pvc->rx_filter_out[x][y] = pvc->rx_filter_out[x][y + f_size]; + pvc->rx_filter_out[x][y + f_size] = 0; + } + /* perform transform */ + voss_x3_multiply_double(pvc->rx_filter_in[x], + f_data, pvc->rx_filter_out[x], f_size); + } + pvc->rx_filter_offset = 0; + } + if (total == 0) + break; + } +} + +int +vclient_eq_alloc(struct virtual_client *pvc) +{ + uint8_t x; + + pvc->tx_filter_offset = 0; + pvc->rx_filter_offset = 0; + + for (x = 0; x != pvc->channels; x++) { + uint32_t size; + + size = pvc->profile->tx_filter_size; + if (size != 0) { + pvc->tx_filter_in[x] = + malloc(sizeof(pvc->tx_filter_in[x][0]) * size); + pvc->tx_filter_out[x] = + calloc(2 * size, sizeof(pvc->tx_filter_out[x][0])); + if (pvc->tx_filter_in[x] == NULL || + pvc->tx_filter_out[x] == NULL) + goto error; + } + size = pvc->profile->rx_filter_size; + if (size != 0) { + pvc->rx_filter_in[x] = + malloc(sizeof(pvc->rx_filter_in[x][0]) * size); + pvc->rx_filter_out[x] = + calloc(2 * size, sizeof(pvc->rx_filter_out[x][0])); + if (pvc->rx_filter_in[x] == NULL || + pvc->rx_filter_out[x] == NULL) + goto error; + } + } + return (0); + +error: + vclient_eq_free(pvc); + return (ENOMEM); +} + +void +vclient_eq_free(struct virtual_client *pvc) +{ + uint8_t x; + + pvc->tx_filter_offset = 0; + pvc->rx_filter_offset = 0; + + for (x = 0; x != VMAX_CHAN; x++) { + free(pvc->tx_filter_in[x]); + pvc->tx_filter_in[x] = NULL; + + free(pvc->rx_filter_in[x]); + pvc->rx_filter_in[x] = NULL; + + free(pvc->tx_filter_out[x]); + pvc->tx_filter_out[x] = NULL; + + free(pvc->rx_filter_out[x]); + pvc->rx_filter_out[x] = NULL; + } +} diff --git a/usr.sbin/virtual_oss/virtual_oss/format.c b/usr.sbin/virtual_oss/virtual_oss/format.c new file mode 100644 index 000000000000..d32d0c726510 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/format.c @@ -0,0 +1,429 @@ +/*- + * Copyright (c) 2012-2020 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include + +#include "int.h" + +void +format_import(uint32_t fmt, const uint8_t *src, uint32_t len, + int64_t *dst) +{ + const uint8_t *end = src + len; + int64_t val; + + if (fmt & AFMT_16BIT) { + while (src != end) { + if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) + val = src[0] | (src[1] << 8); + else + val = src[1] | (src[0] << 8); + + src += 2; + + if (fmt & (AFMT_U16_LE | AFMT_U16_BE)) + val = val ^ 0x8000; + + val <<= (64 - 16); + val >>= (64 - 16); + + *dst++ = val; + } + + } else if (fmt & AFMT_24BIT) { + while (src < end) { + if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) + val = src[0] | (src[1] << 8) | (src[2] << 16); + else + val = src[2] | (src[1] << 8) | (src[0] << 16); + + src += 3; + + if (fmt & (AFMT_U24_LE | AFMT_U24_BE)) + val = val ^ 0x800000; + + val <<= (64 - 24); + val >>= (64 - 24); + + *dst++ = val; + } + } else if (fmt & AFMT_32BIT) { + while (src < end) { + int64_t e, m, s; + + if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) + val = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); + else + val = src[3] | (src[2] << 8) | (src[1] << 16) | (src[0] << 24); + + src += 4; + + if (fmt & (AFMT_U32_LE | AFMT_U32_BE)) + val = val ^ 0x80000000LL; + + if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) { + e = (val >> 23) & 0xff; + /* NaN, +/- Inf or too small */ + if (e == 0xff || e < 96) { + val = 0; + goto skip; + } + s = val & 0x80000000U; + if (e > 126) { + val = s == 0 ? format_max(fmt) : + -0x80000000LL; + goto skip; + } + m = 0x800000 | (val & 0x7fffff); + e += 8 - 127; + if (e < 0) + m >>= -e; + else + m <<= e; + val = s == 0 ? m : -m; + } +skip: + val <<= (64 - 32); + val >>= (64 - 32); + + *dst++ = val; + } + + } else if (fmt & AFMT_8BIT) { + while (src < end) { + val = src[0]; + + src += 1; + + if (fmt & AFMT_U8) + val = val ^ 0x80; + + val <<= (64 - 8); + val >>= (64 - 8); + + *dst++ = val; + } + } +} + +void +format_export(uint32_t fmt, const int64_t *src, uint8_t *dst, uint32_t len) +{ + const uint8_t *end = dst + len; + int64_t val; + + if (fmt & AFMT_16BIT) { + while (dst != end) { + + val = *src++; + + if (val > 0x7FFF) + val = 0x7FFF; + else if (val < -0x7FFF) + val = -0x7FFF; + + if (fmt & (AFMT_U16_LE | AFMT_U16_BE)) + val = val ^ 0x8000; + + if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) { + dst[0] = val; + dst[1] = val >> 8; + } else { + dst[1] = val; + dst[0] = val >> 8; + } + + dst += 2; + } + + } else if (fmt & AFMT_24BIT) { + while (dst != end) { + + val = *src++; + + if (val > 0x7FFFFF) + val = 0x7FFFFF; + else if (val < -0x7FFFFF) + val = -0x7FFFFF; + + if (fmt & (AFMT_U24_LE | AFMT_U24_BE)) + val = val ^ 0x800000; + + if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) { + dst[0] = val; + dst[1] = val >> 8; + dst[2] = val >> 16; + } else { + dst[2] = val; + dst[1] = val >> 8; + dst[0] = val >> 16; + } + + dst += 3; + } + } else if (fmt & AFMT_32BIT) { + while (dst != end) { + int64_t r, e; + + val = *src++; + + if (val > 0x7FFFFFFFLL) + val = 0x7FFFFFFFLL; + else if (val < -0x7FFFFFFFLL) + val = -0x7FFFFFFFLL; + + if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) { + if (val == 0) + r = 0; + else if (val == format_max(fmt)) + r = 0x3f800000; + else if (val == -0x80000000LL) + r = 0x80000000U | 0x3f800000; + else { + r = 0; + if (val < 0) { + r |= 0x80000000U; + val = -val; + } + e = 127 - 8; + while ((val & 0x7f000000) != 0) { + val >>= 1; + e++; + } + while ((val & 0x7f800000) == 0) { + val <<= 1; + e--; + } + r |= (e & 0xff) << 23; + r |= val & 0x7fffff; + } + val = r; + } + + if (fmt & (AFMT_U32_LE | AFMT_U32_BE)) + val = val ^ 0x80000000LL; + + if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) { + dst[0] = val; + dst[1] = val >> 8; + dst[2] = val >> 16; + dst[3] = val >> 24; + } else { + dst[3] = val; + dst[2] = val >> 8; + dst[1] = val >> 16; + dst[0] = val >> 24; + } + + dst += 4; + } + + } else if (fmt & AFMT_8BIT) { + while (dst != end) { + + val = *src++; + + if (val > 0x7F) + val = 0x7F; + else if (val < -0x7F) + val = -0x7F; + + if (fmt & (AFMT_U8)) + val = val ^ 0x80; + + dst[0] = val; + + dst += 1; + } + } +} + +int64_t +format_max(uint32_t fmt) +{ + if (fmt & AFMT_16BIT) + return (0x7FFF); + else if (fmt & AFMT_24BIT) + return (0x7FFFFF); + else if (fmt & AFMT_32BIT) + return (0x7FFFFFFF); + else if (fmt & AFMT_8BIT) + return (0x7F); + return (0); +} + +void +format_maximum(const int64_t *src, int64_t *dst, uint32_t ch, + uint32_t samples, int8_t shift) +{ + const int64_t *end = src + (samples * ch); + int64_t max[ch]; + int64_t temp; + uint32_t x; + + memset(max, 0, sizeof(max)); + + while (src != end) { + for (x = 0; x != ch; x++) { + temp = *src++; + if (temp < 0) + temp = -temp; + if (temp > max[x]) + max[x] = temp; + } + } + + for (x = 0; x != ch; x++) { + if (shift < 0) + max[x] >>= -shift; + else + max[x] <<= shift; + if (dst[x] < max[x]) + dst[x] = max[x]; + } +} + +void +format_remix(int64_t *buffer_data, uint32_t in_chans, + uint32_t out_chans, uint32_t samples) +{ + uint32_t x; + + if (out_chans > in_chans) { + uint32_t dst = out_chans * (samples - 1); + uint32_t src = in_chans * (samples - 1); + uint32_t fill = out_chans - in_chans; + + for (x = 0; x != samples; x++) { + memset(buffer_data + dst + in_chans, 0, 8 * fill); + if (src != dst) { + memcpy(buffer_data + dst, + buffer_data + src, + in_chans * 8); + } + dst -= out_chans; + src -= in_chans; + } + } else if (out_chans < in_chans) { + uint32_t dst = 0; + uint32_t src = 0; + + for (x = 0; x != samples; x++) { + if (src != dst) { + memcpy(buffer_data + dst, + buffer_data + src, + out_chans * 8); + } + dst += out_chans; + src += in_chans; + } + } +} + +void +format_silence(uint32_t fmt, uint8_t *dst, uint32_t len) +{ + const uint8_t *end = dst + len; + + if (fmt & AFMT_16BIT) { + uint16_t val; + + if (fmt & (AFMT_U16_LE | AFMT_U16_BE)) + val = 1U << 15; + else + val = 0; + + while (dst != end) { + if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) { + dst[0] = val; + dst[1] = val >> 8; + } else { + dst[1] = val; + dst[0] = val >> 8; + } + dst += 2; + } + + } else if (fmt & AFMT_24BIT) { + uint32_t val; + + if (fmt & (AFMT_U24_LE | AFMT_U24_BE)) + val = 1U << 23; + else + val = 0; + + while (dst != end) { + if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) { + dst[0] = val; + dst[1] = val >> 8; + dst[2] = val >> 16; + } else { + dst[2] = val; + dst[1] = val >> 8; + dst[0] = val >> 16; + } + dst += 3; + } + } else if (fmt & AFMT_32BIT) { + uint32_t val; + + if (fmt & (AFMT_U32_LE | AFMT_U32_BE)) + val = 1U << 31; + else + val = 0; + + while (dst != end) { + if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) { + dst[0] = val; + dst[1] = val >> 8; + dst[2] = val >> 16; + dst[3] = val >> 24; + } else { + dst[3] = val; + dst[2] = val >> 8; + dst[1] = val >> 16; + dst[0] = val >> 24; + } + dst += 4; + } + + } else if (fmt & AFMT_8BIT) { + uint8_t val; + + if (fmt & AFMT_U8) + val = 1U << 7; + else + val = 0; + + while (dst != end) { + dst[0] = val; + dst += 1; + } + } +} diff --git a/usr.sbin/virtual_oss/virtual_oss/httpd.c b/usr.sbin/virtual_oss/virtual_oss/httpd.c new file mode 100644 index 000000000000..c05d5839e96b --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/httpd.c @@ -0,0 +1,844 @@ +/*- + * Copyright (c) 2020 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include "int.h" + +#define VOSS_HTTPD_BIND_MAX 8 +#define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */ + +struct http_state { + int fd; + uint64_t ts; +}; + +struct rtp_raw_packet { + struct { + uint32_t padding; + uint8_t dhost[6]; + uint8_t shost[6]; + uint16_t ether_type; + } __packed eth; + struct { + uint8_t hl_ver; + uint8_t tos; + uint16_t len; + uint16_t ident; + uint16_t offset; + uint8_t ttl; + uint8_t protocol; + uint16_t chksum; + union { + uint32_t sourceip; + uint16_t source16[2]; + }; + union { + uint32_t destip; + uint16_t dest16[2]; + }; + } __packed ip; + struct { + uint16_t srcport; + uint16_t dstport; + uint16_t len; + uint16_t chksum; + } __packed udp; + union { + uint8_t header8[12]; + uint16_t header16[6]; + uint32_t header32[3]; + } __packed rtp; + +} __packed; + +static const char * +voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd) +{ + const char *perr = NULL; + struct vlanreq vr = {}; + struct ifreq ifr = {}; + int fd; + + fd = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (fd < 0) { + perr = "Cannot open raw RTP socket"; + goto done; + } + + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_data = (void *)&vr; + + if (ioctl(fd, SIOCGETVLAN, &ifr) == 0) + pvc->profile->http.rtp_vlanid = vr.vlr_tag; + else + pvc->profile->http.rtp_vlanid = 0; + + close(fd); + + ifr.ifr_data = NULL; + + *pfd = fd = open("/dev/bpf", O_RDWR); + if (fd < 0) { + perr = "Cannot open BPF device"; + goto done; + } + + if (ioctl(fd, BIOCSETIF, &ifr) != 0) { + perr = "Cannot bind BPF device to network interface"; + goto done; + } +done: + if (perr != NULL && fd > -1) + close(fd); + return (perr); +} + +static uint16_t +voss_ipv4_csum(const uint16_t *ptr, size_t count) +{ + uint32_t sum = 0; + + while (count--) + sum += *ptr++; + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + return (~sum); +} + +static uint16_t +voss_udp_csum(uint32_t sum, const uint16_t *hdr, size_t count, + const uint16_t *ptr, size_t length) +{ + while (count--) + sum += *hdr++; + + while (length > 1) { + sum += *ptr++; + length -= 2; + } + + if (length & 1) + sum += *__DECONST(uint8_t *, ptr); + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + return (~sum); +} + +static void +voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts) +{ + struct rtp_raw_packet pkt = {}; + struct iovec iov[2]; + size_t total_ip; + uint16_t port = atoi(pvc->profile->http.rtp_port); + size_t x; + + /* NOTE: BPF filter will insert VLAN header for us */ + memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost)); + memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost)); + pkt.eth.ether_type = htobe16(0x0800); + total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len; + + iov[0].iov_base = pkt.eth.dhost; + iov[0].iov_len = 14 + total_ip - len; + + iov[1].iov_base = alloca(len); + iov[1].iov_len = len; + + /* byte swap data - WAV files are 16-bit little endian */ + for (x = 0; x != (len / 2); x++) + ((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]); + + pkt.ip.hl_ver = 0x45; + pkt.ip.len = htobe16(total_ip); + pkt.ip.ttl = 8; + pkt.ip.protocol = 17; /* UDP */ + pkt.ip.sourceip = 0x01010101U; + pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0)); + pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2); + + pkt.udp.srcport = htobe16(port); + pkt.udp.dstport = htobe16(port); + pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip)); + + pkt.rtp.header8[0] = (2 << 6); + pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80; + + pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum); + pkt.rtp.header32[1] = htobe32(ts); + pkt.rtp.header32[2] = htobe32(0); + + pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] + + pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len, + (void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2, + iov[1].iov_base, iov[1].iov_len); + + pvc->profile->http.rtp_seqnum++; + pvc->profile->http.rtp_ts += len / (2 * pvc->channels); + + if (writev(fd, iov, 2) < 0) + ; +} + +static void +voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts) +{ + const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc); + const uint32_t max = 1420 - (1420 % mod); + + while (len >= max) { + voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts); + len -= max; + ptr = (uint8_t *)ptr + max; + } + + if (len != 0) + voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts); +} + +static size_t +voss_httpd_usage(vclient_t *pvc) +{ + size_t usage = 0; + size_t x; + + for (x = 0; x < pvc->profile->http.nstate; x++) + usage += (pvc->profile->http.state[x].fd != -1); + return (usage); +} + +static char * +voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen) +{ + char buffer[2]; + size_t size = 0; + + if (fread(buffer, 1, 2, io) != 2) + return (NULL); + + while (1) { + if (buffer[0] == '\r' && buffer[1] == '\n') + break; + if (size == (linelen - 1)) + return (NULL); + linebuffer[size++] = buffer[0]; + buffer[0] = buffer[1]; + if (fread(buffer + 1, 1, 1, io) != 1) + return (NULL); + } + linebuffer[size++] = 0; + + return (linebuffer); +} + +static int +voss_http_generate_wav_header(vclient_t *pvc, FILE *io, + uintmax_t r_start, uintmax_t r_end, bool is_partial) +{ + uint8_t buffer[256]; + uint8_t *ptr; + uintmax_t dummy_len; + uintmax_t delta; + size_t mod; + size_t len; + size_t buflen; + + ptr = buffer; + mod = pvc->channels * vclient_sample_bytes(pvc); + + if (mod == 0 || sizeof(buffer) < (44 + mod - 1)) + return (-1); + + /* align to next sample */ + len = 44 + mod - 1; + len -= len % mod; + + buflen = len; + + /* clear block */ + memset(ptr, 0, len); + + /* fill out data header */ + ptr[len - 8] = 'd'; + ptr[len - 7] = 'a'; + ptr[len - 6] = 't'; + ptr[len - 5] = 'a'; + + /* magic for unspecified length */ + ptr[len - 4] = 0x00; + ptr[len - 3] = 0xF0; + ptr[len - 2] = 0xFF; + ptr[len - 1] = 0x7F; + + /* fill out header */ + *ptr++ = 'R'; + *ptr++ = 'I'; + *ptr++ = 'F'; + *ptr++ = 'F'; + + /* total chunk size - unknown */ + + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + + *ptr++ = 'W'; + *ptr++ = 'A'; + *ptr++ = 'V'; + *ptr++ = 'E'; + *ptr++ = 'f'; + *ptr++ = 'm'; + *ptr++ = 't'; + *ptr++ = ' '; + + /* make sure header fits in PCM block */ + len -= 28; + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* audioformat = PCM */ + + *ptr++ = 0x01; + *ptr++ = 0x00; + + /* number of channels */ + + len = pvc->channels; + + *ptr++ = len; + *ptr++ = len >> 8; + + /* sample rate */ + + len = pvc->sample_rate; + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* byte rate */ + + len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* block align */ + + len = pvc->channels * vclient_sample_bytes(pvc); + + *ptr++ = len; + *ptr++ = len >> 8; + + /* bits per sample */ + + len = vclient_sample_bytes(pvc) * 8; + + *ptr++ = len; + *ptr++ = len >> 8; + + /* check if alignment is correct */ + if (r_start >= buflen && (r_start % mod) != 0) + return (2); + + dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); + dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME; + + /* fixup end */ + if (r_end >= dummy_len) + r_end = dummy_len - 1; + + delta = r_end - r_start + 1; + + if (is_partial) { + fprintf(io, "HTTP/1.1 206 Partial Content\r\n" + "Content-Type: audio/wav\r\n" + "Server: virtual_oss/1.0\r\n" + "Cache-Control: no-cache, no-store\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "Connection: Close\r\n" + "Content-Range: bytes %ju-%ju/%ju\r\n" + "Content-Length: %ju\r\n" + "\r\n", r_start, r_end, dummy_len, delta); + } else { + fprintf(io, "HTTP/1.0 200 OK\r\n" + "Content-Type: audio/wav\r\n" + "Server: virtual_oss/1.0\r\n" + "Cache-Control: no-cache, no-store\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "Connection: Close\r\n" + "Content-Length: %ju\r\n" + "\r\n", dummy_len); + } + + /* check if we should insert a header */ + if (r_start < buflen) { + buflen -= r_start; + if (buflen > delta) + buflen = delta; + /* send data */ + if (fwrite(buffer + r_start, buflen, 1, io) != 1) + return (-1); + /* check if all data was read */ + if (buflen == delta) + return (1); + } + return (0); +} + +static void +voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa) +{ + char linebuffer[2048]; + uintmax_t r_start = 0; + uintmax_t r_end = -1ULL; + bool is_partial = false; + char *line; + FILE *io; + size_t x; + int page; + + io = fdopen(fd, "r+"); + if (io == NULL) + goto done; + + page = -1; + + /* dump HTTP request header */ + while (1) { + line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer)); + if (line == NULL) + goto done; + if (line[0] == 0) + break; + if (page < 0 && (strstr(line, "GET / ") == line || + strstr(line, "GET /index.html") == line)) { + page = 0; + } else if (page < 0 && strstr(line, "GET /stream.wav") == line) { + page = 1; + } else if (page < 0 && strstr(line, "GET /stream.m3u") == line) { + page = 2; + } else if (strstr(line, "Range: bytes=") == line && + sscanf(line, "Range: bytes=%zu-%zu", &r_start, &r_end) >= 1) { + is_partial = true; + } + } + + switch (page) { + case 0: + x = voss_httpd_usage(pvc); + + fprintf(io, "HTTP/1.0 200 OK\r\n" + "Content-Type: text/html\r\n" + "Server: virtual_oss/1.0\r\n" + "Cache-Control: no-cache, no-store\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "\r\n" + "Welcome to live streaming" + "" + "" + "" + "" + "" + "

Live HD stream

" + "
" + "
" + "

Alternative 1 (recommended)

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

Alternative 2 (on your own)

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

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

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

There are %zu free slots (%zu active)

", pvc->profile->http.nstate - x, x); + + fprintf(io, ""); + break; + case 1: + for (x = 0; x < pvc->profile->http.nstate; x++) { + if (pvc->profile->http.state[x].fd >= 0) + continue; + switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) { + static const int enable = 1; + + case 0: + fflush(io); + fdclose(io, NULL); + if (ioctl(fd, FIONBIO, &enable) != 0) { + close(fd); + return; + } + pvc->profile->http.state[x].ts = + virtual_oss_timestamp() - 1000000000ULL; + pvc->profile->http.state[x].fd = fd; + return; + case 1: + fclose(io); + return; + case 2: + fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n" + "Server: virtual_oss/1.0\r\n" + "\r\n"); + goto done; + default: + goto done; + } + } + fprintf(io, "HTTP/1.0 503 Out of Resources\r\n" + "Server: virtual_oss/1.0\r\n" + "\r\n"); + break; + case 2: + fprintf(io, "HTTP/1.0 200 OK\r\n" + "Content-Type: audio/mpegurl\r\n" + "Server: virtual_oss/1.0\r\n" + "Cache-Control: no-cache, no-store\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "\r\n"); + if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) { + fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port); + } else { + fprintf(io, "http://%s:%s/stream.wav\r\n", + pvc->profile->http.host, pvc->profile->http.port); + } + break; + default: + fprintf(io, "HTTP/1.0 404 Not Found\r\n" + "Content-Type: text/html\r\n" + "Server: virtual_oss/1.0\r\n" + "\r\n" + "virtual_oss" + "" + "

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


" + "" + ""); + break; + } +done: + if (io != NULL) + fclose(io); + else + close(fd); +} + +static int +voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port, + struct pollfd *pfd, int num_sock, int buffer) +{ + static const struct timeval timeout = {.tv_sec = 1}; + struct addrinfo hints = {}; + struct addrinfo *res; + struct addrinfo *res0; + int error; + int flag; + int s; + int ns = 0; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + if ((error = getaddrinfo(host, port, &hints, &res))) + return (-1); + + res0 = res; + + do { + if ((s = socket(res0->ai_family, res0->ai_socktype, + res0->ai_protocol)) < 0) + continue; + + flag = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag)); + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer)); + setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer)); + setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout)); + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout)); + + if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) { + if (listen(s, pvc->profile->http.nstate) == 0) { + if (ns < num_sock) { + pfd[ns++].fd = s; + continue; + } + close(s); + break; + } + } + close(s); + } while ((res0 = res0->ai_next) != NULL); + + freeaddrinfo(res); + + return (ns); +} + +static size_t +voss_httpd_buflimit(vclient_t *pvc) +{ + /* don't buffer more than 250ms */ + return ((pvc->sample_rate / 4) * + pvc->channels * vclient_sample_bytes(pvc)); +}; + +static void +voss_httpd_server(vclient_t *pvc) +{ + const size_t bufferlimit = voss_httpd_buflimit(pvc); + const char *host = pvc->profile->http.host; + const char *port = pvc->profile->http.port; + struct sockaddr sa = {}; + struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {}; + int nfd; + + nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit); + if (nfd < 1) { + errx(EX_SOFTWARE, "Could not bind to " + "'%s' and '%s'", host, port); + } + + while (1) { + struct sockaddr_in si; + int ns = nfd; + int c; + int f; + + for (c = 0; c != ns; c++) { + fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | + POLLERR | POLLHUP | POLLNVAL); + fds[c].revents = 0; + } + if (poll(fds, ns, -1) < 0) + errx(EX_SOFTWARE, "Polling failed"); + + for (c = 0; c != ns; c++) { + socklen_t socklen = sizeof(sa); + + if (fds[c].revents == 0) + continue; + f = accept(fds[c].fd, &sa, &socklen); + if (f < 0) + continue; + memcpy(&si, &sa, sizeof(sa)); + voss_httpd_handle_connection(pvc, f, &si); + } + } +} + +static void +voss_httpd_streamer(vclient_t *pvc) +{ + const size_t bufferlimit = voss_httpd_buflimit(pvc); + uint8_t *ptr; + size_t len; + uint64_t ts; + size_t x; + + atomic_lock(); + while (1) { + if (vclient_export_read_locked(pvc) != 0) { + atomic_wait(); + continue; + } + vring_get_read(&pvc->rx_ring[1], &ptr, &len); + if (len == 0) { + /* try to avoid ring wraps */ + vring_reset(&pvc->rx_ring[1]); + atomic_wait(); + continue; + } + atomic_unlock(); + + ts = virtual_oss_timestamp(); + + /* check if we should send RTP data, if any */ + if (pvc->profile->http.rtp_fd > -1) { + voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd, + ptr, len, pvc->profile->http.rtp_ts); + } + + /* send HTTP data, if any */ + for (x = 0; x < pvc->profile->http.nstate; x++) { + int fd = pvc->profile->http.state[x].fd; + uint64_t delta = ts - pvc->profile->http.state[x].ts; + uint8_t buf[1]; + int write_len; + + if (fd < 0) { + /* do nothing */ + } else if (delta >= (8ULL * 1000000000ULL)) { + /* no data for 8 seconds - terminate */ + pvc->profile->http.state[x].fd = -1; + close(fd); + } else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) { + pvc->profile->http.state[x].fd = -1; + close(fd); + } else if (ioctl(fd, FIONWRITE, &write_len) < 0) { + pvc->profile->http.state[x].fd = -1; + close(fd); + } else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) { + /* do nothing */ + } else if (write(fd, ptr, len) != (ssize_t)len) { + pvc->profile->http.state[x].fd = -1; + close(fd); + } else { + /* update timestamp */ + pvc->profile->http.state[x].ts = ts; + } + } + + atomic_lock(); + vring_inc_read(&pvc->rx_ring[1], len); + } +} + +const char * +voss_httpd_start(vprofile_t *pvp) +{ + vclient_t *pvc; + pthread_t td; + int error; + size_t x; + + if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0) + return (NULL); + + pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate); + if (pvp->http.state == NULL) + return ("Could not allocate HTTP states"); + + for (x = 0; x != pvp->http.nstate; x++) { + pvp->http.state[x].fd = -1; + pvp->http.state[x].ts = 0; + } + + pvc = vclient_alloc(); + if (pvc == NULL) + return ("Could not allocate client for HTTP server"); + + pvc->profile = pvp; + + if (pvp->http.rtp_ifname != NULL) { + const char *perr; + + if (pvc->channels > 2) + return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth"); + + /* bind to UDP port */ + perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname, + &pvp->http.rtp_fd); + if (perr != NULL) + return (perr); + + /* setup buffers */ + error = vclient_setup_buffers(pvc, 0, 0, + pvp->channels, AFMT_S16_LE, 44100); + } else { + pvp->http.rtp_fd = -1; + + /* setup buffers */ + error = vclient_setup_buffers(pvc, 0, 0, pvp->channels, + vclient_get_default_fmt(pvp, VTYPE_WAV_HDR), + voss_dsp_sample_rate); + } + + if (error != 0) { + vclient_free(pvc); + return ("Could not allocate buffers for HTTP server"); + } + + /* trigger enabled */ + pvc->rx_enabled = 1; + + pvc->type = VTYPE_OSS_DAT; + + atomic_lock(); + TAILQ_INSERT_TAIL(&pvp->head, pvc, entry); + atomic_unlock(); + + if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc)) + return ("Could not create HTTP daemon thread"); + if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc)) + return ("Could not create HTTP streamer thread"); + + return (NULL); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/int.h b/usr.sbin/virtual_oss/virtual_oss/int.h new file mode 100644 index 000000000000..4fea69f1a11f --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/int.h @@ -0,0 +1,327 @@ +/*- + * Copyright (c) 2012-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _VIRTUAL_INT_H_ +#define _VIRTUAL_INT_H_ + +#include +#include + +#include +#include + +extern pthread_mutex_t atomic_mtx; +extern pthread_cond_t atomic_cv; + +#define atomic_lock() pthread_mutex_lock(&atomic_mtx) +#define atomic_unlock() pthread_mutex_unlock(&atomic_mtx) +#define atomic_wait() pthread_cond_wait(&atomic_cv, &atomic_mtx) +#define atomic_wakeup() do { \ + pthread_cond_broadcast(&atomic_cv); \ + cuse_poll_wakeup(); \ +} while (0) + +#define AFMT_32BIT \ + (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE | \ + AFMT_F32_LE | AFMT_F32_BE) +#define AFMT_24BIT \ + (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) +#define AFMT_16BIT \ + (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) +#define AFMT_8BIT \ + (AFMT_U8 | AFMT_S8) + +#define VMAX_CHAN 64 +#define VMAX_STRING 64 /* characters */ + +#define VTYPE_OSS_DAT 0 +#define VTYPE_WAV_HDR 1 +#define VTYPE_WAV_DAT 2 + +#define VPREFERRED_SNE_AFMT \ + (AFMT_S8 | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE | AFMT_F32_NE) +#define VPREFERRED_UNE_AFMT \ + (AFMT_U8 | AFMT_U16_NE | AFMT_U24_NE | AFMT_U32_NE) +#define VPREFERRED_SLE_AFMT \ + (AFMT_S8 | AFMT_S16_LE | AFMT_S24_LE | AFMT_S32_LE | AFMT_F32_LE) +#define VPREFERRED_SBE_AFMT \ + (AFMT_S8 | AFMT_S16_BE | AFMT_S24_BE | AFMT_S32_BE | AFMT_F32_BE) +#define VPREFERRED_ULE_AFMT \ + (AFMT_U8 | AFMT_U16_LE | AFMT_U24_LE | AFMT_U32_LE) +#define VPREFERRED_UBE_AFMT \ + (AFMT_U8 | AFMT_U16_BE | AFMT_U24_BE | AFMT_U32_BE) + +#define VSUPPORTED_AFMT \ + (AFMT_S16_BE | AFMT_S16_LE | AFMT_U16_BE | AFMT_U16_LE | \ + AFMT_S24_BE | AFMT_S24_LE | AFMT_U24_BE | AFMT_U24_LE | \ + AFMT_S32_BE | AFMT_S32_LE | AFMT_U32_BE | AFMT_U32_LE | \ + AFMT_F32_BE | AFMT_F32_LE | \ + AFMT_U8 | AFMT_S8) + +#define VVOLUME_UNIT_SHIFT 7 + +struct virtual_profile; + +typedef TAILQ_ENTRY(virtual_profile) vprofile_entry_t; +typedef TAILQ_HEAD(, virtual_profile) vprofile_head_t; +typedef struct virtual_profile vprofile_t; + +struct virtual_client; + +typedef TAILQ_ENTRY(virtual_client) vclient_entry_t; +typedef TAILQ_HEAD(, virtual_client) vclient_head_t; +typedef struct virtual_client vclient_t; + +struct virtual_monitor; +typedef TAILQ_ENTRY(virtual_monitor) vmonitor_entry_t; +typedef TAILQ_HEAD(, virtual_monitor) vmonitor_head_t; +typedef struct virtual_monitor vmonitor_t; + +struct virtual_resample; +typedef struct virtual_resample vresample_t; + +struct cuse_methods; + +struct virtual_compressor { + uint8_t enabled; /* 0..1 */ + uint8_t knee; /* 0..255 */ + uint8_t attack; /* 0..62 */ + uint8_t decay; /* 0..62 */ +}; + +struct virtual_profile { + vprofile_entry_t entry; + vclient_head_t head; + char oss_name[VMAX_STRING]; + char wav_name[VMAX_STRING]; + uint32_t rx_filter_size; + uint32_t tx_filter_size; + double *rx_filter_data[VMAX_CHAN]; + double *tx_filter_data[VMAX_CHAN]; + int64_t rx_peak_value[VMAX_CHAN]; + int64_t tx_peak_value[VMAX_CHAN]; + int8_t rx_shift[VMAX_CHAN]; + int8_t tx_shift[VMAX_CHAN]; + uint8_t rx_src[VMAX_CHAN]; + uint8_t tx_dst[VMAX_CHAN]; + uint8_t rx_mute[VMAX_CHAN]; + uint8_t tx_mute[VMAX_CHAN]; + uint8_t rx_pol[VMAX_CHAN]; + uint8_t tx_pol[VMAX_CHAN]; + uint8_t bits; + uint8_t channels; + struct virtual_compressor rx_compressor_param; + double rx_compressor_gain[VMAX_CHAN]; + uint8_t synchronized; + uint32_t rec_delay; + int fd_sta; + struct { + const char * host; + const char * port; + const char * rtp_ifname; + const char * rtp_port; + volatile struct http_state * state; + size_t nstate; + int rtp_fd; + int rtp_vlanid; + uint32_t rtp_ts; + uint16_t rtp_seqnum; + } http; +}; + +struct virtual_ring { + uint8_t *buf_start; + uint32_t pos_read; + uint32_t total_size; + uint32_t len_write; +}; + +struct virtual_resample { + SRC_DATA data; + SRC_STATE *state; + float *data_in; + float *data_out; +}; + +struct virtual_client { + vclient_entry_t entry; + uint32_t tx_filter_offset; + uint32_t rx_filter_offset; + int64_t *tx_filter_in[VMAX_CHAN]; + int64_t *rx_filter_in[VMAX_CHAN]; + double *tx_filter_out[VMAX_CHAN]; + double *rx_filter_out[VMAX_CHAN]; + struct virtual_ring rx_ring[2]; + struct virtual_ring tx_ring[2]; + vresample_t rx_resample; + vresample_t tx_resample; + struct virtual_profile *profile; + uint64_t rx_samples; + uint64_t rx_timestamp; + uint64_t tx_samples; + uint64_t tx_timestamp; + uint32_t buffer_frags; + uint32_t buffer_size; + uint32_t low_water; + uint32_t rec_delay; + uint32_t rx_noise_rem; + uint32_t tx_noise_rem; + int rx_busy; + int tx_busy; + int channels; + int format; + int rx_enabled; + int tx_enabled; + int rx_volume; + int tx_volume; + int type; /* VTYPE_XXX */ + int sample_rate; + uint32_t buffer_size_set:1; + uint32_t buffer_frags_set:1; + uint32_t sync_busy:1; + uint32_t sync_wakeup:1; + int padding:28; +}; + +struct virtual_monitor { + vmonitor_entry_t entry; + int64_t peak_value; + uint8_t src_chan; + uint8_t dst_chan; + uint8_t pol; + uint8_t mute; + int8_t shift; +}; + +extern vprofile_head_t virtual_profile_client_head; +extern vprofile_head_t virtual_profile_loopback_head; + +extern vmonitor_head_t virtual_monitor_input; +extern vmonitor_head_t virtual_monitor_local; +extern vmonitor_head_t virtual_monitor_output; + +extern const struct cuse_methods vctl_methods; + +extern struct virtual_compressor voss_output_compressor_param; +extern double voss_output_compressor_gain[VMAX_CHAN]; +extern int64_t voss_output_peak[VMAX_CHAN]; +extern int64_t voss_input_peak[VMAX_CHAN]; +extern uint32_t voss_jitter_up; +extern uint32_t voss_jitter_down; +extern uint32_t voss_max_channels; +extern uint32_t voss_mix_channels; +extern uint32_t voss_dsp_samples; +extern uint32_t voss_dsp_max_channels; +extern uint32_t voss_dsp_sample_rate; +extern uint32_t voss_dsp_bits; +extern uint8_t voss_libsamplerate_enable; +extern uint8_t voss_libsamplerate_quality; +extern int voss_is_recording; +extern int voss_has_synchronization; +extern char voss_dsp_rx_device[VMAX_STRING]; +extern char voss_dsp_tx_device[VMAX_STRING]; +extern char voss_ctl_device[VMAX_STRING]; +extern volatile sig_atomic_t voss_exit; + +extern int vring_alloc(struct virtual_ring *, size_t); +extern void vring_free(struct virtual_ring *); +extern void vring_reset(struct virtual_ring *); +extern void vring_get_read(struct virtual_ring *, uint8_t **, size_t *); +extern void vring_get_write(struct virtual_ring *, uint8_t **, size_t *); +extern void vring_inc_read(struct virtual_ring *, size_t); +extern void vring_inc_write(struct virtual_ring *, size_t); +extern size_t vring_total_read_len(struct virtual_ring *); +extern size_t vring_total_write_len(struct virtual_ring *); +extern size_t vring_write_linear(struct virtual_ring *, const uint8_t *, size_t); +extern size_t vring_read_linear(struct virtual_ring *, uint8_t *, size_t); +extern size_t vring_write_zero(struct virtual_ring *, size_t); + +extern vclient_t *vclient_alloc(void); +extern void vclient_free(vclient_t *); + +extern int vclient_get_default_fmt(vprofile_t *, int type); +extern int vclient_setup_buffers(vclient_t *, int size, int frags, + int channels, int format, int sample_rate); +extern int vclient_export_read_locked(vclient_t *); +extern void vclient_import_write_locked(vclient_t *); + +extern uint32_t vclient_sample_bytes(vclient_t *); +extern uint32_t vclient_bufsize_internal(vclient_t *); +extern uint32_t vclient_bufsize_scaled(vclient_t *); + +extern int64_t vclient_noise(uint32_t *, int64_t, int8_t); + +extern vmonitor_t *vmonitor_alloc(int *, vmonitor_head_t *); + +extern uint32_t format_best(uint32_t); +extern void format_import(uint32_t, const uint8_t *, uint32_t, int64_t *); +extern void format_export(uint32_t, const int64_t *, uint8_t *, uint32_t); +extern int64_t format_max(uint32_t); +extern void format_maximum(const int64_t *, int64_t *, uint32_t, uint32_t, int8_t); +extern void format_remix(int64_t *, uint32_t, uint32_t, uint32_t); +extern void format_silence(uint32_t, uint8_t *, uint32_t); + +extern void *virtual_oss_process(void *); + +/* Audio Delay prototypes */ +extern uint32_t voss_ad_last_delay; +extern uint32_t voss_dsp_rx_refresh; +extern uint32_t voss_dsp_tx_refresh; +extern uint8_t voss_ad_enabled; +extern uint8_t voss_ad_output_signal; +extern uint8_t voss_ad_input_channel; +extern uint8_t voss_ad_output_channel; +extern void voss_ad_reset(void); +extern void voss_ad_init(uint32_t); +extern double voss_ad_getput_sample(double); + +/* Add audio options prototype */ +extern void voss_add_options(char *); + +/* Get current timestamp */ +extern uint64_t virtual_oss_delay_ns(void); +extern void virtual_oss_wait(void); +extern uint64_t virtual_oss_timestamp(void); + +/* Fast array multiplication */ +extern void voss_x3_multiply_double(const int64_t *, const double *, double *, const size_t); + +/* Equalizer support */ +extern void vclient_tx_equalizer(struct virtual_client *, int64_t *, size_t); +extern void vclient_rx_equalizer(struct virtual_client *, int64_t *, size_t); +extern int vclient_eq_alloc(struct virtual_client *); +extern void vclient_eq_free(struct virtual_client *); + +/* Internal utilities */ +extern int bt_speaker_main(int argc, char **argv); + +/* Internal compressor */ +extern void voss_compressor(int64_t *, double *, const struct virtual_compressor *, + const unsigned, const unsigned, const int64_t); + +/* HTTP daemon support */ +extern const char *voss_httpd_start(vprofile_t *); + +#endif /* _VIRTUAL_INT_H_ */ diff --git a/usr.sbin/virtual_oss/virtual_oss/main.c b/usr.sbin/virtual_oss/virtual_oss/main.c new file mode 100644 index 000000000000..760747e90091 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/main.c @@ -0,0 +1,2625 @@ +/*- + * Copyright (c) 2012-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "backend.h" +#include "int.h" +#include "utils.h" +#include "virtual_oss.h" + +pthread_mutex_t atomic_mtx; +pthread_cond_t atomic_cv; + +static void +atomic_init(void) +{ + if (pthread_mutex_init(&atomic_mtx, NULL) != 0) + err(1, "pthread_mutex_init"); + if (pthread_cond_init(&atomic_cv, NULL) != 0) + err(1, "pthread_cond_init"); +} + +uint32_t +vclient_sample_bytes(vclient_t *pvc) +{ + uint32_t fmt = pvc->format; + + if (fmt & AFMT_16BIT) + return (2); + else if (fmt & AFMT_24BIT) + return (3); + else if (fmt & AFMT_32BIT) + return (4); + else if (fmt & AFMT_8BIT) + return (1); + else + return (0); + /* TODO AFMT_BPS */ +} + +static uint32_t +vclient_output_delay(vclient_t *pvc) +{ + uint64_t size; + uint64_t mod; + + if (pvc->tx_busy == 0) + vclient_import_write_locked(pvc); + + mod = pvc->channels * vclient_sample_bytes(pvc); + + size = vring_total_read_len(&pvc->tx_ring[0]); + size = (size / 8) * vclient_sample_bytes(pvc); + + size = (size * (uint64_t)pvc->sample_rate) / + (uint64_t)voss_dsp_sample_rate; + size += vring_total_read_len(&pvc->tx_ring[1]); + size -= size % mod; + + return (size); +} + +static uint32_t +vclient_input_delay(vclient_t *pvc) +{ + if (pvc->rx_busy == 0) + vclient_export_read_locked(pvc); + return (vring_total_read_len(&pvc->rx_ring[1])); +} + +uint32_t +vclient_bufsize_scaled(vclient_t *pvc) +{ + uint32_t samples_scaled = ((uint64_t)voss_dsp_samples * + (uint64_t)pvc->sample_rate) / (uint64_t)voss_dsp_sample_rate; + if (samples_scaled == 0) + samples_scaled = 1; + return (pvc->channels * samples_scaled * vclient_sample_bytes(pvc)); +} + +static uint64_t +vclient_bufsize_consumed(vclient_t *pvc, uint64_t ts) +{ + int64_t delta; + int64_t samples_scaled; + int64_t retval; + + delta = virtual_oss_timestamp() - ts; + if (delta < 0) + delta = 0; + samples_scaled = (delta * (uint64_t)pvc->sample_rate) / 1000000000ULL; + if (samples_scaled < 0) + samples_scaled = 0; + retval = pvc->channels * samples_scaled * vclient_sample_bytes(pvc); + if (retval < 0) + retval = 0; + return (retval); +} + +/* + * VLC and some other audio player use this value for jitter + * computations and expect it to be very accurate. VirtualOSS is block + * based and does not have sample accuracy. Use the system clock to + * update this value as we go along instead: + */ +static uint32_t +vclient_output_delay_adjusted(vclient_t *pvc) +{ + int64_t retval = vclient_output_delay(pvc) - + vclient_bufsize_consumed(pvc, pvc->tx_timestamp); + if (retval < 0) + retval = 0; + return (retval); +} + +vmonitor_t * +vmonitor_alloc(int *pid, vmonitor_head_t *phead) +{ + int id = 0; + vmonitor_t *pvm; + + TAILQ_FOREACH(pvm, phead, entry) + id++; + + if (id >= 64) { + *pid = 0; + return (NULL); + } + pvm = malloc(sizeof(*pvm)); + if (pvm == NULL) { + *pid = 0; + return (NULL); + } + memset(pvm, 0, sizeof(*pvm)); + + pvm->mute = 1; + + TAILQ_INSERT_TAIL(phead, pvm, entry); + + *pid = id; + return (pvm); +} + +int64_t +vclient_noise(uint32_t *pnoise, int64_t volume, int8_t shift) +{ + const uint32_t prime = 0xFFFF1DU; + int64_t temp; + + /* compute next noise sample */ + temp = *pnoise; + if (temp & 1) + temp += prime; + temp /= 2; + *pnoise = temp; + + /* unsigned to signed conversion */ + temp ^= 0x800000ULL; + if (temp & 0x800000U) + temp |= -0x800000ULL; + + /* properly amplify */ + temp *= volume; + + /* bias shift */ + shift -= 23 + VVOLUME_UNIT_SHIFT; + + /* range check and shift noise */ + if (__predict_false(shift < -63 || shift > 63)) + temp = 0; + else if (shift < 0) + temp >>= -shift; + else + temp <<= shift; + + return (temp); +} + +static void +vresample_free(vresample_t *pvr) +{ + if (pvr->state != NULL) + src_delete(pvr->state); + free(pvr->data_in); + free(pvr->data_out); + memset(pvr, 0, sizeof(*pvr)); +} + +static int +vresample_setup(vclient_t *pvc, vresample_t *pvr, int samples) +{ + int code = 0; + + if (pvr->state != NULL) + return (0); + pvr->state = src_new(voss_libsamplerate_quality, pvc->channels, &code); + if (pvr->state == NULL) + goto error; + pvr->data_in = malloc(sizeof(float) * samples); + if (pvr->data_in == NULL) + goto error; + pvr->data_out = malloc(sizeof(float) * samples); + if (pvr->data_out == NULL) + goto error; + pvr->data.data_in = pvr->data_in; + pvr->data.data_out = pvr->data_out; + return (0); +error: + vresample_free(pvr); + return (CUSE_ERR_NO_MEMORY); +} + +void +vclient_free(vclient_t *pvc) +{ + vresample_free(&pvc->rx_resample); + vresample_free(&pvc->tx_resample); + + /* free equalizer */ + vclient_eq_free(pvc); + + /* free ring buffers */ + vring_free(&pvc->rx_ring[0]); + vring_free(&pvc->rx_ring[1]); + vring_free(&pvc->tx_ring[0]); + vring_free(&pvc->tx_ring[1]); + + free(pvc); +} + +vclient_t * +vclient_alloc(void) +{ + vclient_t *pvc; + + pvc = malloc(sizeof(*pvc)); + if (pvc == NULL) + return (NULL); + + memset(pvc, 0, sizeof(*pvc)); + + pvc->rx_noise_rem = 1; + pvc->tx_noise_rem = 1; + pvc->rx_volume = 1 << VVOLUME_UNIT_SHIFT; + pvc->tx_volume = 1 << VVOLUME_UNIT_SHIFT; + + return (pvc); +} + +int +vclient_get_default_fmt(vprofile_t *pvp, int type) +{ + int retval; + + if (type == VTYPE_WAV_HDR) { + switch (pvp->bits) { + case 16: + retval = AFMT_S16_LE; + break; + case 24: + retval = AFMT_S24_LE; + break; + case 32: + retval = AFMT_S32_LE; + break; + default: + retval = AFMT_S8; + break; + } + } else { + switch (pvp->bits) { + case 16: + retval = AFMT_S16_NE; + break; + case 24: + retval = AFMT_S24_NE; + break; + case 32: + retval = AFMT_S32_NE; + break; + default: + retval = AFMT_S8; + break; + } + } + return (retval); +} + +int +vclient_setup_buffers(vclient_t *pvc, int size, int frags, + int channels, int format, int sample_rate) +{ + size_t bufsize_internal; + size_t bufsize_min; + size_t mod_internal; + size_t mod; + uint64_t ts; + int bufsize; + + /* check we are not busy */ + if (pvc->rx_busy || pvc->tx_busy) + return (CUSE_ERR_BUSY); + + /* free equalizer */ + vclient_eq_free(pvc); + + /* free existing ring buffers */ + vring_free(&pvc->rx_ring[0]); + vring_free(&pvc->rx_ring[1]); + vring_free(&pvc->tx_ring[0]); + vring_free(&pvc->tx_ring[1]); + + /* reset resampler */ + vresample_free(&pvc->rx_resample); + vresample_free(&pvc->tx_resample); + + if (sample_rate > 0) + pvc->sample_rate = sample_rate; + if (format != 0) + pvc->format = format; + if (channels > 0) + pvc->channels = channels; + + mod = pvc->channels * vclient_sample_bytes(pvc); + mod_internal = pvc->channels * 8; + + if (size > 0) { + size += mod - 1; + size -= size % mod; + + pvc->buffer_size = size; + pvc->buffer_size_set = 1; + } else if (pvc->buffer_size_set == 0) + pvc->buffer_size = vclient_bufsize_scaled(pvc); + + pvc->low_water = pvc->buffer_size; + + if (frags > 0) { + pvc->buffer_frags = frags; + pvc->buffer_frags_set = 1; + } else if (pvc->buffer_frags_set == 0) + pvc->buffer_frags = 2; + + /* sanity checks */ + if (frags < 0 || size < 0) + return (CUSE_ERR_INVALID); + if (pvc->format == 0) + return (CUSE_ERR_INVALID); + if (pvc->buffer_frags <= 0 || pvc->buffer_frags >= 1024) + return (CUSE_ERR_INVALID); + if (pvc->buffer_size <= 0 || pvc->buffer_size >= (1024 * 1024)) + return (CUSE_ERR_INVALID); + if ((pvc->buffer_size * pvc->buffer_frags) >= (128 * 1024 * 1024)) + return (CUSE_ERR_INVALID); + if (pvc->channels <= 0 || pvc->channels > pvc->profile->channels) + return (CUSE_ERR_INVALID); + + /* get buffer sizes */ + bufsize = pvc->buffer_frags * pvc->buffer_size; + bufsize_internal = ((uint64_t)bufsize * (uint64_t)voss_dsp_sample_rate * 8ULL) / + ((uint64_t)pvc->sample_rate * (uint64_t)vclient_sample_bytes(pvc)); + + bufsize_min = voss_dsp_samples * pvc->channels * 8; + + /* check for too small buffer size */ + if (bufsize_internal < bufsize_min) + return (CUSE_ERR_INVALID); + + /* allow for jitter */ + bufsize_internal *= 2ULL; + + /* align buffer size */ + bufsize_internal += (mod_internal - 1); + bufsize_internal -= (bufsize_internal % mod_internal); + + /* allocate new buffers */ + if (vring_alloc(&pvc->rx_ring[0], bufsize_internal)) + goto err_0; + if (vring_alloc(&pvc->rx_ring[1], bufsize)) + goto err_1; + if (vring_alloc(&pvc->tx_ring[0], bufsize_internal)) + goto err_2; + if (vring_alloc(&pvc->tx_ring[1], bufsize)) + goto err_3; + if (vclient_eq_alloc(pvc)) + goto err_4; + + ts = virtual_oss_timestamp(); + + pvc->rx_samples = 0; + pvc->tx_samples = 0; + pvc->tx_timestamp = ts; + pvc->rx_timestamp = ts; + + return (0); + +err_4: + vring_free(&pvc->tx_ring[1]); +err_3: + vring_free(&pvc->tx_ring[0]); +err_2: + vring_free(&pvc->rx_ring[1]); +err_1: + vring_free(&pvc->rx_ring[0]); +err_0: + return (CUSE_ERR_NO_MEMORY); +} + +static int +vclient_open_sub(struct cuse_dev *pdev, int fflags __unused, int type) +{ + vclient_t *pvc; + vprofile_t *pvp; + int error; + + pvp = cuse_dev_get_priv0(pdev); + + pvc = vclient_alloc(); + if (pvc == NULL) + return (CUSE_ERR_NO_MEMORY); + + pvc->profile = pvp; + + /* setup buffers */ + error = vclient_setup_buffers(pvc, 0, 0, pvp->channels, + vclient_get_default_fmt(pvp, type), voss_dsp_sample_rate); + if (error != 0) { + vclient_free(pvc); + return (error); + } + + pvc->type = type; + + cuse_dev_set_per_file_handle(pdev, pvc); + + atomic_lock(); + /* only allow one synchronization source at a time */ + if (pvc->profile->synchronized) { + if (voss_has_synchronization != 0) + error = CUSE_ERR_BUSY; + else + voss_has_synchronization++; + } + if (error == 0) + TAILQ_INSERT_TAIL(&pvc->profile->head, pvc, entry); + atomic_unlock(); + + return (error); +} + +static int +vclient_open_wav(struct cuse_dev *pdev, int fflags) +{ + return (vclient_open_sub(pdev, fflags, VTYPE_WAV_HDR)); +} + +static int +vclient_open_oss(struct cuse_dev *pdev, int fflags) +{ + return (vclient_open_sub(pdev, fflags, VTYPE_OSS_DAT)); +} + +static int +vclient_close(struct cuse_dev *pdev, int fflags __unused) +{ + vclient_t *pvc; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (CUSE_ERR_INVALID); + + atomic_lock(); + if (pvc->profile->synchronized) { + voss_has_synchronization--; + + /* wait for virtual_oss_process(), if any */ + while (pvc->sync_busy) { + pvc->sync_wakeup = 1; + atomic_wakeup(); + atomic_wait(); + } + } + TAILQ_REMOVE(&pvc->profile->head, pvc, entry); + atomic_unlock(); + + vclient_free(pvc); + + return (0); +} + +static int +vclient_read_silence_locked(vclient_t *pvc) +{ + size_t size; + int delta_in; + + delta_in = pvc->profile->rec_delay - pvc->rec_delay; + if (delta_in < 1) + return (0); + + size = delta_in * pvc->channels * 8; + size = vring_write_zero(&pvc->rx_ring[0], size); + pvc->rec_delay += size / (pvc->channels * 8); + + delta_in = pvc->profile->rec_delay - pvc->rec_delay; + if (delta_in < 1) + return (0); + + return (1); +} + +static int +vclient_generate_wav_header_locked(vclient_t *pvc) +{ + uint8_t *ptr; + size_t mod; + size_t len; + + vring_get_write(&pvc->rx_ring[1], &ptr, &len); + + mod = pvc->channels * vclient_sample_bytes(pvc); + + if (mod == 0 || len < (44 + mod - 1)) + return (CUSE_ERR_INVALID); + + /* align to next sample */ + len = 44 + mod - 1; + len -= len % mod; + + /* pre-advance write pointer */ + vring_inc_write(&pvc->rx_ring[1], len); + + /* clear block */ + memset(ptr, 0, len); + + /* fill out data header */ + ptr[len - 8] = 'd'; + ptr[len - 7] = 'a'; + ptr[len - 6] = 't'; + ptr[len - 5] = 'a'; + + /* magic for unspecified length */ + ptr[len - 4] = 0x00; + ptr[len - 3] = 0xF0; + ptr[len - 2] = 0xFF; + ptr[len - 1] = 0x7F; + + /* fill out header */ + *ptr++ = 'R'; + *ptr++ = 'I'; + *ptr++ = 'F'; + *ptr++ = 'F'; + + /* total chunk size - unknown */ + + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + + *ptr++ = 'W'; + *ptr++ = 'A'; + *ptr++ = 'V'; + *ptr++ = 'E'; + *ptr++ = 'f'; + *ptr++ = 'm'; + *ptr++ = 't'; + *ptr++ = ' '; + + /* make sure header fits in PCM block */ + len -= 28; + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* audioformat = PCM */ + + *ptr++ = 0x01; + *ptr++ = 0x00; + + /* number of channels */ + + len = pvc->channels; + + *ptr++ = len; + *ptr++ = len >> 8; + + /* sample rate */ + + len = pvc->sample_rate; + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* byte rate */ + + len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); + + *ptr++ = len; + *ptr++ = len >> 8; + *ptr++ = len >> 16; + *ptr++ = len >> 24; + + /* block align */ + + len = pvc->channels * vclient_sample_bytes(pvc); + + *ptr++ = len; + *ptr++ = len >> 8; + + /* bits per sample */ + + len = vclient_sample_bytes(pvc) * 8; + + *ptr++ = len; + *ptr++ = len >> 8; + + return (0); +} + +int +vclient_export_read_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx) +{ + enum { MAX_FRAME = 1024 }; + size_t dst_mod; + size_t src_mod; + int error; + + if (pvc->type == VTYPE_WAV_HDR) { + error = vclient_generate_wav_header_locked(pvc); + if (error != 0) + return (error); + /* only write header once */ + pvc->type = VTYPE_WAV_DAT; + } + error = vclient_read_silence_locked(pvc); + if (error != 0) + return (0); + + dst_mod = pvc->channels * vclient_sample_bytes(pvc); + src_mod = pvc->channels * 8; + + if (pvc->sample_rate == (int)voss_dsp_sample_rate) { + while (1) { + uint8_t *src_ptr; + size_t src_len; + uint8_t *dst_ptr; + size_t dst_len; + + vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len); + vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len); + + src_len /= src_mod; + dst_len /= dst_mod; + + /* compare number of samples */ + if (dst_len > src_len) + dst_len = src_len; + else + src_len = dst_len; + + if (dst_len == 0) + break; + + src_len *= src_mod; + dst_len *= dst_mod; + + format_export(pvc->format, + (const int64_t *)(uintptr_t)src_ptr, + dst_ptr, dst_len); + + vring_inc_read(&pvc->rx_ring[0], src_len); + vring_inc_write(&pvc->rx_ring[1], dst_len); + } + } else { + vresample_t *pvr = &pvc->rx_resample; + + if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0) + return (CUSE_ERR_NO_MEMORY); + + while (1) { + uint8_t *src_ptr; + size_t src_len; + uint8_t *dst_ptr; + size_t dst_len; + int64_t temp[MAX_FRAME * pvc->channels]; + size_t samples; + size_t y; + + vring_get_read(&pvc->rx_ring[0], &src_ptr, &src_len); + vring_get_write(&pvc->rx_ring[1], &dst_ptr, &dst_len); + + src_len /= src_mod; + dst_len /= dst_mod; + + /* compare number of samples */ + if (dst_len > src_len) + dst_len = src_len; + else + src_len = dst_len; + + if (dst_len > MAX_FRAME) + dst_len = src_len = MAX_FRAME; + + if (dst_len == 0) + break; + + src_len *= src_mod; + dst_len *= dst_mod; + + for (y = 0; y != src_len; y += 8) { + pvr->data_in[y / 8] = + *(int64_t *)(uintptr_t)(src_ptr + y); + } + + /* setup parameters for transform */ + pvr->data.input_frames = src_len / src_mod; + pvr->data.output_frames = dst_len / dst_mod; + pvr->data.src_ratio = (float)pvc->sample_rate / (float)voss_dsp_sample_rate; + + pvc->rx_busy = 1; + atomic_unlock(); + error = src_process(pvr->state, &pvr->data); + atomic_lock(); + pvc->rx_busy = 0; + + if (error != 0) + break; + + src_len = pvr->data.input_frames_used * src_mod; + dst_len = pvr->data.output_frames_gen * dst_mod; + + samples = pvr->data.output_frames_gen * pvc->channels; + + for (y = 0; y != samples; y++) + temp[y] = pvr->data_out[y]; + + format_export(pvc->format, temp, dst_ptr, dst_len); + + vring_inc_read(&pvc->rx_ring[0], src_len); + vring_inc_write(&pvc->rx_ring[1], dst_len); + + /* check if no data was moved */ + if (src_len == 0 && dst_len == 0) + break; + } + } + if (pvc->sync_busy) + atomic_wakeup(); + return (0); +} + +static int +vclient_read(struct cuse_dev *pdev, int fflags, + void *peer_ptr, int len) +{ + vclient_t *pvc; + + int error; + int retval; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (CUSE_ERR_INVALID); + + atomic_lock(); + + if (pvc->rx_busy) { + atomic_unlock(); + return (CUSE_ERR_BUSY); + } + pvc->rx_enabled = 1; + + retval = 0; + + while (len > 0) { + uint8_t *buf_ptr; + size_t buf_len; + + error = vclient_export_read_locked(pvc); + if (error != 0) { + retval = error; + break; + } + + vring_get_read(&pvc->rx_ring[1], &buf_ptr, &buf_len); + + if (buf_len == 0) { + /* out of data */ + if (fflags & CUSE_FFLAG_NONBLOCK) { + if (retval == 0) + retval = CUSE_ERR_WOULDBLOCK; + break; + } + pvc->rx_busy = 1; + atomic_wait(); + pvc->rx_busy = 0; + if (cuse_got_peer_signal() == 0) { + if (retval == 0) + retval = CUSE_ERR_SIGNAL; + break; + } + continue; + } + if ((int)buf_len > len) + buf_len = len; + + pvc->rx_busy = 1; + atomic_unlock(); + error = cuse_copy_out(buf_ptr, peer_ptr, buf_len); + atomic_lock(); + pvc->rx_busy = 0; + + if (error != 0) { + retval = error; + break; + } + peer_ptr = ((uint8_t *)peer_ptr) + buf_len; + retval += buf_len; + len -= buf_len; + + vring_inc_read(&pvc->rx_ring[1], buf_len); + } + atomic_unlock(); + + return (retval); +} + +void +vclient_import_write_locked(vclient_t *pvc) __requires_exclusive(atomic_mtx) +{ + enum { MAX_FRAME = 1024 }; + size_t dst_mod; + size_t src_mod; + + dst_mod = pvc->channels * 8; + src_mod = pvc->channels * vclient_sample_bytes(pvc); + + if (pvc->sample_rate == (int)voss_dsp_sample_rate) { + while (1) { + uint8_t *src_ptr; + size_t src_len; + uint8_t *dst_ptr; + size_t dst_len; + + vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len); + vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len); + + src_len /= src_mod; + dst_len /= dst_mod; + + /* compare number of samples */ + if (dst_len > src_len) + dst_len = src_len; + else + src_len = dst_len; + + if (dst_len == 0) + break; + + src_len *= src_mod; + dst_len *= dst_mod; + + format_import(pvc->format, src_ptr, src_len, + (int64_t *)(uintptr_t)dst_ptr); + + vring_inc_read(&pvc->tx_ring[1], src_len); + vring_inc_write(&pvc->tx_ring[0], dst_len); + } + } else { + vresample_t *pvr = &pvc->tx_resample; + + if (vresample_setup(pvc, pvr, MAX_FRAME * pvc->channels) != 0) + return; + + while (1) { + uint8_t *src_ptr; + size_t src_len; + uint8_t *dst_ptr; + size_t dst_len; + int64_t temp[MAX_FRAME * pvc->channels]; + size_t samples; + size_t y; + int error; + + vring_get_read(&pvc->tx_ring[1], &src_ptr, &src_len); + vring_get_write(&pvc->tx_ring[0], &dst_ptr, &dst_len); + + src_len /= src_mod; + dst_len /= dst_mod; + + /* compare number of samples */ + if (dst_len > src_len) + dst_len = src_len; + else + src_len = dst_len; + + if (dst_len > MAX_FRAME) + dst_len = src_len = MAX_FRAME; + + if (dst_len == 0) + break; + + src_len *= src_mod; + dst_len *= dst_mod; + + format_import(pvc->format, src_ptr, src_len, temp); + + src_len /= vclient_sample_bytes(pvc); + + for (y = 0; y != src_len; y++) + pvr->data_in[y] = temp[y]; + + src_len *= vclient_sample_bytes(pvc); + + /* setup parameters for transform */ + pvr->data.input_frames = src_len / src_mod; + pvr->data.output_frames = dst_len / dst_mod; + pvr->data.src_ratio = (float)voss_dsp_sample_rate / (float)pvc->sample_rate; + + pvc->tx_busy = 1; + atomic_unlock(); + error = src_process(pvr->state, &pvr->data); + atomic_lock(); + pvc->tx_busy = 0; + + if (error != 0) + break; + + src_len = pvr->data.input_frames_used * src_mod; + dst_len = pvr->data.output_frames_gen * dst_mod; + + samples = pvr->data.output_frames_gen * pvc->channels; + + for (y = 0; y != samples; y++) { + ((int64_t *)(uintptr_t)dst_ptr)[y] = + pvr->data_out[y]; + } + + vring_inc_read(&pvc->tx_ring[1], src_len); + vring_inc_write(&pvc->tx_ring[0], dst_len); + + /* check if no data was moved */ + if (src_len == 0 && dst_len == 0) + break; + } + } + if (pvc->sync_busy) + atomic_wakeup(); +} + +static int +vclient_write_oss(struct cuse_dev *pdev, int fflags, + const void *peer_ptr, int len) +{ + vclient_t *pvc; + + int error; + int retval; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (CUSE_ERR_INVALID); + + retval = 0; + + atomic_lock(); + + if (pvc->tx_busy) { + atomic_unlock(); + return (CUSE_ERR_BUSY); + } + pvc->tx_enabled = 1; + + while (1) { + uint8_t *buf_ptr; + size_t buf_len; + + vclient_import_write_locked(pvc); + + if (len < 1) + break; + + vring_get_write(&pvc->tx_ring[1], &buf_ptr, &buf_len); + + if (buf_len == 0) { + /* out of data */ + if (fflags & CUSE_FFLAG_NONBLOCK) { + if (retval == 0) + retval = CUSE_ERR_WOULDBLOCK; + break; + } + pvc->tx_busy = 1; + atomic_wait(); + pvc->tx_busy = 0; + if (cuse_got_peer_signal() == 0) { + if (retval == 0) + retval = CUSE_ERR_SIGNAL; + break; + } + continue; + } + if ((int)buf_len > len) + buf_len = len; + + pvc->tx_busy = 1; + atomic_unlock(); + error = cuse_copy_in(peer_ptr, buf_ptr, buf_len); + atomic_lock(); + pvc->tx_busy = 0; + + if (error != 0) { + retval = error; + break; + } + peer_ptr = ((const uint8_t *)peer_ptr) + buf_len; + retval += buf_len; + len -= buf_len; + + vring_inc_write(&pvc->tx_ring[1], buf_len); + } + atomic_unlock(); + + return (retval); +} + +static int +vclient_write_wav(struct cuse_dev *pdev __unused, int fflags __unused, + const void *peer_ptr __unused, int len __unused) +{ + return (CUSE_ERR_INVALID); +} + +static int +vclient_set_channels(vclient_t *pvc, int channels) +{ + if (pvc->channels == channels) + return (0); + return (vclient_setup_buffers(pvc, 0, 0, channels, 0, 0)); +} + +/* greatest common divisor, Euclid equation */ +static uint64_t +vclient_gcd_64(uint64_t a, uint64_t b) +{ + uint64_t an; + uint64_t bn; + + while (b != 0) { + an = b; + bn = a % b; + a = an; + b = bn; + } + return (a); +} + +static uint64_t +vclient_scale(uint64_t value, uint64_t mul, uint64_t div) +{ + uint64_t gcd = vclient_gcd_64(mul, div); + + mul /= gcd; + div /= gcd; + + return ((value * mul) / div); +} + +static int +vclient_ioctl_oss(struct cuse_dev *pdev, int fflags __unused, + unsigned long cmd, void *peer_data) +{ + union { + int val; + unsigned long long lval; + oss_sysinfo sysinfo; + oss_card_info card_info; + oss_audioinfo audioinfo; + audio_buf_info buf_info; + oss_count_t oss_count; + count_info oss_count_info; + audio_errinfo errinfo; + oss_label_t label; + oss_longname_t longname; + } data; + + vclient_t *pvc; + + uint64_t bytes; + + int len; + int error; + int temp; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (CUSE_ERR_INVALID); + + len = IOCPARM_LEN(cmd); + + if (len < 0 || len > (int)sizeof(data)) + return (CUSE_ERR_INVALID); + + if (cmd & IOC_IN) { + error = cuse_copy_in(peer_data, &data, len); + if (error) + return (error); + } else { + error = 0; + } + + atomic_lock(); + + switch (cmd) { + case OSS_GETVERSION: + data.val = SOUND_VERSION; + break; + case SNDCTL_SYSINFO: + memset(&data.sysinfo, 0, sizeof(data.sysinfo)); + strcpy(data.sysinfo.product, "VOSS"); + strcpy(data.sysinfo.version, "1.0"); + data.sysinfo.versionnum = SOUND_VERSION; + data.sysinfo.numaudios = 1; + data.sysinfo.numcards = 1; + data.sysinfo.numaudioengines = 1; + strcpy(data.sysinfo.license, "BSD"); + memset(data.sysinfo.filler, -1, sizeof(data.sysinfo.filler)); + break; + case SNDCTL_CARDINFO: + memset(&data.card_info, 0, sizeof(data.card_info)); + strlcpy(data.card_info.shortname, pvc->profile->oss_name, + sizeof(data.card_info.shortname)); + break; + case SNDCTL_AUDIOINFO: + case SNDCTL_AUDIOINFO_EX: + case SNDCTL_ENGINEINFO: + memset(&data.audioinfo, 0, sizeof(data.audioinfo)); + strlcpy(data.audioinfo.name, pvc->profile->oss_name, + sizeof(data.audioinfo.name)); + snprintf(data.audioinfo.devnode, sizeof(data.audioinfo.devnode), + _PATH_DEV "%s", pvc->profile->oss_name); + data.audioinfo.caps = DSP_CAP_INPUT | DSP_CAP_OUTPUT; + data.audioinfo.iformats = VSUPPORTED_AFMT; + data.audioinfo.oformats = VSUPPORTED_AFMT; + data.audioinfo.enabled = 1; + data.audioinfo.min_rate = (int)8000; + data.audioinfo.max_rate = (int)voss_dsp_sample_rate; + data.audioinfo.max_channels = pvc->profile->channels; + /* range check */ + if (voss_libsamplerate_enable == 0 || + data.audioinfo.min_rate > data.audioinfo.max_rate) + data.audioinfo.min_rate = data.audioinfo.max_rate; + data.audioinfo.nrates = 1; + data.audioinfo.rates[0] = (int)voss_dsp_sample_rate; + if (voss_libsamplerate_enable != 0 && + 96000 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 96000; + if (voss_libsamplerate_enable != 0 && + 48000 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 48000; + if (voss_libsamplerate_enable != 0 && + 44100 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 44100; + if (voss_libsamplerate_enable != 0 && + 24000 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 24000; + if (voss_libsamplerate_enable != 0 && + 16000 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 16000; + if (voss_libsamplerate_enable != 0 && + 8000 != voss_dsp_sample_rate) + data.audioinfo.rates[data.audioinfo.nrates++] = 8000; + data.audioinfo.latency = -1; + break; + case FIONREAD: + data.val = vclient_input_delay(pvc); + break; + case FIONWRITE: + data.val = vring_total_read_len(&pvc->tx_ring[1]); + break; + case FIOASYNC: + case SNDCTL_DSP_NONBLOCK: + case FIONBIO: + break; + case SNDCTL_DSP_SETBLKSIZE: + case _IOWR('P', 4, int): + error = vclient_setup_buffers(pvc, data.val, 0, 0, 0, 0); + /* FALLTHROUGH */ + case SNDCTL_DSP_GETBLKSIZE: + data.val = pvc->buffer_size; + break; + case SNDCTL_DSP_SETFRAGMENT: + if ((data.val & 0xFFFF) < 4) { + /* need at least 16 bytes of buffer */ + data.val &= ~0xFFFF; + data.val |= 4; + } else if ((data.val & 0xFFFF) > 24) { + /* no more than 16MBytes of buffer */ + data.val &= ~0xFFFF; + data.val |= 24; + } + error = vclient_setup_buffers(pvc, + (1 << (data.val & 0xFFFF)), (data.val >> 16), 0, 0, 0); + if (error) { + /* fallback to defaults */ + pvc->buffer_size_set = 0; + pvc->buffer_frags_set = 0; + error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0); + if (error) + break; + /* figure out log2() of actual buffer size */ + for (data.val = 0; + data.val < 24 && (1U << data.val) < pvc->buffer_size; + data.val++) + ; + /* or in the actual number of fragments */ + data.val |= (pvc->buffer_frags << 16); + } + break; + case SNDCTL_DSP_RESET: + error = vclient_setup_buffers(pvc, 0, 0, 0, 0, 0); + break; + case SNDCTL_DSP_SYNC: + break; + case SNDCTL_DSP_SPEED: + if (data.val >= 8000 && data.val <= 96000 && + voss_libsamplerate_enable != 0) { + error = vclient_setup_buffers(pvc, 0, 0, 0, 0, data.val); + } + /* return current speed */ + data.val = (int)pvc->sample_rate; + break; + case SOUND_PCM_READ_RATE: + data.val = (int)pvc->sample_rate; + break; + case SNDCTL_DSP_STEREO: + if (data.val != 0) { + error = vclient_set_channels(pvc, 2); + } else { + error = vclient_set_channels(pvc, 1); + } + data.val = (pvc->channels == 2); + break; + case SOUND_PCM_WRITE_CHANNELS: + if (data.val < 0) { + data.val = 0; + error = CUSE_ERR_INVALID; + break; + } + if (data.val == 0) { + data.val = pvc->channels; + } else { + error = vclient_set_channels(pvc, data.val); + } + break; + case SOUND_PCM_READ_CHANNELS: + data.val = pvc->channels; + break; + case AIOGFMT: + case SNDCTL_DSP_GETFMTS: + data.val = VSUPPORTED_AFMT | AFMT_FULLDUPLEX | + (pvc->profile->channels > 1 ? AFMT_STEREO : 0); + break; + case AIOSFMT: + case SNDCTL_DSP_SETFMT: + if (data.val != AFMT_QUERY) { + temp = data.val & VSUPPORTED_AFMT; + if (temp == 0 || (temp & (temp - 1)) != 0) { + error = CUSE_ERR_INVALID; + } else { + error = vclient_setup_buffers(pvc, 0, 0, 0, temp, 0); + } + } else { + data.val = pvc->format; + } + break; + case SNDCTL_DSP_GETISPACE: + memset(&data.buf_info, 0, sizeof(data.buf_info)); + data.buf_info.fragsize = pvc->buffer_size; + data.buf_info.fragstotal = pvc->buffer_frags; + bytes = (pvc->buffer_size * pvc->buffer_frags); + temp = vclient_input_delay(pvc); + if (temp < 0 || (uint64_t)temp > bytes) + temp = bytes; + data.buf_info.fragments = temp / pvc->buffer_size; + data.buf_info.bytes = temp; + break; + case SNDCTL_DSP_GETOSPACE: + memset(&data.buf_info, 0, sizeof(data.buf_info)); + data.buf_info.fragsize = pvc->buffer_size; + data.buf_info.fragstotal = pvc->buffer_frags; + bytes = (pvc->buffer_size * pvc->buffer_frags); + temp = vclient_output_delay(pvc); + if (temp < 0 || (uint64_t)temp >= bytes) { + /* buffer is full */ + data.buf_info.fragments = 0; + data.buf_info.bytes = 0; + } else { + /* buffer is not full */ + bytes -= temp; + data.buf_info.fragments = bytes / pvc->buffer_size; + data.buf_info.bytes = bytes; + } + break; + case SNDCTL_DSP_GETCAPS: + data.val = PCM_CAP_REALTIME | PCM_CAP_DUPLEX | + PCM_CAP_INPUT | PCM_CAP_OUTPUT | PCM_CAP_TRIGGER | + PCM_CAP_VIRTUAL; + break; + case SOUND_PCM_READ_BITS: + data.val = vclient_sample_bytes(pvc) * 8; + break; + case SNDCTL_DSP_SETTRIGGER: + if (data.val & PCM_ENABLE_INPUT) { + pvc->rx_enabled = 1; + } else { + pvc->rx_enabled = 0; + vring_reset(&pvc->rx_ring[1]); + } + + if (data.val & PCM_ENABLE_OUTPUT) { + pvc->tx_enabled = 1; + } else { + pvc->tx_enabled = 0; + vring_reset(&pvc->tx_ring[1]); + } + break; + case SNDCTL_DSP_GETTRIGGER: + data.val = 0; + if (pvc->rx_enabled) + data.val |= PCM_ENABLE_INPUT; + if (pvc->tx_enabled) + data.val |= PCM_ENABLE_OUTPUT; + break; + case SNDCTL_DSP_GETODELAY: + data.val = vclient_output_delay_adjusted(pvc); + break; + case SNDCTL_DSP_POST: + break; + case SNDCTL_DSP_SETDUPLEX: + break; + case SNDCTL_DSP_GETRECVOL: + temp = (pvc->rx_volume * 100) >> VVOLUME_UNIT_SHIFT; + data.val = (temp & 0x00FF) | + ((temp << 8) & 0xFF00); + break; + case SNDCTL_DSP_SETRECVOL: + pvc->rx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100; + break; + case SNDCTL_DSP_GETPLAYVOL: + temp = (pvc->tx_volume * 100) >> VVOLUME_UNIT_SHIFT; + data.val = (temp & 0x00FF) | + ((temp << 8) & 0xFF00); + break; + case SNDCTL_DSP_SETPLAYVOL: + pvc->tx_volume = ((data.val & 0xFF) << VVOLUME_UNIT_SHIFT) / 100; + break; + case SNDCTL_DSP_CURRENT_IPTR: + memset(&data.oss_count, 0, sizeof(data.oss_count)); + /* compute input samples per channel */ + data.oss_count.samples = + vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate); + data.oss_count.samples /= pvc->channels; + data.oss_count.fifo_samples = + vclient_input_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc)); + break; + case SNDCTL_DSP_CURRENT_OPTR: + memset(&data.oss_count, 0, sizeof(data.oss_count)); + /* compute output samples per channel */ + data.oss_count.samples = + vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate); + data.oss_count.samples /= pvc->channels; + data.oss_count.fifo_samples = + vclient_output_delay(pvc) / (pvc->channels * vclient_sample_bytes(pvc)); + break; + case SNDCTL_DSP_GETIPTR: + memset(&data.oss_count_info, 0, sizeof(data.oss_count_info)); + /* compute input bytes */ + bytes = + vclient_scale(pvc->rx_samples, pvc->sample_rate, voss_dsp_sample_rate) * + vclient_sample_bytes(pvc); + data.oss_count_info.bytes = bytes; + data.oss_count_info.blocks = bytes / pvc->buffer_size; + data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags); + break; + case SNDCTL_DSP_GETOPTR: + memset(&data.oss_count_info, 0, sizeof(data.oss_count_info)); + /* compute output bytes */ + bytes = + vclient_scale(pvc->tx_samples, pvc->sample_rate, voss_dsp_sample_rate) * + vclient_sample_bytes(pvc); + data.oss_count_info.bytes = bytes; + data.oss_count_info.blocks = bytes / pvc->buffer_size; + data.oss_count_info.ptr = bytes % (pvc->buffer_size * pvc->buffer_frags); + break; + case SNDCTL_DSP_HALT_OUTPUT: + pvc->tx_enabled = 0; + break; + case SNDCTL_DSP_HALT_INPUT: + pvc->rx_enabled = 0; + break; + case SNDCTL_DSP_LOW_WATER: + if (data.val > 0 && data.val < + (int)(pvc->buffer_frags * pvc->buffer_size)) { + pvc->low_water = data.val; + } else { + error = CUSE_ERR_INVALID; + } + break; + case SNDCTL_DSP_GETERROR: + memset(&data.errinfo, 0, sizeof(data.errinfo)); + break; + case SNDCTL_DSP_SYNCGROUP: + case SNDCTL_DSP_SYNCSTART: + break; + case SNDCTL_DSP_POLICY: + break; + case SNDCTL_DSP_COOKEDMODE: + break; + case SNDCTL_DSP_GET_CHNORDER: + data.lval = CHNORDER_NORMAL; + break; + case SNDCTL_DSP_GETCHANNELMASK: + data.val = DSP_BIND_FRONT; + break; + case SNDCTL_DSP_BIND_CHANNEL: + break; + case SNDCTL_GETLABEL: + memset(&data.label, 0, sizeof(data.label)); + break; + case SNDCTL_SETLABEL: + break; + case SNDCTL_GETSONG: + memset(&data.longname, 0, sizeof(data.longname)); + break; + case SNDCTL_SETSONG: + break; + case SNDCTL_SETNAME: + break; + default: + error = CUSE_ERR_INVALID; + break; + } + atomic_unlock(); + + if (error == 0) { + if (cmd & IOC_OUT) + error = cuse_copy_out(&data, peer_data, len); + } + return (error); +} + +static int +vclient_ioctl_wav(struct cuse_dev *pdev, int fflags __unused, + unsigned long cmd, void *peer_data) +{ + union { + int val; + } data; + + vclient_t *pvc; + int len; + int error; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (CUSE_ERR_INVALID); + + len = IOCPARM_LEN(cmd); + + if (len < 0 || len > (int)sizeof(data)) + return (CUSE_ERR_INVALID); + + if (cmd & IOC_IN) { + error = cuse_copy_in(peer_data, &data, len); + if (error) + return (error); + } else { + error = 0; + } + + atomic_lock(); + switch (cmd) { + case FIONREAD: + data.val = vclient_input_delay(pvc); + break; + case FIOASYNC: + case SNDCTL_DSP_NONBLOCK: + case FIONBIO: + break; + default: + error = CUSE_ERR_INVALID; + break; + } + atomic_unlock(); + + if (error == 0) { + if (cmd & IOC_OUT) + error = cuse_copy_out(&data, peer_data, len); + } + return (error); +} + +static int +vclient_poll(struct cuse_dev *pdev, int fflags, int events) +{ + vclient_t *pvc; + + int retval = CUSE_POLL_NONE; + + pvc = cuse_dev_get_per_file_handle(pdev); + if (pvc == NULL) + return (retval); + + atomic_lock(); + if ((events & CUSE_POLL_READ) && (fflags & CUSE_FFLAG_READ)) { + pvc->rx_enabled = 1; + if (vclient_input_delay(pvc) >= pvc->low_water) + retval |= CUSE_POLL_READ; + } + if ((events & CUSE_POLL_WRITE) && (fflags & CUSE_FFLAG_WRITE)) { + const uint32_t out_dly = vclient_output_delay(pvc); + const uint32_t out_buf = (pvc->buffer_frags * pvc->buffer_size); + + if (out_dly < out_buf && (out_buf - out_dly) >= pvc->low_water) + retval |= CUSE_POLL_WRITE; + } + atomic_unlock(); + + return (retval); +} + +static const struct cuse_methods vclient_oss_methods = { + .cm_open = vclient_open_oss, + .cm_close = vclient_close, + .cm_read = vclient_read, + .cm_write = vclient_write_oss, + .cm_ioctl = vclient_ioctl_oss, + .cm_poll = vclient_poll, +}; + +static const struct cuse_methods vclient_wav_methods = { + .cm_open = vclient_open_wav, + .cm_close = vclient_close, + .cm_read = vclient_read, + .cm_write = vclient_write_wav, + .cm_ioctl = vclient_ioctl_wav, + .cm_poll = vclient_poll, +}; + +vprofile_head_t virtual_profile_client_head; +vprofile_head_t virtual_profile_loopback_head; + +vmonitor_head_t virtual_monitor_input; +vmonitor_head_t virtual_monitor_output; +vmonitor_head_t virtual_monitor_local; + +uint32_t voss_max_channels; +uint32_t voss_mix_channels; +uint32_t voss_dsp_samples; +uint32_t voss_dsp_max_channels; +uint32_t voss_dsp_sample_rate; +uint32_t voss_dsp_bits; +uint8_t voss_libsamplerate_enable; +uint8_t voss_libsamplerate_quality = SRC_SINC_FASTEST; +int voss_is_recording = 1; +int voss_has_synchronization; +volatile sig_atomic_t voss_exit = 0; + +static int voss_dsp_perm = 0666; +static int voss_do_background; +static const char *voss_pid_path; + +uint32_t voss_dsp_rx_refresh; +uint32_t voss_dsp_tx_refresh; +char voss_dsp_rx_device[VMAX_STRING]; +char voss_dsp_tx_device[VMAX_STRING]; +char voss_ctl_device[VMAX_STRING]; + +uint32_t voss_jitter_up; +uint32_t voss_jitter_down; + +struct voss_backend *voss_rx_backend; +struct voss_backend *voss_tx_backend; + +static int voss_dups; +static int voss_ntds; +static pthread_t *voss_tds; + +/* XXX I do not like the prefix argument... */ +static struct voss_backend * +voss_load_backend(const char *prefix, const char *name, const char *dir) +{ + struct voss_backend *backend; + void *hdl; + char lpath[64], bsym[64]; + + snprintf(lpath, sizeof(lpath), "%s/lib/virtual_oss/voss_%s.so", + prefix, name); + snprintf(bsym, sizeof(bsym), "voss_backend_%s_%s", name, dir); + + if ((hdl = dlopen(lpath, RTLD_NOW)) == NULL) + errx(1, "%s", dlerror()); + if ((backend = dlsym(hdl, bsym)) == NULL) { + warnx("%s", dlerror()); + dlclose(hdl); + exit(EXIT_FAILURE); + } + + return (backend); +} + +static void +voss_rx_backend_refresh(void) +{ + /* setup RX backend */ + if (strcmp(voss_dsp_rx_device, "/dev/null") == 0) { + voss_rx_backend = voss_load_backend("/usr", "null", "rec"); + } else if (strstr(voss_dsp_rx_device, "/dev/bluetooth/") == voss_dsp_rx_device) { + voss_rx_backend = voss_load_backend("/usr/local", "bt", "rec"); + } else if (strstr(voss_dsp_rx_device, "/dev/sndio/") == voss_dsp_rx_device) { + voss_rx_backend = voss_load_backend("/usr/local", "sndio", "rec"); + } else { + voss_rx_backend = voss_load_backend("/usr", "oss", "rec"); + } +} + +static void +voss_tx_backend_refresh(void) +{ + /* setup TX backend */ + if (strcmp(voss_dsp_tx_device, "/dev/null") == 0) { + voss_tx_backend = voss_load_backend("/usr", "null", "play"); + } else if (strstr(voss_dsp_tx_device, "/dev/bluetooth/") == voss_dsp_tx_device) { + voss_tx_backend = voss_load_backend("/usr/local", "bt", "play"); + } else if (strstr(voss_dsp_tx_device, "/dev/sndio/") == voss_dsp_tx_device) { + voss_tx_backend = voss_load_backend("/usr/local", "sndio", "play"); + } else { + voss_tx_backend = voss_load_backend("/usr", "oss", "play"); + } +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: virtual_oss [options...] [device] \\\n" + "\t" "-C 2 -c 2 -r 48000 -b 16 -s 100.0ms -f /dev/dsp3 \\\n" + "\t" "-P /dev/dsp3 -R /dev/dsp1 \\\n" + "\t" "-O /dev/dsp3 -R /dev/null \\\n" + "\t" "-c 1 -m 0,0 [-w wav.0] -d dsp100.0 \\\n" + "\t" "-c 1 -m 0,0 [-w wav.0] -d vdsp.0 \\\n" + "\t" "-c 2 -m 0,0,1,1 [-w wav.1] -d vdsp.1 \\\n" + "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -l vdsp.loopback \\\n" + "\t" "-c 2 -m 0,0,1,1 [-w wav.loopback] -L vdsp.loopback \\\n" + "\t" "-B # run in background \\\n" + "\t" "-s or ms \\\n" + "\t" "-S # enable automatic resampling using libsamplerate \\\n" + "\t" "-Q <0,1,2> # quality of resampling 0=best,1=medium,2=fastest (default) \\\n" + "\t" "-b \\\n" + "\t" "-r \\\n" + "\t" "-i \\\n" + "\t" "-a \\\n" + "\t" "-a i, \\\n" + "\t" "-a o, \\\n" + "\t" "-g # enable device RX compressor\\\n" + "\t" "-x # enable output compressor\\\n" + "\t" "-p \\\n" + "\t" "-e \\\n" + "\t" "-e , \\\n" + "\t" "-m \\\n" + "\t" "-m \\\n" + "\t" "-C \\\n" + "\t" "-c \\\n" + "\t" "-M \\\n" + "\t" "-M i,,,,, \\\n" + "\t" "-M o,,,,, \\\n" + "\t" "-M x,,,,, \\\n" + "\t" "-F or ms \\\n" + "\t" "-G or ms \\\n" + "\t" "-E \\\n" + "\t" "-N \\\n" + "\t" "-H \\\n" + "\t" "-o \\\n" + "\t" "-J \\\n" + "\t" "-k \\\n" + "\t" "-t vdsp.ctl \n" + "\t" "Left channel = 0\n" + "\t" "Right channel = 1\n" + "\t" "Max channels = %d\n", VMAX_CHAN); + + exit(EX_USAGE); +} + +static void +init_compressor(struct virtual_profile *pvp) +{ + int x; + + memset(&pvp->rx_compressor_param, 0, sizeof(pvp->rx_compressor_param)); + + pvp->rx_compressor_param.knee = 85; + pvp->rx_compressor_param.attack = 3; + pvp->rx_compressor_param.decay = 20; + + for (x = 0; x != VMAX_CHAN; x++) + pvp->rx_compressor_gain[x] = 1.0; +} + +static void +init_mapping(struct virtual_profile *pvp) +{ + int x; + + for (x = 0; x != VMAX_CHAN; x++) { + pvp->rx_src[x] = x; + pvp->tx_dst[x] = x; + } +} + +static void +init_sndstat(vprofile_t *ptr) +{ + int err; + nvlist_t *nvl; + nvlist_t *di = NULL, *dichild = NULL; + struct sndstioc_nv_arg arg; + unsigned int min_rate, max_rate; + + nvl = nvlist_create(0); + if (nvl == NULL) { + warn("Failed to create nvlist"); + goto done; + } + + di = nvlist_create(0); + if (di == NULL) { + warn("Failed to create nvlist"); + goto done; + } + + dichild = nvlist_create(0); + if (dichild == NULL) { + warn("Failed to create nvlist"); + goto done; + } + + nvlist_add_string(di, SNDST_DSPS_PROVIDER, "virtual_oss"); + nvlist_add_string(di, SNDST_DSPS_DESC, "virtual_oss device"); + nvlist_add_number(di, SNDST_DSPS_PCHAN, 1); + nvlist_add_number(di, SNDST_DSPS_RCHAN, 1); + min_rate = 8000; + max_rate = voss_dsp_sample_rate; + if (voss_libsamplerate_enable == 0 || + min_rate > max_rate) + min_rate = max_rate; + if (voss_libsamplerate_enable != 0 && max_rate < 96000) + max_rate = 96000; + nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_RATE, min_rate); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_RATE, max_rate); + nvlist_add_number(dichild, SNDST_DSPS_INFO_FORMATS, VSUPPORTED_AFMT); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MIN_CHN, ptr->channels); + nvlist_add_number(dichild, SNDST_DSPS_INFO_MAX_CHN, ptr->channels); + nvlist_add_nvlist(di, SNDST_DSPS_INFO_PLAY, dichild); + nvlist_add_nvlist(di, SNDST_DSPS_INFO_REC, dichild); + + nvlist_add_string(di, SNDST_DSPS_DEVNODE, + ptr->oss_name); + nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); + + if (nvlist_error(nvl)) { + warn("Failed building nvlist"); + goto done; + } + + arg.buf = nvlist_pack(nvl, &arg.nbytes); + if (arg.buf == NULL) { + warn("Failed to pack nvlist"); + goto done; + } + err = ioctl(ptr->fd_sta, SNDSTIOC_ADD_USER_DEVS, &arg); + free(arg.buf); + if (err != 0) { + warn("Failed to issue ioctl(SNDSTIOC_ADD_USER_DEVS)"); + goto done; + } + +done: + nvlist_destroy(di); + nvlist_destroy(dichild); + nvlist_destroy(nvl); +} + +static const char * +dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute, + int tx_mute, int synchronized, int is_client) +{ + vprofile_t *ptr; + struct cuse_dev *pdev; + int x; + + rx_mute = rx_mute ? 1 : 0; + tx_mute = tx_mute ? 1 : 0; + pol = pol ? 1 : 0; + + /* Range check amplitude argument. */ + for (x = 0; x != 2; x++) { + if (pamp[x] < -63) + pamp[x] = -63; + else if (pamp[x] > 63) + pamp[x] = 63; + } + + ptr = malloc(sizeof(*ptr)); + if (ptr == NULL) + return ("Out of memory"); + + memcpy(ptr, pvp, sizeof(*ptr)); + + ptr->synchronized = synchronized; + ptr->fd_sta = -1; + TAILQ_INIT(&ptr->head); + + for (x = 0; x != ptr->channels; x++) { + ptr->tx_mute[x] = tx_mute; + ptr->rx_mute[x] = rx_mute; + ptr->tx_shift[x] = pamp[1]; + ptr->rx_shift[x] = pamp[0]; + ptr->tx_pol[x] = pol; + ptr->rx_pol[x] = pol; + } + + /* create DSP device */ + if (ptr->oss_name[0] != 0) { + /* + * Detect /dev/dsp creation and try to disable system + * basename cloning automatically: + */ + if (strcmp(ptr->oss_name, "dsp") == 0) + system("sysctl hw.snd.basename_clone=0"); + + /* create DSP character device */ + pdev = cuse_dev_create(&vclient_oss_methods, ptr, NULL, + 0, 0, voss_dsp_perm, ptr->oss_name); + if (pdev == NULL) { + free(ptr); + return ("Could not create CUSE DSP device"); + } + + /* register to sndstat */ + ptr->fd_sta = open("/dev/sndstat", O_WRONLY); + if (ptr->fd_sta < 0) { + warn("Could not open /dev/sndstat"); + } else { + init_sndstat(ptr); + } + } + /* create WAV device */ + if (ptr->wav_name[0] != 0) { + pdev = cuse_dev_create(&vclient_wav_methods, ptr, NULL, + 0, 0, voss_dsp_perm, ptr->wav_name); + if (pdev == NULL) { + free(ptr); + return ("Could not create CUSE WAV device"); + } + } + + atomic_lock(); + if (is_client) + TAILQ_INSERT_TAIL(&virtual_profile_client_head, ptr, entry); + else + TAILQ_INSERT_TAIL(&virtual_profile_loopback_head, ptr, entry); + atomic_unlock(); + + voss_dups++; + + /* need new names next time */ + memset(pvp->oss_name, 0, sizeof(pvp->oss_name)); + memset(pvp->wav_name, 0, sizeof(pvp->wav_name)); + + /* need to set new filter sizes */ + pvp->rx_filter_size = 0; + pvp->tx_filter_size = 0; + + /* need to specify new HTTP parameters next time */ + pvp->http.host = NULL; + pvp->http.port = NULL; + pvp->http.nstate = 0; + pvp->http.rtp_ifname = NULL; + pvp->http.rtp_port = NULL; + + /* need to specify new amplification next time */ + pamp[0] = 0; + pamp[1] = 0; + + /* need to set new compressor parameters next time */ + init_compressor(pvp); + + return (voss_httpd_start(ptr)); +} + +static void +virtual_pipe(int sig __unused) +{ + voss_dsp_tx_refresh = 1; + voss_dsp_rx_refresh = 1; +} + +static void +virtual_cuse_hup(int sig __unused) +{ + atomic_wakeup(); +} + +static void * +virtual_cuse_process(void *arg __unused) +{ + signal(SIGHUP, &virtual_cuse_hup); + + while (1) { + if (cuse_wait_and_process() != 0) + break; + } + return (NULL); +} + +static void +virtual_cuse_init_profile(struct virtual_profile *pvp) +{ + memset(pvp, 0, sizeof(*pvp)); + + init_compressor(pvp); + init_mapping(pvp); +} + +static void +virtual_sig_exit(int sig __unused) +{ + voss_exit = 1; +} + +static const char * +parse_options(int narg, char **pparg, int is_main) +{ + const char *ptr; + int a, b, c; + int val; + int idx; + int type; + int opt_mute[2] = {0, 0}; + int opt_amp[2] = {0, 0}; + int opt_pol = 0; + const char *optstr; + struct virtual_profile profile; + struct rtprio rtp; + float samples_ms; + + if (is_main) + optstr = "N:J:k:H:o:F:G:w:e:p:a:C:c:r:b:f:g:x:i:m:M:d:l:L:s:t:h?O:P:Q:R:SBD:E:"; + else + optstr = "F:G:w:e:p:a:c:b:f:m:M:d:l:L:s:O:P:R:E:"; + + virtual_cuse_init_profile(&profile); + + /* reset getopt parsing */ + optreset = 1; + optind = 1; + + while ((c = getopt(narg, pparg, optstr)) != -1) { + switch (c) { + case 'B': + voss_do_background = 1; + break; + case 'D': + voss_pid_path = optarg; + break; + case 'C': + if (voss_mix_channels != 0) { + return ("The -C argument may only be used once"); + } + voss_mix_channels = atoi(optarg); + if (voss_mix_channels >= VMAX_CHAN) { + return ("Number of mixing channels is too high"); + } + break; + case 'a': + switch (optarg[0]) { + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + opt_amp[0] = -(opt_amp[1] = atoi(optarg)); + break; + case 'i': + if (optarg[1] != ',') + return ("Expected comma after 'i'"); + opt_amp[0] = atoi(optarg + 2); + break; + case 'o': + if (optarg[1] != ',') + return ("Expected comma after 'o'"); + opt_amp[1] = atoi(optarg + 2); + break; + default: + return ("Invalid syntax for amplitude argument"); + } + break; + case 'E': + voss_is_recording = (atoi(optarg) != 0); + break; + case 'e': + idx = 0; + ptr = optarg; + memset(opt_mute, 0, sizeof(opt_mute)); + while (1) { + c = *ptr++; + if (c == ',' || c == 0) { + idx++; + if (c == 0) + break; + continue; + } + if (idx < 2 && c >= '0' && c <= '1') { + opt_mute[idx] = c - '0'; + } else { + return ("Invalid -e parameter"); + } + } + switch (idx) { + case 1: + opt_mute[1] = opt_mute[0]; + break; + case 2: + break; + default: + return ("Invalid -e parameter"); + } + break; + case 'p': + opt_pol = atoi(optarg); + break; + case 'c': + profile.channels = atoi(optarg); + if (profile.channels == 0) + return ("Number of channels is zero"); + if (profile.channels > VMAX_CHAN) + return ("Number of channels is too high"); + break; + case 'r': + voss_dsp_sample_rate = atoi(optarg); + if (voss_dsp_sample_rate < 8000) + return ("Sample rate is too low, 8000 Hz"); + if (voss_dsp_sample_rate > 0xFFFFFF) + return ("Sample rate is too high"); + break; + case 'i': + memset(&rtp, 0, sizeof(rtp)); + rtp.type = RTP_PRIO_REALTIME; + rtp.prio = atoi(optarg); + if (rtprio(RTP_SET, getpid(), &rtp) != 0) + printf("Cannot set realtime priority\n"); + break; + case 'b': + profile.bits = atoi(optarg); + switch (profile.bits) { + case 8: + case 16: + case 24: + case 32: + break; + default: + return ("Invalid number of sample bits"); + } + break; + case 'g': + if (profile.rx_compressor_param.enabled) + return ("Compressor already enabled for this device"); + if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 || + a < VIRTUAL_OSS_KNEE_MIN || + a > VIRTUAL_OSS_KNEE_MAX || + b < VIRTUAL_OSS_ATTACK_MIN || + b > VIRTUAL_OSS_ATTACK_MAX || + c < VIRTUAL_OSS_DECAY_MIN || + c > VIRTUAL_OSS_DECAY_MAX) + return ("Invalid device compressor argument(s)"); + profile.rx_compressor_param.enabled = 1; + profile.rx_compressor_param.knee = a; + profile.rx_compressor_param.attack = b; + profile.rx_compressor_param.decay = c; + break; + case 'x': + if (voss_output_compressor_param.enabled) + return ("Compressor already enabled for output"); + if (sscanf(optarg, "%d,%d,%d", &a, &b, &c) != 3 || + a < VIRTUAL_OSS_KNEE_MIN || + a > VIRTUAL_OSS_KNEE_MAX || + b < VIRTUAL_OSS_ATTACK_MIN || + b > VIRTUAL_OSS_ATTACK_MAX || + c < VIRTUAL_OSS_DECAY_MIN || + c > VIRTUAL_OSS_DECAY_MAX) + return ("Invalid output compressor argument(s)"); + voss_output_compressor_param.enabled = 1; + voss_output_compressor_param.knee = a; + voss_output_compressor_param.attack = b; + voss_output_compressor_param.decay = c; + break; + case 'f': + case 'O': + case 'P': + case 'R': + if (voss_dsp_sample_rate == 0 || voss_dsp_samples == 0) + return ("Missing -r or -s parameters"); + if (voss_dsp_bits == 0) { + if (profile.bits == 0) + return ("Missing -b parameter"); + voss_dsp_bits = profile.bits; + } + if (voss_dsp_max_channels == 0) { + if (profile.channels == 0) + return ("Missing -c parameter"); + voss_dsp_max_channels = profile.channels; + } + if (c == 'f' || c == 'R') { + if (strlen(optarg) > VMAX_STRING - 1) + return ("Device name too long"); + strncpy(voss_dsp_rx_device, optarg, sizeof(voss_dsp_rx_device)); + voss_rx_backend_refresh(); + voss_dsp_rx_refresh = 1; + } + if (c == 'f' || c == 'P' || c == 'O') { + if (strlen(optarg) > VMAX_STRING - 1) + return ("Device name too long"); + strncpy(voss_dsp_tx_device, optarg, sizeof(voss_dsp_tx_device)); + voss_tx_backend_refresh(); + voss_dsp_tx_refresh = 1; + + if (c == 'O' && voss_has_synchronization == 0) + voss_has_synchronization++; + } + break; + case 'w': + if (strlen(optarg) > VMAX_STRING - 1) + return ("Device name too long"); + strncpy(profile.wav_name, optarg, sizeof(profile.wav_name)); + break; + case 'd': + if (strlen(optarg) > VMAX_STRING - 1) + return ("Device name too long"); + strncpy(profile.oss_name, optarg, sizeof(profile.oss_name)); + + if (profile.bits == 0 || voss_dsp_sample_rate == 0 || + profile.channels == 0 || voss_dsp_samples == 0) + return ("Missing -b, -r, -c or -s parameters"); + + val = (voss_dsp_samples * + profile.bits * profile.channels) / 8; + if (val <= 0 || val >= (1024 * 1024)) + return ("-s option value is too big"); + + ptr = dup_profile(&profile, opt_amp, opt_pol, + opt_mute[0], opt_mute[1], 0, 1); + if (ptr != NULL) + return (ptr); + break; + case 'L': + case 'l': + if (strlen(optarg) > VMAX_STRING - 1) + return ("Device name too long"); + strncpy(profile.oss_name, optarg, sizeof(profile.oss_name)); + + if (profile.bits == 0 || voss_dsp_sample_rate == 0 || + profile.channels == 0 || voss_dsp_samples == 0) + return ("Missing -b, -r, -r or -s parameters"); + + val = (voss_dsp_samples * + profile.bits * profile.channels) / 8; + if (val <= 0 || val >= (1024 * 1024)) + return ("-s option value is too big"); + + ptr = dup_profile(&profile, opt_amp, opt_pol, + opt_mute[0], opt_mute[1], c == 'L', 0); + if (ptr != NULL) + return (ptr); + break; + case 'S': + voss_libsamplerate_enable = 1; + break; + case 'Q': + c = atoi(optarg); + switch (c) { + case 0: + voss_libsamplerate_quality = SRC_SINC_BEST_QUALITY; + break; + case 1: + voss_libsamplerate_quality = SRC_SINC_MEDIUM_QUALITY; + break; + default: + voss_libsamplerate_quality = SRC_SINC_FASTEST; + break; + } + break; + case 's': + if (voss_dsp_samples != 0) + return ("-s option may only be used once"); + if (profile.bits == 0 || profile.channels == 0) + return ("-s option requires -b and -c options"); + if (strlen(optarg) > 2 && + sscanf(optarg, "%f", &samples_ms) == 1 && + strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { + if (voss_dsp_sample_rate == 0) + return ("-s ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-s ms option has invalid value"); + voss_dsp_samples = voss_dsp_sample_rate * samples_ms / 1000.0; + } else { + voss_dsp_samples = atoi(optarg); + } + if (voss_dsp_samples >= (1U << 24)) + return ("-s option requires a non-zero positive value"); + break; + case 't': + if (voss_ctl_device[0]) + return ("-t parameter may only be used once"); + + strncpy(voss_ctl_device, optarg, sizeof(voss_ctl_device)); + break; + case 'm': + ptr = optarg; + val = 0; + idx = 0; + init_mapping(&profile); + while (1) { + c = *ptr++; + if (c == ',' || c == 0) { + if (idx >= (2 * VMAX_CHAN)) + return ("Too many channels in mask"); + if (idx & 1) + profile.tx_dst[idx / 2] = val; + else + profile.rx_src[idx / 2] = val; + if (c == 0) + break; + val = 0; + idx++; + continue; + } + if (c >= '0' && c <= '9') { + val *= 10; + val += c - '0'; + } + } + break; + case 'M': + ptr = optarg; + type = *ptr; + if (type == 'i' || type == 'o' || type == 'x') { + vmonitor_t *pvm; + + int src = 0; + int dst = 0; + int pol = 0; + int mute = 0; + int amp = 0; + int neg; + + ptr++; + if (*ptr == ',') + ptr++; + else if (type == 'i') + return ("Expected comma after 'i'"); + else if (type == 'o') + return ("Expected comma after 'o'"); + else + return ("Expected comma after 'x'"); + + val = 0; + neg = 0; + idx = 0; + while (1) { + c = *ptr++; + if (c == '-') { + neg = 1; + continue; + } + if (c == ',' || c == 0) { + switch (idx) { + case 0: + src = val; + break; + case 1: + dst = val; + break; + case 2: + pol = val ? 1 : 0; + break; + case 3: + mute = val ? 1 : 0; + break; + case 4: + if (val > 31) { + return ("Absolute amplitude " + "for -M parameter " + "cannot exceed 31"); + } + amp = neg ? -val : val; + break; + default: + break; + } + if (c == 0) + break; + val = 0; + neg = 0; + idx++; + continue; + } + if (c >= '0' && c <= '9') { + val *= 10; + val += c - '0'; + } + } + if (idx < 4) + return ("Too few parameters for -M"); + + pvm = vmonitor_alloc(&idx, + (type == 'i') ? &virtual_monitor_input : + (type == 'x') ? &virtual_monitor_local : + &virtual_monitor_output); + + if (pvm == NULL) + return ("Out of memory"); + + pvm->src_chan = src; + pvm->dst_chan = dst; + pvm->pol = pol; + pvm->mute = mute; + pvm->shift = amp; + } else { + return ("Invalid -M parameter"); + } + break; + case 'F': + if (strlen(optarg) > 2 && + sscanf(optarg, "%f", &samples_ms) == 1 && + strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { + if (voss_dsp_sample_rate == 0) + return ("-F ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-F ms option has invalid value"); + profile.rx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0; + } else { + profile.rx_filter_size = atoi(optarg); + } + /* make value power of two */ + while ((profile.rx_filter_size - 1) & profile.rx_filter_size) + profile.rx_filter_size += ~(profile.rx_filter_size - 1) & profile.rx_filter_size; + /* range check */ + if (profile.rx_filter_size > VIRTUAL_OSS_FILTER_MAX) + return ("Invalid -F parameter is out of range"); + break; + case 'G': + if (strlen(optarg) > 2 && + sscanf(optarg, "%f", &samples_ms) == 1 && + strcmp(optarg + strlen(optarg) - 2, "ms") == 0) { + if (voss_dsp_sample_rate == 0) + return ("-G ms option requires -r option"); + if (samples_ms < 0.125 || samples_ms >= 1000.0) + return ("-G ms option has invalid value"); + profile.tx_filter_size = voss_dsp_sample_rate * samples_ms / 1000.0; + } else { + profile.tx_filter_size = atoi(optarg); + } + /* make value power of two */ + while ((profile.tx_filter_size - 1) & profile.tx_filter_size) + profile.tx_filter_size += ~(profile.tx_filter_size - 1) & profile.tx_filter_size; + /* range check */ + if (profile.tx_filter_size > VIRTUAL_OSS_FILTER_MAX) + return ("Invalid -F parameter is out of range"); + break; + case 'N': + profile.http.nstate = atoi(optarg); + break; + case 'H': + profile.http.host = optarg; + if (profile.http.port == NULL) + profile.http.port = "80"; + if (profile.http.nstate == 0) + profile.http.nstate = 1; + break; + case 'o': + profile.http.port = optarg; + break; + case 'J': + profile.http.rtp_ifname = optarg; + if (profile.http.rtp_port == NULL) + profile.http.rtp_port = "8080"; + break; + case 'k': + profile.http.rtp_port = optarg; + break; + default: + if (is_main) + usage(); + else + return ("Invalid option detected"); + break; + } + } + return (NULL); +} + +static void +create_threads(void) +{ + int idx; + + /* Give each DSP device 4 threads */ + voss_ntds = voss_dups * 4; + voss_tds = malloc(voss_ntds * sizeof(pthread_t)); + if (voss_tds == NULL) + err(1, "malloc"); + + for (idx = 0; idx < voss_ntds; idx++) { + if (pthread_create(&voss_tds[idx], NULL, &virtual_cuse_process, + NULL) != 0) + err(1, "pthread_create"); + } + + /* Reset until next time called */ + voss_dups = 0; +} + +static void +destroy_threads(void) +{ + int idx; + + for (idx = 0; idx < voss_ntds; idx++) + pthread_cancel(voss_tds[idx]); + free(voss_tds); +} + +void +voss_add_options(char *str) +{ + static char name[] = { "virtual_oss" }; + const char sep[] = "\t "; + const char *ptrerr; + char *parg[64]; + char *word; + char *brkt; + int narg = 0; + + parg[narg++] = name; + + for (word = strtok_r(str, sep, &brkt); word != NULL; + word = strtok_r(NULL, sep, &brkt)) { + if (narg >= 64) { + ptrerr = "Too many arguments"; + goto done; + } + parg[narg++] = word; + } + ptrerr = parse_options(narg, parg, 0); +done: + if (ptrerr != NULL) { + strlcpy(str, ptrerr, VIRTUAL_OSS_OPTIONS_MAX); + } else { + str[0] = 0; + create_threads(); + } +} + +int +main(int argc, char **argv) +{ + const char *ptrerr; + struct sigaction sa; + struct cuse_dev *pdev; + + TAILQ_INIT(&virtual_profile_client_head); + TAILQ_INIT(&virtual_profile_loopback_head); + + TAILQ_INIT(&virtual_monitor_input); + TAILQ_INIT(&virtual_monitor_output); + TAILQ_INIT(&virtual_monitor_local); + + atomic_init(); + + /* automagically load the cuse.ko module, if any */ + if (feature_present("cuse") == 0) { + if (system("kldload cuse") == -1) + warn("Failed to kldload cuse"); + } + + if (cuse_init() != 0) + errx(EX_USAGE, "Could not connect to cuse module"); + + signal(SIGPIPE, &virtual_pipe); + + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_handler = virtual_sig_exit; + if (sigaction(SIGINT, &sa, NULL) < 0) + err(1, "sigaction(SIGINT)"); + if (sigaction(SIGTERM, &sa, NULL) < 0) + err(1, "sigaction(SIGTERM)"); + + ptrerr = parse_options(argc, argv, 1); + if (ptrerr != NULL) + errx(EX_USAGE, "%s", ptrerr); + + if (voss_dsp_rx_device[0] == 0 || voss_dsp_tx_device[0] == 0) + errx(EX_USAGE, "Missing -f argument"); + + /* use DSP channels as default */ + if (voss_mix_channels == 0) + voss_mix_channels = voss_dsp_max_channels; + + if (voss_mix_channels > voss_dsp_max_channels) + voss_max_channels = voss_mix_channels; + else + voss_max_channels = voss_dsp_max_channels; + + if (voss_dsp_samples > (voss_dsp_sample_rate / 4)) + errx(EX_USAGE, "Too many buffer samples given by -s argument"); + + /* check if daemon mode is requested */ + if (voss_do_background != 0 && daemon(0, 0) != 0) + errx(EX_SOFTWARE, "Cannot become daemon"); + + if (voss_pid_path != NULL) { + int pidfile = open(voss_pid_path, O_RDWR | O_CREAT | O_TRUNC, 0600); + pid_t mypid = getpid(); + char mypidstr[8]; + snprintf(mypidstr, sizeof(mypidstr), "%d\n", mypid); + if (pidfile < 0) + errx(EX_SOFTWARE, "Cannot create PID file '%s'", voss_pid_path); + if (write(pidfile, mypidstr, strlen(mypidstr)) != + (ssize_t)strlen(mypidstr)) + errx(EX_SOFTWARE, "Cannot write PID file"); + close(pidfile); + } + + /* setup audio delay unit */ + voss_ad_init(voss_dsp_sample_rate); + + /* Create CTL device */ + + if (voss_ctl_device[0] != 0) { + pdev = cuse_dev_create(&vctl_methods, NULL, NULL, + 0, 0, voss_dsp_perm, voss_ctl_device); + if (pdev == NULL) + errx(EX_USAGE, "Could not create '/dev/%s'", voss_ctl_device); + + voss_dups++; + } + + /* Create worker threads */ + create_threads(); + + /* Run DSP threads */ + + virtual_oss_process(NULL); + + destroy_threads(); + + if (voss_ctl_device[0] != 0) + cuse_dev_destroy(pdev); + + return (0); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/mul.c b/usr.sbin/virtual_oss/virtual_oss/mul.c new file mode 100644 index 000000000000..76cb570eef74 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/mul.c @@ -0,0 +1,175 @@ +/*- + * Copyright (c) 2017 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include + +#include "int.h" + +#ifndef VOSS_X3_LOG2_COMBA +#define VOSS_X3_LOG2_COMBA 5 +#endif + +#if (VOSS_X3_LOG2_COMBA < 2) +#error "VOSS_X3_LOG2_COMBA must be greater than 1" +#endif + +struct voss_x3_input_double { + double a; + double b; +} __aligned(16); + +/* + * = "stride" + * = 2 * "stride" + */ +static void +voss_x3_multiply_sub_double(struct voss_x3_input_double *input, double *ptr_low, double *ptr_high, + const size_t stride, const uint8_t toggle) +{ + size_t x; + size_t y; + + if (stride >= (1UL << VOSS_X3_LOG2_COMBA)) { + const size_t strideh = stride >> 1; + + if (toggle) { + + /* inverse step */ + for (x = 0; x != strideh; x++) { + double a, b, c, d; + + a = ptr_low[x]; + b = ptr_low[x + strideh]; + c = ptr_high[x]; + d = ptr_high[x + strideh]; + + ptr_low[x + strideh] = a + b; + ptr_high[x] = a + b + c + d; + } + + voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 1); + + for (x = 0; x != strideh; x++) + ptr_low[x + strideh] = -ptr_low[x + strideh]; + + voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 1); + + /* forward step */ + for (x = 0; x != strideh; x++) { + double a, b, c, d; + + a = ptr_low[x]; + b = ptr_low[x + strideh]; + c = ptr_high[x]; + d = ptr_high[x + strideh]; + + ptr_low[x + strideh] = -a - b; + ptr_high[x] = c + b - d; + + input[x + strideh].a += input[x].a; + input[x + strideh].b += input[x].b; + } + + voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 0); + } else { + voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high, strideh, 1); + + /* inverse step */ + for (x = 0; x != strideh; x++) { + double a, b, c, d; + + a = ptr_low[x]; + b = ptr_low[x + strideh]; + c = ptr_high[x]; + d = ptr_high[x + strideh]; + + ptr_low[x + strideh] = -a - b; + ptr_high[x] = a + b + c + d; + + input[x + strideh].a -= input[x].a; + input[x + strideh].b -= input[x].b; + } + + voss_x3_multiply_sub_double(input + strideh, ptr_low + strideh, ptr_high + strideh, strideh, 0); + + for (x = 0; x != strideh; x++) + ptr_low[x + strideh] = -ptr_low[x + strideh]; + + voss_x3_multiply_sub_double(input, ptr_low, ptr_low + strideh, strideh, 0); + + /* forward step */ + for (x = 0; x != strideh; x++) { + double a, b, c, d; + + a = ptr_low[x]; + b = ptr_low[x + strideh]; + c = ptr_high[x]; + d = ptr_high[x + strideh]; + + ptr_low[x + strideh] = b - a; + ptr_high[x] = c - b - d; + } + } + } else { + for (x = 0; x != stride; x++) { + double value = input[x].a; + + for (y = 0; y != (stride - x); y++) { + ptr_low[x + y] += input[y].b * value; + } + + for (; y != stride; y++) { + ptr_high[x + y - stride] += input[y].b * value; + } + } + } +} + +/* + * = "max" + * = 2 * "max" + */ +void +voss_x3_multiply_double(const int64_t *va, const double *vb, double *pc, const size_t max) +{ + struct voss_x3_input_double input[max]; + size_t x; + + /* check for non-power of two */ + if (max & (max - 1)) + return; + + /* setup input vector */ + for (x = 0; x != max; x++) { + input[x].a = va[x]; + input[x].b = vb[x]; + } + + /* do multiplication */ + voss_x3_multiply_sub_double(input, pc, pc + max, max, 1); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/ring.c b/usr.sbin/virtual_oss/virtual_oss/ring.c new file mode 100644 index 000000000000..3c97bdfc2e84 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/ring.c @@ -0,0 +1,213 @@ +/*- + * Copyright (c) 2018 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "int.h" + +int +vring_alloc(struct virtual_ring *pvr, size_t size) +{ + + if (pvr->buf_start != NULL) + return (EBUSY); + pvr->buf_start = malloc(size); + if (pvr->buf_start == NULL) + return (ENOMEM); + pvr->pos_read = 0; + pvr->total_size = size; + pvr->len_write = 0; + return (0); +} + +void +vring_free(struct virtual_ring *pvr) +{ + + if (pvr->buf_start != NULL) { + free(pvr->buf_start); + pvr->buf_start = NULL; + } +} + +void +vring_reset(struct virtual_ring *pvr) +{ + pvr->pos_read = 0; + pvr->len_write = 0; +} + +void +vring_get_read(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen) +{ + uint32_t delta; + + if (pvr->buf_start == NULL) { + *pptr = NULL; + *plen = 0; + return; + } + delta = pvr->total_size - pvr->pos_read; + if (delta > pvr->len_write) + delta = pvr->len_write; + + *pptr = pvr->buf_start + pvr->pos_read; + *plen = delta; +} + +void +vring_get_write(struct virtual_ring *pvr, uint8_t **pptr, size_t *plen) +{ + uint32_t delta; + uint32_t len_read; + uint32_t pos_write; + + if (pvr->buf_start == NULL) { + *pptr = NULL; + *plen = 0; + return; + } + pos_write = pvr->pos_read + pvr->len_write; + if (pos_write >= pvr->total_size) + pos_write -= pvr->total_size; + + len_read = pvr->total_size - pvr->len_write; + + delta = pvr->total_size - pos_write; + if (delta > len_read) + delta = len_read; + + *pptr = pvr->buf_start + pos_write; + *plen = delta; +} + +void +vring_inc_read(struct virtual_ring *pvr, size_t len) +{ + + pvr->pos_read += len; + pvr->len_write -= len; + + /* check for wrap-around */ + if (pvr->pos_read == pvr->total_size) + pvr->pos_read = 0; +} + +void +vring_inc_write(struct virtual_ring *pvr, size_t len) +{ + + pvr->len_write += len; +} + +size_t +vring_total_read_len(struct virtual_ring *pvr) +{ + + return (pvr->len_write); +} + +size_t +vring_total_write_len(struct virtual_ring *pvr) +{ + + return (pvr->total_size - pvr->len_write); +} + +size_t +vring_write_linear(struct virtual_ring *pvr, const uint8_t *src, size_t total) +{ + uint8_t *buf_ptr; + size_t buf_len; + size_t sum = 0; + + while (total != 0) { + vring_get_write(pvr, &buf_ptr, &buf_len); + if (buf_len == 0) + break; + if (buf_len > total) + buf_len = total; + memcpy(buf_ptr, src, buf_len); + vring_inc_write(pvr, buf_len); + src += buf_len; + sum += buf_len; + total -= buf_len; + } + return (sum); +} + +size_t +vring_read_linear(struct virtual_ring *pvr, uint8_t *dst, size_t total) +{ + uint8_t *buf_ptr; + size_t buf_len; + size_t sum = 0; + + if (total > vring_total_read_len(pvr)) + return (0); + + while (total != 0) { + vring_get_read(pvr, &buf_ptr, &buf_len); + if (buf_len == 0) + break; + if (buf_len > total) + buf_len = total; + memcpy(dst, buf_ptr, buf_len); + vring_inc_read(pvr, buf_len); + dst += buf_len; + sum += buf_len; + total -= buf_len; + } + return (sum); +} + +size_t +vring_write_zero(struct virtual_ring *pvr, size_t total) +{ + uint8_t *buf_ptr; + size_t buf_len; + size_t sum = 0; + + while (total != 0) { + vring_get_write(pvr, &buf_ptr, &buf_len); + if (buf_len == 0) + break; + if (buf_len > total) + buf_len = total; + memset(buf_ptr, 0, buf_len); + vring_inc_write(pvr, buf_len); + sum += buf_len; + total -= buf_len; + } + return (sum); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/utils.h b/usr.sbin/virtual_oss/virtual_oss/utils.h new file mode 100644 index 000000000000..f0998dc75dae --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/utils.h @@ -0,0 +1,31 @@ +/*- + * Copyright (c) 2019 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _VIRTUAL_UTILS_H_ +#define _VIRTUAL_UTILS_H_ + +int bt_speaker_main(int argc, char **argv); + +#endif /* _VIRTUAL_UTILS_H_ */ diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 new file mode 100644 index 000000000000..6aa9f1289b35 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 @@ -0,0 +1,355 @@ +.\" +.\" Copyright (c) 2017-2022 Hans Petter Selasky +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" +.Dd September 3, 2024 +.Dt VIRTUAL_OSS 8 +.Os +.Sh NAME +.Nm virtual_oss +.Nd daemon to multiplex and demultiplex an OSS device +.Sh SYNOPSIS +.Nm +.Op Fl h +.Sh DESCRIPTION +.Nm +is an audio mixing application that multiplexes and demultiplexes a +single OSS device into multiple customizable OSS compatible devices +using character devices from userspace. +These devices can be used to record played back audio and mix the individual +channels in multiple ways. +.Pp +.Nm +requires the +.Xr cuse 3 +kernel module. +To load the driver as a module at boot time, place the following line in +.Xr loader.conf 5 : +.Pp + cuse_load="YES" +.Pp +All channel numbers start at zero. +Left channel is zero and right channel is one. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl B +Run program in background. +.It Fl S +Enable automatic DSP rate resampling. +.It Fl Q Ar quality +Set resampling quality: 0=best, 1=medium and 2=fastest (default). +.It Fl b Ar bits +Set sample depth to +.Fa bits +for the subsequent commands. +Valid values are 8, 16, 24 and 32. +.It Fl r Ar rate +Set default sample-rate for the subsequent commands. +.It Fl s Ar value +Set default buffer size to +.Fa value . +If the argument is suffixed by "ms" it is interpreted as milliseconds. +Else the argument gives number of samples. +The buffer size specified is per channel. +If there are multiple channels, the total buffer size will be larger. +.It Fl i Ar priority +Set real-time priority to +.Fa priority . +Refer to +.Xr rtprio 1 +for more information. +.It Fl a Ar log2_amp +Set the default DSP output and input device amplification to +.Fa log2_amp . +The specified amplification is logarithmic. +Valid values range from -63 to 63 inclusivly. +The device input amplification gets set to minus +.Fa log2_amp +and the device output amplification gets set to +.Fa log2_amp . +.It Fl a Ar i,log2_amp +Set the default DSP input device amplification to +.Fa log2_amp . +The specified amplification is logarithmic. +Valid values range from -63 to 63 inclusivly. +.It Fl a Ar o,log2_amp +Set default DSP output device amplification to +.Fa log2_amp . +The specified amplification is logarithmic. +Valid values range from -63 to 63 inclusivly. +.It Fl p Ar polarity +Set default polarity of DSP device. +A value of zero means normal polarity. +A value of one means negative polarity. +.It Fl e Ar rx_mute,tx_mute +Set default mute state of DSP device. +A value of zero means unmuted. +A value of one means muted. +.It Fl m Ar rx_ch,tx_ch,.... +Set default channel mapping of DSP device, as a comma separated list of +integers. +The first integer selects the receive channel, the second value selects the +transmit channel and then it repeats. +A value of zero indicates the first receive or transmit channel. +.It Fl C Ar num +Set the maximum number of mix channels to +.Fa num . +.It Fl c Ar num +Set mix channels for the subsequent commands. +.It Fl M Ar type,src_ch,dst_ch,pol,mute,log2_gain +Add a monitoring filter. +The filter consists of a list of comma separated arguments. +The first argument indicates the type of monitoring filter: +.Bl -tag -width indent +.It i +Feedback one mix input channel into another mix output channel, for remote +feedback. +.It o +Add one mix output channel into another mix output channel, for creating a mix +of multiple output channels. +.It x +Feedback one mix output channel into another mix input channel, for local +feedback. +.El +The second argument gives the source mix channel. +The third argument gives the destination mix channel. +The fourth argument gives the polarity, default is zero. +The fifth argument gives the mute state, default is one or muted. +The sixth argument gives the amplitude, default is zero or no gain. +.It Fl t Ar devname +Set control device name. +.It Fl P Ar devname +Set playback DSP device only. +Specifying /dev/null is magic and means no playback device. +Specifying a +.Xr sndio 7 +device descriptor prefixed by "/dev/sndio/" is also magic, and will use a sndio +backend rather than an OSS device. +.It Fl O Ar devname +Set playback DSP device only which acts as a master device. +This option is used in conjunction with -R /dev/null . +.It Fl R Ar devname +Set recording DSP device only. +Specifying /dev/null is magic and means no recording device. +.It Fl f Ar devname +Set both playback and recording DSP device +.It Fl w Ar name +Create a WAV file format compatible companion device by given name. +This option should be specified before the -d and -l options. +.It Fl d Ar name +Create an OSS device by given name. +.It Fl l Ar name +Create a loopback OSS device by given name. +.It Fl L Ar name +Create a loopback OSS device which acts as a master device. +This option is used in conjunction with -f /dev/null . +.It Fl F Ar size +Set receive filter size in number of samples or ms for the next +device to be created. +.It Fl G Ar size +Set transmit filter size in number of samples or ms for the next +device to be created. +.It Fl D Ar file +Write process ID of virtual_oss to file. +.It Fl g Ar knee,attack,decay +Enable device compressor in receive direction. +See description of -x option. +.It Fl x Ar knee,attack,decay +Enable output compressor and set knee, attack and decay. +Knee is in the range 0..255, while attack and decay are between 0 and 62. +Samples having an absolute value lower than the knee are transmitted +unchanged. +Sample values over the knee are lowered "a little bit". +You can think about attack and decay as a measure of how fast or slow the +gain of the compressor will work. +It is advised that attack is low, so it reacts fast once too high +sample values appear. +It is also advised that the decay value is higher than the attack value so +that the gain reduction is gradually removed. +The reasoning behind this is that the compressor should react almost +immediately when high volume signals arrive to protect the hardware, +but it slowly changes gain when there are no loud signals to avoid +distorting the signal. +The default values are 85,3,20 . +.It Fl E Ar enable_recording +If the value passed is non-zero, recording is enabled. +Else recording is disabled. +This can be used to synchronize multiple recording streams. +.It Fl h +Show usage and all available options. +.El +.Sh EXAMPLES +Split a 2-channel OSS compatible sound device into multiple subdevices: +.Bd -literal -offset indent +virtual_oss \\ + -S \\ + -c 2 -r 48000 -b 16 -s 4ms -f /dev/dspX \\ + -a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.zyn \\ + -a 0 -b 16 -c 2 -m 0,0,1,1 -d vdsp.fld \\ + -a 0 -b 16 -c 2 -m 0,0,1,1 -d dsp \\ + -a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.jack.wav -d vdsp.jack \\ + -a 0 -b 16 -c 2 -m 0,0,1,1 -w vdsp.rec.wav -l vdsp.rec \\ + -M i,0,0,0,1,0 \\ + -M i,0,0,0,1,0 \\ + -M i,0,0,0,1,0 \\ + -M i,0,0,0,1,0 \\ + -t vdsp.ctl +.Ed +.Pp +Split an 8-channel 24-bit OSS compatible sound device into multiple subdevices: +.Bd -literal -offset indent +sysctl dev.pcm.X.rec.vchanformat=s24le:7.1 +sysctl dev.pcm.X.rec.vchanrate=48000 +sysctl dev.pcm.X.play.vchanformat=s24le:7.1 +sysctl dev.pcm.X.play.vchanrate=48000 +sysctl dev.pcm.X.bitperfect=1 + +mixer vol.volume=1 pcm.volume=1 + +virtual_oss \\ + -S \\ + -i 8 \\ + -x 85,3,20 \\ + -C 16 -c 8 -r 48000 -b 32 -s 4ms -f /dev/dspX \\ + -a 12 -b 16 -c 2 -m 0,4,1,5 -d dsp \\ + -a 12 -b 16 -c 2 -m 8,8,9,9 -d vdsp \\ + -a 13 -b 16 -c 2 -m 10,10,11,11 -d vdsp.fld \\ + -a 0 -b 32 -c 4 -m 4,2,5,3,6,4,7,5 -d vdsp.jack \\ + -a -3 -b 32 -c 2 -m 14,14,15,15 -d vdsp.zyn \\ + -e 0,1 \\ + -a 0 -b 32 -c 8 -m 0,8,1,9,2,8,3,9,4,8,5,9,6,8,7,9 -w vdsp.rec.mic.wav -d vdsp.rec.mic \\ + -a 0 -b 32 -c 2 -m 0,8,1,9 -w vdsp.rec.master.wav -d vdsp.master.mic \\ + -a 0 -b 32 -c 2 -m 10,10,11,11 -w vdsp.rec.fld.wav -l vdsp.rec.fld \\ + -a 0 -b 32 -c 2 -m 12,12,13,13 -w vdsp.rec.jack.wav -l vdsp.rec.jack \\ + -a 0 -b 32 -c 2 -m 14,14,15,15 -w vdsp.rec.zyn.wav -l vdsp.rec.zyn \\ + -M o,8,0,0,0,0 \\ + -M o,9,1,0,0,0 \\ + -M o,10,0,0,0,0 \\ + -M o,11,1,0,0,0 \\ + -M o,12,0,0,0,0 \\ + -M o,13,1,0,0,0 \\ + -M o,14,0,0,0,0 \\ + -M o,15,1,0,0,0 \\ + -M i,14,14,0,1,0 \\ + -M i,15,15,0,1,0 \\ + -M x,8,0,0,1,0 \\ + -M x,8,1,0,1,0 \\ + -t vdsp.ctl + +.Ed +.Pp +Create a secondary audio device sending its output audio into both +input and output channels of the main DSP device. +.Bd -literal -offset indent +virtual_oss \\ + -C 4 -c 2 \\ + -r 48000 \\ + -b 24 \\ + -s 4ms \\ + -f /dev/dsp3 \\ + -c 2 \\ + -d dsp \\ + -m 2,2,3,3 \\ + -d dsp.speech \\ + -M o,2,0,0,0,0 \\ + -M o,3,1,0,0,0 \\ + -M x,2,0,0,0,0 \\ + -M x,3,1,0,0,0 +.Ed +.Pp +Connect to a bluetooth audio headset, playback only: +.Bd -literal -offset indent +virtual_oss \\ + -C 2 -c 2 -r 48000 -b 16 -s 4ms \\ + -R /dev/null -P /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp +.Ed +.Pp +Connect to a bluetooth audio headset, playback and recording: +.Bd -literal -offset indent +virtual_oss \\ + -C 2 -c 2 -r 48000 -b 16 -s 4ms \\ + -f /dev/bluetooth/xx:xx:xx:xx:xx:xx -d dsp +.Ed +.Pp +Create recording device which outputs a WAV-formatted file: +.Bd -literal -offset indent +virtual_oss \\ + -C 2 -c 2 -r 48000 -b 16 -s 4ms \\ + -f /dev/dspX -w dsp.wav -d dsp +.Ed +.Pp +Create a device named dsp.virtual which mix the samples written by all +clients and outputs the result for further processing into +dsp.virtual_out: +.Bd -literal -offset indent +virtual_oss \\ + -S -Q 0 -b 16 -c 2 -r 96000 -s 100ms -i 20 \\ + -f /dev/null -d dsp.virtual -L dsp.virtual_out +.Ed +.Pp +Create a playback-only audio device which sends its output to a remote +.Xr sndio 7 +server: +.Bd -literal -offset indent +virtual_oss \\ + -b 16 -c 2 -r 44100 -s 50ms \\ + -R /dev/null -O /dev/sndio/snd@remotehost/0 -d dsp +.Ed +.Pp +Create a full-duplex audio device exchanging audio using the default +.Xr sndio 7 +server: +.Bd -literal -offset indent +virtual_oss -S -b 16 -C 2 -c 2 -r 48000 -s 4ms \\ + -f /dev/sndio/default -d dsp +.Ed +.Pp +How to set intel based CPUs in performance mode: +.Bd -literal -offset indent +sysctl -aN | fgrep dev.hwpstate | fgrep epp | \ +while read OID +do +sysctl ${OID}=0 +done + +sysctl kern.sched.preempt_thresh=224 +.Ed +.Sh NOTES +All character devices are created using the 0666 mode which gives +everyone in the system access. +.Sh SEE ALSO +.Xr cuse 3 , +.Xr sound 4 , +.Xr loader.conf 5 , +.Xr sndio 7 , +.Xr mixer 8 , +.Xr sysctl 8 , +.Xr virtual_bt_speaker 8 , +.Xr virtual_equalizer 8 , +.Xr virtual_oss_cmd 8 +.Sh AUTHORS +.Nm +was written by +.An Hans Petter Selasky hselasky@freebsd.org . diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c new file mode 100644 index 000000000000..891653494f06 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c @@ -0,0 +1,914 @@ +/*- + * Copyright (c) 2012-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "backend.h" +#include "int.h" + +uint64_t +virtual_oss_delay_ns(void) +{ + uint64_t delay; + + delay = voss_dsp_samples; + delay *= 1000000000ULL; + delay /= voss_dsp_sample_rate; + + return (delay); +} + +void +virtual_oss_wait(void) +{ + struct timespec ts; + uint64_t delay; + uint64_t nsec; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + + /* TODO use virtual_oss_delay_ns() */ + delay = voss_dsp_samples; + delay *= 1000000000ULL; + delay /= voss_dsp_sample_rate; + + usleep((delay - (nsec % delay)) / 1000); +} + +uint64_t +virtual_oss_timestamp(void) +{ + struct timespec ts; + uint64_t nsec; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + nsec = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + return (nsec); +} + +static size_t +vclient_read_linear(struct virtual_client *pvc, struct virtual_ring *pvr, + int64_t *dst, size_t total) __requires_exclusive(atomic_mtx) +{ + size_t total_read = 0; + + pvc->sync_busy = 1; + while (1) { + size_t read = vring_read_linear(pvr, (uint8_t *)dst, 8 * total) / 8; + + total_read += read; + dst += read; + total -= read; + + if (!pvc->profile->synchronized || pvc->sync_wakeup || + total == 0) { + /* fill rest of buffer with silence, if any */ + if (total_read != 0 && total != 0) + memset(dst, 0, 8 * total); + break; + } + atomic_wait(); + } + pvc->sync_busy = 0; + if (pvc->sync_wakeup) + atomic_wakeup(); + + vclient_tx_equalizer(pvc, dst - total_read, total_read); + + return (total_read); +} + +static size_t +vclient_write_linear(struct virtual_client *pvc, struct virtual_ring *pvr, + int64_t *src, size_t total) __requires_exclusive(atomic_mtx) +{ + size_t total_written = 0; + + vclient_rx_equalizer(pvc, src, total); + + pvc->sync_busy = 1; + while (1) { + size_t written = vring_write_linear(pvr, (uint8_t *)src, total * 8) / 8; + + total_written += written; + src += written; + total -= written; + + if (!pvc->profile->synchronized || pvc->sync_wakeup || + total == 0) + break; + atomic_wait(); + } + pvc->sync_busy = 0; + if (pvc->sync_wakeup) + atomic_wakeup(); + + return (total_written); +} + +static inline void +virtual_oss_mixer_core_sub(const int64_t *src, int64_t *dst, + uint32_t *pnoise, int src_chan, int dst_chan, int num, + int64_t volume, int shift, int shift_orig, bool pol, + bool assign) +{ + if (pol) + volume = -volume; + + if (shift < 0) { + shift = -shift; + while (num--) { + if (assign) + *dst = (*src * volume) >> shift; + else + *dst += (*src * volume) >> shift; + if (__predict_true(pnoise != NULL)) + *dst += vclient_noise(pnoise, volume, shift_orig); + src += src_chan; + dst += dst_chan; + } + } else { + while (num--) { + if (assign) + *dst = (*src * volume) << shift; + else + *dst += (*src * volume) << shift; + if (__predict_true(pnoise != NULL)) + *dst += vclient_noise(pnoise, volume, shift_orig); + src += src_chan; + dst += dst_chan; + } + } +} + +static inline void +virtual_oss_mixer_core(const int64_t *src, int64_t *dst, + uint32_t *pnoise, int src_chan, int dst_chan, int num, + int64_t volume, int shift, int shift_orig, bool pol, + bool assign) +{ + const uint8_t selector = (shift_orig > 0) + assign * 2; + + /* optimize some cases */ + switch (selector) { + case 0: + virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan, + num, volume, shift, shift_orig, pol, false); + break; + case 1: + virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan, + num, volume, shift, shift_orig, pol, false); + break; + case 2: + virtual_oss_mixer_core_sub(src, dst, NULL, src_chan, dst_chan, + num, volume, shift, shift_orig, pol, true); + break; + case 3: + virtual_oss_mixer_core_sub(src, dst, pnoise, src_chan, dst_chan, + num, volume, shift, shift_orig, pol, true); + break; + } +} + +void * +virtual_oss_process(void *arg __unused) +{ + vprofile_t *pvp; + vclient_t *pvc; + vmonitor_t *pvm; + struct voss_backend *rx_be = voss_rx_backend; + struct voss_backend *tx_be = voss_tx_backend; + int rx_fmt; + int tx_fmt; + int rx_chn; + int tx_chn; + int off; + int src_chans; + int dst_chans; + int src; + int len; + int samples; + int shift; + int shift_orig; + int shift_fmt; + int buffer_dsp_max_size; + int buffer_dsp_half_size; + int buffer_dsp_rx_sample_size; + int buffer_dsp_rx_size; + int buffer_dsp_tx_size_ref; + int buffer_dsp_tx_size; + uint64_t nice_timeout = 0; + uint64_t last_timestamp; + int blocks; + int volume; + int x_off; + int x; + int y; + + uint8_t *buffer_dsp; + int64_t *buffer_monitor; + int64_t *buffer_temp; + int64_t *buffer_data; + int64_t *buffer_local; + int64_t *buffer_orig; + + bool need_delay = false; + + buffer_dsp_max_size = voss_dsp_samples * + voss_dsp_max_channels * (voss_dsp_bits / 8); + buffer_dsp_half_size = (voss_dsp_samples / 2) * + voss_dsp_max_channels * (voss_dsp_bits / 8); + + buffer_dsp = malloc(buffer_dsp_max_size); + buffer_temp = malloc(voss_dsp_samples * voss_max_channels * 8); + buffer_monitor = malloc(voss_dsp_samples * voss_max_channels * 8); + buffer_local = malloc(voss_dsp_samples * voss_max_channels * 8); + buffer_data = malloc(voss_dsp_samples * voss_max_channels * 8); + buffer_orig = malloc(voss_dsp_samples * voss_max_channels * 8); + + if (buffer_dsp == NULL || buffer_temp == NULL || + buffer_monitor == NULL || buffer_local == NULL || + buffer_data == NULL || buffer_orig == NULL) + errx(1, "Cannot allocate buffer memory"); + + while (1) { + rx_be->close(rx_be); + tx_be->close(tx_be); + + if (voss_exit) + break; + if (need_delay) + sleep(2); + + voss_dsp_rx_refresh = 0; + voss_dsp_tx_refresh = 0; + + rx_be = voss_rx_backend; + tx_be = voss_tx_backend; + + switch (voss_dsp_bits) { + case 8: + rx_fmt = tx_fmt = + AFMT_S8 | AFMT_U8; + break; + case 16: + rx_fmt = tx_fmt = + AFMT_S16_BE | AFMT_S16_LE | + AFMT_U16_BE | AFMT_U16_LE; + break; + case 24: + rx_fmt = tx_fmt = + AFMT_S24_BE | AFMT_S24_LE | + AFMT_U24_BE | AFMT_U24_LE; + break; + case 32: + rx_fmt = tx_fmt = + AFMT_S32_BE | AFMT_S32_LE | + AFMT_U32_BE | AFMT_U32_LE | + AFMT_F32_BE | AFMT_F32_LE; + break; + default: + rx_fmt = tx_fmt = 0; + break; + } + + rx_chn = voss_dsp_max_channels; + + if (rx_be->open(rx_be, voss_dsp_rx_device, voss_dsp_sample_rate, + buffer_dsp_half_size, &rx_chn, &rx_fmt) < 0) { + need_delay = true; + continue; + } + + buffer_dsp_rx_sample_size = rx_chn * (voss_dsp_bits / 8); + buffer_dsp_rx_size = voss_dsp_samples * buffer_dsp_rx_sample_size; + + tx_chn = voss_dsp_max_channels; + if (tx_be->open(tx_be, voss_dsp_tx_device, voss_dsp_sample_rate, + buffer_dsp_max_size, &tx_chn, &tx_fmt) < 0) { + need_delay = true; + continue; + } + + buffer_dsp_tx_size_ref = voss_dsp_samples * + tx_chn * (voss_dsp_bits / 8); + + /* reset compressor gain */ + for (x = 0; x != VMAX_CHAN; x++) + voss_output_compressor_gain[x] = 1.0; + + /* reset local buffer */ + memset(buffer_local, 0, 8 * voss_dsp_samples * voss_max_channels); + + while (1) { + uint64_t delta_time; + + /* Check if DSP device should be re-opened */ + if (voss_exit) + break; + if (voss_dsp_rx_refresh || voss_dsp_tx_refresh) { + need_delay = false; + break; + } + delta_time = nice_timeout - virtual_oss_timestamp(); + + /* Don't service more than 2x sample rate */ + nice_timeout = virtual_oss_delay_ns() / 2; + if (delta_time >= 1000 && delta_time <= nice_timeout) { + /* convert from ns to us */ + usleep(delta_time / 1000); + } + /* Compute next timeout */ + nice_timeout += virtual_oss_timestamp(); + + /* Read in samples */ + len = rx_be->transfer(rx_be, buffer_dsp, buffer_dsp_rx_size); + if (len < 0 || (len % buffer_dsp_rx_sample_size) != 0) { + need_delay = true; + break; + } + if (len == 0) + continue; + + /* Convert to 64-bit samples */ + format_import(rx_fmt, buffer_dsp, len, buffer_data); + + samples = len / buffer_dsp_rx_sample_size; + src_chans = voss_mix_channels; + + /* Compute master input peak values */ + format_maximum(buffer_data, voss_input_peak, rx_chn, samples, 0); + + /* Remix format */ + format_remix(buffer_data, rx_chn, src_chans, samples); + + /* Refresh timestamp */ + last_timestamp = virtual_oss_timestamp(); + + atomic_lock(); + + if (TAILQ_FIRST(&virtual_monitor_input) != NULL) { + /* make a copy of the input data, in case of remote monitoring */ + memcpy(buffer_monitor, buffer_data, 8 * samples * src_chans); + } + + /* (0) Check for local monitoring of output data */ + + TAILQ_FOREACH(pvm, &virtual_monitor_local, entry) { + + int64_t val; + + if (pvm->mute != 0 || pvm->src_chan >= src_chans || + pvm->dst_chan >= src_chans) + continue; + + src = pvm->src_chan; + shift = pvm->shift; + x = pvm->dst_chan; + + if (pvm->pol) { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = -(buffer_local[(y * src_chans) + src] >> shift); + buffer_data[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = -(buffer_local[(y * src_chans) + src] << shift); + buffer_data[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } else { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = (buffer_local[(y * src_chans) + src] >> shift); + buffer_data[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = (buffer_local[(y * src_chans) + src] << shift); + buffer_data[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } + } + + /* make a copy of the input data */ + memcpy(buffer_orig, buffer_data, 8 * samples * src_chans); + + /* (1) Distribute input samples to all clients */ + + TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) { + + if (TAILQ_FIRST(&pvp->head) == NULL) + continue; + + /* check if compressor should be applied */ + voss_compressor(buffer_data, pvp->rx_compressor_gain, + &pvp->rx_compressor_param, samples * src_chans, + src_chans, (1ULL << (pvp->bits - 1)) - 1ULL); + + TAILQ_FOREACH(pvc, &pvp->head, entry) { + + dst_chans = pvc->channels; + + if (dst_chans > (int)voss_max_channels) + continue; + + shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8); + + for (x = 0; x != dst_chans; x++) { + src = pvp->rx_src[x]; + shift_orig = pvp->rx_shift[x] - shift_fmt; + shift = shift_orig - VVOLUME_UNIT_SHIFT; + volume = pvc->rx_volume; + + if (pvp->rx_mute[x] || src >= src_chans || volume == 0) { + for (y = 0; y != (samples * dst_chans); y += dst_chans) + buffer_temp[y + x] = 0; + continue; + } + + virtual_oss_mixer_core(buffer_data + src, buffer_temp + x, + &pvc->rx_noise_rem, src_chans, dst_chans, samples, + volume, shift, shift_orig, pvp->rx_pol[x], true); + } + + format_maximum(buffer_temp, pvp->rx_peak_value, + dst_chans, samples, shift_fmt); + + /* check if recording is disabled */ + if (pvc->rx_enabled == 0 || + (voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT)) + continue; + + pvc->rx_timestamp = last_timestamp; + pvc->rx_samples += samples * dst_chans; + + /* store data into ring buffer */ + vclient_write_linear(pvc, &pvc->rx_ring[0], + buffer_temp, samples * dst_chans); + } + + /* restore buffer, if any */ + if (pvp->rx_compressor_param.enabled) + memcpy(buffer_data, buffer_orig, 8 * samples * src_chans); + } + + /* fill main output buffer with silence */ + + memset(buffer_temp, 0, sizeof(buffer_temp[0]) * + samples * src_chans); + + /* (2) Run audio delay locator */ + + if (voss_ad_enabled != 0) { + y = (samples * voss_mix_channels); + for (x = 0; x != y; x += voss_mix_channels) { + buffer_temp[x + voss_ad_output_channel] += + voss_ad_getput_sample(buffer_data + [x + voss_ad_input_channel]); + } + } + + /* (3) Load output samples from all clients */ + + TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) { + TAILQ_FOREACH(pvc, &pvp->head, entry) { + + if (pvc->tx_enabled == 0) + continue; + + dst_chans = pvc->channels; + + if (dst_chans > (int)voss_max_channels) + continue; + + /* update counters regardless of data presence */ + pvc->tx_timestamp = last_timestamp; + pvc->tx_samples += samples * dst_chans; + + /* read data from ring buffer */ + if (vclient_read_linear(pvc, &pvc->tx_ring[0], + buffer_data, samples * dst_chans) == 0) + continue; + + shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8); + + format_maximum(buffer_data, pvp->tx_peak_value, + dst_chans, samples, shift_fmt); + + for (x = 0; x != pvp->channels; x++) { + src = pvp->tx_dst[x]; + shift_orig = pvp->tx_shift[x] + shift_fmt; + shift = shift_orig - VVOLUME_UNIT_SHIFT; + volume = pvc->tx_volume; + + if (pvp->tx_mute[x] || src >= src_chans || volume == 0) + continue; + + /* + * Automagically re-map + * channels when the client is + * requesting fewer channels + * than specified in the + * profile. This typically + * allows automagic mono to + * stereo conversion. + */ + if (__predict_false(x >= dst_chans)) + x_off = x % dst_chans; + else + x_off = x; + + virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src, + &pvc->tx_noise_rem, dst_chans, src_chans, samples, + volume, shift, shift_orig, pvp->tx_pol[x], false); + } + } + } + + /* (4) Load output samples from all loopbacks */ + + TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) { + TAILQ_FOREACH(pvc, &pvp->head, entry) { + + if (pvc->tx_enabled == 0) + continue; + + dst_chans = pvc->channels; + + if (dst_chans > (int)voss_max_channels) + continue; + + /* read data from ring buffer */ + if (vclient_read_linear(pvc, &pvc->tx_ring[0], + buffer_data, samples * dst_chans) == 0) + continue; + + pvc->tx_timestamp = last_timestamp; + pvc->tx_samples += samples * dst_chans; + + shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8); + + format_maximum(buffer_data, pvp->tx_peak_value, + dst_chans, samples, shift_fmt); + + for (x = 0; x != pvp->channels; x++) { + src = pvp->tx_dst[x]; + shift_orig = pvp->tx_shift[x] + shift_fmt; + shift = shift_orig - VVOLUME_UNIT_SHIFT; + volume = pvc->tx_volume; + + if (pvp->tx_mute[x] || src >= src_chans || volume == 0) + continue; + + /* + * Automagically re-map + * channels when the client is + * requesting fewer channels + * than specified in the + * profile. This typically + * allows automagic mono to + * stereo conversion. + */ + if (__predict_false(x >= dst_chans)) + x_off = x % dst_chans; + else + x_off = x; + + virtual_oss_mixer_core(buffer_data + x_off, buffer_temp + src, + &pvc->tx_noise_rem, dst_chans, src_chans, samples, + volume, shift, shift_orig, pvp->tx_pol[x], false); + } + } + } + + /* (5) Check for input monitoring */ + + TAILQ_FOREACH(pvm, &virtual_monitor_input, entry) { + + int64_t val; + + if (pvm->mute != 0 || pvm->src_chan >= src_chans || + pvm->dst_chan >= src_chans) + continue; + + src = pvm->src_chan; + shift = pvm->shift; + x = pvm->dst_chan; + + if (pvm->pol) { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = -(buffer_monitor[(y * src_chans) + src] >> shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = -(buffer_monitor[(y * src_chans) + src] << shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } else { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = (buffer_monitor[(y * src_chans) + src] >> shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = (buffer_monitor[(y * src_chans) + src] << shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } + } + + if (TAILQ_FIRST(&virtual_monitor_output) != NULL) { + memcpy(buffer_monitor, buffer_temp, + 8 * samples * src_chans); + } + + /* (6) Check for output monitoring */ + + TAILQ_FOREACH(pvm, &virtual_monitor_output, entry) { + + int64_t val; + + if (pvm->mute != 0 || pvm->src_chan >= src_chans || + pvm->dst_chan >= src_chans) + continue; + + src = pvm->src_chan; + shift = pvm->shift; + x = pvm->dst_chan; + + if (pvm->pol) { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = -(buffer_monitor[(y * src_chans) + src] >> shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = -(buffer_monitor[(y * src_chans) + src] << shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } else { + if (shift < 0) { + shift = -shift; + for (y = 0; y != samples; y++) { + val = (buffer_monitor[(y * src_chans) + src] >> shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } else { + for (y = 0; y != samples; y++) { + val = (buffer_monitor[(y * src_chans) + src] << shift); + buffer_temp[(y * src_chans) + x] += val; + if (val < 0) + val = -val; + if (val > pvm->peak_value) + pvm->peak_value = val; + } + } + } + } + + /* make a copy of the output data */ + memcpy(buffer_data, buffer_temp, 8 * samples * src_chans); + + /* make a copy for local monitoring, if any */ + if (TAILQ_FIRST(&virtual_monitor_local) != NULL) { + const int end = src_chans * (voss_dsp_samples - samples); + const int offs = src_chans * samples; + + assert(end >= 0); + + /* shift down samples */ + for (int xx = 0; xx != end; xx++) + buffer_local[xx] = buffer_local[xx + offs]; + /* copy in new ones */ + memcpy(buffer_local + end, buffer_temp, 8 * samples * src_chans); + } + + /* (7) Check for output recording */ + + TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) { + + if (TAILQ_FIRST(&pvp->head) == NULL) + continue; + + /* check if compressor should be applied */ + voss_compressor(buffer_temp, pvp->rx_compressor_gain, + &pvp->rx_compressor_param, samples, + samples * src_chans, (1ULL << (pvp->bits - 1)) - 1ULL); + + TAILQ_FOREACH(pvc, &pvp->head, entry) { + + dst_chans = pvc->channels; + + if (dst_chans > (int)voss_max_channels) + continue; + + shift_fmt = pvp->bits - (vclient_sample_bytes(pvc) * 8); + + for (x = 0; x != dst_chans; x++) { + src = pvp->rx_src[x]; + shift_orig = pvp->rx_shift[x] - shift_fmt; + shift = shift_orig - VVOLUME_UNIT_SHIFT; + volume = pvc->rx_volume; + + if (pvp->rx_mute[x] || src >= src_chans || volume == 0) { + for (y = 0; y != (samples * dst_chans); y += dst_chans) + buffer_monitor[y + x] = 0; + continue; + } + + virtual_oss_mixer_core(buffer_temp + src, buffer_monitor + x, + &pvc->rx_noise_rem, src_chans, dst_chans, samples, + volume, shift, shift_orig, pvp->rx_pol[x], true); + } + + format_maximum(buffer_monitor, pvp->rx_peak_value, + dst_chans, samples, shift_fmt); + + /* check if recording is disabled */ + if (pvc->rx_enabled == 0 || + (voss_is_recording == 0 && pvc->type != VTYPE_OSS_DAT)) + continue; + + pvc->rx_timestamp = last_timestamp; + pvc->rx_samples += samples * dst_chans; + + /* store data into ring buffer */ + vclient_write_linear(pvc, &pvc->rx_ring[0], + buffer_monitor, samples * dst_chans); + } + + /* restore buffer, if any */ + if (pvp->rx_compressor_param.enabled) + memcpy(buffer_temp, buffer_data, 8 * samples * src_chans); + } + + atomic_wakeup(); + + format_remix(buffer_temp, voss_mix_channels, tx_chn, samples); + + /* Compute master output peak values */ + + format_maximum(buffer_temp, voss_output_peak, + tx_chn, samples, 0); + + /* Apply compressor, if any */ + + voss_compressor(buffer_temp, voss_output_compressor_gain, + &voss_output_compressor_param, samples * tx_chn, + tx_chn, format_max(tx_fmt)); + + /* Recompute buffer DSP transmit size according to received number of samples */ + + buffer_dsp_tx_size = samples * tx_chn * (voss_dsp_bits / 8); + + /* Export and transmit resulting audio */ + + format_export(tx_fmt, buffer_temp, buffer_dsp, + buffer_dsp_tx_size); + + atomic_unlock(); + + /* Get output delay in bytes */ + tx_be->delay(tx_be, &blocks); + + /* + * Simple fix for jitter: Repeat data when too + * little. Skip data when too much. This + * should not happen during normal operation. + */ + if (blocks == 0) { + blocks = 2; /* buffer is empty */ + voss_jitter_up++; + } else if (blocks >= (3 * buffer_dsp_tx_size_ref)) { + blocks = 0; /* too much data */ + voss_jitter_down++; + } else { + blocks = 1; /* normal */ + } + + len = 0; + while (blocks--) { + off = 0; + while (off < (int)buffer_dsp_tx_size) { + len = tx_be->transfer(tx_be, buffer_dsp + off, + buffer_dsp_tx_size - off); + if (len <= 0) + break; + off += len; + } + if (len <= 0) + break; + } + + /* check for error only */ + if (len < 0) { + need_delay = true; + break; + } + } + } + + free(buffer_dsp); + free(buffer_temp); + free(buffer_monitor); + free(buffer_local); + free(buffer_data); + free(buffer_orig); + + return (NULL); +} diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h new file mode 100644 index 000000000000..616de2e1abd0 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.h @@ -0,0 +1,206 @@ +/*- + * Copyright (c) 2012-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _VIRTUAL_OSS_H_ +#define _VIRTUAL_OSS_H_ + +#include + +#define VIRTUAL_OSS_NAME_MAX 32 +#define VIRTUAL_OSS_VERSION 0x00010008 +#define VIRTUAL_OSS_OPTIONS_MAX 1024 /* bytes */ +#define VIRTUAL_OSS_FILTER_MAX 65536 /* samples */ + +#define VIRTUAL_OSS_GET_VERSION _IOR('O', 0, int) + +struct virtual_oss_io_info { + int number; /* must be first */ + int channel; + char name[VIRTUAL_OSS_NAME_MAX]; + int bits; + int rx_amp; + int tx_amp; + int rx_chan; + int tx_chan; + int rx_mute; + int tx_mute; + int rx_pol; + int tx_pol; + int rx_delay; /* in samples */ + int rx_delay_limit; /* in samples */ +}; + +#define VIRTUAL_OSS_GET_DEV_INFO _IOWR('O', 1, struct virtual_oss_io_info) +#define VIRTUAL_OSS_SET_DEV_INFO _IOW('O', 2, struct virtual_oss_io_info) + +#define VIRTUAL_OSS_GET_LOOP_INFO _IOWR('O', 3, struct virtual_oss_io_info) +#define VIRTUAL_OSS_SET_LOOP_INFO _IOW('O', 4, struct virtual_oss_io_info) + +struct virtual_oss_mon_info { + int number; + int bits; + int src_chan; + int dst_chan; + int pol; + int mute; + int amp; +}; + +#define VIRTUAL_OSS_GET_INPUT_MON_INFO _IOWR('O', 5, struct virtual_oss_mon_info) +#define VIRTUAL_OSS_SET_INPUT_MON_INFO _IOW('O', 6, struct virtual_oss_mon_info) + +#define VIRTUAL_OSS_GET_OUTPUT_MON_INFO _IOWR('O', 7, struct virtual_oss_mon_info) +#define VIRTUAL_OSS_SET_OUTPUT_MON_INFO _IOW('O', 8, struct virtual_oss_mon_info) + +#define VIRTUAL_OSS_GET_LOCAL_MON_INFO _IOWR('O', 43, struct virtual_oss_mon_info) +#define VIRTUAL_OSS_SET_LOCAL_MON_INFO _IOW('O', 44, struct virtual_oss_mon_info) + +struct virtual_oss_io_peak { + int number; /* must be first */ + int channel; + char name[VIRTUAL_OSS_NAME_MAX]; + int bits; + long long rx_peak_value; + long long tx_peak_value; +}; + +#define VIRTUAL_OSS_GET_DEV_PEAK _IOWR('O', 9, struct virtual_oss_io_peak) +#define VIRTUAL_OSS_GET_LOOP_PEAK _IOWR('O', 10, struct virtual_oss_io_peak) + +struct virtual_oss_mon_peak { + int number; + int bits; + long long peak_value; +}; + +#define VIRTUAL_OSS_GET_INPUT_MON_PEAK _IOWR('O', 11, struct virtual_oss_mon_peak) +#define VIRTUAL_OSS_GET_OUTPUT_MON_PEAK _IOWR('O', 12, struct virtual_oss_mon_peak) +#define VIRTUAL_OSS_GET_LOCAL_MON_PEAK _IOWR('O', 45, struct virtual_oss_mon_peak) + +#define VIRTUAL_OSS_ADD_INPUT_MON _IOR('O', 13, int) +#define VIRTUAL_OSS_ADD_OUTPUT_MON _IOR('O', 14, int) +#define VIRTUAL_OSS_ADD_LOCAL_MON _IOR('O', 46, int) + +struct virtual_oss_compressor { + int enabled; + int knee; +#define VIRTUAL_OSS_KNEE_MAX 255 /* inclusive */ +#define VIRTUAL_OSS_KNEE_MIN 0 + int attack; +#define VIRTUAL_OSS_ATTACK_MAX 62 /* inclusive */ +#define VIRTUAL_OSS_ATTACK_MIN 0 + int decay; +#define VIRTUAL_OSS_DECAY_MAX 62 /* inclusive */ +#define VIRTUAL_OSS_DECAY_MIN 0 + int gain; /* read only */ +#define VIRTUAL_OSS_GAIN_MAX 1000 /* inclusive */ +#define VIRTUAL_OSS_GAIN_MIN 0 +}; + +#define VIRTUAL_OSS_SET_OUTPUT_LIMIT _IOW('O', 17, struct virtual_oss_compressor) +#define VIRTUAL_OSS_GET_OUTPUT_LIMIT _IOWR('O', 18, struct virtual_oss_compressor) + +struct virtual_oss_io_limit { + int number; /* must be first */ + struct virtual_oss_compressor param; +}; + +#define VIRTUAL_OSS_SET_DEV_LIMIT _IOW('O', 19, struct virtual_oss_io_limit) +#define VIRTUAL_OSS_GET_DEV_LIMIT _IOWR('O', 20, struct virtual_oss_io_limit) + +#define VIRTUAL_OSS_SET_LOOP_LIMIT _IOW('O', 21, struct virtual_oss_io_limit) +#define VIRTUAL_OSS_GET_LOOP_LIMIT _IOWR('O', 22, struct virtual_oss_io_limit) + +struct virtual_oss_master_peak { + int channel; + int bits; + long long peak_value; +}; + +#define VIRTUAL_OSS_GET_OUTPUT_PEAK _IOWR('O', 23, struct virtual_oss_master_peak) +#define VIRTUAL_OSS_GET_INPUT_PEAK _IOWR('O', 24, struct virtual_oss_master_peak) + +#define VIRTUAL_OSS_SET_RECORDING _IOW('O', 25, int) +#define VIRTUAL_OSS_GET_RECORDING _IOR('O', 26, int) + +struct virtual_oss_audio_delay_locator { + int channel_output; + int channel_input; + int channel_last; + int signal_output_level; /* 2**n */ + int signal_input_delay; /* in samples, roundtrip */ + int signal_delay_hz; /* in samples, HZ */ + int locator_enabled; +}; + +#define VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR _IOW('O', 27, struct virtual_oss_audio_delay_locator) +#define VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR _IOR('O', 28, struct virtual_oss_audio_delay_locator) +#define VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR _IO('O', 29) + +struct virtual_oss_midi_delay_locator { + int channel_output; + int channel_input; + int signal_delay; + int signal_delay_hz; /* in samples, HZ */ + int locator_enabled; +}; + +#define VIRTUAL_OSS_SET_MIDI_DELAY_LOCATOR _IOW('O', 30, struct virtual_oss_midi_delay_locator) +#define VIRTUAL_OSS_GET_MIDI_DELAY_LOCATOR _IOR('O', 31, struct virtual_oss_midi_delay_locator) +#define VIRTUAL_OSS_RST_MIDI_DELAY_LOCATOR _IO('O', 32) + +#define VIRTUAL_OSS_ADD_OPTIONS _IOWR('O', 33, char [VIRTUAL_OSS_OPTIONS_MAX]) + +struct virtual_oss_fir_filter { + int number; /* must be first */ + int channel; + int filter_size; + double *filter_data; +}; + +#define VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER _IOWR('O', 34, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER _IOWR('O', 35, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER _IOWR('O', 36, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER _IOWR('O', 37, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER _IOWR('O', 38, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER _IOWR('O', 39, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER _IOWR('O', 40, struct virtual_oss_fir_filter) +#define VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER _IOWR('O', 41, struct virtual_oss_fir_filter) + +#define VIRTUAL_OSS_GET_SAMPLE_RATE _IOR('O', 42, int) + +struct virtual_oss_system_info { + unsigned tx_jitter_up; + unsigned tx_jitter_down; + unsigned sample_rate; + unsigned sample_bits; + unsigned sample_channels; + char rx_device_name[64]; + char tx_device_name[64]; +}; + +#define VIRTUAL_OSS_GET_SYSTEM_INFO _IOR('O', 43, struct virtual_oss_system_info) + +#endif /* _VIRTUAL_OSS_H_ */ diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile b/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile new file mode 100644 index 000000000000..b209d3dca068 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss_cmd/Makefile @@ -0,0 +1,8 @@ +PROG= virtual_oss_cmd +MAN= ${PROG}.8 + +SRCS= command.c + +CFLAGS+= -I${SRCTOP}/usr.sbin/virtual_oss/virtual_oss + +.include diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/command.c b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c new file mode 100644 index 000000000000..64781992ddfd --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2021-2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virtual_oss.h" + +static void +message(const char *fmt, ...) +{ + va_list list; + + va_start(list, fmt); + vfprintf(stderr, fmt, list); + va_end(list); +} + +static void +usage(void) +{ + message("Usage: virtual_oss_cmd /dev/vdsp.ctl [command line arguments to pass to virtual_oss]\n"); + exit(EX_USAGE); +} + +int +main(int argc, char **argv) +{ + char options[VIRTUAL_OSS_OPTIONS_MAX] = {}; + size_t offset = 0; + size_t len = VIRTUAL_OSS_OPTIONS_MAX - 1; + int fd; + + /* check if no options */ + if (argc < 2) + usage(); + + fd = open(argv[1], O_RDWR); + if (fd < 0) + errx(EX_SOFTWARE, "Could not open '%s'", argv[1]); + + for (int x = 2; x != argc; x++) { + size_t tmp = strlen(argv[x]) + 1; + if (tmp > len) + errx(EX_SOFTWARE, "Too many options passed"); + memcpy(options + offset, argv[x], tmp); + options[offset + tmp - 1] = ' '; + offset += tmp; + len -= tmp; + } + + if (options[0] == 0) { + struct virtual_oss_system_info info; + if (ioctl(fd, VIRTUAL_OSS_GET_SYSTEM_INFO, &info) < 0) + errx(EX_SOFTWARE, "Cannot get system information"); + + info.rx_device_name[sizeof(info.rx_device_name) - 1] = 0; + info.tx_device_name[sizeof(info.tx_device_name) - 1] = 0; + + printf("Sample rate: %u Hz\n" + "Sample width: %u bits\n" + "Sample channels: %u\n" + "Output jitter: %u / %u\n" + "Input device name: %s\n" + "Output device name: %s\n", + info.sample_rate, + info.sample_bits, + info.sample_channels, + info.tx_jitter_down, + info.tx_jitter_up, + info.rx_device_name, + info.tx_device_name); + } else { + /* execute options */ + if (ioctl(fd, VIRTUAL_OSS_ADD_OPTIONS, options) < 0) + errx(EX_SOFTWARE, "One or more invalid options"); + /* show error, if any */ + if (options[0] != '\0') + errx(EX_SOFTWARE, "%s", options); + } + + close(fd); + return (0); +} diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8 b/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8 new file mode 100644 index 000000000000..a200d88a4a32 --- /dev/null +++ b/usr.sbin/virtual_oss/virtual_oss_cmd/virtual_oss_cmd.8 @@ -0,0 +1,103 @@ +.\" +.\" Copyright (c) 2021-2022 Hans Petter Selasky +.\" +.\" 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 .