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:
@@ -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>
|
||||
|
||||

|
||||
|
||||
<h3>1: Common stream parameters</h3>
|
||||
|
||||

|
||||
|
||||
<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>
|
||||
|
||||

|
||||
|
||||
<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>
|
||||
|
||||

|
||||
|
||||
<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>
|
||||
|
||||

|
||||
|
||||
<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_ */
|
||||
Reference in New Issue
Block a user