1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-09 09:25:07 -04:00

SDRdaemonFEC: renamed folder to sdrdaemonsource

This commit is contained in:
f4exb
2017-06-09 13:42:33 +02:00
parent 9c1a38a8cf
commit 06c8df5d75
17 changed files with 36 additions and 32 deletions
@@ -0,0 +1,84 @@
project(sdrdaemonfec)
find_package(LibNANOMSG)
if (HAS_SSSE3)
message(STATUS "SDRdaemonFEC: use SSSE3 SIMD" )
elseif (HAS_NEON)
message(STATUS "SDRdaemonFEC: use Neon SIMD" )
else()
message(STATUS "SDRdaemonFEC: Unsupported architecture")
return()
endif()
set(sdrdaemonfec_SOURCES
sdrdaemonfecbuffer.cpp
sdrdaemonfecgui.cpp
sdrdaemonfecinput.cpp
sdrdaemonfecsettings.cpp
sdrdaemonfecplugin.cpp
sdrdaemonfecudphandler.cpp
)
set(sdrdaemonfec_HEADERS
sdrdaemonfecbuffer.h
sdrdaemonfecgui.h
sdrdaemonfecinput.h
sdrdaemonfecsettings.h
sdrdaemonfecplugin.h
sdrdaemonfecudphandler.h
)
set(sdrdaemonfec_FORMS
sdrdaemonfecgui.ui
)
#include(${QT_USE_FILE})
add_definitions(${QT_DEFINITIONS})
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_SHARED)
#qt4_wrap_cpp(sdrdaemonfec_HEADERS_MOC ${sdrdaemonfec_HEADERS})
qt5_wrap_ui(sdrdaemonfec_FORMS_HEADERS ${sdrdaemonfec_FORMS})
add_library(inputsdrdaemonfec SHARED
${sdrdaemonfec_SOURCES}
${sdrdaemonfec_HEADERS_MOC}
${sdrdaemonfec_FORMS_HEADERS}
)
if (BUILD_DEBIAN)
target_include_directories(inputsdrdaemonfec PUBLIC
.
${CMAKE_CURRENT_BINARY_DIR}
${LIBCM256CCSRC}
${LIBNANOMSG_INCLUDE_DIR}
)
else (BUILD_DEBIAN)
target_include_directories(inputsdrdaemonfec PUBLIC
.
${CMAKE_CURRENT_BINARY_DIR}
${CM256CC_INCLUDE_DIR}
${LIBNANOMSG_INCLUDE_DIR}
)
endif (BUILD_DEBIAN)
if (BUILD_DEBIAN)
target_link_libraries(inputsdrdaemonfec
${QT_LIBRARIES}
cm256cc
${LIBNANOMSG_LIBRARIES}
sdrbase
)
else (BUILD_DEBIAN)
target_link_libraries(inputsdrdaemonfec
${QT_LIBRARIES}
${CM256CC_LIBRARIES}
${LIBNANOMSG_LIBRARIES}
sdrbase
)
endif (BUILD_DEBIAN)
qt5_use_modules(inputsdrdaemonfec Core Widgets)
install(TARGETS inputsdrdaemonfec DESTINATION lib/plugins/samplesource)
@@ -0,0 +1,163 @@
<h1>SDRdaemonFEC plugin</h1>
<h2>Introduction</h2>
This input sample source plugin gets its samples over tbe network from a SDRdaemon server using UDP connection. SDRdaemon refers to the SDRdaemon utility found in [this](https://github.com/f4exb/sdrdaemon) Github repostory. This plugin is specialized in the version of SDRdaemon that sends data with FEC (Forward Erasure Correction). When FEC is used the format of the data is completely different from what it is without FEC.
The addition of FEC blocks and the sequence tagging of frames and blocks make the transmission more robust. While it is unlikely to be beneficial with copper or fiber links it can improve links over WiFi particularly on distant links.
Please note that there is no provision for handling out of sync UDP blocks. It is assumed that frames and block numbers always increase with possible blocks missing.
<h2>Build</h2>
The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. You will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands.
<h2>Interface</h2>
![SDR Daemon with FEC plugin GUI](../../../doc/img/SDRdaemonFEC_plugin.png)
<h3>1: Common stream parameters</h3>
![SDR Daemon FEC stream GUI](../../../doc/img/SDRdaemonFEC_plugin_01.png)
<h4>1.1: Frequency</h4>
This is the center frequency in kHz sent in the meta data from the distant SDRdaemon instance and corresponds to the center frequency of reception.
<h4>1.2: Start/Stop</h4>
Device start / stop button.
- Blue triangle icon: device is ready and can be started
- Green square icon: device is running and can be stopped
<h4>1.3: Record</h4>
Record I/Q stresm toggle button
<h4>1.4: Stream sample rate</h4>
Stream I/Q sample rate in kS/s
<h3>2: Auto correction options</h3>
These buttons control the local DSP auto correction options:
- **DC**: auto remove DC component
- **IQ**: auto make I/Q balance
<h3>3: Date/time</h3>
This is the current timestamp of the block of data sent from the receiver. It is refreshed about every second. The plugin tries to take into account the buffer that is used between the data received from the network and the data effectively used by the system however this may not be extremely accurate. It is based on the timestamps sent from the SDRdaemon utility at the other hand that does not take into account its own buffers.
<h3>9: Main buffer R/W pointers gauge</h3>
There are two gauges separated by a dot in the center. Ideally these gauges should not display any value thus read and write pointers are always half a buffer apart. However due to the fact that a whole frame is reconstructed at once up to ~10% variation is normal and should appear on the left gauge (write leads).
- The left gauge is the negative gauge. It is the value in percent of buffer size from the write pointer position to the read pointer position when this difference is less than half of a buffer distance. It means that the writes are leading or reads are lagging.
- The right gauge is the positive gauge. It is the value in percent of buffer size of the difference from the read pointer position to the write pointer position when this difference is less than half of a buffer distance. It menas that the writes are lagging or reads are leading.
The system tries to compensate read / write unbalance however at start or when a large stream distruption has occured a delay of a few tens of seconds is necessary before read / write reaches equilibrium.
<h3>4: Stream status and sizes</h3>
![SDR Daemon FEC status1 GUI](../../../doc/img/SDRdaemonFEC_plugin_04.png)
<h4>4.1: Minimum total number of blocks per frame</h4>
This is the minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks and the background lits in green.
<h4>4.2: Average total number of blocks received by frame</h4>
Moving average over the last 10 frames of the total number of blocks received per frame.
<h4>4.3: Stream status</h4>
The color of the icon indicates stream status:
- Green: all original blocks have been received for all frames during the last polling timeframe
- Pink: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe
- No color: some original blocks were definitely lost for some frames during the last polling timeframe
<h4>4.4: Minimum number of original blocks received by frame</h4>
Minimum number of original blocks received by frame during the last polling timeframe. Ideally this should match the nominal number of original blocks per frame which is 128 (green lock icon). Anything below the nominal number of original blocks minus FEC blocks means data loss (lock icon off). In betweem FEC is used to recover lost blocks (pink lock icon)
<h4>4.5: Maximum number of FEC blocks used by frame</h4>
Maximum number of FEC blocks used for original blocks recovery during the last polling timeframe. Ideally this should be 0 when no blocks are lost but the system is able to correct lost blocks up to the nominal number of FEC blocks (Pink lock icon).
<h4>4.6: Average number of FEC blocks used for original blocks recovery by frame</h4>
Moving average over the last 10 frames of the number of FEC blocks used for original blocks recovery per frame.
<h4>4.7: Receive buffer length</h4>
This is the main buffer (writes from UDP / reads from DSP engine) length in units of time (seconds). As read and write pointers are normally about half the buffer apart the nominal delay introduced by the buffer is the half of this value.
<h4>4.8: FEC nominal values</h4>
This is the nominal (Tx side) total number of blocks sent by frame (original blocks plus FEC blocks) and the nominal number of FEC blocks sent by frame separated by a slash (/)
<h4>4.9: Main buffer R/W pointers positions</h4>
Read and write pointers should always be a half buffer distance buffer apart. This is the difference in percent of the main buffer size from this ideal position.
- When positive it means that the read pointer is leading
- When negative it means that the write pointer is leading (read is lagging)
This corresponds to the value shown in the gauges above (9)
<h3>5: Network parameters</h3>
![SDR Daemon status3 GUI](../../../doc/img/SDRdaemon_plugin_06.png)
<h4>5.1: Local interface IP address</h4>
Address of the network interface on the local (your) machine to which the SDRdaemon server sends samples to.
<h4>5.2: Local data port</h4>
UDP port on the local (your) machine to which the SDRdaemon server sends samples to.
<h4>5.3 Distant configuration port</h4>
TCP port on the distant machine hosting the SDRdaemon instance to send control messages to. The IP address of the host where the SDRdaemon instance runs is guessed from the address sending the data blocks hence it does not need to be specified.
<h4>5.4: Validation button</h4>
Whenever the address (6.1), data port (6.2) or configuration port (6.3) change this button is enabled to validate the new values.
<h3>6: Configuration parameters</h3>
![SDR Daemon status4 GUI](../../../doc/img/SDRdaemon_plugin_07.png)
<h4>6.1: Center frequency in kHz</h4>
This is the center frequency in kHz to which the hardware attached to the SDRdaemon instance will get tuned to.
<h4>6.2: Decimation factor</h4>
These are successive powers of two from 0 (1) to 6 (64). The SDRdaemon instance will decimate the samples coming from the attached hardware by this value. Thus the sample rate (see 7.5) will be decimated by the same value before it is sent over through the network.
<h4>6.3: Center frequency position</h4>
The center frequency in the passband wil be set either:
- below the local oscillator (NCO) or infradyne. Actually -1/4th the bandwidth.
- above the local oscillator (NCO) or supradyne. Actually +1/4th the bandwidth.
- centered on the local oscillator or zero IF.
<h4>6.4: Send data to the distant SDRdaemon instance</h4>
Whenever any of the parameters change this button gets enabled. When clicked a message is sent on the configuration port of the distant machine to which the SDRdaemon listens for instructions. Leave time for the buffering system to stabilize to get the samples flow through normally.
<h4>6.5: Sample rate in kS/s</h4>
The sample rate of the hardware device attached to the SDRdaemon instance will be set to this value in kS/s.
<h4>6.6: Other parameters hardware specific</h4>
These are the parameters that are specific to the hardware attached to the distant SDRdaemon instance. You have to know which device is attached to send the proper parameters. Please refer to the SDRdaemon documentation or its line help to get information on these parameters.
In addition you can specify the inter-block transmission delay (txdelay) and number of FEC blocks per frame (fecblk).
@@ -0,0 +1,62 @@
#--------------------------------------------------------
#
# Pro file for Android and Windows builds with Qt Creator
#
#--------------------------------------------------------
TEMPLATE = lib
CONFIG += plugin
QT += core gui widgets multimedia network opengl
TARGET = inputsdrdaemonfec
CONFIG(MINGW32):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta"
CONFIG(MINGW64):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta"
CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc"
CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc"
INCLUDEPATH += $$PWD
INCLUDEPATH += ../../../sdrbase
INCLUDEPATH += ../../../lz4
INCLUDEPATH += $$LIBNANOMSGSRC/src
INCLUDEPATH += $$LIBCM256CCSRC
DEFINES += USE_SSE2=1
QMAKE_CXXFLAGS += -msse2
DEFINES += USE_SSSE3=1
QMAKE_CXXFLAGS += -mssse3
DEFINES += USE_SSE4_1=1
QMAKE_CXXFLAGS += -msse4.1
CONFIG(Release):build_subdir = release
CONFIG(Debug):build_subdir = debug
CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0"
CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0"
SOURCES += sdrdaemonfecbuffer.cpp\
sdrdaemonfecgui.cpp\
sdrdaemonfecinput.cpp\
sdrdaemonfecsettings.cpp\
sdrdaemonfecplugin.cpp\
sdrdaemonfecudphandler.cpp
HEADERS += sdrdaemonfecbuffer.h\
sdrdaemonfecgui.h\
sdrdaemonfecinput.h\
sdrdaemonfecsettings.h\
sdrdaemonfecplugin.h\
sdrdaemonfecudphandler.h
FORMS += sdrdaemonfecgui.ui
LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
LIBS += -L../../../lz4/$${build_subdir} -llz4
LIBS += -L../../../nanomsg/$${build_subdir} -lnanomsg
LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc
RESOURCES = ../../../sdrbase/resources/res.qrc
CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1
@@ -0,0 +1,452 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 "sdrdaemonfecbuffer.h"
#include <QDebug>
#include <cassert>
#include <cstring>
#include <cmath>
#include <lz4.h>
#include <boost/crc.hpp>
#include <boost/cstdint.hpp>
const int SDRdaemonFECBuffer::m_sampleSize = 2;
const int SDRdaemonFECBuffer::m_iqSampleSize = 2 * m_sampleSize;
SDRdaemonFECBuffer::SDRdaemonFECBuffer(uint32_t throttlems) :
m_decoderIndexHead(nbDecoderSlots/2),
m_frameHead(0),
m_curNbBlocks(0),
m_minNbBlocks(256),
m_curOriginalBlocks(0),
m_minOriginalBlocks(128),
m_curNbRecovery(0),
m_maxNbRecovery(0),
m_framesDecoded(true),
m_readIndex(0),
m_throttlemsNominal(throttlems),
m_readBuffer(0),
m_readSize(0),
m_bufferLenSec(0.0f),
m_nbReads(0),
m_nbWrites(0),
m_balCorrection(0),
m_balCorrLimit(0)
{
m_currentMeta.init();
m_framesNbBytes = nbDecoderSlots * sizeof(BufferFrame);
m_wrDeltaEstimate = m_framesNbBytes / 2;
m_tvOut_sec = 0;
m_tvOut_usec = 0;
m_readNbBytes = 1;
m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes
m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes
if (!m_cm256.isInitialized()) {
m_cm256_OK = false;
qDebug() << "SDRdaemonFECBuffer::SDRdaemonFECBuffer: cannot initialize CM256 library";
} else {
m_cm256_OK = true;
}
}
SDRdaemonFECBuffer::~SDRdaemonFECBuffer()
{
if (m_readBuffer) {
delete[] m_readBuffer;
}
}
void SDRdaemonFECBuffer::initDecodeAllSlots()
{
for (int i = 0; i < nbDecoderSlots; i++)
{
m_decoderSlots[i].m_blockCount = 0;
m_decoderSlots[i].m_originalCount = 0;
m_decoderSlots[i].m_recoveryCount = 0;
m_decoderSlots[i].m_decoded = false;
m_decoderSlots[i].m_metaRetrieved = false;
resetOriginalBlocks(i);
memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock));
}
}
void SDRdaemonFECBuffer::initDecodeSlot(int slotIndex)
{
// collect stats before voiding the slot
m_curNbBlocks = m_decoderSlots[slotIndex].m_blockCount;
m_curOriginalBlocks = m_decoderSlots[slotIndex].m_originalCount;
m_curNbRecovery = m_decoderSlots[slotIndex].m_recoveryCount;
m_avgNbBlocks(m_curNbBlocks);
m_avgOrigBlocks(m_curOriginalBlocks);
m_avgNbRecovery(m_curNbRecovery);
m_framesDecoded = m_framesDecoded && m_decoderSlots[slotIndex].m_decoded;
if (m_curNbBlocks < m_minNbBlocks) {
m_minNbBlocks = m_curNbBlocks;
}
if (m_curOriginalBlocks < m_minOriginalBlocks) {
m_minOriginalBlocks = m_curOriginalBlocks;
}
if (m_curNbRecovery > m_maxNbRecovery) {
m_maxNbRecovery = m_curNbRecovery;
}
// void the slot
m_decoderSlots[slotIndex].m_blockCount = 0;
m_decoderSlots[slotIndex].m_originalCount = 0;
m_decoderSlots[slotIndex].m_recoveryCount = 0;
m_decoderSlots[slotIndex].m_decoded = false;
m_decoderSlots[slotIndex].m_metaRetrieved = false;
resetOriginalBlocks(slotIndex);
memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock));
}
void SDRdaemonFECBuffer::initReadIndex()
{
m_readIndex = ((m_decoderIndexHead + (nbDecoderSlots/2)) % nbDecoderSlots) * sizeof(BufferFrame);
m_wrDeltaEstimate = m_framesNbBytes / 2;
m_nbReads = 0;
m_nbWrites = 0;
}
void SDRdaemonFECBuffer::rwCorrectionEstimate(int slotIndex)
{
if (m_nbReads >= 40) // check every ~1s as tick is ~50ms
{
int targetPivotSlot = (slotIndex + (nbDecoderSlots/2)) % nbDecoderSlots; // slot at half buffer opposite of current write slot
int targetPivotIndex = targetPivotSlot * sizeof(BufferFrame); // buffer index corresponding to start of above slot
int normalizedReadIndex = (m_readIndex < targetPivotIndex ? m_readIndex + nbDecoderSlots * sizeof(BufferFrame) : m_readIndex)
- (targetPivotSlot * sizeof(BufferFrame)); // normalize read index so it is positive and zero at start of pivot slot
int dBytes;
int rwDelta = (m_nbReads * m_readNbBytes) - (m_nbWrites * sizeof(BufferFrame));
if (normalizedReadIndex < (nbDecoderSlots/ 2) * (int) sizeof(BufferFrame)) // read leads
{
dBytes = - normalizedReadIndex - rwDelta;
}
else // read lags
{
dBytes = (nbDecoderSlots * sizeof(BufferFrame)) - normalizedReadIndex - rwDelta;
}
m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_iqSampleSize * m_nbReads)); // correction is in number of samples. Alpha = 0.25
if (m_balCorrection < -m_balCorrLimit) {
m_balCorrection = -m_balCorrLimit;
} else if (m_balCorrection > m_balCorrLimit) {
m_balCorrection = m_balCorrLimit;
}
m_nbReads = 0;
m_nbWrites = 0;
}
}
void SDRdaemonFECBuffer::checkSlotData(int slotIndex)
{
int pseudoWriteIndex = slotIndex * sizeof(BufferFrame);
m_wrDeltaEstimate = pseudoWriteIndex - m_readIndex;
m_nbWrites++;
int rwDelayBytes = (m_wrDeltaEstimate > 0 ? m_wrDeltaEstimate : sizeof(BufferFrame) * nbDecoderSlots + m_wrDeltaEstimate);
int sampleRate = m_currentMeta.m_sampleRate;
if (sampleRate > 0)
{
int64_t ts = m_currentMeta.m_tv_sec * 1000000LL + m_currentMeta.m_tv_usec;
ts -= (rwDelayBytes * 1000000LL) / (sampleRate * sizeof(Sample));
m_tvOut_sec = ts / 1000000LL;
m_tvOut_usec = ts - (m_tvOut_sec * 1000000LL);
}
if (!m_decoderSlots[slotIndex].m_decoded)
{
qDebug() << "SDRdaemonFECBuffer::checkSlotData: incomplete frame:"
<< " m_blockCount: " << m_decoderSlots[slotIndex].m_blockCount
<< " m_recoveryCount: " << m_decoderSlots[slotIndex].m_recoveryCount;
}
}
void SDRdaemonFECBuffer::writeData(char *array)
{
SuperBlock *superBlock = (SuperBlock *) array;
int frameIndex = superBlock->header.frameIndex;
int decoderIndex = frameIndex % nbDecoderSlots;
// frame break
if (m_frameHead == -1) // initial state
{
m_decoderIndexHead = decoderIndex; // new decoder slot head
m_frameHead = frameIndex;
initReadIndex(); // reset read index
initDecodeAllSlots(); // initialize all slots
}
else if (m_frameHead != frameIndex) // frame break => new frame starts
{
m_decoderIndexHead = decoderIndex; // new decoder slot head
m_frameHead = frameIndex; // new frame head
checkSlotData(decoderIndex); // check slot before re-init
rwCorrectionEstimate(decoderIndex);
initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot
}
// Block processing
if (m_decoderSlots[decoderIndex].m_blockCount < m_nbOriginalBlocks) // not enough blocks to decode -> store data
{
int blockIndex = superBlock->header.blockIndex;
int blockCount = m_decoderSlots[decoderIndex].m_blockCount;
int recoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount;
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Index = blockIndex;
if (blockIndex == 0) // first block with meta
{
m_decoderSlots[decoderIndex].m_metaRetrieved = true;
}
if (blockIndex < m_nbOriginalBlocks) // original data
{
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->protectedBlock);
m_decoderSlots[decoderIndex].m_originalCount++;
}
else // recovery data
{
m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->protectedBlock;
m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) &m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount];
m_decoderSlots[decoderIndex].m_recoveryCount++;
}
}
m_decoderSlots[decoderIndex].m_blockCount++;
if (m_decoderSlots[decoderIndex].m_blockCount == m_nbOriginalBlocks) // ready to decode
{
m_decoderSlots[decoderIndex].m_decoded = true;
if (m_cm256_OK && (m_decoderSlots[decoderIndex].m_recoveryCount > 0)) // recovery data used => need to decode FEC
{
m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes
m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes
if (m_decoderSlots[decoderIndex].m_metaRetrieved) {
m_paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks;
} else {
m_paramsCM256.RecoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount;
}
if (m_cm256.cm256_decode(m_paramsCM256, m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks)) // CM256 decode
{
qDebug() << "SDRdaemonFECBuffer::writeData: decode CM256 error:"
<< " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount
<< " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount;
}
else
{
qDebug() << "SDRdaemonFECBuffer::writeData: decode CM256 success:"
<< " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount
<< " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount;
for (int ir = 0; ir < m_decoderSlots[decoderIndex].m_recoveryCount; ir++) // restore missing blocks
{
int recoveryIndex = m_nbOriginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir;
int blockIndex = m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Index;
ProtectedBlock *recoveredBlock = (ProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block;
if (blockIndex == 0) // first block with meta
{
MetaDataFEC *metaData = (MetaDataFEC *) recoveredBlock;
boost::crc_32_type crc32;
crc32.process_bytes(metaData, 20);
if (crc32.checksum() == metaData->m_crc32)
{
m_decoderSlots[decoderIndex].m_metaRetrieved = true;
printMeta("SDRdaemonFECBuffer::writeData: recovered meta", metaData);
}
else
{
qDebug() << "SDRdaemonFECBuffer::writeData: recovered meta: invalid CRC32";
}
}
storeOriginalBlock(decoderIndex, blockIndex, *recoveredBlock);
qDebug() << "SDRdaemonFECBuffer::writeData: recovered block #" << blockIndex;
} // restore missing blocks
} // CM256 decode
} // revovery
if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received
{
MetaDataFEC *metaData = getMetaData(decoderIndex);
if (!(*metaData == m_currentMeta))
{
int sampleRate = metaData->m_sampleRate;
if (sampleRate > 0) {
m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_iqSampleSize);
m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read
m_readNbBytes = (sampleRate * m_iqSampleSize) / 20;
}
printMeta("SDRdaemonFECBuffer::writeData: new meta", metaData); // print for change other than timestamp
}
m_currentMeta = *metaData; // renew current meta
} // check block 0
} // decode
}
void SDRdaemonFECBuffer::writeData0(char *array __attribute__((unused)), uint32_t length __attribute__((unused)))
{
// Kept as comments for the out of sync blocks algorithms
// assert(length == m_udpPayloadSize);
//
// bool dataAvailable = false;
// SuperBlock *superBlock = (SuperBlock *) array;
// int frameIndex = superBlock->header.frameIndex;
// int decoderIndex = frameIndex % nbDecoderSlots;
// int blockIndex = superBlock->header.blockIndex;
//
//// qDebug() << "SDRdaemonFECBuffer::writeData:"
//// << " frameIndex: " << frameIndex
//// << " decoderIndex: " << decoderIndex
//// << " blockIndex: " << blockIndex;
//
// if (m_frameHead == -1) // initial state
// {
// m_decoderIndexHead = decoderIndex; // new decoder slot head
// m_frameHead = frameIndex;
// initReadIndex(); // reset read index
// initDecodeAllSlots(); // initialize all slots
// }
// else
// {
// int frameDelta = m_frameHead - frameIndex;
//
// if (frameDelta < 0)
// {
// if (-frameDelta < nbDecoderSlots) // new frame head not too new
// {
// //qDebug() << "SDRdaemonFECBuffer::writeData: new frame head (1): " << frameIndex << ":" << frameDelta << ":" << decoderIndex;
// m_decoderIndexHead = decoderIndex; // new decoder slot head
// m_frameHead = frameIndex;
// checkSlotData(decoderIndex);
// dataAvailable = true;
// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot
// }
// else if (-frameDelta <= 65536 - nbDecoderSlots) // loss of sync start over
// {
// //qDebug() << "SDRdaemonFECBuffer::writeData: loss of sync start over (1)" << frameIndex << ":" << frameDelta << ":" << decoderIndex;
// m_decoderIndexHead = decoderIndex; // new decoder slot head
// m_frameHead = frameIndex;
// initReadIndex(); // reset read index
// initDecodeAllSlots(); // re-initialize all slots
// }
// }
// else
// {
// if (frameDelta > 65536 - nbDecoderSlots) // new frame head not too new
// {
// //qDebug() << "SDRdaemonFECBuffer::writeData: new frame head (2): " << frameIndex << ":" << frameDelta << ":" << decoderIndex;
// m_decoderIndexHead = decoderIndex; // new decoder slot head
// m_frameHead = frameIndex;
// checkSlotData(decoderIndex);
// dataAvailable = true;
// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot
// }
// else if (frameDelta >= nbDecoderSlots) // loss of sync start over
// {
// //qDebug() << "SDRdaemonFECBuffer::writeData: loss of sync start over (2)" << frameIndex << ":" << frameDelta << ":" << decoderIndex;
// m_decoderIndexHead = decoderIndex; // new decoder slot head
// m_frameHead = frameIndex;
// initReadIndex(); // reset read index
// initDecodeAllSlots(); // re-initialize all slots
// }
// }
// }
//
// // decoderIndex should now be correctly set
//
}
uint8_t *SDRdaemonFECBuffer::readData(int32_t length)
{
uint8_t *buffer = (uint8_t *) m_frames;
uint32_t readIndex = m_readIndex;
m_nbReads++;
// SEGFAULT FIX: arbitratily truncate so that it does not exceed buffer length
if (length > framesSize) {
length = framesSize;
}
if (m_readIndex + length < m_framesNbBytes) // ends before buffer bound
{
m_readIndex += length;
return &buffer[readIndex];
}
else if (m_readIndex + length == m_framesNbBytes) // ends at buffer bound
{
m_readIndex = 0;
return &buffer[readIndex];
}
else // ends after buffer bound
{
if (length > m_readSize) // reallocate composition buffer if necessary
{
if (m_readBuffer) {
delete[] m_readBuffer;
}
m_readBuffer = new uint8_t[length];
m_readSize = length;
}
std::memcpy((void *) m_readBuffer, (const void *) &buffer[m_readIndex], m_framesNbBytes - m_readIndex); // copy end of buffer
length -= m_framesNbBytes - m_readIndex;
std::memcpy((void *) &m_readBuffer[m_framesNbBytes - m_readIndex], (const void *) buffer, length); // copy start of buffer
m_readIndex = length;
return m_readBuffer;
}
}
void SDRdaemonFECBuffer::printMeta(const QString& header, MetaDataFEC *metaData)
{
qDebug() << header << ": "
<< "|" << 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
<< "|";
}
@@ -0,0 +1,273 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECBUFFER_H_
#define PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECBUFFER_H_
#include <QString>
#include <QDebug>
#include <cstdlib>
#include "cm256.h"
#include "util/movingaverage.h"
#define SDRDAEMONFEC_UDPSIZE 512 // UDP payload size
#define SDRDAEMONFEC_NBORIGINALBLOCKS 128 // number of sample blocks per frame excluding FEC blocks
#define SDRDAEMONFEC_NBDECODERSLOTS 16 // power of two sub multiple of uint16_t size. A too large one is superfluous.
class SDRdaemonFECBuffer
{
public:
#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));
}
};
struct Sample
{
int16_t i;
int16_t q;
};
struct Header
{
uint16_t frameIndex;
uint8_t blockIndex;
uint8_t filler;
};
static const int samplesPerBlock = (SDRDAEMONFEC_UDPSIZE - sizeof(Header)) / sizeof(Sample);
static const int framesSize = SDRDAEMONFEC_NBDECODERSLOTS * (SDRDAEMONFEC_NBORIGINALBLOCKS - 1) * (SDRDAEMONFEC_UDPSIZE - sizeof(Header));
struct ProtectedBlock
{
Sample samples[samplesPerBlock];
};
struct SuperBlock
{
Header header;
ProtectedBlock protectedBlock;
};
#pragma pack(pop)
SDRdaemonFECBuffer(uint32_t throttlems);
~SDRdaemonFECBuffer();
// R/W operations
void writeData(char *array); //!< Write data into buffer.
void writeData0(char *array, uint32_t length); //!< Write data into buffer.
uint8_t *readData(int32_t length); //!< Read data from buffer
// meta data
const MetaDataFEC& getCurrentMeta() const { return m_currentMeta; }
// samples timestamp
uint32_t getTVOutSec() const { return m_tvOut_sec; }
uint32_t getTVOutUsec() const { return m_tvOut_usec; }
// stats
int getCurNbBlocks() const { return m_curNbBlocks; }
int getCurOriginalBlocks() const { return m_curOriginalBlocks; }
int getCurNbRecovery() const { return m_curNbRecovery; }
float getAvgNbBlocks() const { return m_avgNbBlocks; }
float getAvgOriginalBlocks() const { return m_avgOrigBlocks; }
float getAvgNbRecovery() const { return m_avgNbRecovery; }
int getMinNbBlocks()
{
int minNbBlocks = m_minNbBlocks;
m_minNbBlocks = 256;
return minNbBlocks;
}
int getMinOriginalBlocks()
{
int minOriginalBlocks = m_minOriginalBlocks;
m_minOriginalBlocks = 128;
return minOriginalBlocks;
}
int getMaxNbRecovery()
{
int maxNbRecovery = m_maxNbRecovery;
m_maxNbRecovery = 0;
return maxNbRecovery;
}
bool allFramesDecoded()
{
bool framesDecoded = m_framesDecoded;
m_framesDecoded = true;
return framesDecoded;
}
float getBufferLengthInSecs() const { return m_bufferLenSec; }
int32_t getRWBalanceCorrection() const { return m_balCorrection; }
/** Get buffer gauge value in % of buffer size ([-50:50])
* [-50:0] : write leads or read lags
* [0:50] : read leads or write lags
*/
inline int32_t getBufferGauge() const
{
if (m_framesNbBytes)
{
int32_t val = (m_wrDeltaEstimate * 100) / (int32_t) m_framesNbBytes;
int32_t ret = val < 0 ? -val - 50 : 50 -val;
return ret;
}
else
{
return 0; // default position
}
}
static const int m_udpPayloadSize = SDRDAEMONFEC_UDPSIZE;
static const int m_nbOriginalBlocks = SDRDAEMONFEC_NBORIGINALBLOCKS;
static const int m_sampleSize;
static const int m_iqSampleSize;
private:
static const int nbDecoderSlots = SDRDAEMONFEC_NBDECODERSLOTS;
#pragma pack(push, 1)
struct BufferFrame
{
ProtectedBlock m_blocks[m_nbOriginalBlocks - 1];
};
#pragma pack(pop)
struct DecoderSlot
{
ProtectedBlock m_blockZero; //!< First block of a frame. Has meta data.
ProtectedBlock m_originalBlocks[m_nbOriginalBlocks]; //!< Original blocks retrieved directly or by later FEC
ProtectedBlock m_recoveryBlocks[m_nbOriginalBlocks]; //!< Recovery blocks (FEC blocks) with max size
CM256::cm256_block m_cm256DescriptorBlocks[m_nbOriginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes)
int m_blockCount; //!< number of blocks received for this frame
int m_originalCount; //!< number of original blocks received
int m_recoveryCount; //!< number of recovery blocks received
bool m_decoded; //!< true if decoded
bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved
};
MetaDataFEC m_currentMeta; //!< Stored current meta data
CM256::cm256_encoder_params m_paramsCM256; //!< CM256 decoder parameters block
DecoderSlot m_decoderSlots[nbDecoderSlots]; //!< CM256 decoding control/buffer slots
BufferFrame m_frames[nbDecoderSlots]; //!< Samples buffer
int m_framesNbBytes; //!< Number of bytes in samples buffer
int m_decoderIndexHead; //!< index of the current head frame slot in decoding slots
int m_frameHead; //!< index of the current head frame sent
int m_curNbBlocks; //!< (stats) instantaneous number of blocks received
int m_minNbBlocks; //!< (stats) minimum number of blocks received since last poll
int m_curOriginalBlocks; //!< (stats) instantanous number of original blocks received
int m_minOriginalBlocks; //!< (stats) minimum number of original blocks received since last poll
int m_curNbRecovery; //!< (stats) instantaneous number of recovery blocks used
int m_maxNbRecovery; //!< (stats) maximum number of recovery blocks used since last poll
MovingAverage<int, int, 10> m_avgNbBlocks; //!< (stats) average number of blocks received
MovingAverage<int, int, 10> m_avgOrigBlocks; //!< (stats) average number of original blocks received
MovingAverage<int, int, 10> m_avgNbRecovery; //!< (stats) average number of recovery blocks used
bool m_framesDecoded; //!< [stats] true if all frames were decoded since last poll
int m_readIndex; //!< current byte read index in frames buffer
int m_wrDeltaEstimate; //!< Sampled estimate of write to read indexes difference
uint32_t m_tvOut_sec; //!< Estimated returned samples timestamp (seconds)
uint32_t m_tvOut_usec; //!< Estimated returned samples timestamp (microseconds)
int m_readNbBytes; //!< Nominal number of bytes per read (50ms)
uint32_t m_throttlemsNominal; //!< Initial throttle in ms
uint8_t* m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer
int m_readSize; //!< Read buffer size
float m_bufferLenSec;
int m_nbReads; //!< Number of buffer reads since start of auto R/W balance correction period
int m_nbWrites; //!< Number of buffer writes since start of auto R/W balance correction period
int m_balCorrection; //!< R/W balance correction in number of samples
int m_balCorrLimit; //!< Correction absolute value limit in number of samples
CM256 m_cm256; //!< CM256 library
bool m_cm256_OK; //!< CM256 library initialized OK
inline ProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const ProtectedBlock& protectedBlock)
{
if (blockIndex == 0) {
// m_decoderSlots[slotIndex].m_originalBlocks[0] = protectedBlock;
// return &m_decoderSlots[slotIndex].m_originalBlocks[0];
m_decoderSlots[slotIndex].m_blockZero = protectedBlock;
return &m_decoderSlots[slotIndex].m_blockZero;
} else {
// m_decoderSlots[slotIndex].m_originalBlocks[blockIndex] = protectedBlock;
// return &m_decoderSlots[slotIndex].m_originalBlocks[blockIndex];
m_frames[slotIndex].m_blocks[blockIndex - 1] = protectedBlock;
return &m_frames[slotIndex].m_blocks[blockIndex - 1];
}
}
inline ProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex)
{
if (blockIndex == 0) {
// return m_decoderSlots[slotIndex].m_originalBlocks[0];
return m_decoderSlots[slotIndex].m_blockZero;
} else {
// return m_decoderSlots[slotIndex].m_originalBlocks[blockIndex];
return m_frames[slotIndex].m_blocks[blockIndex - 1];
}
}
inline MetaDataFEC *getMetaData(int slotIndex)
{
// return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_originalBlocks[0];
return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero;
}
inline void resetOriginalBlocks(int slotIndex)
{
// memset((void *) m_decoderSlots[slotIndex].m_originalBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock));
memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(ProtectedBlock));
memset((void *) m_frames[slotIndex].m_blocks, 0, (m_nbOriginalBlocks - 1) * sizeof(ProtectedBlock));
}
void initDecodeAllSlots();
void initReadIndex();
void rwCorrectionEstimate(int slotIndex);
void checkSlotData(int slotIndex);
void initDecodeSlot(int slotIndex);
static void printMeta(const QString& header, MetaDataFEC *metaData);
};
#endif /* PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECBUFFER_H_ */
@@ -0,0 +1,778 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 "sdrdaemonfecgui.h"
#include <stdint.h>
#include <sstream>
#include <iostream>
#include <cassert>
#include <QDebug>
#include <QMessageBox>
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QFileDialog>
#include <nanomsg/nn.h>
#include <nanomsg/pair.h>
#include "ui_sdrdaemonfecgui.h"
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "mainwindow.h"
#include "util/simpleserializer.h"
#include <device/devicesourceapi.h>
#include <dsp/filerecord.h>
SDRdaemonFECGui::SDRdaemonFECGui(DeviceSourceAPI *deviceAPI, QWidget* parent) :
QWidget(parent),
ui(new Ui::SDRdaemonFECGui),
m_deviceAPI(deviceAPI),
m_sampleSource(NULL),
m_acquisition(false),
m_lastEngineState((DSPDeviceSourceEngine::State)-1),
m_sampleRate(0),
m_centerFrequency(0),
m_framesDecodingStatus(0),
m_bufferLengthInSecs(0.0),
m_bufferGauge(-50),
m_nbOriginalBlocks(128),
m_nbFECBlocks(0),
m_samplesCount(0),
m_tickCount(0),
m_address("127.0.0.1"),
m_dataPort(9090),
m_controlPort(9091),
m_addressEdited(false),
m_dataPortEdited(false),
m_initSendConfiguration(false),
m_countUnrecoverable(0),
m_countRecovered(0),
m_doApplySettings(true),
m_forceSettings(true),
m_txDelay(0.0),
m_dcBlock(false),
m_iqCorrection(false)
{
m_sender = nn_socket(AF_SP, NN_PAIR);
assert(m_sender != -1);
int millis = 500;
int rc __attribute__((unused)) = nn_setsockopt (m_sender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis));
assert (rc == 0);
m_paletteGreenText.setColor(QPalette::WindowText, Qt::green);
m_paletteWhiteText.setColor(QPalette::WindowText, Qt::white);
m_startingTimeStamp.tv_sec = 0;
m_startingTimeStamp.tv_usec = 0;
ui->setupUi(this);
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->centerFrequency->setValueRange(7, 0, 9999999U);
ui->freq->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->freq->setValueRange(7, 0, 9999999U);
ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->sampleRate->setValueRange(7, 32000U, 9999999U);
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(&(deviceAPI->getMainWindow()->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
m_sampleSource = new SDRdaemonFECInput(deviceAPI->getMainWindow()->getMasterTimer(), m_deviceAPI);
connect(m_sampleSource->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_deviceAPI->setSource(m_sampleSource);
displaySettings();
char recFileNameCStr[30];
sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID());
m_fileSink = new FileRecord(std::string(recFileNameCStr));
m_deviceAPI->addSink(m_fileSink);
connect(m_deviceAPI->getDeviceOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleDSPMessages()), Qt::QueuedConnection);
m_eventsTime.start();
displayEventCounts();
displayEventTimer();
displaySettings();
sendControl(true);
sendSettings();
}
SDRdaemonFECGui::~SDRdaemonFECGui()
{
m_deviceAPI->removeSink(m_fileSink);
delete m_fileSink;
delete m_sampleSource;
delete ui;
}
void SDRdaemonFECGui::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void SDRdaemonFECGui::destroy()
{
delete this;
}
void SDRdaemonFECGui::setName(const QString& name)
{
setObjectName(name);
}
QString SDRdaemonFECGui::getName() const
{
return objectName();
}
void SDRdaemonFECGui::resetToDefaults()
{
blockApplySettings(true);
m_settings.resetToDefaults();
displaySettings();
blockApplySettings(false);
sendSettings();
}
QByteArray SDRdaemonFECGui::serialize() const
{
return m_settings.serialize();
}
bool SDRdaemonFECGui::deserialize(const QByteArray& data)
{
blockApplySettings(true);
if(m_settings.deserialize(data))
{
displaySettings();
configureUDPLink();
updateTxDelay();
sendControl();
blockApplySettings(false);
sendControl(true);
m_forceSettings = true;
sendSettings();
return true;
}
else
{
blockApplySettings(false);
return false;
}
}
qint64 SDRdaemonFECGui::getCenterFrequency() const
{
return m_settings.m_centerFrequency;
}
void SDRdaemonFECGui::setCenterFrequency(qint64 centerFrequency)
{
m_settings.m_centerFrequency = centerFrequency;
displaySettings();
sendControl();
sendSettings();
}
bool SDRdaemonFECGui::handleMessage(const Message& message)
{
if (SDRdaemonFECInput::MsgReportSDRdaemonAcquisition::match(message))
{
m_acquisition = ((SDRdaemonFECInput::MsgReportSDRdaemonAcquisition&)message).getAcquisition();
updateWithAcquisition();
return true;
}
else if (SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData::match(message))
{
int sampleRate = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData&)message).getSampleRate();
if (m_sampleRate != sampleRate)
{
m_sampleRate = sampleRate;
updateTxDelay();
sendControl();
}
m_centerFrequency = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData&)message).getCenterFrequency();
m_startingTimeStamp.tv_sec = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData&)message).get_tv_sec();
m_startingTimeStamp.tv_usec = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData&)message).get_tv_usec();
updateWithStreamData();
return true;
}
else if (SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming::match(message))
{
m_startingTimeStamp.tv_sec = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).get_tv_sec();
m_startingTimeStamp.tv_usec = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).get_tv_usec();
m_framesDecodingStatus = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getFramesDecodingStatus();
m_allBlocksReceived = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).allBlocksReceived();
m_bufferLengthInSecs = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getBufferLengthInSecs();
m_bufferGauge = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getBufferGauge();
m_minNbBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getMinNbBlocks();
m_minNbOriginalBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getMinNbOriginalBlocks();
m_maxNbRecovery = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getMaxNbRecovery();
m_avgNbBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getAvgNbBlocks();
m_avgNbOriginalBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getAvgNbOriginalBlocks();
m_avgNbRecovery = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getAvgNbRecovery();
m_nbOriginalBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getNbOriginalBlocksPerFrame();
int nbFECBlocks = ((SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming&)message).getNbFECBlocksPerFrame();
if (m_nbFECBlocks != nbFECBlocks)
{
m_nbFECBlocks = nbFECBlocks;
updateTxDelay();
sendControl();
}
updateWithStreamTime();
return true;
}
else
{
return false;
}
}
void SDRdaemonFECGui::handleDSPMessages()
{
Message* message;
while ((message = m_deviceAPI->getDeviceOutputMessageQueue()->pop()) != 0)
{
qDebug("SDRdaemonGui::handleDSPMessages: message: %s", message->getIdentifier());
if (DSPSignalNotification::match(*message))
{
DSPSignalNotification* notif = (DSPSignalNotification*) message;
m_deviceSampleRate = notif->getSampleRate();
m_deviceCenterFrequency = notif->getCenterFrequency();
qDebug("SDRdaemonGui::handleDSPMessages: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency());
updateSampleRateAndFrequency();
m_fileSink->handleMessage(*notif); // forward to file sink
delete message;
}
}
}
void SDRdaemonFECGui::handleSourceMessages()
{
Message* message;
while ((message = m_sampleSource->getOutputMessageQueueToGUI()->pop()) != 0)
{
//qDebug("SDRdaemonGui::handleSourceMessages: message: %s", message->getIdentifier());
if (handleMessage(*message))
{
delete message;
}
}
}
void SDRdaemonFECGui::updateSampleRateAndFrequency()
{
m_deviceAPI->getSpectrum()->setSampleRate(m_deviceSampleRate);
m_deviceAPI->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency);
ui->deviceRateText->setText(tr("%1k").arg((float)m_deviceSampleRate / 1000));
}
void SDRdaemonFECGui::updateTxDelay()
{
m_txDelay = ((127*127*m_settings.m_txDelay) / m_sampleRate)/(128 + m_nbFECBlocks);
ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(m_txDelay*1e6, 'f', 0)));
}
void SDRdaemonFECGui::displaySettings()
{
ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000);
ui->deviceRateText->setText(tr("%1k").arg(m_sampleRate / 1000.0));
ui->freq->setValue(m_settings.m_centerFrequency / 1000);
ui->decim->setCurrentIndex(m_settings.m_log2Decim);
ui->fcPos->setCurrentIndex(m_settings.m_fcPos);
ui->sampleRate->setValue(m_settings.m_sampleRate);
ui->specificParms->setText(m_settings.m_specificParameters);
ui->specificParms->setCursorPosition(0);
ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100));
ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks);
QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0'));
ui->nbFECBlocksText->setText(nstr);
QString s0 = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0);
ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(nstr));
ui->address->setText(m_settings.m_address);
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
ui->controlPort->setText(tr("%1").arg(m_settings.m_controlPort));
ui->specificParms->setText(m_settings.m_specificParameters);
ui->dcOffset->setChecked(m_settings.m_dcBlock);
ui->iqImbalance->setChecked(m_settings.m_iqCorrection);
}
void SDRdaemonFECGui::sendControl(bool force)
{
QString remoteAddress;
((SDRdaemonFECInput *) m_sampleSource)->getRemoteAddress(remoteAddress);
if ((remoteAddress != m_remoteAddress) ||
(m_settings.m_controlPort != m_controlSettings.m_controlPort) || force)
{
m_remoteAddress = remoteAddress;
int rc = nn_shutdown(m_sender, 0);
if (rc < 0) {
qDebug() << "SDRdaemonFECGui::sendControl: disconnection failed";
} else {
qDebug() << "SDRdaemonFECGui::sendControl: disconnection successful";
}
std::ostringstream os;
os << "tcp://" << m_remoteAddress.toStdString() << ":" << m_settings.m_controlPort;
std::string addrstrng = os.str();
rc = nn_connect(m_sender, addrstrng.c_str());
if (rc < 0) {
qDebug() << "SDRdaemonFECGui::sendConfiguration: connexion to " << addrstrng.c_str() << " failed";
QMessageBox::information(this, tr("Message"), tr("Cannot connect to remote control port"));
} else {
qDebug() << "SDRdaemonFECGui::sendConfiguration: connexion to " << addrstrng.c_str() << " successful";
}
}
std::ostringstream os;
int nbArgs = 0;
if ((m_settings.m_centerFrequency != m_controlSettings.m_centerFrequency) || force)
{
os << "freq=" << m_settings.m_centerFrequency;
nbArgs++;
}
if ((m_settings.m_sampleRate != m_controlSettings.m_sampleRate) || (m_settings.m_log2Decim != m_controlSettings.m_log2Decim) || force)
{
if (nbArgs > 0) os << ",";
os << "srate=" << m_settings.m_sampleRate;
nbArgs++;
}
if ((m_settings.m_log2Decim != m_controlSettings.m_log2Decim) || force)
{
if (nbArgs > 0) os << ",";
os << "decim=" << m_settings.m_log2Decim;
nbArgs++;
}
if ((m_settings.m_fcPos != m_controlSettings.m_fcPos) || force)
{
if (nbArgs > 0) os << ",";
os << "fcpos=" << m_settings.m_fcPos;
nbArgs++;
}
if ((m_settings.m_nbFECBlocks != m_controlSettings.m_nbFECBlocks) || force)
{
if (nbArgs > 0) os << ",";
os << "fecblk=" << m_settings.m_nbFECBlocks;
nbArgs++;
}
if (m_txDelay != 0.0)
{
if (nbArgs > 0) os << ",";
os << "txdelay=" << (int) (m_txDelay*1e6);
nbArgs++;
m_txDelay = 0.0;
}
if ((m_settings.m_specificParameters != m_controlSettings.m_specificParameters) || force)
{
if (m_settings.m_specificParameters.size() > 0)
{
if (nbArgs > 0) os << ",";
os << m_settings.m_specificParameters.toStdString();
nbArgs++;
}
}
if (nbArgs > 0)
{
int config_size = os.str().size();
int rc = nn_send(m_sender, (void *) os.str().c_str(), config_size, 0);
if (rc != config_size)
{
//QMessageBox::information(this, tr("Message"), tr("Cannot send message to remote control port"));
qDebug() << "SDRdaemonFECGui::sendControl: Cannot send message to remote control port."
<< " remoteAddress: " << m_remoteAddress
<< " remotePort: " << m_settings.m_controlPort
<< " message: " << os.str().c_str();
}
else
{
qDebug() << "SDRdaemonFECGui::sendControl:"
<< "remoteAddress:" << m_remoteAddress
<< "remotePort:" << m_settings.m_controlPort
<< "message:" << os.str().c_str();
}
}
m_controlSettings.m_address = m_settings.m_address;
m_controlSettings.m_controlPort = m_settings.m_controlPort;
m_controlSettings.m_centerFrequency = m_settings.m_centerFrequency;
m_controlSettings.m_sampleRate = m_settings.m_sampleRate;
m_controlSettings.m_log2Decim = m_settings.m_log2Decim;
m_controlSettings.m_fcPos = m_settings.m_fcPos;
m_controlSettings.m_nbFECBlocks = m_settings.m_nbFECBlocks;
m_controlSettings.m_specificParameters = m_settings.m_specificParameters;
}
void SDRdaemonFECGui::sendSettings()
{
if(!m_updateTimer.isActive())
m_updateTimer.start(100);
}
void SDRdaemonFECGui::on_applyButton_clicked(bool checked __attribute__((unused)))
{
m_settings.m_address = ui->address->text();
bool ctlOk;
int udpCtlPort = ui->controlPort->text().toInt(&ctlOk);
if((ctlOk) && (udpCtlPort >= 1024) && (udpCtlPort < 65535))
{
m_settings.m_controlPort = udpCtlPort;
}
bool dataOk;
int udpDataPort = ui->dataPort->text().toInt(&dataOk);
if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535))
{
m_settings.m_dataPort = udpDataPort;
}
configureUDPLink();
}
void SDRdaemonFECGui::on_sendButton_clicked(bool checked __attribute__((unused)))
{
updateTxDelay();
sendControl(true);
ui->specificParms->setCursorPosition(0);
}
void SDRdaemonFECGui::on_address_returnPressed()
{
m_settings.m_address = ui->address->text();
configureUDPLink();
}
void SDRdaemonFECGui::on_dataPort_returnPressed()
{
bool dataOk;
int udpDataPort = ui->dataPort->text().toInt(&dataOk);
if((!dataOk) || (udpDataPort < 1024) || (udpDataPort > 65535))
{
return;
}
else
{
m_settings.m_dataPort = udpDataPort;
}
configureUDPLink();
}
void SDRdaemonFECGui::on_controlPort_returnPressed()
{
bool ctlOk;
int udpCtlPort = ui->controlPort->text().toInt(&ctlOk);
if((!ctlOk) || (udpCtlPort < 1024) || (udpCtlPort > 65535))
{
return;
}
else
{
m_settings.m_controlPort = udpCtlPort;
}
sendControl();
}
void SDRdaemonFECGui::on_dcOffset_toggled(bool checked)
{
if (m_dcBlock != checked)
{
m_dcBlock = checked;
configureAutoCorrections();
}
}
void SDRdaemonFECGui::on_iqImbalance_toggled(bool checked)
{
if (m_iqCorrection != checked)
{
m_iqCorrection = checked;
configureAutoCorrections();
}
}
void SDRdaemonFECGui::on_freq_changed(quint64 value)
{
m_settings.m_centerFrequency = value * 1000;
sendControl();
}
void SDRdaemonFECGui::on_sampleRate_changed(quint64 value)
{
m_settings.m_sampleRate = value;
sendControl();
}
void SDRdaemonFECGui::on_specificParms_returnPressed()
{
if ((ui->specificParms->text()).size() > 0) {
m_settings.m_specificParameters = ui->specificParms->text();
sendControl();
}
}
void SDRdaemonFECGui::on_decim_currentIndexChanged(int index __attribute__((unused)))
{
m_settings.m_log2Decim = ui->decim->currentIndex();
sendControl();
}
void SDRdaemonFECGui::on_fcPos_currentIndexChanged(int index __attribute__((unused)))
{
m_settings.m_fcPos = ui->fcPos->currentIndex();
sendControl();
}
void SDRdaemonFECGui::on_txDelay_valueChanged(int value)
{
m_settings.m_txDelay = value / 100.0;
ui->txDelayText->setText(tr("%1").arg(value));
updateTxDelay();
sendControl();
}
void SDRdaemonFECGui::on_nbFECBlocks_valueChanged(int value)
{
m_settings.m_nbFECBlocks = value;
QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0'));
ui->nbFECBlocksText->setText(nstr);
sendControl();
}
void SDRdaemonFECGui::on_startStop_toggled(bool checked)
{
if (checked)
{
if (m_deviceAPI->initAcquisition())
{
m_deviceAPI->startAcquisition();
DSPEngine::instance()->startAudioOutput();
}
}
else
{
m_deviceAPI->stopAcquisition();
DSPEngine::instance()->stopAudioOutput();
}
}
void SDRdaemonFECGui::on_record_toggled(bool checked)
{
if (checked)
{
ui->record->setStyleSheet("QToolButton { background-color : red; }");
m_fileSink->startRecording();
}
else
{
ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
m_fileSink->stopRecording();
}
}
void SDRdaemonFECGui::on_eventCountsReset_clicked(bool checked __attribute__((unused)))
{
m_countUnrecoverable = 0;
m_countRecovered = 0;
m_eventsTime.start();
displayEventCounts();
displayEventTimer();
}
void SDRdaemonFECGui::displayEventCounts()
{
QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0'));
ui->eventUnrecText->setText(nstr);
nstr = QString("%1").arg(m_countRecovered, 3, 10, QChar('0'));
ui->eventRecText->setText(nstr);
}
void SDRdaemonFECGui::displayEventTimer()
{
int elapsedTimeMillis = m_eventsTime.elapsed();
QTime recordLength(0, 0, 0, 0);
recordLength = recordLength.addSecs(elapsedTimeMillis/1000);
QString s_time = recordLength.toString("hh:mm:ss");
ui->eventCountsTimeText->setText(s_time);
}
void SDRdaemonFECGui::configureUDPLink()
{
qDebug() << "SDRdaemonGui::configureUDPLink: " << m_settings.m_address.toStdString().c_str()
<< " : " << m_settings.m_dataPort;
SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink* message = SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink::create(m_settings.m_address, m_settings.m_dataPort);
m_sampleSource->getInputMessageQueue()->push(message);
}
void SDRdaemonFECGui::configureAutoCorrections()
{
SDRdaemonFECInput::MsgConfigureSDRdaemonAutoCorr* message = SDRdaemonFECInput::MsgConfigureSDRdaemonAutoCorr::create(m_dcBlock, m_iqCorrection);
m_sampleSource->getInputMessageQueue()->push(message);
}
void SDRdaemonFECGui::updateWithAcquisition()
{
}
void SDRdaemonFECGui::updateWithStreamData()
{
ui->centerFrequency->setValue(m_centerFrequency / 1000);
updateWithStreamTime();
}
void SDRdaemonFECGui::updateWithStreamTime()
{
bool updateEventCounts = false;
quint64 startingTimeStampMsec = ((quint64) m_startingTimeStamp.tv_sec * 1000LL) + ((quint64) m_startingTimeStamp.tv_usec / 1000LL);
QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec);
QString s_date = dt.toString("yyyy-MM-dd hh:mm:ss.zzz");
ui->absTimeText->setText(s_date);
if (m_framesDecodingStatus == 2)
{
ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }");
}
else if (m_framesDecodingStatus == 1)
{
if (m_countRecovered < 999) m_countRecovered++;
updateEventCounts = true;
ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(56,56,56); }");
}
else
{
if (m_countUnrecoverable < 999) m_countUnrecoverable++;
updateEventCounts = true;
ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }");
}
QString s = QString::number(m_bufferLengthInSecs, 'f', 1);
ui->bufferLenSecsText->setText(tr("%1").arg(s));
s = QString::number(m_bufferGauge, 'f', 0);
ui->bufferRWBalanceText->setText(tr("%1").arg(s));
ui->bufferGaugeNegative->setValue((m_bufferGauge < 0 ? -m_bufferGauge : 0));
ui->bufferGaugePositive->setValue((m_bufferGauge < 0 ? 0 : m_bufferGauge));
s = QString::number(m_minNbBlocks, 'f', 0);
ui->minNbBlocksText->setText(tr("%1").arg(s));
s = QString("%1").arg(m_maxNbRecovery, 2, 10, QChar('0'));
ui->maxNbRecoveryText->setText(tr("%1").arg(s));
s = QString::number(m_nbOriginalBlocks + m_nbFECBlocks, 'f', 0);
QString s1 = QString("%1").arg(m_nbFECBlocks, 2, 10, QChar('0'));
ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1));
if (updateEventCounts)
{
displayEventCounts();
}
displayEventTimer();
}
void SDRdaemonFECGui::updateHardware()
{
qDebug() << "SDRdaemonSinkGui::updateHardware";
SDRdaemonFECInput::MsgConfigureSDRdaemonFEC* message = SDRdaemonFECInput::MsgConfigureSDRdaemonFEC::create(m_settings, m_forceSettings);
m_sampleSource->getInputMessageQueue()->push(message);
m_forceSettings = false;
m_updateTimer.stop();
}
void SDRdaemonFECGui::updateStatus()
{
int state = m_deviceAPI->state();
if(m_lastEngineState != state)
{
switch(state)
{
case DSPDeviceSourceEngine::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case DSPDeviceSourceEngine::StIdle:
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case DSPDeviceSourceEngine::StRunning:
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case DSPDeviceSourceEngine::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_deviceAPI->errorMessage());
break;
default:
break;
}
m_lastEngineState = state;
}
}
void SDRdaemonFECGui::tick()
{
if ((++m_tickCount & 0xf) == 0) {
SDRdaemonFECInput::MsgConfigureSDRdaemonStreamTiming* message = SDRdaemonFECInput::MsgConfigureSDRdaemonStreamTiming::create();
m_sampleSource->getInputMessageQueue()->push(message);
}
}
@@ -0,0 +1,148 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 INCLUDE_SDRDAEMONSOURCEGUI_H
#define INCLUDE_SDRDAEMONSOURCEGUI_H
#include <QTimer>
#include <sys/time.h>
#include "plugin/plugingui.h"
#include "sdrdaemonfecinput.h"
class DeviceSourceAPI;
class FileRecord;
namespace Ui {
class SDRdaemonFECGui;
}
class SDRdaemonFECGui : public QWidget, public PluginGUI {
Q_OBJECT
public:
explicit SDRdaemonFECGui(DeviceSourceAPI *deviceAPI, QWidget* parent = NULL);
virtual ~SDRdaemonFECGui();
void destroy();
void setName(const QString& name);
QString getName() const;
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual qint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
virtual bool handleMessage(const Message& message);
private:
Ui::SDRdaemonFECGui* ui;
DeviceSourceAPI* m_deviceAPI;
SDRdaemonFECSettings m_settings; //!< current settings
SDRdaemonFECSettings m_controlSettings; //!< settings last sent to device via control port
QTimer m_updateTimer;
QTimer m_statusTimer;
DeviceSampleSource* m_sampleSource;
bool m_acquisition;
FileRecord *m_fileSink; //!< File sink to record device I/Q output
int m_deviceSampleRate;
quint64 m_deviceCenterFrequency; //!< Center frequency in device
int m_lastEngineState;
int m_sampleRate;
quint64 m_centerFrequency;
struct timeval m_startingTimeStamp;
int m_framesDecodingStatus;
bool m_allBlocksReceived;
float m_bufferLengthInSecs;
int32_t m_bufferGauge;
int m_minNbBlocks;
int m_minNbOriginalBlocks;
int m_maxNbRecovery;
float m_avgNbBlocks;
float m_avgNbOriginalBlocks;
float m_avgNbRecovery;
int m_nbOriginalBlocks;
int m_nbFECBlocks;
int m_samplesCount;
std::size_t m_tickCount;
QString m_address;
QString m_remoteAddress;
quint16 m_dataPort;
quint16 m_controlPort;
bool m_addressEdited;
bool m_dataPortEdited;
bool m_initSendConfiguration;
int m_sender;
uint32_t m_countUnrecoverable;
uint32_t m_countRecovered;
QTime m_eventsTime;
bool m_doApplySettings;
bool m_forceSettings;
double m_txDelay;
bool m_dcBlock;
bool m_iqCorrection;
QPalette m_paletteGreenText;
QPalette m_paletteWhiteText;
void blockApplySettings(bool block);
void displaySettings();
void displayTime();
void sendControl(bool force = false);
void sendSettings();
void configureUDPLink();
void configureAutoCorrections();
void updateWithAcquisition();
void updateWithStreamData();
void updateWithStreamTime();
void updateSampleRateAndFrequency();
void updateTxDelay();
void displayEventCounts();
void displayEventTimer();
private slots:
void handleDSPMessages();
void handleSourceMessages();
void on_applyButton_clicked(bool checked);
void on_dcOffset_toggled(bool checked);
void on_iqImbalance_toggled(bool checked);
void on_address_returnPressed();
void on_dataPort_returnPressed();
void on_controlPort_returnPressed();
void on_sendButton_clicked(bool checked);
void on_freq_changed(quint64 value);
void on_sampleRate_changed(quint64 value);
void on_specificParms_returnPressed();
void on_decim_currentIndexChanged(int index);
void on_fcPos_currentIndexChanged(int index);
void on_startStop_toggled(bool checked);
void on_record_toggled(bool checked);
void on_eventCountsReset_clicked(bool checked);
void on_txDelay_valueChanged(int value);
void on_nbFECBlocks_valueChanged(int value);
void updateHardware();
void updateStatus();
void tick();
};
#endif // INCLUDE_SDRDAEMONSOURCEGUI_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 "sdrdaemonfecinput.h"
#include <string.h>
#include <errno.h>
#include <QDebug>
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include <device/devicesourceapi.h>
#include <dsp/filerecord.h>
#include "../sdrdaemonsource/sdrdaemonfecgui.h"
#include "../sdrdaemonsource/sdrdaemonfecudphandler.h"
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonFEC, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonAutoCorr, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonWork, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonAcquisition, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming, Message)
SDRdaemonFECInput::SDRdaemonFECInput(const QTimer& masterTimer, DeviceSourceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_address("127.0.0.1"),
m_port(9090),
m_SDRdaemonUDPHandler(0),
m_deviceDescription(),
m_sampleRate(0),
m_centerFrequency(0),
m_startingTimeStamp(0),
m_masterTimer(masterTimer),
m_autoFollowRate(false),
m_autoCorrBuffer(false)
{
m_sampleFifo.setSize(96000 * 4);
m_SDRdaemonUDPHandler = new SDRdaemonFECUDPHandler(&m_sampleFifo, getOutputMessageQueueToGUI(), m_deviceAPI);
m_SDRdaemonUDPHandler->connectTimer(&m_masterTimer);
}
SDRdaemonFECInput::~SDRdaemonFECInput()
{
stop();
delete m_SDRdaemonUDPHandler;
}
bool SDRdaemonFECInput::start()
{
qDebug() << "SDRdaemonInput::start";
MsgConfigureSDRdaemonWork *command = MsgConfigureSDRdaemonWork::create(true);
getInputMessageQueue()->push(command);
return true;
}
void SDRdaemonFECInput::stop()
{
qDebug() << "SDRdaemonInput::stop";
MsgConfigureSDRdaemonWork *command = MsgConfigureSDRdaemonWork::create(false);
getInputMessageQueue()->push(command);
}
const QString& SDRdaemonFECInput::getDeviceDescription() const
{
return m_deviceDescription;
}
int SDRdaemonFECInput::getSampleRate() const
{
return m_sampleRate;
}
quint64 SDRdaemonFECInput::getCenterFrequency() const
{
return m_centerFrequency;
}
std::time_t SDRdaemonFECInput::getStartingTimeStamp() const
{
return m_startingTimeStamp;
}
void SDRdaemonFECInput::getRemoteAddress(QString &s)
{
if (m_SDRdaemonUDPHandler) {
m_SDRdaemonUDPHandler->getRemoteAddress(s);
}
}
bool SDRdaemonFECInput::handleMessage(const Message& message)
{
if (MsgConfigureSDRdaemonFEC::match(message))
{
qDebug() << "SDRdaemonFECInput::handleMessage:" << message.getIdentifier();
//MsgConfigureSDRdaemonFEC& conf = (MsgConfigureSDRdaemonFEC&) message;
//applySettings(conf.getSettings(), conf.getForce());
return true;
}
else if (MsgConfigureSDRdaemonUDPLink::match(message))
{
MsgConfigureSDRdaemonUDPLink& conf = (MsgConfigureSDRdaemonUDPLink&) message;
m_SDRdaemonUDPHandler->configureUDPLink(conf.getAddress(), conf.getPort());
return true;
}
else if (MsgConfigureSDRdaemonAutoCorr::match(message))
{
MsgConfigureSDRdaemonAutoCorr& conf = (MsgConfigureSDRdaemonAutoCorr&) message;
bool dcBlock = conf.getDCBlock();
bool iqImbalance = conf.getIQImbalance();
m_deviceAPI->configureCorrections(dcBlock, iqImbalance);
return true;
}
else if (MsgConfigureSDRdaemonWork::match(message))
{
MsgConfigureSDRdaemonWork& conf = (MsgConfigureSDRdaemonWork&) message;
bool working = conf.isWorking();
if (working) {
m_SDRdaemonUDPHandler->start();
} else {
m_SDRdaemonUDPHandler->stop();
}
return true;
}
else if (MsgConfigureSDRdaemonStreamTiming::match(message))
{
return true;
}
else
{
return false;
}
}
@@ -0,0 +1,315 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 INCLUDE_SDRDAEMONSOURCEINPUT_H
#define INCLUDE_SDRDAEMONSOURCEINPUT_H
#include <dsp/devicesamplesource.h>
#include <QString>
#include <QTimer>
#include <ctime>
#include <iostream>
#include <stdint.h>
#include "sdrdaemonfecsettings.h"
class DeviceSourceAPI;
class SDRdaemonFECUDPHandler;
class SDRdaemonFECInput : public DeviceSampleSource {
public:
class MsgConfigureSDRdaemonFEC : public Message {
MESSAGE_CLASS_DECLARATION
public:
const SDRdaemonFECSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureSDRdaemonFEC* create(const SDRdaemonFECSettings& settings, bool force = false)
{
return new MsgConfigureSDRdaemonFEC(settings, force);
}
private:
SDRdaemonFECSettings m_settings;
bool m_force;
MsgConfigureSDRdaemonFEC(const SDRdaemonFECSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureSDRdaemonUDPLink : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getAddress() const { return m_address; }
quint16 getPort() const { return m_port; }
static MsgConfigureSDRdaemonUDPLink* create(const QString& address, quint16 port)
{
return new MsgConfigureSDRdaemonUDPLink(address, port);
}
private:
QString m_address;
quint16 m_port;
MsgConfigureSDRdaemonUDPLink(const QString& address, quint16 port) :
Message(),
m_address(address),
m_port(port)
{ }
};
class MsgConfigureSDRdaemonAutoCorr : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getDCBlock() const { return m_dcBlock; }
bool getIQImbalance() const { return m_iqCorrection; }
static MsgConfigureSDRdaemonAutoCorr* create(bool dcBlock, bool iqImbalance)
{
return new MsgConfigureSDRdaemonAutoCorr(dcBlock, iqImbalance);
}
private:
bool m_dcBlock;
bool m_iqCorrection;
MsgConfigureSDRdaemonAutoCorr(bool dcBlock, bool iqImbalance) :
Message(),
m_dcBlock(dcBlock),
m_iqCorrection(iqImbalance)
{ }
};
class MsgConfigureSDRdaemonWork : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool isWorking() const { return m_working; }
static MsgConfigureSDRdaemonWork* create(bool working)
{
return new MsgConfigureSDRdaemonWork(working);
}
private:
bool m_working;
MsgConfigureSDRdaemonWork(bool working) :
Message(),
m_working(working)
{ }
};
class MsgConfigureSDRdaemonStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureSDRdaemonStreamTiming* create()
{
return new MsgConfigureSDRdaemonStreamTiming();
}
private:
MsgConfigureSDRdaemonStreamTiming() :
Message()
{ }
};
class MsgReportSDRdaemonAcquisition : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getAcquisition() const { return m_acquisition; }
static MsgReportSDRdaemonAcquisition* create(bool acquisition)
{
return new MsgReportSDRdaemonAcquisition(acquisition);
}
protected:
bool m_acquisition;
MsgReportSDRdaemonAcquisition(bool acquisition) :
Message(),
m_acquisition(acquisition)
{ }
};
class MsgReportSDRdaemonFECStreamData : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
quint64 getCenterFrequency() const { return m_centerFrequency; }
uint32_t get_tv_sec() const { return m_tv_sec; }
uint32_t get_tv_usec() const { return m_tv_usec; }
static MsgReportSDRdaemonFECStreamData* create(int sampleRate, quint64 centerFrequency, uint32_t tv_sec, uint32_t tv_usec)
{
return new MsgReportSDRdaemonFECStreamData(sampleRate, centerFrequency, tv_sec, tv_usec);
}
protected:
int m_sampleRate;
quint64 m_centerFrequency;
uint32_t m_tv_sec;
uint32_t m_tv_usec;
MsgReportSDRdaemonFECStreamData(int sampleRate, quint64 centerFrequency, uint32_t tv_sec, uint32_t tv_usec) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency),
m_tv_sec(tv_sec),
m_tv_usec(tv_usec)
{ }
};
class MsgReportSDRdaemonFECStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
uint32_t get_tv_sec() const { return m_tv_sec; }
uint32_t get_tv_usec() const { return m_tv_usec; }
int getFramesDecodingStatus() const { return m_framesDecodingStatus; }
bool allBlocksReceived() const { return m_allBlocksReceived; }
float getBufferLengthInSecs() const { return m_bufferLenSec; }
int32_t getBufferGauge() const { return m_bufferGauge; }
int getMinNbBlocks() const { return m_minNbBlocks; }
int getMinNbOriginalBlocks() const { return m_minNbOriginalBlocks; }
int getMaxNbRecovery() const { return m_maxNbRecovery; }
float getAvgNbBlocks() const { return m_avgNbBlocks; }
float getAvgNbOriginalBlocks() const { return m_avgNbOriginalBlocks; }
float getAvgNbRecovery() const { return m_avgNbRecovery; }
int getNbOriginalBlocksPerFrame() const { return m_nbOriginalBlocksPerFrame; }
int getNbFECBlocksPerFrame() const { return m_nbFECBlocksPerFrame; }
static MsgReportSDRdaemonFECStreamTiming* create(uint32_t tv_sec,
uint32_t tv_usec,
float bufferLenSec,
int32_t bufferGauge,
int framesDecodingStatus,
bool allBlocksReceived,
int minNbBlocks,
int minNbOriginalBlocks,
int maxNbRecovery,
float avgNbBlocks,
float avgNbOriginalBlocks,
float avgNbRecovery,
int nbOriginalBlocksPerFrame,
int nbFECBlocksPerFrame)
{
return new MsgReportSDRdaemonFECStreamTiming(tv_sec,
tv_usec,
bufferLenSec,
bufferGauge,
framesDecodingStatus,
allBlocksReceived,
minNbBlocks,
minNbOriginalBlocks,
maxNbRecovery,
avgNbBlocks,
avgNbOriginalBlocks,
avgNbRecovery,
nbOriginalBlocksPerFrame,
nbFECBlocksPerFrame);
}
protected:
uint32_t m_tv_sec;
uint32_t m_tv_usec;
int m_framesDecodingStatus;
bool m_allBlocksReceived;
float m_bufferLenSec;
int32_t m_bufferGauge;
int m_minNbBlocks;
int m_minNbOriginalBlocks;
int m_maxNbRecovery;
float m_avgNbBlocks;
float m_avgNbOriginalBlocks;
float m_avgNbRecovery;
int m_nbOriginalBlocksPerFrame;
int m_nbFECBlocksPerFrame;
MsgReportSDRdaemonFECStreamTiming(uint32_t tv_sec,
uint32_t tv_usec,
float bufferLenSec,
int32_t bufferGauge,
int framesDecodingStatus,
bool allBlocksReceived,
int minNbBlocks,
int minNbOriginalBlocks,
int maxNbRecovery,
float avgNbBlocks,
float avgNbOriginalBlocks,
float avgNbRecovery,
int nbOriginalBlocksPerFrame,
int nbFECBlocksPerFrame) :
Message(),
m_tv_sec(tv_sec),
m_tv_usec(tv_usec),
m_framesDecodingStatus(framesDecodingStatus),
m_allBlocksReceived(allBlocksReceived),
m_bufferLenSec(bufferLenSec),
m_bufferGauge(bufferGauge),
m_minNbBlocks(minNbBlocks),
m_minNbOriginalBlocks(minNbOriginalBlocks),
m_maxNbRecovery(maxNbRecovery),
m_avgNbBlocks(avgNbBlocks),
m_avgNbOriginalBlocks(avgNbOriginalBlocks),
m_avgNbRecovery(avgNbRecovery),
m_nbOriginalBlocksPerFrame(nbOriginalBlocksPerFrame),
m_nbFECBlocksPerFrame(nbFECBlocksPerFrame)
{ }
};
SDRdaemonFECInput(const QTimer& masterTimer, DeviceSourceAPI *deviceAPI);
virtual ~SDRdaemonFECInput();
virtual bool start();
virtual void stop();
virtual const QString& getDeviceDescription() const;
virtual int getSampleRate() const;
virtual quint64 getCenterFrequency() const;
std::time_t getStartingTimeStamp() const;
void getRemoteAddress(QString &s);
virtual bool handleMessage(const Message& message);
private:
DeviceSourceAPI *m_deviceAPI;
QMutex m_mutex;
QString m_address;
quint16 m_port;
SDRdaemonFECUDPHandler* m_SDRdaemonUDPHandler;
QString m_deviceDescription;
int m_sampleRate;
quint64 m_centerFrequency;
std::time_t m_startingTimeStamp;
const QTimer& m_masterTimer;
bool m_autoFollowRate;
bool m_autoCorrBuffer;
};
#endif // INCLUDE_SDRDAEMONSOURCEINPUT_H
@@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 "sdrdaemonfecplugin.h"
#include <QtPlugin>
#include <QAction>
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include <device/devicesourceapi.h>
#include "sdrdaemonfecgui.h"
const PluginDescriptor SDRdaemonFECPlugin::m_pluginDescriptor = {
QString("SDRdaemon with FEC input"),
QString("3.5.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
const QString SDRdaemonFECPlugin::m_hardwareID = "SDRdaemonFEC";
const QString SDRdaemonFECPlugin::m_deviceTypeID = SDRDAEMONFEC_DEVICE_TYPE_ID;
SDRdaemonFECPlugin::SDRdaemonFECPlugin(QObject* parent) :
QObject(parent)
{
}
const PluginDescriptor& SDRdaemonFECPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void SDRdaemonFECPlugin::initPlugin(PluginAPI* pluginAPI)
{
pluginAPI->registerSampleSource(m_deviceTypeID, this);
}
PluginInterface::SamplingDevices SDRdaemonFECPlugin::enumSampleSources()
{
SamplingDevices result;
int count = 1;
for(int i = 0; i < count; i++)
{
QString displayedName(QString("SDRdaemonFEC[%1]").arg(i));
result.append(SamplingDevice(displayedName,
m_hardwareID,
m_deviceTypeID,
QString::null,
i));
}
return result;
}
PluginGUI* SDRdaemonFECPlugin::createSampleSourcePluginGUI(const QString& sourceId, QWidget **widget, DeviceSourceAPI *deviceAPI)
{
if(sourceId == m_deviceTypeID)
{
SDRdaemonFECGui* gui = new SDRdaemonFECGui(deviceAPI);
*widget = gui;
return gui;
}
else
{
return NULL;
}
}
@@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 INCLUDE_SDRDAEMONSOURCEPLUGIN_H
#define INCLUDE_SDRDAEMONSOURCEPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
#define SDRDAEMONFEC_DEVICE_TYPE_ID "sdrangel.samplesource.sdrdaemonfec"
class PluginAPI;
class SDRdaemonFECPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID SDRDAEMONFEC_DEVICE_TYPE_ID)
public:
explicit SDRdaemonFECPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual SamplingDevices enumSampleSources();
virtual PluginGUI* createSampleSourcePluginGUI(const QString& sourceId, QWidget **widget, DeviceSourceAPI *deviceAPI);
static const QString m_hardwareID;
static const QString m_deviceTypeID;
private:
static const PluginDescriptor m_pluginDescriptor;
};
#endif // INCLUDE_SDRDAEMONSOURCEPLUGIN_H
@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "sdrdaemonfecsettings.h"
#include "util/simpleserializer.h"
SDRdaemonFECSettings::SDRdaemonFECSettings()
{
resetToDefaults();
}
void SDRdaemonFECSettings::resetToDefaults()
{
m_centerFrequency = 435000*1000;
m_sampleRate = 256000;
m_log2Decim = 4;
m_txDelay = 0.5;
m_nbFECBlocks = 0;
m_address = "127.0.0.1";
m_dataPort = 9092;
m_controlPort = 9093;
m_specificParameters = "";
m_dcBlock = false;
m_iqCorrection = false;
m_fcPos = 2; // center
}
QByteArray SDRdaemonFECSettings::serialize() const
{
SimpleSerializer s(1);
s.writeU64(1, m_sampleRate);
s.writeU32(2, m_log2Decim);
s.writeFloat(3, m_txDelay);
s.writeU32(4, m_nbFECBlocks);
s.writeString(5, m_address);
s.writeU32(6, m_dataPort);
s.writeU32(7, m_controlPort);
s.writeString(8, m_specificParameters);
s.writeBool(9, m_dcBlock);
s.writeBool(10, m_iqCorrection);
s.writeU32(11, m_fcPos);
return s.final();
}
bool SDRdaemonFECSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
quint32 uintval;
d.readU64(1, &m_sampleRate, 48000);
d.readU32(2, &m_log2Decim, 0);
d.readFloat(3, &m_txDelay, 0.5);
d.readU32(4, &m_nbFECBlocks, 0);
d.readString(5, &m_address, "127.0.0.1");
d.readU32(6, &uintval, 9090);
m_dataPort = uintval % (1<<16);
d.readU32(7, &uintval, 9090);
m_controlPort = uintval % (1<<16);
d.readString(8, &m_specificParameters, "");
d.readBool(9, &m_dcBlock, false);
d.readBool(10, &m_iqCorrection, false);
d.readU32(11, &m_fcPos, 2);
return true;
}
else
{
resetToDefaults();
return false;
}
}
@@ -0,0 +1,43 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONSOURCESETTINGS_H_
#define PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONSOURCESETTINGS_H_
#include <QByteArray>
#include <QString>
struct SDRdaemonFECSettings {
quint64 m_centerFrequency;
quint64 m_sampleRate;
quint32 m_log2Decim;
float m_txDelay;
quint32 m_nbFECBlocks;
QString m_address;
quint16 m_dataPort;
quint16 m_controlPort;
QString m_specificParameters;
bool m_dcBlock;
bool m_iqCorrection;
quint32 m_fcPos;
SDRdaemonFECSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONSOURCESETTINGS_H_ */
@@ -0,0 +1,263 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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 "sdrdaemonfecudphandler.h"
#include <QUdpSocket>
#include <QDebug>
#include <QTimer>
#include <unistd.h>
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include <device/devicesourceapi.h>
#include "../sdrdaemonsource/sdrdaemonfecinput.h"
SDRdaemonFECUDPHandler::SDRdaemonFECUDPHandler(SampleSinkFifo *sampleFifo, MessageQueue *outputMessageQueueToGUI, DeviceSourceAPI *devieAPI) :
m_deviceAPI(devieAPI),
m_sdrDaemonBuffer(m_rateDivider),
m_dataSocket(0),
m_dataAddress(QHostAddress::LocalHost),
m_remoteAddress(QHostAddress::LocalHost),
m_dataPort(9090),
m_dataConnected(false),
m_udpBuf(0),
m_udpReadBytes(0),
m_sampleFifo(sampleFifo),
m_samplerate(0),
m_centerFrequency(0),
m_tv_sec(0),
m_tv_usec(0),
m_outputMessageQueueToGUI(outputMessageQueueToGUI),
m_tickCount(0),
m_samplesCount(0),
m_timer(0),
m_throttlems(SDRDAEMONFEC_THROTTLE_MS),
m_readLengthSamples(0),
m_readLength(0),
m_throttleToggle(false),
m_rateDivider(1000/SDRDAEMONFEC_THROTTLE_MS),
m_autoCorrBuffer(true)
{
m_udpBuf = new char[SDRdaemonFECBuffer::m_udpPayloadSize];
}
SDRdaemonFECUDPHandler::~SDRdaemonFECUDPHandler()
{
stop();
delete[] m_udpBuf;
#ifdef USE_INTERNAL_TIMER
if (m_timer) {
delete m_timer;
}
#endif
}
void SDRdaemonFECUDPHandler::start()
{
qDebug("SDRdaemonFECUDPHandler::start");
if (!m_dataSocket)
{
m_dataSocket = new QUdpSocket(this);
}
if (!m_dataConnected)
{
connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection
if (m_dataSocket->bind(m_dataAddress, m_dataPort))
{
qDebug("SDRdaemonFECUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort);
m_dataConnected = true;
}
else
{
qWarning("SDRdaemonFECUDPHandler::start: cannot bind data port %d", m_dataPort);
disconnect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
m_dataConnected = false;
}
}
// Need to notify the DSP engine to actually start
DSPSignalNotification *notif = new DSPSignalNotification(m_samplerate, m_centerFrequency * 1000); // Frequency in Hz for the DSP engine
m_deviceAPI->getDeviceInputMessageQueue()->push(notif);
m_elapsedTimer.start();
}
void SDRdaemonFECUDPHandler::stop()
{
qDebug("SDRdaemonFECUDPHandler::stop");
if (m_dataConnected)
{
m_dataConnected = false;
disconnect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
}
if (m_dataSocket)
{
delete m_dataSocket;
m_dataSocket = 0;
}
}
void SDRdaemonFECUDPHandler::configureUDPLink(const QString& address, quint16 port)
{
qDebug("SDRdaemonFECUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port);
bool addressOK = m_dataAddress.setAddress(address);
if (!addressOK)
{
qWarning("SDRdaemonFECUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str());
m_dataAddress = QHostAddress::LocalHost;
}
stop();
m_dataPort = port;
start();
}
void SDRdaemonFECUDPHandler::dataReadyRead()
{
m_udpReadBytes = 0;
while (m_dataSocket->hasPendingDatagrams() && m_dataConnected)
{
qint64 pendingDataSize = m_dataSocket->pendingDatagramSize();
m_udpReadBytes += m_dataSocket->readDatagram(&m_udpBuf[m_udpReadBytes], pendingDataSize, &m_remoteAddress, 0);
if (m_udpReadBytes == SDRdaemonFECBuffer::m_udpPayloadSize) {
processData();
m_udpReadBytes = 0;
}
}
}
void SDRdaemonFECUDPHandler::processData()
{
m_sdrDaemonBuffer.writeData(m_udpBuf);
const SDRdaemonFECBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta();
bool change = false;
// m_tv_sec = metaData.m_tv_sec;
// m_tv_usec = metaData.m_tv_usec;
m_tv_sec = m_sdrDaemonBuffer.getTVOutSec();
m_tv_usec = m_sdrDaemonBuffer.getTVOutUsec();
if (m_centerFrequency != metaData.m_centerFrequency)
{
m_centerFrequency = metaData.m_centerFrequency;
change = true;
}
if (m_samplerate != metaData.m_sampleRate)
{
m_samplerate = metaData.m_sampleRate;
change = true;
}
if (change)
{
DSPSignalNotification *notif = new DSPSignalNotification(m_samplerate, m_centerFrequency * 1000); // Frequency in Hz for the DSP engine
m_deviceAPI->getDeviceInputMessageQueue()->push(notif);
SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData *report = SDRdaemonFECInput::MsgReportSDRdaemonFECStreamData::create(
m_samplerate,
m_centerFrequency * 1000, // Frequency in Hz for the GUI
m_tv_sec,
m_tv_usec);
m_outputMessageQueueToGUI->push(report);
}
}
void SDRdaemonFECUDPHandler::connectTimer(const QTimer* timer)
{
qDebug() << "SDRdaemonFECUDPHandler::connectTimer";
#ifdef USE_INTERNAL_TIMER
#warning "Uses internal timer"
m_timer = new QTimer();
m_timer->start(50);
m_throttlems = m_timer->interval();
connect(m_timer, SIGNAL(timeout()), this, SLOT(tick()));
#else
m_throttlems = timer->interval();
connect(timer, SIGNAL(timeout()), this, SLOT(tick()));
#endif
m_rateDivider = 1000 / m_throttlems;
}
void SDRdaemonFECUDPHandler::tick()
{
// auto throttling
int throttlems = m_elapsedTimer.restart();
if (throttlems != m_throttlems)
{
m_throttlems = throttlems;
m_readLengthSamples = (m_sdrDaemonBuffer.getCurrentMeta().m_sampleRate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000;
m_throttleToggle = !m_throttleToggle;
}
if (m_autoCorrBuffer) {
m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection();
}
m_readLength = m_readLengthSamples * SDRdaemonFECBuffer::m_iqSampleSize;
// read samples directly feeding the SampleFifo (no callback)
m_sampleFifo->write(reinterpret_cast<quint8*>(m_sdrDaemonBuffer.readData(m_readLength)), m_readLength);
m_samplesCount += m_readLengthSamples;
if (m_tickCount < m_rateDivider)
{
m_tickCount++;
}
else
{
int framesDecodingStatus;
int minNbBlocks = m_sdrDaemonBuffer.getMinNbBlocks();
int minNbOriginalBlocks = m_sdrDaemonBuffer.getMinOriginalBlocks();
int nbOriginalBlocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbOriginalBlocks;
int nbFECblocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbFECBlocks;
m_tickCount = 0;
//framesDecodingStatus = (minNbOriginalBlocks == nbOriginalBlocks ? 2 : (minNbOriginalBlocks < nbOriginalBlocks - nbFECblocks ? 0 : 1));
if (minNbBlocks < nbOriginalBlocks) {
framesDecodingStatus = 0;
} else if (minNbBlocks < nbOriginalBlocks + nbFECblocks) {
framesDecodingStatus = 1;
} else {
framesDecodingStatus = 2;
}
SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming *report = SDRdaemonFECInput::MsgReportSDRdaemonFECStreamTiming::create(
m_tv_sec,
m_tv_usec,
m_sdrDaemonBuffer.getBufferLengthInSecs(),
m_sdrDaemonBuffer.getBufferGauge(),
framesDecodingStatus,
minNbBlocks == nbOriginalBlocks + nbFECblocks,
minNbBlocks,
minNbOriginalBlocks,
m_sdrDaemonBuffer.getMaxNbRecovery(),
m_sdrDaemonBuffer.getAvgNbBlocks(),
m_sdrDaemonBuffer.getAvgOriginalBlocks(),
m_sdrDaemonBuffer.getAvgNbRecovery(),
nbOriginalBlocks,
nbFECblocks);
m_outputMessageQueueToGUI->push(report);
}
}
@@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 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_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECUDPHANDLER_H_
#define PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECUDPHANDLER_H_
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QMutex>
#include <QElapsedTimer>
#include "sdrdaemonfecbuffer.h"
#define SDRDAEMONFEC_THROTTLE_MS 50
class SampleSinkFifo;
class MessageQueue;
class QTimer;
class DeviceSourceAPI;
class SDRdaemonFECUDPHandler : public QObject
{
Q_OBJECT
public:
SDRdaemonFECUDPHandler(SampleSinkFifo* sampleFifo, MessageQueue *outputMessageQueueToGUI, DeviceSourceAPI *deviceAPI);
~SDRdaemonFECUDPHandler();
void connectTimer(const QTimer* timer);
void start();
void stop();
void configureUDPLink(const QString& address, quint16 port);
void getRemoteAddress(QString& s) const { s = m_remoteAddress.toString(); }
int getNbOriginalBlocks() const { return SDRdaemonFECBuffer::m_nbOriginalBlocks; }
public slots:
void dataReadyRead();
private:
DeviceSourceAPI *m_deviceAPI;
SDRdaemonFECBuffer m_sdrDaemonBuffer;
QUdpSocket *m_dataSocket;
QHostAddress m_dataAddress;
QHostAddress m_remoteAddress;
quint16 m_dataPort;
bool m_dataConnected;
char *m_udpBuf;
qint64 m_udpReadBytes;
SampleSinkFifo *m_sampleFifo;
uint32_t m_samplerate;
uint32_t m_centerFrequency;
uint32_t m_tv_sec;
uint32_t m_tv_usec;
MessageQueue *m_outputMessageQueueToGUI;
uint32_t m_tickCount;
std::size_t m_samplesCount;
QTimer *m_timer;
QElapsedTimer m_elapsedTimer;
int m_throttlems;
uint32_t m_readLengthSamples;
uint32_t m_readLength;
bool m_throttleToggle;
uint32_t m_rateDivider;
bool m_autoCorrBuffer;
void processData();
private slots:
void tick();
};
#endif /* PLUGINS_SAMPLESOURCE_SDRDAEMONSOURCE_SDRDAEMONFECUDPHANDLER_H_ */