diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index a4521a72..99452cc8 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -11,22 +11,223 @@ concurrency: env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Debug + BUILD_TYPE: RelWithDebInfo jobs: - build: + lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install required packages + shell: bash + run: | + sudo apt-get update + sudo apt-get upgrade -y + sudo apt-get install codespell + + - name: Spellcheck codebase + shell: bash + run: codespell --ignore-words-list=caf,radae,rade,inout,nin,ontop,parm,tthe,ue `find src -name '*.c*' -o -name '*.h' | grep -v 3rdparty` + + dist: + runs-on: ubuntu-22.04 + needs: lint + outputs: + APPIMAGE_FILENAME: ${{ steps.appimage-filename.outputs.APPIMAGE_FILENAME }} + + steps: + - uses: actions/checkout@v4 + + - name: Install required packages + shell: bash + run: | + sudo apt-get update + sudo apt-get upgrade -y + sudo apt-get install libpulse-dev sox git libasound2-dev libao-dev libgsm1-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libdbus-1-dev libgtk-3-dev + + - name: Install Python required modules + shell: bash + working-directory: ${{github.workspace}} + run: | + python3 -m venv rade-venv + . ./rade-venv/bin/activate + pip3 install torch torchaudio --index-url https://download.pytorch.org/whl/cpu + pip3 install matplotlib + + - name: Build freedv-gui using PulseAudio + shell: bash + working-directory: ${{github.workspace}} + run: | + . ./rade-venv/bin/activate + ./build_linux.sh + + - name: Build AppImage + shell: bash + working-directory: ${{github.workspace}}/appimage + run: | + ./make-appimage.sh + + - name: Calculate AppImage filename + shell: bash + id: appimage-filename + working-directory: ${{github.workspace}} + run: | + export FREEDV_VERSION=`cat build_linux/freedv-version.txt` + echo "APPIMAGE_FILENAME=FreeDV-${FREEDV_VERSION}-x86_64.AppImage" >> "$GITHUB_OUTPUT" + + - name: Upload AppImage + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.appimage-filename.outputs.APPIMAGE_FILENAME }} + path: ${{github.workspace}}/appimage/${{ steps.appimage-filename.outputs.APPIMAGE_FILENAME }} + compression-level: 0 + + test-appimage-fedora: + runs-on: ubuntu-24.04 + needs: [build, dist] + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: ${{needs.dist.outputs.APPIMAGE_FILENAME}} + path: ${{github.workspace}} + + - name: Start Fedora container + shell: bash + run: | + sudo podman run -d --name fedora-test --systemd=always --privileged -v ${{github.workspace}}:/workspace docker.io/jrei/systemd-fedora:42 + + - name: Install rtkit for RT threading + shell: bash + run: | + sudo podman exec fedora-test sudo dnf -y update + sudo podman exec fedora-test sudo dnf -y install dbus dbus-x11 dbus-tools rtkit dbus-devel polkit + sudo podman exec fedora-test sudo sed -i 's/no/yes/g' /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy + + - name: Install required packages for audio/display + shell: bash + run: | + sudo podman exec fedora-test sudo dnf -y install sox xorg-x11-server-Xvfb pipewire pipewire-pulseaudio pulseaudio-libs-devel pulseaudio-utils wireplumber metacity + + - name: Sanity check RADE + shell: bash + working-directory: ${{github.workspace}} + run: | + chmod +x ${{needs.dist.outputs.APPIMAGE_FILENAME}} + echo "FREEDV_BINARY=./${{needs.dist.outputs.APPIMAGE_FILENAME}} --appimage-extract-and-run" > tmp.env + sudo podman exec --env-file ./tmp.env fedora-test /workspace/appimage/container-test.sh | tee tmp.log + grep "Got 1 sync changes" tmp.log + + test-appimage-ubuntu: strategy: fail-fast: true matrix: os: [ubuntu-24.04, ubuntu-22.04] runs-on: ${{ matrix.os }} + needs: [build, dist] + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: ${{needs.dist.outputs.APPIMAGE_FILENAME}} + path: ${{github.workspace}} + + - name: Install rtkit for RT threading + shell: bash + run: | + sudo apt-get update + sudo apt-get upgrade -y + sudo apt-get install dbus-x11 rtkit libdbus-1-dev polkitd + sudo sed -i 's/no/yes/g' /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy + sudo systemctl restart polkit + + - name: Install required packages for audio/display + shell: bash + if: ${{ matrix.os == 'ubuntu-24.04' }} + run: | + sudo apt-get install sox xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity + + - name: Install required packages for audio/display + shell: bash + if: ${{ matrix.os == 'ubuntu-22.04' }} + run: | + sudo apt-get install sox xvfb pulseaudio pulseaudio-utils metacity + systemctl --user --now enable pulseaudio.service pulseaudio.socket + + - name: Sanity check RADE + shell: bash + working-directory: ${{github.workspace}} + if: ${{ matrix.os == 'ubuntu-22.04' }} + run: | + sudo systemctl enable rtkit-daemon + sudo systemctl start rtkit-daemon + Xvfb :99 -screen 0 1024x768x16 & + sleep 5 + export DISPLAY=:99.0 + export XDG_RUNTIME_DIR=/run/user/$(id -u) + mkdir -p $XDG_RUNTIME_DIR + chmod 700 $XDG_RUNTIME_DIR + eval "$(dbus-launch --sh-syntax --exit-with-x11)" + metacity --sm-disable --replace & + sleep 5 + chmod +x ${{needs.dist.outputs.APPIMAGE_FILENAME}} + FREEDV_BINARY=./${{needs.dist.outputs.APPIMAGE_FILENAME}} ./test/test_zeros.sh txrx RADEV1 | tee tmp.log + grep "Got 1 sync changes" tmp.log + + - name: Sanity check RADE + shell: bash + working-directory: ${{github.workspace}} + if: ${{ matrix.os == 'ubuntu-24.04' }} + run: | + sudo systemctl enable rtkit-daemon + sudo systemctl start rtkit-daemon + Xvfb :99 -screen 0 1024x768x16 & + sleep 5 + export DISPLAY=:99.0 + export XDG_RUNTIME_DIR=/run/user/$(id -u) + mkdir -p $XDG_RUNTIME_DIR + chmod 700 $XDG_RUNTIME_DIR + eval "$(dbus-launch --sh-syntax --exit-with-x11)" + pipewire & + pipewire-pulse & + wireplumber & + metacity --sm-disable --replace & + sleep 5 + chmod +x ${{needs.dist.outputs.APPIMAGE_FILENAME}} + FREEDV_BINARY=./${{needs.dist.outputs.APPIMAGE_FILENAME}} ./test/test_zeros.sh txrx RADEV1 | tee tmp.log + grep "Got 1 sync changes" tmp.log + + build: + strategy: + fail-fast: true + matrix: + os: [ubuntu-24.04, ubuntu-22.04] + sanitizer: [WITH_ASAN=0, WITH_RTSAN=1, WITH_ASAN=1] + native-audio: [USE_NATIVE_AUDIO=0, USE_NATIVE_AUDIO=1] + exclude: + - os: ubuntu-22.04 + sanitizer: WITH_RTSAN=1 + - os: ubuntu-22.04 + sanitizer: WITH_ASAN=1 + - os: ubuntu-24.04 + sanitizer: WITH_RTSAN=1 + native-audio: USE_NATIVE_AUDIO=0 + - os: ubuntu-24.04 + sanitizer: WITH_ASAN=1 + native-audio: USE_NATIVE_AUDIO=0 + + runs-on: ${{ matrix.os }} + needs: lint steps: - uses: actions/checkout@v4 - name: Install LLVM/Clang >=20.0 (Required for RTSan) - if: ${{ matrix.os == 'ubuntu-24.04' }} + if: ${{ matrix.sanitizer == 'WITH_RTSAN=1' }} uses: MorganCaron/latest-clang-action@master - name: Install rtkit for RT threading @@ -41,25 +242,21 @@ jobs: - name: Install common packages shell: bash run: | - sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core octave octave-signal + sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core - name: Install version-specific packages if: ${{ matrix.os == 'ubuntu-22.04' }} shell: bash run: | - sudo apt-get install libwxgtk3.0-gtk3-dev + sudo apt-get install libwxgtk3.0-gtk3-dev pulseaudio + systemctl --user --now enable pulseaudio.service pulseaudio.socket - name: Install version-specific packages if: ${{ matrix.os == 'ubuntu-24.04' }} shell: bash run: | - sudo apt-get install codespell libwxgtk3.2-dev + sudo apt-get install libwxgtk3.2-dev pipewire pipewire-pulse wireplumber - - name: Spellcheck codebase - if: ${{ matrix.os == 'ubuntu-24.04' }} - shell: bash - run: codespell --ignore-words-list=caf,radae,rade,inout,nin,ontop,parm,tthe,ue `find src -name '*.c*' -o -name '*.h' | grep -v 3rdparty` - - name: Install Python required modules shell: bash working-directory: ${{github.workspace}} @@ -69,23 +266,45 @@ jobs: pip3 install torch torchaudio --index-url https://download.pytorch.org/whl/cpu pip3 install matplotlib - - name: Build freedv-gui using PortAudio + - name: Build freedv-gui shell: bash working-directory: ${{github.workspace}} + if: ${{ matrix.sanitizer == 'WITH_RTSAN=1' }} run: | . ./rade-venv/bin/activate - UT_ENABLE=1 USE_NATIVE_AUDIO=0 ./build_linux.sh + UT_ENABLE=1 CC=clang CXX=clang++ ${{matrix.native-audio}} ${{matrix.sanitizer}} ./build_linux.sh - - name: Build freedv-gui using PulseAudio + - name: Build freedv-gui shell: bash working-directory: ${{github.workspace}} + if: ${{ matrix.sanitizer != 'WITH_RTSAN=1' }} run: | . ./rade-venv/bin/activate - BUILD_TYPE=RelWithDebInfo UT_ENABLE=1 ./build_linux.sh + UT_ENABLE=1 ${{matrix.native-audio}} ${{matrix.sanitizer}} ./build_linux.sh - name: Execute unit tests shell: bash - if: ${{ matrix.os == 'ubuntu-24.04' }} + if: ${{ matrix.os == 'ubuntu-22.04' && matrix.native-audio == 'USE_NATIVE_AUDIO=1' }} + working-directory: ${{github.workspace}}/build_linux + run: | + sudo systemctl enable rtkit-daemon + sudo systemctl start rtkit-daemon + Xvfb :99 -screen 0 1024x768x16 & + sleep 5 + export DISPLAY=:99.0 + export XDG_RUNTIME_DIR=/run/user/$(id -u) + mkdir -p $XDG_RUNTIME_DIR + chmod 700 $XDG_RUNTIME_DIR + eval "$(dbus-launch --sh-syntax --exit-with-x11)" + metacity --sm-disable --replace & + sleep 5 + ln -s ${{github.workspace}}/build_linux/rade_src/model19_check3 model19_check3 + . ../rade-venv/bin/activate + ASAN_OPTIONS=suppressions=${{github.workspace}}/test/asan_suppressions.txt LSAN_OPTIONS=suppressions=${{github.workspace}}/test/lsan_suppressions.txt PYTHONPATH=${{github.workspace}}/build_linux/rade_src:$PYTHONPATH ctest -V -R fullduplex_ + + - name: Execute unit tests + shell: bash + if: ${{ matrix.os == 'ubuntu-24.04' && matrix.native-audio == 'USE_NATIVE_AUDIO=1' }} working-directory: ${{github.workspace}}/build_linux run: | sudo systemctl enable rtkit-daemon @@ -104,41 +323,6 @@ jobs: sleep 5 ln -s ${{github.workspace}}/build_linux/rade_src/model19_check3 model19_check3 . ../rade-venv/bin/activate - PYTHONPATH=${{github.workspace}}/build_linux/rade_src:$PYTHONPATH ctest -V - - - name: Instrument AddressSanitizer - shell: bash - if: ${{ matrix.os == 'ubuntu-24.04' }} - working-directory: ${{github.workspace}} - run: | - . ./rade-venv/bin/activate - BUILD_TYPE=RelWithDebInfo WITH_ASAN=1 UT_ENABLE=1 ./build_linux.sh + ASAN_OPTIONS=suppressions=${{github.workspace}}/test/asan_suppressions.txt LSAN_OPTIONS=suppressions=${{github.workspace}}/test/lsan_suppressions.txt PYTHONPATH=${{github.workspace}}/build_linux/rade_src:$PYTHONPATH ctest -V - - name: Check for memory leaks - shell: bash - if: ${{ matrix.os == 'ubuntu-24.04' }} - working-directory: ${{github.workspace}}/build_linux - run: | - export DISPLAY=:99.0 - export XDG_RUNTIME_DIR=/run/user/$(id -u) - . ../rade-venv/bin/activate - ASAN_OPTIONS=suppressions=${{github.workspace}}/test/asan_suppressions.txt LSAN_OPTIONS=suppressions=${{github.workspace}}/test/lsan_suppressions.txt PYTHONPATH=${{github.workspace}}/build_linux/rade_src:$PYTHONPATH ctest -V -R "fullduplex_(RADE|700D)|rade_reporting_clean" - - - name: Instrument RealtimeSanitizer - shell: bash - if: ${{ matrix.os == 'ubuntu-24.04' }} - working-directory: ${{github.workspace}} - run: | - . ./rade-venv/bin/activate - BUILD_TYPE=RelWithDebInfo WITH_RTSAN=1 UT_ENABLE=1 CC=clang CXX=clang++ ./build_linux.sh - - - name: Check for RT-unsafe function calls - shell: bash - if: ${{ matrix.os == 'ubuntu-24.04' }} - working-directory: ${{github.workspace}}/build_linux - run: | - export DISPLAY=:99.0 - export XDG_RUNTIME_DIR=/run/user/$(id -u) - . ../rade-venv/bin/activate - PYTHONPATH=${{github.workspace}}/build_linux/rade_src:$PYTHONPATH ctest -V -R "fullduplex_(RADE|700D)|rade_reporting_clean" diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml index d2876df6..29de1962 100644 --- a/.github/workflows/cmake-macos.yml +++ b/.github/workflows/cmake-macos.yml @@ -21,33 +21,13 @@ jobs: os: [macos-13, macos-latest] # x86_64, ARM64 runs-on: ${{ matrix.os }} - needs: dist steps: - uses: actions/checkout@v4 - uses: gerlero/brew-install@v1 with: - packages: automake libtool numpy sox octave wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib - - - name: Install octave-signal - if: ${{ matrix.os == 'macos-13' }} - shell: bash - working-directory: ${{github.workspace}} - run: | - # make sure gfortran is available - gfortran --version - octave-cli --eval "pkg install -forge control; pkg install -forge signal" - - - name: Install octave-signal - if: ${{ matrix.os == 'macos-latest' }} - shell: bash - working-directory: ${{github.workspace}} - run: | - # make sure gfortran is available - sudo ln -s /opt/homebrew/bin/gfortran-14 /usr/local/bin/gfortran - gfortran --version - octave-cli --eval "pkg install -forge control; pkg install -forge signal" + packages: automake libtool numpy sox wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib - name: Install VB-Cable shell: bash diff --git a/.gitignore b/.gitignore index efda533b..9c5e4b50 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ a.out *.make *.log *.marks +*.AppDir +*.AppImage diff --git a/USER_MANUAL.md b/USER_MANUAL.md index 93cbdfe4..d8036256 100644 --- a/USER_MANUAL.md +++ b/USER_MANUAL.md @@ -9,6 +9,20 @@ This is a live document. Notes on new FreeDV features are being added as they a This section contains instructions to help you get started. +## System Requirements + +FreeDV is officially supported on the following operating systems and versions: + +* Windows: Windows 10 and later +* macOS: macOS Big Sur (11.0) and later (ARM and Intel) +* Linux: Ubuntu 22.04 LTS and later, Fedora 42 and later + +While it may be possible to execute FreeDV on additional platforms and distributions +(especially ones similar to ones currently supported), this is not guaranteed. Any +bugfixes required for an unsupported platform may be deferred or rejected, especially +if there is impact to supported platforms. Requests for official support of new platforms +should go through our existing [Feature Request process](https://github.com/drowe67/freedv-gui/issues/new?template=feature_request.yml). + ## Easy Setup Upon starting FreeDV for the first time, the Easy Setup dialog will appear. This @@ -814,6 +828,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes 3. Build system: * Update Hamlib to 4.6.3 (macOS/Windows). (PR #930) * Reload current Git hash every time it changes. (PR #935) + * Add infrastructure for generating AppImage builds. (PR #937) ## V2.0.0 June 2025 diff --git a/appimage/AppRun.sh b/appimage/AppRun.sh new file mode 100644 index 00000000..14fbc7bb --- /dev/null +++ b/appimage/AppRun.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e +echo "In AppImage AppRun" +export LD_LIBRARY_PATH="${APPIMAGE_LIBRARY_PATH}:${APPDIR}/usr/lib:${LD_LIBRARY_PATH}" +export PATH="$APPDIR/usr/bin:$APPDIR/rade-venv/bin" +export GDK_BACKEND=x11 +echo "PATH=$PATH" +export PYTHONHOME="$APPDIR/usr" +export PYTHONPATH="$APPDIR/rade_src:$APPDIR/rade-venv/lib/python3.10/site-packages" +echo "PYTHONPATH=$PYTHONPATH" +echo "PYTHONHOME=$PYTHONHOME" +cd "$APPDIR" +echo "#### after import" +"$APPDIR/usr/bin/freedv" $@ diff --git a/appimage/FreeDV.desktop b/appimage/FreeDV.desktop new file mode 100644 index 00000000..2ea5029e --- /dev/null +++ b/appimage/FreeDV.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Name=FreeDV +Exec=freedv +Icon=freedv256x256 +Type=Application +Categories=Utility; diff --git a/appimage/container-test.sh b/appimage/container-test.sh new file mode 100755 index 00000000..d8b019aa --- /dev/null +++ b/appimage/container-test.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +cd /workspace +Xvfb :99 -screen 0 1024x768x16 & +sleep 5 +export DISPLAY=:99.0 +export XDG_RUNTIME_DIR=/run/user/$(id -u) +mkdir -p $XDG_RUNTIME_DIR +chmod 700 $XDG_RUNTIME_DIR +eval "$(dbus-launch --sh-syntax --exit-with-x11)" +sudo systemctl restart polkit +sudo systemctl enable rtkit-daemon +sudo systemctl start rtkit-daemon +pipewire & +pipewire-pulse & +wireplumber & +metacity --sm-disable --replace & +sleep 5 +./test/test_zeros.sh txrx RADEV1 diff --git a/appimage/make-appimage.sh b/appimage/make-appimage.sh new file mode 100755 index 00000000..f32b0289 --- /dev/null +++ b/appimage/make-appimage.sh @@ -0,0 +1,82 @@ +#!/bin/bash -e + +APPNAME="FreeDV" +APPDIR="$APPNAME.AppDir" +BUILDDIR="../" + +# Change to the directory where this script is located +cd "$(dirname "$(realpath "$0")")" + +if [ -d "$APPDIR" ]; then + echo "Deleting $APPDIR..." + rm -rf "$APPDIR" +else + echo "$APPDIR does not exist." +fi + +echo "Bundle dependencies..." +if test -f linuxdeploy-x86_64.AppImage; then + echo "linuxdeploy exists" +else + wget -c "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" + wget https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-gtk.sh +fi + +./linuxdeploy-x86_64.AppImage \ +--executable /usr/bin/python3 \ +--executable ../build_linux/src/freedv \ +--appdir "$APPDIR" \ +--icon-file ../contrib/freedv256x256.png \ +--custom-apprun=AppRun.sh \ +--desktop-file FreeDV.desktop + +# create the virtual environment (copied from Brian's build script) +cd FreeDV.AppDir +python3 -m venv rade-venv # || { echo "ERROR: create venv failed"; exit 1; } +# Activate it +source rade-venv/bin/activate # || { echo "ERROR: activate venv failed"; exit 1; } + +# Clear cache in venv +pip3 cache purge +pip3 install --upgrade pip || echo "WARNING: pip upgrade failed" +pip3 install numpy +pip3 install torch torchaudio --index-url https://download.pytorch.org/whl/cpu +pip3 install matplotlib +cd - + +echo "Fix venv python links..." +echo "Now in $(pwd)" +cd "$APPDIR/rade-venv/bin" +echo "Now in $(pwd)" +ln -s -f ../../usr/bin/python3 python +ln -s -f ../../usr/bin/python3 python3 +ln -s -f ../../usr/bin/python3 python3.10 +cd - # back to the previous directory +echo "### Now in $(pwd)" + +# Copy /usr/lib/python3.10 to image +cd $APPDIR/usr +cp -a /usr/lib/python3.10 lib/ +cd - + +# Copy the models and symlink +echo "Copying rade_src..." +# ls freedv-rade/freedv-gui/build_linux/rade_src/model +# model05/ model17/ model18/ model19/ model19_check3/ model_bbfm_01/ +cp -r "$BUILDDIR/build_linux/rade_src" "$APPDIR/." +cd "$APPDIR/usr/bin" +ln -s "../../rade_src/model19_check3" "model19_check3" +cd - + +# Create the output +./linuxdeploy-x86_64.AppImage \ +--appdir "$APPDIR" \ +--plugin gtk \ +--output appimage + +# Include version number in AppImage filename +FREEDV_VERSION=`cat ../build_linux/freedv-version.txt` +mv FreeDV-x86_64.AppImage FreeDV-$FREEDV_VERSION-x86_64.AppImage + +echo "Done" diff --git a/cmake/CheckGit.cmake b/cmake/CheckGit.cmake index 3d487463..67e78571 100644 --- a/cmake/CheckGit.cmake +++ b/cmake/CheckGit.cmake @@ -50,6 +50,7 @@ function(CheckGitVersion) else() set(FREEDV_VERSION "${FreeDV_VERSION}") endif() + file(WRITE ${CMAKE_BINARY_DIR}/freedv-version.txt ${FREEDV_VERSION}) # Only update the git_version.cpp if the hash has changed. This will # prevent us from rebuilding the project more than we need to. @@ -57,7 +58,6 @@ function(CheckGitVersion) # Set che GIT_HASH_CACHE variable the next build won't have # to regenerate the source file. CheckGitWrite(${GIT_HASH}) - configure_file(${pre_configure_file} ${post_configure_file} @ONLY) endif () diff --git a/src/audio/IAudioDevice.h b/src/audio/IAudioDevice.h index 725b68ee..21d6be5e 100644 --- a/src/audio/IAudioDevice.h +++ b/src/audio/IAudioDevice.h @@ -63,7 +63,7 @@ public: // Lets audio system know that we're done with the work on the received // audio. - virtual void stopRealTimeWork() override { std::this_thread::sleep_for(10ms); } + virtual void stopRealTimeWork(bool fastMode = false) override { std::this_thread::sleep_for(10ms); } // Reverts real-time priority for current thread. virtual void clearHelperRealTime() override { /* empty */ } diff --git a/src/audio/MacAudioDevice.h b/src/audio/MacAudioDevice.h index e91b1c9d..d11a4aab 100644 --- a/src/audio/MacAudioDevice.h +++ b/src/audio/MacAudioDevice.h @@ -59,7 +59,7 @@ public: // Lets audio system know that we're done with the work on the received // audio. - virtual void stopRealTimeWork() override; + virtual void stopRealTimeWork(bool fastMode = false) override; // Reverts real-time priority for current thread. virtual void clearHelperRealTime() override; diff --git a/src/audio/MacAudioDevice.mm b/src/audio/MacAudioDevice.mm index 7c26ab9d..cde77aea 100644 --- a/src/audio/MacAudioDevice.mm +++ b/src/audio/MacAudioDevice.mm @@ -706,9 +706,9 @@ void MacAudioDevice::startRealTimeWork() } } -void MacAudioDevice::stopRealTimeWork() +void MacAudioDevice::stopRealTimeWork(bool fastMode) { - dispatch_semaphore_wait(sem_, dispatch_time(DISPATCH_TIME_NOW, AUDIO_SAMPLE_BLOCK_SEC * kOneNanosecond)); + dispatch_semaphore_wait(sem_, dispatch_time(DISPATCH_TIME_NOW, (int)(AUDIO_SAMPLE_BLOCK_SEC * kOneNanosecond) >> (fastMode ? 1 : 0))); } void MacAudioDevice::clearHelperRealTime() diff --git a/src/audio/PulseAudioDevice.cpp b/src/audio/PulseAudioDevice.cpp index b072bef5..431e5958 100644 --- a/src/audio/PulseAudioDevice.cpp +++ b/src/audio/PulseAudioDevice.cpp @@ -28,6 +28,7 @@ #include #include "PulseAudioDevice.h" +#include "../util/timespec.h" #if defined(USE_RTKIT) #include "rtkit.h" @@ -41,7 +42,6 @@ using namespace std::chrono_literals; // TX audio to reach the radio. #define PULSE_TARGET_LATENCY_US 20000 -thread_local std::chrono::high_resolution_clock::time_point PulseAudioDevice::StartTime_; thread_local bool PulseAudioDevice::MustStopWork_ = false; PulseAudioDevice::PulseAudioDevice(pa_threaded_mainloop *mainloop, pa_context* context, wxString devName, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels) @@ -278,20 +278,19 @@ void PulseAudioDevice::setHelperRealTime() void PulseAudioDevice::startRealTimeWork() { - StartTime_ = std::chrono::high_resolution_clock::now(); - sleepFallback_ = false; - if (clock_gettime(CLOCK_REALTIME, &ts_) == -1) + + if (clock_gettime(CLOCK_MONOTONIC, &ts_) == -1) { sleepFallback_ = true; } } -void PulseAudioDevice::stopRealTimeWork() +void PulseAudioDevice::stopRealTimeWork(bool fastMode) { if (sleepFallback_) { // Fallback to simple sleep. - IAudioDevice::stopRealTimeWork(); + IAudioDevice::stopRealTimeWork(fastMode); return; } @@ -300,28 +299,22 @@ void PulseAudioDevice::stopRealTimeWork() { latency = PULSE_TARGET_LATENCY_US; } + latency >>= fastMode ? 1 : 0; ts_.tv_nsec += latency * 1000; - if (ts_.tv_nsec >= 1000000000) - { - ts_.tv_sec++; - ts_.tv_nsec -= 1000000000; - } + ts_ = timespec_normalise(ts_); - if (sem_timedwait(&sem_, &ts_) < 0 && errno != ETIMEDOUT) + int rv = 0; + while ((rv = sem_clockwait(&sem_, CLOCK_MONOTONIC, &ts_)) == -1 && errno == EINTR) + { + // empty + } + if (rv == -1 && errno != ETIMEDOUT) { // Fallback to simple sleep. + sleepFallback_ = true; IAudioDevice::stopRealTimeWork(); } - else if (errno == ETIMEDOUT) - { - auto endTime = std::chrono::high_resolution_clock::now(); - if ((endTime - StartTime_) >= std::chrono::microseconds(PULSE_TARGET_LATENCY_US * 10)) - { - // Took a lot longer than expected. Force a sleep so we don't get killed by rtkit. - std::this_thread::sleep_for(std::chrono::microseconds(PULSE_TARGET_LATENCY_US)); - } - } MustStopWork_ = false; } diff --git a/src/audio/PulseAudioDevice.h b/src/audio/PulseAudioDevice.h index c680ecc2..cac50d10 100644 --- a/src/audio/PulseAudioDevice.h +++ b/src/audio/PulseAudioDevice.h @@ -57,7 +57,7 @@ public: // Lets audio system know that we're done with the work on the received // audio. - virtual void stopRealTimeWork() override; + virtual void stopRealTimeWork(bool fastMode = false) override; // Reverts real-time priority for current thread. virtual void clearHelperRealTime() override; @@ -82,7 +82,6 @@ private: int sampleRate_; int numChannels_; - thread_local static std::chrono::high_resolution_clock::time_point StartTime_; thread_local static bool MustStopWork_; sem_t sem_; diff --git a/src/audio/WASAPIAudioDevice.cpp b/src/audio/WASAPIAudioDevice.cpp index ec81d6df..67e30f2e 100644 --- a/src/audio/WASAPIAudioDevice.cpp +++ b/src/audio/WASAPIAudioDevice.cpp @@ -560,7 +560,7 @@ void WASAPIAudioDevice::startRealTimeWork() // empty } -void WASAPIAudioDevice::stopRealTimeWork() +void WASAPIAudioDevice::stopRealTimeWork(bool fastMode) { if (semaphore_ == nullptr) { @@ -570,12 +570,12 @@ void WASAPIAudioDevice::stopRealTimeWork() } // Wait a maximum of (bufferSize / sampleRate) seconds for the semaphore to return - DWORD result = WaitForSingleObject(semaphore_, (1000 * bufferFrameCount_) / sampleRate_); + DWORD result = WaitForSingleObject(semaphore_, ((1000 * bufferFrameCount_) / sampleRate_) >> (fastMode ? 1 : 0)); if (result != WAIT_TIMEOUT && result != WAIT_OBJECT_0) { // Fallback to a simple sleep. - IAudioDevice::stopRealTimeWork(); + IAudioDevice::stopRealTimeWork(fastMode); } } diff --git a/src/audio/WASAPIAudioDevice.h b/src/audio/WASAPIAudioDevice.h index 6bad6399..881d4936 100644 --- a/src/audio/WASAPIAudioDevice.h +++ b/src/audio/WASAPIAudioDevice.h @@ -60,7 +60,7 @@ public: // Lets audio system know that we're done with the work on the received // audio. - virtual void stopRealTimeWork() override; + virtual void stopRealTimeWork(bool fastMode = false) override; // Reverts real-time priority for current thread. virtual void clearHelperRealTime() override; diff --git a/src/gui/controls/plot_scalar.cpp b/src/gui/controls/plot_scalar.cpp index 9442f643..9fbaa11e 100644 --- a/src/gui/controls/plot_scalar.cpp +++ b/src/gui/controls/plot_scalar.cpp @@ -62,7 +62,7 @@ PlotScalar::PlotScalar(wxWindow* parent, m_graticule_a_step = graticule_a_step; assert(strlen(a_fmt) < 15); memset(m_a_fmt, 0, sizeof(m_a_fmt)); - strncpy(m_a_fmt, a_fmt, strlen(a_fmt)); + strncpy(m_a_fmt, a_fmt, sizeof(m_a_fmt) - 1); m_mini = mini; m_bar_graph = 0; m_logy = 0; diff --git a/src/gui/controls/plot_spectrum.cpp b/src/gui/controls/plot_spectrum.cpp index 44707a7a..7e5f113a 100644 --- a/src/gui/controls/plot_spectrum.cpp +++ b/src/gui/controls/plot_spectrum.cpp @@ -162,6 +162,7 @@ void PlotSpectrum::draw(wxGraphicsContext* ctx) break; default: assert(0); + mag = m_magdB[index]; // assume m_numSampleAveraging = 1 break; } @@ -396,4 +397,4 @@ void PlotSpectrum::OnKeyDown(wxKeyEvent& event) void PlotSpectrum::OnMouseMiddleDown(wxMouseEvent& event) { clickTune(FDMDV_FCENTRE); -} \ No newline at end of file +} diff --git a/src/gui/dialogs/freedv_reporter.cpp b/src/gui/dialogs/freedv_reporter.cpp index 7efb8e99..2c93f6d6 100644 --- a/src/gui/dialogs/freedv_reporter.cpp +++ b/src/gui/dialogs/freedv_reporter.cpp @@ -1018,7 +1018,7 @@ void FreeDVReporterDialog::OnStatusTextChange(wxCommandEvent& event) // reduces the number of times one must backspace in order to completely // remove the flag. int index = 0; - while (index < statusMsg.Length()) + while (index < (int)statusMsg.Length()) { auto chr = statusMsg.GetChar(index); if (chr.GetValue() == 0x1F3F4) @@ -1026,7 +1026,7 @@ void FreeDVReporterDialog::OnStatusTextChange(wxCommandEvent& event) log_debug("Found start char at index %d", index); auto endIndex = index + 1; bool foundEnd = false; - while (endIndex < statusMsg.Length()) + while (endIndex < (int)statusMsg.Length()) { auto endChar = statusMsg.GetChar(endIndex).GetValue(); if (endChar == 0xE007F) @@ -1432,7 +1432,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::execQueuedAction_() fn(); lk.lock(); - fnQueue_.erase(fnQueue_.begin()); + fnQueue_.pop_front(); size = fnQueue_.size(); lk.unlock(); } @@ -1529,6 +1529,12 @@ FreeDVReporterDialog::FreeDVReporterDataModel::FreeDVReporterDataModel(FreeDVRep FreeDVReporterDialog::FreeDVReporterDataModel::~FreeDVReporterDataModel() { setReporter(nullptr); + + for (auto& kvp : allReporterData_) + { + delete kvp.second; + } + allReporterData_.clear(); } void FreeDVReporterDialog::FreeDVReporterDataModel::setReporter(std::shared_ptr reporter) diff --git a/src/gui/dialogs/freedv_reporter.h b/src/gui/dialogs/freedv_reporter.h index cd7e4f77..0a19a317 100644 --- a/src/gui/dialogs/freedv_reporter.h +++ b/src/gui/dialogs/freedv_reporter.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include @@ -259,7 +259,7 @@ class FreeDVReporterDialog : public wxFrame std::shared_ptr reporter_; std::map allReporterData_; - std::vector > fnQueue_; + std::deque > fnQueue_; std::mutex fnQueueMtx_; std::recursive_mutex dataMtx_; bool isConnected_; diff --git a/src/main.cpp b/src/main.cpp index fed0143b..f722edcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -558,7 +558,7 @@ bool MainApp::OnInit() log_info("PYTHONPATH is %s", (const char*)ppath.ToUTF8()); #endif // __APPLE__ -#endif // _WIN32 || __APPLE__ +#endif // _WIN32 || __APPLE__ #if defined(UNOFFICIAL_RELEASE) // Terminate the application if the current date > expiration date diff --git a/src/pipeline/PlaybackStep.cpp b/src/pipeline/PlaybackStep.cpp index 0cfb4e54..41da686b 100644 --- a/src/pipeline/PlaybackStep.cpp +++ b/src/pipeline/PlaybackStep.cpp @@ -144,7 +144,7 @@ void PlaybackStep::nonRtThreadEntry_() } } - if (numRead < samplesAtSourceRate && codec2_fifo_used(outputFifo_) == 0) + if ((int)numRead < samplesAtSourceRate && codec2_fifo_used(outputFifo_) == 0) { //log_info("file read complete"); fileCompleteFn_(); diff --git a/src/pipeline/RADEReceiveStep.cpp b/src/pipeline/RADEReceiveStep.cpp index 221d4a1c..be8a28f7 100644 --- a/src/pipeline/RADEReceiveStep.cpp +++ b/src/pipeline/RADEReceiveStep.cpp @@ -38,6 +38,8 @@ RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan, rade_text , fargan_(fargan) , inputSampleFifo_(nullptr) , outputSampleFifo_(nullptr) + , pendingFeatures_(nullptr) + , pendingFeaturesIdx_(0) , featuresFile_(nullptr) , textPtr_(textPtr) { @@ -76,7 +78,8 @@ RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan, rade_text eooOut_ = new float[rade_n_eoo_bits(dv_)]; assert(eooOut_ != nullptr); - pendingFeatures_.reserve(rade_n_features_in_out(dv_)); + pendingFeatures_ = new float[NB_TOTAL_FEATURES]; + assert(pendingFeatures_ != nullptr); } RADEReceiveStep::~RADEReceiveStep() @@ -85,6 +88,7 @@ RADEReceiveStep::~RADEReceiveStep() delete[] inputBufCplx_; delete[] featuresOut_; delete[] eooOut_; + delete[] pendingFeatures_; outputSamples_ = nullptr; if (featuresFile_ != nullptr) @@ -181,35 +185,23 @@ std::shared_ptr RADEReceiveStep::execute(std::shared_ptr inputSamp for (int i = 0; i < nout; i++) { - pendingFeatures_.push_back(featuresOut_[i]); - } - - // FARGAN processing (features->analog audio) - while (pendingFeatures_.size() >= NB_TOTAL_FEATURES) - { - // XXX - lpcnet_demo reads NB_TOTAL_FEATURES from RADE - // but only processes NB_FEATURES of those for some reason. - float featuresIn[NB_FEATURES]; - for (int i = 0; i < NB_FEATURES; i++) + pendingFeatures_[pendingFeaturesIdx_++] = featuresOut_[i]; + if (pendingFeaturesIdx_ == NB_TOTAL_FEATURES) { - featuresIn[i] = pendingFeatures_[0]; - pendingFeatures_.erase(pendingFeatures_.begin()); - } - for (int i = 0; i < (NB_TOTAL_FEATURES - NB_FEATURES); i++) - { - pendingFeatures_.erase(pendingFeatures_.begin()); - } + pendingFeaturesIdx_ = 0; - float fpcm[LPCNET_FRAME_SIZE]; - short pcm[LPCNET_FRAME_SIZE]; - fargan_synthesize(fargan_, fpcm, featuresIn); - for (int i = 0; i < LPCNET_FRAME_SIZE; i++) - { - pcm[i] = (int)floor(.5 + MIN32(32767, MAX32(-32767, 32768.f*fpcm[i]))); - } + // FARGAN processing (features->analog audio) + float fpcm[LPCNET_FRAME_SIZE]; + short pcm[LPCNET_FRAME_SIZE]; + fargan_synthesize(fargan_, fpcm, pendingFeatures_); + for (int i = 0; i < LPCNET_FRAME_SIZE; i++) + { + pcm[i] = (int)floor(.5 + MIN32(32767, MAX32(-32767, 32768.f*fpcm[i]))); + } - *numOutputSamples += LPCNET_FRAME_SIZE; - codec2_fifo_write(outputSampleFifo_, pcm, LPCNET_FRAME_SIZE); + *numOutputSamples += LPCNET_FRAME_SIZE; + codec2_fifo_write(outputSampleFifo_, pcm, LPCNET_FRAME_SIZE); + } } } @@ -250,5 +242,5 @@ void RADEReceiveStep::reset() { codec2_fifo_read(outputSampleFifo_, &buf, 1); } - pendingFeatures_.clear(); + pendingFeaturesIdx_ = 0; } diff --git a/src/pipeline/RADEReceiveStep.h b/src/pipeline/RADEReceiveStep.h index 0b11b150..1131689d 100644 --- a/src/pipeline/RADEReceiveStep.h +++ b/src/pipeline/RADEReceiveStep.h @@ -25,7 +25,6 @@ #include #include -#include #include "IPipelineStep.h" #include "../freedv_interface.h" #include "rade_api.h" @@ -57,7 +56,8 @@ private: FARGANState* fargan_; struct FIFO* inputSampleFifo_; struct FIFO* outputSampleFifo_; - std::vector pendingFeatures_; + float* pendingFeatures_; + int pendingFeaturesIdx_; FILE* featuresFile_; rade_text_t textPtr_; diff --git a/src/pipeline/RADETransmitStep.cpp b/src/pipeline/RADETransmitStep.cpp index eae893a6..bb264da5 100644 --- a/src/pipeline/RADETransmitStep.cpp +++ b/src/pipeline/RADETransmitStep.cpp @@ -42,6 +42,8 @@ RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState) , encState_(encState) , inputSampleFifo_(nullptr) , outputSampleFifo_(nullptr) + , featureList_(nullptr) + , featureListIdx_(0) , featuresFile_(nullptr) { inputSampleFifo_ = codec2_fifo_create(RADE_SPEECH_SAMPLE_RATE); @@ -79,7 +81,8 @@ RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState) eooOutShort_ = new short[numEOOSamples + NUM_SAMPLES_SILENCE]; assert(eooOutShort_ != nullptr); - featureList_.reserve(rade_n_features_in_out(dv_)); + featureList_ = new float[rade_n_features_in_out(dv_)]; + assert(featureList_ != nullptr); } RADETransmitStep::~RADETransmitStep() @@ -88,6 +91,7 @@ RADETransmitStep::~RADETransmitStep() delete[] radeOutShort_; delete[] eooOut_; delete[] eooOutShort_; + delete[] featureList_; outputSamples_ = nullptr; if (featuresFile_ != nullptr) @@ -143,7 +147,7 @@ std::shared_ptr RADETransmitStep::execute(std::shared_ptr inputSam if ((*numOutputSamples + numSamplesPerTx) < maxSamples && codec2_fifo_used(inputSampleFifo_) >= LPCNET_FRAME_SIZE) { - unsigned int numRequiredFeaturesForRADE = rade_n_features_in_out(dv_); + int numRequiredFeaturesForRADE = rade_n_features_in_out(dv_); short pcm[LPCNET_FRAME_SIZE]; float features[NB_TOTAL_FEATURES]; @@ -160,36 +164,33 @@ std::shared_ptr RADETransmitStep::execute(std::shared_ptr inputSam for (int index = 0; index < NB_TOTAL_FEATURES; index++) { - featureList_.push_back(features[index]); - } + featureList_[featureListIdx_++] = features[index]; + if (featureListIdx_ == numRequiredFeaturesForRADE) + { + featureListIdx_ = 0; - // RADE TX handling - while (featureList_.size() >= numRequiredFeaturesForRADE) - { + // RADE TX handling #if defined(__clang__) #if defined(__has_feature) && __has_feature(realtime_sanitizer) - __rtsan_disable(); + __rtsan_disable(); #endif // defined(__has_feature) && __has_feature(realtime_sanitizer) #endif // defined(__clang__) - rade_tx(dv_, radeOut_, &featureList_[0]); + rade_tx(dv_, radeOut_, &featureList_[0]); #if defined(__clang__) #if defined(__has_feature) && __has_feature(realtime_sanitizer) - __rtsan_enable(); + __rtsan_enable(); #endif // defined(__has_feature) && __has_feature(realtime_sanitizer) #endif // defined(__clang__) - for (unsigned int index = 0; index < numRequiredFeaturesForRADE; index++) - { - featureList_.erase(featureList_.begin()); + for (int index = 0; index < numSamplesPerTx; index++) + { + // We only need the real component for TX. + radeOutShort_[index] = radeOut_[index].real * RADE_SCALING_FACTOR; + } + codec2_fifo_write(outputSampleFifo_, radeOutShort_, numSamplesPerTx); } - for (int index = 0; index < numSamplesPerTx; index++) - { - // We only need the real component for TX. - radeOutShort_[index] = radeOut_[index].real * RADE_SCALING_FACTOR; - } - codec2_fifo_write(outputSampleFifo_, radeOutShort_, numSamplesPerTx); } } } @@ -246,5 +247,5 @@ void RADETransmitStep::reset() { codec2_fifo_read(outputSampleFifo_, &buf, 1); } - featureList_.clear(); + featureListIdx_ = 0; } diff --git a/src/pipeline/RADETransmitStep.h b/src/pipeline/RADETransmitStep.h index ebade449..fd79542d 100644 --- a/src/pipeline/RADETransmitStep.h +++ b/src/pipeline/RADETransmitStep.h @@ -24,7 +24,6 @@ #define AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H #include -#include #include "IPipelineStep.h" #include "../freedv_interface.h" #include "rade_api.h" @@ -49,7 +48,8 @@ private: LPCNetEncState* encState_; struct FIFO* inputSampleFifo_; struct FIFO* outputSampleFifo_; - std::vector featureList_; + float* featureList_; + int featureListIdx_; FILE* featuresFile_; diff --git a/src/pipeline/TxRxThread.cpp b/src/pipeline/TxRxThread.cpp index d2502476..4122bd67 100644 --- a/src/pipeline/TxRxThread.cpp +++ b/src/pipeline/TxRxThread.cpp @@ -476,8 +476,18 @@ void* TxRxThread::Entry() if (m_tx) txProcessing_(); else rxProcessing_(); - - helper_->stopRealTimeWork(); + + // Determine whether we need to pause for a shorter amount + // of time to avoid dropouts. + paCallBackData *cbData = g_rxUserdata; + struct FIFO* outFifo = cbData->outfifo1; + if (!m_tx) + { + outFifo = (g_nSoundCards == 1) ? cbData->outfifo1 : cbData->outfifo2; + } + auto totalFifoCapacity = codec2_fifo_used(outFifo) + codec2_fifo_free(outFifo); + auto fifoUsed = codec2_fifo_used(outFifo); + helper_->stopRealTimeWork(fifoUsed < totalFifoCapacity / 2); } // Force pipeline to delete itself when we're done with the thread. diff --git a/src/topFrame.cpp b/src/topFrame.cpp index cf117559..8d4a92fb 100644 --- a/src/topFrame.cpp +++ b/src/topFrame.cpp @@ -537,7 +537,12 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const m_btnHelp->SetToolTip(_("Get help with FreeDV.")); helpSizer->Add(m_btnHelp, 0, wxALIGN_CENTER|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5); leftSizer->SetMinSize(wxSize(-1, 375)); + +#if !wxCHECK_VERSION(3,2,0) + leftOuterSizer->Add(leftSizer, 0, wxALL | wxEXPAND | wxFIXED_MINSIZE, 1); +#else leftOuterSizer->Add(leftSizer, 2, wxALL | wxEXPAND | wxFIXED_MINSIZE, 1); +#endif // !wxCHECK_VERSION(3,2,0) leftOuterSizer->Add(helpSizer, 0, wxFIXED_MINSIZE | wxALL | wxEXPAND, 1); bSizer1->Add(leftOuterSizer, 0, wxALL|wxEXPAND, 5); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 8afd262e..22c46fcc 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -2,11 +2,16 @@ if(WIN32) set(UTIL_WIN32_FILES logging/libfmemopen.c Win32COMObject.cpp) endif(WIN32) +if(LINUX) + set(UTIL_LINUX_FILES timespec.c) +endif(LINUX) + add_library(fdv_util STATIC SocketIoClient.cpp ThreadedObject.cpp ThreadedTimer.cpp TcpConnectionHandler.cpp logging/ulog.c + ${UTIL_LINUX_FILES} ${UTIL_WIN32_FILES} ) diff --git a/src/util/IRealtimeHelper.h b/src/util/IRealtimeHelper.h index 4975ad77..24b3b24b 100644 --- a/src/util/IRealtimeHelper.h +++ b/src/util/IRealtimeHelper.h @@ -36,8 +36,9 @@ public: virtual void startRealTimeWork() = 0; // Lets audio system know that we're done with the work on the received - // audio. - virtual void stopRealTimeWork() = 0; + // audio. fastMode is an optional argument that indicates whether we + // need to reduce timeouts due to buffers running empty. + virtual void stopRealTimeWork(bool fastMode = false) = 0; // Reverts real-time priority for current thread. virtual void clearHelperRealTime() = 0; diff --git a/src/util/ThreadedObject.cpp b/src/util/ThreadedObject.cpp index 194369d3..ed97b2e1 100644 --- a/src/util/ThreadedObject.cpp +++ b/src/util/ThreadedObject.cpp @@ -106,7 +106,7 @@ void ThreadedObject::eventLoop_() } fn = eventQueue_[0]; - eventQueue_.erase(eventQueue_.begin()); + eventQueue_.pop_front(); } if (fn) @@ -117,4 +117,4 @@ void ThreadedObject::eventLoop_() count--; } while (count > 0); } -} \ No newline at end of file +} diff --git a/src/util/ThreadedObject.h b/src/util/ThreadedObject.h index d0abc3ab..eff29c4d 100644 --- a/src/util/ThreadedObject.h +++ b/src/util/ThreadedObject.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include class ThreadedObject @@ -44,11 +44,11 @@ protected: private: bool isDestroying_; std::thread objectThread_; - std::vector > eventQueue_; + std::deque > eventQueue_; std::recursive_mutex eventQueueMutex_; std::condition_variable_any eventQueueCV_; void eventLoop_(); }; -#endif // THREADED_OBJECT_H \ No newline at end of file +#endif // THREADED_OBJECT_H diff --git a/src/util/timespec.c b/src/util/timespec.c new file mode 100644 index 00000000..bd339936 --- /dev/null +++ b/src/util/timespec.c @@ -0,0 +1,979 @@ +/* Functions for working with timespec structures + * Written by Daniel Collins (2017-2021) + * timespec_mod by Alex Forencich (2019) + * Various contributions by Ingo Albrecht (2021) + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * 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 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. + * + * For more information, please refer to +*/ + +/** \file timespec.c + * \brief Functions for working with timespec structures. + * + * This library aims to provide a comprehensive set of functions with + * well-defined behaviour that handle all edge cases (e.g. negative values) in + * a sensible manner. + * + * Negative values are allowed in the tv_sec and/or tv_usec field of timespec + * structures, tv_usec is always relative to tv_sec, so mixing positive and + * negative values will produce consistent results: + * + *
+ * { tv_sec = 1,  tv_nsec = 500000000  } ==  1.5 seconds
+ * { tv_sec = 1,  tv_nsec = 0          } ==  1.0 seconds
+ * { tv_sec = 1,  tv_nsec = -500000000 } ==  0.5 seconds
+ * { tv_sec = 0,  tv_nsec = 500000000  } ==  0.5 seconds
+ * { tv_sec = 0,  tv_nsec = 0          } ==  0.0 seconds
+ * { tv_sec = 0,  tv_nsec = -500000000 } == -0.5 seconds
+ * { tv_sec = -1, tv_nsec = 500000000  } == -0.5 seconds
+ * { tv_sec = -1, tv_nsec = 0          } == -1.0 seconds
+ * { tv_sec = -1, tv_nsec = -500000000 } == -1.5 seconds
+ * 
+ * + * Furthermore, any timespec structure processed or returned by library functions + * is normalised according to the rules in timespec_normalise(). +*/ + +#include +#include +#include +#include + +#include "timespec.h" + +#define NSEC_PER_SEC 1000000000 + +/** \fn struct timespec timespec_add(struct timespec ts1, struct timespec ts2) + * \brief Returns the result of adding two timespec structures. +*/ +struct timespec timespec_add(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + ts1.tv_sec += ts2.tv_sec; + ts1.tv_nsec += ts2.tv_nsec; + + return timespec_normalise(ts1); +} + +/** \fn struct timespec timespec_sub(struct timespec ts1, struct timespec ts2) + * \brief Returns the result of subtracting ts2 from ts1. +*/ +struct timespec timespec_sub(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + ts1.tv_sec -= ts2.tv_sec; + ts1.tv_nsec -= ts2.tv_nsec; + + return timespec_normalise(ts1); +} + +/** \fn struct timespec timespec_mod(struct timespec ts1, struct timespec ts2) + * \brief Returns the remainder left over after dividing ts1 by ts2 (ts1%ts2). +*/ +struct timespec timespec_mod(struct timespec ts1, struct timespec ts2) +{ + int i = 0; + bool neg1 = false; + bool neg2 = false; + + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + /* If ts2 is zero, just return ts1 + */ + if (ts2.tv_sec == 0 && ts2.tv_nsec == 0) + { + return ts1; + } + + /* If inputs are negative, flip and record sign + */ + if (ts1.tv_sec < 0 || ts1.tv_nsec < 0) + { + neg1 = true; + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + if (ts2.tv_sec < 0 || ts2.tv_nsec < 0) + { + neg2 = true; + ts2.tv_sec = -ts2.tv_sec; + ts2.tv_nsec = -ts2.tv_nsec; + } + + /* Shift ts2 until it is larger than ts1 or is about to overflow + */ + while ((ts2.tv_sec < (LONG_MAX >> 1)) && timespec_ge(ts1, ts2)) + { + i++; + ts2.tv_nsec <<= 1; + ts2.tv_sec <<= 1; + if (ts2.tv_nsec > NSEC_PER_SEC) + { + ts2.tv_nsec -= NSEC_PER_SEC; + ts2.tv_sec++; + } + } + + /* Division by repeated subtraction + */ + while (i >= 0) + { + if (timespec_ge(ts1, ts2)) + { + ts1 = timespec_sub(ts1, ts2); + } + + if (i == 0) + { + break; + } + + i--; + if (ts2.tv_sec & 1) + { + ts2.tv_nsec += NSEC_PER_SEC; + } + ts2.tv_nsec >>= 1; + ts2.tv_sec >>= 1; + } + + /* If signs differ and result is nonzero, subtract once more to cross zero + */ + if (neg1 ^ neg2 && (ts1.tv_sec != 0 || ts1.tv_nsec != 0)) + { + ts1 = timespec_sub(ts1, ts2); + } + + /* Restore sign + */ + if (neg1) + { + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + return ts1; +} + +/** \fn struct timespec timespec_min(struct timespec ts1, struct timespec ts2) + * \brief Return the lesser one of the two given timespec values. +*/ +struct timespec timespec_min(struct timespec ts1, struct timespec ts2) { + if(timespec_le(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +/** \fn struct timespec timespec_max(struct timespec ts1, struct timespec ts2) + * \brief Return the greater one of the two given timespec values. +*/ +struct timespec timespec_max(struct timespec ts1, struct timespec ts2) { + if(timespec_ge(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +/** \fn struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) + * \brief Clamp the value of TS between MIN and MAX. +*/ +struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) { + if(timespec_gt(ts, max)) { + return max; + } + if(timespec_lt(ts, min)) { + return min; + } + return ts; +} + +/** \fn int timespec_cmp(struct timespec ts1, struct timespec ts2) + * \brief Returns (1, 0, -1) if ts1 is (greater than, equal to, less than) to ts2. +*/ +int timespec_cmp(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + if(ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec) + { + return 0; + } + else if((ts1.tv_sec > ts2.tv_sec) + || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)) + { + return 1; + } + else { + return -1; + } +} + +/** \fn bool timespec_eq(struct timespec ts1, struct timespec ts2) + * \brief Returns true if the two timespec structures are equal. +*/ +bool timespec_eq(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec); +} + +/** \fn bool timespec_gt(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is greater than ts2. +*/ +bool timespec_gt(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)); +} + +/** \fn bool timespec_ge(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is greater than or equal to ts2. +*/ +bool timespec_ge(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec >= ts2.tv_nsec)); +} + +/** \fn bool timespec_lt(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is less than ts2. +*/ +bool timespec_lt(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec)); +} + +/** \fn bool timespec_le(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is less than or equal to ts2. +*/ +bool timespec_le(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec <= ts2.tv_nsec)); +} + +/** \fn struct timespec timespec_from_double(double s) + * \brief Converts a fractional number of seconds to a timespec. +*/ +struct timespec timespec_from_double(double s) +{ + struct timespec ts = { + .tv_sec = s, + .tv_nsec = (s - (long)(s)) * NSEC_PER_SEC, + }; + + return timespec_normalise(ts); +} + +/** \fn double timespec_to_double(struct timespec ts) + * \brief Converts a timespec to a fractional number of seconds. +*/ +double timespec_to_double(struct timespec ts) +{ + return ((double)(ts.tv_sec) + ((double)(ts.tv_nsec) / NSEC_PER_SEC)); +} + +/** \fn struct timespec timespec_from_timeval(struct timeval tv) + * \brief Converts a timeval to a timespec. +*/ +struct timespec timespec_from_timeval(struct timeval tv) +{ + struct timespec ts = { + .tv_sec = tv.tv_sec, + .tv_nsec = tv.tv_usec * 1000 + }; + + return timespec_normalise(ts); +} + +/** \fn struct timeval timespec_to_timeval(struct timespec ts) + * \brief Converts a timespec to a timeval. +*/ +struct timeval timespec_to_timeval(struct timespec ts) +{ + ts = timespec_normalise(ts); + + struct timeval tv = { + .tv_sec = ts.tv_sec, + .tv_usec = ts.tv_nsec / 1000, + }; + + return tv; +} + +/** \fn struct timespec timespec_from_ms(long milliseconds) + * \brief Converts an integer number of milliseconds to a timespec. +*/ +struct timespec timespec_from_ms(long milliseconds) +{ + struct timespec ts = { + .tv_sec = (milliseconds / 1000), + .tv_nsec = (milliseconds % 1000) * 1000000, + }; + + return timespec_normalise(ts); +} + +/** \fn long timespec_to_ms(struct timespec ts) + * \brief Converts a timespec to an integer number of milliseconds. +*/ +long timespec_to_ms(struct timespec ts) +{ + return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); +} + +/** \fn struct timespec timespec_normalise(struct timespec ts) + * \brief Normalises a timespec structure. + * + * Returns a normalised version of a timespec structure, according to the + * following rules: + * + * 1) If tv_nsec is >=1,000,000,00 or <=-1,000,000,000, flatten the surplus + * nanoseconds into the tv_sec field. + * + * 2) If tv_nsec is negative, decrement tv_sec and roll tv_nsec up to represent + * the same value attainable by ADDING nanoseconds to tv_sec. +*/ +struct timespec timespec_normalise(struct timespec ts) +{ + while(ts.tv_nsec >= NSEC_PER_SEC) + { + ++(ts.tv_sec); + ts.tv_nsec -= NSEC_PER_SEC; + } + + while(ts.tv_nsec <= -NSEC_PER_SEC) + { + --(ts.tv_sec); + ts.tv_nsec += NSEC_PER_SEC; + } + + if(ts.tv_nsec < 0) + { + /* Negative nanoseconds isn't valid according to POSIX. + * Decrement tv_sec and roll tv_nsec over. + */ + + --(ts.tv_sec); + ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec); + } + + return ts; +} + +#ifdef TEST +#include + +#define TEST_NORMALISE(ts_sec, ts_nsec, expect_sec, expect_nsec) { \ + struct timespec in = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + struct timespec got = timespec_normalise(in); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_normalise({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_BINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect_sec, expect_nsec) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + struct timespec got = func(ts1, ts2); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf(#func "({%ld, %ld}, {%ld, %ld}) returned wrong values\n", \ + (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TRINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, ts3_sec, ts3_nsec, expect_sec, expect_nsec) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + struct timespec ts3 = { .tv_sec = ts3_sec, .tv_nsec = ts3_nsec }; \ + struct timespec got = func(ts1, ts2, ts3); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf(#func "({%ld, %ld}, {%ld, %ld}, {%ld, %ld}) returned wrong values\n", \ + (long)(ts1_sec), (long)(ts1_nsec), \ + (long)(ts2_sec), (long)(ts2_nsec), \ + (long)(ts3_sec), (long)(ts3_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TEST_FUNC(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + int got = func(ts1, ts2); \ + if(got != expect) \ + { \ + printf("%s:%d: " #func "({%ld, %ld}, {%ld, %ld}) returned %d, expected %s\n", __FILE__, __LINE__, \ + (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec), \ + got, #expect); \ + ++result; \ + } \ +} + +#define TEST_FROM_DOUBLE(d_secs, expect_sec, expect_nsec) { \ + struct timespec got = timespec_from_double(d_secs); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_double(%f) returned wrong values\n", __FILE__, __LINE__, (double)(d_secs)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_DOUBLE(ts_sec, ts_nsec, expect) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + double got = timespec_to_double(ts); \ + if(got != expect) { \ + printf("%s:%d: timespec_to_double({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: %f\n", (double)(expect)); \ + printf(" Got: %f\n", got); \ + ++result; \ + } \ +} + +#define TEST_FROM_TIMEVAL(in_sec, in_usec, expect_sec, expect_nsec) { \ + struct timeval tv = { .tv_sec = in_sec, .tv_usec = in_usec }; \ + struct timespec got = timespec_from_timeval(tv); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(in_sec), (long)(in_usec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_TIMEVAL(ts_sec, ts_nsec, expect_sec, expect_usec) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + struct timeval got = timespec_to_timeval(ts); \ + if(got.tv_sec != expect_sec || got.tv_usec != expect_usec) \ + { \ + printf("%s:%d: timespec_to_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_usec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_usec)); \ + ++result; \ + } \ +} + +#define TEST_FROM_MS(msecs, expect_sec, expect_nsec) { \ + struct timespec got = timespec_from_ms(msecs); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_ms(%ld) returned wrong values\n", __FILE__, __LINE__, (long)(msecs)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_MS(ts_sec, ts_nsec, expect) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + long got = timespec_to_ms(ts); \ + if(got != expect) { \ + printf("%s:%d: timespec_to_ms({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: %ld\n", (long)(expect)); \ + printf(" Got: %ld\n", got); \ + ++result; \ + } \ +} + +int main() +{ + int result = 0; + + // timespec_add + + TEST_BINOP(timespec_add, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_add, 0,0, 1,0, 1,0); + TEST_BINOP(timespec_add, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_add, 1,0, 1,0, 2,0); + TEST_BINOP(timespec_add, 1,500000000, 1,0, 2,500000000); + TEST_BINOP(timespec_add, 1,0, 1,500000000, 2,500000000); + TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0); + TEST_BINOP(timespec_add, 1,500000000, 1,499999999, 2,999999999); + TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0); + TEST_BINOP(timespec_add, 1,999999999, 1,999999999, 3,999999998); + TEST_BINOP(timespec_add, 0,500000000, 1,500000000, 2,0); + TEST_BINOP(timespec_add, 1,500000000, 0,500000000, 2,0); + + // timespec_sub + + TEST_BINOP(timespec_sub, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_sub, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_sub, 1,0, 1,0, 0,0); + TEST_BINOP(timespec_sub, 1,500000000, 0,500000000, 1,0); + TEST_BINOP(timespec_sub, 5,500000000, 2,999999999, 2,500000001); + TEST_BINOP(timespec_sub, 0,0, 1,0, -1,0); + TEST_BINOP(timespec_sub, 0,500000000, 1,500000000, -1,0); + TEST_BINOP(timespec_sub, 0,0, 1,500000000, -2,500000000); + TEST_BINOP(timespec_sub, 1,0, 1,500000000, -1,500000000); + TEST_BINOP(timespec_sub, 1,0, 1,499999999, -1,500000001); + + // timespec_mod + + TEST_BINOP(timespec_mod, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_mod, 0,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_mod, 1,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, 3,0, 1,0); + TEST_BINOP(timespec_mod, 10,0, -3,0, -2,0); + TEST_BINOP(timespec_mod, -10,0, 3,0, 2,0); + TEST_BINOP(timespec_mod, -10,0, -3,0, -1,0); + TEST_BINOP(timespec_mod, 10,0, 5,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, -5,0, 0,0); + TEST_BINOP(timespec_mod, -10,0, 5,0, 0,0); + TEST_BINOP(timespec_mod, -10,0, -5,0, 0,0); + TEST_BINOP(timespec_mod, 1,500000000, 0,500000000, 0,0); + TEST_BINOP(timespec_mod, 5,500000000, 2,999999999, 2,500000001); + TEST_BINOP(timespec_mod, 0,500000000, 1,500000000, 0,500000000); + TEST_BINOP(timespec_mod, 0,0, 1,500000000, 0,0); + TEST_BINOP(timespec_mod, 1,0, 1,500000000, 1,0); + TEST_BINOP(timespec_mod, 1,0, 0,1, 0,0); + TEST_BINOP(timespec_mod, 1,123456789, 0,1000, 0,789); + TEST_BINOP(timespec_mod, 1,0, 0,9999999, 0,100); + TEST_BINOP(timespec_mod, 12345,54321, 0,100001, 0,5555); + TEST_BINOP(timespec_mod, LONG_MAX,0, 0,1, 0,0); + TEST_BINOP(timespec_mod, LONG_MAX,0, LONG_MAX,1, LONG_MAX,0); + + // timespec_clamp + + TEST_TRINOP(timespec_clamp, 0,0, 0,0, 0,0, 0,0); + + TEST_TRINOP(timespec_clamp, 1000,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 1500,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 1999,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 2000,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 2001,0, 2000,0, 3000,0, 2001,0); + TEST_TRINOP(timespec_clamp, 2250,0, 2000,0, 3000,0, 2250,0); + TEST_TRINOP(timespec_clamp, 2500,0, 2000,0, 3000,0, 2500,0); + TEST_TRINOP(timespec_clamp, 2750,0, 2000,0, 3000,0, 2750,0); + TEST_TRINOP(timespec_clamp, 2999,0, 2000,0, 3000,0, 2999,0); + TEST_TRINOP(timespec_clamp, 3000,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 3001,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 3500,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 4000,0, 2000,0, 3000,0, 3000,0); + + TEST_TRINOP(timespec_clamp, 0,1000, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,1500, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,1999, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,2000, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,2001, 0,2000, 0,3000, 0,2001); + TEST_TRINOP(timespec_clamp, 0,2250, 0,2000, 0,3000, 0,2250); + TEST_TRINOP(timespec_clamp, 0,2500, 0,2000, 0,3000, 0,2500); + TEST_TRINOP(timespec_clamp, 0,2750, 0,2000, 0,3000, 0,2750); + TEST_TRINOP(timespec_clamp, 0,2999, 0,2000, 0,3000, 0,2999); + TEST_TRINOP(timespec_clamp, 0,3000, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,3001, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,3500, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,4000, 0,2000, 0,3000, 0,3000); + + TEST_TRINOP(timespec_clamp,0,-1000, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-1999, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2001, 0,-3000, 0,-2000, 0,-2001); + TEST_TRINOP(timespec_clamp,0,-2250, 0,-3000, 0,-2000, 0,-2250); + TEST_TRINOP(timespec_clamp,0,-2500, 0,-3000, 0,-2000, 0,-2500); + TEST_TRINOP(timespec_clamp,0,-2750, 0,-3000, 0,-2000, 0,-2750); + TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,-2000, 0,-2999); + TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3500, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000); + + TEST_TRINOP(timespec_clamp,0,-4000, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,3000, 0,-2999); + TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,3000, 0,-1500); + TEST_TRINOP(timespec_clamp,0, -1, 0,-3000, 0,3000, 0, -1); + TEST_TRINOP(timespec_clamp,0, 0, 0,-3000, 0,3000, 0, 0); + TEST_TRINOP(timespec_clamp,0, 1, 0,-3000, 0,3000, 0, 1); + TEST_TRINOP(timespec_clamp,0, 1500, 0,-3000, 0,3000, 0, 1500); + TEST_TRINOP(timespec_clamp,0, 2999, 0,-3000, 0,3000, 0, 2999); + TEST_TRINOP(timespec_clamp,0, 3000, 0,-3000, 0,3000, 0, 3000); + TEST_TRINOP(timespec_clamp,0, 3001, 0,-3000, 0,3000, 0, 3000); + TEST_TRINOP(timespec_clamp,0, 4000, 0,-3000, 0,3000, 0, 3000); + + // timespec_min + + TEST_BINOP(timespec_min, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_min, 0,0, 1,0, 0,0); + TEST_BINOP(timespec_min, 1,0, 0,0, 0,0); + TEST_BINOP(timespec_min, 1,0, 1,0, 1,0); + TEST_BINOP(timespec_min, 10,0, 1,0, 1,0); + TEST_BINOP(timespec_min, 10,0, 3,0, 3,0); + TEST_BINOP(timespec_min, 10,0, -3,0, -3,0); + TEST_BINOP(timespec_min, -10,0, 3,0, -10,0); + TEST_BINOP(timespec_min, -10,0, -3,0, -10,0); + TEST_BINOP(timespec_min, 10,0, 5,0, 5,0); + TEST_BINOP(timespec_min, 10,0, -5,0, -5,0); + TEST_BINOP(timespec_min, -10,0, 5,0, -10,0); + TEST_BINOP(timespec_min, -10,0, -5,0, -10,0); + TEST_BINOP(timespec_min, 1,500000000, 0,500000000, 0,500000000); + TEST_BINOP(timespec_min, 5,500000000, 2,999999999, 2,999999999); + TEST_BINOP(timespec_min, 0,500000000, 1,500000000, 0,500000000); + TEST_BINOP(timespec_min, 0,0, 1,500000000, 0,0); + TEST_BINOP(timespec_min, 1,0, 1,500000000, 1,0); + TEST_BINOP(timespec_min, 1,0, 0,1, 0,1); + TEST_BINOP(timespec_min, 1,123456789, 0,1000, 0,1000); + TEST_BINOP(timespec_min, 1,0, 0,9999999, 0,9999999); + TEST_BINOP(timespec_min, 12345,54321, 0,100001, 0,100001); + TEST_BINOP(timespec_min, LONG_MIN,0, 0,1, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, 0,-1, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MAX,0, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MAX,0, 0,1, 0,1); + TEST_BINOP(timespec_min, LONG_MAX,0, 0,-1, 0,-1); + TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MIN,0, LONG_MIN,0); + + // timespec_max + + TEST_BINOP(timespec_max, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_max, 0,0, 1,0, 1,0); + TEST_BINOP(timespec_max, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_max, 1,0, 1,0, 1,0); + TEST_BINOP(timespec_max, 10,0, 1,0, 10,0); + TEST_BINOP(timespec_max, 10,0, 3,0, 10,0); + TEST_BINOP(timespec_max, 10,0, -3,0, 10,0); + TEST_BINOP(timespec_max, -10,0, 3,0, 3,0); + TEST_BINOP(timespec_max, -10,0, -3,0, -3,0); + TEST_BINOP(timespec_max, 10,0, 5,0, 10,0); + TEST_BINOP(timespec_max, 10,0, -5,0, 10,0); + TEST_BINOP(timespec_max, -10,0, 5,0, 5,0); + TEST_BINOP(timespec_max, -10,0, -5,0, -5,0); + TEST_BINOP(timespec_max, 1,500000000, 0,500000000, 1,500000000); + TEST_BINOP(timespec_max, 5,500000000, 2,999999999, 5,500000000); + TEST_BINOP(timespec_max, 0,500000000, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 0,0, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 1,0, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 1,0, 0,1, 1,0); + TEST_BINOP(timespec_max, 1,123456789, 0,1000, 1,123456789); + TEST_BINOP(timespec_max, 1,0, 0,9999999, 1,0); + TEST_BINOP(timespec_max, 12345,54321, 0,100001, 12345,54321); + TEST_BINOP(timespec_max, LONG_MIN,0, 0,1, 0,1); + TEST_BINOP(timespec_max, LONG_MIN,0, 0,-1, 0,-1); + TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0); + TEST_BINOP(timespec_max, LONG_MAX,0, 0,1, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, 0,-1, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MIN,0, LONG_MAX,0); + + // timespec_cmp + + TEST_TEST_FUNC(timespec_cmp, 0,0, 0,0, 0); + TEST_TEST_FUNC(timespec_cmp, 100,0, 100,0, 0); + TEST_TEST_FUNC(timespec_cmp, -100,0, -100,0, 0); + + TEST_TEST_FUNC(timespec_cmp, 1,0, 0,0, 1); + TEST_TEST_FUNC(timespec_cmp, 0,0, 1,0, -1); + TEST_TEST_FUNC(timespec_cmp, 0,1, 0,0, 1); + TEST_TEST_FUNC(timespec_cmp, 0,0, 0,1, -1); + TEST_TEST_FUNC(timespec_cmp, 1,0, 0,100, 1); + TEST_TEST_FUNC(timespec_cmp, 0,100 , 1,0, -1); + + TEST_TEST_FUNC(timespec_cmp, -0,-0, 0,0, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000000, -11,500000000, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,499999999, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,500000001, -1); + TEST_TEST_FUNC(timespec_cmp, -11,500000001, -10,-500000001, 1); + + // timespec_eq + + TEST_TEST_FUNC(timespec_eq, 0,0, 0,0, true); + TEST_TEST_FUNC(timespec_eq, 100,0, 100,0, true); + TEST_TEST_FUNC(timespec_eq, -200,0, -200,0, true); + TEST_TEST_FUNC(timespec_eq, 0,300, 0,300, true); + TEST_TEST_FUNC(timespec_eq, 0,-400, 0,-400, true); + + TEST_TEST_FUNC(timespec_eq, 100,1, 100,0, false); + TEST_TEST_FUNC(timespec_eq, 101,0, 100,0, false); + TEST_TEST_FUNC(timespec_eq, -100,0, 100,0, false); + TEST_TEST_FUNC(timespec_eq, 0,10, 0,-10, false); + + TEST_TEST_FUNC(timespec_eq, -0,-0, 0,0, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,500000001, false); + + // timespec_gt + + TEST_TEST_FUNC(timespec_gt, 1,0, 0,0, true); + TEST_TEST_FUNC(timespec_gt, 0,0, -1,0, true); + TEST_TEST_FUNC(timespec_gt, 0,1, 0,0, true); + TEST_TEST_FUNC(timespec_gt, 0,0, 0,-1, true); + + TEST_TEST_FUNC(timespec_gt, 1,0, 1,0, false); + TEST_TEST_FUNC(timespec_gt, 1,1, 1,1, false); + TEST_TEST_FUNC(timespec_gt, -1,0, 0,0, false); + TEST_TEST_FUNC(timespec_gt, 0,-1, 0,0, false); + + TEST_TEST_FUNC(timespec_gt, 0,0, -0,-0, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000000, -11,500000000, false); + TEST_TEST_FUNC(timespec_gt, -11,500000000, -10,-500000000, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,499999999, false); + TEST_TEST_FUNC(timespec_gt, -11,499999999, -11,499999999, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,500000001, false); + TEST_TEST_FUNC(timespec_gt, -11,500000001, -10,-500000001, true); + + // timespec_ge + + TEST_TEST_FUNC(timespec_ge, 1,0, 0,0, true); + TEST_TEST_FUNC(timespec_ge, 0,0, -1,0, true); + TEST_TEST_FUNC(timespec_ge, 0,1, 0,0, true); + TEST_TEST_FUNC(timespec_ge, 0,0, 0,-1, true); + TEST_TEST_FUNC(timespec_ge, 1,0, 1,0, true); + TEST_TEST_FUNC(timespec_ge, 1,1, 1,1, true); + + TEST_TEST_FUNC(timespec_ge, -1,0, 0,0, false); + TEST_TEST_FUNC(timespec_ge, 0,-1, 0,0, false); + + TEST_TEST_FUNC(timespec_ge, 0,0, -0,-0, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_ge, -11,500000000, -10,-500000000, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_ge, -11,499999999, -11,499999999, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,500000001, false); + TEST_TEST_FUNC(timespec_ge, -11,500000001, -10,-500000001, true); + + // timespec_lt + + TEST_TEST_FUNC(timespec_lt, 0,0, 1,0, true); + TEST_TEST_FUNC(timespec_lt, -1,0, 0,0, true); + TEST_TEST_FUNC(timespec_lt, 0,0, 0,1, true); + TEST_TEST_FUNC(timespec_lt, 0,-1, 0,0, true); + + TEST_TEST_FUNC(timespec_lt, 1,0, 1,0, false); + TEST_TEST_FUNC(timespec_lt, 1,1, 1,1, false); + TEST_TEST_FUNC(timespec_lt, 0,0, -1,0, false); + TEST_TEST_FUNC(timespec_lt, 0,0, 0,-1, false); + + TEST_TEST_FUNC(timespec_lt, 0,0, -0,-0, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000000, -11,500000000, false); + TEST_TEST_FUNC(timespec_lt, -11,500000000, -10,-500000000, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,499999999, false); + TEST_TEST_FUNC(timespec_lt, -11,499999999, -11,499999999, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,500000001, true); + TEST_TEST_FUNC(timespec_lt, -11,500000001, -10,-500000001, false); + + // timespec_le + + TEST_TEST_FUNC(timespec_le, 0,0, 1,0, true); + TEST_TEST_FUNC(timespec_le, -1,0, 0,0, true); + TEST_TEST_FUNC(timespec_le, 0,0, 0,1, true); + TEST_TEST_FUNC(timespec_le, 0,-1, 0,0, true); + TEST_TEST_FUNC(timespec_le, 1,0, 1,0, true); + TEST_TEST_FUNC(timespec_le, 1,1, 1,1, true); + + TEST_TEST_FUNC(timespec_le, 0,0, -1,0, false); + TEST_TEST_FUNC(timespec_le, 0,0, 0,-1, false); + + TEST_TEST_FUNC(timespec_le, 0,0, -0,-0, true); + TEST_TEST_FUNC(timespec_le, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_le, -11,500000000, -10,-500000000, true); + TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_le, -11,499999999, -11,499999999, true); + TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,500000001, true); + TEST_TEST_FUNC(timespec_le, -11,500000001, -10,-500000001, false); + + // timespec_from_double + + TEST_FROM_DOUBLE(0.0, 0,0); + TEST_FROM_DOUBLE(10.0, 10,0); + TEST_FROM_DOUBLE(-10.0, -10,0); + TEST_FROM_DOUBLE(0.5, 0,500000000); + TEST_FROM_DOUBLE(-0.5, -1,500000000); + TEST_FROM_DOUBLE(10.5, 10,500000000); + TEST_FROM_DOUBLE(-10.5, -11,500000000); + + // timespec_to_double + + TEST_TO_DOUBLE(0,0, 0.0); + TEST_TO_DOUBLE(10,0, 10.0); + TEST_TO_DOUBLE(-10,0, -10.0); + TEST_TO_DOUBLE(0,500000000, 0.5); + TEST_TO_DOUBLE(0,-500000000, -0.5); + TEST_TO_DOUBLE(10,500000000, 10.5); + TEST_TO_DOUBLE(10,-500000000, 9.5); + TEST_TO_DOUBLE(-10,500000000, -9.5); + TEST_TO_DOUBLE(-10,-500000000, -10.5); + + // timespec_from_timeval + + TEST_FROM_TIMEVAL(0,0, 0,0); + TEST_FROM_TIMEVAL(1,0, 1,0); + TEST_FROM_TIMEVAL(1000,0, 1000,0); + TEST_FROM_TIMEVAL(0,0, 0,0); + TEST_FROM_TIMEVAL(-1,0, -1,0); + TEST_FROM_TIMEVAL(-1000,0, -1000,0); + + TEST_FROM_TIMEVAL(1,1, 1,1000); + TEST_FROM_TIMEVAL(1,1000, 1,1000000); + TEST_FROM_TIMEVAL(1,-1, 0,999999000); + TEST_FROM_TIMEVAL(1,-1000, 0,999000000); + TEST_FROM_TIMEVAL(-1,-1, -2,999999000); + TEST_FROM_TIMEVAL(-1,-1000, -2,999000000); + + // timespec_to_timeval + + TEST_TO_TIMEVAL(0,0, 0,0); + TEST_TO_TIMEVAL(1,0, 1,0); + TEST_TO_TIMEVAL(10,0, 10,0); + TEST_TO_TIMEVAL(-1,0, -1,0); + TEST_TO_TIMEVAL(-10,0, -10,0); + + TEST_TO_TIMEVAL(1,1, 1,0); + TEST_TO_TIMEVAL(1,999, 1,0); + TEST_TO_TIMEVAL(1,1000, 1,1); + TEST_TO_TIMEVAL(1,1001, 1,1); + TEST_TO_TIMEVAL(1,2000, 1,2); + TEST_TO_TIMEVAL(1,2000000, 1,2000); + + TEST_TO_TIMEVAL(1,-1, 0,999999); + TEST_TO_TIMEVAL(1,-999, 0,999999); + TEST_TO_TIMEVAL(1,-1000, 0,999999); + TEST_TO_TIMEVAL(1,-1001, 0,999998); + TEST_TO_TIMEVAL(1,-2000, 0,999998); + TEST_TO_TIMEVAL(1,-2000000, 0,998000); + + TEST_TO_TIMEVAL(-1,-1, -2,999999); + TEST_TO_TIMEVAL(-1,-999, -2,999999); + TEST_TO_TIMEVAL(-1,-1000, -2,999999); + TEST_TO_TIMEVAL(-1,-1001, -2,999998); + TEST_TO_TIMEVAL(-1,-2000, -2,999998); + TEST_TO_TIMEVAL(-1,-2000000, -2,998000); + + TEST_TO_TIMEVAL(1,1500000000, 2,500000); + TEST_TO_TIMEVAL(1,-1500000000, -1,500000); + TEST_TO_TIMEVAL(-1,-1500000000, -3,500000); + + // timespec_from_ms + + TEST_FROM_MS(0, 0,0); + TEST_FROM_MS(1, 0,1000000); + TEST_FROM_MS(-1, -1,999000000); + TEST_FROM_MS(1500, 1,500000000); + TEST_FROM_MS(-1000, -1,0); + TEST_FROM_MS(-1500, -2,500000000); + + // timespec_to_ms + + TEST_TO_MS(0,0, 0); + TEST_TO_MS(10,0, 10000); + TEST_TO_MS(-10,0, -10000); + TEST_TO_MS(0,500000000, 500); + TEST_TO_MS(0,-500000000, -500); + TEST_TO_MS(10,500000000, 10500); + TEST_TO_MS(10,-500000000, 9500); + TEST_TO_MS(-10,500000000, -9500); + TEST_TO_MS(-10,-500000000, -10500); + + // timespec_normalise + + TEST_NORMALISE(0,0, 0,0); + + TEST_NORMALISE(0,1000000000, 1,0); + TEST_NORMALISE(0,1500000000, 1,500000000); + TEST_NORMALISE(0,-1000000000, -1,0); + TEST_NORMALISE(0,-1500000000, -2,500000000); + + TEST_NORMALISE(5,1000000000, 6,0); + TEST_NORMALISE(5,1500000000, 6,500000000); + TEST_NORMALISE(-5,-1000000000, -6,0); + TEST_NORMALISE(-5,-1500000000, -7,500000000); + + TEST_NORMALISE(0,2000000000, 2,0); + TEST_NORMALISE(0,2100000000, 2,100000000); + TEST_NORMALISE(0,-2000000000, -2,0); + TEST_NORMALISE(0,-2100000000, -3,900000000); + + TEST_NORMALISE(1,-500000001, 0,499999999); + TEST_NORMALISE(1,-500000000, 0,500000000); + TEST_NORMALISE(1,-499999999, 0,500000001); + TEST_NORMALISE(0,-499999999, -1,500000001); + + TEST_NORMALISE(-1,500000000, -1,500000000); + TEST_NORMALISE(-1,499999999, -1,499999999); + + if(result > 0) + { + printf("%d tests failed\n", result); + } + else{ + printf("All tests passed\n"); + } + + return !!result; /* Don't overflow the exit status */ +} +#endif diff --git a/src/util/timespec.h b/src/util/timespec.h new file mode 100644 index 00000000..3f4b9a71 --- /dev/null +++ b/src/util/timespec.h @@ -0,0 +1,71 @@ +/* Functions for working with timespec structures + * Written by Daniel Collins (2017-2021) + * timespec_mod by Alex Forencich (2019) + * Various contributions by Ingo Albrecht (2021) + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * 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 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. + * + * For more information, please refer to +*/ + +#ifndef DAN_TIMESPEC_H +#define DAN_TIMESPEC_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct timespec timespec_add(struct timespec ts1, struct timespec ts2); +struct timespec timespec_sub(struct timespec ts1, struct timespec ts2); +struct timespec timespec_mod(struct timespec ts1, struct timespec ts2); + +struct timespec timespec_min(struct timespec ts1, struct timespec ts2); +struct timespec timespec_max(struct timespec ts1, struct timespec ts2); +struct timespec timespec_clamp(struct timespec ts1, struct timespec min, struct timespec max); + +int timespec_cmp(struct timespec ts1, struct timespec ts2); +bool timespec_eq(struct timespec ts1, struct timespec ts2); +bool timespec_gt(struct timespec ts1, struct timespec ts2); +bool timespec_ge(struct timespec ts1, struct timespec ts2); +bool timespec_lt(struct timespec ts1, struct timespec ts2); +bool timespec_le(struct timespec ts1, struct timespec ts2); + +struct timespec timespec_from_double(double s); +double timespec_to_double(struct timespec ts); +struct timespec timespec_from_timeval(struct timeval tv); +struct timeval timespec_to_timeval(struct timespec ts); +struct timespec timespec_from_ms(long milliseconds); +long timespec_to_ms(struct timespec ts); + +struct timespec timespec_normalise(struct timespec ts); + +#ifdef __cplusplus +} +#endif + +#endif /* !DAN_TIMESPEC_H */ diff --git a/test/fading/fast_fading_samples.float b/test/fading/fast_fading_samples.float new file mode 100644 index 00000000..107ad9a9 Binary files /dev/null and b/test/fading/fast_fading_samples.float differ diff --git a/test/fading/faster_fading_samples.float b/test/fading/faster_fading_samples.float new file mode 100644 index 00000000..efc6fa7a Binary files /dev/null and b/test/fading/faster_fading_samples.float differ diff --git a/test/hamlibserver.py b/test/hamlibserver.py index a7b86e85..d0cc9b42 100755 --- a/test/hamlibserver.py +++ b/test/hamlibserver.py @@ -321,7 +321,7 @@ class HamlibHandler: except: self.ErrParam() else: - if (not x) and self.app.ptt: + if (not x) and self.app.ptt and self.app.timesBeforeKill == 0: # Sleep for 20ms to match typical SDR behavior + 60ms to account for varying system load/virtual audio latency. # References: # Virtual audio latency: https://vb-audio.com/Cable/VBCABLE_ReferenceManual.pdf (assuming 20ms/1024 sample buffer size @ 48 kHz) @@ -332,6 +332,7 @@ class HamlibHandler: self.app.ptt = 1 else: self.app.ptt = 0 + self.app.timesBeforeKill = self.app.timesBeforeKill - 1 def GetSplitVfo(self): self.Reply('SPLIT', self.app.splitenable, 'TXVFO', self.app.txvfo, 0) def SetSplitVfo(self): @@ -369,8 +370,9 @@ class HamlibHandler: class App: """This is the main application class. It listens for connectons from clients and creates a server for each one.""" - def __init__(self, pid): + def __init__(self, pid, timesBeforeKill): self.pid = pid + self.timesBeforeKill = timesBeforeKill self.hamlib_clients = [] self.hamlib_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: @@ -412,8 +414,12 @@ class App: if __name__ == "__main__": try: - if len(sys.argv) != 2: + if len(sys.argv) < 2: raise RuntimeError("A PID for the process to kill on TX->RX is required") - App(int(sys.argv[1])).Run() + if len(sys.argv) != 3: + timesBeforeKill = 100 + else: + timesBeforeKill = int(sys.argv[2]) + App(int(sys.argv[1]), timesBeforeKill).Run() except KeyboardInterrupt: sys.exit(0) diff --git a/test/test_rade_reporting.sh b/test/test_rade_reporting.sh index e1026bf7..e308f7d3 100755 --- a/test/test_rade_reporting.sh +++ b/test/test_rade_reporting.sh @@ -11,7 +11,7 @@ fi createVirtualAudioCable () { CABLE_NAME=$1 - pactl load-module module-null-sink sink_name=$CABLE_NAME sink_properties=device.description=$CABLE_NAME latency_msec=1 + pactl load-module module-null-sink sink_name=$CABLE_NAME sink_properties=device.description=$CABLE_NAME } FREEDV_RADIO_TO_COMPUTER_DEVICE="${FREEDV_RADIO_TO_COMPUTER_DEVICE:-FreeDV_Radio_To_Computer}" @@ -26,7 +26,7 @@ if [ "$OPERATING_SYSTEM" == "Linux" ]; then DRIVER_INDEX_FREEDV_COMPUTER_TO_SPEAKER=$(createVirtualAudioCable FreeDV_Computer_To_Speaker) DRIVER_INDEX_FREEDV_MICROPHONE_TO_COMPUTER=$(createVirtualAudioCable FreeDV_Microphone_To_Computer) DRIVER_INDEX_FREEDV_COMPUTER_TO_RADIO=$(createVirtualAudioCable FreeDV_Computer_To_Radio) - DRIVER_INDEX_LOOPBACK=`pactl load-module module-loopback source="FreeDV_Computer_To_Radio.monitor" sink="FreeDV_Radio_To_Computer" latency_msec=1` + DRIVER_INDEX_LOOPBACK=`pactl load-module module-loopback source="FreeDV_Computer_To_Radio.monitor" sink="FreeDV_Radio_To_Computer"` fi # Determine correct record device to retrieve TX data @@ -71,14 +71,19 @@ fi RECORD_PID=$! # Start "radio" -python3 $SCRIPTPATH/hamlibserver.py $RECORD_PID & +if [ "$2" == "mpp" ]; then + TIMES_BEFORE_KILL=5 +else + TIMES_BEFORE_KILL=1 +fi +python3 $SCRIPTPATH/hamlibserver.py $RECORD_PID $TIMES_BEFORE_KILL & RADIO_PID=$! # Start FreeDV in test mode to record TX if [ "$2" == "mpp" ]; then TX_ARGS="-txtime 1 -txattempts 6 " else - TX_ARGS="-txtime 5 " + TX_ARGS="-txtime 1 -txattempts 2 " fi $FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut tx -utmode RADE $TX_ARGS >tmp.log 2>&1 & @@ -99,11 +104,8 @@ cat tmp.log kill $RECORD_PID if [ "$1" != "" ]; then - FADING_DIR="$(pwd)/fading" - if [ ! -d "$FADING_DIR" ]; then - mkdir $FADING_DIR - (cd $1/../codec2_src/unittest && ./fading_files.sh $FADING_DIR) - fi + FADING_DIR="$SCRIPTPATH/fading" + # Add noise to recording to test performance if [ "$2" == "mpp" ]; then sox $(pwd)/test.wav -t raw -r 8000 -c 1 -e signed-integer -b 16 - | $1/src/ch - - --No -24 --mpp --fading_dir $FADING_DIR | sox -t raw -r 8000 -c 1 -e signed-integer -b 16 - -t wav $(pwd)/testwithnoise.wav diff --git a/test/test_zeros.sh b/test/test_zeros.sh index dd084acf..06e1f8be 100755 --- a/test/test_zeros.sh +++ b/test/test_zeros.sh @@ -7,7 +7,7 @@ FREEDV_RX_FILE=$3 # Determine sox driver to use for recording/playback OPERATING_SYSTEM=`uname` SOX_DRIVER=alsa -FREEDV_BINARY=src/freedv +FREEDV_BINARY=${FREEDV_BINARY:-src/freedv} if [ "$OPERATING_SYSTEM" == "Darwin" ]; then SOX_DRIVER=coreaudio FREEDV_BINARY=src/FreeDV.app/Contents/MacOS/freedv