mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-09-05 14:47:50 -04:00
SDRdaemonSink: added UDPSinkFEC class
This commit is contained in:
parent
7f539f0314
commit
8b703a1302
@ -6,6 +6,7 @@ set(sdrdaemonsink_SOURCES
|
|||||||
# sdrdaemonsinkplugin.cpp
|
# sdrdaemonsinkplugin.cpp
|
||||||
sdrdaemonsinksettings.cpp
|
sdrdaemonsinksettings.cpp
|
||||||
# sdrdaemonsinkthread.cpp
|
# sdrdaemonsinkthread.cpp
|
||||||
|
udpsinkfec.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(sdrdaemonsink_HEADERS
|
set(sdrdaemonsink_HEADERS
|
||||||
@ -14,6 +15,7 @@ set(sdrdaemonsink_HEADERS
|
|||||||
# sdrdaemonsinkplugin.h
|
# sdrdaemonsinkplugin.h
|
||||||
sdrdaemonsinksettings.h
|
sdrdaemonsinksettings.h
|
||||||
# sdrdaemonsinkthread.h
|
# sdrdaemonsinkthread.h
|
||||||
|
udpsinkfec.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(sdrdaemonsink_FORMS
|
set(sdrdaemonsink_FORMS
|
||||||
@ -23,6 +25,8 @@ set(sdrdaemonsink_FORMS
|
|||||||
include_directories(
|
include_directories(
|
||||||
.
|
.
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
${CMAKE_SOURCE_DIR}/devices
|
||||||
|
${CM256CC_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(${QT_DEFINITIONS})
|
add_definitions(${QT_DEFINITIONS})
|
||||||
@ -40,6 +44,7 @@ add_library(outputsdrdaemonsink SHARED
|
|||||||
target_link_libraries(outputsdrdaemonsink
|
target_link_libraries(outputsdrdaemonsink
|
||||||
${QT_LIBRARIES}
|
${QT_LIBRARIES}
|
||||||
sdrbase
|
sdrbase
|
||||||
|
${CM256CC_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
qt5_use_modules(outputsdrdaemonsink Core Widgets)
|
qt5_use_modules(outputsdrdaemonsink Core Widgets)
|
||||||
|
@ -65,7 +65,7 @@ FileSinkGui::FileSinkGui(DeviceSinkAPI *deviceAPI, QWidget* parent) :
|
|||||||
|
|
||||||
displaySettings();
|
displaySettings();
|
||||||
|
|
||||||
m_deviceSampleSink = new FileSinkOutput(m_deviceAPI, m_deviceAPI->getMainWindow()->getMasterTimer());
|
m_deviceSampleSink = new SDRdaemonSinkOutput(m_deviceAPI, m_deviceAPI->getMainWindow()->getMasterTimer());
|
||||||
connect(m_deviceSampleSink->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSinkMessages()));
|
connect(m_deviceSampleSink->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSinkMessages()));
|
||||||
m_deviceAPI->setSink(m_deviceSampleSink);
|
m_deviceAPI->setSink(m_deviceSampleSink);
|
||||||
|
|
||||||
@ -130,15 +130,15 @@ bool FileSinkGui::deserialize(const QByteArray& data)
|
|||||||
|
|
||||||
bool FileSinkGui::handleMessage(const Message& message)
|
bool FileSinkGui::handleMessage(const Message& message)
|
||||||
{
|
{
|
||||||
if (FileSinkOutput::MsgReportFileSinkGeneration::match(message))
|
if (SDRdaemonSinkOutput::MsgReportSDRdaemonSinkGeneration::match(message))
|
||||||
{
|
{
|
||||||
m_generation = ((FileSinkOutput::MsgReportFileSinkGeneration&)message).getAcquisition();
|
m_generation = ((SDRdaemonSinkOutput::MsgReportSDRdaemonSinkGeneration&)message).getAcquisition();
|
||||||
updateWithGeneration();
|
updateWithGeneration();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (FileSinkOutput::MsgReportFileSinkStreamTiming::match(message))
|
else if (SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming::match(message))
|
||||||
{
|
{
|
||||||
m_samplesCount = ((FileSinkOutput::MsgReportFileSinkStreamTiming&)message).getSamplesCount();
|
m_samplesCount = ((SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming&)message).getSamplesCount();
|
||||||
updateWithStreamTime();
|
updateWithStreamTime();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ void FileSinkGui::sendSettings()
|
|||||||
void FileSinkGui::updateHardware()
|
void FileSinkGui::updateHardware()
|
||||||
{
|
{
|
||||||
qDebug() << "FileSinkGui::updateHardware";
|
qDebug() << "FileSinkGui::updateHardware";
|
||||||
FileSinkOutput::MsgConfigureFileSink* message = FileSinkOutput::MsgConfigureFileSink::create(m_settings);
|
SDRdaemonSinkOutput::MsgConfigureSDRdaemonSink* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSink::create(m_settings);
|
||||||
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
||||||
m_updateTimer.stop();
|
m_updateTimer.stop();
|
||||||
}
|
}
|
||||||
@ -301,7 +301,7 @@ void FileSinkGui::on_showFileDialog_clicked(bool checked)
|
|||||||
void FileSinkGui::configureFileName()
|
void FileSinkGui::configureFileName()
|
||||||
{
|
{
|
||||||
qDebug() << "FileSinkGui::configureFileName: " << m_fileName.toStdString().c_str();
|
qDebug() << "FileSinkGui::configureFileName: " << m_fileName.toStdString().c_str();
|
||||||
FileSinkOutput::MsgConfigureFileSinkName* message = FileSinkOutput::MsgConfigureFileSinkName::create(m_fileName);
|
SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkName* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkName::create(m_fileName);
|
||||||
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +331,7 @@ void FileSinkGui::tick()
|
|||||||
{
|
{
|
||||||
if ((++m_tickCount & 0xf) == 0)
|
if ((++m_tickCount & 0xf) == 0)
|
||||||
{
|
{
|
||||||
FileSinkOutput::MsgConfigureFileSinkStreamTiming* message = FileSinkOutput::MsgConfigureFileSinkStreamTiming::create();
|
SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create();
|
||||||
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
m_deviceSampleSink->getInputMessageQueue()->push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>FileSource</string>
|
<string>SDRdaemon Sink</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
|
@ -29,14 +29,14 @@
|
|||||||
#include "sdrdaemonsinkoutput.h"
|
#include "sdrdaemonsinkoutput.h"
|
||||||
#include "sdrdaemonsinkthread.h"
|
#include "sdrdaemonsinkthread.h"
|
||||||
|
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSink, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSink, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkName, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkName, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkWork, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkWork, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkStreamTiming, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgReportFileSinkGeneration, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgReportSDRdaemonSinkGeneration, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgReportFileSinkStreamTiming, Message)
|
MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming, Message)
|
||||||
|
|
||||||
FileSinkOutput::FileSinkOutput(DeviceSinkAPI *deviceAPI, const QTimer& masterTimer) :
|
SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI, const QTimer& masterTimer) :
|
||||||
m_deviceAPI(deviceAPI),
|
m_deviceAPI(deviceAPI),
|
||||||
m_settings(),
|
m_settings(),
|
||||||
m_fileSinkThread(0),
|
m_fileSinkThread(0),
|
||||||
@ -47,12 +47,12 @@ FileSinkOutput::FileSinkOutput(DeviceSinkAPI *deviceAPI, const QTimer& masterTim
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSinkOutput::~FileSinkOutput()
|
SDRdaemonSinkOutput::~SDRdaemonSinkOutput()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSinkOutput::openFileStream()
|
void SDRdaemonSinkOutput::openFileStream()
|
||||||
{
|
{
|
||||||
if (m_ofstream.is_open()) {
|
if (m_ofstream.is_open()) {
|
||||||
m_ofstream.close();
|
m_ofstream.close();
|
||||||
@ -70,7 +70,7 @@ void FileSinkOutput::openFileStream()
|
|||||||
qDebug() << "FileSinkOutput::openFileStream: " << m_fileName.toStdString().c_str();
|
qDebug() << "FileSinkOutput::openFileStream: " << m_fileName.toStdString().c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSinkOutput::start()
|
bool SDRdaemonSinkOutput::start()
|
||||||
{
|
{
|
||||||
QMutexLocker mutexLocker(&m_mutex);
|
QMutexLocker mutexLocker(&m_mutex);
|
||||||
qDebug() << "FileSinkOutput::start";
|
qDebug() << "FileSinkOutput::start";
|
||||||
@ -93,13 +93,13 @@ bool FileSinkOutput::start()
|
|||||||
//applySettings(m_generalSettings, m_settings, true);
|
//applySettings(m_generalSettings, m_settings, true);
|
||||||
qDebug("FileSinkOutput::start: started");
|
qDebug("FileSinkOutput::start: started");
|
||||||
|
|
||||||
MsgReportFileSinkGeneration *report = MsgReportFileSinkGeneration::create(true); // acquisition on
|
MsgReportSDRdaemonSinkGeneration *report = MsgReportSDRdaemonSinkGeneration::create(true); // acquisition on
|
||||||
getOutputMessageQueueToGUI()->push(report);
|
getOutputMessageQueueToGUI()->push(report);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSinkOutput::stop()
|
void SDRdaemonSinkOutput::stop()
|
||||||
{
|
{
|
||||||
qDebug() << "FileSourceInput::stop";
|
qDebug() << "FileSourceInput::stop";
|
||||||
QMutexLocker mutexLocker(&m_mutex);
|
QMutexLocker mutexLocker(&m_mutex);
|
||||||
@ -115,49 +115,49 @@ void FileSinkOutput::stop()
|
|||||||
m_ofstream.close();
|
m_ofstream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgReportFileSinkGeneration *report = MsgReportFileSinkGeneration::create(false); // acquisition off
|
MsgReportSDRdaemonSinkGeneration *report = MsgReportSDRdaemonSinkGeneration::create(false); // acquisition off
|
||||||
getOutputMessageQueueToGUI()->push(report);
|
getOutputMessageQueueToGUI()->push(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString& FileSinkOutput::getDeviceDescription() const
|
const QString& SDRdaemonSinkOutput::getDeviceDescription() const
|
||||||
{
|
{
|
||||||
return m_deviceDescription;
|
return m_deviceDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FileSinkOutput::getSampleRate() const
|
int SDRdaemonSinkOutput::getSampleRate() const
|
||||||
{
|
{
|
||||||
return m_settings.m_sampleRate;
|
return m_settings.m_sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 FileSinkOutput::getCenterFrequency() const
|
quint64 SDRdaemonSinkOutput::getCenterFrequency() const
|
||||||
{
|
{
|
||||||
return m_settings.m_centerFrequency;
|
return m_settings.m_centerFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::time_t FileSinkOutput::getStartingTimeStamp() const
|
std::time_t SDRdaemonSinkOutput::getStartingTimeStamp() const
|
||||||
{
|
{
|
||||||
return m_startingTimeStamp;
|
return m_startingTimeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSinkOutput::handleMessage(const Message& message)
|
bool SDRdaemonSinkOutput::handleMessage(const Message& message)
|
||||||
{
|
{
|
||||||
if (MsgConfigureFileSinkName::match(message))
|
if (MsgConfigureSDRdaemonSinkName::match(message))
|
||||||
{
|
{
|
||||||
MsgConfigureFileSinkName& conf = (MsgConfigureFileSinkName&) message;
|
MsgConfigureSDRdaemonSinkName& conf = (MsgConfigureSDRdaemonSinkName&) message;
|
||||||
m_fileName = conf.getFileName();
|
m_fileName = conf.getFileName();
|
||||||
openFileStream();
|
openFileStream();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (MsgConfigureFileSink::match(message))
|
else if (MsgConfigureSDRdaemonSink::match(message))
|
||||||
{
|
{
|
||||||
qDebug() << "FileSinkOutput::handleMessage: MsgConfigureFileSink";
|
qDebug() << "FileSinkOutput::handleMessage: MsgConfigureFileSink";
|
||||||
MsgConfigureFileSink& conf = (MsgConfigureFileSink&) message;
|
MsgConfigureSDRdaemonSink& conf = (MsgConfigureSDRdaemonSink&) message;
|
||||||
applySettings(conf.getSettings(), false);
|
applySettings(conf.getSettings(), false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (MsgConfigureFileSinkWork::match(message))
|
else if (MsgConfigureSDRdaemonSinkWork::match(message))
|
||||||
{
|
{
|
||||||
MsgConfigureFileSinkWork& conf = (MsgConfigureFileSinkWork&) message;
|
MsgConfigureSDRdaemonSinkWork& conf = (MsgConfigureSDRdaemonSinkWork&) message;
|
||||||
bool working = conf.isWorking();
|
bool working = conf.isWorking();
|
||||||
|
|
||||||
if (m_fileSinkThread != 0)
|
if (m_fileSinkThread != 0)
|
||||||
@ -174,13 +174,13 @@ bool FileSinkOutput::handleMessage(const Message& message)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (MsgConfigureFileSinkStreamTiming::match(message))
|
else if (MsgConfigureSDRdaemonSinkStreamTiming::match(message))
|
||||||
{
|
{
|
||||||
MsgReportFileSinkStreamTiming *report;
|
MsgReportSDRdaemonSinkStreamTiming *report;
|
||||||
|
|
||||||
if (m_fileSinkThread != 0)
|
if (m_fileSinkThread != 0)
|
||||||
{
|
{
|
||||||
report = MsgReportFileSinkStreamTiming::create(m_fileSinkThread->getSamplesCount());
|
report = MsgReportSDRdaemonSinkStreamTiming::create(m_fileSinkThread->getSamplesCount());
|
||||||
getOutputMessageQueueToGUI()->push(report);
|
getOutputMessageQueueToGUI()->push(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ bool FileSinkOutput::handleMessage(const Message& message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSinkOutput::applySettings(const FileSinkSettings& settings, bool force)
|
void SDRdaemonSinkOutput::applySettings(const FileSinkSettings& settings, bool force)
|
||||||
{
|
{
|
||||||
QMutexLocker mutexLocker(&m_mutex);
|
QMutexLocker mutexLocker(&m_mutex);
|
||||||
bool forwardChange = false;
|
bool forwardChange = false;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
|
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
|
||||||
// //
|
// //
|
||||||
// This program is free software; you can redistribute it and/or modify //
|
// 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 //
|
// it under the terms of the GNU General Public License as published by //
|
||||||
@ -14,8 +14,8 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#ifndef INCLUDE_FILESINKOUTPUT_H
|
#ifndef INCLUDE_SDRDAEMONSINKOUTPUT_H
|
||||||
#define INCLUDE_FILESINKOUTPUT_H
|
#define INCLUDE_SDRDAEMONSINKOUTPUT_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -27,130 +27,130 @@
|
|||||||
|
|
||||||
#include "sdrdaemonsinksettings.h"
|
#include "sdrdaemonsinksettings.h"
|
||||||
|
|
||||||
class FileSinkThread;
|
class SDRdaemonSinkThread;
|
||||||
class DeviceSinkAPI;
|
class DeviceSinkAPI;
|
||||||
|
|
||||||
class FileSinkOutput : public DeviceSampleSink {
|
class SDRdaemonSinkOutput : public DeviceSampleSink {
|
||||||
public:
|
public:
|
||||||
class MsgConfigureFileSink : public Message {
|
class MsgConfigureSDRdaemonSink : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const FileSinkSettings& getSettings() const { return m_settings; }
|
const SDRdaemonSinkSettings& getSettings() const { return m_settings; }
|
||||||
|
|
||||||
static MsgConfigureFileSink* create(const FileSinkSettings& settings)
|
static MsgConfigureSDRdaemonSink* create(const SDRdaemonSinkSettings& settings)
|
||||||
{
|
{
|
||||||
return new MsgConfigureFileSink(settings);
|
return new MsgConfigureSDRdaemonSink(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileSinkSettings m_settings;
|
SDRdaemonSinkSettings m_settings;
|
||||||
|
|
||||||
MsgConfigureFileSink(const FileSinkSettings& settings) :
|
MsgConfigureSDRdaemonSink(const SDRdaemonSinkSettings& settings) :
|
||||||
Message(),
|
Message(),
|
||||||
m_settings(settings)
|
m_settings(settings)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgConfigureFileSinkName : public Message {
|
class MsgConfigureSDRdaemonSinkName : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const QString& getFileName() const { return m_fileName; }
|
const QString& getFileName() const { return m_fileName; }
|
||||||
|
|
||||||
static MsgConfigureFileSinkName* create(const QString& fileName)
|
static MsgConfigureSDRdaemonSinkName* create(const QString& fileName)
|
||||||
{
|
{
|
||||||
return new MsgConfigureFileSinkName(fileName);
|
return new MsgConfigureSDRdaemonSinkName(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_fileName;
|
QString m_fileName;
|
||||||
|
|
||||||
MsgConfigureFileSinkName(const QString& fileName) :
|
MsgConfigureSDRdaemonSinkName(const QString& fileName) :
|
||||||
Message(),
|
Message(),
|
||||||
m_fileName(fileName)
|
m_fileName(fileName)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgConfigureFileSinkWork : public Message {
|
class MsgConfigureSDRdaemonSinkWork : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isWorking() const { return m_working; }
|
bool isWorking() const { return m_working; }
|
||||||
|
|
||||||
static MsgConfigureFileSinkWork* create(bool working)
|
static MsgConfigureSDRdaemonSinkWork* create(bool working)
|
||||||
{
|
{
|
||||||
return new MsgConfigureFileSinkWork(working);
|
return new MsgConfigureSDRdaemonSinkWork(working);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_working;
|
bool m_working;
|
||||||
|
|
||||||
MsgConfigureFileSinkWork(bool working) :
|
MsgConfigureSDRdaemonSinkWork(bool working) :
|
||||||
Message(),
|
Message(),
|
||||||
m_working(working)
|
m_working(working)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgConfigureFileSinkStreamTiming : public Message {
|
class MsgConfigureSDRdaemonSinkStreamTiming : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static MsgConfigureFileSinkStreamTiming* create()
|
static MsgConfigureSDRdaemonSinkStreamTiming* create()
|
||||||
{
|
{
|
||||||
return new MsgConfigureFileSinkStreamTiming();
|
return new MsgConfigureSDRdaemonSinkStreamTiming();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
MsgConfigureFileSinkStreamTiming() :
|
MsgConfigureSDRdaemonSinkStreamTiming() :
|
||||||
Message()
|
Message()
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgReportFileSinkGeneration : public Message {
|
class MsgReportSDRdaemonSinkGeneration : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool getAcquisition() const { return m_acquisition; }
|
bool getAcquisition() const { return m_acquisition; }
|
||||||
|
|
||||||
static MsgReportFileSinkGeneration* create(bool acquisition)
|
static MsgReportSDRdaemonSinkGeneration* create(bool acquisition)
|
||||||
{
|
{
|
||||||
return new MsgReportFileSinkGeneration(acquisition);
|
return new MsgReportSDRdaemonSinkGeneration(acquisition);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool m_acquisition;
|
bool m_acquisition;
|
||||||
|
|
||||||
MsgReportFileSinkGeneration(bool acquisition) :
|
MsgReportSDRdaemonSinkGeneration(bool acquisition) :
|
||||||
Message(),
|
Message(),
|
||||||
m_acquisition(acquisition)
|
m_acquisition(acquisition)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgReportFileSinkStreamTiming : public Message {
|
class MsgReportSDRdaemonSinkStreamTiming : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::size_t getSamplesCount() const { return m_samplesCount; }
|
std::size_t getSamplesCount() const { return m_samplesCount; }
|
||||||
|
|
||||||
static MsgReportFileSinkStreamTiming* create(std::size_t samplesCount)
|
static MsgReportSDRdaemonSinkStreamTiming* create(std::size_t samplesCount)
|
||||||
{
|
{
|
||||||
return new MsgReportFileSinkStreamTiming(samplesCount);
|
return new MsgReportSDRdaemonSinkStreamTiming(samplesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::size_t m_samplesCount;
|
std::size_t m_samplesCount;
|
||||||
|
|
||||||
MsgReportFileSinkStreamTiming(std::size_t samplesCount) :
|
MsgReportSDRdaemonSinkStreamTiming(std::size_t samplesCount) :
|
||||||
Message(),
|
Message(),
|
||||||
m_samplesCount(samplesCount)
|
m_samplesCount(samplesCount)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
FileSinkOutput(DeviceSinkAPI *deviceAPI, const QTimer& masterTimer);
|
SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI, const QTimer& masterTimer);
|
||||||
virtual ~FileSinkOutput();
|
virtual ~SDRdaemonSinkOutput();
|
||||||
|
|
||||||
virtual bool start();
|
virtual bool start();
|
||||||
virtual void stop();
|
virtual void stop();
|
||||||
@ -165,16 +165,16 @@ public:
|
|||||||
private:
|
private:
|
||||||
DeviceSinkAPI *m_deviceAPI;
|
DeviceSinkAPI *m_deviceAPI;
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
FileSinkSettings m_settings;
|
SDRdaemonSinkSettings m_settings;
|
||||||
std::ofstream m_ofstream;
|
std::ofstream m_ofstream;
|
||||||
FileSinkThread* m_fileSinkThread;
|
SDRdaemonSinkThread* m_fileSinkThread;
|
||||||
QString m_deviceDescription;
|
QString m_deviceDescription;
|
||||||
QString m_fileName;
|
QString m_fileName;
|
||||||
std::time_t m_startingTimeStamp;
|
std::time_t m_startingTimeStamp;
|
||||||
const QTimer& m_masterTimer;
|
const QTimer& m_masterTimer;
|
||||||
|
|
||||||
void openFileStream();
|
void openFileStream();
|
||||||
void applySettings(const FileSinkSettings& settings, bool force = false);
|
void applySettings(const SDRdaemonSinkSettings& settings, bool force = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INCLUDE_FILESINKOUTPUT_H
|
#endif // INCLUDE_SDRDAEMONSINKOUTPUT_H
|
||||||
|
@ -121,7 +121,7 @@ void SDRdaemonSinkThread::setLog2Interpolation(int log2Interpolation)
|
|||||||
|
|
||||||
if (log2Interpolation != m_log2Interpolation)
|
if (log2Interpolation != m_log2Interpolation)
|
||||||
{
|
{
|
||||||
qDebug() << "FileSinkThread::setLog2Interpolation:"
|
qDebug() << "SDRdaemonSinkThread::setLog2Interpolation:"
|
||||||
<< " new:" << log2Interpolation
|
<< " new:" << log2Interpolation
|
||||||
<< " old:" << m_log2Interpolation;
|
<< " old:" << m_log2Interpolation;
|
||||||
|
|
||||||
@ -187,44 +187,47 @@ void SDRdaemonSinkThread::tick()
|
|||||||
|
|
||||||
SampleVector::iterator readUntil;
|
SampleVector::iterator readUntil;
|
||||||
|
|
||||||
m_sampleFifo->readAdvance(readUntil, m_samplesChunkSize);
|
m_sampleFifo->readAdvance(readUntil, m_samplesChunkSize); // pull samples
|
||||||
SampleVector::iterator beginRead = readUntil - m_samplesChunkSize;
|
SampleVector::iterator beginRead = readUntil - m_samplesChunkSize;
|
||||||
m_samplesCount += m_samplesChunkSize;
|
m_samplesCount += m_samplesChunkSize;
|
||||||
|
|
||||||
if (m_log2Interpolation == 0)
|
m_ofstream->write(reinterpret_cast<char*>(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples
|
||||||
{
|
|
||||||
m_ofstream->write(reinterpret_cast<char*>(&(*beginRead)), m_samplesChunkSize*sizeof(Sample));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int chunkSize = std::min((int) m_samplesChunkSize, m_samplerate);
|
|
||||||
|
|
||||||
switch (m_log2Interpolation)
|
// interpolation is done on the far side
|
||||||
{
|
// if (m_log2Interpolation == 0)
|
||||||
case 1:
|
// {
|
||||||
m_interpolators.interpolate2_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// m_ofstream->write(reinterpret_cast<char*>(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples
|
||||||
break;
|
// }
|
||||||
case 2:
|
// else
|
||||||
m_interpolators.interpolate4_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// {
|
||||||
break;
|
// int chunkSize = std::min((int) m_samplesChunkSize, m_samplerate);
|
||||||
case 3:
|
//
|
||||||
m_interpolators.interpolate8_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// switch (m_log2Interpolation)
|
||||||
break;
|
// {
|
||||||
case 4:
|
// case 1:
|
||||||
m_interpolators.interpolate16_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// m_interpolators.interpolate2_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
break;
|
// break;
|
||||||
case 5:
|
// case 2:
|
||||||
m_interpolators.interpolate32_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// m_interpolators.interpolate4_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
break;
|
// break;
|
||||||
case 6:
|
// case 3:
|
||||||
m_interpolators.interpolate64_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
// m_interpolators.interpolate8_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
break;
|
// break;
|
||||||
default:
|
// case 4:
|
||||||
break;
|
// m_interpolators.interpolate16_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
}
|
// break;
|
||||||
|
// case 5:
|
||||||
m_ofstream->write(reinterpret_cast<char*>(m_buf), m_samplesChunkSize*(1<<m_log2Interpolation)*2*sizeof(int16_t));
|
// m_interpolators.interpolate32_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
}
|
// break;
|
||||||
|
// case 6:
|
||||||
|
// m_interpolators.interpolate64_cen(&beginRead, m_buf, chunkSize*(1<<m_log2Interpolation)*2);
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// m_ofstream->write(reinterpret_cast<char*>(m_buf), m_samplesChunkSize*(1<<m_log2Interpolation)*2*sizeof(int16_t)); // send samples
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
298
plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp
Normal file
298
plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (C) 2017 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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <boost/cstdint.hpp>
|
||||||
|
|
||||||
|
#include "udpsinkfec.h"
|
||||||
|
|
||||||
|
MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message)
|
||||||
|
MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message)
|
||||||
|
|
||||||
|
|
||||||
|
UDPSinkFEC::UDPSinkFEC() :
|
||||||
|
m_centerFrequency(100000),
|
||||||
|
m_sampleRate(48000),
|
||||||
|
m_sampleBytes(1),
|
||||||
|
m_sampleBits(8),
|
||||||
|
m_nbSamples(0),
|
||||||
|
m_nbBlocksFEC(0),
|
||||||
|
m_txDelay(0),
|
||||||
|
m_txBlockIndex(0),
|
||||||
|
m_txBlocksIndex(0),
|
||||||
|
m_frameCount(0),
|
||||||
|
m_sampleIndex(0)
|
||||||
|
{
|
||||||
|
m_currentMetaFEC.init();
|
||||||
|
m_bufMeta = new uint8_t[m_udpSize];
|
||||||
|
m_buf = new uint8_t[m_udpSize];
|
||||||
|
m_udpWorker = new UDPSinkFECWorker();
|
||||||
|
|
||||||
|
m_udpWorker->moveToThread(&m_udpThread);
|
||||||
|
connect(&(m_udpWorker->m_inputMessageQueue), SIGNAL(messageEnqueued()), m_udpWorker, SLOT(handleInputMessages()));
|
||||||
|
m_udpThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPSinkFEC::~UDPSinkFEC()
|
||||||
|
{
|
||||||
|
disconnect(&(m_udpWorker->m_inputMessageQueue), SIGNAL(messageEnqueued()), m_udpWorker, SLOT(handleInputMessages()));
|
||||||
|
m_udpThread.exit();
|
||||||
|
m_udpThread.wait();
|
||||||
|
|
||||||
|
delete[] m_buf;
|
||||||
|
delete[] m_bufMeta;
|
||||||
|
delete m_udpWorker;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFEC::setTxDelay(uint32_t txDelay)
|
||||||
|
{
|
||||||
|
qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelay;
|
||||||
|
m_txDelay = txDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFEC::setNbBlocksFEC(uint32_t nbBlocksFEC)
|
||||||
|
{
|
||||||
|
qDebug() << "UDPSinkFEC::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC;
|
||||||
|
m_nbBlocksFEC = nbBlocksFEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFEC::setRemoteAddress(const QString& address, uint16_t port)
|
||||||
|
{
|
||||||
|
qDebug() << "UDPSinkFEC::setRemoteAddress: address: " << address << " port: " << port;
|
||||||
|
m_udpWorker->setRemoteAddress(address, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunkSize)
|
||||||
|
{
|
||||||
|
const SampleVector::iterator end = begin + sampleChunkSize;
|
||||||
|
SampleVector::iterator it = begin;
|
||||||
|
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
int inSamplesIndex = it - begin;
|
||||||
|
int inRemainingSamples = end - it;
|
||||||
|
|
||||||
|
if (m_txBlockIndex == 0) // Tx block index 0 is a block with only meta data
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
MetaDataFEC metaData;
|
||||||
|
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
|
||||||
|
// create meta data TODO: semaphore
|
||||||
|
metaData.m_centerFrequency = m_centerFrequency;
|
||||||
|
metaData.m_sampleRate = m_sampleRate;
|
||||||
|
metaData.m_sampleBytes = m_sampleBytes;
|
||||||
|
metaData.m_sampleBits = m_sampleBits;
|
||||||
|
metaData.m_nbOriginalBlocks = m_nbOriginalBlocks;
|
||||||
|
metaData.m_nbFECBlocks = m_nbBlocksFEC;
|
||||||
|
metaData.m_tv_sec = tv.tv_sec;
|
||||||
|
metaData.m_tv_usec = tv.tv_usec;
|
||||||
|
|
||||||
|
boost::crc_32_type crc32;
|
||||||
|
crc32.process_bytes(&metaData, 20);
|
||||||
|
|
||||||
|
metaData.m_crc32 = crc32.checksum();
|
||||||
|
|
||||||
|
memset((void *) &m_superBlock, 0, m_udpSize);
|
||||||
|
|
||||||
|
m_superBlock.header.frameIndex = m_frameCount;
|
||||||
|
m_superBlock.header.blockIndex = m_txBlockIndex;
|
||||||
|
memcpy((void *) &m_superBlock.protectedBlock, (const void *) &metaData, sizeof(MetaDataFEC));
|
||||||
|
|
||||||
|
if (!(metaData == m_currentMetaFEC))
|
||||||
|
{
|
||||||
|
qDebug() << "UDPSinkFEC::write: meta: "
|
||||||
|
<< "|" << metaData.m_centerFrequency
|
||||||
|
<< ":" << metaData.m_sampleRate
|
||||||
|
<< ":" << (int) (metaData.m_sampleBytes & 0xF)
|
||||||
|
<< ":" << (int) metaData.m_sampleBits
|
||||||
|
<< "|" << (int) metaData.m_nbOriginalBlocks
|
||||||
|
<< ":" << (int) metaData.m_nbFECBlocks
|
||||||
|
<< "|" << metaData.m_tv_sec
|
||||||
|
<< ":" << metaData.m_tv_usec
|
||||||
|
<< "|";
|
||||||
|
|
||||||
|
m_currentMetaFEC = metaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_txBlocks[m_txBlocksIndex][0] = m_superBlock;
|
||||||
|
m_txBlockIndex = 1; // next Tx block with data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block
|
||||||
|
{
|
||||||
|
memcpy((void *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex],
|
||||||
|
(const void *) &(*it),
|
||||||
|
inRemainingSamples * sizeof(Sample));
|
||||||
|
m_sampleIndex += inRemainingSamples;
|
||||||
|
it = end; // all input samples are consumed
|
||||||
|
}
|
||||||
|
else // complete super block and initiate the next if not end of frame
|
||||||
|
{
|
||||||
|
memcpy((void *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex],
|
||||||
|
(const void *) &(*it),
|
||||||
|
(samplesPerBlock - m_sampleIndex) * sizeof(Sample));
|
||||||
|
it += samplesPerBlock - m_sampleIndex;
|
||||||
|
m_sampleIndex = 0;
|
||||||
|
|
||||||
|
m_superBlock.header.frameIndex = m_frameCount;
|
||||||
|
m_superBlock.header.blockIndex = m_txBlockIndex;
|
||||||
|
m_txBlocks[m_txBlocksIndex][m_txBlockIndex] = m_superBlock;
|
||||||
|
|
||||||
|
if (m_txBlockIndex == m_nbOriginalBlocks - 1) // frame complete
|
||||||
|
{
|
||||||
|
int nbBlocksFEC = m_nbBlocksFEC;
|
||||||
|
int txDelay = m_txDelay;
|
||||||
|
|
||||||
|
// TODO: send blocks
|
||||||
|
m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount);
|
||||||
|
//m_txThread = new std::thread(transmitUDP, this, m_txBlocks[m_txBlocksIndex], m_frameCount, nbBlocksFEC, txDelay, m_cm256Valid);
|
||||||
|
//transmitUDP(this, m_txBlocks[m_txBlocksIndex], m_frameCount, m_nbBlocksFEC, m_txDelay, m_cm256Valid);
|
||||||
|
|
||||||
|
m_txBlocksIndex = (m_txBlocksIndex + 1) % 4;
|
||||||
|
m_txBlockIndex = 0;
|
||||||
|
m_frameCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_txBlockIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPSinkFECWorker::UDPSinkFECWorker() : m_remotePort(9090)
|
||||||
|
{
|
||||||
|
m_cm256Valid = m_cm256.isInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPSinkFECWorker::~UDPSinkFECWorker()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFECWorker::pushTxFrame(const UDPSinkFEC::SuperBlock *txBlocks,
|
||||||
|
uint32_t nbBlocksFEC,
|
||||||
|
uint32_t txDelay,
|
||||||
|
uint16_t frameIndex)
|
||||||
|
{
|
||||||
|
m_inputMessageQueue.push(MsgUDPFECEncodeAndSend::create(txBlocks, nbBlocksFEC, txDelay, frameIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port)
|
||||||
|
{
|
||||||
|
m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFECWorker::handleInputMessages()
|
||||||
|
{
|
||||||
|
Message* message;
|
||||||
|
|
||||||
|
while ((message = m_inputMessageQueue.pop()) != 0)
|
||||||
|
{
|
||||||
|
if (MsgUDPFECEncodeAndSend::match(*message))
|
||||||
|
{
|
||||||
|
MsgUDPFECEncodeAndSend *sendMsg = (MsgUDPFECEncodeAndSend *) message;
|
||||||
|
}
|
||||||
|
else if (MsgConfigureRemoteAddress::match(*message))
|
||||||
|
{
|
||||||
|
MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message;
|
||||||
|
m_remoteAddress.setAddress(addressMsg->getAddress());
|
||||||
|
m_remotePort = addressMsg->getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPSinkFECWorker::transmitUDP(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, int nbBlocksFEC, int txDelay)
|
||||||
|
{
|
||||||
|
CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder
|
||||||
|
CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder
|
||||||
|
UDPSinkFEC::ProtectedBlock fecBlocks[256]; //!< FEC data
|
||||||
|
|
||||||
|
if ((nbBlocksFEC == 0) || !m_cm256Valid)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < UDPSinkFEC::m_nbOriginalBlocks; i++)
|
||||||
|
{
|
||||||
|
m_udpSocket.writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort);
|
||||||
|
usleep(txDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cm256Params.BlockBytes = sizeof(UDPSinkFEC::ProtectedBlock);
|
||||||
|
cm256Params.OriginalCount = UDPSinkFEC::m_nbOriginalBlocks;
|
||||||
|
cm256Params.RecoveryCount = nbBlocksFEC;
|
||||||
|
|
||||||
|
|
||||||
|
// Fill pointers to data
|
||||||
|
for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i)
|
||||||
|
{
|
||||||
|
if (i >= cm256Params.OriginalCount) {
|
||||||
|
memset((void *) &txBlockx[i].protectedBlock, 0, sizeof(UDPSinkFEC::ProtectedBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
txBlockx[i].header.frameIndex = frameIndex;
|
||||||
|
txBlockx[i].header.blockIndex = i;
|
||||||
|
descriptorBlocks[i].Block = (void *) &(txBlockx[i].protectedBlock);
|
||||||
|
descriptorBlocks[i].Index = txBlockx[i].header.blockIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode FEC blocks
|
||||||
|
if (m_cm256.cm256_encode(cm256Params, descriptorBlocks, fecBlocks))
|
||||||
|
{
|
||||||
|
qDebug() << "UDPSinkFECWorker::transmitUDP: CM256 encode failed. No transmission.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge FEC with data to transmit
|
||||||
|
for (int i = 0; i < cm256Params.RecoveryCount; i++)
|
||||||
|
{
|
||||||
|
txBlockx[i + cm256Params.OriginalCount].protectedBlock = fecBlocks[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmit all blocks
|
||||||
|
for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++)
|
||||||
|
{
|
||||||
|
#ifdef SDRDAEMON_PUNCTURE
|
||||||
|
if (i == SDRDAEMON_PUNCTURE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// std::cerr << "UDPSinkFEC::transmitUDP:"
|
||||||
|
// << " i: " << i
|
||||||
|
// << " frameIndex: " << (int) m_txBlocks[i].header.frameIndex
|
||||||
|
// << " blockIndex: " << (int) m_txBlocks[i].header.blockIndex
|
||||||
|
// << " i.q:";
|
||||||
|
//
|
||||||
|
// for (int j = 0; j < 10; j++)
|
||||||
|
// {
|
||||||
|
// std::cerr << " " << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_real
|
||||||
|
// << "." << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_imag;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// std::cerr << std::endl;
|
||||||
|
|
||||||
|
m_udpSocket.writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort);
|
||||||
|
usleep(txDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
248
plugins/samplesink/sdrdaemonsink/udpsinkfec.h
Normal file
248
plugins/samplesink/sdrdaemonsink/udpsinkfec.h
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright (C) 2017 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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_
|
||||||
|
#define PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QString>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "cm256.h"
|
||||||
|
|
||||||
|
#include "dsp/dsptypes.h"
|
||||||
|
#include "util/CRC64.h"
|
||||||
|
#include "util/messagequeue.h"
|
||||||
|
#include "util/message.h"
|
||||||
|
|
||||||
|
class UDPSinkFECWorker;
|
||||||
|
|
||||||
|
class UDPSinkFEC : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
static const uint32_t m_udpSize = 512; //!< Size of UDP block in number of bytes
|
||||||
|
static const uint32_t m_nbOriginalBlocks = 128; //!< Number of original blocks in a protected block sequence
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MetaDataFEC
|
||||||
|
{
|
||||||
|
uint32_t m_centerFrequency; //!< 4 center frequency in kHz
|
||||||
|
uint32_t m_sampleRate; //!< 8 sample rate in Hz
|
||||||
|
uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample
|
||||||
|
uint8_t m_sampleBits; //!< 10 number of effective bits per sample
|
||||||
|
uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data
|
||||||
|
uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC
|
||||||
|
uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing
|
||||||
|
uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing
|
||||||
|
uint32_t m_crc32; //!< 24 CRC32 of the above
|
||||||
|
|
||||||
|
bool operator==(const MetaDataFEC& rhs)
|
||||||
|
{
|
||||||
|
return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
memset((void *) this, 0, sizeof(MetaDataFEC));
|
||||||
|
m_nbFECBlocks = -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Header
|
||||||
|
{
|
||||||
|
uint16_t frameIndex;
|
||||||
|
uint8_t blockIndex;
|
||||||
|
uint8_t filler;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int samplesPerBlock = (m_udpSize - sizeof(Header)) / sizeof(Sample);
|
||||||
|
|
||||||
|
struct ProtectedBlock
|
||||||
|
{
|
||||||
|
Sample m_samples[samplesPerBlock];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SuperBlock
|
||||||
|
{
|
||||||
|
Header header;
|
||||||
|
ProtectedBlock protectedBlock;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct UDP sink
|
||||||
|
*/
|
||||||
|
UDPSinkFEC();
|
||||||
|
|
||||||
|
/** Destroy UDP sink */
|
||||||
|
~UDPSinkFEC();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write IQ samples
|
||||||
|
*/
|
||||||
|
void write(const SampleVector::iterator& begin, uint32_t sampleChunkSize);
|
||||||
|
|
||||||
|
/** Return the last error, or return an empty string if there is no error. */
|
||||||
|
std::string error()
|
||||||
|
{
|
||||||
|
std::string ret(m_error);
|
||||||
|
m_error.clear();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set center frequency given in Hz */
|
||||||
|
void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency / 1000; }
|
||||||
|
|
||||||
|
/** Set sample rate given in Hz */
|
||||||
|
void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; }
|
||||||
|
|
||||||
|
void setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = (sampleBytes & 0x0F) + (m_sampleBytes & 0xF0); }
|
||||||
|
void setSampleBits(uint8_t sampleBits) { m_sampleBits = sampleBits; }
|
||||||
|
|
||||||
|
void setNbBlocksFEC(uint32_t nbBlocksFEC);
|
||||||
|
void setTxDelay(uint32_t txDelay);
|
||||||
|
void setRemoteAddress(const QString& address, uint16_t port);
|
||||||
|
|
||||||
|
/** Return true if the stream is OK, return false if there is an error. */
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return m_error.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_error;
|
||||||
|
|
||||||
|
uint32_t m_centerFrequency; //!< center frequency in kHz
|
||||||
|
uint32_t m_sampleRate; //!< sample rate in Hz
|
||||||
|
uint8_t m_sampleBytes; //!< number of bytes per sample
|
||||||
|
uint8_t m_sampleBits; //!< number of effective bits per sample
|
||||||
|
uint32_t m_nbSamples; //!< total number of samples sent int the last frame
|
||||||
|
|
||||||
|
QHostAddress m_ownAddress;
|
||||||
|
|
||||||
|
CRC64 m_crc64;
|
||||||
|
uint8_t* m_bufMeta;
|
||||||
|
uint8_t* m_buf;
|
||||||
|
|
||||||
|
MetaDataFEC m_currentMetaFEC; //!< Meta data for current frame
|
||||||
|
uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks
|
||||||
|
uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram
|
||||||
|
SuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC
|
||||||
|
SuperBlock m_superBlock; //!< current super block being built
|
||||||
|
int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row
|
||||||
|
int m_txBlocksIndex; //!< Current index of Tx blocks row
|
||||||
|
uint16_t m_frameCount; //!< transmission frame count
|
||||||
|
int m_sampleIndex; //!< Current sample index in protected block data
|
||||||
|
|
||||||
|
QThread m_udpThread;
|
||||||
|
UDPSinkFECWorker *m_udpWorker;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class UDPSinkFECWorker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
class MsgUDPFECEncodeAndSend : public Message
|
||||||
|
{
|
||||||
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
public:
|
||||||
|
const UDPSinkFEC::SuperBlock *getTxBlocks() const { return m_txBlockx; }
|
||||||
|
uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; }
|
||||||
|
uint32_t getTxDelay() const { return m_txDelay; }
|
||||||
|
uint16_t getFrameIndex() const { return m_frameIndex; }
|
||||||
|
|
||||||
|
static MsgUDPFECEncodeAndSend* create(
|
||||||
|
const UDPSinkFEC::SuperBlock *txBlocks,
|
||||||
|
uint32_t nbBlocksFEC,
|
||||||
|
uint32_t txDelay,
|
||||||
|
uint16_t frameIndex)
|
||||||
|
{
|
||||||
|
return new MsgUDPFECEncodeAndSend(txBlocks, nbBlocksFEC, txDelay, frameIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const UDPSinkFEC::SuperBlock *m_txBlockx;
|
||||||
|
uint32_t m_nbBlocksFEC;
|
||||||
|
uint32_t m_txDelay;
|
||||||
|
uint16_t m_frameIndex;
|
||||||
|
|
||||||
|
MsgUDPFECEncodeAndSend(
|
||||||
|
const UDPSinkFEC::SuperBlock *txBlocks,
|
||||||
|
uint32_t nbBlocksFEC,
|
||||||
|
uint32_t txDelay,
|
||||||
|
uint16_t frameIndex) :
|
||||||
|
m_txBlockx(txBlocks),
|
||||||
|
m_nbBlocksFEC(nbBlocksFEC),
|
||||||
|
m_txDelay(txDelay),
|
||||||
|
m_frameIndex(frameIndex)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MsgConfigureRemoteAddress : public Message
|
||||||
|
{
|
||||||
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
public:
|
||||||
|
const QString& getAddress() const { return m_address; }
|
||||||
|
uint16_t getPort() const { return m_port; }
|
||||||
|
|
||||||
|
static MsgConfigureRemoteAddress* create(const QString& address, uint16_t port)
|
||||||
|
{
|
||||||
|
return new MsgConfigureRemoteAddress(address, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_address;
|
||||||
|
uint16_t m_port;
|
||||||
|
|
||||||
|
MsgConfigureRemoteAddress(const QString& address, uint16_t port) :
|
||||||
|
m_address(address),
|
||||||
|
m_port(port)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
UDPSinkFECWorker();
|
||||||
|
~UDPSinkFECWorker();
|
||||||
|
|
||||||
|
void pushTxFrame(const UDPSinkFEC::SuperBlock *txBlocks,
|
||||||
|
uint32_t nbBlocksFEC,
|
||||||
|
uint32_t txDelay,
|
||||||
|
uint16_t frameIndex);
|
||||||
|
void setRemoteAddress(const QString& address, uint16_t port);
|
||||||
|
|
||||||
|
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void handleInputMessages();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void transmitUDP(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, int nbBlocksFEC, int txDelay);
|
||||||
|
|
||||||
|
QUdpSocket m_udpSocket;
|
||||||
|
CM256 m_cm256; //!< CM256 library object
|
||||||
|
bool m_cm256Valid; //!< true if CM256 library is initialized correctly
|
||||||
|
QHostAddress m_remoteAddress;
|
||||||
|
uint16_t m_remotePort;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_ */
|
Loading…
x
Reference in New Issue
Block a user