From ae70341365e27d766c8530924209fc4826036aea Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 31 Mar 2023 14:31:32 +0200 Subject: Add JSON output to RC --- src/DabModulator.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/DabModulator.h') diff --git a/src/DabModulator.h b/src/DabModulator.h index 00d71f5..0d46e9f 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -60,11 +60,9 @@ public: EtiSource* getEtiSource() { return &myEtiSource; } /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value) override; - - virtual const std::string get_parameter( - const std::string& parameter) const override; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: void setMode(unsigned mode); -- cgit v1.2.3 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(-) (limited to 'src/DabModulator.h') 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 From d521d4f0c5ad3b663a322453c5798626081cb1f3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 19 Jul 2023 20:26:36 +0200 Subject: Change RC showjson command --- Makefile.am | 2 + lib/Json.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++ lib/Json.h | 66 +++++++++++++++++++++++++++ lib/RemoteControl.cpp | 94 ++++++++------------------------------ lib/RemoteControl.h | 8 ++-- src/DabModulator.cpp | 4 +- src/DabModulator.h | 2 +- src/FIRFilter.cpp | 4 +- src/FIRFilter.h | 2 +- src/GainControl.cpp | 4 +- src/GainControl.h | 2 +- src/GuardIntervalInserter.cpp | 4 +- src/GuardIntervalInserter.h | 2 +- src/MemlessPoly.cpp | 4 +- src/MemlessPoly.h | 2 +- src/OfdmGenerator.cpp | 4 +- src/OfdmGenerator.h | 2 +- src/TII.cpp | 4 +- src/TII.h | 2 +- src/TimestampDecoder.cpp | 4 +- src/TimestampDecoder.h | 2 +- src/output/SDR.cpp | 6 +-- src/output/SDR.h | 2 +- src/output/SDRDevice.h | 2 +- 24 files changed, 220 insertions(+), 110 deletions(-) create mode 100644 lib/Json.cpp create mode 100644 lib/Json.h (limited to 'src/DabModulator.h') diff --git a/Makefile.am b/Makefile.am index 0e09236..6e7c9ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ lib/RemoteControl.h \ lib/Log.cpp \ lib/Log.h \ + lib/Json.h \ + lib/Json.cpp \ lib/Globals.cpp \ lib/INIReader.h \ lib/crc.h \ diff --git a/lib/Json.cpp b/lib/Json.cpp new file mode 100644 index 0000000..9bda8c3 --- /dev/null +++ b/lib/Json.cpp @@ -0,0 +1,102 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "Json.h" + +namespace json { + static std::string escape_json(const std::string &s) { + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) { + switch (*c) { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); + } else { + o << *c; + } + } + } + return o.str(); + } + + std::string map_to_json(const map_t& values) { + std::ostringstream ss; + ss << "{ "; + size_t ix = 0; + for (const auto& element : values) { + if (ix > 0) { + ss << ","; + } + + ss << "\"" << escape_json(element.first) << "\": "; + + const auto& value = element.second.data; + if (std::holds_alternative(value)) { + ss << "\"" << escape_json(std::get(value)) << "\""; + } + else if (std::holds_alternative(value)) { + ss << std::defaultfloat << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << (std::get(value) ? "true" : "false"); + } + else if (std::holds_alternative(value)) { + ss << "null"; + } + else if (std::holds_alternative(value)) { + ss << map_to_json(std::get(value)); + } + else { + throw std::logic_error("variant alternative not handled"); + } + + ix++; + } + ss << " }"; + + return ss.str(); + } +} diff --git a/lib/Json.h b/lib/Json.h new file mode 100644 index 0000000..26da9a8 --- /dev/null +++ b/lib/Json.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + This module adds remote-control capability to some of the dabmux/dabmod modules. + */ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace json { + + using map_t = std::unordered_map; + + struct value_t { + std::variant< + map_t, + std::string, + double, + size_t, + ssize_t, + bool, + std::nullopt_t> data; + + template + value_t operator=(const T& map) { + value_t v; + v.data = map; + return v; + } + + }; + + std::string map_to_json(const map_t& values); +} diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index fd0ea77..b544461 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -105,71 +105,14 @@ std::list< std::vector > RemoteControllers::get_param_list_values(c } -static std::string escape_json(const std::string &s) { - std::ostringstream o; - for (auto c = s.cbegin(); c != s.cend(); c++) { - switch (*c) { - case '"': o << "\\\""; break; - case '\\': o << "\\\\"; break; - case '\b': o << "\\b"; break; - case '\f': o << "\\f"; break; - case '\n': o << "\\n"; break; - case '\r': o << "\\r"; break; - case '\t': o << "\\t"; break; - default: - if ('\x00' <= *c && *c <= '\x1f') { - o << "\\u" - << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); - } else { - o << *c; - } - } - } - return o.str(); -} - -std::string RemoteControllers::get_params_json(const std::string& name) { - RemoteControllable* controllable = get_controllable_(name); - const auto& values = controllable->get_all_values(); - - std::ostringstream ss; - ss << "{ "; - size_t ix = 0; - for (const auto& element : values) { - if (ix > 0) { - ss << ","; - } - - ss << "\"" << escape_json(element.first) << "\": "; - - const auto& value = element.second; - if (std::holds_alternative(value)) { - ss << "\"" << escape_json(std::get(value)) << "\""; - } - else if (std::holds_alternative(value)) { - ss << std::defaultfloat << std::get(value); - } - else if (std::holds_alternative(value)) { - ss << std::get(value); - } - else if (std::holds_alternative(value)) { - ss << std::get(value); - } - else if (std::holds_alternative(value)) { - ss << (std::get(value) ? "true" : "false"); - } - else if (std::holds_alternative(value)) { - ss << "null"; - } - else { - throw std::logic_error("variant alternative not handled"); - } - ix++; +std::string RemoteControllers::get_showjson() { + json::map_t root; + for (auto &controllable : rcs.controllables) { + root[controllable->get_rc_name()].data = controllable->get_all_values(); } - ss << " }"; - return ss.str(); + return json::map_to_json(root); } std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { @@ -590,6 +533,19 @@ void RemoteControllerZmq::process() repSocket.send(zmsg, (--cohort_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } + else if (msg.size() == 1 && command == "showjson") { + try { + std::string json = rcs.get_showjson(); + + zmq::message_t zmsg(json.size()); + memcpy(zmsg.data(), json.data(), json.size()); + + repSocket.send(zmsg, zmq::send_flags::none); + } + catch (const ParameterError &err) { + send_fail_reply(repSocket, err.what()); + } + } else if (msg.size() == 2 && command == "show") { const std::string module((char*) msg[1].data(), msg[1].size()); try { @@ -608,20 +564,6 @@ void RemoteControllerZmq::process() send_fail_reply(repSocket, err.what()); } } - else if (msg.size() == 2 && command == "showjson") { - const std::string module((char*) msg[1].data(), msg[1].size()); - try { - std::string json = rcs.get_params_json(module); - - zmq::message_t zmsg(json.size()); - memcpy(zmsg.data(), json.data(), json.size()); - - repSocket.send(zmsg, zmq::send_flags::none); - } - catch (const ParameterError &err) { - send_fail_reply(repSocket, err.what()); - } - } else if (msg.size() == 3 && command == "get") { const std::string module((char*) msg[1].data(), msg[1].size()); const std::string parameter((char*) msg[2].data(), msg[2].size()); diff --git a/lib/RemoteControl.h b/lib/RemoteControl.h index 4bc3b68..26f30d9 100644 --- a/lib/RemoteControl.h +++ b/lib/RemoteControl.h @@ -48,6 +48,7 @@ #include "Log.h" #include "Socket.h" +#include "Json.h" #define RC_ADD_PARAMETER(p, desc) { \ std::vector p; \ @@ -120,10 +121,7 @@ class RemoteControllable { /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const = 0; - using value_t = std::variant; - using map_t = std::unordered_map; - - virtual const map_t get_all_values() const = 0; + virtual const json::map_t get_all_values() const = 0; protected: std::string m_rc_name; @@ -140,7 +138,7 @@ class RemoteControllers { void check_faults(); std::list< std::vector > get_param_list_values(const std::string& name); std::string get_param(const std::string& name, const std::string& param); - std::string get_params_json(const std::string& name); + std::string get_showjson(); void set_param( const std::string& name, diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 5213d8d..0fe9c6d 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -435,9 +435,9 @@ const string DabModulator::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t DabModulator::get_all_values() const +const json::map_t DabModulator::get_all_values() const { - map_t map; + json::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 6381252..140f313 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -64,7 +64,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: void setMode(unsigned mode); diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index 523d405..d2a6121 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -347,9 +347,9 @@ const string FIRFilter::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t FIRFilter::get_all_values() const +const json::map_t FIRFilter::get_all_values() const { - map_t map; + json::map_t map; map["ntaps"] = m_taps.size(); map["tapsfile"] = m_taps_file; return map; diff --git a/src/FIRFilter.h b/src/FIRFilter.h index 2a469aa..a4effa1 100644 --- a/src/FIRFilter.h +++ b/src/FIRFilter.h @@ -61,7 +61,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: virtual int internal_process(Buffer* const dataIn, Buffer* dataOut) override; diff --git a/src/GainControl.cpp b/src/GainControl.cpp index d90da45..beb93f6 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -583,9 +583,9 @@ const string GainControl::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t GainControl::get_all_values() const +const json::map_t GainControl::get_all_values() const { - map_t map; + json::map_t map; map["digital"] = m_digGain; switch (m_gainmode) { case GainMode::GAIN_FIX: diff --git a/src/GainControl.h b/src/GainControl.h index f024fa2..04f6b58 100644 --- a/src/GainControl.h +++ b/src/GainControl.h @@ -66,7 +66,7 @@ class GainControl : public PipelinedModCodec, public RemoteControllable /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: virtual int internal_process( diff --git a/src/GuardIntervalInserter.cpp b/src/GuardIntervalInserter.cpp index d5c71fb..80394b7 100644 --- a/src/GuardIntervalInserter.cpp +++ b/src/GuardIntervalInserter.cpp @@ -303,9 +303,9 @@ const std::string GuardIntervalInserter::get_parameter(const std::string& parame return ss.str(); } -const RemoteControllable::map_t GuardIntervalInserter::get_all_values() const +const json::map_t GuardIntervalInserter::get_all_values() const { - map_t map; + json::map_t map; map["windowlen"] = d_windowOverlap; return map; } diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index f88bdac..5aaad2b 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -58,7 +58,7 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: void update_window(size_t new_window_overlap); diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index a2b0082..30d4ce9 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -467,9 +467,9 @@ const string MemlessPoly::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t MemlessPoly::get_all_values() const +const json::map_t MemlessPoly::get_all_values() const { - map_t map; + json::map_t map; map["ncoefs"] = m_coefs_am.size(); map["coefs"] = serialise_coefficients(); map["coeffile"] = m_coefs_file; diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h index 09adc13..91e6860 100644 --- a/src/MemlessPoly.h +++ b/src/MemlessPoly.h @@ -68,7 +68,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; private: int internal_process(Buffer* const dataIn, Buffer* dataOut) override; diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp index d161861..cb799d3 100644 --- a/src/OfdmGenerator.cpp +++ b/src/OfdmGenerator.cpp @@ -458,9 +458,9 @@ const std::string OfdmGenerator::get_parameter(const std::string& parameter) con return ss.str(); } -const RemoteControllable::map_t OfdmGenerator::get_all_values() const +const json::map_t OfdmGenerator::get_all_values() const { - map_t map; + json::map_t map; // TODO needs rework of the values return map; } diff --git a/src/OfdmGenerator.h b/src/OfdmGenerator.h index 90e562a..dc1ad46 100644 --- a/src/OfdmGenerator.h +++ b/src/OfdmGenerator.h @@ -61,7 +61,7 @@ class OfdmGenerator : public ModCodec, public RemoteControllable /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: struct cfr_iter_stat_t { diff --git a/src/TII.cpp b/src/TII.cpp index b329cdb..9068630 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -385,9 +385,9 @@ const std::string TII::get_parameter(const std::string& parameter) const return ss.str(); } -const RemoteControllable::map_t TII::get_all_values() const +const json::map_t TII::get_all_values() const { - map_t map; + json::map_t map; map["enable"] = m_conf.enable; map["pattern"] = m_conf.pattern; map["comb"] = m_conf.comb; diff --git a/src/TII.h b/src/TII.h index b0ba646..a8d0ca9 100644 --- a/src/TII.h +++ b/src/TII.h @@ -89,7 +89,7 @@ class TII : public ModCodec, public RemoteControllable /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: // Fill m_Acp with the correct carriers for the pattern/comb diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 149cd50..4277e55 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -301,9 +301,9 @@ const std::string TimestampDecoder::get_parameter( return ss.str(); } -const RemoteControllable::map_t TimestampDecoder::get_all_values() const +const json::map_t TimestampDecoder::get_all_values() const { - map_t map; + json::map_t map; map["offset"] = timestamp_offset; if (full_timestamp_received) { map["timestamp"] = time_secs + ((double)time_pps / 16384000.0); diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index dc5aa78..b90c328 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -120,7 +120,7 @@ class TimestampDecoder : public RemoteControllable /* Base function to set parameters. */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; const char* name() { return "TS"; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 11321f2..4fc3277 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -450,7 +450,7 @@ const string SDR::get_parameter(const string& parameter) const if (m_device) { const auto stat = m_device->get_run_statistics(); try { - const auto& value = stat.at(parameter); + const auto& value = stat.at(parameter).data; if (std::holds_alternative(value)) { ss << std::get(value); } @@ -485,9 +485,9 @@ const string SDR::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t SDR::get_all_values() const +const json::map_t SDR::get_all_values() const { - map_t stat = m_device->get_run_statistics(); + json::map_t stat = m_device->get_run_statistics(); stat["txgain"] = m_config.txgain; stat["rxgain"] = m_config.rxgain; diff --git a/src/output/SDR.h b/src/output/SDR.h index 94c972b..960de0c 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -67,7 +67,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { virtual const std::string get_parameter( const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; private: void process_thread_entry(void); diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index f84b340..f728d8b 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -116,7 +116,7 @@ struct FrameData { // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: - using run_statistics_t = RemoteControllable::map_t; + using run_statistics_t = json::map_t; virtual void tune(double lo_offset, double frequency) = 0; virtual double get_tx_freq(void) const = 0; -- cgit v1.2.3 From 0887d7e859605fad9617681695e70e3ef738a19c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 20 Jul 2023 18:14:51 +0200 Subject: Add main loop RC metrics --- lib/Socket.cpp | 2 +- src/DabMod.cpp | 165 ++++++++++++++++++-------------------------- src/DabModulator.h | 2 + src/EtiReader.cpp | 2 +- src/GuardIntervalInserter.h | 2 + src/InputTcpReader.cpp | 3 + src/TII.h | 1 + src/TimestampDecoder.h | 1 + src/Utils.cpp | 82 ++++++++++++++++++++++ src/Utils.h | 13 ++-- 10 files changed, 169 insertions(+), 104 deletions(-) (limited to 'src/DabModulator.h') diff --git a/lib/Socket.cpp b/lib/Socket.cpp index 10ec1ca..b71c01e 100644 --- a/lib/Socket.cpp +++ b/lib/Socket.cpp @@ -893,7 +893,7 @@ ssize_t TCPClient::recv(void *buffer, size_t length, int flags, int timeout_ms) return 0; } - return 0; + throw std::logic_error("unreachable"); } void TCPClient::reconnect() diff --git a/src/DabMod.cpp b/src/DabMod.cpp index fdd9e93..7daa72a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -96,18 +96,58 @@ void signalHandler(int signalNb) running = 0; } -struct modulator_data -{ - // For ETI - std::shared_ptr inputReader; - std::shared_ptr etiReader; +class ModulatorData : public RemoteControllable { + public: + // For ETI + std::shared_ptr inputReader; + std::shared_ptr etiReader; + + // For EDI + std::shared_ptr ediInput; + + // Common to both EDI and EDI + uint64_t framecount = 0; + Flowgraph *flowgraph = nullptr; + + + // RC-related + ModulatorData() : RemoteControllable("mainloop") { + RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); + RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + } + + virtual ~ModulatorData() {} + + virtual void set_parameter(const std::string& parameter, const std::string& value) { + throw ParameterError("Parameter " + parameter + " is read-only"); + } + + virtual const std::string get_parameter(const std::string& parameter) const { + stringstream ss; + if (parameter == "num_modulator_restarts") { + ss << num_modulator_restarts; + } + else if (parameter == "most_recent_edi_decoded") { + ss << most_recent_edi_decoded; + } + else { + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + return ss.str(); + } - // For EDI - std::shared_ptr ediInput; + virtual const json::map_t get_all_values() const + { + json::map_t map; + map["num_modulator_restarts"].v = num_modulator_restarts; + map["most_recent_edi_decoded"].v = most_recent_edi_decoded; + return map; + } - // Common to both EDI and EDI - uint64_t framecount = 0; - Flowgraph *flowgraph = nullptr; + size_t num_modulator_restarts = 0; + time_t most_recent_edi_decoded = 0; }; enum class run_modulator_state_t { @@ -117,88 +157,8 @@ enum class run_modulator_state_t { reconfigure // Some sort of change of configuration we cannot handle happened }; -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m); - -static void printModSettings(const mod_settings_t& mod_settings) -{ - stringstream ss; - // Print settings - ss << "Input\n"; - ss << " Type: " << mod_settings.inputTransport << "\n"; - ss << " Source: " << mod_settings.inputName << "\n"; - - ss << "Output\n"; - - if (mod_settings.useFileOutput) { - ss << " Name: " << mod_settings.outputName << "\n"; - } -#if defined(HAVE_OUTPUT_UHD) - else if (mod_settings.useUHDOutput) { - ss << " UHD\n" << - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " Subdevice: " << - mod_settings.sdr_device_config.subDevice << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n" << - " refclk: " << - mod_settings.sdr_device_config.refclk_src << "\n" << - " pps source: " << - mod_settings.sdr_device_config.pps_src << "\n"; - } -#endif -#if defined(HAVE_SOAPYSDR) - else if (mod_settings.useSoapyOutput) { - ss << " SoapySDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_DEXTER) - else if (mod_settings.useDexterOutput) { - ss << " PrecisionWave DEXTER\n"; - } -#endif -#if defined(HAVE_LIMESDR) - else if (mod_settings.useLimeOutput) { - ss << " LimeSDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_BLADERF) - else if (mod_settings.useBladeRFOutput) { - ss << " BladeRF\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; - } -#endif - else if (mod_settings.useZeroMQOutput) { - ss << " ZeroMQ\n" << - " Listening on: " << mod_settings.outputName << "\n" << - " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; - } +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m); - ss << " Sampling rate: "; - if (mod_settings.outputRate > 1000) { - if (mod_settings.outputRate > 1000000) { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000000.0 << - " MHz\n"; - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000.0 << - " kHz\n"; - } - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate << " Hz\n"; - } - fprintf(stderr, "%s", ss.str().c_str()); -} static shared_ptr prepare_output(mod_settings_t& s) { @@ -346,6 +306,9 @@ int launch_modulator(int argc, char* argv[]) printModSettings(mod_settings); + ModulatorData m; + rcs.enrol(&m); + { // This is mostly useful on ARM systems where FFTW planning takes some time. If we do it here // it will be done before the modulator starts up @@ -422,14 +385,15 @@ int launch_modulator(int argc, char* argv[]) "invalid input transport " + mod_settings.inputTransport + " selected!"); } + m.ediInput = ediInput; + m.inputReader = inputReader; + bool run_again = true; while (run_again) { Flowgraph flowgraph(mod_settings.showProcessTime); - modulator_data m; - m.ediInput = ediInput; - m.inputReader = inputReader; + m.framecount = 0; m.flowgraph = &flowgraph; shared_ptr modulator; @@ -493,13 +457,14 @@ int launch_modulator(int argc, char* argv[]) } etiLog.level(info) << m.framecount << " DAB frames, " << ((float)m.framecount * 0.024f) << " seconds encoded"; + m.num_modulator_restarts++; } etiLog.level(info) << "Terminating"; return ret; } -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m) +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m) { auto ret = run_modulator_state_t::failure; try { @@ -579,6 +544,12 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m break; } + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + m.most_recent_edi_decoded = t.tv_sec; fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); @@ -611,7 +582,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m last_eti_fct = fct; } else { - etiLog.level(info) << "ETI FCT discontinuity, expected " << + etiLog.level(warn) << "ETI FCT discontinuity, expected " << expected_fct << " received " << fct; if (m.ediInput) { m.ediInput->ediReader.clearFrame(); diff --git a/src/DabModulator.h b/src/DabModulator.h index 140f313..093a782 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -53,6 +53,8 @@ public: DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); // Allowed formats: s8, u8 and s16. Empty string means no conversion + virtual ~DabModulator() {} + int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index e992e62..580088b 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -646,7 +646,7 @@ bool EdiTransport::rxPacket() const int timeout_ms = 1000; try { ssize_t ret = m_tcpclient.recv(m_tcpbuffer.data(), m_tcpbuffer.size(), 0, timeout_ms); - if (ret == 0 or ret == -1) { + if (ret <= 0) { return false; } else if (ret > (ssize_t)m_tcpbuffer.size()) { diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index 5aaad2b..f78ac91 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -52,6 +52,8 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable size_t symSize, size_t& windowOverlap); + virtual ~GuardIntervalInserter() {} + int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "GuardIntervalInserter"; } diff --git a/src/InputTcpReader.cpp b/src/InputTcpReader.cpp index 21f8496..8ba4d74 100644 --- a/src/InputTcpReader.cpp +++ b/src/InputTcpReader.cpp @@ -79,6 +79,9 @@ int InputTcpReader::GetNextFrame(void* buffer) etiLog.level(debug) << "TCP input auto reconnect"; std::this_thread::sleep_for(std::chrono::seconds(1)); } + else if (ret == -2) { + etiLog.level(debug) << "TCP input timeout"; + } return ret; } diff --git a/src/TII.h b/src/TII.h index a8d0ca9..f6de70b 100644 --- a/src/TII.h +++ b/src/TII.h @@ -82,6 +82,7 @@ class TII : public ModCodec, public RemoteControllable { public: TII(unsigned int dabmode, tii_config_t& tii_config); + virtual ~TII() {} int process(Buffer* dataIn, Buffer* dataOut) override; const char* name() override; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index b90c328..25796ca 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -98,6 +98,7 @@ class TimestampDecoder : public RemoteControllable * frame transmission */ TimestampDecoder(double& offset_s); + virtual ~TimestampDecoder() {} frame_timestamp getTimestamp(void); diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f378a7..94f198c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -25,6 +25,7 @@ along with ODR-DabMod. If not, see . */ +#include "sstream" #include "Utils.h" #include "GainControl.h" #if defined(HAVE_PRCTL) @@ -144,6 +145,87 @@ void printStartupInfo() printHeader(); } +void printModSettings(const mod_settings_t& mod_settings) +{ + std::stringstream ss; + // Print settings + ss << "Input\n"; + ss << " Type: " << mod_settings.inputTransport << "\n"; + ss << " Source: " << mod_settings.inputName << "\n"; + + ss << "Output\n"; + + if (mod_settings.useFileOutput) { + ss << " Name: " << mod_settings.outputName << "\n"; + } +#if defined(HAVE_OUTPUT_UHD) + else if (mod_settings.useUHDOutput) { + ss << " UHD\n" << + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " Subdevice: " << + mod_settings.sdr_device_config.subDevice << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n" << + " refclk: " << + mod_settings.sdr_device_config.refclk_src << "\n" << + " pps source: " << + mod_settings.sdr_device_config.pps_src << "\n"; + } +#endif +#if defined(HAVE_SOAPYSDR) + else if (mod_settings.useSoapyOutput) { + ss << " SoapySDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_DEXTER) + else if (mod_settings.useDexterOutput) { + ss << " PrecisionWave DEXTER\n"; + } +#endif +#if defined(HAVE_LIMESDR) + else if (mod_settings.useLimeOutput) { + ss << " LimeSDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_BLADERF) + else if (mod_settings.useBladeRFOutput) { + ss << " BladeRF\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; + } +#endif + else if (mod_settings.useZeroMQOutput) { + ss << " ZeroMQ\n" << + " Listening on: " << mod_settings.outputName << "\n" << + " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; + } + + ss << " Sampling rate: "; + if (mod_settings.outputRate > 1000) { + if (mod_settings.outputRate > 1000000) { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000000.0 << + " MHz\n"; + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000.0 << + " kHz\n"; + } + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate << " Hz\n"; + } + fprintf(stderr, "%s", ss.str().c_str()); +} + int set_realtime_prio(int prio) { // Set thread priority to realtime diff --git a/src/Utils.h b/src/Utils.h index 9e88488..82728e9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -31,12 +31,13 @@ # include "config.h" #endif -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include "ConfigParser.h" void printUsage(const char* progName); @@ -44,6 +45,8 @@ void printVersion(void); void printStartupInfo(void); +void printModSettings(const mod_settings_t& mod_settings); + // Set SCHED_RR with priority prio (0=lowest) int set_realtime_prio(int prio); -- cgit v1.2.3