mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-22 16:08:39 -05:00
FileSource channel: initial commit
This commit is contained in:
parent
d0c2b73d99
commit
7e6d02f675
@ -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)
|
||||
|
@ -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
|
||||
${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})
|
||||
@ -46,11 +52,12 @@ add_library(${TARGET_NAME} SHARED
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
Qt5::Network
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
swagger
|
||||
swagger
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
670
plugins/channeltx/filesource/filesource.cpp
Normal file
670
plugins/channeltx/filesource/filesource.cpp
Normal 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());
|
||||
}
|
398
plugins/channeltx/filesource/filesource.h
Normal file
398
plugins/channeltx/filesource/filesource.h
Normal 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_
|
371
plugins/channeltx/filesource/filesourcegui.cpp
Normal file
371
plugins/channeltx/filesource/filesourcegui.cpp
Normal 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()
|
||||
{
|
||||
}
|
109
plugins/channeltx/filesource/filesourcegui.h
Normal file
109
plugins/channeltx/filesource/filesourcegui.h
Normal 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_ */
|
603
plugins/channeltx/filesource/filesourcegui.ui
Normal file
603
plugins/channeltx/filesource/filesourcegui.ui
Normal 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>
|
80
plugins/channeltx/filesource/filesourceplugin.cpp
Normal file
80
plugins/channeltx/filesource/filesourceplugin.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
47
plugins/channeltx/filesource/filesourceplugin.h
Normal file
47
plugins/channeltx/filesource/filesourceplugin.h
Normal 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_ */
|
109
plugins/channeltx/filesource/filesourcesettings.cpp
Normal file
109
plugins/channeltx/filesource/filesourcesettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
49
plugins/channeltx/filesource/filesourcesettings.h
Normal file
49
plugins/channeltx/filesource/filesourcesettings.h
Normal 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_ */
|
79
plugins/channeltx/filesource/readme.md
Normal file
79
plugins/channeltx/filesource/readme.md
Normal 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 ✕ 126 samples (16 bit I or Q samples) or 127 ✕ 63 samples (24 bit I or Q samples).
|
@ -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"),
|
||||
|
Loading…
Reference in New Issue
Block a user