/*
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 .
*/
#include "OutputSoapy.h"
#ifdef HAVE_SOAPYSDR
#include
#include
#include
#include "Log.h"
#include "Utils.h"
#include
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 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(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(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