Enable capture of RX features from RADE decoder (#776)

* 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.
ms-suppress-rade-output
Mooneer Salem 2024-12-03 23:51:44 -08:00 committed by GitHub
parent 4c9c2c1529
commit ce76e7fb2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 128 additions and 16 deletions

View File

@ -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)

View File

@ -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
@ -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");
@ -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
@ -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)
@ -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;

View File

@ -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);
@ -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_);
@ -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]);

View File

@ -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"
@ -53,6 +54,8 @@ private:
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
std::vector<float> pendingFeatures_;
FILE* featuresFile_;
};
#endif // AUDIO_PIPELINE__RADE_RECEIVE_STEP_H

View File

@ -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_);
@ -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]);
@ -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);
}

View File

@ -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"
@ -48,6 +49,8 @@ private:
struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_;
std::vector<float> featureList_;
FILE* featuresFile_;
};
#endif // AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H

View File

@ -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);
}

View File

@ -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;