1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 17:58:43 -05:00
sdrangel/plugins/channeltx/moddatv/datvmodsource.cpp
2021-03-18 20:33:52 +00:00

641 lines
21 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#define BOOST_CHRONO_HEADER_ONLY
#include <boost/chrono/chrono.hpp>
//#include <time.h>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QUdpSocket>
#include <QHostAddress>
#include <QNetworkDatagram>
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "util/messagequeue.h"
#include "datvmodreport.h"
#include "datvmodsource.h"
const int DATVModSource::m_levelNbSamples = 10000; // every 10ms
// Get transport stream bitrate from file
int DATVModSource::getTSBitrate(const QString& filename)
{
AVFormatContext *fmtCtx = nullptr;
QByteArray ba = filename.toLocal8Bit();
const char *filenameChars = ba.data();
if (avformat_open_input(&fmtCtx, filenameChars, nullptr, nullptr) < 0)
{
qCritical() << "DATVModSource: Could not open source file " << filename;
return -1;
}
if (avformat_find_stream_info(fmtCtx, nullptr) < 0)
{
qCritical() << "DATVModSource: Could not find stream information for " << filename;
avformat_close_input(&fmtCtx);
return -1;
}
int bitrate = fmtCtx->bit_rate;
avformat_close_input(&fmtCtx);
return bitrate;
}
// Get data bitrate (i.e. excluding FEC overhead)
int DATVModSource::getDVBSDataBitrate(const DATVModSettings& settings)
{
float rsFactor = DVBS::tsPacketLen/(float)DVBS::rsPacketLen;
float convFactor;
float bitsPerSymbol;
switch (settings.m_fec)
{
case DATVModSettings::FEC12:
convFactor = 1.0f/2.0f;
break;
case DATVModSettings::FEC23:
convFactor = 2.0f/3.0f;
break;
case DATVModSettings::FEC34:
convFactor = 3.0f/4.0f;
break;
case DATVModSettings::FEC56:
convFactor = 5.0f/6.0f;
break;
case DATVModSettings::FEC78:
convFactor = 7.0f/8.0f;
break;
case DATVModSettings::FEC45:
convFactor = 4.0f/5.0f;
break;
case DATVModSettings::FEC89:
convFactor = 8.0f/9.0f;
break;
case DATVModSettings::FEC910:
convFactor = 9.0f/10.0f;
break;
case DATVModSettings::FEC14:
convFactor = 1.0f/4.0f;
break;
case DATVModSettings::FEC13:
convFactor = 1.0f/3.0f;
break;
case DATVModSettings::FEC25:
convFactor = 2.0f/5.0f;
break;
case DATVModSettings::FEC35:
convFactor = 3.0f/5.0f;
break;
}
switch (settings.m_modulation)
{
case DATVModSettings::BPSK:
bitsPerSymbol = 1.0f;
break;
case DATVModSettings::QPSK:
bitsPerSymbol = 2.0f;
break;
case DATVModSettings::PSK8:
bitsPerSymbol = 3.0f;
break;
case DATVModSettings::APSK16:
bitsPerSymbol = 4.0f;
break;
case DATVModSettings::APSK32:
bitsPerSymbol = 5.0f;
break;
}
return std::round(settings.m_symbolRate * bitsPerSymbol * rsFactor * convFactor);
}
void DATVModSource::checkBitrates()
{
int dataBitrate = getDVBSDataBitrate(m_settings);
qDebug() << "MPEG-TS bitrate: " << m_mpegTSBitrate;
qDebug() << "DVB-S data bitrate: " << dataBitrate;
if (dataBitrate < m_mpegTSBitrate)
qWarning() << "DVB-S data bitrate is lower than the bitrate of the MPEG transport stream";
m_tsRatio = m_mpegTSBitrate/(float)dataBitrate;
}
DATVModSource::DATVModSource() :
m_sampleIdx(0),
m_frameIdx(0),
m_frameCount(0),
m_tsRatio(0.0f),
m_symbolCount(0),
m_symbolSel(0),
m_symbolIdx(0),
m_samplesPerSymbol(1),
m_udpSocket(nullptr),
m_udpByteCount(0),
m_udpBufferIdx(0),
m_udpBufferCount(0),
m_sampleRate(0),
m_channelSampleRate(1000000),
m_channelFrequencyOffset(0),
m_tsFileOK(false),
m_messageQueueToGUI(nullptr)
{
m_interpolatorDistanceRemain = 0.0f;
m_interpolatorDistance = 1.0f;
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
applySettings(m_settings, true);
}
DATVModSource::~DATVModSource()
{
}
void DATVModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
{
std::for_each(
begin,
begin + nbSamples,
[this](Sample& s) {
pullOne(s);
}
);
}
void DATVModSource::prefetch(unsigned int nbSamples)
{
(void) nbSamples;
}
void DATVModSource::pullOne(Sample& sample)
{
if (m_settings.m_channelMute)
{
sample.m_real = 0.0f;
sample.m_imag = 0.0f;
return;
}
Complex ci;
if (m_sampleRate == m_channelSampleRate) // no interpolation nor decimation
{
modulateSample();
pullFinalize(m_modSample, sample);
}
else
{
if (m_interpolatorDistance > 1.0f) // decimate
{
modulateSample();
while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
modulateSample();
}
else
{
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
modulateSample();
}
m_interpolatorDistanceRemain += m_interpolatorDistance;
pullFinalize(ci, sample);
}
}
void DATVModSource::pullFinalize(Complex& ci, Sample& sample)
{
ci *= m_carrierNco.nextIQ(); // shift to carrier frequency
double magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
m_movingAverage(magsq);
sample.m_real = (FixReal) (ci.real() * SDR_TX_SCALEF);
sample.m_imag = (FixReal) (ci.imag() * SDR_TX_SCALEF);
}
void DATVModSource::modulateSample()
{
Real i, q;
if (m_sampleIdx == 0)
{
while (m_symbolCount == 0)
{
bool tsFileReady = (m_settings.m_source == DATVModSettings::SourceFile)
&& m_settings.m_tsFilePlay
&& m_tsFileOK
&& !m_mpegTSStream.eof();
if ( tsFileReady
&& (m_frameIdx/(m_frameCount+1.0f) < m_tsRatio) // Insert null packets if file rate is lower than DVB-S data rate
)
{
// Read transport stream packet from file
m_mpegTSStream.read((char *)m_mpegTS, sizeof(m_mpegTS));
m_frameIdx++;
m_frameCount++;
}
else if ( (m_settings.m_source == DATVModSettings::SourceUDP)
&& (m_udpBufferIdx < m_udpBufferCount)
)
{
// Copy transport stream packet from UDP buffer
memcpy(m_mpegTS, &m_udpBuffer[m_udpBufferIdx*sizeof(m_mpegTS)], sizeof(m_mpegTS));
m_udpBufferIdx++;
}
else if ( (m_settings.m_source == DATVModSettings::SourceUDP)
&& ((m_udpSocket != nullptr) && m_udpSocket->hasPendingDatagrams())
)
{
// Get transport stream packets from UDP - buffer if more than one
QNetworkDatagram datagram = m_udpSocket->receiveDatagram();
QByteArray ba = datagram.data();
int size = ba.size();
char *data = ba.data();
if (size <= (int)sizeof(m_udpBuffer))
{
memcpy(m_mpegTS, data, sizeof(m_mpegTS));
if (size >= (int)sizeof(m_mpegTS))
memcpy(&m_udpBuffer[0], &data[sizeof(m_mpegTS)], size - sizeof(m_mpegTS));
m_udpBufferIdx = 0;
m_udpBufferCount = (size / sizeof(m_mpegTS)) - 1;
if (size % sizeof(m_mpegTS) != 0)
qWarning() << "DATVModSource::modulateSample: UDP packet size (" << size << ") is not a multiple of " << sizeof(m_mpegTS);
}
else
qWarning() << "DATVModSource::modulateSample: UDP packet size (" << size << ") exceeds buffer size " << sizeof(m_udpBuffer) << ")";
m_udpByteCount += ba.size();
}
else
{
// Insert null packet. PID=0x1fff
memset(m_mpegTS, 0, sizeof(m_mpegTS));
m_mpegTS[0] = 0x47; // Sync byte
m_mpegTS[1] = 0x01;
m_mpegTS[2] = 0xff;
m_mpegTS[3] = 0xff;
m_mpegTS[4] = 0x10;
if (tsFileReady)
m_frameCount++;
//qDebug() << "null " << tsFileReady << " " << (m_frameIdx/(m_frameCount+1.0f)) << " " << m_tsRatio;
}
if (m_settings.m_standard == DATVModSettings::DVB_S)
{
// Encode using DVB-S
m_symbolCount = m_dvbs.encode(m_mpegTS, m_iqSymbols);
}
// Loop file if we reach the end
if ((m_frameIdx*DVBS::tsPacketLen >= m_mpegTSSize) && m_settings.m_tsFilePlayLoop)
{
m_frameIdx = 0;
m_frameCount = 0;
}
m_symbolIdx = 0;
}
if (m_settings.m_modulation == DATVModSettings::BPSK)
{
// BPSK
i = m_pulseShapeI.filter(m_iqSymbols[m_symbolIdx*2+m_symbolSel] ? -1.0f : 1.0f);
q = 0.0f;
if (m_symbolSel == 1)
{
m_symbolIdx++;
m_symbolCount--;
m_symbolSel = 0;
}
else
m_symbolSel++;
}
else
{
// QPSK
if (m_settings.m_standard == DATVModSettings::DVB_S)
{
// Does the 45-degree rotation matter?
// Makes a little difference to amplitude of filter output, but we could scale that
i = m_pulseShapeI.filter(m_iqSymbols[m_symbolIdx*2] ? -1.0f : 1.0f);
q = m_pulseShapeQ.filter(m_iqSymbols[m_symbolIdx*2+1] ? -1.0f : 1.0f);
/*
int sym = (m_iqSymbols[m_symbolIdx*2] << 1) | m_iqSymbols[m_symbolIdx*2+1];
if (sym == 0)
{
i = m_pulseShapeI.filter(cos(M_PI/4));
q = m_pulseShapeQ.filter(sin(M_PI/4));
}
else if (sym == 1)
{
i = m_pulseShapeI.filter(cos(7*M_PI/4));
q = m_pulseShapeQ.filter(sin(7*M_PI/4));
}
else if (sym == 2)
{
i = m_pulseShapeI.filter(cos(3*M_PI/4));
q = m_pulseShapeQ.filter(sin(3*M_PI/4));
}
else if (sym == 3)
{
i = m_pulseShapeI.filter(cos(5*M_PI/4));
q = m_pulseShapeQ.filter(sin(5*M_PI/4));
}
*/
}
m_symbolIdx++;
m_symbolCount--;
}
}
else
{
i = m_pulseShapeI.filter(0.0f);
q = m_pulseShapeQ.filter(0.0f);
}
m_sampleIdx++;
if (m_sampleIdx >= m_samplesPerSymbol)
m_sampleIdx = 0;
m_modSample.real(i);
m_modSample.imag(q);
// These levels aren't currently used in the GUI
Real t = std::abs(m_modSample);
calculateLevel(t);
}
void DATVModSource::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
{
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
m_levelSum += sample * sample;
m_levelCalcCount++;
}
else
{
m_rmsLevel = std::sqrt(m_levelSum / m_levelNbSamples);
m_peakLevelOut = m_peakLevel;
m_peakLevel = 0.0f;
m_levelSum = 0.0f;
m_levelCalcCount = 0;
}
}
void DATVModSource::openTsFile(const QString& fileName)
{
m_tsFileOK = false;
m_mpegTSBitrate = getTSBitrate(fileName);
if (m_mpegTSBitrate > 0)
{
m_mpegTSStream.open(qPrintable(fileName), std::ifstream::binary);
if (m_mpegTSStream.is_open())
{
m_mpegTSStream.seekg (0, m_mpegTSStream.end);
m_mpegTSSize = m_mpegTSStream.tellg();
m_mpegTSStream.seekg (0, m_mpegTSStream.beg);
m_frameIdx = 0;
m_frameCount = 0;
m_tsFileOK = true;
}
checkBitrates();
}
else
qDebug() << "DATVModSource::openTsFile: Failed to get bitrate for transport stream file: " << fileName;
if (m_tsFileOK)
{
m_settings.m_tsFileName = fileName;
if (getMessageQueueToGUI())
{
DATVModReport::MsgReportTsFileSourceStreamData *report;
report = DATVModReport::MsgReportTsFileSourceStreamData::create(m_mpegTSBitrate, m_mpegTSSize);
getMessageQueueToGUI()->push(report);
}
}
else
{
m_settings.m_tsFileName.clear();
qDebug() << "DATVModSource::openTsFile: Cannot open file: " << fileName;
}
}
void DATVModSource::seekTsFileStream(int seekPercentage)
{
if (m_tsFileOK)
{
m_frameIdx = ((m_mpegTSSize / DVBS::tsPacketLen) * seekPercentage) / 100;
m_mpegTSStream.seekg (m_frameIdx * (std::streampos)DVBS::tsPacketLen, m_mpegTSStream.beg);
m_frameCount = (int)(m_frameIdx / m_tsRatio);
}
}
void DATVModSource::reportTsFileSourceStreamTiming()
{
int framesCount = m_tsFileOK ? m_frameIdx : 0;
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(DATVModReport::MsgReportTsFileSourceStreamTiming::create(framesCount));
}
void DATVModSource::reportUDPBitrate()
{
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_udpTimingStart;
double seconds = sec.count();
int bitrate = seconds > 0.0 ? m_udpByteCount * 8 / seconds : 0;
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(DATVModReport::MsgReportUDPBitrate::create(bitrate));
m_udpTimingStart = boost::chrono::steady_clock::now();
m_udpByteCount = 0;
}
void DATVModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "DATVModSource::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((channelFrequencyOffset != m_channelFrequencyOffset) ||
(channelSampleRate != m_channelSampleRate) || force)
{
m_carrierNco.setFreq(channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
m_sampleRate = (channelSampleRate/m_settings.m_symbolRate)*m_settings.m_symbolRate;
// Create interpolator if not integer multiple
if (m_sampleRate != channelSampleRate)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_sampleRate / (Real) channelSampleRate;
m_interpolator.create(32, m_sampleRate, m_settings.m_rfBandwidth / 2.2f, 3.0);
}
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(DATVModReport::MsgReportRates::create(
channelSampleRate, m_sampleRate,
getDVBSDataBitrate(m_settings)));
}
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_samplesPerSymbol = m_channelSampleRate/m_settings.m_symbolRate;
m_pulseShapeI.create(m_settings.m_rollOff, 8, m_samplesPerSymbol, false);
m_pulseShapeQ.create(m_settings.m_rollOff, 8, m_samplesPerSymbol, false);
}
void DATVModSource::applySettings(const DATVModSettings& settings, bool force)
{
qDebug() << "DATVModSource::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_source: " << (int) settings.m_source
<< " m_standard: " << (int) settings.m_standard
<< " m_modulation: " << (int) settings.m_modulation
<< " m_fec: " << (int) settings.m_fec
<< " m_symbolRate: " << (int) settings.m_symbolRate
<< " m_rollOff: " << (int) settings.m_rollOff
<< " m_tsFilePlayLoop: " << settings.m_tsFilePlayLoop
<< " m_tsFilePlay: " << settings.m_tsFilePlay
<< " m_udpAddress: " << settings.m_udpAddress
<< " m_udpPort: " << settings.m_udpPort
<< " m_channelMute: " << settings.m_channelMute
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|| (settings.m_modulation != m_settings.m_modulation)
|| (settings.m_symbolRate != m_settings.m_symbolRate)
|| force)
{
m_sampleRate = (m_channelSampleRate/settings.m_symbolRate)*settings.m_symbolRate;
if (m_sampleRate != m_channelSampleRate)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_sampleRate / (Real) m_channelSampleRate;
m_interpolator.create(32, m_sampleRate, settings.m_rfBandwidth / 2.2f, 3.0);
}
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(DATVModReport::MsgReportRates::create(
m_channelSampleRate, m_sampleRate,
getDVBSDataBitrate(settings)));
}
}
if ((settings.m_source != m_settings.m_source)
|| (settings.m_udpAddress != m_settings.m_udpAddress)
|| (settings.m_udpPort != m_settings.m_udpPort)
|| force)
{
if (m_udpSocket)
{
m_udpSocket->close();
delete m_udpSocket;
m_udpSocket = nullptr;
}
if (settings.m_source == DATVModSettings::SourceUDP)
{
m_udpSocket = new QUdpSocket();
m_udpSocket->bind(QHostAddress(settings.m_udpAddress), settings.m_udpPort);
m_udpSocket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 1000000);
m_udpTimingStart = boost::chrono::steady_clock::now();
m_udpByteCount = 0;
}
}
if ((settings.m_standard != m_settings.m_standard)
|| (settings.m_modulation != m_settings.m_modulation)
|| force)
{
m_symbolSel = 0;
m_symbolIdx = 0;
m_symbolCount = 0;
m_sampleIdx = 0;
}
if ((settings.m_standard != m_settings.m_standard)
|| (settings.m_modulation != m_settings.m_modulation)
|| (settings.m_fec != m_settings.m_fec)
|| (settings.m_rollOff != m_settings.m_rollOff)
|| force)
{
if (settings.m_standard == DATVModSettings::DVB_S)
{
switch (settings.m_fec)
{
case DATVModSettings::FEC12:
m_dvbs.setCodeRate(DVBS::RATE_1_2);
break;
case DATVModSettings::FEC23:
m_dvbs.setCodeRate(DVBS::RATE_2_3);
break;
case DATVModSettings::FEC34:
m_dvbs.setCodeRate(DVBS::RATE_3_4);
break;
case DATVModSettings::FEC56:
m_dvbs.setCodeRate(DVBS::RATE_5_6);
break;
case DATVModSettings::FEC78:
m_dvbs.setCodeRate(DVBS::RATE_7_8);
break;
default:
qCritical() << "DATVModSource::applySettings: Unsupported FEC code rate for DVB-S: " << settings.m_fec;
break;
}
}
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(DATVModReport::MsgReportRates::create(
m_channelSampleRate, m_sampleRate,
getDVBSDataBitrate(settings)));
}
}
m_settings = settings;
m_samplesPerSymbol = m_channelSampleRate/m_settings.m_symbolRate;
m_pulseShapeI.create(m_settings.m_rollOff, 8, m_samplesPerSymbol, false);
m_pulseShapeQ.create(m_settings.m_rollOff, 8, m_samplesPerSymbol, false);
checkBitrates();
}