Further audio dropout improvements (#876)

* Adjust constants based on Instruments analysis.

* Update comments.

* Try reading only FRAME_DURATION at a time during TX.

* Speex step doesn't need to be locked.

* Use shared pointers instead of locking during EqualizerStep.

* Move file read out of the RT context.

* Remove file I/O from RT context for RecordStep too.

* Try requesting 10ms blocks from Windows.

* Allow all GH tests to run.

* Revert back to 20ms frames.

* Minor tweak to Easy Setup window formatting.

* Revert "Revert back to 20ms frames."

This reverts commit b67313cdef.

* Update freedv-ctest-fullduplex.conf.tmpl

Disable multiple RX for testing.

* Turn off multiple RX for reporting tests as well.

* Force single threaded if only one step is available.

* Revert changes to freedv-ctest-fullduplex.conf.tmpl.

* Revert changes to freedv-ctest-reporting.conf.tmpl.

* Need to wake up all threads whenever we get a block of audio.

* Fix compiler errors.

* Revert previous changes as they didn't help.

* Refactor ParallelStep to remove usage of locks.

* Fix compiler errors and warnings.

* Fix issue preventing EOO from being sent.

* Another attempt at fixing the test failures.

* Use semaphores to wake up ParallelStep threads on receipt of new audio data.

* Fix compiler errors.

* Forgot to wake up helper threads.

* Helper threads should wait until ended or until there's data.

* Fix compiler error.

* Increase fifo sizes for FreeDV TX and RX steps.

* macOS: add protection in case semaphore wasn't created.

* Prevent waterfall from clearing itself when RX mode changes.

* Add logic to read in any data that may have come in during processing.

* GitHub Actions: run all Windows tests even if one or more fail.

* Revert "Add logic to read in any data that may have come in during processing."

This reverts commit 11ce99bb6a.

* Meter out only a little bit of data at a time.

* Revert "Meter out only a little bit of data at a time."

This reverts commit 35e81d6ce5.

* Use 20ms blocks instead of 10ms.

* Revert "Use 20ms blocks instead of 10ms."

This reverts commit 1ec6fb308a.

* Update FRAME_DURATION instead.

* Only process entire TX blocks at a time.

* Set sRGB color space for all open windows.

* Change version tag to rc to reflect release candidate status.

* Revert waveform FIFO changes from previous PR.

* libsamplerate needs to be built with optimizations on macOS and Windows.

* Fix Windows build failure.

* Update LLVM MinGW in Windows CI build to match version used for packaging.

* Test: use RADE main to see if Python GC actually matters.
pull/894/head
Mooneer Salem 2025-05-19 14:56:05 -04:00 committed by GitHub
parent 12d549f8cd
commit 919bba2c14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 390 additions and 307 deletions

View File

@ -12,7 +12,7 @@ env:
jobs:
test:
strategy:
fail-fast: true
fail-fast: false
matrix:
os: [macos-13, macos-latest] # x86_64, ARM64

View File

@ -55,15 +55,15 @@ jobs:
shell: bash
working-directory: ${{github.workspace}}
run: |
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20230320/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64.tar.xz
tar xvf llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64.tar.xz
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20250430/llvm-mingw-20250430-ucrt-ubuntu-22.04-x86_64.tar.xz
tar xvf llvm-mingw-20250430-ucrt-ubuntu-22.04-x86_64.tar.xz
- name: Configure freedv-gui
shell: bash
working-directory: ${{github.workspace}}
run: |
export WINEPREFIX=`pwd`/wine-env
export PATH=${{github.workspace}}/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64/bin:$PATH
export PATH=${{github.workspace}}/llvm-mingw-20250430-ucrt-ubuntu-22.04-x86_64/bin:$PATH
mkdir build_windows
cd build_windows
cmake -DLPCNET_DISABLE=1 -DCMAKE_TOOLCHAIN_FILE=${{github.workspace}}/cross-compile/freedv-mingw-llvm-x86_64.cmake -DPython3_ROOT_DIR=$WINEPREFIX/drive_c/Program\ Files/Python312 ..
@ -72,7 +72,7 @@ jobs:
shell: bash
working-directory: ${{github.workspace}}/build_windows
run: |
export PATH=${{github.workspace}}/llvm-mingw-20230320-ucrt-ubuntu-18.04-x86_64/bin:$PATH
export PATH=${{github.workspace}}/llvm-mingw-20250430-ucrt-ubuntu-22.04-x86_64/bin:$PATH
make -j$(nproc) package
- name: Rename installer
@ -159,6 +159,7 @@ jobs:
- name: Test RADE
shell: pwsh
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
if: ${{ !cancelled() }}
run: |
.\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest RADEV1 -NumberOfRuns 1
timeout-minutes: 10
@ -166,6 +167,7 @@ jobs:
- name: Test RADE Reporting
shell: pwsh
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
if: ${{ !cancelled() }}
run: |
.\TestFreeDVReporting.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}"
timeout-minutes: 10
@ -173,6 +175,7 @@ jobs:
- name: Test 700D
shell: pwsh
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
if: ${{ !cancelled() }}
run: |
.\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 700D -NumberOfRuns 1
timeout-minutes: 10
@ -180,6 +183,7 @@ jobs:
- name: Test 700E
shell: pwsh
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
if: ${{ !cancelled() }}
run: |
.\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 700E -NumberOfRuns 1
timeout-minutes: 10
@ -187,6 +191,7 @@ jobs:
- name: Test 1600
shell: pwsh
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
if: ${{ !cancelled() }}
run: |
.\TestFreeDVFullDuplex.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}" -ModeToTest 1600 -NumberOfRuns 1
timeout-minutes: 10

View File

@ -49,7 +49,11 @@ endif(APPLE)
# Adds a tag to the end of the version string. Leave empty
# for official release builds.
if(NOT DEFINED FREEDV_VERSION_TAG)
set(FREEDV_VERSION_TAG "dev2")
set(FREEDV_VERSION_TAG "rc")
# Uncomment the below definition to cause the build to expire
# six months from the day it was built. This should be commented
# for official releases.
add_definitions(-DUNOFFICIAL_RELEASE)
endif(NOT DEFINED FREEDV_VERSION_TAG)

View File

@ -13,7 +13,7 @@ ExternalProject_Add(build_rade
SOURCE_DIR rade_src
BINARY_DIR rade_build
GIT_REPOSITORY https://github.com/drowe67/radae.git
GIT_TAG ms-disable-python-gc
GIT_TAG main
CMAKE_ARGS ${RADE_CMAKE_ARGS}
#CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET}
INSTALL_COMMAND ""

View File

@ -16,6 +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
endif()
list(APPEND FREEDV_LINK_LIBS samplerate)

View File

@ -603,8 +603,7 @@ void MacAudioDevice::setHelperRealTime()
// Define constants determining how much time the audio thread can
// use in a given time quantum. All times are in milliseconds.
//auto sampleBuffer = pow(2, ceil(log(0.01 * sampleRate_) / log(2))); // next power of two
const double kTimeQuantum = AUDIO_SAMPLE_BLOCK_SEC * 1000;
const double kTimeQuantum = 60; // 60ms, 1/2 of a RADEV1 block and confirmed to be sufficient with Instruments analysis.
// Time guaranteed each quantum.
const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;

View File

@ -29,7 +29,7 @@
#include <avrt.h>
#include "../util/logging/ulog.h"
#define BLOCK_TIME_NS (0)
#define BLOCK_TIME_NS (10000000)
// Nanoseconds per REFERENCE_TIME unit
#define NS_PER_REFTIME (100)

View File

@ -6,11 +6,14 @@
#include "main.h"
#include <functional>
using namespace std::placeholders;
extern int g_nSoundCards;
#define SBQ_MAX_ARGS 5
void *MainFrame::designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q, int sampleRate)
std::shared_ptr<void> MainFrame::designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q, int sampleRate)
{
const int STR_LENGTH = 80;
@ -55,7 +58,7 @@ void *MainFrame::designAnEQFilter(const char filterType[], float freqHz, float g
assert(argc <= SBQ_MAX_ARGS);
// Note - the argc count doesn't include the command!
return sox_biquad_create(argc-1, (const char **)arg);
return std::shared_ptr<void>(sox_biquad_create(argc-1, (const char **)arg), [](void* p) { if (p != nullptr) sox_biquad_destroy(p); });
}
void MainFrame::designEQFilters(paCallBackData *cb, int rxSampleRate, int txSampleRate)
@ -91,52 +94,13 @@ void MainFrame::designEQFilters(paCallBackData *cb, int rxSampleRate, int txSam
void MainFrame::deleteEQFilters(paCallBackData *cb)
{
if (cb->sbqMicInBass != nullptr)
{
sox_biquad_destroy(cb->sbqMicInBass);
}
cb->sbqMicInBass = nullptr;
if (cb->sbqMicInTreble != nullptr)
{
sox_biquad_destroy(cb->sbqMicInTreble);
}
cb->sbqMicInTreble = nullptr;
if (cb->sbqMicInMid != nullptr)
{
sox_biquad_destroy(cb->sbqMicInMid);
}
cb->sbqMicInMid = nullptr;
if (cb->sbqMicInVol != nullptr)
{
sox_biquad_destroy(cb->sbqMicInVol);
}
cb->sbqMicInVol = nullptr;
if (cb->sbqSpkOutBass != nullptr)
{
sox_biquad_destroy(cb->sbqSpkOutBass);
}
cb->sbqSpkOutBass = nullptr;
if (cb->sbqSpkOutTreble != nullptr)
{
sox_biquad_destroy(cb->sbqSpkOutTreble);
}
cb->sbqSpkOutTreble = nullptr;
if (cb->sbqSpkOutMid != nullptr)
{
sox_biquad_destroy(cb->sbqSpkOutMid);
}
cb->sbqSpkOutMid = nullptr;
if (cb->sbqSpkOutVol != nullptr)
{
sox_biquad_destroy(cb->sbqSpkOutVol);
}
cb->sbqSpkOutVol = nullptr;
}

View File

@ -809,7 +809,7 @@ IPipelineStep* FreeDVInterface::createReceivePipeline(
auto parallelStep = new ParallelStep(
inputSampleRate,
outputSampleRate,
!singleRxThread_,
!singleRxThread_ && parallelSteps.size() > 1,
state->preProcessFn,
state->postProcessFn,
parallelSteps,

View File

@ -129,7 +129,6 @@ EasySetupDialog::EasySetupDialog(wxWindow* parent, wxWindowID id, const wxString
wxStaticBoxSizer* hamlibBoxSizer = new wxStaticBoxSizer(m_hamlibBox, wxVERTICAL);
wxGridSizer* gridSizerhl = new wxGridSizer(5, 2, 0, 0);
hamlibBoxSizer->Add(gridSizerhl);
setupCatControlBoxSizer->Add(hamlibBoxSizer);
/* Hamlib Rig Type combobox. */
@ -176,6 +175,8 @@ EasySetupDialog::EasySetupDialog(wxWindow* parent, wxWindowID id, const wxString
m_cbPttMethod->Append(wxT("None"));
m_cbPttMethod->Append(wxT("CAT via Data port"));
m_cbPttMethod->SetSelection(0);
setupCatControlBoxSizer->Add(hamlibBoxSizer, 0, wxALL | wxEXPAND, 2);
/* Serial port box */
m_serialBox = new wxStaticBox(setupCatControlBox, wxID_ANY, _("Serial PTT"));

View File

@ -1028,6 +1028,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ")
#endif // defined(__linux__)
terminating_ = false;
realigned_ = false;
// Add config file name to title bar if provided at the command line.
if (wxGetApp().customConfigFileName != "")
@ -1110,17 +1111,17 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ")
// Add Demod Input window
m_panelDemodIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
m_auiNbookCtrl->AddPage(m_panelDemodIn, _("Frm Radio"), true, wxNullBitmap);
g_plotDemodInFifo = codec2_fifo_create(10*WAVEFORM_PLOT_FS);
g_plotDemodInFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
// Add Speech Input window
m_panelSpeechIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
m_auiNbookCtrl->AddPage(m_panelSpeechIn, _("Frm Mic"), true, wxNullBitmap);
g_plotSpeechInFifo = codec2_fifo_create(10*WAVEFORM_PLOT_FS);
g_plotSpeechInFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
// Add Speech Output window
m_panelSpeechOut = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("To Spkr/Hdphns"), true, wxNullBitmap);
g_plotSpeechOutFifo = codec2_fifo_create(10*WAVEFORM_PLOT_FS);
g_plotSpeechOutFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
// Add Timing Offset window
m_panelTimeOffset = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 5.0, DT, -0.5, 0.5, 1, 0.1, "%2.1f", 0);
@ -2051,7 +2052,9 @@ void MainFrame::OnTimer(wxTimerEvent &evt)
mode[STR_LENGTH], bits[STR_LENGTH], errors[STR_LENGTH], ber[STR_LENGTH],
resyncs[STR_LENGTH], clockoffset[STR_LENGTH], freqoffset[STR_LENGTH], syncmetric[STR_LENGTH];
snprintf(mode, STR_LENGTH, "Mode: %s", freedvInterface.getCurrentModeStr()); wxString modeString(mode);
bool relayout = m_textCurrentDecodeMode->GetLabel() != modeString;
bool relayout =
m_textCurrentDecodeMode->GetLabel() != modeString &&
!realigned_;
m_textCurrentDecodeMode->SetLabel(modeString);
if (relayout)
{
@ -2072,6 +2075,8 @@ void MainFrame::OnTimer(wxTimerEvent &evt)
{
SetSize(w, h);
});
realigned_ = true;
}
snprintf(bits, STR_LENGTH, "Bits: %d", freedvInterface.getTotalBits()); wxString bits_string(bits); m_textBits->SetLabel(bits_string);

View File

@ -473,7 +473,7 @@ class MainFrame : public TopFrame
bool m_newMicInFilter;
bool m_newSpkOutFilter;
void* designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q = 0.0, int sampleRate = 8000);
std::shared_ptr<void> designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q = 0.0, int sampleRate = 8000);
void designEQFilters(paCallBackData *cb, int rxSampleRate, int txSampleRate);
void deleteEQFilters(paCallBackData *cb);
@ -496,6 +496,7 @@ class MainFrame : public TopFrame
wxMenuItem* recordNewVoiceKeyerFileMenuItem_;
bool terminating_; // used for terminating FreeDV
bool realigned_; // used to inhibit resize hack once already done
int getSoundCardIDFromName(wxString& name, bool input);
bool validateSoundCardSetup();

View File

@ -72,12 +72,13 @@ void VerifyMicrophonePermissions(std::promise<bool>& microphonePromise)
void ResetMainWindowColorSpace()
{
NSWindow* win = [NSApp mainWindow];
CGColorSpaceRef cs = CGColorSpaceCreateWithName( kCGColorSpaceSRGB );
[win setColorSpace:[[[NSColorSpace alloc]initWithCGColorSpace:cs] autorelease]];
[NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack usingBlock:^(NSWindow *win, BOOL *stop) {
CGColorSpaceRef cs = CGColorSpaceCreateWithName( kCGColorSpaceSRGB );
[win setColorSpace:[[[NSColorSpace alloc]initWithCGColorSpace:cs] autorelease]];
assert(cs != nullptr);
CGColorSpaceRelease(cs);
assert(cs != nullptr);
CGColorSpaceRelease(cs);
}];
}
std::string GetOperatingSystemString()

View File

@ -53,7 +53,7 @@ std::shared_ptr<short> AudioPipeline::execute(std::shared_ptr<short> inputSample
for (size_t index = 0; index < pipelineSteps_.size(); index++)
{
if (resamplers_[index])
if (resamplers_[index] != nullptr)
{
if (resamplers_[index]->getOutputSampleRate() != pipelineSteps_[index]->getInputSampleRate())
{
@ -84,7 +84,7 @@ std::shared_ptr<short> AudioPipeline::execute(std::shared_ptr<short> inputSample
void AudioPipeline::appendPipelineStep(std::shared_ptr<IPipelineStep> pipelineStep)
{
pipelineSteps_.push_back(pipelineStep);
resamplers_.push_back(nullptr); // will be updated by reloadResampler_() below.
resamplers_.resize(pipelineSteps_.size(), nullptr); // will be updated by reloadResampler_() below.
reloadResampler_(pipelineSteps_.size() - 1);
}

View File

@ -27,7 +27,7 @@
#include "../sox_biquad.h"
#include <assert.h>
EqualizerStep::EqualizerStep(int sampleRate, bool* enableFilter, void** bassFilter, void** midFilter, void** trebleFilter, void** volFilter)
EqualizerStep::EqualizerStep(int sampleRate, bool* enableFilter, std::shared_ptr<void>* bassFilter, std::shared_ptr<void>* midFilter, std::shared_ptr<void>* trebleFilter, std::shared_ptr<void>* volFilter)
: sampleRate_(sampleRate)
, enableFilter_(enableFilter)
, bassFilter_(bassFilter)
@ -64,21 +64,25 @@ std::shared_ptr<short> EqualizerStep::execute(std::shared_ptr<short> inputSample
if (*enableFilter_)
{
if (*bassFilter_)
std::shared_ptr<void> tmpBassFilter = *bassFilter_;
std::shared_ptr<void> tmpTrebleFilter = *trebleFilter_;
std::shared_ptr<void> tmpMidFilter = *midFilter_;
std::shared_ptr<void> tmpVolFilter = *volFilter_;
if (tmpBassFilter != nullptr)
{
sox_biquad_filter(*bassFilter_, outputSamples_.get(), outputSamples_.get(), numInputSamples);
sox_biquad_filter(tmpBassFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
if (*trebleFilter_)
if (tmpTrebleFilter != nullptr)
{
sox_biquad_filter(*trebleFilter_, outputSamples_.get(), outputSamples_.get(), numInputSamples);
sox_biquad_filter(tmpTrebleFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
if (*midFilter_)
if (tmpMidFilter != nullptr)
{
sox_biquad_filter(*midFilter_, outputSamples_.get(), outputSamples_.get(), numInputSamples);
sox_biquad_filter(tmpMidFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
if (*volFilter_)
if (tmpVolFilter != nullptr)
{
sox_biquad_filter(*volFilter_, outputSamples_.get(), outputSamples_.get(), numInputSamples);
sox_biquad_filter(tmpVolFilter.get(), outputSamples_.get(), outputSamples_.get(), numInputSamples);
}
}

View File

@ -29,7 +29,7 @@
class EqualizerStep : public IPipelineStep
{
public:
EqualizerStep(int sampleRate, bool* enableFilter, void** bassFilter, void** midFilter, void** trebleFilter, void** volFilter);
EqualizerStep(int sampleRate, bool* enableFilter, std::shared_ptr<void>* bassFilter, std::shared_ptr<void>* midFilter, std::shared_ptr<void>* trebleFilter, std::shared_ptr<void>* volFilter);
virtual ~EqualizerStep();
virtual int getInputSampleRate() const;
@ -40,10 +40,10 @@ public:
private:
int sampleRate_;
bool* enableFilter_;
void** bassFilter_;
void** midFilter_;
void** trebleFilter_;
void** volFilter_;
std::shared_ptr<void>* bassFilter_;
std::shared_ptr<void>* midFilter_;
std::shared_ptr<void>* trebleFilter_;
std::shared_ptr<void>* volFilter_;
std::shared_ptr<short> outputSamples_;
};

View File

@ -37,7 +37,7 @@ FreeDVReceiveStep::FreeDVReceiveStep(struct freedv* dv)
, freqOffsetHz_(0)
{
// Set FIFO to be 2x the number of samples per run so we don't lose anything.
inputSampleFifo_ = codec2_fifo_create(freedv_get_n_max_modem_samples(dv_) * 2);
inputSampleFifo_ = codec2_fifo_create(freedv_get_modem_sample_rate(dv_));
assert(inputSampleFifo_ != nullptr);
rxFreqOffsetPhaseRectObjs_.real = cos(0.0);

View File

@ -35,7 +35,7 @@ FreeDVTransmitStep::FreeDVTransmitStep(struct freedv* dv, std::function<float()>
, inputSampleFifo_(nullptr)
{
// Set FIFO to be 2x the number of samples per run so we don't lose anything.
inputSampleFifo_ = codec2_fifo_create(freedv_get_n_speech_samples(dv_) * 2);
inputSampleFifo_ = codec2_fifo_create(freedv_get_speech_sample_rate(dv_));
assert(inputSampleFifo_ != nullptr);
txFreqOffsetPhaseRectObj_.real = cos(0.0);

View File

@ -22,7 +22,11 @@
#include <chrono>
#include <cassert>
#include <cstring>
#include <sstream>
#include "ParallelStep.h"
#include "AudioPipeline.h"
#include "../util/logging/ulog.h"
using namespace std::chrono_literals;
@ -39,34 +43,104 @@ ParallelStep::ParallelStep(
, runMultiThreaded_(runMultiThreaded)
, inputRouteFn_(inputRouteFn)
, outputRouteFn_(outputRouteFn)
, state_(state)
, realtimeHelper_(realtimeHelper)
, state_(state)
{
for (auto& step : parallelSteps)
{
parallelSteps_.push_back(std::shared_ptr<IPipelineStep>(step));
auto sharedStep = std::shared_ptr<IPipelineStep>(step);
parallelSteps_.push_back(sharedStep);
auto pipeline = std::make_shared<AudioPipeline>(inputSampleRate, outputSampleRate);
pipeline->appendPipelineStep(sharedStep);
auto threadState = new ThreadInfo();
assert(threadState != nullptr);
threads_.push_back(threadState);
threadState->step = 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->exitingThread = false;
if (runMultiThreaded)
{
auto state = new ThreadInfo();
assert(state != nullptr);
// Initialize semaphore
#if defined(_WIN32)
threadState->sem = CreateSemaphore(nullptr, 0, 1, nullptr);
if (threadState->sem == nullptr)
{
std::stringstream ss;
ss << "Could not create semaphore (err = " << GetLastError() << ")";
log_warn(ss.str().c_str());
}
#elif defined(__APPLE__)
threadState->sem = dispatch_semaphore_create(0);
#else
if (sem_init(&threadState->sem, 0, 0) < 0)
{
log_warn("Could not set up semaphore (errno = %d)", errno);
}
#endif // defined(_WIN32) || defined(__APPLE__)
state->exitingThread = false;
state->thread = std::thread([this](ThreadInfo* threadState)
threadState->thread = std::thread([this](ThreadInfo* s)
{
if (realtimeHelper_)
{
realtimeHelper_->setHelperRealTime();
}
executeRunnerThread_(threadState);
while(!s->exitingThread)
{
bool fallbackToSleep = false;
auto beginTime = std::chrono::high_resolution_clock::now();
#if defined(_WIN32) || defined(__APPLE__)
fallbackToSleep = s->sem != nullptr;
#endif // defined(_WIN32) || defined(__APPLE__)
executeRunnerThread_(s);
if (!fallbackToSleep)
{
#if defined(_WIN32)
DWORD result = WaitForSingleObject(s->sem, INFINITE);
if (result != WAIT_TIMEOUT && result != WAIT_OBJECT_0)
{
fallbackToSleep = true;
}
#elif defined(__APPLE__)
if (s->sem != nullptr)
{
dispatch_semaphore_wait(s->sem, DISPATCH_TIME_FOREVER);
}
#else
if (sem_wait(&s->sem) < 0)
{
fallbackToSleep = true;
}
#endif // defined(_WIN32) || defined(__APPLE__)
}
if (fallbackToSleep)
{
std::this_thread::sleep_until(beginTime + 10ms);
}
}
if (realtimeHelper_)
{
realtimeHelper_->clearHelperRealTime();
}
}, state);
threads_.push_back(state);
}, threadState);
}
}
}
@ -75,11 +149,36 @@ ParallelStep::~ParallelStep()
{
// Exit all spawned threads, if any.
for (auto& taskThread : threads_)
{
{
taskThread->exitingThread = true;
taskThread->queueCV.notify_one();
taskThread->thread.join();
if (taskThread->thread.joinable())
{
// Destroy semaphore
#if defined(_WIN32)
if (taskThread->sem != nullptr)
{
auto tmpSem = taskThread->sem;
taskThread->sem = nullptr;
ReleaseSemaphore(tmpSem, 1, nullptr);
CloseHandle(tmpSem);
}
#elif defined(__APPLE__)
if (taskThread->sem != nullptr)
{
dispatch_semaphore_signal(taskThread->sem);
dispatch_release(taskThread->sem);
}
#else
sem_post(&taskThread->sem);
sem_destroy(&taskThread->sem);
#endif // defined(_WIN32) || defined(__APPLE__)
// Join thread
taskThread->thread.join();
}
codec2_fifo_destroy(taskThread->inputFifo);
codec2_fifo_destroy(taskThread->outputFifo);
delete taskThread;
}
}
@ -96,153 +195,68 @@ int ParallelStep::getOutputSampleRate() const
std::shared_ptr<short> ParallelStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
{
// Step 0: determine what steps to execute.
// Step 1: determine what steps to execute.
auto stepToExecute = inputRouteFn_(this);
assert(stepToExecute == -1 || (stepToExecute >= 0 && (size_t)stepToExecute < parallelSteps_.size()));
assert(stepToExecute == -1 || (stepToExecute >= 0 && (size_t)stepToExecute < threads_.size()));
// Step 1: resample inputs as needed
std::map<int, std::future<TaskResult>> resampledInputFutures;
std::map<int, TaskResult> resampledInputs;
for (size_t index = 0; index < parallelSteps_.size(); index++)
// Step 2: execute steps
for (size_t index = 0; index < threads_.size(); index++)
{
ThreadInfo* threadInfo = threads_[index];
memset(threadInfo->tempOutput.get(), 0, sizeof(short) * outputSampleRate_);
if (index == (size_t)stepToExecute || stepToExecute == -1)
{
int destinationSampleRate = parallelSteps_[index]->getInputSampleRate();
if (destinationSampleRate != inputSampleRate_)
codec2_fifo_write(threadInfo->inputFifo, inputSamples.get(), numInputSamples);
if (!runMultiThreaded_)
{
ThreadInfo* threadInfo = nullptr;
if (runMultiThreaded_)
{
threadInfo = threads_[index];
}
if (resampledInputFutures.find(destinationSampleRate) == resampledInputFutures.end())
{
if (resamplers_.find(std::pair<int, int>(inputSampleRate_, destinationSampleRate)) == resamplers_.end())
{
auto tmpStep = std::make_shared<ResampleStep>(inputSampleRate_, destinationSampleRate);
resamplers_[std::pair<int, int>(inputSampleRate_, destinationSampleRate)] = tmpStep;
}
resampledInputFutures[destinationSampleRate] = enqueueTask_(threadInfo, resamplers_[std::pair<int, int>(inputSampleRate_, destinationSampleRate)].get(), inputSamples, numInputSamples);
}
executeRunnerThread_(threadInfo);
}
else
{
resampledInputs[inputSampleRate_] = TaskResult(inputSamples, numInputSamples);
}
}
}
for (auto& fut : resampledInputFutures)
{
resampledInputs[fut.first] = fut.second.get();
}
// Step 2: execute steps
std::vector<std::future<TaskResult>> executedResultFutures;
std::vector<TaskResult> executedResults;
for (size_t index = 0; index < parallelSteps_.size(); index++)
{
if (index == (size_t)stepToExecute || stepToExecute == -1)
{
ThreadInfo* threadInfo = nullptr;
if (runMultiThreaded_)
{
threadInfo = threads_[index];
// Wake up thread
#if defined(_WIN32)
if (threadInfo->sem != nullptr)
{
ReleaseSemaphore(threadInfo->sem, 1, nullptr);
}
#elif defined(__APPLE__)
if (threadInfo->sem != nullptr)
{
dispatch_semaphore_signal(threadInfo->sem);
}
#else
sem_post(&threadInfo->sem);
#endif // defined(_WIN32) || defined(__APPLE__)
}
int destinationSampleRate = parallelSteps_[index]->getInputSampleRate();
auto resampledInput = resampledInputs[destinationSampleRate];
executedResultFutures.push_back(enqueueTask_(threadInfo, parallelSteps_[index].get(), resampledInput.first, resampledInput.second));
if (stepToExecute != -1) break;
}
else
{
std::promise<TaskResult> tempPromise;
tempPromise.set_value(TaskResult(nullptr, 0));
std::future<TaskResult> tempFuture = tempPromise.get_future();
executedResultFutures.push_back(std::move(tempFuture));
}
}
for (auto& fut : executedResultFutures)
{
executedResults.push_back(fut.get());
}
// Step 3: determine which output to return
auto stepToOutput = outputRouteFn_(this);
auto stepToOutput = outputRouteFn_(this);
assert(stepToOutput >= 0 && (size_t)stepToOutput < parallelSteps_.size());
assert(stepToOutput >= 0 && (size_t)stepToOutput < executedResults.size());
TaskResult output = executedResults[stepToOutput];
// Step 4: resample to destination rate
int sourceRate = parallelSteps_[stepToOutput]->getOutputSampleRate();
if (sourceRate == outputSampleRate_)
{
*numOutputSamples = output.second;
return output.first;
}
else
{
if (resamplers_.find(std::pair<int, int>(sourceRate, outputSampleRate_)) == resamplers_.end())
{
auto tmpStep = std::make_shared<ResampleStep>(sourceRate, outputSampleRate_);
resamplers_[std::pair<int, int>(sourceRate, outputSampleRate_)] = tmpStep;
}
return resamplers_[std::pair<int, int>(sourceRate, outputSampleRate_)]->execute(output.first, output.second, numOutputSamples);
}
ThreadInfo* outputTask = threads_[stepToOutput];
*numOutputSamples = codec2_fifo_used(outputTask->outputFifo);
codec2_fifo_read(outputTask->outputFifo, outputTask->tempOutput.get(), *numOutputSamples);
return outputTask->tempOutput;
}
void ParallelStep::executeRunnerThread_(ThreadInfo* threadState)
{
#if defined(__linux__)
pthread_setname_np(pthread_self(), "FreeDV PS");
#endif // defined(__linux__)
while(!threadState->exitingThread)
int samplesIn = codec2_fifo_used(threadState->inputFifo);
int samplesOut = 0;
if (codec2_fifo_read(threadState->inputFifo, threadState->tempInput.get(), samplesIn) != 0)
{
std::unique_lock<std::mutex> lock(threadState->queueMutex);
threadState->queueCV.wait_for(lock, 20ms);
if (!threadState->exitingThread && threadState->tasks.size() > 0)
{
auto& taskEntry = threadState->tasks.front();
lock.unlock();
taskEntry.task(taskEntry.inputSamples, taskEntry.numInputSamples);
lock.lock();
threadState->tasks.pop();
}
samplesIn = 0;
}
auto output = threadState->step->execute(threadState->tempInput, samplesIn, &samplesOut);
if (samplesOut > 0)
{
codec2_fifo_write(threadState->outputFifo, output.get(), samplesOut);
}
}
std::future<ParallelStep::TaskResult> ParallelStep::enqueueTask_(ThreadInfo* taskQueueThread, IPipelineStep* step, std::shared_ptr<short> inputSamples, int numInputSamples)
{
TaskEntry taskEntry;
taskEntry.task = ThreadTask([step](std::shared_ptr<short> inSamples, int numSamples)
{
int numOutputSamples = 0;
auto result = step->execute(inSamples, numSamples, &numOutputSamples);
return TaskResult(result, numOutputSamples);
});
taskEntry.inputSamples = inputSamples;
taskEntry.numInputSamples = numInputSamples;
auto theFuture = taskEntry.task.get_future();
if (taskQueueThread != nullptr)
{
std::unique_lock<std::mutex> lock(taskQueueThread->queueMutex);
taskQueueThread->tasks.push(std::move(taskEntry));
taskQueueThread->queueCV.notify_one();
}
else
{
// Execute the task immediately as there's no thread to post it to.
taskEntry.task(inputSamples, numInputSamples);
}
return theFuture;
}

View File

@ -32,7 +32,16 @@
#include <queue>
#include <map>
#if defined(_WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <dispatch/dispatch.h>
#else
#include <semaphore.h>
#endif // defined(_WIN32) || defined(__APPLE__)
#include "../util/IRealtimeHelper.h"
#include "codec2_fifo.h"
class ParallelStep : public IPipelineStep
{
@ -52,27 +61,27 @@ public:
virtual std::shared_ptr<short> execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples);
const std::vector<std::shared_ptr<IPipelineStep>> getParallelSteps() const { return parallelSteps_; }
std::shared_ptr<void> getState() { return state_; }
private:
typedef std::pair<std::shared_ptr<short>, int> TaskResult;
typedef std::packaged_task<TaskResult(std::shared_ptr<short>, int)> ThreadTask;
struct TaskEntry
{
ThreadTask task;
std::shared_ptr<short> inputSamples;
int numInputSamples;
};
private:
struct ThreadInfo
{
std::thread thread;
std::mutex queueMutex;
std::condition_variable queueCV;
std::queue<TaskEntry> tasks;
bool exitingThread;
std::shared_ptr<IPipelineStep> step;
FIFO* inputFifo;
FIFO* outputFifo;
std::shared_ptr<short> tempInput;
std::shared_ptr<short> tempOutput;
#if defined(_WIN32)
HANDLE sem;
#elif defined(__APPLE__)
dispatch_semaphore_t sem;
#else
sem_t sem;
#endif // defined(_WIN32) || defined(__APPLE__)
};
int inputSampleRate_;
@ -80,14 +89,12 @@ private:
bool runMultiThreaded_;
std::function<int(ParallelStep*)> inputRouteFn_;
std::function<int(ParallelStep*)> outputRouteFn_;
std::vector<std::shared_ptr<IPipelineStep>> parallelSteps_;
std::map<std::pair<int, int>, std::shared_ptr<ResampleStep>> resamplers_;
std::vector<ThreadInfo*> threads_;
std::shared_ptr<void> state_;
std::shared_ptr<IRealtimeHelper> realtimeHelper_;
std::shared_ptr<void> state_;
std::vector<std::shared_ptr<IPipelineStep>> parallelSteps_;
void executeRunnerThread_(ThreadInfo* threadState);
std::future<TaskResult> enqueueTask_(ThreadInfo* taskQueueThread, IPipelineStep* step, std::shared_ptr<short> inputSamples, int numInputSamples);
};
#endif // AUDIO_PIPELINE__PARALLEL_STEP_H

View File

@ -21,17 +21,23 @@
//=========================================================================
#include "PlaybackStep.h"
#include <cassert>
#include <cassert>
#include <chrono>
#include "wx/thread.h"
extern wxMutex g_mutexProtectingCallbackData;
using namespace std::chrono_literals;
PlaybackStep::PlaybackStep(
int inputSampleRate, std::function<int()> fileSampleRateFn,
std::function<SNDFILE*()> getSndFileFn, std::function<void()> fileCompleteFn)
: inputSampleRate_(inputSampleRate)
, fileSampleRateFn_(fileSampleRateFn)
, getSndFileFn_(getSndFileFn)
, fileCompleteFn_(fileCompleteFn)
: inputSampleRate_(inputSampleRate)
, fileSampleRateFn_(fileSampleRateFn)
, getSndFileFn_(getSndFileFn)
, fileCompleteFn_(fileCompleteFn)
, nonRtThreadEnding_(false)
{
// Pre-allocate buffers so we don't have to do so during real-time operation.
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
@ -39,11 +45,24 @@ PlaybackStep::PlaybackStep(
new short[maxSamples],
std::default_delete<short[]>());
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));
}
PlaybackStep::~PlaybackStep()
{
// empty
nonRtThreadEnding_ = true;
if (nonRtThread_.joinable())
{
nonRtThread_.join();
}
codec2_fifo_destroy(outputFifo_);
}
int PlaybackStep::getInputSampleRate() const
@ -58,19 +77,45 @@ int PlaybackStep::getOutputSampleRate() const
std::shared_ptr<short> PlaybackStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
{
auto playFile = getSndFileFn_();
assert(playFile != nullptr);
unsigned int nsf = numInputSamples * getOutputSampleRate()/getInputSampleRate();
*numOutputSamples = 0;
if (nsf > 0)
*numOutputSamples = std::min((unsigned int)codec2_fifo_used(outputFifo_), nsf);
if (*numOutputSamples > 0)
{
*numOutputSamples = sf_read_short(playFile, outputSamples_.get(), nsf);
if ((unsigned)*numOutputSamples < nsf)
{
fileCompleteFn_();
}
codec2_fifo_read(outputFifo_, outputSamples_.get(), *numOutputSamples);
}
return outputSamples_;
}
void PlaybackStep::nonRtThreadEntry_()
{
auto maxSamples = std::max(getInputSampleRate(), getOutputSampleRate());
short* buf = new short[maxSamples];
assert(buf != nullptr);
while (!nonRtThreadEnding_)
{
g_mutexProtectingCallbackData.Lock();
auto playFile = getSndFileFn_();
if (playFile != nullptr)
{
unsigned int nsf = codec2_fifo_free(outputFifo_);
if (nsf > 0)
{
unsigned int numRead = sf_read_short(playFile, buf, nsf);
if (numRead < nsf && codec2_fifo_used(outputFifo_) == 0)
{
fileCompleteFn_();
}
codec2_fifo_write(outputFifo_, buf, numRead);
}
}
g_mutexProtectingCallbackData.Unlock();
std::this_thread::sleep_for(100ms);
}
delete[] buf;
}

View File

@ -24,8 +24,11 @@
#define AUDIO_PIPELINE__PLAYBACK_STEP_H
#include "IPipelineStep.h"
#include <functional>
#include <thread>
#include <sndfile.h>
#include "codec2_fifo.h"
class PlaybackStep : public IPipelineStep
{
@ -45,6 +48,11 @@ private:
std::function<SNDFILE*()> getSndFileFn_;
std::function<void()> fileCompleteFn_;
std::shared_ptr<short> outputSamples_;
std::thread nonRtThread_;
bool nonRtThreadEnding_;
FIFO* outputFifo_;
void nonRtThreadEntry_();
};
#endif // AUDIO_PIPELINE__PLAYBACK_STEP_H

View File

@ -22,19 +22,36 @@
#include "RecordStep.h"
#include <chrono>
#include "wx/thread.h"
extern wxMutex g_mutexProtectingCallbackData;
using namespace std::chrono_literals;
RecordStep::RecordStep(
int inputSampleRate, std::function<SNDFILE*()> getSndFileFn,
std::function<void(int)> isFileCompleteFn)
: inputSampleRate_(inputSampleRate)
, getSndFileFn_(getSndFileFn)
, isFileCompleteFn_(isFileCompleteFn)
: inputSampleRate_(inputSampleRate)
, getSndFileFn_(getSndFileFn)
, isFileCompleteFn_(isFileCompleteFn)
, fileIoThreadEnding_(false)
{
// empty
inputFifo_ = codec2_fifo_create(inputSampleRate_);
assert(inputFifo_ != nullptr);
fileIoThread_ = std::thread(std::bind(&RecordStep::fileIoThreadEntry_, this));
}
RecordStep::~RecordStep()
{
// empty
fileIoThreadEnding_ = true;
if (fileIoThread_.joinable())
{
fileIoThread_.join();
}
codec2_fifo_destroy(inputFifo_);
}
int RecordStep::getInputSampleRate() const
@ -48,12 +65,34 @@ int RecordStep::getOutputSampleRate() const
}
std::shared_ptr<short> RecordStep::execute(std::shared_ptr<short> inputSamples, int numInputSamples, int* numOutputSamples)
{
auto recordFile = getSndFileFn_();
sf_write_short(recordFile, inputSamples.get(), numInputSamples);
isFileCompleteFn_(numInputSamples);
{
codec2_fifo_write(inputFifo_, inputSamples.get(), numInputSamples);
*numOutputSamples = 0;
return std::shared_ptr<short>((short*)nullptr, std::default_delete<short[]>());
}
void RecordStep::fileIoThreadEntry_()
{
short* buf = new short[inputSampleRate_];
assert(buf != nullptr);
while (!fileIoThreadEnding_)
{
g_mutexProtectingCallbackData.Lock();
auto recordFile = getSndFileFn_();
if (recordFile != nullptr)
{
int numInputSamples = codec2_fifo_used(inputFifo_);
codec2_fifo_read(inputFifo_, buf, numInputSamples);
sf_write_short(recordFile, buf, numInputSamples);
isFileCompleteFn_(numInputSamples);
}
g_mutexProtectingCallbackData.Unlock();
std::this_thread::sleep_for(100ms);
}
delete[] buf;
}

View File

@ -24,8 +24,11 @@
#define AUDIO_PIPELINE__RECORD_STEP_H
#include "IPipelineStep.h"
#include <functional>
#include <sndfile.h>
#include <thread>
#include "codec2_fifo.h"
class RecordStep : public IPipelineStep
{
@ -43,6 +46,11 @@ private:
std::function<int()> fileSampleRateFn_;
std::function<SNDFILE*()> getSndFileFn_;
std::function<void(int)> isFileCompleteFn_;
std::thread fileIoThread_;
FIFO* inputFifo_;
bool fileIoThreadEnding_;
void fileIoThreadEntry_();
};
#endif // AUDIO_PIPELINE__RECORD_STEP_H

View File

@ -43,7 +43,6 @@ using namespace std::chrono_literals;
#include "ToneInterfererStep.h"
#include "ComputeRfSpectrumStep.h"
#include "FreeDVReceiveStep.h"
#include "ExclusiveAccessStep.h"
#include "MuteStep.h"
#include "LinkStep.h"
@ -98,7 +97,6 @@ extern FreeDVInterface freedvInterface;
#include <wx/wx.h>
#include "../main.h"
extern wxMutex txModeChangeMutex;
extern wxMutex g_mutexProtectingCallbackData;
extern wxWindow* g_parent;
#include <sndfile.h>
@ -124,16 +122,6 @@ extern int resample(SRC_STATE *src,
void TxRxThread::initializePipeline_()
{
// Function definitions shared across both pipelines.
auto callbackLockFn = []() {
// Prevent priority inversions by bounding the time we can wait for a lock.
g_mutexProtectingCallbackData.LockTimeout(5);
};
auto callbackUnlockFn = []() {
g_mutexProtectingCallbackData.Unlock();
};
if (m_tx)
{
pipeline_ = std::shared_ptr<AudioPipeline>(new AudioPipeline(inputSampleRate_, outputSampleRate_));
@ -158,8 +146,7 @@ void TxRxThread::initializePipeline_()
std::shared_ptr<IPipelineStep>(recordMicTap),
std::shared_ptr<IPipelineStep>(bypassRecordMic)
);
auto recordMicLockStep = new ExclusiveAccessStep(eitherOrRecordMic, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordMicLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordMic));
// Mic In playback step (optional)
auto eitherOrBypassPlay = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -183,8 +170,7 @@ void TxRxThread::initializePipeline_()
[]() { return g_playFileToMicIn && (g_sfPlayFile != NULL); },
std::shared_ptr<IPipelineStep>(eitherOrPlayMicIn),
std::shared_ptr<IPipelineStep>(eitherOrBypassPlay));
auto playMicLockStep = new ExclusiveAccessStep(eitherOrPlayStep, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(playMicLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrPlayStep));
// Speex step (optional)
auto eitherOrProcessSpeex = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -197,8 +183,7 @@ void TxRxThread::initializePipeline_()
[]() { return wxGetApp().appConfiguration.filterConfiguration.speexppEnable; },
std::shared_ptr<IPipelineStep>(eitherOrProcessSpeex),
std::shared_ptr<IPipelineStep>(eitherOrBypassSpeex));
auto speexLockStep = new ExclusiveAccessStep(eitherOrSpeexStep, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(speexLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrSpeexStep));
// Equalizer step (optional based on filter state)
auto equalizerStep = new EqualizerStep(
@ -208,8 +193,7 @@ void TxRxThread::initializePipeline_()
&g_rxUserdata->sbqMicInMid,
&g_rxUserdata->sbqMicInTreble,
&g_rxUserdata->sbqMicInVol);
auto equalizerLockStep = new ExclusiveAccessStep(equalizerStep, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerStep));
// Take TX audio post-equalizer and send it to RX for possible monitoring use.
if (equalizedMicAudioLink_ != nullptr)
@ -264,8 +248,7 @@ void TxRxThread::initializePipeline_()
[]() { return g_recFileFromModulator && (g_sfRecFileFromModulator != NULL); },
std::shared_ptr<IPipelineStep>(recordModulatedTapPipeline),
std::shared_ptr<IPipelineStep>(bypassRecordModulated));
auto recordModulatedLockStep = new ExclusiveAccessStep(eitherOrRecordModulated, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordModulatedLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordModulated));
// TX attenuation step
auto txAttenuationStep = new LevelAdjustStep(outputSampleRate_, []() {
@ -303,8 +286,7 @@ void TxRxThread::initializePipeline_()
std::shared_ptr<IPipelineStep>(recordRadioTap),
std::shared_ptr<IPipelineStep>(bypassRecordRadio)
);
auto recordRadioLockStep = new ExclusiveAccessStep(eitherOrRecordRadio, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(recordRadioLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrRecordRadio));
// Play from radio step (optional)
auto eitherOrBypassPlayRadio = new AudioPipeline(inputSampleRate_, inputSampleRate_);
@ -326,16 +308,12 @@ void TxRxThread::initializePipeline_()
auto eitherOrPlayRadioStep = new EitherOrStep(
[]() {
// Prevent priority inversions by bounding the time we can wait for a lock.
g_mutexProtectingCallbackData.LockTimeout(5);
auto result = g_playFileFromRadio && (g_sfPlayFileFromRadio != NULL);
g_mutexProtectingCallbackData.Unlock();
return result;
},
std::shared_ptr<IPipelineStep>(eitherOrPlayRadio),
std::shared_ptr<IPipelineStep>(eitherOrBypassPlayRadio));
auto playRadioLockStep = new ExclusiveAccessStep(eitherOrPlayRadioStep, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(playRadioLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(eitherOrPlayRadioStep));
// Resample for plot step (demod in)
auto resampleForPlotStep = new ResampleForPlotStep(g_plotDemodInFifo);
@ -451,8 +429,7 @@ void TxRxThread::initializePipeline_()
&g_rxUserdata->sbqSpkOutMid,
&g_rxUserdata->sbqSpkOutTreble,
&g_rxUserdata->sbqSpkOutVol);
auto equalizerLockStep = new ExclusiveAccessStep(equalizerStep, callbackLockFn, callbackUnlockFn);
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerLockStep));
pipeline_->appendPipelineStep(std::shared_ptr<IPipelineStep>(equalizerStep));
// Resample for plot step (speech out)
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);

View File

@ -45,14 +45,14 @@ typedef struct paCallBackData
struct FIFO *rxoutfifo;
// EQ filter states
void *sbqMicInBass;
void *sbqMicInTreble;
void *sbqMicInMid;
void *sbqMicInVol;
void *sbqSpkOutBass;
void *sbqSpkOutTreble;
void *sbqSpkOutMid;
void *sbqSpkOutVol;
std::shared_ptr<void> sbqMicInBass;
std::shared_ptr<void> sbqMicInTreble;
std::shared_ptr<void> sbqMicInMid;
std::shared_ptr<void> sbqMicInVol;
std::shared_ptr<void> sbqSpkOutBass;
std::shared_ptr<void> sbqSpkOutTreble;
std::shared_ptr<void> sbqSpkOutMid;
std::shared_ptr<void> sbqSpkOutVol;
bool micInEQEnable;
bool spkOutEQEnable;