diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 3c83a2a3d..2e1533ecd 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -15,33 +15,64 @@ jobs: # well on Windows or Mac. You can convert this to a matrix build if you need # cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install packages shell: bash run: | sudo apt-get update - sudo apt-get install codespell libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git libwxgtk3.0-gtk3-dev portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev python3-numpy + sudo apt-get upgrade -y + sudo apt-get install codespell libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git libwxgtk3.2-dev portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity dbus-x11 at-spi2-core rtkit - name: Spellcheck codebase shell: bash - run: codespell --ignore-words-list=radae,rade,inout,nin,ontop,parm,tthe,ue `find src -name '*.c*' -o -name '*.h' -o -name '*.mm'` + run: codespell --ignore-words-list=caf,radae,rade,inout,nin,ontop,parm,tthe,ue `find src -name '*.c*' -o -name '*.h' -o -name '*.mm'` + + - 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 PortAudio shell: bash working-directory: ${{github.workspace}} - run: UT_ENABLE=1 ./build_linux.sh portaudio + run: | + . ./rade-venv/bin/activate + UT_ENABLE=1 ./build_linux.sh portaudio - name: Build freedv-gui using PulseAudio shell: bash working-directory: ${{github.workspace}} - run: UT_ENABLE=1 ./build_linux.sh pulseaudio + run: | + . ./rade-venv/bin/activate + UT_ENABLE=1 ./build_linux.sh pulseaudio - name: Execute unit tests shell: bash working-directory: ${{github.workspace}}/build_linux - run: make test + 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 + 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 diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml index 94366c8d9..fd1d41374 100644 --- a/.github/workflows/cmake-macos.yml +++ b/.github/workflows/cmake-macos.yml @@ -18,20 +18,31 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install packages shell: bash working-directory: ${{github.workspace}} - run: brew install automake libtool numpy + run: brew install automake libtool numpy sox + + - name: Install virtual audio devices + shell: bash + working-directory: ${{github.workspace}} + run: ./build_macos_sound_drivers.sh - name: Build freedv-gui shell: bash working-directory: ${{github.workspace}} run: UT_ENABLE=1 ./build_osx.sh + - name: Workaround macOS permission issues + run: | + sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159,NULL,NULL,'UNUSED',1687786159);" + sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159,NULL,NULL,'UNUSED',1687786159);" + - name: Execute unit tests shell: bash working-directory: ${{github.workspace}}/build_osx - run: make test - + run: | + FREEDV_COMPUTER_TO_RADIO_DEVICE="BlackHoleRadio 2ch" FREEDV_RADIO_TO_COMPUTER_DEVICE="BlackHoleRadio 2ch 2" FREEDV_COMPUTER_TO_SPEAKER_DEVICE="BlackHole1 2ch" FREEDV_MICROPHONE_TO_COMPUTER_DEVICE="BlackHole2 2ch" ctest -V + diff --git a/.github/workflows/cmake-windows.yml b/.github/workflows/cmake-windows.yml index ef993ec03..1b3ebb971 100644 --- a/.github/workflows/cmake-windows.yml +++ b/.github/workflows/cmake-windows.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install WINE run: | @@ -48,6 +48,7 @@ jobs: export WINEPREFIX=`pwd`/wine-env wget https://www.python.org/ftp/python/3.12.7/python-3.12.7-amd64.exe Xvfb :99 -screen 0 1024x768x16 & + sleep 10 export DISPLAY=:99.0 wine ./python-3.12.7-amd64.exe /quiet /log c:\\python.log InstallAllUsers=1 Include_doc=0 Include_tcltk=0 || : cat $WINEPREFIX/drive_c/python.log @@ -76,3 +77,105 @@ jobs: run: | export PATH=${{github.workspace}}/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64/bin:$PATH make -j6 package + + - name: Rename installer + shell: bash + working-directory: ${{github.workspace}}/build_windows + run: | + mv FreeDV*.exe FreeDV.exe + + - name: Stash for next step + uses: actions/upload-artifact@v4 + with: + name: FreeDVSetupProgram + path: ${{github.workspace}}/build_windows/FreeDV.exe + + test: + runs-on: windows-latest + needs: build + env: + RADIO_TO_COMPUTER_DEVICE: "CABLE Output (VB-Audio Virtual Cable)" + COMPUTER_TO_RADIO_DEVICE: "Speakers (VB-Audio Virtual Cable)" + MICROPHONE_TO_COMPUTER_DEVICE: "Line 1 (Virtual Audio Cable)" + COMPUTER_TO_SPEAKER_DEVICE: "Line 1 (Virtual Audio Cable)" + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: FreeDVSetupProgram + path: ${{github.workspace}} + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Install FreeDV on hard drive + shell: pwsh + run: | + .\FreeDV.exe /S /D=${{github.workspace}}\FreeDV-Install-Location | Out-Null + + - name: Copy test script to install folder + shell: pwsh + run: | + Copy-Item -Path ${{github.workspace}}/test/TestFreeDVFullDuplex.ps1 -Destination ${{github.workspace}}\FreeDV-Install-Location\bin + Copy-Item -Path ${{github.workspace}}/test/freedv-ctest-fullduplex.conf.tmpl -Destination ${{github.workspace}}\FreeDV-Install-Location\bin + + - name: Install VB-Cable ("Radio" sound device) + uses: LABSN/sound-ci-helpers@v1 + + - run: 'Invoke-WebRequest https://software.muzychenko.net/trials/vac464.zip -OutFile vac464.zip' + - run: 'Expand-Archive -Path vac464.zip -DestinationPath vac464' + - run: 'Import-Certificate -FilePath ${{github.workspace}}\test\vac464.cer -CertStoreLocation Cert:\LocalMachine\root' + - run: 'Import-Certificate -FilePath ${{github.workspace}}\test\vac464.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher' + - name: Install driver + shell: pwsh + run: | + .\vac464\setup64.exe -s -k 30570681-0a8b-46e5-8cb2-d835f43af0c5 | Out-Null + Start-Sleep -Seconds 10 + # For convenience, make sure we fail fast if for whatever reason the install gets blocked on some GUI prompt. + timeout-minutes: 5 + + - name: Grant FreeDV access to the microphone + run: | + Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone" -Name Value -Value Allow + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone" -Name Value -Value Allow + New-Item -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\" -Name "NonPackaged" -Force + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged" -Name Value -Value Allow + New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\" -Name "AppPrivacy" -Force + Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" -Name LetAppsAccessMicrophone -Value 0 + + - name: Start Windows Audio Service + run: | + net start audiosrv + + - name: List audio devices + shell: pwsh + run: | + Get-CimInstance win32_sounddevice + + - name: Test RADE + shell: pwsh + working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin + run: | + .\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest RADE -NumberOfRuns 1 + timeout-minutes: 5 + + - name: Test 700D + shell: pwsh + working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin + run: | + .\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 700D -NumberOfRuns 1 + timeout-minutes: 5 + + - name: Test 700E + shell: pwsh + working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin + run: | + .\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 700E -NumberOfRuns 1 + timeout-minutes: 5 + + - name: Test 1600 + shell: pwsh + working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin + run: | + .\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 1600 -NumberOfRuns 1 + timeout-minutes: 5 + diff --git a/CMakeLists.txt b/CMakeLists.txt index 042cacb24..64d81252b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -719,3 +719,17 @@ elseif(UNIX AND NOT APPLE) include(CPack) endif(WIN32) + +if(UNITTEST) +# The below tests are currently Linux-only due to a dependency on +# PulseAudio/pipewire. +macro(DefineAudioTest utName) + add_test(NAME fullduplex_${utName} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test/test_zeros.sh txrx ${utName}) + set_tests_properties(fullduplex_${utName} PROPERTIES PASS_REGULAR_EXPRESSION "Got 1 sync changes") +endmacro() + +DefineAudioTest(RADE) +DefineAudioTest(700D) +DefineAudioTest(700E) +DefineAudioTest(1600) +endif(UNITTEST) diff --git a/build_macos_sound_drivers.sh b/build_macos_sound_drivers.sh new file mode 100755 index 000000000..c93b8edf0 --- /dev/null +++ b/build_macos_sound_drivers.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +git clone https://github.com/tmiw/BlackHole.git +cd BlackHole + +bundleID=audio.existential.BlackHoleRadio +driverName=BlackHoleRadio + +xcodebuild \ + -project BlackHole.xcodeproj \ + -configuration Release \ + -target BlackHole \ + CONFIGURATION_BUILD_DIR=build \ + PRODUCT_BUNDLE_IDENTIFIER=$bundleID \ + GCC_PREPROCESSOR_DEFINITIONS="$GCC_PREPROCESSOR_DEFINITIONS \ + kNumber_Of_Channels='2' \ + kPlugIn_BundleID='\"$bundleID\"' \ + kDriver_Name='\"$driverName\"' \ + kDevice2_IsHidden=false \ + kDevice2_HasInput=true \ + kDevice2_HasOutput=true" \ + MACOSX_DEPLOYMENT_TARGET=10.13 + +sudo mv build/BlackHole.driver /Library/Audio/Plug-Ins/HAL/$driverName.driver + +for i in {1..2}; do + git reset --hard + rm -rf build + + export bundleID=audio.existential.BlackHole$i + export driverName=BlackHole$i + + xcodebuild \ + -project BlackHole.xcodeproj \ + -configuration Release \ + -target BlackHole \ + CONFIGURATION_BUILD_DIR=build \ + PRODUCT_BUNDLE_IDENTIFIER=$bundleID \ + GCC_PREPROCESSOR_DEFINITIONS="$GCC_PREPROCESSOR_DEFINITIONS \ + kNumber_Of_Channels='2' \ + kPlugIn_BundleID='\"$bundleID\"' \ + kDriver_Name='\"$driverName\"' \ + kDevice2_IsHidden=false \ + kDevice2_HasInput=true \ + kDevice2_HasOutput=true" \ + MACOSX_DEPLOYMENT_TARGET=10.13 + + sudo mv build/BlackHole.driver /Library/Audio/Plug-Ins/HAL/$driverName.driver +done + +sudo killall -9 coreaudiod diff --git a/cmake/BuildRADE.cmake b/cmake/BuildRADE.cmake index dfb698630..3fcdf13bf 100644 --- a/cmake/BuildRADE.cmake +++ b/cmake/BuildRADE.cmake @@ -13,7 +13,7 @@ ExternalProject_Add(build_rade SOURCE_DIR rade_src BINARY_DIR rade_build GIT_REPOSITORY https://github.com/drowe67/radae.git - GIT_TAG dr-reset + GIT_TAG main CMAKE_ARGS ${RADE_CMAKE_ARGS} #CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET} INSTALL_COMMAND "" diff --git a/cmake/Buildportaudio-2.0.cmake b/cmake/Buildportaudio-2.0.cmake index 3c32191b1..788d7806e 100644 --- a/cmake/Buildportaudio-2.0.cmake +++ b/cmake/Buildportaudio-2.0.cmake @@ -1,4 +1,5 @@ #set(BUILD_SHARED_LIBS OFF CACHE STRING "Disable shared libraries for portaudio") +#set(PA_ENABLE_DEBUG_OUTPUT ON CACHE STRING "Enable debug output") include(FetchContent) FetchContent_Declare( diff --git a/src/audio/PulseAudioDevice.cpp b/src/audio/PulseAudioDevice.cpp index 942fd7f32..eb649fdc3 100644 --- a/src/audio/PulseAudioDevice.cpp +++ b/src/audio/PulseAudioDevice.cpp @@ -145,6 +145,7 @@ void PulseAudioDevice::start() outputPendingLength_ = 0; targetOutputPendingLength_ = PULSE_FPB * getNumChannels() * 2; outputPendingThreadActive_ = true; +#if 0 if (direction_ == IAudioEngine::AUDIO_ENGINE_OUT) { outputPendingThread_ = new std::thread([&]() { @@ -200,6 +201,7 @@ void PulseAudioDevice::start() }); assert(outputPendingThread_ != nullptr); } +#endif } pa_threaded_mainloop_unlock(mainloop_); @@ -268,6 +270,7 @@ void PulseAudioDevice::StreamWriteCallback_(pa_stream *s, size_t length, void *u memset(data, 0, sizeof(data)); PulseAudioDevice* thisObj = static_cast<PulseAudioDevice*>(userdata); +#if 0 { std::unique_lock<std::mutex> lk(thisObj->outputPendingMutex_); if (thisObj->outputPendingLength_ >= numSamples) @@ -286,7 +289,12 @@ void PulseAudioDevice::StreamWriteCallback_(pa_stream *s, size_t length, void *u thisObj->targetOutputPendingLength_ = std::max(thisObj->targetOutputPendingLength_, 2 * numSamples); } +#endif + if (thisObj->onAudioDataFunction) + { + thisObj->onAudioDataFunction(*thisObj, data, numSamples / thisObj->getNumChannels(), thisObj->onAudioDataState); + } pa_stream_write(s, &data[0], length, NULL, 0LL, PA_SEEK_RELATIVE); } } diff --git a/src/freedv_interface.cpp b/src/freedv_interface.cpp index 06312a210..b89fb8c35 100644 --- a/src/freedv_interface.cpp +++ b/src/freedv_interface.cpp @@ -72,7 +72,8 @@ FreeDVInterface::FreeDVInterface() : lastSyncRxMode_(nullptr), rade_(nullptr), lpcnetEncState_(nullptr), - radeTxStep_(nullptr) + radeTxStep_(nullptr), + sync_(0) { // empty } @@ -125,6 +126,7 @@ float FreeDVInterface::GetMinimumSNR_(int mode) void FreeDVInterface::start(int txMode, int fifoSizeMs, bool singleRxThread, bool usingReliableText) { + sync_ = 0; singleRxThread_ = singleRxThread; modemStatsList_ = new MODEM_STATS[enabledModes_.size()]; @@ -426,13 +428,7 @@ void FreeDVInterface::setSync(int val) int FreeDVInterface::getSync() const { - // Special case for RADE. - if (currentRxMode_ == nullptr) - { - return rade_sync(rade_); - } - - return freedv_get_sync(currentRxMode_); + return sync_; } void FreeDVInterface::setEq(int val) @@ -676,7 +672,7 @@ IPipelineStep* FreeDVInterface::createTransmitPipeline(int inputSampleRate, int std::function<int(ParallelStep*)> modeFn = [&](ParallelStep*) { int index = 0; - + // Special handling for RADE. if (txMode_ >= FREEDV_MODE_RADE) return 0; @@ -879,5 +875,7 @@ int FreeDVInterface::postProcessRxFn_(ParallelStep* stepObj) *state->getRxStateFn() = rade_sync(rade_); } + sync_ = *state->getRxStateFn(); + return indexWithSync; }; diff --git a/src/freedv_interface.h b/src/freedv_interface.h index 648a95fbd..1d7f2d57e 100644 --- a/src/freedv_interface.h +++ b/src/freedv_interface.h @@ -191,7 +191,8 @@ class FreeDVInterface FARGANState fargan_; LPCNetEncState *lpcnetEncState_; RADETransmitStep *radeTxStep_; - + int sync_; + int preProcessRxFn_(ParallelStep* ps); int postProcessRxFn_(ParallelStep* ps); }; diff --git a/src/main.cpp b/src/main.cpp index bdbaa7530..fecf59640 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ #include <climits> #include <wx/cmdline.h> #include <wx/stdpaths.h> +#include <wx/uiaction.h> #include "version.h" #include "main.h" @@ -49,6 +50,8 @@ #include "rade_api.h" +using namespace std::chrono_literals; + #define wxUSE_FILEDLG 1 #define wxUSE_LIBPNG 1 #define wxUSE_LIBJPEG 1 @@ -184,15 +187,174 @@ FILE *ftest; // Config file management wxConfigBase *pConfig = NULL; +// Unit test management +wxString testName; +wxString utFreeDVMode; + // WxWidgets - initialize the application IMPLEMENT_APP(MainApp); +void MainApp::UnitTest_() +{ + // List audio devices + auto engine = AudioEngineFactory::GetAudioEngine(); + engine->start(); + for (auto& dev : engine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_IN)) + { + fprintf(stderr, "Input audio device: %s (ID %d, sample rate %d, valid channels: %d-%d)\n", (const char*)dev.name.ToUTF8(), dev.deviceId, dev.defaultSampleRate, dev.minChannels, dev.maxChannels); + } + for (auto& dev : engine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_OUT)) + { + fprintf(stderr, "Output audio device: %s (ID %d, sample rate %d, valid channels: %d-%d)\n", (const char*)dev.name.ToUTF8(), dev.deviceId, dev.defaultSampleRate, dev.minChannels, dev.maxChannels); + } + engine->stop(); + + // Bring window to the front + CallAfter([&]() { + frame->Iconize(false); + frame->SetFocus(); + frame->Raise(); + frame->Show(true); + }); + + // Wait 100ms for FreeDV to come to foreground + std::this_thread::sleep_for(100ms); + + // Select FreeDV mode. Note, 2020 is deprecated so not testable here. + wxRadioButton* modeBtn = nullptr; + if (utFreeDVMode == "RADE") + { + modeBtn = frame->m_rbRADE; + } + /*else if (utFreeDVMode == "700C") + { + modeBtn = frame->m_rb700c; + }*/ + else if (utFreeDVMode == "700D") + { + modeBtn = frame->m_rb700d; + } + else if (utFreeDVMode == "700E") + { + modeBtn = frame->m_rb700e; + } + /*else if (utFreeDVMode == "800XA") + { + modeBtn = frame->m_rb800xa; + }*/ + else if (utFreeDVMode == "1600") + { + modeBtn = frame->m_rb1600; + } + + if (modeBtn != nullptr) + { + fprintf(stderr, "Firing mode change\n"); + /*sim.MouseMove(modeBtn->GetScreenPosition()); + sim.MouseClick();*/ + CallAfter([&]() { + modeBtn->SetValue(true); + wxCommandEvent* modeEvent = new wxCommandEvent(wxEVT_RADIOBUTTON, modeBtn->GetId()); + modeEvent->SetEventObject(modeBtn); + QueueEvent(modeEvent); + }); + } + + // Fire event to start FreeDV + fprintf(stderr, "Firing start\n"); + CallAfter([&]() { + frame->m_togBtnOnOff->SetValue(true); + wxCommandEvent* onEvent = new wxCommandEvent(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, frame->m_togBtnOnOff->GetId()); + onEvent->SetEventObject(frame->m_togBtnOnOff); + frame->OnTogBtnOnOff(*onEvent); + delete onEvent; + //QueueEvent(onEvent); + }); + /*sim.MouseMove(frame->m_togBtnOnOff->GetScreenPosition()); + sim.MouseClick();*/ + + // Wait 5 seconds for FreeDV to start + std::this_thread::sleep_for(5s); + + if (testName == "tx") + { + // Fire event to begin TX + //sim.MouseMove(frame->m_btnTogPTT->GetScreenPosition()); + //sim.MouseClick(); + fprintf(stderr, "Firing PTT\n"); + CallAfter([&]() { + frame->m_btnTogPTT->SetValue(true); + wxCommandEvent* txEvent = new wxCommandEvent(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, frame->m_btnTogPTT->GetId()); + txEvent->SetEventObject(frame->m_btnTogPTT); + //QueueEvent(txEvent); + frame->OnTogBtnPTT(*txEvent); + delete txEvent; + }); + + // Transmit for 60 seconds + std::this_thread::sleep_for(60s); + + // Stop transmitting + fprintf(stderr, "Firing PTT\n"); + CallAfter([&]() { + frame->m_btnTogPTT->SetValue(false); + wxCommandEvent* rxEvent = new wxCommandEvent(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, frame->m_btnTogPTT->GetId()); + rxEvent->SetEventObject(frame->m_btnTogPTT); + frame->OnTogBtnPTT(*rxEvent); + delete rxEvent; + //QueueEvent(rxEvent); + }); + /*sim.MouseMove(frame->m_btnTogPTT->GetScreenPosition()); + sim.MouseClick();*/ + + // Wait 5 seconds for FreeDV to stop + std::this_thread::sleep_for(5s); + } + else + { + // Receive for 60 seconds + auto sync = 0; + for (int i = 0; i < 60*10; i++) + { + std::this_thread::sleep_for(100ms); + auto newSync = freedvInterface.getSync(); + if (newSync != sync) + { + fprintf(stderr, "Sync changed from %d to %d\n", sync, newSync); + sync = newSync; + } + } + } + + // Fire event to stop FreeDV + fprintf(stderr, "Firing stop\n"); + CallAfter([&]() { + wxCommandEvent* offEvent = new wxCommandEvent(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, frame->m_togBtnOnOff->GetId()); + offEvent->SetEventObject(frame->m_togBtnOnOff); + frame->m_togBtnOnOff->SetValue(false); + //QueueEvent(offEvent); + frame->OnTogBtnOnOff(*offEvent); + delete offEvent; + }); + //sim.MouseMove(frame->m_togBtnOnOff->GetScreenPosition()); + //sim.MouseClick(); + + // Wait 5 seconds for FreeDV to stop + std::this_thread::sleep_for(5s); + + // Destroy main window to exit application. Must be done in UI thread to avoid problems. + CallAfter([&]() { + frame->Destroy(); + }); +} void MainApp::OnInitCmdLine(wxCmdLineParser& parser) { wxApp::OnInitCmdLine(parser); parser.AddOption("f", "config", "Use different configuration file instead of the default."); + parser.AddOption("ut", "unit_test", "Execute FreeDV in unit test mode."); + parser.AddOption("utmode", wxEmptyString, "Switch FreeDV to the given mode before UT execution."); } bool MainApp::OnCmdLineParsed(wxCmdLineParser& parser) @@ -216,6 +378,15 @@ bool MainApp::OnCmdLineParsed(wxCmdLineParser& parser) } pConfig->SetRecordDefaults(); + if (parser.Found("ut", &testName)) + { + fprintf(stderr, "Executing test %s\n", (const char*)testName.ToUTF8()); + if (parser.Found("utmode", &utFreeDVMode)) + { + fprintf(stderr, "Using mode %s for tests\n", (const char*)utFreeDVMode.ToUTF8()); + } + } + return true; } @@ -298,8 +469,15 @@ bool MainApp::OnInit() frame->m_auiNbookCtrl->ChangeSelection(0); frame->Layout(); frame->Show(); - g_parent =frame; + g_parent = frame; + // Begin test execution + if (testName != "") + { + std::thread utThread(std::bind(&MainApp::UnitTest_, this)); + utThread.detach(); + } + return true; } @@ -2794,15 +2972,15 @@ void MainFrame::startRxStream() // loop. int m_fifoSize_ms = wxGetApp().appConfiguration.fifoSizeMs; - int soundCard1InFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard1In.sampleRate/1000; - int soundCard1OutFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard1Out.sampleRate/1000; + int soundCard1InFifoSizeSamples = wxGetApp().appConfiguration.audioConfiguration.soundCard1In.sampleRate; + int soundCard1OutFifoSizeSamples = wxGetApp().appConfiguration.audioConfiguration.soundCard1Out.sampleRate; g_rxUserdata->infifo1 = codec2_fifo_create(soundCard1InFifoSizeSamples); g_rxUserdata->outfifo1 = codec2_fifo_create(soundCard1OutFifoSizeSamples); if (txInSoundDevice && txOutSoundDevice) { - int soundCard2InFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard2In.sampleRate/1000; - int soundCard2OutFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard2Out.sampleRate/1000; + int soundCard2InFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard2In.sampleRate / 1000; + int soundCard2OutFifoSizeSamples = m_fifoSize_ms*wxGetApp().appConfiguration.audioConfiguration.soundCard2Out.sampleRate / 1000; g_rxUserdata->outfifo2 = codec2_fifo_create(soundCard2OutFifoSizeSamples); g_rxUserdata->infifo2 = codec2_fifo_create(soundCard2InFifoSizeSamples); @@ -2885,15 +3063,18 @@ void MainFrame::startRxStream() // Set sound card callbacks auto errorCallback = [&](IAudioDevice&, std::string error, void*) { - CallAfter([&, error]() { + fprintf(stderr, "AUDIO ERROR: %s\n", error.c_str()); + /*CallAfter([&, error]() { wxMessageBox(wxString::Format("Error encountered while processing audio: %s", error), wxT("Error"), wxOK); - }); + });*/ }; rxInSoundDevice->setOnAudioData([&](IAudioDevice& dev, void* data, size_t size, void* state) { paCallBackData* cbData = static_cast<paCallBackData*>(state); short* audioData = static_cast<short*>(data); short indata[size]; + + //fprintf(stderr, "recoded %d samples\n", size); for (size_t i = 0; i < size; i++, audioData += dev.getNumChannels()) { indata[i] = audioData[0]; @@ -2901,6 +3082,7 @@ void MainFrame::startRxStream() if (codec2_fifo_write(cbData->infifo1, indata, size)) { + fprintf(stderr, "RX FIFO full\n"); g_infifo1_full++; } diff --git a/src/main.h b/src/main.h index f445600b4..df5eb0285 100644 --- a/src/main.h +++ b/src/main.h @@ -225,6 +225,8 @@ class MainApp : public wxApp int m_reportCounter; protected: + private: + void UnitTest_(); }; // declare global static function wxGetApp() @@ -442,6 +444,8 @@ class MainFrame : public TopFrame void OnSetMonitorTxAudioVol( wxCommandEvent& event ); private: + friend class MainApp; // needed for unit tests + std::shared_ptr<IAudioDevice> rxInSoundDevice; std::shared_ptr<IAudioDevice> rxOutSoundDevice; std::shared_ptr<IAudioDevice> txInSoundDevice; diff --git a/src/pipeline/TxRxThread.cpp b/src/pipeline/TxRxThread.cpp index 36390e138..c89c2b10d 100644 --- a/src/pipeline/TxRxThread.cpp +++ b/src/pipeline/TxRxThread.cpp @@ -20,6 +20,9 @@ // //========================================================================= +#include <chrono> +using namespace std::chrono_literals; + // This forces us to use freedv-gui's version rather than another one. // TBD -- may not be needed once we fully switch over to the audio pipeline. #include "../defines.h" @@ -478,6 +481,7 @@ void* TxRxThread::Entry() pthread_setname_np(pthread_self(), threadName); #endif // defined(__linux__) +#if 0 { std::unique_lock<std::mutex> lk(m_processingMutex); if (m_processingCondVar.wait_for(lk, std::chrono::milliseconds(100)) == std::cv_status::timeout) @@ -485,9 +489,15 @@ void* TxRxThread::Entry() fprintf(stderr, "txRxThread: timeout while waiting for CV, tx = %d\n", m_tx); } } +#endif + + auto currentTime = std::chrono::steady_clock::now(); + if (!m_run) break; if (m_tx) txProcessing_(); else rxProcessing_(); + + std::this_thread::sleep_until(currentTime + 20ms); } // Force pipeline to delete itself when we're done with the thread. @@ -509,8 +519,10 @@ void TxRxThread::terminateThread() void TxRxThread::notify() { +#if 0 std::unique_lock<std::mutex> lk(m_processingMutex); m_processingCondVar.notify_all(); +#endif } void TxRxThread::clearFifos_() diff --git a/src/topFrame.h b/src/topFrame.h index 48f583996..d78123f28 100644 --- a/src/topFrame.h +++ b/src/topFrame.h @@ -82,9 +82,7 @@ class wxListViewComboPopup; /// Class TopFrame /////////////////////////////////////////////////////////////////////////////// class TopFrame : public wxFrame -{ - private: - +{ protected: wxPanel* m_panel; wxMenuBar* m_menubarMain; diff --git a/test/TestFreeDVFullDuplex.ps1 b/test/TestFreeDVFullDuplex.ps1 new file mode 100644 index 000000000..dd3fd962e --- /dev/null +++ b/test/TestFreeDVFullDuplex.ps1 @@ -0,0 +1,138 @@ +<# + .SYNOPSIS + Executes full-duplex test of FreeDV. + + .DESCRIPTION + This script starts FreeDV in full-duplex mode for approximately 60 seconds using an autogenerated configuration file + that will access the audio devices passed in. After 60 seconds, FreeDV will terminate and this script will examine + the output to determine the number of times that it went into and out of sync. If it detects that FreeDV went out + of sync during the test, the test is marked as having failed. + + .INPUTS + None. You can't pipe objects to this script. + + .OUTPUTS + The script outputs the mode tested as well as the number of passed/failed tests to the console. + + .EXAMPLE + PS> .\TestFreeDVFullDuplex.ps1 -ModeToTest RADE -RadioToComputerDevice "Microphone (USB Audio CODEC)" -ComputerToRadioDevice "Speakers (USB Audio CODEC)" -ComputerToSpeakerDevice "Speakers (Realtek High Definition Audio(SST))" -MicrophoneToComputerDevice "Microphone Array (Realtek High Definition Audio(SST))" + +#> + +param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + # The sound device to receive RX audio from. + $RadioToComputerDevice, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + # The sound device to emit decoded audio to. + $ComputerToSpeakerDevice, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + # The sound device to receive analog audio from. + $MicrophoneToComputerDevice, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + # The sound device to emit TX audio to. + $ComputerToRadioDevice, + + [ValidateSet("RADE", "700D", "700E", "1600")] + [ValidateNotNullOrEmpty()] + [string] + # The FreeDV mode to use for testing. + $ModeToTest="RADE", + + [int] + # The number of times to execute the test. + $NumberOfRuns=10) + + +<# + .Description + Performs the actual test with FreeDV by generating the needed configuration file, starting FreeDV and then examining the output. +#> +function Test-FreeDV { + param ( + $ModeToTest, + $RadioToComputerDevice, + $ComputerToSpeakerDevice, + $MicrophoneToComputerDevice, + $ComputerToRadioDevice + ) + + $current_loc = Get-Location + + # Generate new conf + $conf_tmpl = Get-Content "$current_loc\freedv-ctest-fullduplex.conf.tmpl" + $conf_tmpl = $conf_tmpl.Replace("@FREEDV_RADIO_TO_COMPUTER_DEVICE@", $RadioToComputerDevice) + $conf_tmpl = $conf_tmpl.Replace("@FREEDV_COMPUTER_TO_RADIO_DEVICE@", $ComputerToRadioDevice) + $conf_tmpl = $conf_tmpl.Replace("@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@", $MicrophoneToComputerDevice) + $conf_tmpl = $conf_tmpl.Replace("@FREEDV_COMPUTER_TO_SPEAKER_DEVICE@", $ComputerToSpeakerDevice) + $tmp_file = New-TemporaryFile + $conf_tmpl | Set-Content -Path $tmp_file.FullName + + # Start freedv.exe + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.CreateNoWindow = $true + $psi.UseShellExecute = $false + $psi.RedirectStandardError = $true + $psi.RedirectStandardOutput = $true + $psi.FileName = "$current_loc\freedv.exe" + $psi.WorkingDirectory = $current_loc + $quoted_tmp_filename = "`"" + $tmp_file.FullName + "`"" + $psi.Arguments = @("/f $quoted_tmp_filename /ut txrx /utmode $ModeToTest") + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $psi + [void]$process.Start() + + # Read output from process + $err_output = $process.StandardError.ReadToEnd(); + $output = $process.StandardOutput.ReadToEnd(); + $process.WaitForExit() + + Write-Host "$err_output" + + $syncs = $err_output.Split([Environment]::NewLine) | Where { $_.Contains("Sync changed") } + if ($syncs.Count -eq 1) { + return $true + } + return $false +} + +$passes = 0 +$fails = 0 + +for (($i = 0); $i -lt $NumberOfRuns; $i++) +{ + $result = Test-FreeDV ` + -ModeToTest $ModeToTest ` + -RadioToComputerDevice $RadioToComputerDevice ` + -ComputerToSpeakerDevice $ComputerToSpeakerDevice ` + -MicrophoneToComputerDevice $MicrophoneToComputerDevice ` + -ComputerToRadioDevice $ComputerToRadioDevice + if ($result -eq $true) + { + $passes++ + } + else + { + $fails++ + } +} + +Write-Host "Mode: $ModeToTest, Total Runs: $NumberOfRuns, Passed: $passes, Failures: $fails" + +if ($fails -gt 0) { + throw "Test failed" + exit 1 +} + diff --git a/test/check-for-zeros.py b/test/check-for-zeros.py new file mode 100644 index 000000000..cad78c369 --- /dev/null +++ b/test/check-for-zeros.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 + +import sys +import struct + +index = 0 +detected = False +detected_index = -1 +first_detected_index = -1 +with open(sys.argv[1], "rb") as f: + while True: + bytes_to_read = struct.calcsize("h") + buffer = f.read(bytes_to_read) + if len(buffer) != bytes_to_read: + break + + if buffer[0] == 0: + if first_detected_index == -1: + first_detected_index = index + detected = True + detected_index = index + elif index == (detected_index + 1): + if (index - first_detected_index) > 1: + sys.stderr.write(f"Zero audio detected at index {index} ({index / 8000} seconds)") + sys.stderr.write(f" - lasted {(index - first_detected_index) / 8000} seconds\n") + first_detected_index = -1 + index = index + 1 + +if detected is False: + print("No zeros found.") diff --git a/test/freedv-ctest-fullduplex.conf.tmpl b/test/freedv-ctest-fullduplex.conf.tmpl new file mode 100644 index 000000000..8b38ad70d --- /dev/null +++ b/test/freedv-ctest-fullduplex.conf.tmpl @@ -0,0 +1,189 @@ +FirstTimeUse=0 +ExperimentalFeatures=0 +[Audio] +soundCard1SampleRate=-1 +soundCard2SampleRate=-1 +soundCard1InDeviceName=@FREEDV_RADIO_TO_COMPUTER_DEVICE@ +soundCard1InSampleRate=48000 +soundCard1OutDeviceName=@FREEDV_COMPUTER_TO_RADIO_DEVICE@ +soundCard1OutSampleRate=48000 +soundCard2InDeviceName=@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@ +soundCard2InSampleRate=48000 +soundCard2OutDeviceName=@FREEDV_COMPUTER_TO_SPEAKER_DEVICE@ +soundCard2OutSampleRate=48000 +SquelchActive=1 +SquelchLevel=-4 +fifoSize_ms=440 +transmitLevel=0 +snrSlow=0 +mode=4 +TxRxDelayMilliseconds=0 +[Filter] +codec2LPCPostFilterGamma=50 +codec2LPCPostFilterBeta=20 +MicInBassFreqHz=100 +MicInBassGaindB=0 +MicInTrebleFreqHz=3000 +MicInTrebleGaindB=0 +MicInMidFreqHz=1500 +MicInMidGaindB=0 +MicInMidQ=100 +MicInVolInDB=0 +SpkOutBassFreqHz=100 +SpkOutBassGaindB=0 +SpkOutTrebleFreqHz=3000 +SpkOutTrebleGaindB=0 +SpkOutMidFreqHz=1500 +SpkOutMidGaindB=0 +SpkOutMidQ=100 +SpkOutVolInDB=0 +codec2LPCPostFilterEnable=1 +codec2LPCPostFilterBassBoost=1 +speexpp_enable=1 +700C_EQ=1 +[Filter/MicIn] +EQEnable=0 +BassFreqHz=100 +BassGaindB=0 +TrebleFreqHz=3000 +TrebleGaindB=0 +MidFreqHz=1500 +MidGaindB=0 +MidQ=1 +VolInDB=0 +[Filter/SpkOut] +EQEnable=0 +BassFreqHz=100 +BassGaindB=0 +TrebleFreqHz=3000 +TrebleGaindB=0 +MidFreqHz=1500 +MidGaindB=0 +MidQ=1 +VolInDB=0 +[Filter/codec2LPCPostFilter] +Gamma=50 +Beta=20 +[Hamlib] +UseForPTT=0 +EnableFreqModeChanges=1 +UseAnalogModes=0 +IcomCIVHex=0 +RigNameStr=ADAT www.adat.ch ADT-200A +PttType=0 +SerialRate=0 +SerialPort= +PttSerialPort= +RigName=0 +[Rig] +UseSerialPTT=0 +Port= +UseRTS=1 +RTSPolarity=1 +UseDTR=0 +DTRPolarity=0 +UseSerialPTTInput=0 +PttInPort= +CTSPolarity=0 +leftChannelVoxTone=0 +EnableSpacebarForPTT=1 +HalfDuplex=0 +MultipleRx=1 +SingleRxThread=0 +[PSKReporter] +Enable=0 +Callsign= +GridSquare= +FrequencyHzStr=0 +[Data] +CallSign= +[Reporting] +Enable=0 +Callsign= +GridSquare= +FrequencyAsKHz=0 +FrequencyList=1.997000,3.625000,3.643000,3.693000,3.697000,3.850000,5.403500,5.366500,5.368500,7.177000,7.197000,14.236000,14.240000,18.118000,21.313000,24.933000,28.330000,28.720000,10489.640000 +ManualFrequencyReporting=0 +DirectionAsCardinal=0 +Frequency=0 +[Reporting/PSKReporter] +Enable=1 +[Reporting/FreeDV] +Enable=1 +Hostname=qso.freedv.org +CurrentBandFilter=0 +UseMetricDistances=1 +BandFilterTracksFrequency=0 +ForceReceiveOnly=0 +StatusText= +RecentStatusTexts= +TxRowBackgroundColor=#FC4500 +TxRowForegroundColor=#000000 +RxRowBackgroundColor=#379BAF +RxRowForegroundColor=#000000 +MsgRowBackgroundColor=#E58BE5 +MsgRowForegroundColor=#000000 +[Reporting/FreeDV/BandFilterTracking] +TracksFreqBand=1 +TracksExactFreq=0 +[CallsignList] +UseUTCTime=0 +[FreeDV2020] +Allowed=0 +[MainFrame] +left=26 +top=23 +width=800 +height=780 +rxNbookCtrl=0 +TabLayout= +[Windows] +[Windows/AudioConfig] +left=26 +top=23 +width=1148 +height=732 +[Windows/FreeDVReporter] +left=20 +top=20 +width=-1 +height=-1 +visible=0 +currentSort=-1 +currentSortDirection=1 +reportingUserMsgColWidth=130 +[File] +playFileToMicInPath= +recFileFromRadioPath= +recFileFromRadioSecs=60 +recFileFromModulatorPath= +recFileFromModulatorSecs=60 +playFileFromRadioPath= +[VoiceKeyer] +WaveFilePath=/home/mooneer/Documents +WaveFile= +RxPause=10 +Repeats=5 +[FreeDV700] +txClip=1 +txBPF=1 +[Noise] +noise_snr=2 +[Debug] +console=0 +verbose=0 +APIverbose=0 +[Waterfall] +Color=0 +[Stats] +ResetTime=10 +[Plot] +[Plot/Spectrum] +CurrentAveraging=0 +[Monitor] +VoiceKeyerAudio=0 +TransmitAudio=0 +VoiceKeyerAudioVol=0 +TransmitAudioVol=0 +[QuickRecord] +SavePath=/home/mooneer/Documents diff --git a/test/freedv-ctest.conf.tmpl b/test/freedv-ctest.conf.tmpl new file mode 100644 index 000000000..c12fe1d96 --- /dev/null +++ b/test/freedv-ctest.conf.tmpl @@ -0,0 +1,189 @@ +FirstTimeUse=0 +ExperimentalFeatures=0 +[Audio] +soundCard1SampleRate=-1 +soundCard2SampleRate=-1 +soundCard1InDeviceName=@FREEDV_RADIO_TO_COMPUTER_DEVICE@ +soundCard1InSampleRate=48000 +soundCard1OutDeviceName=@FREEDV_COMPUTER_TO_RADIO_DEVICE@ +soundCard1OutSampleRate=48000 +soundCard2InDeviceName=@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@ +soundCard2InSampleRate=48000 +soundCard2OutDeviceName=@FREEDV_COMPUTER_TO_SPEAKER_DEVICE@ +soundCard2OutSampleRate=48000 +SquelchActive=1 +SquelchLevel=-4 +fifoSize_ms=440 +transmitLevel=0 +snrSlow=0 +mode=257 +TxRxDelayMilliseconds=0 +[Filter] +codec2LPCPostFilterGamma=50 +codec2LPCPostFilterBeta=20 +MicInBassFreqHz=100 +MicInBassGaindB=0 +MicInTrebleFreqHz=3000 +MicInTrebleGaindB=0 +MicInMidFreqHz=1500 +MicInMidGaindB=0 +MicInMidQ=100 +MicInVolInDB=0 +SpkOutBassFreqHz=100 +SpkOutBassGaindB=0 +SpkOutTrebleFreqHz=3000 +SpkOutTrebleGaindB=0 +SpkOutMidFreqHz=1500 +SpkOutMidGaindB=0 +SpkOutMidQ=100 +SpkOutVolInDB=0 +codec2LPCPostFilterEnable=1 +codec2LPCPostFilterBassBoost=1 +speexpp_enable=1 +700C_EQ=1 +[Filter/MicIn] +EQEnable=0 +BassFreqHz=100 +BassGaindB=0 +TrebleFreqHz=3000 +TrebleGaindB=0 +MidFreqHz=1500 +MidGaindB=0 +MidQ=1 +VolInDB=0 +[Filter/SpkOut] +EQEnable=0 +BassFreqHz=100 +BassGaindB=0 +TrebleFreqHz=3000 +TrebleGaindB=0 +MidFreqHz=1500 +MidGaindB=0 +MidQ=1 +VolInDB=0 +[Filter/codec2LPCPostFilter] +Gamma=50 +Beta=20 +[Hamlib] +UseForPTT=0 +EnableFreqModeChanges=1 +UseAnalogModes=0 +IcomCIVHex=0 +RigNameStr=ADAT www.adat.ch ADT-200A +PttType=0 +SerialRate=0 +SerialPort= +PttSerialPort= +RigName=0 +[Rig] +UseSerialPTT=0 +Port= +UseRTS=1 +RTSPolarity=1 +UseDTR=0 +DTRPolarity=0 +UseSerialPTTInput=0 +PttInPort= +CTSPolarity=0 +leftChannelVoxTone=0 +EnableSpacebarForPTT=1 +HalfDuplex=1 +MultipleRx=1 +SingleRxThread=1 +[PSKReporter] +Enable=0 +Callsign= +GridSquare= +FrequencyHzStr=0 +[Data] +CallSign= +[Reporting] +Enable=0 +Callsign= +GridSquare= +FrequencyAsKHz=0 +FrequencyList=1.9970,3.6250,3.6430,3.6930,3.6970,3.8500,5.4035,5.3665,5.3685,7.1770,7.1970,14.2360,14.2400,18.1180,21.3130,24.9330,28.3300,28.7200,10489.6400 +ManualFrequencyReporting=0 +DirectionAsCardinal=0 +Frequency=0 +[Reporting/PSKReporter] +Enable=1 +[Reporting/FreeDV] +Enable=1 +Hostname=qso.freedv.org +CurrentBandFilter=0 +UseMetricDistances=1 +BandFilterTracksFrequency=0 +ForceReceiveOnly=0 +StatusText= +RecentStatusTexts= +TxRowBackgroundColor=#fc4500 +TxRowForegroundColor=#000000 +RxRowBackgroundColor=#379baf +RxRowForegroundColor=#000000 +MsgRowBackgroundColor=#E58BE5 +MsgRowForegroundColor=#000000 +[Reporting/FreeDV/BandFilterTracking] +TracksFreqBand=1 +TracksExactFreq=0 +[CallsignList] +UseUTCTime=0 +[FreeDV2020] +Allowed=0 +[MainFrame] +left=26 +top=23 +width=800 +height=780 +rxNbookCtrl=0 +TabLayout= +[Windows] +[Windows/AudioConfig] +left=26 +top=23 +width=918 +height=739 +[Windows/FreeDVReporter] +left=20 +top=20 +width=-1 +height=-1 +visible=0 +currentSort=-1 +currentSortDirection=1 +reportingUserMsgColWidth=130 +[File] +playFileToMicInPath= +recFileFromRadioPath= +recFileFromRadioSecs=60 +recFileFromModulatorPath= +recFileFromModulatorSecs=60 +playFileFromRadioPath= +[VoiceKeyer] +WaveFilePath=/home/mooneer/Documents +WaveFile=voicekeyer.wav +RxPause=10 +Repeats=5 +[FreeDV700] +txClip=1 +txBPF=1 +[Noise] +noise_snr=2 +[Debug] +console=0 +verbose=0 +APIverbose=0 +[Waterfall] +Color=0 +[Stats] +ResetTime=10 +[Plot] +[Plot/Spectrum] +CurrentAveraging=0 +[Monitor] +VoiceKeyerAudio=0 +TransmitAudio=0 +VoiceKeyerAudioVol=0 +TransmitAudioVol=0 +[QuickRecord] +SavePath=/home/mooneer/Documents diff --git a/test/test_zeros.sh b/test/test_zeros.sh new file mode 100755 index 000000000..69797e83d --- /dev/null +++ b/test/test_zeros.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +FREEDV_TEST=$1 +FREEDV_MODE=$2 +FREEDV_RX_FILE=$3 + +# Determine sox driver to use for recording/playback +OPERATING_SYSTEM=`uname` +SOX_DRIVER=alsa +FREEDV_BINARY=src/freedv +if [ "$OPERATING_SYSTEM" == "Darwin" ]; then + SOX_DRIVER=coreaudio + FREEDV_BINARY=src/FreeDV.app/Contents/MacOS/freedv +fi + +createVirtualAudioCable () { + CABLE_NAME=$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}" +FREEDV_COMPUTER_TO_SPEAKER_DEVICE="${FREEDV_COMPUTER_TO_SPEAKER_DEVICE:-FreeDV_Computer_To_Speaker}" +FREEDV_MICROPHONE_TO_COMPUTER_DEVICE="${FREEDV_MICROPHONE_TO_COMPUTER_DEVICE:-FreeDV_Microphone_To_Computer}" +FREEDV_COMPUTER_TO_RADIO_DEVICE="${FREEDV_COMPUTER_TO_RADIO_DEVICE:-FreeDV_Computer_To_Radio}" + +# Automated script to help find audio dropouts. +# NOTE: this must be run from "build_linux". Also assumes PulseAudio/pipewire. +if [ "$OPERATING_SYSTEM" == "Linux" ]; then + DRIVER_INDEX_FREEDV_RADIO_TO_COMPUTER=$(createVirtualAudioCable FreeDV_Radio_To_Computer) + 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"` +fi + +# For debugging--list sink info +#pactl list sinks +# If full duplex test, use correct config file and assume "rx" mode. +FREEDV_CONF_FILE=freedv-ctest.conf +if [ "$FREEDV_TEST" == "txrx" ]; then + FREEDV_TEST=rx + FREEDV_CONF_FILE=freedv-ctest-fullduplex.conf + REC_DEVICE="$FREEDV_COMPUTER_TO_SPEAKER_DEVICE" + + # Generate sine wave for input + if [ "$OPERATING_SYSTEM" == "Linux" ]; then + sox -r 48000 -n -b 16 -c 1 -t wav - synth 120 sin 1000 vol -10dB | paplay -d "$FREEDV_MICROPHONE_TO_COMPUTER_DEVICE" & + else + sox -r 48000 -n -b 16 -c 1 -t $SOX_DRIVER "$FREEDV_MICROPHONE_TO_COMPUTER_DEVICE" - synth 120 sin 1000 vol -10dB & + fi + PLAY_PID=$! +elif [ "$FREEDV_TEST" == "rx" ]; then + # Start playback if RX + if [ "$OPERATING_SYSTEM" == "Linux" ]; then + paplay -d "$FREEDV_RADIO_TO_COMPUTER_DEVICE" $FREEDV_RX_FILE & + else + sox $FREEDV_RX_FILE -t $SOX_DRIVER "$FREEDV_RADIO_TO_COMPUTER_DEVICE" & + fi + PLAY_PID=$! + REC_DEVICE="$FREEDV_COMPUTER_TO_SPEAKER_DEVICE.monitor" +else + REC_DEVICE="$FREEDV_COMPUTER_TO_RADIO_DEVICE.monitor" +fi + +# Generate config file +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +if [ "$FREEDV_RADIO_TO_COMPUTER_DEVICE" == "FreeDV_Radio_To_Computer" ] && [ "$OPERATING_SYSTEM" == "Linux" ]; then + sed "s/@FREEDV_RADIO_TO_COMPUTER_DEVICE@/$FREEDV_RADIO_TO_COMPUTER_DEVICE.monitor/g" $SCRIPTPATH/$FREEDV_CONF_FILE.tmpl > $(pwd)/$FREEDV_CONF_FILE +else + sed "s/@FREEDV_RADIO_TO_COMPUTER_DEVICE@/$FREEDV_RADIO_TO_COMPUTER_DEVICE/g" $SCRIPTPATH/$FREEDV_CONF_FILE.tmpl > $(pwd)/$FREEDV_CONF_FILE +fi + +sed "s/@FREEDV_COMPUTER_TO_RADIO_DEVICE@/$FREEDV_COMPUTER_TO_RADIO_DEVICE/g" $(pwd)/$FREEDV_CONF_FILE > $(pwd)/$FREEDV_CONF_FILE.tmp +mv $(pwd)/$FREEDV_CONF_FILE.tmp $(pwd)/$FREEDV_CONF_FILE +sed "s/@FREEDV_COMPUTER_TO_SPEAKER_DEVICE@/$FREEDV_COMPUTER_TO_SPEAKER_DEVICE/g" $(pwd)/$FREEDV_CONF_FILE > $(pwd)/$FREEDV_CONF_FILE.tmp +mv $(pwd)/$FREEDV_CONF_FILE.tmp $(pwd)/$FREEDV_CONF_FILE + +if [ "$FREEDV_MICROPHONE_TO_COMPUTER_DEVICE" == "FreeDV_Microphone_To_Computer" ] && [ "$OPERATING_SYSTEM" == "Linux" ]; then + sed "s/@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@/$FREEDV_MICROPHONE_TO_COMPUTER_DEVICE.monitor/g" $(pwd)/$FREEDV_CONF_FILE > $(pwd)/$FREEDV_CONF_FILE.tmp +else + sed "s/@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@/$FREEDV_MICROPHONE_TO_COMPUTER_DEVICE/g" $(pwd)/$FREEDV_CONF_FILE > $(pwd)/$FREEDV_CONF_FILE.tmp +fi +mv $(pwd)/$FREEDV_CONF_FILE.tmp $(pwd)/$FREEDV_CONF_FILE + +# Start recording +if [ "$FREEDV_TEST" == "tx" ]; then + if [ "$OPERATING_SYSTEM" == "Linux" ]; then + parecord --channels=1 --rate 8000 --file-format=wav --device "$REC_DEVICE" --latency 1 test.wav & + else + sox -t $SOX_DRIVER "$REC_DEVICE" -c 1 -r 8000 -t wav test.wav & + fi + RECORD_PID=$! +fi + +# Start FreeDV in test mode +$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut $FREEDV_TEST -utmode $FREEDV_MODE 2>&1 | tee tmp.log + +FDV_PID=$! +#sleep 30 +#screencapture ../screenshot.png +#wpctl status +#pw-top -b -n 5 +#wait $FDV_PID + +# Stop recording/playback and process data +if [ "$FREEDV_TEST" == "rx" ]; then + kill $PLAY_PID || echo "Already done playing" + NUM_RESYNCS=`grep "Sync changed" tmp.log | wc -l | xargs` + echo "Got $NUM_RESYNCS sync changes" +else + kill $RECORD_PID + sox test.wav -t raw -r 8k -c 1 -b 16 -e signed-integer test.raw silence 1 0.1 0.1% reverse silence 1 0.1 0.1% reverse + python3 $SCRIPTPATH/check-for-zeros.py test.raw +fi + +# Clean up PulseAudio virtual devices +if [ "$OPERATING_SYSTEM" == "Linux" ]; then + pactl unload-module $DRIVER_INDEX_LOOPBACK + pactl unload-module $DRIVER_INDEX_FREEDV_RADIO_TO_COMPUTER + pactl unload-module $DRIVER_INDEX_FREEDV_COMPUTER_TO_SPEAKER + pactl unload-module $DRIVER_INDEX_FREEDV_COMPUTER_TO_RADIO + pactl unload-module $DRIVER_INDEX_FREEDV_MICROPHONE_TO_COMPUTER +fi diff --git a/test/vac464.cer b/test/vac464.cer new file mode 100644 index 000000000..3e797f2b7 --- /dev/null +++ b/test/vac464.cer @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGDDCCBPSgAwIBAgIMW5i7HVbfDAJdVbddMA0GCSqGSIb3DQEBCwUAMG4xCzAJ +BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMUQwQgYDVQQDEztH +bG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ29kZVNpZ25pbmcgQ0EgLSBT +SEEyNTYgLSBHMzAeFw0yMDA1MjYxMjE4NDBaFw0yMzA4MjYxMjE4NDBaMIIBOzEd +MBsGA1UEDwwUUHJpdmF0ZSBPcmdhbml6YXRpb24xGDAWBgNVBAUTDzMxNjU0NzYw +MDEwNTU1NjETMBEGCysGAQQBgjc8AgEDEwJSVTEmMCQGCysGAQQBgjc8AgECExVO +b3Zvc2liaXJza2F5YSBvYmxhc3QxCzAJBgNVBAYTAlJVMR4wHAYDVQQIExVOb3Zv +c2liaXJza2F5YSBvYmxhc3QxFDASBgNVBAcTC05vdm9zaWJpcnNrMSswKQYDVQQK +EyJNdXp5Y2hlbmtvIEV2Z2VuaWkgVmlrdG9yb3ZpY2gsIElQMSswKQYDVQQDEyJN +dXp5Y2hlbmtvIEV2Z2VuaWkgVmlrdG9yb3ZpY2gsIElQMSYwJAYJKoZIhvcNAQkB +Fhdzb2Z0d2FyZUBtdXp5Y2hlbmtvLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMzy2+Ke77GcrnUkMh5D8SB99OrONoC3QkniNZ78H0k1t/9qdBB7 +PgsGIGMXlyo3tLx5A6kWI9iczp5iWEpdZUmvTs7AV/NDPGIDbHU7dJR1gFlzc4yn +0CSYbW5GkQEnYLZutYDzfJP6WkteKdj7EHg2N1iO9AtpcyyC8k4CWRahymT8wQLO +TmhcwoIgayY1rn4Qfyx5rO4EPf4dYl3NwNItJk5IAL+QgWuSQSIHptqbxM8G1Eiu +wR0Q+Ab5v3tKktdQc7OQootJv1IX1IhLaT/AYHTxrKugiTArwzAv77fYo6HUZPZ4 +a+lKt/FH/FF6wH0BZZoDtAab1NU2m15Cz8kCAwEAAaOCAdkwggHVMA4GA1UdDwEB +/wQEAwIHgDCBoAYIKwYBBQUHAQEEgZMwgZAwTgYIKwYBBQUHMAKGQmh0dHA6Ly9z +ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzZXh0ZW5kY29kZXNpZ25zaGEy +ZzNvY3NwLmNydDA+BggrBgEFBQcwAYYyaHR0cDovL29jc3AyLmdsb2JhbHNpZ24u +Y29tL2dzZXh0ZW5kY29kZXNpZ25zaGEyZzMwVQYDVR0gBE4wTDBBBgkrBgEEAaAy +AQIwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVw +b3NpdG9yeS8wBwYFZ4EMAQMwCQYDVR0TBAIwADBFBgNVHR8EPjA8MDqgOKA2hjRo +dHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZXh0ZW5kY29kZXNpZ25zaGEyZzMu +Y3JsMCIGA1UdEQQbMBmBF3NvZnR3YXJlQG11enljaGVua28ubmV0MBMGA1UdJQQM +MAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFNwsWCwqbzUtn3mVqEhdxG0+U7+5MB0G +A1UdDgQWBBSIENjDBY/ZwM9nURvPaBjwD0BpIDANBgkqhkiG9w0BAQsFAAOCAQEA +KAHk8s47IfKW8g/P2+Ia1koc/CkHFy4Q+R+uVC3DF3GVvNeBY9qCj8f8xUpQfkF1 +mRUyIxfGCWS6sg+/CtzMY+vuSZnGb1C/2GQ5hBP/CvXXGz1W+sKASy29EU/gzzKd +j0sBVAUk+dgQ18P8cyUDnBxOGaizG/Yn10/2jzLi2eyBoxnunpzxMcgaaLUhtt/7 +q7Jmor+A6FQ2xiuJlWE6TnUTR4aJH6uPswa6s6msHwNj9P7mbQfgK9P3pVmLuHBK +k9K9jkyL9KAAlNa7GBoT98a86z4x2lC/GcKOAD78M09qrfsd9+8f2II0XIXVuEuE +nabx4jlGD9IwXJ+daDrhfA== +-----END CERTIFICATE-----