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 commitms-hamlib-friendly-error8f53d1a980. * 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 commit1273d6531e. * 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 commit86d1c640ff. * 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 commit711f83be0c. * 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 commit364bc62e8c. * Revert "Try using relaxed memory ordering." This reverts commitbac5a8a799. * 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 commita913e8573e. * Revert "macOS: Begin wait from end of work." This reverts commit7c719a3e3c. * 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 commitdecff7a5b2. * 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 commite1522a7ab3. * 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 commit3ca16e0335. * 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 commit0ee65a38eb. * 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.
parent
e93f8c2d42
commit
12844f67c5
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ private:
|
|||
AudioBufferList* bufferList_;
|
||||
bool running_;
|
||||
int chosenFrameSize_;
|
||||
std::atomic<int> numRealTimeWorkers_;
|
||||
|
||||
void joinWorkgroup_();
|
||||
void leaveWorkgroup_();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
|||
786
src/main.cpp
786
src/main.cpp
File diff suppressed because it is too large
Load Diff
28
src/main.h
28
src/main.h
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,17 @@ void ResetMainWindowColorSpace()
|
|||
// empty
|
||||
}
|
||||
|
||||
void StartLowLatencyActivity()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
void StopLowLatencyActivity()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
std::string GetOperatingSystemString()
|
||||
{
|
||||
return "windows";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ private:
|
|||
uint64_t origFreq_;
|
||||
rmode_t origMode_;
|
||||
bool freqOnly_;
|
||||
bool destroying_;
|
||||
|
||||
int rigResponseTime_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ private:
|
|||
long writableParams_; // used to help determine VFO
|
||||
bool freqOnly_;
|
||||
int rigResponseTime_;
|
||||
bool destroying_;
|
||||
|
||||
void connectImpl_();
|
||||
void disconnectImpl_();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ else(APPLE)
|
|||
endif(APPLE)
|
||||
|
||||
add_library(fdv_util STATIC
|
||||
Semaphore.cpp
|
||||
SocketIoClient.cpp
|
||||
${THREADED_OBJECT_FILES}
|
||||
ThreadedTimer.cpp
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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_();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ ThreadedObject::ThreadedObject(ThreadedObject* parent)
|
|||
|
||||
ThreadedObject::~ThreadedObject()
|
||||
{
|
||||
// empty
|
||||
dispatch_release(queue_);
|
||||
}
|
||||
|
||||
void ThreadedObject::enqueue_(std::function<void()> fn, int timeoutMilliseconds)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue