aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2017-02-10 11:31:05 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2017-02-10 11:31:05 +0100
commit51afbb4f9d0009b88de6cb065bbbb25a4c97f711 (patch)
treedf0163a2387e1424ebe104439333ad09361bfed6
parent245e62aed2fd55ef3b6569e5c0b8466f5aa72cbd (diff)
downloaddabmod-51afbb4f9d0009b88de6cb065bbbb25a4c97f711.tar.gz
dabmod-51afbb4f9d0009b88de6cb065bbbb25a4c97f711.tar.bz2
dabmod-51afbb4f9d0009b88de6cb065bbbb25a4c97f711.zip
Add support for SoapySDR
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac13
-rw-r--r--doc/example.ini11
-rw-r--r--src/DabMod.cpp111
-rw-r--r--src/OutputSoapy.cpp287
-rw-r--r--src/OutputSoapy.h138
-rw-r--r--src/OutputUHD.cpp3
-rw-r--r--src/OutputUHD.h3
-rw-r--r--src/Utils.cpp47
-rw-r--r--src/Utils.h9
10 files changed, 567 insertions, 57 deletions
diff --git a/Makefile.am b/Makefile.am
index c0dbe46..96e2ff3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,6 +78,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/TimestampDecoder.cpp \
src/OutputUHD.cpp \
src/OutputUHD.h \
+ src/OutputSoapy.cpp \
+ src/OutputSoapy.h \
src/InputMemory.cpp \
src/InputMemory.h \
src/InputFileReader.cpp \
diff --git a/configure.ac b/configure.ac
index f622471..e2a7dd5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,9 +105,11 @@ AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git'])
# Defines for config.h
AX_PTHREAD([], AC_MSG_ERROR([requires pthread]))
-AC_SUBST([CFLAGS], ["$EXTRA $FFTW_CFLAGS $PTHREAD_CFLAGS"])
-AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $PTHREAD_CFLAGS"])
-AC_SUBST([LIBS], ["$FFTW_LIBS $PTHREAD_LIBS $ZMQ_LIBS"])
+PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], enable_soapysdr=yes, enable_soapysdr=no)
+
+AC_SUBST([CFLAGS], ["$EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"])
+AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"])
+AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS"])
# Checks for UHD.
AS_IF([test "x$enable_output_uhd" = "xyes"],
@@ -117,6 +119,9 @@ AS_IF([test "x$enable_output_uhd" = "xyes"],
AS_IF([test "x$enable_output_uhd" = "xyes"],
[AC_DEFINE(HAVE_OUTPUT_UHD, [1], [Define if UHD output is enabled])])
+AS_IF([test "x$enable_soapysdr" = "xyes"],
+ [AC_DEFINE(HAVE_SOAPYSDR, [1], [Define if SoapySDR output is enabled])])
+
AX_BOOST_BASE([1.54.0], [], AC_MSG_ERROR([BOOST 1.54 or later is required]))
AC_CHECK_LIB([boost_system], [main], [], [AC_MSG_ERROR([library boost_system is missing])])
AC_CHECK_LIB([boost_thread], [main], [], [AC_MSG_ERROR([library boost_thread is missing])])
@@ -192,7 +197,7 @@ echo "***********************************************"
echo
enabled=""
disabled=""
-for feat in debug prof trace output_uhd zeromq
+for feat in debug prof trace output_uhd zeromq soapysdr
do
eval var=\$enable_$feat
AS_IF([test "x$var" = "xyes"],
diff --git a/doc/example.ini b/doc/example.ini
index 3dd8505..6887972 100644
--- a/doc/example.ini
+++ b/doc/example.ini
@@ -132,7 +132,7 @@ enabled=1
;filtertapsfile=simple_taps.txt
[output]
-; choose output: possible values: uhd, file, zmq
+; choose output: possible values: uhd, file, zmq, soapysdr
output=uhd
[fileoutput]
@@ -236,6 +236,15 @@ listen=tcp://*:54001
; Please see man zmq_socket for documentation
socket_type=pub
+; section defining the SoapySDR output settings. All these
+; options are given to the SoapySDR library.
+[soapyoutput]
+device=
+master_clock_rate=32768000
+txgain=40
+;frequency=234208000
+channel=13C
+
; Used for SFN with the UHD output
[delaymanagement]
diff --git a/src/DabMod.cpp b/src/DabMod.cpp
index 44951d7..9cb25a3 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -39,6 +39,9 @@
#if defined(HAVE_OUTPUT_UHD)
# include "OutputUHD.h"
#endif
+#if defined(HAVE_SOAPYSDR)
+# include "OutputSoapy.h"
+#endif
#include "OutputZeroMQ.h"
#include "InputReader.h"
#include "PcDebug.h"
@@ -140,6 +143,7 @@ int launch_modulator(int argc, char* argv[])
int useFileOutput = 0;
std::string fileOutputFormat = "complexf";
int useUHDOutput = 0;
+ int useSoapyOutput = 0;
size_t outputRate = 2048000;
size_t clockRate = 0;
@@ -171,6 +175,10 @@ int launch_modulator(int argc, char* argv[])
OutputUHDConfig outputuhd_conf;
#endif
+#if defined(HAVE_SOAPYSDR)
+ OutputSoapyConfig outputsoapy_conf;
+#endif
+
modulator_data m;
// To handle the timestamp offset of the modulator
@@ -312,6 +320,9 @@ int launch_modulator(int argc, char* argv[])
#if defined(HAVE_OUTPUT_UHD)
"output_uhd " <<
#endif
+#if defined(HAVE_SOAPYSDR)
+ "output_soapysdr " <<
+#endif
#if defined(__FAST_MATH__)
"fast-math" <<
#endif
@@ -485,50 +496,7 @@ int launch_modulator(int argc, char* argv[])
throw std::runtime_error("Configuration error");
}
else if (outputuhd_conf.frequency == 0) {
- double freq;
- if (chan == "5A") freq = 174928000;
- else if (chan == "5B") freq = 176640000;
- else if (chan == "5C") freq = 178352000;
- else if (chan == "5D") freq = 180064000;
- else if (chan == "6A") freq = 181936000;
- else if (chan == "6B") freq = 183648000;
- else if (chan == "6C") freq = 185360000;
- else if (chan == "6D") freq = 187072000;
- else if (chan == "7A") freq = 188928000;
- else if (chan == "7B") freq = 190640000;
- else if (chan == "7C") freq = 192352000;
- else if (chan == "7D") freq = 194064000;
- else if (chan == "8A") freq = 195936000;
- else if (chan == "8B") freq = 197648000;
- else if (chan == "8C") freq = 199360000;
- else if (chan == "8D") freq = 201072000;
- else if (chan == "9A") freq = 202928000;
- else if (chan == "9B") freq = 204640000;
- else if (chan == "9C") freq = 206352000;
- else if (chan == "9D") freq = 208064000;
- else if (chan == "10A") freq = 209936000;
- else if (chan == "10B") freq = 211648000;
- else if (chan == "10C") freq = 213360000;
- else if (chan == "10D") freq = 215072000;
- else if (chan == "11A") freq = 216928000;
- else if (chan == "11B") freq = 218640000;
- else if (chan == "11C") freq = 220352000;
- else if (chan == "11D") freq = 222064000;
- else if (chan == "12A") freq = 223936000;
- else if (chan == "12B") freq = 225648000;
- else if (chan == "12C") freq = 227360000;
- else if (chan == "12D") freq = 229072000;
- else if (chan == "13A") freq = 230784000;
- else if (chan == "13B") freq = 232496000;
- else if (chan == "13C") freq = 234208000;
- else if (chan == "13D") freq = 235776000;
- else if (chan == "13E") freq = 237488000;
- else if (chan == "13F") freq = 239200000;
- else {
- std::cerr << " UHD output: channel " << chan << " does not exist in table\n";
- throw std::out_of_range("UHD channel selection error");
- }
- outputuhd_conf.frequency = freq;
+ outputuhd_conf.frequency = parseChannel(chan);
}
else if (outputuhd_conf.frequency != 0 && chan != "") {
std::cerr << " UHD output: cannot define both frequency and channel.\n";
@@ -558,6 +526,31 @@ int launch_modulator(int argc, char* argv[])
useUHDOutput = 1;
}
#endif
+#if defined(HAVE_SOAPYSDR)
+ else if (output_selected == "soapysdr") {
+ outputsoapy_conf.device = pt.get("soapyoutput.device", "");
+ outputsoapy_conf.masterClockRate = pt.get<long>("soapyoutput.master_clock_rate", 0);
+
+ outputsoapy_conf.txgain = pt.get("soapyoutput.txgain", 0.0);
+ outputsoapy_conf.frequency = pt.get<double>("soapyoutput.frequency", 0);
+ std::string chan = pt.get<std::string>("soapyoutput.channel", "");
+ outputsoapy_conf.dabMode = dabMode;
+
+ if (outputsoapy_conf.frequency == 0 && chan == "") {
+ std::cerr << " soapy output enabled, but neither frequency nor channel defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+ else if (outputsoapy_conf.frequency == 0) {
+ outputsoapy_conf.frequency = parseChannel(chan);
+ }
+ else if (outputsoapy_conf.frequency != 0 && chan != "") {
+ std::cerr << " soapy output: cannot define both frequency and channel.\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+ useSoapyOutput = 1;
+ }
+#endif
#if defined(HAVE_ZEROMQ)
else if (output_selected == "zmq") {
outputName = pt.get<std::string>("zmqoutput.listen");
@@ -649,7 +642,7 @@ int launch_modulator(int argc, char* argv[])
throw std::invalid_argument("Invalid command line options");
}
- if (!useFileOutput && !useUHDOutput && !useZeroMQOutput) {
+ if (!useFileOutput && !useUHDOutput && !useZeroMQOutput && !useSoapyOutput) {
etiLog.level(error) << "Output not specified";
fprintf(stderr, "Must specify output !");
throw std::runtime_error("Configuration error");
@@ -679,6 +672,15 @@ int launch_modulator(int argc, char* argv[])
outputuhd_conf.pps_src.c_str());
}
#endif
+#if defined(HAVE_SOAPYSDR)
+ else if (useSoapyOutput) {
+ fprintf(stderr, " SoapySDR\n"
+ " Device: %s\n"
+ " master_clock_rate: %ld\n",
+ outputsoapy_conf.device.c_str(),
+ outputsoapy_conf.masterClockRate);
+ }
+#endif
else if (useZeroMQOutput) {
fprintf(stderr, " ZeroMQ\n"
" Listening on: %s\n"
@@ -763,6 +765,15 @@ int launch_modulator(int argc, char* argv[])
rcs.enrol((OutputUHD*)output.get());
}
#endif
+#if defined(HAVE_SOAPYSDR)
+ else if (useSoapyOutput) {
+ /* We normalise the same way as for the UHD output */
+ normalise = 1.0f / normalise_factor;
+ outputsoapy_conf.sampleRate = outputRate;
+ output = make_shared<OutputSoapy>(outputsoapy_conf);
+ rcs.enrol((OutputSoapy*)output.get());
+ }
+#endif
#if defined(HAVE_ZEROMQ)
else if (useZeroMQOutput) {
/* We normalise the same way as for the UHD output */
@@ -812,6 +823,11 @@ int launch_modulator(int argc, char* argv[])
((OutputUHD*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
+#if defined(HAVE_SOAPYSDR)
+ if (useSoapyOutput) {
+ ((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource());
+ }
+#endif
size_t framecount = 0;
@@ -863,6 +879,11 @@ int launch_modulator(int argc, char* argv[])
((OutputUHD*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
+#if defined(HAVE_SOAPYSDR)
+ if (useSoapyOutput) {
+ ((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource());
+ }
+#endif
m.inputReader->PrintInfo();
diff --git a/src/OutputSoapy.cpp b/src/OutputSoapy.cpp
new file mode 100644
index 0000000..3f8db88
--- /dev/null
+++ b/src/OutputSoapy.cpp
@@ -0,0 +1,287 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 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
+
+DESCRIPTION:
+ It is an output driver using the SoapySDR library that can output to
+ many devices.
+*/
+
+/*
+ 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 "OutputSoapy.h"
+#ifdef HAVE_SOAPYSDR
+
+#include <SoapySDR/Errors.hpp>
+#include <deque>
+#include <chrono>
+
+#include "Log.h"
+#include "Utils.h"
+
+#include <stdio.h>
+
+static const size_t FRAMES_MAX_SIZE = 2;
+
+
+using namespace std;
+
+
+
+OutputSoapy::OutputSoapy(OutputSoapyConfig& config) :
+ ModOutput(),
+ RemoteControllable("soapy"),
+ m_conf(config),
+ m_device(nullptr)
+{
+ RC_ADD_PARAMETER(txgain, "SoapySDR analog daughterboard TX gain");
+ RC_ADD_PARAMETER(freq, "SoapySDR transmission frequency");
+ RC_ADD_PARAMETER(overflows, "SoapySDR overflow count [r/o]");
+ RC_ADD_PARAMETER(underflows, "SoapySDR underflow count [r/o]");
+
+ etiLog.level(info) <<
+ "OutputSoapy:Creating the device with: " <<
+ config.device;
+ try
+ {
+ m_device = SoapySDR::Device::make(config.device);
+ stringstream ss;
+ ss << "SoapySDR driver=" << m_device->getDriverKey();
+ ss << " hardware=" << m_device->getHardwareKey();
+ for (const auto &it : m_device->getHardwareInfo())
+ {
+ ss << " " << it.first << "=" << it.second;
+ }
+ }
+ catch (const std::exception &ex)
+ {
+ etiLog.level(error) << "Error making SoapySDR device: " <<
+ ex.what();
+ throw std::runtime_error("Cannot create SoapySDR output");
+ }
+
+ m_device->setMasterClockRate(config.masterClockRate);
+ etiLog.level(info) << "SoapySDR master clock rate set to " <<
+ m_device->getMasterClockRate()/1000.0 << " kHz";
+
+ m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate);
+ etiLog.level(info) << "OutputSoapySDR:Actual TX rate: " <<
+ m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 <<
+ " ksps.";
+
+ m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency);
+ m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0);
+ etiLog.level(info) << "OutputSoapySDR:Actual frequency: " <<
+ m_conf.frequency / 1000.0 <<
+ " kHz.";
+
+ m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain);
+ etiLog.level(info) << "OutputSoapySDR:Actual tx gain: " <<
+ m_device->getGain(SOAPY_SDR_TX, 0);
+
+}
+
+OutputSoapy::~OutputSoapy()
+{
+ m_worker.stop();
+ if (m_device != nullptr) {
+ SoapySDR::Device::unmake(m_device);
+ }
+}
+
+void SoapyWorker::stop()
+{
+ running = false;
+ queue.push({});
+ if (m_thread.joinable()) {
+ m_thread.join();
+ }
+}
+
+void SoapyWorker::start(SoapySDR::Device *device)
+{
+ m_device = device;
+ underflows = 0;
+ overflows = 0;
+ running = true;
+ m_thread = std::thread(&SoapyWorker::process_start, this);
+}
+
+void SoapyWorker::process_start()
+{
+ // Set thread priority to realtime
+ if (int ret = set_realtime_prio(1)) {
+ etiLog.level(error) << "Could not set priority for SoapySDR worker:" << ret;
+ }
+
+ set_thread_name("soapyworker");
+
+ std::vector<size_t> channels;
+ channels.push_back(0);
+ auto stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels);
+ m_device->activateStream(stream);
+ process(stream);
+ m_device->closeStream(stream);
+ running = false;
+ etiLog.level(warn) << "SoapySDR worker terminated";
+}
+
+void SoapyWorker::process(SoapySDR::Stream *stream)
+{
+ while (running) {
+ struct SoapyWorkerFrameData frame;
+ queue.wait_and_pop(frame);
+
+ // The frame buffer contains bytes representing FC32 samples
+ const complexf *buf = reinterpret_cast<complexf*>(frame.buf.data());
+ const size_t numSamples = frame.buf.size() / sizeof(complexf);
+ if ((frame.buf.size() % sizeof(complexf)) != 0) {
+ throw std::runtime_error("OutputSoapy: invalid buffer size");
+ }
+
+ // Stream MTU is in samples, not bytes.
+ const size_t mtu = m_device->getStreamMTU(stream);
+
+ size_t num_acc_samps = 0;
+ while (running && (num_acc_samps < numSamples)) {
+ const void *buffs[1];
+ buffs[0] = buf + num_acc_samps;
+
+ const size_t samps_to_send = std::min(numSamples - num_acc_samps, mtu);
+
+ int flags = 0;
+
+ auto ret = m_device->writeStream(stream, buffs, samps_to_send, flags);
+
+ if (ret == SOAPY_SDR_TIMEOUT) {
+ continue;
+ }
+ else if (ret == SOAPY_SDR_OVERFLOW) {
+ overflows++;
+ continue;
+ }
+ else if (ret == SOAPY_SDR_UNDERFLOW) {
+ underflows++;
+ continue;
+ }
+
+ if (ret < 0) {
+ etiLog.level(error) << "Unexpected stream error " <<
+ SoapySDR::errToStr(ret);
+ running = false;
+ }
+
+ num_acc_samps += ret;
+ }
+ }
+}
+
+int OutputSoapy::process(Buffer* dataIn)
+{
+ if (first_run) {
+ m_worker.start(m_device);
+ first_run = false;
+ }
+ else if (!m_worker.running) {
+ etiLog.level(error) << "OutputSoapy: worker thread died";
+ throw std::runtime_error("Fault in OutputSoapy");
+ }
+
+ SoapyWorkerFrameData frame;
+ m_eti_source->calculateTimestamp(frame.ts);
+
+
+ if (frame.ts.fct == -1) {
+ etiLog.level(info) <<
+ "OutputSoapy: dropping one frame with invalid FCT";
+ }
+ else {
+ const uint8_t* pInData = reinterpret_cast<uint8_t*>(dataIn->getData());
+ frame.buf.resize(dataIn->getLength());
+ std::copy(pInData, pInData + dataIn->getLength(),
+ frame.buf.begin());
+ m_worker.queue.push_wait_if_full(frame, FRAMES_MAX_SIZE);
+ }
+
+ return dataIn->getLength();
+}
+
+
+void OutputSoapy::setETISource(EtiSource *etiSource)
+{
+ m_eti_source = etiSource;
+}
+
+void OutputSoapy::set_parameter(const string& parameter, const string& value)
+{
+ stringstream ss(value);
+ ss.exceptions ( stringstream::failbit | stringstream::badbit );
+
+ if (parameter == "txgain") {
+ ss >> m_conf.txgain;
+ m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain);
+ }
+ else if (parameter == "freq") {
+ ss >> m_conf.frequency;
+ m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency);
+ m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0);
+ }
+ else if (parameter == "underflows") {
+ throw ParameterError("Parameter 'underflows' is read-only");
+ }
+ else if (parameter == "overflows") {
+ throw ParameterError("Parameter 'overflows' is read-only");
+ }
+ else {
+ stringstream ss;
+ ss << "Parameter '" << parameter
+ << "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+}
+
+const string OutputSoapy::get_parameter(const string& parameter) const
+{
+ stringstream ss;
+ if (parameter == "txgain") {
+ ss << m_conf.txgain;
+ }
+ else if (parameter == "freq") {
+ ss << m_conf.frequency;
+ }
+ else if (parameter == "underflows") {
+ ss << m_worker.underflows;
+ }
+ else if (parameter == "overflows") {
+ ss << m_worker.overflows;
+ }
+ else {
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+ return ss.str();
+}
+
+#endif // HAVE_SOAPYSDR
+
diff --git a/src/OutputSoapy.h b/src/OutputSoapy.h
new file mode 100644
index 0000000..230f11b
--- /dev/null
+++ b/src/OutputSoapy.h
@@ -0,0 +1,138 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 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
+
+DESCRIPTION:
+ It is an output driver using the SoapySDR library that can output to
+ many devices.
+*/
+
+/*
+ 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_SOAPYSDR
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Device.hpp>
+
+#include <string>
+#include <memory>
+
+#include "ModPlugin.h"
+#include "EtiReader.h"
+#include "RemoteControl.h"
+#include "ThreadsafeQueue.h"
+
+typedef std::complex<float> complexf;
+
+/* This structure is used as initial configuration for the Soapy output.
+ * It must also contain all remote-controllable settings, otherwise
+ * they will get lost on a modulator restart. */
+struct OutputSoapyConfig {
+ std::string device;
+
+ long masterClockRate = 32768000;
+ unsigned sampleRate = 2048000;
+ double frequency = 0.0;
+ double txgain = 0.0;
+ unsigned dabMode = 0;
+};
+
+// Each frame contains one OFDM frame, and its
+// associated timestamp
+struct SoapyWorkerFrameData {
+ // Buffer holding frame data
+ std::vector<uint8_t> buf;
+
+ // A full timestamp contains a TIST according to standard
+ // and time information within MNSC with tx_second.
+ struct frame_timestamp ts;
+};
+
+class SoapyWorker
+{
+ public:
+ ThreadsafeQueue<SoapyWorkerFrameData> queue;
+ SoapySDR::Device *m_device;
+ std::atomic<bool> running;
+ size_t underflows;
+ size_t overflows;
+
+ SoapyWorker() {}
+ SoapyWorker(const SoapyWorker&) = delete;
+ SoapyWorker operator=(const SoapyWorker&) = delete;
+ ~SoapyWorker() { stop(); }
+
+ void start(SoapySDR::Device *device);
+ void stop(void);
+
+ private:
+ std::thread m_thread;
+
+ void process_start(void);
+ void process(SoapySDR::Stream *stream);
+};
+
+class OutputSoapy: public ModOutput, public RemoteControllable
+{
+ public:
+ OutputSoapy(OutputSoapyConfig& config);
+ OutputSoapy(const OutputSoapy& other) = delete;
+ OutputSoapy& operator=(const OutputSoapy& other) = delete;
+ ~OutputSoapy();
+
+ int process(Buffer* dataIn);
+
+ const char* name() { return "OutputSoapy"; }
+
+ void setETISource(EtiSource *etiSource);
+
+ /*********** REMOTE CONTROL ***************/
+
+ /* Base function to set parameters. */
+ virtual void set_parameter(const std::string& parameter,
+ const std::string& value);
+
+ /* Getting a parameter always returns a string. */
+ virtual const std::string get_parameter(
+ const std::string& parameter) const;
+
+
+ protected:
+ SoapyWorker m_worker;
+ EtiSource *m_eti_source;
+ OutputSoapyConfig& m_conf;
+
+ SoapySDR::Device *m_device;
+
+ bool first_run = true;
+};
+
+
+#endif //HAVE_SOAPYSDR
diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp
index d26ce77..b4344f5 100644
--- a/src/OutputUHD.cpp
+++ b/src/OutputUHD.cpp
@@ -49,6 +49,9 @@
using namespace std;
+// Maximum number of frames that can wait in uwd.frames
+static const size_t FRAMES_MAX_SIZE = 2;
+
typedef std::complex<float> complexf;
std::string stringtrim(const std::string &s)
diff --git a/src/OutputUHD.h b/src/OutputUHD.h
index 9766f36..e477e47 100644
--- a/src/OutputUHD.h
+++ b/src/OutputUHD.h
@@ -71,9 +71,6 @@ DESCRIPTION:
// frames are too far in the future
#define TIMESTAMP_MARGIN_FUTURE 0.5
-// Maximum number of frames that can wait in uwd.frames
-#define FRAMES_MAX_SIZE 2
-
typedef std::complex<float> complexf;
// Each frame contains one OFDM frame, and its
diff --git a/src/Utils.cpp b/src/Utils.cpp
index b93f2c1..f4610a4 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -131,3 +131,50 @@ void set_thread_name(const char *name)
prctl(PR_SET_NAME,name,0,0,0);
}
+double parseChannel(const std::string& chan)
+{
+ double freq;
+ if (chan == "5A") freq = 174928000;
+ else if (chan == "5B") freq = 176640000;
+ else if (chan == "5C") freq = 178352000;
+ else if (chan == "5D") freq = 180064000;
+ else if (chan == "6A") freq = 181936000;
+ else if (chan == "6B") freq = 183648000;
+ else if (chan == "6C") freq = 185360000;
+ else if (chan == "6D") freq = 187072000;
+ else if (chan == "7A") freq = 188928000;
+ else if (chan == "7B") freq = 190640000;
+ else if (chan == "7C") freq = 192352000;
+ else if (chan == "7D") freq = 194064000;
+ else if (chan == "8A") freq = 195936000;
+ else if (chan == "8B") freq = 197648000;
+ else if (chan == "8C") freq = 199360000;
+ else if (chan == "8D") freq = 201072000;
+ else if (chan == "9A") freq = 202928000;
+ else if (chan == "9B") freq = 204640000;
+ else if (chan == "9C") freq = 206352000;
+ else if (chan == "9D") freq = 208064000;
+ else if (chan == "10A") freq = 209936000;
+ else if (chan == "10B") freq = 211648000;
+ else if (chan == "10C") freq = 213360000;
+ else if (chan == "10D") freq = 215072000;
+ else if (chan == "11A") freq = 216928000;
+ else if (chan == "11B") freq = 218640000;
+ else if (chan == "11C") freq = 220352000;
+ else if (chan == "11D") freq = 222064000;
+ else if (chan == "12A") freq = 223936000;
+ else if (chan == "12B") freq = 225648000;
+ else if (chan == "12C") freq = 227360000;
+ else if (chan == "12D") freq = 229072000;
+ else if (chan == "13A") freq = 230784000;
+ else if (chan == "13B") freq = 232496000;
+ else if (chan == "13C") freq = 234208000;
+ else if (chan == "13D") freq = 235776000;
+ else if (chan == "13E") freq = 237488000;
+ else if (chan == "13F") freq = 239200000;
+ else {
+ std::cerr << " soapy output: channel " << chan << " does not exist in table\n";
+ throw std::out_of_range("soapy channel selection error");
+ }
+ return freq;
+}
diff --git a/src/Utils.h b/src/Utils.h
index 46c46d5..e2b8a2f 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -3,7 +3,7 @@
Her Majesty the Queen in Right of Canada (Communications Research
Center Canada)
- Copyright (C) 2015
+ Copyright (C) 2017
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -25,8 +25,7 @@
along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef __UTILS_H_
-#define __UTILS_H_
+#pragma once
#ifdef HAVE_CONFIG_H
# include "config.h"
@@ -36,6 +35,7 @@
#include <unistd.h>
#include <stdio.h>
#include <time.h>
+#include <string>
void printUsage(char* progName);
@@ -62,5 +62,6 @@ int set_realtime_prio(int prio);
void set_thread_name(const char *name);
-#endif
+// Convert a channel like 10A to a frequency
+double parseChannel(const std::string& chan);