summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--src/ConfigParser.cpp478
-rw-r--r--src/ConfigParser.h88
-rw-r--r--src/DabMod.cpp844
-rw-r--r--src/Utils.cpp48
-rw-r--r--src/Utils.h4
6 files changed, 810 insertions, 654 deletions
diff --git a/Makefile.am b/Makefile.am
index 96e2ff3..f4e8e00 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -50,6 +50,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/DabModulator.h \
src/Buffer.cpp \
src/Buffer.h \
+ src/ConfigParser.cpp \
+ src/ConfigParser.h \
src/ModPlugin.cpp \
src/ModPlugin.h \
src/EtiReader.cpp \
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
new file mode 100644
index 0000000..a1e6e34
--- /dev/null
+++ b/src/ConfigParser.cpp
@@ -0,0 +1,478 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
+ 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/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "ConfigParser.h"
+#include "porting.h"
+#include "Utils.h"
+#include "Log.h"
+#include "DabModulator.h"
+
+#include <unistd.h>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ini_parser.hpp>
+
+using namespace std;
+
+static GainMode parse_gainmode(const std::string &gainMode_setting)
+{
+ string gainMode_minuscule(gainMode_setting);
+ std::transform(gainMode_minuscule.begin(), gainMode_minuscule.end(),
+ gainMode_minuscule.begin(), ::tolower);
+
+ if (gainMode_minuscule == "0" or gainMode_minuscule == "fix") {
+ return GainMode::GAIN_FIX;
+ }
+ else if (gainMode_minuscule == "1" or gainMode_minuscule == "max") {
+ return GainMode::GAIN_MAX;
+ }
+ else if (gainMode_minuscule == "2" or gainMode_minuscule == "var") {
+ return GainMode::GAIN_VAR;
+ }
+
+ cerr << "Modulator gainmode setting '" << gainMode_setting <<
+ "' not recognised." << endl;
+ throw std::runtime_error("Configuration error");
+}
+
+static void parse_configfile(
+ const std::string& configuration_file,
+ mod_settings_t& mod_settings)
+{
+ // First read parameters from the file
+ using boost::property_tree::ptree;
+ ptree pt;
+
+ try {
+ read_ini(configuration_file, pt);
+ }
+ catch (boost::property_tree::ini_parser::ini_parser_error &e)
+ {
+ std::cerr << "Error, cannot read configuration file '" << configuration_file.c_str() << "'" << std::endl;
+ std::cerr << " " << e.what() << std::endl;
+ throw std::runtime_error("Cannot read configuration file");
+ }
+
+ // remote controller:
+ if (pt.get("remotecontrol.telnet", 0) == 1) {
+ try {
+ int telnetport = pt.get<int>("remotecontrol.telnetport");
+ auto telnetrc = make_shared<RemoteControllerTelnet>(telnetport);
+ rcs.add_controller(telnetrc);
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ std::cerr << " telnet remote control enabled, but no telnetport defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+ }
+
+#if defined(HAVE_ZEROMQ)
+ if (pt.get("remotecontrol.zmqctrl", 0) == 1) {
+ try {
+ std::string zmqCtrlEndpoint = pt.get("remotecontrol.zmqctrlendpoint", "");
+ std::cerr << "ZmqCtrlEndpoint: " << zmqCtrlEndpoint << std::endl;
+ auto zmqrc = make_shared<RemoteControllerZmq>(zmqCtrlEndpoint);
+ rcs.add_controller(zmqrc);
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ std::cerr << " zmq remote control enabled, but no endpoint defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+ }
+#endif
+
+ // input params:
+ if (pt.get("input.loop", 0) == 1) {
+ mod_settings.loop = true;
+ }
+
+ mod_settings.inputTransport = pt.get("input.transport", "file");
+ mod_settings.inputMaxFramesQueued = pt.get("input.max_frames_queued",
+ ZMQ_INPUT_MAX_FRAME_QUEUE);
+
+ mod_settings.edi_max_delay_ms = pt.get("input.edi_max_delay", 0.0f);
+
+ mod_settings.inputName = pt.get("input.source", "/dev/stdin");
+
+ // log parameters:
+ if (pt.get("log.syslog", 0) == 1) {
+ LogToSyslog* log_syslog = new LogToSyslog();
+ etiLog.register_backend(log_syslog);
+ }
+
+ if (pt.get("log.filelog", 0) == 1) {
+ std::string logfilename;
+ try {
+ logfilename = pt.get<std::string>("log.filename");
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ std::cerr << " Configuration enables file log, but does not specify log filename\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+ LogToFile* log_file = new LogToFile(logfilename);
+ etiLog.register_backend(log_file);
+ }
+
+ auto trace_filename = pt.get<std::string>("log.trace", "");
+ if (not trace_filename.empty()) {
+ LogTracer* tracer = new LogTracer(trace_filename);
+ etiLog.register_backend(tracer);
+ }
+
+
+ // modulator parameters:
+ const string gainMode_setting = pt.get("modulator.gainmode", "var");
+ mod_settings.gainMode = parse_gainmode(gainMode_setting);
+
+ mod_settings.dabMode = pt.get("modulator.mode", mod_settings.dabMode);
+ mod_settings.clockRate = pt.get("modulator.dac_clk_rate", (size_t)0);
+ mod_settings.digitalgain = pt.get("modulator.digital_gain", mod_settings.digitalgain);
+ mod_settings.outputRate = pt.get("modulator.rate", mod_settings.outputRate);
+
+ // FIR Filter parameters:
+ if (pt.get("firfilter.enabled", 0) == 1) {
+ mod_settings.filterTapsFilename =
+ pt.get<std::string>("firfilter.filtertapsfile", "default");
+ }
+
+ // Output options
+ std::string output_selected;
+ try {
+ output_selected = pt.get<std::string>("output.output");
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ std::cerr << " Configuration does not specify output\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+ if (output_selected == "file") {
+ try {
+ mod_settings.outputName = pt.get<std::string>("fileoutput.filename");
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ std::cerr << " Configuration does not specify file name for file output\n";
+ throw std::runtime_error("Configuration error");
+ }
+ mod_settings.useFileOutput = 1;
+
+ mod_settings.fileOutputFormat = pt.get("fileoutput.format", mod_settings.fileOutputFormat);
+ }
+#if defined(HAVE_OUTPUT_UHD)
+ else if (output_selected == "uhd") {
+ OutputUHDConfig outputuhd_conf;
+
+ outputuhd_conf.device = pt.get("uhdoutput.device", "");
+ outputuhd_conf.usrpType = pt.get("uhdoutput.type", "");
+ outputuhd_conf.subDevice = pt.get("uhdoutput.subdevice", "");
+ outputuhd_conf.masterClockRate = pt.get<long>("uhdoutput.master_clock_rate", 0);
+
+ if (outputuhd_conf.device.find("master_clock_rate") != std::string::npos) {
+ std::cerr << "Warning:"
+ "setting master_clock_rate in [uhd] device is deprecated !\n";
+ }
+
+ if (outputuhd_conf.device.find("type=") != std::string::npos) {
+ std::cerr << "Warning:"
+ "setting type in [uhd] device is deprecated !\n";
+ }
+
+ outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0);
+ outputuhd_conf.frequency = pt.get<double>("uhdoutput.frequency", 0);
+ std::string chan = pt.get<std::string>("uhdoutput.channel", "");
+ outputuhd_conf.dabMode = mod_settings.dabMode;
+
+ if (outputuhd_conf.frequency == 0 && chan == "") {
+ std::cerr << " UHD output enabled, but neither frequency nor channel defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+ else if (outputuhd_conf.frequency == 0) {
+ outputuhd_conf.frequency = parseChannel(chan);
+ }
+ else if (outputuhd_conf.frequency != 0 && chan != "") {
+ std::cerr << " UHD output: cannot define both frequency and channel.\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+ outputuhd_conf.lo_offset = pt.get<double>("uhdoutput.lo_offset", 0);
+
+ outputuhd_conf.refclk_src = pt.get("uhdoutput.refclk_source", "internal");
+ outputuhd_conf.pps_src = pt.get("uhdoutput.pps_source", "none");
+ outputuhd_conf.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos");
+
+ std::string behave = pt.get("uhdoutput.behaviour_refclk_lock_lost", "ignore");
+
+ if (behave == "crash") {
+ outputuhd_conf.refclk_lock_loss_behaviour = CRASH;
+ }
+ else if (behave == "ignore") {
+ outputuhd_conf.refclk_lock_loss_behaviour = IGNORE;
+ }
+ else {
+ std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl;
+ throw std::runtime_error("Configuration error");
+ }
+
+ outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0);
+
+ mod_settings.outputuhd_conf = outputuhd_conf;
+ mod_settings.useUHDOutput = 1;
+ }
+#endif
+#if defined(HAVE_SOAPYSDR)
+ else if (output_selected == "soapysdr") {
+ auto& outputsoapy_conf = mod_settings.outputsoapy_conf;
+ 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 = mod_settings.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");
+ }
+
+ mod_settings.useSoapyOutput = 1;
+ }
+#endif
+#if defined(HAVE_ZEROMQ)
+ else if (output_selected == "zmq") {
+ mod_settings.outputName = pt.get<std::string>("zmqoutput.listen");
+ mod_settings.zmqOutputSocketType = pt.get<std::string>("zmqoutput.socket_type");
+ mod_settings.useZeroMQOutput = 1;
+ }
+#endif
+ else {
+ std::cerr << "Error: Invalid output defined.\n";
+ throw std::runtime_error("Configuration error");
+ }
+
+#if defined(HAVE_OUTPUT_UHD)
+ mod_settings.outputuhd_conf.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1);
+ mod_settings.outputuhd_conf.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1);
+ if (mod_settings.outputuhd_conf.enableSync) {
+ std::string delay_mgmt = pt.get<std::string>("delaymanagement.management", "");
+ std::string fixedoffset = pt.get<std::string>("delaymanagement.fixedoffset", "");
+ std::string offset_filename = pt.get<std::string>("delaymanagement.dynamicoffsetfile", "");
+
+ if (not(delay_mgmt.empty() and fixedoffset.empty() and offset_filename.empty())) {
+ std::cerr << "Warning: you are using the old config syntax for the offset management.\n";
+ std::cerr << " Please see the example.ini configuration for the new settings.\n";
+ }
+
+ try {
+ mod_settings.tist_offset_s = pt.get<double>("delaymanagement.offset");
+ }
+ catch (std::exception &e) {
+ std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n";
+ throw std::runtime_error("Configuration error");
+ }
+ }
+
+#endif
+
+ /* Read TII parameters from config file */
+ mod_settings.tiiConfig.enable = pt.get("tii.enable", 0);
+ mod_settings.tiiConfig.comb = pt.get("tii.comb", 0);
+ mod_settings.tiiConfig.pattern = pt.get("tii.pattern", 0);
+ mod_settings.tiiConfig.old_variant = pt.get("tii.old_variant", 0);
+}
+
+
+void parse_args(int argc, char **argv, mod_settings_t& mod_settings)
+{
+ bool use_configuration_cmdline = false;
+ bool use_configuration_file = false;
+ std::string configuration_file;
+
+ // No argument given ? You can't be serious ! Show usage.
+ if (argc == 1) {
+ printUsage(argv[0]);
+ throw std::invalid_argument("Invalid command line options");
+ }
+
+ while (true) {
+ int c = getopt(argc, argv, "a:C:c:f:F:g:G:hlm:o:O:r:T:u:V");
+ if (c == -1) {
+ break;
+ }
+
+ if (c != 'C') {
+ use_configuration_cmdline = true;
+ }
+
+ switch (c) {
+ case 'C':
+ use_configuration_file = true;
+ configuration_file = optarg;
+ break;
+
+ case 'a':
+ mod_settings.digitalgain = strtof(optarg, NULL);
+ break;
+ case 'c':
+ mod_settings.clockRate = strtol(optarg, NULL, 0);
+ break;
+ case 'f':
+#if defined(HAVE_OUTPUT_UHD)
+ if (mod_settings.useUHDOutput) {
+ fprintf(stderr, "Options -u and -f are mutually exclusive\n");
+ throw std::invalid_argument("Invalid command line options");
+ }
+#endif
+ mod_settings.outputName = optarg;
+ mod_settings.useFileOutput = 1;
+ break;
+ case 'F':
+#if defined(HAVE_OUTPUT_UHD)
+ mod_settings.outputuhd_conf.frequency = strtof(optarg, NULL);
+#endif
+ break;
+ case 'g':
+ mod_settings.gainMode = parse_gainmode(optarg);
+ break;
+ case 'G':
+#if defined(HAVE_OUTPUT_UHD)
+ mod_settings.outputuhd_conf.txgain = strtod(optarg, NULL);
+#endif
+ break;
+ case 'l':
+ mod_settings.loop = true;
+ break;
+ case 'o':
+ mod_settings.tist_offset_s = strtod(optarg, NULL);
+#if defined(HAVE_OUTPUT_UHD)
+ mod_settings.outputuhd_conf.enableSync = true;
+#endif
+ break;
+ case 'm':
+ mod_settings.dabMode = strtol(optarg, NULL, 0);
+ break;
+ case 'r':
+ mod_settings.outputRate = strtol(optarg, NULL, 0);
+ break;
+ case 'T':
+ mod_settings.filterTapsFilename = optarg;
+ break;
+ case 'u':
+#if defined(HAVE_OUTPUT_UHD)
+ if (mod_settings.useFileOutput) {
+ fprintf(stderr, "Options -u and -f are mutually exclusive\n");
+ throw std::invalid_argument("Invalid command line options");
+ }
+ mod_settings.outputuhd_conf.device = optarg;
+ mod_settings.outputuhd_conf.refclk_src = "internal";
+ mod_settings.outputuhd_conf.pps_src = "none";
+ mod_settings.outputuhd_conf.pps_polarity = "pos";
+ mod_settings.useUHDOutput = 1;
+#endif
+ break;
+ case 'V':
+ printVersion();
+ throw std::invalid_argument("");
+ break;
+ case '?':
+ case 'h':
+ printUsage(argv[0]);
+ throw std::invalid_argument("");
+ break;
+ default:
+ fprintf(stderr, "Option '%c' not coded yet!\n", c);
+ throw std::invalid_argument("Invalid command line options");
+ }
+ }
+
+ // If only one argument is given, interpret as configuration file name
+ if (argc == 2) {
+ use_configuration_file = true;
+ configuration_file = argv[1];
+ }
+
+ if (use_configuration_file && use_configuration_cmdline) {
+ fprintf(stderr, "Warning: configuration file and command "
+ "line parameters are defined:\n\t"
+ "Command line parameters override settings "
+ "in the configuration file !\n");
+ }
+
+ // Setting ETI input filename
+ if (use_configuration_cmdline && mod_settings.inputName == "") {
+ if (optind < argc) {
+ mod_settings.inputName = argv[optind++];
+
+ if (mod_settings.inputName.substr(0, 4) == "zmq+" &&
+ mod_settings.inputName.find("://") != std::string::npos) {
+ // if the name starts with zmq+XYZ://somewhere:port
+ mod_settings.inputTransport = "zeromq";
+ }
+ else if (mod_settings.inputName.substr(0, 6) == "tcp://") {
+ mod_settings.inputTransport = "tcp";
+ }
+ else if (mod_settings.inputName.substr(0, 6) == "udp://") {
+ mod_settings.inputTransport = "edi";
+ }
+ }
+ else {
+ mod_settings.inputName = "/dev/stdin";
+ }
+ }
+
+ // Checking unused arguments
+ if (use_configuration_cmdline && optind != argc) {
+ fprintf(stderr, "Invalid arguments:");
+ while (optind != argc) {
+ fprintf(stderr, " %s", argv[optind++]);
+ }
+ fprintf(stderr, "\n");
+ printUsage(argv[0]);
+ etiLog.level(error) << "Received invalid command line arguments";
+ throw std::invalid_argument("Invalid command line options");
+ }
+
+ if (use_configuration_file) {
+ parse_configfile(configuration_file, mod_settings);
+ }
+}
+
diff --git a/src/ConfigParser.h b/src/ConfigParser.h
new file mode 100644
index 0000000..fe48f01
--- /dev/null
+++ b/src/ConfigParser.h
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012
+ 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 <string>
+#include "GainControl.h"
+#include "TII.h"
+#if defined(HAVE_OUTPUT_UHD)
+# include "OutputUHD.h"
+#endif
+#if defined(HAVE_SOAPYSDR)
+# include "OutputSoapy.h"
+#endif
+
+#define ZMQ_INPUT_MAX_FRAME_QUEUE 500
+
+struct mod_settings_t {
+ std::string outputName;
+ int useZeroMQOutput = 0;
+ std::string zmqOutputSocketType = "";
+ int useFileOutput = 0;
+ std::string fileOutputFormat = "complexf";
+ int useUHDOutput = 0;
+ int useSoapyOutput = 0;
+
+ size_t outputRate = 2048000;
+ size_t clockRate = 0;
+ unsigned dabMode = 0;
+ float digitalgain = 1.0f;
+ float normalise = 1.0f;
+ GainMode gainMode = GainMode::GAIN_VAR;
+
+ // To handle the timestamp offset of the modulator
+ unsigned tist_delay_stages = 0;
+ double tist_offset_s = 0.0;
+
+ bool loop = false;
+ std::string inputName = "";
+ std::string inputTransport = "file";
+ unsigned inputMaxFramesQueued = ZMQ_INPUT_MAX_FRAME_QUEUE;
+ float edi_max_delay_ms = 0.0f;
+
+ tii_config_t tiiConfig;
+
+ std::string filterTapsFilename = "";
+
+
+#if defined(HAVE_OUTPUT_UHD)
+ OutputUHDConfig outputuhd_conf;
+#endif
+
+#if defined(HAVE_SOAPYSDR)
+ OutputSoapyConfig outputsoapy_conf;
+#endif
+
+};
+
+void parse_args(int argc, char **argv, mod_settings_t& mod_settings);
+
diff --git a/src/DabMod.cpp b/src/DabMod.cpp
index bc3ee30..7fc395e 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -48,14 +48,12 @@
#include "TimestampDecoder.h"
#include "FIRFilter.h"
#include "RemoteControl.h"
+#include "ConfigParser.h"
#include <memory>
-#include <boost/property_tree/ptree.hpp>
-#include <boost/property_tree/ini_parser.hpp>
#include <complex>
#include <string>
#include <stdlib.h>
-#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -72,8 +70,16 @@
# define memalign(a, b) malloc(b)
#endif
-#define ZMQ_INPUT_MAX_FRAME_QUEUE 500
+/* UHD requires the input I and Q samples to be in the interval
+ * [-1.0,1.0], otherwise they get truncated, which creates very
+ * wide-spectrum spikes. Depending on the Transmission Mode, the
+ * Gain Mode and the sample rate (and maybe other parameters), the
+ * samples can have peaks up to about 48000. The value of 50000
+ * should guarantee that with a digital gain of 1.0, UHD never clips
+ * our samples.
+ */
+static const float normalise_factor = 50000.0f;
typedef std::complex<float> complexf;
@@ -94,8 +100,7 @@ struct modulator_data
inputReader(nullptr),
framecount(0),
flowgraph(nullptr),
- etiReader(nullptr),
- rcs(nullptr) {}
+ etiReader(nullptr) {}
InputReader* inputReader;
Buffer data;
@@ -103,7 +108,6 @@ struct modulator_data
Flowgraph* flowgraph;
EtiReader* etiReader;
- RemoteControllers* rcs;
};
enum class run_modulator_state_t {
@@ -115,688 +119,179 @@ enum class run_modulator_state_t {
run_modulator_state_t run_modulator(modulator_data& m);
-static GainMode parse_gainmode(const std::string &gainMode_setting)
+static void printModSettings(const mod_settings_t& mod_settings)
{
- string gainMode_minuscule(gainMode_setting);
- std::transform(gainMode_minuscule.begin(), gainMode_minuscule.end(), gainMode_minuscule.begin(), ::tolower);
-
- if (gainMode_minuscule == "0" or gainMode_minuscule == "fix") { return GainMode::GAIN_FIX; }
- else if (gainMode_minuscule == "1" or gainMode_minuscule == "max") { return GainMode::GAIN_MAX; }
- else if (gainMode_minuscule == "2" or gainMode_minuscule == "var") { return GainMode::GAIN_VAR; }
-
- cerr << "Modulator gainmode setting '" << gainMode_setting << "' not recognised." << endl;
- throw std::runtime_error("Configuration error");
-}
-
-int launch_modulator(int argc, char* argv[])
-{
- int ret = 0;
- bool loop = false;
- std::string inputName = "";
- std::string inputTransport = "file";
- unsigned inputMaxFramesQueued = ZMQ_INPUT_MAX_FRAME_QUEUE;
- float edi_max_delay_ms = 0.0f;
-
- std::string outputName;
- int useZeroMQOutput = 0;
- std::string zmqOutputSocketType = "";
- int useFileOutput = 0;
- std::string fileOutputFormat = "complexf";
- int useUHDOutput = 0;
- int useSoapyOutput = 0;
-
- size_t outputRate = 2048000;
- size_t clockRate = 0;
- unsigned dabMode = 0;
- float digitalgain = 1.0f;
- float normalise = 1.0f;
- GainMode gainMode = GainMode::GAIN_VAR;
-
- tii_config_t tiiConfig;
-
- /* UHD requires the input I and Q samples to be in the interval
- * [-1.0,1.0], otherwise they get truncated, which creates very
- * wide-spectrum spikes. Depending on the Transmission Mode, the
- * Gain Mode and the sample rate (and maybe other parameters), the
- * samples can have peaks up to about 48000. The value of 50000
- * should guarantee that with a digital gain of 1.0, UHD never clips
- * our samples.
- */
- const float normalise_factor = 50000.0f;
-
- std::string filterTapsFilename = "";
-
- // Two configuration sources exist: command line and (new) INI file
- bool use_configuration_cmdline = false;
- bool use_configuration_file = false;
- std::string configuration_file;
-
-#if defined(HAVE_OUTPUT_UHD)
- OutputUHDConfig outputuhd_conf;
-#endif
-
-#if defined(HAVE_SOAPYSDR)
- OutputSoapyConfig outputsoapy_conf;
-#endif
-
- modulator_data m;
-
- // To handle the timestamp offset of the modulator
- unsigned tist_delay_stages = 0;
- double tist_offset_s = 0.0;
-
- auto flowgraph = make_shared<Flowgraph>();
- shared_ptr<FormatConverter> format_converter;
- shared_ptr<ModOutput> output;
-
- m.rcs = &rcs;
-
- bool run_again = true;
-
- InputFileReader inputFileReader;
-#if defined(HAVE_ZEROMQ)
- auto inputZeroMQReader = make_shared<InputZeroMQReader>();
-#endif
-
- auto inputTcpReader = make_shared<InputTcpReader>();
-
- struct sigaction sa;
- memset(&sa, 0, sizeof(struct sigaction));
- sa.sa_handler = &signalHandler;
-
- if (sigaction(SIGINT, &sa, NULL) == -1) {
- perror("sigaction");
- return EXIT_FAILURE;
- }
-
- while (true) {
- int c = getopt(argc, argv, "a:C:c:f:F:g:G:hlm:o:O:r:T:u:V");
- if (c == -1) {
- break;
- }
-
- if (c != 'C') {
- use_configuration_cmdline = true;
- }
-
- switch (c) {
- case 'C':
- use_configuration_file = true;
- configuration_file = optarg;
- break;
-
- case 'a':
- digitalgain = strtof(optarg, NULL);
- break;
- case 'c':
- clockRate = strtol(optarg, NULL, 0);
- break;
- case 'f':
-#if defined(HAVE_OUTPUT_UHD)
- if (useUHDOutput) {
- fprintf(stderr, "Options -u and -f are mutually exclusive\n");
- throw std::invalid_argument("Invalid command line options");
- }
-#endif
- outputName = optarg;
- useFileOutput = 1;
- break;
- case 'F':
-#if defined(HAVE_OUTPUT_UHD)
- outputuhd_conf.frequency = strtof(optarg, NULL);
-#endif
- break;
- case 'g':
- gainMode = parse_gainmode(optarg);
- break;
- case 'G':
-#if defined(HAVE_OUTPUT_UHD)
- outputuhd_conf.txgain = strtod(optarg, NULL);
-#endif
- break;
- case 'l':
- loop = true;
- break;
- case 'o':
- tist_offset_s = strtod(optarg, NULL);
-#if defined(HAVE_OUTPUT_UHD)
- outputuhd_conf.enableSync = true;
-#endif
- break;
- case 'm':
- dabMode = strtol(optarg, NULL, 0);
- break;
- case 'r':
- outputRate = strtol(optarg, NULL, 0);
- break;
- case 'T':
- filterTapsFilename = optarg;
- break;
- case 'u':
-#if defined(HAVE_OUTPUT_UHD)
- if (useFileOutput) {
- fprintf(stderr, "Options -u and -f are mutually exclusive\n");
- throw std::invalid_argument("Invalid command line options");
- }
- outputuhd_conf.device = optarg;
- outputuhd_conf.refclk_src = "internal";
- outputuhd_conf.pps_src = "none";
- outputuhd_conf.pps_polarity = "pos";
- useUHDOutput = 1;
-#endif
- break;
- case 'V':
- printVersion();
- throw std::invalid_argument("");
- break;
- case '?':
- case 'h':
- printUsage(argv[0]);
- throw std::invalid_argument("");
- break;
- default:
- fprintf(stderr, "Option '%c' not coded yet!\n", c);
- ret = -1;
- throw std::invalid_argument("Invalid command line options");
- }
- }
-
- std::cerr << "ODR-DabMod version " <<
-#if defined(GITVERSION)
- GITVERSION
-#else
- VERSION
-#endif
- << std::endl;
-
- std::cerr << "Compiled with features: " <<
-#if defined(HAVE_ZEROMQ)
- "zeromq " <<
-#endif
-#if defined(HAVE_OUTPUT_UHD)
- "output_uhd " <<
-#endif
-#if defined(HAVE_SOAPYSDR)
- "output_soapysdr " <<
-#endif
-#if defined(__FAST_MATH__)
- "fast-math" <<
-#endif
- "\n";
-
- if (use_configuration_file && use_configuration_cmdline) {
- fprintf(stderr, "Warning: configuration file and command line parameters are defined:\n\t"
- "Command line parameters override settings in the configuration file !\n");
- }
-
- // No argument given ? You can't be serious ! Show usage.
- if (argc == 1) {
- printUsage(argv[0]);
- throw std::invalid_argument("Invalid command line options");
- }
-
- // If only one argument is given, interpret as configuration file name
- if (argc == 2) {
- use_configuration_file = true;
- configuration_file = argv[1];
- }
-
- if (use_configuration_file) {
- // First read parameters from the file
- using boost::property_tree::ptree;
- ptree pt;
-
- try {
- read_ini(configuration_file, pt);
- }
- catch (boost::property_tree::ini_parser::ini_parser_error &e)
- {
- std::cerr << "Error, cannot read configuration file '" << configuration_file.c_str() << "'" << std::endl;
- std::cerr << " " << e.what() << std::endl;
- throw std::runtime_error("Cannot read configuration file");
- }
-
- // remote controller:
- if (pt.get("remotecontrol.telnet", 0) == 1) {
- try {
- int telnetport = pt.get<int>("remotecontrol.telnetport");
- auto telnetrc = make_shared<RemoteControllerTelnet>(telnetport);
- rcs.add_controller(telnetrc);
- }
- catch (std::exception &e) {
- std::cerr << "Error: " << e.what() << "\n";
- std::cerr << " telnet remote control enabled, but no telnetport defined.\n";
- throw std::runtime_error("Configuration error");
- }
- }
-
-#if defined(HAVE_ZEROMQ)
- if (pt.get("remotecontrol.zmqctrl", 0) == 1) {
- try {
- std::string zmqCtrlEndpoint = pt.get("remotecontrol.zmqctrlendpoint", "");
- std::cerr << "ZmqCtrlEndpoint: " << zmqCtrlEndpoint << std::endl;
- auto zmqrc = make_shared<RemoteControllerZmq>(zmqCtrlEndpoint);
- rcs.add_controller(zmqrc);
- }
- catch (std::exception &e) {
- std::cerr << "Error: " << e.what() << "\n";
- std::cerr << " zmq remote control enabled, but no endpoint defined.\n";
- throw std::runtime_error("Configuration error");
- }
- }
-#endif
-
- // input params:
- if (pt.get("input.loop", 0) == 1) {
- loop = true;
- }
-
- inputTransport = pt.get("input.transport", "file");
- inputMaxFramesQueued = pt.get("input.max_frames_queued",
- ZMQ_INPUT_MAX_FRAME_QUEUE);
-
- edi_max_delay_ms = pt.get("input.edi_max_delay", 0.0f);
-
- inputName = pt.get("input.source", "/dev/stdin");
-
- // log parameters:
- if (pt.get("log.syslog", 0) == 1) {
- LogToSyslog* log_syslog = new LogToSyslog();
- etiLog.register_backend(log_syslog);
- }
-
- if (pt.get("log.filelog", 0) == 1) {
- std::string logfilename;
- try {
- logfilename = pt.get<std::string>("log.filename");
- }
- catch (std::exception &e) {
- std::cerr << "Error: " << e.what() << "\n";
- std::cerr << " Configuration enables file log, but does not specify log filename\n";
- throw std::runtime_error("Configuration error");
- }
-
- LogToFile* log_file = new LogToFile(logfilename);
- etiLog.register_backend(log_file);
- }
-
- auto trace_filename = pt.get<std::string>("log.trace", "");
- if (not trace_filename.empty()) {
- LogTracer* tracer = new LogTracer(trace_filename);
- etiLog.register_backend(tracer);
- }
-
-
- // modulator parameters:
- const string gainMode_setting = pt.get("modulator.gainmode", "var");
- gainMode = parse_gainmode(gainMode_setting);
-
- dabMode = pt.get("modulator.mode", dabMode);
- clockRate = pt.get("modulator.dac_clk_rate", (size_t)0);
- digitalgain = pt.get("modulator.digital_gain", digitalgain);
- outputRate = pt.get("modulator.rate", outputRate);
-
- // FIR Filter parameters:
- if (pt.get("firfilter.enabled", 0) == 1) {
- filterTapsFilename = pt.get<std::string>("firfilter.filtertapsfile", "default");
- }
-
- // Output options
- std::string output_selected;
- try {
- output_selected = pt.get<std::string>("output.output");
- }
- catch (std::exception &e) {
- std::cerr << "Error: " << e.what() << "\n";
- std::cerr << " Configuration does not specify output\n";
- throw std::runtime_error("Configuration error");
- }
-
- if (output_selected == "file") {
- try {
- outputName = pt.get<std::string>("fileoutput.filename");
- }
- catch (std::exception &e) {
- std::cerr << "Error: " << e.what() << "\n";
- std::cerr << " Configuration does not specify file name for file output\n";
- throw std::runtime_error("Configuration error");
- }
- useFileOutput = 1;
-
- fileOutputFormat = pt.get("fileoutput.format", fileOutputFormat);
- }
-#if defined(HAVE_OUTPUT_UHD)
- else if (output_selected == "uhd") {
- outputuhd_conf.device = pt.get("uhdoutput.device", "");
- outputuhd_conf.usrpType = pt.get("uhdoutput.type", "");
- outputuhd_conf.subDevice = pt.get("uhdoutput.subdevice", "");
- outputuhd_conf.masterClockRate = pt.get<long>("uhdoutput.master_clock_rate", 0);
-
- if (outputuhd_conf.device.find("master_clock_rate") != std::string::npos) {
- std::cerr << "Warning:"
- "setting master_clock_rate in [uhd] device is deprecated !\n";
- }
-
- if (outputuhd_conf.device.find("type=") != std::string::npos) {
- std::cerr << "Warning:"
- "setting type in [uhd] device is deprecated !\n";
- }
-
- outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0);
- outputuhd_conf.frequency = pt.get<double>("uhdoutput.frequency", 0);
- std::string chan = pt.get<std::string>("uhdoutput.channel", "");
- outputuhd_conf.dabMode = dabMode;
-
- if (outputuhd_conf.frequency == 0 && chan == "") {
- std::cerr << " UHD output enabled, but neither frequency nor channel defined.\n";
- throw std::runtime_error("Configuration error");
- }
- else if (outputuhd_conf.frequency == 0) {
- outputuhd_conf.frequency = parseChannel(chan);
- }
- else if (outputuhd_conf.frequency != 0 && chan != "") {
- std::cerr << " UHD output: cannot define both frequency and channel.\n";
- throw std::runtime_error("Configuration error");
- }
-
- outputuhd_conf.lo_offset = pt.get<double>("uhdoutput.lo_offset", 0);
-
- outputuhd_conf.refclk_src = pt.get("uhdoutput.refclk_source", "internal");
- outputuhd_conf.pps_src = pt.get("uhdoutput.pps_source", "none");
- outputuhd_conf.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos");
-
- std::string behave = pt.get("uhdoutput.behaviour_refclk_lock_lost", "ignore");
-
- if (behave == "crash") {
- outputuhd_conf.refclk_lock_loss_behaviour = CRASH;
- }
- else if (behave == "ignore") {
- outputuhd_conf.refclk_lock_loss_behaviour = IGNORE;
- }
- else {
- std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl;
- throw std::runtime_error("Configuration error");
- }
-
- outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0);
-
- 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");
- zmqOutputSocketType = pt.get<std::string>("zmqoutput.socket_type");
- useZeroMQOutput = 1;
- }
-#endif
- else {
- std::cerr << "Error: Invalid output defined.\n";
- throw std::runtime_error("Configuration error");
- }
-
-#if defined(HAVE_OUTPUT_UHD)
- outputuhd_conf.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1);
- if (outputuhd_conf.enableSync) {
- std::string delay_mgmt = pt.get<std::string>("delaymanagement.management", "");
- std::string fixedoffset = pt.get<std::string>("delaymanagement.fixedoffset", "");
- std::string offset_filename = pt.get<std::string>("delaymanagement.dynamicoffsetfile", "");
-
- if (not(delay_mgmt.empty() and fixedoffset.empty() and offset_filename.empty())) {
- std::cerr << "Warning: you are using the old config syntax for the offset management.\n";
- std::cerr << " Please see the example.ini configuration for the new settings.\n";
- }
-
- try {
- tist_offset_s = pt.get<double>("delaymanagement.offset");
- }
- catch (std::exception &e) {
- std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n";
- throw std::runtime_error("Configuration error");
- }
- }
-
- outputuhd_conf.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1);
-#endif
-
- /* Read TII parameters from config file */
- tiiConfig.enable = pt.get("tii.enable", 0);
- tiiConfig.comb = pt.get("tii.comb", 0);
- tiiConfig.pattern = pt.get("tii.pattern", 0);
- tiiConfig.old_variant = pt.get("tii.old_variant", 0);
- }
-
- etiLog.level(info) << "Starting up version " <<
-#if defined(GITVERSION)
- GITVERSION;
-#else
- VERSION;
-#endif
-
-
- // When using the FIRFilter, increase the modulator offset pipelining delay
- // by the correct amount
- if (not filterTapsFilename.empty()) {
- tist_delay_stages += FIRFILTER_PIPELINE_DELAY;
- }
-
- // Setting ETI input filename
- if (use_configuration_cmdline && inputName == "") {
- if (optind < argc) {
- inputName = argv[optind++];
-
- if (inputName.substr(0, 4) == "zmq+" &&
- inputName.find("://") != std::string::npos) {
- // if the name starts with zmq+XYZ://somewhere:port
- inputTransport = "zeromq";
- }
- else if (inputName.substr(0, 6) == "tcp://") {
- inputTransport = "tcp";
- }
- else if (inputName.substr(0, 6) == "udp://") {
- inputTransport = "edi";
- }
- }
- else {
- inputName = "/dev/stdin";
- }
- }
-
- // Checking unused arguments
- if (use_configuration_cmdline && optind != argc) {
- fprintf(stderr, "Invalid arguments:");
- while (optind != argc) {
- fprintf(stderr, " %s", argv[optind++]);
- }
- fprintf(stderr, "\n");
- printUsage(argv[0]);
- ret = -1;
- etiLog.level(error) << "Received invalid command line arguments";
- throw std::invalid_argument("Invalid command line options");
- }
-
- if (!useFileOutput && !useUHDOutput && !useZeroMQOutput && !useSoapyOutput) {
- etiLog.level(error) << "Output not specified";
- fprintf(stderr, "Must specify output !");
- throw std::runtime_error("Configuration error");
- }
-
// Print settings
fprintf(stderr, "Input\n");
- fprintf(stderr, " Type: %s\n", inputTransport.c_str());
- fprintf(stderr, " Source: %s\n", inputName.c_str());
+ fprintf(stderr, " Type: %s\n", mod_settings.inputTransport.c_str());
+ fprintf(stderr, " Source: %s\n", mod_settings.inputName.c_str());
fprintf(stderr, "Output\n");
- if (useFileOutput) {
- fprintf(stderr, " Name: %s\n", outputName.c_str());
+ if (mod_settings.useFileOutput) {
+ fprintf(stderr, " Name: %s\n", mod_settings.outputName.c_str());
}
#if defined(HAVE_OUTPUT_UHD)
- else if (useUHDOutput) {
+ else if (mod_settings.useUHDOutput) {
fprintf(stderr, " UHD\n"
" Device: %s\n"
" Type: %s\n"
" master_clock_rate: %ld\n"
" refclk: %s\n"
" pps source: %s\n",
- outputuhd_conf.device.c_str(),
- outputuhd_conf.usrpType.c_str(),
- outputuhd_conf.masterClockRate,
- outputuhd_conf.refclk_src.c_str(),
- outputuhd_conf.pps_src.c_str());
+ mod_settings.outputuhd_conf.device.c_str(),
+ mod_settings.outputuhd_conf.usrpType.c_str(),
+ mod_settings.outputuhd_conf.masterClockRate,
+ mod_settings.outputuhd_conf.refclk_src.c_str(),
+ mod_settings.outputuhd_conf.pps_src.c_str());
}
#endif
#if defined(HAVE_SOAPYSDR)
- else if (useSoapyOutput) {
+ else if (mod_settings.useSoapyOutput) {
fprintf(stderr, " SoapySDR\n"
" Device: %s\n"
" master_clock_rate: %ld\n",
- outputsoapy_conf.device.c_str(),
- outputsoapy_conf.masterClockRate);
+ mod_settings.outputsoapy_conf.device.c_str(),
+ mod_settings.outputsoapy_conf.masterClockRate);
}
#endif
- else if (useZeroMQOutput) {
+ else if (mod_settings.useZeroMQOutput) {
fprintf(stderr, " ZeroMQ\n"
" Listening on: %s\n"
" Socket type : %s\n",
- outputName.c_str(),
- zmqOutputSocketType.c_str());
+ mod_settings.outputName.c_str(),
+ mod_settings.zmqOutputSocketType.c_str());
}
fprintf(stderr, " Sampling rate: ");
- if (outputRate > 1000) {
- if (outputRate > 1000000) {
- fprintf(stderr, "%.4g MHz\n", outputRate / 1000000.0f);
+ if (mod_settings.outputRate > 1000) {
+ if (mod_settings.outputRate > 1000000) {
+ fprintf(stderr, "%.4g MHz\n", mod_settings.outputRate / 1000000.0f);
} else {
- fprintf(stderr, "%.4g kHz\n", outputRate / 1000.0f);
+ fprintf(stderr, "%.4g kHz\n", mod_settings.outputRate / 1000.0f);
}
} else {
- fprintf(stderr, "%zu Hz\n", outputRate);
- }
-
-
- EdiReader ediReader(tist_offset_s, tist_delay_stages);
- EdiDecoder::ETIDecoder ediInput(ediReader, false);
- if (edi_max_delay_ms > 0.0f) {
- // setMaxDelay wants number of AF packets, which correspond to 24ms ETI frames
- ediInput.setMaxDelay(lroundf(edi_max_delay_ms / 24.0f));
+ fprintf(stderr, "%zu Hz\n", mod_settings.outputRate);
}
- EdiUdpInput ediUdpInput(ediInput);
-
- if (inputTransport == "file") {
- // Opening ETI input file
- if (inputFileReader.Open(inputName, loop) == -1) {
- fprintf(stderr, "Unable to open input file!\n");
- etiLog.level(error) << "Unable to open input file!";
- ret = -1;
- throw std::runtime_error("Unable to open input");
- }
+}
- m.inputReader = &inputFileReader;
- }
- else if (inputTransport == "zeromq") {
-#if !defined(HAVE_ZEROMQ)
- fprintf(stderr, "Error, ZeroMQ input transport selected, but not compiled in!\n");
- ret = -1;
- throw std::runtime_error("Unable to open input");
-#else
- inputZeroMQReader->Open(inputName, inputMaxFramesQueued);
- m.inputReader = inputZeroMQReader.get();
-#endif
- }
- else if (inputTransport == "tcp") {
- inputTcpReader->Open(inputName);
- m.inputReader = inputTcpReader.get();
- }
- else if (inputTransport == "edi") {
- ediUdpInput.Open(inputName);
- }
- else
- {
- fprintf(stderr, "Error, invalid input transport %s selected!\n", inputTransport.c_str());
- ret = -1;
- throw std::runtime_error("Unable to open input");
- }
+static shared_ptr<ModOutput> prepare_output(
+ mod_settings_t& s)
+{
+ shared_ptr<ModOutput> output;
- if (useFileOutput) {
- if (fileOutputFormat == "complexf") {
- output = make_shared<OutputFile>(outputName);
+ if (s.useFileOutput) {
+ if (s.fileOutputFormat == "complexf") {
+ output = make_shared<OutputFile>(s.outputName);
}
- else if (fileOutputFormat == "s8") {
+ else if (s.fileOutputFormat == "s8") {
// We must normalise the samples to the interval [-127.0; 127.0]
- normalise = 127.0f / normalise_factor;
+ s.normalise = 127.0f / normalise_factor;
- format_converter = make_shared<FormatConverter>();
-
- output = make_shared<OutputFile>(outputName);
+ output = make_shared<OutputFile>(s.outputName);
}
}
#if defined(HAVE_OUTPUT_UHD)
- else if (useUHDOutput) {
- normalise = 1.0f / normalise_factor;
- outputuhd_conf.sampleRate = outputRate;
- output = make_shared<OutputUHD>(outputuhd_conf);
+ else if (s.useUHDOutput) {
+ s.normalise = 1.0f / normalise_factor;
+ s.outputuhd_conf.sampleRate = s.outputRate;
+ output = make_shared<OutputUHD>(s.outputuhd_conf);
rcs.enrol((OutputUHD*)output.get());
}
#endif
#if defined(HAVE_SOAPYSDR)
- else if (useSoapyOutput) {
+ else if (s.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);
+ s.normalise = 1.0f / normalise_factor;
+ s.outputsoapy_conf.sampleRate = s.outputRate;
+ output = make_shared<OutputSoapy>(s.outputsoapy_conf);
rcs.enrol((OutputSoapy*)output.get());
}
#endif
#if defined(HAVE_ZEROMQ)
- else if (useZeroMQOutput) {
+ else if (s.useZeroMQOutput) {
/* We normalise the same way as for the UHD output */
- normalise = 1.0f / normalise_factor;
- if (zmqOutputSocketType == "pub") {
- output = make_shared<OutputZeroMQ>(outputName, ZMQ_PUB);
+ s.normalise = 1.0f / normalise_factor;
+ if (s.zmqOutputSocketType == "pub") {
+ output = make_shared<OutputZeroMQ>(s.outputName, ZMQ_PUB);
}
- else if (zmqOutputSocketType == "rep") {
- output = make_shared<OutputZeroMQ>(outputName, ZMQ_REP);
+ else if (s.zmqOutputSocketType == "rep") {
+ output = make_shared<OutputZeroMQ>(s.outputName, ZMQ_REP);
}
else {
std::stringstream ss;
- ss << "ZeroMQ output socket type " << zmqOutputSocketType << " invalid";
+ ss << "ZeroMQ output socket type " << s.zmqOutputSocketType << " invalid";
throw std::invalid_argument(ss.str());
}
}
#endif
+ return output;
+}
+
+int launch_modulator(int argc, char* argv[])
+{
+ int ret = 0;
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = &signalHandler;
+
+ if (sigaction(SIGINT, &sa, NULL) == -1) {
+ perror("sigaction");
+ return EXIT_FAILURE;
+ }
+
+ // Set timezone to UTC
+ setenv("TZ", "", 1);
+ tzset();
+
+ mod_settings_t mod_settings;
+ parse_args(argc, argv, mod_settings);
+
+ printStartupInfo();
+
+ if (not (mod_settings.useFileOutput or
+ mod_settings.useUHDOutput or
+ mod_settings.useZeroMQOutput or
+ mod_settings.useSoapyOutput)) {
+ etiLog.level(error) << "Output not specified";
+ fprintf(stderr, "Must specify output !");
+ throw std::runtime_error("Configuration error");
+ }
+
+ // When using the FIRFilter, increase the modulator offset pipelining delay
+ // by the correct amount
+ if (not mod_settings.filterTapsFilename.empty()) {
+ mod_settings.tist_delay_stages += FIRFILTER_PIPELINE_DELAY;
+ }
+
+ printModSettings(mod_settings);
+
+ modulator_data m;
+
+ shared_ptr<FormatConverter> format_converter;
+ if (mod_settings.useFileOutput and mod_settings.fileOutputFormat == "s8") {
+ format_converter = make_shared<FormatConverter>();
+ }
+
+ auto output = prepare_output(mod_settings);
+
// Set thread priority to realtime
if (int r = set_realtime_prio(1)) {
etiLog.level(error) << "Could not set priority for modulator:" << r;
}
set_thread_name("modulator");
- if (inputTransport == "edi") {
+ if (mod_settings.inputTransport == "edi") {
+ EdiReader ediReader(mod_settings.tist_offset_s, mod_settings.tist_delay_stages);
+ EdiDecoder::ETIDecoder ediInput(ediReader, false);
+ if (mod_settings.edi_max_delay_ms > 0.0f) {
+ // setMaxDelay wants number of AF packets, which correspond to 24ms ETI frames
+ ediInput.setMaxDelay(lroundf(mod_settings.edi_max_delay_ms / 24.0f));
+ }
+ EdiUdpInput ediUdpInput(ediInput);
+
+ ediUdpInput.Open(mod_settings.inputName);
if (not ediUdpInput.isEnabled()) {
etiLog.level(error) << "inputTransport is edi, but ediUdpInput is not enabled";
return -1;
@@ -804,9 +299,15 @@ int launch_modulator(int argc, char* argv[])
Flowgraph flowgraph;
auto modulator = make_shared<DabModulator>(
- ediReader, tiiConfig, outputRate, clockRate,
- dabMode, gainMode, digitalgain, normalise,
- filterTapsFilename);
+ ediReader,
+ mod_settings.tiiConfig,
+ mod_settings.outputRate,
+ mod_settings.clockRate,
+ mod_settings.dabMode,
+ mod_settings.gainMode,
+ mod_settings.digitalgain,
+ mod_settings.normalise,
+ mod_settings.filterTapsFilename);
if (format_converter) {
flowgraph.connect(modulator, format_converter);
@@ -817,12 +318,12 @@ int launch_modulator(int argc, char* argv[])
}
#if defined(HAVE_OUTPUT_UHD)
- if (useUHDOutput) {
+ if (mod_settings.useUHDOutput) {
((OutputUHD*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
#if defined(HAVE_SOAPYSDR)
- if (useSoapyOutput) {
+ if (mod_settings.useSoapyOutput) {
((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
@@ -849,20 +350,67 @@ int launch_modulator(int argc, char* argv[])
}
}
else {
+ shared_ptr<InputReader> inputReader;
+
+ if (mod_settings.inputTransport == "file") {
+ auto inputFileReader = make_shared<InputFileReader>();
+
+ // Opening ETI input file
+ if (inputFileReader->Open(mod_settings.inputName, mod_settings.loop) == -1) {
+ fprintf(stderr, "Unable to open input file!\n");
+ etiLog.level(error) << "Unable to open input file!";
+ ret = -1;
+ throw std::runtime_error("Unable to open input");
+ }
+
+ inputReader = inputFileReader;
+ }
+ else if (mod_settings.inputTransport == "zeromq") {
+#if !defined(HAVE_ZEROMQ)
+ fprintf(stderr, "Error, ZeroMQ input transport selected, but not compiled in!\n");
+ ret = -1;
+ throw std::runtime_error("Unable to open input");
+#else
+ auto inputZeroMQReader = make_shared<InputZeroMQReader>();
+ inputZeroMQReader->Open(mod_settings.inputName, mod_settings.inputMaxFramesQueued);
+ inputReader = inputZeroMQReader;
+#endif
+ }
+ else if (mod_settings.inputTransport == "tcp") {
+ auto inputTcpReader = make_shared<InputTcpReader>();
+ inputTcpReader->Open(mod_settings.inputName);
+ inputReader = inputTcpReader;
+ }
+ else
+ {
+ fprintf(stderr, "Error, invalid input transport %s selected!\n", mod_settings.inputTransport.c_str());
+ ret = -1;
+ throw std::runtime_error("Unable to open input");
+ }
+
+ bool run_again = true;
+
while (run_again) {
Flowgraph flowgraph;
+ m.inputReader = inputReader.get();
m.flowgraph = &flowgraph;
m.data.setLength(6144);
- EtiReader etiReader(tist_offset_s, tist_delay_stages);
+ EtiReader etiReader(mod_settings.tist_offset_s, mod_settings.tist_delay_stages);
m.etiReader = &etiReader;
auto input = make_shared<InputMemory>(&m.data);
auto modulator = make_shared<DabModulator>(
- etiReader, tiiConfig, outputRate, clockRate,
- dabMode, gainMode, digitalgain, normalise,
- filterTapsFilename);
+ etiReader,
+ mod_settings.tiiConfig,
+ mod_settings.outputRate,
+ mod_settings.clockRate,
+ mod_settings.dabMode,
+ mod_settings.gainMode,
+ mod_settings.digitalgain,
+ mod_settings.normalise,
+ mod_settings.filterTapsFilename);
if (format_converter) {
flowgraph.connect(modulator, format_converter);
@@ -873,17 +421,17 @@ int launch_modulator(int argc, char* argv[])
}
#if defined(HAVE_OUTPUT_UHD)
- if (useUHDOutput) {
+ if (mod_settings.useUHDOutput) {
((OutputUHD*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
#if defined(HAVE_SOAPYSDR)
- if (useSoapyOutput) {
+ if (mod_settings.useSoapyOutput) {
((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource());
}
#endif
- m.inputReader->PrintInfo();
+ inputReader->PrintInfo();
run_modulator_state_t st = run_modulator(m);
etiLog.log(trace, "DABMOD,run_modulator() = %d", st);
@@ -897,8 +445,8 @@ int launch_modulator(int argc, char* argv[])
case run_modulator_state_t::again:
etiLog.level(warn) << "Restart modulator.";
run_again = false;
- if (inputTransport == "file") {
- if (inputFileReader.Open(inputName, loop) == -1) {
+ if (auto in = dynamic_pointer_cast<InputFileReader>(inputReader)) {
+ if (in->Open(mod_settings.inputName, mod_settings.loop) == -1) {
etiLog.level(error) << "Unable to open input file!";
ret = 1;
}
@@ -906,19 +454,19 @@ int launch_modulator(int argc, char* argv[])
run_again = true;
}
}
- else if (inputTransport == "zeromq") {
#if defined(HAVE_ZEROMQ)
+ else if (auto in = dynamic_pointer_cast<InputZeroMQReader>(inputReader)) {
run_again = true;
// Create a new input reader
- inputZeroMQReader = make_shared<InputZeroMQReader>();
- inputZeroMQReader->Open(inputName, inputMaxFramesQueued);
- m.inputReader = inputZeroMQReader.get();
-#endif
+ auto inputZeroMQReader = make_shared<InputZeroMQReader>();
+ inputZeroMQReader->Open(mod_settings.inputName, mod_settings.inputMaxFramesQueued);
+ inputReader = inputZeroMQReader;
}
- else if (inputTransport == "tcp") {
- inputTcpReader = make_shared<InputTcpReader>();
- inputTcpReader->Open(inputName);
- m.inputReader = inputTcpReader.get();
+#endif
+ else if (auto in = dynamic_pointer_cast<InputTcpReader>(inputReader)) {
+ auto inputTcpReader = make_shared<InputTcpReader>();
+ inputTcpReader->Open(mod_settings.inputName);
+ inputReader = inputTcpReader;
}
break;
case run_modulator_state_t::reconfigure:
diff --git a/src/Utils.cpp b/src/Utils.cpp
index f4610a4..5bd93d9 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -30,7 +30,7 @@
#include <sys/prctl.h>
#include <pthread.h>
-void printUsage(char* progName)
+void printUsage(const char* progName)
{
FILE* out = stderr;
@@ -89,13 +89,19 @@ void printVersion(void)
{
FILE *out = stderr;
- fprintf(out, "Welcome to %s %s, compiled at %s, %s\n\n",
- PACKAGE, VERSION, __DATE__, __TIME__);
+ fprintf(out, "%s %s, compiled at %s, %s\n\n",
+ PACKAGE,
+#if defined(GITVERSION)
+ GITVERSION
+#else
+ VERSION
+#endif
+ ,__DATE__, __TIME__);
fprintf(out,
" ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n"
- " 2009, 2010, 2011, 2012 Communications Research Centre (CRC),\n"
+ " 2005 -- 2012 Communications Research Centre (CRC),\n"
" and\n"
- " Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li\n"
+ " Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li\n"
"\n"
" http://opendigitalradio.org\n"
"\n"
@@ -113,7 +119,39 @@ void printVersion(void)
" with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.\n"
"\n"
);
+}
+void printStartupInfo()
+{
+ std::cerr << "ODR-DabMod version " <<
+#if defined(GITVERSION)
+ GITVERSION
+#else
+ VERSION
+#endif
+ << std::endl;
+
+ std::cerr << "Compiled with features: " <<
+#if defined(HAVE_ZEROMQ)
+ "zeromq " <<
+#endif
+#if defined(HAVE_OUTPUT_UHD)
+ "output_uhd " <<
+#endif
+#if defined(HAVE_SOAPYSDR)
+ "output_soapysdr " <<
+#endif
+#if defined(__FAST_MATH__)
+ "fast-math" <<
+#endif
+ "\n";
+
+ etiLog.level(info) << "Starting up version " <<
+#if defined(GITVERSION)
+ GITVERSION;
+#else
+ VERSION;
+#endif
}
int set_realtime_prio(int prio)
diff --git a/src/Utils.h b/src/Utils.h
index e2b8a2f..8da3a1b 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -37,10 +37,12 @@
#include <time.h>
#include <string>
-void printUsage(char* progName);
+void printUsage(const char* progName);
void printVersion(void);
+void printStartupInfo(void);
+
inline long timespecdiff_us(struct timespec& oldTime, struct timespec& time)
{
long tv_sec;