diff options
author | IOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at> | 2022-03-07 19:33:37 +0100 |
---|---|---|
committer | IOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at> | 2022-03-07 19:33:37 +0100 |
commit | 98e24db30fdaf5b07d14b9f411bd835ecd96e5de (patch) | |
tree | eeaae853ee19dbe577d0189fa57a1128bbb6ebb8 | |
parent | 2eca06839daae7d78b8143b6c29ba51f4b93e2d1 (diff) | |
parent | e5e207088ba5998820287321b5b2e8a367405657 (diff) |
Update upstream source from tag 'upstream/1.5.2+ds0'
Update to upstream version '1.5.2+ds0'
with Debian dir 601b13054a74065ff4fd16c3c0853604e0dc7e81
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rwxr-xr-x | build | 7 | ||||
-rw-r--r-- | docs/Install.md | 2 | ||||
-rw-r--r-- | docs/changelog.yml | 16 | ||||
-rw-r--r-- | jacktrip.pro | 4 | ||||
-rwxr-xr-x | macos/assemble_app.sh | 87 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | src/JackTrip.cpp | 100 | ||||
-rw-r--r-- | src/JackTrip.h | 1 | ||||
-rw-r--r-- | src/PacketHeader.cpp | 2 | ||||
-rw-r--r-- | src/PoolBuffer.cpp | 522 | ||||
-rw-r--r-- | src/Regulator.cpp | 658 | ||||
-rw-r--r-- | src/Regulator.h (renamed from src/PoolBuffer.h) | 131 | ||||
-rw-r--r-- | src/UdpDataProtocol.cpp | 138 | ||||
-rw-r--r-- | src/gui/qjacktrip.cpp | 79 | ||||
-rw-r--r-- | src/gui/qjacktrip.h | 5 | ||||
-rw-r--r-- | src/gui/qjacktrip.ui | 236 | ||||
-rw-r--r-- | src/gui/qjacktrip_novs.ui | 184 | ||||
-rw-r--r-- | src/jacktrip_globals.cpp | 31 | ||||
-rw-r--r-- | src/jacktrip_globals.h | 2 | ||||
-rw-r--r-- | src/main.cpp | 13 |
21 files changed, 1255 insertions, 967 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 47f4c32..40cae87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ set(qjacktrip_SRC src/PacketHeader.cpp src/RingBuffer.cpp src/JitterBuffer.cpp - src/PoolBuffer.cpp + src/Regulator.cpp src/Compressor.cpp src/Limiter.cpp src/Reverb.cpp @@ -94,6 +94,10 @@ MCMD=make elif [[ $platform == 'macosx' ]]; then QCMD=qmake QSPEC=macx-clang + arch=`arch` + if [[ "$arch" == 'arm64' ]]; then + QSPEC=macx-clang-arm64 + fi # if qmake is not in path, try homebrew qt5 if ! command -v $QCMD &> /dev/null; then # echo "qmake not found in \$PATH, searching for homebrew qt5" @@ -144,6 +148,9 @@ fi mkdir -p builddir cd builddir +# exit if build commands fail +set -e + # Build echo "qmake command:" echo "$QCMD -spec $QSPEC $CONFIG $PRO_FILE" $UNKNOWN_OPTIONS diff --git a/docs/Install.md b/docs/Install.md index 7acdb37..f44fa5c 100644 --- a/docs/Install.md +++ b/docs/Install.md @@ -1,6 +1,6 @@ # Installation ## Linux -On Linux the easiest way to install JackTrip is to use the distribution's package manager. +On Linux the easiest way to install JackTrip is to use the distribution's package manager. However, this may not be the most up-to-date version. For the most recent version, go to the Github releases page below. === "Fedora" diff --git a/docs/changelog.yml b/docs/changelog.yml index 9935aeb..1db1e26 100644 --- a/docs/changelog.yml +++ b/docs/changelog.yml @@ -1,3 +1,19 @@ +- Version: "1.5.2" + Date: 2022-03-02 + Description: + - (update) RingBuffer replaced by Regulator for experimental buffer strategy 3 + - (update) call out old jacktrip versions in package repos on Debian and others + - (update) first attempt to support BSDs (especially FreeBSD) + - (update) add signing to macOS packaging script + - (update) Rename __MANUAL_POLL__ TO MANUAL_POLL + - (update) RtAudio warning changes + - (update) Use bundled rtaudio for Windows release builds + - (update) Incorporate meson build in the main GHA file + - (fixed) build script doesn't exit if build fails + - (fixed) false error message for JACK 1.9.20 on M1 builds + - (fixed) Don't connect our UDP socket + - (fixed) Fix GUI command line warning + - (fixed) Limiter allocation in GUI - Version: "1.5.1" Date: 2022-01-07 Description: diff --git a/jacktrip.pro b/jacktrip.pro index c811c80..3bb1cdd 100644 --- a/jacktrip.pro +++ b/jacktrip.pro @@ -198,7 +198,7 @@ HEADERS += src/DataProtocol.h \ src/Compressor.h \ src/CompressorPresets.h \ src/Limiter.h \ - src/PoolBuffer.h \ + src/Regulator.h \ src/Reverb.h \ src/AudioTester.h \ src/jacktrip_globals.h \ @@ -242,7 +242,7 @@ SOURCES += src/DataProtocol.cpp \ src/JackTrip.cpp \ src/Compressor.cpp \ src/Limiter.cpp \ - src/PoolBuffer.cpp \ + src/Regulator.cpp \ src/Reverb.cpp \ src/AudioTester.cpp \ src/jacktrip_globals.cpp \ diff --git a/macos/assemble_app.sh b/macos/assemble_app.sh index d52a3f8..aa7c4b4 100755 --- a/macos/assemble_app.sh +++ b/macos/assemble_app.sh @@ -3,9 +3,11 @@ APPNAME="JackTrip" BUNDLE_ID="org.jacktrip.jacktrip" BUILD_INSTALLER=false +NOTARIZE=false #If you're lazy like I am, you can pre-populate these variables to save you stuffing about with command line options. #CERTIFICATE="" +#PACKAGE_CERT="" #USERNAME="" #PASSWORD="" #ASC_PROVIDER="" @@ -13,14 +15,20 @@ BINARY="../builddir/jacktrip" OPTIND=1 -while getopts ":ihc:u:p:a:b:" opt; do +while getopts ":inhc:d:u:p:a:b:" opt; do case $opt in i) BUILD_INSTALLER=true ;; + n) + NOTARIZE=true + ;; c) CERTIFICATE=$OPTARG ;; + d) + PACKAGE_CERT=$OPTARG + ;; u) USERNAME=$OPTARG ;; @@ -40,17 +48,23 @@ while getopts ":ihc:u:p:a:b:" opt; do echo "JackTrip App Bundle assembly script." echo "Copyright (C) 2020-2021 Aaron Wyatt et al." echo "Relased under the GNU GPLv3 License." - echo "" - echo "Usage: ./assemble-app.sh [options]" - echo "" + echo + echo "Usage: ./assemble-app.sh [options] [appname] [bundlename]" + echo echo "Options:" echo " -b <filename> The binary file to be placed in the app bundle. (Defaults to ../builddir/jacktrip)" echo " -i Build an installer package as well. (Requires Packages to be installed.)" - echo " -c <certname> Name of the developer certificate to use for signing (No signing by default.)" + echo " -n Send a notarization request to Apple. (Only takes effect if building an installer.)" + echo " -c <certname> Name of the developer certificate to use for code signing. (No signing by default.)" + echo " -d <certname> Name of the certificate to use for package signing. (No signing by default.)" echo " -u <username> Apple ID username (email address) for installer notarization." echo " -p <password> App specific password for installer notarization." echo " -a <ascprovider> ASC provider for notarization. (Only required if you belong to multiple dev teams.)" echo " -h Display this help screen and exit." + echo + echo "By default, appname is set to JackTrip and bundlename is org.jacktrip.jacktrip." + echo "(These should be left as is for official builds.)" + exit 0 ;; :) @@ -107,7 +121,7 @@ if [ ! -z "$DYNAMIC_QT" ]; then fi fi -[ "$BUILD_INSTALLER" = true ] || exit 0 +[ $BUILD_INSTALLER = true ] || exit 0 # If you have Packages installed, you can build an installer for the newly created app bundle. [ -z $(which packagesbuild) ] && { echo "You need to have Packages installed to build a package."; exit 1; } @@ -128,7 +142,6 @@ cat ../LICENSES/LGPL-3.0-only.txt >> "$LICENSE_PATH" sed -i '' "s/# //" "$LICENSE_PATH" # remove markdown header perl -ane 'chop;print "\n\n" if(/^\s*$/); map{print "$_ ";}@F;' "$LICENSE_PATH" > tmp && mv tmp "$LICENSE_PATH" # unwrap lines - # prepare readme README_PATH="package/readme.txt" cp ../README.md "$README_PATH" @@ -141,21 +154,61 @@ sed -i '' "s/%BUNDLENAME%/$APPNAME/" package/JackTrip.pkgproj sed -i '' "s/%BUNDLEID%/$BUNDLE_ID/" package/JackTrip.pkgproj packagesbuild package/JackTrip.pkgproj +if pkgutil --check-signature package/build/JackTrip.pkg; then + echo "Package already signed." + SIGNED=true +else + if [ ! -z "$PACKAGE_CERT" ]; then + echo "Signing package." + if productsign --sign "$PACKAGE_CERT" package/build/JackTrip.pkg package/build/JackTrip-signed.pkg; then + mv package/build/JackTrip-signed.pkg package/build/JackTrip.pkg + SIGNED=true + else + echo "Unable to sign package." + exit 1 + fi + else + SIGNED=false + fi +fi + +[ $NOTARIZE = true ] || exit 0 -# Offer to submit a notarization request to apple if we have the required credentials. -if [ -z "$CERTIFICATE" ] || [ -z "$USERNAME" ] || [ -z "$PASSWORD" ]; then +# Submit a notarization request to apple if we have the required credentials. +if [ -z "$CERTIFICATE" ] || [ -z "$USERNAME" ] || [ -z "$PASSWORD" ] || [ $SIGNED = false ] ; then echo "Not sending notarization request: incomplete credentials." - exit 0 + exit 1 fi ASC="" if [ ! -z "$ASC_PROVIDER" ]; then - ASC=" --asc-provider \"$ASC_PROVIDER\"" + ASC=" --asc-provider $ASC_PROVIDER" fi +CREDENTIALS="--username $USERNAME --password $PASSWORD$ASC" -read -n1 -rsp "Press any key to submit a notarization request to apple..." -echo -xcrun altool --notarize-app --primary-bundle-id "$BUNDLE_ID" --username "$USERNAME" --password "$PASSWORD"$ASC --file "package/build/$APPNAME.pkg" -read -n1 -rsp "Press any key to staple the notarization once it's been approved..." -echo -xcrun stapler staple "package/build/$APPNAME.pkg" +echo "Sending notarization request" +UUID=$(xcrun altool --notarize-app --primary-bundle-id "$BUNDLE_ID" $CREDENTIALS --file "package/build/$APPNAME.pkg" | awk '/RequestUUID/{print $NF}') +if [ -z "$UUID" ]; then + echo "Error sending notarization request" + exit 1 +fi +echo "Package uploaded" +echo "Request UUID is $UUID" +echo "Awaiting response from Apple" +STATUS="in progress" +ELAPSED=0 +while [ "$STATUS" = "in progress" ]; do + sleep 60 + ((ELAPSED++)) + STATUS=$(xcrun altool --notarization-info "$UUID" $CREDENTIALS | awk '/Status:/{for(i = 2; i <= NF - 1; i++) printf $i" "; print $NF}') + echo "Waited $ELAPSED minute(s). Current status: $STATUS" +done +#read -n1 -rsp "Press any key to staple the notarization once it's been approved..." +#echo + +if [ "$STATUS" = "success" ]; then + xcrun stapler staple "package/build/$APPNAME.pkg" +else + echo "Notarization failed" + exit 1 +fi diff --git a/meson.build b/meson.build index 3de6dad..4293f56 100644 --- a/meson.build +++ b/meson.build @@ -27,7 +27,7 @@ src = [ 'src/DataProtocol.cpp', 'src/PacketHeader.cpp', 'src/RingBuffer.cpp', 'src/JitterBuffer.cpp', - 'src/PoolBuffer.cpp', + 'src/Regulator.cpp', 'src/Settings.cpp', 'src/UdpDataProtocol.cpp', 'src/UdpHubListener.cpp', diff --git a/src/JackTrip.cpp b/src/JackTrip.cpp index 661a87b..11df4a9 100644 --- a/src/JackTrip.cpp +++ b/src/JackTrip.cpp @@ -42,7 +42,7 @@ #endif #include "Auth.h" #include "JitterBuffer.h" -#include "PoolBuffer.h" +#include "Regulator.h" #include "RingBufferWavetable.h" #include "UdpDataProtocol.h" #include "jacktrip_globals.h" @@ -57,8 +57,10 @@ #include <QTimer> #include <QtEndian> #include <cstdlib> +#include <iomanip> #include <iostream> #include <stdexcept> +using std::setw; using std::cout; using std::endl; @@ -379,11 +381,9 @@ void JackTrip::setupRingBuffers() mSendRingBuffer = new RingBuffer(audio_input_slot_size, gDefaultOutputQueueLength); mReceiveRingBuffer = - new PoolBuffer(mSampleRate, mNumAudioChansIn, mAudioBitResolution, - mAudioBufferSize, mBufferQueueLength); - // connect(mReceiveRingBuffer, SIGNAL(print(QString)), this, - // SLOT(onStatTimer(QString))); connect(mReceiveRingBuffer, - // SIGNAL(printStats(QString)), this, SLOT(onStatTimer(QString))); + new Regulator(mSampleRate, mNumAudioChansIn, mAudioBitResolution, + mAudioBufferSize, mBufferQueueLength); + // bufStrategy 3, mBufferQueueLength is in integer msec not packets mPacketHeader->setBufferRequiresSameSettings(true); } else { @@ -629,22 +629,72 @@ void JackTrip::onStatTimer() if (!mAudioTesterP.isNull() && mAudioTesterP->getEnabled()) { mIOStatLogStream << "\n"; } - mIOStatLogStream << now.toLocal8Bit().constData() << " " - << getPeerAddress().toLocal8Bit().constData() - << " send: " << send_io_stat.underruns << "/" - << send_io_stat.overflows << " recv: " << recv_io_stat.underruns - << "/" << recv_io_stat.overflows << " prot: " << pkt_stat.lost << "/" - << pkt_stat.outOfOrder << "/" << pkt_stat.revived - << " tot: " << pkt_stat.tot << " sync: " << recv_io_stat.level << "/" - << recv_io_stat.buf_inc_underrun << "/" - << recv_io_stat.buf_inc_compensate << "/" - << recv_io_stat.buf_dec_overflows << "/" - << recv_io_stat.buf_dec_pktloss << " skew: " << recv_io_stat.skew - << "/" << recv_io_stat.skew_raw - << " bcast: " << recv_io_stat.broadcast_skew << "/" - << recv_io_stat.broadcast_delta - << " autoq: " << 0.1 * recv_io_stat.autoq_corr << "/" - << 0.1 * recv_io_stat.autoq_rate << endl; + if (getBufferStrategy() != 3) + mIOStatLogStream << now.toLocal8Bit().constData() << " " + << getPeerAddress().toLocal8Bit().constData() + << " send: " << send_io_stat.underruns << "/" + << send_io_stat.overflows << " recv: " << recv_io_stat.underruns + << "/" << recv_io_stat.overflows << " prot: " << pkt_stat.lost + << "/" << pkt_stat.outOfOrder << "/" << pkt_stat.revived + << " tot: " << pkt_stat.tot << " sync: " << recv_io_stat.level + << "/" << recv_io_stat.buf_inc_underrun << "/" + << recv_io_stat.buf_inc_compensate << "/" + << recv_io_stat.buf_dec_overflows << "/" + << recv_io_stat.buf_dec_pktloss << " skew: " << recv_io_stat.skew + << "/" << recv_io_stat.skew_raw + << " bcast: " << recv_io_stat.broadcast_skew << "/" + << recv_io_stat.broadcast_delta + << " autoq: " << 0.1 * recv_io_stat.autoq_corr << "/" + << 0.1 * recv_io_stat.autoq_rate << endl; + else { // bufstrategy 3 + mIOStatLogStream + << now.toLocal8Bit().constData() << " " + << getPeerAddress().toLocal8Bit().constData() + << " send: " << send_io_stat.underruns << "/" << send_io_stat.overflows + << " Pull underruns: " + << recv_io_stat.underruns // pullStat->lastPlcUnderruns; +#define INVFLOATFACTOR 0.001 + << "\nPUSH -- SD avg/last: " << setw(5) + << INVFLOATFACTOR * recv_io_stat.overflows // pushStat->longTermStdDev; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.buf_dec_overflows // pushStat->lastStdDev; + << " \t mean/min/max: " << setw(5) + << INVFLOATFACTOR * recv_io_stat.skew // pushStat->lastMean; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.skew_raw // pushStat->lastMin; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.level // pushStat->lastMax; + + << "\nPULL -- SD avg/last: " << setw(5) + << INVFLOATFACTOR * recv_io_stat.buf_dec_pktloss // pullStat->longTermStdDev; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.broadcast_delta // pullStat->lastStdDev; + << " \t mean/min/max: " << setw(5) + << INVFLOATFACTOR * recv_io_stat.buf_inc_underrun // pullStat->lastMean; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.buf_inc_compensate // pullStat->lastMin; + << " / " << setw(5) + << INVFLOATFACTOR * recv_io_stat.broadcast_skew // pullStat->lastMax; + + // << "/" << recv_io_stat.overflows << " prot: " << + // pkt_stat.lost << "/" + // << pkt_stat.outOfOrder << "/" << pkt_stat.revived + << " \n tot: " + << pkt_stat.tot + // << " sync: " << recv_io_stat.level << "/" + // << recv_io_stat.buf_inc_underrun << "/" + // << recv_io_stat.buf_inc_compensate << "/" + // << recv_io_stat.buf_dec_overflows << "/" + // << recv_io_stat.buf_dec_pktloss << " skew: " << + // recv_io_stat.skew + // << "/" << recv_io_stat.skew_raw + // << " bcast: " << recv_io_stat.broadcast_skew << "/" + // << recv_io_stat.broadcast_delta + // << " autoq: " << 0.1 * recv_io_stat.autoq_corr << "/" + // << 0.1 * recv_io_stat.autoq_rate + << "\n" + << endl; + } } void JackTrip::receivedConnectionTCP() @@ -985,7 +1035,9 @@ void JackTrip::tcpTimerTick() //******************************************************************************* void JackTrip::stop(const QString& errorMessage) { - mStopped = true; + // Take a snapshot of sJackStopped + bool serverStopped = sJackStopped; + mStopped = true; // Make sure we're only run once if (mHasShutdown) { return; @@ -1009,7 +1061,7 @@ void JackTrip::stop(const QString& errorMessage) cout << gPrintSeparator << endl; // Emit the jack stopped signal - if (sJackStopped) { + if (serverStopped) { emit signalError(QStringLiteral("The Jack Server was shut down!")); } else if (errorMessage.isEmpty()) { emit signalProcessesStopped(); diff --git a/src/JackTrip.h b/src/JackTrip.h index d2bde18..0c2ec25 100644 --- a/src/JackTrip.h +++ b/src/JackTrip.h @@ -306,6 +306,7 @@ class JackTrip : public QObject { mDataProtocolReceiver = DataProtocolReceiver; } + virtual int getBufferStrategy() const noexcept { return mBufferStrategy; } virtual RingBuffer* getSendRingBuffer() const { return mSendRingBuffer; } virtual RingBuffer* getReceiveRingBuffer() const { return mReceiveRingBuffer; } diff --git a/src/PacketHeader.cpp b/src/PacketHeader.cpp index 6653e01..cbd3bdf 100644 --- a/src/PacketHeader.cpp +++ b/src/PacketHeader.cpp @@ -37,7 +37,7 @@ #include "PacketHeader.h" -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 #include <sys/time.h> #endif diff --git a/src/PoolBuffer.cpp b/src/PoolBuffer.cpp deleted file mode 100644 index 343c284..0000000 --- a/src/PoolBuffer.cpp +++ /dev/null @@ -1,522 +0,0 @@ -//***************************************************************** -/* - JackTrip: A System for High-Quality Audio Network Performance - over the Internet - - Copyright (c) 2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. -*/ -//***************************************************************** - -/** - * \file PoolBuffer.cpp - * \author Chris Chafe - * \date May 2021 - */ - -// EXPERIMENTAL for testing in JackTrip v1.4.0 -// runs ok from FPP 16 up to 512, but don't try 1024 yet -// in / out channels are the same -- mono, stereo and -n3 tested fine - -// for example, server -// jacktrip -S --udprt -p1 --bufstrategy 3 -q2 -// and client -// jacktrip -C cmn9.stanford.edu --udprt --bufstrategy 3 -q2 - -#include "PoolBuffer.h" - -#include <iomanip> - -#include "jacktrip_globals.h" -using std::cout; -using std::endl; -using std::setw; - -#define STDDEVINDOW 200 // packets -#define STDDEV2POOLSIZE 30.0 -#define MAXPOOLSIZE 6000 // insanely large pool, 2 sec FPP16 -#define HIST 6 // at FPP32 -#define OUT(x, ch, s) sampleToBits(x, ch, s) -// from listening tests iteration performs better than '=' operator -#define PACKETSAMP (int s = 0; s < mFPP; s++) - -//******************************************************************************* -PoolBuffer::PoolBuffer(int /*sample_rate*/, int channels, int bit_res, int FPP, int qLen) - : RingBuffer(0, 0) - , mNumChannels(channels) - , mAudioBitRes(bit_res) - , mFPP(FPP) - , mPoolSize(qLen) - , mQlen(qLen) -{ - switch (mAudioBitRes) { // int from JitterBuffer to AudioInterface enum - case 1: - mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT8; - break; - case 2: - mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT16; - break; - case 3: - mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT24; - break; - case 4: - mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT32; - break; - } - mHist = 6 * 32; // samples, from original settings - double histFloat = mHist / (double)mFPP; // packets for other FPP - mHist = (int)histFloat; - if (!mHist) - mHist++; // min packets - else if (mHist > 6) - mHist = 6; // max packets - if (gVerboseFlag) - cout << "mHist = " << mHist << " at " << mFPP << "\n"; - // qDebug() << "mHist =" << mHist << "@" << mFPP; - mBytes = mFPP * mNumChannels * mBitResolutionMode; - mXfrBuffer = new int8_t[mBytes]; - mPacketCnt = 0; // burg - mFadeUp.resize(mFPP, 0.0); - mFadeDown.resize(mFPP, 0.0); - for (int i = 0; i < mFPP; i++) { - mFadeUp[i] = (double)i / (double)mFPP; - mFadeDown[i] = 1.0 - mFadeUp[i]; - } - mLastWasGlitch = false; - mOutgoingCnt = 0; - for (int i = 0; i < mPoolSize; i++) { - int8_t* tmp = new int8_t[mBytes]; - mIncomingDat.push_back(tmp); - } - mZeros = new int8_t[mBytes]; - for - PACKETSAMP OUT(0.0, 0, s); - for - PACKETSAMP OUT(0.0, 1, s); - memcpy(mZeros, mXfrBuffer, mBytes); - mIncomingCnt = 0; - mIndexPool.resize(mPoolSize); - for (int i = 0; i < mPoolSize; i++) - mIndexPool[i] = -1; - mTimer0 = new QElapsedTimer(); - mTimer0->start(); - mGlitchCnt = 0; - mGlitchMax = mHist * 2 * mFPP; // tested with 400 @ FPP32 - for (int i = 0; i < mNumChannels; i++) { - ChanData* tmp = new ChanData(i, mFPP, mHist); - mChanData.push_back(tmp); - } - stdDev = new StdDev(STDDEVINDOW); -} - -//******************************************************************************* -bool PoolBuffer::pushPacket(const int8_t* buf) -{ - QMutexLocker locker(&mMutex); - - mIncomingCnt++; - if (mGlitchCnt > mGlitchMax) { - // double elapsed0 = (double)mTimer0->nsecsElapsed() / 1000000.0; - // qDebug() << mGlitchCnt << mIncomingCnt << mOutgoingCnt - // << elapsed0/1000.0 << "\n"; - mIncomingCnt = mOutgoingCnt; - mGlitchCnt = 0; - } - int oldest = 214748364; // not so BIGNUM, approx. 2^31-1 / 10 - int oldestIndex = 0; - for (int i = 0; i < mPoolSize; i++) { - if (mIndexPool[i] < oldest) { - oldest = mIndexPool[i]; - oldestIndex = i; - } - } - mIndexPool[oldestIndex] = mIncomingCnt; - memcpy(mIncomingDat[oldestIndex], buf, mBytes); - // qDebug() << oldestIndex << mIndexPool[oldestIndex]; - - stdDev->tick(); - if (stdDev->longTermStdDevAcc > 0.0) { - double FPPfactor = 0.25 + (32 / (double)mFPP); - int newPoolSize = (int)(stdDev->longTermStdDev * STDDEV2POOLSIZE * FPPfactor); - if (newPoolSize > mPoolSize) { - if (newPoolSize > MAXPOOLSIZE) - newPoolSize = MAXPOOLSIZE; // avoid insanely large pool - mIndexPool.resize(newPoolSize); - for (int i = mPoolSize; i < newPoolSize; i++) { - int8_t* tmp = new int8_t[mBytes]; - mIncomingDat.push_back(tmp); - } - if (gVerboseFlag) - cout << "growing to " << newPoolSize << " from " << mPoolSize << "\n"; - mPoolSize = newPoolSize; - } - if (newPoolSize != mPoolSize) { - if (newPoolSize < mQlen) - newPoolSize = mQlen; // avoid insanely small pool - if (gVerboseFlag) - cout << "shrinking to " << newPoolSize << " from " << mPoolSize << "\n"; - mPoolSize = newPoolSize; - } - // qDebug() << (int) (stdDev->longTermStdDev*30.0); - } - return true; -}; - -//******************************************************************************* -void PoolBuffer::pullPacket(int8_t* buf) -{ - QMutexLocker locker(&mMutex); - mOutgoingCnt++; // will saturate in 33 days at FPP 32 - // (/ (* (- (expt 2 32) 1) (/ 32 48000.0)) (* 60 60 24)) - bool glitch = false; - int target = mOutgoingCnt - mQlen; - int targetIndex = mPoolSize; - int oldest = 999999; - int oldestIndex = 0; - for (int i = 0; i < mPoolSize; i++) { - if (mIndexPool[i] == target) { - targetIndex = i; - } - if (mIndexPool[i] < oldest) { - oldest = mIndexPool[i]; - oldestIndex = i; - } - } - if (targetIndex == mPoolSize) { - // qDebug() << " "; - // qDebug() << "!available" << target; - // for ( int i = 0; i < POOLSIZE; i++ ) qDebug() << i << mIndexPool[i]; - // qDebug() << " "; - targetIndex = oldestIndex; - mIndexPool[targetIndex] = -1; - glitch = true; - mGlitchCnt++; - // QThread::usleep(450); force lateness for debugging - } else { - mIndexPool[targetIndex] = 0; - memcpy(mXfrBuffer, mIncomingDat[targetIndex], mBytes); - } - if (mIncomingCnt) { - processPacket(glitch); - } else { - memcpy(mXfrBuffer, mZeros, mBytes); - } - memcpy(buf, mXfrBuffer, mBytes); -}; - -//******************************************************************************* -void PoolBuffer::processPacket(bool glitch) -{ - for (int ch = 0; ch < mNumChannels; ch++) - processChannel(ch, glitch, mPacketCnt, mLastWasGlitch); - mLastWasGlitch = glitch; - mPacketCnt++; -} - -//******************************************************************************* -void PoolBuffer::processChannel(int ch, bool glitch, int packetCnt, bool lastWasGlitch) -{ - // if(glitch) qDebug() << "glitch"; else fprintf(stderr,"."); - - ChanData* cd = mChanData[ch]; - for - PACKETSAMP cd->mTruth[s] = bitsToSample(ch, s); - if (packetCnt) { - for (int i = 0; i < mHist; i++) { - for - PACKETSAMP cd->mTrain[s + ((mHist - (i + 1)) * mFPP)] = - cd->mLastPackets[i][s]; - } - - // GET LINEAR PREDICTION COEFFICIENTS - ba.train(cd->mCoeffs, cd->mTrain); - - // LINEAR PREDICT DATA - vector<sample_t> tail(cd->mTrain); - - ba.predict(cd->mCoeffs, tail); // resizes to TRAINSAMPS-2 + TRAINSAMPS - - for (int i = 0; i < (cd->trainSamps - 1); i++) - cd->mPrediction[i] = tail[i + cd->trainSamps]; - - for - PACKETSAMP cd->mXfadedPred[s] = - cd->mTruth[s] * mFadeUp[s] + cd->mNextPred[s] * mFadeDown[s]; - - for - PACKETSAMP - OUT((glitch) ? cd->mPrediction[s] - : ((lastWasGlitch) ? cd->mXfadedPred[s] : cd->mTruth[s]), - ch, s); - - for - PACKETSAMP cd->mNextPred[s] = cd->mPrediction[s + mFPP]; - } - - // if mPacketCnt==0 initialization follows - for (int i = mHist - 1; i > 0; i--) { - for - PACKETSAMP cd->mLastPackets[i][s] = cd->mLastPackets[i - 1][s]; - } - - // will only be able to glitch if mPacketCnt>0 - for - PACKETSAMP cd->mLastPackets[0][s] = - ((!glitch) || (packetCnt < mHist)) ? cd->mTruth[s] : cd->mPrediction[s]; -} - -//******************************************************************************* -// copped from AudioInterface.cpp - -sample_t PoolBuffer::bitsToSample(int ch, int frame) -{ - sample_t sample = 0.0; - AudioInterface::fromBitToSampleConversion( - &mXfrBuffer[(frame * mBitResolutionMode * mNumChannels) - + (ch * mBitResolutionMode)], - &sample, mBitResolutionMode); - return sample; -} - -void PoolBuffer::sampleToBits(sample_t sample, int ch, int frame) -{ - AudioInterface::fromSampleToBitConversion( - &sample, - &mXfrBuffer[(frame * mBitResolutionMode * mNumChannels) - + (ch * mBitResolutionMode)], - mBitResolutionMode); -} - -//******************************************************************************* -bool BurgAlgorithm::classify(double d) -{ - bool tmp = false; - switch (fpclassify(d)) { - case FP_INFINITE: - qDebug() << ("infinite"); - tmp = true; - break; - case FP_NAN: - qDebug() << ("NaN"); - tmp = true; - break; - case FP_ZERO: - // qDebug() << ("zero"); - tmp = true; - break; - case FP_SUBNORMAL: - qDebug() << ("subnormal"); - tmp = true; - break; - // case FP_NORMAL: qDebug() << ("normal"); break; - } - // if (signbit(d)) qDebug() << (" negative\n"); else qDebug() << (" positive or - // unsigned\n"); - return tmp; -} - -void BurgAlgorithm::train(vector<long double>& coeffs, const vector<float>& x) -{ - // GET SIZE FROM INPUT VECTORS - size_t N = x.size() - 1; - size_t m = coeffs.size(); - - // if (x.size() < m) qDebug() << "time_series should have more elements than - // the AR order is"; - - // INITIALIZE Ak - vector<long double> Ak(m + 1, 0.0); - Ak[0] = 1.0; - - // INITIALIZE f and b - vector<long double> f; - f.resize(x.size()); - for (unsigned int i = 0; i < x.size(); i++) - f[i] = x[i]; - vector<long double> b(f); - - // INITIALIZE Dk - long double Dk = 0.0; - for (size_t j = 0; j <= N; j++) // CC: N is $#x-1 in C++ but $#x in perl - { - Dk += 2.00001 * f[j] * f[j]; // CC: needs more damping than orig 2.0 - } - Dk -= f[0] * f[0] + b[N] * b[N]; - - // qDebug() << "Dk" << qStringFromLongDouble1(Dk); - // if ( classify(Dk) ) - // { qDebug() << pCnt << "init"; - // } - - // BURG RECURSION - for (size_t k = 0; k < m; k++) { - // COMPUTE MU - long double mu = 0.0; - for (size_t n = 0; n <= N - k - 1; n++) { - mu += f[n + k + 1] * b[n]; - } - - if (Dk == 0.0) - Dk = 0.0000001; // CC: from testing, needs eps - // if ( classify(Dk) ) qDebug() << pCnt << "run"; - - mu *= -2.0 / Dk; - // if ( isnan(Dk) ) { qDebug() << "k" << k; } - // if (Dk==0.0) qDebug() << "k" << k << "Dk==0"; - - // UPDATE Ak - for (size_t n = 0; n <= (k + 1) / 2; n++) { - long double t1 = Ak[n] + mu * Ak[k + 1 - n]; - long double t2 = Ak[k + 1 - n] + mu * Ak[n]; - Ak[n] = t1; - Ak[k + 1 - n] = t2; - } - - // UPDATE f and b - for (size_t n = 0; n <= N - k - 1; n++) { - long double t1 = f[n + k + 1] + mu * b[n]; // were double - long double t2 = b[n] + mu * f[n + k + 1]; - f[n + k + 1] = t1; - b[n] = t2; - } - - // UPDATE Dk - Dk = (1.0 - mu * mu) * Dk - f[k + 1] * f[k + 1] - b[N - k - 1] * b[N - k - 1]; - } - // ASSIGN COEFFICIENTS - coeffs.assign(++Ak.begin(), Ak.end()); -} - -void BurgAlgorithm::predict(vector<long double>& coeffs, vector<float>& tail) -{ - size_t m = coeffs.size(); - // qDebug() << "tail.at(0)" << tail[0]*32768; - // qDebug() << "tail.at(1)" << tail[1]*32768; - tail.resize(m + tail.size()); - // qDebug() << "tail.at(m)" << tail[m]*32768; - // qDebug() << "tail.at(...end...)" << tail[tail.size()-1]*32768; - // qDebug() << "m" << m << "tail.size()" << tail.size(); - for (size_t i = m; i < tail.size(); i++) { - tail[i] = 0.0; - for (size_t j = 0; j < m; j++) { - tail[i] -= coeffs[j] * tail[i - 1 - j]; - } - } -} - -//******************************************************************************* -ChanData::ChanData(int i, int FPP, int hist) : ch(i) -{ - trainSamps = (hist * FPP); - mTruth.resize(FPP, 0.0); - mXfadedPred.resize(FPP, 0.0); - mNextPred.resize(FPP, 0.0); - for (int i = 0; i < hist; i++) { - vector<sample_t> tmp(FPP, 0.0); - mLastPackets.push_back(tmp); - } - mTrain.resize(trainSamps, 0.0); - mPrediction.resize(trainSamps - 1, 0.0); // ORDER - mCoeffs.resize(trainSamps - 2, 0.0); -} - -//******************************************************************************* -StdDev::StdDev(int w) : window(w) -{ - reset(); - longTermStdDev = 0.0; - longTermStdDevAcc = 0.0; - longTermCnt = 0; - lastMean = 0.0; - lastMin = 0; - lastMax = 0; - mTimer = new QElapsedTimer(); - mTimer->start(); - data.resize(w, 0.0); -} - -void StdDev::reset() -{ - mean = 0.0; - // varRunning = 0.0; - acc = 0.0; - min = 999999.0; - max = 0.0; - ctr = 0; -}; - -void StdDev::tick() -{ // stdDev based on mean of last windowful - double msElapsed = (double)mTimer->nsecsElapsed() / 1000000.0; - mTimer->start(); - if (ctr != window) { - data[ctr] = msElapsed; - if (msElapsed < min) - min = msElapsed; - else if (msElapsed > max) - max = msElapsed; - acc += msElapsed; - // double tmp = msElapsed - mean; // last window - // varRunning += (tmp*tmp); - ctr++; - } else { - mean = (double)acc / (double)window; - double var = 0.0; - for (int i = 0; i < window; i++) { - double tmp = data[i] - mean; - var += (tmp * tmp); - } - // varRunning /= (double) window; - // stdDev = sqrt(varRunning); - // qDebug() << mean << min << max << stdDev; - var /= (double)window; - double stdDev = sqrt(var); - if (longTermCnt) { - longTermStdDevAcc += stdDev; - longTermStdDev = longTermStdDevAcc / (double)longTermCnt; - // qDebug() << mean << min << max << stdDev << longTermStdDev; - if (gVerboseFlag) - cout << setw(10) << mean << setw(10) << min << setw(10) << max << setw(10) - << stdDev << setw(10) << longTermStdDev << endl; - } else if (gVerboseFlag) - cout << "printing from PoolBuffer->stdDev->tick:\n (mean / min / max / " - "stdDev / longTermStdDev) \n"; - - longTermCnt++; - // QString out; - // out += (QString::number(msNow) + QString("\t")); - // out += (QString::number(mean) + QString("\t")); - // out += (QString::number(min) + QString("\t")); - // out += (QString::number(max) + QString("\t")); - // out += (QString::number(stdDev) + QString("\t")); - // emit printStats(out); - // build-jacktrip-Desktop-Release/jacktrip -C cmn9.stanford.edu --bufstrategy 3 -I - // 1 -G /tmp/iostat.log plot 'iostat.log' u 1:2 w l, 'iostat.log' u 1:3 w l, - // 'iostat.log' u 1:4 w l, 'iostat.log' u 1:5 w l, - lastMean = mean; - lastMin = min; - lastMax = max; - reset(); - } -} diff --git a/src/Regulator.cpp b/src/Regulator.cpp new file mode 100644 index 0000000..1eb8d84 --- /dev/null +++ b/src/Regulator.cpp @@ -0,0 +1,658 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2021 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file Regulator.cpp + * \author Chris Chafe + * \date May-Sep 2021 + */ + +// EXPERIMENTAL for testing in JackTrip v1.5.0 +// requires server and client have same FPP +// runs ok from FPP 16 up to 1024 +// number of in / out channels should be the same +// mono, stereo and -n3 tested fine + +// ./jacktrip -S --udprt -p1 --bufstrategy 3 -I 1 -q10 +// PIPEWIRE_LATENCY=32/48000 ./jacktrip -C <SERV> --udprt --bufstrategy 3 -I 1 -q4 + +// at 48000 / 32 = 2.667 ms total roundtrip latency +// local loopback test with 4 terminals running and the following jmess file +// jacktrip -S --udprt --nojackportsconnect -q1 --bufstrategy 3 +// jacktrip -C localhost --udprt --nojackportsconnect -q1 --bufstrategy 3 +// use jack_iodelay +// use jmess -s delay.xml and jmess -c delay.xml + +// tested outgoing loss impairments with +// sudo tc qdisc add dev lo root netem loss 2% +// sudo tc qdisc del dev lo root netem loss 2% +// tested jitter impairments with +// for wifi +// sudo tc qdisc add dev lo root netem slot distribution pareto 0.1ms 3.0ms +// sudo tc qdisc del dev lo root netem slot distribution pareto 0.1ms 3.0ms +// for wired cmn9 +// sudo tc qdisc add dev lo root netem slot distribution pareto 0.2ms 0.3ms +// sudo tc qdisc del dev lo root netem slot distribution pareto 0.2ms 0.3ms + +#include "Regulator.h" + +#include <iomanip> +#include <sstream> + +#include "jacktrip_globals.h" +using std::cout; +using std::endl; +using std::setw; + +// constants... tested for now +constexpr int HIST = 6; // at FPP32 +constexpr int ModSeqNumInit = 256; // bounds on seqnums, 65536 is max in packet header +constexpr int NumSlotsMax = 128; // mNumSlots looped for recent arrivals +constexpr int LostWindowMax = 32; // mLostWindow looped for recent arrivals +//******************************************************************************* +Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qLen) + : RingBuffer(0, 0) + , mNumChannels(channels) + , mAudioBitRes(bit_res) + , mFPP(FPP) + , mSampleRate(sample_rate) + , mMsecTolerance((double)qLen) +{ + switch (mAudioBitRes) { // int from JitterBuffer to AudioInterface enum + case 1: + mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT8; + break; + case 2: + mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT16; + break; + case 3: + mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT24; + break; + case 4: + mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT32; + break; + } + mHist = HIST * 32; // samples, from original settings + double histFloat = mHist / (double)mFPP; // packets for other FPP + mHist = (int)histFloat; + if (mHist < 2) + mHist = 2; // min packets for prediction, needs at least 2 + else if (mHist > 6) + mHist = 6; // max packets, keep a lid on CPU load + if (gVerboseFlag) + cout << "mHist = " << mHist << " at " << mFPP << "\n"; + mBytes = mFPP * mNumChannels * mBitResolutionMode; + mXfrBuffer = new int8_t[mBytes]; + mPacketCnt = 0; // burg initialization + mFadeUp.resize(mFPP, 0.0); + mFadeDown.resize(mFPP, 0.0); + for (int i = 0; i < mFPP; i++) { + mFadeUp[i] = (double)i / (double)mFPP; + mFadeDown[i] = 1.0 - mFadeUp[i]; + } + mLastWasGlitch = false; + mPacketDurMsec = 1000.0 * (double)mFPP / (double)mSampleRate; + if (mMsecTolerance < mPacketDurMsec) + mMsecTolerance = mPacketDurMsec; // absolute minimum + mNumSlots = NumSlotsMax; //((int)ceil(mMsecTolerance / mPacketDurMsec)) + PADSLOTS; + + for (int i = 0; i < mNumSlots; i++) { + int8_t* tmp = new int8_t[mBytes]; + mSlots.push_back(tmp); + } + for (int i = 0; i < mNumChannels; i++) { + ChanData* tmp = new ChanData(i, mFPP, mHist); + mChanData.push_back(tmp); + for (int s = 0; s < mFPP; s++) + sampleToBits(0.0, i, s); // zero all channels in mXfrBuffer + } + mZeros = new int8_t[mBytes]; + memcpy(mZeros, mXfrBuffer, mBytes); + pushStat = new StdDev((int)(floor(48000.0 / (double)mFPP)), 1); + pullStat = new StdDev((int)(floor(48000.0 / (double)mFPP)), 2); + mLastLostCount = 0; // for stats + mIncomingTimer.start(); + mLastSeqNumIn = -1; + mLastSeqNumOut = -1; + mPhasor.resize(mNumChannels, 0.0); + mIncomingTiming.resize(ModSeqNumInit); + for (int i = 0; i < ModSeqNumInit; i++) + mIncomingTiming[i] = 0.0; + mModSeqNum = mNumSlots * 2; +#ifdef GUIBS3 + // hg for GUI + hg = new HerlperGUI(qApp->activeWindow()); + connect(hg, SIGNAL(moved(double)), this, SLOT(changeGlobal(double))); + connect(hg, SIGNAL(moved_2(int)), this, SLOT(changeGlobal_2(int))); + connect(hg, SIGNAL(moved_3(int)), this, SLOT(changeGlobal_3(int))); +#endif + changeGlobal_3(LostWindowMax); + changeGlobal_2(NumSlotsMax); // need hg if running GUI + changeGlobal((double)qLen); +} + +void Regulator::changeGlobal(double x) +{ // mMsecTolerance + mMsecTolerance = x; + printParams(); +} + +void Regulator::changeGlobal_2(int x) +{ // mNumSlots + mNumSlots = x; + if (!mNumSlots) + mNumSlots = 1; + if (mNumSlots > NumSlotsMax) + mNumSlots = NumSlotsMax; + mModSeqNum = mNumSlots * 2; + printParams(); +} + +void Regulator::changeGlobal_3(int x) +{ // mLostWindow + mLostWindow = x; + printParams(); +} + +void Regulator::printParams() +{ + qDebug() << "mMsecTolerance" << mMsecTolerance << "mNumSlots" << mNumSlots + << "mModSeqNum" << mModSeqNum << "mLostWindow" << mLostWindow; +#ifdef GUIBS3 + updateGUI((int)mMsecTolerance, mNumSlots); +#endif +}; + +#ifdef GUIBS3 +void Regulator::updateGUI(double msTol, int nSlots) +{ + hg->updateDisplay(msTol, nSlots, 0); // need to remove last param +} +#endif + +Regulator::~Regulator() +{ + delete mXfrBuffer; + delete mZeros; + for (int i = 0; i < mNumChannels; i++) + delete mChanData[i]; +} +//******************************************************************************* +void Regulator::pushPacket(const int8_t* buf, int seq_num) +{ + QMutexLocker locker(&mMutex); + seq_num %= mModSeqNum; + // if (seq_num==0) return; // if (seq_num==1) return; // impose regular loss + mIncomingTiming[seq_num] = + mMsecTolerance + (double)mIncomingTimer.nsecsElapsed() / 1000000.0; + mLastSeqNumIn = seq_num; + if (mLastSeqNumIn != -1) + memcpy(mSlots[mLastSeqNumIn % mNumSlots], buf, mBytes); + pushStat->tick(); +}; + +//******************************************************************************* +void Regulator::pullPacket(int8_t* buf) +{ + QMutexLocker locker(&mMutex); + mSkip = 0; + if (mLastSeqNumIn == -1) { + goto ZERO_OUTPUT; + } else { + mLastSeqNumOut++; + mLastSeqNumOut %= mModSeqNum; + double now = (double)mIncomingTimer.nsecsElapsed() / 1000000.0; + for (int i = mLostWindow; i >= 0; i--) { + int next = mLastSeqNumIn - i; + if (next < 0) + next += mModSeqNum; + if (mIncomingTiming[next] < mIncomingTiming[mLastSeqNumOut]) + continue; + mSkip = next - mLastSeqNumOut; + if (mSkip < 0) + mSkip += mModSeqNum; + mLastSeqNumOut = next; + if (mIncomingTiming[next] > now) { + memcpy(mXfrBuffer, mSlots[mLastSeqNumOut % mNumSlots], mBytes); + goto PACKETOK; + } + } + goto UNDERRUN; + } + +PACKETOK : { + if (mSkip) + processPacket(true); + else + processPacket(false); + goto OUTPUT; +} + +UNDERRUN : { + processPacket(true); + pullStat->plcUnderruns++; // count late + goto OUTPUT; +} + +ZERO_OUTPUT: + memcpy(mXfrBuffer, mZeros, mBytes); + +OUTPUT: + memcpy(buf, mXfrBuffer, mBytes); + pullStat->tick(); +}; + +//******************************************************************************* +void Regulator::processPacket(bool glitch) +{ + for (int ch = 0; ch < mNumChannels; ch++) + processChannel(ch, glitch, mPacketCnt, mLastWasGlitch); + mLastWasGlitch = glitch; + mPacketCnt++; + // 32 bit is good for days: (/ (* (- (expt 2 32) 1) (/ 32 48000.0)) (* 60 60 24)) +} + +//******************************************************************************* +void Regulator::processChannel(int ch, bool glitch, int packetCnt, bool lastWasGlitch) +{ + // if(glitch) qDebug() << "glitch"; else fprintf(stderr,"."); + ChanData* cd = mChanData[ch]; + for (int s = 0; s < mFPP; s++) + cd->mTruth[s] = bitsToSample(ch, s); + if (packetCnt) { + // always update mTrain + for (int i = 0; i < mHist; i++) { + for (int s = 0; s < mFPP; s++) + cd->mTrain[s + ((mHist - (i + 1)) * mFPP)] = cd->mLastPackets[i][s]; + } + if (glitch) { + // GET LINEAR PREDICTION COEFFICIENTS + ba.train(cd->mCoeffs, cd->mTrain); + + // LINEAR PREDICT DATA + cd->mTail = cd->mTrain; + + ba.predict(cd->mCoeffs, cd->mTail); // resizes to TRAINSAMPS-2 + TRAINSAMPS + + for (int i = 0; i < (cd->trainSamps - 1); i++) + cd->mPrediction[i] = cd->mTail[i + cd->trainSamps]; + } + // cross fade last prediction with mTruth + if (lastWasGlitch) + for (int s = 0; s < mFPP; s++) + cd->mXfadedPred[s] = + cd->mTruth[s] * mFadeUp[s] + cd->mLastPred[s] * mFadeDown[s]; + for (int s = 0; s < mFPP; s++) + sampleToBits((glitch) + ? cd->mPrediction[s] + : ((lastWasGlitch) ? cd->mXfadedPred[s] : cd->mTruth[s]), + ch, s); + if (glitch) { + for (int s = 0; s < mFPP; s++) + cd->mLastPred[s] = cd->mPrediction[s + mFPP]; + } + } + + // copy down history + + for (int i = mHist - 1; i > 0; i--) { + for (int s = 0; s < mFPP; s++) + cd->mLastPackets[i][s] = cd->mLastPackets[i - 1][s]; + } + + // add prediction or current input to history, the former checking if primed + + for (int s = 0; s < mFPP; s++) + cd->mLastPackets[0][s] = + // ((!glitch) || (packetCnt < mHist)) ? cd->mTruth[s] : + // cd->mPrediction[s]; + ((glitch) ? ((packetCnt >= mHist) ? cd->mPrediction[s] : cd->mTruth[s]) + : ((lastWasGlitch) ? cd->mXfadedPred[s] : cd->mTruth[s])); + + // diagnostic output + ///////////////////// + if (false) + for (int s = 0; s < mFPP; s++) { + sampleToBits(0.7 * sin(mPhasor[ch]), ch, s); + mPhasor[ch] += (!ch) ? 0.1 : 0.11; + } + ///////////////////// +} + +//******************************************************************************* +// copped from AudioInterface.cpp + +sample_t Regulator::bitsToSample(int ch, int frame) +{ + sample_t sample = 0.0; + AudioInterface::fromBitToSampleConversion( + &mXfrBuffer[(frame * mBitResolutionMode * mNumChannels) + + (ch * mBitResolutionMode)], + &sample, mBitResolutionMode); + return sample; +} + +void Regulator::sampleToBits(sample_t sample, int ch, int frame) +{ + AudioInterface::fromSampleToBitConversion( + &sample, + &mXfrBuffer[(frame * mBitResolutionMode * mNumChannels) + + (ch * mBitResolutionMode)], + mBitResolutionMode); +} + +//******************************************************************************* +bool BurgAlgorithm::classify(double d) +{ + bool tmp = false; + switch (fpclassify(d)) { + case FP_INFINITE: + qDebug() << ("infinite"); + tmp = true; + break; + case FP_NAN: + qDebug() << ("NaN"); + tmp = true; + break; + case FP_ZERO: + // qDebug() << ("zero"); + tmp = true; + break; + case FP_SUBNORMAL: + qDebug() << ("subnormal"); + tmp = true; + break; + // case FP_NORMAL: qDebug() << ("normal"); break; + } + // if (signbit(d)) qDebug() << (" negative\n"); else qDebug() << (" positive or + // unsigned\n"); + return tmp; +} + +void BurgAlgorithm::train(std::vector<long double>& coeffs, const std::vector<float>& x) +{ + // GET SIZE FROM INPUT VECTORS + size_t N = x.size() - 1; + size_t m = coeffs.size(); + + // if (x.size() < m) qDebug() << "time_series should have more elements than + // the AR order is"; + + // INITIALIZE Ak + // vector<long double> Ak(m + 1, 0.0); + Ak.assign(m + 1, 0.0); + Ak[0] = 1.0; + + // INITIALIZE f and b + // vector<long double> f; + f.resize(x.size()); + for (unsigned int i = 0; i < x.size(); i++) + f[i] = x[i]; + // vector<long double> b(f); + b = f; + + // INITIALIZE Dk + long double Dk = 0.0; + for (size_t j = 0; j <= N; j++) // CC: N is $#x-1 in C++ but $#x in perl + { + Dk += 2.00001 * f[j] * f[j]; // CC: needs more damping than orig 2.0 + } + Dk -= f[0] * f[0] + b[N] * b[N]; + + // qDebug() << "Dk" << qStringFromLongDouble1(Dk); + // if ( classify(Dk) ) + // { qDebug() << pCnt << "init"; + // } + + // BURG RECURSION + for (size_t k = 0; k < m; k++) { + // COMPUTE MU + long double mu = 0.0; + for (size_t n = 0; n <= N - k - 1; n++) { + mu += f[n + k + 1] * b[n]; + } + + if (Dk == 0.0) + Dk = 0.0000001; // CC: from testing, needs eps + // if ( classify(Dk) ) qDebug() << pCnt << "run"; + + mu *= -2.0 / Dk; + // if ( isnan(Dk) ) { qDebug() << "k" << k; } + // if (Dk==0.0) qDebug() << "k" << k << "Dk==0"; + + // UPDATE Ak + for (size_t n = 0; n <= (k + 1) / 2; n++) { + long double t1 = Ak[n] + mu * Ak[k + 1 - n]; + long double t2 = Ak[k + 1 - n] + mu * Ak[n]; + Ak[n] = t1; + Ak[k + 1 - n] = t2; + } + + // UPDATE f and b + for (size_t n = 0; n <= N - k - 1; n++) { + long double t1 = f[n + k + 1] + mu * b[n]; // were double + long double t2 = b[n] + mu * f[n + k + 1]; + f[n + k + 1] = t1; + b[n] = t2; + } + + // UPDATE Dk + Dk = (1.0 - mu * mu) * Dk - f[k + 1] * f[k + 1] - b[N - k - 1] * b[N - k - 1]; + } + // ASSIGN COEFFICIENTS + coeffs.assign(++Ak.begin(), Ak.end()); +} + +void BurgAlgorithm::predict(std::vector<long double>& coeffs, std::vector<float>& tail) +{ + size_t m = coeffs.size(); + // qDebug() << "tail.at(0)" << tail[0]*32768; + // qDebug() << "tail.at(1)" << tail[1]*32768; + tail.resize(m + tail.size()); + // qDebug() << "tail.at(m)" << tail[m]*32768; + // qDebug() << "tail.at(...end...)" << tail[tail.size()-1]*32768; + // qDebug() << "m" << m << "tail.size()" << tail.size(); + for (size_t i = m; i < tail.size(); i++) { + tail[i] = 0.0; + for (size_t j = 0; j < m; j++) { + tail[i] -= coeffs[j] * tail[i - 1 - j]; + } + } +} + +//******************************************************************************* +ChanData::ChanData(int i, int FPP, int hist) : ch(i) +{ + trainSamps = (hist * FPP); + mTruth.resize(FPP, 0.0); + mXfadedPred.resize(FPP, 0.0); + mLastPred.resize(FPP, 0.0); + for (int i = 0; i < hist; i++) { + std::vector<sample_t> tmp(FPP, 0.0); + mLastPackets.push_back(tmp); + } + mTrain.resize(trainSamps, 0.0); + mPrediction.resize(trainSamps - 1, 0.0); // ORDER + mCoeffs.resize(trainSamps - 2, 0.0); + mCrossFadeDown.resize(FPP, 0.0); + mCrossFadeUp.resize(FPP, 0.0); + mCrossfade.resize(FPP, 0.0); +} + +//******************************************************************************* +StdDev::StdDev(int w, int id) : window(w), mId(id) +{ + reset(); + longTermStdDev = 0.0; + longTermStdDevAcc = 0.0; + longTermCnt = 0; + lastMean = 0.0; + lastMin = 0; + lastMax = 0; + lastPlcUnderruns = 0; + mTimer.start(); + data.resize(w, 0.0); +} + +void StdDev::reset() +{ + mean = 0.0; + // varRunning = 0.0; + acc = 0.0; + min = 999999.0; + max = 0.0; + ctr = 0; + plcUnderruns = 0; +}; + +double StdDev::tick() +{ + double msElapsed = (double)mTimer.nsecsElapsed() / 1000000.0; + mTimer.start(); + if (ctr != window) { + data[ctr] = msElapsed; + if (msElapsed < min) + min = msElapsed; + else if (msElapsed > max) + max = msElapsed; + acc += msElapsed; + ctr++; + } else { + mean = (double)acc / (double)window; + double var = 0.0; + for (int i = 0; i < window; i++) { + double tmp = data[i] - mean; + var += (tmp * tmp); + } + var /= (double)window; + double stdDev = sqrt(var); + if (longTermCnt) { + longTermStdDevAcc += stdDev; + longTermStdDev = longTermStdDevAcc / (double)longTermCnt; + if (gVerboseFlag) + cout << setw(10) << mean << setw(10) << lastMin << setw(10) << max + << setw(10) << stdDev << setw(10) << longTermStdDev << " " << mId + << endl; + } else if (gVerboseFlag) + cout << "printing directly from Regulator->stdDev->tick:\n (mean / min / " + "max / " + "stdDev / longTermStdDev) \n"; + + longTermCnt++; + lastMean = mean; + lastMin = min; + lastMax = max; + lastStdDev = stdDev; + lastPlcUnderruns = plcUnderruns; + reset(); + } + return msElapsed; +} +//******************************************************************************* +bool Regulator::getStats(RingBuffer::IOStat* stat, bool reset) +{ + QMutexLocker locker(&mMutex); + if (reset) { // all are unused + mUnderruns = 0; + mOverflows = 0; + mSkew0 = mLevel; + mSkewRaw = 0; + mBufDecOverflow = 0; + mBufDecPktLoss = 0; + mBufIncUnderrun = 0; + mBufIncCompensate = 0; + mBroadcastSkew = 0; + } + // hijack of struct IOStat { + stat->underruns = pullStat->lastPlcUnderruns; +#define FLOATFACTOR 1000.0 + stat->overflows = FLOATFACTOR * pushStat->longTermStdDev; + stat->skew = FLOATFACTOR * pushStat->lastMean; + stat->skew_raw = FLOATFACTOR * pushStat->lastMin; + stat->level = FLOATFACTOR * pushStat->lastMax; + stat->buf_dec_overflows = FLOATFACTOR * pushStat->lastStdDev; + + stat->buf_dec_pktloss = FLOATFACTOR * pullStat->longTermStdDev; + stat->buf_inc_underrun = FLOATFACTOR * pullStat->lastMean; + stat->buf_inc_compensate = FLOATFACTOR * pullStat->lastMin; + stat->broadcast_skew = FLOATFACTOR * pullStat->lastMax; + stat->broadcast_delta = FLOATFACTOR * pullStat->lastStdDev; + // unused + // int32_t autoq_corr; + // int32_t autoq_rate; + return true; +} +/* +QString Regulator::getStats(uint32_t statCount, uint32_t lostCount) +{ + // formatting floats in columns looks better with std::stringstream than with + // QTextStream + QString tmp; + if (!statCount) { + tmp = QString("Regulator: inter-packet intervals msec\n"); + tmp += " (window of last "; + tmp += QString::number(pullStat->window); + tmp += " packets)\n"; + tmp += + "secs avgStdDev (mean min max stdDev) " + "PLC(under over skipped) lost\n"; + } else { + uint32_t lost = lostCount - mLastLostCount; + mLastLostCount = lostCount; +#define PDBL(x) << setw(10) << (QString("%1").arg(pushStat->x, 0, 'f', 2)).toStdString() +#define PDBL2(x) << setw(10) << (QString("%1").arg(pullStat->x, 0, 'f', 2)).toStdString() + std::stringstream logger; + logger << setw(2) + << statCount + PDBL(longTermStdDev) PDBL(lastMean) PDBL(lastMin) PDBL(lastMax) +PDBL(lastStdDev) + << setw(8) << pushStat->lastPlcSkipped + #ifndef GUIBS3 + // comment out this next line for GUI because... + << endl + // ...print all stats in one line when running in GUI because can't +handle extra crlf + // and... to actually see the two lines, need to run it in terminal + #endif + ; + tmp = QString::fromStdString(logger.str()); + std::stringstream logger2; + logger2 << setw(2) + << "" PDBL2(longTermStdDev) PDBL2(lastMean) PDBL2(lastMin) PDBL2(lastMax) + PDBL2(lastStdDev) + << setw(8) << pullStat->lastPlcUnderruns << setw(8) + << pullStat->lastPlcOverruns << setw(8) << pullStat->lastPlcSkipped + << setw(8) << lost << endl; + tmp += QString::fromStdString(logger2.str()); + } + return tmp; +} +*/ diff --git a/src/PoolBuffer.h b/src/Regulator.h index b7c4d7a..eb3da37 100644 --- a/src/PoolBuffer.h +++ b/src/Regulator.h @@ -30,7 +30,7 @@ //***************************************************************** /** - * \file PoolBuffer.h + * \file Regulator.h * \author Chris Chafe * \date May 2021 */ @@ -40,23 +40,37 @@ // http://www.emptyloop.com/technotes/A%20tutorial%20on%20Burg's%20method,%20algorithm%20and%20recursion.pdf // https://metacpan.org/source/SYP/Algorithm-Burg-0.001/README -#ifndef __POOLUFFER_H__ -#define __POOLUFFER_H__ +#ifndef __REGULATOR_H__ +#define __REGULATOR_H__ + +#include <math.h> #include <QDebug> #include <QElapsedTimer> #include "AudioInterface.h" #include "RingBuffer.h" -using std::vector; -#include <math.h> + +//#define GUIBS3 +#ifdef GUIBS3 +#include <QWidget> + +#include "herlpergui.h" +#include "ui_herlpergui.h" +#endif class BurgAlgorithm { public: bool classify(double d); - void train(vector<long double>& coeffs, const vector<float>& x); - void predict(vector<long double>& coeffs, vector<float>& tail); + void train(std::vector<long double>& coeffs, const std::vector<float>& x); + void predict(std::vector<long double>& coeffs, std::vector<float>& tail); + + private: + // the following are class members to minimize heap memory allocations + std::vector<long double> Ak; + std::vector<long double> f; + std::vector<long double> b; }; class ChanData @@ -65,27 +79,32 @@ class ChanData ChanData(int i, int FPP, int hist); int ch; int trainSamps; - vector<sample_t> mTruth; - vector<sample_t> mTrain; - vector<sample_t> mPrediction; // ORDER - vector<long double> mCoeffs; - vector<sample_t> mXfadedPred; - vector<sample_t> mNextPred; - vector<vector<sample_t>> mLastPackets; + std::vector<sample_t> mTruth; + std::vector<sample_t> mTrain; + std::vector<sample_t> mTail; + std::vector<sample_t> mPrediction; // ORDER + std::vector<long double> mCoeffs; + std::vector<sample_t> mXfadedPred; + std::vector<sample_t> mLastPred; + std::vector<std::vector<sample_t>> mLastPackets; + std::vector<sample_t> mCrossFadeDown; + std::vector<sample_t> mCrossFadeUp; + std::vector<sample_t> mCrossfade; }; class StdDev { public: - StdDev(int w); + StdDev(int w, int id); void reset(); - void tick(); - QElapsedTimer* mTimer; - vector<double> data; + double tick(); + QElapsedTimer mTimer; + std::vector<double> data; double mean; double var; // double varRunning; int window; + int mId; double acc; double min; double max; @@ -93,20 +112,29 @@ class StdDev double lastMean; double lastMin; double lastMax; + double lastStdDev; double longTermStdDev; double longTermStdDevAcc; int longTermCnt; + int plcUnderruns; + int lastPlcUnderruns; }; -class PoolBuffer : public RingBuffer +#ifdef GUIBS3 +class Regulator + : public QObject + , public RingBuffer { - // Q_OBJECT; - + Q_OBJECT; +#else +class Regulator : public RingBuffer +{ +#endif public: - PoolBuffer(int sample_rate, int channels, int bit_res, int FPP, int qLen); - virtual ~PoolBuffer() {} + Regulator(int sample_rate, int channels, int bit_res, int FPP, int qLen); + virtual ~Regulator(); - bool pushPacket(const int8_t* buf); + void pushPacket(const int8_t* buf, int seq_num); // can hijack unused2 to propagate incoming seq num if needed // option is in UdpDataProtocol // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, last_seq_num)) @@ -114,9 +142,9 @@ class PoolBuffer : public RingBuffer // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size)) virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, [[maybe_unused]] int unused, - [[maybe_unused]] int unused2) + [[maybe_unused]] int seq_num) { - pushPacket(ptrToSlot); + pushPacket(ptrToSlot, seq_num); return (true); } @@ -124,36 +152,53 @@ class PoolBuffer : public RingBuffer virtual void readSlotNonBlocking(int8_t* ptrToReadSlot) { pullPacket(ptrToReadSlot); } + // virtual QString getStats(uint32_t statCount, uint32_t lostCount); + virtual bool getStats(IOStat* stat, bool reset); + private: void processPacket(bool glitch); void processChannel(int ch, bool glitch, int packetCnt, bool lastWasGlitch); int mNumChannels; int mAudioBitRes; int mFPP; - - int mPoolSize; + int mSampleRate; + uint32_t mLastLostCount; + int mNumSlots; int mHist; AudioInterface::audioBitResolutionT mBitResolutionMode; BurgAlgorithm ba; + int mBytes; int8_t* mXfrBuffer; int mPacketCnt; sample_t bitsToSample(int ch, int frame); void sampleToBits(sample_t sample, int ch, int frame); - vector<sample_t> mFadeUp; - vector<sample_t> mFadeDown; + std::vector<sample_t> mFadeUp; + std::vector<sample_t> mFadeDown; bool mLastWasGlitch; - unsigned int mOutgoingCnt; - int mBytes; - vector<int8_t*> mIncomingDat; + std::vector<int8_t*> mSlots; int8_t* mZeros; - QElapsedTimer* mTimer0; - unsigned int mIncomingCnt; - vector<int> mIndexPool; - int mQlen; - int mGlitchCnt; - int mGlitchMax; - vector<ChanData*> mChanData; - StdDev* stdDev; + double mMsecTolerance; + std::vector<ChanData*> mChanData; + StdDev* pushStat; + StdDev* pullStat; + QElapsedTimer mIncomingTimer; + int mLastSeqNumIn; + int mLastSeqNumOut; + double mPacketDurMsec; + std::vector<double> mPhasor; + std::vector<double> mIncomingTiming; + int mModSeqNum; + int mLostWindow; + int mSkip; + std::vector<bool> mIncomingLost; +#ifdef GUIBS3 + HerlperGUI* hg; + void updateGUI(double msTol, int nSlots, int lostWin); + public slots: +#endif + void changeGlobal(double); + void changeGlobal_2(int); + void changeGlobal_3(int); + void printParams(); }; - -#endif //__POOLUFFER_H__ +#endif //__REGULATOR_H__ diff --git a/src/UdpDataProtocol.cpp b/src/UdpDataProtocol.cpp index 5752448..c1226ec 100644 --- a/src/UdpDataProtocol.cpp +++ b/src/UdpDataProtocol.cpp @@ -35,7 +35,7 @@ * \date June 2008 */ -//#define __MANUAL_POLL__ +//#define MANUAL_POLL #include "UdpDataProtocol.h" @@ -51,17 +51,18 @@ #ifdef _WIN32 //#include <winsock.h> #include <winsock2.h> //cc need SD_SEND -#endif -#if defined(__linux__) || defined(__APPLE__) +#else #include <sys/fcntl.h> #include <sys/socket.h> // for POSIX Sockets #include <unistd.h> -#endif -#if defined(__APPLE__) && !defined(__MANUAL_POLL__) -#include <sys/event.h> -#elif defined(__linux__) && !defined(__MANUAL_POLL__) +#ifndef MANUAL_POLL +#ifdef __linux__ #include <sys/epoll.h> -#endif +#else +#include <sys/event.h> +#endif // __linux__ +#endif // MANUAL_POLL +#endif // _WIN32 using std::cout; using std::endl; @@ -123,7 +124,7 @@ UdpDataProtocol::~UdpDataProtocol() void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP) { // Get DNS Address -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 // Don't make the following code conditional on windows //(Addresses a weird timing bug when in hub client mode) if (!mPeerAddress.setAddress(peerHostOrIP)) { @@ -135,7 +136,7 @@ void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP) } // cout << "UdpDataProtocol::setPeerAddress IP Address Number: " // << mPeerAddress.toString().toStdString() << endl; -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 } #endif @@ -207,7 +208,7 @@ int UdpDataProtocol::bindSocket() { QMutexLocker locker(&sUdpMutex); -#if defined _WIN32 +#ifdef _WIN32 WORD wVersionRequested; WSADATA wsaData; int err; @@ -232,9 +233,7 @@ int UdpDataProtocol::bindSocket() } SOCKET sock_fd; -#endif - -#if defined(__linux__) || defined(__APPLE__) +#else int sock_fd; #endif @@ -262,18 +261,16 @@ int UdpDataProtocol::bindSocket() // Set socket to be reusable, this is platform dependent int one = 1; -#if defined(__linux__) +#if defined(_WIN32) + // make address/port reusable + setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); +#elif defined(__linux__) ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); -#endif -#if defined(__APPLE__) +#else // This option is not avialable on Linux, and without it MAC OS X // has problems rebinding a socket ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif -#if defined(_WIN32) - // make address/port reusable - setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); -#endif // Bind the Socket if (mIPv6) { @@ -286,60 +283,9 @@ int UdpDataProtocol::bindSocket() } } - // To be able to use the two UDP sockets bound to the same port number, - // we connect the receiver and issue a SHUT_WR. - - // This didn't work for IPv6, so we'll instead share a full duplex socket. - /*if (mRunMode == SENDER) { - // We use the sender as an unconnected UDP socket - UdpSocket.setSocketDescriptor(sock_fd, QUdpSocket::BoundState, - QUdpSocket::WriteOnly); - }*/ - if (!mIPv6) { - // Connect only if we're using IPv4. - // (Connecting presents an issue when a host has multiple IP addresses and the - // peer decides to send from a different address. While this generally won't be a - // problem for IPv4, it will for IPv6.) - if ((::connect(sock_fd, (struct sockaddr*)&mPeerAddr, sizeof(mPeerAddr))) < 0) { - throw std::runtime_error("ERROR: Could not connect UDP socket"); - } -#if defined(__linux__) || defined(__APPLE__) - // if ( (::shutdown(sock_fd,SHUT_WR)) < 0) - //{ throw std::runtime_error("ERROR: Could shutdown SHUT_WR UDP socket"); } -#endif -#if defined _WIN32 - /*int shut_sr = shutdown(sock_fd, SD_SEND); //shut down sender's receive function - if ( shut_sr< 0) - { - fprintf(stderr, "ERROR: Could not shutdown SD_SEND UDP socket"); - throw std::runtime_error("ERROR: Could not shutdown SD_SEND UDP socket"); - }*/ -#endif - } - + // Return our file descriptor so the socket can be shared for a + // full duplex connection. return sock_fd; - - // OLD CODE WITHOUT POSIX FIX-------------------------------------------------- - /* - /// \todo if port is already used, try binding in a different port - QUdpSocket::BindMode bind_mode; - if (mRunMode == RECEIVER) { - bind_mode = QUdpSocket::DontShareAddress; } - else if (mRunMode == SENDER) { //Share sender socket - bind_mode = QUdpSocket::ShareAddress; } - - // QHostAddress::Any : let the kernel decide the active address - if ( !UdpSocket.bind(QHostAddress::Any, mBindPort, bind_mode) ) { - throw std::runtime_error("Could not bind UDP socket. It may be already binded."); - } - else { - if ( mRunMode == RECEIVER ) { - cout << "UDP Socket Receiving in Port: " << mBindPort << endl; - cout << gPrintSeparator << endl; - } - } - */ - // ---------------------------------------------------------------------------- } //******************************************************************************* @@ -388,7 +334,8 @@ functions. DWORD n_bytes; WSABUF buffer; int error; buffer.len = n; buffer.buf = n_bytes = ::sendto(mSocket, buf, n, 0, (struct sockaddr*)&mPeerAddr6, sizeof(mPeerAddr6)); } else { - n_bytes = ::send(mSocket, buf, n, 0); + n_bytes = + ::sendto(mSocket, buf, n, 0, (struct sockaddr*)&mPeerAddr, sizeof(mPeerAddr)); } return n_bytes; //#endif @@ -639,8 +586,14 @@ void UdpDataProtocol::run() mStatCount = 0; //Set up our platform specific polling mechanism. (kqueue, epoll) -#if !defined (__MANUAL_POLL__) && !defined (_WIN32) -#if defined (__APPLE__) +#if !defined (MANUAL_POLL) && !defined (_WIN32) +#ifdef __linux__ + int epollfd = epoll_create1(0); + struct epoll_event change, event; + change.events = EPOLLIN; + change.data.fd = mSocket; + epoll_ctl(epollfd, EPOLL_CTL_ADD, mSocket, &change); +#else int kq = kqueue(); struct kevent change; struct kevent event; @@ -648,15 +601,9 @@ void UdpDataProtocol::run() struct timespec timeout; timeout.tv_sec = 0; timeout.tv_nsec = 10000000; -#else - int epollfd = epoll_create1(0); - struct epoll_event change, event; - change.events = EPOLLIN; - change.data.fd = mSocket; - epoll_ctl(epollfd, EPOLL_CTL_ADD, mSocket, &change); #endif int waitTime = 0; -#endif // __MANUAL_POLL__ +#endif // MANUAL_POLL if (gVerboseFlag) std::cout << "step 8" << std::endl; while (!mStopped) { @@ -666,7 +613,7 @@ void UdpDataProtocol::run() // arrive for a longer time //timeout = UdpSocket.waitForReadyRead(30); // timeout = cc unused! -#if defined (_WIN32) || defined (__MANUAL_POLL__) +#if defined (_WIN32) || defined (MANUAL_POLL) waitForReady(60000); //60 seconds receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size, full_packet_size, current_seq_num, last_seq_num, @@ -688,10 +635,10 @@ void UdpDataProtocol::run() */ //---------------------------------------------------------------------------------- -#ifdef __APPLE__ - int n = kevent(kq, &change, 1, &event, 1, &timeout); -#else +#ifdef __linux__ int n = epoll_wait(epollfd, &event, 1, 10); +#else + int n = kevent(kq, &change, 1, &event, 1, &timeout); #endif if (n > 0) { waitTime = 0; @@ -703,12 +650,12 @@ void UdpDataProtocol::run() emit signalWaitingTooLong(waitTime); } } -#ifdef __APPLE__ - close(kq); -#else +#ifdef __linux__ close(epollfd); +#else + close(kq); #endif -#endif // _WIN32 || __MANUAL_POLL__ +#endif // _WIN32 || MANUAL_POLL break; } case SENDER : { @@ -861,7 +808,12 @@ void UdpDataProtocol::receivePacketRedundancy( } src = dst; } - if (!mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size)) { + int ok = true; // send audio buf to + ok = (mJackTrip->getBufferStrategy() !=3) ? // ring or jitter + mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size) + : // regulator needs matching local and peer buffer settings + mJackTrip->writeAudioBuffer(src, host_buf_size, last_seq_num); + if (!ok) { emit signalError("Local and Peer buffer settings are incompatible"); cout << "ERROR: Local and Peer buffer settings are incompatible" << endl; mStopped = true; diff --git a/src/gui/qjacktrip.cpp b/src/gui/qjacktrip.cpp index ef2c438..3330d25 100644 --- a/src/gui/qjacktrip.cpp +++ b/src/gui/qjacktrip.cpp @@ -52,7 +52,7 @@ #include "../Limiter.h" #include "../Reverb.h" -QJackTrip::QJackTrip(QWidget* parent) +QJackTrip::QJackTrip(int argc, QWidget* parent) : QMainWindow(parent) #ifdef NO_JTVS , m_ui(new Ui::QJackTrip) @@ -67,7 +67,7 @@ QJackTrip::QJackTrip(QWidget* parent) , m_jackTripRunning(false) , m_isExiting(false) , m_hasIPv4Reply(false) - , m_argc(1) + , m_argc(argc) , m_hideWarning(false) { m_ui->setupUi(this); @@ -233,6 +233,7 @@ QJackTrip::QJackTrip(QWidget* parent) m_ui->basePortSpinBox->setVisible(false); m_ui->autoPatchGroupBox->setVisible(false); m_ui->requireAuthGroupBox->setVisible(false); + m_ui->backendWarningLabel->setVisible(false); #ifdef RT_AUDIO connect(m_ui->backendComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), @@ -247,6 +248,7 @@ QJackTrip::QJackTrip(QWidget* parent) m_ui->outputDeviceComboBox->setEnabled(true); m_ui->outputDeviceLabel->setEnabled(true); m_ui->refreshDevicesButton->setEnabled(true); + m_ui->backendWarningLabel->setVisible(true); populateDeviceMenu(m_ui->inputDeviceComboBox, true); populateDeviceMenu(m_ui->outputDeviceComboBox, false); } else { @@ -259,6 +261,7 @@ QJackTrip::QJackTrip(QWidget* parent) m_ui->outputDeviceComboBox->setEnabled(false); m_ui->outputDeviceLabel->setEnabled(false); m_ui->refreshDevicesButton->setEnabled(false); + m_ui->backendWarningLabel->setVisible(false); } }); connect(m_ui->refreshDevicesButton, &QPushButton::clicked, this, [=]() { @@ -272,6 +275,17 @@ QJackTrip::QJackTrip(QWidget* parent) } #endif + // One of our arguments will always be --gui, so if that's the only one + // then we don't need to show the warning message. + if ((!gVerboseFlag && m_argc > 2) || m_argc > 3) { + QMessageBox msgBox; + msgBox.setText( + "The GUI version of JackTrip currently ignores any command line " + "options other than the verbose option (-V).\n\nThis may change in future."); + msgBox.setWindowTitle(QStringLiteral("Command line options")); + msgBox.exec(); + } + migrateSettings(); loadSettings(); @@ -296,6 +310,7 @@ QJackTrip::QJackTrip(QWidget* parent) // Check if Jack is actually available if (have_libjack() != 0) { #ifdef RT_AUDIO + bool usingRtAudioAlready = m_ui->backendComboBox->currentIndex() == 1; m_ui->backendComboBox->setCurrentIndex(1); m_ui->backendComboBox->setEnabled(false); m_ui->backendLabel->setEnabled(false); @@ -305,7 +320,11 @@ QJackTrip::QJackTrip(QWidget* parent) m_ui->typeComboBox->setCurrentIndex(P2P_SERVER); } m_ui->typeComboBox->removeItem(HUB_SERVER); + m_ui->backendWarningLabel->setText( + "JACK was not found. This means that only the RtAudio backend is available " + "and that JackTrip cannot be run in hub server mode."); +#ifdef NO_JTVS QSettings settings; settings.beginGroup(QStringLiteral("Audio")); if (!settings.value(QStringLiteral("HideJackWarning"), false).toBool()) { @@ -313,9 +332,11 @@ QJackTrip::QJackTrip(QWidget* parent) new QCheckBox(QStringLiteral("Don't show this warning again")); QMessageBox msgBox; msgBox.setText( - "An installation of JACK was not found. Only the RtAudio\nbackend will " - "be available. (Hub Server mode is not\ncurrently supported in this " - "configuration."); + "An installation of JACK was not found. JackTrip will still run using " + "a different audio backend (RtAudio) but some more advanced features, " + "like the ability to run your own hub server, will not be available." + "\n\n(If you install JACK at a later stage, these features will " + "automatically be re-enabled.)"); msgBox.setWindowTitle(QStringLiteral("JACK Not Available")); msgBox.setCheckBox(dontBugMe); QObject::connect(dontBugMe, &QCheckBox::stateChanged, this, [=]() { @@ -325,13 +346,26 @@ QJackTrip::QJackTrip(QWidget* parent) if (m_hideWarning) { settings.setValue(QStringLiteral("HideJackWarning"), true); } + if (!usingRtAudioAlready) { + settings.setValue(QStringLiteral("UsingFallback"), true); + } } settings.endGroup(); -#else + } else { + // If we've fallen back to RtAudio before and JACK is now installed, use JACK. + QSettings settings; + settings.beginGroup(QStringLiteral("Audio")); + if (settings.value(QStringLiteral("UsingFallback"), false).toBool()) { + m_ui->backendComboBox->setCurrentIndex(0); + settings.setValue(QStringLiteral("UsingFallback"), false); + } + settings.endGroup(); +#endif // NO_JTVS +#else // RT_AUDIO QMessageBox msgBox; msgBox.setText( - "An installation of JACK was not found, and no other audio\nbackends are " - "available. JackTrip will not be able to start.\n(Please install JACK to fix " + "An installation of JACK was not found, and no other audio backends are " + "available. JackTrip will not be able to start. (Please install JACK to fix " "this.)"); msgBox.setWindowTitle("JACK Not Available"); msgBox.exec(); @@ -381,27 +415,6 @@ void QJackTrip::resizeEvent(QResizeEvent* event) m_ui->authDisclaimerLabel->setMinimumHeight(rect.height()); } -void QJackTrip::showEvent(QShowEvent* event) -{ - QMainWindow::showEvent(event); - - // One of our arguments will always be --gui, so if that's the only one - // then we don't need to show the warning message. - if ((!gVerboseFlag && m_argc > 2) || m_argc > 3) { - QMessageBox msgBox; - msgBox.setText( - "The GUI version of JackTrip currently\nignores any command line " - "options other\nthan the verbose option (-V).\n\nThis may change in future."); - msgBox.setWindowTitle(QStringLiteral("Command line options")); - msgBox.exec(); - } -} - -void QJackTrip::setArgc(int argc) -{ - m_argc = argc; -} - void QJackTrip::processFinished() { if (!m_jackTripRunning) { @@ -1326,12 +1339,12 @@ void QJackTrip::appendPlugins(JackTrip* jackTrip, int numSendChannels, } // Limiters go last in the plugin sequence. - if (m_ui->inLimiterCheckBox->isChecked()) { - jackTrip->appendProcessPluginFromNetwork(new Limiter(numSendChannels, 1)); - } if (m_ui->outLimiterCheckBox->isChecked()) { jackTrip->appendProcessPluginToNetwork( - new Limiter(numRecvChannels, m_ui->outClientsSpinBox->value())); + new Limiter(numSendChannels, m_ui->outClientsSpinBox->value())); + } + if (m_ui->inLimiterCheckBox->isChecked()) { + jackTrip->appendProcessPluginFromNetwork(new Limiter(numRecvChannels, 1)); } } diff --git a/src/gui/qjacktrip.h b/src/gui/qjacktrip.h index 77286b4..27528a7 100644 --- a/src/gui/qjacktrip.h +++ b/src/gui/qjacktrip.h @@ -63,14 +63,11 @@ class QJackTrip : public QMainWindow Q_OBJECT public: - explicit QJackTrip(QWidget* parent = nullptr); + explicit QJackTrip(int argc = 0, QWidget* parent = nullptr); ~QJackTrip() override; void closeEvent(QCloseEvent* event) override; void resizeEvent(QResizeEvent* event) override; - void showEvent(QShowEvent* event) override; - - void setArgc(int argc); signals: void signalExit(); diff --git a/src/gui/qjacktrip.ui b/src/gui/qjacktrip.ui index d206d60..52dc336 100644 --- a/src/gui/qjacktrip.ui +++ b/src/gui/qjacktrip.ui @@ -999,6 +999,13 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st <string>Audio Backend</string> </attribute> <layout class="QGridLayout" name="gridLayout_11"> + <item row="3" column="1"> + <widget class="QComboBox" name="inputDeviceComboBox"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> <item row="0" column="0"> <widget class="QLabel" name="backendLabel"> <property name="sizePolicy"> @@ -1015,156 +1022,101 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </property> </widget> </item> - <item row="0" column="1"> - <widget class="QComboBox" name="backendComboBox"> - <property name="toolTip"> - <string><html><head/><body><p>Choose the audio backend to use. JACK is the default and is well tested, but requires the JACK audio server to be installed.</p><p>RtAudio is still a work in progress, but it works with your operating system's native audio drivers and requires no additional software.</p></body></html></string> - </property> - <item> - <property name="text"> - <string>JACK</string> - </property> - </item> - <item> - <property name="text"> - <string>RtAudio</string> - </property> - </item> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="sampleRateLabel"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>&Sampling Rate:</string> - </property> - <property name="buddy"> - <cstring>sampleRateComboBox</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="sampleRateComboBox"> + <item row="2" column="1"> + <widget class="QComboBox" name="bufferSizeComboBox"> <property name="enabled"> <bool>false</bool> </property> <property name="toolTip"> - <string><html><head/><body><p>Set the audio sample rate to use with the RtAudio backend. This setting should be the same on both ends of the connection.</p></body></html></string> - </property> - <property name="currentText"> - <string>48000</string> + <string><html><head/><body><p>Set the driver's buffer size to use with the RtAudio backend.</p></body></html></string> </property> <property name="currentIndex"> <number>3</number> </property> <item> <property name="text"> - <string>22050</string> + <string>16</string> </property> </item> <item> <property name="text"> - <string>32000</string> + <string>32</string> </property> </item> <item> <property name="text"> - <string>44100</string> + <string>64</string> </property> </item> <item> <property name="text"> - <string>48000</string> + <string>128</string> </property> </item> <item> <property name="text"> - <string>88200</string> + <string>256</string> </property> </item> <item> <property name="text"> - <string>96000</string> + <string>512</string> </property> </item> <item> <property name="text"> - <string>192000</string> + <string>1024</string> </property> </item> </widget> </item> - <item row="2" column="0"> - <widget class="QLabel" name="bufferSizeLabel"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>&Buffer Size:</string> - </property> - <property name="buddy"> - <cstring>bufferSizeComboBox</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="bufferSizeComboBox"> + <item row="1" column="1"> + <widget class="QComboBox" name="sampleRateComboBox"> <property name="enabled"> <bool>false</bool> </property> <property name="toolTip"> - <string><html><head/><body><p>Set the driver's buffer size to use with the RtAudio backend.</p></body></html></string> + <string><html><head/><body><p>Set the audio sample rate to use with the RtAudio backend. This setting should be the same on both ends of the connection.</p></body></html></string> + </property> + <property name="currentText"> + <string>48000</string> </property> <property name="currentIndex"> <number>3</number> </property> <item> <property name="text"> - <string>16</string> + <string>22050</string> </property> </item> <item> <property name="text"> - <string>32</string> + <string>32000</string> </property> </item> <item> <property name="text"> - <string>64</string> + <string>44100</string> </property> </item> <item> <property name="text"> - <string>128</string> + <string>48000</string> </property> </item> <item> <property name="text"> - <string>256</string> + <string>88200</string> </property> </item> <item> <property name="text"> - <string>512</string> + <string>96000</string> </property> </item> <item> <property name="text"> - <string>1024</string> + <string>192000</string> </property> </item> </widget> @@ -1188,40 +1140,7 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </property> </widget> </item> - <item row="3" column="1"> - <widget class="QComboBox" name="inputDeviceComboBox"> - <property name="enabled"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="outputDeviceLabel"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>&Output Device:</string> - </property> - <property name="buddy"> - <cstring>outputDeviceComboBox</cstring> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QComboBox" name="outputDeviceComboBox"> - <property name="enabled"> - <bool>false</bool> - </property> - </widget> - </item> - <item row="5" column="1"> + <item row="6" column="1"> <spacer name="backendSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -1234,7 +1153,7 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </property> </spacer> </item> - <item row="8" column="0" colspan="2"> + <item row="9" column="0" colspan="2"> <layout class="QHBoxLayout" name="deviceManagementLayout"> <item> <spacer name="backendTabSpacer"> @@ -1258,6 +1177,97 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </item> </layout> </item> + <item row="4" column="0"> + <widget class="QLabel" name="outputDeviceLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Output Device:</string> + </property> + <property name="buddy"> + <cstring>outputDeviceComboBox</cstring> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="bufferSizeLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Buffer Size:</string> + </property> + <property name="buddy"> + <cstring>bufferSizeComboBox</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="backendComboBox"> + <property name="toolTip"> + <string><html><head/><body><p>Choose the audio backend to use. JACK is the default and is well tested, but requires the JACK audio server to be installed.</p><p>RtAudio is still a work in progress, but it works with your operating system's native audio drivers and requires no additional software.</p></body></html></string> + </property> + <item> + <property name="text"> + <string>JACK</string> + </property> + </item> + <item> + <property name="text"> + <string>RtAudio</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="sampleRateLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Sampling Rate:</string> + </property> + <property name="buddy"> + <cstring>sampleRateComboBox</cstring> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QComboBox" name="outputDeviceComboBox"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="backendWarningLabel"> + <property name="text"> + <string>These settings are ignored in Hub Server mode which requires JACK to operate.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="JitterTab"> diff --git a/src/gui/qjacktrip_novs.ui b/src/gui/qjacktrip_novs.ui index 69bd2c3..6e6ce34 100644 --- a/src/gui/qjacktrip_novs.ui +++ b/src/gui/qjacktrip_novs.ui @@ -999,21 +999,29 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st <string>Audio Backend</string> </attribute> <layout class="QGridLayout" name="gridLayout_11"> - <item row="0" column="0"> - <widget class="QLabel" name="backendLabel"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Audio &Backend:</string> - </property> - <property name="buddy"> - <cstring>backendComboBox</cstring> - </property> - </widget> + <item row="9" column="0" colspan="2"> + <layout class="QHBoxLayout" name="deviceManagementLayout"> + <item> + <spacer name="backendTabSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="refreshDevicesButton"> + <property name="text"> + <string>&Refresh Device List</string> + </property> + </widget> + </item> + </layout> </item> <item row="0" column="1"> <widget class="QComboBox" name="backendComboBox"> @@ -1032,8 +1040,28 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </item> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="sampleRateLabel"> + <item row="3" column="1"> + <widget class="QComboBox" name="inputDeviceComboBox"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="6" column="1"> + <spacer name="backendSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>444</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="inputDeviceLabel"> <property name="enabled"> <bool>false</bool> </property> @@ -1044,64 +1072,77 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </sizepolicy> </property> <property name="text"> - <string>&Sampling Rate:</string> + <string>&Input Device:</string> </property> <property name="buddy"> - <cstring>sampleRateComboBox</cstring> + <cstring>inputDeviceComboBox</cstring> </property> </widget> </item> - <item row="1" column="1"> - <widget class="QComboBox" name="sampleRateComboBox"> + <item row="2" column="1"> + <widget class="QComboBox" name="bufferSizeComboBox"> <property name="enabled"> <bool>false</bool> </property> <property name="toolTip"> - <string><html><head/><body><p>Set the audio sample rate to use with the RtAudio backend. This setting should be the same on both ends of the connection.</p></body></html></string> - </property> - <property name="currentText"> - <string>48000</string> + <string><html><head/><body><p>Set the driver's buffer size to use with the RtAudio backend.</p></body></html></string> </property> <property name="currentIndex"> <number>3</number> </property> <item> <property name="text"> - <string>22050</string> + <string>16</string> </property> </item> <item> <property name="text"> - <string>32000</string> + <string>32</string> </property> </item> <item> <property name="text"> - <string>44100</string> + <string>64</string> </property> </item> <item> <property name="text"> - <string>48000</string> + <string>128</string> </property> </item> <item> <property name="text"> - <string>88200</string> + <string>256</string> </property> </item> <item> <property name="text"> - <string>96000</string> + <string>512</string> </property> </item> <item> <property name="text"> - <string>192000</string> + <string>1024</string> </property> </item> </widget> </item> + <item row="0" column="0"> + <widget class="QLabel" name="backendLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Audio &Backend:</string> + </property> + <property name="buddy"> + <cstring>backendComboBox</cstring> + </property> + </widget> + </item> <item row="2" column="0"> <widget class="QLabel" name="bufferSizeLabel"> <property name="enabled"> @@ -1121,56 +1162,59 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </property> </widget> </item> - <item row="2" column="1"> - <widget class="QComboBox" name="bufferSizeComboBox"> + <item row="1" column="1"> + <widget class="QComboBox" name="sampleRateComboBox"> <property name="enabled"> <bool>false</bool> </property> <property name="toolTip"> - <string><html><head/><body><p>Set the driver's buffer size to use with the RtAudio backend.</p></body></html></string> + <string><html><head/><body><p>Set the audio sample rate to use with the RtAudio backend. This setting should be the same on both ends of the connection.</p></body></html></string> + </property> + <property name="currentText"> + <string>48000</string> </property> <property name="currentIndex"> <number>3</number> </property> <item> <property name="text"> - <string>16</string> + <string>22050</string> </property> </item> <item> <property name="text"> - <string>32</string> + <string>32000</string> </property> </item> <item> <property name="text"> - <string>64</string> + <string>44100</string> </property> </item> <item> <property name="text"> - <string>128</string> + <string>48000</string> </property> </item> <item> <property name="text"> - <string>256</string> + <string>88200</string> </property> </item> <item> <property name="text"> - <string>512</string> + <string>96000</string> </property> </item> <item> <property name="text"> - <string>1024</string> + <string>192000</string> </property> </item> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="inputDeviceLabel"> + <item row="1" column="0"> + <widget class="QLabel" name="sampleRateLabel"> <property name="enabled"> <bool>false</bool> </property> @@ -1181,17 +1225,10 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </sizepolicy> </property> <property name="text"> - <string>&Input Device:</string> + <string>&Sampling Rate:</string> </property> <property name="buddy"> - <cstring>inputDeviceComboBox</cstring> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QComboBox" name="inputDeviceComboBox"> - <property name="enabled"> - <bool>false</bool> + <cstring>sampleRateComboBox</cstring> </property> </widget> </item> @@ -1221,42 +1258,15 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st </property> </widget> </item> - <item row="5" column="1"> - <spacer name="backendSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="backendWarningLabel"> + <property name="text"> + <string>These settings are ignored in hub server mode which requires JACK to operate.</string> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>444</height> - </size> + <property name="wordWrap"> + <bool>true</bool> </property> - </spacer> - </item> - <item row="8" column="0" colspan="2"> - <layout class="QHBoxLayout" name="deviceManagementLayout"> - <item> - <spacer name="backendTabSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="refreshDevicesButton"> - <property name="text"> - <string>&Refresh Device List</string> - </property> - </widget> - </item> - </layout> + </widget> </item> </layout> </widget> diff --git a/src/jacktrip_globals.cpp b/src/jacktrip_globals.cpp index ce2c04d..6a31fbf 100644 --- a/src/jacktrip_globals.cpp +++ b/src/jacktrip_globals.cpp @@ -149,17 +149,25 @@ void setRealtimeProcessPriority(int bufferSize, int sampleRate) return; } -#endif //__APPLE__ - -#if defined(__linux__) +#elif defined(_WIN32) +void setRealtimeProcessPriority() +{ + if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == 0) { + std::cerr << "Failed to set process priority class." << std::endl; + } + if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == 0) { + std::cerr << "Failed to set thread priority." << std::endl; + } +} +#else //******************************************************************************* void setRealtimeProcessPriority() { int priority = sched_get_priority_max(SCHED_FIFO); // 99 is the highest possible #ifdef __UBUNTU__ - priority = 95; // anything higher is silently ignored by Ubuntu 18.04 + priority = 95; // anything higher is silently ignored by Ubuntu 18.04 #endif - priority = 3; + priority = 3; struct sched_param sp = {.sched_priority = priority}; @@ -168,16 +176,5 @@ void setRealtimeProcessPriority() ; } } -#endif //__linux__ -#if defined(_WIN32) -void setRealtimeProcessPriority() -{ - if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == 0) { - std::cerr << "Failed to set process priority class." << std::endl; - } - if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == 0) { - std::cerr << "Failed to set thread priority." << std::endl; - } -} -#endif //_WIN32 +#endif diff --git a/src/jacktrip_globals.h b/src/jacktrip_globals.h index 369a968..7513b1f 100644 --- a/src/jacktrip_globals.h +++ b/src/jacktrip_globals.h @@ -40,7 +40,7 @@ #include "AudioInterface.h" -constexpr const char* const gVersion = "1.5.1"; ///< JackTrip version +constexpr const char* const gVersion = "1.5.2"; ///< JackTrip version //******************************************************************************* /// \name Default Values diff --git a/src/main.cpp b/src/main.cpp index 3858720..6ff7909 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,7 +97,7 @@ QCoreApplication* createApplication(int& argc, char* argv[]) return new QCoreApplication(argc, argv); } #else -#ifdef __linux__ +#if defined(__unix__) // Check if X or Wayland environment variables are set. if (std::getenv("WAYLAND_DISPLAY") == nullptr && std::getenv("DISPLAY") == nullptr) { @@ -108,7 +108,7 @@ QCoreApplication* createApplication(int& argc, char* argv[]) << std::endl; std::exit(1); } -#endif // __linux__ +#endif return new QApplication(argc, argv); #endif // NO_GUI } else { @@ -123,7 +123,7 @@ void qtMessageHandler([[maybe_unused]] QtMsgType type, std::cerr << msg.toStdString() << std::endl; } -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 static int setupUnixSignalHandler(void (*handler)(int)) { // Setup our SIGINT handler. @@ -235,8 +235,7 @@ int main(int argc, char* argv[]) gVerboseFlag = true; } - window.reset(new QJackTrip); - window->setArgc(argc); + window.reset(new QJackTrip(argc)); QObject::connect(window.data(), &QJackTrip::signalExit, app.data(), &QCoreApplication::quit, Qt::QueuedConnection); window->show(); @@ -260,7 +259,7 @@ int main(int argc, char* argv[]) Qt::QueuedConnection); QObject::connect(udpHub.data(), &UdpHubListener::signalError, app.data(), &QCoreApplication::quit, Qt::QueuedConnection); -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 setupUnixSignalHandler(UdpHubListener::sigIntHandler); #else isHubServer = true; @@ -277,7 +276,7 @@ int main(int argc, char* argv[]) Qt::QueuedConnection); QObject::connect(jackTrip.data(), &JackTrip::signalError, app.data(), &QCoreApplication::quit, Qt::QueuedConnection); -#if defined(__linux__) || defined(__APPLE__) +#ifndef _WIN32 setupUnixSignalHandler(JackTrip::sigIntHandler); #else std::cout << SetConsoleCtrlHandler(windowsCtrlHandler, true) << std::endl; |