Skip to content

Commit

Permalink
Enable capture of RX features from RADE decoder (#776)
Browse files Browse the repository at this point in the history
* Initial implementation of RX feature capture.

* Allow path to feature file to be specified at the command line.

* Fix compilation error after upgrading MacPorts.

* Add command line option for TX feature capture.

* Add -txfile command line argument to feed in WAV file through TX pipeline.

* Adjust scaling to match PR example.

* Opt for improved resampling audio quality.

* We don't actually need to add additional attenuation anymore.

* Switch over to soxr for further experimentation.

* Forgot change to have Windows build work.

* Update Linux build instructions.

* Fix additional compiler error.

* Update paCallbackData.h

* Remove missed code that's no longer needed.

* Update main.cpp

* Try to reduce latency.

* Another experiment to decrease latency.

* Go back to default settings.

* Fix failing ctests.

* Fix Windows packaging failures.

* Disable ctests for soxr.

* Enable SIMD for aarch64.

* Smooth out gaps in audio caused by how soxr works.

* Fix build errors.

* Ensure we're flushing out our output FIFO if we stop receiving input.

* ctests should now be fixed.

* Revert all samplerate changes. These will go in another PR.
  • Loading branch information
tmiw authored Dec 4, 2024
1 parent 4c9c2c1 commit ce76e7f
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 16 deletions.
4 changes: 2 additions & 2 deletions cmake/Buildportaudio-2.0.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if(NOT portaudio_POPULATED)
list(APPEND FREEDV_PACKAGE_SEARCH_PATHS ${portaudio_BINARY_DIR})
endif()

list(APPEND FREEDV_LINK_LIBS PortAudio)
list(APPEND FREEDV_STATIC_DEPS PortAudio)
list(APPEND FREEDV_LINK_LIBS portaudio)
list(APPEND FREEDV_STATIC_DEPS portaudio)

include_directories(${portaudio_SOURCE_DIR}/include)
88 changes: 76 additions & 12 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ wxConfigBase *pConfig = NULL;
// Unit test management
wxString testName;
wxString utFreeDVMode;
wxString utTxFile;
wxString utRxFile;
wxString utTxFeatureFile;
wxString utRxFeatureFile;

// WxWidgets - initialize the application

Expand Down Expand Up @@ -306,8 +310,26 @@ void MainApp::UnitTest_()
delete txEvent;
});

// Transmit for 60 seconds
std::this_thread::sleep_for(60s);
if (utTxFile != "")
{
// Transmit until file has finished playing
SF_INFO sfInfo;
sfInfo.format = 0;
g_sfPlayFile = sf_open((const char*)utTxFile.ToUTF8(), SFM_READ, &sfInfo);
g_sfTxFs = sfInfo.samplerate;
g_loopPlayFileToMicIn = false;
g_playFileToMicIn = true;

while (g_playFileToMicIn)
{
std::this_thread::sleep_for(20ms);
}
}
else
{
// Transmit for 60 seconds
std::this_thread::sleep_for(60s);
}

// Stop transmitting
log_info("Firing PTT");
Expand All @@ -323,22 +345,40 @@ void MainApp::UnitTest_()
sim.MouseClick();*/

// Wait 5 seconds for FreeDV to stop
std::this_thread::sleep_for(5s);
//std::this_thread::sleep_for(5s);
}
else
{
// Receive for 60 seconds
auto sync = 0;
for (int i = 0; i < 60*10; i++)
if (utRxFile != "")
{
std::this_thread::sleep_for(100ms);
auto newSync = freedvInterface.getSync();
if (newSync != sync)
// Receive until file has finished playing
SF_INFO sfInfo;
sfInfo.format = 0;
g_sfPlayFileFromRadio = sf_open((const char*)utRxFile.ToUTF8(), SFM_READ, &sfInfo);
g_sfFs = sfInfo.samplerate;
g_loopPlayFileFromRadio = false;
g_playFileFromRadio = true;

while (g_playFileFromRadio)
{
log_info("Sync changed from %d to %d", sync, newSync);
sync = newSync;
std::this_thread::sleep_for(20ms);
}
}
}
else
{
// Receive for 60 seconds
auto sync = 0;
for (int i = 0; i < 60*10; i++)
{
std::this_thread::sleep_for(100ms);
auto newSync = freedvInterface.getSync();
if (newSync != sync)
{
log_info("Sync changed from %d to %d", sync, newSync);
sync = newSync;
}
}
}
}

// Fire event to stop FreeDV
Expand Down Expand Up @@ -369,6 +409,10 @@ void MainApp::OnInitCmdLine(wxCmdLineParser& parser)
parser.AddOption("f", "config", "Use different configuration file instead of the default.");
parser.AddOption("ut", "unit_test", "Execute FreeDV in unit test mode.");
parser.AddOption("utmode", wxEmptyString, "Switch FreeDV to the given mode before UT execution.");
parser.AddOption("rxfile", wxEmptyString, "In UT mode, pipes given WAV file through receive pipeline.");
parser.AddOption("txfile", wxEmptyString, "In UT mode, pipes given WAV file through transmit pipeline.");
parser.AddOption("rxfeaturefile", wxEmptyString, "Capture RX features from RADE decoder into the provided file.");
parser.AddOption("txfeaturefile", wxEmptyString, "Capture TX features from FARGAN encoder into the provided file.");
}

bool MainApp::OnCmdLineParsed(wxCmdLineParser& parser)
Expand Down Expand Up @@ -403,6 +447,26 @@ bool MainApp::OnCmdLineParsed(wxCmdLineParser& parser)
{
log_info("Using mode %s for tests", (const char*)utFreeDVMode.ToUTF8());
}

if (parser.Found("rxfile", &utRxFile))
{
log_info("Piping %s through RX pipeline", (const char*)utRxFile.ToUTF8());
}

if (parser.Found("txfile", &utTxFile))
{
log_info("Piping %s through TX pipeline", (const char*)utTxFile.ToUTF8());
}
}

if (parser.Found("rxfeaturefile", &utRxFeatureFile))
{
log_info("Capturing RADE RX features into file %s", (const char*)utRxFeatureFile.ToUTF8());
}

if (parser.Found("txfeaturefile", &utTxFeatureFile))
{
log_info("Capturing RADE TX features into file %s", (const char*)utTxFeatureFile.ToUTF8());
}

return true;
Expand Down
19 changes: 19 additions & 0 deletions src/pipeline/RADEReceiveStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
#include "../defines.h"
#include "lpcnet.h" // from Opus source tree

extern wxString utRxFeatureFile;

RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan)
: dv_(dv)
, fargan_(fargan)
, inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr)
, featuresFile_(nullptr)
{
// Set FIFO to be 2x the number of samples per run so we don't lose anything.
inputSampleFifo_ = codec2_fifo_create(rade_nin_max(dv_) * 2);
Expand All @@ -38,10 +41,21 @@ RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan)
// Enough for one second of audio. Probably way overkill.
outputSampleFifo_ = codec2_fifo_create(16000);
assert(outputSampleFifo_ != nullptr);

if (utRxFeatureFile != "")
{
featuresFile_ = fopen((const char*)utRxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
}
}

RADEReceiveStep::~RADEReceiveStep()
{
if (featuresFile_ != nullptr)
{
fclose(featuresFile_);
}

if (inputSampleFifo_ != nullptr)
{
codec2_fifo_free(inputSampleFifo_);
Expand Down Expand Up @@ -93,6 +107,11 @@ std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamp

// RADE processing (input signal->features).
nout = rade_rx(dv_, features_out, input_buf_cplx);
if (featuresFile_)
{
fwrite(features_out, sizeof(float), nout, featuresFile_);
}

for (int i = 0; i < nout; i++)
{
pendingFeatures_.push_back(features_out[i]);
Expand Down
3 changes: 3 additions & 0 deletions src/pipeline/RADEReceiveStep.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#ifndef AUDIO_PIPELINE__RADE_RECEIVE_STEP_H
#define AUDIO_PIPELINE__RADE_RECEIVE_STEP_H

#include <cstdio>
#include <vector>
#include "IPipelineStep.h"
#include "../freedv_interface.h"
Expand Down Expand Up @@ -53,6 +54,8 @@ class RADEReceiveStep : public IPipelineStep
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
std::vector<float> pendingFeatures_;

FILE* featuresFile_;
};

#endif // AUDIO_PIPELINE__RADE_RECEIVE_STEP_H
23 changes: 22 additions & 1 deletion src/pipeline/RADETransmitStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,38 @@
#include <cstring>
#include <cassert>
#include <cmath>
#include "../defines.h"
#include "codec2_fifo.h"
#include "RADETransmitStep.h"

extern wxString utTxFeatureFile;

RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState)
: dv_(dv)
, encState_(encState)
, inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr)
, featuresFile_(nullptr)
{
inputSampleFifo_ = codec2_fifo_create(RADE_SPEECH_SAMPLE_RATE);
assert(inputSampleFifo_ != nullptr);
outputSampleFifo_ = codec2_fifo_create(RADE_MODEM_SAMPLE_RATE);
assert(outputSampleFifo_ != nullptr);

if (utTxFeatureFile != "")
{
featuresFile_ = fopen((const char*)utTxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
}
}

RADETransmitStep::~RADETransmitStep()
{
if (featuresFile_ != nullptr)
{
fclose(featuresFile_);
}

if (inputSampleFifo_ != nullptr)
{
codec2_fifo_free(inputSampleFifo_);
Expand Down Expand Up @@ -86,6 +101,12 @@ std::shared_ptr<short> RADETransmitStep::execute(std::shared_ptr<short> inputSam
// Feature extraction
codec2_fifo_read(inputSampleFifo_, pcm, LPCNET_FRAME_SIZE);
lpcnet_compute_single_frame_features(encState_, pcm, features, arch);

if (featuresFile_)
{
fwrite(features, sizeof(float), NB_TOTAL_FEATURES, featuresFile_);
}

for (int index = 0; index < NB_TOTAL_FEATURES; index++)
{
featureList_.push_back(features[index]);
Expand All @@ -102,7 +123,7 @@ std::shared_ptr<short> RADETransmitStep::execute(std::shared_ptr<short> inputSam
for (int index = 0; index < numOutputSamples; index++)
{
// We only need the real component for TX.
radeOutShort[index] = radeOut[index].real * 32767;
radeOutShort[index] = radeOut[index].real * 16383;
}
codec2_fifo_write(outputSampleFifo_, radeOutShort, numOutputSamples);
}
Expand Down
3 changes: 3 additions & 0 deletions src/pipeline/RADETransmitStep.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#ifndef AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H
#define AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H

#include <cstdio>
#include <vector>
#include "IPipelineStep.h"
#include "../freedv_interface.h"
Expand All @@ -48,6 +49,8 @@ class RADETransmitStep : public IPipelineStep
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
std::vector<float> featureList_;

FILE* featuresFile_;
};

#endif // AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H
2 changes: 1 addition & 1 deletion src/pipeline/ResampleStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ ResampleStep::ResampleStep(int inputSampleRate, int outputSampleRate)
, outputSampleRate_(outputSampleRate)
{
int src_error;
resampleState_ = src_new(SRC_SINC_FASTEST, 1, &src_error);
resampleState_ = src_new(SRC_SINC_MEDIUM_QUALITY, 1, &src_error);
assert(resampleState_ != nullptr);
}

Expand Down
2 changes: 2 additions & 0 deletions src/pipeline/TxRxThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,13 @@ void TxRxThread::initializePipeline_()
auto txAttenuationStep = new LevelAdjustStep(outputSampleRate_, []() {
double dbLoss = g_txLevel / 10.0;

#if 0
if (freedvInterface.getTxMode() == FREEDV_MODE_RADE)
{
// Attenuate by 4 dB as there's no BPF; anything louder distorts the signal
dbLoss -= 4.0;
}
#endif // 0

double scaleFactor = exp(dbLoss/20.0 * log(10.0));
return scaleFactor;
Expand Down

0 comments on commit ce76e7f

Please sign in to comment.