Build AppImages as part of CI workflow. (#937)

* Initial check-in of files from Peter's repo.

* NO_STRIP isn't necessary on Ubuntu.

* Build AppImage as part of CI workflow.

* Try copying user's Python libraries.

* Force setting PYTHONHOME/PYTHONPATH inside FreeDV code.

* Fix PYTHONHOME paths.

* Clean up unneeded code.

* Have GitHub sanity check AppImage on clean build agents.

* Forgot to install pipewire and X inside test agent.

* Build AppImage on 22.04 instead due to glibc incompatibilities.

* apt update prior to building AppImage.

* Need GTK if we're going to build our own wxWidgets.

* Need to use Python 3.10 when building AppImages on 22.04.

* Restructure Linux tests to hopefully take less time to run.

* Forgot keyword.

* Fix typo.

* Need to run UT in debug mode.

* ASan needs to be run with gcc.

* Use PulseAudio instead of pipewire when testing AppImage on 22.04.

* Include version number in AppImage filename.

* Fix typo.

* Fix filename for AppImage testing.

* Build dependencies ourselves.

* Forgot that we duplicated the Sanity Check step.

* Ensure sanity check step fails if we lose sync.

* Run unit tests on 22.04 as well.

* Add missed GDK_BACKEND export.

* Transmit at least 2x during rade_reporting_* tests.

This is because the logic to calculate RTT between "rig" and FreeDV doesn't
fully complete until after the first TX->RX transition. By transmitting 2x,
we ensure that this logic is also exercised and thus reduce the number of
failures in the GitHub environment.

* latency_msec doesn't work with PulseAudio.

* On 22.04, limit tests to fullduplex_* to avoid GH environment problem.

* Add minimum requirements to user manual.

* Fix rendering issue while debugging test problem in local Ubuntu 22.04 environment.

* Forgot to add a tee command to the 24.04 test.

* Try 10ms latency for Pulse audio.

* Remove std::vector from RADE TX/RX steps just in case that's causing problems.

* CLOCK_MONOTONIC seems to have higher reliability for Linux tests.

* Fix compiler warning.

* WIP user manual update.

* Revert UI fix as it breaks things on macOS.

* Experiment: try to test AppImage in Fedora on GitHub.

* Try again without rtkit.

* Forgot to remove systemctl calls

* FUSE is required for AppImage.

* Use alternate method of running AppImage.

* Try third-party Fedora 41 systemd image to stop dropouts.

* Pass --init to Fedora container.

* Try another container for Fedora.

* No ps in container.

* Use podman to run container instead.

* Add missed DBus packages.

* Forgot to start pipewire.

* Start pipewire after dbus is set up.

* Use different way to start pipewire.

* podman might need to run as root.

* Forgot quote.

* Fix environment not getting passed to podman.

* Experiment: see if Fedora 41 can be tested too.

* Revert "Experiment: see if Fedora 41 can be tested too."

This reverts commit c751ab2c77.

* Bring back previous GUI bugfix but limit it to wxWidgets < 3.2.

* Fix timespec bug discovered during performance profiling.

* Forgot to capture rv.

* Revert back to CLOCK_REALTIME.

* Remove additional sleep on semaphore timeout.

* Need to use sem_clockwait if we're going to use monotonic time.

* Remove comment that no longer applies.

* Add PR #937 to changelog.

* Fix memory leak reported by ASan.

* Deploy GTK dependencies as part of AppImage.

* Add additional protection to avoid dropouts.

* Use deque instead of vector for function queues due to better worst-case complexity.

* Warning cleanup.

* Documentation update.

* Fix macOS compiler error.

* Eliminate the need to install Octave in GitHub environment.

* Have all macOS jobs run at once to save time.
pull/951/head
Mooneer Salem 2025-06-24 08:57:46 -07:00 committed by GitHub
parent e699e59e73
commit 1cb14f6d8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1561 additions and 189 deletions

View File

@ -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"

View File

@ -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

2
.gitignore vendored
View File

@ -11,3 +11,5 @@ a.out
*.make
*.log
*.marks
*.AppDir
*.AppImage

View File

@ -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

13
appimage/AppRun.sh 100644
View File

@ -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" $@

View File

@ -0,0 +1,6 @@
[Desktop Entry]
Name=FreeDV
Exec=freedv
Icon=freedv256x256
Type=Application
Categories=Utility;

View File

@ -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

View File

@ -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"

View File

@ -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 ()

View File

@ -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 */ }

View File

@ -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;

View File

@ -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()

View File

@ -28,6 +28,7 @@
#include <signal.h>
#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;
}

View File

@ -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_;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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<FreeDVReporter> reporter)

View File

@ -24,7 +24,7 @@
#include <string>
#include <map>
#include <vector>
#include <deque>
#include <mutex>
#include <wx/tipwin.h>
@ -259,7 +259,7 @@ class FreeDVReporterDialog : public wxFrame
std::shared_ptr<FreeDVReporter> reporter_;
std::map<std::string, ReporterData*> allReporterData_;
std::vector<std::function<void()> > fnQueue_;
std::deque<std::function<void()> > fnQueue_;
std::mutex fnQueueMtx_;
std::recursive_mutex dataMtx_;
bool isConnected_;

View File

@ -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

View File

@ -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_();

View File

@ -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<short> RADEReceiveStep::execute(std::shared_ptr<short> 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;
}

View File

@ -25,7 +25,6 @@
#include <atomic>
#include <cstdio>
#include <vector>
#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<float> pendingFeatures_;
float* pendingFeatures_;
int pendingFeaturesIdx_;
FILE* featuresFile_;
rade_text_t textPtr_;

View File

@ -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<short> RADETransmitStep::execute(std::shared_ptr<short> 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<short> RADETransmitStep::execute(std::shared_ptr<short> 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;
}

View File

@ -24,7 +24,6 @@
#define AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H
#include <cstdio>
#include <vector>
#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<float> featureList_;
float* featureList_;
int featureListIdx_;
FILE* featuresFile_;

View File

@ -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.

View File

@ -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);

View File

@ -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}
)

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -26,7 +26,7 @@
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <deque>
#include <functional>
class ThreadedObject
@ -44,11 +44,11 @@ protected:
private:
bool isDestroying_;
std::thread objectThread_;
std::vector<std::function<void()> > eventQueue_;
std::deque<std::function<void()> > eventQueue_;
std::recursive_mutex eventQueueMutex_;
std::condition_variable_any eventQueueCV_;
void eventLoop_();
};
#endif // THREADED_OBJECT_H
#endif // THREADED_OBJECT_H

979
src/util/timespec.c 100644
View File

@ -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 <http://unlicense.org/>
*/
/** \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:
*
* <PRE>
* { 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
* </PRE>
*
* Furthermore, any timespec structure processed or returned by library functions
* is normalised according to the rules in timespec_normalise().
*/
#include <limits.h>
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#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 <stdio.h>
#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

View File

@ -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 <http://unlicense.org/>
*/
#ifndef DAN_TIMESPEC_H
#define DAN_TIMESPEC_H
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#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 */

Binary file not shown.

Binary file not shown.

View File

@ -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)

View File

@ -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

View File

@ -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