diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | doc/example.ini | 15 | ||||
-rw-r--r-- | src/ConfigParser.cpp | 6 | ||||
-rw-r--r-- | src/ConfigParser.h | 2 | ||||
-rw-r--r-- | src/DabMod.cpp | 6 | ||||
-rw-r--r-- | src/DabModulator.cpp | 66 | ||||
-rw-r--r-- | src/DabModulator.h | 4 | ||||
-rw-r--r-- | src/MemlessPoly.cpp | 187 | ||||
-rw-r--r-- | src/MemlessPoly.h | 78 |
9 files changed, 343 insertions, 23 deletions
diff --git a/Makefile.am b/Makefile.am index f4e8e00..d4365fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,6 +62,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/FicSource.h \ src/FIRFilter.cpp \ src/FIRFilter.h \ + src/MemlessPoly.cpp \ + src/MemlessPoly.h \ src/PuncturingRule.cpp \ src/PuncturingRule.h \ src/PuncturingEncoder.cpp \ diff --git a/doc/example.ini b/doc/example.ini index f8cec36..ffb20d7 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -135,6 +135,21 @@ enabled=1 ; If filtertapsfile is not given, the default taps are used. ;filtertapsfile=simple_taps.txt +[poly] +;Predistortion using memoryless polynom +enabled=1 +polycoeffile=polyCoefs +;eg: +;echo "8 +;0.1 +;0 +;0 +;0 +;0 +;0 +;0 +;0" > polyCoefs + [output] ; choose output: possible values: uhd, file, zmq, soapysdr output=uhd diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 393f58a..b7649df 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -168,6 +168,12 @@ static void parse_configfile( pt.get<std::string>("firfilter.filtertapsfile", "default"); } + // Poly coefficients: + if (pt.get("poly.enabled", 0) == 1) { + mod_settings.polyCoefFilename = + pt.get<std::string>("poly.polycoeffile", "default"); + } + // Output options std::string output_selected; try { diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 02b798a..fede801 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -74,6 +74,8 @@ struct mod_settings_t { std::string filterTapsFilename = ""; + std::string polyCoefFilename = ""; + #if defined(HAVE_OUTPUT_UHD) OutputUHDConfig outputuhd_conf; diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 4e4cdab..adc4cf2 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -322,7 +322,8 @@ int launch_modulator(int argc, char* argv[]) mod_settings.digitalgain, mod_settings.normalise, mod_settings.gainmodeVariance, - mod_settings.filterTapsFilename); + mod_settings.filterTapsFilename, + mod_settings.polyCoefFilename); if (format_converter) { flowgraph.connect(modulator, format_converter); @@ -426,7 +427,8 @@ int launch_modulator(int argc, char* argv[]) mod_settings.digitalgain, mod_settings.normalise, mod_settings.gainmodeVariance, - mod_settings.filterTapsFilename); + mod_settings.filterTapsFilename, + mod_settings.polyCoefFilename); if (format_converter) { flowgraph.connect(modulator, format_converter); diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index c41b8fc..8e3af8a 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -47,6 +47,7 @@ #include "Resampler.h" #include "ConvEncoder.h" #include "FIRFilter.h" +#include "MemlessPoly.h" #include "TII.h" #include "PuncturingEncoder.h" #include "TimeInterleaver.h" @@ -61,7 +62,8 @@ DabModulator::DabModulator( unsigned dabMode, GainMode gainMode, float& digGain, float normalise, float gainmodeVariance, - const std::string& filterTapsFilename + const std::string& filterTapsFilename, + const std::string& polyCoefFilename ) : ModInput(), myOutputRate(outputRate), @@ -74,6 +76,7 @@ DabModulator::DabModulator( myEtiSource(etiSource), myFlowgraph(NULL), myFilterTapsFilename(filterTapsFilename), + myPolyCoefFilename(polyCoefFilename), myTiiConfig(tiiConfig) { PDEBUG("DabModulator::DabModulator(%u, %u, %u, %zu) @ %p\n", @@ -203,7 +206,8 @@ int DabModulator::process(Buffer* dataOut) (1 + myNbSymbols), myNbCarriers, mySpacing); auto cifGain = make_shared<GainControl>( - mySpacing, myGainMode, myDigGain, myNormalise, myGainmodeVariance); + mySpacing, myGainMode, myDigGain, myNormalise, + myGainmodeVariance); rcs.enrol(cifGain.get()); @@ -215,6 +219,19 @@ int DabModulator::process(Buffer* dataOut) cifFilter = make_shared<FIRFilter>(myFilterTapsFilename); rcs.enrol(cifFilter.get()); } + + shared_ptr<MemlessPoly> cifPoly; + if (not myPolyCoefFilename.empty()) { + cifPoly = make_shared<MemlessPoly>(myPolyCoefFilename); + etiLog.level(debug) << myPolyCoefFilename << "\n"; + etiLog.level(debug) << cifPoly->m_coefs[0] << " " << + cifPoly->m_coefs[1] << " "<< cifPoly->m_coefs[2] << " "<< + cifPoly->m_coefs[3] << " "<< cifPoly->m_coefs[4] << " "<< + cifPoly->m_coefs[5] << " "<< cifPoly->m_coefs[6] << " "<< + cifPoly->m_coefs[7] << "\n"; + rcs.enrol(cifPoly.get()); + } + auto myOutput = make_shared<OutputMemory>(dataOut); shared_ptr<Resampler> cifRes; @@ -306,7 +323,8 @@ int DabModulator::process(Buffer* dataOut) auto subchConv = make_shared<ConvEncoder>(subchSizeIn); // Configuring puncturing encoder - auto subchPunc = make_shared<PuncturingEncoder>(subchannel->framesizeCu()); + auto subchPunc = + make_shared<PuncturingEncoder>(subchannel->framesizeCu()); for (const auto& rule : subchannel->get_rules()) { PDEBUG(" Adding rule:\n"); @@ -348,23 +366,31 @@ int DabModulator::process(Buffer* dataOut) myFlowgraph->connect(cifOfdm, cifGain); myFlowgraph->connect(cifGain, cifGuard); - if (cifFilter) { - myFlowgraph->connect(cifGuard, cifFilter); - if (cifRes) { - myFlowgraph->connect(cifFilter, cifRes); - myFlowgraph->connect(cifRes, myOutput); - } else { - myFlowgraph->connect(cifFilter, myOutput); - } - } - else { //no filtering - if (cifRes) { - myFlowgraph->connect(cifGuard, cifRes); - myFlowgraph->connect(cifRes, myOutput); - } else { - myFlowgraph->connect(cifGuard, myOutput); - } - +#warning "Flowgraph logic incomplete (skips FIRFilter)!" + //if (cifFilter) { + // myFlowgraph->connect(cifGuard, cifFilter); + // if (cifRes) { + // myFlowgraph->connect(cifFilter, cifRes); + // myFlowgraph->connect(cifRes, myOutput); + // } else { + // myFlowgraph->connect(cifFilter, myOutput); + // } + //} + //else { //no filtering + // if (cifRes) { + // myFlowgraph->connect(cifGuard, cifRes); + // myFlowgraph->connect(cifRes, myOutput); + // } else { + // myFlowgraph->connect(cifGuard, myOutput); + // } + //} + if (cifRes) { + myFlowgraph->connect(cifGuard, cifRes); + myFlowgraph->connect(cifRes, cifPoly); + myFlowgraph->connect(cifPoly, myOutput); + } else { + myFlowgraph->connect(cifGuard, cifPoly); + myFlowgraph->connect(cifPoly, myOutput); } } diff --git a/src/DabModulator.h b/src/DabModulator.h index c9bdbe1..0c691dd 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -55,7 +55,8 @@ public: unsigned dabMode, GainMode gainMode, float& digGain, float normalise, float gainmodeVariance, - const std::string& filterTapsFilename); + const std::string& filterTapsFilename, + const std::string& polyCoefFilename); DabModulator(const DabModulator& other) = delete; DabModulator& operator=(const DabModulator& other) = delete; virtual ~DabModulator(); @@ -80,6 +81,7 @@ protected: Flowgraph* myFlowgraph; OutputMemory* myOutput; std::string myFilterTapsFilename; + std::string myPolyCoefFilename; tii_config_t& myTiiConfig; size_t myNbSymbols; diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp new file mode 100644 index 0000000..7e074eb --- /dev/null +++ b/src/MemlessPoly.cpp @@ -0,0 +1,187 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in + Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + Andreas Steger, andreas.steger@digris.ch + + http://opendigitalradio.org + + This block implements a memoryless polynom for digital predistortion. + For better performance, multiplying is done in another thread, leading + to a pipeline delay of two calls to MemlessPoly::process + */ +/* + 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 <http://www.gnu.org/licenses/>. + */ + +#include "MemlessPoly.h" +#include "PcDebug.h" +#include "Utils.h" + +#include <stdio.h> +#include <stdexcept> + +#include <array> +#include <iostream> +#include <fstream> +#include <memory> + +using namespace std; + + +// By default the signal is unchanged +static const std::array<float, 8> default_coefficients({ + 1, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0 + }); + + +MemlessPoly::MemlessPoly(const std::string& coefs_file) : + PipelinedModCodec(), + RemoteControllable("memlesspoly"), + m_coefs_file(coefs_file) +{ + PDEBUG("MemlessPoly::MemlessPoly(%s) @ %p\n", + coefs_file.c_str(), this); + + RC_ADD_PARAMETER(ncoefs, "(Read-only) number of coefficients."); + RC_ADD_PARAMETER(coeffile, "Filename containing coefficients. When written to, the new file gets automatically loaded."); + + load_coefficients(m_coefs_file); + + start_pipeline_thread(); +} + +void MemlessPoly::load_coefficients(const std::string &coefFile) +{ + std::vector<float> coefs; + if (coefFile == "default") { + std::copy(default_coefficients.begin(), default_coefficients.end(), + std::back_inserter(coefs)); + } + else { + std::ifstream coef_fstream(coefFile.c_str()); + if(!coef_fstream) { + fprintf(stderr, "MemlessPoly: file %s could not be opened !\n", coefFile.c_str()); + throw std::runtime_error("MemlessPoly: Could not open file with coefs! "); + } + int n_coefs; + coef_fstream >> n_coefs; + + if (n_coefs <= 0) { + fprintf(stderr, "MemlessPoly: warning: coefs file has invalid format\n"); + throw std::runtime_error("MemlessPoly: coefs file has invalid format."); + } + + if (n_coefs != 8) { + throw std::runtime_error( "MemlessPoly: error: coefs file does not have 8 coefs\n"); + } + + fprintf(stderr, "MemlessPoly: Reading %d coefs...\n", n_coefs); + + coefs.resize(n_coefs); + + int n; + for (n = 0; n < n_coefs; n++) { + coef_fstream >> coefs[n]; + PDEBUG("MemlessPoly: coef: %f\n", coefs[n] ); + if (coef_fstream.eof()) { + fprintf(stderr, "MemlessPoly: file %s should contains %d coefs, but EOF reached "\ + "after %d coefs !\n", coefFile.c_str(), n_coefs, n); + throw std::runtime_error("MemlessPoly: coefs file invalid ! "); + } + } + } + + { + std::lock_guard<std::mutex> lock(m_coefs_mutex); + + m_coefs = coefs; + } +} + + +int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut) +{ + const float* in = reinterpret_cast<const float*>(dataIn->getData()); + float* out = reinterpret_cast<float*>(dataOut->getData()); + size_t sizeIn = dataIn->getLength() / sizeof(float); + + { + std::lock_guard<std::mutex> lock(m_coefs_mutex); + for (size_t i = 0; i < sizeIn; i += 1) { + float mag = std::abs(in[i]); + //out[i] = in[i]; + out[i] = in[i] * ( + m_coefs[0] + + m_coefs[1] * mag + + m_coefs[2] * mag*mag + + m_coefs[3] * mag*mag*mag + + m_coefs[4] * mag*mag*mag*mag + + m_coefs[5] * mag*mag*mag*mag*mag + + m_coefs[6] * mag*mag*mag*mag*mag*mag + + m_coefs[7] * mag*mag*mag*mag*mag*mag*mag + ); + } + } + + return dataOut->getLength(); +} + +void MemlessPoly::set_parameter(const string& parameter, const string& value) +{ + stringstream ss(value); + ss.exceptions ( stringstream::failbit | stringstream::badbit ); + + if (parameter == "ncoefs") { + throw ParameterError("Parameter 'ncoefs' is read-only"); + } + else if (parameter == "coeffile") { + try { + load_coefficients(value); + m_coefs_file = value; + } + catch (std::runtime_error &e) { + throw ParameterError(e.what()); + } + } + else { + stringstream ss; + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } +} + +const string MemlessPoly::get_parameter(const string& parameter) const +{ + stringstream ss; + if (parameter == "ncoefs") { + ss << m_coefs.size(); + } + else if (parameter == "coefFile") { + ss << m_coefs_file; + } + else { + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + return ss.str(); +} + diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h new file mode 100644 index 0000000..210b4b4 --- /dev/null +++ b/src/MemlessPoly.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in + Right of Canada (Communications Research Center Canada) + + Copyright (C) 2017 + 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + + +#include "RemoteControl.h" +#include "ModPlugin.h" +#include "PcDebug.h" +#include "ThreadsafeQueue.h" + +#include <sys/types.h> +#include <complex> +#include <thread> +#include <vector> +#include <time.h> +#include <cstdio> +#include <string> +#include <memory> + +#define MEMLESSPOLY_PIPELINE_DELAY 1 + +typedef std::complex<float> complexf; + +class MemlessPoly : public PipelinedModCodec, public RemoteControllable +{ +public: + MemlessPoly(const std::string& coefs_file); + + virtual const char* name() { return "MemlessPoly"; } + + /******* REMOTE CONTROL ********/ + virtual void set_parameter(const std::string& parameter, + const std::string& value); + + virtual const std::string get_parameter( + const std::string& parameter) const; + +//TODO to protected + std::vector<float> m_coefs; + + +protected: + int internal_process(Buffer* const dataIn, Buffer* dataOut); + void load_coefficients(const std::string &coefFile); + + std::string m_coefs_file; + + mutable std::mutex m_coefs_mutex; +}; + |