/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Edouard Griffiths, F4EXB // // // // This program 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 as version 3 of the License, or // // // // This program 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 V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include "dsp/samplesourcefifo.h" #include "soapysdroutputthread.h" SoapySDROutputThread::SoapySDROutputThread(SoapySDR::Device* dev, unsigned int nbTxChannels, QObject* parent) : QThread(parent), m_running(false), m_dev(dev), m_sampleRate(0), m_nbChannels(nbTxChannels), m_interpolatorType(InterpolatorFloat) { qDebug("SoapySDROutputThread::SoapySDROutputThread"); m_channels = new Channel[nbTxChannels]; } SoapySDROutputThread::~SoapySDROutputThread() { qDebug("SoapySDROutputThread::~SoapySDROutputThread"); if (m_running) { stopWork(); } delete[] m_channels; } void SoapySDROutputThread::startWork() { if (m_running) { return; } m_startWaitMutex.lock(); start(); while(!m_running) { m_startWaiter.wait(&m_startWaitMutex, 100); } m_startWaitMutex.unlock(); } void SoapySDROutputThread::stopWork() { if (!m_running) { return; } m_running = false; wait(); } void SoapySDROutputThread::run() { m_running = true; m_startWaiter.wakeAll(); unsigned int nbFifos = getNbFifos(); if ((m_nbChannels > 0) && (nbFifos > 0)) { // build channels list std::vector channels(m_nbChannels); std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. //initialize the sample rate for all channels qDebug("SoapySDROutputThread::run: m_sampleRate: %u", m_sampleRate); for (const auto &it : channels) { m_dev->setSampleRate(SOAPY_SDR_TX, it, m_sampleRate); } // Determine sample format to be used double fullScale(0.0); std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_TX, channels.front(), fullScale); qDebug("SoapySDROutputThread::run: format: %s fullScale: %f", format.c_str(), fullScale); if ((format == "CS8") && (fullScale == 128.0)) { // 8 bit signed - native m_interpolatorType = Interpolator8; } else if ((format == "CS16") && (fullScale == 2048.0)) { // 12 bit signed - native m_interpolatorType = Interpolator12; } else if ((format == "CS16") && (fullScale == 32768.0)) { // 16 bit signed - native m_interpolatorType = Interpolator16; } else { // for other types make a conversion to float m_interpolatorType = InterpolatorFloat; format = "CF32"; } unsigned int elemSize = SoapySDR::formatToSize(format); // sample (I+Q) size in bytes SoapySDR::Stream *stream = m_dev->setupStream(SOAPY_SDR_TX, format, channels); //allocate buffers for the stream read/write const unsigned int numElems = m_dev->getStreamMTU(stream); // number of samples (I+Q) std::vector> buffMem(m_nbChannels, std::vector(elemSize*numElems)); std::vector buffs(m_nbChannels); for (std::size_t i = 0; i < m_nbChannels; i++) { buffs[i] = buffMem[i].data(); } m_dev->activateStream(stream); int flags(0); long long timeNs(0); float blockTime = ((float) numElems) / (m_sampleRate <= 0 ? 1024000 : m_sampleRate); long timeoutUs = 2000000 * blockTime; // 10 times the block time qDebug("SoapySDROutputThread::run: numElems: %u elemSize: %u timeoutUs: %ld", numElems, elemSize, timeoutUs); qDebug("SoapySDROutputThread::run: start running loop"); while (m_running) { int ret = m_dev->writeStream(stream, buffs.data(), numElems, flags, timeNs, timeoutUs); if (ret == SOAPY_SDR_TIMEOUT) { qWarning("SoapySDROutputThread::run: timeout: flags: %d timeNs: %lld timeoutUs: %ld", flags, timeNs, timeoutUs); } else if (ret < 0) { qCritical("SoapySDROutputThread::run: Unexpected write stream error: %s", SoapySDR::errToStr(ret)); break; } if (m_nbChannels > 1) { callbackMO(buffs, numElems); // size given in number of samples (1 item per sample) } else { switch (m_interpolatorType) { case Interpolator8: callbackSO8((qint8*) buffs[0], numElems); break; case Interpolator12: callbackSO12((qint16*) buffs[0], numElems); break; case Interpolator16: callbackSO16((qint16*) buffs[0], numElems); break; case InterpolatorFloat: default: // TODO break; } } } qDebug("SoapySDROutputThread::run: stop running loop"); m_dev->deactivateStream(stream); m_dev->closeStream(stream); } else { qWarning("SoapySDROutputThread::run: no channels or FIFO allocated. Aborting"); } m_running = false; } unsigned int SoapySDROutputThread::getNbFifos() { unsigned int fifoCount = 0; for (unsigned int i = 0; i < m_nbChannels; i++) { if (m_channels[i].m_sampleFifo) { fifoCount++; } } return fifoCount; } void SoapySDROutputThread::setLog2Interpolation(unsigned int channel, unsigned int log2_interp) { if (channel < m_nbChannels) { m_channels[channel].m_log2Interp = log2_interp; } } unsigned int SoapySDROutputThread::getLog2Interpolation(unsigned int channel) const { if (channel < m_nbChannels) { return m_channels[channel].m_log2Interp; } else { return 0; } } void SoapySDROutputThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) { if (channel < m_nbChannels) { m_channels[channel].m_sampleFifo = sampleFifo; } } SampleSourceFifo *SoapySDROutputThread::getFifo(unsigned int channel) { if (channel < m_nbChannels) { return m_channels[channel].m_sampleFifo; } else { return 0; } } void SoapySDROutputThread::callbackMO(std::vector& buffs, qint32 samplesPerChannel) { for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) { if (m_channels[ichan].m_sampleFifo) { switch (m_interpolatorType) { case Interpolator8: callbackSO8((qint8*) buffs[ichan], samplesPerChannel, ichan); break; case Interpolator12: callbackSO12((qint16*) buffs[ichan], samplesPerChannel, ichan); break; case Interpolator16: callbackSO16((qint16*) buffs[ichan], samplesPerChannel, ichan); break; case InterpolatorFloat: default: // TODO break; } } else // no FIFO for this channel means channel is unused: fill with zeros { switch (m_interpolatorType) { case Interpolator8: std::fill((qint8*) buffs[ichan], (qint8*) buffs[ichan] + 2*samplesPerChannel, 0); break; case Interpolator12: case Interpolator16: std::fill((qint16*) buffs[ichan], (qint16*) buffs[ichan] + 2*samplesPerChannel, 0); break; case InterpolatorFloat: default: // TODO break; } } } } // Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) void SoapySDROutputThread::callbackSO8(qint8* buf, qint32 len, unsigned int channel) { if (m_channels[channel].m_sampleFifo) { float bal = m_channels[channel].m_sampleFifo->getRWBalance(); if (bal < -0.25) { qDebug("SoapySDROutputThread::callbackSO8: read lags: %f", bal); } else if (bal > 0.25) { qDebug("SoapySDROutputThread::callbackSO8: read leads: %f", bal); } SampleVector::iterator beginRead; m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); if (bal < -0.25) { qDebug("SoapySDROutputThread::callbackSO12: read lags: %f", bal); } else if (bal > 0.25) { qDebug("SoapySDROutputThread::callbackSO12: read leads: %f", bal); } SampleVector::iterator beginRead; m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); if (bal < -0.25) { qDebug("SoapySDROutputThread::callbackSO16: read lags: %f", bal); } else if (bal > 0.25) { qDebug("SoapySDROutputThread::callbackSO16: read leads: %f", bal); } SampleVector::iterator beginRead; m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<