aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--README.md3
-rw-r--r--doc/README-RC.md58
-rw-r--r--doc/example.ini34
-rw-r--r--src/OfdmGenerator.cpp41
-rw-r--r--src/OfdmGenerator.h10
-rw-r--r--src/PAPRStats.cpp152
-rw-r--r--src/PAPRStats.h78
8 files changed, 338 insertions, 40 deletions
diff --git a/Makefile.am b/Makefile.am
index 8b7d5f4..9991d63 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/Utils.h \
src/TII.cpp \
src/TII.h \
+ src/PAPRStats.cpp \
+ src/PAPRStats.h \
src/zmq.hpp \
lib/crc.h \
lib/crc.c \
diff --git a/README.md b/README.md
index 4711028..b0b4635 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,8 @@ Features
- Logging: log to file, to syslog
- ETI sources: ETI-over-TCP, file (Raw, Framed and Streamed) and ZeroMQ
- A Telnet and ZeroMQ remote-control that can be used to change
- some parameters during runtime
+ some parameters during runtime and retrieve statistics.
+ See doc/README-RC.md for more information
- ZeroMQ PUB and REP output.
- Ongoing work about digital predistortion for PA linearisation.
See dpd/README.md
diff --git a/doc/README-RC.md b/doc/README-RC.md
new file mode 100644
index 0000000..c0fe4a7
--- /dev/null
+++ b/doc/README-RC.md
@@ -0,0 +1,58 @@
+Remote Control Interface
+========================
+
+The RC interface allows you to change settings at runtime and to access some
+statistics. Two interfaces are available: Telnet and based on ZeroMQ.
+
+The Telnet interface is designed for human interaction. Once you have enabled
+the interface and set the port, use any telnet client to connect to the server
+to get the RC command line interface. Since this is totally unsecure telnet,
+the software will only listen on the local loopback interface. To get secure
+remote access, use SSH port forwarding.
+
+The ZeroMQ interface is designed for machine interaction, e.g. for usage in
+scripts or from third party tools. An example python script to connect to that
+interface is available in `doc/zmq-ctrl/zmq_remote.py`, and example C++ code is available in `doc/zmq-ctrl/cpp/`.
+
+Both interfaces may be enabled simultaneously.
+
+Statistics available
+--------------------
+
+The following statistics are presented through the RC:
+
+ * Value of TIST in `tist timestamp`
+ * UHD: number of underruns, overruns and frames transmitted
+ * SoapySDR: number of underruns and overruns
+ * OFDM Generator: CFR stats and MER after CFR (if CFR enabled) in `ofdm clip_stats`
+ * OFDM Generator: PAPR before and after CFR in `ofdm papr`
+
+More statistics are likely to be added in the future, and we are always open
+for suggestions.
+
+
+ZMQ RC Protocol
+---------------
+
+ODR-DabMod binds a zmq rep socket so clients must connect
+using either req or dealer socket.
+[] denotes message part as zmq multi-part message are used for delimitation.
+All message parts are utf-8 encoded strings and match the Telnet command set.
+Messages to be sent as literal strings are denoted with "" below.
+
+The following commands are supported:
+
+ REQ: ["ping"]
+ REP: ["ok"]
+
+ REQ: ["list"]
+ REP: ["ok"][module name][module name]...
+
+ REQ: ["show"][module name]
+ REP: ["ok"][parameter: value][parameter: value]...
+
+ REQ: ["get"][module name][parameter]
+ REP: [value] _OR_ ["fail"][error description]
+
+ REQ: ["set"][module name][parameter][value]
+ REP: ["ok"] _OR_ ["fail"][error description]
diff --git a/doc/example.ini b/doc/example.ini
index ec0525c..d04c1a7 100644
--- a/doc/example.ini
+++ b/doc/example.ini
@@ -1,39 +1,13 @@
; Sample configuration file for ODR-DabMod
[remotecontrol]
+; The RC feature is described in detail in doc/README-RC.md
+
; enable the telnet remote control on localhost:2121
-; Since this is totally unsecure telnet, the software
-; will only listen on the local loopback interface.
-; To get secure remote access, use SSH port forwarding
telnet=1
telnetport=2121
; Enable zmq remote control.
-; The zmq remote control is intended for machine-to-machine
-; integration. It may run in parallel with Telnet.
-;
-; Protocol:
-; ODR-DabMod binds a zmq rep socket so clients must connect
-; using either req or dealer socket.
-; [] denotes message part as zmq multi-part message are used for delimitation.
-; All message parts are utf-8 encoded strings and match the Telnet command set.
-; Messages to be sent as literal strings are denoted with "" below.
-;
-; The following commands are supported:
-; REQ: ["ping"]
-; REP: ["ok"]
-;
-; REQ: ["list"]
-; REP: ["ok"][module name][module name]...
-;
-; REQ: ["show"][module name]
-; REP: ["ok"][parameter: value][parameter: value]...
-;
-; REQ: ["get"][module name][parameter]
-; REP: [value] _OR_ ["fail"][error description]
-;
-; REQ: ["set"][module name][parameter][value]
-; REP: ["ok"] _OR_ ["fail"][error description]
zmqctrl=1
zmqctrlendpoint=tcp://127.0.0.1:9400
@@ -141,11 +115,11 @@ rate=2048000
enable=0
; At what amplitude the signal should be clipped
-clip=70.0
+clip=50.0
; How much to clip the error signal used to compensate the effect
; of clipping
-error_clip=0.05
+error_clip=0.1
[firfilter]
; The FIR Filter can be used to create a better spectral quality.
diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp
index 915d568..b00d66b 100644
--- a/src/OfdmGenerator.cpp
+++ b/src/OfdmGenerator.cpp
@@ -27,11 +27,8 @@
#include "OfdmGenerator.h"
#include "PcDebug.h"
-#include <complex>
-#include "fftw3.h"
#define FFT_TYPE fftwf_complex
-#include <stdio.h>
#include <string.h>
#include <stdexcept>
#include <assert.h>
@@ -56,7 +53,10 @@ OfdmGenerator::OfdmGenerator(size_t nbSymbols,
myCfr(enableCfr),
myCfrClip(cfrClip),
myCfrErrorClip(cfrErrorClip),
- myCfrFft(nullptr)
+ myCfrFft(nullptr),
+ // Initialise the PAPRStats to a few seconds worth of samples
+ myPaprBeforeCFR(nbSymbols * 50),
+ myPaprAfterCFR(nbSymbols * 50)
{
PDEBUG("OfdmGenerator::OfdmGenerator(%zu, %zu, %zu, %s) @ %p\n",
nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this);
@@ -71,6 +71,7 @@ OfdmGenerator::OfdmGenerator(size_t nbSymbols,
RC_ADD_PARAMETER(clip, "CFR: Clip to amplitude");
RC_ADD_PARAMETER(errorclip, "CFR: Limit error");
RC_ADD_PARAMETER(clip_stats, "CFR: statistics (clip ratio, errorclip ratio)");
+ RC_ADD_PARAMETER(papr, "PAPR measurements (before CFR, after CFR)");
if (inverse) {
myPosDst = (nbCarriers & 1 ? 0 : 1);
@@ -186,6 +187,14 @@ int OfdmGenerator::process(Buffer* const dataIn, Buffer* dataOut)
// For performance reasons, do not calculate MER for every symbol.
myMERCalcIndex = (myMERCalcIndex + 1) % myNbSymbols;
+ // The PAPRStats' clear() is not threadsafe, do not access it
+ // from the RC functions.
+ if (myPaprClearRequest.load()) {
+ myPaprBeforeCFR.clear();
+ myPaprAfterCFR.clear();
+ myPaprClearRequest.store(false);
+ }
+
for (size_t i = 0; i < myNbSymbols; ++i) {
myFftIn[0][0] = 0;
myFftIn[0][1] = 0;
@@ -203,19 +212,25 @@ int OfdmGenerator::process(Buffer* const dataIn, Buffer* dataOut)
fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut
+ complexf *symbol = reinterpret_cast<complexf*>(myFftOut);
+ myPaprBeforeCFR.process_block(symbol, mySpacing);
+
if (myCfr) {
if (myMERCalcIndex == i) {
before_cfr.resize(mySpacing);
memcpy(before_cfr.data(), myFftOut, mySpacing * sizeof(FFT_TYPE));
}
- complexf *symbol = reinterpret_cast<complexf*>(myFftOut);
/* cfr_one_iteration runs the myFftPlan again at the end, and
* therefore writes the output data to myFftOut.
*/
const auto stat = cfr_one_iteration(symbol, reference.data());
// i == 0 always zero power, so the MER ends up being NaN
+ if (i > 0) {
+ myPaprAfterCFR.process_block(symbol, mySpacing);
+ }
+
if (i > 0 and myMERCalcIndex == i) {
/* MER definition, ETSI ETR 290, Annex C
*
@@ -351,15 +366,18 @@ void OfdmGenerator::set_parameter(const std::string& parameter,
if (parameter == "cfr") {
ss >> myCfr;
+ myPaprClearRequest.store(true);
}
else if (parameter == "clip") {
ss >> myCfrClip;
+ myPaprClearRequest.store(true);
}
else if (parameter == "errorclip") {
ss >> myCfrErrorClip;
+ myPaprClearRequest.store(true);
}
- else if (parameter == "clip_stats") {
- throw ParameterError("Parameter 'clip_stats' is read-only");
+ else if (parameter == "clip_stats" or parameter == "papr") {
+ throw ParameterError("Parameter '" + parameter + "' is read-only");
}
else {
stringstream ss_err;
@@ -406,6 +424,15 @@ const std::string OfdmGenerator::get_parameter(const std::string& parameter) con
"MER after CFR: " << avg_mer << " dB";
}
}
+ else if (parameter == "papr") {
+ const double papr_before = myPaprBeforeCFR.calculate_papr();
+ const double papr_after = myPaprAfterCFR.calculate_papr();
+
+ ss << "PAPR [dB]: " << std::fixed <<
+ (papr_before == 0 ? string("N/A") : to_string(papr_before)) <<
+ ", " <<
+ (papr_after == 0 ? string("N/A") : to_string(papr_after));
+ }
else {
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
diff --git a/src/OfdmGenerator.h b/src/OfdmGenerator.h
index 008d84e..cccccf5 100644
--- a/src/OfdmGenerator.h
+++ b/src/OfdmGenerator.h
@@ -30,13 +30,14 @@
# include "config.h"
#endif
-#include "porting.h"
#include "ModPlugin.h"
#include "RemoteControl.h"
+#include "PAPRStats.h"
#include "fftw3.h"
-#include <sys/types.h>
+#include <cstddef>
#include <vector>
#include <complex>
+#include <atomic>
typedef std::complex<float> complexf;
@@ -102,6 +103,11 @@ class OfdmGenerator : public ModCodec, public RemoteControllable
std::deque<double> myClipRatios;
std::deque<double> myErrorClipRatios;
+ // Measure PAPR before and after CFR
+ PAPRStats myPaprBeforeCFR;
+ PAPRStats myPaprAfterCFR;
+ std::atomic<bool> myPaprClearRequest;
+
size_t myMERCalcIndex = 0;
std::deque<double> myMERs;
};
diff --git a/src/PAPRStats.cpp b/src/PAPRStats.cpp
new file mode 100644
index 0000000..1a72238
--- /dev/null
+++ b/src/PAPRStats.cpp
@@ -0,0 +1,152 @@
+/*
+ Copyright (C) 2005, 2006, 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/>.
+ */
+
+#include "PAPRStats.h"
+#include <numeric>
+#include <cmath>
+#if defined(TEST)
+/* compile with g++ -std=c++11 -Wall -DTEST PAPRStats.cpp -o paprtest */
+# include <iostream>
+#endif
+
+
+PAPRStats::PAPRStats(size_t num_blocks_to_accumulate) :
+ m_num_blocks_to_accumulate(num_blocks_to_accumulate)
+{
+}
+
+void PAPRStats::process_block(const complexf* data, size_t data_len)
+{
+ double norm_peak = 0;
+ double rms2 = 0;
+
+ for (size_t i = 0; i < data_len; i++) {
+ const double x_norm = std::norm(data[i]);
+
+ if (x_norm > norm_peak) {
+ norm_peak = x_norm;
+ }
+
+ rms2 += x_norm;
+ }
+
+ rms2 /= data_len;
+
+#if defined(TEST)
+ std::cerr << "Accumulating peak " << norm_peak <<
+ " rms2 " << rms2 << std::endl;
+#endif
+
+ m_squared_peaks.push_back(norm_peak);
+ m_squared_mean.push_back(rms2);
+
+ if (m_squared_mean.size() > m_num_blocks_to_accumulate) {
+ m_squared_mean.pop_front();
+ m_squared_peaks.pop_front();
+ }
+}
+
+double PAPRStats::calculate_papr() const
+{
+ if (m_squared_mean.size() < m_num_blocks_to_accumulate) {
+ return 0;
+ }
+
+ if (m_squared_mean.size() != m_squared_peaks.size()) {
+ throw std::logic_error("Invalid PAPR measurement sizes");
+ }
+
+ double peak = 0;
+ double rms2 = 0;
+ for (size_t i = 0; i < m_squared_peaks.size(); i++) {
+ if (m_squared_peaks[i] > peak) {
+ peak = m_squared_peaks[i];
+ }
+
+ rms2 += m_squared_mean[i];
+ }
+
+ // This assumes all blocks given to process have the same length
+ rms2 /= m_squared_peaks.size();
+
+#if defined(TEST)
+ std::cerr << "Calculate peak " << peak <<
+ " rms2 " << rms2 << std::endl;
+#endif
+
+ return 10.0 * std::log10(peak / rms2);
+}
+
+void PAPRStats::clear()
+{
+ m_squared_peaks.clear();
+ m_squared_mean.clear();
+}
+
+#if defined(TEST)
+/* Test python code:
+import numpy as np
+vec = 0.5 * np.exp(np.complex(0, 0.3) * np.arange(40))
+vec[26] = 10.0 * vec[26]
+rms = np.mean(vec * np.conj(vec)).real
+peak = np.amax(vec * np.conj(vec)).real
+print("rms {}".format(rms))
+print("peak {}".format(peak))
+print(10. * np.log10(peak / rms))
+*/
+int main(int argc, char **argv)
+{
+ using namespace std;
+ vector<complexf> vec(40);
+
+ for (size_t i = 0; i < vec.size(); i++) {
+ vec[i] = polar(0.5, 0.3 * i);
+ if (i == 26) {
+ vec[i] *= 10;
+ }
+ cout << " " << vec[i];
+ }
+ cout << endl;
+
+ PAPRStats stats(4);
+
+ for (size_t i = 0; i < 3; i++) {
+ stats.process_block(vec.data(), vec.size());
+ }
+
+ const auto papr0 = stats.calculate_papr();
+ if (papr0 != 0) {
+ cerr << "Expected 0, got " << papr0 << endl;
+ }
+
+ stats.process_block(vec.data(), vec.size());
+
+ const auto papr1 = stats.calculate_papr();
+ cout << "PAPR = " << papr1 << " dB" << endl;
+
+}
+
+#endif
diff --git a/src/PAPRStats.h b/src/PAPRStats.h
new file mode 100644
index 0000000..86ad8b0
--- /dev/null
+++ b/src/PAPRStats.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2005, 2006, 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 <cstddef>
+#include <vector>
+#include <deque>
+#include <complex>
+
+typedef std::complex<float> complexf;
+
+/* Helper class to calculate Peak-to-average-power ratio.
+ * Definition of PAPR:
+ *
+ * PAPR_dB = 10 * log_10 ( abs(x_peak)^2 / x_rms^2 )
+ *
+ * with abs(x_peak) the peak amplitude of the signal, and
+ * x_rms the Root Mean Squared.
+ *
+ * x_rms^2 = 1/n * Sum abs(x_n)^2
+ * = 1/n * Sum norm(x_n)
+ *
+ * Given that peaks are rare in a DAB signal, we want to accumulate
+ * several seconds worth of samples to do our calculation.
+ */
+class PAPRStats
+{
+ public:
+ PAPRStats(size_t num_blocks_to_accumulate);
+
+ /* Push in a new block of samples to measure. calculate_papr()
+ * assumes all blocks have the same size.
+ */
+ void process_block(const complexf* data, size_t data_len);
+
+ /* Returns PAPR in dB if enough blocks were processed, or
+ * 0 otherwise.
+ */
+ double calculate_papr(void) const;
+
+ void clear(void);
+
+ private:
+ size_t m_num_blocks_to_accumulate;
+ std::deque<double> m_squared_peaks;
+ std::deque<double> m_squared_mean;
+};
+
+
+