SDRdaemonFEC support: added plugin as copy of SDRdaemon plugin

This commit is contained in:
f4exb 2016-06-19 09:56:49 +02:00
parent 83d57032fe
commit 4386e0fbab
15 changed files with 3745 additions and 0 deletions

View File

@ -49,4 +49,5 @@ endif(LIBUSB_FOUND AND LIBHACKRF_FOUND)
add_subdirectory(filesource)
add_subdirectory(sdrdaemon)
add_subdirectory(sdrdaemonfec)

View File

@ -0,0 +1,59 @@
project(sdrdaemonfec)
find_package(LZ4)
find_package(LibNANOMSG)
set(sdrdaemonfec_SOURCES
sdrdaemonfecbuffer.cpp
sdrdaemonfecgui.cpp
sdrdaemonfecinput.cpp
sdrdaemonfecplugin.cpp
sdrdaemonfecudphandler.cpp
)
set(sdrdaemonfec_HEADERS
sdrdaemonfecbuffer.h
sdrdaemonfecgui.h
sdrdaemonfecinput.h
sdrdaemonfecplugin.h
sdrdaemonfecudphandler.h
)
set(sdrdaemonfec_FORMS
sdrdaemonfecgui.ui
)
include_directories(
.
${CMAKE_CURRENT_BINARY_DIR}
)
#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}
)
target_include_directories(inputsdrdaemonfec PUBLIC
${LZ4_INCLUDE_DIRS}
${LIBNANOMSG_INCLUDE_DIR}
)
target_link_libraries(inputsdrdaemonfec
${QT_LIBRARIES}
${LZ4_LIBRARIES}
${LIBNANOMSG_LIBRARIES}
sdrbase
)
qt5_use_modules(inputsdrdaemonfec Core Widgets OpenGL Multimedia)
install(TARGETS inputsdrdaemonfec DESTINATION lib/plugins/samplesource)

View File

@ -0,0 +1,152 @@
<h1>SDRdaemon 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.
<h2>Interface</h2>
![SDR Daemon plugin GUI](/doc/img/SDRdaemon_plugin.png)
<h3>1: Frequency</h3>
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.
<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. This may not and is usually not the timestamp of the samples currently shown in the displays and in the audio since there is a failty large buffer in place to damper the variations of data receiving speed due to the network (see: 5.5: Main buffer length in seconds).
<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.
- 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.
<h3>4: Lock and sizes</h3>
![SDR Daemon status1 GUI](/doc/img/SDRdaemon_plugin_04.png)
<h4>4.1: Stream lock</h4>
Turns green when stream is locked to meta data. Meta data carries the number of UDP packets in following frame. Lock status is obtained when the correct number of UDP packets have been received when the next meta data block occurs.
<h4>4.2: Frame size</h4>
Data is sent in frames with one meta data header. This is the size of such a frame. Frames may be compressed however the frame will be copied uncompressed to the main buffer.
<h4>4.3: Sample rate as sent in the meta data</h4>
The receiver sends the stream data rate with this nominal value
<h4>4.4: Actual stream sample rate</h4>
When the auto follow sample rate is engaged the actual system sample rate may differ from the nominal sample rate sent in the meta data. This is the actual system sample rate.
<h4>4.5: Skew rate</h4>
This is the difference in percent between nominal sample rate sent in the meta data and actual system sample rate
<h4>4.6: 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: Compressed stream status and auto follow control buttons</h3>
![SDR Daemon status2 GUI](/doc/img/SDRdaemon_plugin_05.png)
<h4>5.1: Compressed stream</h4>
When lit in green it means the stream is compressed using LZ4.
<h4>5.2: Compression ratio</h4>
This is the ratio between the compressed data size and the original data size.
<h4>5.3: CRC OK ratio</h4>
This is the percentage of received data blocks with a correct CRC. It should be as closed to 100% as possible.
<h4>5.4: LZ4 successful decodes ratio</h4>
This is the percentage of LZ4 data blocks that were successfully uncompressed. It should be as closed to 100% as possible.
<h4>5.5: Main buffer length in seconds</h4>
This is the main buffer (writes from UDP / reads from DSP engine) length in units of time (seconds). Initially the write pointer is at the start of buffer and the read pointer is on the middle. Thus it takes half a buffer length in time to get the first useful sample. The minimum length is 8s and can be as long as to fit 50 average read chunks.
<h4>5.6: Reset buffer indexes push button</h4>
This forces the write and read pointers in their initial position regardless of the contents of the buffer. The write pointer position is set at the start of buffer and the read pointer position is set at the middle.
<h4>5.7: Auto lock main buffer R/W pointers position toggle</h4>
When set it engages the auto lock of R/W pointers position. It will try to maintain a half buffer distance between read and write pointers.
<h4>5.8: Auto lock to actual stream sample rate</h4>
This is rarely necessary. Only use it when you suspect that the sender data sample rate is not exactly as advertised in the meta data. This is normally exclusive of the auto lock R/W pointers position however the GUI allows both. You are advised to chose only one of the two.
<h3>6: Network parameters</h3>
![SDR Daemon status3 GUI](/doc/img/SDRdaemon_plugin_06.png)
<h4>6.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>6.2: Local data port</h4>
UDP port on the local (your) machine to which the SDRdaemon server sends samples to.
<h4>6.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>6.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>7: Configuration parameters</h3>
![SDR Daemon status4 GUI](/doc/img/SDRdaemon_plugin_07.png)
<h4>7.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>7.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>7.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>7.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>7.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>7.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.

View File

@ -0,0 +1,45 @@
#--------------------------------------------------------
#
# 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"
INCLUDEPATH += $$PWD
INCLUDEPATH += ../../../sdrbase
INCLUDEPATH += ../../../lz4
INCLUDEPATH += $$LIBNANOMSGSRC/src
CONFIG(Release):build_subdir = release
CONFIG(Debug):build_subdir = debug
SOURCES += sdrdaemonfecbuffer.cpp\
sdrdaemonfecgui.cpp\
sdrdaemonfecinput.cpp\
sdrdaemonfecplugin.cpp\
sdrdaemonfecudphandler.cpp
HEADERS += sdrdaemonfecbuffer.h\
sdrdaemonfecgui.h\
sdrdaemonfecinput.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
RESOURCES = ../../../sdrbase/resources/res.qrc
CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1

View File

@ -0,0 +1,453 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QDebug>
#include <cassert>
#include <cstring>
#include <cmath>
#include <lz4.h>
#include "sdrdaemonfecbuffer.h"
const int SDRdaemonFECBuffer::m_udpPayloadSize = 512;
const int SDRdaemonFECBuffer::m_sampleSize = 2;
const int SDRdaemonFECBuffer::m_iqSampleSize = 2 * m_sampleSize;
const int SDRdaemonFECBuffer::m_rawBufferLengthSeconds = 8; // should be even
const int SDRdaemonFECBuffer::m_rawBufferMinNbFrames = 50;
SDRdaemonFECBuffer::SDRdaemonFECBuffer(uint32_t throttlems) :
m_throttlemsNominal(throttlems),
m_rawSize(0),
m_rawBuffer(0),
m_sampleRateStream(0),
m_sampleRate(0),
m_sampleBytes(2),
m_sampleBits(12),
m_sync(false),
m_syncLock(false),
m_lz4(false),
m_nbBlocks(0),
m_bytesInBlock(0),
m_dataCRC(0),
m_inCount(0),
m_lz4InCount(0),
m_lz4InSize(0),
m_lz4InBuffer(0),
m_lz4OutBuffer(0),
m_frameSize(0),
m_bufferLenSec(0.0),
m_nbLz4Decodes(0),
m_nbLz4SuccessfulDecodes(0),
m_nbLz4CRCOK(0),
m_nbLastLz4SuccessfulDecodes(0),
m_nbLastLz4CRCOK(0),
m_writeIndex(0),
m_readIndex(0),
m_readSize(0),
m_readBuffer(0),
m_autoFollowRate(false),
m_autoCorrBuffer(false),
m_skewTest(false),
m_skewCorrection(false),
m_resetIndexes(false),
m_readCount(0),
m_writeCount(0),
m_nbCycles(0),
m_nbReads(0),
m_balCorrection(0),
m_balCorrLimit(0)
{
m_currentMeta.init();
}
SDRdaemonFECBuffer::~SDRdaemonFECBuffer()
{
if (m_rawBuffer) {
delete[] m_rawBuffer;
}
if (m_lz4InBuffer) {
delete[] m_lz4InBuffer;
}
if (m_lz4OutBuffer) {
delete[] m_lz4OutBuffer;
}
if (m_readBuffer) {
delete[] m_readBuffer;
}
}
void SDRdaemonFECBuffer::updateBufferSize(uint32_t sampleRate)
{
uint32_t rawSize = sampleRate * m_iqSampleSize * m_rawBufferLengthSeconds; // store worth of this seconds of samples at this sample rate
if ((m_frameSize > 0) && (rawSize / m_frameSize < m_rawBufferMinNbFrames))
{
rawSize = m_rawBufferMinNbFrames * m_frameSize; // ensure a minimal size of this times the write block size so that auto follow ups work fine
}
if (rawSize != m_rawSize)
{
m_rawSize = rawSize;
m_balCorrLimit = sampleRate / 50; // +/- 20 ms correction max per read
m_bufferLenSec = m_rawSize / (sampleRate * m_iqSampleSize);
if (m_rawBuffer) {
delete[] m_rawBuffer;
}
m_rawBuffer = new uint8_t[m_rawSize];
resetIndexes();
qDebug() << "SDRdaemonBuffer::updateBufferSize:"
<< " sampleRate: " << sampleRate
<< " m_frameSize: " << m_frameSize
<< " m_rawSize: " << m_rawSize;
}
}
void SDRdaemonFECBuffer::updateLZ4Sizes(uint32_t frameSize)
{
uint32_t maxInputSize = LZ4_compressBound(frameSize);
if (m_lz4InBuffer) {
delete[] m_lz4InBuffer;
}
m_lz4InBuffer = new uint8_t[maxInputSize];
if (m_lz4OutBuffer) {
delete[] m_lz4OutBuffer;
}
m_lz4OutBuffer = new uint8_t[frameSize];
}
void SDRdaemonFECBuffer::updateReadBufferSize(uint32_t length)
{
if (m_readBuffer) {
delete[] m_readBuffer;
}
m_readBuffer = new uint8_t[length];
}
bool SDRdaemonFECBuffer::readMeta(char *array, uint32_t length)
{
assert(length >= sizeof(MetaData) + 8);
MetaData *metaData = (MetaData *) array;
if (m_crc64.calculate_crc((uint8_t *) array, sizeof(MetaData) - 8) == metaData->m_crc)
{
// sync condition:
if (m_currentMeta.m_blockSize > 0)
{
uint32_t nbBlocks = m_currentMeta.m_nbBytes / m_currentMeta.m_blockSize;
m_syncLock = nbBlocks + (m_lz4 ? 2 : 1) == m_nbBlocks;
//qDebug("SDRdaemonBuffer::readMeta: m_nbBlocks: %d:%d %s", nbBlocks, m_nbBlocks, (m_syncLock ? "locked" : "unlocked"));
}
else
{
m_syncLock = false;
}
memcpy((void *) &m_dataCRC, (const void *) &array[sizeof(MetaData)], 8);
m_nbBlocks = 0;
m_inCount = 0;
if (!m_lz4 && !(m_currentMeta == *metaData))
{
printMeta(QString("SDRdaemonBuffer::readMeta"), metaData);
}
m_currentMeta = *metaData;
// sanity checks
if (metaData->m_blockSize == m_udpPayloadSize) // sent blocksize matches given blocksize
{
m_sampleBytes = metaData->m_sampleBytes & 0x0F;
uint32_t frameSize = m_iqSampleSize * metaData->m_nbSamples * metaData->m_nbBlocks;
int sampleRate = metaData->m_sampleRate;
if (sampleRate != m_sampleRateStream) // change of nominal stream sample rate
{
updateBufferSize(sampleRate);
m_sampleRateStream = sampleRate;
m_sampleRate = sampleRate;
}
// auto skew rate compensation
if (m_autoFollowRate)
{
if (m_skewCorrection)
{
int64_t deltaRate = (m_writeCount - m_readCount) / (m_nbCycles * m_rawBufferLengthSeconds * m_iqSampleSize);
m_sampleRate = ((m_sampleRate + deltaRate) / m_iqSampleSize) * m_iqSampleSize; // ensure it is a multiple of the I/Q sample size
resetIndexes();
}
}
else
{
m_sampleRate = sampleRate;
}
// Reset indexes if requested
if (m_resetIndexes)
{
resetIndexes();
m_resetIndexes = false;
}
if (metaData->m_sampleBytes & 0x10)
{
m_lz4 = true;
m_lz4InSize = metaData->m_nbBytes; // compressed input size
m_lz4InCount = 0;
if (frameSize != m_frameSize)
{
updateLZ4Sizes(frameSize);
}
}
else
{
m_lz4 = false;
}
if (frameSize != m_frameSize) {
m_frameSize = frameSize;
updateBufferSize(m_sampleRate);
}
m_sync = true;
}
else
{
m_sync = false;
}
return m_sync;
}
else
{
return false;
}
}
void SDRdaemonFECBuffer::writeData(char *array, uint32_t length)
{
if ((m_sync) && (m_nbBlocks > 0))
{
if (m_lz4)
{
writeDataLZ4(array, length);
}
else
{
writeToRawBufferUncompressed(array, length);
}
}
}
uint8_t *SDRdaemonFECBuffer::readData(int32_t length)
{
// auto compensation calculations
if (m_skewTest && ((m_readIndex + length) > (m_rawSize / 2)))
{
// auto follow sample rate calculation
int dIndex = (m_readIndex - m_writeIndex > 0 ? m_readIndex - m_writeIndex : m_writeIndex - m_readIndex); // absolute delta
m_skewCorrection = (dIndex < m_rawSize / 10); // close by 10%
m_nbCycles++;
// auto R/W balance calculation
if ((m_nbReads > 5*m_rawBufferLengthSeconds) && m_autoCorrBuffer)
{
int32_t dBytes;
int32_t dI = (m_rawSize / 2) - m_readIndex; // delta of read index to the middle of buffer (positive)
if (m_readIndex > m_writeIndex) { // write leads
dBytes = m_writeIndex + dI; // positive from start of buffer + delta read index
} else { // read leads
dBytes = m_writeIndex - (int32_t) m_rawSize + dI; // negative from end of buffer minus delta read index
}
m_balCorrection = (m_balCorrection / 4) + ((int32_t) dBytes / (int32_t) (m_nbReads * m_iqSampleSize)); // 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;
}
}
else
{
m_balCorrection = 0;
}
m_nbReads = 0;
// un-arm
m_skewTest = false;
}
m_readCount += length;
m_nbReads++;
if (m_readIndex + length < m_rawSize)
{
uint32_t readIndex = m_readIndex;
m_readIndex += length;
return &m_rawBuffer[readIndex];
}
else if (m_readIndex + length == m_rawSize)
{
uint32_t readIndex = m_readIndex;
m_readIndex = 0;
m_skewTest = true; // re-arm
return &m_rawBuffer[readIndex];
}
else
{
if (length > m_readSize)
{
updateReadBufferSize(length);
m_readSize = length;
}
std::memcpy((void *) m_readBuffer, (const void *) &m_rawBuffer[m_readIndex], m_rawSize - m_readIndex);
length -= m_rawSize - m_readIndex;
std::memcpy((void *) &m_readBuffer[m_rawSize - m_readIndex], (const void *) m_rawBuffer, length);
m_readIndex = length;
m_skewTest = true; // re-arm
return m_readBuffer;
}
}
void SDRdaemonFECBuffer::writeDataLZ4(const char *array, uint32_t length)
{
if (m_lz4InCount + length < m_lz4InSize)
{
std::memcpy((void *) &m_lz4InBuffer[m_lz4InCount], (const void *) array, length);
m_lz4InCount += length;
}
else
{
std::memcpy((void *) &m_lz4InBuffer[m_lz4InCount], (const void *) array, m_lz4InSize - m_lz4InCount); // copy rest of data in compressed Buffer
m_lz4InCount += length;
}
if (m_lz4InCount >= m_lz4InSize) // full input compressed block retrieved
{
if (m_nbLz4Decodes == 100)
{
qDebug() << "SDRdaemonBuffer::writeAndReadLZ4:"
<< " decoding: " << m_nbLz4CRCOK
<< ":" << m_nbLz4SuccessfulDecodes
<< "/" << m_nbLz4Decodes;
m_nbLastLz4SuccessfulDecodes = m_nbLz4SuccessfulDecodes;
m_nbLastLz4CRCOK = m_nbLz4CRCOK;
m_nbLz4Decodes = 0;
m_nbLz4SuccessfulDecodes = 0;
m_nbLz4CRCOK = 0;
}
writeToRawBufferLZ4();
m_lz4InCount = 0;
}
}
void SDRdaemonFECBuffer::writeToRawBufferLZ4()
{
uint64_t crc64 = m_crc64.calculate_crc(m_lz4InBuffer, m_lz4InSize);
if (memcmp(&crc64, &m_dataCRC, 8) == 0)
{
m_nbLz4CRCOK++;
}
else
{
return;
}
int compressedSize = LZ4_decompress_fast((const char*) m_lz4InBuffer, (char*) m_lz4OutBuffer, m_frameSize);
m_nbLz4Decodes++;
if (compressedSize == m_lz4InSize)
{
m_nbLz4SuccessfulDecodes++;
writeToRawBufferUncompressed((const char *) m_lz4OutBuffer, m_frameSize);
}
}
void SDRdaemonFECBuffer::writeToRawBufferUncompressed(const char *array, uint32_t length)
{
// TODO: handle the 1 byte per I or Q sample
if (m_writeIndex + length < m_rawSize)
{
std::memcpy((void *) &m_rawBuffer[m_writeIndex], (const void *) array, length);
m_writeIndex += length;
}
else if (m_writeIndex + length == m_rawSize)
{
std::memcpy((void *) &m_rawBuffer[m_writeIndex], (const void *) array, length);
m_writeIndex = 0;
}
else
{
std::memcpy((void *) &m_rawBuffer[m_writeIndex], (const void *) array, m_rawSize - m_writeIndex);
length -= m_rawSize - m_writeIndex;
std::memcpy((void *) m_rawBuffer, (const void *) &array[m_rawSize - m_writeIndex], length);
m_writeIndex = length;
}
m_writeCount += length;
}
void SDRdaemonFECBuffer::resetIndexes()
{
m_writeIndex = 0;
m_readIndex = m_rawSize / 2;
m_readCount = 0;
m_writeCount = 0;
m_nbCycles = 0;
m_skewTest = false;
m_skewCorrection = false;
m_nbReads = 0;
m_balCorrection = 0;
}
void SDRdaemonFECBuffer::updateBlockCounts(uint32_t nbBytesReceived)
{
m_nbBlocks += m_bytesInBlock + nbBytesReceived > m_udpPayloadSize ? 1 : 0;
m_bytesInBlock = m_bytesInBlock + nbBytesReceived > m_udpPayloadSize ? nbBytesReceived : m_bytesInBlock + nbBytesReceived;
}
void SDRdaemonFECBuffer::printMeta(const QString& header, MetaData *metaData)
{
qDebug() << header << ": "
<< "|" << metaData->m_centerFrequency
<< ":" << metaData->m_sampleRate
<< ":" << (int) (metaData->m_sampleBytes & 0xF)
<< ":" << (int) metaData->m_sampleBits
<< ":" << metaData->m_blockSize
<< ":" << metaData->m_nbSamples
<< "||" << metaData->m_nbBlocks
<< ":" << metaData->m_nbBytes
<< "|" << metaData->m_tv_sec
<< ":" << metaData->m_tv_usec;
}

View File

@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SDRDAEMONFEC_SDRDAEMONFECBUFFER_H_
#define PLUGINS_SAMPLESOURCE_SDRDAEMONFEC_SDRDAEMONFECBUFFER_H_
#include <QString>
#include <cstdlib>
#include "util/CRC64.h"
class SDRdaemonFECBuffer
{
public:
public:
#pragma pack(push, 1)
struct MetaData
{
// critical data
uint32_t m_centerFrequency; //!< center frequency in kHz
uint32_t m_sampleRate; //!< sample rate in Hz
uint8_t m_sampleBytes; //!< MSB(4): indicators, LSB(4) number of bytes per sample
uint8_t m_sampleBits; //!< number of effective bits per sample
uint16_t m_blockSize; //!< payload size
uint32_t m_nbSamples; //!< number of samples in a hardware block
// end of critical data
uint16_t m_nbBlocks; //!< number of hardware blocks in the frame
uint32_t m_nbBytes; //!< total number of bytes in the frame
uint32_t m_tv_sec; //!< seconds of timestamp at start time of frame processing
uint32_t m_tv_usec; //!< microseconds of timestamp at start time of frame processing
uint64_t m_crc; //!< 64 bit CRC of the above
bool operator==(const MetaData& rhs)
{
return (memcmp((const void *) this, (const void *) &rhs, 20) == 0); // Only the 20 first bytes are relevant (critical)
}
void init()
{
memset((void *) this, 0, sizeof(MetaData));
}
void operator=(const MetaData& rhs)
{
memcpy((void *) this, (const void *) &rhs, sizeof(MetaData));
}
};
#pragma pack(pop)
SDRdaemonFECBuffer(uint32_t throttlems);
~SDRdaemonFECBuffer();
bool readMeta(char *array, uint32_t length); //!< Attempt to read meta. Returns true if meta block
void writeData(char *array, uint32_t length); //!< Write data into buffer.
uint8_t *readData(int32_t length);
void updateBlockCounts(uint32_t nbBytesReceived);
const MetaData& getCurrentMeta() const { return m_currentMeta; }
uint32_t getSampleRateStream() const { return m_sampleRateStream; }
uint32_t getSampleRate() const { return m_sampleRate; }
bool isSync() const { return m_sync; }
bool isSyncLocked() const { return m_syncLock; }
uint32_t getFrameSize() const { return m_frameSize; }
bool isLz4Compressed() const { return m_lz4; }
float getCompressionRatio() const { return (m_frameSize > 0 ? (float) m_lz4InSize / (float) m_frameSize : 1.0); }
uint32_t getLz4DataCRCOK() const { return m_nbLastLz4CRCOK; }
uint32_t getLz4SuccessfulDecodes() const { return m_nbLastLz4SuccessfulDecodes; }
float getBufferLengthInSecs() const { return m_bufferLenSec; }
void setAutoFollowRate(bool autoFollowRate) { m_autoFollowRate = autoFollowRate; }
void setAutoCorrBuffer(bool autoCorrBuffer) { m_autoCorrBuffer = autoCorrBuffer; }
void setResetIndexes() { m_resetIndexes = true; }
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_rawSize)
{
int32_t val = ((m_writeIndex - m_readIndex) * 100) / (int32_t) m_rawSize;
if (val < -50) {
return val + 100; // read leads (positive)
} else if (val < 50) {
return val; // read leads (positive) or write leads (negative)
} else {
return val - 100; // write leads (negative)
}
}
else
{
return -50; // default position
}
}
static const int m_udpPayloadSize;
static const int m_sampleSize;
static const int m_iqSampleSize;
static const int m_rawBufferLengthSeconds;
static const int m_rawBufferMinNbFrames; //!< Minimum number of frames for the length of buffer
private:
void updateBufferSize(uint32_t sampleRate);
void updateLZ4Sizes(uint32_t frameSize);
void updateReadBufferSize(uint32_t length);
void writeDataLZ4(const char *array, uint32_t length);
void writeToRawBufferLZ4();
void writeToRawBufferUncompressed(const char *array, uint32_t length);
void resetIndexes();
static void printMeta(const QString& header, MetaData *metaData);
uint32_t m_throttlemsNominal; //!< Initial throttle in ms
uint32_t m_rawSize; //!< Size of the raw samples buffer in bytes
uint8_t *m_rawBuffer; //!< Buffer for raw samples obtained from UDP (I/Q not in a formal I/Q structure)
uint32_t m_sampleRateStream; //!< Current sample rate from the stream meta data
uint32_t m_sampleRate; //!< Current actual sample rate in Hz
uint8_t m_sampleBytes; //!< Current number of bytes per I or Q sample
uint8_t m_sampleBits; //!< Current number of effective bits per sample
bool m_sync; //!< Meta data acquired
bool m_syncLock; //!< Meta data expected (Stream synchronized)
bool m_lz4; //!< Stream is compressed with LZ4
MetaData m_currentMeta; //!< Stored current meta data
CRC64 m_crc64; //!< CRC64 calculator
uint32_t m_nbBlocks; //!< Number of UDP blocks received in the current frame
uint32_t m_bytesInBlock; //!< Number of bytes received in the current UDP block
uint64_t m_dataCRC; //!< CRC64 of the data block
uint32_t m_inCount; //!< Current position of uncompressed input
uint32_t m_lz4InCount; //!< Current position in LZ4 input buffer
uint32_t m_lz4InSize; //!< Size in bytes of the LZ4 input data
uint8_t *m_lz4InBuffer; //!< Buffer for LZ4 compressed input
uint8_t *m_lz4OutBuffer; //!< Buffer for LZ4 uncompressed output
uint32_t m_frameSize; //!< Size in bytes of one uncompressed frame
float m_bufferLenSec; //!< Raw buffer length in seconds
uint32_t m_nbLz4Decodes;
uint32_t m_nbLz4SuccessfulDecodes;
uint32_t m_nbLz4CRCOK;
uint32_t m_nbLastLz4SuccessfulDecodes;
uint32_t m_nbLastLz4CRCOK;
int32_t m_writeIndex; //!< Current write position in the raw samples buffer
int32_t m_readIndex; //!< Current read position in the raw samples buffer
uint32_t m_readSize; //!< Read buffer size
uint8_t *m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer
bool m_autoFollowRate; //!< Auto follow stream sample rate else stick with meta data sample rate
bool m_autoCorrBuffer; //!< Auto correct buffer read / write balance (attempt to ...)
bool m_skewTest;
bool m_skewCorrection; //!< Do a skew rate correction at next meta data reception
bool m_resetIndexes; //!< Do a reset indexes at next meta data reception
int64_t m_readCount; //!< Number of bytes read for auto skew compensation
int64_t m_writeCount; //!< Number of bytes written for auto skew compensation
uint32_t m_nbCycles; //!< Number of buffer cycles since start of auto skew compensation byte counting
uint32_t m_nbReads; //!< Number of buffer reads since start of auto R/W balance correction period
int32_t m_balCorrection; //!< R/W balance correction in number of samples
int32_t m_balCorrLimit; //!< Correction absolute value limit in number of samples
};
#endif /* PLUGINS_SAMPLESOURCE_SDRDAEMONFEC_SDRDAEMONFECBUFFER_H_ */

View File

@ -0,0 +1,736 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <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 "device/deviceapi.h"
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/filesink.h"
#include "mainwindow.h"
#include "util/simpleserializer.h"
#include "sdrdaemonfecgui.h"
SDRdaemonFECGui::SDRdaemonFECGui(DeviceAPI *deviceAPI, QWidget* parent) :
QWidget(parent),
ui(new Ui::SDRdaemonFECGui),
m_deviceAPI(deviceAPI),
m_sampleSource(NULL),
m_acquisition(false),
m_lastEngineState((DSPDeviceEngine::State)-1),
m_sampleRate(0),
m_sampleRateStream(0),
m_centerFrequency(0),
m_syncLocked(false),
m_frameSize(0),
m_lz4(false),
m_compressionRatio(1.0),
m_nbLz4DataCRCOK(0),
m_nbLz4SuccessfulDecodes(0),
m_bufferLengthInSecs(0.0),
m_bufferGauge(-50),
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_dcBlock(false),
m_iqCorrection(false),
m_autoFollowRate(false)
{
m_sender = nn_socket(AF_SP, NN_PAIR);
assert(m_sender != -1);
int millis = 500;
int rc = nn_setsockopt (m_sender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis));
assert (rc == 0);
m_startingTimeStamp.tv_sec = 0;
m_startingTimeStamp.tv_usec = 0;
ui->setupUi(this);
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold));
ui->centerFrequency->setValueRange(7, 0, pow(10,7));
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(&(deviceAPI->getMainWindow()->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
m_sampleSource = new SDRdaemonFECInput(deviceAPI->getMainWindow()->getMasterTimer(), m_deviceAPI);
connect(m_sampleSource->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_deviceAPI->setSource(m_sampleSource);
displaySettings();
ui->applyButton->setEnabled(false);
ui->sendButton->setEnabled(false);
char recFileNameCStr[30];
sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID());
m_fileSink = new FileSink(std::string(recFileNameCStr));
m_deviceAPI->addSink(m_fileSink);
connect(m_deviceAPI->getDeviceOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleDSPMessages()), Qt::QueuedConnection);
}
SDRdaemonFECGui::~SDRdaemonFECGui()
{
m_deviceAPI->removeSink(m_fileSink);
delete m_fileSink;
delete ui;
}
void SDRdaemonFECGui::destroy()
{
delete this;
}
void SDRdaemonFECGui::setName(const QString& name)
{
setObjectName(name);
}
QString SDRdaemonFECGui::getName() const
{
return objectName();
}
void SDRdaemonFECGui::resetToDefaults()
{
m_address = "127.0.0.1";
m_remoteAddress = "127.0.0.1";
m_dataPort = 9090;
m_controlPort = 9091;
m_dcBlock = false;
m_iqCorrection = false;
m_autoFollowRate = false;
displaySettings();
}
QByteArray SDRdaemonFECGui::serialize() const
{
bool ok;
SimpleSerializer s(1);
s.writeString(1, ui->address->text());
uint32_t uintval = ui->dataPort->text().toInt(&ok);
if((!ok) || (uintval < 1024) || (uintval > 65535)) {
uintval = 9090;
}
s.writeU32(2, uintval);
s.writeBool(3, m_dcBlock);
s.writeBool(4, m_iqCorrection);
s.writeBool(5, m_autoFollowRate);
s.writeBool(6, m_autoCorrBuffer);
uintval = ui->controlPort->text().toInt(&ok);
if((!ok) || (uintval < 1024) || (uintval > 65535)) {
uintval = 9091;
}
s.writeU32(7, uintval);
uint32_t confFrequency = ui->freq->text().toInt(&ok);
if (ok) {
s.writeU32(8, confFrequency);
}
s.writeU32(9, ui->decim->currentIndex());
s.writeU32(10, ui->fcPos->currentIndex());
uint32_t sampleRate = ui->sampleRate->text().toInt(&ok);
if (ok) {
s.writeU32(11, sampleRate);
}
s.writeString(12, ui->specificParms->text());
return s.final();
}
bool SDRdaemonFECGui::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
QString address;
uint32_t uintval;
quint16 dataPort;
bool dcBlock;
bool iqCorrection;
bool autoFollowRate;
bool autoCorrBuffer;
uint32_t confFrequency;
uint32_t confSampleRate;
uint32_t confDecim;
uint32_t confFcPos;
QString confSpecificParms;
if (!d.isValid())
{
resetToDefaults();
displaySettings();
return false;
}
if (d.getVersion() == 1)
{
uint32_t uintval;
d.readString(1, &address, "127.0.0.1");
d.readU32(2, &uintval, 9090);
if ((uintval > 1024) && (uintval < 65536)) {
dataPort = uintval;
} else {
dataPort = 9090;
}
d.readBool(3, &dcBlock, false);
d.readBool(4, &iqCorrection, false);
d.readBool(5, &autoFollowRate, false);
d.readBool(6, &autoCorrBuffer, false);
d.readU32(7, &uintval, 9091);
if ((uintval > 1024) && (uintval < 65536)) {
m_controlPort = uintval;
} else {
m_controlPort = 9091;
}
d.readU32(8, &confFrequency, 435000);
d.readU32(9, &confDecim, 3);
d.readU32(10, &confFcPos, 2);
d.readU32(11, &confSampleRate, 1000);
d.readString(12, &confSpecificParms, "");
if ((address != m_address) || (dataPort != m_dataPort))
{
m_address = address;
m_dataPort = dataPort;
configureUDPLink();
}
if ((dcBlock != m_dcBlock) || (iqCorrection != m_iqCorrection))
{
m_dcBlock = dcBlock;
m_iqCorrection = iqCorrection;
configureAutoCorrections();
}
if ((m_autoFollowRate != autoFollowRate) || (m_autoCorrBuffer != autoCorrBuffer))
{
m_autoFollowRate = autoFollowRate;
m_autoCorrBuffer = autoCorrBuffer;
configureAutoFollowPolicy();
}
displaySettings();
displayConfigurationParameters(confFrequency, confDecim, confFcPos, confSampleRate, confSpecificParms);
m_initSendConfiguration = true;
return true;
}
else
{
resetToDefaults();
displaySettings();
QString defaultSpecificParameters("");
displayConfigurationParameters(435000, 3, 2, 1000, defaultSpecificParameters);
m_initSendConfiguration = true;
return false;
}
}
qint64 SDRdaemonFECGui::getCenterFrequency() const
{
return m_centerFrequency;
}
void SDRdaemonFECGui::setCenterFrequency(qint64 centerFrequency)
{
m_centerFrequency = centerFrequency;
displaySettings();
}
bool SDRdaemonFECGui::handleMessage(const Message& message)
{
if (SDRdaemonFECInput::MsgReportSDRdaemonAcquisition::match(message))
{
m_acquisition = ((SDRdaemonFECInput::MsgReportSDRdaemonAcquisition&)message).getAcquisition();
updateWithAcquisition();
return true;
}
else if (SDRdaemonFECInput::MsgReportSDRdaemonStreamData::match(message))
{
m_sampleRateStream = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamData&)message).getSampleRateStream();
m_sampleRate = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamData&)message).getSampleRate();
m_centerFrequency = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamData&)message).getCenterFrequency();
m_startingTimeStamp.tv_sec = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamData&)message).get_tv_sec();
m_startingTimeStamp.tv_usec = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamData&)message).get_tv_usec();
updateWithStreamData();
return true;
}
else if (SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming::match(message))
{
m_startingTimeStamp.tv_sec = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).get_tv_sec();
m_startingTimeStamp.tv_usec = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).get_tv_usec();
m_syncLocked = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getSyncLock();
m_frameSize = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getFrameSize();
m_lz4 = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getLz4Compression();
if (m_lz4) {
m_compressionRatio = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getLz4CompressionRatio();
} else {
m_compressionRatio = 1.0;
}
m_nbLz4DataCRCOK = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getLz4DataCRCOK();
m_nbLz4SuccessfulDecodes = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getLz4SuccessfulDecodes();
m_bufferLengthInSecs = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getBufferLengthInSecs();
m_bufferGauge = ((SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming&)message).getBufferGauge();
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::displaySettings()
{
ui->address->setText(m_address);
ui->dataPort->setText(QString::number(m_dataPort));
ui->controlPort->setText(QString::number(m_controlPort));
ui->dcOffset->setChecked(m_dcBlock);
ui->iqImbalance->setChecked(m_iqCorrection);
ui->autoFollowRate->setChecked(m_autoFollowRate);
ui->autoCorrBuffer->setChecked(m_autoCorrBuffer);
}
void SDRdaemonFECGui::displayConfigurationParameters(uint32_t freq,
uint32_t log2Decim,
uint32_t fcPos,
uint32_t sampleRate,
QString& specParms)
{
ui->freq->setText(QString::number(freq));
ui->decim->setCurrentIndex(log2Decim);
ui->fcPos->setCurrentIndex(fcPos);
ui->sampleRate->setText(QString::number(sampleRate));
ui->specificParms->setText(specParms);
ui->specificParms->setCursorPosition(0);
}
void SDRdaemonFECGui::on_applyButton_clicked(bool checked)
{
bool dataOk, ctlOk;
QString udpAddress = ui->address->text();
int udpDataPort = ui->dataPort->text().toInt(&dataOk);
int tcpCtlPort = ui->controlPort->text().toInt(&ctlOk);
if((!dataOk) || (udpDataPort < 1024) || (udpDataPort > 65535))
{
udpDataPort = 9090;
}
if((!ctlOk) || (tcpCtlPort < 1024) || (tcpCtlPort > 65535))
{
tcpCtlPort = 9091;
}
m_address = udpAddress;
m_dataPort = udpDataPort;
m_controlPort = tcpCtlPort;
if (m_addressEdited || m_dataPortEdited)
{
configureUDPLink();
m_addressEdited = false;
m_dataPortEdited = false;
}
ui->applyButton->setEnabled(false);
}
void SDRdaemonFECGui::on_sendButton_clicked(bool checked)
{
sendConfiguration();
ui->specificParms->setCursorPosition(0);
ui->sendButton->setEnabled(false);
}
void SDRdaemonFECGui::sendConfiguration()
{
QString remoteAddress;
((SDRdaemonFECInput *) m_sampleSource)->getRemoteAddress(remoteAddress);
if (remoteAddress != m_remoteAddress)
{
m_remoteAddress = remoteAddress;
std::ostringstream os;
os << "tcp://" << m_remoteAddress.toStdString() << ":" << m_controlPort;
std::string addrstrng = os.str();
int rc = nn_connect(m_sender, addrstrng.c_str());
if (rc < 0) {
QMessageBox::information(this, tr("Message"), tr("Cannot connect to remote control port"));
} else {
qDebug() << "SDRdaemonGui::sendConfiguration: connexion to " << addrstrng.c_str() << " successful";
}
}
std::ostringstream os;
bool ok;
os << "decim=" << ui->decim->currentIndex()
<< ",fcpos=" << ui->fcPos->currentIndex();
uint64_t freq = ui->freq->text().toInt(&ok);
if (ok) {
os << ",freq=" << freq*1000LL;
} else {
QMessageBox::information(this, tr("Message"), tr("Invalid frequency"));
}
uint32_t srate = ui->sampleRate->text().toInt(&ok);
if (ok) {
os << ",srate=" << srate*1000;
} else {
QMessageBox::information(this, tr("Message"), tr("invalid sample rate"));
}
if ((ui->specificParms->text()).size() > 0) {
os << "," << ui->specificParms->text().toStdString();
}
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"));
}
else
{
qDebug() << "SDRdaemonGui::sendConfiguration:"
<< " remoteAddress: " << remoteAddress
<< " message: " << os.str().c_str();
}
}
void SDRdaemonFECGui::on_address_textEdited(const QString& arg1)
{
ui->applyButton->setEnabled(true);
m_addressEdited = true;
}
void SDRdaemonFECGui::on_dataPort_textEdited(const QString& arg1)
{
ui->applyButton->setEnabled(true);
m_dataPortEdited = true;
}
void SDRdaemonFECGui::on_controlPort_textEdited(const QString& arg1)
{
ui->applyButton->setEnabled(true);
}
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_autoFollowRate_toggled(bool checked)
{
if (m_autoFollowRate != checked) {
m_autoFollowRate = checked;
configureAutoFollowPolicy();
}
}
void SDRdaemonFECGui::on_autoCorrBuffer_toggled(bool checked)
{
if (m_autoCorrBuffer != checked) {
m_autoCorrBuffer = checked;
configureAutoFollowPolicy();
}
}
void SDRdaemonFECGui::on_resetIndexes_clicked(bool checked)
{
SDRdaemonFECInput::MsgConfigureSDRdaemonResetIndexes* message = SDRdaemonFECInput::MsgConfigureSDRdaemonResetIndexes::create();
m_sampleSource->getInputMessageQueue()->push(message);
}
void SDRdaemonFECGui::on_freq_textEdited(const QString& arg1)
{
ui->sendButton->setEnabled(true);
}
void SDRdaemonFECGui::on_sampleRate_textEdited(const QString& arg1)
{
ui->sendButton->setEnabled(true);
}
void SDRdaemonFECGui::on_specificParms_textEdited(const QString& arg1)
{
ui->sendButton->setEnabled(true);
}
void SDRdaemonFECGui::on_decim_currentIndexChanged(int index)
{
ui->sendButton->setEnabled(true);
}
void SDRdaemonFECGui::on_fcPos_currentIndexChanged(int index)
{
ui->sendButton->setEnabled(true);
}
void SDRdaemonFECGui::on_startStop_toggled(bool checked)
{
if (checked)
{
if (m_deviceAPI->initAcquisition())
{
m_deviceAPI->startAcquisition();
DSPEngine::instance()->startAudio();
}
}
else
{
m_deviceAPI->stopAcquisition();
DSPEngine::instance()->stopAudio();
}
}
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::configureUDPLink()
{
qDebug() << "SDRdaemonGui::configureUDPLink: " << m_address.toStdString().c_str()
<< " : " << m_dataPort;
SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink* message = SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink::create(m_address, 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::configureAutoFollowPolicy()
{
SDRdaemonFECInput::MsgConfigureSDRdaemonAutoFollowPolicy* message = SDRdaemonFECInput::MsgConfigureSDRdaemonAutoFollowPolicy::create(m_autoFollowRate, m_autoCorrBuffer);
m_sampleSource->getInputMessageQueue()->push(message);
}
void SDRdaemonFECGui::updateWithAcquisition()
{
}
void SDRdaemonFECGui::updateWithStreamData()
{
ui->centerFrequency->setValue(m_centerFrequency / 1000);
QString s0 = QString::number(m_sampleRateStream/1000.0, 'f', 2);
ui->sampleRateStreamText->setText(tr("%1").arg(s0));
QString s1 = QString::number(m_sampleRate/1000.0, 'f', 3);
ui->sampleRateText->setText(tr("%1").arg(s1));
float skewPerCent = (float) ((m_sampleRate - m_sampleRateStream) * 100) / (float) m_sampleRateStream;
QString s2 = QString::number(skewPerCent, 'f', 2);
ui->skewRateText->setText(tr("%1").arg(s2));
updateWithStreamTime();
}
void SDRdaemonFECGui::updateWithStreamTime()
{
if (m_initSendConfiguration)
{
sendConfiguration();
m_initSendConfiguration = 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_syncLocked) {
ui->streamLocked->setStyleSheet("QToolButton { background-color : green; }");
} else {
ui->streamLocked->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
QString s = QString::number(m_frameSize / 1024.0, 'f', 0);
ui->frameSizeText->setText(tr("%1").arg(s));
if (m_lz4) {
ui->lz4Compressed->setStyleSheet("QToolButton { background-color : green; }");
} else {
ui->lz4Compressed->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
s = QString::number(m_compressionRatio, 'f', 2);
ui->compressionRatioText->setText(tr("%1").arg(s));
s = QString::number(m_nbLz4DataCRCOK, 'f', 0);
ui->dataCRCOKText->setText(tr("%1").arg(s));
s = QString::number(m_nbLz4SuccessfulDecodes, 'f', 0);
ui->lz4DecodesOKText->setText(tr("%1").arg(s));
s = QString::number(m_bufferLengthInSecs, 'f', 1);
ui->bufferLenSecsText->setText(tr("%1").arg(s));
s = QString::number((m_bufferGauge < 0 ? -50 - m_bufferGauge : 50 - m_bufferGauge), 'f', 0);
ui->bufferRWBalanceText->setText(tr("%1").arg(s));
ui->bufferGaugeNegative->setValue((m_bufferGauge < 0 ? 50 + m_bufferGauge : 0));
ui->bufferGaugePositive->setValue((m_bufferGauge < 0 ? 0 : 50 - m_bufferGauge));
}
void SDRdaemonFECGui::updateStatus()
{
int state = m_deviceAPI->state();
if(m_lastEngineState != state)
{
switch(state)
{
case DSPDeviceEngine::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case DSPDeviceEngine::StIdle:
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case DSPDeviceEngine::StRunning:
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case DSPDeviceEngine::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);
}
}

View File

@ -0,0 +1,136 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SDRDAEMONFECGUI_H
#define INCLUDE_SDRDAEMONFECGUI_H
#include <QTimer>
#include <sys/time.h>
#include "plugin/plugingui.h"
#include "sdrdaemonfecinput.h"
class DeviceAPI;
class FileSink;
namespace Ui {
class SDRdaemonFECGui;
}
class SDRdaemonFECGui : public QWidget, public PluginGUI {
Q_OBJECT
public:
explicit SDRdaemonFECGui(DeviceAPI *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;
DeviceAPI* m_deviceAPI;
QTimer m_updateTimer;
QTimer m_statusTimer;
SampleSource* m_sampleSource;
bool m_acquisition;
FileSink *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;
int m_sampleRateStream;
quint64 m_centerFrequency;
struct timeval m_startingTimeStamp;
bool m_syncLocked;
uint32_t m_frameSize;
bool m_lz4;
float m_compressionRatio;
uint32_t m_nbLz4DataCRCOK;
uint32_t m_nbLz4SuccessfulDecodes;
float m_bufferLengthInSecs;
int32_t m_bufferGauge;
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;
bool m_dcBlock;
bool m_iqCorrection;
bool m_autoFollowRate;
bool m_autoCorrBuffer;
void displaySettings();
void displayConfigurationParameters(uint32_t freq,
uint32_t log2Decim,
uint32_t fcPos,
uint32_t sampleRate,
QString& specParms);
void displayTime();
void configureUDPLink();
void configureAutoCorrections();
void configureAutoFollowPolicy();
void updateWithAcquisition();
void updateWithStreamData();
void updateWithStreamTime();
void sendConfiguration();
void updateSampleRateAndFrequency();
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_autoFollowRate_toggled(bool checked);
void on_autoCorrBuffer_toggled(bool checked);
void on_resetIndexes_clicked(bool checked);
void on_address_textEdited(const QString& arg1);
void on_dataPort_textEdited(const QString& arg1);
void on_controlPort_textEdited(const QString& arg1);
void on_sendButton_clicked(bool checked);
void on_freq_textEdited(const QString& arg1);
void on_sampleRate_textEdited(const QString& arg1);
void on_specificParms_textEdited(const QString& arg1);
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 updateStatus();
void tick();
};
#endif // INCLUDE_SDRDAEMONGUI_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <string.h>
#include <errno.h>
#include <QDebug>
#include "device/deviceapi.h"
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include "dsp/filesink.h"
#include "sdrdaemonfecinput.h"
#include "sdrdaemonfecgui.h"
#include "sdrdaemonfecudphandler.h"
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonUDPLink, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonAutoCorr, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonWork, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonAutoFollowPolicy, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonResetIndexes, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgConfigureSDRdaemonStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonAcquisition, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonStreamData, Message)
MESSAGE_CLASS_DEFINITION(SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming, Message)
SDRdaemonFECInput::SDRdaemonFECInput(const QTimer& masterTimer, DeviceAPI *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::init(const Message& message)
{
qDebug() << "SDRdaemonInput::init";
return false;
}
bool SDRdaemonFECInput::start(int device)
{
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 (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 (MsgConfigureSDRdaemonAutoFollowPolicy::match(message))
{
MsgConfigureSDRdaemonAutoFollowPolicy& conf = (MsgConfigureSDRdaemonAutoFollowPolicy&) message;
bool autoFollowRate = conf.autoFollowRate();
bool autoCorrBuffer = conf.autoCorrBuffer();
if (autoFollowRate != m_autoFollowRate) {
m_SDRdaemonUDPHandler->setAutoFollowRate(autoFollowRate);
m_autoFollowRate = autoFollowRate;
}
if (autoCorrBuffer != m_autoCorrBuffer) {
m_SDRdaemonUDPHandler->setAutoCorrBuffer(autoCorrBuffer);
m_autoCorrBuffer = autoCorrBuffer;
}
return true;
}
else if (MsgConfigureSDRdaemonResetIndexes::match(message))
{
m_SDRdaemonUDPHandler->resetIndexes();
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;
}
}

View File

@ -0,0 +1,307 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SDRDAEMONFECINPUT_H
#define INCLUDE_SDRDAEMONFECINPUT_H
#include "dsp/samplesource.h"
#include <QString>
#include <QTimer>
#include <ctime>
#include <iostream>
#include <stdint.h>
class DeviceAPI;
class SDRdaemonFECUDPHandler;
class SDRdaemonFECInput : public SampleSource {
public:
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 MsgConfigureSDRdaemonAutoFollowPolicy : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool autoFollowRate() const { return m_autoFollowRate; }
bool autoCorrBuffer() const { return m_autoCorrBuffer; }
static MsgConfigureSDRdaemonAutoFollowPolicy* create(bool autoFollowRate, bool autoCorrBuffer)
{
return new MsgConfigureSDRdaemonAutoFollowPolicy(autoFollowRate, autoCorrBuffer);
}
private:
bool m_autoFollowRate;
bool m_autoCorrBuffer;
MsgConfigureSDRdaemonAutoFollowPolicy(bool autoFollowRate, bool autoCorrBuffer) :
Message(),
m_autoFollowRate(autoFollowRate),
m_autoCorrBuffer(autoCorrBuffer)
{ }
};
class MsgConfigureSDRdaemonResetIndexes : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureSDRdaemonResetIndexes* create()
{
return new MsgConfigureSDRdaemonResetIndexes();
}
private:
MsgConfigureSDRdaemonResetIndexes() :
Message()
{ }
};
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 MsgReportSDRdaemonStreamData : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRateStream() const { return m_sampleRateStream; }
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 MsgReportSDRdaemonStreamData* create(int sampleRateStream, int sampleRate, quint64 centerFrequency, uint32_t tv_sec, uint32_t tv_usec)
{
return new MsgReportSDRdaemonStreamData(sampleRateStream, sampleRate, centerFrequency, tv_sec, tv_usec);
}
protected:
int m_sampleRateStream;
int m_sampleRate;
quint64 m_centerFrequency;
uint32_t m_tv_sec;
uint32_t m_tv_usec;
MsgReportSDRdaemonStreamData(int sampleRateStream, int sampleRate, quint64 centerFrequency, uint32_t tv_sec, uint32_t tv_usec) :
Message(),
m_sampleRateStream(sampleRateStream),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency),
m_tv_sec(tv_sec),
m_tv_usec(tv_usec)
{ }
};
class MsgReportSDRdaemonStreamTiming : 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; }
bool getSyncLock() const { return m_syncLock; }
uint32_t getFrameSize() const { return m_frameSize; }
float getBufferLengthInSecs() const { return m_bufferLenSec; }
bool getLz4Compression() const { return m_lz4; }
float getLz4CompressionRatio() const { return m_compressionRatio; }
uint32_t getLz4DataCRCOK() const { return m_nbLz4CRCOK; }
uint32_t getLz4SuccessfulDecodes() const { return m_nbLz4SuccessfulDecodes; }
int32_t getBufferGauge() const { return m_bufferGauge; }
static MsgReportSDRdaemonStreamTiming* create(uint32_t tv_sec,
uint32_t tv_usec,
bool syncLock,
uint32_t frameSize,
float bufferLenSec,
bool lz4,
float compressionRatio,
uint32_t nbLz4CRCOK,
uint32_t nbLz4SuccessfulDecodes,
int32_t bufferGauge)
{
return new MsgReportSDRdaemonStreamTiming(tv_sec,
tv_usec,
syncLock,
frameSize,
bufferLenSec,
lz4,
compressionRatio,
nbLz4CRCOK,
nbLz4SuccessfulDecodes,
bufferGauge);
}
protected:
uint32_t m_tv_sec;
uint32_t m_tv_usec;
bool m_syncLock;
uint32_t m_frameSize;
float m_bufferLenSec;
bool m_lz4;
float m_compressionRatio;
uint32_t m_nbLz4CRCOK;
uint32_t m_nbLz4SuccessfulDecodes;
int32_t m_bufferGauge;
MsgReportSDRdaemonStreamTiming(uint32_t tv_sec,
uint32_t tv_usec,
bool syncLock,
uint32_t frameSize,
float bufferLenSec,
bool lz4,
float compressionRatio,
uint32_t nbLz4CRCOK,
uint32_t nbLz4SuccessfulDecodes,
int32_t bufferGauge) :
Message(),
m_tv_sec(tv_sec),
m_tv_usec(tv_usec),
m_syncLock(syncLock),
m_frameSize(frameSize),
m_bufferLenSec(bufferLenSec),
m_lz4(lz4),
m_compressionRatio(compressionRatio),
m_nbLz4CRCOK(nbLz4CRCOK),
m_nbLz4SuccessfulDecodes(nbLz4SuccessfulDecodes),
m_bufferGauge(bufferGauge)
{ }
};
SDRdaemonFECInput(const QTimer& masterTimer, DeviceAPI *deviceAPI);
virtual ~SDRdaemonFECInput();
virtual bool init(const Message& message);
virtual bool start(int device);
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:
DeviceAPI *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_SDRDAEMONFECINPUT_H

View File

@ -0,0 +1,83 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QtPlugin>
#include <QAction>
#include "plugin/pluginapi.h"
#include "device/deviceapi.h"
#include "util/simpleserializer.h"
#include "sdrdaemonfecplugin.h"
#include "sdrdaemonfecgui.h"
const PluginDescriptor SDRdaemonFECPlugin::m_pluginDescriptor = {
QString("SDRdaemon input"),
QString("2.0.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
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::SampleSourceDevices SDRdaemonFECPlugin::enumSampleSources()
{
SampleSourceDevices result;
int count = 1;
for(int i = 0; i < count; i++)
{
QString displayedName(QString("SDRdaemon[%1]").arg(i));
result.append(SampleSourceDevice(displayedName,
m_deviceTypeID,
QString::null,
i));
}
return result;
}
PluginGUI* SDRdaemonFECPlugin::createSampleSourcePluginGUI(const QString& sourceId, QWidget **widget, DeviceAPI *deviceAPI)
{
if(sourceId == m_deviceTypeID)
{
SDRdaemonFECGui* gui = new SDRdaemonFECGui(deviceAPI);
*widget = gui;
return gui;
}
else
{
return NULL;
}
}

View File

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SDRDAEMONFECPLUGIN_H
#define INCLUDE_SDRDAEMONFECPLUGIN_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 SampleSourceDevices enumSampleSources();
virtual PluginGUI* createSampleSourcePluginGUI(const QString& sourceId, QWidget **widget, DeviceAPI *deviceAPI);
static const QString m_deviceTypeID;
private:
static const PluginDescriptor m_pluginDescriptor;
};
#endif // INCLUDE_SDRDAEMONFECPLUGIN_H

View File

@ -0,0 +1,261 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QUdpSocket>
#include <QDebug>
#include <QTimer>
#include <unistd.h>
#include "device/deviceapi.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include "sdrdaemonfecinput.h"
#include "sdrdaemonfecudphandler.h"
SDRdaemonFECUDPHandler::SDRdaemonFECUDPHandler(SampleFifo *sampleFifo, MessageQueue *outputMessageQueueToGUI, DeviceAPI *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(false)
{
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("SDRdaemonUDPHandler::start");
if (!m_dataSocket)
{
m_dataSocket = new QUdpSocket(this);
}
if (!m_dataConnected)
{
if (m_dataSocket->bind(m_dataAddress, m_dataPort))
{
qDebug("SDRdaemonUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort);
connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection
m_dataConnected = true;
}
else
{
qWarning("SDRdaemonUDPHandler::start: cannot bind data port %d", m_dataPort);
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("SDRdaemonUDPHandler::stop");
if (m_dataConnected) {
disconnect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
m_dataConnected = false;
}
if (m_dataSocket)
{
delete m_dataSocket;
m_dataSocket = 0;
}
}
void SDRdaemonFECUDPHandler::configureUDPLink(const QString& address, quint16 port)
{
qDebug("SDRdaemonUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port);
bool addressOK = m_dataAddress.setAddress(address);
if (!addressOK)
{
qWarning("SDRdaemonUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str());
m_dataAddress = QHostAddress::LocalHost;
}
stop();
m_dataPort = port;
start();
}
void SDRdaemonFECUDPHandler::dataReadyRead()
{
while (m_dataSocket->hasPendingDatagrams())
{
qint64 pendingDataSize = m_dataSocket->pendingDatagramSize();
m_udpReadBytes = m_dataSocket->readDatagram(m_udpBuf, pendingDataSize, &m_remoteAddress, 0);
processData();
}
}
void SDRdaemonFECUDPHandler::processData()
{
if (m_udpReadBytes < 0)
{
qDebug() << "SDRdaemonThread::processData: read failed";
}
else if (m_udpReadBytes > 0)
{
m_sdrDaemonBuffer.updateBlockCounts(m_udpReadBytes);
if (m_sdrDaemonBuffer.readMeta(m_udpBuf, m_udpReadBytes))
{
const SDRdaemonFECBuffer::MetaData& metaData = m_sdrDaemonBuffer.getCurrentMeta();
bool change = false;
m_tv_sec = metaData.m_tv_sec;
m_tv_usec = metaData.m_tv_usec;
uint32_t sampleRate = m_sdrDaemonBuffer.getSampleRate();
if (m_samplerate != sampleRate)
{
setSamplerate(sampleRate);
m_samplerate = sampleRate;
change = true;
}
if (m_centerFrequency != metaData.m_centerFrequency)
{
m_centerFrequency = metaData.m_centerFrequency;
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::MsgReportSDRdaemonStreamData *report = SDRdaemonFECInput::MsgReportSDRdaemonStreamData::create(
m_sdrDaemonBuffer.getSampleRateStream(),
m_samplerate,
m_centerFrequency * 1000, // Frequency in Hz for the GUI
m_tv_sec,
m_tv_usec);
m_outputMessageQueueToGUI->push(report);
}
}
else if (m_sdrDaemonBuffer.isSync())
{
m_sdrDaemonBuffer.writeData(m_udpBuf, m_udpReadBytes);
}
}
}
void SDRdaemonFECUDPHandler::setSamplerate(uint32_t samplerate)
{
qDebug() << "SDRdaemonUDPHandler::setSamplerate:"
<< " new:" << samplerate
<< " old:" << m_samplerate;
m_samplerate = samplerate;
}
void SDRdaemonFECUDPHandler::connectTimer(const QTimer* timer)
{
qDebug() << "SDRdaemonUDPHandler::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.getSampleRate() * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000;
m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection();
m_readLength = m_readLengthSamples * SDRdaemonFECBuffer::m_iqSampleSize;
m_throttleToggle = !m_throttleToggle;
}
if (m_autoCorrBuffer) {
m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection();
}
// 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
{
m_tickCount = 0;
SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming *report = SDRdaemonFECInput::MsgReportSDRdaemonStreamTiming::create(
m_tv_sec,
m_tv_usec,
m_sdrDaemonBuffer.isSyncLocked(),
m_sdrDaemonBuffer.getFrameSize(),
m_sdrDaemonBuffer.getBufferLengthInSecs(),
m_sdrDaemonBuffer.isLz4Compressed(),
m_sdrDaemonBuffer.getCompressionRatio(),
m_sdrDaemonBuffer.getLz4DataCRCOK(),
m_sdrDaemonBuffer.getLz4SuccessfulDecodes(),
m_sdrDaemonBuffer.getBufferGauge());
m_outputMessageQueueToGUI->push(report);
}
}

View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SDRDAEMONFEC_SDRDAEMONFECUDPHANDLER_H_
#define PLUGINS_SAMPLESOURCE_SDRDAEMONFEC_SDRDAEMONFECUDPHANDLER_H_
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QMutex>
#include <QElapsedTimer>
#include "sdrdaemonfecbuffer.h"
#define SDRDAEMONFEC_THROTTLE_MS 50
class SampleFifo;
class MessageQueue;
class QTimer;
class DeviceAPI;
class SDRdaemonFECUDPHandler : public QObject
{
Q_OBJECT
public:
SDRdaemonFECUDPHandler(SampleFifo* sampleFifo, MessageQueue *outputMessageQueueToGUI, DeviceAPI *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(); }
void setAutoFollowRate(bool autoFollowRate) { m_sdrDaemonBuffer.setAutoFollowRate(autoFollowRate); }
void setAutoCorrBuffer(bool autoCorrBuffer) { m_autoCorrBuffer = autoCorrBuffer; m_sdrDaemonBuffer.setAutoCorrBuffer(autoCorrBuffer); }
void resetIndexes() { m_sdrDaemonBuffer.setResetIndexes(); }
public slots:
void dataReadyRead();
private:
DeviceAPI *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;
SampleFifo *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;
int m_rateDivider;
bool m_autoCorrBuffer;
void setSamplerate(uint32_t samplerate);
void processData();
private slots:
void tick();
};
#endif /* PLUGINS_SAMPLESOURCE_SDRDAEMON_SDRDAEMONUDPHANDLER_H_ */