aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/OfdmGenerator.cpp41
-rw-r--r--src/OfdmGenerator.h10
-rw-r--r--src/PAPRStats.cpp152
-rw-r--r--src/PAPRStats.h78
4 files changed, 272 insertions, 9 deletions
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;
+};
+
+
+