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.
pull/786/head
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}) list(APPEND FREEDV_PACKAGE_SEARCH_PATHS ${portaudio_BINARY_DIR})
endif() endif()
list(APPEND FREEDV_LINK_LIBS PortAudio) list(APPEND FREEDV_LINK_LIBS portaudio)
list(APPEND FREEDV_STATIC_DEPS PortAudio) list(APPEND FREEDV_STATIC_DEPS portaudio)
include_directories(${portaudio_SOURCE_DIR}/include) include_directories(${portaudio_SOURCE_DIR}/include)

View File

@ -191,6 +191,10 @@ wxConfigBase *pConfig = NULL;
// Unit test management // Unit test management
wxString testName; wxString testName;
wxString utFreeDVMode; wxString utFreeDVMode;
wxString utTxFile;
wxString utRxFile;
wxString utTxFeatureFile;
wxString utRxFeatureFile;
// WxWidgets - initialize the application // WxWidgets - initialize the application
@ -306,8 +310,26 @@ void MainApp::UnitTest_()
delete txEvent; delete txEvent;
}); });
// Transmit for 60 seconds if (utTxFile != "")
std::this_thread::sleep_for(60s); {
// 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 // Stop transmitting
log_info("Firing PTT"); log_info("Firing PTT");
@ -323,22 +345,40 @@ void MainApp::UnitTest_()
sim.MouseClick();*/ sim.MouseClick();*/
// Wait 5 seconds for FreeDV to stop // Wait 5 seconds for FreeDV to stop
std::this_thread::sleep_for(5s); //std::this_thread::sleep_for(5s);
} }
else else
{ {
// Receive for 60 seconds if (utRxFile != "")
auto sync = 0;
for (int i = 0; i < 60*10; i++)
{ {
std::this_thread::sleep_for(100ms); // Receive until file has finished playing
auto newSync = freedvInterface.getSync(); SF_INFO sfInfo;
if (newSync != sync) 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); std::this_thread::sleep_for(20ms);
sync = newSync;
} }
} }
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 // 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("f", "config", "Use different configuration file instead of the default.");
parser.AddOption("ut", "unit_test", "Execute FreeDV in unit test mode."); 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("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) 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()); 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; return true;

View File

@ -25,11 +25,14 @@
#include "../defines.h" #include "../defines.h"
#include "lpcnet.h" // from Opus source tree #include "lpcnet.h" // from Opus source tree
extern wxString utRxFeatureFile;
RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan) RADEReceiveStep::RADEReceiveStep(struct rade* dv, FARGANState* fargan)
: dv_(dv) : dv_(dv)
, fargan_(fargan) , fargan_(fargan)
, inputSampleFifo_(nullptr) , inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr) , outputSampleFifo_(nullptr)
, featuresFile_(nullptr)
{ {
// Set FIFO to be 2x the number of samples per run so we don't lose anything. // 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); 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. // Enough for one second of audio. Probably way overkill.
outputSampleFifo_ = codec2_fifo_create(16000); outputSampleFifo_ = codec2_fifo_create(16000);
assert(outputSampleFifo_ != nullptr); assert(outputSampleFifo_ != nullptr);
if (utRxFeatureFile != "")
{
featuresFile_ = fopen((const char*)utRxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
}
} }
RADEReceiveStep::~RADEReceiveStep() RADEReceiveStep::~RADEReceiveStep()
{ {
if (featuresFile_ != nullptr)
{
fclose(featuresFile_);
}
if (inputSampleFifo_ != nullptr) if (inputSampleFifo_ != nullptr)
{ {
codec2_fifo_free(inputSampleFifo_); codec2_fifo_free(inputSampleFifo_);
@ -93,6 +107,11 @@ std::shared_ptr<short> RADEReceiveStep::execute(std::shared_ptr<short> inputSamp
// RADE processing (input signal->features). // RADE processing (input signal->features).
nout = rade_rx(dv_, features_out, input_buf_cplx); 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++) for (int i = 0; i < nout; i++)
{ {
pendingFeatures_.push_back(features_out[i]); pendingFeatures_.push_back(features_out[i]);

View File

@ -23,6 +23,7 @@
#ifndef AUDIO_PIPELINE__RADE_RECEIVE_STEP_H #ifndef AUDIO_PIPELINE__RADE_RECEIVE_STEP_H
#define AUDIO_PIPELINE__RADE_RECEIVE_STEP_H #define AUDIO_PIPELINE__RADE_RECEIVE_STEP_H
#include <cstdio>
#include <vector> #include <vector>
#include "IPipelineStep.h" #include "IPipelineStep.h"
#include "../freedv_interface.h" #include "../freedv_interface.h"
@ -53,6 +54,8 @@ private:
struct FIFO* inputSampleFifo_; struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_; struct FIFO* outputSampleFifo_;
std::vector<float> pendingFeatures_; std::vector<float> pendingFeatures_;
FILE* featuresFile_;
}; };
#endif // AUDIO_PIPELINE__RADE_RECEIVE_STEP_H #endif // AUDIO_PIPELINE__RADE_RECEIVE_STEP_H

View File

@ -23,23 +23,38 @@
#include <cstring> #include <cstring>
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
#include "../defines.h"
#include "codec2_fifo.h" #include "codec2_fifo.h"
#include "RADETransmitStep.h" #include "RADETransmitStep.h"
extern wxString utTxFeatureFile;
RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState) RADETransmitStep::RADETransmitStep(struct rade* dv, LPCNetEncState* encState)
: dv_(dv) : dv_(dv)
, encState_(encState) , encState_(encState)
, inputSampleFifo_(nullptr) , inputSampleFifo_(nullptr)
, outputSampleFifo_(nullptr) , outputSampleFifo_(nullptr)
, featuresFile_(nullptr)
{ {
inputSampleFifo_ = codec2_fifo_create(RADE_SPEECH_SAMPLE_RATE); inputSampleFifo_ = codec2_fifo_create(RADE_SPEECH_SAMPLE_RATE);
assert(inputSampleFifo_ != nullptr); assert(inputSampleFifo_ != nullptr);
outputSampleFifo_ = codec2_fifo_create(RADE_MODEM_SAMPLE_RATE); outputSampleFifo_ = codec2_fifo_create(RADE_MODEM_SAMPLE_RATE);
assert(outputSampleFifo_ != nullptr); assert(outputSampleFifo_ != nullptr);
if (utTxFeatureFile != "")
{
featuresFile_ = fopen((const char*)utTxFeatureFile.ToUTF8(), "wb");
assert(featuresFile_ != nullptr);
}
} }
RADETransmitStep::~RADETransmitStep() RADETransmitStep::~RADETransmitStep()
{ {
if (featuresFile_ != nullptr)
{
fclose(featuresFile_);
}
if (inputSampleFifo_ != nullptr) if (inputSampleFifo_ != nullptr)
{ {
codec2_fifo_free(inputSampleFifo_); codec2_fifo_free(inputSampleFifo_);
@ -86,6 +101,12 @@ std::shared_ptr<short> RADETransmitStep::execute(std::shared_ptr<short> inputSam
// Feature extraction // Feature extraction
codec2_fifo_read(inputSampleFifo_, pcm, LPCNET_FRAME_SIZE); codec2_fifo_read(inputSampleFifo_, pcm, LPCNET_FRAME_SIZE);
lpcnet_compute_single_frame_features(encState_, pcm, features, arch); 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++) for (int index = 0; index < NB_TOTAL_FEATURES; index++)
{ {
featureList_.push_back(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++) for (int index = 0; index < numOutputSamples; index++)
{ {
// We only need the real component for TX. // 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); codec2_fifo_write(outputSampleFifo_, radeOutShort, numOutputSamples);
} }

View File

@ -23,6 +23,7 @@
#ifndef AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H #ifndef AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H
#define AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H #define AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H
#include <cstdio>
#include <vector> #include <vector>
#include "IPipelineStep.h" #include "IPipelineStep.h"
#include "../freedv_interface.h" #include "../freedv_interface.h"
@ -48,6 +49,8 @@ private:
struct FIFO* inputSampleFifo_; struct FIFO* inputSampleFifo_;
struct FIFO* outputSampleFifo_; struct FIFO* outputSampleFifo_;
std::vector<float> featureList_; std::vector<float> featureList_;
FILE* featuresFile_;
}; };
#endif // AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H #endif // AUDIO_PIPELINE__RADE_TRANSMIT_STEP_H

View File

@ -73,7 +73,7 @@ ResampleStep::ResampleStep(int inputSampleRate, int outputSampleRate)
, outputSampleRate_(outputSampleRate) , outputSampleRate_(outputSampleRate)
{ {
int src_error; 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); assert(resampleState_ != nullptr);
} }

View File

@ -268,11 +268,13 @@ void TxRxThread::initializePipeline_()
auto txAttenuationStep = new LevelAdjustStep(outputSampleRate_, []() { auto txAttenuationStep = new LevelAdjustStep(outputSampleRate_, []() {
double dbLoss = g_txLevel / 10.0; double dbLoss = g_txLevel / 10.0;
#if 0
if (freedvInterface.getTxMode() == FREEDV_MODE_RADE) if (freedvInterface.getTxMode() == FREEDV_MODE_RADE)
{ {
// Attenuate by 4 dB as there's no BPF; anything louder distorts the signal // Attenuate by 4 dB as there's no BPF; anything louder distorts the signal
dbLoss -= 4.0; dbLoss -= 4.0;
} }
#endif // 0
double scaleFactor = exp(dbLoss/20.0 * log(10.0)); double scaleFactor = exp(dbLoss/20.0 * log(10.0));
return scaleFactor; return scaleFactor;