/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include "DabModulator.h" #include "PcDebug.h" #include "BlockPartitioner.h" #include "CicEqualizer.h" #include "ConvEncoder.h" #include "DifferentialModulator.h" #include "FIRFilter.h" #include "FrameMultiplexer.h" #include "FrequencyInterleaver.h" #include "GainControl.h" #include "GuardIntervalInserter.h" #include "Log.h" #include "MemlessPoly.h" #include "NullSymbol.h" #include "OfdmGenerator.h" #include "PhaseReference.h" #include "PrbsGenerator.h" #include "PuncturingEncoder.h" #include "QpskSymbolMapper.h" #include "RemoteControl.h" #include "Resampler.h" #include "SignalMultiplexer.h" #include "TII.h" #include "TimeInterleaver.h" using namespace std; DabModulator::DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format) : ModInput(), RemoteControllable("modulator"), m_settings(settings), 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(1); } else { setMode(m_settings.dabMode); } } void DabModulator::setMode(unsigned mode) { switch (mode) { case 1: m_nbSymbols = 76; m_nbCarriers = 1536; m_spacing = 2048; m_nullSize = 2656; m_symSize = 2552; m_ficSizeOut = 288; break; case 2: m_nbSymbols = 76; m_nbCarriers = 384; m_spacing = 512; m_nullSize = 664; m_symSize = 638; m_ficSizeOut = 288; break; case 3: m_nbSymbols = 153; m_nbCarriers = 192; m_spacing = 256; m_nullSize = 345; m_symSize = 319; m_ficSizeOut = 384; break; case 4: 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"); } } int DabModulator::process(Buffer* dataOut) { using namespace std; PDEBUG("DabModulator::process(dataOut: %p)\n", dataOut); if (not m_flowgraph) { etiLog.level(debug) << "Setting up DabModulator..."; const unsigned mode = m_settings.dabMode; setMode(mode); m_flowgraph = make_shared(m_settings.showProcessTime); //////////////////////////////////////////////////////////////// // CIF data initialisation //////////////////////////////////////////////////////////////// auto cifPrbs = make_shared(864 * 8, 0x110); auto cifMux = make_shared(m_etiSource); auto cifPart = make_shared(mode); const bool fixedPoint = m_settings.fftEngine != FFTEngine::FFTW; auto cifMap = make_shared(m_nbCarriers, fixedPoint); auto cifRef = make_shared(mode, fixedPoint); auto cifFreq = make_shared(mode, fixedPoint); auto cifDiff = make_shared(m_nbCarriers, fixedPoint); auto cifNull = make_shared(m_nbCarriers, fixedPoint ? sizeof(complexfix) : sizeof(complexf)); auto cifSig = make_shared(); // TODO this needs a review bool useCicEq = false; unsigned cic_ratio = 1; if (m_settings.clockRate) { cic_ratio = m_settings.clockRate / m_settings.outputRate; cic_ratio /= 4; // FPGA DUC if (m_settings.clockRate == 400000000) { // USRP2 if (cic_ratio & 1) { // odd useCicEq = true; } // even, no filter } else { useCicEq = true; } } shared_ptr cifCicEq; if (useCicEq) { cifCicEq = make_shared( m_nbCarriers, (float)m_spacing * (float)m_settings.outputRate / 2048000.0f, cic_ratio); } shared_ptr tii; shared_ptr tiiRef; try { tii = make_shared( m_settings.dabMode, m_settings.tiiConfig, fixedPoint); rcs.enrol(tii.get()); tiiRef = make_shared(mode, fixedPoint); } catch (const TIIError& e) { etiLog.level(error) << "Could not initialise TII: " << e.what(); } shared_ptr cifOfdm; switch (m_settings.fftEngine) { case FFTEngine::FFTW: { auto ofdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); rcs.enrol(ofdm.get()); cifOfdm = ofdm; } break; case FFTEngine::KISS: cifOfdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); break; case FFTEngine::DEXTER: #if defined(HAVE_DEXTER) cifOfdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); #else throw std::runtime_error("Cannot use DEXTER fft engine without --enable-dexter"); #endif break; } shared_ptr cifGain; if (not fixedPoint) { cifGain = make_shared( m_spacing, m_settings.gainMode, m_settings.digitalgain, m_settings.normalise, m_settings.gainmodeVariance); rcs.enrol(cifGain.get()); } auto cifGuard = make_shared( m_nbSymbols, m_spacing, m_nullSize, m_symSize, m_settings.ofdmWindowOverlap, m_settings.fftEngine); rcs.enrol(cifGuard.get()); shared_ptr cifFilter; if (not m_settings.filterTapsFilename.empty()) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support fir filter"); cifFilter = make_shared(m_settings.filterTapsFilename); rcs.enrol(cifFilter.get()); } shared_ptr cifPoly; if (not m_settings.polyCoefFilename.empty()) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support predistortion"); cifPoly = make_shared(m_settings.polyCoefFilename, m_settings.polyNumThreads); rcs.enrol(cifPoly.get()); } shared_ptr cifRes; if (m_settings.outputRate != 2048000) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support resampler"); cifRes = make_shared( 2048000, m_settings.outputRate, m_spacing); } if (m_settings.fftEngine == FFTEngine::FFTW and not m_format.empty()) { m_formatConverter = make_shared(false, m_format); } else if (m_settings.fftEngine == FFTEngine::DEXTER) { m_formatConverter = make_shared(true, m_format); } // KISS is already in s16 m_output = make_shared(dataOut); m_flowgraph->connect(cifPrbs, cifMux); //////////////////////////////////////////////////////////////// // Processing FIC //////////////////////////////////////////////////////////////// shared_ptr fic(m_etiSource.getFic()); //////////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////////// size_t ficSizeIn = fic->getFramesize(); //////////////////////////////////////////////////////////////// // Modules configuration //////////////////////////////////////////////////////////////// // Configuring FIC channel PDEBUG("FIC:\n"); PDEBUG(" Framesize: %zu\n", fic->getFramesize()); // Configuring prbs generator auto ficPrbs = make_shared(ficSizeIn, 0x110); // Configuring convolutionnal encoder auto ficConv = make_shared(ficSizeIn); // Configuring puncturing encoder auto ficPunc = make_shared(); for (const auto &rule : fic->get_rules()) { PDEBUG(" Adding rule:\n"); PDEBUG(" Length: %zu\n", rule.length()); PDEBUG(" Pattern: 0x%x\n", rule.pattern()); ficPunc->append_rule(rule); } PDEBUG(" Adding tail\n"); ficPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); 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 : m_etiSource.getSubchannels()) { //////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////// size_t subchSizeIn = subchannel->framesize(); size_t subchSizeOut = subchannel->framesizeCu() * 8; //////////////////////////////////////////////////////////// // Modules configuration //////////////////////////////////////////////////////////// // Configuring subchannel PDEBUG("Subchannel:\n"); PDEBUG(" Start address: %zu\n", subchannel->startAddress()); PDEBUG(" Framesize: %zu\n", subchannel->framesize()); PDEBUG(" Bitrate: %zu\n", subchannel->bitrate()); PDEBUG(" Framesize CU: %zu\n", subchannel->framesizeCu()); PDEBUG(" Protection: %zu\n", subchannel->protection()); PDEBUG(" Form: %zu\n", subchannel->protectionForm()); PDEBUG(" Level: %zu\n", subchannel->protectionLevel()); PDEBUG(" Option: %zu\n", subchannel->protectionOption()); // Configuring prbs genrerator auto subchPrbs = make_shared(subchSizeIn, 0x110); // Configuring convolutionnal encoder auto subchConv = make_shared(subchSizeIn); // Configuring puncturing encoder auto subchPunc = make_shared(subchannel->framesizeCu()); for (const auto& rule : subchannel->get_rules()) { PDEBUG(" Adding rule:\n"); PDEBUG(" Length: %zu\n", rule.length()); PDEBUG(" Pattern: 0x%x\n", rule.pattern()); subchPunc->append_rule(rule); } PDEBUG(" Adding tail\n"); subchPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); // Configuring time interleaver auto subchInterleaver = make_shared(subchSizeOut); 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); } 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) { m_flowgraph->connect(tiiRef, tii); m_flowgraph->connect(tii, cifSig); } shared_ptr prev_plugin = static_pointer_cast(cifSig); const std::vector > plugins({ static_pointer_cast(cifCicEq), static_pointer_cast(cifOfdm), static_pointer_cast(cifGain), static_pointer_cast(cifGuard), // 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) { m_flowgraph->connect(prev_plugin, p); prev_plugin = p; } } etiLog.level(debug) << "DabModulator set up."; } //////////////////////////////////////////////////////////////////// // Processing data //////////////////////////////////////////////////////////////////// return m_flowgraph->run(); } meta_vec_t DabModulator::process_metadata(const meta_vec_t& metadataIn) { if (m_output) { return m_output->get_latest_metadata(); } return {}; } 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 << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string DabModulator::get_parameter(const string& parameter) const { stringstream ss; 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(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t DabModulator::get_all_values() const { json::map_t map; map["rate"].v = m_settings.outputRate; map["num_clipped_samples"].v = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; }