aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/SDR.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/SDR.cpp')
-rw-r--r--src/output/SDR.cpp442
1 files changed, 442 insertions, 0 deletions
diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp
new file mode 100644
index 0000000..5290e5d
--- /dev/null
+++ b/src/output/SDR.cpp
@@ -0,0 +1,442 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2018
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMod.
+
+ ODR-DabMod is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMod is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "output/SDR.h"
+
+#include "PcDebug.h"
+#include "Log.h"
+#include "RemoteControl.h"
+#include "Utils.h"
+
+#include <cmath>
+#include <iostream>
+#include <assert.h>
+#include <stdexcept>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pthread.h>
+
+using namespace std;
+
+namespace Output {
+
+// Maximum number of frames that can wait in frames
+static constexpr size_t FRAMES_MAX_SIZE = 8;
+
+// If the timestamp is further in the future than
+// 100 seconds, abort
+static constexpr double TIMESTAMP_ABORT_FUTURE = 100;
+
+// Add a delay to increase buffers when
+// frames are too far in the future
+static constexpr double TIMESTAMP_MARGIN_FUTURE = 0.5;
+
+SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :
+ ModOutput(), ModMetadata(), RemoteControllable("sdr"),
+ m_config(config),
+ m_running(false),
+ m_device(device)
+{
+ // muting is remote-controllable
+ m_config.muting = false;
+
+ time_last_frame.tv_sec = 0;
+ time_last_frame.tv_nsec = 0;
+
+ m_device_thread = std::thread(&SDR::process_thread_entry, this);
+
+ m_dpd_feedback_server = make_shared<DPDFeedbackServer>(
+ m_device,
+ m_config.dpdFeedbackServerPort,
+ m_config.sampleRate);
+}
+
+SDR::~SDR()
+{
+ stop();
+}
+
+void SDR::stop()
+{
+ m_running.store(false);
+
+ FrameData end_marker;
+ end_marker.buf.resize(0);
+ m_queue.push(end_marker);
+
+ if (m_device_thread.joinable()) {
+ m_device_thread.join();
+ }
+}
+
+int SDR::process(Buffer *dataIn)
+{
+ const uint8_t* pDataIn = (uint8_t*)dataIn->getData();
+ m_frame.resize(dataIn->getLength());
+ std::copy(pDataIn, pDataIn + dataIn->getLength(),
+ m_frame.begin());
+
+ // We will effectively transmit the frame once we got the metadata.
+
+ return dataIn->getLength();
+}
+
+meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn)
+{
+ if (m_device) {
+ FrameData frame;
+ frame.buf = std::move(m_frame);
+
+ if (metadataIn.empty()) {
+ etiLog.level(info) <<
+ "SDR output: dropping one frame with invalid FCT";
+ }
+ else {
+ /* In transmission modes where several ETI frames are needed to
+ * build one transmission frame (like in TM 1), we will have
+ * several entries in metadataIn. Take the first one, which
+ * comes from the earliest ETI frame.
+ * This behaviour is different to earlier versions of ODR-DabMod,
+ * which took the timestamp from the latest ETI frame.
+ */
+ frame.ts = *(metadataIn[0].ts);
+
+ // TODO check device running
+
+ try {
+ if (m_dpd_feedback_server) {
+ m_dpd_feedback_server->set_tx_frame(frame.buf, frame.ts);
+ }
+ }
+ catch (const runtime_error& e) {
+ etiLog.level(warn) <<
+ "SDR output: Feedback server failed, restarting...";
+
+ m_dpd_feedback_server = std::make_shared<DPDFeedbackServer>(
+ m_device,
+ m_config.dpdFeedbackServerPort,
+ m_config.sampleRate);
+ }
+
+ size_t num_frames = m_queue.push_wait_if_full(frame,
+ FRAMES_MAX_SIZE);
+ etiLog.log(trace, "SDR,push %zu", num_frames);
+ }
+ }
+ else {
+ // Ignore frame
+ }
+ return {};
+}
+
+
+void SDR::process_thread_entry()
+{
+ // Set thread priority to realtime
+ if (int ret = set_realtime_prio(1)) {
+ etiLog.level(error) << "Could not set priority for SDR device thread:" << ret;
+ }
+
+ set_thread_name("sdrdevice");
+
+ last_tx_time_initialised = false;
+
+ size_t last_num_underflows = 0;
+ size_t pop_prebuffering = FRAMES_MAX_SIZE;
+
+ m_running.store(true);
+
+ while (m_running.load()) {
+ struct FrameData frame;
+ etiLog.log(trace, "SDR,wait");
+ m_queue.wait_and_pop(frame, pop_prebuffering);
+ etiLog.log(trace, "SDR,pop");
+
+ if (m_running.load() == false or frame.buf.empty()) {
+ break;
+ }
+
+ if (m_device) {
+ handle_frame(frame);
+
+ const auto rs = m_device->get_run_statistics();
+
+ /* Ensure we fill frames after every underrun and
+ * at startup to reduce underrun likelihood. */
+ if (last_num_underflows < rs.num_underruns) {
+ pop_prebuffering = FRAMES_MAX_SIZE;
+ }
+ else {
+ pop_prebuffering = 1;
+ }
+
+ last_num_underflows = rs.num_underruns;
+ }
+
+ }
+
+ m_running.store(false);
+ etiLog.level(warn) << "SDR Device thread terminated";
+}
+
+const char* SDR::name()
+{
+ if (m_device) {
+ m_name = "OutputSDR(";
+ m_name += m_device->device_name();
+ m_name += ")";
+ }
+ else {
+ m_name = "OutputSDR(<no device>)";
+ }
+ return m_name.c_str();
+}
+
+void SDR::sleep_through_frame()
+{
+ struct timespec now;
+ if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+ stringstream ss;
+ ss << "clock_gettime failure: " << strerror(errno);
+ throw runtime_error(ss.str());
+ }
+
+ if (time_last_frame.tv_sec == 0) {
+ if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) {
+ stringstream ss;
+ ss << "clock_gettime failure: " << strerror(errno);
+ throw runtime_error(ss.str());
+ }
+ }
+
+ long delta_us = timespecdiff_us(time_last_frame, now);
+ long wait_time_us = transmission_frame_duration_ms(m_config.dabMode);
+
+ if (wait_time_us - delta_us > 0) {
+ usleep(wait_time_us - delta_us);
+ }
+
+ time_last_frame.tv_nsec += wait_time_us * 1000;
+ while (time_last_frame.tv_nsec >= 1000000000L) {
+ time_last_frame.tv_nsec -= 1000000000L;
+ time_last_frame.tv_sec++;
+ }
+}
+
+void SDR::handle_frame(struct FrameData& frame)
+{
+ // Assumes m_device is valid
+
+ constexpr double tx_timeout = 20.0;
+
+ if (not m_device->is_clk_source_ok()) {
+ sleep_through_frame();
+ return;
+ }
+
+ double device_time = m_device->get_real_secs();
+ bool timestamp_discontinuity = false;
+ const auto& time_spec = frame.ts;
+
+ if (sourceContainsTimestamp) {
+ // Tx time from MNSC and TIST
+ const uint32_t tx_second = frame.ts.timestamp_sec;
+ const uint32_t tx_pps = frame.ts.timestamp_pps;
+
+ if (not frame.ts.timestamp_valid) {
+ /* We have not received a full timestamp through
+ * MNSC. We sleep through the frame.
+ */
+ etiLog.level(info) <<
+ "OutputSDR: Throwing sample " << frame.ts.fct <<
+ " away: incomplete timestamp " << tx_second <<
+ " / " << tx_pps;
+ return;
+ }
+
+ if (last_tx_time_initialised) {
+ const size_t sizeIn = frame.buf.size() / sizeof(complexf);
+ uint64_t increment = (uint64_t)sizeIn * 16384000ul /
+ (uint64_t)m_config.sampleRate;
+ // samps * ticks/s / (samps/s)
+ // (samps * ticks * s) / (s * samps)
+ // ticks
+
+ uint32_t expected_sec = last_tx_second + increment / 16384000ul;
+ uint32_t expected_pps = last_tx_pps + increment % 16384000ul;
+
+ while (expected_pps >= 16384000) {
+ expected_sec++;
+ expected_pps -= 16384000;
+ }
+
+ if (expected_sec != tx_second or expected_pps != tx_pps) {
+ etiLog.level(warn) << "OutputSDR: timestamp irregularity!" <<
+ std::fixed <<
+ " Expected " <<
+ expected_sec << "+" << (double)expected_pps/16384000.0 <<
+ "(" << expected_pps << ")" <<
+ " Got " <<
+ tx_second << "+" << (double)tx_pps/16384000.0 <<
+ "(" << tx_pps << ")";
+
+ timestamp_discontinuity = true;
+ }
+ }
+
+ last_tx_second = tx_second;
+ last_tx_pps = tx_pps;
+ last_tx_time_initialised = true;
+
+ const double pps_offset = tx_pps / 16384000.0;
+
+ etiLog.log(trace, "SDR,tist %f", time_spec.get_real_secs());
+
+ if (time_spec.get_real_secs() + tx_timeout < device_time) {
+ etiLog.level(warn) <<
+ "OutputSDR: Timestamp in the past! offset: " <<
+ std::fixed <<
+ time_spec.get_real_secs() - device_time <<
+ " (" << device_time << ")"
+ " frame " << frame.ts.fct <<
+ ", tx_second " << tx_second <<
+ ", pps " << pps_offset;
+ return;
+ }
+
+ if (time_spec.get_real_secs() > device_time + TIMESTAMP_ABORT_FUTURE) {
+ etiLog.level(error) <<
+ "OutputSDR: Timestamp way too far in the future! offset: " <<
+ std::fixed <<
+ time_spec.get_real_secs() - device_time;
+ throw std::runtime_error("Timestamp error. Aborted.");
+ }
+ }
+ else { // !sourceContainsTimestamp
+ if (m_config.muting or m_config.muteNoTimestamps) {
+ /* There was some error decoding the timestamp */
+ if (m_config.muting) {
+ etiLog.log(info,
+ "OutputSDR: Muting sample %d requested\n",
+ frame.ts.fct);
+ }
+ else {
+ etiLog.log(info,
+ "OutputSDR: Muting sample %d : no timestamp\n",
+ frame.ts.fct);
+ }
+ return;
+ }
+ }
+
+ if (timestamp_discontinuity) {
+ frame.ts.timestamp_refresh = true;
+ }
+ m_device->transmit_frame(frame);
+}
+
+// =======================================
+// Remote Control
+// =======================================
+void SDR::set_parameter(const string& parameter, const string& value)
+{
+ stringstream ss(value);
+ ss.exceptions ( stringstream::failbit | stringstream::badbit );
+
+ if (parameter == "txgain") {
+ ss >> m_config.txgain;
+ m_device->set_txgain(m_config.txgain);
+ }
+ else if (parameter == "rxgain") {
+ ss >> m_config.rxgain;
+ // TODO myUsrp->set_rx_gain(m_config.rxgain);
+ throw ParameterError("Parameter " + parameter + " is TODO.");
+ }
+ else if (parameter == "freq") {
+ ss >> m_config.frequency;
+ m_device->tune(m_config.lo_offset, m_config.frequency);
+ m_config.frequency = m_device->get_tx_freq();
+ }
+ else if (parameter == "muting") {
+ ss >> m_config.muting;
+ }
+ else if (parameter == "underruns" or
+ parameter == "latepackets" or
+ parameter == "frames") {
+ throw ParameterError("Parameter " + parameter + " is read-only.");
+ }
+ else {
+ stringstream ss_err;
+ ss_err << "Parameter '" << parameter
+ << "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss_err.str());
+ }
+}
+
+const string SDR::get_parameter(const string& parameter) const
+{
+ stringstream ss;
+ if (parameter == "txgain") {
+ ss << m_config.txgain;
+ }
+ else if (parameter == "rxgain") {
+ ss << m_config.rxgain;
+ }
+ else if (parameter == "freq") {
+ ss << m_config.frequency;
+ }
+ else if (parameter == "muting") {
+ ss << m_config.muting;
+ }
+ else if (parameter == "underruns" or
+ parameter == "latepackets" or
+ parameter == "frames" ) {
+ if (not m_device) {
+ throw ParameterError("OutputSDR has no device");
+ }
+ const auto stat = m_device->get_run_statistics();
+
+ if (parameter == "underruns") {
+ ss << stat.num_underruns;
+ }
+ else if (parameter == "latepackets") {
+ ss << stat.num_late_packets;
+ }
+ else if (parameter == "frames") {
+ ss << stat.num_frames_modulated;
+ }
+ }
+ else {
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+ return ss.str();
+}
+
+} // namespace Output