Second pass at pipeline optimization. (#975)

* Don't write UT features until the end of TX/RX.

* Attempt 2 at preventing spurious Linux asan failures.

* Add PR #975 to changelog.

* Revert "Attempt 2 at preventing spurious Linux asan failures."

This reverts commit 8f53d1a980.

* Another attempt at preventing spurious asan failures.

* Revert back to k6aq.net hosted wheels.

* Resolve warnings in pipeline tests.

* ResampleStep: skip processing if no samples available.

* TapStep: execute in separate thread.

* Fix issue causing mic audio to stop when using Speex.

* Add a bit more time between loads.

* Use -O3 for libsamplerate to see if that helps.

* Revert "Add a bit more time between loads."

This reverts commit 1273d6531e.

* Disable content caching during CI build.

* Add printout of sysctl for debugging.

* Try speexdsp resampling to see if we can reduce CPU usage.

* Slightly increase quality.

* Increase quality again.

* Fix monitor functions not producing audio.

* Reenable Instruments tracing.

* Experiment: update librade to grab sync and SNR while running RX.

* Update SNR retrieval to make sure it happens on RX thread.

* Bump up resample quality again.

* Go back to libsamplerate.

* Use same semaphore wait time calculation for macOS as with other platforms.

* Try 20ms time quantum.

* Try 40ms.

* Back to 60ms.

* Wait needs to be based on start of work.

* Revert "Go back to libsamplerate."

This reverts commit 86d1c640ff.

* Try maximum quality on speex resampler first.

* Go back to calling fwrite() immediately on FIFO read.

* Revert resampler changes, again.

* Try experimental RADE branch again.

* Out of left field: try 2.9ms quantum like what Chrome uses.

* Improve reliability of Linux rade_loss test.

* Smooth out longer than expected semaphore waits on macOS.

* Fix compiler errors.

* Put quantum back to 60ms.

* Fix compiler error.

* More experimentation with time quantums.

* Use relaxed atomic semantics for macOS worker counter.

* Back to 2.9ms due to mpp test failures.

* Disable processing stats.

* Time quantum should match buffer size.

* Remove unneeded variable.

* Time quantum was accidentally getting set to 0.

* Try to reduce standard deviation for macOS wait time.

* Revert previous change, try just halving the wait time.

* Try to reduce likelihood of scheduler contention on macOS.

* macOS: Set lower thread QoS for non-audio threads.

* Using infinite wait for ThreadedObject.

* Force lowest possible latency.

* We can reference the activity ID directly.

* Avoid crash on stop from previous changes.

* Revert recent PulseAudioDevice changes in case those are causing 22.04 tests to fail.

* Try different macOS flag to improve responsiveness.

* Remove memset call from TX handling.

* Disable real-time threading on macOS.

* Use setiopolicy_np() to make sure writing RADE features doesn't interfere with the rest of the app.

* Resample input file for rade_loss test to lower CPU usage.

* Try soxr resampler for further CPU usage reductions.

* Forgot missed file.

* On macOS CI runs, we should also use the Homebrew soxr package.

* Revert soxr adoption, does not meet real time safety guidelines.

* Try deferring feature file writes until the end again.

* Turn on PYTHONOPTIMIZE.

* Read/write entire blocks from FIFO, not just single samples.

* Warning cleanup.

* Allow partial output if FIFO gets empty.

* Remove unneeded memset().

* Fix race condition preventing user from properly being hidden from FreeDV Reporter on startup.

* Downgrade additional pipeline threads to Utility QoS.

* Try malloc-free FIFO for RADE steps.

* Use 2048 samples instead.

* Use PreAllocatedFIFO in RADE steps to avoid cache misses.

* Use MKL enabled version of numpy for Intel macOS.

* Try Intel MKL version of PyTorch for macOS.

* Fix GH compiler issue on macOS ARM.

* Replace sleep_for with semaphore for TapStep.

* Only signal semaphore every 100ms like with previous sleep_based impl.

* Replace additional sleeps with semaphore waits.

* Fix compiler error.

* Adjust timing thresholds for dumping feature debug data.

* macOS: reenable real-time threading as a test.

* Make sure both RX and TX threads start processing at the same time.

* Update missed shared_ptr reference.

* Remove hidden allocation due to use of lambdas.

* Undo FIFO size reduction.

* Suppress spurious PlaybackStep completion messages.

* Fix CI compiler error.

* Use Time Profiler to get a better idea as to why RADE steps intermittently take longer to execute.

* g_voice_keyer_tx should use acquire/release.

* Revert "Use Time Profiler to get a better idea as to why RADE steps intermittently take longer to execute."

This reverts commit 711f83be0c.

* Only grab TX status once through the GUI timer.

* Only get sync once per timer run.

* Remove use of wxThread from TxRxThread.

* Use relaxed for retrieving sync status.

* Test: disable Mic In plot.

* Try using relaxed memory ordering.

* Revert "Use relaxed for retrieving sync status."

This reverts commit 364bc62e8c.

* Revert "Try using relaxed memory ordering."

This reverts commit bac5a8a799.

* One more attempt at using Speex resampler.

* Add optimizations for PlotScalar in case it's impacting test runs.

* Reenable lower quality level for plots.

* Try quality level 6.

* Actually fix spurious playback complete log messages.

* Back to quality level 10.

* macOS: Begin wait from end of work.

* Try DISPATCH_TIME_FOREVER.

* Revert "Try DISPATCH_TIME_FOREVER."

This reverts commit a913e8573e.

* Revert "macOS: Begin wait from end of work."

This reverts commit 7c719a3e3c.

* Add crazy hack to inhibit the UI thread while the pipeline is executing.

* Try checking pipeline spinlock every time we have to redraw something.

* Try actually sleeping instead of yielding.

* Revert crazy hack.

* Use separate RX thread instead of ThreadedTimer.

* Begin splitting GUI timer work into separate timers.

* Only update sync and TX state on the timers that need it.

* Defer item updates until highlights are updated.

* Delete accidentally added files.

* Try smaller block sizes.

* Try bigger block size for comparison.

* Re-disable real-time threading.

* Preallocate what we need to output RX/TX features.

* No need to duplicate bitmap to draw onto graphics context. (Reduces number of mmap() calls req'd on macOS.)

* Reenable real time threading.

* Force 50ms time quantum regardless of buffer size.

* Update reporting timers to attempt to guarantee that only one is active at a time.

* Add safety offset to BlackHole virtual audio device.

* WIP: Reduce CPU usage in PlotWaterfall.

* Fix compiler errors and rendering bugs.

* Fix compile error for real this time.

* Don't update FreeDV Reporter window if it's not actually visible.

* Back to 60ms time quantum.

* Back to 50ms.

* Slightly adjust how IsShownOnScreen() suppresses updates.

* Just use an array instead of map for PlotScalar.

* Prevent propagation of changes to FreeDV Reporter window unless something actually changed.

* Tweak Speex resampler quality again.

* Resample level 7.

* Don't need to recreate ThreadedTimer thread every time we need to restart.

* Try not running xctrace to see if we're close to fixing the issues.

* Wait a bit more time between loop iterations when playing UT recordings.

* Revert "Try not running xctrace to see if we're close to fixing the issues."

This reverts commit decff7a5b2.

* Add Time Profiler to xctrace files.

* Make all FreeDV Reporter rows invisible when window is not visible.

* Make FreeDV Reporter object synchronous.

* Add support for queuing on parent object thread instead of own.

* Fix Linux segfault.

* Use macOS Dispatch for ThreadedObject.

* Try disabling threading in TapStep.

* Reenable threading for TapStep.

* More quality level tweaking.

* Re-disable separate threading.

* Only allocate memory for feature storage if necessary.

* Use GCD for TapStep.

* Fix compiler error on Linux.

* Tweak maximum sizes of FIFOs based on recorded max usage during ctests.

* Additional adjustments to FIFO sizes.

* More tweaks of FIFO sizes for Linux.

* Disable workflow telemtry for macOS runs.

* Fix syntax error.

* Revert "Use GCD for TapStep."

This reverts commit e1522a7ab3.

* Fix memory violations when using PlotScalar.

* Fix rendering bugs from previous optimization.

* Try only returning numInputSamples at a time from vocoders.

* Revert src/pipeline to last passed build.

* Quality level 8 works better locally.

* Try 40ms blocks on macOS.

* Reduce the number of context switches while handling FreeDV Reporter traffic.

* Try reducing FIFO sizes again.

* Try reverting PyTorch upgrade.

* Didn't fully revert PyTorch changes.

* SoX is ignoring errors from Core Audio, use buffer sizes known to work.

* SoX divides by 8 to produce frame size sent to CoreAudio.

* Reset resampler back to last known working level.

* Update Linux/macOS audio code to match Windows implementation more closely.

* Try 40ms blocks again.

* Add middle click reset support for speaker/mic level.

* Revert "Try 40ms blocks again."

This reverts commit 3ca16e0335.

* Have FreeDV itself load test.wav due to performance limitations in GH environment.

* GitHub Actions: Disable iCloud (!)

* Need to set config value and not the slider (middle-click).

* Disable xctrace collection.

* Disable mic/speaker level slider unless modem is active.

* Return to libsamplerate for ResampleStep.

* MacAudioDevice cleanup.

* Remove unneeded warning message.

* Need memory_order_acquire for legacy sync state.

* Try RADE main branch again.

* Debugging: see if anything else is running that shouldn't be.

* GitHub: disable Spotlight too.

* Fix typo in README.

* Reenable macOS runner debugging.

* Include mimalloc for a bit extra boost.

* Disable mimalloc ctests.

* Disable macOS debugging again.

* mimalloc wasn't actually being used on macOS.

* mimalloc: decrease purge interval.

* Fix hidden memory allocation during TX output handling.

* Fix additional hidden memory allocations.

* Unit test: make sure rade_loss shows sync state.

* More hidden memory allocation removals.

* Further reduce duplicate memory allocations.

* Fix voice keyer bug reported by @barjac.

* More cleanup of unneeded allocations.

* Fix mini versions of scalar plots (discovered during testing).

* Remove some wxAsyncCallEventFunctor instances from gprof profile.

* Fix compiler error.

* Additional refactoring and memory allocation reduction.

* Extend purge delay a bit just in case.

* Allow GitHub Actions to re-execute test in case of an intermittent failure.

* Fix typo in rade_reporting test.

* Minor reduction in allocations during FreeDV Reporter RX handling.

* Use std::atomic for PlaybackStep.

* Revert "Use std::atomic for PlaybackStep."

This reverts commit 0ee65a38eb.

* Return nullptr to PlaybackStep if not actively playing.

* PlaybackStep: wait for main thread to finish stopping TX instead of previous approach.

* Fix rendering issue going between TX and RX.

* Fix issue where VK monitor plays previously TX audio prior to current VK cycle.

* Fix segfault in Hamlib on pressing Stop.

* Add missed destroying check while setting PTT.

* Additional cleanup in rig_control due to previous commits.
ms-hamlib-friendly-error
Mooneer Salem 2025-08-30 09:18:53 -07:00 committed by GitHub
parent e93f8c2d42
commit 12844f67c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 2191 additions and 1374 deletions

View File

@ -313,7 +313,7 @@ jobs:
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
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 --repeat until-pass:2
- name: Execute unit tests
shell: bash
@ -336,6 +336,6 @@ jobs:
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
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 --repeat until-pass:2

View File

@ -57,16 +57,18 @@ jobs:
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: Make sure iCloud isn't running
- name: Make sure resource hogs aren't running
shell: bash
run: |
sudo launchctl unload /System/Library/LaunchDaemons/com.apple.cloudd.plist
sudo mdutil -a -i off
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist
- name: Execute unit tests
shell: bash
working-directory: ${{github.workspace}}/build_osx
run: |
FREEDV_COMPUTER_TO_RADIO_DEVICE="VB-Cable" FREEDV_RADIO_TO_COMPUTER_DEVICE="VB-Cable" FREEDV_COMPUTER_TO_SPEAKER_DEVICE="BlackHole1 2ch" FREEDV_MICROPHONE_TO_COMPUTER_DEVICE="BlackHole2 2ch" ctest -V
FREEDV_COMPUTER_TO_RADIO_DEVICE="VB-Cable" FREEDV_RADIO_TO_COMPUTER_DEVICE="VB-Cable" FREEDV_COMPUTER_TO_SPEAKER_DEVICE="BlackHole1 2ch" FREEDV_MICROPHONE_TO_COMPUTER_DEVICE="BlackHole2 2ch" ctest -V --repeat until-pass:2
#- name: Zip up traces
# shell: bash

View File

@ -535,6 +535,11 @@ else()
include(cmake/BuildSpeex.cmake)
endif()
# On Linux/macOS, build mimalloc for a bit more performance
if ((LINUX OR APPLE) AND (NOT ENABLE_ASAN AND NOT ENABLE_RTSAN))
include(cmake/BuildMimalloc.cmake)
endif ((LINUX OR APPLE) AND (NOT ENABLE_ASAN AND NOT ENABLE_RTSAN))
#
# Find libdl for dlopen/dlclose
#

View File

@ -1,4 +1,4 @@
# Building FreeDV GUI
# Building FreeDV GUI
This document describes how to build the FreeDV GUI program for various operating systems. FreeDV GUI is developed on Ubuntu Linux, and then cross compiled for Windows using Fedora Linux (Fedora has great cross compiling support) and Docker.

View File

@ -851,6 +851,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
2. Enhancements:
* Add Mic/Speaker volume control to main window. (PR #980)
* Move less used Spectrum plot configuration to free up space on main window. (PR #996)
* Further audio performance improvements. (PR #975)
* Add Automatic Gain Control (AGC) to microphone input. (PR #997)
* Linux: Search for and list serial devices from /dev/serial for PTT config. (PR #999)
* Add RADEV1 sample file and remove samples for unsupported modes. (PR #998)

View File

@ -23,7 +23,8 @@ for i in {1..2}; do
kDevice2_IsHidden=false \
kDevice2_HasInput=true \
kDevice2_HasOutput=true" \
MACOSX_DEPLOYMENT_TARGET=10.13
kLatency_Frame_Size='128' \
MACOSX_DEPLOYMENT_TARGET=11.0
sudo mv build/BlackHole.driver /Library/Audio/Plug-Ins/HAL/$driverName.driver
done

View File

@ -0,0 +1,23 @@
if(WIN32)
message(FATAL_ERROR "mimalloc is only supported on Linux and macOS")
endif(WIN32)
set(MI_BUILD_TESTS FALSE)
include(FetchContent)
FetchContent_Declare(
mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
GIT_TAG v2.2.4
UPDATE_DISCONNECTED 1
)
FetchContent_GetProperties(mimalloc)
if(NOT mimalloc_POPULATED)
FetchContent_Populate(mimalloc)
add_subdirectory(${mimalloc_SOURCE_DIR} ${mimalloc_BINARY_DIR})
include_directories(${mimalloc_SOURCE_DIR}/include)
add_definitions(-DUSING_MIMALLOC)
endif()

View File

@ -8,6 +8,8 @@ if(BUILD_OSX_UNIVERSAL)
set(RADE_CMAKE_ARGS ${RADE_CMAKE_ARGS} -DBUILD_OSX_UNIVERSAL=1)
endif(BUILD_OSX_UNIVERSAL)
set(RADE_CMAKE_ARGS ${RADE_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
include(ExternalProject)
ExternalProject_Add(build_rade
SOURCE_DIR rade_src

View File

@ -16,7 +16,7 @@ if(NOT samplerate_POPULATED)
FetchContent_Populate(samplerate)
add_subdirectory(${samplerate_SOURCE_DIR} ${samplerate_BINARY_DIR} EXCLUDE_FROM_ALL)
list(APPEND FREEDV_PACKAGE_SEARCH_PATHS ${samplerate_BINARY_DIR}/src)
target_compile_options(samplerate PRIVATE -g -O2) # Ensure that samplerate is built with optimizations
target_compile_options(samplerate PRIVATE -g -O3) # Ensure that samplerate is built with optimizations
endif()
list(APPEND FREEDV_LINK_LIBS samplerate)

View File

@ -1,4 +1,9 @@
if((LINUX OR APPLE) AND (NOT ENABLE_ASAN AND NOT ENABLE_RTSAN))
set(MIMALLOC_SOURCE_FILE $<TARGET_OBJECTS:mimalloc-obj>)
endif((LINUX OR APPLE) AND (NOT ENABLE_ASAN AND NOT ENABLE_RTSAN))
set(FREEDV_SOURCES
${MIMALLOC_SOURCE_FILE}
main.cpp
topFrame.cpp
sox_biquad.c

View File

@ -37,11 +37,11 @@ using namespace std::chrono_literals;
class IAudioDevice : public IRealtimeHelper
{
public:
typedef std::function<void(IAudioDevice&, void*, size_t, void*)> AudioDataCallbackFn;
typedef std::function<void(IAudioDevice&, void*)> AudioUnderflowCallbackFn;
typedef std::function<void(IAudioDevice&, void*)> AudioOverflowCallbackFn;
typedef std::function<void(IAudioDevice&, std::string, void*)> AudioErrorCallbackFn;
typedef std::function<void(IAudioDevice&, std::string, void*)> AudioDeviceChangedCallbackFn;
typedef void (*AudioDataCallbackFn)(IAudioDevice&, void*, size_t, void*);
typedef void (*AudioUnderflowCallbackFn)(IAudioDevice&, void*);
typedef void (*AudioOverflowCallbackFn)(IAudioDevice&, void*);
typedef void (*AudioErrorCallbackFn)(IAudioDevice&, std::string, void*);
typedef void (*AudioDeviceChangedCallbackFn)(IAudioDevice&, std::string, void*);
virtual int getNumChannels() = 0;
virtual int getSampleRate() const = 0;

View File

@ -228,7 +228,7 @@ void MacAudioDevice::start()
// Set maxFrameSize to something reasonable for further down.
maxFrameSize = 4096;
}
chosenFrameSize_ = desiredFrameSize;
// Create AUHAL object
@ -629,6 +629,8 @@ int MacAudioDevice::getLatencyInMicroseconds()
void MacAudioDevice::setHelperRealTime()
{
numRealTimeWorkers_.fetch_add(1, std::memory_order_release);
// Set thread QoS to "user interactive"
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
@ -750,8 +752,12 @@ OSStatus MacAudioDevice::InputProc_(
thisObj->onAudioDataFunction(*thisObj, thisObj->inputFrames_, inNumberFrames, thisObj->onAudioDataState);
}
dispatch_semaphore_signal(thisObj->sem_);
auto numWorkers = thisObj->numRealTimeWorkers_.load(std::memory_order_acquire);
for (; numWorkers > 0; numWorkers--)
{
dispatch_semaphore_signal(thisObj->sem_);
}
}
else
{
@ -771,8 +777,6 @@ OSStatus MacAudioDevice::OutputProc_(
{
MacAudioDevice* thisObj = (MacAudioDevice*)inRefCon;
memset(thisObj->inputFrames_, 0, sizeof(short) * thisObj->numChannels_ * inNumberFrames);
if (thisObj->onAudioDataFunction)
{
thisObj->onAudioDataFunction(*thisObj, thisObj->inputFrames_, inNumberFrames, thisObj->onAudioDataState);
@ -875,6 +879,7 @@ void MacAudioDevice::stopRealTimeWork(bool fastMode)
void MacAudioDevice::clearHelperRealTime()
{
numRealTimeWorkers_.fetch_sub(1, std::memory_order_release);
leaveWorkgroup_();
}

View File

@ -84,6 +84,7 @@ private:
AudioBufferList* bufferList_;
bool running_;
int chosenFrameSize_;
std::atomic<int> numRealTimeWorkers_;
void joinWorkgroup_();
void leaveWorkgroup_();

View File

@ -135,7 +135,7 @@ float FreeDVInterface::GetMinimumSNR_(int mode)
void FreeDVInterface::start(int txMode, int fifoSizeMs, bool singleRxThread, bool usingReliableText)
{
sync_ = 0;
sync_.store(0, std::memory_order_release);
singleRxThread_ = enabledModes_.size() > 1 ? singleRxThread : true;
modemStatsList_ = new MODEM_STATS[enabledModes_.size()];
@ -457,7 +457,7 @@ void FreeDVInterface::setSync(int val)
int FreeDVInterface::getSync() const
{
return sync_;
return sync_.load(std::memory_order_acquire);
}
void FreeDVInterface::setEq(int val)
@ -702,7 +702,7 @@ float FreeDVInterface::getSNREstimate()
if (txMode_ >= FREEDV_MODE_RADE)
{
// Special handling for RADE
return (getSync() ? rade_snrdB_3k_est(rade_) : 0);
return (getSync() ? radeSnr_.load(std::memory_order_acquire) : 0);
}
else
{
@ -726,7 +726,7 @@ IPipelineStep* FreeDVInterface::createTransmitPipeline(
radeTxStep_ = new RADETransmitStep(rade_, lpcnetEncState_);
auto pipeline = new AudioPipeline(inputSampleRate, outputSampleRate);
pipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(radeTxStep_));
pipeline->appendPipelineStep(radeTxStep_);
return pipeline;
}
@ -793,11 +793,12 @@ IPipelineStep* FreeDVInterface::createReceivePipeline(
auto rxStep = new RADEReceiveStep(rade_, &fargan_, radeTextPtr_, [&, getRxStateFn](RADEReceiveStep* s) {
auto finalSync = s->getSync();
*getRxStateFn() = finalSync;
sync_ = finalSync;
sync_.store(finalSync, std::memory_order_release);
radeSnr_.store(s->getSnr(), std::memory_order_release);
});
auto pipeline = new AudioPipeline(inputSampleRate, outputSampleRate);
pipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(rxStep));
pipeline->appendPipelineStep(rxStep);
return pipeline;
}
else
@ -850,7 +851,7 @@ int FreeDVInterface::preProcessRxFn_(ParallelStep* stepObj)
{
assert(step != nullptr);
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)step.get();
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)step;
castedStep->setSigPwrAvg(*state->getSigPwrAvgFn());
castedStep->setChannelNoiseEnable(state->getChannelNoiseFn(), state->getChannelNoiseSnrFn());
castedStep->setFreqOffset(state->getFreqOffsetFn());
@ -859,7 +860,7 @@ int FreeDVInterface::preProcessRxFn_(ParallelStep* stepObj)
// If the current RX mode is still sync'd, only process through that one.
for (auto& dv : dvObjects_)
{
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex].get();
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex];
if (dv == currentRxMode_ && castedStep->getSync())
{
return rxIndex;
@ -885,7 +886,7 @@ int FreeDVInterface::postProcessRxFn_(ParallelStep* stepObj)
for (auto& dv : dvObjects_)
{
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex].get();
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex];
if (dv == currentRxMode_ && castedStep->getSync())
{
dvWithSync = dv;
@ -910,7 +911,7 @@ int FreeDVInterface::postProcessRxFn_(ParallelStep* stepObj)
bool canUnsquelch = !squelchEnabled_ ||
(squelchEnabled_ && snr >= squelchVals_[rxIndex]);
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex].get();
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[rxIndex];
if (snr > maxSyncFound && castedStep->getSync() != 0 && canUnsquelch)
{
maxSyncFound = snr;
@ -944,7 +945,7 @@ skipSyncCheck:
int finalSync = 0;
if (dvWithSync != nullptr)
{
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[indexWithSync].get();
FreeDVReceiveStep* castedStep = (FreeDVReceiveStep*)parallelSteps[indexWithSync];
// grab extended stats so we can plot spectrum, scatter diagram etc
freedv_get_modem_extended_stats(dvWithSync, stats);
@ -963,12 +964,12 @@ skipSyncCheck:
}
else
{
RADEReceiveStep* castedStep = (RADEReceiveStep*)parallelSteps[0].get();
RADEReceiveStep* castedStep = (RADEReceiveStep*)parallelSteps[0];
finalSync = castedStep->getSync();
}
*state->getRxStateFn() = finalSync;
sync_ = finalSync;
sync_.store(finalSync, std::memory_order_release);
return indexWithSync;
};

View File

@ -204,6 +204,7 @@ private:
LPCNetEncState *lpcnetEncState_;
RADETransmitStep *radeTxStep_;
std::atomic<int> sync_;
std::atomic<int> radeSnr_;
rade_text_t radeTextPtr_;
int preProcessRxFn_(ParallelStep* ps);

View File

@ -302,11 +302,18 @@ void PlotScalar::draw(wxGraphicsContext* ctx, bool repaintDataOnly)
if (!m_bar_graph)
{
int offsetX = 0;
int offsetY = 0;
if (!m_mini)
{
offsetX = PLOT_BORDER + XLEFT_OFFSET;
offsetY = PLOT_BORDER;
}
for (int index = 0; index < plotWidth; index++)
{
auto item = &lineMap_[index];
int x = index + PLOT_BORDER + XLEFT_OFFSET;
ctx->StrokeLine(x, item->y1 + PLOT_BORDER, x, item->y2 + PLOT_BORDER);
int x = index + offsetX;
ctx->StrokeLine(x, item->y1 + offsetY, x, item->y2 + offsetY);
}
}

View File

@ -47,6 +47,14 @@ using namespace std::chrono_literals;
extern wxConfigBase *pConfig;
struct AudioDeviceCapture
{
FIFO* fifo;
std::condition_variable* cv;
bool* running;
int* n;
};
void AudioOptsDialog::audioEngineInit(void)
{
m_isPaInitialized = true;
@ -871,16 +879,19 @@ void AudioOptsDialog::plotDeviceInputForAFewSecs(wxString devName, PlotScalar *p
bool running = true;
callbackFifo = codec2_fifo_create(sampleRate);
assert(callbackFifo != nullptr);
device->setOnAudioData([&](IAudioDevice&, void* data, size_t numSamples, void* state) {
if (running && data != nullptr)
AudioDeviceCapture capture { .fifo = callbackFifo, .cv = &callbackFifoCV, .running = &running };
device->setOnAudioData([](IAudioDevice&, void* data, size_t numSamples, void* state) {
AudioDeviceCapture* castedState = (AudioDeviceCapture*)state;
if (*castedState->running && data != nullptr)
{
short* in48k_short = static_cast<short*>(data);
codec2_fifo_write(callbackFifo, in48k_short, numSamples);
codec2_fifo_write(castedState->fifo, in48k_short, numSamples);
}
callbackFifoCV.notify_one();
}, nullptr);
castedState->cv->notify_one();
}, &capture);
device->setDescription("Device Input Test");
device->start();
@ -1004,21 +1015,24 @@ void AudioOptsDialog::plotDeviceOutputForAFewSecs(wxString devName, PlotScalar *
callbackFifo = codec2_fifo_create(sampleRate);
assert(callbackFifo != nullptr);
device->setOnAudioData([&](IAudioDevice&, void* data, size_t numSamples, void* state) {
if (running && data != nullptr)
AudioDeviceCapture capture { .fifo = callbackFifo, .cv = &callbackFifoCV, .running = &running, .n = &n };
device->setOnAudioData([](IAudioDevice& dev, void* data, size_t numSamples, void* state) {
AudioDeviceCapture* castedState = (AudioDeviceCapture*)state;
if (*castedState->running && data != nullptr)
{
short* out48k_short = static_cast<short*>(data);
for(size_t j = 0; j < numSamples; j++, n++)
for(size_t j = 0; j < numSamples; j++, (*castedState->n)++)
{
out48k_short[j] = 2000.0*cos(6.2832*(n)*400.0/sampleRate);
out48k_short[j] = 2000.0*cos(6.2832*(*castedState->n)*400.0/dev.getSampleRate());
}
codec2_fifo_write(callbackFifo, out48k_short, numSamples);
codec2_fifo_write(castedState->fifo, out48k_short, numSamples);
}
callbackFifoCV.notify_one();
}, nullptr);
castedState->cv->notify_one();
}, &capture);
device->setDescription("Device Output Test");
device->start();

View File

@ -915,16 +915,20 @@ void EasySetupDialog::OnTest(wxCommandEvent& event)
}
sineWaveSampleNumber_ = 0;
txTestAudioDevice_->setOnAudioData([&, radioOutSampleRate](IAudioDevice& dev, void* data, size_t size, void* state) {
txTestAudioDevice_->setOnAudioData([](IAudioDevice& dev, void* data, size_t size, void* state) {
auto sr = dev.getSampleRate();
EasySetupDialog* castedThis = (EasySetupDialog*)state;
short* audioData = static_cast<short*>(data);
for (unsigned long index = 0; index < size; index++)
{
*audioData++ = (SHRT_MAX) * sin(2 * PI * (1500) * sineWaveSampleNumber_ / radioOutSampleRate);
sineWaveSampleNumber_ = (sineWaveSampleNumber_ + 1) % radioOutSampleRate;
*audioData++ = (SHRT_MAX) * sin(2 * PI * (1500) * castedThis->sineWaveSampleNumber_ / sr);
castedThis->sineWaveSampleNumber_ = (castedThis->sineWaveSampleNumber_ + 1) % sr;
}
}, this);
txTestAudioDevice_->start();
}

View File

@ -1412,26 +1412,19 @@ void OptionsDlg::OnFreqModeChangeEnable(wxCommandEvent& event)
}
void OptionsDlg::DisplayFifoPACounters() {
const int STR_LENGTH = 256;
char fifo_counters[STR_LENGTH];
if (IsShownOnScreen())
{
wxString fifo_counters = wxString::Format(wxT("Fifos: infull1: %d outempty1: %d infull2: %d outempty2: %d"), g_infifo1_full, g_outfifo1_empty, g_infifo2_full, g_outfifo2_empty);
m_textFifos->SetLabel(fifo_counters);
snprintf(fifo_counters, STR_LENGTH, "Fifos: infull1: %d outempty1: %d infull2: %d outempty2: %d", g_infifo1_full, g_outfifo1_empty, g_infifo2_full, g_outfifo2_empty);
wxString fifo_counters_string(fifo_counters);
m_textFifos->SetLabel(fifo_counters_string);
// input: underflow overflow output: underflow overflow
wxString pa_counters_1 = wxString::Format(wxT("Audio1: inUnderflow: %d inOverflow: %d outUnderflow %d outOverflow %d"), g_AEstatus1[0], g_AEstatus1[1], g_AEstatus1[2], g_AEstatus1[3]);
m_textPA1->SetLabel(pa_counters_1);
char pa_counters1[STR_LENGTH];
// input: underflow overflow output: underflow overflow
snprintf(pa_counters1, STR_LENGTH, "Audio1: inUnderflow: %d inOverflow: %d outUnderflow %d outOverflow %d", g_AEstatus1[0], g_AEstatus1[1], g_AEstatus1[2], g_AEstatus1[3]);
wxString pa_counters1_string(pa_counters1); m_textPA1->SetLabel(pa_counters1_string);
char pa_counters2[STR_LENGTH];
// input: underflow overflow output: underflow overflow
snprintf(pa_counters2, STR_LENGTH, "Audio2: inUnderflow: %d inOverflow: %d outUnderflow %d outOverflow %d", g_AEstatus2[0], g_AEstatus2[1], g_AEstatus2[2], g_AEstatus2[3]);
wxString pa_counters2_string(pa_counters2);
m_textPA2->SetLabel(pa_counters2_string);
// input: underflow overflow output: underflow overflow
wxString pa_counters_2 = wxString::Format(wxT("Audio2: inUnderflow: %d inOverflow: %d outUnderflow %d outOverflow %d"), g_AEstatus2[0], g_AEstatus2[1], g_AEstatus2[2], g_AEstatus2[3]);
m_textPA2->SetLabel(pa_counters_2);
}
}
void OptionsDlg::OnReportingFreqSelectionChange(wxCommandEvent& event)

View File

@ -46,7 +46,7 @@ extern FreeDVInterface freedvInterface;
#define RIGHTMOST_COL (LAST_UPDATE_DATE_COL + 1)
#define UNKNOWN_SNR_VAL (-99)
#define UNKNOWN_STR ""
const wxString UNKNOWN_STR("");
#define NUM_COLS (LAST_UPDATE_DATE_COL + 1)
#define RX_ONLY_STATUS "RX Only"
#define RX_COLORING_LONG_TIMEOUT_SEC (20)
@ -731,13 +731,13 @@ void FreeDVReporterDialog::OnBandFilterChange(wxCommandEvent& event)
setBandFilter(freq);
// Defer deselection until after UI updates
CallAfter([&]() { DeselectItem(); });
CallAfter([this]() { DeselectItem(); });
}
void FreeDVReporterDialog::FreeDVReporterDataModel::deallocateRemovedItems()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
std::unique_lock<std::recursive_mutex> lk(dataMtx_);
std::vector<std::string> keysToRemove;
@ -765,7 +765,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::deallocateRemovedItems()
void FreeDVReporterDialog::FreeDVReporterDataModel::triggerResort()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
Resort();
});
parent_->CallAfter(std::bind(&FreeDVReporterDialog::FreeDVReporterDataModel::execQueuedAction_, this));
@ -774,7 +774,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::triggerResort()
void FreeDVReporterDialog::FreeDVReporterDataModel::updateHighlights()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
std::unique_lock<std::recursive_mutex> lk(dataMtx_);
// Iterate across all visible rows. If a row is currently highlighted
@ -930,7 +930,7 @@ void FreeDVReporterDialog::OnFilterTrackingEnable(wxCommandEvent& event)
setBandFilter(freq);
// Defer deselection until after UI updates
CallAfter([&]() { DeselectItem(); });
CallAfter([this]() { DeselectItem(); });
}
void FreeDVReporterDialog::OnItemDoubleClick(wxDataViewEvent& event)
@ -972,7 +972,6 @@ void FreeDVReporterDialog::AdjustToolTip(wxMouseEvent& event)
if (col->GetModelColumn() == desiredCol)
{
auto textSize = m_listSpots->GetTextExtent(tempUserMessage_);
rect = m_listSpots->GetItemRect(item, col);
if (tipWindow_ == nullptr)
{
@ -1325,11 +1324,11 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::setBandFilter(FilterFrequenc
wxString FreeDVReporterDialog::FreeDVReporterDataModel::makeValidTime_(std::string timeStr, wxDateTime& timeObj)
{
wxRegEx millisecondsRemoval(_("\\.[^+-]+"));
wxRegEx millisecondsRemoval(wxT("\\.[^+-]+"));
wxString tmp = timeStr;
millisecondsRemoval.Replace(&tmp, _(""));
millisecondsRemoval.Replace(&tmp, wxT(""));
wxRegEx timezoneRgx(_("([+-])([0-9]+):([0-9]+)$"));
wxRegEx timezoneRgx(wxT("([+-])([0-9]+):([0-9]+)$"));
wxDateTime::TimeZone timeZone(0); // assume UTC by default
if (timezoneRgx.Matches(tmp))
{
@ -1340,12 +1339,12 @@ wxString FreeDVReporterDialog::FreeDVReporterDataModel::makeValidTime_(std::stri
int tzMinutes = wxAtoi(hours) * 60;
tzMinutes += wxAtoi(minutes);
if (tzOffset == "-")
if (tzOffset == wxT("-"))
{
tzMinutes = -tzMinutes;
}
timezoneRgx.Replace(&tmp, _(""));
timezoneRgx.Replace(&tmp, wxT(""));
timeZone = wxDateTime::TimeZone(tzMinutes);
}
@ -1364,7 +1363,7 @@ wxString FreeDVReporterDialog::FreeDVReporterDataModel::makeValidTime_(std::stri
timeZone = wxDateTime::TimeZone(wxDateTime::TZ::Local);
}
wxString formatStr = "%x %X";
wxString formatStr = wxT("%x %X");
#if __APPLE__
// Workaround for weird macOS bug preventing .Format from working properly when double-clicking
@ -1393,7 +1392,7 @@ wxString FreeDVReporterDialog::FreeDVReporterDataModel::makeValidTime_(std::stri
else
{
timeObj = wxDateTime();
return _(UNKNOWN_STR);
return UNKNOWN_STR;
}
}
@ -1444,7 +1443,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::calculateLatLonFromGridSquar
// If grid square is 6 or more letters, THEN use the next two.
// Otherwise, optional.
wxString optionalSegment = gridSquare.Mid(4, 2);
wxRegEx allLetters(_("^[A-Z]{2}$"));
const wxRegEx allLetters(_("^[A-Z]{2}$"));
if (gridSquare.Length() >= 6 && allLetters.Matches(optionalSegment))
{
lon += ((char)gridSquare.GetChar(4) - charA) * 5.0 / 60;
@ -1669,7 +1668,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::setReporter(std::shared_ptr<
// actions fully execute before clearing entries.
log_debug("Reporter object set to null");
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
clearAllEntries_();
});
@ -2106,7 +2105,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::requestQSY(wxDataViewItem se
void FreeDVReporterDialog::FreeDVReporterDataModel::onReporterConnect_()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
log_debug("Connected to server");
filterSelfMessageUpdates_ = false;
clearAllEntries_();
@ -2118,7 +2117,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReporterConnect_()
void FreeDVReporterDialog::FreeDVReporterDataModel::onReporterDisconnect_()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
log_debug("Disconnected from server");
isConnected_ = false;
filterSelfMessageUpdates_ = false;
@ -2131,7 +2130,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReporterDisconnect_()
void FreeDVReporterDialog::FreeDVReporterDataModel::onUserConnectFn_(std::string sid, std::string lastUpdate, std::string callsign, std::string gridSquare, std::string version, bool rxOnly)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid, lastUpdate, callsign, gridSquare, version, rxOnly]() {
fnQueue_.push_back([this, sid, lastUpdate, callsign, gridSquare, version, rxOnly]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
assert(wxThread::IsMain());
@ -2269,7 +2268,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onUserConnectFn_(std::string
void FreeDVReporterDialog::FreeDVReporterDataModel::onConnectionSuccessfulFn_()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
log_debug("Fully connected to server");
@ -2284,7 +2283,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onConnectionSuccessfulFn_()
void FreeDVReporterDialog::FreeDVReporterDataModel::onUserDisconnectFn_(std::string sid, std::string lastUpdate, std::string callsign, std::string gridSquare, std::string version, bool rxOnly)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid]() {
fnQueue_.push_back([this, sid]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
assert(wxThread::IsMain());
@ -2313,7 +2312,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onUserDisconnectFn_(std::str
void FreeDVReporterDialog::FreeDVReporterDataModel::onFrequencyChangeFn_(std::string sid, std::string lastUpdate, std::string callsign, std::string gridSquare, uint64_t frequencyHz)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid, frequencyHz, lastUpdate]() {
fnQueue_.push_back([this, sid, frequencyHz, lastUpdate]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
auto iter = allReporterData_.find(sid);
@ -2380,7 +2379,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onFrequencyChangeFn_(std::st
void FreeDVReporterDialog::FreeDVReporterDataModel::onTransmitUpdateFn_(std::string sid, std::string lastUpdate, std::string callsign, std::string gridSquare, std::string txMode, bool transmitting, std::string lastTxDate)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid, txMode, transmitting, lastTxDate, lastUpdate]() {
fnQueue_.push_back([this, sid, txMode, transmitting, lastTxDate, lastUpdate]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
auto iter = allReporterData_.find(sid);
@ -2439,7 +2438,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onTransmitUpdateFn_(std::str
void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::string sid, std::string lastUpdate, std::string callsign, std::string gridSquare, std::string receivedCallsign, float snr, std::string rxMode)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid, lastUpdate, receivedCallsign, snr, rxMode]() {
fnQueue_.push_back([this, sid, lastUpdate, receivedCallsign, snr, rxMode]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
auto iter = allReporterData_.find(sid);
@ -2451,18 +2450,21 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::stri
return;
}
wxString receivedCallsignWx(receivedCallsign);
wxString rxModeWx(rxMode);
auto sortingColumn = parent_->m_listSpots->GetSortingColumn();
bool isChanged =
(sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != receivedCallsign) ||
(sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_MODE_COL) && iter->second->lastRxMode != rxMode);
(sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_CALLSIGN_COL) && iter->second->lastRxCallsign != receivedCallsignWx) ||
(sortingColumn == parent_->m_listSpots->GetColumn(LAST_RX_MODE_COL) && iter->second->lastRxMode != rxModeWx);
bool isDataChanged =
iter->second->lastRxCallsign != receivedCallsign ||
iter->second->lastRxMode != rxMode;
iter->second->lastRxCallsign != receivedCallsignWx ||
iter->second->lastRxMode != rxModeWx;
iter->second->lastRxCallsign = receivedCallsign;
iter->second->lastRxMode = rxMode;
iter->second->lastRxCallsign = receivedCallsignWx;
iter->second->lastRxMode = rxModeWx;
wxString snrString = wxString::Format(_("%.01f"), snr);
wxString snrString = wxString::Format(wxT("%.01f"), snr);
if (receivedCallsign == "" && rxMode == "")
{
// Frequency change--blank out SNR too.
@ -2512,7 +2514,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onReceiveUpdateFn_(std::stri
void FreeDVReporterDialog::FreeDVReporterDataModel::onMessageUpdateFn_(std::string sid, std::string lastUpdate, std::string message)
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&, sid, lastUpdate, message]() {
fnQueue_.push_back([this, sid, lastUpdate, message]() {
std::unique_lock<std::recursive_mutex> lk(const_cast<std::recursive_mutex&>(dataMtx_));
auto iter = allReporterData_.find(sid);
@ -2572,7 +2574,7 @@ void FreeDVReporterDialog::FreeDVReporterDataModel::onMessageUpdateFn_(std::stri
void FreeDVReporterDialog::FreeDVReporterDataModel::onAboutToShowSelfFn_()
{
std::unique_lock<std::mutex> lk(fnQueueMtx_);
fnQueue_.push_back([&]() {
fnQueue_.push_back([this]() {
filterSelfMessageUpdates_ = true;
});

File diff suppressed because it is too large Load Diff

View File

@ -458,7 +458,8 @@ class MainFrame : public TopFrame
private:
friend class MainApp; // needed for unit tests
friend class TxRxThread; // XXX - needed for execOnUiThreadAndWait_().
std::shared_ptr<IAudioDevice> rxInSoundDevice;
std::shared_ptr<IAudioDevice> rxOutSoundDevice;
std::shared_ptr<IAudioDevice> txInSoundDevice;
@ -532,6 +533,31 @@ class MainFrame : public TopFrame
void onFrequencyModeChange_(IRigFrequencyController*, uint64_t freq, IRigFrequencyController::Mode mode);
void onRadioConnected_(IRigController* ptr);
void onRadioDisconnected_(IRigController* ptr);
// Audio error handlers
void onAudioEngineError_(IAudioEngine&, std::string error, void* state);
void onAudioDeviceError_(std::string error);
static void OnAudioDeviceError_(IAudioDevice&, std::string error, void* state);
// Audio device change handling
template<int soundCardId, bool isOut>
void handleAudioDeviceChange_(std::string newDeviceName);
// Audio device data handlers
static void OnTxInAudioData_(IAudioDevice& dev, void* data, size_t size, void* state);
static void OnTxOutAudioData_(IAudioDevice& dev, void* data, size_t size, void* state);
static void OnRxInAudioData_(IAudioDevice& dev, void* data, size_t size, void* state);
static void OnRxOutAudioData_(IAudioDevice& dev, void* data, size_t size, void* state);
// QSY request handling
struct QsyRequestArgs {
std::string callsign;
uint64_t freqHz;
std::string message;
};
void onQsyRequest_(std::string callsign, uint64_t freqHz, std::string message);
void onQsyRequestUIThread_(QsyRequestArgs* args);
};
void resample_for_plot(struct FIFO *plotFifo, short buf[], short* dec_samples, int length, int fs);

View File

@ -38,6 +38,8 @@ extern short *g_error_hist, *g_error_histn;
extern int g_resyncs;
extern int g_Nc;
extern int g_txLevel;
extern std::atomic<float> g_txLevelScale;
extern wxConfigBase *pConfig;
extern bool endingTx;
extern int g_outfifo1_empty;
@ -701,6 +703,10 @@ void MainFrame::OnChangeTxLevel( wxScrollEvent& event )
char fmt[15];
g_txLevel = m_sliderTxLevel->GetValue();
float dbLoss = g_txLevel / 10.0;
float scaleFactor = exp(dbLoss/20.0 * log(10.0));
g_txLevelScale.store(scaleFactor, std::memory_order_release);
snprintf(fmt, 15, "%0.1f dB", (double)(g_txLevel)/10.0);
wxString fmtString(fmt);
m_txtTxLevelNum->SetLabel(fmtString);
@ -717,7 +723,7 @@ void MainFrame::OnChangeMicSpkrLevel( wxScrollEvent& event )
auto sliderLevel = (double)m_sliderMicSpkrLevel->GetValue() / 10.0;
if (g_tx)
if (g_tx.load(std::memory_order_acquire))
{
wxGetApp().appConfiguration.filterConfiguration.micInChannel.volInDB = sliderLevel;
m_newMicInFilter = true;
@ -847,7 +853,7 @@ void MainFrame::OnTogBtnPTT (wxCommandEvent& event)
// on Windows. Just to be sure, we force the correct state
// here (similar to what's already done for ending TX while
// using the voice keyer).
m_btnTogPTT->SetValue(!g_tx);
m_btnTogPTT->SetValue(!g_tx.load(std::memory_order_acquire));
m_btnTogPTT->SetBackgroundColour(m_btnTogPTT->GetValue() ? *wxRED : wxNullColour);
togglePTT();
@ -860,7 +866,7 @@ void MainFrame::togglePTT(void) {
// Change tabbed page in centre panel depending on PTT state
if (g_tx)
if (g_tx.load(std::memory_order_acquire))
{
// Sleep for long enough that we get the remaining [blocksize] ms of audio.
int msSleep = (1000 * freedvInterface.getTxNumSpeechSamples()) / freedvInterface.getTxSpeechSampleRate();
@ -990,7 +996,7 @@ void MainFrame::togglePTT(void) {
wxGetApp().Yield(true);
}
}
g_tx = false;
g_tx.store(false, std::memory_order_release);
endingTx = false;
char fmt[16];
@ -1001,7 +1007,12 @@ void MainFrame::togglePTT(void) {
// tx-> rx transition, swap to the page we were on for last rx
m_auiNbookCtrl->ChangeSelection(wxGetApp().appConfiguration.currentNotebookTab);
for (int index = 0; index < m_auiNbookCtrl->GetPageCount(); index++)
{
auto page = m_auiNbookCtrl->GetPage(index);
page->Refresh();
}
// enable sync text
m_textSync->Enable();
@ -1026,6 +1037,7 @@ void MainFrame::togglePTT(void) {
if (page == (wxWindow *)m_panelSpeechIn)
{
m_auiNbookCtrl->ChangeSelection(index);
page->Refresh();
break;
}
}
@ -1099,7 +1111,7 @@ void MainFrame::togglePTT(void) {
// g_tx governs when audio actually goes out during TX, so don't set to true until
// after the delay occurs.
g_tx = true;
g_tx.store(true, std::memory_order_release);
char fmt[16];
m_sliderMicSpkrLevel->SetValue(wxGetApp().appConfiguration.filterConfiguration.micInChannel.volInDB * 10);

View File

@ -43,4 +43,11 @@ extern "C" void ResetMainWindowColorSpace();
// This can be either "windows", "linux", "macos" or "other".
std::string GetOperatingSystemString();
// Tells the operating system that we need the lowest latency available.
// Note: only implemented on macOS.
void StartLowLatencyActivity();
// Tells the operating system that we no longer need low latency.
void StopLowLatencyActivity();
#endif // __OS_INTERFACE__

View File

@ -20,10 +20,13 @@
//
//==========================================================================
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AppKit/AppKit.h>
#include "os_interface.h"
static id<NSObject> Activity = nil;
void VerifyMicrophonePermissions(std::promise<bool>& microphonePromise)
{
#ifndef APPLE_OLD_XCODE
@ -81,7 +84,25 @@ void ResetMainWindowColorSpace()
}];
}
void StartLowLatencyActivity()
{
NSActivityOptions options = NSActivityUserInitiated | NSActivityIdleSystemSleepDisabled | NSActivityLatencyCritical;
Activity = [[NSProcessInfo processInfo] beginActivityWithOptions: options reason:@"FreeDV provides low latency audio processing and should not be inturrupted by system throttling."];
[Activity retain];
}
void StopLowLatencyActivity()
{
if (Activity)
{
[[NSProcessInfo processInfo] endActivity: Activity];
[Activity release];
Activity = nil;
}
}
std::string GetOperatingSystemString()
{
return "macos";
}
}

View File

@ -32,6 +32,16 @@ void ResetMainWindowColorSpace()
// empty
}
void StartLowLatencyActivity()
{
// empty
}
void StopLowLatencyActivity()
{
// empty
}
std::string GetOperatingSystemString()
{
#ifdef __linux__
@ -39,4 +49,4 @@ std::string GetOperatingSystemString()
#else
return "other";
#endif // __linux__
}
}

View File

@ -49,7 +49,17 @@ void ResetMainWindowColorSpace()
// empty
}
void StartLowLatencyActivity()
{
// empty
}
void StopLowLatencyActivity()
{
// empty
}
std::string GetOperatingSystemString()
{
return "windows";
}
}

View File

@ -32,7 +32,7 @@ AudioPipeline::AudioPipeline(int inputSampleRate, int outputSampleRate)
AudioPipeline::~AudioPipeline()
{
// empty, shared_ptr will automatically deallocate.
// empty, unique_ptr will automatically deallocate.
}
int AudioPipeline::getInputSampleRate() const
@ -69,29 +69,32 @@ void AudioPipeline::dumpSetup() const
log_debug("End at SR %d", getOutputSampleRate());
}
std::shared_ptr<short> AudioPipeline::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* AudioPipeline::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
std::shared_ptr<short> tempInput = inputSamples;
std::shared_ptr<short> tempResult = inputSamples;
short* tempInput = inputSamples;
short* tempResult = inputSamples;
int tempInputSamples = numInputSamples;
int tempOutputSamples = tempInputSamples;
for (size_t index = 0; index < pipelineSteps_.size(); index++)
auto numSteps = pipelineSteps_.size();
for (size_t index = 0; index < numSteps; index++)
{
if (resamplers_[index] != nullptr)
auto& resampler = resamplers_[index];
auto& step = pipelineSteps_[index];
if (resampler != nullptr)
{
if (resamplers_[index]->getOutputSampleRate() != pipelineSteps_[index]->getInputSampleRate())
if (resampler->getOutputSampleRate() != step->getInputSampleRate())
{
resamplers_[index] = nullptr;
resampler = nullptr;
reloadResampler_(index);
}
tempResult = resamplers_[index]->execute(tempInput, tempInputSamples, &tempOutputSamples);
tempResult = resampler->execute(tempInput, tempInputSamples, &tempOutputSamples);
tempInput = tempResult;
tempInputSamples = tempOutputSamples;
}
tempResult = pipelineSteps_[index]->execute(tempInput, tempInputSamples, &tempOutputSamples);
tempResult = step->execute(tempInput, tempInputSamples, &tempOutputSamples);
tempInput = tempResult;
tempInputSamples = tempOutputSamples;
}
@ -105,17 +108,17 @@ std::shared_ptr<short> AudioPipeline::execute(std::shared_ptr<short> inputSample
return tempResult;
}
void AudioPipeline::appendPipelineStep(std::shared_ptr<IPipelineStep> pipelineStep)
void AudioPipeline::appendPipelineStep(IPipelineStep* pipelineStep)
{
pipelineSteps_.push_back(pipelineStep);
resamplers_.resize(pipelineSteps_.size(), nullptr); // will be updated by reloadResampler_() below.
pipelineSteps_.push_back(std::unique_ptr<IPipelineStep>(pipelineStep)); // take ownership of pointer
resamplers_.push_back(nullptr); // will be updated by reloadResampler_() below.
reloadResampler_(pipelineSteps_.size() - 1);
reloadResultResampler_();
}
void AudioPipeline::reloadResampler_(int index)
{
std::shared_ptr<ResampleStep> resampleStep = resamplers_[index];
ResampleStep* resampleStep = resamplers_[index].get();
bool createResampler =
resampleStep == nullptr || resampleStep->getOutputSampleRate() == pipelineSteps_[index]->getInputSampleRate();
@ -131,8 +134,7 @@ void AudioPipeline::reloadResampler_(int index)
{
if (pipelineSteps_[0]->getInputSampleRate() != getInputSampleRate())
{
resampleStep = std::shared_ptr<ResampleStep>(
new ResampleStep(getInputSampleRate(), pipelineSteps_[0]->getInputSampleRate()));
resampleStep = new ResampleStep(getInputSampleRate(), pipelineSteps_[0]->getInputSampleRate());
}
else
{
@ -144,8 +146,7 @@ void AudioPipeline::reloadResampler_(int index)
int prevOutputSampleRate = pipelineSteps_[index - 1]->getOutputSampleRate();
if (pipelineSteps_[index]->getInputSampleRate() != prevOutputSampleRate)
{
resampleStep = std::shared_ptr<ResampleStep>(
new ResampleStep(prevOutputSampleRate, pipelineSteps_[index]->getInputSampleRate()));
resampleStep = new ResampleStep(prevOutputSampleRate, pipelineSteps_[index]->getInputSampleRate());
}
else
{
@ -154,7 +155,7 @@ void AudioPipeline::reloadResampler_(int index)
}
}
resamplers_[index] = resampleStep;
resamplers_[index] = std::unique_ptr<ResampleStep>(resampleStep);
}
void AudioPipeline::reloadResultResampler_()
@ -173,8 +174,7 @@ void AudioPipeline::reloadResultResampler_()
getInputSampleRate();
if (lastOutputSampleRate != getOutputSampleRate())
{
resultSampler_ = std::shared_ptr<ResampleStep>(
new ResampleStep(lastOutputSampleRate, getOutputSampleRate()));
resultSampler_ = std::make_unique<ResampleStep>(lastOutputSampleRate, getOutputSampleRate());
}
else
{

View File

@ -35,22 +35,22 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
void appendPipelineStep(std::shared_ptr<IPipelineStep> pipelineStep);
void appendPipelineStep(IPipelineStep* pipelineStep);
void dumpSetup() const;
private:
int inputSampleRate_;
int outputSampleRate_;
std::vector<std::shared_ptr<IPipelineStep>> pipelineSteps_;
std::vector<std::shared_ptr<ResampleStep>> resamplers_;
std::shared_ptr<ResampleStep> resultSampler_;
std::vector<std::unique_ptr<IPipelineStep>> pipelineSteps_;
std::vector<std::unique_ptr<ResampleStep>> resamplers_;
std::unique_ptr<ResampleStep> resultSampler_;
void reloadResampler_(int pipelineStepIndex);
void reloadResultResampler_();
};
#endif // AUDIO_PIPELINE__AUDIO_PIPELINE_H
#endif // AUDIO_PIPELINE__AUDIO_PIPELINE_H

View File

@ -52,9 +52,9 @@ int ComputeRfSpectrumStep::getOutputSampleRate() const
return FS;
}
std::shared_ptr<short> ComputeRfSpectrumStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* ComputeRfSpectrumStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto inputSamplesPtr = inputSamples.get();
auto inputSamplesPtr = inputSamples;
for (int i = 0; i < numInputSamples; i++)
{
rxFdm_[i].real = inputSamplesPtr[i];
@ -72,5 +72,5 @@ std::shared_ptr<short> ComputeRfSpectrumStep::execute(std::shared_ptr<short> inp
// Tap only, no output.
*numOutputSamples = 0;
return std::shared_ptr<short>(nullptr);
return nullptr;
}

View File

@ -41,7 +41,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
std::function<struct MODEM_STATS*()> modemStatsFn_;
@ -50,4 +50,4 @@ private:
COMP* rxFdm_;
};
#endif // AUDIO_PIPELINE__COMPUTE_RF_SPECTRUM_STEP_H
#endif // AUDIO_PIPELINE__COMPUTE_RF_SPECTRUM_STEP_H

View File

@ -23,10 +23,10 @@
#include <assert.h>
#include "EitherOrStep.h"
EitherOrStep::EitherOrStep(std::function<bool()> conditionalFn, std::shared_ptr<IPipelineStep> trueStep, std::shared_ptr<IPipelineStep> falseStep)
EitherOrStep::EitherOrStep(std::function<bool()> conditionalFn, IPipelineStep* trueStep, IPipelineStep* falseStep)
: conditionalFn_(conditionalFn)
, falseStep_(falseStep)
, trueStep_(trueStep)
, falseStep_(std::unique_ptr<IPipelineStep>(falseStep))
, trueStep_(std::unique_ptr<IPipelineStep>(trueStep))
{
// empty
}
@ -48,7 +48,7 @@ int EitherOrStep::getOutputSampleRate() const
return trueStep_->getOutputSampleRate();
}
std::shared_ptr<short> EitherOrStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* EitherOrStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
bool condResult = conditionalFn_();
if (condResult)
@ -65,4 +65,4 @@ void EitherOrStep::reset()
{
trueStep_->reset();
falseStep_->reset();
}
}

View File

@ -30,18 +30,18 @@
class EitherOrStep : public IPipelineStep
{
public:
EitherOrStep(std::function<bool()> conditionalFn, std::shared_ptr<IPipelineStep> trueStep, std::shared_ptr<IPipelineStep> falseStep);
EitherOrStep(std::function<bool()> conditionalFn, IPipelineStep* trueStep, IPipelineStep* falseStep);
virtual ~EitherOrStep();
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
std::function<bool()> conditionalFn_;
std::shared_ptr<IPipelineStep> falseStep_;
std::shared_ptr<IPipelineStep> trueStep_;
std::unique_ptr<IPipelineStep> falseStep_;
std::unique_ptr<IPipelineStep> trueStep_;
};
#endif // AUDIO_PIPELINE__EITHER_OR_STEP_H
#endif // AUDIO_PIPELINE__EITHER_OR_STEP_H

View File

@ -37,9 +37,7 @@ EqualizerStep::EqualizerStep(int sampleRate, bool* enableFilter, std::shared_ptr
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
}
@ -58,18 +56,26 @@ int EqualizerStep::getOutputSampleRate() const
return sampleRate_;
}
std::shared_ptr<short> EqualizerStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* EqualizerStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
memcpy(outputSamples_.get(), inputSamples.get(), sizeof(short)*numInputSamples);
bool copiedToOutput = false;
std::shared_ptr<void> tmpVolFilter = *volFilter_;
if (tmpVolFilter != nullptr)
{
memcpy(outputSamples_.get(), inputSamples, sizeof(short)*numInputSamples);
copiedToOutput = true;
*numOutputSamples = numInputSamples;
sox_biquad_filter(tmpVolFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
if (*enableFilter_)
{
if (!copiedToOutput)
{
memcpy(outputSamples_.get(), inputSamples, sizeof(short)*numInputSamples);
}
std::shared_ptr<void> tmpBassFilter = *bassFilter_;
std::shared_ptr<void> tmpTrebleFilter = *trebleFilter_;
std::shared_ptr<void> tmpMidFilter = *midFilter_;
@ -85,8 +91,11 @@ std::shared_ptr<short> EqualizerStep::execute(std::shared_ptr<short> inputSample
{
sox_biquad_filter(tmpMidFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
}
else if (!copiedToOutput)
{
return inputSamples;
}
*numOutputSamples = numInputSamples;
return outputSamples_;
}
return outputSamples_.get();
}

View File

@ -34,7 +34,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
int sampleRate_;
@ -43,8 +43,8 @@ private:
std::shared_ptr<void>* midFilter_;
std::shared_ptr<void>* trebleFilter_;
std::shared_ptr<void>* volFilter_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__EQUALIZER_STEP_H
#endif // AUDIO_PIPELINE__EQUALIZER_STEP_H

View File

@ -47,9 +47,7 @@ FreeDVReceiveStep::FreeDVReceiveStep(struct freedv* dv)
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
inputBuf_ = new short[freedv_get_n_max_modem_samples(dv_)];
@ -85,13 +83,13 @@ int FreeDVReceiveStep::getOutputSampleRate() const
return freedv_get_speech_sample_rate(dv_);
}
std::shared_ptr<short> FreeDVReceiveStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* FreeDVReceiveStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
auto maxSpeechSamples = freedv_get_n_max_speech_samples(dv_);
*numOutputSamples = 0;
short* inputPtr = inputSamples.get();
short* inputPtr = inputSamples;
while (numInputSamples > 0 && inputPtr != nullptr)
{
codec2_fifo_write(inputSampleFifo_, inputPtr++, 1);
@ -123,8 +121,8 @@ std::shared_ptr<short> FreeDVReceiveStep::execute(std::shared_ptr<short> inputSa
}
}
syncState_ = freedv_get_sync(dv_);
return outputSamples_;
syncState_.store(freedv_get_sync(dv_), std::memory_order_release);
return outputSamples_.get();
}
void FreeDVReceiveStep::reset()

View File

@ -44,12 +44,12 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
void setSigPwrAvg(float newVal) { sigPwrAvg_ = newVal; }
float getSigPwrAvg() const { return sigPwrAvg_; }
int getSync() const { return syncState_.load(); }
int getSync() const { return syncState_.load(std::memory_order_acquire); }
void setChannelNoiseEnable(bool enabled, int snr)
{
channelNoiseEnabled_ = enabled;
@ -67,7 +67,7 @@ private:
int channelNoiseSnr_;
float freqOffsetHz_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
short* inputBuf_;
COMP* rxFdm_;
COMP* rxFdmOffset_;

View File

@ -43,9 +43,7 @@ FreeDVTransmitStep::FreeDVTransmitStep(struct freedv* dv, std::function<float()>
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
codecInput_ = new short[maxSamples];
@ -86,7 +84,7 @@ int FreeDVTransmitStep::getOutputSampleRate() const
return freedv_get_modem_sample_rate(dv_);
}
std::shared_ptr<short> FreeDVTransmitStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* FreeDVTransmitStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
int mode = freedv_get_mode(dv_);
@ -95,7 +93,7 @@ std::shared_ptr<short> FreeDVTransmitStep::execute(std::shared_ptr<short> inputS
*numOutputSamples = 0;
short* inputPtr = inputSamples.get();
short* inputPtr = inputSamples;
while (numInputSamples > 0 && inputPtr)
{
codec2_fifo_write(inputSampleFifo_, inputPtr++, 1);
@ -124,7 +122,7 @@ std::shared_ptr<short> FreeDVTransmitStep::execute(std::shared_ptr<short> inputS
}
}
return outputSamples_;
return outputSamples_.get();
}
void FreeDVTransmitStep::reset()

View File

@ -43,7 +43,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
@ -56,7 +56,7 @@ private:
COMP* txFdmOffset_;
short* codecInput_;
short* tmpOutput_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__FREEDV_TRANSMIT_STEP_H

View File

@ -23,8 +23,6 @@
#ifndef AUDIO_PIPELINE__I_PIPELINE_STEP_H
#define AUDIO_PIPELINE__I_PIPELINE_STEP_H
#include <memory>
class IPipelineStep
{
public:
@ -42,11 +40,11 @@ public:
// numInputSamples: Number of samples in the input array.
// numOutputSamples: Location to store number of output samples.
// Returns: Array of int16 values corresponding to result audio.
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) = 0;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) = 0;
// Resets internal state of the pipeline step.
virtual void reset() { /* empty */ }
};
#endif // AUDIO_PIPELINE__I_PIPELINE_STEP_H
#endif // AUDIO_PIPELINE__I_PIPELINE_STEP_H

View File

@ -24,15 +24,13 @@
#include <assert.h>
LevelAdjustStep::LevelAdjustStep(int sampleRate, std::function<double()> scaleFactorFn)
LevelAdjustStep::LevelAdjustStep(int sampleRate, std::function<float()> scaleFactorFn)
: scaleFactorFn_(scaleFactorFn)
, sampleRate_(sampleRate)
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
}
@ -51,15 +49,16 @@ int LevelAdjustStep::getOutputSampleRate() const
return sampleRate_;
}
std::shared_ptr<short> LevelAdjustStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* LevelAdjustStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
double scaleFactor = scaleFactorFn_();
float scaleFactor = scaleFactorFn_();
short* outPtr = outputSamples_.get();
for (int index = 0; index < numInputSamples; index++)
{
outputSamples_.get()[index] = inputSamples.get()[index] * scaleFactor;
outPtr[index] = inputSamples[index] * scaleFactor;
}
*numOutputSamples = numInputSamples;
return outputSamples_;
return outPtr;
}

View File

@ -24,23 +24,24 @@
#define AUDIO_PIPELINE__LEVEL_ADJUST_STEP_H
#include <functional>
#include <memory>
#include "IPipelineStep.h"
class LevelAdjustStep : public IPipelineStep
{
public:
LevelAdjustStep(int sampleRate, std::function<double()> scaleFactorFn);
LevelAdjustStep(int sampleRate, std::function<float()> scaleFactorFn);
virtual ~LevelAdjustStep();
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
std::function<double()> scaleFactorFn_;
std::function<float()> scaleFactorFn_;
int sampleRate_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__LEVEL_ADJUST_STEP_H
#endif // AUDIO_PIPELINE__LEVEL_ADJUST_STEP_H

View File

@ -26,42 +26,30 @@
LinkStep::LinkStep(int outputSampleRate, size_t numSamples)
: sampleRate_(outputSampleRate)
, fifo_(numSamples)
{
fifo_ = codec2_fifo_create(numSamples);
assert(fifo_ != nullptr);
// Create pipeline steps
inputPipelineStep_ = std::make_shared<InputStep>(this);
outputPipelineStep_ = std::make_shared<OutputStep>(this);
tmpBuffer_ = new short[numSamples];
assert(tmpBuffer_ != nullptr);
}
LinkStep::~LinkStep()
{
codec2_fifo_destroy(fifo_);
fifo_ = nullptr;
delete[] tmpBuffer_;
}
void LinkStep::clearFifo()
{
// Read data and then promptly throw it out.
while (codec2_fifo_used(fifo_) > 0)
{
codec2_fifo_read(fifo_, tmpBuffer_, 1);
}
fifo_.reset();
}
std::shared_ptr<short> LinkStep::InputStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* LinkStep::InputStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto fifo = parent_->getFifo();
auto samplePtr = inputSamples.get();
auto& fifo = parent_->getFifo();
auto samplePtr = inputSamples;
if (numInputSamples > 0 && samplePtr != nullptr)
{
codec2_fifo_write(fifo, samplePtr, numInputSamples);
fifo.write(samplePtr, numInputSamples);
}
// Since we short circuited to the output step, don't return any samples here.
@ -69,15 +57,15 @@ std::shared_ptr<short> LinkStep::InputStep::execute(std::shared_ptr<short> input
return nullptr;
}
std::shared_ptr<short> LinkStep::OutputStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* LinkStep::OutputStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto fifo = parent_->getFifo();
*numOutputSamples = std::min(codec2_fifo_used(fifo), numInputSamples);
auto& fifo = parent_->getFifo();
*numOutputSamples = numInputSamples > 0 ? std::min(fifo.numUsed(), numInputSamples) : fifo.numUsed();
if (*numOutputSamples > 0)
{
codec2_fifo_read(fifo, outputSamples_.get(), *numOutputSamples);
return outputSamples_;
fifo.read(outputSamples_.get(), *numOutputSamples);
return outputSamples_.get();
}
else
{

View File

@ -23,8 +23,10 @@
#ifndef AUDIO_PIPELINE__LINK_STEP_H
#define AUDIO_PIPELINE__LINK_STEP_H
#include <memory>
#include "IPipelineStep.h"
#include "codec2_fifo.h"
#include "../util/GenericFIFO.h"
class LinkStep
{
@ -33,18 +35,18 @@ public:
virtual ~LinkStep();
// Get the constituent pipeline steps.
std::shared_ptr<IPipelineStep> getInputPipelineStep()
IPipelineStep* getInputPipelineStep()
{
return inputPipelineStep_;
return new InputStep(this);
}
std::shared_ptr<IPipelineStep> getOutputPipelineStep()
IPipelineStep* getOutputPipelineStep()
{
return outputPipelineStep_;
return new OutputStep(this);
}
int getSampleRate() const { return sampleRate_; }
FIFO* getFifo() { return fifo_; }
GenericFIFO<short>& getFifo() { return fifo_; }
void clearFifo();
@ -72,7 +74,7 @@ private:
// numInputSamples: Number of samples in the input array.
// numOutputSamples: Location to store number of output samples.
// Returns: Array of int16 values corresponding to result audio.
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
LinkStep* parent_;
@ -86,9 +88,7 @@ private:
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
}
@ -106,19 +106,17 @@ private:
// numInputSamples: Number of samples in the input array.
// numOutputSamples: Location to store number of output samples.
// Returns: Array of int16 values corresponding to result audio.
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
LinkStep* parent_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
int sampleRate_;
std::shared_ptr<IPipelineStep> inputPipelineStep_;
std::shared_ptr<IPipelineStep> outputPipelineStep_;
FIFO* fifo_;
GenericFIFO<short> fifo_;
short* tmpBuffer_;
};
#endif // AUDIO_PIPELINE__LINK_STEP_H
#endif // AUDIO_PIPELINE__LINK_STEP_H

View File

@ -30,9 +30,7 @@ MuteStep::MuteStep(int outputSampleRate)
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
memset(outputSamples_.get(), 0, sizeof(short) * maxSamples);
@ -44,13 +42,13 @@ MuteStep::MuteStep(int outputSampleRate)
// numInputSamples: Number of samples in the input array.
// numOutputSamples: Location to store number of output samples.
// Returns: Array of int16 values corresponding to result audio.
std::shared_ptr<short> MuteStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* MuteStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
*numOutputSamples = numInputSamples;
if (*numOutputSamples > 0)
{
return outputSamples_;
return outputSamples_.get();
}
else
{

View File

@ -23,6 +23,7 @@
#ifndef AUDIO_PIPELINE__MUTE_STEP_H
#define AUDIO_PIPELINE__MUTE_STEP_H
#include <memory>
#include "IPipelineStep.h"
class MuteStep : public IPipelineStep
@ -43,11 +44,11 @@ public:
// numInputSamples: Number of samples in the input array.
// numOutputSamples: Location to store number of output samples.
// Returns: Array of int16 values corresponding to result audio.
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
int sampleRate_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__MUTE_STEP_H
#endif // AUDIO_PIPELINE__MUTE_STEP_H

View File

@ -50,27 +50,22 @@ ParallelStep::ParallelStep(
{
for (auto& step : parallelSteps)
{
auto sharedStep = std::shared_ptr<IPipelineStep>(step);
parallelSteps_.push_back(sharedStep);
auto pipeline = std::make_shared<AudioPipeline>(inputSampleRate, outputSampleRate);
pipeline->appendPipelineStep(sharedStep);
parallelSteps_.push_back(step);
auto pipeline = new AudioPipeline(inputSampleRate, outputSampleRate);
pipeline->appendPipelineStep(step);
auto threadState = new ThreadInfo();
assert(threadState != nullptr);
threads_.push_back(threadState);
threadState->step = pipeline;
threadState->step = std::unique_ptr<IPipelineStep>(pipeline);
threadState->inputFifo = codec2_fifo_create(inputSampleRate);
assert(threadState->inputFifo != nullptr);
threadState->outputFifo = codec2_fifo_create(outputSampleRate);
assert(threadState->outputFifo != nullptr);
threadState->tempOutput = std::shared_ptr<short>(
new short[outputSampleRate],
std::default_delete<short[]>());
threadState->tempInput = std::shared_ptr<short>(
new short[inputSampleRate],
std::default_delete<short[]>());
threadState->tempOutput = std::make_unique<short[]>(outputSampleRate);
threadState->tempInput = std::make_unique<short[]>(inputSampleRate);
threadState->exitingThread = false;
if (runMultiThreaded)
@ -210,7 +205,7 @@ int ParallelStep::getOutputSampleRate() const
return outputSampleRate_;
}
std::shared_ptr<short> ParallelStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* ParallelStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
// Step 1: determine what steps to execute.
auto stepToExecute = inputRouteFn_(this);
@ -223,7 +218,7 @@ std::shared_ptr<short> ParallelStep::execute(std::shared_ptr<short> inputSamples
if (index == (size_t)stepToExecute || stepToExecute == -1)
{
codec2_fifo_write(threadInfo->inputFifo, inputSamples.get(), numInputSamples);
codec2_fifo_write(threadInfo->inputFifo, inputSamples, numInputSamples);
if (!runMultiThreaded_)
{
executeRunnerThread_(threadInfo);
@ -265,7 +260,7 @@ std::shared_ptr<short> ParallelStep::execute(std::shared_ptr<short> inputSamples
memset(outputTask->tempOutput.get(), 0, sizeof(short) * outputSampleRate_);
}
codec2_fifo_read(outputTask->outputFifo, outputTask->tempOutput.get(), *numOutputSamples);
return outputTask->tempOutput;
return outputTask->tempOutput.get();
}
void ParallelStep::executeRunnerThread_(ThreadInfo* threadState) noexcept
@ -284,10 +279,10 @@ void ParallelStep::executeRunnerThread_(ThreadInfo* threadState) noexcept
samplesIn = 0;
}
auto output = threadState->step->execute(threadState->tempInput, samplesIn, &samplesOut);
auto output = threadState->step->execute(threadState->tempInput.get(), samplesIn, &samplesOut);
if (samplesOut > 0)
{
codec2_fifo_write(threadState->outputFifo, output.get(), samplesOut);
codec2_fifo_write(threadState->outputFifo, output, samplesOut);
}
samplesIn = std::min((inputSampleRate_ * FRAME_DURATION_MS) / MS_TO_SEC, codec2_fifo_used(threadState->inputFifo));
} while (samplesIn > 0 && !threadState->exitingThread);

View File

@ -58,10 +58,10 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
const std::vector<std::shared_ptr<IPipelineStep>>& getParallelSteps() const { return parallelSteps_; }
const std::vector<IPipelineStep*>& getParallelSteps() const { return parallelSteps_; }
std::shared_ptr<void> getState() { return state_; }
@ -70,11 +70,11 @@ private:
{
std::thread thread;
bool exitingThread;
std::shared_ptr<IPipelineStep> step;
std::unique_ptr<IPipelineStep> step;
FIFO* inputFifo;
FIFO* outputFifo;
std::shared_ptr<short> tempInput;
std::shared_ptr<short> tempOutput;
std::unique_ptr<short[]> tempInput;
std::unique_ptr<short[]> tempOutput;
#if defined(_WIN32)
HANDLE sem;
@ -93,7 +93,7 @@ private:
std::vector<ThreadInfo*> threads_;
std::shared_ptr<IRealtimeHelper> realtimeHelper_;
std::shared_ptr<void> state_;
std::vector<std::shared_ptr<IPipelineStep>> parallelSteps_;
std::vector<IPipelineStep*> parallelSteps_;
void executeRunnerThread_(ThreadInfo* threadState) noexcept
#if defined(__clang__)

View File

@ -28,10 +28,16 @@
#include "../util/logging/ulog.h"
#if defined(__APPLE__)
#include <pthread.h>
#endif // defined(__APPLE__)
extern wxMutex g_mutexProtectingCallbackData;
using namespace std::chrono_literals;
#define NUM_SECONDS_TO_READ 1
PlaybackStep::PlaybackStep(
int inputSampleRate, std::function<int()> fileSampleRateFn,
std::function<SNDFILE*()> getSndFileFn, std::function<void()> fileCompleteFn)
@ -41,18 +47,13 @@ PlaybackStep::PlaybackStep(
, fileCompleteFn_(fileCompleteFn)
, nonRtThreadEnding_(false)
, playbackResampler_(nullptr)
, outputFifo_(inputSampleRate * NUM_SECONDS_TO_READ)
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
// Create output FIFO
outputFifo_ = codec2_fifo_create(maxSamples);
assert(outputFifo_ != nullptr);
// Create non-RT thread to perform audio I/O
nonRtThread_ = std::thread(std::bind(&PlaybackStep::nonRtThreadEntry_, this));
}
@ -60,12 +61,11 @@ PlaybackStep::PlaybackStep(
PlaybackStep::~PlaybackStep()
{
nonRtThreadEnding_ = true;
fileIoThreadSem_.signal();
if (nonRtThread_.joinable())
{
nonRtThread_.join();
}
codec2_fifo_destroy(outputFifo_);
}
int PlaybackStep::getInputSampleRate() const
@ -78,22 +78,29 @@ int PlaybackStep::getOutputSampleRate() const
return inputSampleRate_;
}
std::shared_ptr<short> PlaybackStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* PlaybackStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
unsigned int nsf = numInputSamples * getOutputSampleRate()/getInputSampleRate();
*numOutputSamples = std::min((unsigned int)codec2_fifo_used(outputFifo_), nsf);
*numOutputSamples = std::min((unsigned int)outputFifo_.numUsed(), nsf);
if (*numOutputSamples > 0)
{
codec2_fifo_read(outputFifo_, outputSamples_.get(), *numOutputSamples);
outputFifo_.read(outputSamples_.get(), *numOutputSamples);
}
fileIoThreadSem_.signal();
return outputSamples_;
return outputSamples_.get();
}
void PlaybackStep::nonRtThreadEntry_()
{
std::shared_ptr<short> buf = nullptr;
std::unique_ptr<short[]> buf = nullptr;
#if defined(__APPLE__)
// Downgrade thread QoS to Utility to avoid thread contention issues.
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
#endif // defined(__APPLE__)
while (!nonRtThreadEnding_)
{
@ -118,13 +125,11 @@ void PlaybackStep::nonRtThreadEntry_()
if (buf == nullptr)
{
buf = std::shared_ptr<short>(
new short[fileSampleRate],
std::default_delete<short[]>());
buf = std::make_unique<short[]>(fileSampleRate * NUM_SECONDS_TO_READ);
assert(buf != nullptr);
}
unsigned int nsf = codec2_fifo_free(outputFifo_);
unsigned int nsf = outputFifo_.numFree();
//log_info("nsf = %d", (int)nsf);
if (nsf > 0)
{
@ -137,9 +142,9 @@ void PlaybackStep::nonRtThreadEntry_()
if (playbackResampler_ != nullptr)
{
int outSamples = 0;
auto outBuf = playbackResampler_->execute(buf, numRead, &outSamples);
auto outBuf = playbackResampler_->execute(buf.get(), numRead, &outSamples);
//log_info("Resampled %u samples and created %d samples", numRead, outSamples);
if (codec2_fifo_write(outputFifo_, outBuf.get(), outSamples) != 0)
if (outputFifo_.write(outBuf, outSamples) != 0)
{
log_warn("Could not write %d samples to buffer, dropping", outSamples);
}
@ -147,24 +152,29 @@ void PlaybackStep::nonRtThreadEntry_()
else
{
//log_info("Writing %u samples to FIFO", numRead);
if (codec2_fifo_write(outputFifo_, buf.get(), numRead) != 0)
if (outputFifo_.write(buf.get(), numRead) != 0)
{
log_warn("Could not write %d samples to buffer, dropping", numRead);
}
}
}
if ((int)numRead < samplesAtSourceRate && codec2_fifo_used(outputFifo_) == 0)
if ((int)numRead == 0 && outputFifo_.numUsed() == 0)
{
//log_info("file read complete");
buf = nullptr;
// Unlock prior to calling completion function just in case
// something in here causes the lock to be taken.
g_mutexProtectingCallbackData.Unlock();
fileCompleteFn_();
g_mutexProtectingCallbackData.Lock();
}
}
}
g_mutexProtectingCallbackData.Unlock();
std::this_thread::sleep_for(100ms);
fileIoThreadSem_.wait();
}
if (playbackResampler_ != nullptr)
@ -175,9 +185,5 @@ void PlaybackStep::nonRtThreadEntry_()
void PlaybackStep::reset()
{
short buf;
while (codec2_fifo_used(outputFifo_) > 0)
{
codec2_fifo_read(outputFifo_, &buf, 1);
}
outputFifo_.reset();
}

View File

@ -29,7 +29,8 @@
#include <functional>
#include <thread>
#include <sndfile.h>
#include "codec2_fifo.h"
#include "../util/GenericFIFO.h"
#include "../util/Semaphore.h"
class PlaybackStep : public IPipelineStep
{
@ -41,7 +42,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
@ -49,13 +50,13 @@ private:
std::function<int()> fileSampleRateFn_;
std::function<SNDFILE*()> getSndFileFn_;
std::function<void()> fileCompleteFn_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
std::thread nonRtThread_;
bool nonRtThreadEnding_;
FIFO* outputFifo_;
Semaphore fileIoThreadSem_;
ResampleStep* playbackResampler_;
GenericFIFO<short> outputFifo_;
void nonRtThreadEntry_();
};

View File

@ -27,70 +27,48 @@
#endif // defined(__clang__)
#include <cassert>
#include <functional>
#include "RADEReceiveStep.h"
#include "../defines.h"
#include "lpcnet.h" // from Opus source tree
#if defined(__APPLE__)
#include <pthread.h>
#include <sys/resource.h>
#endif // defined(__APPLE__)
using namespace std::chrono_literals;
extern wxString utRxFeatureFile;
#define FEATURE_FIFO_SIZE (4096)
#define FEATURE_FIFO_SIZE ((RADE_SPEECH_SAMPLE_RATE / LPCNET_FRAME_SIZE) * rade_n_features_in_out(dv_))
RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan, rade_text_t textPtr, std::function<void(RADEReceiveStep*)> syncFn)
: dv_(dv)
, fargan_(fargan)
, inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr)
, pendingFeatures_(nullptr)
, pendingFeaturesIdx_(0)
, featuresFile_(nullptr)
, textPtr_(textPtr)
, syncFn_(syncFn)
, utFeatures_(FEATURE_FIFO_SIZE)
, exitingFeatureThread_(false)
{
assert(syncState_.is_lock_free());
// Set FIFO to be 2x the number of samples per run so we don't lose anything.
inputSampleFifo_ = codec2_fifo_create(rade_nin_max(dv_) * 2);
assert(inputSampleFifo_ != nullptr);
// Enough for one second of audio. Probably way overkill.
outputSampleFifo_ = codec2_fifo_create(16000);
assert(outputSampleFifo_ != nullptr);
if (utRxFeatureFile != "")
{
utFeatures_ = new PreAllocatedFIFO<float, NUM_FEATURES_TO_STORE>();
assert(utFeatures_ != nullptr);
featuresFile_ = fopen((const char*)utRxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
utFeatureThread_ = std::thread([&]() {
float* fifoRead = new float[FEATURE_FIFO_SIZE];
assert(fifoRead != nullptr);
while (!exitingFeatureThread_)
{
auto numToRead = std::min(utFeatures_.numUsed(), FEATURE_FIFO_SIZE);
while (numToRead > 0)
{
utFeatures_.read(fifoRead, numToRead);
fwrite(fifoRead, sizeof(float), numToRead, featuresFile_);
numToRead = std::min(utFeatures_.numUsed(), FEATURE_FIFO_SIZE);
}
std::this_thread::sleep_for(10ms);
}
delete[] fifoRead;
});
utFeatureThread_ = std::thread(std::bind(&RADEReceiveStep::utFeatureThreadEntry_, this));
}
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
inputBufCplx_ = new RADE_COMP[rade_nin_max(dv_)];
@ -121,18 +99,11 @@ RADEReceiveStep::~RADEReceiveStep()
if (featuresFile_ != nullptr)
{
exitingFeatureThread_ = true;
featuresAvailableSem_.signal();
utFeatureThread_.join();
fclose(featuresFile_);
}
if (inputSampleFifo_ != nullptr)
{
codec2_fifo_destroy(inputSampleFifo_);
}
if (outputSampleFifo_ != nullptr)
{
codec2_fifo_destroy(outputSampleFifo_);
delete utFeatures_;
}
}
@ -146,40 +117,53 @@ int RADEReceiveStep::getOutputSampleRate() const
return RADE_SPEECH_SAMPLE_RATE;
}
std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* RADEReceiveStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
*numOutputSamples = 0;
short* inputPtr = inputSamples.get();
while (numInputSamples > 0 && inputPtr != nullptr)
{
codec2_fifo_write(inputSampleFifo_, inputPtr++, 1);
numInputSamples--;
inputSampleFifo_.write(inputSamples, numInputSamples);
int nin = rade_nin(dv_);
int nout = 0;
while ((*numOutputSamples + LPCNET_FRAME_SIZE) < maxSamples && codec2_fifo_read(inputSampleFifo_, inputBuf_, nin) == 0)
int nin = rade_nin(dv_);
int nout = 0;
while ((*numOutputSamples + LPCNET_FRAME_SIZE) < maxSamples && inputSampleFifo_.read(inputBuf_, nin) == 0)
{
assert(nin <= rade_nin_max(dv_));
// demod per frame processing
for(int i=0; i<nin; i++)
{
assert(nin <= rade_nin_max(dv_));
inputBufCplx_[i].real = inputBuf_[i] / 32767.0;
inputBufCplx_[i].imag = 0.0;
}
// demod per frame processing
for(int i=0; i<nin; i++)
{
inputBufCplx_[i].real = inputBuf_[i] / 32767.0;
inputBufCplx_[i].imag = 0.0;
}
// RADE processing (input signal->features).
int hasEooOut = 0;
// RADE processing (input signal->features).
int hasEooOut = 0;
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_disable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
nout = rade_rx(dv_, featuresOut_, &hasEooOut, eooOut_, inputBufCplx_);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_enable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
if (hasEooOut && textPtr_ != nullptr)
{
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_disable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
nout = rade_rx(dv_, featuresOut_, &hasEooOut, eooOut_, inputBufCplx_);
// Handle RX of bits from EOO.
rade_text_rx(textPtr_, eooOut_, rade_n_eoo_bits(dv_) / 2);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
@ -187,60 +171,46 @@ std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamp
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
if (hasEooOut && textPtr_ != nullptr)
{
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_disable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
// Handle RX of bits from EOO.
rade_text_rx(textPtr_, eooOut_, rade_n_eoo_bits(dv_) / 2);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_enable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
}
else if (!hasEooOut)
{
if (featuresFile_)
{
utFeatures_.write(featuresOut_, nout);
}
for (int i = 0; i < nout; i++)
{
pendingFeatures_[pendingFeaturesIdx_++] = featuresOut_[i];
if (pendingFeaturesIdx_ == NB_TOTAL_FEATURES)
{
pendingFeaturesIdx_ = 0;
// 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);
}
}
}
nin = rade_nin(dv_);
}
else if (!hasEooOut)
{
if (featuresFile_)
{
utFeatures_->write(featuresOut_, nout);
if (utFeatures_->numUsed() > (0.75 * utFeatures_->capacity()))
{
featuresAvailableSem_.signal();
}
}
for (int i = 0; i < nout; i++)
{
pendingFeatures_[pendingFeaturesIdx_++] = featuresOut_[i];
if (pendingFeaturesIdx_ == NB_TOTAL_FEATURES)
{
pendingFeaturesIdx_ = 0;
// 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;
outputSampleFifo_.write(pcm, LPCNET_FRAME_SIZE);
}
}
}
nin = rade_nin(dv_);
}
if (*numOutputSamples > 0)
{
codec2_fifo_read(outputSampleFifo_, outputSamples_.get(), *numOutputSamples);
outputSampleFifo_.read(outputSamples_.get(), *numOutputSamples);
}
#if defined(__clang__)
@ -249,7 +219,8 @@ std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamp
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
syncState_ = rade_sync(dv_);
syncState_.store(rade_sync(dv_), std::memory_order_release);
snr_.store(rade_snrdB_3k_est(dv_), std::memory_order_release);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
@ -259,19 +230,49 @@ std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamp
syncFn_(this);
return outputSamples_;
return outputSamples_.get();
}
void RADEReceiveStep::reset()
{
short buf;
while (codec2_fifo_used(inputSampleFifo_) > 0)
{
codec2_fifo_read(inputSampleFifo_, &buf, 1);
}
while (codec2_fifo_used(outputSampleFifo_) > 0)
{
codec2_fifo_read(outputSampleFifo_, &buf, 1);
}
inputSampleFifo_.reset();
outputSampleFifo_.reset();
pendingFeaturesIdx_ = 0;
}
void RADEReceiveStep::utFeatureThreadEntry_()
{
#if defined(__APPLE__)
// Downgrade thread QoS to Utility to avoid thread contention issues.
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
// Make sure other I/O can throttle us.
setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE);
#endif // defined(__APPLE__)
float* featureBuf = new float[utFeatures_->capacity()];
assert(featureBuf != nullptr);
while (!exitingFeatureThread_)
{
auto numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
while (numToRead > 0)
{
utFeatures_->read(featureBuf, numToRead);
fwrite(featureBuf, sizeof(float) * numToRead, 1, featuresFile_);
numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
}
featuresAvailableSem_.wait();
}
auto numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
while (numToRead > 0)
{
utFeatures_->read(featureBuf, numToRead);
fwrite(featureBuf, sizeof(float) * numToRead, 1, featuresFile_);
numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
}
delete[] featureBuf;
}

View File

@ -30,9 +30,14 @@
#include "IPipelineStep.h"
#include "../freedv_interface.h"
#include "rade_api.h"
#include "codec2_fifo.h"
#include "rade_text.h"
#include "../util/GenericFIFO.h"
#include "../util/Semaphore.h"
// Number of features to store. This is set to be close to the
// typical size for RX/TX features for the rade_loss ctest to
// avoid contention with normal RADE operation.
#define NUM_FEATURES_TO_STORE (256 * 1024)
// TBD - need to wrap in "extern C" to avoid linker errors
extern "C"
@ -48,17 +53,19 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
int getSync() const { return syncState_.load(); }
int getSync() const { return syncState_.load(std::memory_order_acquire); }
int getSnr() const { return snr_.load(std::memory_order_acquire); }
private:
std::atomic<int> syncState_;
std::atomic<int> snr_;
struct rade* dv_;
FARGANState* fargan_;
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
PreAllocatedFIFO<short, RADE_MODEM_SAMPLE_RATE> inputSampleFifo_;
PreAllocatedFIFO<short, RADE_SPEECH_SAMPLE_RATE> outputSampleFifo_;
float* pendingFeatures_;
int pendingFeaturesIdx_;
FILE* featuresFile_;
@ -69,11 +76,14 @@ private:
short* inputBuf_;
float* featuresOut_;
float* eooOut_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
GenericFIFO<float> utFeatures_;
PreAllocatedFIFO<float, NUM_FEATURES_TO_STORE>* utFeatures_;
std::thread utFeatureThread_;
bool exitingFeatureThread_;
Semaphore featuresAvailableSem_;
void utFeatureThreadEntry_();
};
#endif // AUDIO_PIPELINE__RADE_RECEIVE_STEP_H

View File

@ -29,13 +29,19 @@
#include <cstring>
#include <cassert>
#include <cmath>
#include <functional>
#include "../defines.h"
#include "codec2_fifo.h"
#include "RADETransmitStep.h"
#if defined(__APPLE__)
#include <pthread.h>
#include <sys/resource.h>
#endif // defined(__APPLE__)
using namespace std::chrono_literals;
#define FEATURE_FIFO_SIZE (4096)
#define FEATURE_FIFO_SIZE ((RADE_SPEECH_SAMPLE_RATE / LPCNET_FRAME_SIZE) * rade_n_features_in_out(dv_))
const int RADE_SCALING_FACTOR = 16383;
@ -44,50 +50,25 @@ extern wxString utTxFeatureFile;
RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState)
: dv_(dv)
, encState_(encState)
, inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr)
, featureList_(nullptr)
, featureListIdx_(0)
, featuresFile_(nullptr)
, utFeatures_(FEATURE_FIFO_SIZE)
, exitingFeatureThread_(false)
{
inputSampleFifo_ = codec2_fifo_create(RADE_SPEECH_SAMPLE_RATE);
assert(inputSampleFifo_ != nullptr);
outputSampleFifo_ = codec2_fifo_create(RADE_MODEM_SAMPLE_RATE);
assert(outputSampleFifo_ != nullptr);
if (utTxFeatureFile != "")
{
utFeatures_ = new PreAllocatedFIFO<float, NUM_FEATURES_TO_STORE>;
assert(utFeatures_ != nullptr);
featuresFile_ = fopen((const char*)utTxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
utFeatureThread_ = std::thread([&]() {
float* fifoRead = new float[FEATURE_FIFO_SIZE];
assert(fifoRead != nullptr);
while (!exitingFeatureThread_)
{
auto numToRead = std::min(utFeatures_.numUsed(), FEATURE_FIFO_SIZE);
while (numToRead > 0)
{
utFeatures_.read(fifoRead, numToRead);
fwrite(fifoRead, sizeof(float), numToRead, featuresFile_);
numToRead = std::min(utFeatures_.numUsed(), FEATURE_FIFO_SIZE);
}
std::this_thread::sleep_for(10ms);
}
delete[] fifoRead;
});
utFeatureThread_ = std::thread(std::bind(&RADETransmitStep::utFeatureThreadEntry_, this));
}
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
int numOutputSamples = rade_n_tx_out(dv_);
@ -125,18 +106,11 @@ RADETransmitStep::~RADETransmitStep()
if (featuresFile_ != nullptr)
{
exitingFeatureThread_ = true;
featuresAvailableSem_.signal();
utFeatureThread_.join();
fclose(featuresFile_);
}
if (inputSampleFifo_ != nullptr)
{
codec2_fifo_destroy(inputSampleFifo_);
}
if (outputSampleFifo_ != nullptr)
{
codec2_fifo_destroy(outputSampleFifo_);
delete utFeatures_;
}
}
@ -150,7 +124,7 @@ int RADETransmitStep::getOutputSampleRate() const
return RADE_MODEM_SAMPLE_RATE;
}
std::shared_ptr<short> RADETransmitStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* RADETransmitStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
int numSamplesPerTx = rade_n_tx_out(dv_);
@ -160,77 +134,75 @@ std::shared_ptr<short> RADETransmitStep::execute(std::shared_ptr<short> inputSam
if (numInputSamples == 0)
{
// Special case logic for EOO
*numOutputSamples = std::min(codec2_fifo_used(outputSampleFifo_), (FRAME_DURATION_MS * getOutputSampleRate()) / MS_TO_SEC);
*numOutputSamples = std::min(outputSampleFifo_.numUsed(), (FRAME_DURATION_MS * getOutputSampleRate()) / MS_TO_SEC);
if (*numOutputSamples > 0)
{
codec2_fifo_read(outputSampleFifo_, outputSamples_.get(), *numOutputSamples);
outputSampleFifo_.read(outputSamples_.get(), *numOutputSamples);
}
return outputSamples_;
return outputSamples_.get();
}
short* inputPtr = inputSamples.get();
while (numInputSamples > 0 && inputPtr != nullptr)
inputSampleFifo_.write(inputSamples, numInputSamples);
while ((*numOutputSamples + numSamplesPerTx) < maxSamples && inputSampleFifo_.numUsed() >= LPCNET_FRAME_SIZE)
{
codec2_fifo_write(inputSampleFifo_, inputPtr++, 1);
numInputSamples--;
while ((*numOutputSamples + numSamplesPerTx) < maxSamples && codec2_fifo_used(inputSampleFifo_) >= LPCNET_FRAME_SIZE)
int numRequiredFeaturesForRADE = rade_n_features_in_out(dv_);
short pcm[LPCNET_FRAME_SIZE];
float features[NB_TOTAL_FEATURES];
// Feature extraction
inputSampleFifo_.read(pcm, LPCNET_FRAME_SIZE);
lpcnet_compute_single_frame_features(encState_, pcm, features, arch_);
if (featuresFile_)
{
int numRequiredFeaturesForRADE = rade_n_features_in_out(dv_);
short pcm[LPCNET_FRAME_SIZE];
float features[NB_TOTAL_FEATURES];
// Feature extraction
codec2_fifo_read(inputSampleFifo_, pcm, LPCNET_FRAME_SIZE);
lpcnet_compute_single_frame_features(encState_, pcm, features, arch_);
if (featuresFile_)
utFeatures_->write(features, NB_TOTAL_FEATURES);
if (utFeatures_->numUsed() > (0.75 * utFeatures_->capacity()))
{
utFeatures_.write(features, NB_TOTAL_FEATURES);
featuresAvailableSem_.signal();
}
for (int index = 0; index < NB_TOTAL_FEATURES; index++)
{
featureList_[featureListIdx_++] = features[index];
if (featureListIdx_ == numRequiredFeaturesForRADE)
{
featureListIdx_ = 0;
// RADE TX handling
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_disable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
rade_tx(dv_, radeOut_, &featureList_[0]);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_enable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
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);
}
}
*numOutputSamples = codec2_fifo_used(outputSampleFifo_);
}
for (int index = 0; index < NB_TOTAL_FEATURES; index++)
{
featureList_[featureListIdx_++] = features[index];
if (featureListIdx_ == numRequiredFeaturesForRADE)
{
featureListIdx_ = 0;
// RADE TX handling
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_disable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
rade_tx(dv_, radeOut_, &featureList_[0]);
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
__rtsan_enable();
#endif // defined(__has_feature) && __has_feature(realtime_sanitizer)
#endif // defined(__clang__)
for (int index = 0; index < numSamplesPerTx; index++)
{
// We only need the real component for TX.
radeOutShort_[index] = radeOut_[index].real * RADE_SCALING_FACTOR;
}
outputSampleFifo_.write(radeOutShort_, numSamplesPerTx);
}
}
*numOutputSamples = outputSampleFifo_.numUsed();
}
if (*numOutputSamples > 0)
{
codec2_fifo_read(outputSampleFifo_, outputSamples_.get(), *numOutputSamples);
outputSampleFifo_.read(outputSamples_.get(), *numOutputSamples);
}
return outputSamples_;
return outputSamples_.get();
}
void RADETransmitStep::restartVocoder()
@ -259,22 +231,52 @@ void RADETransmitStep::restartVocoder()
eooOutShort_[index] = eooOut_[index].real * RADE_SCALING_FACTOR;
}
if (codec2_fifo_write(outputSampleFifo_, eooOutShort_, numEOOSamples + NUM_SAMPLES_SILENCE) != 0)
if (outputSampleFifo_.write(eooOutShort_, numEOOSamples + NUM_SAMPLES_SILENCE) != 0)
{
log_warn("Could not queue EOO samples (remaining space in FIFO = %d)", codec2_fifo_free(outputSampleFifo_));
log_warn("Could not queue EOO samples (remaining space in FIFO = %d)", outputSampleFifo_.numFree());
}
}
void RADETransmitStep::reset()
{
short buf;
while (codec2_fifo_used(inputSampleFifo_) > 0)
{
codec2_fifo_read(inputSampleFifo_, &buf, 1);
}
while (codec2_fifo_used(outputSampleFifo_) > 0)
{
codec2_fifo_read(outputSampleFifo_, &buf, 1);
}
inputSampleFifo_.reset();
outputSampleFifo_.reset();
featureListIdx_ = 0;
}
void RADETransmitStep::utFeatureThreadEntry_()
{
#if defined(__APPLE__)
// Downgrade thread QoS to Utility to avoid thread contention issues.
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
// Make sure other I/O can throttle us.
setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE);
#endif // defined(__APPLE__)
float* featureBuf = new float[utFeatures_->capacity()];
assert(featureBuf != nullptr);
while (!exitingFeatureThread_)
{
auto numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
while (numToRead > 0)
{
utFeatures_->read(featureBuf, numToRead);
fwrite(featureBuf, sizeof(float) * numToRead, 1, featuresFile_);
numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
}
featuresAvailableSem_.wait();
}
auto numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
while (numToRead > 0)
{
utFeatures_->read(featureBuf, numToRead);
fwrite(featureBuf, sizeof(float) * numToRead, 1, featuresFile_);
numToRead = std::min(utFeatures_->numUsed(), utFeatures_->capacity());
}
delete[] featureBuf;
}

View File

@ -31,6 +31,12 @@
#include "rade_api.h"
#include "lpcnet.h"
#include "../util/GenericFIFO.h"
#include "../util/Semaphore.h"
// Number of features to store. This is set to be close to the
// typical size for RX/TX features for the rade_loss ctest to
// avoid contention with normal RADE operation.
#define NUM_FEATURES_TO_STORE (256 * 1024)
class RADETransmitStep : public IPipelineStep
{
@ -40,7 +46,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
// For triggering EOO
@ -49,23 +55,26 @@ public:
private:
struct rade* dv_;
LPCNetEncState* encState_;
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
PreAllocatedFIFO<short, RADE_SPEECH_SAMPLE_RATE> inputSampleFifo_;
PreAllocatedFIFO<short, RADE_MODEM_SAMPLE_RATE> outputSampleFifo_;
float* featureList_;
int featureListIdx_;
int arch_;
FILE* featuresFile_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
RADE_COMP* radeOut_;
short* radeOutShort_;
RADE_COMP* eooOut_;
short* eooOutShort_;
GenericFIFO<float> utFeatures_;
PreAllocatedFIFO<float, NUM_FEATURES_TO_STORE>* utFeatures_;
std::thread utFeatureThread_;
bool exitingFeatureThread_;
Semaphore featuresAvailableSem_;
void utFeatureThreadEntry_();
};
#endif // AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H

View File

@ -25,6 +25,10 @@
#include <chrono>
#include "wx/thread.h"
#if defined(__APPLE__)
#include <pthread.h>
#endif // defined(__APPLE__)
extern wxMutex g_mutexProtectingCallbackData;
using namespace std::chrono_literals;
@ -46,6 +50,7 @@ RecordStep::RecordStep(
RecordStep::~RecordStep()
{
fileIoThreadEnding_ = true;
fileIoThreadSem_.signal();
if (fileIoThread_.joinable())
{
fileIoThread_.join();
@ -64,12 +69,13 @@ int RecordStep::getOutputSampleRate() const
return inputSampleRate_;
}
std::shared_ptr<short> RecordStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* RecordStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
codec2_fifo_write(inputFifo_, inputSamples.get(), numInputSamples);
codec2_fifo_write(inputFifo_, inputSamples, numInputSamples);
fileIoThreadSem_.signal();
*numOutputSamples = 0;
return std::shared_ptr<short>((short*)nullptr);
*numOutputSamples = 0;
return nullptr;
}
void RecordStep::reset()
@ -85,7 +91,12 @@ void RecordStep::fileIoThreadEntry_()
{
short* buf = new short[inputSampleRate_];
assert(buf != nullptr);
#if defined(__APPLE__)
// Downgrade thread QoS to Utility to avoid thread contention issues.
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
#endif // defined(__APPLE__)
while (!fileIoThreadEnding_)
{
g_mutexProtectingCallbackData.Lock();
@ -100,7 +111,7 @@ void RecordStep::fileIoThreadEntry_()
}
g_mutexProtectingCallbackData.Unlock();
std::this_thread::sleep_for(100ms);
fileIoThreadSem_.wait();
}
delete[] buf;

View File

@ -29,6 +29,7 @@
#include <sndfile.h>
#include <thread>
#include "codec2_fifo.h"
#include "../util/Semaphore.h"
class RecordStep : public IPipelineStep
{
@ -39,7 +40,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
@ -50,8 +51,9 @@ private:
std::thread fileIoThread_;
FIFO* inputFifo_;
bool fileIoThreadEnding_;
Semaphore fileIoThreadSem_;
void fileIoThreadEntry_();
};
#endif // AUDIO_PIPELINE__RECORD_STEP_H
#endif // AUDIO_PIPELINE__RECORD_STEP_H

View File

@ -48,10 +48,10 @@ int ResampleForPlotStep::getOutputSampleRate() const
return FS;
}
std::shared_ptr<short> ResampleForPlotStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* ResampleForPlotStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
resample_for_plot(fifo_, inputSamples.get(), decSamples_, numInputSamples, FS);
resample_for_plot(fifo_, inputSamples, decSamples_, numInputSamples, FS);
*numOutputSamples = 0;
return std::shared_ptr<short>(nullptr);
return nullptr;
}

View File

@ -37,7 +37,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
struct FIFO* fifo_;

View File

@ -76,11 +76,9 @@ ResampleStep::ResampleStep(int inputSampleRate, int outputSampleRate, bool forPl
resampleState_ = src_new(forPlotsOnly ? SRC_LINEAR : SRC_SINC_MEDIUM_QUALITY, 1, &src_error);
assert(resampleState_ != nullptr);
// Pre-allocate buffers so we don't have to do so during real-time operation.
outputSamples_ = std::shared_ptr<short>(
new short[outputSampleRate],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(outputSampleRate);
assert(outputSamples_ != nullptr);
tempInput_ = new float[inputSampleRate * 10 / 1000];
@ -93,7 +91,7 @@ ResampleStep::ResampleStep(int inputSampleRate, int outputSampleRate, bool forPl
ResampleStep::~ResampleStep()
{
src_delete(resampleState_);
delete[] tempInput_;
delete[] tempOutput_;
}
@ -108,8 +106,15 @@ int ResampleStep::getOutputSampleRate() const
return outputSampleRate_;
}
std::shared_ptr<short> ResampleStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* ResampleStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
if (numInputSamples == 0)
{
// Not generating any samples if we haven't gotten any.
*numOutputSamples = 0;
return inputSamples;
}
if (inputSampleRate_ == outputSampleRate_)
{
// shortcut - just return what we got.
@ -119,7 +124,7 @@ std::shared_ptr<short> ResampleStep::execute(std::shared_ptr<short> inputSamples
*numOutputSamples = 0;
auto inputPtr = inputSamples.get();
auto inputPtr = inputSamples;
auto outputPtr = outputSamples_.get();
while (numInputSamples > 0)
{
@ -135,6 +140,6 @@ std::shared_ptr<short> ResampleStep::execute(std::shared_ptr<short> inputSamples
numInputSamples -= inputSize;
*numOutputSamples += numSamples;
}
return outputSamples_;
return outputSamples_.get();
}

View File

@ -25,6 +25,7 @@
#include "IPipelineStep.h"
#include <memory>
#include <samplerate.h>
class ResampleStep : public IPipelineStep
@ -35,7 +36,7 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
int inputSampleRate_;
@ -44,7 +45,7 @@ private:
float* tempInput_;
float* tempOutput_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__RESAMPLE_STEP_H

View File

@ -26,14 +26,14 @@
#include "../defines.h"
#include <assert.h>
#include "codec2_fifo.h"
extern std::atomic<bool> g_agcEnabled;
SpeexStep::SpeexStep(int sampleRate)
: sampleRate_(sampleRate)
, numSamplesPerSpeexRun_((FRAME_DURATION_MS * sampleRate_) / MS_TO_SEC)
, inputSampleFifo_(sampleRate / 2)
{
numSamplesPerSpeexRun_ = (FRAME_DURATION_MS * sampleRate_) / MS_TO_SEC;
assert(numSamplesPerSpeexRun_ > 0);
speexStateObj_ = speex_preprocess_state_init(
@ -43,15 +43,8 @@ SpeexStep::SpeexStep(int sampleRate)
updateAgcState_();
// Set FIFO to be 2x the number of samples per run so we don't lose anything.
inputSampleFifo_ = codec2_fifo_create(numSamplesPerSpeexRun_ * 2);
assert(inputSampleFifo_ != nullptr);
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(sampleRate);
assert(outputSamples_ != nullptr);
}
@ -59,7 +52,6 @@ SpeexStep::~SpeexStep()
{
outputSamples_ = nullptr;
speex_preprocess_state_destroy(speexStateObj_);
codec2_fifo_destroy(inputSampleFifo_);
}
int SpeexStep::getInputSampleRate() const
@ -72,48 +64,40 @@ int SpeexStep::getOutputSampleRate() const
return sampleRate_;
}
std::shared_ptr<short> SpeexStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* SpeexStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
updateAgcState_();
*numOutputSamples = 0;
int numSpeexRuns = (codec2_fifo_used(inputSampleFifo_) + numInputSamples) / numSamplesPerSpeexRun_;
short* outputSamples = outputSamples_.get();
int numSpeexRuns = (inputSampleFifo_.numUsed() + numInputSamples) / numSamplesPerSpeexRun_;
if (numSpeexRuns > 0)
{
*numOutputSamples = numSpeexRuns * numSamplesPerSpeexRun_;
short* tmpOutput = outputSamples_.get();
short* tmpInput = inputSamples.get();
short* tmpOutput = outputSamples;
while (numInputSamples > 0 && tmpInput != nullptr)
inputSampleFifo_.write(inputSamples, numInputSamples);
while (inputSampleFifo_.numUsed() >= numSamplesPerSpeexRun_)
{
codec2_fifo_write(inputSampleFifo_, tmpInput++, 1);
numInputSamples--;
if (codec2_fifo_used(inputSampleFifo_) >= numSamplesPerSpeexRun_)
{
codec2_fifo_read(inputSampleFifo_, tmpOutput, numSamplesPerSpeexRun_);
speex_preprocess_run(speexStateObj_, tmpOutput);
tmpOutput += numSamplesPerSpeexRun_;
}
inputSampleFifo_.read(tmpOutput, numSamplesPerSpeexRun_);
speex_preprocess_run(speexStateObj_, tmpOutput);
tmpOutput += numSamplesPerSpeexRun_;
}
}
else if (numInputSamples > 0 && inputSamples.get() != nullptr)
else if (numInputSamples > 0 && inputSamples != nullptr)
{
codec2_fifo_write(inputSampleFifo_, inputSamples.get(), numInputSamples);
inputSampleFifo_.write(inputSamples, numInputSamples);
}
return outputSamples_;
return outputSamples;
}
void SpeexStep::reset()
{
short buf;
while (codec2_fifo_used(inputSampleFifo_) > 0)
{
codec2_fifo_read(inputSampleFifo_, &buf, 1);
}
inputSampleFifo_.reset();
}
void SpeexStep::updateAgcState_()
@ -131,4 +115,4 @@ void SpeexStep::updateAgcState_()
uint32_t maxGainDb = 40;
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &maxGainDb);
}
}
}

View File

@ -24,16 +24,11 @@
#define AUDIO_PIPELINE__SPEEX_STEP_H
#include "IPipelineStep.h"
#include "../util/GenericFIFO.h"
#include <memory>
#include <speex/speex_preprocess.h>
// Forward definition of struct FIFO from Codec2.
extern "C"
{
struct FIFO;
}
class SpeexStep : public IPipelineStep
{
public:
@ -42,18 +37,18 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual void reset() override;
private:
int sampleRate_;
SpeexPreprocessState* speexStateObj_;
int numSamplesPerSpeexRun_;
struct FIFO* inputSampleFifo_;
std::shared_ptr<short> outputSamples_;
GenericFIFO<short> inputSampleFifo_;
std::unique_ptr<short[]> outputSamples_;
void updateAgcState_();
};
#endif // AUDIO_PIPELINE__SPEEX_STEP_H
#endif // AUDIO_PIPELINE__SPEEX_STEP_H

View File

@ -24,17 +24,50 @@
#include <assert.h>
#include <future>
#include <chrono>
#if defined(__APPLE__)
#include <pthread.h>
#endif // defined(__APPLE__)
using namespace std::chrono_literals;
TapStep::TapStep(int sampleRate, IPipelineStep* tapStep)
: tapStep_(tapStep)
, sampleRate_(sampleRate)
, endingTapThread_(false)
, tapThreadInput_(sampleRate)
{
// empty
tapThread_ = std::thread([&]() {
const int SAMPLE_RATE_AT_10MS = sampleRate_ / 100;
short* fifoInput = new short[SAMPLE_RATE_AT_10MS];
assert(fifoInput != nullptr);
#if defined(__APPLE__)
// Downgrade thread QoS to Utility to avoid thread contention issues.
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
#endif // defined(__APPLE__)
while (!endingTapThread_)
{
while (tapThreadInput_.numUsed() >= SAMPLE_RATE_AT_10MS)
{
int temp = 0;
tapThreadInput_.read(fifoInput, SAMPLE_RATE_AT_10MS);
tapStep_->execute(fifoInput, SAMPLE_RATE_AT_10MS, &temp);
}
sem_.wait();
}
delete[] fifoInput;
});
}
TapStep::~TapStep()
{
// empty
endingTapThread_ = true;
sem_.signal();
tapThread_.join();
}
int TapStep::getInputSampleRate() const
@ -47,13 +80,16 @@ int TapStep::getOutputSampleRate() const
return sampleRate_;
}
std::shared_ptr<short> TapStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* TapStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
assert(tapStep_->getInputSampleRate() == sampleRate_);
int temp = 0;
tapStep_->execute(inputSamples, numInputSamples, &temp);
tapThreadInput_.write(inputSamples, numInputSamples);
if (tapThreadInput_.numUsed() > (100 * sampleRate_ / 1000))
{
sem_.signal();
}
*numOutputSamples = numInputSamples;
return inputSamples;
}

View File

@ -24,6 +24,9 @@
#define AUDIO_PIPELINE__TAP_STEP_H
#include <memory>
#include <thread>
#include "../util/GenericFIFO.h"
#include "../util/Semaphore.h"
#include "IPipelineStep.h"
@ -35,11 +38,15 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
std::shared_ptr<IPipelineStep> tapStep_;
std::unique_ptr<IPipelineStep> tapStep_;
int sampleRate_;
std::thread tapThread_;
bool endingTapThread_;
GenericFIFO<short> tapThreadInput_;
Semaphore sem_;
};
#endif // AUDIO_PIPELINE__TAP_STEP_H
#endif // AUDIO_PIPELINE__TAP_STEP_H

View File

@ -40,9 +40,7 @@ ToneInterfererStep::ToneInterfererStep(
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
outputSamples_ = std::shared_ptr<short>(
new short[maxSamples],
std::default_delete<short[]>());
outputSamples_ = std::make_unique<short[]>(maxSamples);
assert(outputSamples_ != nullptr);
}
@ -61,12 +59,11 @@ int ToneInterfererStep::getOutputSampleRate() const
return sampleRate_;
}
std::shared_ptr<short> ToneInterfererStep::execute(
std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
short* ToneInterfererStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
*numOutputSamples = numInputSamples;
memcpy(outputSamples_.get(), inputSamples.get(), numInputSamples * sizeof(short));
memcpy(outputSamples_.get(), inputSamples, numInputSamples * sizeof(short));
auto toneFrequency = toneFrequencyFn_();
auto toneAmplitude = toneAmplitudeFn_();
@ -80,5 +77,5 @@ std::shared_ptr<short> ToneInterfererStep::execute(
}
*tonePhase -= 2.0 * M_PI * floor(*tonePhase / (2.0 * M_PI));
return outputSamples_;
}
return outputSamples_.get();
}

View File

@ -38,14 +38,14 @@ public:
virtual int getInputSampleRate() const override;
virtual int getOutputSampleRate() const override;
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples) override;
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
private:
int sampleRate_;
std::function<float()> toneFrequencyFn_;
std::function<float()> toneAmplitudeFn_;
std::function<float*()> tonePhaseFn_;
std::shared_ptr<short> outputSamples_;
std::unique_ptr<short[]> outputSamples_;
};
#endif // AUDIO_PIPELINE__TONE_INTERFERER_STEP_H
#endif // AUDIO_PIPELINE__TONE_INTERFERER_STEP_H

View File

@ -59,10 +59,9 @@ using namespace std::chrono_literals;
// usage required to generate various plots in the user interface. (Tech note: When
// enabled, libsamplerate is directed to use SRC_LINEAR for the plot resampling.)
// * ENABLE_PROCESSING_STATS: This causes execution statistics to be collected for RX and TX
// processing and output in the log after the user pushes Stop.
// processing and output in the log after the user pushes Stop. (Define in .h file.)
#define ENABLE_FASTER_PLOTS
//#define ENABLE_PROCESSING_STATS
// External globals
// TBD -- work on fully removing the need for these.
@ -83,6 +82,7 @@ extern struct FIFO* g_plotSpeechOutFifo;
extern int g_mode;
extern bool g_recFileFromModulator;
extern int g_txLevel;
extern std::atomic<float> g_txLevelScale;
extern int g_dump_timing;
extern bool g_queueResync;
extern int g_resyncs;
@ -136,7 +136,8 @@ void TxRxThread::initializePipeline_()
{
if (m_tx)
{
pipeline_ = std::shared_ptr<AudioPipeline>(new AudioPipeline(inputSampleRate_, outputSampleRate_));
pipeline_ = std::make_unique<AudioPipeline>(inputSampleRate_, outputSampleRate_);
// Record from mic step (optional)
auto recordMicStep = new RecordStep(
inputSampleRate_,
@ -147,17 +148,17 @@ void TxRxThread::initializePipeline_()
}
);
auto recordMicPipeline = new AudioPipeline(inputSampleRate_, inputSampleRate_);
recordMicPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordMicStep));
recordMicPipeline->appendPipelineStep(recordMicStep);
auto recordMicTap = new TapStep(inputSampleRate_, recordMicPipeline);
auto bypassRecordMic = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto eitherOrRecordMic = new EitherOrStep(
[]() { return (g_recVoiceKeyerFile || g_recFileFromMic) && (g_sfRecMicFile != NULL); },
std::shared_ptr<IPipelineStep>(recordMicTap),
std::shared_ptr<IPipelineStep>(bypassRecordMic)
recordMicTap,
bypassRecordMic
);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordMic));
pipeline_->appendPipelineStep(eitherOrRecordMic);
// Mic In playback step (optional)
auto eitherOrBypassPlay = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -165,36 +166,36 @@ void TxRxThread::initializePipeline_()
auto playMicIn = new PlaybackStep(
inputSampleRate_,
[]() { return g_sfTxFs; },
[]() { return g_sfPlayFile; },
[]() { return g_playFileToMicIn ? g_sfPlayFile : nullptr; },
[]() {
if (g_loopPlayFileToMicIn)
sf_seek(g_sfPlayFile, 0, SEEK_SET);
else {
log_info("playFileFromRadio finished, issuing event!");
g_parent->CallAfter(&MainFrame::StopPlayFileToMicIn);
((MainFrame*)g_parent)->executeOnUiThreadAndWait_([]() { ((MainFrame*)g_parent)->StopPlayFileToMicIn();});
}
}
);
eitherOrPlayMicIn->appendPipelineStep(std::shared_ptr<IPipelineStep>(playMicIn));
eitherOrPlayMicIn->appendPipelineStep(playMicIn);
auto eitherOrPlayStep = new EitherOrStep(
[]() { return g_playFileToMicIn && (g_sfPlayFile != NULL); },
std::shared_ptr<IPipelineStep>(eitherOrPlayMicIn),
std::shared_ptr<IPipelineStep>(eitherOrBypassPlay));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrPlayStep));
eitherOrPlayMicIn,
eitherOrBypassPlay);
pipeline_->appendPipelineStep(eitherOrPlayStep);
// Speex step (optional)
auto eitherOrProcessSpeex = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto eitherOrBypassSpeex = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto speexStep = new SpeexStep(inputSampleRate_);
eitherOrProcessSpeex->appendPipelineStep(std::shared_ptr<IPipelineStep>(speexStep));
eitherOrProcessSpeex->appendPipelineStep(speexStep);
auto eitherOrSpeexStep = new EitherOrStep(
[]() { return wxGetApp().appConfiguration.filterConfiguration.speexppEnable; },
std::shared_ptr<IPipelineStep>(eitherOrProcessSpeex),
std::shared_ptr<IPipelineStep>(eitherOrBypassSpeex));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrSpeexStep));
eitherOrProcessSpeex,
eitherOrBypassSpeex);
pipeline_->appendPipelineStep(eitherOrSpeexStep);
// Equalizer step (optional based on filter state)
auto equalizerStep = new EqualizerStep(
@ -204,7 +205,7 @@ void TxRxThread::initializePipeline_()
&g_rxUserdata->sbqMicInMid,
&g_rxUserdata->sbqMicInTreble,
&g_rxUserdata->sbqMicInVol);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerStep));
pipeline_->appendPipelineStep(equalizerStep);
// Take TX audio post-equalizer and send it to RX for possible monitoring use.
if (equalizedMicAudioLink_ != nullptr)
@ -212,7 +213,7 @@ void TxRxThread::initializePipeline_()
auto micAudioPipeline = new AudioPipeline(inputSampleRate_, equalizedMicAudioLink_->getSampleRate());
micAudioPipeline->appendPipelineStep(equalizedMicAudioLink_->getInputPipelineStep());
auto micAudioTap = std::make_shared<TapStep>(inputSampleRate_, micAudioPipeline);
auto micAudioTap = new TapStep(inputSampleRate_, micAudioPipeline);
pipeline_->appendPipelineStep(micAudioTap);
}
@ -221,17 +222,17 @@ void TxRxThread::initializePipeline_()
auto resampleForPlotPipeline = new AudioPipeline(inputSampleRate_, resampleForPlotStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForPlotResampler = new ResampleStep(inputSampleRate_, resampleForPlotStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
resampleForPlotPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotResampler));
resampleForPlotPipeline->appendPipelineStep(resampleForPlotResampler);
#endif // defined(ENABLE_FASTER_PLOTS)
resampleForPlotPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotStep));
resampleForPlotPipeline->appendPipelineStep(resampleForPlotStep);
auto resampleForPlotTap = new TapStep(inputSampleRate_, resampleForPlotPipeline);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotTap));
pipeline_->appendPipelineStep(resampleForPlotTap);
// FreeDV TX step (analog leg)
auto doubleLevelStep = new LevelAdjustStep(inputSampleRate_, []() { return 2.0; });
auto analogTxPipeline = new AudioPipeline(inputSampleRate_, outputSampleRate_);
analogTxPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(doubleLevelStep));
analogTxPipeline->appendPipelineStep(doubleLevelStep);
auto digitalTxStep = freedvInterface.createTransmitPipeline(
inputSampleRate_,
@ -239,14 +240,14 @@ void TxRxThread::initializePipeline_()
[]() { return g_TxFreqOffsetHz; },
helper_);
auto digitalTxPipeline = new AudioPipeline(inputSampleRate_, outputSampleRate_);
digitalTxPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(digitalTxStep));
digitalTxPipeline->appendPipelineStep(digitalTxStep);
auto eitherOrDigitalAnalog = new EitherOrStep(
[]() { return g_analog; },
std::shared_ptr<IPipelineStep>(analogTxPipeline),
std::shared_ptr<IPipelineStep>(digitalTxPipeline));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrDigitalAnalog));
analogTxPipeline,
digitalTxPipeline);
pipeline_->appendPipelineStep(eitherOrDigitalAnalog);
// Record modulated output (optional)
auto recordModulatedStep = new RecordStep(
outputSampleRate_,
@ -255,31 +256,29 @@ void TxRxThread::initializePipeline_()
// empty
});
auto recordModulatedPipeline = new AudioPipeline(outputSampleRate_, recordModulatedStep->getOutputSampleRate());
recordModulatedPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordModulatedStep));
recordModulatedPipeline->appendPipelineStep(recordModulatedStep);
auto recordModulatedTap = new TapStep(outputSampleRate_, recordModulatedPipeline);
auto recordModulatedTapPipeline = new AudioPipeline(outputSampleRate_, outputSampleRate_);
recordModulatedTapPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordModulatedTap));
recordModulatedTapPipeline->appendPipelineStep(recordModulatedTap);
auto bypassRecordModulated = new AudioPipeline(outputSampleRate_, outputSampleRate_);
auto eitherOrRecordModulated = new EitherOrStep(
[]() { return g_recFileFromModulator && (g_sfRecFileFromModulator != NULL); },
std::shared_ptr<IPipelineStep>(recordModulatedTapPipeline),
std::shared_ptr<IPipelineStep>(bypassRecordModulated));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordModulated));
recordModulatedTapPipeline,
bypassRecordModulated);
pipeline_->appendPipelineStep(eitherOrRecordModulated);
// TX attenuation step
auto txAttenuationStep = new LevelAdjustStep(outputSampleRate_, []() {
double dbLoss = g_txLevel / 10.0;
double scaleFactor = exp(dbLoss/20.0 * log(10.0));
return scaleFactor;
return g_txLevelScale.load(std::memory_order_acquire);
});
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(txAttenuationStep));
pipeline_->appendPipelineStep(txAttenuationStep);
}
else
{
pipeline_ = std::shared_ptr<AudioPipeline>(new AudioPipeline(inputSampleRate_, outputSampleRate_));
pipeline_ = std::make_unique<AudioPipeline>(inputSampleRate_, outputSampleRate_);
// Record from radio step (optional)
auto recordRadioStep = new RecordStep(
inputSampleRate_,
@ -294,17 +293,17 @@ void TxRxThread::initializePipeline_()
}
);
auto recordRadioPipeline = new AudioPipeline(inputSampleRate_, inputSampleRate_);
recordRadioPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordRadioStep));
recordRadioPipeline->appendPipelineStep(recordRadioStep);
auto recordRadioTap = new TapStep(inputSampleRate_, recordRadioPipeline);
auto bypassRecordRadio = new AudioPipeline(inputSampleRate_, inputSampleRate_);
auto eitherOrRecordRadio = new EitherOrStep(
[]() { return g_recFileFromRadio && (g_sfRecFile != NULL); },
std::shared_ptr<IPipelineStep>(recordRadioTap),
std::shared_ptr<IPipelineStep>(bypassRecordRadio)
recordRadioTap,
bypassRecordRadio
);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordRadio));
pipeline_->appendPipelineStep(eitherOrRecordRadio);
// Play from radio step (optional)
auto eitherOrBypassPlayRadio = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -318,32 +317,32 @@ void TxRxThread::initializePipeline_()
sf_seek(g_sfPlayFileFromRadio, 0, SEEK_SET);
else {
log_info("playFileFromRadio finished, issuing event!");
g_parent->CallAfter(&MainFrame::StopPlaybackFileFromRadio);
((MainFrame*)g_parent)->executeOnUiThreadAndWait_([]() { ((MainFrame*)g_parent)->StopPlaybackFileFromRadio();});
}
}
);
eitherOrPlayRadio->appendPipelineStep(std::shared_ptr<IPipelineStep>(playRadio));
eitherOrPlayRadio->appendPipelineStep(playRadio);
auto eitherOrPlayRadioStep = new EitherOrStep(
[]() {
auto result = g_playFileFromRadio && (g_sfPlayFileFromRadio != NULL);
return result;
},
std::shared_ptr<IPipelineStep>(eitherOrPlayRadio),
std::shared_ptr<IPipelineStep>(eitherOrBypassPlayRadio));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrPlayRadioStep));
eitherOrPlayRadio,
eitherOrBypassPlayRadio);
pipeline_->appendPipelineStep(eitherOrPlayRadioStep);
// Resample for plot step (demod in)
auto resampleForPlotStep = new ResampleForPlotStep(g_plotDemodInFifo);
auto resampleForPlotPipeline = new AudioPipeline(inputSampleRate_, resampleForPlotStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForPlotResampler = new ResampleStep(inputSampleRate_, resampleForPlotStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
resampleForPlotPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotResampler));
resampleForPlotPipeline->appendPipelineStep(resampleForPlotResampler);
#endif // defined(ENABLE_FASTER_PLOTS)
resampleForPlotPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotStep));
resampleForPlotPipeline->appendPipelineStep(resampleForPlotStep);
auto resampleForPlotTap = new TapStep(inputSampleRate_, resampleForPlotPipeline);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotTap));
pipeline_->appendPipelineStep(resampleForPlotTap);
// Tone interferer step (optional)
auto bypassToneInterferer = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -355,10 +354,10 @@ void TxRxThread::initializePipeline_()
);
auto eitherOrToneInterferer = new EitherOrStep(
[]() { return wxGetApp().m_tone; },
std::shared_ptr<IPipelineStep>(toneInterfererStep),
std::shared_ptr<IPipelineStep>(bypassToneInterferer)
toneInterfererStep,
bypassToneInterferer
);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrToneInterferer));
pipeline_->appendPipelineStep(eitherOrToneInterferer);
// RF spectrum computation step
auto computeRfSpectrumStep = new ComputeRfSpectrumStep(
@ -369,12 +368,12 @@ void TxRxThread::initializePipeline_()
inputSampleRate_, computeRfSpectrumStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForRfSpectrum = new ResampleStep(inputSampleRate_, computeRfSpectrumStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
computeRfSpectrumPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForRfSpectrum));
computeRfSpectrumPipeline->appendPipelineStep(resampleForRfSpectrum);
#endif // defined(ENABLE_FASTER_PLOTS)
computeRfSpectrumPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(computeRfSpectrumStep));
computeRfSpectrumPipeline->appendPipelineStep(computeRfSpectrumStep);
auto computeRfSpectrumTap = new TapStep(inputSampleRate_, computeRfSpectrumPipeline);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(computeRfSpectrumTap));
pipeline_->appendPipelineStep(computeRfSpectrumTap);
// RX demodulation step
auto bypassRfDemodulationPipeline = new AudioPipeline(inputSampleRate_, outputSampleRate_);
@ -388,7 +387,7 @@ void TxRxThread::initializePipeline_()
[]() { return &g_sig_pwr_av; },
helper_
);
rfDemodulationPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(rfDemodulationStep));
rfDemodulationPipeline->appendPipelineStep(rfDemodulationStep);
// Replace received audio with microphone audio if we're monitoring TX/voice keyer recording.
if (equalizedMicAudioLink_ != nullptr)
@ -399,9 +398,9 @@ void TxRxThread::initializePipeline_()
auto monitorPipeline = new AudioPipeline(inputSampleRate_, outputSampleRate_);
monitorPipeline->appendPipelineStep(equalizedMicAudioLink_->getOutputPipelineStep());
auto monitorLevelStep = std::make_shared<LevelAdjustStep>(outputSampleRate_, [&]() {
double volInDb = 0;
if (g_voice_keyer_tx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio)
auto monitorLevelStep = new LevelAdjustStep(outputSampleRate_, [&]() {
float volInDb = 0;
if (g_voice_keyer_tx.load(std::memory_order_acquire) && wxGetApp().appConfiguration.monitorVoiceKeyerAudio)
{
volInDb = wxGetApp().appConfiguration.monitorVoiceKeyerAudioVol;
}
@ -416,36 +415,36 @@ void TxRxThread::initializePipeline_()
auto muteStep = new MuteStep(outputSampleRate_);
mutePipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(muteStep));
mutePipeline->appendPipelineStep(muteStep);
auto eitherOrMuteStep = new EitherOrStep(
[]() { return g_recVoiceKeyerFile; },
std::shared_ptr<IPipelineStep>(mutePipeline),
std::shared_ptr<IPipelineStep>(bypassMonitorAudio)
mutePipeline,
bypassMonitorAudio
);
auto eitherOrMicMonitorStep = new EitherOrStep(
[]() { return
(g_voice_keyer_tx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx && wxGetApp().appConfiguration.monitorTxAudio); },
std::shared_ptr<IPipelineStep>(monitorPipeline),
std::shared_ptr<IPipelineStep>(eitherOrMuteStep)
(g_voice_keyer_tx.load(std::memory_order_acquire) && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx.load(std::memory_order_acquire) && wxGetApp().appConfiguration.monitorTxAudio); },
monitorPipeline,
eitherOrMuteStep
);
bypassRfDemodulationPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrMicMonitorStep));
bypassRfDemodulationPipeline->appendPipelineStep(eitherOrMicMonitorStep);
}
auto eitherOrRfDemodulationStep = new EitherOrStep(
[this]() { return g_analog ||
(equalizedMicAudioLink_ != nullptr && (
(g_recVoiceKeyerFile) ||
(g_voice_keyer_tx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx && wxGetApp().appConfiguration.monitorTxAudio)
(g_voice_keyer_tx.load(std::memory_order_acquire) && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx.load(std::memory_order_acquire) && wxGetApp().appConfiguration.monitorTxAudio)
)); },
std::shared_ptr<IPipelineStep>(bypassRfDemodulationPipeline),
std::shared_ptr<IPipelineStep>(rfDemodulationPipeline)
bypassRfDemodulationPipeline,
rfDemodulationPipeline
);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRfDemodulationStep));
pipeline_->appendPipelineStep(eitherOrRfDemodulationStep);
// Equalizer step (optional based on filter state)
auto equalizerStep = new EqualizerStep(
@ -455,19 +454,19 @@ void TxRxThread::initializePipeline_()
&g_rxUserdata->sbqSpkOutMid,
&g_rxUserdata->sbqSpkOutTreble,
&g_rxUserdata->sbqSpkOutVol);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerStep));
pipeline_->appendPipelineStep(equalizerStep);
// Resample for plot step (speech out)
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);
auto resampleForPlotOutPipeline = new AudioPipeline(outputSampleRate_, resampleForPlotOutStep->getOutputSampleRate());
#if defined(ENABLE_FASTER_PLOTS)
auto resampleForPlotOutResampler = new ResampleStep(outputSampleRate_, resampleForPlotOutStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
resampleForPlotOutPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotOutResampler));
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutResampler);
#endif // defined(ENABLE_FASTER_PLOTS)
resampleForPlotOutPipeline->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotOutStep));
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutStep);
auto resampleForPlotOutTap = new TapStep(outputSampleRate_, resampleForPlotOutPipeline);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(resampleForPlotOutTap));
pipeline_->appendPipelineStep(resampleForPlotOutTap);
// Clear anything in the FIFO before resuming decode.
clearFifos_();
@ -476,130 +475,148 @@ void TxRxThread::initializePipeline_()
void* TxRxThread::Entry()
{
// Get raw pointer so we don't need to constantly access the shared_ptr
// and thus constantly increment/decrement refcounts.
IRealtimeHelper* helper = helper_.get();
// Ensure that O(1) memory allocator is used for Codec2
// instead of standard malloc().
codec2_initialize_realtime(CODEC2_REAL_TIME_MEMORY_SIZE);
initializePipeline_();
// Request real-time scheduling from the operating system.
helper_->setHelperRealTime();
// Request real-time scheduling from the operating system.
helper->setHelperRealTime();
#if defined(ENABLE_PROCESSING_STATS)
int numTimeSamples = 0;
double minDuration = 1e9;
double maxDuration = 0;
double sumDuration = 0;
double sumDoubleDuration = 0;
resetStats_();
#endif // defined(ENABLE_PROCESSING_STATS)
#if defined(__linux__)
const char* threadName = nullptr;
if (m_tx) threadName = "FreeDV txThread";
else threadName = "FreeDV rxThread";
pthread_setname_np(pthread_self(), threadName);
#endif // defined(__linux__)
// Make sure we don't start processing until
// the main thread is ready.
readySem_.signal();
startSem_.wait();
clearFifos_();
while (m_run)
{
#if defined(__linux__)
const char* threadName = nullptr;
if (m_tx) threadName = "FreeDV txThread";
else threadName = "FreeDV rxThread";
pthread_setname_np(pthread_self(), threadName);
#endif // defined(__linux__)
if (!m_run) break;
//log_info("thread woken up: m_tx=%d", (int)m_tx);
helper_->startRealTimeWork();
helper->startRealTimeWork();
#if defined(ENABLE_PROCESSING_STATS)
auto b = std::chrono::high_resolution_clock::now();
#endif // defined(ENABLE_PROCESSING_STATS)
if (m_tx) txProcessing_();
else rxProcessing_();
#if defined(ENABLE_PROCESSING_STATS)
auto e = std::chrono::high_resolution_clock::now();
auto d = std::chrono::duration_cast<std::chrono::nanoseconds>(e - b).count();
numTimeSamples++;
if (d < minDuration) minDuration = d;
if (d > maxDuration) maxDuration = d;
sumDuration += d; sumDoubleDuration += pow(d, 2);
#endif // defined(ENABLE_PROCESSING_STATS)
if (m_tx) txProcessing_(helper);
else rxProcessing_(helper);
// Determine whether we need to pause for a shorter amount
// of time to avoid dropouts.
paCallBackData *cbData = g_rxUserdata;
struct FIFO* outFifo = cbData->outfifo1;
auto 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);
auto totalFifoCapacity = outFifo->capacity();
auto fifoUsed = outFifo->numUsed();
helper->stopRealTimeWork(fifoUsed < totalFifoCapacity / 2);
}
#if defined(ENABLE_PROCESSING_STATS)
log_info("m_tx = %d, min = %f ns, max = %f ns, mean = %f ns, stdev = %f ns", m_tx, minDuration, maxDuration, sumDuration / numTimeSamples, sqrt((sumDoubleDuration - pow(sumDuration, 2)/numTimeSamples) / (numTimeSamples - 1)));
reportStats_();
#endif // defined(ENABLE_PROCESSING_STATS)
// Force pipeline to delete itself when we're done with the thread.
pipeline_ = nullptr;
// Return to normal scheduling
helper_->clearHelperRealTime();
helper->clearHelperRealTime();
codec2_disable_realtime();
return NULL;
}
void TxRxThread::OnExit()
{
// empty
#if defined(ENABLE_PROCESSING_STATS)
void TxRxThread::resetStats_()
{
numTimeSamples_ = 0;
minDuration_ = 1e9;
maxDuration_ = 0;
sumDuration_ = 0;
sumDoubleDuration_ = 0;
}
void TxRxThread::terminateThread()
void TxRxThread::startTimer_()
{
m_run = 0;
notify();
timeStart_ = std::chrono::high_resolution_clock::now();
}
void TxRxThread::notify()
void TxRxThread::endTimer_()
{
// empty
auto e = std::chrono::high_resolution_clock::now();
auto d = std::chrono::duration_cast<std::chrono::nanoseconds>(e - timeStart_).count();
numTimeSamples_++;
if (d < minDuration_)
{
minDuration_ = d;
minTime_ = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
}
if (d > maxDuration_)
{
maxDuration_ = d;
maxTime_ = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
}
sumDuration_ += d; sumDoubleDuration_ += pow(d, 2);
}
void TxRxThread::reportStats_()
{
if (numTimeSamples_ > 0)
{
std::tm * minTm = std::localtime(&minTime_);
std::tm * maxTm = std::localtime(&maxTime_);
char bufMin[32];
char bufMax[32];
std::strftime(bufMin, 32, "%H:%M:%S", minTm);
std::strftime(bufMax, 32, "%H:%M:%S", maxTm);
log_info("m_tx = %d, min = %f ns [%s], max = %f ns [%s], mean = %f ns, stdev = %f ns (n = %d)", m_tx, minDuration_, bufMin, maxDuration_, bufMax, sumDuration_ / numTimeSamples_, sqrt((sumDoubleDuration_ - pow(sumDuration_, 2)/numTimeSamples_) / (numTimeSamples_ - 1)), numTimeSamples_);
}
}
#endif // defined(ENABLE_PROCESSING_STATS)
void TxRxThread::clearFifos_()
{
paCallBackData *cbData = g_rxUserdata;
if (equalizedMicAudioLink_ != nullptr && !g_tx)
if (equalizedMicAudioLink_ != nullptr && !g_tx.load(std::memory_order_acquire))
{
equalizedMicAudioLink_->clearFifo();
}
if (m_tx)
{
while (codec2_fifo_used(cbData->outfifo1) > 0)
{
codec2_fifo_read(cbData->outfifo1, inputSamples_.get(), 1);
}
while (codec2_fifo_used(cbData->infifo2) > 0)
{
codec2_fifo_read(cbData->infifo2, inputSamples_.get(), 1);
}
cbData->outfifo1->reset();
cbData->infifo2->reset();
}
else
{
while (codec2_fifo_used(cbData->infifo1) > 0)
{
codec2_fifo_read(cbData->infifo1, inputSamples_.get(), 1);
}
cbData->infifo1->reset();
auto outFifo = (g_nSoundCards == 1) ? cbData->outfifo1 : cbData->outfifo2;
while (codec2_fifo_used(outFifo) > 0)
{
codec2_fifo_read(outFifo, inputSamples_.get(), 1);
}
outFifo->reset();
}
if (equalizedMicAudioLink_)
{
equalizedMicAudioLink_->getFifo().reset();
}
}
@ -607,7 +624,7 @@ void TxRxThread::clearFifos_()
// Main real time processing for tx and rx of FreeDV signals, run in its own threads
//---------------------------------------------------------------------------------------------
void TxRxThread::txProcessing_() noexcept
void TxRxThread::txProcessing_(IRealtimeHelper* helper) noexcept
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
[[clang::nonblocking]]
@ -626,7 +643,17 @@ void TxRxThread::txProcessing_() noexcept
// TX side processing --------------------------------------------
//
if (((g_nSoundCards == 2) && ((g_half_duplex && g_tx) || !g_half_duplex || g_voice_keyer_tx || g_recVoiceKeyerFile || g_recFileFromMic))) {
bool tmpHalfDuplex = g_half_duplex.load(std::memory_order_acquire);
if (((g_nSoundCards == 2) && ((tmpHalfDuplex && g_tx.load(std::memory_order_acquire)) || !tmpHalfDuplex || g_voice_keyer_tx.load(std::memory_order_acquire) || g_recVoiceKeyerFile || g_recFileFromMic))) {
if (deferReset_)
{
// We just entered TX from RX.
// Reset pipeline and wipe anything in the FIFO.
deferReset_ = false;
pipeline_->reset();
clearFifos_();
}
// This while loop locks the modulator to the sample rate of
// the input sound card. We want to make sure that modulator samples
// are uninterrupted by differences in sample rate between
@ -643,7 +670,7 @@ void TxRxThread::txProcessing_() noexcept
// If this drops to zero we have a problem as we will run out of output samples
// to send to the sound driver
log_debug("outfifo1 used: %6d free: %6d nsam_one_modem_frame: %d",
codec2_fifo_used(cbData->outfifo1), codec2_fifo_free(cbData->outfifo1), nsam_one_modem_frame);
cbData->outfifo1->numUsed(), cbData->outfifo1->numFree(), nsam_one_modem_frame);
}
int nsam_in_48 = (inputSampleRate_ * FRAME_DURATION_MS) / MS_TO_SEC;
@ -651,9 +678,13 @@ void TxRxThread::txProcessing_() noexcept
int nout;
while(!helper_->mustStopWork() && (unsigned)codec2_fifo_free(cbData->outfifo1) >= nsam_one_modem_frame) {
while(!helper->mustStopWork() && (unsigned)cbData->outfifo1->numFree() >= nsam_one_modem_frame) {
// OK to generate a frame of modem output samples we need
// an input frame of speech samples from the microphone.
#if defined(ENABLE_PROCESSING_STATS)
startTimer_();
#endif // defined(ENABLE_PROCESSING_STATS)
// infifo2 is written to by another sound card so it may
// over or underflow, but we don't really care. It will
@ -661,12 +692,14 @@ void TxRxThread::txProcessing_() noexcept
// to codec2_enc, possibly making a click every now and
// again in the decoded audio at the other end.
// zero speech input just in case infifo2 underflows
memset(inputSamples_.get(), 0, nsam_in_48*sizeof(short));
// There may be recorded audio left to encode while ending TX. To handle this,
// we keep reading from the FIFO until we have less than nsam_in_48 samples available.
int nread = codec2_fifo_read(cbData->infifo2, inputSamples_.get(), nsam_in_48);
auto inputPtr = inputSamples_.get();
int nread = cbData->infifo2->read(inputPtr, nsam_in_48);
if (nread != 0)
{
inputPtr = inputSamplesZeros_.get();
}
if (nread != 0 && endingTx)
{
if (freedvInterface.getCurrentMode() >= FREEDV_MODE_RADE)
@ -678,12 +711,12 @@ void TxRxThread::txProcessing_() noexcept
hasEooBeenSent_ = true;
}
auto outputSamples = pipeline_->execute(inputSamples_, 0, &nout);
if (nout > 0 && outputSamples.get() != nullptr)
auto outputSamples = pipeline_->execute(inputPtr, 0, &nout);
if (nout > 0 && outputSamples != nullptr)
{
if (codec2_fifo_write(cbData->outfifo1, outputSamples.get(), nout) != 0)
if (cbData->outfifo1->write(outputSamples, nout) != 0)
{
log_warn("Could not inject resampled EOO samples (space remaining in FIFO = %d)", codec2_fifo_free(cbData->outfifo1));
log_warn("Could not inject resampled EOO samples (space remaining in FIFO = %d)", cbData->outfifo1->numFree());
}
}
else
@ -699,25 +732,26 @@ void TxRxThread::txProcessing_() noexcept
hasEooBeenSent_ = false;
}
auto outputSamples = pipeline_->execute(inputSamples_, nsam_in_48, &nout);
auto outputSamples = pipeline_->execute(inputPtr, nsam_in_48, &nout);
if (g_dump_fifo_state) {
log_info(" nout: %d", nout);
}
if (outputSamples.get() != nullptr)
if (outputSamples != nullptr)
{
codec2_fifo_write(cbData->outfifo1, outputSamples.get(), nout);
cbData->outfifo1->write(outputSamples, nout);
}
#if defined(ENABLE_PROCESSING_STATS)
endTimer_();
#endif // defined(ENABLE_PROCESSING_STATS)
}
}
else
{
// Reset the pipeline state.
pipeline_->reset();
// Wipe anything added in the FIFO to prevent pops on next TX.
clearFifos_();
// Defer reset until next time we go into TX.
deferReset_ = true;
}
if (g_dump_timing) {
@ -725,7 +759,7 @@ void TxRxThread::txProcessing_() noexcept
}
}
void TxRxThread::rxProcessing_() noexcept
void TxRxThread::rxProcessing_(IRealtimeHelper* helper) noexcept
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
[[clang::nonblocking]]
@ -760,10 +794,13 @@ void TxRxThread::rxProcessing_() noexcept
int nout;
bool tmpTx = g_tx.load(std::memory_order_acquire);
bool tmpVkTx = g_voice_keyer_tx.load(std::memory_order_acquire);
bool tmpHalfDuplex = g_half_duplex.load(std::memory_order_acquire);
bool processInputFifo =
(g_voice_keyer_tx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx && wxGetApp().appConfiguration.monitorTxAudio) ||
(!g_voice_keyer_tx && ((g_half_duplex && !g_tx) || !g_half_duplex));
(tmpVkTx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(tmpTx && wxGetApp().appConfiguration.monitorTxAudio) ||
(!tmpVkTx && ((tmpHalfDuplex && !tmpTx) || !tmpHalfDuplex));
if (!processInputFifo)
{
clearFifos_();
@ -773,20 +810,32 @@ void TxRxThread::rxProcessing_() noexcept
auto outFifo = (g_nSoundCards == 1) ? cbData->outfifo1 : cbData->outfifo2;
// while we have enough input samples available and enough space in the output FIFO ...
while (!helper_->mustStopWork() && processInputFifo && codec2_fifo_free(outFifo) >= nsam_one_speech_frame && codec2_fifo_read(cbData->infifo1, inputSamples_.get(), nsam) == 0) {
while (!helper->mustStopWork() && processInputFifo && outFifo->numFree() >= nsam_one_speech_frame && cbData->infifo1->read(inputSamples_.get(), nsam) == 0) {
#if defined(ENABLE_PROCESSING_STATS)
startTimer_();
#endif // defined(ENABLE_PROCESSING_STATS)
// send latest squelch level to FreeDV API, as it handles squelch internally
freedvInterface.setSquelch(g_SquelchActive, g_SquelchLevel);
auto outputSamples = pipeline_->execute(inputSamples_, nsam, &nout);
auto outputSamples = pipeline_->execute(inputSamples_.get(), nsam, &nout);
if (nout > 0 && outputSamples.get() != nullptr)
if (nout > 0 && outputSamples != nullptr)
{
codec2_fifo_write(outFifo, outputSamples.get(), nout);
outFifo->write(outputSamples, nout);
}
tmpTx = g_tx.load(std::memory_order_acquire);
tmpVkTx = g_voice_keyer_tx.load(std::memory_order_acquire);
tmpHalfDuplex = g_half_duplex.load(std::memory_order_acquire);
processInputFifo =
(g_voice_keyer_tx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(g_tx && wxGetApp().appConfiguration.monitorTxAudio) ||
(!g_voice_keyer_tx && ((g_half_duplex && !g_tx) || !g_half_duplex));
(tmpVkTx && wxGetApp().appConfiguration.monitorVoiceKeyerAudio) ||
(tmpTx && wxGetApp().appConfiguration.monitorTxAudio) ||
(!tmpVkTx && ((tmpHalfDuplex && !tmpTx) || !tmpHalfDuplex));
#if defined(ENABLE_PROCESSING_STATS)
endTimer_();
#endif // defined(ENABLE_PROCESSING_STATS)
}
}

View File

@ -24,25 +24,28 @@
#define AUDIO_PIPELINE__TX_RX_THREAD_H
#include <assert.h>
#include <wx/thread.h>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable>
#include "AudioPipeline.h"
#include "util/IRealtimeHelper.h"
#include "util/Semaphore.h"
// Forward declarations
class LinkStep;
//#define ENABLE_PROCESSING_STATS
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=
// class txRxThread - experimental tx/rx processing thread
// class txRxThread - tx/rx processing thread
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=
class TxRxThread : public wxThread
class TxRxThread
{
public:
TxRxThread(bool tx, int inputSampleRate, int outputSampleRate, std::shared_ptr<LinkStep> micAudioLink, std::shared_ptr<IRealtimeHelper> helper)
: wxThread(wxTHREAD_JOINABLE)
, m_tx(tx)
: m_tx(tx)
, m_run(1)
, pipeline_(nullptr)
, inputSampleRate_(inputSampleRate)
@ -50,13 +53,17 @@ public:
, equalizedMicAudioLink_(micAudioLink)
, hasEooBeenSent_(false)
, helper_(helper)
, deferReset_(false)
{
assert(inputSampleRate_ > 0);
assert(outputSampleRate_ > 0);
inputSamples_ = std::shared_ptr<short>(
new short[std::max(inputSampleRate_, outputSampleRate_)],
std::default_delete<short[]>());
auto numSamples = std::max(inputSampleRate_, outputSampleRate_);
inputSamples_ = std::make_unique<short[]>(numSamples);
assert(inputSamples_ != nullptr);
inputSamplesZeros_ = std::make_unique<short[]>(numSamples);
assert(inputSamplesZeros_ != nullptr);
memset(inputSamplesZeros_.get(), 0, numSamples * sizeof(short));
}
virtual ~TxRxThread()
@ -65,29 +72,61 @@ public:
inputSamples_ = nullptr;
}
void start()
{
thread_ = std::thread(std::bind(&TxRxThread::Entry, this));
}
void stop()
{
m_run = false;
if (thread_.joinable())
{
thread_.join();
}
}
// thread execution starts here
void *Entry();
// called when the thread exits - whether it terminates normally or is
// stopped with Delete() (but not when it is Kill()ed!)
void OnExit();
void terminateThread();
void notify();
void waitForReady() { readySem_.wait(); }
void signalToStart() { startSem_.signal(); }
private:
bool m_tx;
bool m_run;
std::shared_ptr<AudioPipeline> pipeline_;
std::unique_ptr<AudioPipeline> pipeline_;
int inputSampleRate_;
int outputSampleRate_;
std::shared_ptr<LinkStep> equalizedMicAudioLink_;
bool hasEooBeenSent_;
std::shared_ptr<IRealtimeHelper> helper_;
std::shared_ptr<short> inputSamples_;
std::unique_ptr<short[]> inputSamples_;
std::unique_ptr<short[]> inputSamplesZeros_;
bool deferReset_;
std::thread thread_;
Semaphore readySem_;
Semaphore startSem_;
#if defined(ENABLE_PROCESSING_STATS)
int numTimeSamples_;
double minDuration_;
std::time_t minTime_;
double maxDuration_;
std::time_t maxTime_;
double sumDuration_;
double sumDoubleDuration_;
std::chrono::time_point<std::chrono::high_resolution_clock> timeStart_;
void resetStats_();
void startTimer_();
void endTimer_();
void reportStats_();
#endif // defined(ENABLE_PROCESSING_STATS)
void initializePipeline_();
void txProcessing_() noexcept
void txProcessing_(IRealtimeHelper* helper) noexcept
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
[[clang::nonblocking]]
@ -95,7 +134,7 @@ private:
#endif // defined(__clang__)
;
void rxProcessing_() noexcept
void rxProcessing_(IRealtimeHelper* helper) noexcept
#if defined(__clang__)
#if defined(__has_feature) && __has_feature(realtime_sanitizer)
[[clang::nonblocking]]

View File

@ -2,7 +2,7 @@
#define AUDIO_PIPELINE_PA_CALLBACK_DATA_H
#include <samplerate.h>
#include "codec2_fifo.h"
#include "../util/GenericFIFO.h"
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=
// paCallBackData
@ -14,8 +14,6 @@ typedef struct paCallBackData
, outfifo1(nullptr)
, infifo2(nullptr)
, outfifo2(nullptr)
, rxinfifo(nullptr)
, rxoutfifo(nullptr)
, sbqMicInBass(nullptr)
, sbqMicInTreble(nullptr)
, sbqMicInMid(nullptr)
@ -33,16 +31,12 @@ typedef struct paCallBackData
}
// FIFOs attached to first sound card
struct FIFO *infifo1;
struct FIFO *outfifo1;
GenericFIFO<short> *infifo1;
GenericFIFO<short> *outfifo1;
// FIFOs attached to second sound card
struct FIFO *infifo2;
struct FIFO *outfifo2;
// FIFOs for rx process
struct FIFO *rxinfifo;
struct FIFO *rxoutfifo;
GenericFIFO<short> *infifo2;
GenericFIFO<short> *outfifo2;
// EQ filter states
std::shared_ptr<void> sbqMicInBass;
@ -60,6 +54,10 @@ typedef struct paCallBackData
// optional loud tone on left channel to reliably trigger vox
bool leftChannelVoxTone;
float voxTonePhase;
// Temporary buffers for reading and writing
std::unique_ptr<short[]> tmpReadBuffer_;
std::unique_ptr<short[]> tmpWriteBuffer_;
} paCallBackData;
#endif // AUDIO_PIPELINE_PA_CALLBACK_DATA_H
#endif // AUDIO_PIPELINE_PA_CALLBACK_DATA_H

View File

@ -5,10 +5,10 @@
static bool passthroughCommon(int inputSampleRate, int outputSampleRate)
{
AudioPipeline pipeline(inputSampleRate, outputSampleRate);
auto sineWave = std::shared_ptr<short>(generateOneSecondSineWave(2000, inputSampleRate), std::default_delete<short[]>());
auto sineWave = std::unique_ptr<short[]>(generateOneSecondSineWave(2000, inputSampleRate));
int outputSamples = 0;
auto result = pipeline.execute(sineWave, inputSampleRate, &outputSamples);
pipeline.execute(sineWave.get(), inputSampleRate, &outputSamples);
auto minOutputSamples = outputSampleRate * 0.9;
auto maxOutputSamples = outputSampleRate * 1.1;
@ -43,11 +43,11 @@ bool resampleBeforeStepCommon(int inputSampleRate, int stepSampleRate, int outpu
auto levelAdjustStep = new LevelAdjustStep(stepSampleRate, []() { return 1.0; });
assert(levelAdjustStep != nullptr);
pipeline.appendPipelineStep(std::shared_ptr<IPipelineStep>(levelAdjustStep));
pipeline.appendPipelineStep(levelAdjustStep);
auto sineWave = std::shared_ptr<short>(generateOneSecondSineWave(2000, inputSampleRate), std::default_delete<short[]>());
auto sineWave = std::unique_ptr<short[]>(generateOneSecondSineWave(2000, inputSampleRate));
int numOutputSamples = 0;
auto result = pipeline.execute(sineWave, inputSampleRate, &numOutputSamples);
pipeline.execute(sineWave.get(), inputSampleRate, &numOutputSamples);
auto minOutputSamples = outputSampleRate * 0.9;
auto maxOutputSamples = outputSampleRate * 1.1;
@ -106,4 +106,4 @@ int main()
TEST_CASE(downsampleOnlyAtEnd);
return 0;
}
}

View File

@ -4,49 +4,60 @@
class FalseStep : public IPipelineStep
{
public:
FalseStep()
{
result_ = std::make_unique<short[]>(1);
result_[0] = 0;
}
virtual int getInputSampleRate() const { return 8000; }
virtual int getOutputSampleRate() const { return 8000; }
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
*numOutputSamples = 1;
short* result = new short[1];
result[0] = 0;
return std::shared_ptr<short>(result, std::default_delete<short[]>());
return result_.get();
}
private:
std::unique_ptr<short[]> result_;
};
class TrueStep : public IPipelineStep
{
public:
TrueStep()
{
result_ = std::make_unique<short[]>(1);
result_[0] = 1;
}
virtual int getInputSampleRate() const { return 8000; }
virtual int getOutputSampleRate() const { return 8000; }
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
*numOutputSamples = 1;
short* result = new short[1];
result[0] = 1;
return std::shared_ptr<short>(result, std::default_delete<short[]>());
return result_.get();
}
private:
std::unique_ptr<short[]> result_;
};
bool eitherOrCommon(bool val)
{
EitherOrStep eitherOrStep([&]() {
return val;
}, std::shared_ptr<IPipelineStep>(new TrueStep()),
std::shared_ptr<IPipelineStep>(new FalseStep()));
}, new TrueStep(), new FalseStep());
int outputSamples = 0;
auto result = eitherOrStep.execute(std::shared_ptr<short>(nullptr), 0, &outputSamples);
auto result = eitherOrStep.execute(nullptr, 0, &outputSamples);
if (outputSamples != 1)
{
std::cerr << "[outputSamples[" << outputSamples << "] != 1]...";
return false;
}
if (result.get()[0] != (val ? 1 : 0))
if (result[0] != (val ? 1 : 0))
{
std::cerr << "[result != " << (val ? 1 : 0) << "]...";
return false;
@ -70,4 +81,4 @@ int main()
TEST_CASE(trueStep);
TEST_CASE(falseStep);
return 0;
}
}

View File

@ -6,10 +6,10 @@ bool levelAdjustCommon(float val)
LevelAdjustStep levelAdjustStep(8000, [val]() { return val; });
int outputSamples = 0;
short* pData = new short[1];
std::unique_ptr<short[]> pData = std::make_unique<short[]>(1);
pData[0] = 10000;
auto result = levelAdjustStep.execute(std::shared_ptr<short>(pData, std::default_delete<short[]>()), 1, &outputSamples);
auto result = levelAdjustStep.execute(pData.get(), 1, &outputSamples);
if (outputSamples != 1)
{
std::cerr << "[outputSamples[" << outputSamples << "] != 1]...";
@ -17,9 +17,9 @@ bool levelAdjustCommon(float val)
}
auto expectedVal = 10000 * val;
if (result.get()[0] != expectedVal)
if (result[0] != expectedVal)
{
std::cerr << "[result[" << result.get()[0] << "] != " << expectedVal << "]...";
std::cerr << "[result[" << result[0] << "] != " << expectedVal << "]...";
return false;
}

View File

@ -67,24 +67,28 @@ int main()
// "Transmit" ~1 second of audio (including EOO) and immediately receive it.
int nout = 0;
int noutRx = 0;
short* inputSamples = new short[16384];
constexpr int SAMPLES_PER_TX = 1638; // 100ms @ 16 kHz sample rate
short* inputSamples = new short[SAMPLES_PER_TX];
assert(inputSamples != nullptr);
memset(inputSamples, 0, sizeof(short) * 16384);
auto inputSamplesPtr = std::shared_ptr<short>(inputSamples, std::default_delete<short[]>());
auto outputSamples = txStep->execute(inputSamplesPtr, 16384, &nout);
addNoise(outputSamples.get(), nout);
recvStep->execute(outputSamples, nout, &noutRx);
memset(inputSamples, 0, sizeof(short) * SAMPLES_PER_TX);
auto inputSamplesPtr = std::unique_ptr<short[]>(inputSamples);
for (int count = 0; count < 11; count++)
{
auto outputSamples = txStep->execute(inputSamplesPtr.get(), SAMPLES_PER_TX, &nout);
addNoise(outputSamples, nout);
recvStep->execute(outputSamples, nout, &noutRx);
}
txStep->restartVocoder();
while (nout > 0)
{
outputSamples = txStep->execute(inputSamplesPtr, 0, &nout);
addNoise(outputSamples.get(), nout);
auto outputSamples = txStep->execute(inputSamplesPtr.get(), 0, &nout);
addNoise(outputSamples, nout);
recvStep->execute(outputSamples, nout, &noutRx);
}
// Send silence through to RX step to trigger EOO processing
recvStep->execute(inputSamplesPtr, 16384, &noutRx);
recvStep->execute(inputSamplesPtr.get(), SAMPLES_PER_TX, &noutRx);
delete recvStep;
delete txStep;

View File

@ -4,11 +4,11 @@
bool resampleTestCaseCommon(int inputSampleRate, int outputSampleRate)
{
ResampleStep resampleStep(inputSampleRate, outputSampleRate);
auto inputSineWave = std::shared_ptr<short>(generateOneSecondSineWave(2000, inputSampleRate), std::default_delete<short[]>());
auto outputSineWave = std::shared_ptr<short>(generateOneSecondSineWave(2000, outputSampleRate), std::default_delete<short[]>());
auto inputSineWave = std::unique_ptr<short[]>(generateOneSecondSineWave(2000, inputSampleRate), std::default_delete<short[]>());
auto outputSineWave = std::unique_ptr<short[]>(generateOneSecondSineWave(2000, outputSampleRate), std::default_delete<short[]>());
int numOutputSamples = 0;
auto result = resampleStep.execute(inputSineWave, inputSampleRate, &numOutputSamples);
resampleStep.execute(inputSineWave.get(), inputSampleRate, &numOutputSamples);
// Allowed output samples are +/- 10% of the theoretical max.
int minOutputSamples = outputSampleRate * 0.9;
@ -18,23 +18,6 @@ bool resampleTestCaseCommon(int inputSampleRate, int outputSampleRate)
std::cerr << "[numOutputSamples(" << numOutputSamples << ") != " << outputSampleRate << "]...";
return false;
}
// NOTE: not sure how we can do any comparison of the actual sine wave here. TBD for future improvement.
#if 0
auto resultPtr = result.get();
auto expectedOutputPtr = outputSineWave.get();
for (auto index = 0; index < numOutputSamples; index++)
{
// Allowed result is +/- 10% of max.
auto minExpectedOutput = expectedOutputPtr[index] * 0.9;
auto maxExpectedOutput = expectedOutputPtr[index] * 1.1;
if (resultPtr[index] < minExpectedOutput || resultPtr[index] > maxExpectedOutput)
{
std::cerr << "[result[" << index << "] == " << resultPtr[index] << " && != " << expectedOutputPtr[index] << "]";
return false;
}
}
#endif
return true;
}
@ -60,4 +43,4 @@ int main()
TEST_CASE(resampleToHigher);
TEST_CASE(resampleToLower);
return 0;
}
}

View File

@ -6,14 +6,14 @@ class PassThroughStep : public IPipelineStep
public:
virtual int getInputSampleRate() const { return 8000; }
virtual int getOutputSampleRate() const { return 8000; }
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
{
lastInputSamples = inputSamples;
*numOutputSamples = numInputSamples;
return inputSamples;
}
std::shared_ptr<short> lastInputSamples;
short* lastInputSamples;
};
bool tapDataEqual()
@ -22,18 +22,17 @@ bool tapDataEqual()
TapStep tapStep(8000, step);
int outputSamples = 0;
short* pData = new short[1];
std::unique_ptr<short[]> pData = std::make_unique<short[]>(1);
pData[0] = 10000;
std::shared_ptr<short> input(pData, std::default_delete<short[]>());
auto result = tapStep.execute(input, 1, &outputSamples);
auto result = tapStep.execute(pData.get(), 1, &outputSamples);
if (outputSamples != 1)
{
std::cerr << "[outputSamples[" << outputSamples << "] != 1]...";
return false;
}
if (result != input)
if (result != pData.get())
{
std::cerr << "[result != input]...";
return false;

View File

@ -314,7 +314,7 @@ void MainFrame::OnRecFileFromRadio(wxCommandEvent& event)
m_menuItemRecFileFromRadio->SetItemLabel(wxString(_("Stop Record File - From Radio...")));
g_sfRecFileFromModulator = g_sfRecFile;
if (!g_tx)
if (!g_tx.load(std::memory_order_acquire))
{
g_recFileFromModulator = false;
g_recFileFromRadio = true;
@ -361,7 +361,7 @@ void MainFrame::OnTogBtnRecord( wxCommandEvent& event )
m_menuItemRecFileFromRadio->SetItemLabel(wxString(_("Stop Record File - From Radio...")));
g_sfRecFileFromModulator = g_sfRecFile;
if (!g_tx)
if (!g_tx.load(std::memory_order_acquire))
{
g_recFileFromModulator = false;
g_recFileFromRadio = true;

View File

@ -299,16 +299,16 @@ void FreeDVReporter::connect_()
onReporterConnectFn_();
}
sioClient_->on("new_connection", [&](nlohmann::json msgParams) {
sioClient_->on("new_connection", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
if (onUserConnectFn_)
{
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto callsign = msgParams["callsign"];
auto gridSquare = msgParams["grid_square"];
auto version = msgParams["version"];
auto rxOnly = msgParams["rx_only"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& callsign = msgParams["callsign"];
auto& gridSquare = msgParams["grid_square"];
auto& version = msgParams["version"];
auto& rxOnly = msgParams["rx_only"];
// Only call event handler if we received the correct data types
// for the items in the message.
@ -331,7 +331,7 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("connection_successful", [&](nlohmann::json) {
sioClient_->on("connection_successful", [&](const nlohmann::json&) {
std::unique_lock<std::mutex> lk(objMutex_);
isFullyConnected_ = true;
@ -356,16 +356,16 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("remove_connection", [&](nlohmann::json msgParams) {
sioClient_->on("remove_connection", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
if (onUserDisconnectFn_)
{
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto callsign = msgParams["callsign"];
auto gridSquare = msgParams["grid_square"];
auto version = msgParams["version"];
auto rxOnly = msgParams["rx_only"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& callsign = msgParams["callsign"];
auto& gridSquare = msgParams["grid_square"];
auto& version = msgParams["version"];
auto& rxOnly = msgParams["rx_only"];
// Only call event handler if we received the correct data types
// for the items in the message.
@ -388,17 +388,17 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("tx_report", [&](nlohmann::json msgParams) {
sioClient_->on("tx_report", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
if (onTransmitUpdateFn_)
{
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto callsign = msgParams["callsign"];
auto gridSquare = msgParams["grid_square"];
auto lastTx = msgParams["last_tx"];
auto mode = msgParams["mode"];
auto transmitting = msgParams["transmitting"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& callsign = msgParams["callsign"];
auto& gridSquare = msgParams["grid_square"];
auto& lastTx = msgParams["last_tx"];
auto& mode = msgParams["mode"];
auto& transmitting = msgParams["transmitting"];
// Only call event handler if we received the correct data types
// for the items in the message.
@ -422,15 +422,15 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("rx_report", [&](nlohmann::json msgParams) {
sioClient_->on("rx_report", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto receiverCallsign = msgParams["receiver_callsign"];
auto receiverGridSquare = msgParams["receiver_grid_square"];
auto callsign = msgParams["callsign"];
auto snr = msgParams["snr"];
auto mode = msgParams["mode"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& receiverCallsign = msgParams["receiver_callsign"];
auto& receiverGridSquare = msgParams["receiver_grid_square"];
auto& callsign = msgParams["callsign"];
auto& snr = msgParams["snr"];
auto& mode = msgParams["mode"];
if (onReceiveUpdateFn_)
{
@ -471,15 +471,15 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("freq_change", [&](nlohmann::json msgParams) {
sioClient_->on("freq_change", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
if (onFrequencyChangeFn_)
{
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto callsign = msgParams["callsign"];
auto gridSquare = msgParams["grid_square"];
auto frequency = msgParams["freq"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& callsign = msgParams["callsign"];
auto& gridSquare = msgParams["grid_square"];
auto& frequency = msgParams["freq"];
// Only call event handler if we received the correct data types
// for the items in the message.
@ -500,13 +500,13 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("message_update", [&](nlohmann::json msgParams) {
sioClient_->on("message_update", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
if (onMessageUpdateFn_)
{
auto sid = msgParams["sid"];
auto lastUpdate = msgParams["last_update"];
auto message = msgParams["message"];
auto& sid = msgParams["sid"];
auto& lastUpdate = msgParams["last_update"];
auto& message = msgParams["message"];
// Only call event handler if we received the correct data types
// for the items in the message.
@ -523,11 +523,11 @@ void FreeDVReporter::connect_()
}
});
sioClient_->on("qsy_request", [&](nlohmann::json msgParams) {
sioClient_->on("qsy_request", [&](const nlohmann::json& msgParams) {
std::unique_lock<std::mutex> lk(objMutex_);
auto callsign = msgParams["callsign"];
auto frequency = msgParams["frequency"];
auto message = msgParams["message"];
auto& callsign = msgParams["callsign"];
auto& frequency = msgParams["frequency"];
auto& message = msgParams["message"];
if (onQsyRequestFn_)
{

View File

@ -23,6 +23,7 @@
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
#include <sstream>
#include <algorithm>
#include <cstring>
@ -77,6 +78,7 @@ HamlibRigController::HamlibRigController(std::string rigName, std::string serial
, origFreq_(0)
, origMode_(RIG_MODE_NONE)
, freqOnly_(freqOnly)
, destroying_(false)
, rigResponseTime_(0)
{
// Perform initial load of rig list if this is our first time being created.
@ -99,6 +101,7 @@ HamlibRigController::HamlibRigController(int rigIndex, std::string serialPort, c
, origFreq_(0)
, origMode_(RIG_MODE_NONE)
, freqOnly_(freqOnly)
, destroying_(false)
, rigResponseTime_(0)
{
// Perform initial load of rig list if this is our first time being created.
@ -107,21 +110,21 @@ HamlibRigController::HamlibRigController(int rigIndex, std::string serialPort, c
HamlibRigController::~HamlibRigController()
{
destroying_ = true;
// Disconnect in a synchronous fashion before killing our thread.
std::condition_variable cv;
std::mutex mtx;
std::unique_lock<std::mutex> lk(mtx);
enqueue_([&]() {
std::unique_lock<std::mutex> innerLock(mtx);
std::shared_ptr<std::promise<void>> prom = std::make_shared<std::promise<void>>();
auto fut = prom->get_future();
enqueue_([this, prom]() {
if (rig_ != nullptr)
{
disconnectImpl_();
}
cv.notify_one();
prom->set_value();
});
cv.wait(lk);
fut.wait();
}
static int LogHamlibErrors_(
@ -466,14 +469,16 @@ void HamlibRigController::pttImpl_(bool state)
}
else
{
bool changed = false;
if (pttSet_ != state)
{
changed = true;
onPttChange(this, state);
}
pttSet_ = state;
if (!state)
if (changed && !state && !destroying_)
{
requestCurrentFrequencyMode();
}
@ -741,7 +746,10 @@ freqAttempt:
if (setOkay)
{
currFreq_ = frequencyHz;
requestCurrentFrequencyMode();
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
}
@ -780,6 +788,9 @@ modeAttempt:
if (setOkay)
{
currMode_ = mode;
requestCurrentFrequencyMode();
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
}

View File

@ -87,6 +87,7 @@ private:
uint64_t origFreq_;
rmode_t origMode_;
bool freqOnly_;
bool destroying_;
int rigResponseTime_;

View File

@ -20,6 +20,8 @@
//
//=========================================================================
#include <future>
#include "SerialPortRigController.h"
#include "../util/logging/ulog.h"
@ -34,20 +36,18 @@ SerialPortRigController::SerialPortRigController(std::string serialPort)
SerialPortRigController::~SerialPortRigController()
{
// Disconnect in a synchronous fashion before killing our thread.
std::condition_variable cv;
std::mutex mtx;
std::unique_lock<std::mutex> lk(mtx);
enqueue_([&]() {
std::unique_lock<std::mutex> innerLock(mtx);
std::shared_ptr<std::promise<void>> prom = std::make_shared<std::promise<void>>();
auto fut = prom->get_future();
enqueue_([this, prom]() {
if (serialPortHandle_ != COM_HANDLE_INVALID)
{
disconnectImpl_();
}
cv.notify_one();
prom->set_value();
});
cv.wait(lk);
fut.wait();
}
void SerialPortRigController::connect()

View File

@ -24,6 +24,7 @@
#include <sstream>
#include <chrono>
#include <cassert>
#include <future>
#include "OmniRigController.h"
#include "../../util/logging/ulog.h"
@ -44,27 +45,28 @@ OmniRigController::OmniRigController(int rigId, bool restoreOnDisconnect, bool f
, writableParams_(0)
, freqOnly_(freqOnly)
, rigResponseTime_(0)
, destroying_(false)
{
// empty
}
OmniRigController::~OmniRigController()
{
// Disconnect in a synchronous fashion before killing our thread.
std::condition_variable cv;
std::mutex mtx;
std::unique_lock<std::mutex> lk(mtx);
destroying_ = true;
enqueue_([&]() {
std::unique_lock<std::mutex> innerLock(mtx);
// Disconnect in a synchronous fashion before killing our thread.
std::shared_ptr<std::promise<void>> prom = std::make_shared<std::promise<void>>();
auto fut = prom->get_future();
enqueue_([this, prom]() {
if (rig_ != nullptr)
{
disconnectImpl_();
}
cv.notify_one();
prom->set_value();
});
cv.wait(lk);
fut.wait();
}
void OmniRigController::connect()
@ -264,7 +266,10 @@ void OmniRigController::setFrequencyImpl_(uint64_t frequencyHz)
if (result == S_OK)
{
currFreq_ = frequencyHz;
requestCurrentFrequencyMode();
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
else
{
@ -320,7 +325,10 @@ void OmniRigController::setModeImpl_(IRigFrequencyController::Mode mode)
// Note: wait required to ensure that change takes effect.
std::this_thread::sleep_for(OMNI_RIG_WAIT_TIME);
requestCurrentFrequencyMode();
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
else
{

View File

@ -56,6 +56,7 @@ private:
long writableParams_; // used to help determine VFO
bool freqOnly_;
int rigResponseTime_;
bool destroying_;
void connectImpl_();
void disconnectImpl_();

View File

@ -13,6 +13,7 @@ else(APPLE)
endif(APPLE)
add_library(fdv_util STATIC
Semaphore.cpp
SocketIoClient.cpp
${THREADED_OBJECT_FILES}
ThreadedTimer.cpp

View File

@ -32,8 +32,8 @@ template<typename T>
class GenericFIFO
{
public:
GenericFIFO(int len);
GenericFIFO(const GenericFIFO<T>& rhs);
GenericFIFO(int len, T* inBuf = nullptr);
GenericFIFO(const GenericFIFO<T>& rhs) = delete;
GenericFIFO(GenericFIFO<T>&& rhs);
virtual ~GenericFIFO();
@ -42,23 +42,31 @@ public:
int numUsed() const noexcept;
int numFree() const noexcept;
int capacity() const noexcept;
void reset() noexcept;
private:
T *buf;
T *pin;
T *pout;
int nelem;
bool ownBuffer_;
};
template<typename T>
GenericFIFO<T>::GenericFIFO(int len)
: buf(nullptr)
GenericFIFO<T>::GenericFIFO(int len, T* inBuf)
: buf(inBuf)
, pin(nullptr)
, pout(nullptr)
, nelem(len)
, ownBuffer_(inBuf == nullptr ? true : false)
{
buf = new T[nelem];
assert(buf != nullptr);
if (ownBuffer_)
{
buf = new T[nelem];
assert(buf != nullptr);
}
pin = buf;
pout = buf;
@ -67,27 +75,12 @@ GenericFIFO<T>::GenericFIFO(int len)
template<typename T>
GenericFIFO<T>::~GenericFIFO()
{
if (buf != nullptr)
if (buf != nullptr && ownBuffer_)
{
delete[] buf;
}
}
template<typename T>
GenericFIFO<T>::GenericFIFO(const GenericFIFO<T>& rhs)
: buf(nullptr)
, pin(nullptr)
, pout(nullptr)
, nelem(rhs.nelem)
{
buf = new T[nelem];
assert(buf != nullptr);
memcpy(buf, rhs.buf, sizeof(T) * nelem);
pin = buf + (rhs.pin - rhs.buf);
pout = buf + (rhs.pout - rhs.buf);
}
template<typename T>
GenericFIFO<T>::GenericFIFO(GenericFIFO<T>&& rhs)
: buf(rhs.buf)
@ -101,6 +94,13 @@ GenericFIFO<T>::GenericFIFO(GenericFIFO<T>&& rhs)
rhs.nelem = 0;
}
template<typename T>
void GenericFIFO<T>::reset() noexcept
{
pin = buf;
pout = buf;
}
template<typename T>
int GenericFIFO<T>::write(T* data, int len) noexcept
{
@ -175,4 +175,32 @@ int GenericFIFO<T>::numFree() const noexcept
return nelem - numUsed() - 1;
}
#endif // GENERIC_FIFO_H
template<typename T>
int GenericFIFO<T>::capacity() const noexcept
{
return nelem - 1;
}
template<typename T, int StaticSize>
class PreAllocatedFIFO : public GenericFIFO<T>
{
public:
PreAllocatedFIFO();
virtual ~PreAllocatedFIFO() = default;
// No move/copies possible.
PreAllocatedFIFO(const PreAllocatedFIFO<T, StaticSize>& rhs) = delete;
PreAllocatedFIFO(PreAllocatedFIFO<T, StaticSize>&& rhs) = delete;
private:
T ourBuf[StaticSize];
};
template<typename T, int StaticSize>
PreAllocatedFIFO<T, StaticSize>::PreAllocatedFIFO()
: GenericFIFO<T>(StaticSize, ourBuf)
{
// empty
}
#endif // GENERIC_FIFO_H

View File

@ -0,0 +1,112 @@
//=========================================================================
// Name: Semaphore.cpp
// Purpose: Implements a semaphore.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. This program is
// distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#include <sstream>
#include "Semaphore.h"
#include "logging/ulog.h"
Semaphore::Semaphore()
{
#if defined(_WIN32)
sem_ = CreateSemaphore(nullptr, 0, 1, nullptr);
if (sem_ == nullptr)
{
std::stringstream ss;
ss << "Could not create semaphore (err = " << GetLastError() << ")";
log_error(ss.str().c_str());
}
#elif defined(__APPLE__)
sem_ = dispatch_semaphore_create(0);
if (sem_ == nullptr)
{
log_error("Could not set up semaphore");
}
#else
if (sem_init(&sem_, 0, 0) < 0)
{
log_error("Could not set up semaphore (errno = %d)", errno);
}
#endif // defined(_WIN32) || defined(__APPLE__)
}
Semaphore::~Semaphore()
{
#if defined(_WIN32)
if (sem_ != nullptr)
{
auto tmpSem = sem_;
sem_ = nullptr;
ReleaseSemaphore(tmpSem, 1, nullptr);
CloseHandle(tmpSem);
}
#elif defined(__APPLE__)
if (sem_ != nullptr)
{
dispatch_semaphore_signal(sem_);
dispatch_release(sem_);
}
#else
sem_post(&sem_);
sem_destroy(&sem_);
#endif // defined(_WIN32) || defined(__APPLE__)
}
void Semaphore::signal()
{
#if defined(_WIN32)
if (sem_ != nullptr)
{
ReleaseSemaphore(sem_, 1, nullptr);
}
#elif defined(__APPLE__)
if (sem_ != nullptr)
{
dispatch_semaphore_signal(sem_);
}
#else
sem_post(&sem_);
#endif // defined(_WIN32) || defined(__APPLE__)
}
void Semaphore::wait()
{
#if defined(_WIN32)
DWORD result = WaitForSingleObject(sem_, INFINITE);
if (result != WAIT_TIMEOUT && result != WAIT_OBJECT_0)
{
std::stringstream ss;
ss << "Could not wait on semaphore (err = " << GetLastError() << ")";
log_error(ss.str().c_str());
}
#elif defined(__APPLE__)
if (sem_ != nullptr)
{
dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER);
}
#else
if (sem_wait(&sem_) < 0)
{
log_error("Could not wait on semaphore (errno = %d)", errno);
}
#endif // defined(_WIN32) || defined(__APPLE__)
}

View File

@ -0,0 +1,53 @@
//=========================================================================
// Name: Semaphore.h
// Purpose: Implements a semaphore.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. This program is
// distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#ifndef UTIL_SEMAPHORE_H
#define UTIL_SEMAPHORE_H
#if defined(_WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <dispatch/dispatch.h>
#else
#include <semaphore.h>
#endif // defined(_WIN32) || defined(__APPLE__)
class Semaphore
{
public:
Semaphore();
virtual ~Semaphore();
void wait();
void signal();
private:
#if defined(_WIN32)
HANDLE sem_;
#elif defined(__APPLE__)
dispatch_semaphore_t sem_;
#else
sem_t sem_;
#endif // defined(_WIN32) || defined(__APPLE__)
};
#endif // UTIL_SEMAPHORE_H

View File

@ -63,14 +63,14 @@ void SocketIoClient::on(std::string eventName, SioMessageReceivedFn fn)
void SocketIoClient::emit(std::string eventName, nlohmann::json params)
{
enqueue_([&, eventName, params]() {
enqueue_([this, eventName, params]() {
emitImpl_(eventName, params);
});
}
void SocketIoClient::emit(std::string eventName)
{
enqueue_([&, eventName]() {
enqueue_([this, eventName]() {
emitImpl_(eventName);
});
}
@ -245,6 +245,8 @@ void SocketIoClient::handleSocketIoMessage_(char* ptr, int length)
case '2':
{
// event received from server
const nlohmann::json emptyEvent = {};
nlohmann::json parsedEvent = nlohmann::json::parse(ptr + 1);
std::string eventName = parsedEvent[0];
if (eventFnMap_[eventName])
@ -255,7 +257,7 @@ void SocketIoClient::handleSocketIoMessage_(char* ptr, int length)
}
else
{
(eventFnMap_[eventName])({});
(eventFnMap_[eventName])(emptyEvent);
}
}
break;

View File

@ -33,7 +33,7 @@
class SocketIoClient : public TcpConnectionHandler
{
public:
using SioMessageReceivedFn = std::function<void(nlohmann::json)>;
using SioMessageReceivedFn = std::function<void(const nlohmann::json&)>;
using OnConnectionStateChangeFn = std::function<void()>;
SocketIoClient();

View File

@ -52,6 +52,7 @@ using namespace std::chrono_literals;
#define RX_ATTEMPT_INTERVAL_MS (50)
#define RECONNECT_INTERVAL_MS (5000)
#define RX_BUFFER_SIZE (128 * 1024)
TcpConnectionHandler::TcpConnectionHandler()
: enableReconnect_(false)
@ -60,6 +61,7 @@ TcpConnectionHandler::TcpConnectionHandler()
}, false)
, socket_(-1)
, cancelConnect_(false)
, receiveBuffer_(RX_BUFFER_SIZE)
{
#if defined(WIN32)
// Initialize Winsock in case it hasn't already been done.
@ -555,7 +557,10 @@ void TcpConnectionHandler::sendImpl_(const char* buf, int length)
void TcpConnectionHandler::receiveImpl_()
{
char buf[1024];
constexpr int READ_SIZE_BYTES = 1024;
char buf[READ_SIZE_BYTES];
while (socket_ > 0)
{
fd_set readSet;
@ -566,21 +571,34 @@ again:
int rv = select(socket_ + 1, &readSet, nullptr, nullptr, nullptr);
if (rv > 0)
{
int numRead = 0;
int numHaveRead = 0;
#if defined(WIN32)
int numRead = recv(socket_, buf, 1024, 0);
while ((numRead = recv(socket_, buf, READ_SIZE_BYTES, 0)) > 0)
#else
int numRead = read(socket_, buf, 1024);
while ((numRead = read(socket_, buf, READ_SIZE_BYTES)) > 0)
#endif // defined(WIN32)
if (numRead > 0)
{
// Queue RX handler
char* allocBuf = new char[numRead];
assert(allocBuf != nullptr);
memcpy(allocBuf, buf, numRead);
enqueue_([&, allocBuf, numRead]() {
onReceive_(allocBuf, numRead);
delete[] allocBuf;
numHaveRead += numRead;
receiveBuffer_.write(buf, numRead);
if (numRead < READ_SIZE_BYTES)
{
break;
}
}
if (numHaveRead > 0)
{
enqueue_([&]() {
char tmp[READ_SIZE_BYTES];
int toRead = std::min(receiveBuffer_.numUsed(), READ_SIZE_BYTES);
while (toRead > 0)
{
receiveBuffer_.read(tmp, toRead);
onReceive_(tmp, toRead);
toRead = std::min(receiveBuffer_.numUsed(), READ_SIZE_BYTES);
}
});
// See if there's any other data waiting to be read.

View File

@ -25,6 +25,7 @@
#include "ThreadedObject.h"
#include "ThreadedTimer.h"
#include "GenericFIFO.h"
#include <vector>
#include <future>
@ -57,6 +58,7 @@ private:
std::atomic<bool> ipv4Complete_;
std::atomic<bool> ipv6Complete_;
std::atomic<bool> cancelConnect_;
GenericFIFO<char> receiveBuffer_;
void connectImpl_();
void disconnectImpl_();

View File

@ -44,7 +44,7 @@ ThreadedObject::ThreadedObject(ThreadedObject* parent)
ThreadedObject::~ThreadedObject()
{
// empty
dispatch_release(queue_);
}
void ThreadedObject::enqueue_(std::function<void()> fn, int timeoutMilliseconds)

View File

@ -249,7 +249,7 @@ int MainFrame::VoiceKeyerStartTx(void)
if (wxGetApp().appConfiguration.monitorVoiceKeyerAudio)
{
g_voice_keyer_tx = true;
g_voice_keyer_tx.store(true, std::memory_order_release);
}
}
@ -263,7 +263,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
switch(vk_state) {
case VK_IDLE:
g_voice_keyer_tx = false;
g_voice_keyer_tx.store(false, std::memory_order_release);
if (vk_event == VK_START) {
// sample these puppies at start just in case they are changed while VK running
@ -312,7 +312,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
break;
case VK_RX:
g_voice_keyer_tx = false;
g_voice_keyer_tx.store(false, std::memory_order_release);
// in this state we are receiving and waiting for
// delay timer or valid sync
@ -339,7 +339,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
break;
case VK_SYNC_WAIT:
g_voice_keyer_tx = false;
g_voice_keyer_tx.store(false, std::memory_order_release);
// In this state we wait for valid sync to last
// VK_SYNC_WAIT_TIME seconds
@ -379,7 +379,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
m_togBtnVoiceKeyer->SetValue(false);
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
next_state = VK_IDLE;
g_voice_keyer_tx = false;
g_voice_keyer_tx.store(false, std::memory_order_release);
}
vk_state = next_state;

View File

@ -0,0 +1,190 @@
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=640
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=0
EnableFreqChangesOnly=0
UseAnalogModes=0
IcomCIVHex=0
RigNameStr=Hamlib NET rigctl
PttType=0
SerialRate=0
SerialPort=localhost:4575
PttSerialPort=localhost:4575
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=ZZ0ZZZ
GridSquare=ZZ12ZZ
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=1
DirectionAsCardinal=0
Frequency=14236000
[Reporting/PSKReporter]
Enable=0
[Reporting/FreeDV]
Enable=0
Hostname=qso.freedv.org
CurrentBandFilter=0
UseMetricDistances=1
BandFilterTracksFrequency=0
ForceReceiveOnly=0
StatusText=FreeDV Automated Test System - https://github.com/drowe67/freedv-gui
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

View File

@ -32,7 +32,7 @@ if [ "$OPERATING_SYSTEM" == "Linux" ]; then
fi
# Determine correct record device to retrieve TX data
FREEDV_CONF_FILE=freedv-ctest-reporting.conf
FREEDV_CONF_FILE=freedv-ctest-loss.conf
PLAY_DEVICE="$FREEDV_RADIO_TO_COMPUTER_DEVICE"
if [ "$OPERATING_SYSTEM" == "Linux" ]; then
@ -61,6 +61,9 @@ else
fi
mv $(pwd)/$FREEDV_CONF_FILE.tmp $(pwd)/$FREEDV_CONF_FILE
# Resample test file to 48 kHz. Needed for CI environment to reduce CPU usage.
sox $(pwd)/rade_src/wav/all.wav -r 48000 $(pwd)/tx_in.wav
# Start recording
if [ "$OPERATING_SYSTEM" == "Linux" ]; then
parecord --channels=1 --file-format=wav --device "$REC_DEVICE" test.wav &
@ -69,19 +72,14 @@ else
fi
RECORD_PID=$!
# Start "radio"
TIMES_BEFORE_KILL=1
python3 $SCRIPTPATH/hamlibserver.py $RECORD_PID $TIMES_BEFORE_KILL &
RADIO_PID=$!
# Start FreeDV in test mode to record TX
TX_ARGS="-txfile $(pwd)/rade_src/wav/all.wav -txfeaturefile $(pwd)/txfeatures.f32 "
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut tx -utmode RADE $TX_ARGS >tmp.log 2>&1 &
TX_ARGS="-txfile $(pwd)/tx_in.wav -txfeaturefile $(pwd)/txfeatures.f32 "
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut tx -utmode RADEV1 $TX_ARGS >tmp.log 2>&1 &
FDV_PID=$!
#if [ "$OPERATING_SYSTEM" != "Linux" ]; then
# xctrace record --template "Audio System Trace" --window 2m --output "instruments_trace_${FDV_PID}.trace" --attach $FDV_PID
# xctrace record --template "Audio System Trace" --instrument "Time Profiler" --window 3m --output "instruments_trace_tx_${FDV_PID}.trace" --attach $FDV_PID
#fi
#sleep 30
@ -93,12 +91,13 @@ cat tmp.log
# Stop recording, play back in RX mode
kill $RECORD_PID
#cp $(pwd)/gmon.out $(pwd)/gmon.out.tx
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut rx -utmode RADEV1 -rxfile $(pwd)/test.wav -rxfeaturefile $(pwd)/rxfeatures.f32 >tmp.log 2>&1 &
FDV_PID=$!
#if [ "$OPERATING_SYSTEM" != "Linux" ]; then
# xctrace record --template "Audio System Trace" --window 2m --output "instruments_trace_${FDV_PID}.trace" --attach $FDV_PID
# xctrace record --template "Audio System Trace" --instrument "Time Profiler" --window 3m --output "instruments_trace_rx_${FDV_PID}.trace" --attach $FDV_PID
#fi
wait $FDV_PID
FREEDV_EXIT_CODE=$?
@ -116,7 +115,4 @@ if [ "$OPERATING_SYSTEM" == "Linux" ]; then
pactl unload-module $DRIVER_INDEX_FREEDV_MICROPHONE_TO_COMPUTER
fi
# End radio process as it's no longer needed
kill $RADIO_PID
exit $FREEDV_EXIT_CODE

View File

@ -85,7 +85,7 @@ if [ "$2" == "mpp" ]; then
else
TX_ARGS="-txtime 1 -txattempts 2 "
fi
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut tx -utmode RADE $TX_ARGS >tmp.log 2>&1 &
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut tx -utmode RADEV1 -txfile $(pwd)/rade_src/wav/all.wav $TX_ARGS >tmp.log 2>&1 &
FDV_PID=$!
@ -115,7 +115,7 @@ if [ "$1" != "" ]; then
mv $(pwd)/testwithnoise.wav $(pwd)/test.wav
fi
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut rx -utmode RADE -rxfile $(pwd)/test.wav >tmp.log 2>&1 &
$FREEDV_BINARY -f $(pwd)/$FREEDV_CONF_FILE -ut rx -utmode RADEV1 -rxfile $(pwd)/test.wav >tmp.log 2>&1 &
FDV_PID=$!
#if [ "$OPERATING_SYSTEM" != "Linux" ]; then