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 commitpull/894/headb67313cdef
. * 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 commit11ce99bb6a
. * 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 commit35e81d6ce5
. * Use 20ms blocks instead of 10ms. * Revert "Use 20ms blocks instead of 10ms." This reverts commit1ec6fb308a
. * 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.
parent
12d549f8cd
commit
919bba2c14
|
@ -12,7 +12,7 @@ env:
|
|||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-13, macos-latest] # x86_64, ARM64
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
46
src/eq.cpp
46
src/eq.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -809,7 +809,7 @@ IPipelineStep* FreeDVInterface::createReceivePipeline(
|
|||
auto parallelStep = new ParallelStep(
|
||||
inputSampleRate,
|
||||
outputSampleRate,
|
||||
!singleRxThread_,
|
||||
!singleRxThread_ && parallelSteps.size() > 1,
|
||||
state->preProcessFn,
|
||||
state->postProcessFn,
|
||||
parallelSteps,
|
||||
|
|
|
@ -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"));
|
||||
|
|
13
src/main.cpp
13
src/main.cpp
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue