From 1d07999d373b0fb0b67de576aa4d7b10308b4150 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 May 2023 22:04:21 +0200 Subject: Avoid undefined format conversion and measure clipping --- src/DabMod.cpp | 28 ++++---- src/DabModulator.cpp | 170 +++++++++++++++++++++++++++--------------------- src/DabModulator.h | 26 ++++---- src/FormatConverter.cpp | 60 ++++++++++++++--- src/FormatConverter.h | 7 +- 5 files changed, 181 insertions(+), 110 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index e5436ad..805fab5 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -364,19 +364,25 @@ int launch_modulator(int argc, char* argv[]) etiLog.level(debug) << "FFTW planning done."; } - shared_ptr format_converter; + std::string output_format; if (mod_settings.useFileOutput and (mod_settings.fileOutputFormat == "s8" or mod_settings.fileOutputFormat == "u8" or mod_settings.fileOutputFormat == "s16")) { - format_converter = make_shared(mod_settings.fileOutputFormat); + output_format = mod_settings.fileOutputFormat; } else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) { - format_converter = make_shared("s16"); + output_format = "s16"; } auto output = prepare_output(mod_settings); + if (not output_format.empty()) { + if (auto o = dynamic_pointer_cast(output)) { + o->set_sample_size(FormatConverter::get_format_size(output_format)); + } + } + // Set thread priority to realtime if (int r = set_realtime_prio(1)) { etiLog.level(error) << "Could not set priority for modulator:" << r; @@ -426,25 +432,15 @@ int launch_modulator(int argc, char* argv[]) shared_ptr modulator; if (inputReader) { m.etiReader = make_shared(mod_settings.tist_offset_s); - modulator = make_shared(*m.etiReader, mod_settings); + modulator = make_shared(*m.etiReader, mod_settings, output_format); } else if (ediInput) { - modulator = make_shared(ediInput->ediReader, mod_settings); + modulator = make_shared(ediInput->ediReader, mod_settings, output_format); } rcs.enrol(modulator.get()); - if (format_converter) { - flowgraph.connect(modulator, format_converter); - flowgraph.connect(format_converter, output); - - if (auto o = dynamic_pointer_cast(output)) { - o->set_sample_size(format_converter->get_format_size()); - } - } - else { - flowgraph.connect(modulator, output); - } + flowgraph.connect(modulator, output); if (inputReader) { etiLog.level(info) << inputReader->GetPrintableInfo(); diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 64ebf03..5213d8d 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -59,19 +59,22 @@ using namespace std; DabModulator::DabModulator(EtiSource& etiSource, - mod_settings_t& settings) : + mod_settings_t& settings, + const std::string& format) : ModInput(), RemoteControllable("modulator"), m_settings(settings), - myEtiSource(etiSource), - myFlowgraph() + m_format(format), + m_etiSource(etiSource), + m_flowgraph() { PDEBUG("DabModulator::DabModulator() @ %p\n", this); RC_ADD_PARAMETER(rate, "(Read-only) IQ output samplerate"); + RC_ADD_PARAMETER(num_clipped_samples, "(Read-only) Number of samples clipped in last frame during format conversion"); if (m_settings.dabMode == 0) { - setMode(2); + setMode(1); } else { setMode(m_settings.dabMode); @@ -83,36 +86,36 @@ void DabModulator::setMode(unsigned mode) { switch (mode) { case 1: - myNbSymbols = 76; - myNbCarriers = 1536; - mySpacing = 2048; - myNullSize = 2656; - mySymSize = 2552; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 1536; + m_spacing = 2048; + m_nullSize = 2656; + m_symSize = 2552; + m_ficSizeOut = 288; break; case 2: - myNbSymbols = 76; - myNbCarriers = 384; - mySpacing = 512; - myNullSize = 664; - mySymSize = 638; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 384; + m_spacing = 512; + m_nullSize = 664; + m_symSize = 638; + m_ficSizeOut = 288; break; case 3: - myNbSymbols = 153; - myNbCarriers = 192; - mySpacing = 256; - myNullSize = 345; - mySymSize = 319; - myFicSizeOut = 384; + m_nbSymbols = 153; + m_nbCarriers = 192; + m_spacing = 256; + m_nullSize = 345; + m_symSize = 319; + m_ficSizeOut = 384; break; case 4: - myNbSymbols = 76; - myNbCarriers = 768; - mySpacing = 1024; - myNullSize = 1328; - mySymSize = 1276; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 768; + m_spacing = 1024; + m_nullSize = 1328; + m_symSize = 1276; + m_ficSizeOut = 288; break; default: throw std::runtime_error("DabModulator::setMode invalid mode size"); @@ -126,27 +129,27 @@ int DabModulator::process(Buffer* dataOut) PDEBUG("DabModulator::process(dataOut: %p)\n", dataOut); - if (not myFlowgraph) { + if (not m_flowgraph) { etiLog.level(debug) << "Setting up DabModulator..."; const unsigned mode = m_settings.dabMode; setMode(mode); - myFlowgraph = make_shared(m_settings.showProcessTime); + m_flowgraph = make_shared(m_settings.showProcessTime); //////////////////////////////////////////////////////////////// // CIF data initialisation //////////////////////////////////////////////////////////////// auto cifPrbs = make_shared(864 * 8, 0x110); - auto cifMux = make_shared(myEtiSource); + auto cifMux = make_shared(m_etiSource); auto cifPart = make_shared(mode); - auto cifMap = make_shared(myNbCarriers); + auto cifMap = make_shared(m_nbCarriers); auto cifRef = make_shared(mode); auto cifFreq = make_shared(mode); - auto cifDiff = make_shared(myNbCarriers); + auto cifDiff = make_shared(m_nbCarriers); - auto cifNull = make_shared(myNbCarriers); + auto cifNull = make_shared(m_nbCarriers); auto cifSig = make_shared( - (1 + myNbSymbols) * myNbCarriers * sizeof(complexf)); + (1 + m_nbSymbols) * m_nbCarriers * sizeof(complexf)); // TODO this needs a review bool useCicEq = false; @@ -167,8 +170,8 @@ int DabModulator::process(Buffer* dataOut) shared_ptr cifCicEq; if (useCicEq) { cifCicEq = make_shared( - myNbCarriers, - (float)mySpacing * (float)m_settings.outputRate / 2048000.0f, + m_nbCarriers, + (float)m_spacing * (float)m_settings.outputRate / 2048000.0f, cic_ratio); } @@ -186,9 +189,9 @@ int DabModulator::process(Buffer* dataOut) } auto cifOfdm = make_shared( - (1 + myNbSymbols), - myNbCarriers, - mySpacing, + (1 + m_nbSymbols), + m_nbCarriers, + m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); @@ -196,7 +199,7 @@ int DabModulator::process(Buffer* dataOut) rcs.enrol(cifOfdm.get()); auto cifGain = make_shared( - mySpacing, + m_spacing, m_settings.gainMode, m_settings.digitalgain, m_settings.normalise, @@ -205,7 +208,7 @@ int DabModulator::process(Buffer* dataOut) rcs.enrol(cifGain.get()); auto cifGuard = make_shared( - myNbSymbols, mySpacing, myNullSize, mySymSize, + m_nbSymbols, m_spacing, m_nullSize, m_symSize, m_settings.ofdmWindowOverlap); rcs.enrol(cifGuard.get()); @@ -227,17 +230,21 @@ int DabModulator::process(Buffer* dataOut) cifRes = make_shared( 2048000, m_settings.outputRate, - mySpacing); + m_spacing); } - myOutput = make_shared(dataOut); + if (not m_format.empty()) { + m_formatConverter = make_shared(m_format); + } + + m_output = make_shared(dataOut); - myFlowgraph->connect(cifPrbs, cifMux); + m_flowgraph->connect(cifPrbs, cifMux); //////////////////////////////////////////////////////////////// // Processing FIC //////////////////////////////////////////////////////////////// - shared_ptr fic(myEtiSource.getFic()); + shared_ptr fic(m_etiSource.getFic()); //////////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////////// @@ -269,15 +276,15 @@ int DabModulator::process(Buffer* dataOut) PDEBUG(" Adding tail\n"); ficPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); - myFlowgraph->connect(fic, ficPrbs); - myFlowgraph->connect(ficPrbs, ficConv); - myFlowgraph->connect(ficConv, ficPunc); - myFlowgraph->connect(ficPunc, cifPart); + m_flowgraph->connect(fic, ficPrbs); + m_flowgraph->connect(ficPrbs, ficConv); + m_flowgraph->connect(ficConv, ficPunc); + m_flowgraph->connect(ficPunc, cifPart); //////////////////////////////////////////////////////////////// // Configuring subchannels //////////////////////////////////////////////////////////////// - for (const auto& subchannel : myEtiSource.getSubchannels()) { + for (const auto& subchannel : m_etiSource.getSubchannels()) { //////////////////////////////////////////////////////////// // Data initialisation @@ -329,23 +336,23 @@ int DabModulator::process(Buffer* dataOut) // Configuring time interleaver auto subchInterleaver = make_shared(subchSizeOut); - myFlowgraph->connect(subchannel, subchPrbs); - myFlowgraph->connect(subchPrbs, subchConv); - myFlowgraph->connect(subchConv, subchPunc); - myFlowgraph->connect(subchPunc, subchInterleaver); - myFlowgraph->connect(subchInterleaver, cifMux); + m_flowgraph->connect(subchannel, subchPrbs); + m_flowgraph->connect(subchPrbs, subchConv); + m_flowgraph->connect(subchConv, subchPunc); + m_flowgraph->connect(subchPunc, subchInterleaver); + m_flowgraph->connect(subchInterleaver, cifMux); } - myFlowgraph->connect(cifMux, cifPart); - myFlowgraph->connect(cifPart, cifMap); - myFlowgraph->connect(cifMap, cifFreq); - myFlowgraph->connect(cifRef, cifDiff); - myFlowgraph->connect(cifFreq, cifDiff); - myFlowgraph->connect(cifNull, cifSig); - myFlowgraph->connect(cifDiff, cifSig); + m_flowgraph->connect(cifMux, cifPart); + m_flowgraph->connect(cifPart, cifMap); + m_flowgraph->connect(cifMap, cifFreq); + m_flowgraph->connect(cifRef, cifDiff); + m_flowgraph->connect(cifFreq, cifDiff); + m_flowgraph->connect(cifNull, cifSig); + m_flowgraph->connect(cifDiff, cifSig); if (tii) { - myFlowgraph->connect(tiiRef, tii); - myFlowgraph->connect(tii, cifSig); + m_flowgraph->connect(tiiRef, tii); + m_flowgraph->connect(tii, cifSig); } shared_ptr prev_plugin = static_pointer_cast(cifSig); @@ -354,15 +361,18 @@ int DabModulator::process(Buffer* dataOut) static_pointer_cast(cifOfdm), static_pointer_cast(cifGain), static_pointer_cast(cifGuard), - static_pointer_cast(cifFilter), // optional block - static_pointer_cast(cifRes), // optional block - static_pointer_cast(cifPoly), // optional block - static_pointer_cast(myOutput), + // optional blocks + static_pointer_cast(cifFilter), + static_pointer_cast(cifRes), + static_pointer_cast(cifPoly), + static_pointer_cast(m_formatConverter), + // mandatory block + static_pointer_cast(m_output), }); for (auto& p : plugins) { if (p) { - myFlowgraph->connect(prev_plugin, p); + m_flowgraph->connect(prev_plugin, p); prev_plugin = p; } } @@ -372,13 +382,13 @@ int DabModulator::process(Buffer* dataOut) //////////////////////////////////////////////////////////////////// // Processing data //////////////////////////////////////////////////////////////////// - return myFlowgraph->run(); + return m_flowgraph->run(); } meta_vec_t DabModulator::process_metadata(const meta_vec_t& metadataIn) { - if (myOutput) { - return myOutput->get_latest_metadata(); + if (m_output) { + return m_output->get_latest_metadata(); } return {}; @@ -390,6 +400,9 @@ void DabModulator::set_parameter(const string& parameter, const string& value) if (parameter == "rate") { throw ParameterError("Parameter 'rate' is read-only"); } + else if (parameter == "num_clipped_samples") { + throw ParameterError("Parameter 'num_clipped_samples' is read-only"); + } else { stringstream ss; ss << "Parameter '" << parameter << @@ -404,6 +417,16 @@ const string DabModulator::get_parameter(const string& parameter) const if (parameter == "rate") { ss << m_settings.outputRate; } + else if (parameter == "num_clipped_samples") { + if (m_formatConverter) { + ss << m_formatConverter->get_num_clipped_samples(); + } + else { + ss << "Parameter '" << parameter << + "' is not available when no format conversion is done."; + throw ParameterError(ss.str()); + } + } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); @@ -416,5 +439,6 @@ const RemoteControllable::map_t DabModulator::get_all_values() const { map_t map; map["rate"] = m_settings.outputRate; + map["num_clipped_samples"] = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; } diff --git a/src/DabModulator.h b/src/DabModulator.h index 0d46e9f..6381252 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -39,6 +39,7 @@ #include "ConfigParser.h" #include "EtiReader.h" #include "Flowgraph.h" +#include "FormatConverter.h" #include "GainControl.h" #include "OutputMemory.h" #include "RemoteControl.h" @@ -49,7 +50,8 @@ class DabModulator : public ModInput, public ModMetadata, public RemoteControllable { public: - DabModulator(EtiSource& etiSource, mod_settings_t& settings); + DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); + // Allowed formats: s8, u8 and s16. Empty string means no conversion int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } @@ -57,7 +59,7 @@ public: virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; /* Required to get the timestamp */ - EtiSource* getEtiSource() { return &myEtiSource; } + EtiSource* getEtiSource() { return &m_etiSource; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; @@ -68,17 +70,19 @@ protected: void setMode(unsigned mode); mod_settings_t& m_settings; + std::string m_format; - EtiSource& myEtiSource; - std::shared_ptr myFlowgraph; + EtiSource& m_etiSource; + std::shared_ptr m_flowgraph; - size_t myNbSymbols; - size_t myNbCarriers; - size_t mySpacing; - size_t myNullSize; - size_t mySymSize; - size_t myFicSizeOut; + size_t m_nbSymbols; + size_t m_nbCarriers; + size_t m_spacing; + size_t m_nullSize; + size_t m_symSize; + size_t m_ficSizeOut; - std::shared_ptr myOutput; + std::shared_ptr m_formatConverter; + std::shared_ptr m_output; }; diff --git a/src/FormatConverter.cpp b/src/FormatConverter.cpp index cda8a4d..fc4cc2f 100644 --- a/src/FormatConverter.cpp +++ b/src/FormatConverter.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -45,6 +45,8 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) PDEBUG("FormatConverter::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); + size_t num_clipped_samples = 0; + size_t sizeIn = dataIn->getLength() / sizeof(float); float* in = reinterpret_cast(dataIn->getData()); @@ -53,7 +55,17 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) int16_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i]; + if (in[i] < INT16_MIN) { + out[i] = INT16_MIN; + num_clipped_samples++; + } + else if (in[i] > INT16_MAX) { + out[i] = INT16_MAX; + num_clipped_samples++; + } + else { + out[i] = in[i]; + } } } else if (m_format == "u8") { @@ -61,7 +73,19 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) uint8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i] + 128; + const auto samp = in[i] + 128.0f; + if (samp < 0) { + out[i] = 0; + num_clipped_samples++; + } + else if (samp > INT8_MAX) { + out[i] = INT8_MAX; + num_clipped_samples++; + } + else { + out[i] = samp; + } + } } else if (m_format == "s8") { @@ -69,13 +93,25 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) int8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i]; + if (in[i] < INT8_MIN) { + out[i] = INT8_MIN; + num_clipped_samples++; + } + else if (in[i] > INT8_MAX) { + out[i] = INT8_MAX; + num_clipped_samples++; + } + else { + out[i] = in[i]; + } } } else { throw std::runtime_error("FormatConverter: Invalid format " + m_format); } + m_num_clipped_samples.store(num_clipped_samples); + return dataOut->getLength(); } @@ -84,19 +120,25 @@ const char* FormatConverter::name() return "FormatConverter"; } -size_t FormatConverter::get_format_size() const +size_t FormatConverter::get_num_clipped_samples() const +{ + return m_num_clipped_samples.load(); +} + + +size_t FormatConverter::get_format_size(const std::string& format) { // Returns 2*sizeof(SAMPLE_TYPE) because we have I + Q - if (m_format == "s16") { + if (format == "s16") { return 4; } - else if (m_format == "u8") { + else if (format == "u8") { return 2; } - else if (m_format == "s8") { + else if (format == "s8") { return 2; } else { - throw std::runtime_error("FormatConverter: Invalid format " + m_format); + throw std::runtime_error("FormatConverter: Invalid format " + format); } } diff --git a/src/FormatConverter.h b/src/FormatConverter.h index ceb2e17..05511c0 100644 --- a/src/FormatConverter.h +++ b/src/FormatConverter.h @@ -34,22 +34,27 @@ #include "ModPlugin.h" #include +#include #include #include class FormatConverter : public ModCodec { public: + static size_t get_format_size(const std::string& format); + // Allowed formats: s8, u8 and s16 FormatConverter(const std::string& format); int process(Buffer* const dataIn, Buffer* dataOut); const char* name(); - size_t get_format_size() const; + size_t get_num_clipped_samples() const; private: std::string m_format; + + std::atomic m_num_clipped_samples = 0; }; -- cgit v1.2.3