diff options
Diffstat (limited to 'src/OutputSoapy.cpp')
-rw-r--r-- | src/OutputSoapy.cpp | 287 |
1 files changed, 287 insertions, 0 deletions
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 + |