1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 09:48:45 -05:00

FileSource channel: initial commit

This commit is contained in:
f4exb 2019-07-08 01:45:29 +02:00
parent d0c2b73d99
commit 7e6d02f675
13 changed files with 2538 additions and 15 deletions

View File

@ -6,6 +6,7 @@ add_subdirectory(modssb)
add_subdirectory(modwfm)
add_subdirectory(udpsource)
add_subdirectory(localsource)
add_subdirectory(filesource)
if(CM256CC_FOUND)
add_subdirectory(remotesource)

View File

@ -1,41 +1,47 @@
project(filesource)
if (HAS_SSSE3)
message(STATUS "RemoteSource: use SSSE3 SIMD" )
elseif (HAS_NEON)
message(STATUS "RemoteSource: use Neon SIMD" )
else()
message(STATUS "RemoteSource: Unsupported architecture")
return()
endif()
set(filesource_SOURCES
filesourceinput.cpp
filesource.cpp
filesourceplugin.cpp
filesourcethread.cpp
filesourceinputsettings.cpp
filesourcesettings.cpp
)
set(filesource_HEADERS
filesourceinput.h
filesource.h
filesourceplugin.h
filesourcethread.h
filesourceinputsettings.h
filesourcesettings.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${CUSTOM_WINDOWS_INCLUDE}
)
if(NOT SERVER_MODE)
set(filesource_SOURCES
${filesource_SOURCES}
filesourcegui.cpp
filesourcegui.ui
)
set(filesource_HEADERS
${filesource_HEADERS}
filesourcegui.h
)
set(TARGET_NAME inputfilesource)
set(TARGET_NAME filesource)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME inputfilesourcesrv)
set(TARGET_NAME filesourcesrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
@ -48,6 +54,7 @@ add_library(${TARGET_NAME} SHARED
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Network
sdrbase
${TARGET_LIB_GUI}
swagger

View File

@ -0,0 +1,670 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "filesource.h"
#if (defined _WIN32_) || (defined _MSC_VER)
#include "windows_time.h"
#include <stdint.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGChannelSettings.h"
#include "SWGChannelReport.h"
#include "SWGFileSourceReport.h"
#include "device/deviceapi.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplesink.h"
#include "dsp/upchannelizer.h"
#include "dsp/threadedbasebandsamplesource.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/filerecord.h"
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgSampleRateNotification, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSource, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceName, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceWork, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceSeek, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceAcquisition, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgPlayPause, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceStreamData, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportHeaderCRC, Message)
const QString FileSource::m_channelIdURI = "sdrangel.channeltx.filesource";
const QString FileSource::m_channelId ="FileSource";
FileSource::FileSource(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
m_deviceAPI(deviceAPI),
m_fileName("..."),
m_sampleSize(0),
m_centerFrequency(0),
m_frequencyOffset(0),
m_fileSampleRate(0),
m_samplesCount(0),
m_sampleRate(0),
m_deviceSampleRate(0),
m_recordLength(0),
m_startingTimeStamp(0),
m_running(false)
{
setObjectName(m_channelId);
m_channelizer = new UpChannelizer(this);
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
m_deviceAPI->addChannelSource(m_threadedChannelizer);
m_deviceAPI->addChannelSourceAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
FileSource::~FileSource()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
delete m_threadedChannelizer;
delete m_channelizer;
}
void FileSource::pull(Sample& sample)
{
struct Sample16
{
int16_t real;
int16_t imag;
};
struct Sample24
{
int32_t real;
int32_t imag;
};
if (!m_running)
{
sample.setReal(0);
sample.setImag(0);
return;
}
if (m_sampleSize == 16)
{
if (SDR_RX_SAMP_SZ == 16)
{
m_ifstream.read(reinterpret_cast<char*>(&sample), sizeof(Sample));
if (m_ifstream.eof()) {
handleEOF();
} else {
m_samplesCount++;
}
}
else if (SDR_RX_SAMP_SZ == 24)
{
Sample16 sample16;
m_ifstream.read(reinterpret_cast<char*>(&sample16), sizeof(Sample16));
if (m_ifstream.eof())
{
handleEOF();
}
else
{
sample.setReal(sample16.real << 8);
sample.setImag(sample16.imag << 8);
m_samplesCount++;
}
}
else
{
sample.setReal(0);
sample.setImag(0);
}
}
else if (m_sampleSize == 24)
{
if (SDR_RX_SAMP_SZ == 24)
{
m_ifstream.read(reinterpret_cast<char*>(&sample), sizeof(Sample));
if (m_ifstream.eof()) {
handleEOF();
} else {
m_samplesCount++;
}
}
else if (SDR_RX_SAMP_SZ == 16)
{
Sample24 sample24;
m_ifstream.read(reinterpret_cast<char*>(&sample24), sizeof(Sample24));
if (m_ifstream.eof())
{
handleEOF();
}
else
{
sample.setReal(sample24.real >> 8);
sample.setImag(sample24.imag >> 8);
m_samplesCount++;
}
}
else
{
sample.setReal(0);
sample.setImag(0);
}
}
else
{
sample.setReal(0);
sample.setImag(0);
}
}
void FileSource::pullAudio(int nbSamples)
{
(void) nbSamples;
}
void FileSource::start()
{
qDebug("FileSource::start");
if (m_running) {
stop();
}
m_running = true;
}
void FileSource::stop()
{
qDebug("FileSource::stop");
m_running = false;
}
bool FileSource::handleMessage(const Message& cmd)
{
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
{
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
int sampleRate = notif.getSampleRate();
qDebug() << "FileSource::handleMessage: MsgChannelizerNotification:"
<< " channelSampleRate: " << sampleRate
<< " offsetFrequency: " << notif.getFrequencyOffset();
if (sampleRate > 0) {
setSampleRate(sampleRate);
}
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "FileSource::handleMessage: DSPSignalNotification:"
<< " inputSampleRate: " << notif.getSampleRate()
<< " centerFrequency: " << notif.getCenterFrequency();
setCenterFrequency(notif.getCenterFrequency());
m_deviceSampleRate = notif.getSampleRate();
calculateFrequencyOffset(); // This is when device sample rate changes
// Redo the channelizer stuff with the new sample rate to re-synchronize everything
m_channelizer->set(m_channelizer->getInputMessageQueue(),
m_settings.m_log2Interp,
m_settings.m_filterChainHash);
if (m_guiMessageQueue)
{
MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate());
m_guiMessageQueue->push(msg);
}
return true;
}
else if (MsgConfigureFileSource::match(cmd))
{
MsgConfigureFileSource& cfg = (MsgConfigureFileSource&) cmd;
qDebug() << "FileSource::handleMessage: MsgConfigureFileSource";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
{
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
m_settings.m_log2Interp = cfg.getLog2Interp();
m_settings.m_filterChainHash = cfg.getFilterChainHash();
qDebug() << "FileSource::handleMessage: MsgConfigureChannelizer:"
<< " log2Interp: " << m_settings.m_log2Interp
<< " filterChainHash: " << m_settings.m_filterChainHash;
m_channelizer->set(m_channelizer->getInputMessageQueue(),
m_settings.m_log2Interp,
m_settings.m_filterChainHash);
calculateFrequencyOffset(); // This is when decimation or filter chain changes
return true;
}
else if (MsgConfigureFileSourceName::match(cmd))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
m_fileName = conf.getFileName();
openFileStream();
return true;
}
else if (MsgConfigureFileSourceWork::match(cmd))
{
MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) cmd;
if (conf.isWorking()) {
start();
} else {
stop();
}
return true;
}
else if (MsgConfigureFileSourceSeek::match(cmd))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
int seekMillis = conf.getMillis();
seekFileStream(seekMillis);
return true;
}
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
{
MsgReportFileSourceStreamTiming *report;
if (getMessageQueueToGUI())
{
report = MsgReportFileSourceStreamTiming::create(getSamplesCount());
getMessageQueueToGUI()->push(report);
}
return true;
}
else
{
return false;
}
}
QByteArray FileSource::serialize() const
{
return m_settings.serialize();
}
bool FileSource::deserialize(const QByteArray& data)
{
(void) data;
if (m_settings.deserialize(data))
{
MsgConfigureFileSource *msg = MsgConfigureFileSource::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureFileSource *msg = MsgConfigureFileSource::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void FileSource::openFileStream()
{
//stop();
if (m_ifstream.is_open()) {
m_ifstream.close();
}
#ifdef Q_OS_WIN
m_ifstream.open(m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
#else
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
#endif
quint64 fileSize = m_ifstream.tellg();
if (fileSize > sizeof(FileRecord::Header))
{
FileRecord::Header header;
m_ifstream.seekg(0,std::ios_base::beg);
bool crcOK = FileRecord::readHeader(m_ifstream, header);
m_fileSampleRate = header.sampleRate;
m_centerFrequency = header.centerFrequency;
m_startingTimeStamp = header.startTimeStamp;
m_sampleSize = header.sampleSize;
QString crcHex = QString("%1").arg(header.crc32 , 0, 16);
if (crcOK)
{
qDebug("FileSource::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
}
else
{
qCritical("FileSource::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
m_recordLength = 0;
}
if (getMessageQueueToGUI()) {
MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK);
getMessageQueueToGUI()->push(report);
}
}
else
{
m_recordLength = 0;
}
qDebug() << "FileSource::openFileStream: " << m_fileName.toStdString().c_str()
<< " fileSize: " << fileSize << " bytes"
<< " length: " << m_recordLength << " seconds"
<< " sample rate: " << m_fileSampleRate << " S/s"
<< " center frequency: " << m_centerFrequency << " Hz"
<< " sample size: " << m_sampleSize << " bits";
if (getMessageQueueToGUI()) {
MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_fileSampleRate,
m_sampleSize,
m_centerFrequency,
m_startingTimeStamp,
m_recordLength); // file stream data
getMessageQueueToGUI()->push(report);
}
if (m_recordLength == 0) {
m_ifstream.close();
}
}
void FileSource::seekFileStream(int seekMillis)
{
QMutexLocker mutexLocker(&m_mutex);
if ((m_ifstream.is_open()) && !m_running)
{
quint64 seekPoint = ((m_recordLength * seekMillis) / 1000) * m_fileSampleRate;
seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header)
m_ifstream.clear();
m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg);
}
}
void FileSource::handleEOF()
{
stop();
if (getMessageQueueToGUI())
{
MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(getSamplesCount());
getMessageQueueToGUI()->push(report);
}
if (m_settings.m_loop)
{
seekFileStream(0);
start();
}
else
{
if (getMessageQueueToGUI())
{
MsgPlayPause *report = MsgPlayPause::create(false);
getMessageQueueToGUI()->push(report);
}
}
}
void FileSource::applySettings(const FileSourceSettings& settings, bool force)
{
qDebug() << "FileSource::applySettings:"
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_loop != settings.m_loop)) {
reverseAPIKeys.append("loop");
}
if ((m_settings.m_fileName != settings.m_fileName)) {
reverseAPIKeys.append("fileName");
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
}
void FileSource::validateFilterChainHash(FileSourceSettings& settings)
{
unsigned int s = 1;
for (unsigned int i = 0; i < settings.m_log2Interp; i++) {
s *= 3;
}
settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash;
}
void FileSource::calculateFrequencyOffset()
{
double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Interp, m_settings.m_filterChainHash);
m_frequencyOffset = m_deviceSampleRate * shiftFactor;
}
int FileSource::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings());
response.getFileSourceSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int FileSource::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
FileSourceSettings settings = m_settings;
if (channelSettingsKeys.contains("log2Interp")) {
settings.m_log2Interp = response.getFileSourceSettings()->getLog2Interp();
}
if (channelSettingsKeys.contains("filterChainHash"))
{
settings.m_filterChainHash = response.getFileSourceSettings()->getFilterChainHash();
validateFilterChainHash(settings);
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getFileSourceSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getFileSourceSettings()->getTitle();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getFileSourceSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getFileSourceSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getFileSourceSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getFileSourceSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getFileSourceSettings()->getReverseApiChannelIndex();
}
MsgConfigureFileSource *msg = MsgConfigureFileSource::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("FileSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureFileSource *msgToGUI = MsgConfigureFileSource::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
int FileSource::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setFileSourceReport(new SWGSDRangel::SWGFileSourceReport());
response.getFileSourceReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void FileSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FileSourceSettings& settings)
{
response.getFileSourceSettings()->setLog2Interp(settings.m_log2Interp);
response.getFileSourceSettings()->setFilterChainHash(settings.m_filterChainHash);
response.getFileSourceSettings()->setRgbColor(settings.m_rgbColor);
if (response.getFileSourceSettings()->getTitle()) {
*response.getFileSourceSettings()->getTitle() = settings.m_title;
} else {
response.getFileSourceSettings()->setTitle(new QString(settings.m_title));
}
response.getFileSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getFileSourceSettings()->getReverseApiAddress()) {
*response.getFileSourceSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getFileSourceSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getFileSourceSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getFileSourceSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getFileSourceSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void FileSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getFileSourceReport()->setFileSampleRate(m_fileSampleRate);
response.getFileSourceReport()->setFileSampleSize(m_sampleSize);
response.getFileSourceReport()->setSampleRate(m_sampleRate);
}
void FileSource::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FileSourceSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
swgChannelSettings->setDirection(1); // single source (Tx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("FileSource"));
swgChannelSettings->setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings());
SWGSDRangel::SWGFileSourceSettings *swgFileSourceSettings = swgChannelSettings->getFileSourceSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("log2Interp") || force) {
swgFileSourceSettings->setLog2Interp(settings.m_log2Interp);
}
if (channelSettingsKeys.contains("filterChainHash") || force) {
swgFileSourceSettings->setFilterChainHash(settings.m_filterChainHash);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgFileSourceSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgFileSourceSettings->setTitle(new QString(settings.m_title));
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer=new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
delete swgChannelSettings;
}
void FileSource::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "FileSource::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
return;
}
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("FileSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}

View File

@ -0,0 +1,398 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCE_H_
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCE_H_
#include <ctime>
#include <iostream>
#include <fstream>
#include <QObject>
#include <QString>
#include <QByteArray>
#include <QTimer>
#include <QNetworkRequest>
#include "dsp/basebandsamplesource.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "filesourcesettings.h"
class ThreadedBasebandSampleSource;
class UpChannelizer;
class DeviceAPI;
class FileSourceThread;
class QNetworkAccessManager;
class QNetworkReply;
class FileSource : public BasebandSampleSource, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getLog2Interp() const { return m_log2Interp; }
int getFilterChainHash() const { return m_filterChainHash; }
static MsgConfigureChannelizer* create(unsigned int m_log2Interp, unsigned int m_filterChainHash) {
return new MsgConfigureChannelizer(m_log2Interp, m_filterChainHash);
}
private:
unsigned int m_log2Interp;
unsigned int m_filterChainHash;
MsgConfigureChannelizer(unsigned int log2Interp, unsigned int filterChainHash) :
Message(),
m_log2Interp(log2Interp),
m_filterChainHash(filterChainHash)
{ }
};
class MsgConfigureFileSource : public Message {
MESSAGE_CLASS_DECLARATION
public:
const FileSourceSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureFileSource* create(const FileSourceSettings& settings, bool force)
{
return new MsgConfigureFileSource(settings, force);
}
private:
FileSourceSettings m_settings;
bool m_force;
MsgConfigureFileSource(const FileSourceSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgSampleRateNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgSampleRateNotification* create(int sampleRate) {
return new MsgSampleRateNotification(sampleRate);
}
int getSampleRate() const { return m_sampleRate; }
private:
MsgSampleRateNotification(int sampleRate) :
Message(),
m_sampleRate(sampleRate)
{ }
int m_sampleRate;
};
class MsgConfigureFileSourceName : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getFileName() const { return m_fileName; }
static MsgConfigureFileSourceName* create(const QString& fileName)
{
return new MsgConfigureFileSourceName(fileName);
}
private:
QString m_fileName;
MsgConfigureFileSourceName(const QString& fileName) :
Message(),
m_fileName(fileName)
{ }
};
class MsgConfigureFileSourceWork : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool isWorking() const { return m_working; }
static MsgConfigureFileSourceWork* create(bool working)
{
return new MsgConfigureFileSourceWork(working);
}
private:
bool m_working;
MsgConfigureFileSourceWork(bool working) :
Message(),
m_working(working)
{ }
};
class MsgConfigureFileSourceStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureFileSourceStreamTiming* create()
{
return new MsgConfigureFileSourceStreamTiming();
}
private:
MsgConfigureFileSourceStreamTiming() :
Message()
{ }
};
class MsgConfigureFileSourceSeek : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getMillis() const { return m_seekMillis; }
static MsgConfigureFileSourceSeek* create(int seekMillis)
{
return new MsgConfigureFileSourceSeek(seekMillis);
}
protected:
int m_seekMillis; //!< millis of seek position from the beginning 0..1000
MsgConfigureFileSourceSeek(int seekMillis) :
Message(),
m_seekMillis(seekMillis)
{ }
};
class MsgReportFileSourceAcquisition : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getAcquisition() const { return m_acquisition; }
static MsgReportFileSourceAcquisition* create(bool acquisition)
{
return new MsgReportFileSourceAcquisition(acquisition);
}
protected:
bool m_acquisition;
MsgReportFileSourceAcquisition(bool acquisition) :
Message(),
m_acquisition(acquisition)
{ }
};
class MsgPlayPause : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getPlayPause() const { return m_playPause; }
static MsgPlayPause* create(bool playPause) {
return new MsgPlayPause(playPause);
}
protected:
bool m_playPause;
MsgPlayPause(bool playPause) :
Message(),
m_playPause(playPause)
{ }
};
class MsgReportFileSourceStreamData : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
quint32 getSampleSize() const { return m_sampleSize; }
quint64 getCenterFrequency() const { return m_centerFrequency; }
quint64 getStartingTimeStamp() const { return m_startingTimeStamp; }
quint64 getRecordLength() const { return m_recordLength; }
static MsgReportFileSourceStreamData* create(int sampleRate,
quint32 sampleSize,
quint64 centerFrequency,
quint64 startingTimeStamp,
quint64 recordLength)
{
return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength);
}
protected:
int m_sampleRate;
quint32 m_sampleSize;
quint64 m_centerFrequency;
quint64 m_startingTimeStamp;
quint64 m_recordLength;
MsgReportFileSourceStreamData(int sampleRate,
quint32 sampleSize,
quint64 centerFrequency,
quint64 startingTimeStamp,
quint64 recordLength) :
Message(),
m_sampleRate(sampleRate),
m_sampleSize(sampleSize),
m_centerFrequency(centerFrequency),
m_startingTimeStamp(startingTimeStamp),
m_recordLength(recordLength)
{ }
};
class MsgReportFileSourceStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
quint64 getSamplesCount() const { return m_samplesCount; }
static MsgReportFileSourceStreamTiming* create(quint64 samplesCount)
{
return new MsgReportFileSourceStreamTiming(samplesCount);
}
protected:
quint64 m_samplesCount;
MsgReportFileSourceStreamTiming(quint64 samplesCount) :
Message(),
m_samplesCount(samplesCount)
{ }
};
class MsgReportHeaderCRC : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool isOK() const { return m_ok; }
static MsgReportHeaderCRC* create(bool ok) {
return new MsgReportHeaderCRC(ok);
}
protected:
bool m_ok;
MsgReportHeaderCRC(bool ok) :
Message(),
m_ok(ok)
{ }
};
FileSource(DeviceAPI *deviceAPI);
~FileSource();
virtual void destroy() { delete this; }
virtual void pull(Sample& sample);
virtual void pullAudio(int nbSamples);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return 0; }
virtual int getNbSinkStreams() const { return 0; }
virtual int getNbSourceStreams() const { return 1; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
/** Set center frequency given in Hz */
void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; }
/** Set sample rate given in Hz */
void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; }
quint64 getSamplesCount() const { return m_samplesCount; }
static const QString m_channelIdURI;
static const QString m_channelId;
private:
DeviceAPI* m_deviceAPI;
QMutex m_mutex;
ThreadedBasebandSampleSource* m_threadedChannelizer;
UpChannelizer* m_channelizer;
FileSourceSettings m_settings;
std::ifstream m_ifstream;
QString m_fileName;
quint32 m_sampleSize;
quint64 m_centerFrequency;
int64_t m_frequencyOffset;
uint32_t m_fileSampleRate;
quint64 m_samplesCount;
uint32_t m_sampleRate;
uint32_t m_deviceSampleRate;
quint64 m_recordLength; //!< record length in seconds computed from file size
quint64 m_startingTimeStamp;
QTimer m_masterTimer;
bool m_running;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void openFileStream();
void seekFileStream(int seekMillis);
void handleEOF();
void applySettings(const FileSourceSettings& settings, bool force = false);
void validateFilterChainHash(FileSourceSettings& settings);
void calculateFrequencyOffset();
void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FileSourceSettings& settings);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FileSourceSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleData();
};
#endif // PLUGINS_CHANNELTX_FILESOURCE_FILESOURCE_H_

View File

@ -0,0 +1,371 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include "filesourcegui.h"
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
#include "dsp/hbfilterchainconverter.h"
#include "gui/basicchannelsettingsdialog.h"
#include "mainwindow.h"
#include "filesource.h"
#include "ui_filesourcegui.h"
FileSourceGUI* FileSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx)
{
FileSourceGUI* gui = new FileSourceGUI(pluginAPI, deviceUISet, channelTx);
return gui;
}
void FileSourceGUI::destroy()
{
delete this;
}
void FileSourceGUI::setName(const QString& name)
{
setObjectName(name);
}
QString FileSourceGUI::getName() const
{
return objectName();
}
qint64 FileSourceGUI::getCenterFrequency() const {
return 0;
}
void FileSourceGUI::setCenterFrequency(qint64 centerFrequency)
{
(void) centerFrequency;
}
void FileSourceGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray FileSourceGUI::serialize() const
{
return m_settings.serialize();
}
bool FileSourceGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
bool FileSourceGUI::handleMessage(const Message& message)
{
if (FileSource::MsgSampleRateNotification::match(message))
{
FileSource::MsgSampleRateNotification& notif = (FileSource::MsgSampleRateNotification&) message;
m_sampleRate = notif.getSampleRate();
displayRateAndShift();
return true;
}
else if (FileSource::MsgConfigureFileSource::match(message))
{
const FileSource::MsgConfigureFileSource& cfg = (FileSource::MsgConfigureFileSource&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else
{
return false;
}
}
FileSourceGUI::FileSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
RollupWidget(parent),
ui(new Ui::FileSourceGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_tickCount(0)
{
(void) channelTx;
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_fileSource = (FileSource*) channelTx;
m_fileSource->setMessageQueueToGUI(getInputMessageQueue());
connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(m_settings.m_rgbColor);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("Remote source");
m_channelMarker.setSourceOrSinkStream(false);
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_settings.setChannelMarker(&m_channelMarker);
m_deviceUISet->registerTxChannelInstance(FileSource::m_channelIdURI, this);
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_time.start();
displaySettings();
applySettings(true);
}
FileSourceGUI::~FileSourceGUI()
{
m_deviceUISet->removeTxChannelInstance(this);
delete m_fileSource;
delete ui;
}
void FileSourceGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void FileSourceGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
setTitleColor(m_channelMarker.getColor());
FileSource::MsgConfigureFileSource* message = FileSource::MsgConfigureFileSource::create(m_settings, force);
m_fileSource->getInputMessageQueue()->push(message);
}
}
void FileSourceGUI::applyChannelSettings()
{
if (m_doApplySettings)
{
FileSource::MsgConfigureChannelizer *msgChan = FileSource::MsgConfigureChannelizer::create(
m_settings.m_log2Interp,
m_settings.m_filterChainHash);
m_fileSource->getInputMessageQueue()->push(msgChan);
}
}
void FileSourceGUI::configureFileName()
{
qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str();
FileSource::MsgConfigureFileSourceName* message = FileSource::MsgConfigureFileSourceName::create(m_fileName);
m_fileSource->getInputMessageQueue()->push(message);
}
void FileSourceGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.setBandwidth(m_sampleRate);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
blockApplySettings(true);
ui->interpolationFactor->setCurrentIndex(m_settings.m_log2Interp);
applyInterpolation();
blockApplySettings(false);
}
void FileSourceGUI::displayRateAndShift()
{
int shift = m_shiftFrequencyFactor * m_sampleRate;
double channelSampleRate = ((double) m_sampleRate) / (1<<m_settings.m_log2Interp);
QLocale loc;
ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
m_channelMarker.setCenterFrequency(shift);
m_channelMarker.setBandwidth(channelSampleRate);
}
void FileSourceGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void FileSourceGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void FileSourceGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void FileSourceGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void FileSourceGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void FileSourceGUI::on_interpolationFactor_currentIndexChanged(int index)
{
m_settings.m_log2Interp = index;
applyInterpolation();
}
void FileSourceGUI::on_position_valueChanged(int value)
{
m_settings.m_filterChainHash = value;
applyPosition();
}
void FileSourceGUI::on_showFileDialog_clicked(bool checked)
{
(void) checked;
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog);
if (fileName != "")
{
m_fileName = fileName;
ui->fileNameText->setText(m_fileName);
ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
configureFileName();
}
}
void FileSourceGUI::on_playLoop_toggled(bool checked)
{
if (m_doApplySettings)
{
m_settings.m_loop = checked;
FileSource::MsgConfigureFileSource *message = FileSource::MsgConfigureFileSource::create(m_settings, false);
m_fileSource->getInputMessageQueue()->push(message);
}
}
void FileSourceGUI::on_play_toggled(bool checked)
{
FileSource::MsgConfigureFileSourceWork* message = FileSource::MsgConfigureFileSourceWork::create(checked);
m_fileSource->getInputMessageQueue()->push(message);
ui->navTime->setEnabled(!checked);
m_enableNavTime = !checked;
}
void FileSourceGUI::on_navTime_valueChanged(int value)
{
if (m_enableNavTime && ((value >= 0) && (value <= 1000)))
{
FileSource::MsgConfigureFileSourceSeek* message = FileSource::MsgConfigureFileSourceSeek::create(value);
m_fileSource->getInputMessageQueue()->push(message);
}
}
void FileSourceGUI::applyInterpolation()
{
uint32_t maxHash = 1;
for (uint32_t i = 0; i < m_settings.m_log2Interp; i++) {
maxHash *= 3;
}
ui->position->setMaximum(maxHash-1);
ui->position->setValue(m_settings.m_filterChainHash);
m_settings.m_filterChainHash = ui->position->value();
applyPosition();
}
void FileSourceGUI::applyPosition()
{
ui->filterChainIndex->setText(tr("%1").arg(m_settings.m_filterChainHash));
QString s;
m_shiftFrequencyFactor = HBFilterChainConverter::convertToString(m_settings.m_log2Interp, m_settings.m_filterChainHash, s);
ui->filterChainText->setText(s);
displayRateAndShift();
applyChannelSettings();
}
void FileSourceGUI::tick()
{
if (++m_tickCount == 20) // once per second
{
m_tickCount = 0;
}
}
void FileSourceGUI::channelMarkerChangedByCursor()
{
}

View File

@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEGUI_H_
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEGUI_H_
#include <QTime>
#include "plugin/plugininstancegui.h"
#include "dsp/channelmarker.h"
#include "gui/rollupwidget.h"
#include "util/messagequeue.h"
#include "filesourcesettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSource;
class FileSource;
namespace Ui {
class FileSourceGUI;
}
class FileSourceGUI : public RollupWidget, public PluginInstanceGUI {
Q_OBJECT
public:
static FileSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx);
virtual void destroy();
void setName(const QString& name);
QString getName() const;
virtual qint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool handleMessage(const Message& message);
public slots:
void channelMarkerChangedByCursor();
private:
Ui::FileSourceGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
FileSourceSettings m_settings;
int m_sampleRate;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
QString m_fileName;
bool m_enableNavTime;
bool m_doApplySettings;
FileSource* m_fileSource;
MessageQueue m_inputMessageQueue;
QTime m_time;
uint32_t m_tickCount;
explicit FileSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = nullptr);
virtual ~FileSourceGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void applyChannelSettings();
void configureFileName();
void displaySettings();
void displayRateAndShift();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void applyInterpolation();
void applyPosition();
private slots:
void handleSourceMessages();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void on_interpolationFactor_currentIndexChanged(int index);
void on_position_valueChanged(int value);
void on_showFileDialog_clicked(bool checked);
void on_playLoop_toggled(bool checked);
void on_play_toggled(bool checked);
void on_navTime_valueChanged(int value);
void tick();
};
#endif /* PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEGUI_H_ */

View File

@ -0,0 +1,603 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileSourceGUI</class>
<widget class="RollupWidget" name="FileSourceGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>177</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>140</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>File source</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QVBoxLayout" name="interpolationLayer">
<property name="spacing">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="interpolationStageLayer">
<item>
<widget class="QLabel" name="interpolationLabel">
<property name="text">
<string>Int</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="interpolationFactor">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Decimation factor</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="channelRateText">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Effective channel rate (kS/s)</string>
</property>
<property name="text">
<string>0000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain stages left to right (L: low, C: center, H: high) </string>
</property>
<property name="text">
<string>LLLLLL</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="offsetFrequencyText">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Offset frequency with thousands separator (Hz)</string>
</property>
<property name="text">
<string>-9,999,999 Hz</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="decimationShiftLayer">
<property name="rightMargin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="positionLabel">
<property name="text">
<string>Pos</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="position">
<property name="toolTip">
<string>Center frequency position</string>
</property>
<property name="maximum">
<number>2</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainIndex">
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain hash code</string>
</property>
<property name="text">
<string>000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fileSelectionLayout">
<item>
<widget class="QPushButton" name="showFileDialog">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fileNameText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="rateTimeLayout">
<item>
<widget class="QLabel" name="sampleRateText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Record sample rate (kS/s)</string>
</property>
<property name="text">
<string>00000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sampleSizeText">
<property name="minimumSize">
<size>
<width>22</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Record sample size (bits)</string>
</property>
<property name="text">
<string>00b</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="crcLabel">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>CRC status: Green: OK Red: KO Grey: undefined</string>
</property>
<property name="text">
<string>CRC</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="absTimeLine">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="absTimeText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>160</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Record absolute time</string>
</property>
<property name="text">
<string>2015-01-01 00:00:00.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="playControllLayout">
<item>
<widget class="ButtonSwitch" name="playLoop">
<property name="toolTip">
<string>Play in a loop</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="play">
<property name="toolTip">
<string>Stopped / Play / Pause</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/pause.png</normalon>
<disabledoff>:/stop.png</disabledoff>
<disabledon>:/stop.png</disabledon>
<activeoff>:/play.png</activeoff>
<activeon>:/pause.png</activeon>
<selectedoff>:/play.png</selectedoff>
<selectedon>:/pause.png</selectedon>:/play.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="linePlay1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="relTimeText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Record time from start</string>
</property>
<property name="text">
<string>00:00:00.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="linePlay2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="recordLengthText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Total record time</string>
</property>
<property name="text">
<string>00:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="navTimeLayer">
<item>
<widget class="QSlider" name="navTime">
<property name="toolTip">
<string>Time navigator</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "filesourcegui.h"
#endif
#include "filesource.h"
#include "filesourceplugin.h"
const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = {
QString("File channel source"),
QString("4.11.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
FileSourcePlugin::FileSourcePlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& FileSourcePlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void FileSourcePlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register source
m_pluginAPI->registerTxChannel(FileSource::m_channelIdURI, FileSource::m_channelId, this);
}
#ifdef SERVER_MODE
PluginInstanceGUI* FileSourcePlugin::createTxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSource *txChannel)
{
return 0;
}
#else
PluginInstanceGUI* FileSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel)
{
return FileSourceGUI::create(m_pluginAPI, deviceUISet, txChannel);
}
#endif
BasebandSampleSource* FileSourcePlugin::createTxChannelBS(DeviceAPI *deviceAPI)
{
return new FileSource(deviceAPI);
}
ChannelAPI* FileSourcePlugin::createTxChannelCS(DeviceAPI *deviceAPI)
{
return new FileSource(deviceAPI);
}

View File

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEPLUGIN_H_
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEPLUGIN_H_
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSource;
class FileSourcePlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channeltx.filesrc")
public:
explicit FileSourcePlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel);
virtual BasebandSampleSource* createTxChannelBS(DeviceAPI *deviceAPI);
virtual ChannelAPI* createTxChannelCS(DeviceAPI *deviceAPI);
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif /* PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEPLUGIN_H_ */

View File

@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "filesourcesettings.h"
#include <QColor>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
FileSourceSettings::FileSourceSettings()
{
resetToDefaults();
}
void FileSourceSettings::resetToDefaults()
{
m_fileName = "test.sdriq";
m_loop = false;
m_log2Interp = 0;
m_filterChainHash = 0;
m_rgbColor = QColor(140, 4, 4).rgb();
m_title = "File source";
m_channelMarker = nullptr;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
}
QByteArray FileSourceSettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_fileName);
s.writeBool(2, m_loop);
s.writeU32(3, m_log2Interp);
s.writeU32(4, m_filterChainHash);
s.writeU32(5, m_rgbColor);
s.writeString(6, m_title);
s.writeBool(7, m_useReverseAPI);
s.writeString(8, m_reverseAPIAddress);
s.writeU32(9, m_reverseAPIPort);
s.writeU32(10, m_reverseAPIDeviceIndex);
s.writeU32(11, m_reverseAPIChannelIndex);
return s.final();
}
bool FileSourceSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
uint32_t tmp;
QString strtmp;
d.readString(1, &m_fileName, "test.sdriq");
d.readBool(2, &m_loop, false);
d.readU32(3, &tmp, 0);
m_log2Interp = tmp > 6 ? 6 : tmp;
d.readU32(4, &m_filterChainHash, 0);
d.readU32(5, &m_rgbColor, QColor(140, 4, 4).rgb());
d.readString(6, &m_title, "File source");
d.readBool(7, &m_useReverseAPI, false);
d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(9, &tmp, 0);
if ((tmp > 1023) && (tmp < 65535)) {
m_reverseAPIPort = tmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(10, &tmp, 0);
m_reverseAPIDeviceIndex = tmp > 99 ? 99 : tmp;
d.readU32(11, &tmp, 0);
m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESETTINGS_H_
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESETTINGS_H_
#include <QString>
#include <QByteArray>
class Serializable;
struct FileSourceSettings
{
QString m_fileName;
bool m_loop;
uint32_t m_log2Interp;
uint32_t m_filterChainHash;
quint32 m_rgbColor;
QString m_title;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
Serializable *m_channelMarker;
FileSourceSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESETTINGS_H_ */

View File

@ -0,0 +1,79 @@
<h1>Remote source channel plugin</h1>
<h2>Introduction</h2>
This plugin receives I/Q samples from UDP and copies them to the baseband to be transmitted by the sink output device. It uses SDRangel remote format and possible FEC protection.
It is present only in Linux binary releases.
<h2>Build</h2>
The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will 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_DIR=/opt/install/cm256cc` to the cmake commands.
<h2>Interface</h2>
![Remote source channel plugin GUI](../../../doc/img/RemoteSource.png)
<h3>1: Data local address</h2>
IP address of the local network interface from where the I/Q samples are fetched via UDP
<h3>2: Data local port</h2>
Local port from where the I/Q samples are fetched via UDP
<h3>3: Validation button</h3>
When the return key is hit within the address (1) or port (2) the changes are effective immediately. You can also use this button to set again these values.
<h3>4: Stream sample rate</h3>
Stream sample rate as specified in the stream meta data
<h3>5: Stream status</h3>
![Remote source channel plugin GUI](../../../doc/img/RemoteSource_5.png)
<h4>5.1: Total number of frames and number of FEC blocks</h4>
This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server.
A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction.
Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered.
<h4>5.2: 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 (ex: 136)
- No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 135)
- Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128)
<h4>5.3: Actual stream sample rate</h4>
This is the sample rate calculated using the counter of samples between two consecutive polls
<h4>5.4: Reset events counters</h4>
This push button can be used to reset the events counters (5.5 and 5.6) and reset the event counts timer (5.7)
<h4>5.5: Unrecoverable error events counter</h4>
This counter counts the unrecoverable error conditions found (i.e. 4.4 lower than 128) since the last counters reset.
<h4>5.6: Recoverable error events counter</h4>
This counter counts the unrecoverable error conditions found (i.e. 4.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset.
<h4>5.7: events counters timer</h4>
This HH:mm:ss time display shows the time since the reset events counters button (5.4) was pushed.
<h3>6: Transmitter queue length gauge</h3>
This is ratio of the reported number of data frame blocks in the remote queue over the total number of blocks in the queue.
<h3>7: Transmitter queue length status</h3>
This is the detail of the ratio shown in the gauge. Each frame block is a block of 127 &#x2715; 126 samples (16 bit I or Q samples) or 127 &#x2715; 63 samples (24 bit I or Q samples).

View File

@ -28,7 +28,7 @@
#include "fileinputplugin.h"
const PluginDescriptor FileInputPlugin::m_pluginDescriptor = {
QString("File input"),
QString("File device input"),
QString("4.11.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),