summaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to 'src/output')
-rw-r--r--src/output/Lime.cpp461
-rw-r--r--src/output/Lime.h108
-rw-r--r--src/output/SDR.cpp20
-rw-r--r--src/output/SDRDevice.h3
4 files changed, 590 insertions, 2 deletions
diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp
new file mode 100644
index 0000000..6f7eed5
--- /dev/null
+++ b/src/output/Lime.cpp
@@ -0,0 +1,461 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2018
+ Evariste F5OEO, evaristec@gmail.com
+
+ Copyright (C) 2019
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+DESCRIPTION:
+ It is an output driver using the LimeSDR library.
+*/
+
+/*
+ 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 "output/Lime.h"
+
+#ifdef HAVE_LIMESDR
+
+//#define LIMEDEBUG
+#include <chrono>
+#include <limits>
+#include <cstdio>
+#include <iomanip>
+
+#include "Log.h"
+#include "Utils.h"
+#ifdef __ARM_NEON__
+#include <arm_neon.h>
+#endif
+using namespace std;
+
+namespace Output
+{
+
+static constexpr size_t FRAMES_MAX_SIZE = 2;
+static constexpr size_t FRAME_LENGTH = 196608; // at native sample rate!
+
+#ifdef __ARM_NEON__
+void conv_s16_from_float(unsigned n, const float *a, short *b)
+{
+ unsigned i;
+
+ const float32x4_t plusone4 = vdupq_n_f32(1.0f);
+ const float32x4_t minusone4 = vdupq_n_f32(-1.0f);
+ const float32x4_t half4 = vdupq_n_f32(0.5f);
+ const float32x4_t scale4 = vdupq_n_f32(32767.0f);
+ const uint32x4_t mask4 = vdupq_n_u32(0x80000000);
+
+ for (i = 0; i < n / 4; i++)
+ {
+ float32x4_t v4 = ((float32x4_t *)a)[i];
+ v4 = vmulq_f32(vmaxq_f32(vminq_f32(v4, plusone4), minusone4), scale4);
+
+ const float32x4_t w4 = vreinterpretq_f32_u32(vorrq_u32(vandq_u32(
+ vreinterpretq_u32_f32(v4), mask4),
+ vreinterpretq_u32_f32(half4)));
+
+ ((int16x4_t *)b)[i] = vmovn_s32(vcvtq_s32_f32(vaddq_f32(v4, w4)));
+ }
+}
+#else
+void conv_s16_from_float(unsigned n, const float *a, short *b)
+{
+ unsigned i;
+
+ for (i = 0; i < n; i++)
+ {
+ b[i] = (short)(a[i] * 32767.0f);
+ }
+}
+#endif
+
+Lime::Lime(SDRDeviceConfig &config) : SDRDevice(), m_conf(config)
+{
+ m_interpolate = m_conf.upsample;
+
+ etiLog.level(info) << "Lime:Creating the device with: " << m_conf.device;
+
+ const int device_count = LMS_GetDeviceList(nullptr);
+ if (device_count < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot find LimeSDR output device");
+ }
+
+ lms_info_str_t device_list[device_count];
+ if (LMS_GetDeviceList(device_list) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot find LimeSDR output device");
+ }
+
+ size_t device_i = 0; // If several cards, need to get device by configuration
+ if (LMS_Open(&m_device, device_list[device_i], nullptr) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot open LimeSDR output device");
+ }
+
+ if (LMS_Reset(m_device) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot reset LimeSDR output device");
+ }
+
+ if (LMS_Init(m_device) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot init LimeSDR output device");
+ }
+
+ if (m_conf.masterClockRate != 0)
+ {
+ if (LMS_SetClockFreq(m_device, LMS_CLOCK_CGEN, m_conf.masterClockRate) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot set master clock rate (CGEN) for LimeSDR output device");
+ }
+
+ float_type masterClockRate = 0;
+ if (LMS_GetClockFreq(m_device, LMS_CLOCK_CGEN, &masterClockRate) < 0)
+ {
+ etiLog.level(error) << "Error reading CGEN clock LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ }
+ else
+ {
+ etiLog.level(info) << "LimeSDR master clock rate set to " << fixed << setprecision(4) << masterClockRate;
+ }
+ }
+
+ if (LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, true) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot enable channel for LimeSDR output device");
+ }
+
+ if (LMS_SetSampleRate(m_device, m_conf.sampleRate * m_interpolate, 0) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot set sample rate for LimeSDR output device");
+ }
+ float_type host_sample_rate = 0.0;
+
+ if (LMS_GetSampleRate(m_device, LMS_CH_TX, m_channel, &host_sample_rate, NULL) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot get samplerate for LimeSDR output device");
+ }
+ etiLog.level(info) << "LimeSDR sample rate set to " << fixed << setprecision(4) << host_sample_rate / 1000.0 << " kHz";
+
+ tune(m_conf.lo_offset, m_conf.frequency);
+
+ float_type cur_frequency = 0.0;
+
+ if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot get frequency for LimeSDR output device");
+ }
+ etiLog.level(info) << "LimeSDR:Actual frequency: " << fixed << setprecision(3) << cur_frequency / 1000.0 << " kHz.";
+
+ if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0)
+ {
+ //value 0..100 -> Normalize
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot set TX gain for LimeSDR output device");
+ }
+
+ if (LMS_SetAntenna(m_device, LMS_CH_TX, m_channel, LMS_PATH_TX2) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot set antenna for LimeSDR output device");
+ }
+
+ double bandwidth_calibrating = 2.5e6; // Minimal bandwidth
+ if (LMS_Calibrate(m_device, LMS_CH_TX, m_channel, bandwidth_calibrating, 0) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot calibrate LimeSDR output device");
+ }
+
+ switch (m_interpolate)
+ {
+ case 1:
+ {
+ //design matlab
+ static double coeff[61] = {
+ -0.0008126748726, -0.0003874975955, 0.0007290032809, -0.0009636150789, 0.0007643355639,
+ 3.123887291e-05, -0.001263667713, 0.002418729011, -0.002785810735, 0.001787990681,
+ 0.0006407162873, -0.003821208142, 0.006409643684, -0.006850919221, 0.004091503099,
+ 0.00172403187, -0.008917749859, 0.01456955727, -0.01547530293, 0.009518089704,
+ 0.00304264226, -0.01893160492, 0.0322769247, -0.03613986075, 0.02477015182,
+ 0.0041426518, -0.04805115238, 0.09958232939, -0.1481673121, 0.1828524768,
+ 0.8045722842, 0.1828524768, -0.1481673121, 0.09958232939, -0.04805115238,
+ 0.0041426518, 0.02477015182, -0.03613986075, 0.0322769247, -0.01893160492,
+ 0.00304264226, 0.009518089704, -0.01547530293, 0.01456955727, -0.008917749859,
+ 0.00172403187, 0.004091503099, -0.006850919221, 0.006409643684, -0.003821208142,
+ 0.0006407162873, 0.001787990681, -0.002785810735, 0.002418729011, -0.001263667713,
+ 3.123887291e-05, 0.0007643355639, -0.0009636150789, 0.0007290032809, -0.0003874975955,
+ -0.0008126748726};
+
+ LMS_SetGFIRCoeff(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, coeff, 61);
+ }
+ break;
+
+ default:
+ throw runtime_error("Unsupported interpolate: " + to_string(m_interpolate));
+ }
+
+ if (m_conf.sampleRate != 2048000)
+ {
+ throw runtime_error("Lime output only supports native samplerate = 2048000");
+ /* The buffer_size calculation below does not take into account resampling */
+ }
+
+ // Frame duration is 96ms
+ size_t buffer_size = FRAME_LENGTH * m_interpolate * 10; // We take 10 Frame buffer size Fifo
+ // Fifo seems to be round to multiple of SampleRate
+ m_tx_stream.channel = m_channel;
+ m_tx_stream.fifoSize = buffer_size;
+ m_tx_stream.throughputVsLatency = 2.0; // Should be {0..1} but could be extended
+ m_tx_stream.isTx = LMS_CH_TX;
+ m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_I16;
+ if (LMS_SetupStream(m_device, &m_tx_stream) < 0)
+ {
+ etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
+ throw runtime_error("Cannot setup TX stream for LimeSDR output device");
+ }
+ LMS_StartStream(&m_tx_stream);
+ LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true);
+}
+
+Lime::~Lime()
+{
+ if (m_device != nullptr)
+ {
+ LMS_StopStream(&m_tx_stream);
+ LMS_DestroyStream(m_device, &m_tx_stream);
+ LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, false);
+ LMS_Close(m_device);
+ }
+}
+
+void Lime::tune(double lo_offset, double frequency)
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ if (LMS_SetLOFrequency(m_device, LMS_CH_TX, m_channel, m_conf.frequency) < 0)
+ {
+ etiLog.level(error) << "Error setting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage();
+ }
+}
+
+double Lime::get_tx_freq(void) const
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ float_type cur_frequency = 0.0;
+
+ if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0)
+ {
+ etiLog.level(error) << "Error getting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage();
+ }
+
+ return cur_frequency;
+}
+
+void Lime::set_txgain(double txgain)
+{
+ m_conf.txgain = txgain;
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0)
+ {
+ etiLog.level(error) << "Error setting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage();
+ }
+}
+
+double Lime::get_txgain(void) const
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ float_type txgain = 0;
+ if (LMS_GetNormalizedGain(m_device, LMS_CH_TX, m_channel, &txgain) < 0)
+ {
+ etiLog.level(error) << "Error getting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage();
+ }
+ return txgain;
+}
+
+void Lime::set_bandwidth(double bandwidth)
+{
+ LMS_SetLPFBW(m_device, LMS_CH_TX, m_channel, bandwidth);
+}
+
+double Lime::get_bandwidth(void) const
+{
+ double bw;
+ LMS_GetLPFBW(m_device, LMS_CH_TX, m_channel, &bw);
+ return bw;
+}
+
+SDRDevice::RunStatistics Lime::get_run_statistics(void) const
+{
+ RunStatistics rs;
+ rs.num_underruns = underflows;
+ rs.num_overruns = overflows;
+ rs.num_late_packets = late_packets;
+ rs.num_frames_modulated = num_frames_modulated;
+ return rs;
+}
+
+double Lime::get_real_secs(void) const
+{
+ // TODO
+ return 0.0;
+}
+
+void Lime::set_rxgain(double rxgain)
+{
+ // TODO
+}
+
+double Lime::get_rxgain(void) const
+{
+ // TODO
+ return 0.0;
+}
+
+size_t Lime::receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ struct frame_timestamp &ts,
+ double timeout_secs)
+{
+ // TODO
+ return 0;
+}
+
+bool Lime::is_clk_source_ok() const
+{
+ // TODO
+ return true;
+}
+
+const char *Lime::device_name(void) const
+{
+ return "Lime";
+}
+
+double Lime::get_temperature(void) const
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ float_type temp = numeric_limits<float_type>::quiet_NaN();
+ if (LMS_GetChipTemperature(m_device, 0, &temp) < 0)
+ {
+ etiLog.level(error) << "Error getting LimeSDR temperature: %s " << LMS_GetLastErrorMessage();
+ }
+ return temp;
+}
+
+float Lime::get_fifo_fill_percent(void) const
+{
+ return m_last_fifo_fill_percent * 100;
+}
+
+void Lime::transmit_frame(const struct FrameData &frame)
+{
+ if (not m_device)
+ throw runtime_error("Lime device not set up");
+
+ // The frame buffer contains bytes representing FC32 samples
+ const complexf *buf = reinterpret_cast<const complexf *>(frame.buf.data());
+ const size_t numSamples = frame.buf.size() / sizeof(complexf);
+
+ m_i16samples.resize(numSamples * 2);
+ short *buffi16 = &m_i16samples[0];
+
+ conv_s16_from_float(numSamples * 2, (const float *)buf, buffi16);
+ if ((frame.buf.size() % sizeof(complexf)) != 0)
+ {
+ throw runtime_error("Lime: invalid buffer size");
+ }
+
+ lms_stream_status_t LimeStatus;
+ LMS_GetStreamStatus(&m_tx_stream, &LimeStatus);
+ overflows += LimeStatus.overrun;
+ underflows += LimeStatus.underrun;
+ late_packets += LimeStatus.droppedPackets;
+
+#ifdef LIMEDEBUG
+ etiLog.level(info) << LimeStatus.fifoFilledCount << "/" << LimeStatus.fifoSize << ":" << numSamples << "Rate" << LimeStatus.linkRate / (2 * 2.0);
+ etiLog.level(info) << "overrun" << LimeStatus.overrun << "underun" << LimeStatus.underrun << "drop" << LimeStatus.droppedPackets;
+#endif
+
+ m_last_fifo_fill_percent.store((float)LimeStatus.fifoFilledCount / (float)LimeStatus.fifoSize);
+
+ /*
+ if(LimeStatus.fifoFilledCount>=5*FRAME_LENGTH*m_interpolate) // Start if FIFO is half full {
+ if(not m_tx_stream_active) {
+ etiLog.level(info) << "Fifo OK : Normal running";
+ LMS_StartStream(&m_tx_stream);
+ m_tx_stream_active = true;
+ }
+ }
+*/
+
+ ssize_t num_sent = 0;
+
+ lms_stream_meta_t meta;
+ meta.flushPartialPacket = true;
+ meta.timestamp = 0;
+ meta.waitForTimestamp = false;
+
+ if (m_interpolate == 1)
+ {
+ num_sent = LMS_SendStream(&m_tx_stream, buffi16, numSamples, &meta, 1000);
+ }
+
+
+
+ if (num_sent == 0)
+ {
+ etiLog.level(info) << "Lime: zero samples sent" << num_sent;
+ }
+ else if (num_sent == -1)
+ {
+ etiLog.level(error) << "Error sending LimeSDR stream: %s " << LMS_GetLastErrorMessage();
+ }
+
+ num_frames_modulated++;
+}
+
+} // namespace Output
+
+#endif // HAVE_LIMESDR
diff --git a/src/output/Lime.h b/src/output/Lime.h
new file mode 100644
index 0000000..72a018e
--- /dev/null
+++ b/src/output/Lime.h
@@ -0,0 +1,108 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2018
+ Evariste F5OEO, evaristec@gmail.com
+
+ Copyright (C) 2019
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+DESCRIPTION:
+ It is an output driver using the LimeSDR library.
+*/
+
+/*
+ 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
+
+#ifdef HAVE_LIMESDR
+
+#include <atomic>
+#include <string>
+#include <memory>
+
+#include "output/SDR.h"
+#include "ModPlugin.h"
+#include "EtiReader.h"
+#include "RemoteControl.h"
+#include <lime/LimeSuite.h>
+
+namespace Output
+{
+
+class Lime : public Output::SDRDevice
+{
+ public:
+ Lime(SDRDeviceConfig &config);
+ Lime(const Lime &other) = delete;
+ Lime &operator=(const Lime &other) = delete;
+ ~Lime();
+
+ virtual void tune(double lo_offset, double frequency) override;
+ virtual double get_tx_freq(void) const override;
+ virtual void set_txgain(double txgain) override;
+ virtual double get_txgain(void) const override;
+ virtual void set_bandwidth(double bandwidth) override;
+ virtual double get_bandwidth(void) const override;
+ virtual void transmit_frame(const struct FrameData &frame) override;
+ virtual RunStatistics get_run_statistics(void) const override;
+ virtual double get_real_secs(void) const override;
+
+ virtual void set_rxgain(double rxgain) override;
+ virtual double get_rxgain(void) const override;
+ virtual size_t receive_frame(
+ complexf *buf,
+ size_t num_samples,
+ struct frame_timestamp &ts,
+ double timeout_secs) override;
+
+ // Return true if GPS and reference clock inputs are ok
+ virtual bool is_clk_source_ok(void) const override;
+ virtual const char *device_name(void) const override;
+
+ virtual double get_temperature(void) const override;
+ virtual float get_fifo_fill_percent(void) const;
+
+ private:
+ SDRDeviceConfig &m_conf;
+ lms_device_t *m_device = nullptr;
+ size_t m_channel = 0; // Should be set by config
+ lms_stream_t m_tx_stream;
+ bool m_tx_stream_active = false;
+ size_t m_interpolate = 1;
+ std::vector<complexf> interpolatebuf;
+ std::vector<short> m_i16samples;
+ std::atomic<float> m_last_fifo_fill_percent = ATOMIC_VAR_INIT(0);
+
+
+ size_t underflows = 0;
+ size_t overflows = 0;
+ size_t late_packets = 0;
+ size_t num_frames_modulated = 0;
+};
+
+} // namespace Output
+
+#endif //HAVE_SOAPYSDR
diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp
index fd8b4ba..ad65c1c 100644
--- a/src/output/SDR.cpp
+++ b/src/output/SDR.cpp
@@ -25,6 +25,7 @@
*/
#include "output/SDR.h"
+#include "output/Lime.h"
#include "PcDebug.h"
#include "Log.h"
@@ -84,6 +85,10 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :
RC_ADD_PARAMETER(frames, "Counter of number of frames modulated");
RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO");
RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss");
+
+ if (std::dynamic_pointer_cast<Lime>(device)) {
+ RC_ADD_PARAMETER(fifo_fill, "A value representing the Lime FIFO fullness [percent]");
+ }
}
SDR::~SDR()
@@ -393,7 +398,8 @@ void SDR::set_parameter(const string& parameter, const string& value)
parameter == "latepackets" or
parameter == "frames" or
parameter == "gpsdo_num_sv" or
- parameter == "gpsdo_holdover") {
+ parameter == "gpsdo_holdover" or
+ parameter == "fifo_fill") {
throw ParameterError("Parameter " + parameter + " is read-only.");
}
else {
@@ -461,6 +467,18 @@ const string SDR::get_parameter(const string& parameter) const
const auto stat = m_device->get_run_statistics();
ss << (stat.gpsdo_holdover ? 1 : 0);
}
+ else if (parameter == "fifo_fill") {
+ const auto dev = std::dynamic_pointer_cast<Lime>(m_device);
+
+ if (dev) {
+ ss << dev->get_fifo_fill_percent();
+ }
+ else {
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+ }
else {
ss << "Parameter '" << parameter <<
"' is not exported by controllable " << get_rc_name();
diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h
index a1a488f..d84ebf9 100644
--- a/src/output/SDRDevice.h
+++ b/src/output/SDRDevice.h
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2017
+ Copyright (C) 2019
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -64,6 +64,7 @@ struct SDRDeviceConfig {
double rxgain = 0.0;
bool enableSync = false;
double bandwidth = 0.0;
+ unsigned upsample = 1;
// When working with timestamps, mute the frames that
// do not have a timestamp